OMP Workbook

Read the source. Follow the evidence.

Approval Desk: modes and policies

The baseline matrix explains defaults. It does not explain every decision. Nia now compares two plausible rules: “user policy always wins” and “yolo allows everything.” Both are too broad.

The actual resolver gives special treatment to denial, then distinguishes explicit tool policy from an override-only prompt and from the mode fallback.

Follow the implementation’s precedence

First, the tool’s declaration is evaluated against the arguments and normalized. A declaration can be a bare tier, an object carrying a tier and additional fields, or a function returning either form.

The resolver then identifies the effective user policy. Its key is the declaration’s policyKey, if present, otherwise the invoking tool’s name. If a distinct keyed policy is absent or invalid, the invoking tool’s policy is the fallback.

After that lookup, the following order applies:

OrderConditionResult
1Tool decision says denyDeny from tool policy.
2Effective user policy says denyDeny from user policy.
3Mode is yoloAn explicit tool policy wins; otherwise use the effective user policy or allow. Override is reported false.
4Non-yolo decision has override: trueExplicit tool allow remains allow; otherwise prompt. Source is tool and override remains true.
5Tool decision explicitly says allow or promptUse that tool policy.
6A valid effective user policy remainsUse that user policy.
7No earlier branch decidedCompare the tier with the mode matrix.

This table describes resolveApproval(). The wrapper can add provider safety requirements and has a specific forwarded-dispatch prompt predicate, examined later.

An override-only prompt is a decision such as { "tier": "exec", "override": true }, with no explicit policy. It prompts in a non-yolo mode even if a user allow would otherwise admit the call. In yolo, the override-only prompt is ignored. By contrast, { "tier": "exec", "policy": "prompt" } remains a prompt in the resolver’s yolo branch.

Even override: true is not literally “always force a prompt”: a decision that also explicitly says allow returns allow, after the deny checks. Read the fields together.

Work through four policy conflicts

Prediction: for each case below, name the winning source as well as allow, prompt, or deny.

CaseImportant inputsRecorded resolution
approval-tool-deny-winsTool write-tier deny; user allow; yolodeny, source tool, reason Fictional desk is closed
approval-user-deny-winsTool read-tier allow; user deny; yolodeny, source user, policy key seed_note
approval-explicit-tool-promptExplicit tool prompt; user allow; yoloprompt, source tool, override false
approval-tool-allow-before-user-promptExplicit tool allow; user prompt; always-askallow, source tool

The last row is easy to misread. A valid user prompt is not a universal veto over an explicit tool allow. A valid effective user deny is checked earlier and does stop it. Consequently, the short settings description cannot replace the resolver’s actual order.

These records also show why denial provenance matters. denyError() distinguishes a tool-owned refusal from a user-policy refusal. A tool-policy error can include the tool’s reason. A user-policy error names the effective user key. The stock error’s repair hint is not a workbook instruction to remove the policy.

A policy key is a lookup identity, not another cumulative grant

Compare these complete fictional resolver inputs. They are teaching data, not configuration to install.

Approval Desk: modes and policies · source excerpt 1; read surrounding instructions
{
  "tool": "write",
  "approval": { "tier": "exec", "policyKey": "seed_slot" },
  "mode": "yolo",
  "userPolicies": {
    "seed_slot": "not-a-policy",
    "write": "deny"
  }
}

This is the input shape of approval-policy-key-fallback. The keyed value is invalid, so the valid invoking-tool policy supplies the denial.

Recorded answer: deny, source user, policy key write.

Now inspect approval-policy-key-specific:

Approval Desk: modes and policies · source excerpt 2; read surrounding instructions
{
  "tool": "write",
  "approval": { "tier": "exec", "policyKey": "seed_slot" },
  "mode": "always-ask",
  "userPolicies": {
    "seed_slot": " ALLOW ",
    "write": "deny"
  }
}

Recorded answer: allow, source user, policy key seed_slot. The valid device policy replaces the invoking write fallback, including that fallback’s deny. The resolver is not performing an intersection of every policy entry in the record.

That does not contradict deny precedence. The write entry is no longer the effective user policy for this keyed decision. A tool-owned deny or a deny at a later applicable inner gate is a different matter.

Keep normalization claims local

normalizePolicy() accepts strings, trims them, lowercases them, and recognizes only allow, deny, and prompt. Other values do not become policies at that resolver layer. A missing or invalid tier in a returned decision defaults to exec. A function-valued declaration can still throw; the general resolver does not promise to catch every throwing declaration and convert it into a safe result.

These facts are not a claim that every raw settings object is validated everywhere. The wrapper separately uses raw own-property presence when deciding whether an inner prompt is explicit. An invalid entry can therefore be ignored as a policy by the resolver while still mattering to that later predicate. Nor does resolveApproval() normalize arbitrary invalid mode names into one of the three supported modes.

Use the documented record shape and exact mode values. Do not intentionally depend on malformed settings as a safety mechanism.

Read the result as an explanation

A resolved result contains policy, tier, and override, with provenance fields where the selected branch supplies them. policyKey is useful when user policy won. reason is optional, not a complete audit record. For example, ignoring an override-only critical prompt in yolo can also omit its reason from the resolved result.

Paper checkpoint: does write: deny necessarily deny a call whose declaration has policyKey: seed_slot? Worked answer: no. A valid keyed policy is selected first; the invoking write policy is consulted only when the keyed value supplies no valid policy. Then the ordinary precedence applies.

Failure boundary: an allow result is permission at this resolver, not a successful call, a persisted human answer, a domain grant, or proof that another gate agrees.

Source anchors: packages/coding-agent/src/tools/approval.ts — normalizePolicy, normalizeDecision, resolveApproval, requiresApproval, denyError; the fixture references identify the resolver section as lines 104–233. Recorded cases are in proof/report.json, under the Approval Desk IDs above. Existing contract coverage is in packages/coding-agent/test/tools/approval.test.ts.

Tool permissions and approvals · Source chapter: permissions/approval-desk-modes-and-policies. Original evidence remains scoped to its recorded snapshot.

Read this chapter as Markdown

Your lesson ticks

A self-reported reading checklist, not proof of real OMP behavior. Only these ticks are saved in this browser. Reading a milestone does not resume, fork, reset or export a session.

Chapters I have worked through
Start here 1
Sessions, resets, and reviewable history 19
Memory and reusable knowledge 14
Tangent work and live control 17
Tool permissions and approvals 15
Extensions inside those boundaries 23
Connections and next steps 8
0 of 97 checked

Checklist saving needs JavaScript and available browser storage.