mapVerdictToAction
mapVerdictToAction(
command,verdict,opts):RaterDecision
Defined in: _worktrees/docs-release/gaunt-sloth/packages/core/src/core/shell/rater.ts:2039
CFG-27 — pure, testable mapping from a ShellSafetyVerdict + the raw command to a RaterAction, keyed on the rung (spec §4.2, §8):
| Outcome | manual/write |
assisted |
auto |
bypass |
|---|---|---|---|---|
| — (no rating) | escalate | approve | ||
safe |
— | approve | approve | — |
destructive |
— | escalate | reject — §5’s negotiation ([[EXT-29]]); escalate when a preflight floors the command and §4.6’s carve-out did not lift it ([[EXT-106]]) | — |
catastrophic |
— | escalate | escalate — never negotiate | — |
attack |
— | halt | halt | — |
[[EXT-81]] — there is no longer a row cutting across that column. A command whose target the
gate cannot statically resolve used to return abstain here, before the safe check and after
the two severe outcomes. It is now rated like any other command, carrying a neutral note about
the shape the parser saw (buildParserPreflightNote), and this table is the whole mapping
again. The table gained nothing it did not have: what it LOST is a branch that made
catastrophic and attack unreachable for every composed, substituting or redirecting command.
Order of precedence (fail-closed FIRST — this ordering IS the safety property):
bypass→approve. The gate is off. The declared deny list and the exec-time hardline floor still apply, but neither is decided here.- Unrated rungs (
manual,write) →escalate. No model is consulted at all; the allow-list is checked by the caller BEFORE this function, so reaching here means the human decides. (The two rungs behave identically here, and that is not a missing branch: what separates them is which tools they auto-grant, decided once inresolveGatedToolNameswhen the gated set is built. A call that reaches this function is already one the rung did not grant, and the shell — this function’s only subject — is granted by neither.) - The deterministic preflight FINDINGS, which FLOOR the outcome at
destructiveand never lower one (preflightFloorReason): the script-env-leak preflight (hasScriptEnvLeakRisk) and EXT-61’s open-world preflight (findOpenWorldHostLiterals — a host literal in a fetch/transfer position, §4.6). Both are recomputed from the RAW command, independently of what the rater said — the open-world arm additionally againstopts.provenance, since [[EXT-106]] §4.6 lifts it where the user named every host in the command themselves (effectivePreflightFloorFinding). Either rewrites a verdict that sits BELOW the floor — i.e.safe, and onlysafe(isBelowDestructiveFloor) — todestructivewith an honest reason, before thesafecheck, so a manipulatedsafeverdict can never slip one of them through. A rater verdict may only ever make an outcome worse, never better, and so may a preflight:destructive,catastrophicandattackall pass through UNCHANGED. (Before the rescale this branch excluded the single halting outcome BY NAME. Renamed in place it would have let a preflight hit downgrade acatastrophicverdict todestructive— the exact inverse of the invariant above, silently trading an unnegotiable escalation for a negotiable one atauto.) attack→halt, at both rated rungs, never negotiable.catastrophic→escalate, and MUST NOT enter §5’s negotiation.safe→approve;destructive→escalateatassisted, and atautoeitherreject(§5’s negotiation, [[EXT-29]]) or — when a floor in (3) APPLIES to this command —escalate, because [[EXT-106]] §3 will not open a negotiation whose outcome (3) has already decided. The test is isNegotiableCall, shared with the rating prompt, and it is given the same provenance (3) is: a command §4.6’s carve-out lifted the floor from can reachapproveagain, so it is negotiable again.
EXT-58 (§4.4): the verdict’s suggestedTool is not read here, and that is deliberate. A
suggestion is never an approval — it must not change the action, must not approve the original
command, and must not pre-approve the suggested tool. The gate also never decides for itself that
a shell command is “equivalent” to a built-in and substitutes it: any such equivalence test would
be a second command parser, and a second command parser is a second place for the gate to be
bypassed. The suggestion is carried, untouched, to the human (§6) and to the model (§7) — nothing
else. Note that the fail-closed rewrite in (3) builds a FRESH verdict and therefore drops any
suggestion along with the reason it belonged to: a verdict the gate has just declared
untrustworthy must not keep recommending anything. A verdict the preflight leaves alone was never
declared untrustworthy — the gate is agreeing with it, not overriding it — so it keeps both.
Parameters
Section titled “Parameters”command
Section titled “command”string
The raw command string (used to recompute ambiguity + preflight independently of the rater, so the gate is robust even if the rater is wrong or manipulated).
verdict
Section titled “verdict”{ outcome: "safe" | "destructive" | "catastrophic" | "attack"; reason: string; suggestedTool?: string; } | undefined
The rater’s verdict (or FAIL_CLOSED_VERDICT); undefined at the unrated
rungs. A missing verdict at a RATED rung is treated as FAIL_CLOSED_VERDICT.
The rung in force, and [[EXT-106]] §4.6’s user provenance — see RaterDecisionOptions.provenance, whose default floors exactly as before.