@sema-agent/core 7.49.5 → 7.50.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (193) hide show
  1. package/CHANGELOG.md +138 -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 +19 -13
  21. package/dist/core/gate-lanes.js +12 -11
  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/prepare-acquire-reconcile.js +8 -1
  81. package/dist/core/runner/prepare-artifact.d.ts +5 -3
  82. package/dist/core/runner/prepare-artifact.js +2 -8
  83. package/dist/core/runner/prepare-caps-and-workflow.d.ts +4 -3
  84. package/dist/core/runner/prepare-caps-and-workflow.js +1 -1
  85. package/dist/core/runner/prepare-context-lane.js +2 -2
  86. package/dist/core/runner/prepare-gate-stations.d.ts +3 -3
  87. package/dist/core/runner/prepare-gate-stations.js +1 -1
  88. package/dist/core/runner/prepare-hands-readface.d.ts +19 -10
  89. package/dist/core/runner/prepare-hands-readface.js +57 -43
  90. package/dist/core/runner/prepare-lsp.d.ts +15 -13
  91. package/dist/core/runner/prepare-lsp.js +11 -11
  92. package/dist/core/runner/prepare-policy-chain.d.ts +8 -4
  93. package/dist/core/runner/prepare-policy-chain.js +6 -7
  94. package/dist/core/runner/prepare-run-refs.js +1 -1
  95. package/dist/core/runner/prepare-task.js +24 -16
  96. package/dist/core/runner/prepare-turn-wiring.d.ts +13 -6
  97. package/dist/core/runner/prepare-turn-wiring.js +19 -8
  98. package/dist/core/runner/read-root-grant.d.ts +1 -25
  99. package/dist/core/runner/read-root-grant.js +1 -17
  100. package/dist/core/runner/retired-runner-deps.d.ts +2 -2
  101. package/dist/core/runner/run-brain-sinks.d.ts +1 -5
  102. package/dist/core/runner/run-brain-sinks.js +2 -4
  103. package/dist/core/runner/run-harness-handlers.js +5 -4
  104. package/dist/core/runner/run-identity-wiring.d.ts +4 -4
  105. package/dist/core/runner/run-identity-wiring.js +10 -1
  106. package/dist/core/runner/run-leg.d.ts +1 -1
  107. package/dist/core/runner/run-leg.js +22 -9
  108. package/dist/core/runner/run-settle-and-teardown.d.ts +0 -4
  109. package/dist/core/runner/run-settle-and-teardown.js +7 -4
  110. package/dist/core/runner/run-terminal-adoption.d.ts +1 -1
  111. package/dist/core/runner/run-terminal-adoption.js +1 -1
  112. package/dist/core/runner/run-turn-boundary.js +2 -2
  113. package/dist/core/runner/runtask.js +8 -8
  114. package/dist/core/runner/session-file-state-replay.d.ts +14 -4
  115. package/dist/core/runner/session-file-state-replay.js +8 -0
  116. package/dist/core/runner/stream-steer-verb.js +3 -0
  117. package/dist/core/runner/tool-end-body.d.ts +5 -4
  118. package/dist/core/runner/tool-end-body.js +4 -4
  119. package/dist/core/runner/tool-output-projection.d.ts +6 -4
  120. package/dist/core/runner/tool-output-projection.js +66 -47
  121. package/dist/core/runner-deps.d.ts +6 -3
  122. package/dist/core/shared-memory/types.d.ts +2 -2
  123. package/dist/core/shell-read-silence.d.ts +23 -0
  124. package/dist/core/shell-read-silence.js +18 -0
  125. package/dist/core/synthetic-read-verdict.d.ts +80 -21
  126. package/dist/core/synthetic-read-verdict.js +89 -18
  127. package/dist/core/task-event.d.ts +113 -25
  128. package/dist/core/task-spec.d.ts +44 -0
  129. package/dist/core/task-tool-shape.d.ts +1 -1
  130. package/dist/core/tool-catalog-entries.js +16 -2
  131. package/dist/core/tool-policy.d.ts +25 -15
  132. package/dist/core/tool-policy.js +9 -13
  133. package/dist/core/trace.d.ts +3 -2
  134. package/dist/core/types.d.ts +3 -3
  135. package/dist/core/types.js +1 -1
  136. package/dist/core/untrusted-text.d.ts +2 -2
  137. package/dist/core/user-attachment.d.ts +164 -0
  138. package/dist/core/user-attachment.js +437 -0
  139. package/dist/engine/compaction/compaction.js +10 -3
  140. package/dist/engine/compaction/utils.js +6 -0
  141. package/dist/engine/execution-env/node-execution-env.d.ts +19 -1
  142. package/dist/engine/execution-env/node-execution-env.js +125 -0
  143. package/dist/engine/harness/agent-harness.d.ts +12 -2
  144. package/dist/engine/harness/agent-harness.js +15 -3
  145. package/dist/engine/harness/types.d.ts +21 -0
  146. package/dist/engine/llm/content-block-render.d.ts +15 -0
  147. package/dist/engine/llm/content-block-render.js +10 -0
  148. package/dist/engine/llm/index.d.ts +1 -0
  149. package/dist/engine/llm/index.js +1 -0
  150. package/dist/engine/llm/types.d.ts +80 -4
  151. package/dist/engine/llm/types.js +43 -1
  152. package/dist/engine/session/import-validate.js +2 -0
  153. package/dist/index.d.ts +6 -4
  154. package/dist/index.js +2 -1
  155. package/dist/internal/harness-types.d.ts +1 -1
  156. package/dist/internal/llm.d.ts +2 -2
  157. package/dist/internal/llm.js +1 -1
  158. package/dist/orchestration/workflow.d.ts +4 -5
  159. package/dist/orchestration/workflow.js +2 -2
  160. package/dist/prompts/default.d.ts +1 -1
  161. package/dist/tools/artifact/artifact-tool.js +1 -1
  162. package/dist/tools/fs/bash-readonly-classifier.d.ts +1 -1
  163. package/dist/tools/fs/dir-entries.d.ts +4 -10
  164. package/dist/tools/fs/dir-entries.js +4 -11
  165. package/dist/tools/fs/file-read-check.js +3 -3
  166. package/dist/tools/fs/fs-pdf.js +2 -4
  167. package/dist/tools/fs/fs-read.d.ts +47 -4
  168. package/dist/tools/fs/fs-read.js +63 -19
  169. package/dist/tools/fs/fs-search-tools.d.ts +3 -3
  170. package/dist/tools/fs/fs-search-tools.js +7 -2
  171. package/dist/tools/fs/fs-write.d.ts +1 -1
  172. package/dist/tools/fs/fs-write.js +7 -6
  173. package/dist/tools/fs/index.d.ts +7 -1
  174. package/dist/tools/fs/index.js +56 -34
  175. package/dist/tools/fs/read-deny-home.d.ts +19 -1
  176. package/dist/tools/fs/read-deny-home.js +32 -22
  177. package/dist/tools/fs/read-deny.d.ts +14 -0
  178. package/dist/tools/fs/read-deny.js +8 -0
  179. package/dist/tools/fs/repo-map.d.ts +3 -2
  180. package/dist/tools/fs/repo-map.js +11 -3
  181. package/dist/tools/fs/safety.d.ts +41 -2
  182. package/dist/tools/fs/safety.js +12 -3
  183. package/dist/tools/fs/shell-read-check.d.ts +12 -3
  184. package/dist/tools/fs/shell-read-check.js +28 -15
  185. package/dist/tools/fs/shell-read-device.d.ts +1 -1
  186. package/dist/tools/fs/shell-read-judge.d.ts +9 -4
  187. package/dist/tools/fs/shell-read-judge.js +1 -1
  188. package/dist/tools/loop-tick.d.ts +2 -2
  189. package/package.json +3 -1
  190. package/test/export-surface.snapshot.json +39 -1
  191. package/wire-changes/core@7.50.0.json +1699 -0
  192. package/dist/core/runner/persisted-path-rows.d.ts +0 -88
  193. package/dist/core/runner/persisted-path-rows.js +0 -108
@@ -20,7 +20,8 @@ import { RosterBuilder } from "../tool-roster.js";
20
20
  import type { AgentTool, RunnerDeps } from "../types.js";
21
21
  import type { ContainmentFace } from "../../tools/fs/index.js";
22
22
  import type { PlacedReadDenyMatcher } from "../../tools/fs/read-deny-home.js";
23
- import { type ArtifactStoreRef } from "./read-root-grant.js";
23
+ import type { ArtifactStoreRef } from "./read-root-grant.js";
24
+ import type { ReadRootsSeat } from "./person-path-rows.js";
24
25
  import type { Prepared } from "./contracts.js";
25
26
  import type { ToolOccurrenceTable } from "./tool-occurrence.js";
26
27
  export interface PrepareArtifactInput {
@@ -48,8 +49,9 @@ export interface PrepareArtifactInput {
48
49
  /** borrowed-readonly — the canonical task root the fs fence contains reads to; undefined ⇒ no local-file publish. */
49
50
  rootCanonical: string | undefined;
50
51
  /** borrowed-readonly — the extra containment roots (write and read-only) the fence admits, read per call (the run's
51
- * live set: an in-flight read-root grant widens it). */
52
- additionalRootsCanonical: () => readonly string[];
52
+ * live set: an in-flight read-root grant widens it); `ready` settles once the run's newest tables are compiled — this
53
+ * reader is no call, and reads them only then. */
54
+ additionalRootsCanonical: (() => readonly string[]) & Pick<ReadRootsSeat, "ready">;
53
55
  /** borrowed-readonly — the read-deny matcher in force for the run, when any. */
54
56
  readDenyMatcher: PlacedReadDenyMatcher | undefined;
55
57
  /** borrowed-readonly — the resolved read face (`"open"` lifts root containment for reads). */
@@ -5,9 +5,7 @@ import { RosterBuilder } from "../tool-roster.js";
5
5
  import { defineTool } from "../tools.js";
6
6
  import { inlineUntrusted } from "../untrusted-text.js";
7
7
  import { physicalPathOfExisting } from "../physical-path.js";
8
- import { resolveKey, violationDetails, violationText } from "../../tools/fs/safety.js";
9
- import { rootAdmittingArtifactStore, withdrawStandingGrantsAdmitting } from "./read-root-grant.js";
10
- import { emitTrace } from "../trace.js";
8
+ import { resolveKey, rootAdmittingArtifactStore, violationDetails, violationText } from "../../tools/fs/safety.js";
11
9
  import { createArtifactTool } from "../../tools/artifact/artifact-tool.js";
12
10
  import { thrownText } from "../thrown-value.js";
13
11
  export function prepareArtifact(input) {
@@ -30,11 +28,6 @@ export function prepareArtifact(input) {
30
28
  const storePhysical = storageDir !== undefined && rootCanonical !== undefined ? physicalPathOfExisting(storageDir) : undefined;
31
29
  if (artifactStoreRef !== undefined)
32
30
  artifactStoreRef.current = storePhysical;
33
- if (artifactStoreRef !== undefined && storePhysical !== undefined) {
34
- for (const entry of withdrawStandingGrantsAdmitting(storePhysical, artifactStoreRef)) {
35
- emitTrace(deps.tracer, () => ({ kind: "config.additional_directory_skipped", version: 1, taskId, entry, field: "persistedReadRule", reason: `admits_artifact_store: the local artifact store ${storageDir} lies inside it or it inside the store — the Artifact tool's ledger and stored files stay outside every root the file tools reach`, ts: Date.now() }));
36
- }
37
- }
38
31
  if (storePhysical !== undefined && rootCanonical !== undefined) {
39
32
  const overlapping = rootAdmittingArtifactStore(storePhysical, [rootCanonical, ...additionalRootsCanonical()]);
40
33
  if (overlapping !== undefined)
@@ -49,6 +42,7 @@ export function prepareArtifact(input) {
49
42
  const readLocalFile = async (path, maxBytes, signal) => {
50
43
  if (rootCanonical === undefined)
51
44
  return { ok: false, reason: "refused", text: "Error (Artifact): this task has no filesystem root, so a local file cannot be published." };
45
+ await additionalRootsCanonical.ready(signal);
52
46
  const rk = await resolveKey(executionEnv, rootCanonical, path, signal, handsCwdRef?.current, additionalRootsCanonical, undefined, readDenyMatcher, resolvedReadFace);
53
47
  if (!rk.ok)
54
48
  return { ok: false, reason: "refused", text: violationText("Artifact", rk.violation), details: violationDetails(rk.violation) };
@@ -114,8 +114,9 @@ export interface PrepareCapsAndWorkflowInput {
114
114
  * arming facts (the deny bits, the face, the re-entrant arm) are bound onto it here. */
115
115
  permissionMode: PermissionModeController;
116
116
  /** borrowed-readonly — LATE-BOUND: the effective read-deny table the hands phase compiles (the rows the classifier's deny
117
- * paragraph renders); read at ARMING, which the driver runs after that phase — never at this phase's own call. */
118
- readDenyEntries: () => {
117
+ * paragraph renders); read at ARMING, which the driver runs after that phase — never at this phase's own call — once the
118
+ * run's newest tables are compiled (an arming is no call). */
119
+ readDenyEntries: () => Promise<{
119
120
  readonly entries: ReadonlyArray<{
120
121
  readonly pattern: string;
121
122
  readonly caseSensitive?: boolean;
@@ -124,7 +125,7 @@ export interface PrepareCapsAndWorkflowInput {
124
125
  readonly tool: string;
125
126
  readonly pattern: string;
126
127
  }>;
127
- };
128
+ }>;
128
129
  /** borrowed-readonly — the peer lane's mount verdict (the classifier's cross-session rule fills only while mounted). */
129
130
  peerLaneActive: boolean;
130
131
  /** borrowed-readonly — the leg's effective delegation fact (`effectiveDelegationFacts`): the classifier window shows its user lane as the launching agent's. */
@@ -226,7 +226,7 @@ export async function prepareCapsAndWorkflow(input) {
226
226
  }
227
227
  announceClassifierRoleDefaulted({ onNotice: deps.onNotice, seat: { ...classifierSeat, model: classifierModel }, ...(sessionId !== undefined ? { sessionId } : {}), ...(runId !== undefined ? { runId } : {}) });
228
228
  const classifierLaneRule = peerLaneActive && peerSendMessageBuiltIn;
229
- const table = readDenyEntries();
229
+ const table = await readDenyEntries();
230
230
  const derivedDenyRules = readDenyClassifierRules(table.entries, table.ruleRows);
231
231
  let classifierSystemPromptMemo;
232
232
  const classifierSystemPrompt = () => (classifierSystemPromptMemo ??= buildAutoModePrompt({ ...am, derivedDenyRules, ...(classifierLaneRule ? { crossSessionMessagesRule: CROSS_SESSION_CLASSIFIER_RULE } : {}) }));
@@ -2,7 +2,7 @@ import { DEFAULT_CHARS_PER_TOKEN, estimateContextTokens } from "../../internal/h
2
2
  import { TOOL_TURN_REPAIR_DROPS } from "../../internal/llm.js";
3
3
  import { clearStaleToolResults, createClearedProjectionLedger, dropEmptyFailureAssistants, editBudget, replayClearedProjection, resolveTriggerWindow } from "../context-edit.js";
4
4
  import { guardBudget, insertTrimNotice, protectGitFrame, trimToBudget } from "../context-guard.js";
5
- import { AGGREGATE_MEDIA_BUDGET_BYTES, capAggregateMediaBytes } from "../media-byte-cap.js";
5
+ import { capAggregateMediaBytes, mediaBudgetBytes } from "../media-byte-cap.js";
6
6
  import { capAggregateToolResults } from "../tool-result-budget.js";
7
7
  import { createOffloadPersist } from "../tool-result-store.js";
8
8
  import { emitTrace } from "../trace.js";
@@ -50,7 +50,7 @@ export function prepareContextLane(input) {
50
50
  }))),
51
51
  });
52
52
  const mediaCapped = capAggregateMediaBytes(capped, {
53
- limitBytes: deps.mediaByteCapBytes ?? AGGREGATE_MEDIA_BUDGET_BYTES,
53
+ limitBytes: mediaBudgetBytes(deps.mediaByteCapBytes),
54
54
  onStripped: (info) => {
55
55
  const onMediaStripped = deps.onMediaStripped;
56
56
  if (onMediaStripped !== undefined)
@@ -155,9 +155,9 @@ export interface PrepareGateStationsInput {
155
155
  handsCwdRef: CwdRef | undefined;
156
156
  /** borrowed-readonly — the leg's target-reading stamp, or undefined (hands-less): the gate stamps its request with it. */
157
157
  stampCallTarget: CallTargetStamp | undefined;
158
- /** borrowed-readonly — the leg's read-deny table in force, read at a release: the active built-in tiers and the rows
159
- * beside them (the deployment's and the task's patterns, the read-face rule rows). Empty ⇒ no table. */
160
- readDenyInForce: () => {
158
+ /** borrowed-readonly — the read-deny table the released call was judged with, read at its release: the active built-in
159
+ * tiers and the rows beside them (the deployment's and the task's patterns, the read-face rule rows). Empty ⇒ no table. */
160
+ readDenyInForce: (toolCallId: string) => {
161
161
  readonly tiers: readonly string[];
162
162
  readonly rows: readonly string[];
163
163
  };
@@ -50,7 +50,7 @@ export function prepareGateStations(input) {
50
50
  const gatePreToolUse = ownGatePreToolUse !== undefined ? (name, args, ctx) => ownGatePreToolUse(name, args, { ...ctx, permissionMode: permissionMode.observed }) : undefined;
51
51
  const releasesAnnounced = new Map();
52
52
  const onModeReleased = (info) => {
53
- const table = info.readUnestablished === true ? input.readDenyInForce() : undefined;
53
+ const table = info.readUnestablished === true ? input.readDenyInForce(info.toolCallId) : undefined;
54
54
  if (table !== undefined && table.tiers.length + table.rows.length > 0) {
55
55
  const named = [...(table.tiers.length > 0 ? [`built-in tiers ${table.tiers.join(", ")}`] : []), ...table.rows.slice(0, 8), ...(table.rows.length > 8 ? [`${table.rows.length - 8} more`] : [])].join("; ");
56
56
  deliverEngineNotice(deps.onNotice, {
@@ -1,6 +1,6 @@
1
1
  import { type BeforeWriteHook, type CwdRef, type PathRuleDenyRow, type ContainmentFace } from "../../tools/fs/index.js";
2
2
  import { type ReadFileState } from "../../tools/fs/safety.js";
3
- import { type PlacedReadDenyMatcher } from "../../tools/fs/read-deny-home.js";
3
+ import { type PathTablesFacade, type PersonPathRows, type ReadRootsSeat } from "./person-path-rows.js";
4
4
  import type { HeadLeads } from "../path-rule-binding.js";
5
5
  import { type ShellReadAdmission, type ShellReadCheck } from "../../tools/fs/shell-read-check.js";
6
6
  import type { ShellReadBoundary } from "../../tools/fs/shell-read-judge.js";
@@ -56,19 +56,17 @@ export interface PrepareHandsReadFaceInput {
56
56
  /** borrowed-readonly — the registry scope (`internals?.registryScope ?? spec.principal ??
57
57
  * "default"`, computed in the driver); the band's task-registry scope. */
58
58
  taskScope: string;
59
- /** borrowed-readonly — the deny tables' permission-rule rows: the face-keyed path deny rules of the leg's caller policy and
60
- * of every ancestor's, each bound where it was installed (`toolPolicyPathRules`), and the person's deny rows; joined to the
61
- * read face's matcher and to the shell station's write face. */
59
+ /** borrowed-readonly — the leg's own deny-table rows: the face-keyed path deny rules of the leg's caller policy and of every
60
+ * ancestor's, each bound where it was installed (`toolPolicyPathRules`); the person's join them per read ({@link personRows}). */
62
61
  denyRuleRows: readonly PathRuleDenyRow[];
62
+ /** borrowed-readonly — the run's one reader of the person's rules: the run's tables are compiled here over it. */
63
+ personRows: PersonPathRows;
63
64
  /** borrowed-readonly — the leg's one resolution of where rule heads lead (the rows' twins, the same the gate's lanes read). */
64
65
  headLeads: HeadLeads;
65
66
  /** borrowed-readonly — the ONE judgment a shell command's write targets meet for the mode's file-command placement: the
66
67
  * leg's write judgment over the write-protection table and the chain's guarded write rows (read per judgment — it is
67
68
  * compiled at the manifest phase, before any gate is mounted). Absent ⇒ none (a standalone phase drive). */
68
69
  writeTargetJudge?: () => ShellReadBoundary["writeTargetJudge"];
69
- /** borrowed-readonly — the directories of the person's persisted `Read(//dir/**)` allow rows: read directories of this
70
- * run, canonicalized and admitted exactly as `additionalReadDirectories` entries are. */
71
- persistedReadDirectories?: readonly string[];
72
70
  /** borrowed-readonly — the leg's mode seat, read LIVE by the write face ({@link writeFaceReader}): its mode's face
73
71
  * is the resolver's row 3′ on every write. */
74
72
  permissionMode: {
@@ -160,6 +158,11 @@ export interface PrepareHandsReadFaceInput {
160
158
  * band's Read/notebook/PDF trailer outlets bump keys on it at tool-execute time (the driver owns
161
159
  * the object and folds it into result stats). */
162
160
  reminderDisclosureCounts: import("../reminder-disclosure.js").ReminderDisclosureCounts;
161
+ /** borrowed-mutable — where the band hands out the leg's one file reader (the Read tool's), for the leg's other
162
+ * carriers of a file (attachment frames): written once at mount, left empty when hands do not mount. */
163
+ modelFileReaderSeat?: {
164
+ current?: import("../../tools/fs/fs-read.js").ModelFileReader;
165
+ };
163
166
  /** borrowed-readonly — the first-touch history hook prepare built (present iff the run records file history into a
164
167
  * wired store and mounts a real fs env); threaded to the write band's trackFileEdit seat. */
165
168
  trackFileEdit?: import("../types.js").TrackFileEditHook;
@@ -218,6 +221,8 @@ export interface PrepareHandsReadFaceResult {
218
221
  /** owned — the same station's pre-spawn ADMISSION, published for the P11 face that spawns a shell command
219
222
  * and mounts LATER than the band (`Monitor`): the SAME function the band's shell faces got. ⇔ a real shell. */
220
223
  shellReadAdmission: ShellReadAdmission | undefined;
224
+ /** owned — the file read station's admission (`FileReadStation.admitted`) the band's read tools got, for the read tools mounted later (LSP). */
225
+ readAdmission: ((toolCallId: string | undefined, canonicalKey: string) => boolean) | undefined;
221
226
  /** write-back (D-3 hold) — the run's RESOLVED read face where hands mount; undefined ⇔ no hands
222
227
  * (the hands-less value lives in `handsLessResolvedFace`). The driver assigns this onto its
223
228
  * outer `let` for the call-time closures (ctx enrichment / checkpoint mint / envFacts). */
@@ -258,9 +263,13 @@ export interface PrepareHandsReadFaceResult {
258
263
  /** owned — the canonical containment root, published so the working-file ATTACHMENT
259
264
  * reader applies the same containment the hands tools do. Undefined ⇔ hands-less. */
260
265
  attachmentRootCanonical: string | undefined;
261
- /** owned — the ONE compiled deny judge per prepare (built-ins ∪ deployment ∪
262
- * task additions), carrying the home views of its placement. Undefined ⇔ hands-less (its consumers only exist with hands). */
263
- readDenyMatcher: PlacedReadDenyMatcher | undefined;
266
+ /** owned — the run's read deny table ({@link pathTables}' facade: the run's latest, `forCall` a call's own). Undefined ⇔
267
+ * hands-less (its consumers only exist with hands). */
268
+ readDenyMatcher: PathTablesFacade["readDeny"] | undefined;
269
+ /** owned — the read face's root set per judgment: write roots, read roots with grants, the person's read directories. */
270
+ readRoots: ReadRootsSeat;
271
+ /** owned — the run's path tables ({@link PathTablesFacade}); undefined ⇔ hands-less. */
272
+ pathTables: PathTablesFacade | undefined;
264
273
  /** owned — the shell-seat PROVENANCE set: the shell-running tools whose tier the engine's shell seat (read
265
274
  * boundary + shape tier + shellGate floor) is the SOLE POSITIVE source of — no explicit deployment mark on that name.
266
275
  * The one seat every consumer reads (risk grading, the gate's tighten attribution, the persisted-rule mandate
@@ -2,17 +2,19 @@ import { emitTrace } from "../trace.js";
2
2
  import { StubExecutionEnv } from "../stub-env.js";
3
3
  import { hasBackgroundShell } from "../background-shell.js";
4
4
  import { defaultTaskRegistry } from "../task-registry.js";
5
- import { bashReversibilityProbe, activeReadDenyBuiltinTiers, readDenyBuiltinConfigOf, compileReadDeny, compileWriteDeny, rowsWhereTheyLead, readDenySeat, createHandsToolkit, deploymentFaceClampNotice, pdfModelCapabilitiesOf, inheritSessionReadFileState, backgroundOutputReadExemption, resolveContainmentFace, seedReadFileStateFromContext, seedReadFileStateFromTranscript, } from "../../tools/fs/index.js";
6
- import { resolveKey, sha256 } from "../../tools/fs/safety.js";
7
- import { placeReadDenyAtHome } from "../../tools/fs/read-deny-home.js";
5
+ import { bashReversibilityProbe, activeReadDenyBuiltinTiers, readDenyBuiltinConfigOf, compileReadDeny, createHandsToolkit, deploymentFaceClampNotice, pdfModelCapabilitiesOf, inheritSessionReadFileState, backgroundOutputReadExemption, resolveContainmentFace, seedReadFileStateFromContext, seedReadFileStateFromTranscript, } from "../../tools/fs/index.js";
6
+ import { resolveKey, rootAdmittingArtifactStore, sha256 } from "../../tools/fs/safety.js";
7
+ import { thrownText } from "../thrown-value.js";
8
+ import { carryReadFace } from "../../tools/fs/read-deny-home.js";
9
+ import { EMPTY_READ_ROOTS, afterCallTables, denySeatsOf, onPinnedRead, pathTablesCompile, readRootsOver } from "./person-path-rows.js";
8
10
  import { createShellOverflowSpoolFence } from "../../tools/fs/fs-shared.js";
9
11
  import { createShellReadStation } from "../../tools/fs/shell-read-check.js";
10
- import { composeReadChecks, createFileReadStation } from "../../tools/fs/file-read-check.js";
12
+ import { createFileReadStation } from "../../tools/fs/file-read-check.js";
11
13
  import { isShellFaceToolName, predictMountedToolNames, readTargetToolNames } from "../tool-registry.js";
12
14
  import { normalizeFileText } from "../../tools/fs/encoding.js";
13
15
  import { wholeFileRecordsFromTranscript } from "./session-file-state-replay.js";
14
16
  import { deliverEngineNotice } from "../types.js";
15
- import { readRootGrantOver, rootAdmittingArtifactStore } from "./read-root-grant.js";
17
+ import { readRootGrantOver } from "./read-root-grant.js";
16
18
  import { sessionSeatAnchorHolds } from "./session-read-file-states.js";
17
19
  import { rebaseWorkspacePath } from "./workspace-path.js";
18
20
  import { RosterBuilder } from "../tool-roster.js";
@@ -101,6 +103,7 @@ export function resolveHandsLessReadFace(input) {
101
103
  shellReadCheck: undefined,
102
104
  shellReadStationArmed: false,
103
105
  shellReadAdmission: undefined,
106
+ readAdmission: undefined,
104
107
  resolvedReadFace: undefined,
105
108
  resolvedWriteFace: undefined,
106
109
  readDenyAdditionsNormalized,
@@ -113,6 +116,8 @@ export function resolveHandsLessReadFace(input) {
113
116
  artifactStoreRef: undefined,
114
117
  attachmentRootCanonical: undefined,
115
118
  readDenyMatcher: undefined,
119
+ readRoots: EMPTY_READ_ROOTS,
120
+ pathTables: undefined,
116
121
  envHandToolNames: new Set(),
117
122
  shellGatedTools: new Set(),
118
123
  sealReadStateSeat: undefined,
@@ -148,6 +153,8 @@ export async function prepareHandsMount(input) {
148
153
  const artifactStoreRef = { current: undefined };
149
154
  let attachmentRootCanonical;
150
155
  let readDenyMatcher;
156
+ let pathTables;
157
+ let readRoots = EMPTY_READ_ROOTS;
151
158
  if (handsEnabled) {
152
159
  const rootCanonical = taskRootCanonical;
153
160
  attachmentRootCanonical = rootCanonical;
@@ -178,10 +185,6 @@ export async function prepareHandsMount(input) {
178
185
  };
179
186
  await canonicalizeExtraDirs(spec.additionalDirectories, "additionalDirectories", additionalRootsCanonical);
180
187
  await canonicalizeExtraDirs(spec.additionalReadDirectories, "additionalReadDirectories", additionalReadRootsCanonical);
181
- const standingCanonical = [];
182
- await canonicalizeExtraDirs(input.persistedReadDirectories, "persistedReadRule", standingCanonical);
183
- artifactStoreRef.standing = { keys: standingCanonical.filter((k) => !additionalReadRootsCanonical.includes(k) && !additionalRootsCanonical.includes(k)), readRoots: additionalReadRootsCanonical };
184
- additionalReadRootsCanonical.push(...standingCanonical);
185
188
  if (advertisedWritableDirs !== undefined) {
186
189
  if (spec.envFacts?.scratchpadDir)
187
190
  await advertisedWritableDirs.admit({ path: spec.envFacts.scratchpadDir, advertiser: "scratchpad", materializeMarker: ".sema-scratchpad" });
@@ -194,7 +197,7 @@ export async function prepareHandsMount(input) {
194
197
  readFileStateForCheckpoint = readFileState;
195
198
  seedContextFiles = async (files) => {
196
199
  for (const f of files) {
197
- const rk = await resolveKey(executionEnv, rootCanonical, f.path, undefined, undefined, [...additionalRootsCanonical, ...additionalReadRootsCanonical], undefined, undefined, resolvedReadFace);
200
+ const rk = await readRoots.ready().then(() => resolveKey(executionEnv, rootCanonical, f.path, undefined, undefined, readRoots(), undefined, undefined, resolvedReadFace));
198
201
  if (rk.ok)
199
202
  seedReadFileStateFromContext(readFileState, rk.key, f.content);
200
203
  }
@@ -220,9 +223,12 @@ export async function prepareHandsMount(input) {
220
223
  const seedReadFaceSection = resume?.seed.readFace;
221
224
  const readDenyAdditions = unionAddOnly("readDenyPatterns", deps.readDenyPatterns, spec.readDenyPatterns, seedReadFaceSection?.denyEntries);
222
225
  const readDenyBuiltinCfg = readDenyBuiltinConfigOf({ tiers: deps.readDenyBuiltinTiers, exclude: deps.readDenyBuiltinExclude });
223
- const denyRuleRows = rowsWhereTheyLead(input.denyRuleRows, await input.headLeads.resolve(input.denyRuleRows));
224
- const readDenyPlacement = await placeReadDenyAtHome(executionEnv, (homes, linked) => compileReadDeny(readDenyAdditions, "readDenyPatterns", readDenyBuiltinCfg, denyRuleRows, homes, linked), undefined, undefined, deps.agentConfigDir);
225
- readDenyMatcher = readDenyPlacement.matcher;
226
+ const canonicalize = async (dirs) => { const out = []; for (const d of dirs)
227
+ await canonicalizeExtraDirs([d], "persistedReadRule", out).catch((err) => deps.onError?.(new Error(`persistedReadRule entry skipped (its lookup threw): ${d}: ${thrownText(err)}`), { phase: "config", sessionId })); return out; };
228
+ const tables = (pathTables = await input.personRows.attachTables(pathTablesCompile({ env: executionEnv, additions: readDenyAdditions, builtin: readDenyBuiltinCfg, legRows: input.denyRuleRows, leads: input.headLeads, configRoot: deps.agentConfigDir, canonicalize })));
229
+ readDenyMatcher = tables.readDeny;
230
+ readRoots = readRootsOver({ own: () => [...additionalRootsCanonical, ...additionalReadRootsCanonical], tables, ready: input.personRows.tablesReady, store: () => artifactStoreRef.current,
231
+ onHeld: (entry, store) => emitTrace(deps.tracer, () => ({ kind: "config.additional_directory_skipped", version: 1, taskId: hostTaskId, entry, field: "persistedReadRule", reason: `admits_artifact_store: the local artifact store ${store} lies inside it or it inside the store — the Artifact tool's ledger and stored files stay outside every root the file tools reach`, ts: Date.now() })) });
226
232
  readDenyBuiltinTiersActive = activeReadDenyBuiltinTiers(readDenyBuiltinCfg);
227
233
  readDenyAdditionsNormalized = compileReadDeny(readDenyAdditions, "readDenyPatterns", { tiers: [] }).entries;
228
234
  let liveReadFace = resolveContainmentFace({
@@ -247,7 +253,7 @@ export async function prepareHandsMount(input) {
247
253
  seatLeafId = undefined;
248
254
  }
249
255
  const judgeKey = async (path) => {
250
- const rk = await resolveKey(executionEnv, rootCanonical, path, undefined, undefined, [...additionalRootsCanonical, ...additionalReadRootsCanonical], undefined, undefined, liveReadFace);
256
+ const rk = await readRoots.ready().then(() => resolveKey(executionEnv, rootCanonical, path, undefined, undefined, readRoots(), undefined, undefined, liveReadFace));
251
257
  return rk.ok ? rk.key : undefined;
252
258
  };
253
259
  if (resume === undefined && spec.sessionId !== undefined) {
@@ -262,8 +268,12 @@ export async function prepareHandsMount(input) {
262
268
  const prior = await session.buildContext().catch(() => undefined);
263
269
  for (const rec of wholeFileRecordsFromTranscript(prior?.messages ?? [])) {
264
270
  const rk = await judgeKey(rec.path);
265
- if (rk !== undefined)
271
+ if (rk === undefined)
272
+ continue;
273
+ if ("content" in rec)
266
274
  seedReadFileStateFromTranscript(readFileState, rk, rec.content, rec.at);
275
+ else
276
+ readFileState.set(rk, { ...rec.observation, lastReadAt: rec.at });
267
277
  }
268
278
  }
269
279
  }
@@ -290,30 +300,36 @@ export async function prepareHandsMount(input) {
290
300
  sessionSeats.reanchor(sessionId, readFileState, ownCommittedTailEntryId);
291
301
  }
292
302
  : undefined;
293
- const denySeat = readDenySeat(readDenyMatcher);
294
- const writeDeny = compileWriteDeny(denyRuleRows);
295
- const writeDenySeat = writeDeny.ruleRows.length > 0 ? { writeDenyMatch: (p) => writeDeny.matchPath(p)?.pattern ?? null } : {};
296
- const shellReadBoundary = () => ({
297
- roots: [rootCanonical, ...additionalRootsCanonical, ...additionalReadRootsCanonical],
298
- ...(handsCwdRef?.current !== undefined ? { cwd: handsCwdRef.current } : {}),
299
- ...(executionEnv.homeDir !== undefined ? { homeDir: executionEnv.homeDir } : {}),
300
- ...denySeat,
301
- ...writeDenySeat,
302
- writeRoots: [rootCanonical, ...additionalRootsCanonical],
303
- ...(resolvedWriteFace !== undefined ? { writeFace: resolvedWriteFace.inForce() } : {}),
304
- ...((judge) => (judge !== undefined ? { writeTargetJudge: judge } : {}))(input.writeTargetJudge?.()),
305
- ...(resolvedReadFace !== undefined ? { face: resolvedReadFace } : {}),
306
- });
303
+ const shellReadBoundary = (toolCallId) => {
304
+ const { read: denySeat, write: writeDenySeat } = denySeatsOf(tables.forCall(toolCallId));
305
+ return {
306
+ roots: [rootCanonical, ...readRoots.forCall(toolCallId)()],
307
+ ...(handsCwdRef?.current !== undefined ? { cwd: handsCwdRef.current } : {}),
308
+ ...(executionEnv.homeDir !== undefined ? { homeDir: executionEnv.homeDir } : {}),
309
+ ...denySeat,
310
+ ...writeDenySeat,
311
+ ...(input.personRows.unreadFor(toolCallId) ? { unreadPartitions: true } : {}),
312
+ writeRoots: [rootCanonical, ...additionalRootsCanonical],
313
+ ...(resolvedWriteFace !== undefined ? { writeFace: resolvedWriteFace.inForce() } : {}),
314
+ ...((judge) => (judge !== undefined ? { writeTargetJudge: judge } : {}))(input.writeTargetJudge?.()),
315
+ ...(resolvedReadFace !== undefined ? { face: resolvedReadFace } : {}),
316
+ };
317
+ };
307
318
  if (handsIncludeShell) {
308
319
  const spoolFence = handsReadOnly ? createShellOverflowSpoolFence(executionEnv) : undefined;
309
- const readonlyBoundary = () => spoolFence !== undefined ? { roots: [rootCanonical, ...additionalRootsCanonical, ...additionalReadRootsCanonical], cwd: rootCanonical, ...denySeat, ...writeDenySeat, alsoInside: spoolFence } : undefined;
320
+ const readonlyBoundary = (toolCallId) => {
321
+ if (spoolFence === undefined)
322
+ return undefined;
323
+ const { read: denySeat, write: writeDenySeat } = denySeatsOf(tables.forCall(toolCallId));
324
+ return { roots: [rootCanonical, ...readRoots.forCall(toolCallId)()], cwd: rootCanonical, ...denySeat, ...writeDenySeat, ...(input.personRows.unreadFor(toolCallId) ? { unreadPartitions: true } : {}), alsoInside: spoolFence };
325
+ };
310
326
  shellReadTools = new Set(shellFaceHere);
311
327
  shellReadStation = createShellReadStation({
312
328
  env: executionEnv,
313
329
  tools: shellReadTools,
314
330
  seatOf: () => (handsReadOnly ? "readonly" : "full_shell"),
315
- boundaryOf: () => readonlyBoundary() ?? shellReadBoundary(), rootsCanonical: true,
316
- homeViews: readDenyPlacement.matcher.homeViews,
331
+ boundaryOf: (_tool, _cwd, toolCallId) => readonlyBoundary(toolCallId) ?? shellReadBoundary(toolCallId), rootsCanonical: true,
332
+ homeViews: tables.readDeny.homeViews,
317
333
  onAdmissionRefused: (toolCallId) => void input.toolOccurrences.refuse(toolCallId, "read_boundary"),
318
334
  });
319
335
  }
@@ -322,8 +338,8 @@ export async function prepareHandsMount(input) {
322
338
  fileReadStation = createFileReadStation({
323
339
  env: executionEnv,
324
340
  rootCanonical,
325
- roots: () => [...additionalRootsCanonical, ...additionalReadRootsCanonical],
326
- ...(readDenyMatcher !== undefined ? { readDeny: readDenyMatcher } : {}),
341
+ roots: readRoots,
342
+ readDeny: tables.readDeny,
327
343
  ...(liveReadFace !== undefined ? { readFace: liveReadFace } : {}),
328
344
  ...("homeDir" in executionEnv && executionEnv.homeDir !== undefined ? { homeDir: executionEnv.homeDir } : {}),
329
345
  tools: readTargetToolNames(),
@@ -333,17 +349,12 @@ export async function prepareHandsMount(input) {
333
349
  }
334
350
  const band = createHandsToolkit(executionEnv, readFileState, rootCanonical, {
335
351
  ...(additionalRootsCanonical.length > 0 ? { additionalRoots: additionalRootsCanonical } : {}),
336
- additionalReadRoots: additionalReadRootsCanonical,
337
- ...(readDenyAdditions.length > 0 ? { readDenyPatterns: readDenyAdditions } : {}),
338
- ...(denyRuleRows.length > 0 ? { pathDenyRules: denyRuleRows } : {}),
339
- readDenyHomes: readDenyPlacement.homes,
340
- readDenyHomeLinks: readDenyPlacement.linked,
341
- ...(readDenyBuiltinCfg.tiers !== undefined ? { readDenyBuiltinTiers: readDenyBuiltinCfg.tiers } : {}),
342
- ...(readDenyBuiltinCfg.exclude !== undefined ? { readDenyBuiltinExclude: readDenyBuiltinCfg.exclude } : {}),
352
+ readDenyHomes: carryReadFace([], { readDeny: tables.readDeny, writeDeny: tables.writeDeny, readRoots }),
343
353
  readFace: liveReadFace,
344
354
  writeFaceLive: resolvedWriteFace.inForce,
345
355
  reminderMark: input.reminderMark,
346
356
  reminderDisclosureCounts: input.reminderDisclosureCounts,
357
+ ...(input.modelFileReaderSeat !== undefined ? { modelFileReaderSeat: input.modelFileReaderSeat } : {}),
347
358
  ...(input.trackFileEdit !== undefined ? { trackFileEdit: input.trackFileEdit } : {}),
348
359
  ...(input.onFileEdited !== undefined ? { onFileEdited: input.onFileEdited } : {}),
349
360
  includeShell: handsIncludeShell,
@@ -387,7 +398,7 @@ export async function prepareHandsMount(input) {
387
398
  deps.onError?.(new Error(`spec.tools ${shadowed.length > 1 ? "entries" : "entry"} ${names} collide${shadowed.length > 1 ? "" : "s"} with the built-in hands tool band — the built-in wins (registered later; the harness tool Map is last-write-wins) and the caller tool never dispatches this task. Rename the caller tool, or don't inject executionEnv if you intend to replace the band.`), { phase: "config", sessionId });
388
399
  }
389
400
  }
390
- roster.mountAll(band.map((t) => (envHandToolNames.add(t.name), firstPartyOffload(t))), { source: "builtin", mountedBy: "hands" });
401
+ roster.mountAll(band.map((t) => (envHandToolNames.add(t.name), firstPartyOffload(afterCallTables(input.personRows, t)))), { source: "builtin", mountedBy: "hands" });
391
402
  const shellFloor = effectiveShellGate;
392
403
  if (!(executionEnv instanceof StubExecutionEnv) && spec.handsReadOnly !== true) {
393
404
  const installShellSeat = (name) => {
@@ -413,9 +424,10 @@ export async function prepareHandsMount(input) {
413
424
  wsSnapshot,
414
425
  rebaseWsPath,
415
426
  backgroundTaskToolsActive,
416
- shellReadCheck: composeReadChecks([...(shellReadStation !== undefined ? [{ check: shellReadStation.check, tools: shellReadTools }] : []), ...(fileReadStation !== undefined ? [fileReadStation] : [])]),
427
+ shellReadCheck: onPinnedRead(input.personRows, [...(shellReadStation !== undefined ? [{ check: shellReadStation.check, tools: shellReadTools }] : []), ...(fileReadStation !== undefined ? [fileReadStation] : [])]),
417
428
  shellReadStationArmed: shellReadStation !== undefined,
418
429
  shellReadAdmission: shellReadStation?.admit,
430
+ readAdmission: fileReadStation?.admitted,
419
431
  resolvedReadFace,
420
432
  resolvedWriteFace,
421
433
  handsLessResolvedWriteFace,
@@ -428,6 +440,8 @@ export async function prepareHandsMount(input) {
428
440
  artifactStoreRef,
429
441
  attachmentRootCanonical,
430
442
  readDenyMatcher,
443
+ readRoots,
444
+ pathTables,
431
445
  envHandToolNames,
432
446
  shellGatedTools,
433
447
  sealReadStateSeat,
@@ -1,10 +1,11 @@
1
1
  /**
2
2
  * PrepareTask's LSP phase, verbatim from the driver: the manager fold (spec > deps), the
3
- * LSP tool mount over the per-task env with its git-ignore filter, the diagnostics lane behind its four
4
- * preconditions (a manager whose sessions feed a registry, write-capable hands, not opted out), the run-scoped
3
+ * LSP tool mount over the per-task env with its git-ignore filter and the leg's read face, the diagnostics lane behind its
4
+ * four preconditions (a manager whose sessions feed a registry, write-capable hands, not opted out), the run-scoped
5
5
  * registry key, and the edit nudge (raw path → the SAME resolution the tool leg uses → registry reset +
6
- * session didChange). No manager, or no hands (the manifest still reports the seat, `mounted: false`), ⇒ nothing mounts and
7
- * both lane seats are undefined. The interface is the dependency list the segment had implicitly.
6
+ * session didChange through the read face). No manager, or no read face (a hands-less leg; the manifest still reports the
7
+ * seat, `mounted: false`), ⇒ nothing mounts and both lane seats are undefined. The interface is the dependency list the
8
+ * segment had implicitly.
8
9
  *
9
10
  * SYNCHRONOUS FUNCTION, SYNCHRONOUS CALL: the stretch has no await, so the phase adds no yield between the
10
11
  * fork-governance note before it and the memory trio's occupancy read after it (a stretch
@@ -12,9 +13,9 @@
12
13
  * Read-stability of the host handles for the call: {@link RunInternals} (`@contract prepare.deps-read-stable`).
13
14
  */
14
15
  import type { ExecutionEnv } from "../../internal/harness.js";
16
+ import { type LspListedWithholding, type LspReadFace } from "../lsp.js";
15
17
  import type { LspDiagnosticsRegistry } from "../lsp-diagnostics.js";
16
18
  import type { RunnerDeps, TaskSpec } from "../types.js";
17
- import type { Prepared } from "./contracts.js";
18
19
  import { RosterBuilder } from "../tool-roster.js";
19
20
  export interface PrepareLspInput {
20
21
  /** borrowed-readonly — the REBOUND spec. Read: `lspManager` (the per-task manager, outranks the deployment's),
@@ -25,18 +26,16 @@ export interface PrepareLspInput {
25
26
  /** borrowed-readonly — the run's resolved env: the tool's in-env server and git-ignore filter, and the
26
27
  * session lookup the nudge performs. */
27
28
  executionEnv: ExecutionEnv;
28
- /** borrowed-readonly — the SETTLED task root: the tool's git-ignore root, and the nudge's resolution base when
29
- * no cwd is tracked. */
29
+ /** borrowed-readonly — the SETTLED task root: the tool's git-ignore root. */
30
30
  taskRootFinal: string;
31
- /** borrowed-readonly — whether a real fs env is present (the tool mount's and the lane's hands precondition). */
32
- handsEnabled: boolean;
31
+ /** borrowed-readonly — the leg's read face (the hands phase's root, read roots, deny table, containment face, tracked
32
+ * cwd and file read station admission), present exactly when hands mounted: LSP mounts only under it, and the tool,
33
+ * the nudge and the diagnostics lane read every file through it. */
34
+ readFace: LspReadFace | undefined;
33
35
  /** borrowed-readonly — the acquired session id: the run's key into the deployment-shared registry. */
34
36
  sessionId: string;
35
- /** borrowed-readonly — the tracked-cwd cell the hands band writes; the nudge READS `current` per call as its
36
- * resolution base (identity is the contract — the band's `cd` handling writes this exact cell). */
37
- handsCwdRef: Prepared["cwdRef"];
38
37
  /** borrowed-mutable — the run's roster builder (identity is the contract). Writer here: ONE mount (the LSP tool)
39
- * when a manager exists and the leg has hands. */
38
+ * when a manager and a read face exist. */
40
39
  roster: RosterBuilder;
41
40
  /** borrowed-mutable — the env-hand membership set the read-posture observer projects; writer here: `add("LSP")`
42
41
  * on the mount. */
@@ -48,6 +47,9 @@ export interface PrepareLspResult {
48
47
  lspDiagnostics: LspDiagnosticsRegistry | undefined;
49
48
  /** owned — the edit nudge (raw path in), or undefined when the lane is not live. */
50
49
  nudgeLspOnEdit: ((rawPath: string) => void) | undefined;
50
+ /** owned — the files of a drain the read face withholds (`lspListedWithheld`: every view, fail-closed; kept files
51
+ * absent), or undefined when the lane is not live. */
52
+ withheldDiagnosticsOf: ((uris: readonly string[]) => Promise<ReadonlyMap<string, LspListedWithholding>>) | undefined;
51
53
  /** borrowed-readonly — the run's registry key (= the session id): one run per session, released at the terminal. */
52
54
  lspRunIdent: string;
53
55
  /** Did a MANAGER seat resolve? The manifest's LSP-section presence condition, from the ONE fold that decided the mount. */ managerWired: boolean;
@@ -1,28 +1,28 @@
1
- import { createLspTool, gitCheckIgnoreFilter, resolveLspPath } from "../lsp.js";
1
+ import { createLspTool, gitCheckIgnoreFilter, lspBaseOf, lspListedWithheld, lspReadGateOf, resolveLspPath } from "../lsp.js";
2
2
  import { pathToUri } from "../lsp-protocol.js";
3
3
  import { RosterBuilder } from "../tool-roster.js";
4
4
  export function prepareLsp(input) {
5
- const { spec, deps, executionEnv, taskRootFinal, handsEnabled, sessionId, handsCwdRef, roster, envHandToolNames } = input;
5
+ const { spec, deps, executionEnv, taskRootFinal, readFace, sessionId, roster, envHandToolNames } = input;
6
6
  const lspManager = spec.lspManager ?? deps.lspManager;
7
- if (lspManager && handsEnabled) {
8
- const lspRoot = taskRootFinal;
9
- roster.mount(createLspTool(lspManager, { isPathIgnored: gitCheckIgnoreFilter(executionEnv, lspRoot), env: executionEnv }), { source: "builtin", mountedBy: "hands" });
7
+ if (lspManager && readFace) {
8
+ roster.mount(createLspTool(lspManager, { isPathIgnored: gitCheckIgnoreFilter(executionEnv, taskRootFinal), env: executionEnv, readFace }), { source: "builtin", mountedBy: "hands" });
10
9
  envHandToolNames.add("LSP");
11
10
  }
12
- const lspDiagnostics = spec.lspDiagnostics !== false && lspManager?.diagnostics !== undefined && handsEnabled && spec.handsReadOnly !== true
11
+ const lspDiagnostics = spec.lspDiagnostics !== false && lspManager?.diagnostics !== undefined && readFace !== undefined && spec.handsReadOnly !== true
13
12
  ? lspManager.diagnostics
14
13
  : undefined;
15
14
  const lspRunIdent = sessionId;
16
- const nudgeLspOnEdit = lspDiagnostics
15
+ const nudgeLspOnEdit = lspDiagnostics && readFace
17
16
  ? (rawPath) => {
18
- const baseDir = handsCwdRef?.current ?? taskRootFinal;
19
- const filePath = resolveLspPath(rawPath, baseDir);
17
+ const filePath = resolveLspPath(rawPath, lspBaseOf(readFace));
20
18
  lspDiagnostics.fileEdited(lspRunIdent, pathToUri(filePath));
19
+ const gate = lspReadGateOf(executionEnv, readFace);
21
20
  void lspManager
22
21
  .sessionFor(filePath, undefined, executionEnv)
23
- .then((session) => session?.notifyFileChanged?.(filePath))
22
+ .then((session) => session?.notifyFileChanged?.(filePath, undefined, gate))
24
23
  .catch(() => undefined);
25
24
  }
26
25
  : undefined;
27
- return { lspDiagnostics, nudgeLspOnEdit, lspRunIdent, managerWired: lspManager !== undefined };
26
+ const withheldDiagnosticsOf = lspDiagnostics && readFace ? (uris) => lspListedWithheld(executionEnv, readFace, uris) : undefined;
27
+ return { lspDiagnostics, nudgeLspOnEdit, withheldDiagnosticsOf, lspRunIdent, managerWired: lspManager !== undefined };
28
28
  }
@@ -104,10 +104,11 @@ export interface PreparePolicyChainInput {
104
104
  handsCwdRef: CwdRef | undefined;
105
105
  /** borrowed-readonly — the compiled read-deny judge, or undefined (the offer factory's directory negative control). */
106
106
  readDenyMatcher: ReadDenyMatcher | undefined;
107
- /** borrowed-readonly — the run's one notice for a person's rule store that could not be read (`ruleStoreUnreadableNotice`). */
108
- announceRuleStoreSkipped: (at: "prepare" | "call") => void;
109
- /** borrowed-readonly — the run's one notice for rows the person's store served that were left out (`ruleRowsDroppedNotice`). */
110
- announceRuleRowsDropped: (rows: import("../permission-rule-provider.js").EffectivePermissionRules["droppedRows"]) => void;
107
+ /** borrowed-readonly — the run's one reader of the person's rules (`person-path-rows.ts`): the lanes take each call's
108
+ * pinned read from it. */
109
+ personRows: import("./person-path-rows.js").PersonPathRows;
110
+ /** borrowed-readonly — a reader of the same rows the run's cut does not end (the same notices), for the lane asked after the run. */
111
+ personRowsBeyondRun: import("./person-path-rows.js").PersonPathRows;
111
112
  /** borrowed-readonly — whether the question tool mounted (the lanes' question-tool exclusion). */
112
113
  questionToolMounted: boolean;
113
114
  /** borrowed-readonly — the resolved per-principal entitlements; only `forceDurableGate` (the mandate-park facility and
@@ -240,6 +241,9 @@ export interface PreparePolicyChainResult {
240
241
  grantBindingOf: GrantBindingOf;
241
242
  /** owned — the personal permission-rule lane, or undefined. */
242
243
  permissionRuleLane: PermissionRuleLanes["personal"];
244
+ /** owned — the same two lanes (personal and org) over the reader the run's cut does not end, for the one judgment asked after the
245
+ * run has ended (the synthetic write face). */
246
+ ruleLanesBeyondRun: PermissionRuleLanes;
243
247
  /** owned — the org permission-rule lane (`Prepared.permissionRuleOrg`), or undefined. */
244
248
  permissionRuleOrgLane: PermissionRuleLanes["org"];
245
249
  /** owned — whether the sandbox-admission mode armed (declared isolation with its park prerequisites). */
@@ -26,7 +26,7 @@ import { createLiveSessionRules, createSessionRulePolicy } from "./session-rule-
26
26
  import { thrownText } from "../thrown-value.js";
27
27
  import { heldBy, heldValues, seatOf, wireOf } from "../tool-name-readings.js";
28
28
  export async function preparePolicyChain(input) {
29
- const { spec, deps, internals, sessionId, runId, hostTaskId, taskRootFinal, additionalRootsCanonical, executionEnv, lockedPreflight, pathRuleAnchors, headLeads, tools, harnessTools, toolEffects, egressTools, irreversibilityTier, axisExplicitNegatives, shellGatedTools, toolMarkSources, envHandToolNames, handsCwdRef, readDenyMatcher, announceRuleStoreSkipped, announceRuleRowsDropped, questionToolMounted, runtimeCaps, inheritedAncestorRules, ancestorSessionRulesRef, inheritedParentConstraints, seedInheritedGate, skillGrants, skillSpecs, allowedToolTables, frozenOnAsk, hookEnvSource, notifyOwnHookCrash, autoModeDecider, readFace, permissionMode, scratchpadDir, toolRosterDeltas, stopForDenialLimit, delegation, abortController, now, ownSessionRulesRef } = input;
29
+ const { spec, deps, internals, sessionId, runId, hostTaskId, taskRootFinal, additionalRootsCanonical, executionEnv, lockedPreflight, pathRuleAnchors, headLeads, tools, harnessTools, toolEffects, egressTools, irreversibilityTier, axisExplicitNegatives, shellGatedTools, toolMarkSources, envHandToolNames, handsCwdRef, readDenyMatcher, personRows, personRowsBeyondRun, questionToolMounted, runtimeCaps, inheritedAncestorRules, ancestorSessionRulesRef, inheritedParentConstraints, seedInheritedGate, skillGrants, skillSpecs, allowedToolTables, frozenOnAsk, hookEnvSource, notifyOwnHookCrash, autoModeDecider, readFace, permissionMode, scratchpadDir, toolRosterDeltas, stopForDenialLimit, delegation, abortController, now, ownSessionRulesRef } = input;
30
30
  const ancestorArbitrations = new Map();
31
31
  const arbitrateInherited = (ask, toolCallId, judged) => {
32
32
  const verdict = arbitrateByMode(permissionMode.current, judged.classification, { origin: judged.origin, ...askMandateMember(ancestorFacts(judged.ask.action === "ask" ? judged.ask : undefined)) });
@@ -321,7 +321,7 @@ export async function preparePolicyChain(input) {
321
321
  const listFacts = { root: taskRootFinal, sessionId, liveCwd: () => handsCwdRef?.current, anchors: pathRuleAnchors.leg };
322
322
  const skillGrantLane = skillGrants.lane(listFacts);
323
323
  const allowEntryLanes = { request: allowedToolTables.request.lane(listFacts), deployment: allowedToolTables.deployment.lane(listFacts) };
324
- const { personal: permissionRuleLane, org: permissionRuleOrgLane } = createPermissionRuleLanes({
324
+ const ruleLaneConfig = {
325
325
  provider: deps.permissionRuleStore,
326
326
  localOwnerDeclared: deps.localOwnerRules === true,
327
327
  principal: spec.principal,
@@ -336,11 +336,10 @@ export async function preparePolicyChain(input) {
336
336
  questionToolName: ASK_USER_QUESTION_TOOL_NAME,
337
337
  onRevisionDefect: (message) => deps.onError?.(new Error(message), { phase: "config", sessionId }),
338
338
  onDisclosure: (message) => emitTrace(deps.tracer, () => ({ kind: "permission.rule_store_unreadable", version: 1, taskId: hostTaskId, message, ts: Date.now() })),
339
- onStoreUnreadable: announceRuleStoreSkipped,
340
- onRowsDropped: announceRuleRowsDropped,
341
- signal: abortController.signal,
342
339
  stripsAllow: (rule, req) => permissionMode.current === "auto" && autoStrippedRule(rule, req),
343
- });
340
+ };
341
+ const { personal: permissionRuleLane, org: permissionRuleOrgLane } = createPermissionRuleLanes({ ...ruleLaneConfig, rows: personRows });
342
+ const ruleLanesBeyondRun = createPermissionRuleLanes({ ...ruleLaneConfig, rows: personRowsBeyondRun });
344
343
  const ruleOffersOf = createRuleOffersOf({
345
344
  laneArmed: permissionRuleLane !== undefined,
346
345
  principal: spec.principal,
@@ -678,7 +677,7 @@ export async function preparePolicyChain(input) {
678
677
  return decision;
679
678
  })
680
679
  : foldedPolicy;
681
- return { hooks, hookTimeoutMs, preToolUseObservational, ownGatePreToolUse, basePolicyForResumeEdit, denyNarrowingPolicy, skillGrantLane, allowEntryLanes, policyLayers, effectivePolicy, inheritedUnavailableAsks, inheritedAskGrants, askSourceIdentity, riskAxesOf, ruleOffersOf, grantBindingOf, permissionRuleLane, permissionRuleOrgLane, sandboxAdmissionArmed, sandboxBoundaryCapable, emitSandboxAdmitted, foldAskClasses, ancestorSandboxAdmissions, ancestorArbitrations };
680
+ return { hooks, hookTimeoutMs, preToolUseObservational, ownGatePreToolUse, basePolicyForResumeEdit, denyNarrowingPolicy, skillGrantLane, allowEntryLanes, policyLayers, effectivePolicy, inheritedUnavailableAsks, inheritedAskGrants, askSourceIdentity, riskAxesOf, ruleOffersOf, grantBindingOf, permissionRuleLane, ruleLanesBeyondRun, permissionRuleOrgLane, sandboxAdmissionArmed, sandboxBoundaryCapable, emitSandboxAdmitted, foldAskClasses, ancestorSandboxAdmissions, ancestorArbitrations };
682
681
  }
683
682
  const advisedPolicyNames = new WeakMap();
684
683
  const ADVISED_KEYS_CAP = 64;