@sema-agent/core 7.33.8 → 7.35.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 (228) hide show
  1. package/CHANGELOG.md +462 -3
  2. package/dist/agents/cross-session-judge.d.ts +16 -7
  3. package/dist/agents/cross-session-judge.js +18 -15
  4. package/dist/agents/peer-session-drain.d.ts +4 -0
  5. package/dist/agents/peer-session-drain.js +5 -2
  6. package/dist/agents/repair-loop.d.ts +1 -1
  7. package/dist/agents/subagent.d.ts +3 -0
  8. package/dist/agents/subagent.js +12 -3
  9. package/dist/brain/anthropic.js +1 -0
  10. package/dist/brain/errors.d.ts +23 -1
  11. package/dist/brain/errors.js +19 -2
  12. package/dist/brain/open-responses.js +1 -0
  13. package/dist/brain/openai.js +1 -0
  14. package/dist/brain/status-sink.d.ts +7 -1
  15. package/dist/brain/stream-engine.d.ts +3 -0
  16. package/dist/brain/stream-engine.js +2 -1
  17. package/dist/brain/stream-shared.d.ts +3 -1
  18. package/dist/brain/stream-shared.js +16 -4
  19. package/dist/core/a2a.d.ts +3 -1
  20. package/dist/core/a2a.js +1 -1
  21. package/dist/core/ask-origin.d.ts +379 -154
  22. package/dist/core/ask-origin.js +224 -45
  23. package/dist/core/ask-question.js +2 -1
  24. package/dist/core/ask-unresolvable-notice.d.ts +5 -2
  25. package/dist/core/ask-unresolvable-notice.js +4 -3
  26. package/dist/core/auto-mode-arming.d.ts +17 -4
  27. package/dist/core/auto-mode-arming.js +31 -6
  28. package/dist/core/auto-mode-defaults.d.ts +16 -7
  29. package/dist/core/auto-mode-defaults.js +2 -1
  30. package/dist/core/auto-mode-directive.d.ts +18 -30
  31. package/dist/core/auto-mode-directive.js +9 -18
  32. package/dist/core/auto-mode-prompt.d.ts +62 -1
  33. package/dist/core/auto-mode-prompt.js +42 -4
  34. package/dist/core/auto-mode-rebuild.d.ts +2 -1
  35. package/dist/core/auto-mode-rebuild.js +1 -0
  36. package/dist/core/auto-mode.d.ts +32 -3
  37. package/dist/core/auto-mode.js +80 -41
  38. package/dist/core/checkpoint-store.d.ts +54 -35
  39. package/dist/core/checkpoint-store.js +58 -3
  40. package/dist/core/child-clamp-seats.js +1 -0
  41. package/dist/core/classifier-unavailable-arm.d.ts +57 -0
  42. package/dist/core/classifier-unavailable-arm.js +17 -0
  43. package/dist/core/effective-path-target.d.ts +12 -0
  44. package/dist/core/effective-path-target.js +18 -7
  45. package/dist/core/engine-notice.d.ts +53 -2
  46. package/dist/core/fs-write-gate-policy.d.ts +5 -1
  47. package/dist/core/fs-write-gate-policy.js +4 -1
  48. package/dist/core/gate-fold.js +19 -3
  49. package/dist/core/gate-lanes.js +116 -50
  50. package/dist/core/gate-outcome.d.ts +40 -7
  51. package/dist/core/gate-outcome.js +26 -4
  52. package/dist/core/governance-codes.d.ts +18 -2
  53. package/dist/core/governance-codes.js +23 -1
  54. package/dist/core/hooks.d.ts +53 -29
  55. package/dist/core/hooks.js +1 -3
  56. package/dist/core/mcp.js +1 -1
  57. package/dist/core/message-utils.d.ts +2 -1
  58. package/dist/core/message-utils.js +2 -1
  59. package/dist/core/mode-policy.d.ts +185 -0
  60. package/dist/core/mode-policy.js +184 -0
  61. package/dist/core/oracle-isolation.d.ts +3 -3
  62. package/dist/core/permission-rule-model.d.ts +20 -3
  63. package/dist/core/permission-rule-model.js +40 -3
  64. package/dist/core/permission-rule-provider.d.ts +22 -11
  65. package/dist/core/permission-rule-provider.js +17 -7
  66. package/dist/core/permission-rules.d.ts +23 -1
  67. package/dist/core/permission-rules.js +56 -9
  68. package/dist/core/persisted-rule-arms.d.ts +14 -10
  69. package/dist/core/persisted-rule-arms.js +13 -6
  70. package/dist/core/present-plan-tool.d.ts +1 -1
  71. package/dist/core/present-plan-tool.js +3 -3
  72. package/dist/core/read-only-shell.d.ts +19 -19
  73. package/dist/core/read-only-shell.js +38 -12
  74. package/dist/core/retired-keys.d.ts +9 -3
  75. package/dist/core/retired-keys.js +5 -1
  76. package/dist/core/runner/active-skill-scope.js +5 -4
  77. package/dist/core/runner/assemble-result.js +2 -2
  78. package/dist/core/runner/classifier-ladder.d.ts +43 -1
  79. package/dist/core/runner/classifier-ladder.js +29 -0
  80. package/dist/core/runner/contracts.d.ts +62 -38
  81. package/dist/core/runner/denial-limit-arms.d.ts +35 -7
  82. package/dist/core/runner/denial-limit-arms.js +15 -9
  83. package/dist/core/runner/env-capability-census.js +1 -1
  84. package/dist/core/runner/file-history-request.d.ts +26 -0
  85. package/dist/core/runner/file-history-request.js +90 -0
  86. package/dist/core/runner/gate-exit.d.ts +30 -4
  87. package/dist/core/runner/gate-exit.js +28 -7
  88. package/dist/core/runner/permission-rule-lanes.d.ts +11 -6
  89. package/dist/core/runner/permission-rule-lanes.js +55 -48
  90. package/dist/core/runner/persisted-path-rows.d.ts +64 -0
  91. package/dist/core/runner/persisted-path-rows.js +89 -0
  92. package/dist/core/runner/prepare-artifact.d.ts +5 -1
  93. package/dist/core/runner/prepare-artifact.js +9 -3
  94. package/dist/core/runner/prepare-ask-lane.d.ts +6 -4
  95. package/dist/core/runner/prepare-ask-lane.js +3 -3
  96. package/dist/core/runner/prepare-caps-and-workflow.d.ts +39 -13
  97. package/dist/core/runner/prepare-caps-and-workflow.js +58 -29
  98. package/dist/core/runner/prepare-file-history.d.ts +9 -3
  99. package/dist/core/runner/prepare-file-history.js +4 -90
  100. package/dist/core/runner/prepare-gate-stations.d.ts +37 -9
  101. package/dist/core/runner/prepare-gate-stations.js +46 -29
  102. package/dist/core/runner/prepare-hands-readface.d.ts +40 -17
  103. package/dist/core/runner/prepare-hands-readface.js +64 -53
  104. package/dist/core/runner/prepare-inherited-gate.d.ts +30 -23
  105. package/dist/core/runner/prepare-inherited-gate.js +45 -25
  106. package/dist/core/runner/prepare-park-ask.d.ts +6 -4
  107. package/dist/core/runner/prepare-park-ask.js +7 -5
  108. package/dist/core/runner/prepare-policy-chain.d.ts +25 -4
  109. package/dist/core/runner/prepare-policy-chain.js +203 -180
  110. package/dist/core/runner/prepare-protocol-tools.d.ts +28 -0
  111. package/dist/core/runner/prepare-protocol-tools.js +13 -3
  112. package/dist/core/runner/prepare-run-refs.d.ts +6 -6
  113. package/dist/core/runner/prepare-run-refs.js +9 -4
  114. package/dist/core/runner/prepare-safety-scan.d.ts +5 -0
  115. package/dist/core/runner/prepare-safety-scan.js +19 -3
  116. package/dist/core/runner/prepare-suspend-saga.d.ts +5 -5
  117. package/dist/core/runner/prepare-suspend-saga.js +6 -6
  118. package/dist/core/runner/prepare-task.js +56 -24
  119. package/dist/core/runner/prepare-wiring-manifest.d.ts +10 -6
  120. package/dist/core/runner/prepare-wiring-manifest.js +5 -3
  121. package/dist/core/runner/read-root-grant.d.ts +9 -0
  122. package/dist/core/runner/read-root-grant.js +12 -0
  123. package/dist/core/runner/retired-runner-deps.js +1 -0
  124. package/dist/core/runner/run-brain-sinks.js +3 -1
  125. package/dist/core/runner/run-identity-wiring.js +1 -1
  126. package/dist/core/runner/run-leg.js +29 -18
  127. package/dist/core/runner/run-notification-lane.d.ts +3 -0
  128. package/dist/core/runner/run-notification-lane.js +3 -1
  129. package/dist/core/runner/run-telemetry-and-budget-seats.d.ts +2 -2
  130. package/dist/core/runner/run-telemetry-and-budget-seats.js +1 -1
  131. package/dist/core/runner/run-turn-boundary.js +1 -1
  132. package/dist/core/runner/runtask.d.ts +1 -0
  133. package/dist/core/runner/runtask.js +8 -11
  134. package/dist/core/runner/session-mode-seats.d.ts +48 -0
  135. package/dist/core/runner/session-mode-seats.js +73 -0
  136. package/dist/core/runner/session-rule-policy.js +3 -2
  137. package/dist/core/runner/stream-mode-verb.d.ts +27 -0
  138. package/dist/core/runner/stream-mode-verb.js +19 -0
  139. package/dist/core/runner/turn-attachments.d.ts +3 -3
  140. package/dist/core/runner/turn-attachments.js +1 -1
  141. package/dist/core/runner/write-protection-station.d.ts +7 -1
  142. package/dist/core/runner/write-protection-station.js +6 -3
  143. package/dist/core/runner-deps.d.ts +31 -34
  144. package/dist/core/runtime-caps.d.ts +12 -3
  145. package/dist/core/safety-axis-vocab.d.ts +2 -2
  146. package/dist/core/safety-axis-vocab.js +1 -1
  147. package/dist/core/secret-env.d.ts +30 -58
  148. package/dist/core/secret-env.js +149 -586
  149. package/dist/core/sensitive-path-policy.d.ts +4 -2
  150. package/dist/core/sensitive-path-policy.js +41 -31
  151. package/dist/core/store-contracts/checkpoint-store-contract.js +2 -1
  152. package/dist/core/task-event.d.ts +1 -1
  153. package/dist/core/task-result.d.ts +1 -1
  154. package/dist/core/task-spec.d.ts +41 -89
  155. package/dist/core/task-stream.d.ts +16 -0
  156. package/dist/core/terminal-cause.js +1 -0
  157. package/dist/core/tighten-task-spec.d.ts +2 -2
  158. package/dist/core/tighten-task-spec.js +12 -14
  159. package/dist/core/tool-catalog-entries.js +4 -3
  160. package/dist/core/tool-policy.d.ts +84 -30
  161. package/dist/core/tool-policy.js +76 -16
  162. package/dist/core/tool-registry.d.ts +38 -0
  163. package/dist/core/tool-registry.js +25 -1
  164. package/dist/core/tool-roster.d.ts +17 -2
  165. package/dist/core/tool-roster.js +11 -1
  166. package/dist/core/tool-spec.d.ts +8 -9
  167. package/dist/core/trace.d.ts +18 -6
  168. package/dist/core/types.d.ts +2 -1
  169. package/dist/core/wiring-manifest.d.ts +31 -5
  170. package/dist/core/wiring-manifest.js +28 -1
  171. package/dist/core/write-protect.d.ts +11 -1
  172. package/dist/core/write-protect.js +26 -15
  173. package/dist/engine/execution-env/node-execution-env.d.ts +6 -1
  174. package/dist/engine/execution-env/node-execution-env.js +16 -3
  175. package/dist/engine/harness/agent-harness.js +2 -1
  176. package/dist/engine/harness/types.d.ts +6 -7
  177. package/dist/engine/llm/types.d.ts +2 -4
  178. package/dist/engine/loop/types.d.ts +7 -0
  179. package/dist/engine/lsp/node-lsp-manager.js +1 -1
  180. package/dist/index.d.ts +18 -8
  181. package/dist/index.js +15 -6
  182. package/dist/orchestration/workflow-governance.js +1 -1
  183. package/dist/orchestration/workflow-primitives.d.ts +1 -1
  184. package/dist/orchestration/workflow-size-guideline.js +2 -2
  185. package/dist/stores/file/index.d.ts +5 -3
  186. package/dist/stores/file/session-store.d.ts +8 -2
  187. package/dist/stores/file/session-store.js +5 -5
  188. package/dist/tools/artifact/artifact-tool.d.ts +17 -0
  189. package/dist/tools/artifact/artifact-tool.js +16 -1
  190. package/dist/tools/fs/bash-readonly-classifier.d.ts +28 -7
  191. package/dist/tools/fs/bash-readonly-classifier.js +88 -0
  192. package/dist/tools/fs/containment-face.d.ts +9 -3
  193. package/dist/tools/fs/containment-face.js +2 -0
  194. package/dist/tools/fs/file-read-check.d.ts +50 -0
  195. package/dist/tools/fs/file-read-check.js +79 -0
  196. package/dist/tools/fs/fs-bash.d.ts +3 -3
  197. package/dist/tools/fs/fs-read.d.ts +1 -0
  198. package/dist/tools/fs/fs-search-tools.d.ts +2 -2
  199. package/dist/tools/fs/fs-search-tools.js +4 -4
  200. package/dist/tools/fs/fs-shared.js +20 -6
  201. package/dist/tools/fs/fs-write.d.ts +4 -3
  202. package/dist/tools/fs/fs-write.js +9 -9
  203. package/dist/tools/fs/index.d.ts +39 -3
  204. package/dist/tools/fs/index.js +38 -15
  205. package/dist/tools/fs/read-deny-home.d.ts +93 -0
  206. package/dist/tools/fs/read-deny-home.js +225 -0
  207. package/dist/tools/fs/read-deny.d.ts +176 -34
  208. package/dist/tools/fs/read-deny.js +268 -61
  209. package/dist/tools/fs/safety.d.ts +6 -1
  210. package/dist/tools/fs/safety.js +18 -5
  211. package/dist/tools/fs/search.d.ts +3 -0
  212. package/dist/tools/fs/search.js +47 -9
  213. package/dist/tools/fs/shell-read-check.d.ts +52 -13
  214. package/dist/tools/fs/shell-read-check.js +199 -20
  215. package/dist/tools/fs/shell-read-establish.d.ts +7 -3
  216. package/dist/tools/fs/shell-read-establish.js +134 -21
  217. package/dist/tools/fs/shell-read-identity.d.ts +7 -0
  218. package/dist/tools/fs/shell-read-identity.js +21 -0
  219. package/dist/tools/fs/shell-read-judge.d.ts +92 -32
  220. package/dist/tools/fs/shell-read-judge.js +43 -18
  221. package/dist/tools/fs/shell-read-plan.d.ts +4 -0
  222. package/dist/tools/fs/shell-read-plan.js +26 -4
  223. package/dist/tools/fs/shell-read-write-targets.d.ts +73 -0
  224. package/dist/tools/fs/shell-read-write-targets.js +173 -0
  225. package/package.json +1 -1
  226. package/test/export-surface.snapshot.json +117 -11
  227. package/wire-changes/core@7.34.0.json +2621 -0
  228. package/wire-changes/core@7.35.0.json +584 -0
@@ -1,9 +1,14 @@
1
- import type { AskOrigin } from "./ask-origin.js";
1
+ import { type AskOrigin, type AssertAllKeysHandled } from "./ask-origin.js";
2
2
  import { type ReadRootGrantScreen } from "./read-root-grant-screen.js";
3
3
  import { type Settlement } from "./gate-outcome.js";
4
4
  /** A tool call presented to a policy before it executes. */
5
5
  /** design/388 B6 — the per-call face (see {@link ToolCallRequest.face}). */
6
6
  export interface ToolCallFace {
7
+ /** Every name a tool-name rule may spell this tool by: the tool's wire name first, then each alias that resolves to
8
+ * it on the leg's roster (`rosterToolName`). A rule reaches the call iff its tool name is on this list — read it
9
+ * through {@link ruleNamesOf}, never off `toolName` alone. Absent on a face rebuilt from a park row that predates it
10
+ * (the call's own name is then the list). */
11
+ names?: readonly string[];
7
12
  family: import("./tool-face.js").ToolFamily;
8
13
  pathTarget?: import("./tool-face.js").ToolRosterPathTarget;
9
14
  capabilityId: string;
@@ -12,6 +17,8 @@ export interface ToolCallFace {
12
17
  implementationRevision: string;
13
18
  }
14
19
  export interface ToolCallRequest {
20
+ /** The tool's wire name — the Runner's gate resolves a call made under an alias to the wire name of the tool it
21
+ * dispatches to before any policy runs (`rosterToolName`); the names a rule may use for it are {@link ruleNamesOf}. */
15
22
  toolName: string;
16
23
  args: unknown;
17
24
  toolCallId: string;
@@ -325,9 +332,8 @@ export type PermissionResult = {
325
332
  /** This ask is MANDATED — no persisted allow rule and no STANDING ANSWER (a release remembered from an
326
333
  * earlier call, at any scope, a session included) may retire it; only an answer produced for THIS call
327
334
  * clears it — a person, a hook, a deployment's blanket `onAsk: "allow"` seat, or the live deciders the
328
- * mandate tables admit ({@link import("./ask-origin.js").classifierMayAnswerMandate} /
329
- * `sandboxMayAdmitMandate`); {@link requiresRealApproval} is the stronger demand that also refuses the
330
- * blanket seat. ENGINE-STAMPED at the gate's single origin stamp from the ONE derivation
335
+ * ask's class row admits ({@link import("./ask-origin.js").MODE_ARBITRATION}); {@link requiresRealApproval}
336
+ * is the stronger demand that also refuses the blanket seat. ENGINE-STAMPED at the gate's single origin stamp from the ONE derivation
331
337
  * the approval card's rule-offer word also reads ({@link import("./ask-origin.js").ruleClearanceOf},
332
338
  * which lists the doors). TIGHTENING-ONLY: a layer below the mint may not clear one a layer above
333
339
  * set, and a policy self-declaring it only opts its own ask OUT of automatic answering. Carried
@@ -376,19 +382,34 @@ export type PermissionResult = {
376
382
  * fallback / the reserved question tool / a marked call / plain policy). ENGINE-STAMPED inside the
377
383
  * gate at the same single stamp point as `ruleEvidence`, derived from what the gate's own layers
378
384
  * did ({@link import("./ask-origin.js").askOriginOf}) — a policy that self-declares it is overwritten
379
- * there, so the member is never a channel a policy can loosen through. Consumers: the gate's
380
- * auto-mode classifier step admits an ask by ONE table lookup on this word
381
- * ({@link import("./ask-origin.js").classifierMayAnswer}) instead of a chain of per-kind
382
- * exclusions; an approval surface may render the card by it. Optional on the TYPE only because a
383
- * decision is caller-composable; every ask the gate hands to its resolver carries it. */
385
+ * there, so the member is never a channel a policy can loosen through. A DISCLOSURE word: the lanes
386
+ * read the ask's class set ({@link import("./ask-origin.js").classifyAsk}), an approval surface may
387
+ * render the card by it. Optional on the TYPE only because a decision is caller-composable; every ask
388
+ * the gate hands to its resolver carries it. */
384
389
  origin?: import("./ask-origin.js").AskOrigin;
385
- /** #688 C3 (additive): WHICH mechanism stands behind an `origin: "rule_store_unavailable"` — one word from
386
- * {@link import("./ask-origin.js").RULE_STORE_UNREADABLE_KINDS} (`store`: the wired store could not be read;
387
- * `call`: the call could not be read against the person's rows — the lexer's sentence is in `message`).
388
- * ENGINE-STAMPED at the same single stamp point as `origin`, from the gate's own lane read; a policy's
389
- * self-declared value is overwritten or removed there. Present ⇔ the origin is `rule_store_unavailable`.
390
- * Carried onto the approval request and the durable row beside the origin word. */
391
- ruleStoreUnreadable?: import("./ask-origin.js").RuleStoreUnreadable;
390
+ /** WHICH integrity guard minted this ask — the unverifiable recursive delete or the session-transcript
391
+ * write. ENGINE-STAMPED by the two guard policies; a fold-internal fact the class derivation reads
392
+ * ({@link import("./ask-origin.js").classifyAsk}) so the two guards, which mint the same
393
+ * `requiresRealApproval` ask, are told apart by a fact and not by their prose. Never copied onto the
394
+ * approval request or the park row. */
395
+ guard?: "unverifiable_delete" | "transcript_write" | "artifact_shared_read";
396
+ /** The call could not be READ against a rule that may reach it — a content-form deny row, a content-form
397
+ * ask row, or a persisted-rule row (the lexer's word). ENGINE-STAMPED at the three unreadable mint sites;
398
+ * the class derivation reads it as `rule_unreadable` (a mode may release it, a person may clear it, no
399
+ * automatic lane may). It replaces the former practice of stamping `matchedAskRule` on an unreadable
400
+ * read: a rule that COULD NOT be matched is not a rule that matched. Fold-internal; not on the request. */
401
+ ruleUnreadable?: "content_deny" | "content_ask" | "persisted_call";
402
+ /** The ask was authored by the DEPLOYMENT's own governance (an operator's must-ask list, a managed command
403
+ * policy's default ask) — a fact a host's policy states on its own ask so the class derivation reads it
404
+ * as `operator_forced`: a permission mode releases nothing an operator mandated, and `dontAsk` does not
405
+ * settle it. Public: a host composing such a policy sets it; {@link combinePolicies} carries it onto the
406
+ * surviving ask (tighten-only). Not on the approval request — the card shows `origin` / `mandate`. */
407
+ authoredBy?: "deployment";
408
+ /** The classes of the OTHER asks a policy fold collapsed into this one
409
+ * ({@link import("./ask-origin.js").decisionAskClassesOf} per contributing ask, unioned), so a member the
410
+ * fold does not copy — a guard mark, an unreadable-rule fact, a bare real-approval bit — is not lost behind
411
+ * the ask that won the slot. ENGINE-WRITTEN by {@link combinePolicies}; fold-internal, never on the request. */
412
+ foldedAskClasses?: readonly import("./ask-origin.js").AskClass[];
392
413
  } | {
393
414
  action: "deny";
394
415
  updatedInput?: unknown;
@@ -495,7 +516,44 @@ export interface ToolPolicy {
495
516
  * describes the policy must read that bit, not the field's presence.
496
517
  */
497
518
  readonly projection?: ToolPolicyProjection;
519
+ /** The policy's path DENY rules that speak for a face of the engine's deny table (a `Read(<pattern>)` / `Edit(<pattern>)`
520
+ * deny compiled by `createPermissionRulePolicy`), as rows the prepared leg joins to its read and write tables — so the
521
+ * shell's reads and writes and the traversals meet the rule, not only the tool it names. Carried through
522
+ * {@link combinePolicies}; read with {@link toolPolicyPathDenyRules}. Deny-only: a row can only refuse more. */
523
+ readonly pathDenyRules?: readonly import("../tools/fs/read-deny.js").PathRuleDenyRow[];
524
+ /** Does this policy guard a write to this ABSOLUTE target — would it refuse the write if a write tool made it? The
525
+ * policy's answer for a writer it cannot see as a call (the shell's file commands), read by the shell station's
526
+ * `writesPlaced` fact alone: a guarded target is not placed, so no permission mode answers the command and its ask
527
+ * reaches a person — it is not refused. A PURE judgment: no side effect, no audit record, no hook, no approver; `true`
528
+ * on a target it cannot judge. Carried through {@link combinePolicies} (any member's `true` is the answer). */
529
+ readonly writeTargetGuard?: (target: string, signal?: AbortSignal) => Promise<boolean>;
530
+ /** The raw tool-name lists a name-keyed policy was built from — the prepare-time policy-name audit's feed (a retired
531
+ * name refuses the run, an unmounted one is advised). Carried through {@link combinePolicies} (the union of the
532
+ * members' groups); read with {@link toolPolicyNameSets}. */
533
+ readonly nameSets?: readonly ToolPolicyNameSets[];
498
534
  }
535
+ /**
536
+ * The members a policy carries beside `check` — one table for every seat that carries them: {@link wrapToolPolicy} reads
537
+ * each by name off the wrapped policy, {@link combinePolicies} folds each. The fence makes a new {@link ToolPolicy} member
538
+ * a compile error until the table names it.
539
+ */
540
+ declare const TOOL_POLICY_CARRIED_MEMBERS: readonly ["projection", "pathDenyRules", "writeTargetGuard", "nameSets"];
541
+ type ToolPolicyCarriedMember = (typeof TOOL_POLICY_CARRIED_MEMBERS)[number];
542
+ export type ToolPolicyCarriesEveryMember = AssertAllKeysHandled<Exclude<Exclude<keyof ToolPolicy, "check">, ToolPolicyCarriedMember>>;
543
+ /**
544
+ * The ONE way to wrap a policy: the wrapper decides through `check`; every member {@link ToolPolicy} declares beside it
545
+ * (projection, name sets, path deny rows, write-target guard — the carried-member table) rides as `inner` holds it, read
546
+ * by name, so a class instance's accessor or a non-enumerable member rides too — a `{ check }` literal drops them, and
547
+ * engine stations the wrapper never sees read each one. The carried members describe what `inner` refuses, so the wrapper
548
+ * must refuse at least that (an observer, a tightener); one that releases what `inner` refuses is a policy of its own.
549
+ * The ask-class brand is not carried: it attests the replaced `check`.
550
+ */
551
+ export declare function wrapToolPolicy<P extends ToolPolicy>(inner: P, check: ToolPolicy["check"]): Pick<P, ToolPolicyCarriedMember> & ToolPolicy;
552
+ /** Every path deny row the given policies carry (combined policies included), in order — the deny tables' rule rows. */
553
+ export declare function toolPolicyPathDenyRules(policies: readonly (ToolPolicy | undefined)[]): readonly import("../tools/fs/read-deny.js").PathRuleDenyRow[];
554
+ /** The one write-target guard the given policies carry together (combined policies included): `true` when any member
555
+ * guards the target, a member that throws counting as guarding it. `undefined` when none carries one. */
556
+ export declare function toolPolicyWriteTargetGuard(policies: readonly (ToolPolicy | undefined)[]): ToolPolicy["writeTargetGuard"];
499
557
  /**
500
558
  * One deterministic DENY component of a {@link ToolPolicyProjection} (F-012 L1). All fields are plain
501
559
  * serializable data — a component round-trips JSON/`structuredClone` unchanged, which is what lets a
@@ -574,10 +632,7 @@ export declare function namespacedCoveringHit(covering: readonly string[], toolN
574
632
  * trusting a re-supplied policy's execution half for the frozen deny data: the projection persisted
575
633
  * at suspend is the authority for what the chain refused, whatever a re-supplied closure claims.
576
634
  */
577
- export declare function checkToolPolicyProjection(projection: Pick<ToolPolicyProjection, "components">, req: {
578
- toolName: string;
579
- args: unknown;
580
- }): Extract<PermissionResult, {
635
+ export declare function checkToolPolicyProjection(projection: Pick<ToolPolicyProjection, "components">, req: Pick<ToolCallRequest, "toolName" | "args" | "face">): Extract<PermissionResult, {
581
636
  action: "deny";
582
637
  }> | undefined;
583
638
  /**
@@ -751,7 +806,7 @@ export interface ToolPolicyNameSets {
751
806
  * here silently WIDENS (the tool runs without ever asking), so it is audited too. */
752
807
  readonly ask?: readonly string[];
753
808
  }
754
- /** A policy that exposes its name lists for the host-side pool audit. Composition-transparent:
809
+ /** A policy that states its name lists ({@link ToolPolicy.nameSets} set). Composition-transparent:
755
810
  * {@link combinePolicies} (and therefore {@link tightenTaskSpec}) unions children's groups onto the
756
811
  * combined policy, so metadata survives the deployment's tighten/compose path. */
757
812
  export type NamedToolPolicy = ToolPolicy & {
@@ -910,10 +965,10 @@ export declare const COARSE_SHELL_TOOLS: string[];
910
965
  * you MUST compose explicitly: `combinePolicies(deploymentBaseline, createCoarseCommandNamePolicy(…))`
911
966
  * (deny-wins — both policies' denies still fire). {@link tightenTaskSpec} does this for you.
912
967
  *
913
- * Under `TaskSpec.shellGate: "classify"` a plain `ask` from this policy stays a plain ask: the shell seat's
914
- * `"maybe"` tier is the doctrine's probe instrument, not a risk label, so the park is the budgetable kind
968
+ * On the full shell face a plain `ask` from this policy stays a plain ask: the shell seat's
969
+ * `"maybe"` tier is the shape probe's instrument, not a risk label, so the park is the budgetable kind
915
970
  * (`human` under `durableApproval`) unless the probe mandated the call (an out-of-root read) or could not
916
- * answer. A command the doctrine's OWN tighten refuses (`rm -rf …` with no policy asking first) still parks
971
+ * answer. A command the shape tier's OWN tighten refuses (`rm -rf …` with no policy asking first) still parks
917
972
  * non-budgetable — the safety axis is judged per call by `safetyAxisOf`.
918
973
  */
919
974
  export declare function createCoarseCommandNamePolicy(opts: {
@@ -1299,6 +1354,11 @@ export interface AskRequest {
1299
1354
  * gate, never caller/worker-settable; tightening-only.
1300
1355
  */
1301
1356
  readonly mandated?: true;
1357
+ /** The ask was minted by the deployment's own governance layer (the {@link PermissionResult} ask arm's member of the same
1358
+ * name, carried through every request mint and onto the park row): a person's question under every mode — no persisted
1359
+ * rule, standing answer or mode release retires it. A host approval seat that settles asks from a session grant reads
1360
+ * it to keep its hands off. Filled by the gate, never caller/worker-settable. */
1361
+ readonly authoredBy?: "deployment";
1302
1362
  /** WHICH word of the closed {@link import("./ask-origin.js").PERSISTED_RULE_MANDATES} set {@link mandated} stands
1303
1363
  * on — present ⇔ that table word is the bit's WHOLE reason (`probe_mandate` = a read outside the session's
1304
1364
  * directories). ABSENT beside `mandated: true` = another door holds too (governance, a hook, an ancestor's
@@ -1317,12 +1377,6 @@ export interface AskRequest {
1317
1377
  * that forwards a decision's member here does not compile, which is the point: the window is the
1318
1378
  * route's fact, and forwarding it silently is how a configured window becomes a wait with none. */
1319
1379
  readonly denialLimitFallback?: import("./auto-mode.js").DenialLimitFallback;
1320
- /** #688 C3 (additive) — the wire twin of the {@link PermissionResult} ask-arm member of the same name: which
1321
- * mechanism stands behind an `origin: "rule_store_unavailable"` (`store` / `call`,
1322
- * {@link import("./ask-origin.js").RULE_STORE_UNREADABLE_KINDS}). Present ⇔ that origin; a consumer that
1323
- * renders "the rule store is unreachable" vs "this command could not be read against your rules" branches
1324
- * here, never on the message text. Filled by the gate's own carry station from the engine-stamped decision. */
1325
- readonly ruleStoreUnreadable?: import("./ask-origin.js").RuleStoreUnreadable;
1326
1380
  /** (additive) WHICH AUTHORITY raised this ask — the wire twin of the {@link PermissionResult} ask-arm
1327
1381
  * member of the same name, one word from the closed {@link import("./ask-origin.js").AskOrigin} set.
1328
1382
  * The gate's own mint station copies the engine-stamped word; the three inherited-lane stations
@@ -1,12 +1,14 @@
1
1
  import { createHash } from "node:crypto";
2
2
  import { homedir } from "node:os";
3
3
  import { brandPolicyAskClass } from "./ask-class.js";
4
+ import { decisionAskClassesOf, headlessDenialMessage } from "./ask-origin.js";
4
5
  import { ownReadRootCandidate, readRootGrantSeat } from "./read-root-grant-screen.js";
5
6
  import { decidersNote, settledByPerson } from "./gate-outcome.js";
6
7
  import { sanitizeAutoModeArmingRecipe } from "./auto-mode-arming.js";
7
8
  import { join, normalize as normalizePath, sep } from "node:path";
8
9
  import { BASH_READONLY_DEFAULT_ALLOW, parseLeadingCommandName } from "../tools/fs/index.js";
9
10
  import { protectivePathTargetOf } from "./tool-registry.js";
11
+ import { ruleNamesOf, toolNameSetHas } from "./tool-roster.js";
10
12
  import { boundInputHashOf } from "./canonical-json.js";
11
13
  import { delimitUntrusted, inlineUntrusted, REVIEWER_NOTE_MAX_BODY } from "./untrusted-text.js";
12
14
  import { describeRejectedValue } from "./tool-search-setting.js";
@@ -121,6 +123,41 @@ export const ASK_EVIDENCE_ABSENCE_VALUES = ["not_wired", "not_adjudicated", "una
121
123
  export function decisionText(d) {
122
124
  return d.message;
123
125
  }
126
+ const TOOL_POLICY_CARRIED_MEMBERS = ["projection", "pathDenyRules", "writeTargetGuard", "nameSets"];
127
+ function carriedMembersOf(p) {
128
+ const out = {};
129
+ const carry = (k) => {
130
+ const v = p[k];
131
+ if (v !== undefined)
132
+ out[k] = v;
133
+ };
134
+ for (const k of TOOL_POLICY_CARRIED_MEMBERS)
135
+ carry(k);
136
+ return out;
137
+ }
138
+ export function wrapToolPolicy(inner, check) {
139
+ return { ...carriedMembersOf(inner), check };
140
+ }
141
+ export function toolPolicyPathDenyRules(policies) {
142
+ return policies.flatMap((p) => (Array.isArray(p?.pathDenyRules) ? p.pathDenyRules : []));
143
+ }
144
+ export function toolPolicyWriteTargetGuard(policies) {
145
+ const guards = policies.flatMap((p) => (typeof p?.writeTargetGuard === "function" ? [p.writeTargetGuard] : []));
146
+ if (guards.length === 0)
147
+ return undefined;
148
+ return async (target, signal) => {
149
+ for (const guard of guards) {
150
+ try {
151
+ if ((await guard(target, signal)) !== false)
152
+ return true;
153
+ }
154
+ catch {
155
+ return true;
156
+ }
157
+ }
158
+ return false;
159
+ };
160
+ }
124
161
  export function namespacedCoveringEntries(entries) {
125
162
  return (entries ?? []).filter(isNamespacedCoveringRuleName);
126
163
  }
@@ -128,20 +165,21 @@ export function namespacedCoveringHit(covering, toolName) {
128
165
  return covering.length > 0 && covering.some((e) => namespacedRuleNameCovers(e, toolName));
129
166
  }
130
167
  export function checkToolPolicyProjection(projection, req) {
168
+ const names = ruleNamesOf(req);
131
169
  for (const c of projection.components) {
132
170
  if (c.kind === "tool_deny") {
133
- if (c.names.includes(req.toolName) || namespacedCoveringHit(namespacedCoveringEntries(c.names), req.toolName)) {
171
+ if (names.some((n) => c.names.includes(n)) || namespacedCoveringHit(namespacedCoveringEntries(c.names), req.toolName)) {
134
172
  return { action: "deny", message: `tool "${req.toolName}" is denied by a frozen inherited policy projection` };
135
173
  }
136
174
  continue;
137
175
  }
138
176
  if (c.kind === "tool_allowlist") {
139
- if (!c.names.includes(req.toolName) && !namespacedCoveringHit(namespacedCoveringEntries(c.names), req.toolName)) {
177
+ if (!names.some((n) => c.names.includes(n)) && !namespacedCoveringHit(namespacedCoveringEntries(c.names), req.toolName)) {
140
178
  return { action: "deny", message: `tool "${req.toolName}" is not in a frozen inherited policy projection's allowlist` };
141
179
  }
142
180
  continue;
143
181
  }
144
- if (!c.tools.includes(req.toolName))
182
+ if (!names.some((n) => c.tools.includes(n)))
145
183
  continue;
146
184
  const command = req.args?.command;
147
185
  if (typeof command !== "string") {
@@ -350,10 +388,10 @@ export function createAllowDenyPolicy(opts) {
350
388
  nameSets: [{ ...(opts.allow ? { allow: [...opts.allow] } : {}), ...(opts.deny ? { deny: [...opts.deny] } : {}) }],
351
389
  check(req) {
352
390
  const toolName = req.toolName;
353
- if (deny.has(toolName) || namespacedCoveringHit(denyCovering, toolName)) {
391
+ if (toolNameSetHas(deny, req) || namespacedCoveringHit(denyCovering, toolName)) {
354
392
  return { action: "deny", message: `tool "${req.toolName}" is denied by policy` };
355
393
  }
356
- if (allow && !allow.has(toolName) && !namespacedCoveringHit(allowCovering, toolName)) {
394
+ if (allow && !toolNameSetHas(allow, req) && !namespacedCoveringHit(allowCovering, toolName)) {
357
395
  return { action: "deny", message: `tool "${req.toolName}" is not in the allowlist` };
358
396
  }
359
397
  return ALLOW;
@@ -390,10 +428,10 @@ export function createApprovalPolicy(opts) {
390
428
  ],
391
429
  async check(req, signal) {
392
430
  const toolName = req.toolName;
393
- if (deny.has(toolName) || namespacedCoveringHit(denyCovering, toolName)) {
431
+ if (toolNameSetHas(deny, req) || namespacedCoveringHit(denyCovering, toolName)) {
394
432
  return { action: "deny", message: `tool "${req.toolName}" is denied by policy` };
395
433
  }
396
- if (need.has(toolName) || namespacedCoveringHit(needCovering, toolName)) {
434
+ if (toolNameSetHas(need, req) || namespacedCoveringHit(needCovering, toolName)) {
397
435
  const attest = (d, settlement) => attestEngineSettlement(d, { settlement, origin: "policy" }, req);
398
436
  if (signal?.aborted) {
399
437
  return attest({ action: "deny", message: `approval aborted for "${req.toolName}" (run or turn interrupted)` }, settledByNobody("task_aborted"));
@@ -443,7 +481,7 @@ export function createApprovalPolicy(opts) {
443
481
  }
444
482
  return attest({ action: "deny", message: `approval denied for "${req.toolName}"` }, settledByPerson("human_refused", undefined));
445
483
  }
446
- if (opts.denyByDefault && !auto.has(toolName) && !namespacedCoveringHit(autoCovering, toolName)) {
484
+ if (opts.denyByDefault && !toolNameSetHas(auto, req) && !namespacedCoveringHit(autoCovering, toolName)) {
447
485
  return { action: "deny", message: `tool "${req.toolName}" requires explicit allow` };
448
486
  }
449
487
  return ALLOW;
@@ -452,6 +490,8 @@ export function createApprovalPolicy(opts) {
452
490
  }
453
491
  export function combinePolicies(...policies) {
454
492
  const nameSets = policies.flatMap(toolPolicyNameSets);
493
+ const pathDenyRules = toolPolicyPathDenyRules(policies);
494
+ const writeTargetGuard = toolPolicyWriteTargetGuard(policies);
455
495
  const childProjections = policies.map((p) => p.projection);
456
496
  const projectedComponents = [];
457
497
  let combinedRemainder = false;
@@ -469,9 +509,14 @@ export function combinePolicies(...policies) {
469
509
  const combinedProjection = childProjections.some((p) => p !== undefined)
470
510
  ? { components: projectedComponents, requiresLiveRemainder: combinedRemainder }
471
511
  : undefined;
512
+ const folded = {
513
+ projection: combinedProjection,
514
+ nameSets: nameSets.length > 0 ? nameSets : undefined,
515
+ pathDenyRules: pathDenyRules.length > 0 ? pathDenyRules : undefined,
516
+ writeTargetGuard,
517
+ };
472
518
  return {
473
- ...(combinedProjection !== undefined ? { projection: combinedProjection } : {}),
474
- ...(nameSets.length > 0 ? { nameSets } : {}),
519
+ ...carriedMembersOf(folded),
475
520
  async check(req, signal) {
476
521
  let asked;
477
522
  let current = req;
@@ -482,6 +527,8 @@ export function combinePolicies(...policies) {
482
527
  let probeMandateSeen = false;
483
528
  let mandateSeen = false;
484
529
  let fallbackSeen;
530
+ let authoredBySeen = false;
531
+ const contributors = [];
485
532
  for (const p of policies) {
486
533
  const d = refuseOutOfContractDecision(await p.check(current, signal));
487
534
  if (d.action === "deny") {
@@ -516,6 +563,10 @@ export function combinePolicies(...policies) {
516
563
  mandateSeen = true;
517
564
  if (d.action === "ask" && d.denialLimitFallback !== undefined && fallbackSeen === undefined)
518
565
  fallbackSeen = d.denialLimitFallback;
566
+ if (d.action === "ask" && d.authoredBy === "deployment")
567
+ authoredBySeen = true;
568
+ if (d.action === "ask")
569
+ contributors.push(d);
519
570
  }
520
571
  if (asked) {
521
572
  const merged = rewrite?.updatedInput;
@@ -523,7 +574,14 @@ export function combinePolicies(...policies) {
523
574
  const withMark = probeMandateSeen && withRuleAsk.probeMandated !== true ? { ...withRuleAsk, probeMandated: true } : withRuleAsk;
524
575
  const withMandate = mandateSeen && withMark.mandated !== true ? { ...withMark, mandated: true } : withMark;
525
576
  const withFallback = fallbackSeen !== undefined && withMandate.denialLimitFallback === undefined ? { ...withMandate, denialLimitFallback: fallbackSeen, requiresRealApproval: true } : withMandate;
526
- return merged !== undefined ? { ...withFallback, updatedInput: merged } : withFallback;
577
+ const withAuthor = authoredBySeen && withFallback.authoredBy === undefined ? { ...withFallback, authoredBy: "deployment" } : withFallback;
578
+ const folded = new Set(withAuthor.foldedAskClasses ?? []);
579
+ for (const c of contributors)
580
+ if (c !== asked)
581
+ for (const k of decisionAskClassesOf(c))
582
+ folded.add(k);
583
+ const withClasses = folded.size > 0 ? { ...withAuthor, foldedAskClasses: [...folded] } : withAuthor;
584
+ return merged !== undefined ? { ...withClasses, updatedInput: merged } : withClasses;
527
585
  }
528
586
  const allowed = rewrite ?? ALLOW;
529
587
  if (!anyRecord)
@@ -563,7 +621,7 @@ export function createCoarseCommandNamePolicy(opts) {
563
621
  return brandPolicyAskClass({
564
622
  projection,
565
623
  check(req) {
566
- if (!shellTools.has(req.toolName))
624
+ if (!toolNameSetHas(shellTools, req))
567
625
  return ALLOW;
568
626
  const command = req.args?.command;
569
627
  if (typeof command !== "string") {
@@ -817,7 +875,7 @@ export function createUnverifiableDeletePolicy(opts) {
817
875
  const shellTools = canonicalToolNameSet(opts?.tools);
818
876
  return brandPolicyAskClass({
819
877
  check(req) {
820
- if (!shellTools.has(req.toolName))
878
+ if (!toolNameSetHas(shellTools, req))
821
879
  return ALLOW;
822
880
  const command = req.args?.command;
823
881
  if (typeof command !== "string")
@@ -829,6 +887,7 @@ export function createUnverifiableDeletePolicy(opts) {
829
887
  action: "ask",
830
888
  decisionReason: "safety",
831
889
  requiresRealApproval: true,
890
+ guard: "unverifiable_delete",
832
891
  message: `Unverifiable recursive delete (fail-closed unless cleared): ${finding}. ` +
833
892
  `Re-run the delete with the resolved literal path written into the command itself ` +
834
893
  `(or assign the variable in the same command, e.g. \`DIR=/exact/path; rm -rf "$DIR"\`) so the target can be verified.`,
@@ -905,6 +964,7 @@ export function createTranscriptIntegrityPolicy(opts) {
905
964
  action: "ask",
906
965
  decisionReason: "safety",
907
966
  requiresRealApproval: true,
967
+ guard: "transcript_write",
908
968
  message: `Session-transcript write (fail-closed unless cleared): ${what}. Session transcripts (the .jsonl files ` +
909
969
  `under the agent data dir's sessions/ directory) are harness-written session state, not agent working ` +
910
970
  `files — modifying or deleting them tampers with the run's own audit trail. Reading them (ls/cat/grep ` +
@@ -912,8 +972,7 @@ export function createTranscriptIntegrityPolicy(opts) {
912
972
  });
913
973
  return brandPolicyAskClass({
914
974
  check(req) {
915
- const toolName = req.toolName;
916
- if (shellTools.has(toolName)) {
975
+ if (toolNameSetHas(shellTools, req)) {
917
976
  const command = req.args?.command;
918
977
  if (typeof command !== "string")
919
978
  return ALLOW;
@@ -956,6 +1015,7 @@ export function isLiveApproverSeat(onAsk) {
956
1015
  return typeof onAsk === "function";
957
1016
  }
958
1017
  export const DURABLE_MANDATE_SOURCES = ["force_durable_gate", "durable_approval"];
1018
+ Object.freeze(DURABLE_MANDATE_SOURCES);
959
1019
  export function durableMandateSourceOf(args) {
960
1020
  if (args.forceDurableGate)
961
1021
  return "force_durable_gate";
@@ -1250,7 +1310,7 @@ async function resolveAskArms(req, onAsk, signal, onLateSettlement) {
1250
1310
  if (onAsk === undefined || onAsk === "deny") {
1251
1311
  return {
1252
1312
  action: "deny",
1253
- message: `approval required for "${req.toolName}" but no approver is wired (headless auto-deny): ${req.message}`,
1313
+ message: headlessDenialMessage(req.toolName, req.message),
1254
1314
  decisionReason: "mode",
1255
1315
  settlement: settledByNobody("no_approver"),
1256
1316
  };
@@ -28,6 +28,8 @@ export declare function pathConfinableWriteToolNames(): ReadonlySet<string>;
28
28
  export declare function skillScopeWriteToolNames(): ReadonlySet<string>;
29
29
  /** A-8 — every definition that names a path target at all (read or write): the file-tool glob lane. */
30
30
  export declare function pathTargetingToolNames(): ReadonlySet<string>;
31
+ /** The tools whose declared path target is a READ (a file read, a search root): the file read station's membership. */
32
+ export declare function readTargetToolNames(): ReadonlySet<string>;
31
33
  /** A-10 — the micro-compaction whitelist (`compactable: true`). */
32
34
  export declare function compactableToolNames(): ReadonlySet<string>;
33
35
  /** A-5 — the aggregate-budget exemptions (`budgetExempt: true`). */
@@ -46,9 +48,40 @@ export interface ToolCallLike {
46
48
  args: unknown;
47
49
  face?: {
48
50
  pathTarget?: ToolCallPathTarget;
51
+ names?: readonly string[];
49
52
  } | undefined;
50
53
  }
51
54
  type CallPathTarget = ToolCallPathTarget;
55
+ /**
56
+ * The names a tool-name rule may spell this call's tool by — the face's `names` when they include the call's name,
57
+ * else the call's name alone (a bare request, or a face minted for another name). Every name-keyed matcher and the
58
+ * protective path floor read a rule's tool name against THIS list, which is the roster's one canonicalization seen
59
+ * from the call's side: a rule written against an alias and one written against the wire name reach the same calls.
60
+ */
61
+ export declare function ruleNamesOf(req: Pick<ToolCallLike, "toolName" | "face">): readonly string[];
62
+ /** Does the call answer to `name` — is it one of the call's names ({@link ruleNamesOf})? The engine's TIGHTENS keyed on
63
+ * one of its own tool names read this, so a tool that wears the name under any of its names is tightened (the
64
+ * fail-safe direction); a grant or a routing keyed on an engine tool name reads the wire name. */
65
+ export declare function callAnswersTo(req: Pick<ToolCallLike, "toolName" | "face">, name: string): boolean;
66
+ /** Does a set of rule tool names name this call's tool ({@link ruleNamesOf})? */
67
+ export declare function toolNameSetHas(set: {
68
+ has(name: string): boolean;
69
+ }, req: Pick<ToolCallLike, "toolName" | "face">): boolean;
70
+ /** Does `set` hold any of the call's names ({@link ruleNamesOf})? The read of a MEMBERSHIP tighten keyed on the engine's
71
+ * own tool names (the egress set, a read station's tools, the compliance veto): a tool is held under every name it
72
+ * answers to. A grant or a routing keyed on an engine tool name reads the wire name instead. */
73
+ export declare function anyNamed(set: {
74
+ has(name: string): boolean;
75
+ }, names: readonly string[]): boolean;
76
+ /** The first of the call's names ({@link ruleNamesOf}, the spelling first) that `map` holds an entry for, with the entry.
77
+ * The read of a SEAT keyed on the engine's own tool names (a tier, a mark's sources): every key of one seat is read under
78
+ * the ONE name this returns — a seat is never assembled from two names' entries. */
79
+ export declare function firstNamed<V>(map: {
80
+ get(name: string): V | undefined;
81
+ }, names: readonly string[]): {
82
+ readonly name: string;
83
+ readonly value: V;
84
+ } | undefined;
52
85
  /**
53
86
  * design/388 B6/B17 — the path target a PROTECTIVE fence reads (a deny-list judging a write's target: sensitive paths,
54
87
  * frozen paths, write protection, the transcript gate; edit tracking): the object face's own declaration first, the
@@ -56,6 +89,11 @@ type CallPathTarget = ToolCallPathTarget;
56
89
  * face that declares none. Reading the catalog for an undeclared same-name tool can only TIGHTEN a deny-list (it adds
57
90
  * a slot to judge — the pre-388 posture, never wider), which is exactly why this floor is wrong for a grant.
58
91
  *
92
+ * The floor reads EVERY name the call answers to (`ruleNamesOf`: the face's `names`, else the call's name alone) in
93
+ * the face's order — the name the call was made under first, then the wire name, then the other aliases — and the
94
+ * first name the catalog declares a target for wins: a call made under a catalog name and resolved to a tool that
95
+ * declares none keeps THAT name's slot, also when the tool answers to a second catalog name with another slot.
96
+ *
59
97
  * #635: "can only tighten" is true of the SLOT and false of the RESOLUTION, so the floor hands over the first and
60
98
  * defaults the second (`pathTargetSlotOnly`). Telling a fence that a FOREIGN tool of the same name resolves its slot
61
99
  * against the project root is a claim about someone else's implementation: get it wrong and the judged path moves to
@@ -31,6 +31,9 @@ export function skillScopeWriteToolNames() {
31
31
  export function pathTargetingToolNames() {
32
32
  return names((e) => e.face.pathTarget !== undefined);
33
33
  }
34
+ export function readTargetToolNames() {
35
+ return names((e) => e.face.pathTarget?.access === "read");
36
+ }
34
37
  export function compactableToolNames() {
35
38
  return names((e) => e.face.compactable === true);
36
39
  }
@@ -48,10 +51,31 @@ export function pathTargetValue(args, pt) {
48
51
  }
49
52
  return undefined;
50
53
  }
54
+ export function ruleNamesOf(req) {
55
+ const names = req.face?.names;
56
+ return names !== undefined && names.includes(req.toolName) ? names : [req.toolName];
57
+ }
58
+ export function callAnswersTo(req, name) {
59
+ return ruleNamesOf(req).includes(name);
60
+ }
61
+ export function toolNameSetHas(set, req) {
62
+ return anyNamed(set, ruleNamesOf(req));
63
+ }
64
+ export function anyNamed(set, names) {
65
+ return names.some((n) => set.has(n));
66
+ }
67
+ export function firstNamed(map, names) {
68
+ for (const name of names) {
69
+ const value = map.get(name);
70
+ if (value !== undefined)
71
+ return { name, value };
72
+ }
73
+ return undefined;
74
+ }
51
75
  export function protectivePathTargetOf(call) {
52
76
  if (call.face?.pathTarget !== undefined)
53
77
  return call.face.pathTarget;
54
- const floor = pathTargetOf(call.toolName);
78
+ const floor = ruleNamesOf(call).map((n) => pathTargetOf(n)).find((pt) => pt !== undefined);
55
79
  return floor === undefined || call.face === undefined ? floor : pathTargetSlotOnly(floor);
56
80
  }
57
81
  export const BARE_REQUEST_CODE = "policy.bare_request";
@@ -249,7 +249,19 @@ export type ToolRosterDelta = Static<typeof ToolRosterDelta>;
249
249
  /** The ONE name→row resolution every per-call reader shares (the call face, the park identity): the wire name first,
250
250
  * then an alias — the same order the dispatcher resolves a model's call. */
251
251
  export declare function rosterEntryNamed(roster: ToolRoster, toolName: string): ToolRosterEntry | undefined;
252
- /** design/388 B6 — the per-call face a gated request carries, read off a roster snapshot by name (aliases resolve). */
252
+ /**
253
+ * The ONE tool-name canonicalization: the wire name of the row `name` resolves to on the roster ({@link rosterEntryNamed}
254
+ * — the wire name first, then an alias, the dispatcher's order), or `name` itself when no row answers to it. The gate
255
+ * reads a call's name through it before any policy runs, and a rule's tool name reaches a call exactly when it
256
+ * canonicalizes to the same wire name (`ToolCallFace.names` is that set, minted here) — so a rule written against
257
+ * an alias and one written against the wire name are one rule.
258
+ */
259
+ export declare function rosterToolName(roster: ToolRoster | undefined, name: string): string;
260
+ export { anyNamed, callAnswersTo, firstNamed, ruleNamesOf, toolNameSetHas } from "./tool-registry.js";
261
+ /** design/388 B6 — the per-call face a gated request carries, read off a roster snapshot by name (aliases resolve).
262
+ * `names` leads with the name the face was minted under (the model's spelling at the gate), then the wire name and the
263
+ * other aliases: a reader that must pick ONE of the call's names (the protective path floor) picks the spelling first,
264
+ * as the same call was judged before name resolution. */
253
265
  export declare function toolCallFaceOf(roster: ToolRoster | undefined, toolName: string): import("./tool-policy.js").ToolCallFace | undefined;
254
266
  /** design/388 B18 — the identity members a park row carries, read off a roster snapshot by name; an empty object when
255
267
  * the name is not on the snapshot (the row then reads pre-identity and the resume takes the name-only path). */
@@ -268,6 +280,7 @@ export declare function parkedToolIdentity(roster: ToolRoster | undefined, toolN
268
280
  export declare function parkedToolFace(roster: ToolRoster | undefined, toolName: string): {
269
281
  family?: ToolFamily;
270
282
  pathTarget?: ToolRosterEntry["pathTarget"];
283
+ names?: readonly string[];
271
284
  };
272
285
  /** The call face a park row carries — present iff the row was minted with BOTH its face members and its four
273
286
  * identity members (one mint writes all of them; a row missing any is pre-388 and yields undefined, never a
@@ -275,6 +288,7 @@ export declare function parkedToolFace(roster: ToolRoster | undefined, toolName:
275
288
  export declare function parkedCallFaceOf(pending: {
276
289
  family?: ToolFamily;
277
290
  pathTarget?: ToolRosterPathTarget;
291
+ names?: readonly string[];
278
292
  capabilityId?: string;
279
293
  contractId?: string;
280
294
  shapeDigest?: string;
@@ -321,7 +335,8 @@ export interface RosterBoundMembers {
321
335
  cardId?: string;
322
336
  }
323
337
  /** The typed refusal a member over its bound mints — the door's two codes, and the mint's own for the members no
324
- * declaration door names (`modelGate`, the derived `cardId`). */
338
+ * declaration door names (`modelGate`, the derived `cardId`). Spelled out as literals (a consumer's tooling reads
339
+ * the words); each is a member of {@link ToolDeclarationCode}, which every mint below satisfies. */
325
340
  export type RosterBoundCode = "config.tool_name_too_long" | "config.tool_contract_too_long" | "config.tool_roster_bound";
326
341
  /**
327
342
  * design/388 B20 / #626 ① — the ONE bound judge over a row's identity members: a problem `{ code, message }`, or
@@ -74,13 +74,22 @@ export const ToolRosterDelta = Type.Object({
74
74
  export function rosterEntryNamed(roster, toolName) {
75
75
  return roster.entries.find((r) => r.name === toolName) ?? roster.entries.find((r) => r.aliases.includes(toolName));
76
76
  }
77
+ export function rosterToolName(roster, name) {
78
+ return (roster === undefined ? undefined : rosterEntryNamed(roster, name)?.name) ?? name;
79
+ }
80
+ function namesResolvingTo(roster, e) {
81
+ return [e.name, ...e.aliases.filter((a) => a !== e.name && rosterToolName(roster, a) === e.name)];
82
+ }
83
+ export { anyNamed, callAnswersTo, firstNamed, ruleNamesOf, toolNameSetHas } from "./tool-registry.js";
77
84
  export function toolCallFaceOf(roster, toolName) {
78
85
  if (roster === undefined)
79
86
  return undefined;
80
87
  const e = rosterEntryNamed(roster, toolName);
81
88
  if (e === undefined)
82
89
  return undefined;
90
+ const resolving = namesResolvingTo(roster, e);
83
91
  return {
92
+ names: resolving.includes(toolName) ? [toolName, ...resolving.filter((n) => n !== toolName)] : resolving,
84
93
  family: e.family,
85
94
  ...(e.pathTarget !== undefined ? { pathTarget: e.pathTarget } : {}),
86
95
  capabilityId: e.capabilityId,
@@ -103,12 +112,13 @@ export function parkedToolFace(roster, toolName) {
103
112
  const e = rosterEntryNamed(roster, toolName);
104
113
  if (e === undefined)
105
114
  return {};
106
- return { family: e.family, ...(e.pathTarget !== undefined ? { pathTarget: e.pathTarget } : {}) };
115
+ return { family: e.family, ...(e.pathTarget !== undefined ? { pathTarget: e.pathTarget } : {}), names: namesResolvingTo(roster, e) };
107
116
  }
108
117
  export function parkedCallFaceOf(pending) {
109
118
  if (pending.family === undefined || pending.capabilityId === undefined || pending.contractId === undefined || pending.shapeDigest === undefined || pending.implementationRevision === undefined)
110
119
  return undefined;
111
120
  return {
121
+ ...(pending.names !== undefined ? { names: pending.names } : {}),
112
122
  family: pending.family,
113
123
  ...(pending.pathTarget !== undefined ? { pathTarget: pending.pathTarget } : {}),
114
124
  capabilityId: pending.capabilityId,