@sema-agent/core 7.16.0 → 7.17.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 (193) hide show
  1. package/CHANGELOG.md +173 -0
  2. package/dist/agents/builtin-agents.d.ts +3 -3
  3. package/dist/agents/cross-session-envelope.d.ts +0 -1
  4. package/dist/agents/cross-session-envelope.js +0 -1
  5. package/dist/agents/peer-notices.d.ts +0 -9
  6. package/dist/agents/peer-notices.js +0 -63
  7. package/dist/agents/subagent.js +7 -15
  8. package/dist/brain/request-params.d.ts +2 -2
  9. package/dist/config/defaults.d.ts +0 -7
  10. package/dist/config/defaults.js +0 -2
  11. package/dist/core/artifact-host.d.ts +218 -0
  12. package/dist/core/artifact-host.js +92 -0
  13. package/dist/core/ask-origin.d.ts +29 -3
  14. package/dist/core/ask-origin.js +17 -9
  15. package/dist/core/ask-question.d.ts +25 -0
  16. package/dist/core/ask-question.js +4 -0
  17. package/dist/core/ask-unresolvable-notice.d.ts +30 -21
  18. package/dist/core/ask-unresolvable-notice.js +16 -8
  19. package/dist/core/auto-compaction.d.ts +10 -10
  20. package/dist/core/auto-mode-arming.d.ts +2 -2
  21. package/dist/core/background-agent-store.d.ts +5 -5
  22. package/dist/core/background-shell.d.ts +1 -1
  23. package/dist/core/checkpoint-execution-record.d.ts +0 -3
  24. package/dist/core/checkpoint-execution-record.js +0 -4
  25. package/dist/core/checkpoint-store.d.ts +71 -99
  26. package/dist/core/checkpoint-store.js +9 -16
  27. package/dist/core/context-edit.d.ts +36 -104
  28. package/dist/core/context-edit.js +37 -60
  29. package/dist/core/effective-path-target.d.ts +68 -8
  30. package/dist/core/effective-path-target.js +42 -30
  31. package/dist/core/engine-notice.d.ts +46 -32
  32. package/dist/core/env-budget.d.ts +31 -0
  33. package/dist/core/env-budget.js +39 -0
  34. package/dist/core/exec-output-tail.d.ts +5 -5
  35. package/dist/core/file-history-store.js +3 -6
  36. package/dist/core/fs-write-gate-policy.d.ts +1 -1
  37. package/dist/core/fs-write-gate-policy.js +3 -3
  38. package/dist/core/gate-lanes.js +9 -15
  39. package/dist/core/governance-codes.d.ts +1 -1
  40. package/dist/core/governance-codes.js +2 -0
  41. package/dist/core/hands-band.d.ts +1 -1
  42. package/dist/core/hooks.d.ts +18 -17
  43. package/dist/core/lsp-session.d.ts +1 -1
  44. package/dist/core/lsp.d.ts +1 -1
  45. package/dist/core/lsp.js +5 -4
  46. package/dist/core/mcp.d.ts +5 -5
  47. package/dist/core/mcp.js +4 -48
  48. package/dist/core/memory-recall.d.ts +0 -8
  49. package/dist/core/memory.d.ts +1 -1
  50. package/dist/core/oracle-isolation.js +2 -2
  51. package/dist/core/parent-spec-seat.d.ts +66 -0
  52. package/dist/core/parent-spec-seat.js +47 -0
  53. package/dist/core/permission-rule-model.d.ts +11 -6
  54. package/dist/core/permission-rule-model.js +53 -37
  55. package/dist/core/permission-rules.js +8 -3
  56. package/dist/core/protocol-naming.d.ts +2 -2
  57. package/dist/core/remote-env.d.ts +1 -1
  58. package/dist/core/retired-keys.d.ts +19 -0
  59. package/dist/core/retired-keys.js +15 -0
  60. package/dist/core/runner/active-skill-scope.js +3 -3
  61. package/dist/core/runner/assemble-result.d.ts +15 -13
  62. package/dist/core/runner/assemble-result.js +5 -4
  63. package/dist/core/runner/compaction-call-options.d.ts +25 -89
  64. package/dist/core/runner/contracts.d.ts +10 -9
  65. package/dist/core/runner/gate-exit.js +15 -1
  66. package/dist/core/runner/halt-attribution.d.ts +35 -0
  67. package/dist/core/runner/halt-attribution.js +16 -0
  68. package/dist/core/runner/memory-consolidation.d.ts +0 -2
  69. package/dist/core/runner/memory-consolidation.js +0 -1
  70. package/dist/core/runner/prepare-artifact.d.ts +53 -0
  71. package/dist/core/runner/prepare-artifact.js +77 -0
  72. package/dist/core/runner/prepare-caps-and-workflow.d.ts +4 -2
  73. package/dist/core/runner/prepare-caps-and-workflow.js +2 -3
  74. package/dist/core/runner/prepare-config-doors.d.ts +3 -3
  75. package/dist/core/runner/prepare-config-doors.js +5 -2
  76. package/dist/core/runner/prepare-context-lane.d.ts +2 -2
  77. package/dist/core/runner/prepare-context-lane.js +2 -13
  78. package/dist/core/runner/prepare-file-history.d.ts +1 -1
  79. package/dist/core/runner/prepare-file-history.js +3 -52
  80. package/dist/core/runner/prepare-hands-readface.d.ts +4 -7
  81. package/dist/core/runner/prepare-hands-readface.js +3 -2
  82. package/dist/core/runner/prepare-inherited-gate.d.ts +5 -2
  83. package/dist/core/runner/prepare-inherited-gate.js +7 -8
  84. package/dist/core/runner/prepare-memory.d.ts +4 -6
  85. package/dist/core/runner/prepare-memory.js +4 -2
  86. package/dist/core/runner/prepare-policy-chain.d.ts +3 -3
  87. package/dist/core/runner/prepare-policy-chain.js +15 -30
  88. package/dist/core/runner/prepare-prompt-assembly.js +1 -1
  89. package/dist/core/runner/prepare-safety-scan.d.ts +1 -1
  90. package/dist/core/runner/prepare-suspend-saga.js +0 -2
  91. package/dist/core/runner/prepare-task.js +10 -9
  92. package/dist/core/runner/prepare-wiring-manifest.js +9 -7
  93. package/dist/core/runner/resume-admission.js +2 -1
  94. package/dist/core/runner/resume-internals-and-config.js +4 -0
  95. package/dist/core/runner/retired-runner-deps.d.ts +3 -0
  96. package/dist/core/runner/retired-runner-deps.js +7 -0
  97. package/dist/core/runner/run-harness-handlers.js +2 -3
  98. package/dist/core/runner/run-settle-and-teardown.d.ts +1 -1
  99. package/dist/core/runner/run-settle-and-teardown.js +3 -1
  100. package/dist/core/runner/session-file-state-replay.js +3 -3
  101. package/dist/core/runner/session-rule-policy.js +3 -4
  102. package/dist/core/runner/stream-halt-verbs.js +1 -1
  103. package/dist/core/runner/stream-steer-verb.js +1 -4
  104. package/dist/core/runner/workspace-path.js +3 -0
  105. package/dist/core/runner-deps.d.ts +30 -49
  106. package/dist/core/safe-notify.d.ts +1 -1
  107. package/dist/core/sensitive-path-policy.d.ts +1 -1
  108. package/dist/core/sensitive-path-policy.js +4 -4
  109. package/dist/core/session.d.ts +2 -2
  110. package/dist/core/skill-tool-specifier.d.ts +1 -1
  111. package/dist/core/skills-directory.d.ts +1 -1
  112. package/dist/core/store-contracts/checkpoint-store-contract.js +1 -1
  113. package/dist/core/strategy-store.d.ts +8 -11
  114. package/dist/core/task-event.d.ts +20 -17
  115. package/dist/core/task-limits.d.ts +1 -1
  116. package/dist/core/task-notification.d.ts +5 -5
  117. package/dist/core/task-registry-agent.d.ts +5 -5
  118. package/dist/core/task-registry-monitor.d.ts +2 -2
  119. package/dist/core/task-registry-shared.d.ts +27 -27
  120. package/dist/core/task-registry-shared.js +3 -13
  121. package/dist/core/task-registry.d.ts +8 -8
  122. package/dist/core/task-result.d.ts +37 -37
  123. package/dist/core/task-spec.d.ts +5 -5
  124. package/dist/core/task-stream.d.ts +16 -11
  125. package/dist/core/tool-catalog-entries.d.ts +1 -0
  126. package/dist/core/tool-catalog-entries.js +5 -0
  127. package/dist/core/tool-conformance.d.ts +0 -2
  128. package/dist/core/tool-conformance.js +1 -3
  129. package/dist/core/tool-errors.d.ts +5 -0
  130. package/dist/core/tool-errors.js +1 -0
  131. package/dist/core/tool-policy.d.ts +51 -22
  132. package/dist/core/tool-policy.js +28 -5
  133. package/dist/core/tool-result-budget.d.ts +1 -1
  134. package/dist/core/tool-result-store.d.ts +11 -11
  135. package/dist/core/tool-spec.d.ts +34 -38
  136. package/dist/core/tools.d.ts +2 -2
  137. package/dist/core/trace.d.ts +12 -15
  138. package/dist/core/untrusted-text.d.ts +1 -1
  139. package/dist/core/wiring-manifest.d.ts +4 -3
  140. package/dist/core/workflow-journal-store.d.ts +11 -11
  141. package/dist/engine/execution-env/node-execution-env.d.ts +3 -2
  142. package/dist/engine/execution-env/node-execution-env.js +4 -2
  143. package/dist/engine/harness/agent-harness.d.ts +47 -4
  144. package/dist/engine/harness/agent-harness.js +52 -9
  145. package/dist/engine/loop/agent-loop.js +15 -8
  146. package/dist/engine/loop/types.d.ts +5 -0
  147. package/dist/index.d.ts +19 -14
  148. package/dist/index.js +15 -10
  149. package/dist/internal/harness.d.ts +1 -0
  150. package/dist/orchestration/run-spec.d.ts +3 -0
  151. package/dist/orchestration/run-spec.js +3 -3
  152. package/dist/orchestration/run-workflow-tool.d.ts +12 -6
  153. package/dist/orchestration/run-workflow-tool.js +23 -5
  154. package/dist/orchestration/workflow-primitives.d.ts +4 -2
  155. package/dist/orchestration/workflow-primitives.js +1 -4
  156. package/dist/orchestration/workflow-types.d.ts +34 -4
  157. package/dist/orchestration/workflow-types.js +2 -0
  158. package/dist/orchestration/workflow.d.ts +39 -7
  159. package/dist/orchestration/workflow.js +234 -113
  160. package/dist/prompt-assembly/assemble.d.ts +2 -2
  161. package/dist/prompt-assembly/assemble.js +9 -9
  162. package/dist/prompt-assembly/composer.d.ts +6 -7
  163. package/dist/prompt-assembly/composer.js +10 -10
  164. package/dist/prompt-assembly/epoch.js +1 -1
  165. package/dist/prompt-assembly/event-registry.d.ts +0 -21
  166. package/dist/prompt-assembly/packs/sema-default.js +30 -30
  167. package/dist/prompt-assembly/types.d.ts +14 -14
  168. package/dist/prompts/coordinator.d.ts +0 -13
  169. package/dist/prompts/coordinator.js +0 -3
  170. package/dist/stores/file/checkpoint-store.js +1 -5
  171. package/dist/stores/file/file-history-store.js +2 -2
  172. package/dist/stores/file/permission-rule-store.d.ts +4 -35
  173. package/dist/stores/file/permission-rule-store.js +4 -148
  174. package/dist/tools/artifact/artifact-text.d.ts +62 -0
  175. package/dist/tools/artifact/artifact-text.js +68 -0
  176. package/dist/tools/artifact/artifact-tool.d.ts +52 -0
  177. package/dist/tools/artifact/artifact-tool.js +351 -0
  178. package/dist/tools/artifact/local-stub.d.ts +26 -0
  179. package/dist/tools/artifact/local-stub.js +296 -0
  180. package/dist/tools/fs/bash-readonly-classifier.d.ts +74 -8
  181. package/dist/tools/fs/bash-readonly-classifier.js +171 -60
  182. package/dist/tools/fs/fs-bash.d.ts +14 -10
  183. package/dist/tools/fs/fs-bash.js +19 -27
  184. package/dist/tools/fs/fs-search-tools.js +5 -2
  185. package/dist/tools/fs/fs-shared.d.ts +2 -0
  186. package/dist/tools/fs/fs-shared.js +3 -11
  187. package/dist/tools/fs/index.d.ts +4 -0
  188. package/dist/tools/fs/index.js +4 -1
  189. package/dist/tools/fs/safety.d.ts +117 -1
  190. package/dist/tools/fs/safety.js +131 -31
  191. package/dist/tools/fs/search.js +8 -6
  192. package/package.json +2 -1
  193. package/test/export-surface.snapshot.json +115 -39
@@ -1,34 +1,26 @@
1
- import { isAbsolutePathForm } from "../tools/fs/safety.js";
2
- import { lexicalNormalAbsolutePathOf } from "./permission-rule-model.js";
1
+ import { isAbsoluteForFamily, joinForFamily, pathFamilyOf, pathIdentityOf, win32NamespaceScreen } from "../tools/fs/safety.js";
3
2
  import { pathTargetValue } from "./tool-registry.js";
4
3
  export function pathTargetBaseOf(pt, bases) {
5
4
  return (pt?.base ?? "cwd") === "root" ? bases.root : (bases.cwd ?? bases.root);
6
5
  }
7
- export function splitAbsoluteGlobPattern(pattern) {
8
- if (!isAbsolutePathForm(pattern))
6
+ export function splitAbsoluteGlobPattern(pattern, family) {
7
+ const screened = win32NamespaceScreen(pattern, family);
8
+ if (!screened.ok)
9
+ return { refused: screened.refuse };
10
+ const spelled = screened.spelled;
11
+ if (!isAbsoluteForFamily(family, spelled))
9
12
  return undefined;
10
- const m = /[*?[{]/.exec(pattern);
11
- if (!m) {
12
- const i = Math.max(pattern.lastIndexOf("/"), pattern.lastIndexOf("\\"));
13
- if (i <= 0)
14
- return undefined;
15
- return { baseDir: pattern.slice(0, i), relativePattern: pattern.slice(i + 1) };
16
- }
17
- const prefix = pattern.slice(0, m.index);
18
- const cut = Math.max(prefix.lastIndexOf("/"), prefix.lastIndexOf("\\"));
19
- if (cut === -1)
13
+ const lastSep = (s) => (family === "win" ? Math.max(s.lastIndexOf("/"), s.lastIndexOf("\\")) : s.lastIndexOf("/"));
14
+ const m = /[*?[{]/.exec(spelled);
15
+ const cut = m ? lastSep(spelled.slice(0, m.index)) : lastSep(spelled);
16
+ if (cut <= 0 && (m === null || cut === -1))
20
17
  return undefined;
21
- let baseDir = pattern.slice(0, cut);
22
- if (baseDir === "" && cut === 0)
18
+ let baseDir = spelled.slice(0, cut);
19
+ if (baseDir === "")
23
20
  baseDir = "/";
24
21
  if (/^[A-Za-z]:$/.test(baseDir))
25
- baseDir = baseDir + pattern[cut];
26
- if (!baseDir)
27
- return undefined;
28
- return { baseDir, relativePattern: pattern.slice(cut + 1) };
29
- }
30
- function under(base, rest) {
31
- return `${base === "/" ? "" : base}/${rest}`;
22
+ baseDir = baseDir + spelled[cut];
23
+ return { baseDir, relativePattern: spelled.slice(cut + 1) };
32
24
  }
33
25
  export function effectivePathTargetOf(call, pt, bases) {
34
26
  if (pt === undefined)
@@ -41,16 +33,36 @@ export function effectivePathTargetOf(call, pt, bases) {
41
33
  return undefined;
42
34
  if (pt.patternParam !== undefined) {
43
35
  const pattern = pathTargetValue(call.args, { param: pt.patternParam });
44
- const split = pattern === undefined ? undefined : splitAbsoluteGlobPattern(pattern);
36
+ const split = pattern === undefined ? undefined : splitAbsoluteGlobPattern(pattern, pathFamilyOf(bases));
37
+ if (split !== undefined && "refused" in split)
38
+ return { kind: "refused", reason: split.refused };
45
39
  if (split !== undefined)
46
40
  return resolveSpelling(split.baseDir, declaredBase, bases);
47
41
  }
48
- return declaredBase === undefined ? undefined : lexicalNormalAbsolutePathOf(declaredBase);
42
+ if (declaredBase === undefined)
43
+ return undefined;
44
+ const family = pathFamilyOf(bases);
45
+ return isAbsoluteForFamily(family, declaredBase) ? readingOf(pathIdentityOf(declaredBase, family, bases.root ?? bases.cwd)) : unreadableBase(declaredBase);
49
46
  }
50
47
  function resolveSpelling(spelled, declaredBase, bases) {
51
- if (spelled.startsWith("/"))
52
- return lexicalNormalAbsolutePathOf(spelled);
53
- const landsOn = isAbsolutePathForm(spelled) ? pathTargetBaseOf(undefined, bases) : declaredBase;
54
- const normalBase = landsOn === undefined ? undefined : lexicalNormalAbsolutePathOf(landsOn);
55
- return normalBase === undefined ? undefined : lexicalNormalAbsolutePathOf(under(normalBase, spelled));
48
+ const family = pathFamilyOf(bases);
49
+ const screened = win32NamespaceScreen(spelled, family);
50
+ if (!screened.ok)
51
+ return { kind: "refused", reason: screened.refuse };
52
+ const tree = bases.root ?? bases.cwd;
53
+ if (isAbsoluteForFamily(family, screened.spelled))
54
+ return readingOf(pathIdentityOf(screened.spelled, family, tree));
55
+ if (declaredBase === undefined || family === undefined)
56
+ return undefined;
57
+ if (!isAbsoluteForFamily(family, declaredBase))
58
+ return unreadableBase(declaredBase);
59
+ return readingOf(pathIdentityOf(joinForFamily(family, declaredBase, screened.spelled), family, tree));
60
+ }
61
+ function readingOf(r) {
62
+ if (r.ok)
63
+ return { kind: "path", identity: r.identity };
64
+ return r.reason === "relative" || r.reason === "unanchored" ? undefined : { kind: "refused", reason: r.reason };
65
+ }
66
+ function unreadableBase(base) {
67
+ return { kind: "unreadable", reason: `the base this call resolves against (${JSON.stringify(base)}) is not a fully-qualified path of this tree's family` };
56
68
  }
@@ -138,18 +138,24 @@ export interface EngineNotice {
138
138
  * `onNotice` sink per value (console arm once per process) instead of lying in wait; where the
139
139
  * value IS in force the prepare refuses with the same code as `TaskResult.errorCode` (one
140
140
  * fact, one code, two loudness dialects); `detail: { raw }`.
141
- * - `"config.durable_gate_unavailable"` — the per-principal `forceDurableGate` entitlement is in
142
- * force on a leg whose deployment wired NO checkpoint store. The entitlement is a mandate
143
- * ("every interactive ask of this principal gates durably"), and a mandate with no park
144
- * facility cannot be honored: no ask of the leg can suspend, so each one resolves on the LIVE
141
+ * - `"config.artifact_host_invalid"` — `RunnerDeps.artifactHost` is wired but its `capabilities()`
142
+ * declaration fails one of the seat's invariants (see `ArtifactHostCapabilities`), or its `onMount`
143
+ * threw: the `Artifact` tool is not mounted for the task and this is the loud half (an absent seat
144
+ * is silent; a broken one must not look the same). Once per prepared leg;
145
+ * `detail: { problem, sessionId, runId }`.
146
+ * - `"config.durable_gate_unavailable"` — a durable-approval MANDATE is in force on a leg with NO
147
+ * checkpoint store: the per-principal `forceDurableGate` entitlement, or the task's own
148
+ * `durableApproval` opt-in with no live approver seat (`durableMandateSourceOf`, the predicate the
149
+ * leg's children inherit). A mandate with no park facility cannot be honored: no ask of the leg can suspend, so each one resolves on the LIVE
145
150
  * chain — a live approver / question face answers in-stream, and with none the ask is denied
146
151
  * fail-closed — and NO durable approval record is written. The ask routing itself is not
147
152
  * changed by the notice (a store-less leg never could park); the notice is the loud half of a
148
153
  * posture that used to degrade silently, disclosed before the first ask of the leg. Audience
149
154
  * `"user"` (the person whose asks will not be recorded is entitled to that; the operator hears
150
155
  * it through the sink as always). Once per prepared task leg — a resume or a delegated child
151
- * is its own leg and says so again; `detail: { sessionId, runId, principal?, cause, liveApprover,
152
- * liveQuestionFace }` — `cause` is `"no_deployment_store"` (the deployment wired none) or
156
+ * is its own leg and says so again; `detail: { sessionId, runId, principal?, cause, source,
157
+ * liveApprover, liveQuestionFace }` — `source` is `"force_durable_gate"` or `"durable_approval"`
158
+ * (which mandate is unhonored; the recovery differs); `cause` is `"no_deployment_store"` (the deployment wired none) or
153
159
  * `"task_store_disabled"` (`TaskSpec.checkpointStore: "disabled"`, the per-run off switch, on a leg
154
160
  * whose deployment may well have a store: the fix is the task's); `liveApprover` /
155
161
  * `liveQuestionFace`: whether a live seat will EFFECTIVELY answer the leg's permission asks /
@@ -195,10 +201,12 @@ export interface EngineNotice {
195
201
  * `detail: { cause: "user_halt", sessionId, runId, taskId? }` — no `inputId` and no
196
202
  * `actorId`, because no text entered the model and the verb carries no caller identity. A
197
203
  * consumer keying `detail.inputId` off this row must treat it as ABSENT on this lane.
198
- * Fence tension (design/384 slice 2): this notice asserts THE CUT only — when the cut
199
- * raced a durable park whose commit was already in flight, the gate's durable leg may
200
- * still collect to `suspended` (park wins, row redeemable, no `haltedByUser`); the notice
201
- * stands beside that terminal without contradiction, because a seat really was cut.
204
+ * Fence tension: this notice asserts THE CUT only — when the cut
205
+ * raced an approval park whose commit was already in flight, the gate's durable leg may
206
+ * still collect to `suspended` (park wins, row redeemable, the park's abort owns the
207
+ * ending, no `haltedByUser`, plus a `task.halt_unconsumed`); the notice stands beside that
208
+ * terminal without contradiction, because a seat really was cut — `haltedByUser` answers
209
+ * the other question (was the halt CONSUMED), see that seat.
202
210
  * In both lanes: one notice per REAL cut (a `now` that found nothing in flight or whose frame
203
211
  * already rode the imminent boundary, and a halt with nothing in flight, announce nothing — no
204
212
  * false interrupt claims); `runId` (#499) is the INVOCATION that was cut (the other two ids are
@@ -209,17 +217,21 @@ export interface EngineNotice {
209
217
  * implemented, so the unhonored-knob disclosure it carried has no referent; consumers must
210
218
  * judge ladder support by VERSION, never by that code's absence.
211
219
  *
212
- * - `"task.halt_unconsumed"` (design/384 slice 1) — the user's stop verb ({@link TaskStream.halt})
213
- * was ANSWERED (not refused) while the run's ending was already owned by its own abort: nothing
214
- * was cut and nothing was stopped by the halt, the run's own ending stands, and the result will
215
- * NOT carry `haltedByUser` for it (the attribution seat never signs someone else's stop). This
216
- * notice is the halt's only trace on that arm without it the verb receipt (`{turnCut:false}`)
217
- * was the sole record and the caller could not tell "my stop did nothing because the run beat
218
- * me to ending" from "my stop is being honored at the next boundary". Minted once per such verb
219
- * call (each arrival is a distinct fact); `detail: { sessionId, runId, taskId? }` `runId` is
220
- * the run whose ending the halt failed to claim. Audience `"user"` (the person who pressed
221
- * stop). A REFUSED halt (typed `steering.not_running` throw) mints nothing: the refusal itself
222
- * is the loud answer.
220
+ * - `"task.halt_unconsumed"` — the user's stop verb ({@link TaskStream.halt}) was ANSWERED (not
221
+ * refused) but the run's ending was not its doing: the run's own ending stands and the result will
222
+ * NOT carry `haltedByUser` for it (the seat signs consumption, never acceptance). TWO mint points,
223
+ * one fact, one spelling, disjoint: the VERB arm the halt arrived while the run's ending was already
224
+ * owned by its own abort (nothing cut, nothing stopped; once per such verb call, and only while no halt
225
+ * has been ACCEPTED on the run after one was, the settle arm alone speaks for the run);
226
+ * the SETTLE arm the halt was ACCEPTED (it may even have cut a live seat: `{turnCut:true}` +
227
+ * `task.turn_interrupted`) but the run ended for its own reason before the machinery consumed it
228
+ * no interrupt settlement took the halt's cut as its cause and no boundary consult stopped the loop
229
+ * for it (a brain minting its own `aborted` final beside a late halt, a natural completion the halt
230
+ * arrived too late to prevent, an approval park whose commit won the race and whose abort owned the
231
+ * ending); once per such run, at the result's settlement. Either way the notice is the halt's trace
232
+ * beside the verb receipt. `detail: { sessionId, runId, taskId? }` — `runId` is the run whose ending
233
+ * the halt failed to claim. Audience `"user"`. A REFUSED halt (typed `steering.not_running` throw)
234
+ * mints nothing: the refusal itself is the loud answer.
223
235
  *
224
236
  * - `"task.late_approval"` (design/384 slice 1) — a synchronous ask approval was NOT CONSUMED:
225
237
  * a run or turn interrupt released the wait (the `resolveAsk` race arm detached the approver's
@@ -514,17 +526,19 @@ export interface EngineNotice {
514
526
  * access scope of the read that found the gap — from the continuation read faces (SendMessage preflight /
515
527
  * AgentTranscript's durable leg); the per-call honest refusals are unchanged, and the declared
516
528
  * tier is NOT auto-downgraded (declaration-制 — observation reports, it never re-adjudicates).
517
- * - `"delegation.ask_unresolvable"` (#648) — an `ask` reached its FINAL deny with nobody having judged
518
- * it: the approver consulted for the call (the run's own seat, or an ancestor's frozen seat on a
519
- * delegated child — the sync / workflow lanes, or a background child outside the park family)
520
- * answered `unavailable` (no operator reachable for this ask) and no durable approval park took
521
- * the call afterwards. The deny itself is unchanged (`tool_end.gate.settlement.kind:
522
- * "approver_unavailable"`, the fail-closed sentence as the tool result); this is its person-facing
523
- * half. Minted at the one gate exit both unavailable deny arms reach, once per tool call, by
524
- * `ask-unresolvable-notice.ts`. `detail: { sessionId, toolName, toolCallId, settlementKind,
525
- * parkLaneExisted }` `parkLaneExisted` says whether a park lane was armed and declined/failed
526
- * (`true`) or none existed (`false`, the usual shape). Audience `"user"`: the refused work is the
527
- * session user's own; the operator hears it through the sink like every code.
529
+ * - `"delegation.ask_unresolvable"` — an `ask` reached its FINAL deny with nobody having judged it:
530
+ * the call's approval could not be RESOLVED. `detail.cause` (closed set `ASK_UNRESOLVABLE_CAUSES`):
531
+ * `approver_unavailable` — the approver consulted (the run's own seat, or an ancestor's frozen seat
532
+ * on a delegated child) answered `unavailable` and no park took the call (`tool_end.gate.settlement
533
+ * .kind:"approver_unavailable"`); `no_approver_wired` no approver / question face is wired, or the
534
+ * seat is the deny posture (the headless auto-deny, `settlement.kind:"no_approver"`);
535
+ * `mandate_unreconstructible` an inherited durable mandate binds the ask and this leg has no park
536
+ * facility (an in-fold deny, no settlement on the frame). The deny itself is unchanged (the
537
+ * fail-closed sentence as the tool result); this is its person-facing half, minted at the gate's ONE
538
+ * deny exit, once per tool call, by `ask-unresolvable-notice.ts`. `detail: { sessionId, toolName,
539
+ * toolCallId, cause, parkLaneExisted }` `parkLaneExisted` says whether a park lane was armed and
540
+ * declined/failed (`true`) or none existed (`false`, the usual shape). Audience `"user"`: the refused
541
+ * work is the session user's own; the operator hears it through the sink like every code.
528
542
  *
529
543
  * - `"memory.consolidation_recommended"` (design/339 §2.2/§6.2) — the engine-minted per-scope
530
544
  * session count crossed the consolidation thresholds (time gate open ∧ enough distinct
@@ -0,0 +1,31 @@
1
+ /**
2
+ * The whole-number env grammar, with one deliberate narrowing against the reference harness: no
3
+ * `parseInt` prefix leg, so `"30s"` is a REJECTION rather than `30` — "a typo silently becomes a budget" is
4
+ * the class of defect this chokepoint exists to close, and the prefix leg is survivable upstream only
5
+ * because its clamp floor catches the result. What IS accepted is what an operator plausibly means: plain
6
+ * digits (an explicit sign included), scientific notation, and digit grouping — the last one because this
7
+ * repository writes its own constants that way (`100_000_000`), and copying a source literal into the
8
+ * environment must not silently become `100`. Hex, octal and every other spelling are rejected.
9
+ */
10
+ export declare function parseWholeNumber(raw: string): number | undefined;
11
+ /** What a knob is a budget OF — the unit word the invalid-value announcement names. */
12
+ export type EnvBudgetUnit = "characters" | "tokens" | "milliseconds";
13
+ /** One knob's contract: its name, its unit, the value in force when it is absent or invalid (`undefined`
14
+ * = "a downstream built-in default stands", the SDK-timeout shape), and the range it is held to. */
15
+ export interface EnvBudgetSpec<D extends number | undefined> {
16
+ readonly name: string;
17
+ readonly unit: EnvBudgetUnit;
18
+ readonly default: D;
19
+ /** Inclusive floor; a smaller value is raised to it, loudly. */
20
+ readonly min?: number;
21
+ /** Inclusive ceiling; a larger value is capped to it, loudly. */
22
+ readonly max?: number;
23
+ }
24
+ /**
25
+ * Resolve one knob from its raw environment string (see the module note for the four states).
26
+ *
27
+ * @contract env_budget.single_resolver — every process-level numeric env knob resolves through this
28
+ * function: one grammar, one range rule, one loud outlet. A knob resolved elsewhere is a fork of this
29
+ * contract, not a knob.
30
+ */
31
+ export declare function resolveEnvBudget<D extends number | undefined>(rawEnv: string | undefined, spec: EnvBudgetSpec<D>): number | D;
@@ -0,0 +1,39 @@
1
+ import { announceEnvKnob } from "./env-knob-announce.js";
2
+ const ENV_SCIENTIFIC_RE = /^[+-]?(\d+(\.\d*)?|\.\d+)[eE][+-]?\d+$/;
3
+ const ENV_GROUPED_RE = /^[+-]?\d{1,3}([_,\u00A0\u202F ])\d{3}(?:\1\d{3})*$/;
4
+ const ENV_GROUP_SEPARATORS_RE = /[_,\u00A0\u202F ]/g;
5
+ const ENV_NUMERIC_MAX_LEN = 32;
6
+ export function parseWholeNumber(raw) {
7
+ if (/^[+-]?\d+$/.test(raw)) {
8
+ const n = Number(raw);
9
+ return Number.isSafeInteger(n) ? n : undefined;
10
+ }
11
+ if (raw.length > ENV_NUMERIC_MAX_LEN)
12
+ return undefined;
13
+ if (ENV_SCIENTIFIC_RE.test(raw)) {
14
+ const n = Number(raw);
15
+ return Number.isSafeInteger(n) ? n : undefined;
16
+ }
17
+ if (ENV_GROUPED_RE.test(raw))
18
+ return parseInt(raw.replace(ENV_GROUP_SEPARATORS_RE, ""), 10);
19
+ return undefined;
20
+ }
21
+ export function resolveEnvBudget(rawEnv, spec) {
22
+ if (rawEnv === undefined || rawEnv.trim() === "")
23
+ return spec.default;
24
+ const raw = rawEnv.trim();
25
+ const n = parseWholeNumber(raw);
26
+ if (n === undefined || n <= 0) {
27
+ announceEnvKnob(`${spec.name}=${rawEnv} was ignored — it is not a positive whole number of ${spec.unit}. Using ${spec.default === undefined ? "the built-in default" : spec.default} instead.`);
28
+ return spec.default;
29
+ }
30
+ if (spec.max !== undefined && n > spec.max) {
31
+ announceEnvKnob(`${spec.name}=${rawEnv} Capped from ${n} to ${spec.max}.`);
32
+ return spec.max;
33
+ }
34
+ if (spec.min !== undefined && n < spec.min) {
35
+ announceEnvKnob(`${spec.name}=${rawEnv} Raised from ${n} to ${spec.min}.`);
36
+ return spec.min;
37
+ }
38
+ return n;
39
+ }
@@ -35,18 +35,18 @@ export declare class RollingTailBuffer {
35
35
  text: string;
36
36
  droppedBytes: number;
37
37
  };
38
- /** RB-234: the retained window's RAW bytes — cursor slicing must happen in the byte domain
38
+ /** The retained window's RAW bytes — cursor slicing must happen in the byte domain
39
39
  * (re-encoding a lossily-decoded string inflates every invalid byte to a 3-byte U+FFFD and lands
40
40
  * byte cursors on the wrong offsets). */
41
41
  bytes(): Buffer;
42
- /** RB-234: head-eviction count ALONE — the retention window is [headDroppedBytes, pushed-total)
42
+ /** Head-eviction count ALONE — the retention window is [headDroppedBytes, pushed-total)
43
43
  * in the accepted-byte domain; source skips must not shift this origin. */
44
44
  headDroppedBytes(): number;
45
- /** RB-234: source-skip count alone (see {@link recordSkippedBytes}) — poll disclosure reports
45
+ /** Source-skip count alone (see {@link recordSkippedBytes}) — poll disclosure reports
46
46
  * the per-poll increment of this figure instead of silently absorbing it into the window origin. */
47
47
  sourceSkippedBytes(): number;
48
48
  }
49
- /** RB-234: per-stream cursor state for incremental (poll-style) consumption of a
49
+ /** Per-stream cursor state for incremental (poll-style) consumption of a
50
50
  * {@link RollingTailBuffer}. All counters live in the ACCEPTED-byte domain (bytes actually pushed);
51
51
  * source skips are tracked separately for disclosure. Module-level export (not on the package
52
52
  * surface) so the slice arithmetic is unit-pinnable without an 8MB integration harness. */
@@ -65,7 +65,7 @@ export interface StreamCursorState {
65
65
  /** Fresh cursor state over a new tail. */
66
66
  export declare function newStreamCursorState(tail?: RollingTailBuffer): StreamCursorState;
67
67
  /**
68
- * RB-234: slice the undelivered increment out of a rolling tail — in the BYTE domain, decoded
68
+ * Slice the undelivered increment out of a rolling tail — in the BYTE domain, decoded
69
69
  * incrementally. Replaces the former decode→re-encode→byte-offset-subarray shape, which mixed two
70
70
  * domains: byte cursors landed inside a re-encoded string whose length differs from the raw stream as
71
71
  * soon as any invalid-UTF-8 byte sits in the window (each inflates to a 3-byte U+FFFD), making every
@@ -1,5 +1,5 @@
1
1
  import { createHash } from "node:crypto";
2
- import { isAbsolutePathForm, isWinFormPath } from "../tools/fs/safety.js";
2
+ import { isAbsoluteForFamily, isAbsolutePathForm, isWinFormPath, pathFamilyOf } from "../tools/fs/safety.js";
3
3
  import { fileHistoryBoundariesToKeep, resolveFileHistoryRetention } from "./file-history-retention.js";
4
4
  const normSlash = (p) => p.replace(/\\/g, "/");
5
5
  const stripTrailSep = (p) => (isWinFormPath(p) ? p.replace(/[\\/]+$/, "") : p.replace(/\/+$/, ""));
@@ -21,9 +21,6 @@ function joinUnder(root, rel) {
21
21
  function isUnsafeRelPathLike(relN) {
22
22
  return isAbsolutePathForm(relN) || /^[A-Za-z]:/.test(relN) || relN.split("/").includes("..");
23
23
  }
24
- function isAbsoluteForFamily(root, p) {
25
- return isWinFormPath(root) ? isAbsolutePathForm(p) : p.startsWith("/");
26
- }
27
24
  function parentDirOf(absPath) {
28
25
  const win = isWinFormPath(absPath);
29
26
  const norm = win ? normSlash(absPath) : absPath;
@@ -47,7 +44,7 @@ export function trackKeyOf(root, absPath) {
47
44
  return absPath;
48
45
  const win = isWinFormPath(root);
49
46
  const relN = win ? normSlash(rel) : rel;
50
- if (win ? isUnsafeRelPathLike(relN) : isAbsoluteForFamily(root, relN) || relN.split("/").includes(".."))
47
+ if (win ? isUnsafeRelPathLike(relN) : isAbsoluteForFamily("posix", relN) || relN.split("/").includes(".."))
51
48
  return absPath;
52
49
  return rel;
53
50
  }
@@ -62,7 +59,7 @@ export function resolveTrackKey(root, key, minted) {
62
59
  }
63
60
  }
64
61
  const keyN = win ? normSlash(key) : key;
65
- if (isAbsoluteForFamily(root, keyN) || (win && /^[A-Za-z]:/.test(keyN)))
62
+ if (isAbsoluteForFamily(pathFamilyOf({ root }), keyN) || (win && /^[A-Za-z]:/.test(keyN)))
66
63
  return { ok: true, abs: key };
67
64
  if (win ? isUnsafeRelPathLike(keyN) : keyN.split("/").includes(".."))
68
65
  return { ok: false, reason: `unsafe recorded key escapes root: ${key}` };
@@ -11,7 +11,7 @@ export interface FsWriteGatePolicyOptions {
11
11
  /** The task's real execution env (the one the hands run against) — canonicalization must see the
12
12
  * actual filesystem (a remote/E2B task resolves in its container). */
13
13
  env: ExecutionEnv;
14
- /** The FALLBACK base a relative path resolves against (omit = paths resolve as given). RB-108: the
14
+ /** The FALLBACK base a relative path resolves against (omit = paths resolve as given). The
15
15
  * call's TARGET prefers the live tracked cwd when the engine stamps one ({@link ToolCallRequest.cwd});
16
16
  * the `acceptDirs`/`exemptDirs` entries below always resolve against THIS value. */
17
17
  rootPath?: string;
@@ -1,4 +1,4 @@
1
- import { canonicalizeTarget, writeTargetPath } from "../tools/fs/safety.js";
1
+ import { canonicalizeTarget, pathFamilyOf, writeTargetPath } from "../tools/fs/safety.js";
2
2
  import { isWithin } from "./runner/session-rule-policy.js";
3
3
  import { declaredPathTargetOf, isProtectedWrite } from "./tool-registry.js";
4
4
  import { pathTargetBaseOf } from "./effective-path-target.js";
@@ -27,7 +27,7 @@ export function createFsWriteGatePolicy(opts) {
27
27
  if (path === undefined) {
28
28
  return ask(`write tool "${req.toolName}" requires approval: the call has no resolvable path target to confine${declared === undefined ? " (the tool declares no path target)" : ""}${advisory}`);
29
29
  }
30
- const canon = await canonicalizeTarget(env, path, signal, pathTargetBaseOf(declared, { root: rootPath, cwd: req.cwd }));
30
+ const canon = await canonicalizeTarget(env, path, signal, pathFamilyOf({ root: rootPath, cwd: req.cwd ?? env.cwd }), pathTargetBaseOf(declared, { root: rootPath, cwd: req.cwd }));
31
31
  if (!canon.ok) {
32
32
  return ask(`write to "${path}" requires approval: its real target could not be resolved (${canon.message})${advisory}`);
33
33
  }
@@ -35,7 +35,7 @@ export function createFsWriteGatePolicy(opts) {
35
35
  if (!dirs)
36
36
  continue;
37
37
  for (const raw of dirs) {
38
- const dir = await canonicalizeTarget(env, raw, signal, rootPath);
38
+ const dir = await canonicalizeTarget(env, raw, signal, pathFamilyOf({ root: rootPath ?? env.cwd }), rootPath);
39
39
  if (dir.ok && isWithin(dir.key, canon.key))
40
40
  return { action: "allow" };
41
41
  }
@@ -1,5 +1,5 @@
1
1
  import { attestClassifierDenyCause, classifierDenyCauseOf, decisionText, describeThrown } from "./tool-policy.js";
2
- import { askOriginOf, classifierMayAnswer, persistedRuleMandateOf, probeBoundaryMandate } from "./ask-origin.js";
2
+ import { askOriginOf, classifierMayAnswer, persistedRuleMandateOf, probeBoundaryMandate, safetyAxisOf } from "./ask-origin.js";
3
3
  import { CLASSIFIER_PARSE_FAILURE_DENY_MESSAGE, classifierUnavailableDenyMessage, denialLimitFallbackMessage, unarmedWindow } from "./auto-mode.js";
4
4
  import { inlineUntrusted } from "./untrusted-text.js";
5
5
  import { isRuleBehavior } from "./permission-rule-model.js";
@@ -439,9 +439,14 @@ export async function runGateLanes(pass) {
439
439
  };
440
440
  }
441
441
  }
442
- const egressTool = input.egress === true;
443
- const irreversibleTool = input.irreversibility === "always" || input.irreversibility === "maybe";
444
- const safety = egressTool || irreversibleTool ? { egress: egressTool, irreversible: irreversibleTool } : undefined;
442
+ const safety = safetyAxisOf({
443
+ egress: input.egress,
444
+ shellGated: input.shellGated,
445
+ irreversibility: input.irreversibility,
446
+ probeMandated: pass.decision.action === "ask" && pass.decision.probeMandated === true,
447
+ probeUnanswered: pass.probeUnanswered === true,
448
+ tightened: pass.tightenedBy,
449
+ });
445
450
  const realApprovalOf = (d) => {
446
451
  if (d.action !== "ask" || d.requiresRealApproval !== true)
447
452
  return undefined;
@@ -521,16 +526,6 @@ export async function runGateLanes(pass) {
521
526
  return { suspend: suspended, preToolContext: pass.preToolContext };
522
527
  }
523
528
  }
524
- const noteUnresolvable = (r) => {
525
- if (r.action !== "deny" || r.approverUnavailable !== true)
526
- return;
527
- try {
528
- input.onAskUnresolvable?.({ toolName, toolCallId, settlementKind: "approver_unavailable", parkLaneExisted: suspendAsk !== undefined });
529
- }
530
- catch {
531
- }
532
- };
533
- noteUnresolvable(resolved);
534
529
  if (askBeforeResolve.action === "ask" && askBeforeResolve.denialLimitFallback !== undefined) {
535
530
  if (pass.decision.action === "allow") {
536
531
  input.autoMode?.denialTracking?.recordAllow();
@@ -666,7 +661,6 @@ export async function runGateLanes(pass) {
666
661
  const editAskReq = { toolName, args: editArgs, toolCallId, ...(pass.req.face !== undefined ? { face: pass.req.face } : {}) };
667
662
  const rr = await (callSignal !== undefined ? resolveAsk(editAskDecision, editAskReq, callSignal) : resolveAsk(editAskDecision, editAskReq));
668
663
  ledger.fromResolved(rr, editAskDecision, toolName);
669
- noteUnresolvable(rr);
670
664
  if (rr.action !== "allow") {
671
665
  editDenied = rr;
672
666
  pass.deniedBy = "ask_resolution";
@@ -104,7 +104,7 @@ export type NoticeAudience = "user" | "operator";
104
104
  * src/ for notice mint shapes and names any code that is minted but unregistered, or registered but
105
105
  * no longer minted.
106
106
  */
107
- export declare const ENGINE_NOTICE_CODES: readonly ["config.autocompact_window_clamped", "config.env_timeout_discarded", "config.materialize_env_discarded", "config.models_swapped", "config.read_face_swapped", "config.read_face_deployment_clamped", "config.tool_model_gate_removed", "config.tool_model_gate_unknown_class", "config.tool_model_gate_env_invalid", "config.tool_card_undeclared", "config.tool_face_undeclared", "config.tool_face_invalid", "config.durable_gate_unavailable", "config.peer_admission_out_of_range", "config.peer_lane_unmounted", "peer.inbound_disposition", "peer.held_settled", "peer.idle_subscription", "classifier.denial_limit", "checkpoint.execution_outcome_unrecorded", "delegation.transcript_integrity", "delegation.ask_unresolvable", "mcp.injection_dropped", "mcp.revocation_probe_failed", "workflow.governance_key_stripped", "workflow.agent_option_ignored", "memory.session_polluted", "memory.harvest_quarantined", "memory.delegation_static_mark_waived", "memory.content_class_declared", "memory.hold_opened", "memory.hold_released", "memory.hold_disposed", "memory.consolidation_recommended", "memory.consolidation_committed", "memory.consolidation_conflict", "memory.consolidation_incomplete", "memory.consolidation_refused", "memory.consolidation_withheld", "route.fallback_to_primary", "route.base_url_changed_key_unchanged", "task.user_steer_undrained", "task.user_followup_undrained", "steering.parked_input_blocked", "task.turn_interrupted", "task.halt_unconsumed", "task.late_approval", "task.interrupt_unconsumed", "memory.capture_opted_out", "memory.capture_optout_unpersisted", "tool_result.offload_put_failed"];
107
+ export declare const ENGINE_NOTICE_CODES: readonly ["config.autocompact_window_clamped", "config.env_timeout_discarded", "config.materialize_env_discarded", "config.models_swapped", "config.read_face_swapped", "config.read_face_deployment_clamped", "config.tool_model_gate_removed", "config.tool_model_gate_unknown_class", "config.tool_model_gate_env_invalid", "config.artifact_host_invalid", "config.tool_card_undeclared", "config.tool_face_undeclared", "config.tool_face_invalid", "config.durable_gate_unavailable", "config.peer_admission_out_of_range", "config.peer_lane_unmounted", "peer.inbound_disposition", "peer.held_settled", "peer.idle_subscription", "classifier.denial_limit", "checkpoint.execution_outcome_unrecorded", "delegation.transcript_integrity", "delegation.ask_unresolvable", "mcp.injection_dropped", "mcp.revocation_probe_failed", "workflow.governance_key_stripped", "workflow.agent_option_ignored", "memory.session_polluted", "memory.harvest_quarantined", "memory.delegation_static_mark_waived", "memory.content_class_declared", "memory.hold_opened", "memory.hold_released", "memory.hold_disposed", "memory.consolidation_recommended", "memory.consolidation_committed", "memory.consolidation_conflict", "memory.consolidation_incomplete", "memory.consolidation_refused", "memory.consolidation_withheld", "route.fallback_to_primary", "route.base_url_changed_key_unchanged", "task.user_steer_undrained", "task.user_followup_undrained", "steering.parked_input_blocked", "task.turn_interrupted", "task.halt_unconsumed", "task.late_approval", "task.interrupt_unconsumed", "memory.capture_opted_out", "memory.capture_optout_unpersisted", "tool_result.offload_put_failed"];
108
108
  /** A code this engine mints (see {@link ENGINE_NOTICE_CODES}). NOT the type of
109
109
  * `EngineNotice.code`, which stays `string` — a host forwarding its own notices through the same
110
110
  * sink is a supported shape, and narrowing that field would break it. */
@@ -105,6 +105,7 @@ export const ENGINE_NOTICE_CODES = [
105
105
  "config.tool_model_gate_removed",
106
106
  "config.tool_model_gate_unknown_class",
107
107
  "config.tool_model_gate_env_invalid",
108
+ "config.artifact_host_invalid",
108
109
  "config.tool_card_undeclared",
109
110
  "config.tool_face_undeclared",
110
111
  "config.tool_face_invalid",
@@ -177,6 +178,7 @@ const NOTICE_AUDIENCE_TABLE = {
177
178
  "config.tool_model_gate_removed": "operator",
178
179
  "config.tool_model_gate_unknown_class": "operator",
179
180
  "config.tool_model_gate_env_invalid": "operator",
181
+ "config.artifact_host_invalid": "operator",
180
182
  "config.tool_card_undeclared": "operator",
181
183
  "config.tool_face_undeclared": "operator",
182
184
  "config.tool_face_invalid": "operator",
@@ -46,7 +46,7 @@ export interface TrackEditRequest {
46
46
  export type TrackEditResult = {
47
47
  ok: true;
48
48
  /**
49
- * #491 — the compensating handle for the ordering the topology cannot avoid: the pre-image
49
+ * The compensating handle for the ordering the topology cannot avoid: the pre-image
50
50
  * capture runs BEFORE the write, so a write that then FAILS (lost create race, ENOSPC,
51
51
  * EACCES, read-only mount) would leave a first-touch record for an edit that never happened —
52
52
  * and an `existed-not` record makes an untouched path a rewind DELETE target, removing bytes
@@ -533,7 +533,7 @@ export interface StopHookContext {
533
533
  /** Consecutive blocks so far in this run (resets when a stop() call lets the run proceed). */
534
534
  consecutiveBlocks: number;
535
535
  /**
536
- * RB-177 (measured downstream) — a view of the conversation this hook is being asked to judge.
536
+ * A view of the conversation this hook is being asked to judge.
537
537
  *
538
538
  * Until now a Stop hook received two counters and nothing else. That is enough for a hook that asks
539
539
  * "have I blocked too often?", and structurally insufficient for the entire class of hooks that ask
@@ -1195,13 +1195,13 @@ export interface ToolGateInput {
1195
1195
  * decline/pre-commit-failure paths keep their existing `undefined` fallbacks (the gate then keeps the
1196
1196
  * fail-closed deny / typed refusal the caller carries). */
1197
1197
  liveFaceUnavailable?: boolean,
1198
- /** #130: present ⇒ the surviving ask carries `requiresRealApproval` and the mint must escalate to
1198
+ /** Present ⇒ the surviving ask carries `requiresRealApproval` and the mint must escalate to
1199
1199
  * the non-budgetable `irreversible_ask` kind carrying this bit (a budget resolver must never
1200
1200
  * auto-approve what only judgment may clear). `origin` records whether the bit came from an org
1201
1201
  * ASK rule, from the org-unavailable tighten (whose resume semantics differ — see
1202
1202
  * {@link import("./checkpoint-store.js").RealApprovalGateBit}), or from a policy/hook. */
1203
1203
  realApproval?: import("./checkpoint-store.js").RealApprovalGateBit,
1204
- /** #144: the matched-but-outranked persisted rule (the surviving ask's `persistedRuleShadowed`)
1204
+ /** The matched-but-outranked persisted rule (the surviving ask's `persistedRuleShadowed`)
1205
1205
  * — threaded so the park mint's risk descriptor carries the disclosure on the durable route. */
1206
1206
  shadowedRule?: string,
1207
1207
  /** The surviving ask's `decisionReason` — threaded so the park mint's rule-suggestion door can
@@ -1209,11 +1209,11 @@ export interface ToolGateInput {
1209
1209
  * decision (a hook-raised ask is one no persisted rule may clear, so its parked card must not
1210
1210
  * offer to mint one). */
1211
1211
  askDecisionReason?: DecisionReason,
1212
- /** backlog #239: the reversibility probe's cause for the tighten (the surviving ask's `probeReason`)
1212
+ /** The reversibility probe's cause for the tighten (the surviving ask's `probeReason`)
1213
1213
  * — threaded so the park mint's risk descriptor carries on the durable route the same account of
1214
1214
  * WHY the call was gated that the synchronous card gets. Already neutralized + capped at the gate. */
1215
1215
  probeReason?: string,
1216
- /** backlog #239: the STRUCTURED cause (the surviving ask's `probeCause`) — same durable-route errand
1216
+ /** The STRUCTURED cause (the surviving ask's `probeCause`) — same durable-route errand
1217
1217
  * as `probeReason`, already validated at the gate. */
1218
1218
  probeCause?: import("./checkpoint-store.js").ProbeCause,
1219
1219
  /** design/375 §5.2②: the surviving ask's engine-stamped `segmentCoverage` — threaded so the park
@@ -1229,7 +1229,7 @@ export interface ToolGateInput {
1229
1229
  * logical neighbour would silently re-bind three existing call sites' arguments with no type
1230
1230
  * error to catch it. */
1231
1231
  matchedAskRule?: string,
1232
- /** #502: the surviving ask's engine-stamped `probeMandated` — threaded for the SAME errand as
1232
+ /** The surviving ask's engine-stamped `probeMandated` — threaded for the SAME errand as
1233
1233
  * `askDecisionReason` and `matchedAskRule`: the park mint's rule-suggestion door must know that
1234
1234
  * this ask is one no persisted rule may clear, or the parked card offers a "stop asking me this"
1235
1235
  * rule the lane would then refuse to honor. APPENDED at the tail for the reason its neighbour
@@ -1308,12 +1308,12 @@ export interface ToolGateInput {
1308
1308
  * claim) or a durable suspend (the ask path). runToolGate still try/catches defensively: an observer
1309
1309
  * must never alter the deny outcome.
1310
1310
  *
1311
- * RB-442: ALSO fired for the engine's fail-closed interception of a THROWING PreToolUse hook, with
1311
+ * ALSO fired for the engine's fail-closed interception of a THROWING PreToolUse hook, with
1312
1312
  * `source:"hook"` — the exclusion is the hook's own deny DECISION, not an engine decision.
1313
1313
  */
1314
1314
  permissionDenied?: (payload: PermissionDeniedPayload) => void | Promise<void>;
1315
1315
  /**
1316
- * RB-442: trace sink for a PreToolUse hook that THREW — the deployment-facing half of the crash
1316
+ * Trace sink for a PreToolUse hook that THREW — the deployment-facing half of the crash
1317
1317
  * (`onError(phase:"hook")`, wired by the caller). The model-facing half is the fail-closed block reason;
1318
1318
  * this one carries the exception object itself to whoever runs the deployment, because a crashing hook is
1319
1319
  * a bug someone has to fix and the model-facing summary is bounded/sanitized. Never affects the outcome
@@ -1346,7 +1346,7 @@ export interface ToolGateInput {
1346
1346
  */
1347
1347
  peerMessage?: boolean;
1348
1348
  /**
1349
- * backlog #286 (CC 2.1.233 `DANGEROUS_*` parity): the write-protection judge — ENGINE-BUILT from
1349
+ * The write-protection judge — ENGINE-BUILT from
1350
1350
  * the deployment's table seat by `createWriteProtectionCheck` (write-protect.ts), never a
1351
1351
  * deployment callback (the deployment authors table ROWS, which are validated loudly at compile;
1352
1352
  * the judge itself is pure and synchronous, so it is trusted here like the `egress` mark). Judges
@@ -1381,7 +1381,7 @@ export interface ToolGateInput {
1381
1381
  autoMode?: {
1382
1382
  decider: import("./auto-mode.js").AutoModeDecider;
1383
1383
  /**
1384
- * #548 — the per-run DENIAL-LIMIT tracker beside the decider (CC 2.1.250 `FO`/`Wie`; same owner,
1384
+ * The per-run DENIAL-LIMIT tracker beside the decider (CC 2.1.250 `FO`/`Wie`; same owner,
1385
1385
  * same lifetime). Absent ⇒ the pre-#548 chain byte for byte: every block is a deny, without bound.
1386
1386
  * Present ⇒ a block first counts, then judges; the block that reaches a bound becomes the fallback
1387
1387
  * ask (`requiresRealApproval: true` + `denialLimitFallback`), and a classifier allow or a person's
@@ -1391,7 +1391,7 @@ export interface ToolGateInput {
1391
1391
  denialTracking?: AutoModeDenialTracker;
1392
1392
  };
1393
1393
  /**
1394
- * #548 — the HEADLESS arm of the denial-limit fallback: the fallback ask was resolved by a deny no
1394
+ * The HEADLESS arm of the denial-limit fallback: the fallback ask was resolved by a deny no
1395
1395
  * person made (no approver wired; a blanket `"allow"` refused as no approver; an approver that
1396
1396
  * reported unavailable with no park to take it). The fallback had nowhere to go, so the caller — which
1397
1397
  * owns the run — stops it (`TaskResult.errorCode = "classifier.denial_limit"`, CC's "too many
@@ -1407,17 +1407,18 @@ export interface ToolGateInput {
1407
1407
  fallback: import("./auto-mode.js").DenialLimitFallback;
1408
1408
  }) => void;
1409
1409
  /**
1410
- * #648 — the deny exit nobody judged: the ask's approver (the run's own, or an ancestor's frozen seat on
1411
- * a delegated child) answered `unavailable` and the durable park re-route did not take the call, so the
1412
- * fail-closed deny STANDS. Fired once per such call, after the park has had its one attempt, with the
1413
- * facts the `delegation.ask_unresolvable` notice is composed from (`ask-unresolvable-notice.ts`);
1410
+ * The deny exit nobody judged: the call's approval could not be RESOLVED (`cause` the approver
1411
+ * answered `unavailable`, no approver is wired, or an inherited durable mandate has no park facility on
1412
+ * this leg) and no durable park took the call, so the fail-closed deny STANDS. Fired once per such call
1413
+ * from the gate's single deny exit, after every park re-route has had its attempt, with the facts the
1414
+ * `delegation.ask_unresolvable` notice is composed from (`ask-unresolvable-notice.ts`);
1414
1415
  * `parkLaneExisted` = a park lane was armed for this run. Observe-only: a throwing seat never alters
1415
1416
  * the deny. Absent ⇒ the deny stands silently (a host driving the gate directly, no notice sink).
1416
1417
  */
1417
1418
  onAskUnresolvable?: (info: {
1418
1419
  toolName: string;
1419
1420
  toolCallId: string;
1420
- settlementKind: "approver_unavailable";
1421
+ cause: import("./ask-unresolvable-notice.js").AskUnresolvableCause;
1421
1422
  parkLaneExisted: boolean;
1422
1423
  }) => void;
1423
1424
  /**
@@ -1513,7 +1514,7 @@ export interface ToolGateInput {
1513
1514
  }) => void;
1514
1515
  };
1515
1516
  /**
1516
- * #619 — observation sink for a shell call the READ-ONLY reader cleared (`readOnlyShellVerdict`, the
1517
+ * Observation sink for a shell call the READ-ONLY reader cleared (`readOnlyShellVerdict`, the
1517
1518
  * allow layer's second member beside the persisted allow rule: consulted for the shell tool only, after
1518
1519
  * the person's allow rules, under the SAME consumption predicate — never over a mandated ask, a hook's
1519
1520
  * ask, an explicit ask rule, governance or a marked call). The allow-side disclosure of "why did this run