resolveApprovals
resolveApprovals(
config,command):ResolvedApprovals
Defined in: _worktrees/docs-release/gaunt-sloth/packages/core/src/config/shell-policy.ts:827
CFG-27 — resolve the effective ResolvedApprovals for the active command.
There is no defaults matrix: §1.1 makes auto-safe the default in every context, so this
resolver neither detects nor accepts a “context”. Precedence is the only thing it decides, and
§9.1 splits it in two:
- The scalars —
mode,rater,raterTimeoutMs— are replaced when the per-command value states them and inherited from the root when it does not. So the scalar sugar"code": { "approvals": "bypass" }is exactly{ mode: 'bypass' }merged over the root: it sets the rung and nothing else. denyandescalatenever replace: they CONCATENATE across every scope. A command-specificdenyadds to the root’s. Removing an inherited prohibition for one command is deliberately not expressible.allowis REPLACED when the per-command value states its own, and inherited when it does not. A per-command scope may therefore narrow what runs unprompted, and may never widen what is prohibited.mcp(EXT-70 §4.7) followsallow, not the restrictive lists: replaced when the per-command value states it, inherited when it does not. Believing a hint is a PERMISSIVE act in both directions — it can make anallowhint entry fire and can make adenyhint entry stop firing — so it merges the way the permissive list does, and a per-command scope can narrow the session’s trust ("mcp": {}believes nothing) but never inherits half of it by accident. Deep-merging the two scopes’serversmaps was rejected for the same reason: it would leave a deliberately distrustful per-command block silently carrying the root’s trust.
The two halves differ because the costs differ (§3.1), not for tidiness. A missed allow entry escalates and a missed deny entry falls through to the rater — neither is an execution — while a too-broad allow entry runs, unrated and unprompted. Concatenating the restrictive lists fails toward a prompt; concatenating the permissive one fails toward an execution, and would leave a deliberately restrictive per-command rung with no way to shed the root’s standing grants. Do not “regularize” these three into one policy: the direction each list fails in is the whole design.
On the restrictive side the pressure runs the other way (§11.1f). Were the per-command value to
replace the root wholesale, the friendliest spelling of “stop asking me about code” would also
delete every deny entry — at the one rung where the deny list and the §8 floor are the only
checks left. A prohibition a nested config key can quietly delete is not a hardline.
Concatenation order cannot change any outcome (resolveApprovalRules consults every deny entry
before any escalate entry and every escalate entry before any allow entry), so root-first is a
convention for readability — matching GthAgentRunner.approvalRuleLists, where the declared
entries precede the runtime grants — and never a precedence.
Defaults are applied HERE, at the read site, rather than in DEFAULT_CONFIG — so the
effective-config snapshot the /config panel renders never churns (à la GS2-34
injectModelContext / GS2-63 output.header).
This is the per-command half. The cross-LAYER half (a project config’s lists adding to a global
config’s rather than replacing them) is the additive-array policy in config/loader.ts; both are
needed, since either alone still loses a list silently.
Parameters
Section titled “Parameters”config
Section titled “config”Pick<GthConfig, "commands" | "approvals"> | undefined
command
Section titled “command”GthCommand | undefined
The active command; selects the per-command block.