@sema-agent/core 7.52.0 → 7.53.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 (191) hide show
  1. package/CHANGELOG.md +194 -0
  2. package/README.md +3 -1
  3. package/dist/agents/cross-session-envelope.d.ts +8 -0
  4. package/dist/agents/cross-session-envelope.js +6 -0
  5. package/dist/agents/cross-session-judge.d.ts +21 -1
  6. package/dist/agents/cross-session-judge.js +8 -0
  7. package/dist/agents/cross-session-ref.d.ts +5 -0
  8. package/dist/agents/cross-session-ref.js +1 -0
  9. package/dist/agents/org-delivery-gate.d.ts +122 -0
  10. package/dist/agents/org-delivery-gate.js +69 -0
  11. package/dist/agents/peer-held-queue.d.ts +9 -4
  12. package/dist/agents/peer-held-queue.js +6 -5
  13. package/dist/agents/peer-session-drain.d.ts +35 -5
  14. package/dist/agents/peer-session-drain.js +271 -180
  15. package/dist/agents/read-inbox-tool.d.ts +7 -0
  16. package/dist/agents/read-inbox-tool.js +5 -1
  17. package/dist/agents/subagent.d.ts +3 -0
  18. package/dist/agents/subagent.js +99 -72
  19. package/dist/agents/tool-filter.d.ts +18 -0
  20. package/dist/agents/tool-filter.js +6 -0
  21. package/dist/brain/anthropic.d.ts +6 -1
  22. package/dist/brain/anthropic.js +4 -4
  23. package/dist/brain/model-presets.js +2 -1
  24. package/dist/brain/route-adjudicator.d.ts +6 -0
  25. package/dist/brain/routing.d.ts +24 -1
  26. package/dist/brain/routing.js +54 -19
  27. package/dist/brain/timeout.js +5 -4
  28. package/dist/core/a2a.d.ts +5 -2
  29. package/dist/core/a2a.js +30 -13
  30. package/dist/core/allow-entry.d.ts +2 -1
  31. package/dist/core/allow-entry.js +2 -2
  32. package/dist/core/auto-compaction.d.ts +57 -0
  33. package/dist/core/auto-compaction.js +28 -2
  34. package/dist/core/checkpoint-store.d.ts +29 -20
  35. package/dist/core/checkpoint-store.js +3 -3
  36. package/dist/core/compliance.js +3 -1
  37. package/dist/core/effective-path-target.d.ts +25 -6
  38. package/dist/core/effective-path-target.js +24 -2
  39. package/dist/core/engine-notice.d.ts +13 -2
  40. package/dist/core/gate-fold.js +3 -2
  41. package/dist/core/gate-lanes.js +11 -13
  42. package/dist/core/gate-outcome.d.ts +11 -6
  43. package/dist/core/governance-codes.d.ts +3 -1
  44. package/dist/core/governance-codes.js +3 -0
  45. package/dist/core/hands-band-screen.d.ts +7 -0
  46. package/dist/core/hands-band-screen.js +32 -0
  47. package/dist/core/hooks.d.ts +7 -5
  48. package/dist/core/hooks.js +5 -3
  49. package/dist/core/locked-config.d.ts +84 -32
  50. package/dist/core/locked-config.js +128 -42
  51. package/dist/core/mcp-probe.js +1 -1
  52. package/dist/core/mcp-server-spec.d.ts +88 -10
  53. package/dist/core/mcp-server-spec.js +183 -33
  54. package/dist/core/mcp.d.ts +21 -14
  55. package/dist/core/mcp.js +55 -51
  56. package/dist/core/memory-engine/engine.d.ts +14 -19
  57. package/dist/core/memory-engine/engine.js +7 -5
  58. package/dist/core/mode-policy.d.ts +3 -18
  59. package/dist/core/mode-policy.js +2 -16
  60. package/dist/core/mods/kernel/dispatch.js +13 -15
  61. package/dist/core/mods/kernel/events.d.ts +9 -6
  62. package/dist/core/mods/kernel/events.js +2 -2
  63. package/dist/core/mods/kernel/receive.d.ts +39 -10
  64. package/dist/core/mods/kernel/receive.js +119 -54
  65. package/dist/core/mods/kernel/site-rules.d.ts +80 -15
  66. package/dist/core/mods/kernel/site-rules.js +232 -40
  67. package/dist/core/mods/stations.d.ts +0 -6
  68. package/dist/core/mods/stations.js +0 -7
  69. package/dist/core/mounted-mcp.d.ts +57 -0
  70. package/dist/core/mounted-mcp.js +71 -2
  71. package/dist/core/own-property-receipt.d.ts +13 -0
  72. package/dist/core/own-property-receipt.js +3 -1
  73. package/dist/core/parent-spec-seat.d.ts +2 -2
  74. package/dist/core/permission-rule-model.d.ts +58 -9
  75. package/dist/core/permission-rule-model.js +81 -7
  76. package/dist/core/permission-rule-org.d.ts +163 -33
  77. package/dist/core/permission-rule-org.js +205 -71
  78. package/dist/core/permission-rules.d.ts +7 -1
  79. package/dist/core/permission-rules.js +37 -8
  80. package/dist/core/retention.js +7 -2
  81. package/dist/core/retired-keys.d.ts +8 -2
  82. package/dist/core/retired-keys.js +12 -1
  83. package/dist/core/roles.js +60 -10
  84. package/dist/core/rule-lane-reads.js +1 -1
  85. package/dist/core/runner/contracts.d.ts +1 -0
  86. package/dist/core/runner/gate-exit.js +4 -3
  87. package/dist/core/runner/mcp-redial.d.ts +52 -10
  88. package/dist/core/runner/mcp-redial.js +38 -9
  89. package/dist/core/runner/permission-rule-lanes.d.ts +5 -2
  90. package/dist/core/runner/permission-rule-lanes.js +22 -10
  91. package/dist/core/runner/person-path-rows.d.ts +3 -1
  92. package/dist/core/runner/person-path-rows.js +2 -2
  93. package/dist/core/runner/prepare-config-doors.d.ts +48 -2
  94. package/dist/core/runner/prepare-config-doors.js +161 -145
  95. package/dist/core/runner/prepare-defer-classify.js +4 -7
  96. package/dist/core/runner/prepare-delegation-surface.d.ts +2 -2
  97. package/dist/core/runner/prepare-delegation-surface.js +7 -5
  98. package/dist/core/runner/prepare-hands-readface.d.ts +1 -1
  99. package/dist/core/runner/prepare-hands-readface.js +2 -1
  100. package/dist/core/runner/prepare-inherited-gate.js +2 -2
  101. package/dist/core/runner/prepare-listings.d.ts +1 -1
  102. package/dist/core/runner/prepare-listings.js +4 -2
  103. package/dist/core/runner/prepare-memory-engine-session.d.ts +0 -6
  104. package/dist/core/runner/prepare-memory-engine-session.js +19 -6
  105. package/dist/core/runner/prepare-memory.d.ts +3 -2
  106. package/dist/core/runner/prepare-memory.js +6 -2
  107. package/dist/core/runner/prepare-offload-wrappers.d.ts +4 -4
  108. package/dist/core/runner/prepare-offload-wrappers.js +2 -1
  109. package/dist/core/runner/prepare-policy-chain.d.ts +1 -1
  110. package/dist/core/runner/prepare-policy-chain.js +12 -12
  111. package/dist/core/runner/prepare-project-context.d.ts +1 -1
  112. package/dist/core/runner/prepare-project-context.js +6 -4
  113. package/dist/core/runner/prepare-prompt-assembly.d.ts +1 -1
  114. package/dist/core/runner/prepare-prompt-assembly.js +20 -16
  115. package/dist/core/runner/prepare-prompt-inputs.d.ts +12 -6
  116. package/dist/core/runner/prepare-prompt-inputs.js +6 -3
  117. package/dist/core/runner/prepare-protocol-tools.d.ts +7 -5
  118. package/dist/core/runner/prepare-protocol-tools.js +19 -12
  119. package/dist/core/runner/prepare-question-face.d.ts +4 -2
  120. package/dist/core/runner/prepare-question-face.js +4 -3
  121. package/dist/core/runner/prepare-task.js +10 -10
  122. package/dist/core/runner/prepare-wiring-manifest.d.ts +8 -3
  123. package/dist/core/runner/prepare-wiring-manifest.js +8 -5
  124. package/dist/core/runner/resume-admission.d.ts +4 -1
  125. package/dist/core/runner/resume-admission.js +7 -5
  126. package/dist/core/runner/resume-internals-and-config.d.ts +3 -3
  127. package/dist/core/runner/resume-internals-and-config.js +3 -8
  128. package/dist/core/runner/retired-runner-deps.d.ts +5 -6
  129. package/dist/core/runner/retired-runner-deps.js +1 -4
  130. package/dist/core/runner/run-leg.js +42 -12
  131. package/dist/core/runner/run-recovery-lanes.js +5 -3
  132. package/dist/core/runner/run-terminal-adoption.js +6 -5
  133. package/dist/core/runner/run-turn-boundary.js +9 -3
  134. package/dist/core/runner/runtask.js +1 -1
  135. package/dist/core/runner/skills-seat.d.ts +15 -0
  136. package/dist/core/runner/skills-seat.js +57 -0
  137. package/dist/core/runner/sub-agent-deny-lists.d.ts +13 -11
  138. package/dist/core/runner/sub-agent-deny-lists.js +7 -5
  139. package/dist/core/runner/synthetic-tools.d.ts +5 -2
  140. package/dist/core/runner/synthetic-tools.js +2 -12
  141. package/dist/core/runner-deps.d.ts +29 -14
  142. package/dist/core/session-policy-store.d.ts +19 -0
  143. package/dist/core/session-policy-store.js +22 -2
  144. package/dist/core/store-contracts/mailbox-store-contract.js +13 -0
  145. package/dist/core/task-event.d.ts +38 -2
  146. package/dist/core/task-notification.js +2 -2
  147. package/dist/core/task-spec.d.ts +1 -1
  148. package/dist/core/tool-policy.d.ts +3 -0
  149. package/dist/core/tool-policy.js +10 -4
  150. package/dist/core/types.d.ts +2 -2
  151. package/dist/core/types.js +1 -1
  152. package/dist/core/untrusted-text.d.ts +16 -0
  153. package/dist/core/untrusted-text.js +7 -2
  154. package/dist/core/user-attachment.d.ts +13 -2
  155. package/dist/core/user-attachment.js +47 -41
  156. package/dist/core/wiring-manifest.d.ts +4 -2
  157. package/dist/engine/harness/agent-harness.js +2 -1
  158. package/dist/engine/harness/types.d.ts +3 -0
  159. package/dist/engine/loop/agent-loop.d.ts +12 -1
  160. package/dist/engine/loop/agent-loop.js +17 -5
  161. package/dist/engine/loop/types.d.ts +7 -1
  162. package/dist/index.d.ts +15 -9
  163. package/dist/index.js +12 -6
  164. package/dist/internal/harness.d.ts +1 -0
  165. package/dist/internal/harness.js +1 -0
  166. package/dist/orchestration/goal.d.ts +24 -1
  167. package/dist/orchestration/goal.js +3 -2
  168. package/dist/prompt-assembly/artifact-store.d.ts +44 -9
  169. package/dist/prompt-assembly/artifact-store.js +38 -12
  170. package/dist/prompt-assembly/artifact.d.ts +12 -11
  171. package/dist/prompt-assembly/artifact.js +52 -21
  172. package/dist/prompt-assembly/assemble.d.ts +2 -0
  173. package/dist/prompt-assembly/assemble.js +2 -1
  174. package/dist/prompt-assembly/catalog/index.js +2 -1
  175. package/dist/prompt-assembly/catalog/memory.d.ts +13 -0
  176. package/dist/prompt-assembly/catalog/memory.js +6 -0
  177. package/dist/prompt-assembly/catalog/schema.d.ts +16 -2
  178. package/dist/prompt-assembly/composer.d.ts +2 -0
  179. package/dist/prompt-assembly/composer.js +1 -1
  180. package/dist/prompt-assembly/overlay.d.ts +21 -11
  181. package/dist/prompt-assembly/overlay.js +55 -17
  182. package/dist/prompt-assembly/select.d.ts +21 -6
  183. package/dist/prompt-assembly/select.js +39 -2
  184. package/dist/prompts/default.d.ts +0 -11
  185. package/dist/prompts/default.js +0 -3
  186. package/dist/stores/file/permission-rule-store.d.ts +1 -1
  187. package/dist/stores/file/permission-rule-store.js +2 -1
  188. package/dist/tools/artifact/artifact-tool.js +4 -3
  189. package/package.json +1 -1
  190. package/test/export-surface.snapshot.json +65 -5
  191. package/wire-changes/core@7.53.0.json +1946 -0
@@ -305,7 +305,7 @@ export function summarizeCheckpoint(cp) {
305
305
  ...(hasBidi ? { hasBidiControls: true } : {}),
306
306
  };
307
307
  }
308
- export const CHECKPOINT_CONFIG_REFUSAL_CODES = Object.freeze(["config.spec_retired_key", "config.output_style_invalid", "config.permission_mode_invalid", "config.shell_gate_value_retired", "config.approval_reach_invalid", "config.user_invoked_skill_unknown", "config.skill_deny_unreadable", "config.user_attachment_invalid", "config.tool_face_control"]);
308
+ export const CHECKPOINT_CONFIG_REFUSAL_CODES = Object.freeze(["config.spec_retired_key", "config.output_style_invalid", "config.permission_mode_invalid", "config.shell_gate_value_retired", "config.approval_reach_invalid", "config.user_invoked_skill_unknown", "config.skill_deny_unreadable", "config.user_attachment_invalid", "config.tool_face_control", "config.deps_retired_key", "config.peer_presence_invalid", "config.tool_model_gate_invalid", "config.microcompact_invalid", "config.prompt_selector_invalid", "resume_at.conflicts_resume", "resume_at.mode_without_target", "config.read_face_invalid", "config.write_face_invalid", "config.invalid_argument", "config.memory_delegation_evidence", "config.memory_provenance", "config.memory_capture_policy", "config.auto_consolidation", "config.unknown_role", "config.tool_materialize_invalid", "config.memory_persistence_invalid", "config.tool_materialize_unreachable", "config.locked_registry", "config.locked_key", "config.mcp_content_class", "config.skill_listing_budget_invalid", "config.limit_unknown_key", "config.limit_invalid", "config.attachment_invalid", "config.usage_window_invalid", "config.brain_call_guardrail_invalid", "config.retention_policy", "config.retention_capability", "config.invalid_checkpoint_store", "config.skill_base_dir_invalid"]);
309
309
  export const isCheckpointConfigRefusalCode = (word) => CHECKPOINT_CONFIG_REFUSAL_CODES.some((w) => w === word);
310
310
  export class CheckpointError extends Error {
311
311
  code;
@@ -399,13 +399,13 @@ export function resolveCheckpointStore(spec, deps) {
399
399
  throw checkpointStoreExecutionUndeclared("TaskSpec.checkpointStore", spec.checkpointStore);
400
400
  return spec.checkpointStore;
401
401
  }
402
- if (deps.checkpointStore !== undefined && deps.checkpointStore.execution?.outcome !== true) {
402
+ if (deps.checkpointStore !== undefined && deps.checkpointStore?.execution?.outcome !== true) {
403
403
  throw checkpointStoreExecutionUndeclared("RunnerDeps.checkpointStore", deps.checkpointStore);
404
404
  }
405
405
  return deps.checkpointStore;
406
406
  }
407
407
  export function checkpointStoreExecutionUndeclared(seat, store) {
408
- const ctor = store.constructor?.name;
408
+ const ctor = store?.constructor?.name;
409
409
  const name = typeof ctor === "string" && ctor.length > 0 && ctor !== "Object" ? ` (${ctor})` : "";
410
410
  const e = new Error(`${seat}: the wired CheckpointStore${name} does not declare \`execution = { outcome: true }\` — the execution-outcome ` +
411
411
  "record (recordExecutionOutcome, and reopen refusing a recorded row) is a required member of the store contract; " +
@@ -1,3 +1,4 @@
1
+ import { describeRejectedValue } from "./tool-search-setting.js";
1
2
  export const COMPLIANCE_CAPABILITIES = Object.freeze(["mcp_servers", "workflows", "web_fetch", "org_memory_mount"]);
2
3
  export const BUILTIN_COMPLIANCE_DENIES = {
3
4
  hipaa: ["web_fetch", "org_memory_mount"],
@@ -26,7 +27,8 @@ export function resolveComplianceDenies(posture) {
26
27
  export function complianceCapabilityProblem(cap, owner) {
27
28
  if (COMPLIANCE_CAPABILITIES.some((member) => member === cap))
28
29
  return undefined;
29
- return (`${owner} contains "${String(cap)}", not a member of the closed capability set ` +
30
+ const shown = (typeof cap === "object" && cap !== null) || typeof cap === "function" ? describeRejectedValue(cap) : `"${String(cap)}"`;
31
+ return (`${owner} contains ${shown}, not a member of the closed capability set ` +
30
32
  `(${COMPLIANCE_CAPABILITIES.join(", ")}) — a misspelled deny must never silently not-enforce.`);
31
33
  }
32
34
  export const WEB_FETCH_TOOL_NAME = "WebFetch";
@@ -245,17 +245,36 @@ export declare function readCallTarget(env: ExecutionEnv | undefined, call: Tool
245
245
  readonly root?: string | undefined;
246
246
  readonly cwd?: string | undefined;
247
247
  }, signal?: AbortSignal, leads?: ReadonlyMap<string, string | undefined>): Promise<CallTargetReading>;
248
+ /** The gate's entry records the loop's reading of this call's arguments against the face it stamps on the call. */
249
+ export declare function stampArgsReading(face: object, read: (args: unknown) => unknown): void;
248
250
  /**
249
- * `req` with its arguments replaced by `args` and — when `req` carries a reading of its own args — a reading of the NEW
250
- * args taken the same way (same environment, same root / cwd, same face). Every seat that rewrites a request's arguments
251
- * and hands it on (a composition's rewrite cascade, a re-judgment of the final or an edited form) goes through here, so the
252
- * next layer never judges a request its reading does not describe. `signal` is the CURRENT call's: a reading never carries
253
- * one call's cancellation into another's. `undefined` ⇒ nothing to re-read: use `{ ...req, args }`.
251
+ * `args` as the called tool will run them — its argument preparation and schema coercions, the reading the loop gave the
252
+ * model's call — for the call `req` belongs to; `args` itself when nothing was stamped (a bare request outside a Runner:
253
+ * a host judging a call reads it the tool's way first). Every seat that adopts a rewrite (a hook's, a policy's, a
254
+ * person's edit) takes it through here before anything judges it, so what is judged is what runs. A value the reading
255
+ * leaves as it is comes back as ITSELF (the same object: a target reading taken for it still describes it).
256
+ */
257
+ export declare function argsAsTheToolReads(req: {
258
+ readonly face?: object | undefined;
259
+ }, args: unknown): unknown;
260
+ /** `args` through one call's reading `read` (the face-less seat: the resumed call reads through its live tool): ITSELF when the
261
+ * reading leaves its bytes as they are, else the reading's value, recorded as read from `args`. */
262
+ export declare function argsReadBy(read: (args: unknown) => unknown, args: unknown): unknown;
263
+ /** The value `args` was read from, when a reading moved it; otherwise `args` itself. */
264
+ export declare function argsReadFrom(args: unknown): unknown;
265
+ /**
266
+ * `req` with its arguments replaced by `args` AS THE TOOL READS THEM ({@link argsAsTheToolReads}) and — when `req` carries
267
+ * a reading of its own target — a reading of the NEW args taken the same way (same environment, same root / cwd, same
268
+ * face). Every seat that rewrites a request's arguments and hands it on (a composition's rewrite cascade, a re-judgment of
269
+ * the final or an edited form) goes through here, so the next layer never judges a request its reading does not describe
270
+ * nor arguments the tool would run differently. `signal` is the CURRENT call's: a reading never carries one call's
271
+ * cancellation into another's.
254
272
  */
255
273
  export declare function withArgsReading<R extends {
256
274
  readonly args: unknown;
257
275
  readonly targetReading?: CallTargetReading | undefined;
258
- }>(req: R, args: unknown, signal: AbortSignal | undefined): Promise<R> | undefined;
276
+ readonly face?: object | undefined;
277
+ }>(req: R, args: unknown, signal: AbortSignal | undefined): Promise<R>;
259
278
  /** The request's target reading, when it describes the request's own `args` (the object it was taken for); otherwise
260
279
  * nothing — a rule lane then judges the lexical view it always judged. Every rule lane reads the reading through here. */
261
280
  export declare function callTargetReadingOf(req: {
@@ -3,6 +3,7 @@ import { declaredPathTargetOf, pathTargetValue, protectivePathTargetOf } from ".
3
3
  import { symlinkChainHops } from "./path-identity.js";
4
4
  import { DEFAULT_GATE_AWAIT_TIMEOUT_MS, withGateTimeout } from "./gate-bounded-await.js";
5
5
  import { thrownText } from "./thrown-value.js";
6
+ import { canonicalize } from "./canonical-json.js";
6
7
  export function pathTargetBaseOf(pt, bases) {
7
8
  return (pt?.base ?? "cwd") === "root" ? bases.root : (bases.cwd ?? bases.root);
8
9
  }
@@ -146,10 +147,31 @@ export async function readCallTarget(env, call, signal, leads) {
146
147
  REREAD.set(reading, (args, rereadSignal) => readCallTarget(env, { ...call, args }, rereadSignal, leads));
147
148
  return reading;
148
149
  }
149
- export function withArgsReading(req, args, signal) {
150
+ const ARGS_READING = new WeakMap();
151
+ export function stampArgsReading(face, read) {
152
+ ARGS_READING.set(face, read);
153
+ }
154
+ export function argsAsTheToolReads(req, args) {
155
+ const read = req.face === undefined ? undefined : ARGS_READING.get(req.face);
156
+ return read === undefined ? args : argsReadBy(read, args);
157
+ }
158
+ export function argsReadBy(read, args) {
159
+ const out = read(args);
160
+ if (out === args || canonicalize(out) === canonicalize(args))
161
+ return args;
162
+ if (typeof out === "object" && out !== null)
163
+ READ_FROM.set(out, args);
164
+ return out;
165
+ }
166
+ const READ_FROM = new WeakMap();
167
+ export function argsReadFrom(args) {
168
+ return typeof args === "object" && args !== null && READ_FROM.has(args) ? READ_FROM.get(args) : args;
169
+ }
170
+ export async function withArgsReading(req, args, signal) {
171
+ const read = argsAsTheToolReads(req, args);
150
172
  const reading = callTargetReadingOf(req);
151
173
  const reread = reading === undefined ? undefined : REREAD.get(reading);
152
- return reread === undefined ? undefined : reread(args, signal).then((targetReading) => ({ ...req, args, targetReading }));
174
+ return reread === undefined ? { ...req, args: read } : { ...req, args: read, targetReading: await reread(read, signal) };
153
175
  }
154
176
  function viewFrom(v) {
155
177
  if (v.lexical === undefined)
@@ -68,7 +68,7 @@ export interface EngineNotice {
68
68
  * as usual. Audience `user`; once per prepared task for the skill and request lists (`detail.sessionId` present), once
69
69
  * per Runner for the deployment's (at construction, no session). `detail: { sessionId?, entries: [{ source, entry,
70
70
  * reason, skill?, list? }] }` (`source` ∈ `skill` / `request` / `deployment`; `skill` and `list` only on a skill's;
71
- * `reason` ∈ {@link import("./allow-entry.js").ALLOW_ENTRY_INERT_REASONS}).
71
+ * `reason` ∈ {@link import("./allow-entry.js").ALLOW_ENTRY_INERT_REASONS}; `entry` is the entry as written — the message spells it — when it is at most `NOTICE_RULE_TEXT_MAX` (1 024) code points; a longer one is cut to 1 024 plus the truncation mark, 1 025 in all).
72
72
  * - `"mods.registration_refused"` — an extension registration was not mounted: a registration admitted before it
73
73
  * answered `{ refuse }` on `plugin.register`, or failed while judging it (fail-closed). The run goes on without it.
74
74
  * Audience `user` (the seat is in `detail`). Once per refused registration per run. `detail: { name, seat, tier,
@@ -439,7 +439,18 @@ export interface EngineNotice {
439
439
  * complete absolute path (only a foreign writer or a hand edit makes one; every engine write face refuses it): they
440
440
  * take no part in this run's checks — a deny or ask row among them protects nothing — until they are rewritten or
441
441
  * removed. Once per run, by whichever read met them first. `detail: { rows, count, sessionId, runId }` — `rows`
442
- * the first eight `{ rule, scopeRoot }` (each bounded), `count` all of them. Audience `user`.
442
+ * the first eight `{ rule, scopeRoot }`, `count` all of them; `rule` is the rule text as the store served it, through the same reading
443
+ * as `permission.allowed_tool_not_granted`'s `entries[].entry` (the original, not a spelling of it, when it is at most 1 024 code points;
444
+ * a longer one is cut to 1 024 plus the truncation mark, 1 025 in all), `scopeRoot` neutralized and bounded. Audience `user`.
445
+ * - `"config.session_rules_earlier_grammar"` — a session's stored permission rules (`RunnerDeps.sessionPolicyStore`) hold
446
+ * entries the rule grammar they were written in reads as nothing: an allow entry admits nothing, and a `toolDeny` entry
447
+ * this engine's grammar reads as no rule makes those rules refuse every call until they are rewritten. The message names
448
+ * the session, the bucket, the entries (the first five, the rest counted), the reader's word and the way out. Audience
449
+ * `user`. Once per distinct entry set per bucket per rule layer (the run's own rules, each ancestor's inherited ones).
450
+ * `detail: { bucket, storedGrammar, entries: [{ entry, reader }], rewrite: { by, stampsGrammar }, rulesSessionId,
451
+ * sessionId }` — `entries` every one, `entry` through the same reading as `permission.allowed_tool_not_granted`'s;
452
+ * `reader` ∈ `no_rule` / `named_no_tool`; `rewrite.by` = `operator_write_without_rev`; `rulesSessionId` the session
453
+ * whose rules they are (an ancestor's on a delegated child), `sessionId` the run that read them.
443
454
  * - `"config.durable_gate_unavailable"` — a durable-approval MANDATE is in force on a leg with NO
444
455
  * checkpoint store: the per-principal `forceDurableGate` entitlement, or the task's own
445
456
  * `durableApproval` opt-in with no live approver seat (`durableMandateSourceOf`, the predicate the
@@ -8,6 +8,7 @@ import { createGateDeadline, awaitWriteProtectionJudgment, gateWaitFailureOf, wi
8
8
  import { shellReadFaceWords } from "../tools/fs/shell-read-face.js";
9
9
  import { failedJudgmentDeny } from "./runner/policy-throw.js";
10
10
  import { thrownText } from "./thrown-value.js";
11
+ import { argsAsTheToolReads } from "./effective-path-target.js";
11
12
  import { foldHookAsk, refuseOutOfContractDecision } from "./tool-policy.js";
12
13
  import { applyUnansweredSilence, readStationAnswer, silencedBy } from "./shell-read-silence.js";
13
14
  import { asksUnstated, permissionSeatAt, unstatedAskMessage } from "./mode-policy.js";
@@ -37,10 +38,10 @@ export async function runGateFold(pass) {
37
38
  pass.classifierRound = attributed;
38
39
  }
39
40
  if ((pass.decision.action === "allow" || pass.decision.action === "ask") && pass.decision.updatedInput !== undefined) {
40
- pass.policyRewrite = pass.decision.updatedInput;
41
+ pass.policyRewrite = argsAsTheToolReads(pass.req, pass.decision.updatedInput);
41
42
  }
42
43
  else if (pass.hookAsk?.updatedInput !== undefined) {
43
- pass.policyRewrite = pass.hookAsk.updatedInput;
44
+ pass.policyRewrite = argsAsTheToolReads(pass.req, pass.hookAsk.updatedInput);
44
45
  }
45
46
  if (pass.hookAsk !== undefined && pass.decision.action !== "deny") {
46
47
  pass.decision = foldHookAsk(pass.decision, pass.hookAsk);
@@ -1,4 +1,4 @@
1
- import { withArgsReading } from "./effective-path-target.js";
1
+ import { argsAsTheToolReads, withArgsReading } from "./effective-path-target.js";
2
2
  import { attest, attestationOf, decisionText, foldHookAsk, ownArgs, refusalOf, refuseOutOfContractDecision } from "./tool-policy.js";
3
3
  import { arbitrateByMode, classifyAsk, decisionAskClassesOf, fileCommandOptionsSentence, persistedRuleMandateOf, ruleClearanceOf, safetyAxisOf, stampAskOrigin } from "./ask-origin.js";
4
4
  import { classifierParseFailureDenyMessage, denialLimitFallbackMessage, rungDidNotRule, unarmedWindow } from "./auto-mode.js";
@@ -16,7 +16,7 @@ import { catalogRuleFaceOf } from "./tool-registry.js";
16
16
  import { callFaceSeat } from "./tool-roster.js";
17
17
  import { heldBy, wireOf } from "./tool-name-readings.js";
18
18
  import { asksUnstated, modeAcceptsFileCommand, permissionSeatAt, unstatedAskMessage } from "./mode-policy.js";
19
- import { ORG_RULE_DECISION_REASON, ORG_UNAVAILABLE_DECISION_REASON } from "./permission-rule-org.js";
19
+ import { ORG_RULE_DECISION_REASON, ORG_UNAVAILABLE_DECISION_REASON, orgUnreadableClause } from "./permission-rule-org.js";
20
20
  import { exitGate, passAskFace, unownedArgsMessage } from "./runner/gate-exit.js";
21
21
  import { readOrgRuleLane, readPersistedRuleLane } from "./rule-lane-reads.js";
22
22
  import { traceHookCrash } from "./gate-crash-trace.js";
@@ -137,14 +137,14 @@ export async function runGateLanes(pass) {
137
137
  orgTightenCount += 1;
138
138
  pass.deniedBy = "org";
139
139
  pass.denyCause = undefined;
140
- return { decision: refusalOf({ action: "deny", message: `an organization policy rule (${rule}) denies this call`, decisionReason: ORG_RULE_DECISION_REASON }, args), org: undefined };
140
+ return { decision: refusalOf({ action: "deny", message: `an organization policy rule (${rule}) denies this call${unreadable !== undefined ? `, ${orgUnreadableClause(unreadable)}` : ""}`, decisionReason: ORG_RULE_DECISION_REASON }, args), org: undefined };
141
141
  }
142
142
  if (decided.action === "deny")
143
143
  return { decision: decided, org: undefined };
144
144
  orgTightenCount += 1;
145
145
  orgRealApprovalRequired = true;
146
146
  const orgRuleMessage = unreadable !== undefined
147
- ? `an organization policy rule (${rule}) may reach this call, which could not be read against it (${unreadable}) — a person must decide`
147
+ ? `an organization policy rule (${rule}) may reach this call, ${orgUnreadableClause(unreadable)} — a person must decide`
148
148
  : `an organization policy rule (${rule}) requires approval for this call`;
149
149
  return {
150
150
  decision: decided.action === "ask"
@@ -160,8 +160,7 @@ export async function runGateLanes(pass) {
160
160
  const orgAnswer = await applyOrgLayer(pass.decision, finalArgs);
161
161
  pass.decision = orgAnswer.decision;
162
162
  const readPersistedRules = (req) => readPersistedRuleLane(input.persistedRules, req, input.abortSignal);
163
- const reread = pass.policyRewrite !== undefined ? withArgsReading(pass.req, pass.policyRewrite, pass.callSignal ?? input.abortSignal) : undefined;
164
- const laneReq = Object.freeze(reread !== undefined ? await reread : { ...pass.req, ...(pass.policyRewrite !== undefined ? { args: pass.policyRewrite } : {}) });
163
+ const laneReq = Object.freeze(pass.policyRewrite !== undefined ? await withArgsReading(pass.req, pass.policyRewrite, pass.callSignal ?? input.abortSignal) : { ...pass.req });
165
164
  const laneAnswer = input.persistedRules !== undefined && wireOf("lanes.persistedLaneSkip", pass.req) !== ASK_USER_QUESTION_TOOL_NAME ? await readPersistedRules(laneReq) : undefined;
166
165
  {
167
166
  const tightened = applyPersistedTightening(pass.decision, laneAnswer, laneReq);
@@ -558,7 +557,7 @@ export async function runGateLanes(pass) {
558
557
  pass.deniedBy = "policy";
559
558
  break;
560
559
  }
561
- editArgs = taken.value;
560
+ editArgs = argsAsTheToolReads(pass.req, taken.value);
562
561
  if (round >= 3) {
563
562
  editDenied = {
564
563
  action: "deny",
@@ -589,7 +588,7 @@ export async function runGateLanes(pass) {
589
588
  editHookAsk = ask;
590
589
  }
591
590
  if (hr.updatedInput !== undefined) {
592
- editArgs = hr.updatedInput;
591
+ editArgs = argsAsTheToolReads(pass.req, hr.updatedInput);
593
592
  editRewrittenSinceHuman = true;
594
593
  }
595
594
  }
@@ -622,7 +621,7 @@ export async function runGateLanes(pass) {
622
621
  }
623
622
  }
624
623
  if (recheck.updatedInput !== undefined) {
625
- editArgs = recheck.updatedInput;
624
+ editArgs = argsAsTheToolReads(pass.req, recheck.updatedInput);
626
625
  editRewrittenSinceHuman = true;
627
626
  }
628
627
  const editOrg = await applyOrgLayer(recheck, editArgs);
@@ -632,8 +631,7 @@ export async function runGateLanes(pass) {
632
631
  break;
633
632
  }
634
633
  let editLane;
635
- const editReread = withArgsReading(pass.req, editArgs, callSignal ?? input.abortSignal);
636
- const editLaneReq = Object.freeze(editReread !== undefined ? await editReread : { toolName, args: editArgs, toolCallId, ...(pass.req.face !== undefined ? { face: pass.req.face } : {}) });
634
+ const editLaneReq = Object.freeze(await withArgsReading(pass.req, editArgs, callSignal ?? input.abortSignal));
637
635
  if (input.persistedRules !== undefined && wireOf("lanes.persistedEditSkip", pass.req) !== ASK_USER_QUESTION_TOOL_NAME) {
638
636
  editLane = await readPersistedRules(editLaneReq);
639
637
  const editTightened = applyPersistedTightening(recheck, editLane, editLaneReq);
@@ -653,7 +651,7 @@ export async function runGateLanes(pass) {
653
651
  }
654
652
  if (recheck.action === "allow") {
655
653
  if (recheck.updatedInput !== undefined) {
656
- editArgs = recheck.updatedInput;
654
+ editArgs = argsAsTheToolReads(pass.req, recheck.updatedInput);
657
655
  editRewrittenSinceHuman = true;
658
656
  }
659
657
  if (editRewrittenSinceHuman && asksUnstated(recheck, permissionSeatAt(input, editArgs)?.seat, input.permissionMode?.())) {
@@ -662,7 +660,7 @@ export async function runGateLanes(pass) {
662
660
  }
663
661
  }
664
662
  if (recheck.updatedInput !== undefined) {
665
- editArgs = recheck.updatedInput;
663
+ editArgs = argsAsTheToolReads(pass.req, recheck.updatedInput);
666
664
  editRewrittenSinceHuman = true;
667
665
  }
668
666
  let editFacts;
@@ -336,12 +336,17 @@ export type CauseTableCoversEveryDeniedBy = AssertAllKeysHandled<Exclude<DeniedB
336
336
  * allow or deny) and from BOTH stations (this gate's own classifier step, and an ANCESTOR's frozen
337
337
  * classifier reached through the inherited policy chain, whose allow and deny carry it out on the same seat
338
338
  * that carries the FORM): which model it was for, which one answered, and — only when the ladder moved —
339
- * the rung it left and why. ABSENT reads UNKNOWN, never "the seat answered" (that is `fallback` absent WITH
340
- * the member present), and a consumer cannot tell its two causes apart from the record alone: no
341
- * classification happened, or one ran on a decider given no candidate ladder and can name no model (a
342
- * hand-built decider; a parked ancestor's cross-process rebuild, whose single fresh leg has none). A block
343
- * at the DENIAL-LIMIT bound reaches no attribution seat: it becomes an ask, and the disposition names that
344
- * resolution. Additive. Invariant I6 ({@link screenGateOutcome}) screens its shape. */
339
+ * the rung it left and why. It is the classification's account, not the settlement's: a classification that
340
+ * RULED keeps it whoever then decided the call — a call whose question a person then answered (an ask a
341
+ * station after the classifier raised, or a block at the DENIAL-LIMIT bound, which becomes an ask) carries
342
+ * it beside that person's settlement on {@link GateOutcome}; who decided is read from the settlement
343
+ * (`human_allowed`, `policy_allowed`, …), never from this member's presence. ABSENT reads UNKNOWN, never
344
+ * "the seat answered" (that is `fallback` absent WITH the member present), and a consumer cannot tell its
345
+ * three causes apart from the record alone: no classification happened; one ran on a decider given no
346
+ * candidate ladder and can name no model (a hand-built decider; a parked ancestor's cross-process rebuild,
347
+ * whose single fresh leg has none); or one did not rule (it could not run, or replied with no verdict) and
348
+ * handed the question to the approval seat, whose settlement then names the resolution that ask reached.
349
+ * Additive. Invariant I6 ({@link screenGateOutcome}) screens its shape. */
345
350
  export type GateDisposition = {
346
351
  kind: "allowed";
347
352
  classifier?: AutoModeClassifierRound;
@@ -138,7 +138,7 @@ export type NoticeAudience = "user" | "operator";
138
138
  * the union's catalog ({@link EngineNotice.code}). The groups below say only which family a row joins,
139
139
  * so the catalog is not written a second time here and the two cannot drift.
140
140
  */
141
- export declare const ENGINE_NOTICE_CODES: readonly ["config.autocompact_window_clamped", "config.env_timeout_discarded", "config.materialize_env_discarded", "config.models_swapped", "config.context_settings_swapped", "config.read_face_swapped", "config.read_face_deployment_clamped", "config.write_face_swapped", "config.write_face_deployment_clamped", "config.read_deny_swapped", "config.tool_model_gate_removed", "config.tool_model_gate_unknown_class", "config.tool_model_gate_env_invalid", "config.artifact_host_invalid", "config.task_root_not_canonical", "config.write_protection_target_view_absent", "config.path_view_unresolved", "config.execution_env_capability_invalid", "config.tool_card_undeclared", "config.tool_face_undeclared", "config.tool_face_invalid", "config.forward_sink_failed", "config.durable_gate_unavailable", "config.peer_admission_out_of_range", "config.peer_lane_unmounted", "config.secret_env_scrubbed", "config.credential_not_withheld", "config.permission_mode_unavailable", "permission.ask_released_by_mode", "permission.read_deny_unapplied", "permission.rule_store_unavailable", "permission.rule_row_dropped", "permission.allowed_tool_not_granted", "mods.registration_refused", "peer.inbound_disposition", "peer.held_settled", "peer.idle_subscription", "classifier.denial_limit", "classifier.fallback", "classifier.role_defaulted", "classifier.contract_broken", "classifier.unavailable", "checkpoint.execution_outcome_unrecorded", "delegation.transcript_integrity", "delegation.ask_unresolvable", "mcp.injection_dropped", "mcp.revocation_probe_failed", "mcp.server_redialed", "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", "memory.restore_withheld", "memory.project_scope_absent", "route.fallback_to_primary", "route.classifier_unroutable", "route.credential_origin_mismatch", "route.response_model_mismatch", "route.tool_call_id_folded", "route.finish_reason_unknown", "route.stream_end_ignored", "route.extra_body_reserved_dropped", "context.tool_turn_repaired", "task.user_steer_undrained", "task.user_followup_undrained", "steering.parked_input_blocked", "task.turn_interrupted", "task.halt_unconsumed", "task.late_approval", "task.interrupt_unconsumed", "task.tool_turn_incomplete", "approval.read_root_granted", "approval.read_root_grant_rejected", "memory.capture_opted_out", "memory.capture_optout_unpersisted", "skills.listing_truncated", "agents.listing_truncated", "tool_result.offload_put_failed", "tool_result.media_refused", "instructions.source_changed", "session.records_skipped", "memory.layer_locked_legacy", "server.model_unreachable", "permission.entry_mode_clamped"];
141
+ export declare const ENGINE_NOTICE_CODES: readonly ["config.autocompact_window_clamped", "config.env_timeout_discarded", "config.materialize_env_discarded", "config.models_swapped", "config.context_settings_swapped", "config.read_face_swapped", "config.read_face_deployment_clamped", "config.write_face_swapped", "config.write_face_deployment_clamped", "config.read_deny_swapped", "config.tool_model_gate_removed", "config.tool_model_gate_unknown_class", "config.tool_model_gate_env_invalid", "config.artifact_host_invalid", "config.task_root_not_canonical", "config.write_protection_target_view_absent", "config.path_view_unresolved", "config.execution_env_capability_invalid", "config.tool_card_undeclared", "config.tool_face_undeclared", "config.tool_face_invalid", "config.forward_sink_failed", "config.durable_gate_unavailable", "config.peer_admission_out_of_range", "config.peer_lane_unmounted", "config.secret_env_scrubbed", "config.credential_not_withheld", "config.permission_mode_unavailable", "permission.ask_released_by_mode", "permission.read_deny_unapplied", "permission.rule_store_unavailable", "permission.rule_row_dropped", "config.session_rules_earlier_grammar", "permission.allowed_tool_not_granted", "mods.registration_refused", "peer.inbound_disposition", "peer.held_settled", "peer.idle_subscription", "classifier.denial_limit", "classifier.fallback", "classifier.role_defaulted", "classifier.contract_broken", "classifier.unavailable", "checkpoint.execution_outcome_unrecorded", "delegation.transcript_integrity", "delegation.ask_unresolvable", "mcp.injection_dropped", "mcp.revocation_probe_failed", "mcp.server_redialed", "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", "memory.restore_withheld", "memory.project_scope_absent", "route.fallback_to_primary", "route.classifier_unroutable", "route.credential_origin_mismatch", "route.response_model_mismatch", "route.tool_call_id_folded", "route.finish_reason_unknown", "route.stream_end_ignored", "route.extra_body_reserved_dropped", "context.tool_turn_repaired", "task.user_steer_undrained", "task.user_followup_undrained", "steering.parked_input_blocked", "task.turn_interrupted", "task.halt_unconsumed", "task.late_approval", "task.interrupt_unconsumed", "task.tool_turn_incomplete", "approval.read_root_granted", "approval.read_root_grant_rejected", "memory.capture_opted_out", "memory.capture_optout_unpersisted", "skills.listing_truncated", "agents.listing_truncated", "tool_result.offload_put_failed", "tool_result.media_refused", "instructions.source_changed", "session.records_skipped", "memory.layer_locked_legacy", "server.model_unreachable", "permission.entry_mode_clamped"];
142
142
  /** A catalogued code (see {@link ENGINE_NOTICE_CODES}); {@link NOTICE_MINTER} says who mints it. NOT the type of
143
143
  * `EngineNotice.code`, which stays `string` — a host forwarding its own notices through the same
144
144
  * sink is a supported shape, and narrowing that field would break it. */
@@ -187,6 +187,7 @@ declare const NOTICE_AUDIENCE_TABLE: {
187
187
  readonly "permission.read_deny_unapplied": "user";
188
188
  readonly "permission.rule_store_unavailable": "user";
189
189
  readonly "permission.rule_row_dropped": "user";
190
+ readonly "config.session_rules_earlier_grammar": "user";
190
191
  readonly "permission.allowed_tool_not_granted": "user";
191
192
  readonly "mods.registration_refused": "user";
192
193
  readonly "memory.capture_opted_out": "user";
@@ -309,6 +310,7 @@ declare const NOTICE_MINTER_TABLE: {
309
310
  readonly "permission.read_deny_unapplied": "engine";
310
311
  readonly "permission.rule_store_unavailable": "engine";
311
312
  readonly "permission.rule_row_dropped": "engine";
313
+ readonly "config.session_rules_earlier_grammar": "engine";
312
314
  readonly "permission.allowed_tool_not_granted": "engine";
313
315
  readonly "mods.registration_refused": "engine";
314
316
  readonly "peer.inbound_disposition": "engine";
@@ -151,6 +151,7 @@ export const ENGINE_NOTICE_CODES = Object.freeze([
151
151
  "permission.read_deny_unapplied",
152
152
  "permission.rule_store_unavailable",
153
153
  "permission.rule_row_dropped",
154
+ "config.session_rules_earlier_grammar",
154
155
  "permission.allowed_tool_not_granted",
155
156
  "mods.registration_refused",
156
157
  "peer.inbound_disposition",
@@ -246,6 +247,7 @@ const NOTICE_AUDIENCE_TABLE = {
246
247
  "permission.read_deny_unapplied": "user",
247
248
  "permission.rule_store_unavailable": "user",
248
249
  "permission.rule_row_dropped": "user",
250
+ "config.session_rules_earlier_grammar": "user",
249
251
  "permission.allowed_tool_not_granted": "user",
250
252
  "mods.registration_refused": "user",
251
253
  "memory.capture_opted_out": "user",
@@ -349,6 +351,7 @@ const NOTICE_MINTER_TABLE = {
349
351
  "permission.read_deny_unapplied": "engine",
350
352
  "permission.rule_store_unavailable": "engine",
351
353
  "permission.rule_row_dropped": "engine",
354
+ "config.session_rules_earlier_grammar": "engine",
352
355
  "permission.allowed_tool_not_granted": "engine",
353
356
  "mods.registration_refused": "engine",
354
357
  "peer.inbound_disposition": "engine",
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Total over what a deployment can hand the seat: absent passes; a value that is not a plain record, a member the record does not
3
+ * have, or a member holding a value it does not take refuses by name (`config.invalid_argument`, `detail.field` = `RunnerDeps.hands`
4
+ * or `RunnerDeps.hands.<member>`) — a band setting the engine cannot read is never run as if it were absent. A member holding
5
+ * `undefined` is absence, as on every optional seat.
6
+ */
7
+ export declare function screenHandsBandOptions(value: unknown): void;
@@ -0,0 +1,32 @@
1
+ const HANDS_BAND_MEMBERS = {
2
+ bashReadonlyAllow: { takes: "a list of command-name strings", ok: (v) => Array.isArray(v) && v.every((c) => typeof c === "string") },
3
+ commitCoAuthor: { takes: "a string or false", ok: (v) => typeof v === "string" || v === false },
4
+ readImageDownsampler: { takes: "a function or false", ok: (v) => typeof v === "function" || v === false },
5
+ beforeWrite: { takes: "a function", ok: (v) => typeof v === "function" },
6
+ autoBackgroundOnTimeout: { takes: "a boolean", ok: (v) => typeof v === "boolean" },
7
+ };
8
+ const kindOf = (v) => (v === null ? "null" : Array.isArray(v) ? "an array" : typeof v);
9
+ export function screenHandsBandOptions(value) {
10
+ if (value === undefined)
11
+ return;
12
+ const refuse = (field, problem) => {
13
+ const e = new Error(`${field} ${problem} — a hands band setting the engine cannot read is refused rather than ignored.`);
14
+ e.code = "config.invalid_argument";
15
+ e.detail = { field };
16
+ throw e;
17
+ };
18
+ const proto = typeof value === "object" && value !== null ? Object.getPrototypeOf(value) : undefined;
19
+ if (typeof value !== "object" || value === null || Array.isArray(value) || (proto !== Object.prototype && proto !== null)) {
20
+ refuse("RunnerDeps.hands", `must be a plain record of hands band settings (got ${kindOf(value)})`);
21
+ }
22
+ for (const key of Object.keys(value)) {
23
+ const v = value[key];
24
+ if (v === undefined)
25
+ continue;
26
+ if (!Object.hasOwn(HANDS_BAND_MEMBERS, key))
27
+ refuse(`RunnerDeps.hands.${key}`, `is not a hands band setting (the settings: ${Object.keys(HANDS_BAND_MEMBERS).join(", ")})`);
28
+ const member = HANDS_BAND_MEMBERS[key];
29
+ if (!member.ok(v))
30
+ refuse(`RunnerDeps.hands.${key}`, `must be ${member.takes} (got ${kindOf(v)})`);
31
+ }
32
+ }
@@ -1,7 +1,7 @@
1
1
  import type { ActorAssertion, DocumentContent, ImageContent, TextContent } from "../internal/llm.js";
2
2
  import type { ExecutionEnv, FileError, Result, SessionTreeEntry } from "../internal/harness-types.js";
3
3
  import type { DecisionReason, PermissionResult, ResolvedAsk, ToolCallFace, ToolCallRequest, ToolPolicy } from "./tool-policy.js";
4
- import type { CallTargetStamp } from "./effective-path-target.js";
4
+ import { type CallTargetStamp } from "./effective-path-target.js";
5
5
  import type { GateOutcome } from "./gate-outcome.js";
6
6
  import { type PreToolUseScreening } from "./runner/gate-exit.js";
7
7
  export { normalizeOrgGateVerdict, normalizePersistedRuleHit } from "./rule-lane-reads.js";
@@ -629,9 +629,9 @@ export interface ParkAttemptFailed {
629
629
  */
630
630
  export type OrgGateVerdict = {
631
631
  status: "available";
632
- /** The org rule that speaks for this call, if any. Deny outranks ask; there is no allow bucket. An
633
- * `unreadable` member (the lexer's reason) says the call could not be READ against `rule` — an
634
- * org rule that may reach it — so `behavior` is `ask` and the message names the reason. */
632
+ /** The org rule that speaks for this call, if any. Deny outranks ask; there is no allow bucket. An `unreadable` member says why
633
+ * `rule` could not be judged against the call (the message names it): the call could not be READ against it (the lexer's reason;
634
+ * `behavior` is `ask`), or the row is one the org layer does not read, standing at its strictest reading (`deny`, or `ask`). */
635
635
  verdict?: {
636
636
  behavior: "deny" | "ask";
637
637
  rule: string;
@@ -727,11 +727,13 @@ export interface ToolGateInput extends PermissionSeatInputs, SkillGateSeats, All
727
727
  onNotifyError?: (failure: import("./safe-notify.js").SafeNotifyFailure) => void;
728
728
  /** The call. `toolName` is the name every policy, hook and name-keyed table of this gate reads — the wire name of the
729
729
  * tool the call dispatches to: the Runner's station resolves a call made under an alias through the roster
730
- * (`rosterToolName`) before this gate runs; a host driving the gate resolves it the same way. */
730
+ * (`rosterToolName`) before this gate runs; a host driving the gate resolves it the same way. `readArguments` = the loop's
731
+ * reading of the call's args: stamped on the call's face, every rewrite this gate adopts is read through it (`argsAsTheToolReads`). */
731
732
  event: {
732
733
  toolCallId: string;
733
734
  toolName: string;
734
735
  input: Record<string, unknown>;
736
+ readArguments?: (args: unknown) => unknown;
735
737
  };
736
738
  /** The run/leg identity envelope, put verbatim on every {@link PreToolUseEvent} this
737
739
  * gate call builds and on every {@link PermissionDeniedPayload} it emits. The Runner supplies its
@@ -1,4 +1,5 @@
1
- import { abortedSeatDeny, decisionText, describeThrown, isEngineRefusal, ownArgs, refuseOutOfContractDecision, unaskedSeatAnswer } from "./tool-policy.js";
1
+ import { argsAsTheToolReads, stampArgsReading } from "./effective-path-target.js";
2
+ import { abortedSeatDeny, decisionText, describeThrown, isEngineRefusal, ownArgs, ownedReading, refuseOutOfContractDecision, unaskedSeatAnswer } from "./tool-policy.js";
2
3
  import { createSettlementLedger, engineFailClosedExit, hookDenyExit, unownedArgsExit } from "./runner/gate-exit.js";
3
4
  import { classicEventBase, dispatchClassic, ENGINE_CORE_ANSWER, engineCore, failedHandlerClause, judgedInputOf, observePermissionDenied, recordScreeningChain } from "./mods/stations.js";
4
5
  import { runGateFold } from "./gate-fold.js";
@@ -235,7 +236,8 @@ export async function runToolGate(input) {
235
236
  screening,
236
237
  afterFold: runGateLanes,
237
238
  currentInput: args,
238
- req: Object.freeze({ toolName, args, toolCallId, ...(() => { const f = input.toolFaceOf?.(toolName); return f !== undefined ? { face: f } : {}; })() }),
239
+ req: Object.freeze({ toolName, args, toolCallId, ...(() => { const f = input.toolFaceOf?.(toolName); const read = event.readArguments; if (f !== undefined && read !== undefined)
240
+ stampArgsReading(f, ownedReading(read)); return f !== undefined ? { face: f } : {}; })() }),
239
241
  preToolContext: [],
240
242
  hookAsk: undefined,
241
243
  parkFailed: undefined,
@@ -261,7 +263,7 @@ export async function runToolGate(input) {
261
263
  return hookDenyExit(pass, screening.deniedReason(r));
262
264
  }
263
265
  if (r.updatedInput !== undefined) {
264
- pass.currentInput = r.updatedInput;
266
+ pass.currentInput = argsAsTheToolReads(pass.req, r.updatedInput);
265
267
  }
266
268
  if (r.action === "ask") {
267
269
  pass.hookAsk = r;