/** * [[EXT-106]] (spec §4.6) — **the user-provenance carve-out: where the human named the host * themselves, we WARN rather than ASK.** * * §4.6's open-world preflight floors every command that names a host in a fetch or transfer * position, before any model call. That is right for a host the *agent* chose. It is wrong for the * one the *user* typed: the person who wrote *"fetch https://example.com/install.sh"* is then asked * to confirm the fetch they just requested, on a rung they chose in order not to be asked. This * module decides that one question — **was every host in this command named verbatim by the * human?** — and it decides it deterministically, with no model call. * * ## The six rules that make it safe, and none of them is optional * * 1. **It lifts the FLOOR, never the RATING.** What is carved is the claim *"this command names a * host"*, never *"this command is safe"*. The rater still runs, still rates, and a * `destructive` verdict on a carved command still refuses it — `curl | * sh` is `destructive` on its own merits and stays so. * 2. **EVERY host must be user-named, never the first.** {@link findOpenWorldHostLiterals} returns * all matches for exactly this reason, and {@link carvedOpenWorldHosts} requires all of them, so * one host the user asked for cannot carry an unmentioned second one through with it. * 3. **Token EQUALITY, never substring.** See `userNamedTokens`. * 4. **`auto` only** ({@link isUserProvenanceRung}), enforced HERE rather than at the call site, so * a caller that forgets cannot widen the scope. * 5. **The literal must name a host in the RAW command too**, not only in the normalized form the * match was found in — because the raw string is what runs. See `rawCommandHostLiterals`. * 6. **Only a command whose human turn really is the user's own words has any provenance at all.** * A `human` message the product SYNTHESISED from content it fetched — `review`/`pr`'s diff, the * PR description `gth pr` interpolates into its discovery prompt — never enters the window this * module reads. That rule lives where the window does * (`ShellNegotiationState.admitUserProvenance`) and is keyed on * the CLI verb, never on anything inside a message: the bytes are attacker-controlled, so a * marker in them can be forged by the text it is meant to classify. * * ## The residual hole, stated once * * §4.6.1's error-cost inversion is **narrowed, not falsified**. The rater still rates the command, * so a clear typosquat is `attack` and halts the run, and an unclear one is `destructive` and still * reaches a person. The gap is precisely one case: a deception the rater rates `safe` outright — one * it never noticed — where the user is now **told rather than asked**. The warning the runner emits * on every carved approval is what covers even that case, and Andrew accepted this trade explicitly. * It is not relitigated here. * * **Host trust is still never a model judgement.** Tier 1 moves the decision to a human's verbatim * words, which is an exact match. What changes is *when the deterministic floor fires*, never *who * may judge a hostname*. Tier 2 — a paraphrase the model matches (*"clone the testing repo from our * org"*) — is deliberately out of scope and must not be added here. * * @module */ import type { ApprovalRung } from '#src/config.js'; /** * [[EXT-106]] §4.6 — **the host literals §4.6's preflight found and the user had already named**, or * an empty array when the carve-out does not apply to this call. * * This is the ONE implementation of the carve, read by every reader of it: the floor * ({@link @gaunt-sloth/core!core/shell/rater.mapVerdictToAction | mapVerdictToAction}), negotiability * ({@link @gaunt-sloth/core!core/shell/rater.isNegotiableCall | isNegotiableCall}), the diagnostic archive and the warning. A second * derivation is how a gate and a warning come to disagree about whether a command was carved. * * **The rung is enforced here, not at the call site.** A caller that forgets to check it cannot * widen the scope, which matters because the rung that must NOT carve is the DEFAULT one — see * {@link isUserProvenanceRung}. * * **`provenance` is the user's own retained messages, and it is NOT * `ShellNegotiationState.contextFor`'s.** That function returns * `userMessages: []` at round 1 by design — §5.1's *"round 1 sees the command alone"* — and round 1 * is precisely the round this carve-out exists to act on. §5.1 bounds what the **rater** may see; * the floor is not the rater, and a floor reading nothing at round 1 would never fire at all. The * runner reads `ShellNegotiationState.retainedUserMessages` * instead. * * **The window is cumulative across the turns of a thread** (capped and de-duplicated by that * state), so a host the user named in an earlier turn still carves a command proposed in a later * one. That is a decision, not an accident: *"the user asked for this"* is a property of the * conversation rather than of one message, and an agent that gathers information for two turns * before running the fetch it was asked for is the ordinary case. `resetThread()` clears the window, * which is the right boundary for it. * * @param rung The rung in force. Anything but `auto` returns `[]`. * @param command The raw command string as the model proposed it — read through the same * {@link findOpenWorldHostLiterals} the floor reads, so the two cannot disagree about which hosts * are in play. * @param provenance The user's retained messages. **Empty means "no provenance", which floors * exactly as before** — the default every caller outside a live session gets. */ export declare function carvedOpenWorldHosts(rung: ApprovalRung, command: string, provenance: readonly string[]): readonly string[]; /** * Whether §4.6's open-world floor is lifted on this call — the boolean reading of * {@link carvedOpenWorldHosts}, defined in terms of it so the two can never disagree. */ export declare function isOpenWorldCarved(rung: ApprovalRung, command: string, provenance: readonly string[]): boolean;