@sema-agent/core 7.56.0 → 7.57.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 (173) hide show
  1. package/CHANGELOG.md +541 -0
  2. package/LICENSE +1 -1
  3. package/dist/agents/org-delivery-gate.js +3 -4
  4. package/dist/agents/peer-held-queue.d.ts +69 -49
  5. package/dist/agents/peer-held-queue.js +255 -106
  6. package/dist/agents/peer-session-drain.js +63 -43
  7. package/dist/agents/send-message-tool.js +3 -2
  8. package/dist/agents/subagent.js +8 -8
  9. package/dist/brain/anthropic.js +16 -8
  10. package/dist/brain/open-responses.js +10 -8
  11. package/dist/brain/openai.js +11 -9
  12. package/dist/brain/provider-dialect.d.ts +3 -2
  13. package/dist/brain/provider-dialect.js +2 -2
  14. package/dist/brain/request-params.d.ts +6 -0
  15. package/dist/brain/request-params.js +14 -1
  16. package/dist/brain/stream-engine.d.ts +9 -0
  17. package/dist/brain/stream-engine.js +24 -10
  18. package/dist/brain/stream-shared.d.ts +5 -4
  19. package/dist/brain/stream-shared.js +4 -4
  20. package/dist/core/allow-entry.d.ts +3 -10
  21. package/dist/core/allow-entry.js +17 -25
  22. package/dist/core/approval-object.d.ts +21 -5
  23. package/dist/core/approval-object.js +44 -17
  24. package/dist/core/ask-origin.d.ts +2 -2
  25. package/dist/core/auto-compaction.d.ts +71 -6
  26. package/dist/core/auto-compaction.js +42 -26
  27. package/dist/core/background-agent-store.d.ts +6 -2
  28. package/dist/core/background-agent-store.js +1 -1
  29. package/dist/core/checkpoint-requirements.d.ts +7 -1
  30. package/dist/core/checkpoint-requirements.js +2 -0
  31. package/dist/core/checkpoint-store.d.ts +17 -29
  32. package/dist/core/checkpoint-store.js +19 -11
  33. package/dist/core/cross-boundary-taint.js +4 -2
  34. package/dist/core/engine-notice.d.ts +3 -2
  35. package/dist/core/engine-notice.js +6 -0
  36. package/dist/core/file-history-store.d.ts +14 -8
  37. package/dist/core/file-history-store.js +14 -4
  38. package/dist/core/gate-lanes.js +2 -3
  39. package/dist/core/hooks.d.ts +2 -2
  40. package/dist/core/mailbox-store.d.ts +62 -1
  41. package/dist/core/mailbox-store.js +59 -0
  42. package/dist/core/mcp-name-judge.d.ts +46 -0
  43. package/dist/core/mcp-name-judge.js +61 -0
  44. package/dist/core/mcp-probe.d.ts +1 -1
  45. package/dist/core/mcp-probe.js +2 -5
  46. package/dist/core/mcp.js +10 -30
  47. package/dist/core/memory-engine/consolidation-driver.js +4 -3
  48. package/dist/core/memory-engine/distiller.js +26 -17
  49. package/dist/core/memory-engine/layout.js +5 -2
  50. package/dist/core/park-selfcheck.js +2 -0
  51. package/dist/core/permission-rule-consent.d.ts +5 -3
  52. package/dist/core/permission-rule-consent.js +20 -15
  53. package/dist/core/permission-rule-model.d.ts +43 -2
  54. package/dist/core/permission-rule-model.js +52 -8
  55. package/dist/core/permission-rule-org.js +1 -1
  56. package/dist/core/protocol-http-auth.d.ts +16 -2
  57. package/dist/core/protocol-http-auth.js +42 -11
  58. package/dist/core/protocol-naming.d.ts +20 -0
  59. package/dist/core/protocol-naming.js +20 -3
  60. package/dist/core/read-only-shell.d.ts +20 -5
  61. package/dist/core/read-only-shell.js +29 -14
  62. package/dist/core/remote-env.d.ts +37 -1
  63. package/dist/core/remote-env.js +11 -0
  64. package/dist/core/resume-startup-cause.d.ts +24 -2
  65. package/dist/core/resume-startup-cause.js +20 -1
  66. package/dist/core/roles.js +50 -7
  67. package/dist/core/runner/compact-session.d.ts +10 -0
  68. package/dist/core/runner/compact-session.js +202 -0
  69. package/dist/core/runner/compaction-call-options.d.ts +2 -2
  70. package/dist/core/runner/compaction-seams.d.ts +21 -1
  71. package/dist/core/runner/compaction-seams.js +11 -8
  72. package/dist/core/runner/contracts.d.ts +23 -16
  73. package/dist/core/runner/derived-route-fallback.d.ts +13 -0
  74. package/dist/core/runner/derived-route-fallback.js +6 -0
  75. package/dist/core/runner/env-capability-census.d.ts +3 -2
  76. package/dist/core/runner/env-capability-census.js +9 -3
  77. package/dist/core/runner/permission-rule-lanes.d.ts +7 -7
  78. package/dist/core/runner/permission-rule-lanes.js +17 -11
  79. package/dist/core/runner/prepare-acquire-reconcile.d.ts +3 -0
  80. package/dist/core/runner/prepare-acquire-reconcile.js +2 -1
  81. package/dist/core/runner/prepare-config-doors.d.ts +6 -0
  82. package/dist/core/runner/prepare-config-doors.js +4 -3
  83. package/dist/core/runner/prepare-hands-readface.d.ts +3 -1
  84. package/dist/core/runner/prepare-hands-readface.js +2 -0
  85. package/dist/core/runner/prepare-park-ask.d.ts +2 -0
  86. package/dist/core/runner/prepare-park-ask.js +2 -2
  87. package/dist/core/runner/prepare-policy-chain.js +2 -4
  88. package/dist/core/runner/prepare-task.d.ts +4 -1
  89. package/dist/core/runner/prepare-task.js +23 -27
  90. package/dist/core/runner/prepare-tool-disclosure-mount.d.ts +13 -0
  91. package/dist/core/runner/prepare-tool-disclosure-mount.js +13 -7
  92. package/dist/core/runner/rewind-files.d.ts +2 -5
  93. package/dist/core/runner/rewind-files.js +9 -51
  94. package/dist/core/runner/run-leg.js +10 -21
  95. package/dist/core/runner/run-notification-lane.d.ts +2 -0
  96. package/dist/core/runner/run-notification-lane.js +2 -2
  97. package/dist/core/runner/run-recovery-lanes.js +3 -4
  98. package/dist/core/runner/run-settle-and-teardown.js +5 -11
  99. package/dist/core/runner/run-terminal-adoption.js +3 -4
  100. package/dist/core/runner/run-turn-boundary.js +3 -5
  101. package/dist/core/runner/runtask.d.ts +8 -1
  102. package/dist/core/runner/runtask.js +28 -34
  103. package/dist/core/runner/send-message-continuation.js +1 -1
  104. package/dist/core/runner/session-verb-doors.d.ts +28 -0
  105. package/dist/core/runner/session-verb-doors.js +48 -0
  106. package/dist/core/runner/session-write-ledger.d.ts +30 -0
  107. package/dist/core/runner/session-write-ledger.js +101 -0
  108. package/dist/core/runner/stream-settle-backstop.js +11 -14
  109. package/dist/core/runner/sub-agent-deny-lists.d.ts +25 -3
  110. package/dist/core/runner/sub-agent-deny-lists.js +21 -10
  111. package/dist/core/runner/teardown-bounded.d.ts +1 -0
  112. package/dist/core/runner/teardown-bounded.js +1 -0
  113. package/dist/core/runner-deps.d.ts +2 -2
  114. package/dist/core/secret-env.d.ts +4 -0
  115. package/dist/core/secret-env.js +3 -0
  116. package/dist/core/session-policy-store.d.ts +7 -2
  117. package/dist/core/session-policy-store.js +1 -1
  118. package/dist/core/shell-wrapper-projection.d.ts +1 -1
  119. package/dist/core/store-contracts/background-agent-store-contract.js +1 -0
  120. package/dist/core/store-contracts/checkpoint-store-contract.js +2 -1
  121. package/dist/core/store-contracts/file-history-store-contract.js +9 -0
  122. package/dist/core/store-contracts/mailbox-store-contract.js +49 -1
  123. package/dist/core/task-registry-agent.d.ts +8 -4
  124. package/dist/core/task-registry-agent.js +14 -8
  125. package/dist/core/task-registry-shared.d.ts +35 -6
  126. package/dist/core/task-registry-shared.js +11 -0
  127. package/dist/core/task-registry.d.ts +1 -1
  128. package/dist/core/task-registry.js +1 -1
  129. package/dist/core/tool-catalog-entries.d.ts +6 -0
  130. package/dist/core/tool-catalog-entries.js +1 -0
  131. package/dist/core/tool-name-aliases.d.ts +32 -2
  132. package/dist/core/tool-name-aliases.js +23 -0
  133. package/dist/core/tool-name-readings.d.ts +4 -0
  134. package/dist/core/tool-name-readings.js +1 -0
  135. package/dist/core/tool-policy.d.ts +10 -6
  136. package/dist/core/tool-policy.js +4 -7
  137. package/dist/core/trace.d.ts +4 -1
  138. package/dist/core/trace.js +2 -1
  139. package/dist/core/untrusted-egress.d.ts +20 -0
  140. package/dist/core/untrusted-egress.js +77 -3
  141. package/dist/engine/harness/agent-harness.d.ts +7 -5
  142. package/dist/engine/harness/agent-harness.js +11 -2
  143. package/dist/engine/loop/agent-loop.d.ts +1 -4
  144. package/dist/engine/loop/agent-loop.js +26 -4
  145. package/dist/index.d.ts +12 -7
  146. package/dist/index.js +7 -4
  147. package/dist/internal/harness.d.ts +2 -1
  148. package/dist/internal/harness.js +1 -0
  149. package/dist/primitives/credential-ref.d.ts +89 -0
  150. package/dist/primitives/credential-ref.js +125 -0
  151. package/dist/primitives/index.d.ts +2 -0
  152. package/dist/primitives/index.js +1 -0
  153. package/dist/stores/file/checkpoint-store.d.ts +0 -4
  154. package/dist/stores/file/checkpoint-store.js +0 -1
  155. package/dist/stores/file/file-history-store.js +12 -4
  156. package/dist/stores/file/mailbox-store.d.ts +2 -1
  157. package/dist/stores/file/mailbox-store.js +26 -1
  158. package/dist/tools/fs/bash-readonly-classifier.d.ts +15 -19
  159. package/dist/tools/fs/bash-readonly-classifier.js +5 -10
  160. package/dist/tools/fs/fs-bash.js +10 -3
  161. package/dist/tools/fs/pattern-kill-guard.d.ts +3 -2
  162. package/dist/tools/fs/pattern-kill-guard.js +68 -36
  163. package/dist/tools/fs/shell-read-check.d.ts +10 -0
  164. package/dist/tools/fs/shell-read-check.js +7 -1
  165. package/dist/tools/fs/shell-read-identity.d.ts +12 -9
  166. package/dist/tools/fs/shell-read-identity.js +9 -4
  167. package/dist/tools/web.js +9 -7
  168. package/package.json +1 -1
  169. package/test/export-surface.snapshot.json +79 -3
  170. package/wire-changes/core-stores-sql@7.57.0.json +29 -0
  171. package/wire-changes/core@7.57.0.json +1487 -0
  172. package/wire-changes/mods-kernel@7.57.0.json +29 -0
  173. package/wire-changes/primitives@7.57.0.json +245 -0
@@ -1,4 +1,15 @@
1
+ /**
2
+ * The run loop's COMPACTION SEAMS, verbatim from three private methods of `Runner`: the Seam C
3
+ * cost-optimization options every `maybeCompact` call site spreads, the pre/postCompact hook-option wrapper and
4
+ * the consecutive-reuse counter fold. Layer-1 machinery under a name of its own (the S1 reading: a module the lanes stand
5
+ * on is not a lane) — the turn-boundary lane, the recovery lane and the terminal adoption's end-of-task pass reach DOWN
6
+ * for these three where the driver used to hand each lane the methods as delegates on its Input. Every function reads
7
+ * the Runner's deps LIVE through {@link RunnerDepsSeat}: `runner.deps.x` here is exactly `this.deps.x` in the method it
8
+ * came from, on the tick it is read.
9
+ */
10
+ import type { HookInvocationIdentity } from "../hooks.js";
1
11
  import type { MaybeCompactOptions } from "../auto-compaction.js";
12
+ import { type LegModBus } from "../mods/stations.js";
2
13
  import type { Prepared, RunnerDepsSeat } from "./contracts.js";
3
14
  /**
4
15
  * The cost-optimization compaction options threaded into BOTH `maybeCompact` call
@@ -12,6 +23,11 @@ import type { Prepared, RunnerDepsSeat } from "./contracts.js";
12
23
  * function itself always returns an object.
13
24
  */
14
25
  export declare function seamCCompactionOptions(runner: RunnerDepsSeat, prepared: Prepared): Pick<MaybeCompactOptions, "summaryProvider" | "onCompaction" | "maxConsecutiveProviderReuse" | "consecutiveProviderReuse" | "onCommitted">;
26
+ /** The Seam C PROVIDER half — the deployment's summary provider, its observer and its drift guard, fed the caller's
27
+ * consecutive-reuse count (a run's counter; `0` for a pass outside a run, which has no earlier reuse to count). Still
28
+ * provider-gated: with no provider wired the four keys stay ABSENT, so every caller spreads exactly what it spread
29
+ * before. */
30
+ export declare function summaryProviderOptions(runner: RunnerDepsSeat, consecutive: number): Pick<MaybeCompactOptions, "summaryProvider" | "onCompaction" | "maxConsecutiveProviderReuse" | "consecutiveProviderReuse">;
15
31
  /** The pre/postCompact stations threaded into maybeCompact: `classic.PreCompact` and `classic.PostCompact` raised on the
16
32
  * leg's bus (a station is wired only when a handler hangs on its event). Their failure and abort columns are the
17
33
  * bus's — a handler that throws or runs past its budget is skipped and reported, so a failed gate is "no block, no
@@ -19,7 +35,11 @@ export declare function seamCCompactionOptions(runner: RunnerDepsSeat, prepared:
19
35
  * trigger is reported as ignored (maybeCompact enforces the ignore — blocking a compaction the provider/trim layer
20
36
  * already demanded would kill the run). `signal` is the pass's abort (absent ⇒ each handler's own budget is the
21
37
  * whole bound). */
22
- export declare function compactionHookOptions(runner: RunnerDepsSeat, prepared: Pick<Prepared, "modBus" | "hookIdentity" | "sessionId">, trigger: "auto" | "manual" | "forced", signal: AbortSignal | undefined): Pick<MaybeCompactOptions, "trigger" | "preCompact" | "postCompact">;
38
+ export declare function compactionHookOptions(runner: RunnerDepsSeat, prepared: {
39
+ readonly modBus: LegModBus;
40
+ readonly hookIdentity: HookInvocationIdentity | undefined;
41
+ readonly sessionId: string;
42
+ }, trigger: "auto" | "manual" | "forced", signal: AbortSignal | undefined): Pick<MaybeCompactOptions, "trigger" | "preCompact" | "postCompact">;
23
43
  /** Fold a finished compaction's `reused` flag into the run-scoped consecutive-reuse
24
44
  * counter: a reused (provider) summary increments it, a real (LLM) summary resets it to 0. A no-op
25
45
  * compaction (`compacted:false`) leaves the counter untouched. No-op when no provider is wired. */
@@ -1,14 +1,7 @@
1
1
  import { classicEventBase, dispatchClassic, observeClassic, stringMembersAnswer, stringMembersOf } from "../mods/stations.js";
2
2
  export function seamCCompactionOptions(runner, prepared) {
3
3
  return {
4
- ...(runner.deps.summaryProvider
5
- ? {
6
- summaryProvider: runner.deps.summaryProvider,
7
- onCompaction: runner.deps.onCompaction,
8
- maxConsecutiveProviderReuse: runner.deps.maxConsecutiveProviderReuse,
9
- consecutiveProviderReuse: prepared.compactionReuseRef.consecutive,
10
- }
11
- : {}),
4
+ ...summaryProviderOptions(runner, prepared.compactionReuseRef.consecutive),
12
5
  onCommitted: (effect) => {
13
6
  if (prepared.frameSettlement.wantsEffects?.() !== true)
14
7
  return;
@@ -16,6 +9,16 @@ export function seamCCompactionOptions(runner, prepared) {
16
9
  },
17
10
  };
18
11
  }
12
+ export function summaryProviderOptions(runner, consecutive) {
13
+ return runner.deps.summaryProvider
14
+ ? {
15
+ summaryProvider: runner.deps.summaryProvider,
16
+ onCompaction: runner.deps.onCompaction,
17
+ maxConsecutiveProviderReuse: runner.deps.maxConsecutiveProviderReuse,
18
+ consecutiveProviderReuse: consecutive,
19
+ }
20
+ : {};
21
+ }
19
22
  export function compactionHookOptions(runner, prepared, trigger, signal) {
20
23
  const bus = prepared.modBus;
21
24
  const base = classicEventBase(prepared.hookIdentity);
@@ -234,7 +234,7 @@ export interface FrameReceipt {
234
234
  text: string;
235
235
  /** Which frame this receipt settles. No collector renders a frame whose receipt is still pending (the frame is on
236
236
  * its way, not yet in the transcript) — the one reading is `frameCarried` (compaction-restate.ts). */
237
- source: "git_status" | "agent_listing" | "skills_listing" | "date_change" | "output_style" | "mcp_unavailable" | "posture" | "approval_feedback" | "advisor_tool";
237
+ source: "git_status" | "agent_listing" | "skills_listing" | "date_change" | "output_style" | "mcp_unavailable" | "posture" | "advisor_tool";
238
238
  /** Runs inside the `message_end` walk (synchronous, never awaited): advances the run-local record
239
239
  * and parks any session write for the next serialization point. */
240
240
  commit: (entryId: string) => void;
@@ -480,6 +480,7 @@ export interface Prepared {
480
480
  * admission died with that leg, so the resumed bytes are judged (and admitted for their pre-spawn re-establishment)
481
481
  * on THIS leg — a deny row found now vetoes, an operator's edit is judged on the `rejudge` seat. */
482
482
  shellReadCheck?: import("../../tools/fs/shell-read-check.js").ShellReadCheck;
483
+ shellReadLandings?: import("../../tools/fs/shell-read-check.js").ShellReadLandings;
483
484
  crossBoundary?: {
484
485
  readonly check: import("../cross-boundary-taint.js").CrossBoundaryCheck | undefined;
485
486
  settle(owed: import("../cross-boundary-taint.js").OwedDeliveries): Promise<void>;
@@ -768,8 +769,8 @@ export interface Prepared {
768
769
  current?: CommittedPause;
769
770
  };
770
771
  /** A resumed leg's continuation content that has NOT reached the transcript yet: EVERY input frame the
771
- * prompt carries (parked steers, the caller's wake message) and the orphaned background tasks whose obituary
772
- * rides it. Set before the prompt, emptied at delivery; a re-park carries it forward (the saga folds the
772
+ * prompt carries (parked steers, the wake message, the approver's words) and the orphans whose obituary rides it. Set
773
+ * before the prompt, emptied at the prompt's own receipt (never its hand-off); a re-park carries it forward (the saga folds the
773
774
  * orphans in, the adoption re-parks the frames), and what no re-park carries the undrained account announces. */
774
775
  undeliveredContinuationRef: {
775
776
  current: UndeliveredContinuation;
@@ -2648,17 +2649,17 @@ export interface ResumeRun {
2648
2649
  wakeMessageHookContext?: string;
2649
2650
  /** The resumed leg's bus when the wake-message screen admitted it before the claim — prepare adopts it. */
2650
2651
  legMods?: LegMods;
2651
- /** Compensation hook: called iff the resumed run fails with `resume.env_failed` (post-CAS
2652
- * workspace `resumeVM` failed) OR `resume.tool_unavailable` (P-7: the approved tool vanished) — in both
2653
- * the CAS already consumed the checkpoint but the pending action never ran. `resumeStream` supplies a
2654
- * closure that reopens the checkpoint (`resolved → pending`) so a retry re-resumes the SAME suspended work
2655
- * instead of losing it to a forced "re-initiate" (reopen-by-reason): the `reason` is
2656
- * recorded on the reopened row so the next re-resume validates per reason — an `env_failed` reopen must
2657
- * replay the persisted winner (a system retry of the approved action), while a `tool_unavailable` reopen
2658
- * lets a human re-decide with the tool present (a fresh decision is allowed — preserves P-7). */
2659
- onEnvRestoreFailed?: (reason: ReopenReason) => Promise<void>;
2652
+ /** The claim's reversal, bound to THIS resume's token and scope by `resumeStream` — REQUIRED, as the store's
2653
+ * `reopen` is: called when the claimed leg ends before its approved action started (a failed restore, a
2654
+ * refusal at the leg's prepare, an abort, a leg that returned failed), it returns the row `resolved → pending`
2655
+ * under the failure's reason (`reopenReasonOf`), recorded on the row so the next re-resume validates per
2656
+ * reason — an `env_failed` reopen replays the persisted winner, the fresh-redecision reasons take a new
2657
+ * decision. Answers the store's own answer — `"reopened"` (it flipped the row) or `"declined"` (`reopen`
2658
+ * answered `false`) — and rejects only with what the store threw (the row's state is then unprovable);
2659
+ * every exit reads it through `compensateUnstarted` (resume-startup-cause.ts), the one reading. */
2660
+ reopenUnstarted: (reason: ReopenReason) => Promise<"reopened" | "declined">;
2660
2661
  /** The row's execution-record verb, bound to THIS resume's token and scope by `resumeStream` (the
2661
- * same closure form as {@link onEnvRestoreFailed}, but REQUIRED: the store contract requires the
2662
+ * same closure form as {@link reopenUnstarted}, and REQUIRED for the same reason: the store contract requires the
2662
2663
  * verb, so there is no store this seat could be absent for). The pending-call resolver calls it
2663
2664
  * exactly once per resolution, right after it emits the resolved call's `tool_end` frame, with the
2664
2665
  * {@link import("../gate-outcome.js").GateOutcome} that frame carried — every disposition, the
@@ -2666,8 +2667,8 @@ export interface ResumeRun {
2666
2667
  * allowed). The word it answers is read through `EXECUTION_RECORD_LEAVES_ROW_UNRECORDED`; a
2667
2668
  * conflict throw propagates (an engine defect, not a store condition). */
2668
2669
  recordExecutionOutcome: (gate: import("../gate-outcome.js").GateOutcome) => Promise<ExecutionOutcomeRecordWord>;
2669
- /** Set in the run body right after `applyResumeDecision` completes: the negative-
2670
- * decision twin of `pendingActionStarted`. A reject/deny consumes its gate BY BEING DELIVERED, and
2670
+ /** Set right after `applyResumeDecision` completes (`policy_ask`), and at the continuation's own receipt for `plan_review` / `dry_run_review`
2671
+ * (its decision rides only that message): the negative-decision twin of `pendingActionStarted`. A reject/deny consumes its gate BY BEING DELIVERED, and
2671
2672
  * this bit is the delivery fact — every throw-arm cause (prepare failure, `session_not_found`, a
2672
2673
  * pre-delivery abort) and the walltime-exhausted settle fire BEFORE it is set, so an undelivered
2673
2674
  * negative decision still reopens as `env_failed` (the retry replays the persisted decision; it
@@ -2869,7 +2870,13 @@ export interface RunnerSelfSeat {
2869
2870
  */
2870
2871
  export type PrepareTaskFn = (spec: TaskSpec, deps: RunnerDeps, sessions: SessionStore, resume?: PrepareResume, internals?: RunInternals, runnerSelf?: RunnerSelfSeat, runIdSink?: {
2871
2872
  runId?: string;
2872
- }, holdSessionLock?: (sessionId: string) => Promise<void>) => Promise<Prepared>;
2873
+ }, holdSessionLock?: (sessionId: string) => Promise<void>, legWrites?: LegWrites) => Promise<Prepared>;
2874
+ /** One leg's view of the Runner's session write ledger (`session-write-ledger.ts`): `issue` starts a write of the leg (`what` = the
2875
+ * entry type, for a report) or refuses it once `close` ran — the leg's hold ended; a write still in flight is reported when it settles. */
2876
+ export interface LegWrites {
2877
+ issue<T>(sessionId: string, what: string, start: () => Promise<T>): Promise<T>;
2878
+ close(): void;
2879
+ }
2873
2880
  /**
2874
2881
  * The Result of an INSTALLING phase: a lane whose every product is installed on a seat it borrowed
2875
2882
  * (the harness's stop gate and recovery chain, the run state's counter groups, the stream's result setter, the claimed
@@ -14,6 +14,19 @@ export type DerivedSeatOutcome = {
14
14
  readonly action: "fall_back" | "detach";
15
15
  readonly cause: RouteRefusalCode;
16
16
  };
17
+ /**
18
+ * Whether the compaction-summary model is ROLE-derived — the only one whose route is pre-flighted (an EXPLICIT
19
+ * compaction model is a named leg and keeps the request gate's loud refusal). Synchronous: a caller awaits the
20
+ * pre-flight only when this holds, so a run with nothing to judge takes no suspension point at the seat.
21
+ */
22
+ export declare function compactionSummaryIsDerived(explicit: boolean, compModel: Model | undefined): compModel is Model;
23
+ /**
24
+ * The compaction-summary seat after its route pre-flight: a derived summary model whose key↔URL pairing is broken must
25
+ * not sink the pass at the brain's request gate — it falls back to the primary (`undefined`: the pass then summarizes
26
+ * on the main model, the documented absent-model behavior) and the fallback is announced. The one reading for a run's
27
+ * prepare and the out-of-run compaction.
28
+ */
29
+ export declare function compactionSummaryFallback(args: Omit<Parameters<typeof derivedRouteFallsBack>[0], "seat" | "onBrokenPairing">): Promise<Model | undefined>;
17
30
  /**
18
31
  * Judge the derived model where its route differs from the primary's, announce what a broken pairing means for THIS seat
19
32
  * (`onBrokenPairing`), and ABSTAIN (keep) on ANY throw. The pre-flight is advisory — the brain's request gate re-runs the
@@ -1,5 +1,11 @@
1
1
  import { adjudicateDerivedRoute, fallbackToPrimaryNotice, sameRouteIdentity, seatDetachedNotice, unroutableSeatNotice } from "../../brain/route-adjudicator.js";
2
2
  import { deliverEngineNotice } from "../types.js";
3
+ export function compactionSummaryIsDerived(explicit, compModel) {
4
+ return !explicit && compModel !== undefined;
5
+ }
6
+ export async function compactionSummaryFallback(args) {
7
+ return (await derivedRouteFallsBack({ ...args, seat: "compaction-summary" })).action === "fall_back" ? undefined : args.derived;
8
+ }
3
9
  export async function derivedRouteFallsBack(args) {
4
10
  try {
5
11
  if (sameRouteIdentity(args.derived, args.primary))
@@ -13,8 +13,9 @@ export interface MalformedCapabilityDeclaration {
13
13
  * The probe is the VALUE, not membership, because the value is what the readers get: a `capabilities`
14
14
  * that answers `has` and `get` differently (a Proxy) would otherwise hide from the census exactly the
15
15
  * value a reader goes on to act on. `undefined` is where absence lands — an absent key and an explicit
16
- * `undefined` are the same word to every reader, and both are the documented silent reading — so one
17
- * test covers both and the census needs no membership rule at all.
16
+ * `undefined` are the same word to every reader, and both are the documented silent reading of every key
17
+ * outside {@link NO_ABSENT_READING} (and announced, alike, for a key inside it) — so one test covers both and
18
+ * the census needs no membership rule at all.
18
19
  *
19
20
  * TOTAL by construction: a diagnostic may not be the thing that fails a run. Every read of the adapter
20
21
  * can throw on a getter or a Proxy trap — the structural probe, the block acquisition and each key —
@@ -1,6 +1,9 @@
1
1
  import { isRemoteExecutionEnv } from "../remote-env.js";
2
2
  import { deliverEngineNotice } from "../engine-notice.js";
3
- const BOOLEAN_CAPABILITY_KEYS = ["isolation", "suspendable", "canonicalPathAuthoritative"];
3
+ const BOOLEAN_CAPABILITY_KEYS = ["isolation", "suspendable", "canonicalPathAuthoritative", "sharesProcessTable"];
4
+ const NO_ABSENT_READING = {
5
+ sharesProcessTable: "whether the commands it runs are processes on this engine's own process table has no default, so the Bash tool refuses every pattern kill that needs the question (`pkill`, `killall`, a `kill` fed by `pgrep`; `pkill -P $$`, the shell's own children, runs) on this environment until the adapter declares it — `true` when its commands run on a table that holds the engine (a host lane, or a PID namespace that contains the engine's), `false` only when they run where the engine is not (another machine, a PID namespace that does not contain it)",
6
+ };
4
7
  const OPTIONAL_METHOD_KEYS = ["writeFileExclusive", "writeFileGuarded", "setFileMode", "readLink", "canonicalChain", "fileIdentity", "openBound"];
5
8
  export function malformedCapabilityDeclarations(env) {
6
9
  const out = [];
@@ -36,7 +39,7 @@ export function malformedCapabilityDeclarations(env) {
36
39
  out.push({ key, valueType: "unreadable", expected: "boolean" });
37
40
  continue;
38
41
  }
39
- if (value === undefined || typeof value === "boolean")
42
+ if (typeof value === "boolean" || (value === undefined && NO_ABSENT_READING[key] === undefined))
40
43
  continue;
41
44
  out.push({ key, valueType: value === null ? "null" : typeof value, expected: "boolean" });
42
45
  }
@@ -44,11 +47,14 @@ export function malformedCapabilityDeclarations(env) {
44
47
  }
45
48
  export function announceMalformedCapabilities(env, onNotice) {
46
49
  for (const { key, valueType, expected } of malformedCapabilityDeclarations(env)) {
50
+ const noAbsentReading = NO_ABSENT_READING[key];
47
51
  deliverEngineNotice(onNotice, {
48
52
  code: "config.execution_env_capability_invalid",
49
53
  message: expected === "function"
50
54
  ? `The execution environment declares ${key} as a ${valueType}, not a function. This seat is declared by being present as a callable method and undeclared by being absent, so every reader takes the absent reading — the reach the adapter meant to declare is silently the old one. Fix the adapter — this is a wiring fault, not a supported way to leave the seat unset.`
51
- : `The execution environment declares capabilities.${key} as a ${valueType}, not a boolean. What a reader makes of that is the reader's own business and differs between them — the containment readers take only the boolean, so this reads as undeclared, while the suspend reader takes any truthy value, so a non-empty string reads as TRUE. That is why a declaration of the wrong shape cannot pass quietly: the engine may act on something other than what the adapter meant. Fix the adapter — this is a wiring fault, not a supported way to leave the capability unset.`,
55
+ : noAbsentReading !== undefined
56
+ ? `The execution environment ${valueType === "undefined" ? `does not declare capabilities.${key}` : `declares capabilities.${key} as a ${valueType}, not a boolean`}: ${noAbsentReading}. Fix the adapter — this is a wiring fault, not a supported way to leave the capability unset.`
57
+ : `The execution environment declares capabilities.${key} as a ${valueType}, not a boolean. What a reader makes of that is the reader's own business and differs between them — the containment readers take only the boolean, so this reads as undeclared, while the suspend reader takes any truthy value, so a non-empty string reads as TRUE. That is why a declaration of the wrong shape cannot pass quietly: the engine may act on something other than what the adapter meant. Fix the adapter — this is a wiring fault, not a supported way to leave the capability unset.`,
52
58
  detail: { key, valueType },
53
59
  });
54
60
  }
@@ -29,11 +29,8 @@ import { type OrgGateVerdict, type PersistedRuleAnswer, type PersistedRuleHit, t
29
29
  import type { PersistedRule, PersistedRuleVerdict, RuleOffer, ScopeRootIdentity, SegmentCoverage } from "../permission-rule-model.js";
30
30
  import { type PathRuleBases } from "../permission-rule-model.js";
31
31
  import { type HeadLeads } from "../path-rule-binding.js";
32
- import type { ToolCallLike } from "../tool-registry.js";
33
32
  import { type OrgRuleResolution } from "../permission-rule-org.js";
34
33
  import { type EffectivePermissionRules, type PermissionRuleStoreProvider, type PersonRowsRead } from "../permission-rule-provider.js";
35
- /** The one SHELL tool the persisted-rule lane's COMMAND grammar speaks for (the rule-offer face mints for it, the name tools and the host tools); the other grammars' tools are derived (`ruleToolGrammarOf`). */
36
- export declare const PERSISTED_RULE_TOOL = "Bash";
37
34
  /**
38
35
  * The evidence an INHERITED-lane ask carries.
39
36
  *
@@ -203,9 +200,12 @@ interface RuleOffersAskFacts extends RuleClearanceFacts {
203
200
  * approval-preview field is a standing example of what happens otherwise: it reached two of the
204
201
  * five, so a delegated child's card was silently poorer than the top-level one).
205
202
  *
206
- * Empty unless a rule lane is armed — offering an option that redemption would refuse is worse than
207
- * offering none — and empty for any call the lane cannot speak for (a compound, a redirection, a
208
- * substitution, or a tool that is not the shell tool).
203
+ * Silent (no offer and no reason) only where no rule speaks for the call: no lane armed, the anonymous floor,
204
+ * a call the lane never judges — offering an option that redemption would refuse is worse than offering none.
205
+ * Every other card offers the one rule its call's form mints ({@link cardRuleFormOf}: the shell card's
206
+ * command rules, a name or host tool's exact value rule, the whole-tool rule of a call no rule word speaks
207
+ * for) or names why it offers none (`lane_cannot_speak`: a compound, a redirection, a substitution, a path
208
+ * tool, a shell-face sibling, a tool answering to one of those under an alias).
209
209
  *
210
210
  * Empty, too, on any ask a persisted rule could never CLEAR — "allow rules silence the classifier's
211
211
  * questions, never a mandated one". Two doors, matching the two halves of that boundary:
@@ -270,7 +270,7 @@ export declare function createRuleOffersOf(cfg: {
270
270
  current: string;
271
271
  } | undefined;
272
272
  deniesDirectoryRead: ((directory: string) => boolean) | undefined;
273
- }): (call: Pick<ToolCallLike, "toolName" | "face">, args: unknown, ask: RuleOffersAskFacts | undefined) => {
273
+ }): (call: Pick<ToolCallRequest, "toolName" | "face">, args: unknown, ask: RuleOffersAskFacts | undefined) => {
274
274
  ruleOffers?: readonly RuleOffer[];
275
275
  ruleOffersAbsence?: RuleOffersAbsence;
276
276
  execCwd?: string;
@@ -1,18 +1,18 @@
1
1
  import { persistedRuleMandateOf, ruleClearanceOf } from "../ask-origin.js";
2
2
  import {} from "../hooks.js";
3
- import { adjudicatePersistedDomainRules, adjudicatePersistedNameRules, adjudicatePersistedPathRules, adjudicatePersistedRules, callPathFaceOf, eligiblePersisted, escapeForDisclosure, faceRowsSpeakFor, parseRuleText, laneToolNamesOf, nameRuleSubjectOf, ruleToolGrammarOf, segmentCoverageOf, suggestRuleForHost, suggestRuleForName, suggestRulesForCommand } from "../permission-rule-model.js";
3
+ import { adjudicatePersistedDomainRules, adjudicatePersistedNameRules, adjudicatePersistedPathRules, adjudicatePersistedRules, adjudicatePersistedToolRules, callPathFaceOf, cardRuleFormOf, eligiblePersisted, escapeForDisclosure, faceRowsSpeakFor, parseRuleText, laneToolNamesOf, nameRuleSubjectOf, ruleToolGrammarOf, segmentCoverageOf, suggestRuleForHost, suggestRuleForName, suggestRuleForTool, suggestRulesForCommand, wholeToolRuleReaches } from "../permission-rule-model.js";
4
4
  import { callHostTargetOf } from "../host-target.js";
5
5
  import { callTargetViewsOf, validatePath } from "../effective-path-target.js";
6
6
  import { bindPathRules, persistedRowAnchors, projectScopeIdentity } from "../path-rule-binding.js";
7
7
  import { heldBy, seatOf, wireOf } from "../tool-name-readings.js";
8
8
  import { describeUnreadableOrgRows, readOrgRules, settleOrgVerdictWithin } from "../permission-rule-org.js";
9
9
  import { ASK_USER_QUESTION_TOOL_NAME } from "../ask-question.js";
10
+ import { routesContent } from "../tool-catalog-entries.js";
10
11
  import { ownerStoreOf } from "../permission-rule-provider.js";
11
12
  import { readPrincipal } from "../principal-name.js";
12
13
  import { thrownText } from "../thrown-value.js";
13
14
  import { strongestAnswer } from "../persisted-rule-arms.js";
14
15
  import { ORG_ADJUDICATION_TIMEOUT_MS } from "../permission-rule-org.js";
15
- export const PERSISTED_RULE_TOOL = "Bash";
16
16
  const NO_LEADS = new Map();
17
17
  export function inheritedAskRuleEvidence(provider) {
18
18
  const org = provider?.partitions.org === true ? "not_adjudicated" : "not_wired";
@@ -123,21 +123,23 @@ export function createPermissionRuleLanes(cfg) {
123
123
  if (!judged.some(([, g]) => g === "path") && faceRowsSpeakFor(callPathFaceOf(req)))
124
124
  judged.push([req.toolName, "path"]);
125
125
  const view = await reader.pin(req.toolCallId, req.args);
126
- if (judged.length === 0)
126
+ const ownRow = (r) => judged.some(([n]) => n === r.tool) || wholeToolRuleReaches(r.tool, req);
127
+ if (judged.length === 0 && !view.rules.some(ownRow))
127
128
  return view.unreadable !== undefined ? { unreadable: true } : undefined;
128
129
  try {
129
130
  const stripped = view.rules.filter((r) => r.behavior !== "allow" || cfg.stripsAllow?.(r, req) !== true);
130
131
  const judgesPath = judged.some(([, g]) => g === "path");
131
132
  const noRule = [];
132
133
  const table = stripped.filter((r) => {
133
- const read = judged.some(([n]) => n === r.tool) || (judgesPath && (r.match === "path" || r.match === "subpath")) ? parseRuleText(r.rule, r.behavior) : undefined;
134
+ const read = ownRow(r) || (judgesPath && (r.match === "path" || r.match === "subpath")) ? parseRuleText(r.rule, r.behavior) : undefined;
134
135
  if (read !== undefined && "reject" in read)
135
136
  noRule.push({ row: r, why: read.reject.message });
136
137
  return read === undefined || !("reject" in read) || r.behavior !== "allow";
137
138
  });
138
139
  const leads = judgesPath ? 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;
139
140
  const scopeIdentity = await projectScopeIdentity(table, cfg.rootCanonical, cfg.leads);
140
- const judgedAnswer = strongestAnswer(judged.map(([n, grammar]) => judgeAgainst(table, { ...req, toolName: n }, grammar, leads, scopeIdentity)));
141
+ const wholeTool = persistedRuleAnswerOf(adjudicatePersistedToolRules(table, { request: req, cwd: cfg.root, sessionId: cfg.sessionId, ...(scopeIdentity !== undefined ? { scopeIdentity } : {}) }));
142
+ const judgedAnswer = strongestAnswer([...judged.map(([n, grammar]) => judgeAgainst(table, { ...req, toolName: n }, grammar, leads, scopeIdentity)), wholeTool]);
141
143
  const unjudged = noRule.find(({ row }) => (row.behavior === "deny" || row.behavior === "ask") && eligiblePersisted(row, { tool: row.tool, cwd: cfg.root, sessionId: cfg.sessionId, ...(scopeIdentity !== undefined ? { scopeIdentity } : {}) }, row.behavior));
142
144
  const answer = unjudged !== undefined && !(judgedAnswer !== undefined && "behavior" in judgedAnswer && judgedAnswer.behavior === "deny") ? unjudgedRowAnswer(unjudged.row, unjudged.why, req.toolName) : judgedAnswer;
143
145
  const stands = answer !== undefined && (("behavior" in answer && answer.behavior === "deny") || "unreadable" in answer);
@@ -180,11 +182,9 @@ export function createPermissionRuleLanes(cfg) {
180
182
  export function createRuleOffersOf(cfg) {
181
183
  return (call, args, ask) => {
182
184
  const toolName = wireOf("offer.laneTool", call);
183
- const grammar = ruleToolGrammarOf(toolName);
184
- if (!cfg.laneArmed || (toolName !== PERSISTED_RULE_TOOL && grammar !== "name" && grammar !== "domain"))
185
- return {};
186
- if (readPrincipal(cfg.principal).kind === "absent" && !cfg.localOwnerDeclared)
185
+ if (!cfg.laneArmed || (readPrincipal(cfg.principal).kind === "absent" && !cfg.localOwnerDeclared) || routesContent(call.face))
187
186
  return {};
187
+ const form = cardRuleFormOf(toolName, call);
188
188
  const tierSeat = seatOf("offer.marks", cfg.irreversibilityTier, call);
189
189
  const closedDoor = ruleClearanceOf(ask, {
190
190
  egress: heldBy("offer.marks.egress", cfg.egressTools, call),
@@ -193,11 +193,17 @@ export function createRuleOffersOf(cfg) {
193
193
  }).word;
194
194
  if (closedDoor !== undefined)
195
195
  return { ruleOffersAbsence: closedDoor };
196
- if (grammar === "name") {
196
+ if (form === "tool") {
197
+ const offer = suggestRuleForTool(toolName);
198
+ return offer !== undefined ? { ruleOffers: [offer] } : { ruleOffersAbsence: "lane_cannot_speak" };
199
+ }
200
+ if (form === undefined)
201
+ return { ruleOffersAbsence: "lane_cannot_speak" };
202
+ if (form === "name") {
197
203
  const offer = suggestRuleForName(toolName, nameRuleSubjectOf(toolName, args) ?? "");
198
204
  return offer !== undefined ? { ruleOffers: [offer] } : { ruleOffersAbsence: "lane_cannot_speak" };
199
205
  }
200
- if (grammar === "domain") {
206
+ if (form === "domain") {
201
207
  const host = callHostTargetOf({ toolName: call.toolName, args, face: call.face }, "grant");
202
208
  const offer = host !== undefined && "host" in host ? suggestRuleForHost(toolName, host.host) : undefined;
203
209
  return offer !== undefined ? { ruleOffers: [offer] } : { ruleOffersAbsence: "lane_cannot_speak" };
@@ -24,6 +24,7 @@ import { type RecoveredOrphan } from "../session-reconcile.js";
24
24
  import type { TaskSpec, ToolEffect } from "../types.js";
25
25
  import type { PrepareResume } from "./contracts.js";
26
26
  import { type ResumeAtBranch } from "./branch-writeback.js";
27
+ import { type LegWrites } from "./session-write-ledger.js";
27
28
  export interface PrepareAcquireReconcileInput {
28
29
  /** borrowed-mutable (service port) — the deployment's session store. This phase's verbs on it are
29
30
  * the acquire/forget half of the session resource protocol: `acquire` per attempt, `forget` to
@@ -54,6 +55,8 @@ export interface PrepareAcquireReconcileInput {
54
55
  durableParkGap?: string;
55
56
  /** borrowed-readonly — the incident channel for this phase's own cleanup legs: a `resumeAt` branch it could not write back, a forget that did not settle. */
56
57
  report?: (err: Error, sessionId: string) => void;
58
+ /** borrowed-mutable (the Runner's) — this leg's view of the session write ledger, every storage write issued through it ({@link writesThroughLeg}); absent for a standalone prepare. */
59
+ legWrites?: LegWrites;
57
60
  }
58
61
  /** The phase's outputs (four-class form). All six are fresh bindings —
59
62
  * the driver destructures them into consts, so a consumer moved ahead of this call is a lexical
@@ -6,6 +6,7 @@ import { lastAttachmentFrameOf } from "../user-attachment.js";
6
6
  import { reconcileInterruptedSession } from "../session-reconcile.js";
7
7
  import { detectConflicts, settleBranchWriteBack } from "./branch-writeback.js";
8
8
  import { settleTeardownLeg } from "./teardown-bounded.js";
9
+ import { writesThroughLeg } from "./session-write-ledger.js";
9
10
  const RECONCILE_MAX_RETRIES = 3;
10
11
  export async function prepareAcquireReconcile(input) {
11
12
  const { sessions, spec, resume, wakeEffects } = input;
@@ -29,7 +30,7 @@ export async function prepareAcquireReconcile(input) {
29
30
  }
30
31
  conflictRef = { hit: false };
31
32
  const own = { ids: undefined };
32
- session = new StoredSession(detectConflicts(acquired.session.getStorage(), conflictRef, own));
33
+ session = new StoredSession(detectConflicts(input.legWrites !== undefined ? writesThroughLeg(acquired.session.getStorage(), input.legWrites, acquired.sessionId) : acquired.session.getStorage(), conflictRef, own));
33
34
  if (!spec.sessionId) {
34
35
  break;
35
36
  }
@@ -36,6 +36,12 @@ import type { PrepareResume, RunInternals, ToolFaceLists } from "./contracts.js"
36
36
  import { resolveHost } from "../../prompt-assembly/select.js";
37
37
  import type { PromptTraitName } from "../../prompt-assembly/catalog/schema.js";
38
38
  import { type RosterSpec } from "./caller-roster.js";
39
+ /**
40
+ * The model a compaction pass summarizes on: the explicit `compactionModel` wins (resolved at `seat`, the word a
41
+ * refusal names), else the `summarize` role when one is configured, else `undefined` — the pass then falls back to
42
+ * the main model inside `maybeCompact`. One reading for a run's prepare and the out-of-run `Runner.compactSession`.
43
+ */
44
+ export declare function resolveCompactionModel(spec: Pick<TaskSpec, "compactionModel" | "roles">, deps: Pick<RunnerDeps, "models" | "roles">, seat: string): Model | undefined;
39
45
  export declare function limitConfigError(code: string, message: string): Error & {
40
46
  code?: string;
41
47
  };
@@ -54,6 +54,9 @@ const RETIRED_TASK_LIMIT_KEYS = {
54
54
  callCapByDeadline: "(retired — no replacement)",
55
55
  gracefulFinalize: "(retired — the approach notice is the only end-of-budget prompt)",
56
56
  };
57
+ export function resolveCompactionModel(spec, deps, seat) {
58
+ return spec.compactionModel !== undefined ? resolveModel(spec.compactionModel, deps.models, seat) : roleModelIfSet("summarize", spec, deps);
59
+ }
57
60
  export function limitConfigError(code, message) {
58
61
  const e = new Error(message);
59
62
  e.code = code;
@@ -482,9 +485,7 @@ export function prepareConfigDoors(input) {
482
485
  discardedEnvRaw: gateDecision.discardedEnvRaw,
483
486
  });
484
487
  const thinking = spec.thinking ?? resolvedRole.thinking ?? model.defaultThinking;
485
- const compModel = spec.compactionModel !== undefined
486
- ? resolveModel(spec.compactionModel, deps.models, "TaskSpec.compactionModel")
487
- : roleModelIfSet("summarize", spec, deps);
488
+ const compModel = resolveCompactionModel(spec, deps, "TaskSpec.compactionModel");
488
489
  return {
489
490
  spec,
490
491
  toolFaceLists,
@@ -2,7 +2,7 @@ import { type BeforeWriteHook, type CwdRef, type PathRuleDenyRow, type Containme
2
2
  import { type ReadFileState } from "../../tools/fs/safety.js";
3
3
  import { type PathTablesFacade, type PersonPathRows, type ReadRootsSeat } from "./person-path-rows.js";
4
4
  import type { HeadLeads } from "../path-rule-binding.js";
5
- import { type ShellReadAdmission, type ShellReadCheck } from "../../tools/fs/shell-read-check.js";
5
+ import { type ShellReadAdmission, type ShellReadCheck, type ShellReadLandings } from "../../tools/fs/shell-read-check.js";
6
6
  import type { ShellReadBoundary } from "../../tools/fs/shell-read-judge.js";
7
7
  import type { RunnerDeps, ShellGateDoctrine, TaskSpec } from "../types.js";
8
8
  import type { Model } from "../../internal/llm.js";
@@ -215,6 +215,8 @@ export interface PrepareHandsReadFaceResult {
215
215
  /** owned — the same station's pre-spawn ADMISSION, published for the P11 face that spawns a shell command
216
216
  * and mounts LATER than the band (`Monitor`): the SAME function the band's shell faces got. ⇔ a real shell. */
217
217
  shellReadAdmission: ShellReadAdmission | undefined;
218
+ /** owned — the same station's landings of a judged call (`ShellReadStation.landings`): the park binds them, the resumed leg's execution station compares. ⇔ a real shell. */
219
+ shellReadLandings: ShellReadLandings | undefined;
218
220
  /** owned — the file read station's admission (`FileReadStation.admitted`) the band's read tools got, for the read tools mounted later (LSP). */
219
221
  readAdmission: ((toolCallId: string | undefined, canonicalKey: string) => boolean) | undefined;
220
222
  /** write-back (D-3 hold) — the run's RESOLVED read face where hands mount; undefined ⇔ no hands
@@ -86,6 +86,7 @@ export function resolveHandsLessReadFace(input) {
86
86
  shellReadCheck: undefined,
87
87
  shellReadStationArmed: false,
88
88
  shellReadAdmission: undefined,
89
+ shellReadLandings: undefined,
89
90
  readAdmission: undefined,
90
91
  resolvedReadFace: undefined,
91
92
  resolvedWriteFace: undefined,
@@ -411,6 +412,7 @@ export async function prepareHandsMount(input) {
411
412
  shellReadCheck: onPinnedRead(input.personRows, [...(shellReadStation !== undefined ? [{ check: shellReadStation.check, tools: shellReadTools }] : []), ...(fileReadStation !== undefined ? [fileReadStation] : [])]),
412
413
  shellReadStationArmed: shellReadStation !== undefined,
413
414
  shellReadAdmission: shellReadStation?.admit,
415
+ shellReadLandings: shellReadStation?.landings,
414
416
  readAdmission: fileReadStation?.admitted,
415
417
  resolvedReadFace,
416
418
  resolvedWriteFace,
@@ -102,6 +102,8 @@ export interface PrepareParkAskInput {
102
102
  handsRoot: string | undefined;
103
103
  /** borrowed-readonly — the live gate's target-reading stamp ({@link import("./prepare-ask-lane.js").PrepareAskLaneInput.stampCallTarget}). */
104
104
  stampCallTarget: CallTargetStamp | undefined;
105
+ /** borrowed-readonly — where the leg's shell read station found a judged call's reads land (the approval object's targets). */
106
+ shellReadLandings: import("../../tools/fs/shell-read-check.js").ShellReadLandings | undefined;
105
107
  /** borrowed-readonly — the SAME seat the live gate stamps from ({@link import("./prepare-ask-lane.js").PrepareAskLaneInput.advertisedWritableDirs}): a parked call's projection re-adjudication asks one question about one call, so it must ask it against the same admitted list. Read at park time, not written here. */
106
108
  advertisedWritableDirs: Pick<AdvertisedWritableDirs, "roots"> | undefined;
107
109
  /** borrowed-readonly — the leg's ONE exempt root, the SAME list the live gate stamps ({@link import("./prepare-ask-lane.js").PrepareAskLaneInput.exemptRoots}). */
@@ -29,7 +29,7 @@ const wordlessDemand = (toolName) => {
29
29
  throw new Error(`a real-approval demand for "${toolName}" reached the park with no origin word — the origin stamp precedes every park`);
30
30
  };
31
31
  export function prepareParkAsk(input) {
32
- const { askLane, saga, spec, deps, sessionId, checkpointStore, checkpointScope, toolRosterDeltas, parkLaneArmed, contentAskRoutable, liveQuestionFace, mountedQuestionTool, contentAskBindings, lateStrandedAnswers, discloseStrandedAnswers, onAsk, runtimeCaps, inheritedUnavailableAsks, basePolicyForResumeEdit, budgetSnapshot, handsCwdRef, handsRoot, stampCallTarget, advertisedWritableDirs, exemptRoots, offloadStore, ownedEnv, incompleteSuspendAdapter, session, sessions, suspendChainBase, maxSuspends, remoteEnvFailures, shellGatedTools, irreversibilityTier, toolMarkSources, effectiveShellGate, durableApproval, priorLedger, liveSpendRef, resourceTotal, ruleOffersOf, grantBindingOf, now, humanReviewRef, abortController, harness, pausedRef } = input;
32
+ const { askLane, saga, spec, deps, sessionId, checkpointStore, checkpointScope, toolRosterDeltas, parkLaneArmed, contentAskRoutable, liveQuestionFace, mountedQuestionTool, contentAskBindings, lateStrandedAnswers, discloseStrandedAnswers, onAsk, runtimeCaps, inheritedUnavailableAsks, basePolicyForResumeEdit, budgetSnapshot, handsCwdRef, handsRoot, stampCallTarget, shellReadLandings, advertisedWritableDirs, exemptRoots, offloadStore, ownedEnv, incompleteSuspendAdapter, session, sessions, suspendChainBase, maxSuspends, remoteEnvFailures, shellGatedTools, irreversibilityTier, toolMarkSources, effectiveShellGate, durableApproval, priorLedger, liveSpendRef, resourceTotal, ruleOffersOf, grantBindingOf, now, humanReviewRef, abortController, harness, pausedRef } = input;
33
33
  if (askLane === undefined || saga === undefined)
34
34
  return { parkAsk: undefined };
35
35
  const { composedCallSignal, approvalPreviewOf } = askLane;
@@ -311,7 +311,7 @@ export function prepareParkAsk(input) {
311
311
  toolCallId: req.toolCallId,
312
312
  toolName: req.toolName,
313
313
  ...parkedToolIdentity(toolRosterDeltas.current, req.toolName), ...parkedToolFace(toolRosterDeltas.current, req),
314
- ...approvalTargetsOf(licenseCovers(parkedHash, req.args) ? callTargetReadingOf(req) : undefined),
314
+ ...(licenseCovers(parkedHash, req.args) ? approvalTargetsOf(callTargetReadingOf(req), shellReadLandings?.(req.toolCallId)) : {}),
315
315
  args: parkedArgs,
316
316
  ...(() => {
317
317
  const p = approvalPreviewOf(req.toolName, parkedArgs);
@@ -10,7 +10,7 @@ import { isIsolated } from "../remote-env.js";
10
10
  import { deliverEngineNotice } from "../engine-notice.js";
11
11
  import { earlierGrammarNotice } from "../session-policy-store.js";
12
12
  import { attest, attestationOf, checkToolPolicyProjection, combinePolicies, createTranscriptIntegrityPolicy, createUnverifiableDeletePolicy, durableParkOptInDeclared, isLiveApproverSeat, mergeAttestation, refuseOutOfContractDecision, resolveAsk, toolPolicyNameSets, withAttestation, wrapToolPolicy } from "../tool-policy.js";
13
- import { judgeRuleToolName, mountedToolNames } from "../tool-name-aliases.js";
13
+ import { judgeRuleToolName, mountedToolNames, nearMissSpellingOf } from "../tool-name-aliases.js";
14
14
  import { approvalCardMemberOf } from "../tool-roster.js";
15
15
  import { NAMESPACED_NAME_SHAPES } from "../protocol-table.js";
16
16
  import { emitTrace } from "../trace.js";
@@ -18,7 +18,7 @@ import { skillGrantBindingMembers } from "../skill-grant.js";
18
18
  import { SKILL_TOOL_NAME } from "./synthetic-tools.js";
19
19
  import { delegationPoolReading } from "./sub-agent-deny-lists.js";
20
20
  import { valveLeaves } from "../../agents/tool-filter.js";
21
- import { autoStrippedRule, cardDisclosureOf, nearMissSpellingOf, rootFacingToolNames } from "../allow-entry.js";
21
+ import { autoStrippedRule, cardDisclosureOf, rootFacingToolNames } from "../allow-entry.js";
22
22
  import { arbitrateByMode, askClassMembersOf, askFaceOf, askMandateMember } from "../ask-origin.js";
23
23
  import { createModeGatePolicy, mountedToolDeclaresReadOnly } from "../mode-policy.js";
24
24
  const ancestorFacts = (ask) => ({ ...(ask ?? {}), ancestorResolved: true });
@@ -216,8 +216,6 @@ export async function preparePolicyChain(input) {
216
216
  const missing = [];
217
217
  if (store === undefined)
218
218
  missing.push("a wired CheckpointStore");
219
- else if (store.redecision?.reopen !== true)
220
- missing.push("a CheckpointStore that DECLARES redecision.reopen (declaration, never duck-typing)");
221
219
  if (spec.durableApproval === undefined && runtimeCaps?.forceDurableGate !== true)
222
220
  missing.push("a durable park (spec.durableApproval, or the forceDurableGate entitlement)");
223
221
  if (missing.length === 0)
@@ -1,5 +1,6 @@
1
1
  import type { SessionStore } from "../session.js";
2
2
  export { runGuardChain, type GuardChainArgs, type GuardChainOutcome } from "./prepare-context-lane.js";
3
+ import type { LegWrites } from "./session-write-ledger.js";
3
4
  export { gatedCallIdOf, USAGE_WINDOW_REAP_MARGIN_MS } from "./park-commit.js";
4
5
  export { batchContextAt } from "./prepare-park-ask.js";
5
6
  export { compensateUnparkedPause } from "./prepare-suspend-saga.js";
@@ -115,7 +116,9 @@ runIdSink?: {
115
116
  runId?: string;
116
117
  },
117
118
  /** The Runner's lock on this leg's session, handed only when the spec named none: taken the moment the store mints the id. */
118
- holdSessionLock?: (sessionId: string) => Promise<void>): Promise<Prepared>;
119
+ holdSessionLock?: (sessionId: string) => Promise<void>,
120
+ /** This leg's view of the Runner's session write ledger (`session-write-ledger.ts`): the acquired storage writes through it. */
121
+ legWrites?: LegWrites): Promise<Prepared>;
119
122
  /** The entry IS the contract the notification lane's seat names ({@link PrepareTaskFn}, contracts.ts):
120
123
  * a signature change reds HERE, at the declaration, as `false` failing the `true` bound. IDENTITY, not assignability:
121
124
  * the parameter tuple (arity, optionality, each type) and the return type must be the SAME type in both directions —