@sema-agent/core 5.57.0 → 5.59.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 (192) hide show
  1. package/CHANGELOG.md +96 -0
  2. package/dist/agents/cascade.d.ts +1 -1
  3. package/dist/agents/cumulative-stats.d.ts +1 -1
  4. package/dist/agents/observer.d.ts +2 -2
  5. package/dist/agents/peer-admission.d.ts +1 -1
  6. package/dist/agents/retain-ledger.d.ts +2 -2
  7. package/dist/agents/roster-store.d.ts +8 -8
  8. package/dist/agents/send-message-tool.d.ts +2 -2
  9. package/dist/agents/subagent-steps.d.ts +1 -1
  10. package/dist/agents/subagent.d.ts +13 -13
  11. package/dist/agents/team.d.ts +5 -5
  12. package/dist/agents/tool-filter.d.ts +2 -2
  13. package/dist/agents/verify.d.ts +1 -1
  14. package/dist/bench/metrics.d.ts +35 -35
  15. package/dist/brain/degrading.d.ts +1 -1
  16. package/dist/brain/errors.d.ts +3 -3
  17. package/dist/brain/reasoning.d.ts +2 -2
  18. package/dist/brain/repetition.d.ts +1 -1
  19. package/dist/brain/route-adjudicator.d.ts +8 -1
  20. package/dist/brain/route-adjudicator.js +8 -1
  21. package/dist/brain/status-sink.d.ts +2 -2
  22. package/dist/brain/stream-shared.d.ts +1 -1
  23. package/dist/config/catalog.d.ts +5 -5
  24. package/dist/core/arg-summary.d.ts +4 -4
  25. package/dist/core/ask-class.d.ts +2 -2
  26. package/dist/core/ask-question.d.ts +1 -1
  27. package/dist/core/auto-compaction.d.ts +15 -15
  28. package/dist/core/auto-mode.d.ts +5 -5
  29. package/dist/core/background-agent-store.d.ts +20 -20
  30. package/dist/core/background-shell.d.ts +4 -4
  31. package/dist/core/checkpoint-store.d.ts +35 -27
  32. package/dist/core/context-edit.d.ts +1 -1
  33. package/dist/core/context-guard.d.ts +1 -1
  34. package/dist/core/exec-output-tail.d.ts +6 -6
  35. package/dist/core/file-snapshot-store.d.ts +8 -8
  36. package/dist/core/git-worktree-env.d.ts +3 -3
  37. package/dist/core/governance-codes.d.ts +37 -10
  38. package/dist/core/governance-codes.js +54 -1
  39. package/dist/core/hooks.d.ts +73 -33
  40. package/dist/core/hooks.js +87 -25
  41. package/dist/core/image-downsample.d.ts +1 -1
  42. package/dist/core/locked-config.d.ts +1 -1
  43. package/dist/core/lsp.d.ts +1 -1
  44. package/dist/core/mailbox-store.d.ts +1 -1
  45. package/dist/core/mcp.d.ts +3 -3
  46. package/dist/core/memory-engine/consolidation-driver.d.ts +211 -0
  47. package/dist/core/memory-engine/consolidation-driver.js +427 -0
  48. package/dist/core/memory-engine/consolidation.d.ts +119 -3
  49. package/dist/core/memory-engine/consolidation.js +22 -1
  50. package/dist/core/memory-engine/data-plane.d.ts +1 -1
  51. package/dist/core/memory-engine/distiller.d.ts +550 -0
  52. package/dist/core/memory-engine/distiller.js +598 -0
  53. package/dist/core/memory-engine/dual-root.d.ts +1 -1
  54. package/dist/core/memory-engine/engine.d.ts +143 -10
  55. package/dist/core/memory-engine/engine.js +149 -23
  56. package/dist/core/memory-engine/file-backend.d.ts +14 -2
  57. package/dist/core/memory-engine/file-backend.js +3 -0
  58. package/dist/core/memory-engine/index.d.ts +6 -3
  59. package/dist/core/memory-engine/index.js +4 -2
  60. package/dist/core/memory-engine/layout.js +20 -6
  61. package/dist/core/memory-engine/origin-clearance.d.ts +1 -1
  62. package/dist/core/memory-engine/scope-contract.d.ts +4 -4
  63. package/dist/core/memory-engine/sync-client.d.ts +16 -16
  64. package/dist/core/memory-engine/sync.d.ts +4 -4
  65. package/dist/core/memory-engine/types.d.ts +17 -0
  66. package/dist/core/memory-recall.d.ts +1 -1
  67. package/dist/core/memory.d.ts +2 -2
  68. package/dist/core/permission-rule-consent.d.ts +185 -36
  69. package/dist/core/permission-rule-consent.js +219 -44
  70. package/dist/core/permission-rule-model.d.ts +235 -31
  71. package/dist/core/permission-rule-model.js +105 -35
  72. package/dist/core/permission-rules.d.ts +9 -9
  73. package/dist/core/remote-env.d.ts +8 -8
  74. package/dist/core/roles.d.ts +3 -3
  75. package/dist/core/roles.js +1 -0
  76. package/dist/core/runner/assemble-result.d.ts +2 -2
  77. package/dist/core/runner/compaction-call-options.d.ts +3 -3
  78. package/dist/core/runner/memory-consolidation-driver.d.ts +49 -0
  79. package/dist/core/runner/memory-consolidation-driver.js +60 -0
  80. package/dist/core/runner/memory-consolidation.d.ts +1 -1
  81. package/dist/core/runner/prepare-config-doors.d.ts +3 -3
  82. package/dist/core/runner/prepare-task.d.ts +21 -21
  83. package/dist/core/runner/prepare-task.js +24 -17
  84. package/dist/core/runner/prepare-workspace-restore.d.ts +2 -2
  85. package/dist/core/runner/runtask.d.ts +11 -11
  86. package/dist/core/runner/runtask.js +4 -4
  87. package/dist/core/runner/session-rule-policy.d.ts +1 -1
  88. package/dist/core/runner/teardown-bounded.d.ts +1 -1
  89. package/dist/core/runner/tool-disclosure.d.ts +2 -2
  90. package/dist/core/runner/turn-attachments.d.ts +11 -11
  91. package/dist/core/scheduler.d.ts +5 -5
  92. package/dist/core/secret-env.d.ts +1 -1
  93. package/dist/core/sensitive-path-policy.d.ts +1 -1
  94. package/dist/core/session-policy-store.d.ts +2 -2
  95. package/dist/core/session-reconcile.d.ts +2 -2
  96. package/dist/core/session-store.d.ts +3 -3
  97. package/dist/core/session.d.ts +1 -1
  98. package/dist/core/shutdown-debug.d.ts +2 -2
  99. package/dist/core/side-query.d.ts +2 -2
  100. package/dist/core/spec-contract.d.ts +1 -1
  101. package/dist/core/store-contracts/contract-harness.d.ts +2 -2
  102. package/dist/core/store-contracts/contract-kit-version.d.ts +2 -2
  103. package/dist/core/store-contracts/mailbox-store-contract.d.ts +1 -1
  104. package/dist/core/store-contracts/mailbox-store-contract.js +1 -1
  105. package/dist/core/task-notification.d.ts +5 -5
  106. package/dist/core/task-registry-agent.d.ts +12 -12
  107. package/dist/core/task-registry-monitor.d.ts +1 -1
  108. package/dist/core/task-registry-shared.d.ts +41 -41
  109. package/dist/core/task-registry.d.ts +12 -12
  110. package/dist/core/tool-detach.d.ts +2 -2
  111. package/dist/core/tool-errors.d.ts +3 -3
  112. package/dist/core/tool-policy.d.ts +113 -28
  113. package/dist/core/tool-policy.js +80 -1
  114. package/dist/core/tool-result-budget.d.ts +1 -1
  115. package/dist/core/tool-result-store.d.ts +2 -2
  116. package/dist/core/tools.d.ts +1 -1
  117. package/dist/core/trace.d.ts +26 -23
  118. package/dist/core/types.d.ts +164 -86
  119. package/dist/core/types.js +4 -3
  120. package/dist/core/untrusted-egress.d.ts +1 -1
  121. package/dist/core/untrusted-text.d.ts +7 -7
  122. package/dist/core/wiring-manifest.d.ts +5 -5
  123. package/dist/core/workflow-journal-store.d.ts +14 -14
  124. package/dist/core/workflow-run-store-contract.d.ts +1 -1
  125. package/dist/core/workflow-run-store-contract.js +1 -1
  126. package/dist/core/workflow-run-store.d.ts +4 -4
  127. package/dist/engine/compaction/compaction.d.ts +3 -3
  128. package/dist/engine/compaction/utils.d.ts +2 -2
  129. package/dist/engine/execution-env/kill-tree.d.ts +1 -1
  130. package/dist/engine/execution-env/node-execution-env.d.ts +8 -8
  131. package/dist/engine/harness/agent-harness.d.ts +6 -6
  132. package/dist/engine/harness/messages.d.ts +1 -1
  133. package/dist/engine/harness/types.d.ts +10 -10
  134. package/dist/engine/llm/types.d.ts +14 -14
  135. package/dist/engine/loop/agent-loop.d.ts +3 -3
  136. package/dist/engine/loop/types.d.ts +4 -4
  137. package/dist/engine/lsp/node-lsp-manager.d.ts +2 -2
  138. package/dist/engine/session/import-validate.d.ts +1 -1
  139. package/dist/engine/session/log-digest.d.ts +1 -1
  140. package/dist/engine/session/memory-repo.d.ts +2 -2
  141. package/dist/engine/session/session.d.ts +4 -4
  142. package/dist/fixtures/index.d.ts +4 -4
  143. package/dist/index.d.ts +6 -5
  144. package/dist/index.js +5 -4
  145. package/dist/orchestration/goal.d.ts +1 -1
  146. package/dist/orchestration/run-spec.d.ts +1 -1
  147. package/dist/orchestration/run-workflow-tool.d.ts +12 -12
  148. package/dist/orchestration/workflow-governance.d.ts +4 -4
  149. package/dist/orchestration/workflow-observe.d.ts +1 -1
  150. package/dist/orchestration/workflow-script-runner.d.ts +1 -1
  151. package/dist/orchestration/workflow-script-store.d.ts +9 -9
  152. package/dist/orchestration/workflow-size-guideline.d.ts +1 -1
  153. package/dist/orchestration/workflow-types.d.ts +5 -5
  154. package/dist/orchestration/workflow.d.ts +10 -10
  155. package/dist/prompt-assembly/artifact-store.d.ts +1 -1
  156. package/dist/prompt-assembly/artifact.d.ts +1 -1
  157. package/dist/prompt-assembly/assemble.d.ts +1 -1
  158. package/dist/prompt-assembly/composer.d.ts +2 -2
  159. package/dist/prompt-assembly/epoch.d.ts +2 -2
  160. package/dist/prompt-assembly/event-registry.d.ts +1 -1
  161. package/dist/prompt-assembly/explain.d.ts +3 -3
  162. package/dist/prompt-assembly/tool-catalog.d.ts +1 -1
  163. package/dist/prompt-assembly/turn-snapshot.d.ts +4 -4
  164. package/dist/prompt-assembly/types.d.ts +12 -12
  165. package/dist/prompts/coordinator.d.ts +1 -1
  166. package/dist/prompts/default.d.ts +8 -8
  167. package/dist/prompts/simple-sections.d.ts +3 -3
  168. package/dist/prompts/supervisor.d.ts +2 -2
  169. package/dist/scenarios/full-body.d.ts +3 -3
  170. package/dist/scenarios/scenario-registry.d.ts +1 -1
  171. package/dist/stores/cc/sidecar-transcript.d.ts +3 -3
  172. package/dist/stores/file/fs-atomic.d.ts +2 -2
  173. package/dist/stores/file/index.d.ts +1 -1
  174. package/dist/stores/file/session-store.d.ts +2 -2
  175. package/dist/stores/file/workflow-journal-store.d.ts +4 -4
  176. package/dist/tools/fs/bash-readonly-classifier.d.ts +1 -1
  177. package/dist/tools/fs/encoding.d.ts +4 -4
  178. package/dist/tools/fs/fs-bash.d.ts +3 -3
  179. package/dist/tools/fs/fs-pdf.d.ts +1 -1
  180. package/dist/tools/fs/fs-shared.d.ts +6 -6
  181. package/dist/tools/fs/index.d.ts +2 -2
  182. package/dist/tools/fs/notebook.d.ts +1 -1
  183. package/dist/tools/fs/pdf.d.ts +1 -1
  184. package/dist/tools/fs/read-deny.d.ts +1 -1
  185. package/dist/tools/fs/safety.d.ts +9 -9
  186. package/dist/tools/fs/search.d.ts +2 -2
  187. package/dist/tools/monitor.d.ts +3 -3
  188. package/dist/tools/task-list.d.ts +2 -2
  189. package/dist/tools/web.d.ts +4 -4
  190. package/dist/tools/worktree.d.ts +5 -5
  191. package/package.json +1 -1
  192. package/test/export-surface.snapshot.json +1826 -3
@@ -0,0 +1,211 @@
1
+ /**
2
+ * design/376 片② — the host CONSOLIDATION DRIVER verb: one call that takes a long-accumulated,
3
+ * never-consolidated library to its fold fixpoint through the four engine verbs, as a PERSISTENT
4
+ * MULTI-CYCLE RUN.
5
+ *
6
+ * Why a run is a first-class object: under factory defaults a cold-start consolidation is a
7
+ * dozen-plus cycles with a hard 60-second throttle floor between them — a persistent job measured
8
+ * in minutes, not a call. The engine deliberately keeps NO driver state (its gate row settles per
9
+ * plan and clears the session count), so without a durable run account a process exit between two
10
+ * cycles would strand the work: no open plan to reconcile, no session edge to re-arm, and a naive
11
+ * re-entry would re-mint the whole plan at full model cost. The run row under the control plane is
12
+ * the resume seat: `runMemoryConsolidation` first looks for a PENDING run of the scope and
13
+ * CONTINUES it (same plan cache, same force requestId, zero new mint) — the idempotent re-entry
14
+ * GD-15 pins.
15
+ *
16
+ * Boundary reminders (design/339 §1.1, unchanged by this module):
17
+ * - the engine adjudicates and commits; this driver only proposes and schedules — every write
18
+ * goes through `commitConsolidationPlan`, nothing here touches the store;
19
+ * - the engine holds no model seat; the model reaches this module as an explicit chat function;
20
+ * - core still owns no timer: the throttle waits here run INSIDE a host's explicit call (or the
21
+ * auto lane's task-boundary tick), never on a background schedule of core's own.
22
+ */
23
+ import type { EngineNotice } from "../types.js";
24
+ import { type ConsolidationGateRow, type ConsolidationRunStopReason, type MemoryConsolidationOptions } from "./consolidation.js";
25
+ import { type ConsolidationPlanFoldEvidence, type ConsolidationPlanSummary } from "./engine.js";
26
+ import { mintLlmConsolidationPlan, type ConsolidationDriveCycleRow, type ConsolidationDriveEngine, type ConsolidationFoldState, type DistillerChatFn, type LlmConsolidationPlan, type LlmConsolidationPlanProduct, type LlmDistillerContract } from "./distiller.js";
27
+ export declare const CONSOLIDATION_DRIVER_RUNS_FILE = "distiller-runs.json";
28
+ /** §1.6-4 — the mint archive: what was shown to the model and what it answered, verbatim, per run
29
+ * (the fold law's audit other-half; strict-family placement beside the plan workbook). */
30
+ export declare const CONSOLIDATION_DRIVER_PLANS_DIR = "distiller-plans";
31
+ export interface ConsolidationDriverRunRow {
32
+ runId: string;
33
+ scope: string;
34
+ /** The WRITER claim: every open/resume bumps this under the run
35
+ * store's lock, and every later write of the row is conditional on (runId, attempt) still
36
+ * standing — of two invocations that raced onto one scope, exactly one keeps writing; the
37
+ * superseded one stops with a coded error instead of silently clobbering the winner's counters
38
+ * or terminal state. (Model spend under a genuine concurrent double-call is still at-least-once
39
+ * — the claim serializes the ACCOUNT; the engine's plan-seat CAS serializes the store, and
40
+ * cross-node cost suppression is the engine lease seat's job.) */
41
+ attempt: number;
42
+ /** The run-scoped force credential: ONE requestId across every forced snapshot of the run (the
43
+ * engine dedups the FORCED audit announcement on it — one line per run). */
44
+ requestId: string;
45
+ /** The contract version of the run's surviving MINT — stamped at open, RE-stamped (with
46
+ * {@link model}) by a resume that re-mints. */
47
+ contractVersion: string;
48
+ /** The id that minted the run's surviving plan: stamped at open; RE-stamped on a resume that
49
+ * re-mints (the plan cache absent — nothing of the opening mint survived, so the current seat
50
+ * mints every byte this run commits). The per-attempt plan archive is the byte-level anchor
51
+ * either way. */
52
+ model: string;
53
+ startedAt: number;
54
+ state: "pending" | "settled";
55
+ /** Terminal only; the ONE closed set the receipt and the `_incomplete` notice share. */
56
+ outcome?: ConsolidationRunStopReason;
57
+ settledAt?: number;
58
+ stopDetail?: string;
59
+ /** The minted plan's replayable half (mint ONCE per run; each cycle filters, never re-mints). */
60
+ planCache?: {
61
+ maxInputsPerProduct: number;
62
+ products: LlmConsolidationPlanProduct[];
63
+ };
64
+ /** The archived mint transcript's file name under {@link CONSOLIDATION_DRIVER_PLANS_DIR}. */
65
+ planArchive?: string;
66
+ /** COMMITTED cycles only (the store moved): a refused/fuse-blocked/zero-delta cycle row does
67
+ * not count — the receipt's "committed cycle(s)" label and the incomplete notice's zero-cycle
68
+ * branch both read this number as writes. */
69
+ cyclesDone: number;
70
+ /** Store-delta superseded total across EVERY invocation of this run (the conservation wall's
71
+ * evidence half — see the convergence check in the verb). Accumulated per cycle AFTER the
72
+ * commit, so a crash between a cycle's commit and its row write loses that cycle's delta —
73
+ * {@link foldBaseline} is the wall's immune second witness for exactly that window. */
74
+ entriesSuperseded: number;
75
+ /** The scope's fold-state `superseded` count at run OPEN (fresh claim; absent on rows opened
76
+ * before the field existed). The conservation wall's STORE anchor: `final.superseded − this`
77
+ * attests the run's folds even when a crash lands between a cycle's commit and its account
78
+ * write — the seat serializes this scope's folds, so the since-open delta is this run's. */
79
+ foldBaseline?: {
80
+ superseded: number;
81
+ };
82
+ planIds: string[];
83
+ usage: {
84
+ calls: number;
85
+ promptTokens: number;
86
+ completionTokens: number;
87
+ retries: number;
88
+ truncations: number;
89
+ };
90
+ repairs: {
91
+ hallucinatedMembers: number;
92
+ duplicateMembers: number;
93
+ nonIntegerMembers: number;
94
+ emptyGroups: number;
95
+ oversizeGroups: number;
96
+ };
97
+ writeFailures: Array<{
98
+ key: string;
99
+ members: number;
100
+ reason: string | null;
101
+ }>;
102
+ residue?: Array<{
103
+ name: string;
104
+ targets: number;
105
+ }>;
106
+ /** The next moment a snapshot can pass the hard throttle (resume scheduling hint). */
107
+ nextEligibleAt?: number;
108
+ }
109
+ /** Host read face: the scope's latest driver run row (pending = resumable; settled = audit). */
110
+ export declare function readConsolidationDriverRun(controlDir: string, scope: string): ConsolidationDriverRunRow | undefined;
111
+ /** Archive one mint (prompts + transcript verbatim) beside the run account. The name carries the
112
+ * CLAIM (`attempt`): two invocations that raced onto one run each archive
113
+ * under their own name, so a superseded worker can never overwrite the winning transcript — the
114
+ * run row's `planArchive` (written under the attempt guard) names the one that counts. */
115
+ export declare function distillerPlanArchiveName(runId: string, attempt: number): string;
116
+ export declare function archiveDistillerPlan(controlDir: string, runId: string, plan: LlmConsolidationPlan, attempt?: number): string;
117
+ /** The engine face this verb consumes — `MemoryEngine` satisfies it structurally.
118
+ * (`readConsolidationPlanFoldEvidence` joined in design/376 slice ③, #436: the conservation
119
+ * wall's terminal witness is member-level, so the face is REQUIRED — an engine without it could
120
+ * only offer the store-delta counts an interleaved fold can numerically offset.) */
121
+ export interface ConsolidationDriverEngine extends ConsolidationDriveEngine {
122
+ readConsolidationGate(scope: string): ConsolidationGateRow | undefined;
123
+ listConsolidationPlans(): ConsolidationPlanSummary[];
124
+ readConsolidationPlanFoldEvidence(planId: string): ConsolidationPlanFoldEvidence;
125
+ readConsolidationFoldState(scope: string): Promise<ConsolidationFoldState>;
126
+ readonly controlPlaneRoot: string;
127
+ }
128
+ export interface RunMemoryConsolidationOptions {
129
+ /** The explicit model seat (design/339 §1.1: the ENGINE holds none). Build one with
130
+ * `openAiCompatChatSeat` or hand any JSON-only completion function. */
131
+ chat: DistillerChatFn;
132
+ /** The explicit model id, recorded into the run and the archive (alias spellings refuse). */
133
+ model: string;
134
+ /** Audit locator recorded into the archive when the seat is endpoint-shaped. */
135
+ baseUrl?: string;
136
+ /** The frozen prompt contract; default = the product v1 (dl-3 byte-identical). A different
137
+ * version here is a deployment's explicit measured choice. */
138
+ contract?: LlmDistillerContract;
139
+ /** The PROTOCOL knobs — pass the same object the engine was constructed with
140
+ * (`MemoryEngineOptions.consolidation`); the driver derives its throttle window and per-plan
141
+ * caps from it. Defaults = the factory defaults. */
142
+ consolidation?: MemoryConsolidationOptions;
143
+ /** Abort the run after this many cycles in THIS call (the run row stays pending only on crash;
144
+ * a reached bound settles `max_cycles`). Positive integer; default 64. */
145
+ maxCycles?: number;
146
+ /** R2 — the structural-repair budget over the mint's member references: the ratio of member
147
+ * claims the sanitiser had to DROP (hallucinated + duplicate + non-integer) to all member
148
+ * claims the model made. Above this, the plan does not represent the model's intent and the
149
+ * run aborts with zero writes rather than committing a harness-repaired fold. In [0, 1];
150
+ * default 0.10. (Empty groups are the same drops counted at group grain; oversize groups are
151
+ * not repairs — they ride on, named, and the schedule reports them infeasible. Format repairs
152
+ * and retries are disclosed separately and bounded by the contract's own retry count.) */
153
+ mintRepairBudget?: number;
154
+ /** The run's force credential; defaults to a minted id. A RESUMED run keeps its original. */
155
+ requestId?: string;
156
+ /** Host seams for the throttle waits (tests and hosts with their own clocks). */
157
+ now?: () => number;
158
+ sleep?: (ms: number) => Promise<void>;
159
+ onProgress?: Parameters<typeof mintLlmConsolidationPlan>[0]["onProgress"];
160
+ }
161
+ export interface ConsolidationRunReceipt {
162
+ runId: string;
163
+ scope: string;
164
+ /** Whether this call CONTINUED a pending run (no new mint) instead of starting one. */
165
+ resumed: boolean;
166
+ outcome: ConsolidationRunStopReason;
167
+ converged: boolean;
168
+ stopDetail?: string;
169
+ /** This call's cycles (store-delta accounted); `cyclesDone` is the run total across resumes. */
170
+ cycles: ConsolidationDriveCycleRow[];
171
+ cyclesDone: number;
172
+ productsCommitted: number;
173
+ entriesSuperseded: number;
174
+ planIds: string[];
175
+ model: string;
176
+ contractVersion: string;
177
+ usage: ConsolidationDriverRunRow["usage"];
178
+ repairs: ConsolidationDriverRunRow["repairs"];
179
+ writeFailures: ConsolidationDriverRunRow["writeFailures"];
180
+ residue: Array<{
181
+ name: string;
182
+ targets: number;
183
+ }>;
184
+ planArchive?: string;
185
+ /** Advisory notices minted for this run (the `_incomplete` disclosure when not converged). */
186
+ notices: EngineNotice[];
187
+ /** A failed announcement enqueue is disclosed, never allowed to fail the settled run. */
188
+ announceFailures?: string[];
189
+ }
190
+ /**
191
+ * Run (or RESUME) one scope's consolidation as a persistent multi-cycle job. See the module note
192
+ * for the shape; the per-refusal behavior implements the design/376 §1.2 failure table:
193
+ *
194
+ * - `_disabled` / a corrupt gate / an unreadable governance account BEFORE any commit: the coded
195
+ * error is rethrown (a configuration/store fault the host must repair — a pending run row, if
196
+ * one exists, stays resumable after the repair);
197
+ * - `_gated` (time/session gate): retried under the run's force requestId — the run-level force
198
+ * the design names; the FORCED audit line is engine-deduplicated to once per run;
199
+ * - `_gated` (hard throttle): the driver WAITS the window out (`min(minRunIntervalMs, 60s)`) and
200
+ * continues — a throttled cycle is scheduling, not a terminal;
201
+ * - `_busy`: never a blind snapshot retry (the seat refuses snapshots too — a blind loop spins
202
+ * forever): `reconcileConsolidation` first; a conflict/corrupt plan parks the run for the host
203
+ * valve; a live competing worker is waited out briefly, then reported;
204
+ * - model failures / a mint over the repair budget: the run settles `driver_failed` with ZERO
205
+ * writes — the mint sits between snapshot and commit, so no seat was taken, `lastRunAt` never
206
+ * moved and the time gate is not burned (the failure is free to retry);
207
+ * - a conflict plan mid-run parks (`parked`); fuse residue and the cycle bound settle
208
+ * `fuse_residue` / `max_cycles` with the residue named — the next run's FULL snapshot
209
+ * re-distills the shrunken library (cross-run = re-mint; in-run = plan cache).
210
+ */
211
+ export declare function runMemoryConsolidationDriver(engine: ConsolidationDriverEngine, scope: string, opts: RunMemoryConsolidationOptions): Promise<ConsolidationRunReceipt>;
@@ -0,0 +1,427 @@
1
+ import { join } from "node:path";
2
+ import { uuidv7 } from "../../internal/harness.js";
3
+ import { atomicWriteFileSync, ensureDirExists, lockedStrictUpdate, readStrictSidecar, enqueueMemoryAnnouncement, ControlPlaneCorruptError } from "./layout.js";
4
+ import { inlineUntrusted } from "../untrusted-text.js";
5
+ import { CONSOLIDATION_FORCE_THROTTLE_FLOOR_MS, screenConsolidationOptions, supersessionFuseCeiling, } from "./consolidation.js";
6
+ import { memoryConsolidationIncompleteNotice } from "./engine.js";
7
+ import { CONSOLIDATION_DRIVE_ABORT, CONSOLIDATION_SNAPSHOT_PARKED, MEMORY_DISTILLER_CONTRACT_V1, driveConsolidationToFixpoint, isAliasModelId, llmPlanDistiller, mintLlmConsolidationPlan, } from "./distiller.js";
8
+ export const CONSOLIDATION_DRIVER_RUNS_FILE = "distiller-runs.json";
9
+ export const CONSOLIDATION_DRIVER_PLANS_DIR = "distiller-plans";
10
+ function coerceRunsFile(raw) {
11
+ if (raw === undefined)
12
+ return { v: 1, scopes: {} };
13
+ if (typeof raw !== "object" || raw === null || raw.v !== 1 || typeof raw.scopes !== "object" || raw.scopes === null) {
14
+ throw new ControlPlaneCorruptError("memory consolidation driver run account has an unrecognized shape (fail-closed sidecar)");
15
+ }
16
+ for (const [scope, rowRaw] of Object.entries(raw.scopes)) {
17
+ const r = rowRaw;
18
+ if (typeof r !== "object" || r === null || typeof r["runId"] !== "string" || (r["state"] !== "pending" && r["state"] !== "settled") || typeof r["cyclesDone"] !== "number") {
19
+ throw new ControlPlaneCorruptError(`memory consolidation driver run row for scope ${JSON.stringify(scope)} is malformed (fail-closed sidecar)`);
20
+ }
21
+ }
22
+ return raw;
23
+ }
24
+ export function readConsolidationDriverRun(controlDir, scope) {
25
+ return coerceRunsFile(readStrictSidecar(controlDir, CONSOLIDATION_DRIVER_RUNS_FILE, "memory consolidation driver run account")).scopes[scope];
26
+ }
27
+ function writeDriverRun(controlDir, row) {
28
+ lockedStrictUpdate(controlDir, CONSOLIDATION_DRIVER_RUNS_FILE, "memory consolidation driver run account", coerceRunsFile, (file) => {
29
+ const cur = file.scopes[row.scope];
30
+ if (cur === undefined || cur.runId !== row.runId || (cur.attempt ?? 0) !== row.attempt) {
31
+ const e = new Error(`memory consolidation driver run seat for scope ${JSON.stringify(row.scope)} was taken over by a newer invocation${cur !== undefined ? ` (run ${cur.runId} attempt ${cur.attempt ?? 0})` : ""} — this worker stops without settling; the newer claim owns the account.`);
32
+ e.code = "memory.consolidation_driver_superseded";
33
+ e[CONSOLIDATION_DRIVE_ABORT] = true;
34
+ throw e;
35
+ }
36
+ file.scopes[row.scope] = row;
37
+ return { next: file, result: undefined };
38
+ });
39
+ }
40
+ function claimDriverRun(controlDir, scope, fresh) {
41
+ return lockedStrictUpdate(controlDir, CONSOLIDATION_DRIVER_RUNS_FILE, "memory consolidation driver run account", coerceRunsFile, (file) => {
42
+ const cur = file.scopes[scope];
43
+ if (cur !== undefined && cur.state === "pending") {
44
+ cur.attempt = (cur.attempt ?? 0) + 1;
45
+ file.scopes[scope] = cur;
46
+ return { next: file, result: { row: structuredClone(cur), resumed: true } };
47
+ }
48
+ const row = fresh();
49
+ file.scopes[scope] = row;
50
+ return { next: file, result: { row: structuredClone(row), resumed: false } };
51
+ });
52
+ }
53
+ export function distillerPlanArchiveName(runId, attempt) {
54
+ return `${runId}-a${attempt}.json`;
55
+ }
56
+ export function archiveDistillerPlan(controlDir, runId, plan, attempt = 1) {
57
+ ensureDirExists(join(controlDir, CONSOLIDATION_DRIVER_PLANS_DIR));
58
+ const name = distillerPlanArchiveName(runId, attempt);
59
+ atomicWriteFileSync(join(controlDir, CONSOLIDATION_DRIVER_PLANS_DIR, name), `${JSON.stringify(plan, null, 2)}\n`);
60
+ return name;
61
+ }
62
+ function driverConfigRefusal(message) {
63
+ const e = new Error(message);
64
+ e.code = "config.memory_consolidation_driver";
65
+ return e;
66
+ }
67
+ class DriverMintFailure extends Error {
68
+ detail;
69
+ constructor(detail) {
70
+ super(detail);
71
+ this.detail = detail;
72
+ }
73
+ }
74
+ const codeOf = (err) => {
75
+ const c = err?.code;
76
+ return typeof c === "string" ? c : undefined;
77
+ };
78
+ const MAX_THROTTLE_WAITS_PER_SNAPSHOT = 4;
79
+ const MAX_BUSY_WAITS = 2;
80
+ export async function runMemoryConsolidationDriver(engine, scope, opts) {
81
+ if (typeof opts?.chat !== "function")
82
+ throw driverConfigRefusal("runMemoryConsolidationDriver needs an explicit chat seat (opts.chat) — the engine holds no model seat and a driver that silently skips would publish nothing as a model's answer.");
83
+ if (typeof opts.model !== "string" || opts.model.trim() === "" || isAliasModelId(opts.model)) {
84
+ throw driverConfigRefusal(`runMemoryConsolidationDriver needs an EXPLICIT model id, got ${JSON.stringify(opts.model)} — an alias can be re-pointed upstream and would move the run with no trace in the archive.`);
85
+ }
86
+ if (opts.mintRepairBudget !== undefined && (typeof opts.mintRepairBudget !== "number" || !Number.isFinite(opts.mintRepairBudget) || opts.mintRepairBudget < 0 || opts.mintRepairBudget > 1)) {
87
+ throw driverConfigRefusal(`runMemoryConsolidationDriver: mintRepairBudget must be a number in [0, 1] when present (got ${String(opts.mintRepairBudget)}) — an unevaluable budget is refused loudly, never folded to the default.`);
88
+ }
89
+ if (opts.maxCycles !== undefined && (!Number.isInteger(opts.maxCycles) || opts.maxCycles < 1)) {
90
+ throw driverConfigRefusal(`runMemoryConsolidationDriver: maxCycles must be a positive integer when present (got ${String(opts.maxCycles)}).`);
91
+ }
92
+ const contract = opts.contract ?? MEMORY_DISTILLER_CONTRACT_V1;
93
+ if (typeof contract?.version !== "string" || contract.version === "" || typeof contract?.grouping?.instructions !== "string" || typeof contract?.writing?.instructions !== "string") {
94
+ throw driverConfigRefusal("runMemoryConsolidationDriver: opts.contract does not carry the contract shape (version + grouping/writing instructions).");
95
+ }
96
+ const screened = screenConsolidationOptions(opts.consolidation ?? {}, "carry");
97
+ const mintRepairBudget = opts.mintRepairBudget ?? 0.1;
98
+ const now = opts.now ?? Date.now;
99
+ const rawSleep = opts.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
100
+ const sleep = async (ms) => {
101
+ try {
102
+ await rawSleep(ms);
103
+ }
104
+ catch (err) {
105
+ const e = typeof err === "object" && err !== null ? err : new Error(String(err));
106
+ Reflect.set(e, CONSOLIDATION_DRIVE_ABORT, true);
107
+ throw e;
108
+ }
109
+ };
110
+ const throttleWindowMs = Math.min(screened.minRunIntervalMs, CONSOLIDATION_FORCE_THROTTLE_FLOOR_MS);
111
+ const controlDir = engine.controlPlaneRoot;
112
+ const parkedSeat = await recoverSeat(engine, scope, { sleep, windowMs: throttleWindowMs });
113
+ const openFold = await engine.readConsolidationFoldState(scope);
114
+ const { row: run, resumed } = claimDriverRun(controlDir, scope, () => ({
115
+ runId: uuidv7(),
116
+ scope,
117
+ attempt: 1,
118
+ requestId: opts.requestId ?? `consolidation-run-${uuidv7()}`,
119
+ contractVersion: contract.version,
120
+ model: opts.model,
121
+ startedAt: now(),
122
+ state: "pending",
123
+ cyclesDone: 0,
124
+ entriesSuperseded: 0,
125
+ foldBaseline: { superseded: openFold.superseded },
126
+ planIds: [],
127
+ usage: { calls: 0, promptTokens: 0, completionTokens: 0, retries: 0, truncations: 0 },
128
+ repairs: { hallucinatedMembers: 0, duplicateMembers: 0, nonIntegerMembers: 0, emptyGroups: 0, oversizeGroups: 0 },
129
+ writeFailures: [],
130
+ }));
131
+ if (resumed && run.planCache === undefined && (run.model !== opts.model || run.contractVersion !== contract.version)) {
132
+ run.model = opts.model;
133
+ run.contractVersion = contract.version;
134
+ }
135
+ const announceFailures = [];
136
+ const announce = (items) => {
137
+ try {
138
+ enqueueMemoryAnnouncement(controlDir, { kind: "gate", at: now(), items });
139
+ }
140
+ catch (err) {
141
+ announceFailures.push(err instanceof Error ? err.message : String(err));
142
+ }
143
+ };
144
+ const settle = (outcome, drive, stopDetail) => {
145
+ run.state = "settled";
146
+ run.outcome = outcome;
147
+ run.settledAt = now();
148
+ if (stopDetail !== undefined)
149
+ run.stopDetail = stopDetail;
150
+ run.residue = run.residue ?? drive?.stop?.fuseBlocked;
151
+ writeDriverRun(controlDir, run);
152
+ const notices = [];
153
+ if (outcome !== "converged") {
154
+ const notice = memoryConsolidationIncompleteNotice({ scope, runId: run.runId, stop: outcome, residueProducts: run.residue?.length ?? 0, cyclesDone: run.cyclesDone });
155
+ notices.push(notice);
156
+ announce([notice.message]);
157
+ }
158
+ return {
159
+ runId: run.runId,
160
+ scope,
161
+ resumed,
162
+ outcome,
163
+ converged: outcome === "converged",
164
+ ...(run.stopDetail !== undefined ? { stopDetail: run.stopDetail } : {}),
165
+ cycles: drive?.cycles ?? [],
166
+ cyclesDone: run.cyclesDone,
167
+ productsCommitted: drive?.productsCommitted ?? 0,
168
+ entriesSuperseded: drive?.entriesSuperseded ?? 0,
169
+ planIds: [...run.planIds],
170
+ model: run.model,
171
+ contractVersion: run.contractVersion,
172
+ usage: { ...run.usage },
173
+ repairs: { ...run.repairs },
174
+ writeFailures: [...run.writeFailures],
175
+ residue: run.residue ?? [],
176
+ ...(run.planArchive !== undefined ? { planArchive: run.planArchive } : {}),
177
+ notices,
178
+ ...(announceFailures.length > 0 ? { announceFailures: [...announceFailures] } : {}),
179
+ };
180
+ };
181
+ if (parkedSeat !== undefined) {
182
+ return settle("parked", undefined, parkedSeat);
183
+ }
184
+ let staleResidue = [];
185
+ let staleResidueMemberIds = new Set();
186
+ const distill = async (candidates, snapshot, s) => {
187
+ if (run.planCache === undefined) {
188
+ let minted;
189
+ try {
190
+ minted = await mintLlmConsolidationPlan({
191
+ candidates,
192
+ chat: opts.chat,
193
+ model: opts.model,
194
+ ...(opts.baseUrl !== undefined ? { baseUrl: opts.baseUrl } : {}),
195
+ maxInputsPerProduct: screened.maxInputsPerProduct,
196
+ contract,
197
+ onProgress: opts.onProgress ?? null,
198
+ });
199
+ }
200
+ catch (err) {
201
+ if (codeOf(err) === "config.memory_consolidation_driver")
202
+ throw err;
203
+ throw new DriverMintFailure(`the model seat failed during the mint: ${err instanceof Error ? err.message : String(err)}`);
204
+ }
205
+ const transcript = minted.ok ? minted.plan.minting.transcript : minted.calls;
206
+ run.usage.calls += transcript.length;
207
+ for (const c of transcript) {
208
+ run.usage.promptTokens += c.usage?.promptTokens ?? 0;
209
+ run.usage.completionTokens += c.usage?.completionTokens ?? 0;
210
+ }
211
+ if (!minted.ok) {
212
+ throw new DriverMintFailure(minted.reason);
213
+ }
214
+ const plan = minted.plan;
215
+ run.usage.retries += plan.minting.parseRepairs.retries;
216
+ run.usage.truncations += plan.minting.parseRepairs.lengthCapped.length;
217
+ run.repairs = {
218
+ hallucinatedMembers: plan.minting.repairs.hallucinatedMembers,
219
+ duplicateMembers: plan.minting.repairs.duplicateMembers,
220
+ nonIntegerMembers: plan.minting.repairs.nonIntegerMembers,
221
+ emptyGroups: plan.minting.repairs.emptyGroups,
222
+ oversizeGroups: plan.minting.repairs.oversizeGroups.length,
223
+ };
224
+ run.writeFailures = plan.minting.writeFailures.map((w) => ({ key: w.key, members: w.members, reason: w.reason })) ?? [];
225
+ const dropped = plan.minting.repairs.hallucinatedMembers + plan.minting.repairs.duplicateMembers + plan.minting.repairs.nonIntegerMembers;
226
+ const claimed = plan.products.reduce((a, p) => a + p.inputIds.length, 0) + plan.minting.writeFailures.reduce((a, w) => a + w.members, 0);
227
+ const total = dropped + claimed;
228
+ const ratio = total === 0 ? (dropped > 0 ? 1 : 0) : dropped / total;
229
+ if (ratio > mintRepairBudget) {
230
+ throw new DriverMintFailure(`the mint needed structural repairs on ${dropped} of ${total} member claim(s) (${(ratio * 100).toFixed(1)}% > mintRepairBudget ${(mintRepairBudget * 100).toFixed(1)}%) — a plan repaired this much does not represent the model's intent, and committing it would fold entries the harness chose, not the model (hallucinated ${plan.minting.repairs.hallucinatedMembers}, duplicate ${plan.minting.repairs.duplicateMembers}, nonInteger ${plan.minting.repairs.nonIntegerMembers}; emptied groups ${plan.minting.repairs.emptyGroups}, oversize ${plan.minting.repairs.oversizeGroups.length})`);
231
+ }
232
+ run.planArchive = distillerPlanArchiveName(run.runId, run.attempt);
233
+ run.planCache = { maxInputsPerProduct: plan.maxInputsPerProduct, products: plan.products };
234
+ writeDriverRun(controlDir, run);
235
+ archiveDistillerPlan(controlDir, run.runId, plan, run.attempt);
236
+ }
237
+ const offered = new Map();
238
+ for (const c of candidates)
239
+ if (typeof c?.entry?.id === "string")
240
+ offered.set(c.entry.id, c.entry.rev);
241
+ staleResidueMemberIds = new Set();
242
+ staleResidue = (run.planCache?.products ?? []).flatMap((p) => {
243
+ const ids = p.inputIds ?? [];
244
+ const live = ids.filter((id) => offered.has(id));
245
+ if (live.length === 0)
246
+ return [];
247
+ const stale = p.inputRevs !== undefined && live.some((id) => p.inputRevs[id] !== undefined && offered.get(id) !== p.inputRevs[id]);
248
+ const vanished = live.length < ids.length;
249
+ const unanchored = p.inputRevs === undefined;
250
+ if (!(stale || vanished || unanchored))
251
+ return [];
252
+ for (const id of live)
253
+ staleResidueMemberIds.add(id);
254
+ return [{ name: p.name, targets: live.length }];
255
+ });
256
+ const replayView = run.planCache !== undefined && run.planCache.products.some((p) => p.inputRevs === undefined)
257
+ ? { ...run.planCache, products: run.planCache.products.filter((p) => p.inputRevs !== undefined) }
258
+ : run.planCache;
259
+ return llmPlanDistiller(replayView)(candidates, snapshot, s);
260
+ };
261
+ let firstSnapshotOfNewRun = !resumed;
262
+ const takeSnapshot = async (_cycle) => {
263
+ writeDriverRun(controlDir, run);
264
+ let force = !firstSnapshotOfNewRun;
265
+ let throttleWaits = 0;
266
+ let busyWaits = 0;
267
+ for (;;) {
268
+ try {
269
+ const snap = await engine.snapshotConsolidationCandidates(scope, { full: true, ...(force ? { force: { requestId: run.requestId } } : {}) });
270
+ firstSnapshotOfNewRun = false;
271
+ return snap;
272
+ }
273
+ catch (err) {
274
+ const code = codeOf(err);
275
+ if (code === "memory.consolidation_gated") {
276
+ if (!force) {
277
+ force = true;
278
+ continue;
279
+ }
280
+ if (throttleWaits >= MAX_THROTTLE_WAITS_PER_SNAPSHOT)
281
+ throw err;
282
+ throttleWaits += 1;
283
+ const lastRunAt = engine.readConsolidationGate(scope)?.lastRunAt;
284
+ const until = (lastRunAt ?? now()) + throttleWindowMs;
285
+ run.nextEligibleAt = until;
286
+ writeDriverRun(controlDir, run);
287
+ const waitMs = Math.max(1, until - now());
288
+ await sleep(waitMs);
289
+ continue;
290
+ }
291
+ if (code === "memory.consolidation_busy") {
292
+ if (busyWaits >= MAX_BUSY_WAITS)
293
+ throw err;
294
+ busyWaits += 1;
295
+ const parked = await recoverSeat(engine, scope, { sleep, windowMs: throttleWindowMs });
296
+ if (parked !== undefined) {
297
+ const e = new Error(parked);
298
+ e[CONSOLIDATION_SNAPSHOT_PARKED] = true;
299
+ throw e;
300
+ }
301
+ continue;
302
+ }
303
+ throw err;
304
+ }
305
+ }
306
+ };
307
+ let drive;
308
+ try {
309
+ drive = await driveConsolidationToFixpoint(engine, scope, {
310
+ distill,
311
+ readFoldState: () => engine.readConsolidationFoldState(scope),
312
+ options: screened,
313
+ fuseCeilingOf: supersessionFuseCeiling,
314
+ requestIdPrefix: run.requestId,
315
+ maxCycles: opts.maxCycles ?? 64,
316
+ takeSnapshot,
317
+ onCycle: (row) => {
318
+ if (row.committed.products > 0 || row.committed.superseded > 0)
319
+ run.cyclesDone += 1;
320
+ run.entriesSuperseded = (run.entriesSuperseded ?? 0) + row.committed.superseded;
321
+ if (row.planId !== undefined)
322
+ run.planIds.push(row.planId);
323
+ run.nextEligibleAt = now() + throttleWindowMs;
324
+ writeDriverRun(controlDir, run);
325
+ },
326
+ });
327
+ }
328
+ catch (err) {
329
+ if (err instanceof DriverMintFailure) {
330
+ announce([`memory consolidation driver run ${run.runId} for scope ${inlineUntrusted(scope, 80)} FAILED before any write: ${inlineUntrusted(err.detail, 300)}`]);
331
+ return settle("driver_failed", undefined, err.detail);
332
+ }
333
+ throw err;
334
+ }
335
+ const writeResidue = run.writeFailures.map((w) => ({ name: w.key, targets: w.members }));
336
+ const driverResidue = [...staleResidue, ...writeResidue];
337
+ if (drive.converged) {
338
+ const plannedIds = [...new Set((run.planCache?.products ?? []).flatMap((p) => p.inputIds ?? []))];
339
+ const plannedMembers = (run.planCache?.products ?? []).reduce((a, p) => a + (p.inputIds?.length ?? 0), 0);
340
+ const rowAccount = run.entriesSuperseded ?? 0;
341
+ const storeAccount = run.foldBaseline !== undefined ? Math.max(0, drive.foldState.after.superseded - run.foldBaseline.superseded) : rowAccount;
342
+ const supersededAttested = Math.max(rowAccount, storeAccount);
343
+ const residueTargets = driverResidue.reduce((a, r) => a + r.targets, 0);
344
+ const accounted = supersededAttested + residueTargets;
345
+ if (plannedMembers > accounted) {
346
+ run.residue = driverResidue;
347
+ return settle("driver_failed", drive, `the drive reached a fixpoint but the account does not balance: ${plannedMembers} planned member(s), ${supersededAttested} superseded attested for this run (row account ${rowAccount}, store delta ${run.foldBaseline !== undefined ? storeAccount : "unavailable — the row predates the store anchor"}), ${residueTargets} in named residue — ${plannedMembers - accounted} member(s) left the candidate set without a fold the store attests (deleted or excluded mid-run${run.foldBaseline !== undefined ? "" : ", or folded in a window whose account write did not survive"}); their entries stand (or revive with their exclusion), and a fresh run re-mints over the current library`);
348
+ }
349
+ if (plannedIds.length > 0) {
350
+ const evidence = collectRunFoldEvidence(engine, scope, run.requestId);
351
+ if (evidence.skipped.length > 0) {
352
+ const named = evidence.skipped.slice(0, 5).map((s) => `${s.planId} (${s.state})`).join(", ");
353
+ announce([
354
+ `memory consolidation conservation wall for scope ${inlineUntrusted(scope, 80)}: ${evidence.skipped.length} same-scope plan file(s) answered unreadable and were SKIPPED as foreign to run ${run.runId} — they attest nothing for this run and need out-of-band repair: ${named}${evidence.skipped.length > 5 ? ", …" : ""}`,
355
+ ]);
356
+ }
357
+ if (evidence.unreadable !== undefined) {
358
+ run.residue = driverResidue;
359
+ return settle("driver_failed", drive, `the drive reached a fixpoint but the member-level fold evidence is incomplete: plan ${evidence.unreadable} — one of THIS run's own cycles — answered ${evidence.unreadableState} (control-plane damage on run-owned evidence, not a transient race); a converged label needs attestable members; the library stands as committed and a fresh run re-mints over the current bytes`);
360
+ }
361
+ const missing = plannedIds.filter((id) => !evidence.targets.has(id) && !staleResidueMemberIds.has(id));
362
+ if (missing.length > 0) {
363
+ run.residue = driverResidue;
364
+ return settle("driver_failed", drive, `the drive reached a fixpoint but ${missing.length} of ${plannedIds.length} planned member(s) left the candidate set without a fold this run's plans attest (deleted, excluded, or superseded by another writer mid-run; e.g. ${missing.slice(0, 5).join(", ")}) — a store-delta count can be numerically offset by an unrelated interleaved fold, so member identity is the final judge; the entries stand (or revive with their exclusion) and a fresh run re-mints over the current library`);
365
+ }
366
+ }
367
+ if (driverResidue.length > 0) {
368
+ run.residue = driverResidue;
369
+ return settle("driver_failed", drive, `the drive reached the surviving plan's fixpoint, but the driver's own half left ${driverResidue.length} group(s) unfolded (${staleResidue.length} stale against edited inputs, ${writeResidue.length} writing-pass failure(s)) — their members stay as they were; a fresh run re-mints over the current bytes`);
370
+ }
371
+ return settle("converged", drive);
372
+ }
373
+ if (driverResidue.length > 0)
374
+ run.residue = [...(drive.stop?.fuseBlocked ?? []), ...driverResidue];
375
+ const stop = drive.stop;
376
+ if (stop?.fuseBlocked !== undefined)
377
+ return settle("fuse_residue", drive, stop.reason);
378
+ if (stop?.exhausted === true)
379
+ return settle("max_cycles", drive, stop.reason);
380
+ if (stop?.parked === true)
381
+ return settle("parked", drive, stop.reason);
382
+ return settle("refused", drive, stop?.reason ?? "the drive stopped without a stated reason");
383
+ }
384
+ function collectRunFoldEvidence(engine, scope, requestId) {
385
+ const cycleId = new RegExp(`^${requestId.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}-c\\d+$`);
386
+ const targets = new Set();
387
+ const skipped = [];
388
+ for (const summary of engine.listConsolidationPlans()) {
389
+ if (summary.corrupt === true)
390
+ continue;
391
+ if (summary.scope !== scope)
392
+ continue;
393
+ const ev = engine.readConsolidationPlanFoldEvidence(summary.planId);
394
+ if (ev.state !== "ok") {
395
+ if (ev.state === "corrupt" && ev.requestId !== undefined && cycleId.test(ev.requestId)) {
396
+ return { targets, unreadable: summary.planId, unreadableState: ev.state, skipped };
397
+ }
398
+ skipped.push({ planId: summary.planId, state: ev.state });
399
+ continue;
400
+ }
401
+ if (ev.scope !== scope || !cycleId.test(ev.requestId))
402
+ continue;
403
+ for (const t of ev.supersededTargets)
404
+ targets.add(t);
405
+ }
406
+ return { targets, skipped };
407
+ }
408
+ async function recoverSeat(engine, scope, { sleep, windowMs }) {
409
+ await engine.reconcileConsolidation(scope);
410
+ let seat = engine.readConsolidationGate(scope)?.openPlanId;
411
+ if (seat === undefined)
412
+ return undefined;
413
+ const summary = engine.listConsolidationPlans().find((p) => p.planId === seat);
414
+ if (summary?.corrupt === true || summary?.state === "conflict") {
415
+ return `plan ${seat} holds the ${JSON.stringify(scope)} seat in a ${summary.corrupt === true ? "corrupt" : "conflict"} state — resolveConsolidationPlan(planId, "retry" | "discard") is the host valve; the run parks rather than stacking a second plan on the seat`;
416
+ }
417
+ await sleep(windowMs);
418
+ await engine.reconcileConsolidation(scope);
419
+ seat = engine.readConsolidationGate(scope)?.openPlanId;
420
+ if (seat === undefined)
421
+ return undefined;
422
+ const again = engine.listConsolidationPlans().find((p) => p.planId === seat);
423
+ if (again?.corrupt === true || again?.state === "conflict") {
424
+ return `plan ${seat} holds the ${JSON.stringify(scope)} seat in a ${again.corrupt === true ? "corrupt" : "conflict"} state — resolveConsolidationPlan(planId, "retry" | "discard") is the host valve; the run parks rather than stacking a second plan on the seat`;
425
+ }
426
+ return undefined;
427
+ }