@gaunt-sloth/core 2.0.0-beta.0 → 2.0.0-beta.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (90) hide show
  1. package/LICENSE +6 -6
  2. package/dist/config/loader.d.ts +13 -0
  3. package/dist/config/loader.js +55 -11
  4. package/dist/config/loader.js.map +1 -1
  5. package/dist/config/schema.d.ts +10 -0
  6. package/dist/config/schema.js +46 -24
  7. package/dist/config/schema.js.map +1 -1
  8. package/dist/config/shell-policy.d.ts +142 -1
  9. package/dist/config/shell-policy.js +114 -7
  10. package/dist/config/shell-policy.js.map +1 -1
  11. package/dist/config/types.d.ts +19 -0
  12. package/dist/config/types.js.map +1 -1
  13. package/dist/constants.d.ts +10 -0
  14. package/dist/constants.js +10 -0
  15. package/dist/constants.js.map +1 -1
  16. package/dist/core/GthAbstractAgent.d.ts +31 -0
  17. package/dist/core/GthAbstractAgent.js +159 -4
  18. package/dist/core/GthAbstractAgent.js.map +1 -1
  19. package/dist/core/GthAgentRunner.d.ts +314 -18
  20. package/dist/core/GthAgentRunner.js +1032 -101
  21. package/dist/core/GthAgentRunner.js.map +1 -1
  22. package/dist/core/approvals/approvalRequest.d.ts +247 -0
  23. package/dist/core/approvals/approvalRequest.js +315 -0
  24. package/dist/core/approvals/approvalRequest.js.map +1 -0
  25. package/dist/core/approvals/grants.d.ts +165 -9
  26. package/dist/core/approvals/grants.js +702 -57
  27. package/dist/core/approvals/grants.js.map +1 -1
  28. package/dist/core/plainToolIndication.d.ts +11 -1
  29. package/dist/core/plainToolIndication.js +25 -10
  30. package/dist/core/plainToolIndication.js.map +1 -1
  31. package/dist/core/shell/abstention.d.ts +1 -1
  32. package/dist/core/shell/abstention.js +65 -11
  33. package/dist/core/shell/abstention.js.map +1 -1
  34. package/dist/core/shell/alignment.d.ts +491 -0
  35. package/dist/core/shell/alignment.js +687 -0
  36. package/dist/core/shell/alignment.js.map +1 -0
  37. package/dist/core/shell/approvalCapture.d.ts +53 -41
  38. package/dist/core/shell/approvalCapture.js +0 -42
  39. package/dist/core/shell/approvalCapture.js.map +1 -1
  40. package/dist/core/shell/approvalStop.d.ts +121 -3
  41. package/dist/core/shell/approvalStop.js +153 -90
  42. package/dist/core/shell/approvalStop.js.map +1 -1
  43. package/dist/core/shell/hardline.d.ts +26 -4
  44. package/dist/core/shell/hardline.js +321 -16
  45. package/dist/core/shell/hardline.js.map +1 -1
  46. package/dist/core/shell/negotiation.d.ts +352 -63
  47. package/dist/core/shell/negotiation.js +295 -123
  48. package/dist/core/shell/negotiation.js.map +1 -1
  49. package/dist/core/shell/openWorld.d.ts +135 -0
  50. package/dist/core/shell/openWorld.js +572 -35
  51. package/dist/core/shell/openWorld.js.map +1 -1
  52. package/dist/core/shell/provenance.d.ts +91 -0
  53. package/dist/core/shell/provenance.js +136 -0
  54. package/dist/core/shell/provenance.js.map +1 -0
  55. package/dist/core/shell/rater.d.ts +394 -163
  56. package/dist/core/shell/rater.js +536 -238
  57. package/dist/core/shell/rater.js.map +1 -1
  58. package/dist/core/shell/raterHealth.d.ts +101 -0
  59. package/dist/core/shell/raterHealth.js +121 -0
  60. package/dist/core/shell/raterHealth.js.map +1 -0
  61. package/dist/core/shell/raterModel.d.ts +17 -8
  62. package/dist/core/shell/raterModel.js +11 -8
  63. package/dist/core/shell/raterModel.js.map +1 -1
  64. package/dist/core/shell/rejection.d.ts +20 -5
  65. package/dist/core/shell/rejection.js +18 -4
  66. package/dist/core/shell/rejection.js.map +1 -1
  67. package/dist/core/toolDisplay.d.ts +42 -0
  68. package/dist/core/toolDisplay.js +31 -0
  69. package/dist/core/toolDisplay.js.map +1 -1
  70. package/dist/core/types.d.ts +134 -16
  71. package/dist/core/types.js.map +1 -1
  72. package/dist/providers/configurationPassthrough.d.ts +36 -6
  73. package/dist/providers/configurationPassthrough.js +40 -28
  74. package/dist/providers/configurationPassthrough.js.map +1 -1
  75. package/dist/providers/openrouter.js +36 -6
  76. package/dist/providers/openrouter.js.map +1 -1
  77. package/dist/providers/vertexai.js +21 -0
  78. package/dist/providers/vertexai.js.map +1 -1
  79. package/dist/utils/binaryOutputUtils.js +103 -21
  80. package/dist/utils/binaryOutputUtils.js.map +1 -1
  81. package/dist/utils/fileUtils.d.ts +7 -1
  82. package/dist/utils/fileUtils.js +17 -3
  83. package/dist/utils/fileUtils.js.map +1 -1
  84. package/dist/utils/globalConfigUtils.d.ts +5 -2
  85. package/dist/utils/globalConfigUtils.js +14 -3
  86. package/dist/utils/globalConfigUtils.js.map +1 -1
  87. package/dist/utils/vertexaiUtils.js +61 -3
  88. package/dist/utils/vertexaiUtils.js.map +1 -1
  89. package/package.json +4 -4
  90. package/schema/gsloth-config.schema.json +34 -0
@@ -1,94 +1,96 @@
1
+ import { UNRESOLVED_MCP_SERVER } from '#src/core/approvals/mcpSubjects.js';
2
+ import { renderApprovalEntryObject } from '#src/config/schema.js';
3
+ import { frameUntrustedCommand, frameUntrustedText, frameWidthFor, narrowTerminalNotice, neutralizeToOneLine, neutralizeUntrustedText, } from '#src/core/shell/framing.js';
1
4
  /**
2
- * @module core/shell/approvalStop
5
+ * [[EXT-115]] — **the parts that name WHAT was gated**, branched on the {@link ApprovalSubject}
6
+ * kind the gate itself decided the call on.
3
7
  *
4
- * CFG-27 the two ways the approvals gate **ends a run** rather than answering a tool call.
5
- * Both are thrown from `GthAgentRunner.decideToolApproval`, both carry the command and the reason
6
- * a person needs to see, and both are re-thrown UNCHANGED by `processMessages` (which otherwise
7
- * wraps failures as `Agent processing failed: …`) so the explanation reaches the user intact.
8
+ * Both stops carried the label `Command` unconditionally, so a gated `write_file` in CI read as
9
+ * `Command: write_file` and a gated MCP call read the same way the framing [[TUI-C67]] removed
10
+ * from the two terminal prompts, surviving on the path with nobody to ask, where the message is the
11
+ * only thing anyone sees. It lives here rather than in either subclass because both stops make the
12
+ * identical claim about the identical subject, and a branch written twice is a branch that comes to
13
+ * disagree with itself.
8
14
  *
9
- * They are errors rather than {@link ../types.js ToolApprovalDecision}s on purpose. A decision is
10
- * something the model observes as a `ToolMessage` and can respond to; these two are precisely the
11
- * cases where the spec says the model gets no move at all:
15
+ * **The identifier stays in a `value`/`command` part and is NEVER interpolated into a sentence.**
16
+ * This is the one line the fix must not cross, and it is why the prose form
17
+ * `core/approvals/promptHeader` renders is deliberately not imported here: an `own` part is painted
18
+ * as-is *precisely because nothing can forge it*, everything else goes through the shared renderer,
19
+ * and the label sits on its own row for the reason {@link approvalStopRows} states. On the `mcpTool`
20
+ * arm the name comes from a third-party server's own tool listing, so a sentence carrying it would
21
+ * put attacker-supplied bytes into the gate's own voice — the forgery TUI-C26 closed.
12
22
  *
13
- * - **{@link AttackHaltError}** (§4.2) — an `attack` outcome *halts the run*. "A halt ends the
14
- * agent loop. It is not a rejection the model can respond to, and no rung except `bypass` can
15
- * turn it into anything else."
16
- * - **{@link NonInteractiveEscalationError}** (§6.2) — where no human can answer, *every*
17
- * escalation is an immediate non-zero exit carrying the command, the rating and its reason.
18
- * There is no prompt, no waiting, and never a timeout into approval. Teams that need specific
19
- * commands to run unattended declare them in `approvals.allow` (§3), which is consulted before
20
- * the rater and therefore never escalates.
23
+ * The arms:
21
24
  *
22
- * Exit code: neither class sets one. The single-shot runtime (`runSingleShot`) already reports a
23
- * thrown run as `ok: false`, and each command entry point turns that into `setExitCode(1)` — so
24
- * "immediate non-zero exit carrying the explanation" is what a caller already gets, without a new
25
- * exit path that could diverge from the existing one.
25
+ * - `shell` `Command`, on a `command` part. Unchanged, and unchanged deliberately: that part kind
26
+ * is what runs the site extraction, which is the framing a shell command's composition and
27
+ * substitution boundaries are shown through.
28
+ * - `mcpTool` with a nameable server `MCP tool`, plus a second `MCP server` part. Naming the
29
+ * server is the point rather than a side effect: which server a call reaches is the load-bearing
30
+ * fact the old label hid, and it is the user's own `mcpServers` key (§4.7.5).
31
+ * - everything else — `Tool`, on a `value` part.
26
32
  *
27
- * ## [[TUI-C71]] a stop message is UNTRUSTED TEXT, and it is made safe in two places
33
+ * **A `mcpTool` call whose server would render blank falls to the `Tool` arm**, mirroring
34
+ * `approvalPromptHeader`'s own fallback so the two cannot describe one call two ways. The guard is
35
+ * on the **rendered** server, not the raw one: {@link UNRESOLVED_MCP_SERVER} is the empty string,
36
+ * but a server key of whitespace alone — which `z.string().min(1)` admits — is also empty once
37
+ * neutralised, and it would otherwise render an `MCP server` row promising an identity that is not
38
+ * there. `text` already carries the full registered name in that case, which still shows the
39
+ * `mcp__` namespace.
28
40
  *
29
- * Every string a stop carries except this module's own sentences is written by something other
30
- * than the user: the command the agent proposed, the rater's outcome and reason, the
31
- * `approvals.escalate` entry that matched, and the whole §5 negotiation. On an `attack` verdict
32
- * that is the *worst* text in the system by construction the rater judged the command's own
33
- * structure to evidence deception or obfuscation so a stop is the one message most likely to
34
- * carry a payload crafted to forge terminal output, and the last one that may be printed raw.
35
- *
36
- * The defence [[TUI-C26]] built has two halves, and they belong at different times:
37
- *
38
- * 1. **Neutralisation is done HERE, at construction, unconditionally.** It needs no width and no
39
- * screen, it is idempotent, and it is the half every consumer needs — a thrown stop reaches the
40
- * Ink transcript, `--no-tui` stderr, a session log, the approvals archive (`record.error`), an
41
- * eval turn record, AG-UI and a CI job's output, and only some of those are terminals. Doing it
42
- * at each render site would mean every present and future consumer had to remember; doing it
43
- * once here means none of them can forget. After it, `{@link message}` contains no control or
44
- * format character at all: a carriage return is the five printable characters `\x0d`, and —
45
- * because LF is a control character too — untrusted text cannot even open a new line, let alone
46
- * reach column 0 on one.
47
- * 2. **The gutter and the width bound are done at RENDER, by the surface** ({@link
48
- * approvalStopRows} for the plain surfaces, `<ApprovalStopMessage>` for the Ink TUI). They
49
- * cannot be done here: framing is arithmetic against a terminal width, an `Error.message` has
50
- * no width, and a block wrapped for an 80-column terminal is wrong for every other one. What
51
- * they buy over neutralisation alone is the last case it cannot close one very long
52
- * neutralised line that the *terminal* wraps, whose continuation starts at column 0 carrying
53
- * whatever the attacker chose to put at that offset. That route is measured, not theorised: on
54
- * a 120-column terminal the neutralised `Command:` line of a hostile command is ~164 columns.
55
- *
56
- * **Because half of it is the surface's job, the surfaces are enumerated rather than assumed.**
57
- * Every place that catches an `ApprovalStopError` and prints it calls {@link approvalStopRows} (or
58
- * the Ink component): `runtime/singleShot.ts`, `runtime/conversation.ts`,
59
- * `agent/modules/interactiveSessionModule.ts` (both its per-turn catch and its outermost one, which
60
- * is where the `-m` path lands), `review/modules/reviewModule.ts`, `app/commands/prCommand.ts`
61
- * (which catches what the PR-discovery agent throws) and `app/tui/components/App.tsx`.
62
- * **Adding a surface that catches a stop means adding it to that list.**
41
+ * **A stop built with no subject keeps the shell shape**, which is the opposite of
42
+ * `approvalPromptHeader`'s fail-to-vague and is chosen for a reason that does not apply there. That
43
+ * function picks between three sentences, so it picks the one true of every call; this one also
44
+ * picks the PART KIND, and falling back to `value` would silently drop the extracted-site notice
45
+ * from a shell stop `framing.ts` calls that the most decision-relevant thing in a command. Every
46
+ * production throw site passes a subject (`GthAgentRunner.decideToolApprovalInner` and
47
+ * `haltOrRunAnyway` both have the decision's own subject in hand), so this fallback is reached only
48
+ * by a hand-built error, where showing more is better than labelling more cautiously.
49
+ */
50
+ function subjectParts(text, subject) {
51
+ if (subject === undefined || subject.kind === 'shell') {
52
+ return [{ kind: 'command', label: 'Command', text }];
53
+ }
54
+ if (subject.kind === 'mcpTool' && neutralizeToOneLine(subject.server) !== UNRESOLVED_MCP_SERVER) {
55
+ return [
56
+ { kind: 'value', label: 'MCP tool', text },
57
+ { kind: 'value', label: 'MCP server', text: subject.server },
58
+ ];
59
+ }
60
+ return [{ kind: 'value', label: 'Tool', text }];
61
+ }
62
+ /**
63
+ * [[EXT-115]] **the `approvals.allow` entry the general recovery offers as an example**, one per
64
+ * subject kind, rendered by the one renderer that spells an entry ({@link
65
+ * renderApprovalEntryObject}) so an example and a stored grant cannot come to disagree about the
66
+ * grammar.
63
67
  *
64
- * **That list is over the ERROR, and the error is not the only way a stop reaches a screen.** Some
65
- * consumers take {@link message} and hand the *string* onward, at which point no `instanceof` can
66
- * find it again: `runConversation` records it as a turn's `error`, and `evalRunner` folds that into
67
- * an eval case's `reasons`, which `batch/reporters/textReporter.ts` prints a terminal, reached
68
- * without any of its code ever seeing this class. That one frames the text itself. So the honest
69
- * statement is not "every other consumer is a non-terminal" but: **a consumer that prints to a
70
- * terminal frames what it prints, whether it holds the error or only its text**, and the ones that
71
- * legitimately do neither are the file and protocol sinks (the approvals archive, the JUnit/JSON
72
- * reports, the AG-UI event) plus `reviewModule`'s `displayDebug` of the raw error, which is a debug
73
- * channel. All of them still inherit the neutralisation, which is what keeps the worst case a
74
- * wrapped line rather than a repainted screen.
68
+ * The kinds line up one for one with {@link ApprovalEntryType}, which is why this needs no mapping:
69
+ * a `tool` call's remedy is a `tool` entry, and a `shell` entry can never match one (`matchEntry`
70
+ * refuses a type mismatch outright), so the shell example this used to print unconditionally was a
71
+ * line the reader could paste and watch do nothing.
75
72
  *
76
- * **The structured fields stay RAW; only the message is neutralised.** That is what answers the
77
- * obvious objection to construction-time work that it stops the error being a faithful record of
78
- * what was proposed. `{@link ApprovalStopError.command}`, `reason`, `outcome`, `escalatedBy` and
79
- * `negotiation` are the command as the agent wrote it, byte for byte, for any consumer that needs
80
- * the truth rather than a screen; the message is the *presentation*, and a presentation that can
81
- * repaint the terminal is not one. The render path frames from those raw fields, so its gutter
82
- * numbers the command's real lines rather than one escaped line.
73
+ * **Only the kind is interpolated never a name, a server or a command.** These are the gate's own
74
+ * `own`-part sentences, the one part class a surface may paint raw, and a subject-derived
75
+ * identifier reaching one is exactly the forgery route the part tagging exists to close. The kind is
76
+ * a closed three-literal union, so nothing model-authored can ride in on it.
83
77
  *
84
- * **One parts list, two renderings** ({@link ApprovalStopPart}). The message string and the framed
85
- * block are both derived from {@link ApprovalStopError.parts}, so they cannot come to describe one
86
- * stop two ways the same argument `renderNegotiationTranscript` makes for its own two surfaces.
87
- * A part is tagged with *who wrote it*, which is the only distinction any of this rests on: the
88
- * gate's own prose can be painted as it is because nothing can forge it, and everything else goes
89
- * through the shared renderer.
78
+ * **`*` on the `mcpTool` example, deliberately.** In production this general form is reached for an
79
+ * `mcpTool` subject only when the server could NOT be attributed a specific entry is derived
80
+ * instead whenever one can be (`toolGrantEntry`) — and §4.7.5 makes `*` the reserved literal that is
81
+ * the only thing able to match such a call. Naming a plausible server there would hand the reader a
82
+ * line that cannot match the call they were just refused.
90
83
  */
91
- import { frameUntrustedCommand, frameUntrustedText, frameWidthFor, narrowTerminalNotice, neutralizeUntrustedText, } from '#src/core/shell/framing.js';
84
+ const GENERAL_ALLOW_EXAMPLE = Object.freeze({
85
+ shell: renderApprovalEntryObject({ type: 'shell', matcher: 'exact', pattern: 'npm test' }),
86
+ tool: renderApprovalEntryObject({ type: 'tool', matcher: 'exact', pattern: 'gth_web_fetch' }),
87
+ mcpTool: renderApprovalEntryObject({
88
+ type: 'mcpTool',
89
+ server: '*',
90
+ matcher: 'exact',
91
+ pattern: 'search_issues',
92
+ }),
93
+ });
92
94
  /**
93
95
  * The parts as the one string an `Error.message` has to be, with every untrusted part neutralised.
94
96
  *
@@ -154,7 +156,11 @@ export function approvalStopRows(parts, options) {
154
156
  * cannot shows {@link message}, which is already neutralised and is already the whole explanation.
155
157
  */
156
158
  export class ApprovalStopError extends Error {
157
- /** The command that ended the run, exactly as the agent proposed it. */
159
+ /**
160
+ * What ended the run, exactly as the agent proposed it: the command for a shell subject, and the
161
+ * registered tool name for a gated tool or MCP call. The field name predates the gate widening
162
+ * past the shell; {@link subjectParts} is what decides the word the MESSAGE calls it.
163
+ */
158
164
  command;
159
165
  /** The message's pieces, tagged with who wrote each — see {@link ApprovalStopPart}. */
160
166
  parts;
@@ -167,6 +173,21 @@ export class ApprovalStopError extends Error {
167
173
  this.name = new.target.name;
168
174
  }
169
175
  }
176
+ /**
177
+ * The **blunter** of the two recoveries a stop may name, and it is named second wherever both
178
+ * appear.
179
+ *
180
+ * §4.2 makes `approvals.allow` the supported way to run a specific command unattended: it is
181
+ * consulted before the rater and therefore before §4.6's deterministic preflight, so a command
182
+ * declared there never reaches a halt or an escalation at all. `bypass` also works and is far
183
+ * blunter — it turns off the rater, the prompts and the halt together, for every command, for the
184
+ * whole run. The ordering is the message: the answer first, the last resort after it, with what it
185
+ * costs stated rather than implied.
186
+ *
187
+ * One sentence, shared, so the two stops that offer it cannot come to describe it differently.
188
+ */
189
+ const BYPASS_LAST_RESORT = `Dropping to approvals "bypass" also works, but it turns off the rater, ` +
190
+ `the prompts and the halt for every command in the run.`;
170
191
  /**
171
192
  * §4.2 — an `attack` outcome: the command's own **structure** evidenced compromise (§4.1.1 —
172
193
  * credential targeting, privilege escalation, persistence, deception, obfuscation). Ends the agent
@@ -187,20 +208,19 @@ export class ApprovalStopError extends Error {
187
208
  export class AttackHaltError extends ApprovalStopError {
188
209
  /** The rater's explanation of what the command's structure showed. */
189
210
  reason;
190
- constructor(command, reason) {
211
+ constructor(command, reason, subject) {
191
212
  super([
192
213
  {
193
214
  kind: 'own',
194
215
  text: 'Run halted: the auto-rater rated this command as an attack, which ends the run.',
195
216
  },
196
- { kind: 'command', label: 'Command', text: command },
217
+ ...subjectParts(command, subject),
197
218
  { kind: 'value', label: 'Reason', text: reason },
198
219
  {
199
220
  kind: 'own',
200
221
  text: `This is not negotiable. If this command is legitimate and you need it to run, declare ` +
201
222
  `it in approvals.allow — that list is consulted before the auto-rater, so it never ` +
202
- `reaches a halt. Dropping to approvals "bypass" also works, but it turns off the rater, ` +
203
- `the prompts and the halt for every command in the run.`,
223
+ `reaches a halt. ${BYPASS_LAST_RESORT}`,
204
224
  },
205
225
  ], command);
206
226
  this.reason = reason;
@@ -230,10 +250,35 @@ export class NonInteractiveEscalationError extends ApprovalStopError {
230
250
  * the last command and give no hint that the agent had already been told twice what to fix.
231
251
  */
232
252
  negotiation;
233
- constructor(command, outcome, reason, escalatedBy, negotiation) {
253
+ /**
254
+ * [[EXT-106]] §4 — **the `approvals.allow` entry that would let THIS command run**, rendered in the
255
+ * object form a user writes in a config file, or `undefined` when none can soundly be derived.
256
+ *
257
+ * A refusal that stands has to say what would lift it, and a generic example leaves the reader to
258
+ * translate it — for a command the deterministic preflight floors, that translation is the entire
259
+ * remedy. `approvals.allow` is consulted before the rater and therefore before §4.6's preflight,
260
+ * so it is the one thing that lifts a floor the agent cannot argue past.
261
+ *
262
+ * **Derived by the caller, never here.** The caller holds what an entry has to be derived from —
263
+ * the normalized command for a shell subject (`shellApprovalEntryFor`), the tool's identity and
264
+ * host for a tool or MCP one (`toolGrantEntry`) — and it is the same derivation the escalation
265
+ * menu's *always approve* stores, so a message and a menu cannot promise different things about
266
+ * one call. `undefined` falls back to the general form, which is the right answer whenever a
267
+ * specific one would be wrong — an entry that is subtly too broad, or simply inert, is worse than
268
+ * none. That is what a call whose MCP server could not be attributed gets: `toolGrantEntry`
269
+ * returns `null` for it, because `server` cannot hold the unresolved sentinel.
270
+ *
271
+ * **It is untrusted text.** It embeds the command the model proposed, so it is carried as a
272
+ * labelled `value` part and neutralised with every other untrusted string; putting it in an `own`
273
+ * part would route model-authored bytes into the one part class a surface may paint raw.
274
+ */
275
+ allowEntry;
276
+ constructor(command, outcome, reason, escalatedBy, negotiation, allowEntry, subject) {
234
277
  const parts = [
278
+ // [[EXT-115]] — the lead sentence takes NO branch: it is already kind-neutral and true of a
279
+ // shell command, a tool call and an MCP call alike. Only the subject's own label moves.
235
280
  { kind: 'own', text: 'Approval required, but this session has no one to ask.' },
236
- { kind: 'command', label: 'Command', text: command },
281
+ ...subjectParts(command, subject),
237
282
  ];
238
283
  if (outcome)
239
284
  parts.push({ kind: 'value', label: 'Rating', text: outcome });
@@ -250,13 +295,30 @@ export class NonInteractiveEscalationError extends ApprovalStopError {
250
295
  `should run unattended.`,
251
296
  });
252
297
  }
298
+ else if (allowEntry) {
299
+ // [[EXT-106]] §4 — the same recovery `AttackHaltError` names, in the same order (the
300
+ // allow-list, then `bypass` as the blunter last resort), with the entry filled in for the
301
+ // command in hand. "Before the auto-rater" is stated as reaching further than the rater
302
+ // because that is the fact a floored command's reader needs: §4.6's preflight runs inside the
303
+ // rating decision, so an allow match is ahead of the floor as well as ahead of the model.
304
+ parts.push({
305
+ kind: 'own',
306
+ text: `Declare the commands this run is allowed to execute in approvals.allow — that list is ` +
307
+ `consulted before the auto-rater, ahead of its deterministic preflights, and never ` +
308
+ `escalates. For this command, add:`,
309
+ });
310
+ parts.push({ kind: 'value', label: 'approvals.allow entry', text: allowEntry });
311
+ parts.push({ kind: 'own', text: BYPASS_LAST_RESORT });
312
+ }
253
313
  else {
314
+ // [[EXT-115]] — the example is of the SUBJECT'S OWN KIND. `matchEntry` refuses a type
315
+ // mismatch outright, so the `shell` example this used to print unconditionally was, for a
316
+ // gated tool or MCP call, a line the reader could paste and watch match nothing.
254
317
  parts.push({
255
318
  kind: 'own',
256
319
  text: `Declare the commands this run is allowed to execute in approvals.allow — write each ` +
257
- `one as an explicit entry, for example { "type": "shell", "matcher": "exact", ` +
258
- `"pattern": "npm test" }. That list is consulted before the auto-rater and never ` +
259
- `escalates.`,
320
+ `one as an explicit entry, for example ${GENERAL_ALLOW_EXAMPLE[subject?.kind ?? 'shell']}` +
321
+ `. That list is consulted before the auto-rater and never escalates.`,
260
322
  });
261
323
  }
262
324
  super(parts, command);
@@ -264,6 +326,7 @@ export class NonInteractiveEscalationError extends ApprovalStopError {
264
326
  this.reason = reason;
265
327
  this.escalatedBy = escalatedBy;
266
328
  this.negotiation = negotiation;
329
+ this.allowEntry = allowEntry;
267
330
  }
268
331
  }
269
332
  //# sourceMappingURL=approvalStop.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"approvalStop.js","sourceRoot":"","sources":["../../../src/core/shell/approvalStop.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"approvalStop.js","sourceRoot":"","sources":["../../../src/core/shell/approvalStop.ts"],"names":[],"mappings":"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"}
@@ -19,9 +19,12 @@
19
19
  * asked for no gate. A false positive in this layer has no recovery at any rung; a miss still has
20
20
  * the layers above it. The floor stays narrow and accepts the misses.
21
21
  *
22
- * Two patterns are deliberately NOT anchored, because neither is a command-position construct: the
23
- * `>`-redirect-to-device arm (a redirection operator appears mid-command by definition) and the
24
- * fork-bomb literal (the string *is* the fork bomb).
22
+ * Two patterns carry no {@link CMD_POS} anchor, because neither is a command-position construct.
23
+ * The `>`-redirect-to-device arm is anchored differently instead see
24
+ * {@link OUTSIDE_SINGLE_QUOTES} because a redirection operator appears mid-command by definition,
25
+ * so the discrimination available to it is whether the `>` is QUOTED, not where it sits. The
26
+ * fork-bomb literal is anchored by nothing at all: the string *is* the fork bomb, so prose quoting
27
+ * it is refused, and that false positive is accepted rather than fixed.
25
28
  */
26
29
  export declare const HARDLINE_PATTERNS: ReadonlyArray<readonly [RegExp, string]>;
27
30
  /**
@@ -82,12 +85,31 @@ export interface HardlineMatch {
82
85
  * exfiltration test, which decides per pipeline rather than by one regex.
83
86
  */
84
87
  export declare const EXFILTRATION_ARM = "deterministic-exfiltration";
88
+ /** Options for {@link checkHardline}. */
89
+ export interface HardlineOptions {
90
+ /**
91
+ * The host platform to decide the {@link WINDOWS_HARDLINE_PATTERNS} arm against, in
92
+ * `process.platform`'s vocabulary. Defaults to `process.platform`, which is what every
93
+ * production caller wants.
94
+ *
95
+ * **It exists so the Windows arm is testable from any host, and that is not a convenience.**
96
+ * Without it the arm could only ever be exercised on a Windows CI cell: nobody could develop it
97
+ * on this repo's usual machines, and a regression would be invisible on four of the unit
98
+ * matrix's five cells — the majority of the signal, including every local run before a merge.
99
+ * With it, both arms run everywhere, and the Windows cells additionally prove that the real
100
+ * `process.platform` branch selects the arm the injected value stands in for.
101
+ */
102
+ platform?: NodeJS.Platform;
103
+ }
85
104
  /**
86
105
  * Check a raw command against the hardline blocklist. Normalizes first so
87
106
  * obfuscated variants are caught. Returns the match (with a description) when the
88
107
  * command is catastrophic, or `null` when it is allowed to proceed.
108
+ *
109
+ * The shared patterns are tried on every host. The win32 arm is tried in addition on Windows —
110
+ * never instead of, because Git Bash, MSYS and WSL run the POSIX shapes on a Windows box too.
89
111
  */
90
- export declare function checkHardline(command: string): HardlineMatch | null;
112
+ export declare function checkHardline(command: string, options?: HardlineOptions): HardlineMatch | null;
91
113
  /**
92
114
  * The refusal a floor match produces, shared by both call sites (§8, §4.2).
93
115
  *