@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,7 +1,7 @@
1
1
  import { isRemoteExecutionEnv } from "../remote-env.js";
2
2
  import { deliverEngineNotice } from "../engine-notice.js";
3
3
  const BOOLEAN_CAPABILITY_KEYS = ["isolation", "suspendable", "canonicalPathAuthoritative"];
4
- const OPTIONAL_METHOD_KEYS = ["writeFileExclusive", "writeFileGuarded", "setFileMode", "readLink", "canonicalChain"];
4
+ const OPTIONAL_METHOD_KEYS = ["writeFileExclusive", "writeFileGuarded", "setFileMode", "readLink", "canonicalChain", "fileIdentity"];
5
5
  export function malformedCapabilityDeclarations(env) {
6
6
  const out = [];
7
7
  for (const key of OPTIONAL_METHOD_KEYS) {
@@ -0,0 +1,26 @@
1
+ /** The file-history REQUEST door and its bad-value doors — the pre-acquire half of the file-history phase (`prepare-file-history.ts`). */
2
+ import type { FileHistoryStore } from "../file-history-store.js";
3
+ import type { RunnerDeps, TaskSpec } from "../types.js";
4
+ /**
5
+ * The file-history REQUEST door: every refusal the request (`TaskSpec`) and the deployment's own seats decide
6
+ * without the session, the env or the store's contents — the three bad-value doors (`RunnerDeps.onTrackFailure`,
7
+ * `RunnerDeps.fileHistoryStore` / `fileHistoryCapture`, `TaskSpec.fileHistory`), the retired `rewindFiles` restore
8
+ * sense, the restoreFiles / resumeAt / rewindFilesTo contradictions, and a restore asked of a run that holds no
9
+ * history (`rewind.capture_disabled` / `rewind.store_unconfigured`). SYNCHRONOUS and side-effect free; the driver
10
+ * calls it BEFORE the session is acquired, so a request refused here leaves the session — its leaf included —
11
+ * exactly as it was. What it resolves rides into the file-history phase (`prepareFileHistory`) unchanged.
12
+ */
13
+ export declare function resolveFileHistoryRequest(spec: Pick<TaskSpec, "resumeAt" | "resumeAtMode" | "restoreFiles" | "rewindFilesTo" | "fileHistory">, deps: Pick<RunnerDeps, "onTrackFailure" | "fileHistoryStore" | "fileHistoryCapture">): FileHistoryRequest;
14
+ /** What {@link resolveFileHistoryRequest} resolved: the validated seats and words, the run's store (after every off
15
+ * switch) and its capture store, and the restore target (`undefined` = no restore asked). */
16
+ export interface FileHistoryRequest {
17
+ onTrackFailure: "refuse" | "proceed-unprotected";
18
+ wiredSeat: FileHistoryStore | "disabled" | undefined;
19
+ runDeclaredOff: boolean;
20
+ captureOff: boolean;
21
+ declaredOff: boolean;
22
+ fileHistoryStore: FileHistoryStore | undefined;
23
+ captureStore: FileHistoryStore | undefined;
24
+ rewindTarget: string | undefined;
25
+ rewindBefore: boolean;
26
+ }
@@ -0,0 +1,90 @@
1
+ export function resolveFileHistoryRequest(spec, deps) {
2
+ const onTrackFailure = deps.onTrackFailure === undefined ? "refuse" : deps.onTrackFailure;
3
+ if (onTrackFailure !== "refuse" && onTrackFailure !== "proceed-unprotected") {
4
+ const e = new Error(`RunnerDeps.onTrackFailure must be "refuse" or "proceed-unprotected"; got ${JSON.stringify(deps.onTrackFailure)} — refusing prepare rather than silently defaulting a first-touch-failure policy.`);
5
+ e.code = "config.invalid_on_track_failure";
6
+ throw e;
7
+ }
8
+ const wiredSeat = resolveWiredFileHistoryStore(deps.fileHistoryStore);
9
+ const runDeclaredOff = resolveRunFileHistoryWord(spec.fileHistory);
10
+ const captureOff = resolveFileHistoryCaptureWord(deps.fileHistoryCapture, wiredSeat);
11
+ const declaredOff = wiredSeat === "disabled" || runDeclaredOff;
12
+ const fileHistoryStore = declaredOff ? undefined : wiredSeat;
13
+ const captureStore = captureOff ? undefined : fileHistoryStore;
14
+ const legacyRewindFiles = spec.rewindFiles;
15
+ if (legacyRewindFiles === true) {
16
+ if (spec.resumeAt !== undefined) {
17
+ const e = new Error(`rewind-files: TaskSpec.rewindFiles is retired (design/381 — rewind now converges the per-edited-file history, not a whole-tree snapshot). This request pairs it with resumeAt, i.e. the RESTORE sense: set restoreFiles: true instead. (The capture sense needs no request anymore — first-touch tracking is on whenever a RunnerDeps.fileHistoryStore is wired and neither RunnerDeps.fileHistoryCapture nor TaskSpec.fileHistory turns it off.)`);
18
+ e.code = "rewind.rewind_files_retired";
19
+ throw e;
20
+ }
21
+ }
22
+ if (spec.restoreFiles === true && spec.resumeAt === undefined) {
23
+ const e = new Error(`rewind-files: restoreFiles: true needs a resumeAt anchor — restoring files means converging them to a specific prior entry's boundary, and this spec names none. Use resumeAt + restoreFiles to move conversation and files together, or rewindFilesTo alone for a code-only restore.`);
24
+ e.code = "rewind.invalid_spec";
25
+ throw e;
26
+ }
27
+ if (spec.resumeAt !== undefined && spec.rewindFilesTo !== undefined) {
28
+ const e = new Error(`rewind-files: resumeAt ("${spec.resumeAt}") and rewindFilesTo ("${spec.rewindFilesTo}") were both set — resumeAt already anchors the file restore when restoreFiles is true, so a separate rewindFilesTo target is a conflicting request. Drop one of the two.`);
29
+ e.code = "rewind.conflicting_targets";
30
+ throw e;
31
+ }
32
+ const rewindTarget = spec.resumeAt !== undefined ? (spec.restoreFiles === true ? spec.resumeAt : undefined) : spec.rewindFilesTo;
33
+ const rewindBefore = rewindTarget !== undefined && spec.resumeAt !== undefined && spec.resumeAtMode === "before";
34
+ if (rewindTarget !== undefined && fileHistoryStore === undefined) {
35
+ if (runDeclaredOff)
36
+ throw captureDisabledRefusal(rewindTarget);
37
+ const e = new Error(wiredSeat === "disabled"
38
+ ? `rewind-files: restoring files to entry "${rewindTarget}" requires a history backend, but this deployment declared file history disabled (RunnerDeps.fileHistoryStore: "disabled"), so there is no history to restore — the files were NOT rewound`
39
+ : `rewind-files: restoring files to entry "${rewindTarget}" requires a history backend, but this deployment wired no RunnerDeps.fileHistoryStore — no file history was ever recorded, so the files were NOT rewound`);
40
+ e.code = "rewind.store_unconfigured";
41
+ throw e;
42
+ }
43
+ return { onTrackFailure, wiredSeat, runDeclaredOff, captureOff, declaredOff, fileHistoryStore, captureStore, rewindTarget, rewindBefore };
44
+ }
45
+ function resolveWiredFileHistoryStore(candidate) {
46
+ if (candidate === undefined || candidate === "disabled")
47
+ return candidate;
48
+ const verbs = ["trackEdit", "annulTrack", "snapshot", "restore", "canRestore", "reap", "adoptScope"];
49
+ const verbOf = (v) => {
50
+ try {
51
+ return candidate[v];
52
+ }
53
+ catch {
54
+ return undefined;
55
+ }
56
+ };
57
+ const probeable = (typeof candidate === "object" && candidate !== null) || typeof candidate === "function";
58
+ const missing = probeable ? verbs.filter((v) => typeof verbOf(v) !== "function") : [...verbs];
59
+ if (missing.length > 0) {
60
+ const shape = candidate === null ? "null" : probeable ? "a value that is not a FileHistoryStore" : `a ${typeof candidate}`;
61
+ const e = new Error(`RunnerDeps.fileHistoryStore was wired with ${shape} — missing contract verbs: ${missing.join(", ")}. Refusing prepare rather than half-wiring the rewind seat (a present-but-unusable store makes every Edit/Write/NotebookEdit fail its first-touch backup while restore requests report "no store wired"). Write "disabled" (or omit the key) to run without file history.`);
62
+ e.code = "config.invalid_file_history_store";
63
+ throw e;
64
+ }
65
+ return candidate;
66
+ }
67
+ function resolveRunFileHistoryWord(word) {
68
+ if (word === undefined || word === "disabled")
69
+ return word === "disabled";
70
+ const e = new Error(`rewind-files: TaskSpec.fileHistory holds ${shownWord(word)} — the only value is "disabled"; omit the key to leave the deployment's setting in force`);
71
+ e.code = "rewind.invalid_spec";
72
+ throw e;
73
+ }
74
+ function resolveFileHistoryCaptureWord(word, wiredSeat) {
75
+ if (word === undefined || (word === "off" && wiredSeat !== undefined && wiredSeat !== "disabled"))
76
+ return word === "off";
77
+ const e = new Error(word === "off"
78
+ ? `RunnerDeps.fileHistoryCapture: "off" requires a wired RunnerDeps.fileHistoryStore — nothing to switch off; to drop history entirely write fileHistoryStore: "disabled"`
79
+ : `RunnerDeps.fileHistoryCapture holds ${shownWord(word)} — the only value is "off"; omit the key to record whenever a fileHistoryStore is wired`);
80
+ e.code = "config.invalid_file_history_capture";
81
+ throw e;
82
+ }
83
+ function shownWord(word) {
84
+ return typeof word === "string" ? JSON.stringify(word.slice(0, 40)) : word === null || typeof word === "boolean" || typeof word === "number" ? String(word) : `a ${typeof word}`;
85
+ }
86
+ function captureDisabledRefusal(target) {
87
+ const e = new Error(`rewind-files: this run set TaskSpec.fileHistory: "disabled", so it neither records nor restores file history — restoring files to entry "${target}" was refused and the files were NOT rewound`);
88
+ e.code = "rewind.capture_disabled";
89
+ return e;
90
+ }
@@ -21,9 +21,9 @@
21
21
  * the reminder envelope and the text helpers. Its `import type` edges up to `hooks.ts` name the gate's public
22
22
  * contract (`ToolGateInput`, `ToolGateResult`, the observer payload), which stays declared there.
23
23
  */
24
- import { type ClassifierDenyCause, type DeniedBy, type GateOutcome, type Settlement } from "../gate-outcome.js";
24
+ import { type ClassifierDenyCause, type DeniedBy, type GateOutcome, type ModeRefusal, type ModeRelease, type Settlement } from "../gate-outcome.js";
25
25
  import type { AutoModeClassifierRound } from "../auto-mode.js";
26
- import type { AskOrigin, AskOriginFacts } from "../ask-origin.js";
26
+ import { type Arbitration, type AskClassification, type AskOrigin, type AskOriginFacts } from "../ask-origin.js";
27
27
  import { type PermissionResult, type ResolvedAsk, type ToolCallRequest } from "../tool-policy.js";
28
28
  import type { SafeNotifier } from "../safe-notify.js";
29
29
  import type { HookSeatOutcome, PermissionDeniedPayload, PreToolUseResult, ToolGateInput, ToolGateResult } from "../hooks.js";
@@ -45,6 +45,10 @@ export interface GateExitFacts {
45
45
  * Orthogonal to `deniedBy`: one that ALLOWED is attributed too (a fallback model's allow is exactly the case
46
46
  * an operator needs to see), which is why it rides the disposition's shared member, not the deny arm. */
47
47
  classifier?: AutoModeClassifierRound;
48
+ /** The permission mode RELEASED the surviving ask (the allow exit's account of it); never beside `deniedBy`. */
49
+ released?: ModeRelease;
50
+ /** The permission mode REFUSED the surviving ask — only beside `deniedBy: "mode"`. */
51
+ modeRefusal?: ModeRefusal;
48
52
  }
49
53
  /**
50
54
  * Mint the outcome. A defect here is an ENGINE defect (a refusing site that did not say who refused, a
@@ -228,6 +232,14 @@ export interface GatePass {
228
232
  * card on the gate result, so the refusal's `tool_end` still says WHAT was found (`readonly_out_of_root`) beside
229
233
  * WHO refused (`ask_resolution`). Writer: the fold's read station. */
230
234
  shellReadDetails?: unknown;
235
+ /** The read station PLACED every write the command makes inside the write roots (`ShellReadVerdict.writesPlaced`) — the
236
+ * one precondition the mode's file-command acceptance reads (the reference's path checks precede its acceptEdits arm). */
237
+ shellWritesPlaced?: true;
238
+ /** The read station did NOT place the command's writes because a `cp` / `mv` segment carries an option word
239
+ * (`ShellReadVerdict.unplacedOptions`) — the one non-placement reason the mode's card and the headless refusal name. */
240
+ shellUnplacedOptions?: {
241
+ readonly program: "cp" | "mv";
242
+ };
231
243
  /** borrowed-mutable — the FORM of the classifier round behind a deny (`unavailable` / `parse_error`), written
232
244
  * beside `deniedBy` by the gate's own classifier step (its attestation, read once) or by the fold / the
233
245
  * re-check when an inherited station's attested deny arrives through the chain; undefined for a ruling
@@ -260,14 +272,19 @@ export interface GatePass {
260
272
  * twin on the answered side, and narrower: the ask stays ordinary for every ANSWERER, and only the allow
261
273
  * layer's read-only-command arm stands down. Writer: the fold. Reader: that arm, and nothing else. */
262
274
  probeBoundaryDeclined?: true;
275
+ /** borrowed-mutable — the shell read station could not establish what this command reads (finding `unestablishable`
276
+ * or `declined`): a mode's release of the call is a release past the leg's read-deny table, announced as such.
277
+ * Writer: the fold's read station. Reader: the release point. */
278
+ shellReadUnestablished?: true;
263
279
  /** borrowed-mutable — the shell read station minted the standing ask's directory-grant offer
264
280
  * (`probeReadRootCandidate`). Read by the classify seat's shape probe, which withdraws the offer when its
265
281
  * own objection stands beside the station's — the grant answers the station's finding, not the shape's.
266
282
  * Writer: the fold's read station. */
267
283
  shellReadOffer?: true;
268
284
  /** borrowed-mutable — the write-protection judge covered this call and its table MATCHED: the allow
269
- * layer must not clear the surviving ask, a live decider still may. Writer: the fold's fourth tighten,
270
- * on BOTH its arms. Reader: the lanes' mandate. */
285
+ * layer must not clear the surviving ask, a live decider still may. Writers: the fold's fourth tighten,
286
+ * on BOTH its arms (a write tool's target), and the fold's read station (a shell command's written targets,
287
+ * `ShellReadVerdict.writeProtected`). Reader: the lanes' mandate. */
271
288
  writeProtected?: true;
272
289
  /** borrowed-mutable — the same judge could NOT RESOLVE the target: no automatic lane may answer the
273
290
  * ask. Writer and reader as above; the pair is mutually exclusive (one verdict per judgment). */
@@ -276,6 +293,15 @@ export interface GatePass {
276
293
  * approved path (a redaction is not cancelled because a person confirmed). Writer: the fold. Readers: the
277
294
  * fold's probe and write-protection judges, the lanes, the allow exit. */
278
295
  policyRewrite: unknown;
296
+ /** borrowed-mutable — the standing ask's CLASS SET and folded row (`classifyAsk`), written at the lanes' origin
297
+ * stamp and at every re-mint (the denial-limit fallback, the approved-edit round); read by the classifier
298
+ * station, the sandbox leg and the arbitration point. Writer: the lanes. */
299
+ askClassification?: AskClassification;
300
+ /** borrowed-mutable — the mode arbitration's verdict when it RELEASED or REFUSED the standing ask (the exit
301
+ * projects it onto the disposition). Writer: the lanes' arbitration point. */
302
+ arbitration?: Exclude<Arbitration, {
303
+ kind: "ask";
304
+ }>;
279
305
  }
280
306
  /**
281
307
  * The engine's fail-closed block of phase 1 — the PreToolUse face THREW or never ANSWERED, so the engine
@@ -1,4 +1,6 @@
1
1
  import { screenGateOutcome, SETTLEMENT_IS_REFUSAL } from "../gate-outcome.js";
2
+ import { headlessApprovalRemedy } from "../ask-origin.js";
3
+ import { isProtectedWrite } from "../tool-registry.js";
2
4
  import { askUnresolvableCauseOf, classifierDenyCauseOf, classifierRoundOf, decisionText, engineSettlementOf } from "../tool-policy.js";
3
5
  import { inlineUntrusted } from "../untrusted-text.js";
4
6
  import { formatHookFeedback } from "../reminder-mint.js";
@@ -7,8 +9,14 @@ export function mintGateOutcome(facts) {
7
9
  const attribution = facts.classifier !== undefined ? Object.freeze({ ...facts.classifier, ...(facts.classifier.fallback !== undefined ? { fallback: Object.freeze({ ...facts.classifier.fallback }) } : {}) }) : undefined;
8
10
  const outcome = Object.freeze({
9
11
  disposition: Object.freeze(facts.deniedBy !== undefined
10
- ? { kind: "denied", deniedBy: facts.deniedBy, ...(facts.cause !== undefined ? { cause: facts.cause } : {}), ...(attribution !== undefined ? { classifier: attribution } : {}) }
11
- : { kind: "allowed", ...(attribution !== undefined ? { classifier: attribution } : {}) }),
12
+ ? {
13
+ kind: "denied",
14
+ deniedBy: facts.deniedBy,
15
+ ...(facts.cause !== undefined ? { cause: facts.cause } : {}),
16
+ ...(attribution !== undefined ? { classifier: attribution } : {}),
17
+ ...(facts.modeRefusal !== undefined ? { mode: facts.modeRefusal.mode, askClass: facts.modeRefusal.askClass, classes: Object.freeze([...facts.modeRefusal.classes]) } : {}),
18
+ }
19
+ : { kind: "allowed", ...(attribution !== undefined ? { classifier: attribution } : {}), ...(facts.released !== undefined ? { released: Object.freeze({ ...facts.released, classes: Object.freeze([...facts.released.classes]) }) } : {}) }),
12
20
  ...(facts.settled !== undefined ? { settlement: settlement, origin: facts.settled.origin } : {}),
13
21
  });
14
22
  const defects = screenGateOutcome(outcome);
@@ -16,6 +24,10 @@ export function mintGateOutcome(facts) {
16
24
  throw new Error(`the tool gate composed an outcome that violates its own record: ${defects.join("; ")}`);
17
25
  return outcome;
18
26
  }
27
+ function releaseRecordOf(a) {
28
+ const { kind: _kind, ...record } = a;
29
+ return record;
30
+ }
19
31
  export function directDeny(deniedBy) {
20
32
  return mintGateOutcome({ deniedBy });
21
33
  }
@@ -111,17 +123,24 @@ function unresolvableCauseOfSettlement(gate) {
111
123
  export async function exitGate(pass) {
112
124
  const { input, event, toolName, toolCallId, ledger, notifyPermissionDenied } = pass;
113
125
  if (pass.decision.action === "deny") {
114
- const denyReason = withParkFailureCause(decisionText(pass.decision) ?? `tool "${toolName}" denied by policy`, pass.parkFailed);
115
126
  if (pass.decision.updatedInput !== undefined) {
116
127
  pass.currentInput = pass.decision.updatedInput;
117
128
  }
118
129
  if (pass.deniedBy === undefined)
119
130
  throw new Error(`the tool gate refused "${toolName}" without a refusing layer — every deny site attributes itself`);
120
- const gate = mintGateOutcome({ deniedBy: pass.deniedBy, ...(pass.denyCause !== undefined ? { cause: pass.denyCause } : {}), ...(ledger.settled !== undefined ? { settled: ledger.settled } : {}), ...(pass.classifierRound !== undefined ? { classifier: pass.classifierRound } : {}) });
131
+ const ancestorArbitration = pass.arbitration === undefined && pass.deniedBy === "policy" ? input.ancestorArbitrationOf?.(toolCallId) : undefined;
132
+ const modeRefusal = pass.arbitration?.kind === "denied_by_mode" ? pass.arbitration : ancestorArbitration?.kind === "denied_by_mode" ? ancestorArbitration : undefined;
133
+ if (modeRefusal !== undefined) {
134
+ pass.deniedBy = "mode";
135
+ ledger.supersede();
136
+ }
137
+ const gate = mintGateOutcome({ deniedBy: pass.deniedBy, ...(pass.denyCause !== undefined ? { cause: pass.denyCause } : {}), ...(ledger.settled !== undefined ? { settled: ledger.settled } : {}), ...(pass.classifierRound !== undefined ? { classifier: pass.classifierRound } : {}), ...(modeRefusal !== undefined ? { modeRefusal: { mode: modeRefusal.mode, askClass: modeRefusal.askClass, classes: modeRefusal.classes } } : {}) });
121
138
  const unresolvable = askUnresolvableCauseOf(pass.decision, { toolCallId, toolName }) ?? unresolvableCauseOfSettlement(gate);
139
+ const remedy = unresolvable === "no_approver_wired" ? headlessApprovalRemedy({ mode: input.permissionMode?.(), classification: pass.askClassification, coveredWrite: isProtectedWrite(pass.req), ...(pass.shellUnplacedOptions !== undefined ? { unplacedOptions: pass.shellUnplacedOptions } : {}) }) : undefined;
140
+ const denyReason = withParkFailureCause(`${decisionText(pass.decision) ?? `tool "${toolName}" denied by policy`}${remedy !== undefined ? ` — ${remedy}` : ""}`, pass.parkFailed);
122
141
  if (unresolvable !== undefined) {
123
142
  const cause = unresolvable;
124
- pass.notifier.notify(() => input.onAskUnresolvable?.({ toolName, toolCallId, cause, parkLaneExisted: pass.suspendAsk !== undefined }), "toolGate.onAskUnresolvable");
143
+ pass.notifier.notify(() => input.onAskUnresolvable?.({ toolName, toolCallId, cause, parkLaneExisted: pass.suspendAsk !== undefined, ...(remedy !== undefined ? { remedy } : {}) }), "toolGate.onAskUnresolvable");
125
144
  }
126
145
  await notifyPermissionDenied({
127
146
  toolName,
@@ -136,7 +155,7 @@ export async function exitGate(pass) {
136
155
  reason: formatHookFeedback(denyReason, input.reminderMark),
137
156
  gate,
138
157
  ...(() => {
139
- const details = pass.denyDetails ?? pass.shellReadDetails;
158
+ const details = pass.denyDetails ?? (modeRefusal !== undefined ? undefined : pass.shellReadDetails);
140
159
  return details !== undefined ? { details } : {};
141
160
  })(),
142
161
  preToolContext: pass.preToolContext,
@@ -147,9 +166,11 @@ export async function exitGate(pass) {
147
166
  if (rw !== undefined)
148
167
  pass.currentInput = rw;
149
168
  }
169
+ const release = pass.arbitration?.kind === "released" ? pass.arbitration : (() => { if (ledger.settled !== undefined)
170
+ return undefined; const a = pass.arbitration === undefined ? input.ancestorArbitrationOf?.(toolCallId) : undefined; return a?.kind === "released" ? a : undefined; })();
150
171
  return {
151
172
  updatedInput: pass.currentInput === event.input ? undefined : pass.currentInput,
152
- gate: mintGateOutcome({ ...(ledger.settled !== undefined ? { settled: ledger.settled } : {}), ...(pass.classifierRound !== undefined ? { classifier: pass.classifierRound } : {}) }),
173
+ gate: mintGateOutcome({ ...(ledger.settled !== undefined ? { settled: ledger.settled } : {}), ...(pass.classifierRound !== undefined ? { classifier: pass.classifierRound } : {}), ...(release !== undefined ? { released: releaseRecordOf(release) } : {}) }),
153
174
  preToolContext: pass.preToolContext,
154
175
  };
155
176
  }
@@ -29,8 +29,9 @@ import type { AskRuleEvidence, ToolCallRequest } from "../tool-policy.js";
29
29
  import { persistedRuleMandateOf, type RuleClearanceFacts } from "../ask-origin.js";
30
30
  import { type OrgGateVerdict, type PersistedRuleAnswer, type PersistedRuleHit, type PersistedRuleUnreadable } from "../hooks.js";
31
31
  import type { PersistedRule, PersistedRuleVerdict, RuleOffer, SegmentCoverage } from "../permission-rule-model.js";
32
+ import { type ToolCallLike } from "../tool-registry.js";
32
33
  import { type OrgRuleResolution } from "../permission-rule-org.js";
33
- import type { PermissionRuleStoreProvider } from "../permission-rule-provider.js";
34
+ import { type EffectivePermissionRules, type PermissionRuleStoreProvider } from "../permission-rule-provider.js";
34
35
  /** The one SHELL tool the persisted-rule lane's COMMAND grammar speaks for (the rule-offer face mints for
35
36
  * it alone). The PATH grammar's tools are derived from the catalog (`ruleToolGrammarOf`). */
36
37
  export declare const PERSISTED_RULE_TOOL = "Bash";
@@ -81,10 +82,9 @@ export declare function orgRevisionEvidenceOf(resolution: OrgRuleResolution, onD
81
82
  */
82
83
  export declare function persistedRuleHitOf(verdict: PersistedRuleVerdict | undefined): PersistedRuleHit | undefined;
83
84
  /** The COMMAND arm's projection of a verdict: a call the lexer could not read against a standing deny/ask
84
- * row is the lane's UNREADABLE answer — the same fail-closed shape an unreadable store takes (a
85
- * real-approval ask no automatic lane clears), with the lexer's reason in place of the store's — never a
86
- * matched ask rule, which would let a blanket `onAsk: "allow"` clear a question nobody can answer. Every
87
- * other verdict is {@link persistedRuleHitOf}'s hit. */
85
+ * row is the lane's UNREADABLE answer carrying the lexer's reason — it fails closed (a real-approval ask no
86
+ * automatic lane clears) — never a matched ask rule, which would let a blanket `onAsk: "allow"` clear a
87
+ * question nobody can answer. Every other verdict is {@link persistedRuleHitOf}'s hit. */
88
88
  export declare function persistedRuleAnswerOf(verdict: PersistedRuleVerdict | undefined): PersistedRuleHit | PersistedRuleUnreadable | undefined;
89
89
  /**
90
90
  * Widened to every path-targeting tool — the persisted-rule lane's PATH arm: the call's
@@ -152,6 +152,11 @@ export declare function createPermissionRuleLanes(cfg: {
152
152
  * that were dropped) and for a durable read failure — each read's lines are delivered once, whichever
153
153
  * lane took the read. */
154
154
  onDisclosure: (message: string) => void;
155
+ /** The person is told their store (or the named sources of it) was skipped for a call (the run's one notice,
156
+ * `ruleStoreUnreadableNotice`). */
157
+ onStoreUnreadable: (at: "call", sources?: EffectivePermissionRules["unreadableSources"]) => void;
158
+ /** The run's cut: a read it interrupts is abandoned quietly — a cancelled wait is no fact about the store. */
159
+ signal?: AbortSignal;
155
160
  }): PermissionRuleLanes;
156
161
  /** The closed reason set for the rule-offer factory's empty answer — the same three
157
162
  * spellings the `AskRequest` and durable-row seats declare, kept at the one factory that fills both.
@@ -242,7 +247,7 @@ export declare function createRuleOffersOf(cfg: {
242
247
  current: string;
243
248
  } | undefined;
244
249
  deniesDirectoryRead: ((directory: string) => boolean) | undefined;
245
- }): (toolName: string, args: unknown, ask: RuleOffersAskFacts | undefined) => {
250
+ }): (call: Pick<ToolCallLike, "toolName" | "face">, args: unknown, ask: RuleOffersAskFacts | undefined) => {
246
251
  ruleOffers?: readonly RuleOffer[];
247
252
  ruleOffersAbsence?: RuleOffersAbsence;
248
253
  execCwd?: string;
@@ -1,11 +1,15 @@
1
1
  import { persistedRuleMandateOf, ruleClearanceOf } from "../ask-origin.js";
2
2
  import {} from "../hooks.js";
3
- import { adjudicatePersistedPathRules, adjudicatePersistedRules, ruleToolGrammarOf, segmentCoverageOf, suggestRulesForCommand } from "../permission-rule-model.js";
3
+ import { adjudicatePersistedPathRules, adjudicatePersistedRules, laneToolNamesOf, ruleToolGrammarOf, segmentCoverageOf, suggestRulesForCommand } from "../permission-rule-model.js";
4
4
  import { isAbsolutePathForm } from "../../tools/fs/safety.js";
5
5
  import { effectivePathTargetOf } from "../effective-path-target.js";
6
- import { declaredPathTargetOf, protectivePathTargetOf } from "../tool-registry.js";
6
+ import { anyNamed, declaredPathTargetOf, firstNamed, protectivePathTargetOf, ruleNamesOf } from "../tool-registry.js";
7
7
  import { orgRuleVerdictFor } from "../permission-rule-org.js";
8
+ import { ownerStoreOf } from "../permission-rule-provider.js";
8
9
  import { thrownText } from "../thrown-value.js";
10
+ import { strongestAnswer } from "../persisted-rule-arms.js";
11
+ import { gateWaitFailureOf, withGateTimeout } from "../gate-bounded-await.js";
12
+ import { ORG_ADJUDICATION_TIMEOUT_MS } from "../permission-rule-org.js";
9
13
  export const PERSISTED_RULE_TOOL = "Bash";
10
14
  const HANDOFF_BOUND = 256;
11
15
  export function inheritedAskRuleEvidence(provider) {
@@ -59,10 +63,7 @@ export function createPermissionRuleLanes(cfg) {
59
63
  if (provider === undefined)
60
64
  return { personal: undefined, org: undefined };
61
65
  const anonymous = cfg.principal === undefined || cfg.principal === "";
62
- const store = anonymous && cfg.localOwnerDeclared
63
- ?
64
- provider.forLocalOwner()
65
- : provider.forPrincipal(anonymous ? undefined : cfg.principal);
66
+ const store = ownerStoreOf(provider, cfg.principal, cfg.localOwnerDeclared);
66
67
  const handed = new Map();
67
68
  const read = () => store.effective({ sessionId: cfg.sessionId }).then((view) => {
68
69
  for (const line of view.disclosures)
@@ -105,62 +106,68 @@ export function createPermissionRuleLanes(cfg) {
105
106
  }
106
107
  const revisionCell = orgRevisionEvidenceOf(resolution, cfg.onRevisionDefect);
107
108
  const command = req.args?.command;
108
- if (req.toolName !== PERSISTED_RULE_TOOL || typeof command !== "string")
109
+ if (!laneToolNamesOf(req).includes(PERSISTED_RULE_TOOL) || typeof command !== "string")
109
110
  return { status: "available", ...revisionCell };
110
- const verdict = orgRuleVerdictFor(resolution.rules, { tool: req.toolName, command });
111
+ const verdict = orgRuleVerdictFor(resolution.rules, { tool: PERSISTED_RULE_TOOL, command });
111
112
  return verdict === undefined ? { status: "available", ...revisionCell } : { status: "available", verdict, ...revisionCell };
112
113
  },
113
114
  }
114
115
  : undefined;
115
- const personal = provider.partitions.durable
116
- ? {
117
- adjudicate: async (req) => {
118
- if (anonymous && !cfg.localOwnerDeclared)
119
- return undefined;
120
- const grammar = ruleToolGrammarOf(req.toolName);
121
- if (grammar === undefined)
122
- return undefined;
123
- let view;
124
- try {
125
- view = await claim(req.toolCallId);
126
- }
127
- catch (err) {
128
- cfg.onDisclosure(thrownText(err));
129
- return { unreadable: true };
130
- }
131
- if (view.unreadable !== undefined) {
132
- cfg.onDisclosure(view.unreadable);
133
- return { unreadable: true };
134
- }
135
- const table = view.rules;
136
- const liveCwd = cfg.liveCwd();
137
- if (grammar === "path") {
138
- return pathRuleLaneAnswer(table, req, { root: cfg.root, sessionId: cfg.sessionId, liveCwd, home: cfg.home });
139
- }
140
- const command = req.args?.command;
141
- if (typeof command !== "string")
142
- return undefined;
143
- const execCwd = liveCwd !== undefined ? { execCwd: liveCwd } : {};
144
- const verdict = adjudicatePersistedRules(table, { tool: req.toolName, command, cwd: cfg.root, sessionId: cfg.sessionId, ...execCwd });
145
- if (verdict !== undefined)
146
- return persistedRuleAnswerOf(verdict);
147
- const coverage = segmentCoverageOf(command, { persisted: table }, { tool: req.toolName, cwd: cfg.root, sessionId: cfg.sessionId, ...execCwd });
148
- return coverage !== undefined ? { segmentCoverage: coverage } : undefined;
149
- },
116
+ const personalAdjudicate = async (req) => {
117
+ if (anonymous && !cfg.localOwnerDeclared)
118
+ return undefined;
119
+ const laneNames = laneToolNamesOf(req);
120
+ if (laneNames.length === 0)
121
+ return undefined;
122
+ let view;
123
+ try {
124
+ view = await withGateTimeout(claim(req.toolCallId), ORG_ADJUDICATION_TIMEOUT_MS, cfg.signal, "persistedRules");
150
125
  }
151
- : undefined;
126
+ catch (err) {
127
+ return gateWaitFailureOf(err) === "aborted" ? { unreadable: true } : skipped(thrownText(err));
128
+ }
129
+ if (view.unreadable !== undefined)
130
+ skipped(view.unreadable, view.unreadableSources);
131
+ try {
132
+ const answer = strongestAnswer(laneNames.map((n) => judgeAgainst(view.rules, { ...req, toolName: n }, ruleToolGrammarOf(n))));
133
+ return view.unreadable !== undefined && (answer === undefined || "segmentCoverage" in answer) ? { unreadable: true } : answer;
134
+ }
135
+ catch (err) {
136
+ const why = `judging this call against the rows threw: ${thrownText(err)}`;
137
+ cfg.onDisclosure(`the persisted rule lane could not judge a ${req.toolName} call against the rows it read — ${why}`);
138
+ return { unreadable: true, reason: why };
139
+ }
140
+ };
141
+ const judgeAgainst = (table, req, grammar) => {
142
+ const liveCwd = cfg.liveCwd();
143
+ if (grammar === "path")
144
+ return pathRuleLaneAnswer(table, req, { root: cfg.root, sessionId: cfg.sessionId, liveCwd, home: cfg.home });
145
+ const command = req.args?.command;
146
+ if (typeof command !== "string")
147
+ return undefined;
148
+ const execCwd = liveCwd !== undefined ? { execCwd: liveCwd } : {};
149
+ const verdict = adjudicatePersistedRules(table, { tool: req.toolName, command, cwd: cfg.root, sessionId: cfg.sessionId, ...execCwd });
150
+ if (verdict !== undefined)
151
+ return persistedRuleAnswerOf(verdict);
152
+ const coverage = segmentCoverageOf(command, { persisted: table }, { tool: req.toolName, cwd: cfg.root, sessionId: cfg.sessionId, ...execCwd });
153
+ return coverage !== undefined ? { segmentCoverage: coverage } : undefined;
154
+ };
155
+ const skipped = (why, sources) => (cfg.onDisclosure(why), cfg.onStoreUnreadable("call", sources), { unreadable: true });
156
+ const personal = provider.partitions.durable ? { adjudicate: personalAdjudicate } : undefined;
152
157
  return { personal, org };
153
158
  }
154
159
  export function createRuleOffersOf(cfg) {
155
- return (toolName, args, ask) => {
160
+ return (call, args, ask) => {
161
+ const toolName = call.toolName;
156
162
  if (!cfg.laneArmed || toolName !== PERSISTED_RULE_TOOL)
157
163
  return {};
158
164
  if ((cfg.principal === undefined || cfg.principal === "") && !cfg.localOwnerDeclared)
159
165
  return {};
166
+ const tierSeat = firstNamed(cfg.irreversibilityTier, ruleNamesOf(call));
160
167
  const closedDoor = ruleClearanceOf(ask, {
161
- egress: cfg.egressTools.has(toolName),
162
- irreversibility: cfg.irreversibilityTier.get(toolName),
163
- shellGated: cfg.shellGatedTools.has(toolName),
168
+ egress: anyNamed(cfg.egressTools, ruleNamesOf(call)),
169
+ irreversibility: tierSeat?.value,
170
+ shellGated: tierSeat !== undefined && cfg.shellGatedTools.has(tierSeat.name),
164
171
  }).word;
165
172
  if (closedDoor !== undefined)
166
173
  return { ruleOffersAbsence: closedDoor };
@@ -0,0 +1,64 @@
1
+ /**
2
+ * The person's persisted PATH rows as a leg's read face takes them at prepare (the gate's rule lanes read the same store
3
+ * per call).
4
+ */
5
+ import { type EffectivePermissionRules, type PermissionRuleStoreProvider } from "../permission-rule-provider.js";
6
+ import type { PathRuleDenyRow } from "../../tools/fs/read-deny.js";
7
+ import type { RunnerDeps } from "../runner-deps.js";
8
+ /** The person's persisted PATH rows a leg's read face takes at prepare ({@link persistedPathRowsOf}). */
9
+ export interface PersistedPathRows {
10
+ /** Every live `Read(//dir/**)` allow row's directory (lexical, absolute): a standing yes to reads under it. */
11
+ readonly readDirectories: readonly string[];
12
+ /** Every live face-keyed path DENY row (`Read(...)`, `Edit(...)`), as a deny-table row resolved at the task's bases. */
13
+ readonly denyRows: readonly PathRuleDenyRow[];
14
+ }
15
+ /**
16
+ * The person's persisted PATH rows, read ONCE at prepare from the same store query the gate's lanes read per call: a
17
+ * `Read(//dir/**)` allow row is a READ DIRECTORY of the run (the Bash read station, Read / Grep / Glob — one set), and a
18
+ * `Read(...)` / `Edit(...)` deny row joins the run's read / write deny tables (every read face, the shell's file commands
19
+ * and traversals). One read, two directions, so a person's allow row never reaches further than their deny rows: a
20
+ * directory it admits still refuses the paths a deny row names. A row the store gains mid-run applies from the next
21
+ * prepare; the in-flight grant (`readRootGrant`) is the mid-run door. The store not armed, or the anonymous floor ⇒ no
22
+ * rows. A read that failed (or did not answer in time) ⇒ no rows, and a partition the read could not read ⇒ none of ITS
23
+ * rows (the partition that was read contributes its own): what could not be read is skipped for this run's tables — its
24
+ * directories widen nothing and its deny rows judge nothing here — disclosed on the trace and announced to the person
25
+ * (`onUnread`); the next prepare reads again, as it would for a row the store gained mid-run. A deny row whose
26
+ * pattern needs a base the task cannot supply (`~/` with no declared home) is left to the per-call lane and disclosed.
27
+ */
28
+ export declare function persistedPathRowsOf(cfg: {
29
+ provider: PermissionRuleStoreProvider | undefined;
30
+ localOwnerDeclared: boolean;
31
+ principal: string | undefined;
32
+ sessionId: string;
33
+ root: string;
34
+ home: string | undefined;
35
+ onDisclosure: (message: string) => void;
36
+ /** The person is told the store (or the named sources of it) was skipped ({@link ruleStoreUnreadableNotice}). */
37
+ onUnread: (at: "prepare", sources?: EffectivePermissionRules["unreadableSources"]) => void;
38
+ /** The run's cut: a read that has not answered by then (or by the gate's await bound) reads as unreadable. */
39
+ signal?: AbortSignal;
40
+ }): Promise<PersistedPathRows>;
41
+ /**
42
+ * The run's ONE `permission.rule_store_unavailable` notice (audience user): the person's rules — the whole store, or the
43
+ * sources of it whose partition could not be read — were skipped; minted by whichever read found it first (the prepare
44
+ * read of the path rows, or a call's own read on the rule lane), once per run however many reads fail after it (the
45
+ * prepare sentence says a later call may be judged without them too). The trace keeps every read's line.
46
+ */
47
+ export declare function ruleStoreUnreadableNotice(onNotice: RunnerDeps["onNotice"], ids: {
48
+ readonly sessionId: string;
49
+ readonly runId: string;
50
+ }): (at: "prepare" | "call", sources?: EffectivePermissionRules["unreadableSources"]) => void;
51
+ /** {@link persistedPathRowsOf} for one task's prepare: only where a read face mounts (hands), disclosed on the run's trace,
52
+ * cut by the run's own signals. `undefined` = nothing to read, returned synchronously: a run with no person's store (or
53
+ * no hands) gains no suspension point on its prepare. */
54
+ export declare function persistedPathRowsOfTask(t: {
55
+ deps: Pick<RunnerDeps, "permissionRuleStore" | "localOwnerRules" | "tracer">;
56
+ principal: string | undefined;
57
+ sessionId: string;
58
+ root: string;
59
+ home: string | undefined;
60
+ taskId: string;
61
+ handsEnabled: boolean;
62
+ signals: readonly AbortSignal[];
63
+ onUnread: (at: "prepare", sources?: EffectivePermissionRules["unreadableSources"]) => void;
64
+ }): Promise<PersistedPathRows> | undefined;