## Approval Desk\:&#32;modes and policies

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

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

### Follow the implementation’s precedence

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

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

After that lookup\,&#32;the following order applies\:

| Order | Condition | Result |
| --- | ---: | --- |
| 1 | Tool decision says&#32;`deny` | Deny from tool policy\. |
| 2 | Effective user policy says&#32;`deny` | Deny from user policy\. |
| 3 | Mode is&#32;`yolo` | An explicit tool policy wins\;&#32;otherwise use the effective user policy or allow\.&#32;Override is reported false\. |
| 4 | Non\-yolo decision has&#32;`override: true` | Explicit tool allow remains allow\;&#32;otherwise prompt\.&#32;Source is tool and override remains true\. |
| 5 | Tool decision explicitly says allow or prompt | Use that tool policy\. |
| 6 | A valid effective user policy remains | Use that user policy\. |
| 7 | No earlier branch decided | Compare the tier with the mode matrix\. |

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

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

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

### Work through four policy conflicts

**Prediction\:**&#32;for each case below\,&#32;name the winning source as well as allow\,&#32;prompt\,&#32;or deny\.

| Case | Important inputs | Recorded resolution |
| --- | ---: | --- |
| `approval-tool-deny-wins` | Tool write\-tier deny\;&#32;user allow\;&#32;yolo | deny\,&#32;source tool\,&#32;reason&#32;`Fictional desk is closed` |
| `approval-user-deny-wins` | Tool read\-tier allow\;&#32;user deny\;&#32;yolo | deny\,&#32;source user\,&#32;policy key&#32;`seed_note` |
| `approval-explicit-tool-prompt` | Explicit tool prompt\;&#32;user allow\;&#32;yolo | prompt\,&#32;source tool\,&#32;override false |
| `approval-tool-allow-before-user-prompt` | Explicit tool allow\;&#32;user prompt\;&#32;always\-ask | allow\,&#32;source tool |

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

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

### A policy key is a lookup identity\,&#32;not another cumulative grant

Compare these complete fictional resolver inputs\.&#32;They are teaching data\,&#32;not configuration to install\.

~~~json
{
  "tool": "write",
  "approval": { "tier": "exec", "policyKey": "seed_slot" },
  "mode": "yolo",
  "userPolicies": {
    "seed_slot": "not-a-policy",
    "write": "deny"
  }
}
~~~

This is the input shape of&#32;`approval-policy-key-fallback`\.&#32;The keyed value is invalid\,&#32;so the valid invoking\-tool policy supplies the denial\.

**Recorded answer\:**&#32;deny\,&#32;source user\,&#32;policy key&#32;`write`\.

Now inspect&#32;`approval-policy-key-specific`\:

~~~json
{
  "tool": "write",
  "approval": { "tier": "exec", "policyKey": "seed_slot" },
  "mode": "always-ask",
  "userPolicies": {
    "seed_slot": " ALLOW ",
    "write": "deny"
  }
}
~~~

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

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

### Keep normalization claims local

`normalizePolicy()`&#32;accepts strings\,&#32;trims them\,&#32;lowercases them\,&#32;and recognizes only&#32;`allow`\,&#32;`deny`\,&#32;and&#32;`prompt`\.&#32;Other values do not become policies at that resolver layer\.&#32;A missing or invalid tier in a returned decision defaults to exec\.&#32;A function\-valued declaration can still throw\;&#32;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\.&#32;The wrapper separately uses raw own\-property presence when deciding whether an inner prompt is explicit\.&#32;An invalid entry can therefore be ignored as a policy by the resolver while still mattering to that later predicate\.&#32;Nor does&#32;`resolveApproval()`&#32;normalize arbitrary invalid mode names into one of the three supported modes\.

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

### Read the result as an explanation

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

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

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

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