| 1 | # Authorization order |
| 2 | |
| 3 | Codewhale combines tool availability, hooks, typed permission rules, approval |
| 4 | posture, repository policy, and sandboxing. An approval from one layer is not a |
| 5 | universal bypass: a later safety layer can still require review or block the |
| 6 | call, and an approval is not an operating-system sandbox grant. |
| 7 | |
| 8 | This page records the order implemented by the interactive engine's |
| 9 | model-requested tool path. Entrypoints that use only part of that path, such as |
| 10 | the core runtime's direct tool API, keep the relative order of the layers they |
| 11 | do use. |
| 12 | |
| 13 | ## Model tool-call pipeline |
| 14 | |
| 15 | The interactive engine evaluates a model-requested tool call in this order: |
| 16 | |
| 17 | | Order | Layer | Result | |
| 18 | |---:|---|---| |
| 19 | | 1 | Effective configuration and posture | User settings, command/runtime overrides, and the project overlay resolve before the turn. A project overlay may tighten `approval_policy`, `sandbox_mode`, or shell availability, but may not loosen them. | |
| 20 | | 2 | Mode and tool admission | Plan-mode restrictions, input parse errors, per-command tool deny/allow lists, caller restrictions, and missing execution registrations fail before policy rules are considered. A tool present in both command lists is denied. | |
| 21 | | 3 | Preparation, then `tool_call_before` hooks | Registry preparation is side-effect free. Foreground hooks then fold as `deny > ask > allow`, and a strict matching hook that produces no verdict fails closed. The last `updatedInput` wins; the engine prepares the rewritten input again before later gates inspect it. | |
| 22 | | 4 | Registered-tool baseline | The prepared tool's `ApprovalRequirement` establishes its ordinary approval need. A hook `ask` is applied after that assignment so the baseline cannot erase it. Non-bypassable registered holds remain forced; Full Access converts them to hard blocks instead of opening a contradictory modal. Plan mode also blocks write-capable tools here. | |
| 23 | | 5 | Typed `permissions.toml` rule | A matching `deny` blocks. A matching `allow` may clear only ordinary registry approval; it cannot clear a hook `ask` or a non-bypassable registered hold. A matching `ask` forces review only in a posture that can prompt. Full Access/auto-approval is not downgraded into a prompt, while an explicit typed `deny` still blocks. | |
| 24 | | 6 | Auto-review policy and built-in safety floor | Configured block rules run before the built-in floor, then configured allow rules and the deterministic fallback. This layer runs after typed permissions and can add a prompt or block, but cannot remove an earlier hold. Full Access deliberately skips the interactive publish hold; catastrophic destructive background/headless actions remain protected. | |
| 25 | | 7 | Repository law | Protected path invariants can only add a prompt or block. A repo-law prompt becomes a hard block in Full Access, which has no contradictory approval modal. | |
| 26 | | 8 | Human approval | A remaining prompt is sent to the approval channel. Denial stops the call. Approval authorizes this planned call; session and persistent choices affect later matching calls but do not erase a later gate from this call. | |
| 27 | | 9 | Tool authority and execution sandbox | Worker authority envelopes, native tool path checks, and the selected OS or external sandbox still apply during execution. A sandbox denial remains a denial unless the user separately authorizes a supported elevation path. | |
| 28 | |
| 29 | The ordering is intentionally monotonic after the typed permission layer: |
| 30 | auto-review and repository law can tighten a result, not turn a previous block |
| 31 | or prompt into an unreviewed execution. There are explicit posture choices |
| 32 | inside those layers—for example, Full Access does not create an interactive |
| 33 | publish prompt—but those choices do not let an earlier remembered grant erase |
| 34 | a hold that the layer actually produced. |
| 35 | |
| 36 | ## Typed permission-rule selection |
| 37 | |
| 38 | `permissions.toml` is currently a sibling of the active user `config.toml`. |
| 39 | There is no project-local permission-rule source today. An optional `workspace` |
| 40 | field scopes one user rule to a repository; it does not create a project |
| 41 | overlay. `/permissions` reports that source, matcher, scope, and whether the |
| 42 | scope applies to the current workspace. |
| 43 | |
| 44 | The execution-policy engine evaluates matching rules as follows: |
| 45 | |
| 46 | 1. Normalize the tool, command, workspace, and any workspace-relative path. |
| 47 | Unsafe or external paths do not become matchable file rules. |
| 48 | 2. Check denied command prefixes against the whole command and every chained |
| 49 | segment. These hard prefix denies are merged across rulesets and always win. |
| 50 | 3. Compute a trusted-prefix candidate for an unchained shell command. This is a |
| 51 | candidate for the approval-mode fallback, not an immediate decision. |
| 52 | 4. Select one matching typed rule by this lexicographic precedence: |
| 53 | 1. higher source layer: `User > Agent > BuiltinDefault`; |
| 54 | 2. stronger action inside that layer: `deny > ask > allow`; |
| 55 | 3. the more specific matcher when layer and action tie. |
| 56 | 5. Apply the selected typed action. `deny` forbids the call, `allow` skips the |
| 57 | execution-policy approval, and `ask` requires approval. A typed `ask` |
| 58 | overrides a trusted prefix. |
| 59 | 6. If no typed action decides the result, apply the approval-mode fallback |
| 60 | using the trusted-prefix candidate. |
| 61 | |
| 62 | Source layer is compared before typed action. Consequently, a user-layer typed |
| 63 | `allow` can override an agent-layer typed `deny`; inside the same layer, `deny` |
| 64 | still beats `ask`, which beats `allow`, regardless of file order or matcher |
| 65 | specificity. Hard denied prefixes are the exception: they are checked before |
| 66 | typed-layer selection and cannot be overridden by a typed allow. |
| 67 | |
| 68 | Specificity is only a tie-breaker after source and action. A constrained |
| 69 | command, exact-command, path, or workspace matcher beats a tool-wide rule with |
| 70 | the same action in the same layer. Specificity never lets a narrow allow beat a |
| 71 | same-layer deny. |
| 72 | |
| 73 | For chained shell commands, a trusted prefix never approves the whole chain. A |
| 74 | typed deny that wins for any individual segment blocks the full invocation. |
| 75 | |
| 76 | ## Approval posture and missing prompts |
| 77 | |
| 78 | The execution-policy result is combined with the registered-tool baseline; the |
| 79 | two should not be interpreted independently. |
| 80 | |
| 81 | - Ask and Auto-Review may surface tool safety approvals. Auto-Review does not |
| 82 | pause for model-authored user questions, which are a separate channel. |
| 83 | - Full Access and YOLO-compatible auto-approval paths do not let a typed `ask` |
| 84 | downgrade the session into prompting. Typed deny, non-bypassable registered |
| 85 | holds, catastrophic background/headless safety holds, and repository law |
| 86 | still fail closed where their respective layers apply. |
| 87 | - With `approval_policy = "never"`, a matching typed `ask` is forbidden because |
| 88 | the required prompt cannot be shown. |
| 89 | |
| 90 | Runtime adapters may transport an approval decision differently from the |
| 91 | interactive modal. That transport and any continuation protocol are separate |
| 92 | from this ordering contract; see [Runtime API](RUNTIME_API.md). |
| 93 | |
| 94 | ## Project overlays |
| 95 | |
| 96 | The project config overlay at `<workspace>/.codewhale/config.toml` is not a |
| 97 | permission-rule layer. It can only move approval and sandbox posture toward |
| 98 | more restrictive values: |
| 99 | |
| 100 | - approval: `auto` → `on-request`/`untrusted` → `never`; |
| 101 | - sandbox: `danger-full-access` → `workspace-write` → `read-only`; |
| 102 | - shell availability: `true` may become `false`, never the reverse. |
| 103 | |
| 104 | Project config cannot add credentials, hooks, provider authority, or a |
| 105 | project-local `permissions.toml`. See |
| 106 | [Configuration](CONFIGURATION.md#per-project-overlay-485) for the complete |
| 107 | overlay allow-list. |
| 108 | |
| 109 | ## Regression coverage |
| 110 | |
| 111 | The contract is exercised by tests at the layers that own each decision: |
| 112 | |
| 113 | - `authorization_order_contract_matches_documented_precedence` covers hard |
| 114 | prefix denial and the typed `layer → action → specificity → approval-mode` |
| 115 | sequence through the public execution-policy API. |
| 116 | - `hook_fold_deny_wins_over_ask_and_allow` covers foreground hook folding. |
| 117 | - `non_bypassable_registered_tools_block_without_prompt_in_full_access` covers |
| 118 | registered holds. |
| 119 | - `full_access_permission_allow_cannot_bypass_background_catastrophic_floor` |
| 120 | and `full_access_permission_allow_cannot_bypass_repo_law` cover later safety |
| 121 | layers overriding a remembered allow. |
| 122 | - `project_merge_only_tightens_approval_and_sandbox_policy` covers project |
| 123 | overlay monotonicity. |
| 124 | |
| 125 | Focused commands: |
| 126 | |
| 127 | ```bash |
| 128 | cargo test -p codewhale-execpolicy --test authorization_order --locked |
| 129 | cargo test -p codewhale-tui --bin codewhale-tui --locked full_access_permission_allow_cannot_bypass |
| 130 | cargo test -p codewhale-config --locked project_merge_only_tightens_approval_and_sandbox_policy |
| 131 | ``` |
| 132 | |
| 133 | ## Related references |
| 134 | |
| 135 | - [Configuration](CONFIGURATION.md) — rule schema, `/permissions`, hooks, and |
| 136 | project overlays |
| 137 | - [Modes](MODES.md) — Plan/Act/Operate and permission posture |
| 138 | - [Sandbox threat model](SANDBOX.md) — platform enforcement and fallbacks |
| 139 | - [Runtime API](RUNTIME_API.md) — approval events and remote resolution |
| 140 |