@deksden-com/dd-flow-cli 0.9.0-beta.143 → 0.9.0-beta.145

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 (197) hide show
  1. package/CHANGELOG.md +65 -0
  2. package/README.md +13 -8
  3. package/dist/build-info.json +7 -7
  4. package/dist/cli/command-inputs.js +9 -3
  5. package/dist/cli/command-metadata.js +33 -14
  6. package/dist/cli/help.js +32 -1
  7. package/dist/cli/hook-ingress.js +27 -3
  8. package/dist/cli/memory-flow-admission.js +7 -3
  9. package/dist/cli/memory-init-invocation.js +10 -3
  10. package/dist/cli/memory-init.js +74 -68
  11. package/dist/cli/memory-storage-admission.js +3 -2
  12. package/dist/cli/memory-storage-qualification.js +105 -0
  13. package/dist/cli/memory-storage-transition.js +123 -0
  14. package/dist/cli/memory-upgrade.js +189 -0
  15. package/dist/cli/native-hook-ingress.js +14 -4
  16. package/dist/cli/native-hook-preparation.js +55 -0
  17. package/dist/cli/response-file.js +2 -1
  18. package/dist/cli/run-cli.js +70 -32
  19. package/dist/cli/setup.js +2 -1
  20. package/dist/cli.js +12 -0
  21. package/dist/domain/stage-handler-identities.js +8 -0
  22. package/dist/harness-runtime/bin/dd-agy.mjs +3 -3
  23. package/dist/harness-runtime/bin/dd-grok.mjs +3 -3
  24. package/dist/harness-runtime/bin/dd-zcode.mjs +5 -5
  25. package/dist/harness-runtime/lib/agy-error-boundary.mjs +58 -0
  26. package/dist/harness-runtime/lib/dd-agy-daemon.mjs +62 -20
  27. package/dist/harness-runtime/lib/dd-agy.mjs +17 -3
  28. package/dist/harness-runtime/lib/dd-droid.mjs +1 -1
  29. package/dist/harness-runtime/lib/dd-grok-daemon.mjs +70 -13
  30. package/dist/harness-runtime/lib/dd-grok.mjs +6 -3
  31. package/dist/harness-runtime/lib/dd-zcode.mjs +44 -14
  32. package/dist/harness-runtime/lib/delegation-instructions.mjs +1 -1
  33. package/dist/harness-runtime/lib/entity-references.d.mts +2 -0
  34. package/dist/harness-runtime/lib/entity-references.mjs +9 -0
  35. package/dist/harness-runtime/lib/managed-daemon.mjs +13 -6
  36. package/dist/harness-runtime/lib/native-hook-command.mjs +4 -4
  37. package/dist/harness-runtime/lib/opencode-plugin.mjs +3 -3
  38. package/dist/harness-runtime/lib/operation-errors.mjs +1 -1
  39. package/dist/harness-runtime/lib/provider-overload-policy.d.mts +4 -2
  40. package/dist/harness-runtime/lib/provider-overload-policy.mjs +19 -6
  41. package/dist/harness-runtime/lib/runner-events.mjs +3 -1
  42. package/dist/harness-runtime/lib/setup-progress.mjs +8 -6
  43. package/dist/schemas/mb-upgrade-stage-result.schema.json +863 -0
  44. package/dist/schemas/memory-repair-work-result.schema.json +9 -2
  45. package/dist/schemas/release-impact.schema.json +3 -3
  46. package/dist/schemas/stage-start-response.schema.json +4 -0
  47. package/dist/services/accepted-intent.js +13 -1
  48. package/dist/services/canon.js +43 -7
  49. package/dist/services/cli-operation-classifier.js +1 -1
  50. package/dist/services/cli-upgrade-allowlist.js +1 -1
  51. package/dist/services/code-checks.js +11 -4
  52. package/dist/services/compatibility-preflight.js +5 -2
  53. package/dist/services/controller-fanout.js +21 -4
  54. package/dist/services/controller-memory-task.js +45 -0
  55. package/dist/services/controller-memory-worker.js +48 -0
  56. package/dist/services/dashboard.js +2 -2
  57. package/dist/services/descriptor-run-start.js +26 -19
  58. package/dist/services/descriptor-stage-coordinator.js +30 -10
  59. package/dist/services/descriptor-stage-start.js +40 -4
  60. package/dist/services/engines.js +64 -14
  61. package/dist/services/eval-snapshots.js +104 -93
  62. package/dist/services/external-work-launch.js +5 -3
  63. package/dist/services/flow-catalog.js +8 -4
  64. package/dist/services/frozen-engine.js +113 -11
  65. package/dist/services/hook-memory-routing.js +188 -0
  66. package/dist/services/hook-recovery-guard.js +14 -0
  67. package/dist/services/hook-session-observation.js +24 -0
  68. package/dist/services/hooks.js +27 -8
  69. package/dist/services/hybrid-contracts.js +2 -13
  70. package/dist/services/hybrid-digest.js +14 -0
  71. package/dist/services/installed-flow-source.js +25 -4
  72. package/dist/services/lifecycle-invocations.js +33 -5
  73. package/dist/services/memory-accepted-composition.js +5 -4
  74. package/dist/services/memory-completed-correction-wave.js +23 -15
  75. package/dist/services/memory-compose-handler.js +163 -21
  76. package/dist/services/memory-composition-acceptance.js +186 -10
  77. package/dist/services/memory-composition-graph.js +42 -11
  78. package/dist/services/memory-composition-registration.js +23 -8
  79. package/dist/services/memory-composition-work-custody.js +14 -9
  80. package/dist/services/memory-composition-work-results.js +15 -3
  81. package/dist/services/memory-corrected-review-batch.js +10 -3
  82. package/dist/services/memory-corrected-review-findings.js +4 -2
  83. package/dist/services/memory-corrected-review-inputs.js +10 -5
  84. package/dist/services/memory-corrected-review-receipt.js +17 -9
  85. package/dist/services/memory-corrected-review-registration.js +3 -2
  86. package/dist/services/memory-correction-check-receipt.js +20 -4
  87. package/dist/services/memory-custody-snapshot.js +36 -0
  88. package/dist/services/memory-finish-evidence.js +44 -22
  89. package/dist/services/memory-finish-handler.js +37 -23
  90. package/dist/services/memory-finish-integration.js +218 -48
  91. package/dist/services/memory-finish-readiness.js +66 -19
  92. package/dist/services/memory-git-integration.js +87 -18
  93. package/dist/services/memory-historical-corrected-review.js +21 -15
  94. package/dist/services/memory-historical-original-review.js +22 -9
  95. package/dist/services/memory-historical-repair-registration.js +14 -8
  96. package/dist/services/memory-historical-second-review.js +30 -11
  97. package/dist/services/memory-init-contexts.js +28 -2
  98. package/dist/services/memory-init-controller-start.js +19 -8
  99. package/dist/services/memory-init-git-policy.js +50 -21
  100. package/dist/services/memory-init-lint-capability.js +21 -3
  101. package/dist/services/memory-init-native-capacity.js +13 -0
  102. package/dist/services/memory-init-preflight.js +11 -1
  103. package/dist/services/memory-init-request-candidate.js +73 -17
  104. package/dist/services/memory-init-request-input.js +23 -7
  105. package/dist/services/memory-init-run-bundle.js +49 -12
  106. package/dist/services/memory-init-storage.js +17 -7
  107. package/dist/services/memory-layer.js +68 -4
  108. package/dist/services/memory-lint-config.js +111 -2
  109. package/dist/services/memory-native-review-copy.js +5 -67
  110. package/dist/services/memory-normal-flow-entry.js +12 -0
  111. package/dist/services/memory-ordinary-defaults.js +22 -5
  112. package/dist/services/memory-preflight-handler.js +15 -0
  113. package/dist/services/memory-repair-current-view.js +1 -1
  114. package/dist/services/memory-repair-document-delta.js +70 -33
  115. package/dist/services/memory-repair-history.js +12 -7
  116. package/dist/services/memory-repair-physical-binding.js +1 -119
  117. package/dist/services/memory-repair-publication-plan.js +5 -3
  118. package/dist/services/memory-repair-publication-source.js +1 -1
  119. package/dist/services/memory-repair-publication.js +3 -2
  120. package/dist/services/memory-repair-registration.js +2 -1
  121. package/dist/services/memory-repair-work-baseline.js +4 -2
  122. package/dist/services/memory-repair-work-completion.js +32 -12
  123. package/dist/services/memory-repair-work-results.js +21 -2
  124. package/dist/services/memory-repair-workspace.js +11 -2
  125. package/dist/services/memory-repair-writer-copy.js +2 -1
  126. package/dist/services/memory-result-binding.js +78 -12
  127. package/dist/services/memory-retained-review-authority.js +17 -8
  128. package/dist/services/memory-retained-second-baseline.js +1 -1
  129. package/dist/services/memory-retained-second-repairs.js +12 -6
  130. package/dist/services/memory-review-correction-plan.js +47 -12
  131. package/dist/services/memory-review-handler.js +17 -17
  132. package/dist/services/memory-review-inputs.js +55 -9
  133. package/dist/services/memory-review-limits.js +10 -0
  134. package/dist/services/memory-review-receipt.js +2 -2
  135. package/dist/services/memory-review-registration.js +13 -4
  136. package/dist/services/memory-review-work-custody.js +3 -1
  137. package/dist/services/memory-review-work-input.js +26 -7
  138. package/dist/services/memory-review-work-results.js +26 -4
  139. package/dist/services/memory-second-corrected-review-inputs.js +5 -3
  140. package/dist/services/memory-second-correction-plan.js +2 -1
  141. package/dist/services/memory-second-repair-launch.js +4 -4
  142. package/dist/services/memory-second-repair-source.js +1 -1
  143. package/dist/services/memory-stage-dispatch.js +38 -1
  144. package/dist/services/memory-stage-results.js +113 -22
  145. package/dist/services/memory-upgrade-analysis-handler.js +243 -0
  146. package/dist/services/memory-upgrade-bootstrap-readiness.js +251 -0
  147. package/dist/services/memory-upgrade-evidence.js +100 -0
  148. package/dist/services/memory-upgrade-inventory.js +442 -0
  149. package/dist/services/memory-upgrade-publication-custody.js +73 -0
  150. package/dist/services/memory-upgrade-publication.js +413 -0
  151. package/dist/services/memory-upgrade-selection.js +430 -0
  152. package/dist/services/memory-upgrade-start.js +60 -0
  153. package/dist/services/memory-upgrade-storage-readiness.js +340 -0
  154. package/dist/services/memory-verification-acceptance.js +38 -17
  155. package/dist/services/memory-verification-checks.js +77 -23
  156. package/dist/services/memory-verification-receipt.js +6 -5
  157. package/dist/services/memory-verify-handler.js +15 -13
  158. package/dist/services/memory-work-result-json.js +12 -0
  159. package/dist/services/memory-work-result-preparation.js +5 -4
  160. package/dist/services/merge-server.js +4 -2
  161. package/dist/services/migrations.js +127 -98
  162. package/dist/services/physical-startup.js +45 -0
  163. package/dist/services/project-registry.js +97 -0
  164. package/dist/services/project-summary.js +1 -1
  165. package/dist/services/projects.js +3 -93
  166. package/dist/services/prompt-delivery.js +71 -0
  167. package/dist/services/provider-capacity.js +74 -9
  168. package/dist/services/recovery-snapshot-database.js +32 -4
  169. package/dist/services/repair-intents.js +1 -12
  170. package/dist/services/repair-receipt.js +13 -0
  171. package/dist/services/run-control-worker.js +35 -18
  172. package/dist/services/run-controller-process.js +2 -6
  173. package/dist/services/run-controller-recovery.js +6 -1
  174. package/dist/services/run-controller.js +131 -80
  175. package/dist/services/run-recovery-guard.js +30 -0
  176. package/dist/services/run-recovery.js +11 -44
  177. package/dist/services/run-start-custody.js +17 -5
  178. package/dist/services/runtime-scope-worker.js +46 -19
  179. package/dist/services/sessions.js +3 -24
  180. package/dist/services/setup.js +63 -13
  181. package/dist/services/stage-handlers.js +9 -7
  182. package/dist/services/stage-pause.js +108 -44
  183. package/dist/services/version-status.js +1 -1
  184. package/dist/services/vnext-fanout.js +21 -9
  185. package/dist/services/vnext-plan-review.js +3 -5
  186. package/dist/services/work-registry.js +80 -33
  187. package/dist/services/workspace-files.js +1 -1
  188. package/dist/shared/entity-references.js +1 -8
  189. package/dist/storage/data-preservation.js +65 -0
  190. package/dist/storage/database.js +53 -19
  191. package/dist/storage/import-guard.js +84 -27
  192. package/dist/storage/memory-upgrade-schema-bootstrap.js +38 -0
  193. package/dist/storage/paired-writer-migration.js +424 -0
  194. package/dist/storage/writer-contract.js +11 -4
  195. package/dist/storage/writer-migration.js +313 -52
  196. package/dist/storage/writer-restore.js +217 -0
  197. package/package.json +8 -4
@@ -50,7 +50,7 @@ export class PreparedInstalledFlowSource {
50
50
  }
51
51
  static fromInstalledRelease(home, expectedInstallationSha256, flowId) {
52
52
  try {
53
- if (!path.isAbsolute(home) || !/^[a-f0-9]{64}$/.test(expectedInstallationSha256) || flowId !== "mb-init")
53
+ if (!path.isAbsolute(home) || !/^[a-f0-9]{64}$/.test(expectedInstallationSha256) || !["mb-init", "mb-upgrade"].includes(flowId))
54
54
  refuse("invalid_selection");
55
55
  const selected = readSetupStatus(home);
56
56
  if (!selected.installation || selected.installation_readiness !== "installed")
@@ -120,6 +120,18 @@ export class PreparedInstalledFlowSource {
120
120
  * or admit its engine/storage; those remain the bootstrap owner's duties.
121
121
  */
122
122
  static restoreRetained(input, expectedSha256) {
123
+ const result = this.restoreRetainedData(input, expectedSha256);
124
+ try {
125
+ result.resolveRegisteredFlow();
126
+ return result;
127
+ }
128
+ catch {
129
+ return refuse("invalid_retained_source");
130
+ }
131
+ }
132
+ /** Data recovery before storage/engine admission. This cannot register a
133
+ * handler; productive restoration still uses restoreRetained above. */
134
+ static restoreRetainedData(input, expectedSha256) {
123
135
  try {
124
136
  if (!input || typeof input !== "object" || Array.isArray(input) || typeof expectedSha256 !== "string"
125
137
  || !/^[a-f0-9]{64}$/.test(expectedSha256))
@@ -132,7 +144,7 @@ export class PreparedInstalledFlowSource {
132
144
  || typeof value.canon.archive_sha256 !== "string" || !/^[a-f0-9]{64}$/.test(value.canon.archive_sha256)
133
145
  || typeof value.canon.version !== "string" || !/^\d+\.\d+\.\d+(?:-[A-Za-z0-9.-]+)?$/.test(value.canon.version)
134
146
  || !value.descriptor || Object.keys(value.descriptor).sort().join() !== "path,sha256"
135
- || value.descriptor.path !== "mb-init/flow-descriptor.json" || typeof value.descriptor.sha256 !== "string" || !/^[a-f0-9]{64}$/.test(value.descriptor.sha256)
147
+ || !["mb-init/flow-descriptor.json", "mb-upgrade/flow-descriptor.json"].includes(value.descriptor.path) || typeof value.descriptor.sha256 !== "string" || !/^[a-f0-9]{64}$/.test(value.descriptor.sha256)
136
148
  || !Array.isArray(value.blobs) || value.blobs.length < 1 || value.blobs.length > 129)
137
149
  refuse("invalid_retained_source");
138
150
  const blobs = new Map(), folded = new Set();
@@ -154,10 +166,19 @@ export class PreparedInstalledFlowSource {
154
166
  const descriptor = blobs.get(value.descriptor.path);
155
167
  if (!descriptor || hash(descriptor) !== value.descriptor.sha256)
156
168
  refuse("retained_source_changed");
157
- const result = new PreparedInstalledFlowSource(null, value.installation_sha256, "mb-init", { ...value.descriptor }, { ...value.canon }, blobs);
169
+ const flowId = value.descriptor.path.startsWith("mb-upgrade/") ? "mb-upgrade" : "mb-init";
170
+ const declared = JSON.parse(descriptor.toString("utf8"));
171
+ if (validateHybridShape("flow-descriptor", declared).length || declared.id !== flowId || declared.origin !== "canon")
172
+ refuse("invalid_retained_source");
173
+ for (const stage of declared.stages)
174
+ for (const kind of ["instruction", "result_schema"]) {
175
+ const reference = stage[kind], content = blobs.get(reference.path);
176
+ if (!content || hash(content) !== reference.sha256)
177
+ refuse("retained_source_changed");
178
+ }
179
+ const result = new PreparedInstalledFlowSource(null, value.installation_sha256, flowId, { ...value.descriptor }, { ...value.canon }, blobs);
158
180
  if (result.sha256 !== expectedSha256)
159
181
  refuse("retained_source_changed");
160
- result.resolveRegisteredFlow();
161
182
  return result;
162
183
  }
163
184
  catch (error) {
@@ -8,16 +8,15 @@ import { AppError, errorRecord } from "../shared/errors.js";
8
8
  import { shortEntityReference } from "../shared/entity-references.js";
9
9
  import { resolveWorkReference } from "../storage/work-references.js";
10
10
  import { commandOption, commandPosition, continuationCommand, lifecycleCommandFromPayload, parseLifecycleCommand, shellSuffixOffset } from "./lifecycle-command.js";
11
- import { requireProjectByRoot } from "./projects.js";
12
- import { assertRunMutationAllowed, assertStageSettlementAllowed, assertWorkSettlementAllowed, recoveryGuard, recoveryPacketReceipt } from "./run-recovery.js";
11
+ import { requireProjectByRoot } from "./project-registry.js";
12
+ import { assertRunMutationAllowed, assertStageSettlementAllowed, assertWorkSettlementAllowed, recoveryGuard, recoveryPacketReceipt } from "./run-recovery-guard.js";
13
13
  import { readNativeDaemonState, requireManagedDaemonBinding } from "./managed-daemon-binding.js";
14
14
  import { managedProcessStatus, processIsAlive, processStartedAt } from "./managed-processes.js";
15
15
  import { appendEvent } from "./run-controller-state.js";
16
- import { recoverCommittedWorkStart } from "./work-registry.js";
17
16
  import { observeCommandHook } from "./hooks.js";
18
17
  import { runtimeHarness } from "./execution-policy.js";
19
18
  import { normalizedPathAlias, resolveContextualPaths } from "./command-context.js";
20
- import { isRegisteredRepair } from "./repair-intents.js";
19
+ import { isRegisteredRepair } from "./repair-receipt.js";
21
20
  import { stageAttempt } from "./stage-work-graph.js";
22
21
  import { contextualPathOptionsForLifecycle } from "../cli/command-inputs.js";
23
22
  import { canonicalPath } from "../storage/paths.js";
@@ -1930,9 +1929,38 @@ export async function awaitLifecycleInvocation(context, input) {
1930
1929
  return { outcome: JSON.parse(row.outcome_json), replay: true };
1931
1930
  if (row.status === "executing") {
1932
1931
  const scope = JSON.parse(row.scope_json);
1932
+ const parsed = invocation(row.command);
1933
+ if (parsed.operation === "stage_resume" && scope.runId) {
1934
+ input.assertCurrent(scope);
1935
+ const { recoverCommittedStageResume } = await import("./stage-pause.js");
1936
+ const result = recoverCommittedStageResume(context, { id: row.id, projectRoot: scope.projectRoot, runId: scope.runId, generation: scope.generation, stage: commandOption(parsed, "stage") ?? "", workId: commandOption(parsed, "work") ?? "" });
1937
+ if (result) {
1938
+ const { preparePromptDelivery } = await import("./prompt-delivery.js");
1939
+ const delivered = preparePromptDelivery(result);
1940
+ const previousScope = context.env.DD_FLOW_INVOCATION_SCOPE;
1941
+ context.env.DD_FLOW_INVOCATION_SCOPE = row.scope_json;
1942
+ try {
1943
+ bindLifecyclePublishedPacket(context, String(result.worker_prompt_markdown));
1944
+ }
1945
+ finally {
1946
+ if (previousScope === undefined)
1947
+ delete context.env.DD_FLOW_INVOCATION_SCOPE;
1948
+ else
1949
+ context.env.DD_FLOW_INVOCATION_SCOPE = previousScope;
1950
+ }
1951
+ const project = requireProjectByRoot(context, scope.projectRoot);
1952
+ const event = row.event_key ? context.db.get("SELECT id FROM hook_events WHERE project_id = ? AND event_key = ?", [project.id, row.event_key]) : null;
1953
+ if (event)
1954
+ observeLifecycleCommand(context, row.command, { project_id: project.id, id: event.id })({ result: delivered });
1955
+ const outcome = { result: delivered, effect: "committed" };
1956
+ settleLifecycleInvocation(context, row.id, outcome);
1957
+ return { outcome, replay: true };
1958
+ }
1959
+ }
1933
1960
  if (invocation(row.command).operation === "work_start" && row.event_key) {
1934
1961
  input.assertCurrent(scope);
1935
- const result = recoverCommittedWorkStart(context, row.id, row.event_key, scope.generation);
1962
+ const { recoverCommittedWorkStart } = await import("./work-registry.js");
1963
+ const result = recoverCommittedWorkStart(context, row.id, row.event_key, scope.generation, undefined, input.progress);
1936
1964
  if (result) {
1937
1965
  const outcome = { result, effect: "committed" };
1938
1966
  settleLifecycleInvocation(context, row.id, outcome);
@@ -35,15 +35,16 @@ export class PreparedAcceptedMemoryComposition {
35
35
  }
36
36
  static prepare(context, input) {
37
37
  const { scope, bundle } = input;
38
- if (!(bundle instanceof RetainedExecutionBundle) || bundle.descriptor.descriptor.id !== "mb-init")
38
+ if (!(bundle instanceof RetainedExecutionBundle) || !["mb-init", "mb-upgrade"].includes(bundle.descriptor.descriptor.id))
39
39
  refuse("registered_memory_bundle_required");
40
+ const stage = bundle.descriptor.descriptor.id === "mb-init" ? "compose" : "upgrade";
40
41
  const run = assertPreparedRunBundleSqlAuthority(context, scope, bundle), root = readDescriptorRootWork(context, scope, bundle);
41
- const registration = selectMemoryCompositionRegistration(context, { ...stageGraphScope(context, scope.projectId, scope.runId, "compose"), stage: "compose", parent_work_id: root.workId });
42
+ const registration = selectMemoryCompositionRegistration(context, { ...stageGraphScope(context, scope.projectId, scope.runId, stage), stage, parent_work_id: root.workId });
42
43
  const blueprint = PreparedMemoryResultBinding.prepareRegisteredComposition(context, { ...scope, flow: bundle.descriptor, execution: bundle.execution, bundle, acceptedReplay: true,
43
44
  registrationScope: registration.scope });
44
45
  const outputs = PreparedMemoryCompositionAcceptance.prepare(context, { scope: registration.scope, flow: bundle.descriptor, engine: bundle.engine(), binding: blueprint.binding() });
45
- const composed = readAcceptedStageData(context, { ...scope, stage: "compose" }), runRoot = run.run_root ?? run.run_dir;
46
- const binding = PreparedMemoryResultBinding.prepare(context, { ...scope, stage: "compose", flow: bundle.descriptor, execution: bundle.execution, bundle, acceptedReplay: true,
46
+ const composed = readAcceptedStageData(context, { ...scope, stage }), runRoot = run.run_root ?? run.run_dir;
47
+ const binding = PreparedMemoryResultBinding.prepare(context, { ...scope, stage, flow: bundle.descriptor, execution: bundle.execution, bundle, acceptedReplay: true,
47
48
  sources: captureMemoryCompositionSources(context, bundle, runRoot, composed.bytes, outputs), historicalEvidence: outputs.historicalEvidence() });
48
49
  if (!binding.acceptedBytes()?.equals(composed.bytes))
49
50
  refuse("accepted_composition_changed");
@@ -1,9 +1,11 @@
1
+ import { assertMemorySqlCustody } from "./memory-custody-snapshot.js";
1
2
  import { AppError } from "../shared/errors.js";
2
3
  import { hybridDigest } from "./hybrid-contracts.js";
3
4
  import { PreparedRegisteredMemoryCorrections } from "./memory-repair-registration.js";
4
5
  import { PreparedRetainedSecondMemoryRepairs } from "./memory-retained-second-repairs.js";
5
6
  import { PreparedSettledMemoryRepair } from "./memory-repair-history.js";
6
7
  import { PreparedMemoryRepairWorkspace } from "./memory-repair-workspace.js";
8
+ import { isCompletedMemoryRepairClaim } from "./memory-repair-work-results.js";
7
9
  import { reviewSourcePaths } from "./review-copy.js";
8
10
  import { readAcceptedStageData } from "./stage-report-renderer.js";
9
11
  const token = Symbol("completed-memory-correction-wave");
@@ -41,8 +43,7 @@ export function memoryCorrectionAffectedUnits(units, repairs, composition = [])
41
43
  const affected = new Set(), changed = new Set();
42
44
  for (const repair of repairs) {
43
45
  if (!repair || !Array.isArray(repair.unit_ids) || !repair.unit_ids.length || new Set(repair.unit_ids).size !== repair.unit_ids.length
44
- || repair.unit_ids.some((id) => !applicable.has(id)) || repair.result?.outcome !== "accepted"
45
- || !Array.isArray(repair.result.changes) || !repair.result.changes.length)
46
+ || repair.unit_ids.some((id) => !applicable.has(id)) || !isCompletedMemoryRepairClaim(repair.result))
46
47
  refuse("invalid_settled_repair_projection");
47
48
  for (const id of repair.unit_ids)
48
49
  affected.add(id);
@@ -138,6 +139,7 @@ const nonMemory = (entry) => entry.path !== ".memory-bank" && !entry.path.starts
138
139
  * review. Reconstruction starts at independently retained first-wave state;
139
140
  * neither current files nor a second writer's claimed before-view are anchors. */
140
141
  export class PreparedHistoricalSecondMemoryCorrectionWave {
142
+ #custodyKey = {};
141
143
  static isPrepared(value) {
142
144
  return typeof value === "object" && value !== null && #registration in value;
143
145
  }
@@ -167,11 +169,11 @@ export class PreparedHistoricalSecondMemoryCorrectionWave {
167
169
  for (const history of histories) {
168
170
  if (!prefixes.has(hybridDigest(history.before().entries())))
169
171
  refuse("unexplained_historical_input_view");
170
- baseline = baseline.composeSettledDelta(history.before(), history.after());
172
+ baseline = baseline.composeSettledDelta(history.before(), history.after(), history.result().outcome);
171
173
  prefixes.add(hybridDigest(baseline.entries()));
172
174
  }
173
175
  const batch = registration.batch(), receipt = registration.receipt(), first = registration.initialInputView();
174
- const compose = readAcceptedStageData(context, { ...input.scope, stage: "compose" });
176
+ const compose = readAcceptedStageData(context, { ...input.scope, stage: input.bundle.descriptor.descriptor.id === "mb-upgrade" ? "upgrade" : "compose" });
175
177
  const affected = memoryCorrectionAffectedUnits(memoryCorrectionCurrentUnits(first.unit_inventory, first.assignments), histories.map(history => ({ result: history.result(),
176
178
  unit_ids: batch.works.find(item => receipt.work_ids[item.key] === history.workId).payload.memory_repair.unit_ids })), JSON.parse(compose.bytes.toString("utf8")).details.works);
177
179
  const prepared = new PreparedHistoricalSecondMemoryCorrectionWave(historicalToken, registration, selected, histories, baseline, affected);
@@ -189,10 +191,13 @@ export class PreparedHistoricalSecondMemoryCorrectionWave {
189
191
  unit_ids: batch.works.find(item => receipt.work_ids[item.key] === history.workId).payload.memory_repair.unit_ids }));
190
192
  }
191
193
  assertCustody(context) {
192
- if (hybridDigest(selection(context, this.#registration)) !== hybridDigest(this.#selected))
193
- refuse("historical_second_graph_changed");
194
- for (const history of this.#histories)
195
- history.assertCustody(context);
194
+ const registration = this.#registration;
195
+ assertMemorySqlCustody(context, this.#custodyKey, () => {
196
+ if (hybridDigest(selection(context, registration)) !== hybridDigest(this.#selected))
197
+ refuse("historical_second_graph_changed");
198
+ for (const history of this.#histories)
199
+ history.assertCustody(context);
200
+ });
196
201
  }
197
202
  revalidate(context) {
198
203
  this.#registration.revalidate(context);
@@ -207,6 +212,7 @@ export class PreparedHistoricalSecondMemoryCorrectionWave {
207
212
  * from today's filesystem. This owner neither allocates reviewers nor marks
208
213
  * repair findings resolved; independent re-review must consume it separately. */
209
214
  export class PreparedCompletedMemoryCorrectionWave {
215
+ #custodyKey = {};
210
216
  static isPrepared(value) {
211
217
  return typeof value === "object" && value !== null && #selected in value;
212
218
  }
@@ -252,7 +258,7 @@ export class PreparedCompletedMemoryCorrectionWave {
252
258
  for (const history of histories) {
253
259
  if (!known.has(hybridDigest(history.before().entries())))
254
260
  refuse("unexplained_historical_input_view");
255
- baseline = baseline.composeSettledDelta(history.before(), history.after());
261
+ baseline = baseline.composeSettledDelta(history.before(), history.after(), history.result().outcome);
256
262
  known.add(hybridDigest(baseline.entries()));
257
263
  }
258
264
  const view = PreparedRetainedSecondMemoryRepairs.isPrepared(registration) ? registration.initialInputView()
@@ -272,7 +278,7 @@ export class PreparedCompletedMemoryCorrectionWave {
272
278
  if (histories.some(history => hybridDigest(history.workspaceManifest().entries().filter(nonMemory)) !== sourceSha256))
273
279
  refuse("non_memory_source_changed");
274
280
  const batch = registration.batch(), receipt = registration.receipt();
275
- const compose = readAcceptedStageData(context, { projectId: receipt.scope.project_id, runId: receipt.run_id, stage: "compose" });
281
+ const compose = readAcceptedStageData(context, { projectId: receipt.scope.project_id, runId: receipt.run_id, stage: bundle.descriptor.descriptor.id === "mb-upgrade" ? "upgrade" : "compose" });
276
282
  const dependencies = JSON.parse(compose.bytes.toString("utf8")).details.works;
277
283
  const currentUnits = PreparedRetainedSecondMemoryRepairs.isPrepared(registration)
278
284
  ? memoryCorrectionCurrentUnits(registration.initialInputView().unit_inventory, registration.initialInputView().assignments)
@@ -292,11 +298,13 @@ export class PreparedCompletedMemoryCorrectionWave {
292
298
  repairResults() { return this.#histories.map(history => ({ work_id: history.workId, result: history.result(), reference: history.reference(), authority_sha256: history.sha256 })); }
293
299
  /** Only SQL custody is inspected inside the future review-allocation writer. */
294
300
  assertCustody(context) {
295
- const selected = this.#selected;
296
- if (hybridDigest(selection(context, this.#registration)) !== hybridDigest(selected))
297
- refuse("completed_wave_custody_changed");
298
- for (const history of this.#histories)
299
- history.assertCustody(context);
301
+ assertMemorySqlCustody(context, this.#custodyKey, () => {
302
+ const selected = this.#selected;
303
+ if (hybridDigest(selection(context, this.#registration)) !== hybridDigest(selected))
304
+ refuse("completed_wave_custody_changed");
305
+ for (const history of this.#histories)
306
+ history.assertCustody(context);
307
+ });
300
308
  }
301
309
  /** Fresh allocation fence, before adding successor reviewers. General
302
310
  * custody deliberately stays valid when those admitted children exist. */
@@ -20,6 +20,14 @@ import { PreparedMemoryCompositionAcceptance } from "./memory-composition-accept
20
20
  import { readAcceptedStageData } from "./stage-report-renderer.js";
21
21
  import { ObservedMemorySources } from "./memory-source-observations.js";
22
22
  import { PreparedCheckEvidence } from "./code-checks.js";
23
+ import { captureMemoryUpgradeSources } from "./memory-upgrade-analysis-handler.js";
24
+ import { PreparedMemoryUpgradeBootstrapReadiness, readMemoryUpgradeBootstrapReadiness } from "./memory-upgrade-bootstrap-readiness.js";
25
+ import { readUpgradeStorageReadiness } from "./memory-upgrade-storage-readiness.js";
26
+ import { PreparedMemoryUpgradePublication } from "./memory-upgrade-publication.js";
27
+ import { PreparedMemoryLayer } from "./memory-layer.js";
28
+ import { appendAudit } from "./audit.js";
29
+ import { assertImportAdmission } from "../storage/import-guard.js";
30
+ import { readMemoryUpgradePublication, upgradePublicationKey, upgradePublicationReceipt, upgradePublicationIntentEvent, upgradePublicationCompleteEvent } from "./memory-upgrade-publication-custody.js";
23
31
  function refuse(reason) {
24
32
  throw new AppError("memory_compose_handler_invalid", "Memory composition lacks retained scope, plan or output custody", 2, { reason, effect: "no_effect" });
25
33
  }
@@ -27,12 +35,30 @@ function selected(context, input) {
27
35
  if (!(input.bytes instanceof Uint8Array) || input.bytes.length > 1024 * 1024)
28
36
  refuse("invalid_input_bytes");
29
37
  const bundle = readFlowRunExecutionBundle(context, input);
30
- if (!bundle || bundle.descriptor.descriptor.id !== "mb-init" || bundle.descriptor.successor("discover", "accepted") !== "compose"
31
- || bundle.descriptor.successor("compose", "accepted") !== "verify")
38
+ if (!bundle || !["mb-init", "mb-upgrade"].includes(bundle.descriptor.descriptor.id))
32
39
  refuse("registered_memory_bundle_required");
33
- const project = requireProjectByRoot(context, resolveProjectRoot(input.projectRoot)), scope = { projectId: project.id, runId: input.runId, stage: "compose" };
40
+ const stage = bundle.descriptor.descriptor.id === "mb-init" ? "compose" : "upgrade";
41
+ if (bundle.descriptor.successor(stage === "compose" ? "discover" : "diff-analysis", "accepted") !== stage
42
+ || bundle.descriptor.successor(stage, "accepted") !== "verify")
43
+ refuse("registered_memory_bundle_required");
44
+ const project = requireProjectByRoot(context, resolveProjectRoot(input.projectRoot)), scope = { projectId: project.id, runId: input.runId, stage };
34
45
  const run = assertPreparedRunBundleSqlAuthority(context, scope, bundle), index = JSON.parse(run.index_json);
35
- return { bundle, scope, run, accepted: index.stage_runs.find((stage) => stage.stage === "compose")?.status === "done" };
46
+ return { bundle, scope, run, accepted: index.stage_runs.find((item) => item.stage === stage)?.status === "done" };
47
+ }
48
+ function compositionBaseline(context, bundle) {
49
+ if (bundle.descriptor.descriptor.id === "mb-init")
50
+ return PreparedMemoryBaselinePreservation.prepare(context, bundle.execution);
51
+ const selected = bundle.execution.snapshot().scope;
52
+ const scope = { requestId: selected.requestId, projectRoot: selected.projectRoot, workspaceRoot: selected.workspace };
53
+ const ready = readMemoryUpgradeBootstrapReadiness(context, scope);
54
+ if (!ready?.hasPreservationBaseline || ready.authority.binding.execution_sha256 !== bundle.execution.sha256)
55
+ refuse("accepted_upgrade_bootstrap_required");
56
+ const authority = hybridDigest(ready.authority);
57
+ return {
58
+ assertCustody(writer) { const current = readMemoryUpgradeBootstrapReadiness(writer, scope); if (!current || hybridDigest(current.authority) !== authority)
59
+ refuse("upgrade_bootstrap_changed"); },
60
+ revalidate(reader) { PreparedMemoryUpgradeBootstrapReadiness.assertForRetainedRun(reader, bundle); this.assertCustody(reader); }
61
+ };
36
62
  }
37
63
  /** SQL-only physical binding proof; allocation/prompt custody is checked
38
64
  * separately. No new Session, hook claim or provider invocation is created.
@@ -70,14 +96,14 @@ export function registerMemoryCompositionFromStage(context, input) {
70
96
  const { bundle, scope, accepted } = selected(context, input);
71
97
  if (accepted)
72
98
  refuse("use_accepted_finish_replay");
73
- const coordinator = PreparedDescriptorStageCoordinator.prepare(context, { ...input, stage: "compose" });
99
+ const coordinator = PreparedDescriptorStageCoordinator.prepare(context, { ...input, stage: scope.stage });
74
100
  const allocated = context.db.readSnapshot(() => {
75
101
  coordinator.assertAllocatedCustody(context);
76
102
  const root = readDescriptorRootWork(context, scope, bundle);
77
103
  return { workId: root.workId, physical: coordinatorSession(context, scope, bundle, root.workId) };
78
104
  });
79
105
  const binding = PreparedMemoryResultBinding.prepare(context, { ...scope, flow: bundle.descriptor, execution: bundle.execution, bundle,
80
- sources: captureMemoryPreflightSources(context, bundle, input.bytes) });
106
+ sources: scope.stage === "compose" ? captureMemoryPreflightSources(context, bundle, input.bytes) : captureMemoryUpgradeSources(context, bundle, scope.stage, input.bytes) });
81
107
  let composition;
82
108
  try {
83
109
  composition = PreparedMemoryStageResult.prepare(input.bytes, bundle.descriptor, binding.binding());
@@ -94,7 +120,7 @@ export function registerMemoryCompositionFromStage(context, input) {
94
120
  const ordinaryDefaults = readDescriptorStageInput(context, scope, bundle).input.ordinary_defaults;
95
121
  if (ordinaryDefaults) {
96
122
  assertMemoryOrdinaryConfigurationCoverage(composition.value(), ordinaryDefaults);
97
- if (ordinaryDefaults.missing.length && !context.db.get("SELECT 1 FROM audit_events WHERE project_id=? AND event_type='flow_run.stage_resumed' AND json_extract(payload_json,'$.run_id')=? AND json_extract(payload_json,'$.stage')='compose' LIMIT 1", [scope.projectId, scope.runId])) {
123
+ if (scope.stage === "compose" && ordinaryDefaults.missing.length && !context.db.get("SELECT 1 FROM audit_events WHERE project_id=? AND event_type='flow_run.stage_resumed' AND json_extract(payload_json,'$.run_id')=? AND json_extract(payload_json,'$.stage')='compose' LIMIT 1", [scope.projectId, scope.runId])) {
98
124
  throw new AppError("memory_stage_result_invalid", "Resolve the missing ordinary configuration choice through the existing compose pause/answer path before registering Work", 2, { phase: "prepare", effect: "no_effect", recoverable: true, reason: "ordinary_configuration_choice_required", missing: ordinaryDefaults.missing });
99
125
  }
100
126
  }
@@ -114,25 +140,136 @@ export function startMemoryComposition(context, input) {
114
140
  const { bundle, scope, accepted } = selected(context, input);
115
141
  if (accepted)
116
142
  refuse("use_accepted_finish_replay");
117
- const preservation = PreparedMemoryBaselinePreservation.prepare(context, bundle.execution);
118
- const inherited = context.db.readSnapshot(() => ({ discovery: readAcceptedStageData(context, { ...scope, stage: "discover" }), preflight: readAcceptedStageData(context, { ...scope, stage: "preflight" }) }));
143
+ const preservation = compositionBaseline(context, bundle), discoveryStage = scope.stage === "compose" ? "discover" : "diff-analysis";
144
+ const inherited = context.db.readSnapshot(() => ({ discovery: readAcceptedStageData(context, { ...scope, stage: discoveryStage }), preflight: readAcceptedStageData(context, { ...scope, stage: "preflight" }) }));
119
145
  const preflight = JSON.parse(inherited.preflight.bytes.toString("utf8")), discovery = JSON.parse(inherited.discovery.bytes.toString("utf8"));
120
- if (preflight.schema_id !== "dd-flow/mb-init-preflight-result@1" || preflight.run_id !== input.runId || preflight.outcome !== "accepted"
121
- || discovery.schema_id !== "dd-flow/mb-init-discover-result@1"
146
+ if (preflight.schema_id !== (scope.stage === "compose" ? "dd-flow/mb-init-preflight-result@1" : "dd-flow/mb-upgrade-stage-result@1") || preflight.run_id !== input.runId || preflight.outcome !== "accepted"
147
+ || discovery.schema_id !== (scope.stage === "compose" ? "dd-flow/mb-init-discover-result@1" : "dd-flow/mb-upgrade-stage-result@1")
148
+ || preflight.stage !== "preflight" || discovery.stage !== discoveryStage
122
149
  || discovery.run_id !== input.runId || discovery.outcome !== "accepted")
123
150
  refuse("accepted_discovery_and_baseline_required");
124
151
  const fence = (writer) => {
125
152
  preservation.assertCustody(writer);
126
- if (readAcceptedStageData(writer, { ...scope, stage: "discover" }).authority_sha256 !== inherited.discovery.authority_sha256
153
+ if (readAcceptedStageData(writer, { ...scope, stage: discoveryStage }).authority_sha256 !== inherited.discovery.authority_sha256
127
154
  || readAcceptedStageData(writer, { ...scope, stage: "preflight" }).authority_sha256 !== inherited.preflight.authority_sha256)
128
155
  refuse("inherited_custody_changed");
129
156
  };
130
- const start = PreparedDescriptorStageStart.prepare(context, { ...input, stage: "compose" });
157
+ const start = PreparedDescriptorStageStart.prepare(context, { ...input, stage: scope.stage });
131
158
  preservation.revalidate(context);
132
- const stage = start.apply(context, fence), coordinator = PreparedDescriptorStageCoordinator.prepare(context, { ...input, stage: "compose" });
159
+ const stage = start.apply(context, fence), coordinator = PreparedDescriptorStageCoordinator.prepare(context, { ...input, stage: scope.stage });
133
160
  preservation.revalidate(context);
134
161
  return { ...stage, ...coordinator.apply(context, fence) };
135
162
  }
163
+ /** Mechanical upgrade continuation, not final Stage acceptance. Reuses the
164
+ * registered blueprint and settled Work; the plan is retained in SQL before
165
+ * the first filesystem effect. No provider or second controller is launched.
166
+ * Kept outside the registry until the complete upgrade lifecycle is admitted.
167
+ */
168
+ export function publishMemoryUpgradeFromStage(context, input) {
169
+ const { bundle, scope, accepted } = selected(context, { ...input, bytes: Buffer.alloc(0) });
170
+ if (scope.stage !== "upgrade" || accepted)
171
+ refuse("active_upgrade_publication_required");
172
+ const coordinator = PreparedDescriptorStageCoordinator.prepare(context, { ...input, stage: "upgrade" });
173
+ const allocated = context.db.readSnapshot(() => {
174
+ coordinator.assertAllocatedCustody(context);
175
+ const root = readDescriptorRootWork(context, scope, bundle);
176
+ return { workId: root.workId, physical: coordinatorSession(context, scope, bundle, root.workId) };
177
+ });
178
+ const registration = selectMemoryCompositionRegistration(context, { ...stageGraphScope(context, scope.projectId, scope.runId, "upgrade"), stage: "upgrade", parent_work_id: allocated.workId });
179
+ const binding = PreparedMemoryResultBinding.prepareRegisteredComposition(context, { ...scope, flow: bundle.descriptor, execution: bundle.execution, bundle, registrationScope: registration.scope });
180
+ const outputs = PreparedMemoryCompositionAcceptance.prepareForUpgradePublication(context, { scope: registration.scope, flow: bundle.descriptor, engine: bundle.engine(), binding: binding.binding() });
181
+ const frozen = bundle.execution.snapshot().scope, bootstrapScope = { requestId: frozen.requestId, projectRoot: frozen.projectRoot, workspaceRoot: frozen.workspace };
182
+ const ready = readMemoryUpgradeBootstrapReadiness(context, bootstrapScope), storage = readUpgradeStorageReadiness(context, bootstrapScope);
183
+ if (!ready || !storage?.selection || ready.authority.binding.storage_sha256 !== storage.sha256)
184
+ refuse("accepted_upgrade_bootstrap_required");
185
+ const identity = { scope: registration.scope, selection_sha256: storage.selection.sha256, execution_sha256: bundle.execution.sha256,
186
+ registration_sha256: hybridDigest(registration), bootstrap_sha256: hybridDigest(ready.authority),
187
+ work_custody_sha256: outputs.custodySha256() };
188
+ const fence = (writer) => {
189
+ assertPreparedRunBundleSqlAuthority(writer, scope, bundle);
190
+ coordinator.assertAllocatedCustody(writer);
191
+ binding.assertCustody(writer);
192
+ outputs.assertCustody(writer);
193
+ if (readDescriptorRootWork(writer, scope, bundle).workId !== allocated.workId
194
+ || coordinatorSession(writer, scope, bundle, allocated.workId) !== allocated.physical)
195
+ refuse("upgrade_publication_coordinator_changed");
196
+ const current = readMemoryUpgradeBootstrapReadiness(writer, bootstrapScope), retained = readUpgradeStorageReadiness(writer, bootstrapScope);
197
+ if (!current || hybridDigest(current.authority) !== identity.bootstrap_sha256 || retained?.selection?.sha256 !== identity.selection_sha256)
198
+ refuse("upgrade_publication_dependencies_changed");
199
+ };
200
+ const read = (reader) => readMemoryUpgradePublication(reader, identity);
201
+ let plan = read(context)?.plan;
202
+ if (!plan) {
203
+ binding.revalidate(context);
204
+ outputs.revalidate(context);
205
+ const selected = storage.selection.payload, layer = selected.layer;
206
+ if (!selected.current.old_tree || !selected.current.version_source)
207
+ refuse("upgrade_publication_baseline_required");
208
+ const target = PreparedMemoryLayer.restoreRetained(new Map(layer.files.map(file => [file.path, Buffer.from(file.base64, "base64")])), layer.sha256, layer.installation_sha256);
209
+ const diff = readAcceptedStageData(context, { ...scope, stage: "diff-analysis" }), details = JSON.parse(diff.bytes.toString("utf8")).details;
210
+ plan = PreparedMemoryUpgradePublication.prepare({ selectionSha256: storage.selection.sha256, inventory: selected.current.inventory,
211
+ oldTree: selected.current.old_tree, layer: target, changes: details.path_map, versionSource: selected.current.version_source });
212
+ const before = new Map(plan.beforeFiles().map(file => [file.path, file]));
213
+ const settled = new Map(outputs.settledArtifacts().filter(item => item.artifact.root === "workspace").map(item => [item.artifact.path, item.artifact.sha256]));
214
+ for (const entry of selected.current.inventory.inventory.entries)
215
+ if (entry.current && !before.has(entry.path))
216
+ refuse("unsettled_semantic_removal");
217
+ for (const [name, file] of before) {
218
+ const original = selected.current.inventory.inventory.entries.find(entry => entry.path === name)?.current;
219
+ if (original && file.kind === original.kind && file.mode === original.mode && file.sha256 === original.sha256)
220
+ continue;
221
+ if (file.kind !== "file" || original && file.mode !== original.mode || settled.get(`.memory-bank/${name}`) !== file.sha256)
222
+ refuse("unsettled_semantic_change");
223
+ }
224
+ const prepared = plan;
225
+ plan = context.db.writeTransaction(() => {
226
+ fence(context);
227
+ const prior = read(context)?.plan;
228
+ if (prior) {
229
+ if (prior.sha256 !== prepared.sha256)
230
+ refuse("upgrade_publication_plan_changed");
231
+ return prior;
232
+ }
233
+ const now = context.now();
234
+ context.db.run("INSERT INTO runtime_config (key,value_json,created_at,updated_at) VALUES (?,?,?,?)", [upgradePublicationKey(identity), JSON.stringify({ binding: identity, plan_json: prepared.bytes().toString("utf8"), plan_sha256: prepared.sha256 }), now, now]);
235
+ appendAudit(context, { projectId: scope.projectId, eventType: upgradePublicationIntentEvent, payload: upgradePublicationReceipt(identity, prepared) });
236
+ const retained = read(context);
237
+ if (!retained)
238
+ refuse("upgrade_publication_intent_readback_failed");
239
+ return retained.plan;
240
+ });
241
+ }
242
+ const publication = plan;
243
+ try {
244
+ while (true) {
245
+ assertImportAdmission(context.ddFlowHome);
246
+ context.db.readSnapshot(() => { fence(context); if (read(context)?.plan.sha256 !== publication.sha256)
247
+ refuse("upgrade_publication_intent_changed"); });
248
+ if (publication.applyNextAction().status === "applied")
249
+ break;
250
+ }
251
+ context.db.writeTransaction(() => {
252
+ fence(context);
253
+ if (read(context)?.plan.sha256 !== publication.sha256)
254
+ refuse("upgrade_publication_intent_changed");
255
+ if (!read(context)?.completed)
256
+ appendAudit(context, { projectId: scope.projectId, eventType: upgradePublicationCompleteEvent, payload: upgradePublicationReceipt(identity, publication, true) });
257
+ if (!read(context)?.completed)
258
+ refuse("upgrade_publication_completion_readback_failed");
259
+ });
260
+ const state = publication.classify();
261
+ if (state.completed !== state.total)
262
+ refuse("upgrade_publication_not_complete");
263
+ const record = publication.outputs().find(output => output.path === `archive/mb-upgrade/${identity.selection_sha256}/migration.json`);
264
+ if (!record)
265
+ refuse("upgrade_migration_record_missing");
266
+ return { status: "applied", publication_sha256: publication.sha256, migration_record: { path: `.memory-bank/${record.path}`, sha256: record.sha256 },
267
+ artifacts: publication.outputs().map(output => ({ root: "workspace", path: `.memory-bank/${output.path}`, sha256: output.sha256 })) };
268
+ }
269
+ catch (error) {
270
+ throw new AppError("memory_upgrade_publication_pending", "Preserve the accepted SQL plan and reconcile this upgrade continuation", 1, { effect: "publication_pending", publication_sha256: publication.sha256, cause: error instanceof Error ? error.message : String(error) });
271
+ }
272
+ }
136
273
  /** Agent references are selectors, not permissions. Supplement independent
137
274
  * baseline evidence only with current outputs admitted by completed Work.
138
275
  */
@@ -207,7 +344,10 @@ export function captureMemoryCompositionSources(context, bundle, runRoot, bytes,
207
344
  }
208
345
  // Keep evidence/artifact roles for the shared collector; settled child output
209
346
  // references already have their independent output owner above.
210
- const observedBaseline = captureMemoryPreflightSources(context, bundle, Buffer.from(JSON.stringify(value))), locations = new Map();
347
+ const observedBaseline = bundle.descriptor.descriptor.id === "mb-upgrade"
348
+ ? captureMemoryUpgradeSources(context, bundle, value.stage, Buffer.from(JSON.stringify(value)))
349
+ : captureMemoryPreflightSources(context, bundle, Buffer.from(JSON.stringify(value)));
350
+ const locations = new Map();
211
351
  for (const key of observedBaseline.hashes().keys()) {
212
352
  const [root, logical] = key.split("\0");
213
353
  locations.set(key, { root: root, path: logical });
@@ -230,27 +370,29 @@ export function captureMemoryCompositionSources(context, bundle, runRoot, bytes,
230
370
  * Semantic quality remains the following VERIFY and independent REVIEW gates.
231
371
  */
232
372
  export function finishMemoryComposition(context, input) {
233
- const { bundle, scope, run, accepted } = selected(context, input), root = readDescriptorRootWork(context, scope, bundle);
373
+ const { bundle, scope, run, accepted } = selected(context, input);
374
+ const root = readDescriptorRootWork(context, scope, bundle);
234
375
  if (!input.bytes.length)
235
376
  refuse("invalid_result_bytes");
236
- const registration = selectMemoryCompositionRegistration(context, { ...stageGraphScope(context, scope.projectId, scope.runId, scope.stage), stage: "compose", parent_work_id: root.workId });
377
+ const registration = selectMemoryCompositionRegistration(context, { ...stageGraphScope(context, scope.projectId, scope.runId, scope.stage), stage: scope.stage, parent_work_id: root.workId });
237
378
  const registrationScope = registration.scope;
238
379
  const blueprintBinding = PreparedMemoryResultBinding.prepareRegisteredComposition(context, { ...scope, flow: bundle.descriptor, execution: bundle.execution, bundle,
239
380
  registrationScope, acceptedReplay: accepted });
240
381
  const outputs = PreparedMemoryCompositionAcceptance.prepare(context, { scope: registrationScope, flow: bundle.descriptor, engine: bundle.engine(), binding: blueprintBinding.binding() });
241
- const sources = captureMemoryCompositionSources(context, bundle, run.run_root ?? run.run_dir, input.bytes, outputs);
382
+ const bytes = outputs.projectResult(input.bytes);
383
+ const sources = captureMemoryCompositionSources(context, bundle, run.run_root ?? run.run_dir, bytes, outputs);
242
384
  const binding = PreparedMemoryResultBinding.prepare(context, { ...scope, flow: bundle.descriptor, execution: bundle.execution, bundle, sources, acceptedReplay: accepted, historicalEvidence: outputs.historicalEvidence() });
243
385
  const replay = binding.acceptedBytes();
244
- if (replay && !replay.equals(input.bytes))
386
+ if (replay && !replay.equals(bytes))
245
387
  refuse("accepted_result_changed");
246
- const result = PreparedMemoryStageResult.prepare(input.bytes, bundle.descriptor, binding.binding());
388
+ const result = PreparedMemoryStageResult.prepare(bytes, bundle.descriptor, binding.binding());
247
389
  outputs.assertResult(result);
248
390
  const ordinaryDefaults = readDescriptorStageInput(context, scope, bundle, true, accepted).input.ordinary_defaults;
249
391
  if (ordinaryDefaults && !replay) {
250
392
  const selected = bundle.execution.snapshot().scope;
251
393
  assertMemoryOrdinaryConfigurationOutputs(context, selected.workspace, ordinaryDefaults, result.value().units.flatMap(unit => unit.artifacts), { requestId: selected.requestId, projectRoot: selected.projectRoot, workspaceRoot: selected.workspace });
252
394
  }
253
- const settlement = PreparedDescriptorStageSettlement.prepare(context, { ...input, stage: "compose", bytes: result.bytes() });
395
+ const settlement = PreparedDescriptorStageSettlement.prepare(context, { ...input, stage: scope.stage, bytes: result.bytes() });
254
396
  blueprintBinding.revalidate(context);
255
397
  binding.revalidate(context);
256
398
  outputs.revalidate(context);