@gaunt-sloth/core 2.0.0-alpha.8 → 2.0.0-beta.0

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 (275) hide show
  1. package/.gsloth.review.md +2 -0
  2. package/README.md +71 -20
  3. package/dist/config/colour.d.ts +38 -0
  4. package/dist/config/colour.js +36 -0
  5. package/dist/config/colour.js.map +1 -0
  6. package/dist/config/configDiscovery.d.ts +79 -0
  7. package/dist/config/configDiscovery.js +80 -0
  8. package/dist/config/configDiscovery.js.map +1 -0
  9. package/dist/config/defaults.d.ts +20 -20
  10. package/dist/config/defaults.js +10 -8
  11. package/dist/config/defaults.js.map +1 -1
  12. package/dist/config/filesystem-tools.d.ts +41 -0
  13. package/dist/config/filesystem-tools.js +56 -0
  14. package/dist/config/filesystem-tools.js.map +1 -0
  15. package/dist/config/loader.d.ts +171 -19
  16. package/dist/config/loader.js +1054 -144
  17. package/dist/config/loader.js.map +1 -1
  18. package/dist/config/mouse.d.ts +50 -0
  19. package/dist/config/mouse.js +44 -0
  20. package/dist/config/mouse.js.map +1 -0
  21. package/dist/config/profiles.d.ts +68 -0
  22. package/dist/config/profiles.js +93 -0
  23. package/dist/config/profiles.js.map +1 -0
  24. package/dist/config/providerKeys.d.ts +69 -0
  25. package/dist/config/providerKeys.js +69 -0
  26. package/dist/config/providerKeys.js.map +1 -0
  27. package/dist/config/schema.d.ts +2671 -138
  28. package/dist/config/schema.js +1361 -85
  29. package/dist/config/schema.js.map +1 -1
  30. package/dist/config/shell-policy.d.ts +899 -111
  31. package/dist/config/shell-policy.js +800 -70
  32. package/dist/config/shell-policy.js.map +1 -1
  33. package/dist/config/tool-descriptions.d.ts +211 -0
  34. package/dist/config/tool-descriptions.js +272 -0
  35. package/dist/config/tool-descriptions.js.map +1 -0
  36. package/dist/config/types.d.ts +352 -41
  37. package/dist/config/types.js +1 -0
  38. package/dist/config/types.js.map +1 -1
  39. package/dist/config.d.ts +35 -1
  40. package/dist/config.js +16 -1
  41. package/dist/config.js.map +1 -1
  42. package/dist/constants.d.ts +28 -1
  43. package/dist/constants.js +28 -1
  44. package/dist/constants.js.map +1 -1
  45. package/dist/core/GthAbstractAgent.d.ts +166 -11
  46. package/dist/core/GthAbstractAgent.js +484 -45
  47. package/dist/core/GthAbstractAgent.js.map +1 -1
  48. package/dist/core/GthAgentRunner.d.ts +543 -57
  49. package/dist/core/GthAgentRunner.js +1494 -140
  50. package/dist/core/GthAgentRunner.js.map +1 -1
  51. package/dist/core/GthLangChainAgent.d.ts +117 -2
  52. package/dist/core/GthLangChainAgent.js +602 -29
  53. package/dist/core/GthLangChainAgent.js.map +1 -1
  54. package/dist/core/approvals/annotations.d.ts +122 -0
  55. package/dist/core/approvals/annotations.js +137 -0
  56. package/dist/core/approvals/annotations.js.map +1 -0
  57. package/dist/core/approvals/grants.d.ts +216 -0
  58. package/dist/core/approvals/grants.js +469 -0
  59. package/dist/core/approvals/grants.js.map +1 -0
  60. package/dist/core/approvals/matcher.d.ts +202 -0
  61. package/dist/core/approvals/matcher.js +267 -0
  62. package/dist/core/approvals/matcher.js.map +1 -0
  63. package/dist/core/approvals/mcpSubjects.d.ts +40 -0
  64. package/dist/core/approvals/mcpSubjects.js +99 -0
  65. package/dist/core/approvals/mcpSubjects.js.map +1 -0
  66. package/dist/core/approvals/promptHeader.d.ts +28 -0
  67. package/dist/core/approvals/promptHeader.js +62 -0
  68. package/dist/core/approvals/promptHeader.js.map +1 -0
  69. package/dist/core/approvals/toolAnnotationSources.d.ts +105 -0
  70. package/dist/core/approvals/toolAnnotationSources.js +277 -0
  71. package/dist/core/approvals/toolAnnotationSources.js.map +1 -0
  72. package/dist/core/approvals/toolHost.d.ts +46 -0
  73. package/dist/core/approvals/toolHost.js +108 -0
  74. package/dist/core/approvals/toolHost.js.map +1 -0
  75. package/dist/core/debugCapture.d.ts +74 -0
  76. package/dist/core/debugCapture.js +100 -0
  77. package/dist/core/debugCapture.js.map +1 -0
  78. package/dist/core/gthLeanAgentFactory.d.ts +4 -4
  79. package/dist/core/gthLeanAgentFactory.js +4 -4
  80. package/dist/core/launchBanner.d.ts +127 -0
  81. package/dist/core/launchBanner.js +414 -0
  82. package/dist/core/launchBanner.js.map +1 -0
  83. package/dist/core/modelLabel.d.ts +19 -0
  84. package/dist/core/modelLabel.js +26 -0
  85. package/dist/core/modelLabel.js.map +1 -0
  86. package/dist/core/plainToolIndication.d.ts +15 -0
  87. package/dist/core/plainToolIndication.js +174 -0
  88. package/dist/core/plainToolIndication.js.map +1 -0
  89. package/dist/core/reasoningBlocks.d.ts +65 -0
  90. package/dist/core/reasoningBlocks.js +103 -0
  91. package/dist/core/reasoningBlocks.js.map +1 -0
  92. package/dist/core/refusal.d.ts +53 -0
  93. package/dist/core/refusal.js +133 -0
  94. package/dist/core/refusal.js.map +1 -0
  95. package/dist/core/runHeader.d.ts +38 -0
  96. package/dist/core/runHeader.js +42 -0
  97. package/dist/core/runHeader.js.map +1 -0
  98. package/dist/core/runStats.d.ts +14 -3
  99. package/dist/core/runStats.js +48 -3
  100. package/dist/core/runStats.js.map +1 -1
  101. package/dist/core/shell/ShellCommandFailedError.d.ts +3 -4
  102. package/dist/core/shell/ShellCommandFailedError.js +3 -4
  103. package/dist/core/shell/ShellCommandFailedError.js.map +1 -1
  104. package/dist/core/shell/abstention.d.ts +88 -0
  105. package/dist/core/shell/abstention.js +184 -0
  106. package/dist/core/shell/abstention.js.map +1 -0
  107. package/dist/core/shell/approvalCapture.d.ts +271 -0
  108. package/dist/core/shell/approvalCapture.js +108 -0
  109. package/dist/core/shell/approvalCapture.js.map +1 -0
  110. package/dist/core/shell/approvalStop.d.ts +123 -0
  111. package/dist/core/shell/approvalStop.js +269 -0
  112. package/dist/core/shell/approvalStop.js.map +1 -0
  113. package/dist/core/shell/arity.d.ts +6 -0
  114. package/dist/core/shell/arity.js +20 -6
  115. package/dist/core/shell/arity.js.map +1 -1
  116. package/dist/core/shell/denylist.d.ts +11 -0
  117. package/dist/core/shell/denylist.js +37 -0
  118. package/dist/core/shell/denylist.js.map +1 -0
  119. package/dist/core/shell/escalationSeverity.d.ts +141 -0
  120. package/dist/core/shell/escalationSeverity.js +89 -0
  121. package/dist/core/shell/escalationSeverity.js.map +1 -0
  122. package/dist/core/shell/framing.d.ts +190 -0
  123. package/dist/core/shell/framing.js +633 -0
  124. package/dist/core/shell/framing.js.map +1 -0
  125. package/dist/core/shell/hardline.d.ts +103 -0
  126. package/dist/core/shell/hardline.js +780 -0
  127. package/dist/core/shell/hardline.js.map +1 -0
  128. package/dist/core/shell/negotiation.d.ts +328 -0
  129. package/dist/core/shell/negotiation.js +488 -0
  130. package/dist/core/shell/negotiation.js.map +1 -0
  131. package/dist/core/shell/normalize.d.ts +44 -4
  132. package/dist/core/shell/normalize.js +61 -7
  133. package/dist/core/shell/normalize.js.map +1 -1
  134. package/dist/core/shell/openWorld.d.ts +263 -0
  135. package/dist/core/shell/openWorld.js +1188 -0
  136. package/dist/core/shell/openWorld.js.map +1 -0
  137. package/dist/core/shell/rater.d.ts +873 -0
  138. package/dist/core/shell/rater.js +1454 -0
  139. package/dist/core/shell/rater.js.map +1 -0
  140. package/dist/core/shell/raterModel.d.ts +41 -0
  141. package/dist/core/shell/raterModel.js +51 -0
  142. package/dist/core/shell/raterModel.js.map +1 -0
  143. package/dist/core/shell/raterVocabulary.d.ts +121 -0
  144. package/dist/core/shell/raterVocabulary.js +116 -0
  145. package/dist/core/shell/raterVocabulary.js.map +1 -0
  146. package/dist/core/shell/rejection.d.ts +69 -0
  147. package/dist/core/shell/rejection.js +38 -0
  148. package/dist/core/shell/rejection.js.map +1 -0
  149. package/dist/core/toolCallRepair/grammar.d.ts +41 -0
  150. package/dist/core/toolCallRepair/grammar.js +116 -0
  151. package/dist/core/toolCallRepair/grammar.js.map +1 -0
  152. package/dist/core/toolCallRepair/index.d.ts +2 -0
  153. package/dist/core/toolCallRepair/index.js +7 -0
  154. package/dist/core/toolCallRepair/index.js.map +1 -0
  155. package/dist/core/toolCallRepair/payload.d.ts +36 -0
  156. package/dist/core/toolCallRepair/payload.js +341 -0
  157. package/dist/core/toolCallRepair/payload.js.map +1 -0
  158. package/dist/core/toolCallRepair/promote.d.ts +45 -0
  159. package/dist/core/toolCallRepair/promote.js +90 -0
  160. package/dist/core/toolCallRepair/promote.js.map +1 -0
  161. package/dist/core/toolDisplay.d.ts +123 -0
  162. package/dist/core/toolDisplay.js +451 -0
  163. package/dist/core/toolDisplay.js.map +1 -0
  164. package/dist/core/toolOutputChannel.d.ts +95 -0
  165. package/dist/core/toolOutputChannel.js +165 -0
  166. package/dist/core/toolOutputChannel.js.map +1 -0
  167. package/dist/core/types.d.ts +349 -16
  168. package/dist/core/types.js.map +1 -1
  169. package/dist/history/historyFormat.d.ts +12 -3
  170. package/dist/history/historyFormat.js +50 -8
  171. package/dist/history/historyFormat.js.map +1 -1
  172. package/dist/history/historyStore.d.ts +77 -0
  173. package/dist/history/historyStore.js +173 -6
  174. package/dist/history/historyStore.js.map +1 -1
  175. package/dist/history/recordSession.d.ts +10 -1
  176. package/dist/history/recordSession.js +27 -0
  177. package/dist/history/recordSession.js.map +1 -1
  178. package/dist/providers/anthropic.js +12 -0
  179. package/dist/providers/anthropic.js.map +1 -1
  180. package/dist/providers/configurationPassthrough.d.ts +107 -0
  181. package/dist/providers/configurationPassthrough.js +148 -0
  182. package/dist/providers/configurationPassthrough.js.map +1 -0
  183. package/dist/providers/geminiSchemaSanitizer.d.ts +55 -0
  184. package/dist/providers/geminiSchemaSanitizer.js +347 -0
  185. package/dist/providers/geminiSchemaSanitizer.js.map +1 -0
  186. package/dist/providers/geminiThinking.d.ts +60 -0
  187. package/dist/providers/geminiThinking.js +92 -0
  188. package/dist/providers/geminiThinking.js.map +1 -0
  189. package/dist/providers/google-genai.js +18 -1
  190. package/dist/providers/google-genai.js.map +1 -1
  191. package/dist/providers/groq.js +12 -0
  192. package/dist/providers/groq.js.map +1 -1
  193. package/dist/providers/huggingface.d.ts +25 -0
  194. package/dist/providers/huggingface.js +69 -0
  195. package/dist/providers/huggingface.js.map +1 -0
  196. package/dist/providers/modelCatalog.d.ts +109 -0
  197. package/dist/providers/modelCatalog.js +245 -0
  198. package/dist/providers/modelCatalog.js.map +1 -0
  199. package/dist/providers/modelDiscovery.d.ts +99 -5
  200. package/dist/providers/modelDiscovery.js +191 -35
  201. package/dist/providers/modelDiscovery.js.map +1 -1
  202. package/dist/providers/ollama.d.ts +18 -4
  203. package/dist/providers/ollama.js +67 -37
  204. package/dist/providers/ollama.js.map +1 -1
  205. package/dist/providers/openai.js +34 -0
  206. package/dist/providers/openai.js.map +1 -1
  207. package/dist/providers/openrouter.d.ts +26 -4
  208. package/dist/providers/openrouter.js +83 -26
  209. package/dist/providers/openrouter.js.map +1 -1
  210. package/dist/providers/vertexai.js +19 -1
  211. package/dist/providers/vertexai.js.map +1 -1
  212. package/dist/providers/xai.js +20 -0
  213. package/dist/providers/xai.js.map +1 -1
  214. package/dist/runtime/askStructured.d.ts +105 -0
  215. package/dist/runtime/askStructured.js +120 -0
  216. package/dist/runtime/askStructured.js.map +1 -0
  217. package/dist/runtime/conversation.d.ts +64 -0
  218. package/dist/runtime/conversation.js +171 -0
  219. package/dist/runtime/conversation.js.map +1 -0
  220. package/dist/runtime/singleShot.d.ts +37 -6
  221. package/dist/runtime/singleShot.js +113 -67
  222. package/dist/runtime/singleShot.js.map +1 -1
  223. package/dist/runtime/structuredOutput.d.ts +104 -0
  224. package/dist/runtime/structuredOutput.js +393 -0
  225. package/dist/runtime/structuredOutput.js.map +1 -0
  226. package/dist/utils/ProgressIndicator.d.ts +21 -0
  227. package/dist/utils/ProgressIndicator.js +30 -3
  228. package/dist/utils/ProgressIndicator.js.map +1 -1
  229. package/dist/utils/aiignoreUtils.js.map +1 -1
  230. package/dist/utils/binaryOutputUtils.js.map +1 -1
  231. package/dist/utils/consoleUtils.d.ts +95 -0
  232. package/dist/utils/consoleUtils.js +112 -2
  233. package/dist/utils/consoleUtils.js.map +1 -1
  234. package/dist/utils/crashHandler.d.ts +87 -0
  235. package/dist/utils/crashHandler.js +128 -0
  236. package/dist/utils/crashHandler.js.map +1 -0
  237. package/dist/utils/debugDump.d.ts +134 -0
  238. package/dist/utils/debugDump.js +381 -0
  239. package/dist/utils/debugDump.js.map +1 -0
  240. package/dist/utils/debugUtils.d.ts +13 -4
  241. package/dist/utils/debugUtils.js +36 -13
  242. package/dist/utils/debugUtils.js.map +1 -1
  243. package/dist/utils/displayWidth.d.ts +53 -0
  244. package/dist/utils/displayWidth.js +195 -0
  245. package/dist/utils/displayWidth.js.map +1 -0
  246. package/dist/utils/fileUtils.js.map +1 -1
  247. package/dist/utils/globalConfigUtils.d.ts +14 -2
  248. package/dist/utils/globalConfigUtils.js +22 -4
  249. package/dist/utils/globalConfigUtils.js.map +1 -1
  250. package/dist/utils/llmUtils.d.ts +39 -8
  251. package/dist/utils/llmUtils.js +76 -8
  252. package/dist/utils/llmUtils.js.map +1 -1
  253. package/dist/utils/redactSecrets.d.ts +63 -0
  254. package/dist/utils/redactSecrets.js +286 -0
  255. package/dist/utils/redactSecrets.js.map +1 -0
  256. package/dist/utils/systemPromptNotes.d.ts +222 -0
  257. package/dist/utils/systemPromptNotes.js +338 -0
  258. package/dist/utils/systemPromptNotes.js.map +1 -0
  259. package/dist/utils/systemUtils.d.ts +18 -1
  260. package/dist/utils/systemUtils.js +38 -3
  261. package/dist/utils/systemUtils.js.map +1 -1
  262. package/dist/utils/toolMatching.d.ts +30 -0
  263. package/dist/utils/toolMatching.js +44 -0
  264. package/dist/utils/toolMatching.js.map +1 -0
  265. package/dist/utils/untrustedText.d.ts +86 -0
  266. package/dist/utils/untrustedText.js +101 -0
  267. package/dist/utils/untrustedText.js.map +1 -0
  268. package/package.json +21 -7
  269. package/schema/gsloth-config.schema.json +1921 -326
  270. package/dist/core/shell/allowlist.d.ts +0 -75
  271. package/dist/core/shell/allowlist.js +0 -187
  272. package/dist/core/shell/allowlist.js.map +0 -1
  273. package/dist/core/shell/judge.d.ts +0 -161
  274. package/dist/core/shell/judge.js +0 -261
  275. package/dist/core/shell/judge.js.map +0 -1
@@ -0,0 +1,873 @@
1
+ /**
2
+ * @module core/shell/rater
3
+ *
4
+ * CFG-27 (CFG-26 rework) — the **auto-rater**: the LLM that rates a pending `run_shell_command`
5
+ * before it executes, sitting *in front of* the human approval prompt (EXT-9). It is consulted at
6
+ * exactly two of the five rungs — `assisted` and `auto` (see `APPROVAL_RUNGS`); `manual`,
7
+ * `write` and `bypass` are fully deterministic and never pay for a model call.
8
+ *
9
+ * NOTE ON THE NAME: "judge" is reserved for the **eval grader** (`gth eval --judge <profile>`,
10
+ * `@gaunt-sloth/batch`) — a different concept. This module is the approvals rater.
11
+ *
12
+ * Validated prior art (both place the rater in front of the human prompt as an auto-approve
13
+ * fatigue-reducer): openclaw `exec-auto-reviewer.ts` and hermes-agent `approval.py` "smart" mode.
14
+ *
15
+ * Two hardening guarantees are baked in here:
16
+ *
17
+ * 1. **Prompt-injection defense.** The command is attacker-controlled text. It is normalized
18
+ * (reusing {@link normalizeCommand} + home-path folding) and embedded inside an XML
19
+ * `<command_to_evaluate>` tag, behind a preamble that states the tagged text is UNTRUSTED
20
+ * DATA to be analyzed, never instructions to follow. See {@link buildRaterPrompt}. §5.1's
21
+ * negotiation context ([[EXT-29]]) is admitted on exactly those terms — the justification, the
22
+ * prior rounds and the last user messages are each fenced in the user message, and
23
+ * {@link RATER_NEGOTIATION_GUIDANCE} extends the untrusted-data contract to their tags. Nothing
24
+ * else from the conversation is admitted at any round: no tool output, no file contents, no
25
+ * fetched pages (§4.3).
26
+ * 2. **Fail-closed on error.** If the LLM call throws, times out, or returns unparseable
27
+ * output, the verdict returned NEVER auto-approves — it is `destructive` with an honest
28
+ * "could not assess" reason. A rater failure can never silently green-light a command.
29
+ * See {@link FAIL_CLOSED_VERDICT}.
30
+ *
31
+ * A command whose target the gate cannot statically resolve is RATED like any other ([[EXT-81]]),
32
+ * carrying a neutral note about the shape the parser saw ({@link buildParserPreflightNote}). It is
33
+ * not an action, not a floor, and not a reason to skip the call.
34
+ *
35
+ * Mirrors the QA-3 rating substrate (`packages/review/src/middleware/reviewRateMiddleware.ts`):
36
+ * structured-output evaluation over `config.llm`, wrapped in try/catch.
37
+ */
38
+ import type { BaseChatModel } from '@langchain/core/language_models/chat_models';
39
+ import * as z from 'zod';
40
+ import type { ApprovalRung, GrantedToolSummary, GthConfig } from '#src/config.js';
41
+ import type { EffectiveToolAnnotations } from '#src/core/approvals/matcher.js';
42
+ import { type RaterCallCapture } from '#src/core/shell/approvalCapture.js';
43
+ import type { PreflightFloorKind, RaterAction, RaterOutcome } from '#src/core/shell/raterVocabulary.js';
44
+ /**
45
+ * The gate's closed vocabularies are defined in {@link ./raterVocabulary.js} — a leaf module with no
46
+ * imports, so a checker can read the words without loading this file's model layer. They are
47
+ * re-exported here, where their meaning lives, so a caller needs only one import either way.
48
+ */
49
+ export { PREFLIGHT_FLOOR_KINDS, RATER_ACTIONS, RATER_OUTCOMES, } from '#src/core/shell/raterVocabulary.js';
50
+ export type { PreflightFloorKind, RaterAction, RaterOutcome, } from '#src/core/shell/raterVocabulary.js';
51
+ /**
52
+ * Structured verdict the rater model must return: one outcome plus one short sentence. There is
53
+ * deliberately nothing else — no severity number, no booleans to recombine into a compound
54
+ * condition. The consequence is a property of the rung, not of a knob.
55
+ *
56
+ * **The schema is written plainly, as the verdict the rater's CALLERS want.** `suggestedTool` is a
57
+ * plain `.optional()` and the parsed verdict's `suggestedTool` is `string | undefined`. What a
58
+ * strict `json_schema` provider has to be sent instead — the key required and its type nullable —
59
+ * and the `null` that then comes back are entirely the business of
60
+ * {@link structuredOutputBoundary}, which {@link rateShellCommand} runs both halves of the call
61
+ * through. Nothing about the wire belongs in this object; putting it here is what made the schema we
62
+ * send contradict the schema we validate with.
63
+ */
64
+ export declare const ShellSafetyVerdictSchema: z.ZodObject<{
65
+ outcome: z.ZodEnum<{
66
+ attack: "attack";
67
+ catastrophic: "catastrophic";
68
+ destructive: "destructive";
69
+ safe: "safe";
70
+ }>;
71
+ reason: z.ZodString;
72
+ suggestedTool: z.ZodOptional<z.ZodString>;
73
+ }, z.core.$strip>;
74
+ /**
75
+ * The rater's structured verdict on a single shell command. `suggestedTool` is `string | undefined`
76
+ * and never `null` — the boundary the rating call goes through collapses a `null` to the key being
77
+ * absent before any consumer sees it, so "no suggestion" has exactly one spelling.
78
+ */
79
+ export type ShellSafetyVerdict = z.infer<typeof ShellSafetyVerdictSchema>;
80
+ /**
81
+ * The honest reason text used whenever the outcome was NOT assessed by the rater — a rater failure
82
+ * ({@link FAIL_CLOSED_VERDICT}) or the script-env-leak preflight. Spec rule (§4.1):
83
+ * *uncertainty is not an outcome*, so it maps to `destructive` while SAYING it could not be
84
+ * assessed rather than pretending the command was found harmful.
85
+ */
86
+ export declare const COULD_NOT_ASSESS_PREFIX = "Could not assess this command";
87
+ /**
88
+ * EXT-61 (§4.6) — the reason text prefix for the **open-world** preflight, and deliberately NOT
89
+ * {@link COULD_NOT_ASSESS_PREFIX}: this preflight *did* assess the command and found something
90
+ * specific. Saying "could not assess" here would be a lie, and the named host is the whole value of
91
+ * the escalation — "it downloads something, confirm" and "it fetches from registry.npmjs.ag" are
92
+ * different warnings, and only the second is worth reading.
93
+ */
94
+ export declare const NAMES_A_HOST_PREFIX = "This command names a host";
95
+ /**
96
+ * EXT-70 (§4.7.2, §4.7.3) — the reason text prefix for the **tool** arm of the open-world floor: a
97
+ * call whose EFFECTIVE `openWorldHint` is true. Like {@link NAMES_A_HOST_PREFIX} and unlike
98
+ * {@link COULD_NOT_ASSESS_PREFIX}, it states something the gate positively established.
99
+ *
100
+ * It names the hint rather than paraphrasing it, because the hint is also the thing the user can
101
+ * act on: trust it from that server (`approvals.mcp`), or declare the call in `approvals.allow`.
102
+ */
103
+ export declare const REACHES_OPEN_WORLD_PREFIX = "This tool reaches the open world";
104
+ /**
105
+ * The closing clause **shared by every open-world floor reason**, shell and tool alike.
106
+ *
107
+ * It is a constant rather than two copies of a sentence, and that is load-bearing rather than
108
+ * tidiness: the two arms are one rule (§4.6 for a shell fetch, §4.7.3 for the same fetch reached
109
+ * through a tool), so a reader who has seen one escalation reads the other as the same decision.
110
+ * It is also the one part of the floor a **second implementation** cannot reproduce by accident —
111
+ * an inline `{ outcome: 'destructive', reason: … }` written at some future call site would say
112
+ * something else, and the assertions that compare a floored reason against the exported reason
113
+ * builders are what turn that into a red test rather than a slow divergence.
114
+ */
115
+ export declare const NEVER_AUTO_APPROVED_CLAUSE = "so it is never auto-approved.";
116
+ /**
117
+ * The verdict returned whenever the rater cannot produce a trustworthy answer (LLM throws,
118
+ * times out, or returns unparseable output). Fail-closed: `destructive`, never auto-approved,
119
+ * and never `attack` or `catastrophic` either — a failure to assess must not manufacture a
120
+ * run-halting outcome any more than it may manufacture an approval.
121
+ */
122
+ export declare const FAIL_CLOSED_VERDICT: ShellSafetyVerdict;
123
+ /**
124
+ * EXT-66 — why the gate failed closed. **Every one of these is a fact about the GATE, not about the
125
+ * command**, which is the whole point of naming them: {@link FAIL_CLOSED_VERDICT} collapsed four
126
+ * different gate failures into one verdict that reads, downstream and in every eval report, exactly
127
+ * like a model that looked at the command and judged it `destructive`.
128
+ *
129
+ * That is not hypothetical. The EXT-62 anchoring sweep read its first `gemma4:12b` column as full
130
+ * coverage of the interpreter-wrapper misses; 3 of those escalations were the gate defaulting after
131
+ * 30 seconds, not the model judging, and only the reason string distinguished them.
132
+ *
133
+ * The outcome stays `destructive` for all four — failing closed is right and stays right, and a
134
+ * failure to assess must not manufacture `catastrophic`/`attack` any more than it may manufacture
135
+ * an approval. What changes is that the reason now says which failure happened, which is what lets a
136
+ * caller tell the gate defaulting from the model judging ({@link isFailClosed}).
137
+ */
138
+ export type FailClosedCause = 'no-model' | 'timeout' | 'unparseable' | 'threw';
139
+ /**
140
+ * The fail-closed verdict for a specific {@link FailClosedCause}. Keeps
141
+ * {@link COULD_NOT_ASSESS_PREFIX} — the statement "this was not assessed" is still true and is what
142
+ * downstream keys on — and appends what actually went wrong.
143
+ *
144
+ * The timeout arm names the budget, because "the rater timed out" is not actionable and "the rater
145
+ * did not answer within 30000ms" points straight at `approvals.raterTimeoutMs`.
146
+ */
147
+ export declare function failClosedVerdict(cause: FailClosedCause, timeoutMs?: number): ShellSafetyVerdict;
148
+ /**
149
+ * Whether a verdict is one this gate produced because it could not obtain a rating, as opposed to
150
+ * one a rater actually returned. Keys on {@link COULD_NOT_ASSESS_PREFIX} — the same
151
+ * reason-prefix-as-identity idiom {@link NAMES_A_HOST_PREFIX} already uses — so it covers the
152
+ * legacy {@link FAIL_CLOSED_VERDICT} as well as every {@link failClosedVerdict} cause.
153
+ *
154
+ * Exported so a caller can tell "the gate defaulted" from "the model judged" without string
155
+ * matching at the call site, which is the distinction an eval column and a session summary both
156
+ * need and neither could previously make.
157
+ */
158
+ export declare function isFailClosed(verdict: ShellSafetyVerdict | undefined): boolean;
159
+ /** Whether a verdict is specifically the {@link FailClosedCause} `timeout` arm. */
160
+ export declare function isRaterTimeout(verdict: ShellSafetyVerdict | undefined): boolean;
161
+ /**
162
+ * Default wall-clock budget (ms) for the rater LLM call. Kept low so a slow/hung rater can't
163
+ * wedge the approval flow — on timeout we fail closed. Mirrors openclaw's low exec-reviewer
164
+ * timeout minimum.
165
+ *
166
+ * **EXT-66 — this is a HOSTED-model number, and it is now a default rather than the only value.**
167
+ * `claude-haiku-4-5` and `gemini-3.6-flash` answered a 23-case corpus well inside it, 0 fail-closed.
168
+ * `gemma4:12b` over a local GPU took 6.0s–114.7s on the same corpus, and the harder the command the
169
+ * longer it thought — so the fixed limit preferentially clipped exactly the commands that most
170
+ * needed rating (3 of 18 calls in one run, 9 of 17 in the next; all of them returned real verdicts
171
+ * at 120s, including a correct `catastrophic` returned 85 seconds after the gate had given up).
172
+ * Override with `approvals.raterTimeoutMs`.
173
+ */
174
+ export declare const RATER_DEFAULT_TIMEOUT_MS = 30000;
175
+ /**
176
+ * The invariant half of the rater's system prompt: the role and the untrusted-input contract (the
177
+ * tagged command is DATA, not instructions). Patterned after openclaw's
178
+ * `DEFAULT_EXEC_REVIEWER_SYSTEM_PROMPT` and hermes' untrusted-input framing. Exported so tests can
179
+ * assert the injection-defense preamble survives.
180
+ */
181
+ export declare const RATER_SYSTEM_PREAMBLE: string;
182
+ /**
183
+ * The `catastrophic` half of the rating prompt (§4.1). The whole outcome is one question — *can
184
+ * this be undone from inside the session?* — and the prompt asks it that way rather than offering
185
+ * a severity scale, because a rater given "how bad is it" instead of "is it recoverable" produces a
186
+ * ranking rather than the classification §4.2 acts on.
187
+ *
188
+ * The examples deliberately include the members the §8 deterministic floor **cannot** reach
189
+ * (`DROP DATABASE`, `terraform destroy -auto-approve`, `kubectl delete namespace production`):
190
+ * §11.1 records that only a model recognises that class, which is the reason the outcome exists.
191
+ * The counter-examples matter just as much — `git reset --hard` is reflog-recoverable and therefore
192
+ * `destructive`, and that is precisely what lets §5's negotiation open on it at all (§11.1a).
193
+ */
194
+ export declare const RATER_CATASTROPHIC_GUIDANCE: string;
195
+ /**
196
+ * The `attack` half of the rating prompt, kept as its own export because **§4.1.1 requires the
197
+ * prompt to carry the structural test in its own words, not merely the one-line table
198
+ * definition.**
199
+ *
200
+ * The reason is worth restating where the prompt author will read it: `attack` is the only outcome
201
+ * that halts the run, the halt is not negotiable, and from `assisted` — the default rung for
202
+ * everyone — a non-interactive run has no key at all and the only ways through are an allow-list
203
+ * entry or dropping to `bypass`, i.e. one step from the default gate to no gate. An outcome that
204
+ * expensive MUST NOT fire on ordinary work, so the test is **what the command's own structure
205
+ * shows, not who it talks to**.
206
+ *
207
+ * Two clauses here are measurements rather than opinions, and neither may be quietly relaxed:
208
+ *
209
+ * - **Origin trust is not the rater's job** (§4.1.1's measured note). A cheap model rated
210
+ * `curl https://pypi.org.packages-cdn.io/simple/ -o index.html` as `safe` while *naming the
211
+ * deception in its own reasoning*, and split identically-shaped commands at random. A property
212
+ * that holds only on the good model is not a property, and `assisted` is the default for people
213
+ * pointed at small local models. `curl … | <shell>` is therefore `destructive` whatever the host.
214
+ * - **Clause (1) is narrowed** (§11.1b, 2026-07-28). A secret handed to a tool that is doing some
215
+ * other job is not credential targeting; a secret exposed for its own sake is. Read literally,
216
+ * the un-narrowed clause halted the run on `python deploy.py --key $AWS_SECRET_ACCESS_KEY` — one
217
+ * of the most ordinary shapes in deployment work, from a section that opens by saying an outcome
218
+ * this expensive must not fire on ordinary work. **Nothing is given up**: the deterministic
219
+ * {@link hasScriptEnvLeakRisk} preflight fires on exactly that shape, annotates this prompt, and
220
+ * forces the fail-closed `destructive` path in {@link mapVerdictToAction} — so a secret in an
221
+ * argument cannot reach `safe` however the rater rates it. The change trades a halt for a prompt
222
+ * on commands that were never going to auto-approve.
223
+ */
224
+ export declare const RATER_ATTACK_GUIDANCE: string;
225
+ /**
226
+ * EXT-61 (spec §4.6.1) — **deception becomes a REPORT and an upgrade, never a gate.**
227
+ *
228
+ * This is the rater-side half of the open-world preflight, and it exists because the preflight
229
+ * changed what the rater's hostname judgement is *for*. §4.6 floors every command that names a host
230
+ * at `destructive` before the rater is called, so that judgement no longer stands between a
231
+ * typosquat and execution. Its job is now to *tell the user what it sees*.
232
+ *
233
+ * **The error costs invert, and that is the point.** A missed deception costs a warning rather than
234
+ * a bypass; a wrongly-suspected one costs a sentence rather than a halted run. That is the correct
235
+ * place to put a judgement neither models nor humans perform reliably — §4.1.1's measurement is a
236
+ * cheap model rating a lookalike host `safe` *while naming the deception in its own reasoning*, and
237
+ * a working developer who did not discriminate hostnames in either direction.
238
+ *
239
+ * Both halves are normative and neither may be softened into the other:
240
+ *
241
+ * - **(1) Always report it**, naming the mechanism, **even when the outcome stays `destructive`**.
242
+ * The "even when" is the whole clause; a rater that only names a typosquat when it is halting the
243
+ * run reports nothing on the commands this preflight was built for, since they all land on
244
+ * `destructive`.
245
+ * - **(2) Upgrade to `attack` only when the deception is CLEAR**, resolving uncertainty *downward*
246
+ * to `destructive` with the doubt stated. §12.1 is the reason: a halt that fires is already more
247
+ * likely wrong than right, so it must be spent only where the structure is unambiguous.
248
+ *
249
+ * CFG-28 deliberately left (2) out of the prompt, on the reasoning that it presumes the §4.6 floor.
250
+ * The scoping call was right and the reasoning was not: at both rated rungs a `destructive` outcome
251
+ * escalates anyway, so the clause would have cost nothing had it landed early. **The floor is what
252
+ * makes the `destructive` half of §4.6.1 meaningful, not what gates the upgrade.**
253
+ */
254
+ export declare const RATER_DECEPTION_GUIDANCE: string;
255
+ /**
256
+ * [[EXT-29]] (spec §5.1) — how to WEIGH the negotiation context, added to the SYSTEM prompt for
257
+ * exactly the ratings that carry one ({@link buildNegotiationContextBlock}).
258
+ *
259
+ * **It is conditional on the CONTEXT, and that is the design rather than an optimisation.** Round 1
260
+ * is deliberately context-free (§5.1): there is no justification to weigh and no transcript to
261
+ * reason from, so a prompt explaining how a justification may lower an outcome has nothing to
262
+ * govern and everything to prime. Tying it to the block means the two can never disagree — the
263
+ * rules about weighing the extra context appear exactly when the extra context does.
264
+ *
265
+ * **§5.2's wording rules are NOT here, and the split is the point.** They are scoped by *whether
266
+ * the rejection is addressed to the agent at all* — which is what `auto` means — not by whether a
267
+ * transcript happens to exist yet; see {@link RATER_NEGOTIABLE_REJECTION_GUIDANCE}.
268
+ *
269
+ * Three rules are normative and none may be softened into another:
270
+ *
271
+ * - **A justification may only ever LOWER a rating** — *lower* meaning **less severe**. It may move
272
+ * `destructive` to `safe`; it may never move `safe` to `destructive`. That is the permissive
273
+ * direction on purpose: a negotiation the justification cannot win is not a negotiation.
274
+ * - **A stated intent that does not match what the command does is grounds for REJECTION, not for a
275
+ * discount.** This is the counterweight to the rule above and is not optional — the justification
276
+ * is the one place in the whole design where attacker-influenceable text can *reduce* an outcome.
277
+ * - **`attack` and `catastrophic` are exempt from the entire mechanism** (§4.2). No justification,
278
+ * no revision and no accumulated context moves either.
279
+ *
280
+ * The opening clause extends {@link RATER_SYSTEM_PREAMBLE}'s untrusted-data contract to the new
281
+ * tags. The preamble names `<command_to_evaluate>` alone and cannot be edited without breaking
282
+ * round-1 byte-identity, so the tags this block introduces are declared untrusted *here*, in the
283
+ * same prompt that introduces them.
284
+ */
285
+ export declare const RATER_NEGOTIATION_CONTEXT_GUIDANCE: string;
286
+ /**
287
+ * [[EXT-29]] (spec §5.2) — **how a rejection must be WORDED, added whenever the rejection will be
288
+ * read by the agent** rather than by a person.
289
+ *
290
+ * **This is scoped by MODE, not by round, and the distinction is the whole of §5.2.** §5.1 governs
291
+ * what *context* a rating is allowed to see, and round 1 sees the command alone; §5.2 governs how a
292
+ * rejection is *written*, and a rejection is written the same way in every round of a negotiation
293
+ * — including the first, which is the round §5.6's escalation example requires to name the fix
294
+ * (*"Name the commits to drop, or use `--soft`"*). What decides whether these rules apply at all is
295
+ * whether the rejection is *addressed to the agent*: at `auto` it is, at `assisted` a `destructive`
296
+ * outcome goes to the human instead, so *"MUST invite a response"* would be addressed to nobody.
297
+ *
298
+ * Turning it on therefore keys on {@link import('#src/config.js').isNegotiatingRung} and NOT on
299
+ * whether a negotiation block exists. The two are independent by construction: a cleared transcript
300
+ * (§5.3) produces a round-1 *context* that is still a round of a negotiation.
301
+ *
302
+ * It sits LAST in the system prompt, after {@link buildGrantedToolsGuidance}, because §5.2's list of
303
+ * things a rejection may name ends with *a granted built-in that does the job* — a clause that reads
304
+ * as an instruction only once that list is already on the page.
305
+ *
306
+ * The two named anti-patterns are stated as failures rather than merely left out: a bare *"Rejected.
307
+ * This is destructive."* leaves the agent nothing to act on, and a rejection that restates the danger
308
+ * and asks the agent to explain itself invites another justification rather than a better command.
309
+ */
310
+ export declare const RATER_NEGOTIABLE_REJECTION_GUIDANCE: string;
311
+ /**
312
+ * EXT-58 (spec §4.4) — the granted-alternative section of the rating prompt, built from the
313
+ * already-granted built-in tools of the current rung.
314
+ *
315
+ * Three properties are normative and each is spelled out to the rater:
316
+ *
317
+ * - It must name a granted tool **whenever** the outcome is not `safe` and one of them would do the
318
+ * job, because a free built-in call beats an interruption.
319
+ * - It must **not** name one when none can do the job — a path outside the working folder is the
320
+ * canonical case, where neither the read nor the edit tool can reach either. A facility that
321
+ * manufactures suggestions makes "a suggestion is never an approval" meaningless.
322
+ * - A suggestion is **never an approval**: it does not change the outcome, does not approve the
323
+ * original command, and does not pre-approve the suggested tool (which is gated normally when it
324
+ * arrives). The gate enforces this structurally — {@link mapVerdictToAction} never reads the
325
+ * field — but the rater is told so it does not soften an outcome because an alternative exists.
326
+ *
327
+ * The list is **trusted, locally-generated text** (§4.3) and therefore lives in the SYSTEM prompt,
328
+ * structurally outside the `<command_to_evaluate>` block that carries the untrusted command. Only
329
+ * tool names and one-line descriptions authored in `config/tool-descriptions.ts` ever appear here;
330
+ * no MCP/custom/A2A tool's own description can reach the rater.
331
+ *
332
+ * Returns `null` when nothing is granted (or the caller supplied no list), so the prompt is exactly
333
+ * the pre-EXT-58 text and the rater is never invited to invent a tool out of an empty list.
334
+ */
335
+ export declare function buildGrantedToolsGuidance(grantedTools: readonly GrantedToolSummary[] | undefined): string | null;
336
+ /**
337
+ * Build the rater's system prompt: the invariant {@link RATER_SYSTEM_PREAMBLE}, the four outcome
338
+ * definitions (with §4.1's recoverability question spelled out in
339
+ * {@link RATER_CATASTROPHIC_GUIDANCE} and §4.1.1's structural test in
340
+ * {@link RATER_ATTACK_GUIDANCE}), the rules that make `destructive` the catch-all and uncertainty a
341
+ * `destructive` rather than an outcome of its own, and — when the caller supplies them — the
342
+ * already-granted tools of §4.4 ({@link buildGrantedToolsGuidance}).
343
+ *
344
+ * `destructive` is defined **last and by exclusion** on purpose: the two severe outcomes have to be
345
+ * on the page before "everything else" can mean anything, and defining the catch-all in terms of
346
+ * the other three is what makes it structurally impossible for a command to fall outside the four.
347
+ *
348
+ * There is no strictness parameter: §1 removed strictness levels along with severity thresholds,
349
+ * so the rating criteria are the same at every rated rung. Two blocks are appended on top of them,
350
+ * and they key on **two independent things** ([[EXT-29]]):
351
+ *
352
+ * - `hasNegotiationContext` appends {@link RATER_NEGOTIATION_CONTEXT_GUIDANCE} — the rules for
353
+ * weighing a justification and a transcript, which have nothing to govern until one exists.
354
+ * - `negotiable` appends {@link RATER_NEGOTIABLE_REJECTION_GUIDANCE} — §5.2's rules for wording a
355
+ * rejection that the *agent* will read, which apply at every round of a negotiation including the
356
+ * first.
357
+ *
358
+ * **They are two parameters and not one because they are two questions.** Tying §5.2 to the context
359
+ * would silence it in exactly the two rounds §5.6 requires it in: round 1, and the round right after
360
+ * a §5.3 reset — both of which are round-1 *contexts* inside a live negotiation. Both blocks only
361
+ * ever APPEND, so a negotiated system prompt still has the plain one as its prefix.
362
+ *
363
+ * @param grantedTools §4.4's already-granted built-ins, or nothing.
364
+ * @param options `hasNegotiationContext` — whether this rating carries a §5.1 context; callers
365
+ * should not decide it for themselves, since {@link buildRaterPrompt} derives it from the one
366
+ * thing that decides it, namely whether {@link buildNegotiationContextBlock} produced a block.
367
+ * `negotiable` — whether a rejection will be handed back to the agent (§5.2), i.e. the rung
368
+ * negotiates ({@link import('#src/config.js').isNegotiatingRung}).
369
+ */
370
+ export declare function buildRaterSystemPrompt(grantedTools?: readonly GrantedToolSummary[], options?: {
371
+ hasNegotiationContext?: boolean;
372
+ negotiable?: boolean;
373
+ }): string;
374
+ /**
375
+ * Detect whether the command invokes an interpreter on a script target AND passes an
376
+ * `$ALL_CAPS` shell-variable expansion in its arguments — openclaw's "script preflight". Such a
377
+ * command can leak environment (often secrets) into the script, so it should bias toward
378
+ * escalation. Lightweight heuristic over the normalized command; a positive flag is fed to the
379
+ * rater prompt AND forces the fail-closed `destructive` path in the decision mapping.
380
+ *
381
+ * @returns true when an interpreter+script invocation also expands an ALL_CAPS env var.
382
+ */
383
+ export declare function hasScriptEnvLeakRisk(normalizedCommand: string): boolean;
384
+ /**
385
+ * Fold an absolute home path to `~` so the rater sees a stable, less-identifying form (mirrors
386
+ * hermes `_normalize_command_for_detection` path folding). Best-effort: only the literal home
387
+ * dir prefix is folded.
388
+ */
389
+ export declare function foldHomePath(command: string, home: string | undefined): string;
390
+ /**
391
+ * [[EXT-29]] (spec §5.1) — one COMPLETED round of a negotiation, as the next round's rater sees it.
392
+ *
393
+ * It carries exactly what §5.1's third bullet admits and nothing else: the command the agent
394
+ * proposed, the justification it attached (when it attached one), and **the rater's own outcome and
395
+ * explanation** for that round — *"the rater reasons from its earlier positions rather than
396
+ * re-deriving them."*
397
+ *
398
+ * **The rater's half is two flat fields rather than an embedded {@link ShellSafetyVerdict}**, and
399
+ * that is deliberate. A verdict also carries §4.4's `suggestedTool`, which is advice about the
400
+ * rating that produced it and not part of the history §5.1 admits; a renderer handed a three-field
401
+ * object that renders two of them is a silent drop waiting to be read as a bug. Flat fields mean the
402
+ * builder renders everything it is given, and the type states the admitted set by construction.
403
+ *
404
+ * `command` is the RAW command as the agent proposed it. The builder normalizes and home-folds it
405
+ * with the same functions the live command goes through, so the transcript shows a past round in the
406
+ * form that round was actually rated in, then renders it on one line — see
407
+ * {@link buildNegotiationContextBlock}.
408
+ */
409
+ export interface RaterNegotiationRound {
410
+ /**
411
+ * The command the agent proposed in that round, RAW. The builder normalizes, home-folds and
412
+ * one-lines it; a caller that pre-processes it is doing the work twice and differently.
413
+ */
414
+ command: string;
415
+ /** The justification the agent attached to it, if any. Omitted when it argued nothing. */
416
+ justification?: string;
417
+ /** The rater's own outcome for that round. */
418
+ outcome: RaterOutcome;
419
+ /** The rater's own one-sentence explanation for that round. */
420
+ reason: string;
421
+ }
422
+ /**
423
+ * [[EXT-29]] (spec §5.1) — the additional context a rating from **round 2 onward** carries. Absent,
424
+ * or present but carrying nothing, means a round-1 rating: the command alone, byte-for-byte the
425
+ * prompt this module built before the negotiation existed.
426
+ *
427
+ * "Carrying nothing" is defined so a caller never has to choose a spelling: `undefined`, `{}`, an
428
+ * empty or whitespace-only `justification`, and empty arrays are all the same round-1 context. §5.3
429
+ * clears the transcript with the counter, so the state the runner holds after a reset is exactly
430
+ * this, whichever way it spells it.
431
+ *
432
+ * Nothing here is bounded by the caller: the last-5 rule and the 1000-character truncation are
433
+ * applied by {@link buildNegotiationContextBlock}, so a caller that hands over an entire
434
+ * conversation still cannot put a pasted log into the rater's context.
435
+ */
436
+ export interface RaterNegotiationContext {
437
+ /** The main model's justification for **this** command. Untrusted; fenced like the command. */
438
+ justification?: string;
439
+ /**
440
+ * The conversation's user messages, oldest first. Only the last {@link NEGOTIATION_MAX_USER_MESSAGES}
441
+ * are admitted and each is truncated — hand over as many as are to hand.
442
+ */
443
+ userMessages?: readonly string[];
444
+ /** The negotiation so far, oldest round first. */
445
+ priorRounds?: readonly RaterNegotiationRound[];
446
+ }
447
+ /**
448
+ * Neutralise any sequence that would CLOSE the fence `tag`, so untrusted text cannot escape it.
449
+ *
450
+ * A fenced block is only a boundary if the fenced text cannot write the boundary itself. Untrusted
451
+ * content containing its own closing tag ends the fence early and everything after it reads as our
452
+ * own prose — and inside `<negotiation_so_far>` that is not merely confusing but *persuasive*: the
453
+ * block quotes the rater's own previous positions back to it, and {@link RATER_NEGOTIATION_GUIDANCE}
454
+ * tells it to reason from those positions. A forged prior `safe` therefore argues for approval in
455
+ * the rater's own voice.
456
+ *
457
+ * The replacement carries no angle brackets at all (a marker that spelled the tag out would be the
458
+ * very sequence being removed) and says what happened, because a rater that can see text was
459
+ * tampered with has been told something useful about the command it is rating.
460
+ *
461
+ * **Matching is deliberately loose, and the looseness is the mechanism.** The reader is a language
462
+ * model, not a parser, so a matcher that is stricter than the reader is not a filter — it is a list
463
+ * of spellings the attacker gets to choose from. Four kinds of slack are closed: case, the
464
+ * whitespace an XML parser would ignore (`</ justification >`), the compatibility glyphs NFKC folds
465
+ * (a fullwidth solidus is a solidus to a reader), and any invisible spliced into the tag
466
+ * ({@link INVISIBLE_FORMAT_CHARS}).
467
+ *
468
+ * **That is four kinds of slack, not all of them**, and the difference is worth keeping in view: the
469
+ * invisibles are covered by an enumeration of Unicode properties, so this is as tolerant as those
470
+ * properties are and no more. {@link INVISIBLE_FORMAT_CHARS} states that residual; a character
471
+ * measured to render as blank and walk through belongs in that class, not in a second matcher here.
472
+ *
473
+ * Self-reconstruction is impossible by construction: the replacement contains no angle bracket and
474
+ * no slash, so no arrangement of neutralised text can rebuild a closing tag.
475
+ *
476
+ * Parameterised by tag because it guards every fence in the rating prompt — the three §5.1 ones and
477
+ * `<command_to_evaluate>` — rather than each growing a mechanism that escapes differently. Exported
478
+ * so a test can drive the matcher directly.
479
+ *
480
+ * **THE RESIDUAL IS WIDER THAN A HOMOGLYPH LIST — measured, and do not size it from this comment.**
481
+ * This matcher catches the tag spelled essentially exactly. Four classes walk through, and the first
482
+ * needs no Unicode at all:
483
+ *
484
+ * 1. **Pure ASCII near-misses** — `</tag foo>` (a trailing attribute), `<//tag>`, `</tag/>`, and a
485
+ * plain space inside the name. Note the asymmetry that makes the last one easy to miss: a
486
+ * ZERO-WIDTH space between two letters of the name IS neutralised by the strip below, while an
487
+ * ordinary space in the identical position is not.
488
+ * 2. **Solidus homoglyphs** — NFKC folds the fullwidth solidus (U+FF0F) but not the fraction slash
489
+ * (U+2044), the division slash (U+2215) or the big solidus (U+29F8).
490
+ * 3. **Bracket homoglyphs** — U+2039, U+27E8, U+3008, U+2329, U+276C.
491
+ * 4. **Tag-name homoglyphs** — Cyrillic and Greek lookalikes inside the tag name.
492
+ *
493
+ * **The ASCII class is the serious one.** The reader this function defends against is a language
494
+ * model, and `</tag foo>` reads as a closing tag to a model more readily than any homoglyph does —
495
+ * it is what a closing tag with an attribute looks like in the XML-shaped prompt it sits in.
496
+ *
497
+ * **Nothing mechanical is fooled by any of it:** none produces the literal `</tag>`, so no boundary
498
+ * count is wrong and no test that counts fences can see it. That is precisely why it is written here.
499
+ *
500
+ * **Left open for SCOPE, not because it is mild** — the matcher guards all four fences, so widening
501
+ * it lands on every one at once. That decision, and the shape it should take (a tolerant matcher
502
+ * reaches class 1; a confusable skeleton answers 2-4), is [[EXT-111]].
503
+ */
504
+ export declare function neutralizeClosingTag(text: string, tag: string): string;
505
+ /**
506
+ * [[EXT-29]] (spec §5.1) — the negotiation context block appended to the rater's USER message, or
507
+ * `null` when there is no negotiation (a round-1 rating).
508
+ *
509
+ * **Everything here is attacker-influenceable and every part of it is fenced**, exactly as
510
+ * `<command_to_evaluate>` is: the justification is written by a main model that reads untrusted
511
+ * input, the user messages arrive through a channel an attacker can reach, and the prior rounds
512
+ * carry both. The system prompt's {@link RATER_NEGOTIATION_GUIDANCE} declares these tags untrusted
513
+ * and is emitted with this block for that reason. Only the rater's own past outcomes and our own
514
+ * headings are ours, and they are the block's structure rather than its contents.
515
+ *
516
+ * **The fences here are enforced, not merely drawn.** Two things could otherwise write the block's
517
+ * own structure from inside it, and both are neutralised at the point of rendering: a closing tag
518
+ * ({@link neutralizeClosingTag}) and a newline in any one-line slot ({@link oneLine}). The amplifier
519
+ * that makes this worth more than tidiness is what the block IS — it quotes the rater's previous
520
+ * outcomes back to it under guidance telling it to reason from them, so a forged prior `safe` argues
521
+ * for approval in the rater's own voice.
522
+ *
523
+ * **The order inside the block narrows outward from the command being rated**: the justification is
524
+ * about THIS command, the transcript is the exchange that produced it, and the user messages are the
525
+ * mandate around the whole thing. It also keeps the agent's argument for the pending command out of
526
+ * the final position, which is the one a model weighs hardest.
527
+ *
528
+ * **Prior commands are normalized here**, by the same function the live command goes through, so a
529
+ * past round appears in the form it was actually rated in. **Home-folding applies to every value the
530
+ * block renders** — the justification and the user messages as much as the commands — because
531
+ * {@link foldHomePath} exists to keep the identifying form out of the prompt, and a prose field is
532
+ * where an absolute home path is most likely to appear, not least.
533
+ *
534
+ * Bounds are applied here rather than trusted from the caller: at most
535
+ * {@link NEGOTIATION_MAX_USER_MESSAGES} messages (the LAST that many), each truncated to
536
+ * {@link NEGOTIATION_USER_MESSAGE_MAX_CHARS}. Blank entries are dropped before the last-5 window is
537
+ * taken, so a run of empty messages cannot spend the budget that carries the mandate — and "blank"
538
+ * counts the characters {@link INVISIBLE_FORMAT_CHARS} names as nothing ({@link isBlank}), so a
539
+ * value carrying only those cannot render a block that a plain rating would not have.
540
+ *
541
+ * @param negotiation The §5.1 context, or nothing.
542
+ * @param home The home directory to fold — the caller's own `home`, so the block folds exactly as
543
+ * the live command does.
544
+ * @returns The block, or `null` when nothing would be rendered. `null` is the single signal that
545
+ * this is a round-1 rating: {@link buildRaterPrompt} uses it for both halves of the prompt, so the
546
+ * guidance and the context can never appear without each other.
547
+ */
548
+ export declare function buildNegotiationContextBlock(negotiation: RaterNegotiationContext | undefined, home?: string): string | null;
549
+ /**
550
+ * Build the messages for the rater call: the system prompt ({@link buildRaterSystemPrompt}) plus a
551
+ * human message that embeds the NORMALIZED command inside an XML `<command_to_evaluate>` tag and
552
+ * (optionally) notes what a deterministic preflight already found — the script-env-leak flag,
553
+ * (§4.6) a host literal in a fetch position, and ([[EXT-81]]) the shape our own parser could not
554
+ * resolve. The command text is only ever DATA in the tag — the builder never executes or
555
+ * interpolates it as instructions, and the notes are our own trusted text beside it. That
556
+ * separation is ENFORCED rather than merely drawn: the command cannot close its own fence
557
+ * ({@link neutralizeClosingTag}), so no part of it can render where our notes render.
558
+ *
559
+ * The four preflight notes are worded differently on purpose, and the differences are the design:
560
+ *
561
+ * - The **script-env-leak** note caps nothing but says "treat this as at least destructive".
562
+ * - The **open-world floor** note must NOT, because §4.6.1 asks the rater to *upgrade* a clear
563
+ * typosquat to `attack` and a note that anchors on `destructive` would talk it out of the one
564
+ * judgement it is still being asked for. It may say the command is never auto-approved, because a
565
+ * floor really did fire on it.
566
+ * - The **parser** note ({@link buildParserPreflightNote}) is a third register again: those two
567
+ * describe findings that ARE hazards and are entitled to a floor, while this one reports that our
568
+ * parser could not read the command, which establishes nothing about it. So it states a mechanism
569
+ * and asks a question, carries no verdict and no severity, and — unlike the open-world note —
570
+ * never says the command has already been floored, because for these families none has.
571
+ * - The **composed open-world** note ({@link buildComposedOpenWorldNote}) shares that third register
572
+ * and must not borrow the floor note's wording, for the reason the floor note is entitled to it:
573
+ * no floor fired here. It names the DATA FLOW across the parts rather than restating the hostname,
574
+ * which is already in the command text — see that function for the measurement behind that
575
+ * distinction.
576
+ *
577
+ * **Order matters here and is the order of a reader's attention**: the two hazard notes come first
578
+ * because each names something positively established, then the parser note, then its open-world
579
+ * elaboration — general shape of what could not be resolved, then the specific flow inside it. Then,
580
+ * last, [[EXT-29]]'s negotiation context ({@link buildNegotiationContextBlock}) — the notes describe
581
+ * THIS command, the negotiation is the history around it.
582
+ *
583
+ * **Round 1 is the prompt this function built before the negotiation existed, character for
584
+ * character.** No negotiation, or one carrying nothing, adds no block and no guidance; a negotiated
585
+ * round only ever APPENDS to both halves. §5.3 clears the transcript with the counter, so the rating
586
+ * after a reset is a round-1 rating by construction rather than by the caller remembering to make
587
+ * one.
588
+ *
589
+ * §4.3 defines the rated unit tool-generally (tool name + JSON arguments); `run_shell_command` is
590
+ * the case whose argument is a command string, and it alone is additionally normalized and
591
+ * home-path-folded before fencing. The first implementation covers the shell only — every other
592
+ * tool is granted or escalated by the rung without a rating call until [[EXT-30]] widens the gate.
593
+ *
594
+ * Exposed (and returning plain strings) so tests can assert the structure: the tag is present,
595
+ * the untrusted-input preamble is present, and an injection string inside the command lands
596
+ * inside the tag rather than being acted on.
597
+ */
598
+ export declare function buildRaterPrompt(command: string, options?: {
599
+ home?: string;
600
+ grantedTools?: readonly GrantedToolSummary[];
601
+ /**
602
+ * [[EXT-29]] (§5.1) — the negotiation so far, from round 2 onward. Absent or empty builds
603
+ * exactly the round-1 prompt this function built before the negotiation existed, character for
604
+ * character, which is what makes §5.6's *"a cleared transcript means a round-1 context"* a
605
+ * property of this function rather than a discipline the caller has to keep.
606
+ */
607
+ negotiation?: RaterNegotiationContext;
608
+ /**
609
+ * [[EXT-29]] (§5.2) — whether a rejection will be handed back to the AGENT rather than to a
610
+ * person, i.e. the rung negotiates ({@link import('#src/config.js').isNegotiatingRung}).
611
+ *
612
+ * **Independent of `negotiation` on purpose.** §5.1 decides what the rating may SEE; this
613
+ * decides how a rejection must be WRITTEN, and the two diverge in exactly the round §5.6 cares
614
+ * most about — round 1 of a negotiation, and the round right after a §5.3 reset, where the
615
+ * context is empty and the rejection is still addressed to the agent.
616
+ *
617
+ * It changes the SYSTEM prompt only. The user message is a function of the command and the
618
+ * context alone, so a negotiation's round 1 has a byte-identical user prompt to an
619
+ * `assisted` rating of the same command.
620
+ */
621
+ negotiable?: boolean;
622
+ }): {
623
+ system: string;
624
+ user: string;
625
+ };
626
+ /**
627
+ * Rate a single shell command with the rater model and return a structured
628
+ * {@link ShellSafetyVerdict}.
629
+ *
630
+ * - Builds an injection-hardened, normalized prompt ({@link buildRaterPrompt}).
631
+ * - Calls the rater model (defaults to `config.llm`) via `withStructuredOutput(schema)`.
632
+ * - Races the call against {@link RATER_DEFAULT_TIMEOUT_MS}.
633
+ * - **Fail-closed:** any throw / timeout / parse failure returns {@link FAIL_CLOSED_VERDICT}
634
+ * (`destructive` + "could not assess"), never an approval.
635
+ *
636
+ * Note: this only produces a verdict; the approve / escalate / halt decision (including
637
+ * fail-closed-on-ambiguity) is made by {@link mapVerdictToAction} in the runner.
638
+ */
639
+ export declare function rateShellCommand(command: string, config: GthConfig, options?: {
640
+ model?: BaseChatModel;
641
+ home?: string;
642
+ timeoutMs?: number;
643
+ /**
644
+ * EXT-58 (§4.4) — the already-granted built-ins of the current rung. Supplied, the rater is
645
+ * asked to name one whenever it does not return `safe` and one would do the job; omitted, the
646
+ * prompt is exactly as before and no suggestion is ever produced.
647
+ */
648
+ grantedTools?: readonly GrantedToolSummary[];
649
+ /**
650
+ * [[EXT-29]] (§5.1) — the negotiation so far, for a rating from round 2 onward. Passed straight
651
+ * to {@link buildRaterPrompt}; absent or empty, this call is a round-1 rating and the prompt,
652
+ * the verdict and every decision made from it are exactly what they were before.
653
+ */
654
+ negotiation?: RaterNegotiationContext;
655
+ /**
656
+ * [[EXT-29]] (§5.2) — whether a rejection is addressed to the agent (the rung negotiates).
657
+ * Passed straight to {@link buildRaterPrompt}; see the option there for why it is independent
658
+ * of `negotiation`.
659
+ */
660
+ negotiable?: boolean;
661
+ /**
662
+ * [[TUI-C27]] — the sink for the diagnostic record of THIS call, handed over **at the send
663
+ * site**, carrying the prompt strings that are about to be sent.
664
+ *
665
+ * It is called once, BEFORE the model is invoked, with a record this function then fills in as
666
+ * the answer arrives. Two properties follow, and both are the point:
667
+ *
668
+ * - **The prompt is captured, never re-rendered.** The caller receives the very strings passed
669
+ * to `invoke`, so nothing downstream can rebuild a prompt that disagrees with the one that
670
+ * was actually sent — the one thing a diagnostic archive must never do.
671
+ * - **A rater that never answers still leaves a record of what it was asked.** The record is in
672
+ * the caller's hands before the call is made, so a hang, a timeout or a throw cannot take the
673
+ * question with it.
674
+ *
675
+ * Omitted (the eval target, and every caller that wants no diagnostics) costs nothing: no
676
+ * record is built.
677
+ */
678
+ onCapture?: (capture: RaterCallCapture) => void;
679
+ /** [[TUI-C27]] — `approvals.rater`, recorded on the capture so a dump names WHO rated. */
680
+ raterProfile?: string;
681
+ }): Promise<ShellSafetyVerdict>;
682
+ /** Inputs to the decision mapping: just the rung. Each rung fully determines behaviour (§1). */
683
+ export interface RaterDecisionOptions {
684
+ /** The rung in force for this session. */
685
+ rung: ApprovalRung;
686
+ }
687
+ /**
688
+ * The verdict {@link mapVerdictToAction} acts on, after the gate's own fail-closed preflight has
689
+ * overridden the rater where it must. Returned alongside the action so the caller surfaces the
690
+ * HONEST reason (a "could not assess" note) rather than whatever the rater claimed about a
691
+ * command the gate could not statically vet. `undefined` at the unrated rungs, where no rating
692
+ * call was made at all.
693
+ */
694
+ export interface RaterDecision {
695
+ action: RaterAction;
696
+ /** The verdict actually used — the rater's, or the fail-closed `destructive` override. */
697
+ verdict?: ShellSafetyVerdict;
698
+ }
699
+ /**
700
+ * Is this outcome below the deterministic `destructive` floor — i.e. may a preflight rewrite it?
701
+ *
702
+ * See {@link BELOW_DESTRUCTIVE_FLOOR}. An outcome that is not in the table is treated as below the
703
+ * floor, so an out-of-band value is FLOORED to `destructive` rather than sailing past the preflight
704
+ * carrying the model's own unvalidated reason.
705
+ *
706
+ * The lookup is OWN-PROPERTY-ONLY, and the declared `boolean` return is the reason. `outcome` is
707
+ * only `RaterOutcome` as far as the compiler is concerned — this helper exists to be robust to a
708
+ * value that lied — and a `?? undefined` default would still hand back the *inherited* value for a
709
+ * prototype-chain key (`'toString'` → a function, `'constructor'` → `Object`). Those happen to be
710
+ * truthy, so today's single caller would still floor; but a caller written as `=== true`, which is
711
+ * how a predicate advertised as hardened invites being consumed, would fail OPEN on exactly the
712
+ * class of input this function is for. `Object.hasOwn` makes the advertised invariant true rather
713
+ * than incidentally true.
714
+ */
715
+ export declare function isBelowDestructiveFloor(outcome: RaterOutcome): boolean;
716
+ /**
717
+ * **THE deterministic floor — the one place an outcome is raised to `destructive`.**
718
+ *
719
+ * Every gated call reaches it: a shell command through {@link mapVerdictToAction}'s preflights
720
+ * ({@link preflightFloorReason}), a tool call through its effective `openWorldHint`
721
+ * ({@link openWorldToolFloorReason}, §4.7.3). They differ only in the *reason* they compute; what
722
+ * the reason then does to the outcome is decided here and nowhere else. A second implementation is
723
+ * how a gate and a display come to disagree about what a call is, and how one of them comes to
724
+ * *lower* an outcome the other raised.
725
+ *
726
+ * Two properties, both delegated to {@link isBelowDestructiveFloor} so they hold for every caller:
727
+ *
728
+ * - **It only ever RAISES.** A `destructive`, `catastrophic` or `attack` verdict passes through
729
+ * untouched, keeping its own explanation (and any §4.4 suggestion) — the floor is agreeing with
730
+ * it, not overriding it, and a floor that rewrote `catastrophic` would silently trade an
731
+ * unnegotiable escalation for a negotiable one.
732
+ * - **`undefined` is below the floor.** Nothing has assessed the call, so there is no outcome for
733
+ * the floor to defer to; a call nobody rated is exactly the call this rule exists to speak for.
734
+ * That is what lets a tool call — which no rater sees while §4.3's scope boundary stands — be
735
+ * floored by the same function that floors a rated shell command.
736
+ *
737
+ * @param verdict The outcome so far, or `undefined` when nothing has rated the call.
738
+ * @param reason The floor reason, or `null` when no preflight fired (the verdict is returned as-is).
739
+ */
740
+ export declare function applyDestructiveFloor(verdict: ShellSafetyVerdict, reason: string | null): ShellSafetyVerdict;
741
+ export declare function applyDestructiveFloor(verdict: ShellSafetyVerdict | undefined, reason: string | null): ShellSafetyVerdict | undefined;
742
+ /**
743
+ * EXT-70 (§4.7.2, §4.7.3) — the **tool** arm of the open-world floor: the reason a call whose
744
+ * EFFECTIVE `openWorldHint` is true is floored at `destructive`, or `null` when it is not.
745
+ *
746
+ * It sits beside {@link preflightFloorReason} because it is the same rule seen from the other side.
747
+ * §4.6 floors a shell fetch before any model call precisely so that no misreading of a hostname can
748
+ * auto-approve; *the same fetch reached through a tool instead of through `curl` must not be
749
+ * ungated*, or the preflight is a rule about spelling rather than about fetching. Both feed
750
+ * {@link applyDestructiveFloor}.
751
+ *
752
+ * **Independent of `readOnlyHint`, and that is the whole of §4.7.3.** A fetch tool is read-only in
753
+ * the local sense — it mutates nothing on this machine — while reaching the network; the two facts
754
+ * are unrelated, and `gth_web_fetch` (`readOnlyHint: true`, `openWorldHint: true`) is the case that
755
+ * proves it. `destructiveHint` is not consulted either: §4.7.2 lets it only ever RAISE, so a
756
+ * `destructiveHint: false` can never lower a floor this rule set. `idempotentHint` has no built-in
757
+ * consumer at all — do not invent one here.
758
+ *
759
+ * @param annotations The call's effective set (§4.7.1), never its declared one — trust has already
760
+ * been applied, so an untrusted server's `openWorldHint: false` has already collapsed to the
761
+ * fail-closed `true` by the time it arrives. `undefined` (a source that cannot decide) floors, in
762
+ * the same direction as the fail-closed default it would otherwise have returned.
763
+ */
764
+ export declare function openWorldToolFloorReason(annotations: EffectiveToolAnnotations | undefined): string | null;
765
+ /** A preflight finding: which arm fired, and the reason it floors the command with. */
766
+ export interface PreflightFloorFinding {
767
+ kind: PreflightFloorKind;
768
+ reason: string;
769
+ }
770
+ /**
771
+ * [[TUI-C27]] — the same finding {@link preflightFloorReason} returns, with the ARM NAMED.
772
+ *
773
+ * The reason alone is what the decision needs; a diagnostic archive needs to say *which* stage
774
+ * decided, and "an environment variable was expanded into a script" and "a host literal sat in a
775
+ * fetch position" are two different findings a reader must be able to tell apart without matching
776
+ * prose prefixes. `preflightFloorReason` delegates here rather than the two existing side by side:
777
+ * a second copy of this ordering is how a gate and a dump come to disagree about what floored a
778
+ * command.
779
+ */
780
+ export declare function preflightFloorFinding(command: string): PreflightFloorFinding | null;
781
+ /**
782
+ * CFG-27 — pure, testable mapping from a {@link ShellSafetyVerdict} + the raw command to a
783
+ * {@link RaterAction}, keyed on the **rung** (spec §4.2, §8):
784
+ *
785
+ * | Outcome | `manual`/`write` | `assisted` | `auto` | `bypass` |
786
+ * |---|---|---|---|---|
787
+ * | — (no rating) | escalate | | | approve |
788
+ * | `safe` | — | approve | approve | — |
789
+ * | `destructive` | — | escalate | **reject** — §5's negotiation ([[EXT-29]]) | — |
790
+ * | `catastrophic` | — | escalate | escalate — **never negotiate** | — |
791
+ * | `attack` | — | **halt** | **halt** | — |
792
+ *
793
+ * **[[EXT-81]] — there is no longer a row cutting across that column.** A command whose target the
794
+ * gate cannot statically resolve used to return `abstain` here, before the `safe` check and after
795
+ * the two severe outcomes. It is now rated like any other command, carrying a neutral note about
796
+ * the shape the parser saw ({@link buildParserPreflightNote}), and this table is the whole mapping
797
+ * again. The table gained nothing it did not have: what it LOST is a branch that made
798
+ * `catastrophic` and `attack` unreachable for every composed, substituting or redirecting command.
799
+ *
800
+ * Order of precedence (fail-closed FIRST — **this ordering IS the safety property**):
801
+ *
802
+ * 1. `bypass` → `approve`. The gate is off. The declared deny list and the exec-time hardline
803
+ * floor still apply, but neither is decided here.
804
+ * 2. Unrated rungs (`manual`, `write`) → `escalate`. No model is consulted at all; the
805
+ * allow-list is checked by the caller BEFORE this function, so reaching here means the human
806
+ * decides. (The two rungs behave identically here, and that is not a missing branch: what
807
+ * separates them is which tools they auto-grant, decided once in `resolveGatedToolNames` when the
808
+ * gated set is built. A call that reaches this function is already one the rung did not grant,
809
+ * and the shell — this function's only subject — is granted by neither.)
810
+ * 3. **The deterministic preflight FINDINGS, which FLOOR the outcome at `destructive` and never
811
+ * lower one** ({@link preflightFloorReason}): the script-env-leak preflight
812
+ * ({@link hasScriptEnvLeakRisk}) and EXT-61's open-world preflight
813
+ * ({@link findOpenWorldHostLiterals} — a host literal in a fetch/transfer position, §4.6). Both
814
+ * are recomputed from the RAW command, independently of what the rater said. Either rewrites a
815
+ * verdict that sits BELOW the floor — i.e. `safe`, and only `safe`
816
+ * ({@link isBelowDestructiveFloor}) — to `destructive` with an honest reason, **before the `safe`
817
+ * check**, so a manipulated `safe` verdict can never slip one of them through. **A rater verdict
818
+ * may only ever make an outcome worse, never better**, and so may a preflight: `destructive`,
819
+ * `catastrophic` and `attack` all pass through UNCHANGED. (Before the rescale this branch
820
+ * excluded the single halting outcome BY NAME. Renamed in place it would have let a preflight hit
821
+ * *downgrade* a `catastrophic` verdict to `destructive` — the exact inverse of the invariant
822
+ * above, silently trading an unnegotiable escalation for a negotiable one at `auto`.)
823
+ * 4. `attack` → `halt`, at both rated rungs, never negotiable.
824
+ * 5. `catastrophic` → `escalate`, and MUST NOT enter §5's negotiation.
825
+ * 6. `safe` → `approve`; `destructive` → `escalate` at `assisted`, `reject` at `auto` (§5's
826
+ * negotiation, [[EXT-29]]).
827
+ *
828
+ * **EXT-58 (§4.4): the verdict's `suggestedTool` is not read here, and that is deliberate.** A
829
+ * suggestion is never an approval — it must not change the action, must not approve the original
830
+ * command, and must not pre-approve the suggested tool. The gate also never decides for itself that
831
+ * a shell command is "equivalent" to a built-in and substitutes it: any such equivalence test would
832
+ * be a second command parser, and a second command parser is a second place for the gate to be
833
+ * bypassed. The suggestion is carried, untouched, to the human (§6) and to the model (§7) — nothing
834
+ * else. Note that the fail-closed rewrite in (3) builds a FRESH verdict and therefore drops any
835
+ * suggestion along with the reason it belonged to: a verdict the gate has just declared
836
+ * untrustworthy must not keep recommending anything. A verdict the preflight leaves alone was never
837
+ * declared untrustworthy — the gate is agreeing with it, not overriding it — so it keeps both.
838
+ *
839
+ * @param command The raw command string (used to recompute ambiguity + preflight independently
840
+ * of the rater, so the gate is robust even if the rater is wrong or manipulated).
841
+ * @param verdict The rater's verdict (or {@link FAIL_CLOSED_VERDICT}); `undefined` at the unrated
842
+ * rungs. A missing verdict at a RATED rung is treated as {@link FAIL_CLOSED_VERDICT}.
843
+ * @param opts The rung in force.
844
+ */
845
+ export declare function mapVerdictToAction(command: string, verdict: ShellSafetyVerdict | undefined, opts: RaterDecisionOptions): RaterDecision;
846
+ /**
847
+ * EXT-71 §3.2 — the mapping for a call an **allow entry already matched** while keeping the rater
848
+ * involved (`rate: true`). This is a **TRIPWIRE, not a re-adjudication**, and the difference is the
849
+ * whole reason it is a separate function from {@link mapVerdictToAction}:
850
+ *
851
+ * | Outcome | Action | Why |
852
+ * |---|---|---|
853
+ * | `safe` | approve | nothing to say |
854
+ * | `destructive` | **approve** | the human already authorized this call; the rater does not overrule a standing human decision by disliking it |
855
+ * | `catastrophic` | escalate | a human decides, and per §4.2 that approval is never sticky |
856
+ * | `attack` | **halt** | exactly per §4.2 — the structure evidenced compromise, which no prior grant answers |
857
+ *
858
+ * The rater's job on an allow-listed call is to catch the tail where a broad entry matched
859
+ * something structurally hostile — not to re-ask a question the human answered.
860
+ *
861
+ * **The deterministic preflights are deliberately not consulted** ({@link preflightFloorReason} is
862
+ * not called). §4.6 states it directly for the open-world arm: *an allow match lifts this floor even
863
+ * when the entry keeps the rater involved — the tripwire still sees the call; the floor does not
864
+ * apply to it.* The script-env-leak arm is lifted with it, and doing so changes no outcome: a
865
+ * preflight only ever raises `safe` to `destructive`, and both of those run here. Applying the floor
866
+ * would therefore alter nothing except to replace an honest verdict with a note about a decision
867
+ * this mapping does not make.
868
+ *
869
+ * @param verdict The rater's verdict; `undefined` or a fail-closed verdict is `destructive` and so
870
+ * runs — the tripwire failing to answer does not revoke the human's standing decision, exactly as
871
+ * `rate: false` would not have asked in the first place.
872
+ */
873
+ export declare function mapAllowMatchedVerdictToAction(verdict: ShellSafetyVerdict | undefined): RaterDecision;