@sema-agent/core 7.30.1 → 7.31.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (80) hide show
  1. package/CHANGELOG.md +284 -0
  2. package/dist/agents/cross-session-judge.d.ts +4 -12
  3. package/dist/agents/cross-session-judge.js +4 -11
  4. package/dist/agents/peer-session-drain.js +0 -5
  5. package/dist/agents/send-message-tool.js +1 -0
  6. package/dist/agents/subagent.d.ts +1 -0
  7. package/dist/agents/subagent.js +1 -0
  8. package/dist/core/ask-origin.d.ts +6 -0
  9. package/dist/core/ask-origin.js +10 -0
  10. package/dist/core/checkpoint-store.d.ts +14 -0
  11. package/dist/core/checkpoint-store.js +1 -0
  12. package/dist/core/engine-notice.d.ts +4 -0
  13. package/dist/core/governance-codes.d.ts +1 -1
  14. package/dist/core/governance-codes.js +4 -0
  15. package/dist/core/memory-engine/data-plane.d.ts +3 -1
  16. package/dist/core/memory-engine/data-plane.js +1 -1
  17. package/dist/core/memory-engine/delegation-settlement.d.ts +44 -3
  18. package/dist/core/memory-engine/delegation-settlement.js +38 -3
  19. package/dist/core/memory-engine/engine.d.ts +133 -27
  20. package/dist/core/memory-engine/engine.js +452 -151
  21. package/dist/core/memory-engine/entry-scan.d.ts +23 -0
  22. package/dist/core/memory-engine/entry-scan.js +55 -0
  23. package/dist/core/memory-engine/file-backend.d.ts +31 -23
  24. package/dist/core/memory-engine/file-backend.js +174 -100
  25. package/dist/core/memory-engine/layer-observations.d.ts +109 -0
  26. package/dist/core/memory-engine/layer-observations.js +148 -0
  27. package/dist/core/memory-engine/layout.d.ts +40 -4
  28. package/dist/core/memory-engine/layout.js +43 -7
  29. package/dist/core/memory-engine/types.d.ts +26 -2
  30. package/dist/core/parent-spec-seat.d.ts +4 -1
  31. package/dist/core/parent-spec-seat.js +2 -0
  32. package/dist/core/park-selfcheck.js +1 -0
  33. package/dist/core/runner/announce-once-ledger.d.ts +26 -0
  34. package/dist/core/runner/announce-once-ledger.js +37 -0
  35. package/dist/core/runner/contracts.d.ts +22 -5
  36. package/dist/core/runner/denial-limit-arms.d.ts +2 -2
  37. package/dist/core/runner/prepare-acquire-reconcile.d.ts +0 -4
  38. package/dist/core/runner/prepare-acquire-reconcile.js +1 -3
  39. package/dist/core/runner/prepare-file-history.d.ts +13 -15
  40. package/dist/core/runner/prepare-file-history.js +57 -43
  41. package/dist/core/runner/prepare-hands-readface.d.ts +2 -2
  42. package/dist/core/runner/prepare-listings.d.ts +3 -1
  43. package/dist/core/runner/prepare-listings.js +2 -2
  44. package/dist/core/runner/prepare-memory.js +16 -5
  45. package/dist/core/runner/prepare-park-ask.js +27 -16
  46. package/dist/core/runner/prepare-prompt-inputs.d.ts +8 -1
  47. package/dist/core/runner/prepare-prompt-inputs.js +5 -3
  48. package/dist/core/runner/prepare-task.js +7 -5
  49. package/dist/core/runner/prepare-wiring-manifest.d.ts +1 -0
  50. package/dist/core/runner/prepare-wiring-manifest.js +2 -1
  51. package/dist/core/runner/run-attachment-seats.js +1 -1
  52. package/dist/core/runner/run-leg.js +61 -52
  53. package/dist/core/runner/run-settle-and-teardown.js +4 -0
  54. package/dist/core/runner/run-turn-boundary.js +6 -6
  55. package/dist/core/runner/turn-attachments.d.ts +6 -6
  56. package/dist/core/runner-deps.d.ts +3 -1
  57. package/dist/core/secret-env.d.ts +12 -1
  58. package/dist/core/secret-env.js +27 -2
  59. package/dist/core/store-contracts/checkpoint-store-contract.d.ts +2 -0
  60. package/dist/core/store-contracts/checkpoint-store-contract.js +22 -0
  61. package/dist/core/task-result.d.ts +1 -1
  62. package/dist/core/task-spec.d.ts +17 -8
  63. package/dist/core/tool-spec.d.ts +1 -1
  64. package/dist/core/wiring-manifest.d.ts +10 -0
  65. package/dist/core/wiring-manifest.js +7 -1
  66. package/dist/engine/execution-env/node-execution-env.d.ts +6 -0
  67. package/dist/engine/execution-env/node-execution-env.js +15 -17
  68. package/dist/engine/harness/agent-harness.js +7 -5
  69. package/dist/engine/lsp/node-lsp-manager.d.ts +4 -0
  70. package/dist/engine/lsp/node-lsp-manager.js +10 -4
  71. package/dist/index.d.ts +1 -1
  72. package/dist/index.js +1 -1
  73. package/dist/tools/fs/shell-read-plan.d.ts +2 -2
  74. package/dist/tools/fs/shell-read-plan.js +15 -10
  75. package/dist/tools/fs/shell-read-word.d.ts +5 -4
  76. package/dist/tools/fs/shell-read-word.js +3 -2
  77. package/package.json +1 -1
  78. package/test/export-surface.snapshot.json +7 -1
  79. package/wire-changes/core@7.31.0.json +705 -0
  80. package/wire-changes/core@7.31.1.json +189 -0
@@ -176,11 +176,22 @@ export interface CommittedPause {
176
176
  scope: string;
177
177
  gatedCallId?: string;
178
178
  }
179
+ /** The session's date anchor as prepare read it off the branch. The system-prefix date is frozen per
180
+ * SEGMENT — from session start, or from the latest landed compaction, to the next one. */
181
+ export interface DateAnchorRead {
182
+ /** The anchor written for the current segment; undefined ⇒ the leg renders today and anchors it. */
183
+ current: {
184
+ prefixDate: string;
185
+ announcedDate: string;
186
+ } | undefined;
187
+ /** The current segment: the nearest compaction entry's id, null before the first compaction. */
188
+ segment: string | null;
189
+ }
179
190
  /**
180
191
  * One engine frame whose commit waits for the receipt of the message carrying it.
181
192
  *
182
193
  * @contract frames.commit_on_receipt — an engine-minted frame that rides a user message (the git-status
183
- * frame, an `agent_listing` or `skills_listing` frame) is recorded as announced ONLY when the run loop's
194
+ * frame, an `agent_listing`, `skills_listing` or `date_change` frame) is recorded as announced ONLY when the run loop's
184
195
  * `message_end` walk sees a persisted user entry whose text equals the carrying text it was registered
185
196
  * with; every receipt with that text settles on that one entry. Until then nothing is recorded — a
186
197
  * message that never reaches the transcript (a pre-append failure, a suppressed or undrained steer, a
@@ -193,7 +204,7 @@ export interface FrameReceipt {
193
204
  text: string;
194
205
  /** Which frame this receipt settles. A boundary collector must not render a listing whose receipt is
195
206
  * still pending (the frame is queued, not yet in the transcript). */
196
- source: "git_status" | "agent_listing" | "skills_listing";
207
+ source: "git_status" | "agent_listing" | "skills_listing" | "date_change";
197
208
  /** Runs inside the `message_end` walk (synchronous, never awaited): advances the run-local record
198
209
  * and parks any session write for the next serialization point. */
199
210
  commit: (entryId: string) => void;
@@ -344,7 +355,7 @@ export interface Prepared {
344
355
  * than a snapshot, so the throw-path backstop terminal reports the same ledger the ordinary
345
356
  * assembly would. Always present on Prepared; independent of whether a fileHistoryStore is wired. */
346
357
  editedFilesSnapshot: () => TaskResult["editedFiles"];
347
- /** The run's turn-start boundary seat (present iff a fileHistoryStore is wired and
358
+ /** The run's turn-start boundary seat (present iff the run records file history into a wired store and
348
359
  * the run mounts a real fs env). runtask calls begin() at the first committed user entry and
349
360
  * awaits settle() at the lease close + the finish tail. */
350
361
  fileHistoryBoundary?: FileHistoryBoundarySeat;
@@ -623,6 +634,9 @@ export interface Prepared {
623
634
  /** Merged injection across all planes (single-root sessions: identical to engine.inject(handle)). */
624
635
  inject: () => import("../memory-engine/engine.js").MemoryInjection;
625
636
  harvest: (phase?: "checkpoint" | "terminal") => Promise<HarvestReport | undefined>;
637
+ /** End this mount's session leg WITHOUT a harvest: the run that mounted it will never reach its
638
+ * terminal harvest (a prepare that failed after the mount). Never throws. */
639
+ release: () => void;
626
640
  /**
627
641
  * The session's capture opt-out face (the `TaskStream.optOutMemoryCapture`
628
642
  * verb's target; host-API only — no tool face reads it, §2.6). `flip` re-adjudicates the
@@ -1027,10 +1041,13 @@ export interface Prepared {
1027
1041
  /** The date-flip detector seam: `legDate` = the date frozen
1028
1042
  * into this leg's system prefix; `today()` = the boundary-time LOCAL date (user-zone-bound closure).
1029
1043
  * The run loop feeds both to the `date_change` attachment producer; the prefix itself never
1030
- * re-renders mid-leg (cache preservation, CC parity). Present only when the prompt carries a date. */
1044
+ * re-renders mid-leg (cache preservation, CC parity). Present only when the prompt carries a date.
1045
+ * `anchor` = the session's date anchor as prepare read it (undefined ⇒ the branch was unreadable
1046
+ * and the leg writes none); the settle lane persists it when it is new or the announced date moved. */
1031
1047
  dateChange?: {
1032
1048
  legDate: string;
1033
1049
  today: () => string;
1050
+ anchor: DateAnchorRead | undefined;
1034
1051
  };
1035
1052
  /** [A2] C-4 (design-A §4) — the loadProjectMemory snapshot's declared instruction sources
1036
1053
  * (`ProjectMemoryLoad.instructionSources`): the probe input AND the lane's per-path baseline
@@ -2066,7 +2083,7 @@ export interface RunInternals {
2066
2083
  * re-exposes the SAME triple on its own ctx, so every same-tree descendant of a root session — at
2067
2084
  * any depth — lands in the root session's scope (the fixed point), while a descendant on another
2068
2085
  * tree (worktree isolation, explicit `cwd`, a fresh per-task sandbox) becomes the root of its own
2069
- * subtree's history. Absent on a top-level run, on a run with no live history store, and on a
2086
+ * subtree's history. Absent on a top-level run, on a run that records no file history, and on a
2070
2087
  * tier-3 revival (the reviver's lineage says nothing about the revived row's tree).
2071
2088
  */
2072
2089
  fileHistoryLineage?: {
@@ -60,8 +60,8 @@ export declare function attachRebuiltDenialTrackers<T extends {
60
60
  * in headless mode"). Once per run leg (the first such fallback owns the terminal); the deny that
61
61
  * triggered it stands as that call's result. Minted once so every mint site — the main gate's seat, the
62
62
  * inherited-lane arms, and the durable resume station — stops the SAME leg through the SAME seat.
63
- * `abort` is the run's own abort (controller + harness), handed in because the harness is built after
64
- * the seat is.
63
+ * `abort` is the run's own abort (controller + harness — the one a cancel fires; handed in because the harness
64
+ * is built after the seat): the durable stop fires it before the resume leg's prompt, whose door reads it.
65
65
  *
66
66
  * TWO LANES, ONE SENTENCE. The synchronous lane reached no approver at all; the durable lane parked the
67
67
  * fallback, and the decide that came back named a settlement word the ONE table
@@ -75,10 +75,6 @@ export interface PrepareAcquireReconcileResult {
75
75
  * SUCCEEDS (a conflict-losing attempt re-wakes and re-derives the whole set from the fresh
76
76
  * branch), and stays empty for a fresh session / a clean one with nothing to reconcile. */
77
77
  wakeRecovered: RecoveredOrphan[];
78
- /** owned — §E18 `"before"` mode: the validated target's parentId — the new conversation leaf, and
79
- * the E19 file-anchor walk's starting point (consumed by the rewind-restore block after env
80
- * setup). `null` outside `"before"` mode. */
81
- resumeAtBeforeParentId: string | null;
82
78
  }
83
79
  /** The B-3 phase body — the acquire+reconcile slice, verbatim (see the module header for the contract). */
84
80
  export declare function prepareAcquireReconcile(input: PrepareAcquireReconcileInput): Promise<PrepareAcquireReconcileResult>;
@@ -32,7 +32,6 @@ export async function prepareAcquireReconcile(input) {
32
32
  let session;
33
33
  let conflictRef;
34
34
  let wakeRecovered = [];
35
- let resumeAtBeforeParentId = null;
36
35
  for (let attempt = 0;; attempt++) {
37
36
  try {
38
37
  acquired = await sessions.acquire(spec.sessionId, spec.requireExistingSession ? { requireExisting: true } : input.placement !== undefined ? { placement: input.placement } : undefined);
@@ -105,7 +104,6 @@ export async function prepareAcquireReconcile(input) {
105
104
  e.code = "resume_at.before_root_unsupported";
106
105
  throw e;
107
106
  }
108
- resumeAtBeforeParentId = entry.parentId;
109
107
  }
110
108
  await session.getStorage().setLeafId(spec.resumeAtMode === "before" ? entry.parentId : spec.resumeAt);
111
109
  }
@@ -123,5 +121,5 @@ export async function prepareAcquireReconcile(input) {
123
121
  throw err;
124
122
  }
125
123
  }
126
- return { acquired, session, conflictRef, wakeRecovered, resumeAtBeforeParentId };
124
+ return { acquired, session, conflictRef, wakeRecovered };
127
125
  }
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * PrepareTask's file-history phase, verbatim from the driver: the DV-14 first-touch
3
- * policy door, the `RunnerDeps.fileHistoryStore` bad-value door, the retired `rewindFiles` two-sense
3
+ * policy door, the `RunnerDeps.fileHistoryStore` / `fileHistoryCapture` and `TaskSpec.fileHistory` bad-value doors, the retired `rewindFiles` two-sense
4
4
  * split, the restoreFiles / resumeAt / rewindFilesTo combination gates, the run's history coordinates
5
5
  * (canonical root, recording scope, filesystem identity), the fork carry, the child-scope refusal, the
6
6
  * rewind RESTORE itself (anchor translation in "before" mode, the per-file ledger, partial-restore
@@ -23,12 +23,12 @@ import type { StoredSession } from "../../engine/session/session.js";
23
23
  import type { RunnerDeps, TaskResult, TaskSpec, TrackFileEditHook } from "../types.js";
24
24
  import type { FileHistoryBoundarySeat, RunInternals } from "./contracts.js";
25
25
  export interface PrepareFileHistoryInput {
26
- /** borrowed-readonly — the deployment seats this phase reads: the first-touch failure policy and the
27
- * history store (both validated HERE, once), the incident sink, and the retired-epoch diagnostic probe. */
28
- deps: Pick<RunnerDeps, "onTrackFailure" | "fileHistoryStore" | "onError">;
29
- /** borrowed-readonly — the REBOUND spec's rewind request fields (the retired `rewindFiles` spelling is
30
- * read off the same object as an unknown key) and the caller's cancel signal for the restore. */
31
- spec: Pick<TaskSpec, "resumeAt" | "resumeAtMode" | "restoreFiles" | "rewindFilesTo" | "acceptPartialRestore" | "signal">;
26
+ /** borrowed-readonly — the deployment seats this phase reads: the first-touch failure policy, the history
27
+ * store and its capture switch (all validated HERE, once), the incident sink, and the retired-epoch diagnostic probe. */
28
+ deps: Pick<RunnerDeps, "onTrackFailure" | "fileHistoryStore" | "fileHistoryCapture" | "onError">;
29
+ /** borrowed-readonly — the REBOUND spec's rewind request fields and off-switch word (the retired `rewindFiles`
30
+ * spelling is read off the same object as an unknown key) and the caller's cancel signal for the restore. */
31
+ spec: Pick<TaskSpec, "resumeAt" | "resumeAtMode" | "restoreFiles" | "rewindFilesTo" | "acceptPartialRestore" | "signal" | "fileHistory">;
32
32
  /** borrowed-readonly — the trusted lineage a delegating parent handed this run (absent for a root run
33
33
  * and for a host's own fork), read for the recording-scope decision only. */
34
34
  internals: Pick<RunInternals, "fileHistoryLineage"> | undefined;
@@ -41,19 +41,15 @@ export interface PrepareFileHistoryInput {
41
41
  handsEnabled: boolean;
42
42
  /** borrowed-readonly — the acquired session id: the run's OWN history scope and the restore's session key. */
43
43
  sessionId: string;
44
- /** borrowed-readonly — the acquired session, read for the fork lineage (`getMetadata`) and the "before"
45
- * anchor walk (`getEntry`); never written here. */
46
- session: Pick<StoredSession, "getEntry" | "getMetadata">;
47
- /** borrowed-readonly — §E18 "before" mode: the validated target's parentId (the acquire phase's output);
48
- * `null` outside "before" mode. */
49
- resumeAtBeforeParentId: string | null;
44
+ /** borrowed-readonly — the acquired session, read for the fork lineage (`getMetadata`); never written here. */
45
+ session: Pick<StoredSession, "getMetadata">;
50
46
  /** borrowed-readonly — the run's abort controller (driver-minted); only its `signal` is read, composed
51
47
  * with `spec.signal` for the restore. */
52
48
  abortController: AbortController;
53
49
  }
54
50
  export interface PrepareFileHistoryResult {
55
51
  /** borrowed-readonly — the hook the hands band calls before every mutation lane; `undefined` when the
56
- * machinery is not live (no store, or no real fs env). */
52
+ * run does not record (no store, capture turned off by any layer, or no real fs env). */
57
53
  trackFileEdit: TrackFileEditHook | undefined;
58
54
  /** borrowed-readonly — the turn-start boundary seat runtask drives; `undefined` when not live or when
59
55
  * this run records into its ROOT session's scope (a child mints no boundaries). */
@@ -64,8 +60,10 @@ export interface PrepareFileHistoryResult {
64
60
  /** borrowed-mutable — the non-fatal rewind disclosures (`TaskResult.rewindNotes`). Mutation owners after
65
61
  * this call: none — every push happens inside this phase; the driver only reads it into `Prepared`. */
66
62
  rewindNotes: NonNullable<TaskResult["rewindNotes"]>;
67
- /** borrowed-readonly — whether the file-history machinery is LIVE for this run (store wired AND hands). */
63
+ /** borrowed-readonly — whether this run RECORDS file history (store wired, no layer turned capture off, AND hands); restore reads the store seat. */
68
64
  fileHistoryEnabled: boolean;
65
+ /** borrowed-readonly — the manifest's word: `"on"` iff live, else `"disabled"` (whichever layer or missing half). */
66
+ fileHistoryFact: "on" | "disabled";
69
67
  /** borrowed-readonly — the scope this run's first-touch records land in (own session, or the root
70
68
  * session's for a same-tree delegated child). */
71
69
  historyScope: string;
@@ -2,7 +2,7 @@ import { NodeExecutionEnv } from "../../engine/execution-env/node-execution-env.
2
2
  import { isRemoteExecutionEnv } from "../remote-env.js";
3
3
  import { resolveTrackKey } from "../file-history-store.js";
4
4
  export async function prepareFileHistory(input) {
5
- const { deps, spec, internals, executionEnv, taskRootFinal, handsEnabled, sessionId, session, resumeAtBeforeParentId, abortController } = input;
5
+ const { deps, spec, internals, executionEnv, taskRootFinal, handsEnabled, sessionId, session, abortController } = input;
6
6
  const rewindNotes = [];
7
7
  const onTrackFailure = deps.onTrackFailure === undefined ? "refuse" : deps.onTrackFailure;
8
8
  if (onTrackFailure !== "refuse" && onTrackFailure !== "proceed-unprotected") {
@@ -11,16 +11,20 @@ export async function prepareFileHistory(input) {
11
11
  throw e;
12
12
  }
13
13
  const wiredSeat = resolveWiredFileHistoryStore(deps.fileHistoryStore);
14
- const fileHistoryStore = wiredSeat === "disabled" ? undefined : wiredSeat;
14
+ const runDeclaredOff = resolveRunFileHistoryWord(spec.fileHistory);
15
+ const captureOff = resolveFileHistoryCaptureWord(deps.fileHistoryCapture, wiredSeat);
16
+ const declaredOff = wiredSeat === "disabled" || runDeclaredOff;
17
+ const fileHistoryStore = declaredOff ? undefined : wiredSeat;
18
+ const captureStore = captureOff ? undefined : fileHistoryStore;
15
19
  const legacyRewindFiles = spec.rewindFiles;
16
20
  if (legacyRewindFiles === true) {
17
21
  if (spec.resumeAt !== undefined) {
18
- const e = new Error(`rewind-files: TaskSpec.rewindFiles is retired (design/381 — rewind now converges the per-edited-file history, not a whole-tree snapshot). This request pairs it with resumeAt, i.e. the RESTORE sense: set restoreFiles: true instead. (The capture sense needs no request anymore — first-touch tracking is always on when a RunnerDeps.fileHistoryStore is wired.)`);
22
+ const e = new Error(`rewind-files: TaskSpec.rewindFiles is retired (design/381 — rewind now converges the per-edited-file history, not a whole-tree snapshot). This request pairs it with resumeAt, i.e. the RESTORE sense: set restoreFiles: true instead. (The capture sense needs no request anymore — first-touch tracking is on whenever a RunnerDeps.fileHistoryStore is wired and neither RunnerDeps.fileHistoryCapture nor TaskSpec.fileHistory turns it off.)`);
19
23
  e.code = "rewind.rewind_files_retired";
20
24
  throw e;
21
25
  }
22
26
  try {
23
- deps.onError?.(new Error(`TaskSpec.rewindFiles (capture sense) is retired and was ignored: per-edited-file first-touch tracking is always on when RunnerDeps.fileHistoryStore is wired, so a per-turn capture request has nothing left to ask for. Drop the field; the restore sense moved to TaskSpec.restoreFiles.`), { phase: "config", sessionId });
27
+ deps.onError?.(new Error(`TaskSpec.rewindFiles (capture sense) is retired and was ignored: per-edited-file first-touch tracking is on whenever RunnerDeps.fileHistoryStore is wired and neither RunnerDeps.fileHistoryCapture nor TaskSpec.fileHistory turns it off, so a per-turn capture request has nothing left to ask for. Drop the field; the restore sense moved to TaskSpec.restoreFiles.`), { phase: "config", sessionId });
24
28
  }
25
29
  catch {
26
30
  }
@@ -35,7 +39,7 @@ export async function prepareFileHistory(input) {
35
39
  e.code = "rewind.conflicting_targets";
36
40
  throw e;
37
41
  }
38
- let rewindTarget = spec.resumeAt !== undefined ? (spec.restoreFiles === true ? spec.resumeAt : undefined) : spec.rewindFilesTo;
42
+ const rewindTarget = spec.resumeAt !== undefined ? (spec.restoreFiles === true ? spec.resumeAt : undefined) : spec.rewindFilesTo;
39
43
  const rewindBefore = rewindTarget !== undefined && spec.resumeAt !== undefined && spec.resumeAtMode === "before";
40
44
  if (spec.resumeAt !== undefined && spec.restoreFiles !== true) {
41
45
  rewindNotes.push({
@@ -43,9 +47,9 @@ export async function prepareFileHistory(input) {
43
47
  message: `the conversation was branched at entry "${spec.resumeAt}" but the working tree was NOT rewound — this task set resumeAt without restoreFiles, so files remain at their current state`,
44
48
  });
45
49
  }
46
- const fileHistoryEnabled = fileHistoryStore !== undefined && handsEnabled;
50
+ const fileHistoryEnabled = captureStore !== undefined && handsEnabled;
47
51
  const livenessArm = fileHistoryStore === undefined ? "file-history-no-store" : "file-history-no-env";
48
- if (!fileHistoryEnabled && wiredSeat !== "disabled" && (handsEnabled || fileHistoryStore !== undefined) && deps.onError !== undefined && !toldAlready(deps.onError, livenessArm)) {
52
+ if (!fileHistoryEnabled && !declaredOff && !captureOff && (handsEnabled || fileHistoryStore !== undefined) && deps.onError !== undefined && !toldAlready(deps.onError, livenessArm)) {
49
53
  const reason = livenessArm === "file-history-no-store"
50
54
  ? `this deployment wired no RunnerDeps.fileHistoryStore, so NOTHING is recorded: no first-touch snapshot before an edit, no turn boundary, and a later restoreFiles / rewindFilesTo has nothing to restore from and will refuse (rewind.store_unconfigured). Supply a store, or accept an unrecoverable working tree knowingly (fileHistoryStore: "disabled" says so and silences this line)`
51
55
  : `RunnerDeps.fileHistoryStore is wired but this run mounts no filesystem-capable ExecutionEnv, so the store is INERT: there is no working tree to snapshot and nothing will ever be written to it. Wire RunnerDeps.executionEnv, or drop the store from this deployment shape`;
@@ -55,14 +59,18 @@ export async function prepareFileHistory(input) {
55
59
  catch {
56
60
  }
57
61
  }
58
- const { historyRoot, historyScope, historyFs } = await resolveFileHistoryCoordinates(fileHistoryEnabled, executionEnv, taskRootFinal, internals?.fileHistoryLineage, sessionId);
59
- if (fileHistoryStore !== undefined && internals?.fileHistoryLineage === undefined)
60
- await adoptForkedFileHistory(fileHistoryStore, session, sessionId, deps.onError);
62
+ const { historyRoot, historyScope, historyFs } = await resolveFileHistoryCoordinates(fileHistoryStore !== undefined && handsEnabled, executionEnv, taskRootFinal, internals?.fileHistoryLineage, sessionId);
63
+ if (captureStore !== undefined && internals?.fileHistoryLineage === undefined)
64
+ await adoptForkedFileHistory(captureStore, session, sessionId, deps.onError);
61
65
  if (rewindTarget !== undefined && historyScope !== sessionId)
62
66
  throw childScopeRewindRefusal(historyScope, rewindTarget);
63
67
  if (fileHistoryStore === undefined) {
68
+ if (rewindTarget !== undefined && runDeclaredOff)
69
+ throw captureDisabledRefusal(rewindTarget);
64
70
  if (rewindTarget !== undefined) {
65
- const e = new Error(`rewind-files: restoring files to entry "${rewindTarget}" requires a history backend, but this deployment wired no RunnerDeps.fileHistoryStore — no file history was ever recorded, so the files were NOT rewound`);
71
+ const e = new Error(wiredSeat === "disabled"
72
+ ? `rewind-files: restoring files to entry "${rewindTarget}" requires a history backend, but this deployment declared file history disabled (RunnerDeps.fileHistoryStore: "disabled"), so there is no history to restore — the files were NOT rewound`
73
+ : `rewind-files: restoring files to entry "${rewindTarget}" requires a history backend, but this deployment wired no RunnerDeps.fileHistoryStore — no file history was ever recorded, so the files were NOT rewound`);
66
74
  e.code = "rewind.store_unconfigured";
67
75
  throw e;
68
76
  }
@@ -78,39 +86,17 @@ export async function prepareFileHistory(input) {
78
86
  let restoredFilePaths = [];
79
87
  if (rewindTarget !== undefined && fileHistoryStore !== undefined && handsEnabled) {
80
88
  const historyStore = fileHistoryStore;
81
- if (rewindBefore) {
82
- let anchor;
83
- if (await historyStore.canRestore(sessionId, rewindTarget)) {
84
- anchor = rewindTarget;
85
- }
86
- else {
87
- const walked = new Set();
88
- let cursor = resumeAtBeforeParentId;
89
- while (cursor !== null && !walked.has(cursor)) {
90
- walked.add(cursor);
91
- if (await historyStore.canRestore(sessionId, cursor)) {
92
- anchor = cursor;
93
- break;
94
- }
95
- cursor = (await session.getEntry(cursor))?.parentId ?? null;
96
- }
97
- }
98
- if (anchor === undefined) {
99
- const e = new Error(`rewind-files: no file-history boundary exists at or above the "before" branch point on session "${sessionId}" — cannot restore files consistent with the rewound context. ${rewindRefusalTail("at-or-above")}`);
100
- e.code = "rewind_snapshot.unresolvable";
101
- throw e;
102
- }
103
- rewindTarget = anchor;
104
- }
89
+ if (rewindBefore && !(await historyStore.canRestore(sessionId, rewindTarget)))
90
+ throw beforeWithoutBoundaryRefusal(rewindTarget, sessionId);
105
91
  const restoreRoot = historyRoot;
106
92
  const restoreSignal = spec.signal ? AbortSignal.any([abortController.signal, spec.signal]) : abortController.signal;
107
93
  const restored = await historyStore.restore(sessionId, rewindTarget, executionEnv, restoreRoot, restoreSignal);
108
94
  const ledgerLine = (r) => `applied ${r.filesChanged.length}, identical ${r.identicalSkipped.length}, refused ${r.refused.length}${r.refused.length > 0 ? ` (${r.refused.map((f) => `${f.path}: ${f.reason}`).join("; ")})` : ""}, failed ${r.failed.length}${r.failed.length > 0 ? ` (${r.failed.map((f) => `${f.path}: ${f.reason}`).join("; ")})` : ""}`;
109
95
  if (restored.outcome === "failed") {
110
96
  if (restored.error?.code === "not_found") {
111
- const e = new Error(rewindBefore
112
- ? `rewind-files: the resolved "before" history anchor "${rewindTarget}" disappeared before restore — files were NOT rewound`
113
- : `rewind-files: no file-history boundary exists for entry "${rewindTarget}" on session "${sessionId}" — the working tree was NOT rewound. ${rewindRefusalTail("keyed")}`);
97
+ if (rewindBefore)
98
+ throw beforeWithoutBoundaryRefusal(rewindTarget, sessionId);
99
+ const e = new Error(`rewind-files: no file-history boundary exists for entry "${rewindTarget}" on session "${sessionId}" — the working tree was NOT rewound. ${rewindRefusalTail()}`);
114
100
  e.code = "rewind_snapshot.unresolvable";
115
101
  throw e;
116
102
  }
@@ -170,7 +156,8 @@ export async function prepareFileHistory(input) {
170
156
  const fileHistoryBoundary = fileHistoryEnabled && historyScope === sessionId
171
157
  ? createFileHistoryBoundarySeat({ store: fileHistoryStore, sessionId, env: executionEnv, root: historyRoot, onError: deps.onError })
172
158
  : undefined;
173
- return { trackFileEdit, fileHistoryBoundary, restoredFilePaths, rewindNotes, fileHistoryEnabled, historyScope, historyRoot, historyFs };
159
+ const fileHistoryFact = fileHistoryEnabled ? "on" : "disabled";
160
+ return { trackFileEdit, fileHistoryBoundary, restoredFilePaths, rewindNotes, fileHistoryEnabled, fileHistoryFact, historyScope, historyRoot, historyFs };
174
161
  }
175
162
  function restoredAbsPathsOf(restored, root) {
176
163
  const out = [];
@@ -203,10 +190,37 @@ function resolveWiredFileHistoryStore(candidate) {
203
190
  }
204
191
  return candidate;
205
192
  }
206
- function rewindRefusalTail(form) {
207
- return form === "keyed"
208
- ? `Boundaries are keyed by a turn's INITIAL entry and exist only for turns that ran with a RunnerDeps.fileHistoryStore wired; a boundary from the retired whole-tree snapshot era is not restorable by this seat, and reaped boundaries have none.`
209
- : `Either the boundary does not exist (its turns ran without a RunnerDeps.fileHistoryStore, or it was reaped), or it was produced in the retired whole-tree snapshot era, which this engine's per-edited-file seat does not restore.`;
193
+ function resolveRunFileHistoryWord(word) {
194
+ if (word === undefined || word === "disabled")
195
+ return word === "disabled";
196
+ const e = new Error(`rewind-files: TaskSpec.fileHistory holds ${shownWord(word)} — the only value is "disabled"; omit the key to leave the deployment's setting in force`);
197
+ e.code = "rewind.invalid_spec";
198
+ throw e;
199
+ }
200
+ function resolveFileHistoryCaptureWord(word, wiredSeat) {
201
+ if (word === undefined || (word === "off" && wiredSeat !== undefined && wiredSeat !== "disabled"))
202
+ return word === "off";
203
+ const e = new Error(word === "off"
204
+ ? `RunnerDeps.fileHistoryCapture: "off" requires a wired RunnerDeps.fileHistoryStore — nothing to switch off; to drop history entirely write fileHistoryStore: "disabled"`
205
+ : `RunnerDeps.fileHistoryCapture holds ${shownWord(word)} — the only value is "off"; omit the key to record whenever a fileHistoryStore is wired`);
206
+ e.code = "config.invalid_file_history_capture";
207
+ throw e;
208
+ }
209
+ function shownWord(word) {
210
+ return typeof word === "string" ? JSON.stringify(word.slice(0, 40)) : word === null || typeof word === "boolean" || typeof word === "number" ? String(word) : `a ${typeof word}`;
211
+ }
212
+ function captureDisabledRefusal(target) {
213
+ const e = new Error(`rewind-files: this run set TaskSpec.fileHistory: "disabled", so it neither records nor restores file history — restoring files to entry "${target}" was refused and the files were NOT rewound`);
214
+ e.code = "rewind.capture_disabled";
215
+ return e;
216
+ }
217
+ function rewindRefusalTail() {
218
+ return `Boundaries are keyed by a turn's INITIAL entry and exist only for turns that recorded history (a RunnerDeps.fileHistoryStore wired, capture on, and the run's file history not disabled); a boundary from the retired whole-tree snapshot era is not restorable by this seat, and reaped boundaries have none.`;
219
+ }
220
+ function beforeWithoutBoundaryRefusal(target, sessionId) {
221
+ const e = new Error(`rewind-files: entry "${target}" has no file-history boundary on session "${sessionId}" — a before-restore needs the target's own boundary and never crosses into an earlier turn, so the working tree was NOT rewound. ${rewindRefusalTail()}`);
222
+ e.code = "rewind_snapshot.unresolvable";
223
+ return e;
210
224
  }
211
225
  const FILE_HISTORY_BOUNDARY_TIMEOUT_MS = 30_000;
212
226
  const FILE_HISTORY_LIVENESS_MEMO = new WeakMap();
@@ -218,8 +218,8 @@ export interface PrepareHandsReadFaceInput {
218
218
  * band's Read/notebook/PDF trailer outlets bump keys on it at tool-execute time (the driver owns
219
219
  * the object and folds it into result stats). */
220
220
  reminderDisclosureCounts: import("../reminder-disclosure.js").ReminderDisclosureCounts;
221
- /** borrowed-readonly — the first-touch history hook prepare built (present iff a fileHistoryStore is
222
- * wired and the run mounts a real fs env); threaded to the write band's trackFileEdit seat. */
221
+ /** borrowed-readonly — the first-touch history hook prepare built (present iff the run records file history into a
222
+ * wired store and mounts a real fs env); threaded to the write band's trackFileEdit seat. */
223
223
  trackFileEdit?: import("../types.js").TrackFileEditHook;
224
224
  /** borrowed-readonly — the mutation lane's LANDED observation seat (the accumulator behind
225
225
  * `TaskResult.editedFiles`); threaded to the write band's onFileEdited seat. Unlike
@@ -15,7 +15,7 @@
15
15
  */
16
16
  import type { AgentTool } from "../../internal/harness.js";
17
17
  import type { TaskSpec } from "../types.js";
18
- import type { AnnounceOnceSink, Prepared, PrepareResume, ToolFaceSnapshot } from "./contracts.js";
18
+ import type { AnnounceOnceSink, DateAnchorRead, Prepared, PrepareResume, ToolFaceSnapshot } from "./contracts.js";
19
19
  import type { AnnouncedListingSnapshot } from "../../internal/harness.js";
20
20
  import type { ResolvedSkillListingBudget } from "../skill-listing-budget.js";
21
21
  export interface PrepareListingsInput {
@@ -65,6 +65,8 @@ export interface PrepareListingsInput {
65
65
  tzValid: boolean;
66
66
  /** borrowed-readonly — the client's declared zone (used only when `tzValid`). */
67
67
  userTz: string | undefined;
68
+ /** borrowed-readonly — the session's date anchor as the prompt-inputs phase read it (rides the seam). */
69
+ dateAnchor: DateAnchorRead | undefined;
68
70
  }
69
71
  export interface PrepareListingsResult {
70
72
  /** owned — the agent-listing announce projection (`Prepared.agentListing`): the roster off the
@@ -2,7 +2,7 @@ import { formatLocalDate } from "../../prompts/default.js";
2
2
  import { SKILL_TOOL_NAME } from "./synthetic-tools.js";
3
3
  import { renderAgentListingDelta } from "./turn-attachments.js";
4
4
  export function prepareListings(input) {
5
- const { spec, tools, onceLedger, sessionId, toolFaceSnapshot, activeTools, resume, skillsListing, listingBudget, listingRideRef, renderWithDate, envFacts, tzValid, userTz } = input;
5
+ const { spec, tools, onceLedger, sessionId, toolFaceSnapshot, activeTools, resume, skillsListing, listingBudget, listingRideRef, renderWithDate, envFacts, tzValid, userTz, dateAnchor } = input;
6
6
  if (spec.agents !== undefined && spec.agents.length > 0) {
7
7
  const known = new Set(tools.flatMap((t) => [t.name, ...(t.aliases ?? [])]));
8
8
  for (const def of spec.agents) {
@@ -72,7 +72,7 @@ export function prepareListings(input) {
72
72
  };
73
73
  }
74
74
  const dateChange = renderWithDate
75
- ? { legDate: envFacts.date, today: () => formatLocalDate(new Date(), tzValid ? userTz : undefined) }
75
+ ? { legDate: envFacts.date, today: () => formatLocalDate(new Date(), tzValid ? userTz : undefined), anchor: dateAnchor }
76
76
  : undefined;
77
77
  return { agentListing, announcedListingsRef, dateChange };
78
78
  }
@@ -81,6 +81,7 @@ export async function prepareMemory(input) {
81
81
  let captureIndeterminate = false;
82
82
  let captureLineageIndeterminate = false;
83
83
  let memoryEngineSession;
84
+ const openedLegs = [];
84
85
  let memoryTools;
85
86
  let writeChannelOpen = false;
86
87
  let memoryMarkedEntriesServed = false;
@@ -385,6 +386,7 @@ export async function prepareMemory(input) {
385
386
  sessionId,
386
387
  ...(m.write === null && shadowNames !== undefined && shadowNames.size > 0 ? { shadowNames } : {}),
387
388
  });
389
+ openedLegs.push(() => void m.engine.endSessionLeg(handle, sessionId));
388
390
  mounted.push(handle);
389
391
  materializedResidue.push(...planeScopes(m.scopes, m.write));
390
392
  if (m.write !== null) {
@@ -425,10 +427,10 @@ export async function prepareMemory(input) {
425
427
  committedHeaders: () => personalEngine.listCommittedHeaders(planeScopes(p.personal, p.writePlane === "personal" ? memorySpec.writeScope : null)),
426
428
  },
427
429
  ];
428
- harvestBoth = async () => {
430
+ harvestBoth = async (phase) => {
429
431
  const writeFirst = writeIsPersonal ? [personalEngine, personalHandle] : [projectEngine, projectHandle];
430
432
  const readOther = writeIsPersonal ? [projectEngine, projectHandle] : [personalEngine, personalHandle];
431
- const writeReport = await writeFirst[0].harvest(writeFirst[1], { ...pollutedOpts(writeFirst[0]), sessionId, ...(await admitNothingOptsNow()) });
433
+ const writeReport = await writeFirst[0].harvest(writeFirst[1], { ...pollutedOpts(writeFirst[0]), sessionId, ...(await admitNothingOptsNow()), phase });
432
434
  let readReport;
433
435
  let readFailure;
434
436
  try {
@@ -448,12 +450,13 @@ export async function prepareMemory(input) {
448
450
  const personalEngine = personal.engine;
449
451
  await armCaptureOptOut(personalEngine);
450
452
  const handle = await personalEngine.materialize(memorySpec.scopes, memorySpec.writeScope, { adoptionRestricted: adoptionRestricted || captureOptedOut || captureIndeterminate, sessionId });
453
+ openedLegs.push(() => void personalEngine.endSessionLeg(handle, sessionId));
451
454
  materializedResidue.push(...planeScopes(memorySpec.scopes, memorySpec.writeScope));
452
455
  memoryMarkedEntriesServed = handle.markedEntriesPresent === true;
453
456
  writeEngine = personalEngine;
454
457
  writeHandle = handle;
455
458
  injectFn = () => personalEngine.inject(handle, { writeToolMounted: writeChannelOpen, reminderMark: input.reminderMark });
456
- harvestBoth = async () => personalEngine.harvest(handle, { ...pollutedOpts(personalEngine), sessionId, ...(await admitNothingOptsNow()) });
459
+ harvestBoth = async (phase) => personalEngine.harvest(handle, { ...pollutedOpts(personalEngine), sessionId, ...(await admitNothingOptsNow()), phase });
457
460
  toolPlanes = [
458
461
  {
459
462
  backend: retrievalBackend(personal.backend, adoptionRestricted || captureOptedOut || captureIndeterminate || memorySpec.writeScope === null),
@@ -477,12 +480,13 @@ export async function prepareMemory(input) {
477
480
  });
478
481
  await armCaptureOptOut(engine);
479
482
  const handle = await engine.materialize(memorySpec.scopes, memorySpec.writeScope, { adoptionRestricted: adoptionRestricted || captureOptedOut || captureIndeterminate, sessionId });
483
+ openedLegs.push(() => void engine.endSessionLeg(handle, sessionId));
480
484
  materializedResidue.push(...planeScopes(memorySpec.scopes, memorySpec.writeScope));
481
485
  memoryMarkedEntriesServed = handle.markedEntriesPresent === true;
482
486
  writeEngine = engine;
483
487
  writeHandle = handle;
484
488
  injectFn = () => engine.inject(handle, { writeToolMounted: writeChannelOpen, reminderMark: input.reminderMark });
485
- harvestBoth = async () => engine.harvest(handle, { ...pollutedOpts(engine), sessionId, ...(await admitNothingOptsNow()) });
489
+ harvestBoth = async (phase) => engine.harvest(handle, { ...pollutedOpts(engine), sessionId, ...(await admitNothingOptsNow()), phase });
486
490
  toolPlanes = [
487
491
  {
488
492
  backend: retrievalBackend(backend, adoptionRestricted || captureOptedOut || captureIndeterminate || memorySpec.writeScope === null),
@@ -574,7 +578,7 @@ export async function prepareMemory(input) {
574
578
  };
575
579
  const harvestSafe = async (phase = "terminal") => {
576
580
  try {
577
- const report = await harvestBoth();
581
+ const report = await harvestBoth(phase);
578
582
  if (!report.ok && report.incident) {
579
583
  const named = report.reportId !== undefined ? `${report.incident.kind}, report ${report.reportId}` : report.incident.kind;
580
584
  deps.onError?.(new Error(`memory harvest refused (${named}): ${report.incident.detail}`), { phase: "memory", sessionId });
@@ -615,6 +619,10 @@ export async function prepareMemory(input) {
615
619
  handle: writeHandle,
616
620
  inject: injectFn,
617
621
  harvest: harvestSafe,
622
+ release: () => {
623
+ for (const end of openedLegs)
624
+ end();
625
+ },
618
626
  captureOptOut: {
619
627
  optedOut: () => {
620
628
  if (captureOptedOut)
@@ -733,6 +741,9 @@ export async function prepareMemory(input) {
733
741
  memoryTools = createMemoryEngineTools({ planes: toolPlanes, sessionPollution: () => writeEngine.sessionPollution(sessionId), provenance: input.memoryProvenance, writeScope: memorySpec.writeScope });
734
742
  }
735
743
  catch (err) {
744
+ if (memoryEngineSession === undefined)
745
+ for (const end of openedLegs)
746
+ end();
736
747
  if (typeof err.code === "string" &&
737
748
  (err.code.startsWith("config.memory_scope") ||
738
749
  err.code.startsWith("config.memory_project") ||
@@ -6,6 +6,7 @@ import { boundInputHashOf } from "../canonical-json.js";
6
6
  import { buildRiskDescriptor, checkpointVersionFor, debitLedger, encodeAtFidelity, mintCheckpointId, mintCheckpointToken, resolveDeclaredFidelity, samePlainValue, } from "../checkpoint-store.js";
7
7
  import { pauseOf } from "../pause-registry.js";
8
8
  import { askCarryRowMembers } from "../hooks.js";
9
+ import { askMandateMember, ownedClearanceFacts } from "../ask-origin.js";
9
10
  import { defaultTaskRegistry } from "../task-registry.js";
10
11
  import { advertisedWritableRootsSeat, isLiveApproverSeat, carriesBidiControls, describeThrown, refuseOutOfContractDecision, tryCloneArgs } from "../tool-policy.js";
11
12
  import { InMemoryToolResultStore, ScopedToolResultStore, isVolatileOffloadStore } from "../tool-result-store.js";
@@ -21,6 +22,9 @@ class ParkRefusal extends Error {
21
22
  this.name = "ParkRefusal";
22
23
  }
23
24
  }
25
+ const wordlessDemand = (toolName) => {
26
+ throw new Error(`a real-approval demand for "${toolName}" reached the park with no origin word — the origin stamp precedes every park`);
27
+ };
24
28
  export function prepareParkAsk(input) {
25
29
  const { askLane, saga, spec, deps, sessionId, checkpointStore, toolRosterDeltas, parkLaneArmed, contentAskRoutable, liveQuestionFace, mountedQuestionTool, contentAskBindings, lateStrandedAnswers, discloseStrandedAnswers, onAsk, runtimeCaps, inheritedUnavailableAsks, basePolicyForResumeEdit, budgetSnapshot, handsCwdRef, advertisedWritableDirs, offloadStore, ownedEnv, incompleteSuspendAdapter, session, sessions, suspendChainBase, maxSuspends, remoteEnvFailures, shellGatedTools, effectiveShellGate, durableApproval, priorLedger, liveSpendRef, resourceTotal, faceCheckpointState, f012CheckpointState, orgAdmissionCheckpointState, ruleOffersOf, now, humanReviewRef, abortController, harness, pausedRef } = input;
26
30
  if (askLane === undefined || saga === undefined)
@@ -148,6 +152,7 @@ export function prepareParkAsk(input) {
148
152
  throw new ParkRefusal(`the arguments of "${req.toolName}" change shape when stored by this checkpoint backend, and no policy is wired to re-adjudicate the stored shape ` +
149
153
  `(no basePolicyForResumeEdit override and no caller toolPolicy) — refusing to file a row whose executable form nothing adjudicated`);
150
154
  }
155
+ let storedShapeFacts;
151
156
  if (projectionMovedArgs && basePolicyForResumeEdit !== undefined) {
152
157
  const presented = encodeAtFidelity(backendFidelity, parkedArgs);
153
158
  if (!presented.ok) {
@@ -177,7 +182,10 @@ export function prepareParkAsk(input) {
177
182
  `${reprojected.action === "deny" && reprojected.message ? `: ${reprojected.message}` : ""} — ` +
178
183
  `refusing to file a row whose executable form nothing adjudicated`);
179
184
  }
185
+ if (reprojected.action === "ask")
186
+ storedShapeFacts = ownedClearanceFacts(reprojected);
180
187
  }
188
+ const rowRealApproval = realApproval ?? (storedShapeFacts?.requiresRealApproval === true ? { origin: carry?.origin ?? wordlessDemand(req.toolName) } : undefined);
181
189
  if (offloadStore && isVolatileOffloadStore(offloadStore) && !offloadStore.isEmpty()) {
182
190
  throw new ParkRefusal("durable suspend requires a durable toolResultStore (the default InMemoryToolResultStore " +
183
191
  "holds offloaded results that are lost across a cross-replica resume); inject " +
@@ -237,15 +245,15 @@ export function prepareParkAsk(input) {
237
245
  shellGateDoctrine: effectiveShellGate,
238
246
  });
239
247
  gate =
240
- safety !== undefined || realApproval !== undefined
248
+ safety !== undefined || rowRealApproval !== undefined
241
249
  ? {
242
250
  kind: "irreversible_ask",
243
251
  reason: safety !== undefined
244
252
  ? `human approval required before safety-tightened tool "${req.toolName}"`
245
- : `real human approval required for tool "${req.toolName}" (non-budgetable: ${realApproval.origin})`,
253
+ : `real human approval required for tool "${req.toolName}" (non-budgetable: ${rowRealApproval.origin})`,
246
254
  toolName: req.toolName,
247
255
  ...(safety !== undefined ? { safetyAxis: safety } : {}),
248
- ...(realApproval !== undefined ? { realApproval } : {}),
256
+ ...(rowRealApproval !== undefined ? { realApproval: rowRealApproval } : {}),
249
257
  riskDescriptor,
250
258
  }
251
259
  : durableApproval
@@ -264,15 +272,26 @@ export function prepareParkAsk(input) {
264
272
  const scope = durableApproval?.scope || checkpointScopeOf({ principal: spec.principal });
265
273
  const ttlMs = sanitizedTtlMs(durableApproval?.ttlMs);
266
274
  const checkpointState = serializeCheckpointState(remoteHandle, inFlightSpendMicroUsd());
267
- if (realApproval !== undefined && checkpointState.readFace !== undefined) {
275
+ if (rowRealApproval !== undefined && checkpointState.readFace !== undefined) {
268
276
  checkpointState.readFace = { ...checkpointState.readFace, realApproval: true };
269
277
  }
270
278
  const approvalLedger = debitLedger(priorLedger, liveSpendRef.get?.() ?? { costMicroUsd: 0, tokens: 0, turns: 0, walltimeMs: 0 }, resourceTotal, { countSlice: false });
279
+ const rowMatchedAskRule = matchedAskRule ?? storedShapeFacts?.matchedAskRule;
280
+ const clearanceFacts = {
281
+ ...(carry?.mandated === true || askMandateMember(storedShapeFacts).mandated === true ? { mandated: true } : {}),
282
+ ...(rowRealApproval !== undefined ? { requiresRealApproval: true } : {}),
283
+ ...(shadowedRule !== undefined ? { persistedRuleShadowed: shadowedRule } : {}),
284
+ ...(askDecisionReason !== undefined ? { decisionReason: askDecisionReason } : {}),
285
+ ...(rowMatchedAskRule !== undefined ? { matchedAskRule: rowMatchedAskRule } : {}),
286
+ ...(probeMandated === true ? { probeMandated: true } : {}),
287
+ ...(inheritedUnavailableAsks.has(req.toolCallId) ? { inheritedUnresolved: true } : {}),
288
+ ...(segmentCoverage !== undefined ? { segmentCoverage } : {}),
289
+ };
271
290
  const mintedAt = Date.now();
272
291
  cp = {
273
292
  token,
274
293
  checkpointId: mintCheckpointId(),
275
- version: checkpointVersionFor({ resource: false, binding: true, token: approvalLedger.totalTokens !== undefined, orgAdmission: orgAdmissionCheckpointState(), f012: f012CheckpointState(), realApproval: realApproval !== undefined, face: faceCheckpointState() }),
294
+ version: checkpointVersionFor({ resource: false, binding: true, token: approvalLedger.totalTokens !== undefined, orgAdmission: orgAdmissionCheckpointState(), f012: f012CheckpointState(), realApproval: rowRealApproval !== undefined, face: faceCheckpointState() }),
276
295
  scope,
277
296
  sessionId,
278
297
  leafId,
@@ -291,18 +310,10 @@ export function prepareParkAsk(input) {
291
310
  ...(bidi ? { hasBidiControls: true } : {}),
292
311
  };
293
312
  })(),
294
- ...(realApproval !== undefined ? { requiresRealApproval: true } : {}),
313
+ ...(rowRealApproval !== undefined ? { requiresRealApproval: true } : {}),
314
+ ...askMandateMember(clearanceFacts),
295
315
  ...askCarryRowMembers(carry),
296
- ...ruleOffersOf(req.toolName, parkedArgs, {
297
- ...(carry?.mandated === true ? { mandated: true } : {}),
298
- ...(realApproval !== undefined ? { requiresRealApproval: true } : {}),
299
- ...(shadowedRule !== undefined ? { persistedRuleShadowed: shadowedRule } : {}),
300
- ...(askDecisionReason !== undefined ? { decisionReason: askDecisionReason } : {}),
301
- ...(matchedAskRule !== undefined ? { matchedAskRule } : {}),
302
- ...(probeMandated === true ? { probeMandated: true } : {}),
303
- ...(inheritedUnavailableAsks.has(req.toolCallId) ? { inheritedUnresolved: true } : {}),
304
- ...(segmentCoverage !== undefined ? { segmentCoverage } : {}),
305
- }),
316
+ ...ruleOffersOf(req.toolName, parkedArgs, clearanceFacts),
306
317
  boundInputHash: boundInputHashOf(parkedArgs),
307
318
  batchToolCallIds,
308
319
  completedCallIds,