@sema-agent/core 7.49.5 → 7.50.1

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 (200) hide show
  1. package/CHANGELOG.md +157 -0
  2. package/dist/agents/observer.js +3 -10
  3. package/dist/agents/subagent-steps.d.ts +6 -14
  4. package/dist/agents/subagent-steps.js +3 -17
  5. package/dist/brain/stream-shared.d.ts +1 -1
  6. package/dist/core/allow-entry.d.ts +1 -1
  7. package/dist/core/allow-entry.js +1 -1
  8. package/dist/core/ask-origin.d.ts +15 -11
  9. package/dist/core/ask-origin.js +6 -14
  10. package/dist/core/auto-mode-prompt.js +6 -2
  11. package/dist/core/checkpoint-store.d.ts +6 -4
  12. package/dist/core/checkpoint-store.js +1 -1
  13. package/dist/core/context-edit.d.ts +2 -2
  14. package/dist/core/decision-receipt.d.ts +2 -2
  15. package/dist/core/decision-receipt.js +30 -11
  16. package/dist/core/delegation-frames.d.ts +66 -18
  17. package/dist/core/delegation-frames.js +22 -0
  18. package/dist/core/engine-notice.d.ts +7 -5
  19. package/dist/core/frame-settlement.d.ts +6 -1
  20. package/dist/core/gate-fold.js +34 -19
  21. package/dist/core/gate-lanes.js +15 -13
  22. package/dist/core/gate-outcome.d.ts +2 -2
  23. package/dist/core/governance-codes.js +2 -0
  24. package/dist/core/hooks.d.ts +28 -20
  25. package/dist/core/human-input-projection.js +2 -0
  26. package/dist/core/lsp-diagnostics.d.ts +3 -0
  27. package/dist/core/lsp-diagnostics.js +6 -0
  28. package/dist/core/lsp-session.d.ts +39 -17
  29. package/dist/core/lsp-session.js +118 -58
  30. package/dist/core/lsp.d.ts +83 -3
  31. package/dist/core/lsp.js +146 -16
  32. package/dist/core/mcp.js +2 -1
  33. package/dist/core/media-byte-cap.d.ts +14 -0
  34. package/dist/core/media-byte-cap.js +15 -3
  35. package/dist/core/memory-engine/consolidation-driver.d.ts +23 -1
  36. package/dist/core/memory-engine/consolidation-driver.js +54 -6
  37. package/dist/core/memory-engine/consolidation-write-gate.d.ts +46 -0
  38. package/dist/core/memory-engine/consolidation-write-gate.js +82 -0
  39. package/dist/core/memory-engine/consolidation.d.ts +17 -0
  40. package/dist/core/memory-engine/consolidation.js +1 -0
  41. package/dist/core/memory-engine/distiller.d.ts +8 -2
  42. package/dist/core/memory-engine/distiller.js +8 -6
  43. package/dist/core/memory-engine/engine.d.ts +37 -4
  44. package/dist/core/memory-engine/engine.js +127 -38
  45. package/dist/core/memory-engine/file-backend.d.ts +8 -3
  46. package/dist/core/memory-engine/file-backend.js +44 -16
  47. package/dist/core/memory-engine/index.d.ts +2 -2
  48. package/dist/core/memory-engine/index.js +1 -1
  49. package/dist/core/memory-engine/landing-admission.d.ts +65 -0
  50. package/dist/core/memory-engine/landing-admission.js +68 -0
  51. package/dist/core/memory-engine/types.d.ts +23 -0
  52. package/dist/core/mode-policy.d.ts +3 -2
  53. package/dist/core/mode-policy.js +2 -0
  54. package/dist/core/permission-rule-model.d.ts +19 -13
  55. package/dist/core/permission-rule-model.js +7 -4
  56. package/dist/core/permission-rule-provider.d.ts +11 -0
  57. package/dist/core/permission-rules.d.ts +1 -1
  58. package/dist/core/permission-rules.js +5 -2
  59. package/dist/core/persisted-rule-arms.d.ts +15 -7
  60. package/dist/core/persisted-rule-arms.js +13 -6
  61. package/dist/core/reminder-mint.d.ts +5 -0
  62. package/dist/core/reminder-mint.js +1 -0
  63. package/dist/core/retired-keys.d.ts +8 -0
  64. package/dist/core/retired-keys.js +4 -0
  65. package/dist/core/rule-lane-reads.d.ts +4 -4
  66. package/dist/core/rule-lane-reads.js +10 -1
  67. package/dist/core/runner/construction-doors.d.ts +10 -4
  68. package/dist/core/runner/construction-doors.js +25 -2
  69. package/dist/core/runner/contracts.d.ts +20 -3
  70. package/dist/core/runner/env-capability-census.js +1 -1
  71. package/dist/core/runner/gate-exit.js +2 -1
  72. package/dist/core/runner/memory-consolidation-driver.d.ts +6 -4
  73. package/dist/core/runner/memory-consolidation-driver.js +5 -2
  74. package/dist/core/runner/memory-consolidation-write-judge.d.ts +17 -0
  75. package/dist/core/runner/memory-consolidation-write-judge.js +14 -0
  76. package/dist/core/runner/permission-rule-lanes.d.ts +16 -22
  77. package/dist/core/runner/permission-rule-lanes.js +13 -43
  78. package/dist/core/runner/person-path-rows.d.ts +244 -0
  79. package/dist/core/runner/person-path-rows.js +387 -0
  80. package/dist/core/runner/policy-throw.d.ts +34 -0
  81. package/dist/core/runner/policy-throw.js +25 -0
  82. package/dist/core/runner/prepare-acquire-reconcile.js +8 -1
  83. package/dist/core/runner/prepare-artifact.d.ts +5 -3
  84. package/dist/core/runner/prepare-artifact.js +2 -8
  85. package/dist/core/runner/prepare-ask-lane.d.ts +2 -1
  86. package/dist/core/runner/prepare-ask-lane.js +14 -16
  87. package/dist/core/runner/prepare-caps-and-workflow.d.ts +4 -3
  88. package/dist/core/runner/prepare-caps-and-workflow.js +1 -1
  89. package/dist/core/runner/prepare-context-lane.js +2 -2
  90. package/dist/core/runner/prepare-gate-stations.d.ts +3 -3
  91. package/dist/core/runner/prepare-gate-stations.js +1 -1
  92. package/dist/core/runner/prepare-hands-readface.d.ts +19 -10
  93. package/dist/core/runner/prepare-hands-readface.js +57 -43
  94. package/dist/core/runner/prepare-lsp.d.ts +15 -13
  95. package/dist/core/runner/prepare-lsp.js +11 -11
  96. package/dist/core/runner/prepare-policy-chain.d.ts +8 -4
  97. package/dist/core/runner/prepare-policy-chain.js +11 -10
  98. package/dist/core/runner/prepare-run-refs.js +1 -1
  99. package/dist/core/runner/prepare-task.js +24 -16
  100. package/dist/core/runner/prepare-turn-wiring.d.ts +13 -6
  101. package/dist/core/runner/prepare-turn-wiring.js +19 -8
  102. package/dist/core/runner/read-root-grant.d.ts +1 -25
  103. package/dist/core/runner/read-root-grant.js +1 -17
  104. package/dist/core/runner/retired-runner-deps.d.ts +2 -2
  105. package/dist/core/runner/run-brain-sinks.d.ts +1 -5
  106. package/dist/core/runner/run-brain-sinks.js +2 -4
  107. package/dist/core/runner/run-harness-handlers.js +5 -4
  108. package/dist/core/runner/run-identity-wiring.d.ts +4 -4
  109. package/dist/core/runner/run-identity-wiring.js +10 -1
  110. package/dist/core/runner/run-leg.d.ts +1 -1
  111. package/dist/core/runner/run-leg.js +22 -9
  112. package/dist/core/runner/run-settle-and-teardown.d.ts +0 -4
  113. package/dist/core/runner/run-settle-and-teardown.js +7 -4
  114. package/dist/core/runner/run-terminal-adoption.d.ts +1 -1
  115. package/dist/core/runner/run-terminal-adoption.js +1 -1
  116. package/dist/core/runner/run-turn-boundary.js +2 -2
  117. package/dist/core/runner/runtask.js +8 -8
  118. package/dist/core/runner/session-file-state-replay.d.ts +14 -4
  119. package/dist/core/runner/session-file-state-replay.js +8 -0
  120. package/dist/core/runner/stream-steer-verb.js +3 -0
  121. package/dist/core/runner/tool-end-body.d.ts +5 -4
  122. package/dist/core/runner/tool-end-body.js +4 -4
  123. package/dist/core/runner/tool-output-projection.d.ts +6 -4
  124. package/dist/core/runner/tool-output-projection.js +66 -47
  125. package/dist/core/runner-deps.d.ts +6 -3
  126. package/dist/core/shared-memory/types.d.ts +2 -2
  127. package/dist/core/shell-read-silence.d.ts +23 -0
  128. package/dist/core/shell-read-silence.js +18 -0
  129. package/dist/core/synthetic-read-verdict.d.ts +80 -21
  130. package/dist/core/synthetic-read-verdict.js +89 -18
  131. package/dist/core/task-event.d.ts +113 -25
  132. package/dist/core/task-spec.d.ts +44 -0
  133. package/dist/core/task-tool-shape.d.ts +1 -1
  134. package/dist/core/tool-catalog-entries.js +16 -2
  135. package/dist/core/tool-policy.d.ts +25 -15
  136. package/dist/core/tool-policy.js +9 -13
  137. package/dist/core/trace.d.ts +3 -2
  138. package/dist/core/types.d.ts +3 -3
  139. package/dist/core/types.js +1 -1
  140. package/dist/core/untrusted-text.d.ts +2 -2
  141. package/dist/core/user-attachment.d.ts +164 -0
  142. package/dist/core/user-attachment.js +437 -0
  143. package/dist/engine/compaction/compaction.js +10 -3
  144. package/dist/engine/compaction/utils.js +6 -0
  145. package/dist/engine/execution-env/node-execution-env.d.ts +19 -1
  146. package/dist/engine/execution-env/node-execution-env.js +125 -0
  147. package/dist/engine/harness/agent-harness.d.ts +12 -2
  148. package/dist/engine/harness/agent-harness.js +15 -3
  149. package/dist/engine/harness/types.d.ts +21 -0
  150. package/dist/engine/llm/content-block-render.d.ts +15 -0
  151. package/dist/engine/llm/content-block-render.js +10 -0
  152. package/dist/engine/llm/index.d.ts +1 -0
  153. package/dist/engine/llm/index.js +1 -0
  154. package/dist/engine/llm/types.d.ts +80 -4
  155. package/dist/engine/llm/types.js +43 -1
  156. package/dist/engine/session/import-validate.js +2 -0
  157. package/dist/index.d.ts +6 -4
  158. package/dist/index.js +2 -1
  159. package/dist/internal/harness-types.d.ts +1 -1
  160. package/dist/internal/llm.d.ts +2 -2
  161. package/dist/internal/llm.js +1 -1
  162. package/dist/orchestration/workflow.d.ts +4 -5
  163. package/dist/orchestration/workflow.js +2 -2
  164. package/dist/prompts/default.d.ts +1 -1
  165. package/dist/tools/artifact/artifact-tool.js +1 -1
  166. package/dist/tools/fs/bash-readonly-classifier.d.ts +1 -1
  167. package/dist/tools/fs/dir-entries.d.ts +4 -10
  168. package/dist/tools/fs/dir-entries.js +4 -11
  169. package/dist/tools/fs/file-read-check.js +3 -3
  170. package/dist/tools/fs/fs-pdf.js +2 -4
  171. package/dist/tools/fs/fs-read.d.ts +47 -4
  172. package/dist/tools/fs/fs-read.js +63 -19
  173. package/dist/tools/fs/fs-search-tools.d.ts +3 -3
  174. package/dist/tools/fs/fs-search-tools.js +7 -2
  175. package/dist/tools/fs/fs-write.d.ts +1 -1
  176. package/dist/tools/fs/fs-write.js +7 -6
  177. package/dist/tools/fs/index.d.ts +7 -1
  178. package/dist/tools/fs/index.js +56 -34
  179. package/dist/tools/fs/read-deny-home.d.ts +19 -1
  180. package/dist/tools/fs/read-deny-home.js +32 -22
  181. package/dist/tools/fs/read-deny.d.ts +14 -0
  182. package/dist/tools/fs/read-deny.js +8 -0
  183. package/dist/tools/fs/repo-map.d.ts +3 -2
  184. package/dist/tools/fs/repo-map.js +11 -3
  185. package/dist/tools/fs/safety.d.ts +41 -2
  186. package/dist/tools/fs/safety.js +12 -3
  187. package/dist/tools/fs/shell-read-check.d.ts +12 -3
  188. package/dist/tools/fs/shell-read-check.js +44 -24
  189. package/dist/tools/fs/shell-read-device.d.ts +1 -1
  190. package/dist/tools/fs/shell-read-face.d.ts +15 -0
  191. package/dist/tools/fs/shell-read-face.js +5 -0
  192. package/dist/tools/fs/shell-read-judge.d.ts +9 -4
  193. package/dist/tools/fs/shell-read-judge.js +1 -1
  194. package/dist/tools/loop-tick.d.ts +2 -2
  195. package/package.json +3 -1
  196. package/test/export-surface.snapshot.json +39 -1
  197. package/wire-changes/core@7.50.0.json +1699 -0
  198. package/wire-changes/core@7.50.1.json +65 -0
  199. package/dist/core/runner/persisted-path-rows.d.ts +0 -88
  200. package/dist/core/runner/persisted-path-rows.js +0 -108
@@ -1,3 +1,5 @@
1
+ import { attest } from "./tool-policy.js";
2
+ import { callPathFaceOf } from "./permission-rule-model.js";
1
3
  import { decisionAskClassesOf } from "./ask-origin.js";
2
4
  import { inlineUntrusted } from "./untrusted-text.js";
3
5
  export function strongestAnswer(answers) {
@@ -9,12 +11,15 @@ export function disclosedRuleSet(rules) {
9
11
  const rest = rules.length - shown.length;
10
12
  return rest > 0 ? `${shown.join(", ")} and ${rest} more` : shown.join(", ");
11
13
  }
12
- export function applyPersistedTightening(decision, read) {
13
- if (read?.unreadable === true && read.reason !== undefined) {
14
+ export function applyPersistedTightening(decision, read, call) {
15
+ if (read?.unreadable === true) {
14
16
  if (decision.action === "deny")
15
17
  return { decision };
16
- const message = `this call could not be read against the person's standing deny/ask rules (${read.reason}) — whether one of them forbids it is unknown, so it needs a real approval`;
17
- const unreadableCall = { ruleUnreadable: "persisted_call" };
18
+ const callForm = read.reason !== undefined;
19
+ const message = callForm
20
+ ? `this call could not be read against the person's standing deny/ask rules (${read.reason}) — whether one of them forbids it is unknown, so it needs a real approval`
21
+ : "the person's standing permission rules could not be read — whether one of them forbids this call is unknown, so it needs a real approval";
22
+ const unreadableCall = callForm ? { ruleUnreadable: "persisted_call" } : {};
18
23
  const standingFence = decision.action === "ask" && decisionAskClassesOf({ ...decision, foldedAskClasses: undefined }).has("policy_rra")
19
24
  ? { foldedAskClasses: [...(decision.foldedAskClasses ?? []), "policy_rra"] }
20
25
  : {};
@@ -22,7 +27,7 @@ export function applyPersistedTightening(decision, read) {
22
27
  decision: decision.action === "allow"
23
28
  ? { action: "ask", message, decisionReason: "persisted_rule", requiresRealApproval: true, ...unreadableCall, ...(decision.updatedInput !== undefined ? { updatedInput: decision.updatedInput } : {}) }
24
29
  : { ...decision, requiresRealApproval: true, ...unreadableCall, ...standingFence, message: `${decision.message !== undefined ? `${decision.message} ` : ""}(${message})` },
25
- tightened: "unreadable",
30
+ tightened: callForm ? "unreadable" : "store_unreadable",
26
31
  };
27
32
  }
28
33
  const hit = read?.hit;
@@ -30,7 +35,9 @@ export function applyPersistedTightening(decision, read) {
30
35
  return { decision };
31
36
  const shown = disclosedRuleSet(hit.rules);
32
37
  if (hit.behavior === "deny") {
33
- return { decision: { action: "deny", message: `a persisted deny rule (${shown}) refuses this call`, decisionReason: "persisted_rule" }, tightened: "deny" };
38
+ const denied = { action: "deny", message: `a persisted deny rule (${shown}) refuses this call`, decisionReason: "persisted_rule" };
39
+ const attested = hit.denial !== undefined && call !== undefined && hit.denial.face === callPathFaceOf(call);
40
+ return { decision: attested ? attest(denied, call, { pathDenial: hit.denial }) : denied, tightened: "deny" };
34
41
  }
35
42
  if (decision.action === "deny")
36
43
  return { decision };
@@ -60,6 +60,11 @@ export declare function openSystemReminder(mark: string | undefined): string;
60
60
  * used by the user-lane wrap homes. The BODY is byte-untouched by the mark (design/319 first
61
61
  * invariant: zero data-byte change — the mark rides only the engine's own tag). */
62
62
  export declare function mintSystemReminder(body: string, mark: string | undefined): string;
63
+ /** The one boundary rule for an engine-minted marked block that follows data in the same message or tool result (a
64
+ * read's disclosure trailer, an MCP result's, a user-attachment frame's closing block): it opens on its own line after
65
+ * a blank line, whatever the data ends with. The completions lanes join a message's text blocks with no separator, so
66
+ * the block's own bytes carry the boundary. */
67
+ export declare const MARKED_BLOCK_AFTER_DATA = "\n\n";
63
68
  /** Wrap model-facing hook/gate feedback in a `<system-reminder>` so it reads as guidance, not data.
64
69
  * NOTE (council design/74 #6): this does NOT escape a literal `</system-reminder>` in `text` — callers MUST
65
70
  * pass trusted, first-party strings (every current caller does: fixed gate/limit messages). If a future
@@ -13,6 +13,7 @@ export function openSystemReminder(mark) {
13
13
  export function mintSystemReminder(body, mark) {
14
14
  return `${openSystemReminder(mark)}\n${body}\n</system-reminder>`;
15
15
  }
16
+ export const MARKED_BLOCK_AFTER_DATA = "\n\n";
16
17
  export function formatHookFeedback(text, mark) {
17
18
  return mintSystemReminder(text, mark);
18
19
  }
@@ -34,6 +34,14 @@ export declare const RETIRED_TOOL_POLICY_KEYS: Readonly<Record<string, string>>;
34
34
  * refused by name (`config.deps_retired_key`); the station reads one write seat, so a retired one would leave every write
35
35
  * target unjudged by its rows. (The engine's own station builds its boundaries itself.) */
36
36
  export declare const RETIRED_SHELL_READ_BOUNDARY_KEYS: Readonly<Record<string, string>>;
37
+ /** The RETIRED members of a permission DECISION (`PermissionResult`, a PreToolUse hook's result), each with the sentence that
38
+ * names its replacement — the table the decision receipt reads ({@link import("./tool-policy.js").refuseOutOfContractDecision}).
39
+ * That door does not use {@link retiredKeyOf}: a decision is caller-supplied data the receipt reads by descriptor without
40
+ * running a getter, so a name is retired when the decision carries it by the receipt's presence law (its own descriptor, or a
41
+ * non-absent one on its prototype chain; a data value of `undefined` is absence). A
42
+ * decision that carries one is denied in the engine's own words, never read without it: the type retires a member only where
43
+ * a decision is a literal in an annotated position, and a `check` whose return type is inferred still compiles with it. */
44
+ export declare const RETIRED_DECISION_MEMBERS: Readonly<Record<string, string>>;
37
45
  /** The `ToolPolicy` door ({@link RETIRED_TOOL_POLICY_KEYS}); `undefined` is no policy. */
38
46
  export declare function assertNoRetiredToolPolicyKeys(policy: object | undefined): void;
39
47
  /** The `ShellReadBoundary` door ({@link RETIRED_SHELL_READ_BOUNDARY_KEYS}). */
@@ -37,6 +37,10 @@ export const RETIRED_SHELL_READ_BOUNDARY_KEYS = Object.freeze({
37
37
  writeProtectionJudge: 'the station asks ONE write judgment, `ShellReadBoundary.writeTargetJudge`, over the write-protection table and the deployment\'s guarded write rows together — it answers `"protected"`, `"guarded"` or `undefined`, not a boolean',
38
38
  writeTargetGuard: 'the deployment\'s guarded write rows join the same one judgment, `ShellReadBoundary.writeTargetJudge` (`"guarded"` for a target a `ToolPolicy.writeGuardRows` row covers) — the boundary has no second write seat',
39
39
  });
40
+ export const RETIRED_DECISION_MEMBERS = Object.freeze({
41
+ reason: 'rename it to "message" (the one text field a decision carries)',
42
+ guard: 'write `guards: ["<word>"]` instead (the same five words, as a list: one ask may stand on several integrity guards)',
43
+ });
40
44
  export function assertNoRetiredToolPolicyKeys(policy) {
41
45
  if (policy !== undefined)
42
46
  assertNoRetiredKeys(policy, RETIRED_TOOL_POLICY_KEYS, "ToolPolicy");
@@ -69,10 +69,10 @@ export declare function readOrgRuleLane(org: {
69
69
  /**
70
70
  * The persisted-rule lane's one read for one call — every consumer's (the live gate, the synthetic-Read
71
71
  * judge), normalized once. A lane that throws, hangs past the dependency timeout, or answers something
72
- * unreadable is a lane that could not answer: for the DECISION its rows take no part (the tightening arms
73
- * do not fire — `applyPersistedTightening` reads `{ unreadable }` without a reason that way), and for
74
- * the RECORD it is `"unavailable"`. A call the lane could not read against the rows (`reason`) fails closed
75
- * in the arms. Absent lane ⇒ the clean negative.
72
+ * unreadable is a lane that could not answer: a row nobody could read may forbid the call, so for the DECISION it
73
+ * fails closed (`applyPersistedTightening` reads `{ unreadable }` without a reason as the store form: a real-approval
74
+ * ask), and for the RECORD it is `"unavailable"`. A call the lane could not read against the rows (`reason`) fails
75
+ * closed in the arms the same way, as the call form. Absent lane ⇒ the clean negative.
76
76
  */
77
77
  export declare function readPersistedRuleLane(lane: {
78
78
  adjudicate: (req: ToolCallRequest) => PersistedRuleAnswer | Promise<PersistedRuleAnswer>;
@@ -68,6 +68,14 @@ export function normalizeOrgGateVerdict(answer, unreadable) {
68
68
  const unreadableCell = behavior === "ask" && typeof reason === "string" ? { unreadable: reason } : {};
69
69
  return clean({ status: "available", verdict: clean({ behavior: behavior, rule, ...unreadableCell }), ...revisionCell });
70
70
  }
71
+ function copiedDenial(raw, ruleTexts) {
72
+ if (!isPlainOwnRecord(raw))
73
+ return undefined;
74
+ const rule = ownDataValue(raw, "rule");
75
+ const view = ownDataValue(raw, "view");
76
+ const face = ownDataValue(raw, "face");
77
+ return typeof rule === "string" && typeof view === "string" && view !== "" && ruleTexts.includes(rule) && (face === "read" || face === "write") ? { rule, view, face } : undefined;
78
+ }
71
79
  function copiedUsableDots(dots) {
72
80
  let copied;
73
81
  try {
@@ -133,7 +141,8 @@ export function normalizePersistedRuleHit(hit) {
133
141
  const behavior = ownDataValue(hit, "behavior");
134
142
  if (!isRuleBehavior(behavior))
135
143
  return {};
136
- return { hit: { behavior, rules: members.map((m) => ({ rule: m.rule, ...copiedUsableDots(m.dots) })) } };
144
+ const denial = behavior === "deny" ? copiedDenial(ownDataValue(hit, "denial"), members.map((m) => m.rule)) : undefined;
145
+ return { hit: { behavior, rules: members.map((m) => ({ rule: m.rule, ...copiedUsableDots(m.dots) })), ...(denial !== undefined ? { denial } : {}) } };
137
146
  }
138
147
  export async function readOrgRuleLane(org, req, signal, timeoutMs = ORG_ADJUDICATION_TIMEOUT_MS) {
139
148
  let pending;
@@ -1,5 +1,11 @@
1
1
  import type { RunnerDeps } from "../types.js";
2
- /** The retired-seat door ({@link assertNoRetiredRunnerDeps}), then the seats whose value is screened at construction and
3
- * never again: the declared configuration root must be the absolute path of an existing directory — a root that names no
4
- * directory guards nothing, so it is refused here rather than read as absent. */
5
- export declare function assertRunnerDepsDoors(deps: RunnerDeps): void;
2
+ /** The retired-seat door ({@link assertNoRetiredRunnerDeps}) looks at the caller's own object, where a retired name may sit on
3
+ * the prototype or as a non-enumerable property. Then the deps must be a PLAIN object (prototype `Object.prototype` or none):
4
+ * every seat is read once as an OWN property — enumerable or not, a getter invoked once — into a copy of enumerable data
5
+ * properties, the object the engine keeps and spreads from; the caller's getters are never read again, and a seat a prototype
6
+ * would supply is never read, so an object with another prototype is refused rather than wired without that seat. The seats
7
+ * whose VALUE is screened at construction and never again are judged on the copy: the declared configuration root must be the
8
+ * absolute path of an existing directory — a root that names no directory guards nothing, so it is refused rather than read as
9
+ * absent; the media budget must be a non-negative number of bytes — a bad value is refused rather than read as "no budget" by
10
+ * the request strip and the `tool_end.output` projection. */
11
+ export declare function retainRunnerDeps(deps: RunnerDeps): RunnerDeps;
@@ -1,6 +1,29 @@
1
1
  import { checkedAgentConfigDir } from "../agent-config-dir.js";
2
+ import { checkedMediaByteCap } from "../media-byte-cap.js";
2
3
  import { assertNoRetiredRunnerDeps } from "./retired-runner-deps.js";
3
- export function assertRunnerDepsDoors(deps) {
4
+ export function retainRunnerDeps(deps) {
4
5
  assertNoRetiredRunnerDeps(deps);
5
- checkedAgentConfigDir(deps.agentConfigDir, "RunnerDeps.agentConfigDir");
6
+ const refusal = (message) => {
7
+ const e = new Error(message);
8
+ e.code = "config.invalid_argument";
9
+ return e;
10
+ };
11
+ const proto = Object.getPrototypeOf(deps);
12
+ if (proto !== Object.prototype && proto !== null) {
13
+ throw refusal("RunnerDeps must be a plain object (its prototype is neither Object.prototype nor null): every seat is read once as an own property, and a seat its prototype would supply is not read — refused rather than wired without it");
14
+ }
15
+ const retained = Object.create(Object.prototype);
16
+ for (const name of Object.getOwnPropertyNames(deps)) {
17
+ let value;
18
+ try {
19
+ value = Reflect.get(deps, name);
20
+ }
21
+ catch {
22
+ throw refusal(`RunnerDeps.${name} threw while it was read at new Runner — a seat is read once, here; refused rather than wired without it`);
23
+ }
24
+ Object.defineProperty(retained, name, { value, enumerable: true, writable: true, configurable: true });
25
+ }
26
+ checkedAgentConfigDir(retained.agentConfigDir, "RunnerDeps.agentConfigDir");
27
+ checkedMediaByteCap(retained.mediaByteCapBytes, "RunnerDeps.mediaByteCapBytes");
28
+ return retained;
6
29
  }
@@ -450,6 +450,8 @@ export interface Prepared {
450
450
  };
451
451
  /** The deployment's caller policy, re-checked on a durable resume when the bytes to execute are not the bytes the gate saw. */
452
452
  basePolicyForResumeEdit?: ToolPolicy;
453
+ /** The WRITE face of the run's synthetic judge (the settle tail's background consolidation asks it): an `ask` is an objection. */
454
+ syntheticWriteVerdict: import("../synthetic-read-verdict.js").SyntheticWriteJudge;
453
455
  approvedCallScreen?: (call: {
454
456
  toolCallId: string;
455
457
  toolName: string;
@@ -538,6 +540,18 @@ export interface Prepared {
538
540
  * because nothing registers an interest and the receipt sink only records awaited keys.
539
541
  */
540
542
  frameSettlement: import("../frame-settlement.js").FrameSettlement;
543
+ /** The attachments the objective declared, as the door screened them (absent: none, or a resume leg — whose door
544
+ * refuses any). */
545
+ userAttachments?: readonly import("../user-attachment.js").ScreenedUserAttachment[];
546
+ /** The pending commits of this leg's attachment frames, settled by the transcript-append receipt sink — the one place
547
+ * an attachment's read effects are applied. Always present. */
548
+ attachmentDelivery: import("../user-attachment.js").AttachmentDelivery;
549
+ /** The leg's one file reader — the Read tool's own (one read state, one set of disclosure windows and counters),
550
+ * handed to the leg's other carriers of a file. Present iff hands are mounted. */
551
+ modelFileReader?: import("../../tools/fs/fs-read.js").ModelFileReader;
552
+ /** The run's synthetic-Read judge over every layer that can refuse a model's own `Read` (the changed-file notice reads
553
+ * it; so does the attachment read, as a face that puts contents in front of the model). */
554
+ syntheticReadVerdict: import("../synthetic-read-verdict.js").SyntheticReadJudge;
541
555
  /**
542
556
  * #1036 — the session-box drain's BOUNDARY PASS, called by the turn-boundary lane as its tail
543
557
  * (in a `finally`, so every early exit of the boundary still settles). Present iff this leg bound
@@ -1059,6 +1073,8 @@ export interface Prepared {
1059
1073
  * `NodeLspManager` — so "already delivered" has to be qualified by run). runtask passes it to every
1060
1074
  * `drain` and calls `releaseRun` with it at the run's terminal. */
1061
1075
  runIdent: string;
1076
+ /** The files of a drain the run's read face withholds (`lspListedWithheld`: every view, fail-closed; kept files absent). */
1077
+ withheldOf: (uris: readonly string[]) => Promise<ReadonlyMap<string, unknown>>;
1062
1078
  };
1063
1079
  /** The ctx `requestStopAfterTurn` primitive's run-local mirror (one-way, set when a tool asked for a
1064
1080
  * clean stop after the current turn). The harness latch itself is private to the loop; the turn
@@ -2585,7 +2601,9 @@ export interface RunState {
2585
2601
  * with — minus the conversation bits: `sessionId` comes from the checkpoint and `objective` is replaced by
2586
2602
  * an internally-generated continuation, so both are omitted.
2587
2603
  */
2588
- export type ResumeTaskConfig = Omit<TaskSpec, "objective" | "sessionId">;
2604
+ /** A resume continues the suspended leg: it sends no new human message, so it takes neither an objective nor the
2605
+ * objective's attachments (a config that carries `userAttachments` anyway is refused before the claim). */
2606
+ export type ResumeTaskConfig = Omit<TaskSpec, "objective" | "sessionId" | "userAttachments">;
2589
2607
  /** Resume plan threaded from {@link Runner.resume} into the shared run loop. */
2590
2608
  export interface ResumeRun {
2591
2609
  cp: Checkpoint;
@@ -2757,8 +2775,6 @@ export interface HarnessHandlersDeps {
2757
2775
  parentToolCallId?: string;
2758
2776
  sourceTaskId?: string;
2759
2777
  };
2760
- parentToolCallId: string | undefined;
2761
- subagentName: string | undefined;
2762
2778
  pushContent: (e: TaskEvent) => void;
2763
2779
  emitCommitted: (entryId: string, role: "user" | "assistant" | "toolResult", toolCallId?: string) => void;
2764
2780
  startedToolCallIds: Set<string>;
@@ -2776,6 +2792,7 @@ export interface HarnessHandlersDeps {
2776
2792
  onError: RunnerDeps["onError"];
2777
2793
  onNotice: RunnerDeps["onNotice"];
2778
2794
  };
2795
+ mediaByteCapBytes: RunnerDeps["mediaByteCapBytes"];
2779
2796
  /** The run's ONE isolation scope for the {@link RunInternals} observation sinks, minted by the
2780
2797
  * identity-wiring lane (which calls the first of them) and shared with these handlers, so the
2781
2798
  * "first failure per site" disclosure is one account for the whole run rather than one per lane. */
@@ -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", "fileIdentity"];
4
+ const OPTIONAL_METHOD_KEYS = ["writeFileExclusive", "writeFileGuarded", "setFileMode", "readLink", "canonicalChain", "fileIdentity", "openBound"];
5
5
  export function malformedCapabilityDeclarations(env) {
6
6
  const out = [];
7
7
  for (const key of OPTIONAL_METHOD_KEYS) {
@@ -3,6 +3,7 @@ import { askFaceOf, classifyAsk, headlessApprovalRemedy } from "../ask-origin.js
3
3
  import { wireOf } from "../tool-name-readings.js";
4
4
  import { isProtectedWrite } from "../tool-registry.js";
5
5
  import { attestationOf, decisionText } from "../tool-policy.js";
6
+ import { pathDenialCardOf } from "../../tools/fs/safety.js";
6
7
  import { boundInputHashOf } from "../canonical-json.js";
7
8
  import { askUnresolvableRemedySeat } from "../ask-unresolvable-notice.js";
8
9
  import { inlineUntrusted } from "../untrusted-text.js";
@@ -249,7 +250,7 @@ export async function exitGate(pass) {
249
250
  gate,
250
251
  finalInput,
251
252
  ...(() => {
252
- const details = pass.denyDetails ?? (modeRefusal !== undefined ? undefined : pass.shellReadDetails);
253
+ const details = pathDenialCardOf(attestationOf(pass.decision, { toolCallId, toolName })?.pathDenial) ?? pass.denyDetails ?? (modeRefusal !== undefined ? undefined : pass.shellReadDetails);
253
254
  return details !== undefined ? { details } : {};
254
255
  })(),
255
256
  preToolContext: pass.preToolContext,
@@ -15,7 +15,7 @@
15
15
  import type { Model } from "../../internal/llm.js";
16
16
  import type { Brain, ModelRoles, ModelRef, TaskSpec } from "../types.js";
17
17
  import { type DistillerChatFn } from "../memory-engine/distiller.js";
18
- import { type RunMemoryConsolidationOptions, type ConsolidationDriverEngine } from "../memory-engine/consolidation-driver.js";
18
+ import { type RunMemoryConsolidationOptions, type ConsolidationDriverEngine, type ConsolidationWriteJudge } from "../memory-engine/consolidation-driver.js";
19
19
  import { type MemoryConsolidationOptions } from "../memory-engine/consolidation.js";
20
20
  import type { LlmDistillerContract } from "../memory-engine/distiller.js";
21
21
  import type { EngineNotice } from "../engine-notice.js";
@@ -44,10 +44,10 @@ export interface MemoryConsolidationDriverDeps {
44
44
  * Resolve a deployment's driver options: `{ chat, model }` from the explicit seat or the role
45
45
  * chain, plus the deployment knobs (contract / maxCycles / mintRepairBudget) and the protocol
46
46
  * parameters (`memoryConsolidation`) threaded through verbatim. The result feeds
47
- * `runMemoryConsolidationDriver(engine, scope, resolved)` directly; every bad value refuses
48
- * coded here or in the verb's own screen, never folds to a default.
47
+ * `runMemoryConsolidationDriver(engine, scope, { ...resolved, writeJudge })` (the write judge is the caller's seat,
48
+ * never resolved from deps); every bad value refuses coded here or in the verb's own screen, never folds to a default.
49
49
  */
50
- export declare function resolveMemoryConsolidationDriver(deps: MemoryConsolidationDriverDeps): RunMemoryConsolidationOptions;
50
+ export declare function resolveMemoryConsolidationDriver(deps: MemoryConsolidationDriverDeps): Omit<RunMemoryConsolidationOptions, "writeJudge">;
51
51
  /**
52
52
  * "Is there ANY model seat to resolve a consolidation run from?" (the question the
53
53
  * `autoRunOnRecommendation` construction door asks). It mirrors {@link resolveMemoryConsolidationDriver}'s
@@ -65,12 +65,14 @@ export declare function consolidationSeatResolvable(deps: MemoryConsolidationDri
65
65
  * failure table settles the run (zero-write on mint failure, `_incomplete` on non-convergence), this
66
66
  * bridge only relays the run's advisory notices to `onNotice` and routes a seat/engine fault to
67
67
  * `onError(phase:"memory")`. Adds no failure semantics of its own — the trigger is the only new thing.
68
+ * The write judge is the LEG's; its refusal is a person's standing rule at work, so it is a refusal NOTICE, never a fault.
68
69
  */
69
70
  export declare function autoRunConsolidationFromRecommendation(args: {
70
71
  deps: MemoryConsolidationDriverDeps;
71
72
  engine: ConsolidationDriverEngine;
72
73
  scope: string;
73
74
  sessionId: string;
75
+ writeJudge: ConsolidationWriteJudge;
74
76
  onNotice?: (notice: EngineNotice) => void;
75
77
  onError?: (err: unknown, ctx: {
76
78
  phase: "memory";
@@ -3,6 +3,7 @@ import { expandTiers, roleModelIfSet } from "../roles.js";
3
3
  import { isAliasModelId, openAiCompatChatSeat } from "../memory-engine/distiller.js";
4
4
  import { runMemoryConsolidationDriver } from "../memory-engine/consolidation-driver.js";
5
5
  import { recommendationUnconsumed } from "../memory-engine/consolidation.js";
6
+ import { deliverWriteRefusal } from "./memory-consolidation-write-judge.js";
6
7
  import { asThrownError } from "../thrown-value.js";
7
8
  function seatRefusal(message) {
8
9
  const e = new Error(message);
@@ -68,12 +69,14 @@ export function consolidationSeatResolvable(deps) {
68
69
  return (roleModelIfSet("consolidate", {}, { models, roles: deps.roles }) ?? roleModelIfSet("summarize", {}, { models, roles: deps.roles })) !== undefined;
69
70
  }
70
71
  export function autoRunConsolidationFromRecommendation(args) {
71
- const { deps, engine, scope, sessionId, onNotice, onError } = args;
72
+ const { deps, engine, scope, sessionId, writeJudge, onNotice, onError } = args;
72
73
  return (async () => {
73
74
  try {
74
- await runMemoryConsolidationDriver(engine, scope, { ...resolveMemoryConsolidationDriver(deps), onNotice });
75
+ await runMemoryConsolidationDriver(engine, scope, { ...resolveMemoryConsolidationDriver(deps), writeJudge, onNotice });
75
76
  }
76
77
  catch (err) {
78
+ if (deliverWriteRefusal(err, scope, onNotice))
79
+ return;
77
80
  try {
78
81
  onError?.(asThrownError(err), { phase: "memory", sessionId });
79
82
  }
@@ -0,0 +1,17 @@
1
+ /**
2
+ * The Runner's half of the consolidation driver's write judge: the leg's synthetic write judge as the seat
3
+ * `runMemoryConsolidationDriver` asks (for the library directory before it claims anything, then for each file a commit
4
+ * lands), and the refusal's notice dialect for the auto lane. The judgment itself is `createSyntheticWriteJudge` — the layers
5
+ * a model's own `Write` meets; nothing here judges.
6
+ */
7
+ import type { SyntheticWriteJudge } from "../synthetic-read-verdict.js";
8
+ import { type ConsolidationWriteJudge } from "../memory-engine/consolidation-driver.js";
9
+ import { type EngineNotice } from "../engine-notice.js";
10
+ /**
11
+ * The leg's write judge as the consolidation driver's seat: each target the run asks about is put to the leg's synthetic
12
+ * Write judge as one `Write` of that path, under a deadline of its own. The answer is the judge's, unchanged — nothing here
13
+ * decides, merges or defaults.
14
+ */
15
+ export declare function consolidationWriteJudgeOfLeg(judge: SyntheticWriteJudge): ConsolidationWriteJudge;
16
+ /** Deliver a write-judge refusal as the verb's refusal notice; false (nothing delivered) for any other error. */
17
+ export declare function deliverWriteRefusal(err: unknown, scope: string, onNotice: ((notice: EngineNotice) => void) | undefined): boolean;
@@ -0,0 +1,14 @@
1
+ import { ORG_ADJUDICATION_TIMEOUT_MS } from "../permission-rule-org.js";
2
+ import { CONSOLIDATION_WRITE_REFUSED_CODE } from "../memory-engine/consolidation-driver.js";
3
+ import { ConsolidationRefusedError } from "../memory-engine/consolidation.js";
4
+ import { memoryConsolidationRefusedNotice } from "../memory-engine/engine.js";
5
+ import { deliverEngineNotice } from "../engine-notice.js";
6
+ export function consolidationWriteJudgeOfLeg(judge) {
7
+ return (target) => judge({ file_path: target.path }, "memory-consolidation", { deadlineAt: Date.now() + ORG_ADJUDICATION_TIMEOUT_MS });
8
+ }
9
+ export function deliverWriteRefusal(err, scope, onNotice) {
10
+ if (!(err instanceof ConsolidationRefusedError) || err.code !== CONSOLIDATION_WRITE_REFUSED_CODE)
11
+ return false;
12
+ deliverEngineNotice(onNotice, memoryConsolidationRefusedNotice({ code: CONSOLIDATION_WRITE_REFUSED_CODE, scope, reason: err.message, occurrenceId: err.occurrenceId }));
13
+ return true;
14
+ }
@@ -4,20 +4,18 @@
4
4
  *
5
5
  * `prepareTask` used to assemble the personal lane inline (a store read + a hand-spliced session
6
6
  * overlay + two admission arms) and the org lane from a second seam. Both now read the ONE store query —
7
- * `PermissionRuleStore.effective({ sessionId })` — and this module is where that read is taken and how it
8
- * is shared between the two lanes of one adjudicated call.
7
+ * `PermissionRuleStore.effective({ sessionId })` — through the run's one reader of it (`person-path-rows.ts`).
9
8
  *
10
9
  * ## One read per adjudicated call
11
10
  *
12
11
  * The gate consults the org lane (`orgRules.adjudicate`) and then the personal lane
13
12
  * (`persistedRules.adjudicate`) on every decision, in that order (the personal lane's deny/ask rows must
14
- * be able to tighten an allow, so it can no longer wait for a surviving ask). The org lane's read is
15
- * HANDED to the personal lane by `toolCallId` (taken once, then forgotten), so a governed deployment pays
16
- * one store query — one org-source fetch — per call, exactly as it did with two seams. An entry the
17
- * personal lane never claims (a call on a tool it does not speak for) is evicted first-in-first-out past
18
- * a small bound; an evicted or never-handed entry simply costs the personal lane its own read. The
19
- * handoff is a cost device, not a semantic one: both lanes judge one snapshot, which is the honest shape
20
- * for one decision.
13
+ * be able to tighten an allow, so it can no longer wait for a surviving ask). Both take the call's ONE read,
14
+ * pinned to its `toolCallId` by whichever of the call's consumers asked first (the read station ahead of them
15
+ * in the fold, or the org lane): a governed deployment pays one store query — one org-source fetch — per call,
16
+ * and every consumer of the call judges one snapshot, which is the honest shape for one decision. A partition
17
+ * the read could not read takes the run's one failure law (the reader's): its deny and ask rows as last read,
18
+ * none of its allow rows.
21
19
  *
22
20
  * ## Where the lanes do NOT go
23
21
  *
@@ -33,7 +31,7 @@ import { type PathRuleBases } from "../permission-rule-model.js";
33
31
  import { type HeadLeads } from "../path-rule-binding.js";
34
32
  import type { ToolCallLike } from "../tool-registry.js";
35
33
  import { type OrgRuleResolution } from "../permission-rule-org.js";
36
- import { type EffectivePermissionRules, type PermissionRuleStoreProvider } from "../permission-rule-provider.js";
34
+ import { type EffectivePermissionRules, type PermissionRuleStoreProvider, type PersonRowsRead } from "../permission-rule-provider.js";
37
35
  /** The one SHELL tool the persisted-rule lane's COMMAND grammar speaks for (the rule-offer face mints for it and the name tools); the other grammars' tools are derived (`ruleToolGrammarOf`). */
38
36
  export declare const PERSISTED_RULE_TOOL = "Bash";
39
37
  /**
@@ -119,9 +117,9 @@ export interface PermissionRuleLanes {
119
117
  } | undefined;
120
118
  }
121
119
  /** The ORG lane: the org's verdict about one call, off the store view `read` supplies — the gate's `orgRules` face over the
122
- * per-call handoff ({@link createPermissionRuleLanes}), and {@link orgVerdictBeforeClaim} over a view of its own. */
120
+ * call's pinned read ({@link createPermissionRuleLanes}), and {@link orgVerdictBeforeClaim} over a view of its own. */
123
121
  export declare function orgRuleLane(cfg: {
124
- read: (toolCallId: string) => Promise<EffectivePermissionRules>;
122
+ read: (toolCallId: string) => Promise<Pick<EffectivePermissionRules, "org">>;
125
123
  questionToolMounted: boolean;
126
124
  questionToolName: string;
127
125
  onRevisionDefect: (message: string) => void;
@@ -172,17 +170,13 @@ export declare function createPermissionRuleLanes(cfg: {
172
170
  questionToolMounted: boolean;
173
171
  questionToolName: string;
174
172
  onRevisionDefect: (message: string) => void;
175
- /** Sink for the store's disclosure lines (a session partition that could not be read, rows it served
176
- * that were dropped) and for a durable read failure — each read's lines are delivered once, whichever
177
- * lane took the read. */
173
+ /** Sink for a judgment against the rows that threw. */
178
174
  onDisclosure: (message: string) => void;
179
- /** The person is told their store (or the named sources of it) was skipped for a call (the run's one notice,
180
- * `ruleStoreUnreadableNotice`). */
181
- onStoreUnreadable: (at: "call", sources?: EffectivePermissionRules["unreadableSources"]) => void;
182
- /** The person is told the store served rows that were left out (the run's one `permission.rule_row_dropped`). */
183
- onRowsDropped: (rows: EffectivePermissionRules["droppedRows"]) => void;
184
- /** The run's cut: a read it interrupts is abandoned quietly — a cancelled wait is no fact about the store. */
185
- signal?: AbortSignal;
175
+ /** The run's one reader of the person's rules: the call's ONE read, pinned, that the read station, the org lane and the
176
+ * personal lane all take (the lanes consume a reader; they do not make one). */
177
+ rows: {
178
+ pin(toolCallId: string): Promise<PersonRowsRead>;
179
+ };
186
180
  /** The allow layer's auto strip over a stored ALLOW row (true ⇒ out of this call's table BEFORE the coverage is chosen). */
187
181
  stripsAllow?: (rule: PersistedRule, req: ToolCallRequest) => boolean;
188
182
  }): PermissionRuleLanes;
@@ -1,6 +1,6 @@
1
1
  import { persistedRuleMandateOf, ruleClearanceOf } from "../ask-origin.js";
2
2
  import {} from "../hooks.js";
3
- import { adjudicatePersistedDomainRules, adjudicatePersistedNameRules, adjudicatePersistedPathRules, adjudicatePersistedRules, callPathFaceOf, domainRuleSubjectOf, faceAskSpeaksFor, laneToolNamesOf, nameRuleSubjectOf, ruleToolGrammarOf, segmentCoverageOf, suggestRuleForName, suggestRulesForCommand } from "../permission-rule-model.js";
3
+ import { adjudicatePersistedDomainRules, adjudicatePersistedNameRules, adjudicatePersistedPathRules, adjudicatePersistedRules, callPathFaceOf, domainRuleSubjectOf, faceRowsSpeakFor, laneToolNamesOf, nameRuleSubjectOf, ruleToolGrammarOf, segmentCoverageOf, suggestRuleForName, suggestRulesForCommand } from "../permission-rule-model.js";
4
4
  import { callTargetViewsOf, validatePath } from "../effective-path-target.js";
5
5
  import { bindPathRules, persistedRowAnchors, projectScopeIdentity } from "../path-rule-binding.js";
6
6
  import { heldBy, seatOf, wireOf } from "../tool-name-readings.js";
@@ -9,11 +9,9 @@ import { ASK_USER_QUESTION_TOOL_NAME } from "../ask-question.js";
9
9
  import { ownerStoreOf } from "../permission-rule-provider.js";
10
10
  import { thrownText } from "../thrown-value.js";
11
11
  import { strongestAnswer } from "../persisted-rule-arms.js";
12
- import { gateWaitFailureOf, withGateTimeout } from "../gate-bounded-await.js";
13
12
  import { ORG_ADJUDICATION_TIMEOUT_MS } from "../permission-rule-org.js";
14
13
  export const PERSISTED_RULE_TOOL = "Bash";
15
14
  const NO_LEADS = new Map();
16
- const HANDOFF_BOUND = 256;
17
15
  export function inheritedAskRuleEvidence(provider) {
18
16
  const org = provider?.partitions.org === true ? "not_adjudicated" : "not_wired";
19
17
  const personal = provider?.partitions.durable === true ? "not_adjudicated" : "not_wired";
@@ -32,7 +30,7 @@ export function orgRevisionEvidenceOf(resolution, onDefect) {
32
30
  export function persistedRuleHitOf(verdict) {
33
31
  return verdict === undefined
34
32
  ? undefined
35
- : { behavior: verdict.behavior, rules: verdict.rules.map((r) => ({ rule: r.rule, dots: r.adds.map((a) => ({ actor: a.dot.actor, counter: a.dot.counter })) })) };
33
+ : { behavior: verdict.behavior, rules: verdict.rules.map((r) => ({ rule: r.rule, dots: r.adds.map((a) => ({ actor: a.dot.actor, counter: a.dot.counter })) })), ...(verdict.denial !== undefined ? { denial: { ...verdict.denial } } : {}) };
36
34
  }
37
35
  export function persistedRuleAnswerOf(verdict) {
38
36
  if (verdict?.unreadable !== undefined)
@@ -96,57 +94,30 @@ export function createPermissionRuleLanes(cfg) {
96
94
  if (provider === undefined)
97
95
  return { personal: undefined, org: undefined };
98
96
  const anonymous = cfg.principal === undefined || cfg.principal === "";
99
- const store = ownerStoreOf(provider, cfg.principal, cfg.localOwnerDeclared);
100
- const handed = new Map();
101
- const read = () => store.effective({ sessionId: cfg.sessionId }).then((view) => {
102
- for (const line of view.disclosures)
103
- cfg.onDisclosure(line);
104
- return view;
105
- });
106
- const hand = (toolCallId) => {
107
- const p = read();
108
- handed.delete(toolCallId);
109
- handed.set(toolCallId, p);
110
- if (handed.size > HANDOFF_BOUND) {
111
- const oldest = handed.keys().next().value;
112
- if (oldest !== undefined)
113
- handed.delete(oldest);
114
- }
115
- p.catch(() => undefined);
116
- return p;
117
- };
118
- const claim = (toolCallId) => {
119
- const p = handed.get(toolCallId);
120
- if (p === undefined)
121
- return read();
122
- handed.delete(toolCallId);
123
- return p;
97
+ const reader = cfg.rows;
98
+ const orgViewOf = async (toolCallId) => {
99
+ const read = await reader.pin(toolCallId);
100
+ if (read.org === undefined)
101
+ throw new Error(read.unreadable ?? "the store did not answer");
102
+ return { org: read.org };
124
103
  };
125
- const org = provider.partitions.org ? orgRuleLane({ read: hand, questionToolMounted: cfg.questionToolMounted, questionToolName: cfg.questionToolName, onRevisionDefect: cfg.onRevisionDefect }) : undefined;
104
+ const org = provider.partitions.org ? orgRuleLane({ read: orgViewOf, questionToolMounted: cfg.questionToolMounted, questionToolName: cfg.questionToolName, onRevisionDefect: cfg.onRevisionDefect }) : undefined;
126
105
  const personalAdjudicate = async (req) => {
127
106
  if (anonymous && !cfg.localOwnerDeclared)
128
107
  return undefined;
129
108
  const judged = laneToolNamesOf(req).map((n) => [n, ruleToolGrammarOf(n)]);
130
- if (!judged.some(([, g]) => g === "path") && faceAskSpeaksFor(callPathFaceOf(req)))
109
+ if (!judged.some(([, g]) => g === "path") && faceRowsSpeakFor(callPathFaceOf(req)))
131
110
  judged.push([req.toolName, "path"]);
132
111
  if (judged.length === 0)
133
112
  return undefined;
134
- let view;
135
- try {
136
- view = await withGateTimeout(claim(req.toolCallId), ORG_ADJUDICATION_TIMEOUT_MS, cfg.signal, "persistedRules");
137
- }
138
- catch (err) {
139
- return gateWaitFailureOf(err) === "aborted" ? { unreadable: true } : skipped(thrownText(err));
140
- }
141
- if (view.unreadable !== undefined)
142
- skipped(view.unreadable, view.unreadableSources);
143
- cfg.onRowsDropped(view.droppedRows);
113
+ const view = await reader.pin(req.toolCallId);
144
114
  try {
145
115
  const table = view.rules.filter((r) => r.behavior !== "allow" || cfg.stripsAllow?.(r, req) !== true);
146
116
  const leads = judged.some(([, g]) => g === "path") ? await cfg.leads.resolve(bindPathRules(table.filter((r) => r.match === "path" || r.match === "subpath").map((r) => ({ parsed: r, ruleText: r.rule, rule: r })), (spec) => anchorsOf(spec.rule))) : NO_LEADS;
147
117
  const scopeIdentity = await projectScopeIdentity(table, cfg.rootCanonical, cfg.leads);
148
118
  const answer = strongestAnswer(judged.map(([n, grammar]) => judgeAgainst(table, { ...req, toolName: n }, grammar, leads, scopeIdentity)));
149
- return view.unreadable !== undefined && (answer === undefined || "segmentCoverage" in answer) ? { unreadable: true } : answer;
119
+ const stands = answer !== undefined && (("behavior" in answer && answer.behavior === "deny") || "unreadable" in answer);
120
+ return view.unreadable !== undefined && !stands ? { unreadable: true } : answer;
150
121
  }
151
122
  catch (err) {
152
123
  const why = `judging this call against the rows threw: ${thrownText(err)}`;
@@ -174,7 +145,6 @@ export function createPermissionRuleLanes(cfg) {
174
145
  const coverage = segmentCoverageOf(command, { persisted: table }, { tool: req.toolName, cwd: cfg.root, sessionId: cfg.sessionId, ...execCwd, ...scope });
175
146
  return coverage !== undefined ? { segmentCoverage: coverage } : undefined;
176
147
  };
177
- const skipped = (why, sources) => (cfg.onDisclosure(why), cfg.onStoreUnreadable("call", sources), { unreadable: true });
178
148
  const personal = provider.partitions.durable ? { adjudicate: personalAdjudicate } : undefined;
179
149
  return { personal, org };
180
150
  }