@deksden-com/dd-flow-cli 0.9.0-beta.144 → 0.9.0-beta.149

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 (254) hide show
  1. package/CHANGELOG.md +130 -0
  2. package/README.md +13 -8
  3. package/dist/build-info.json +7 -7
  4. package/dist/cli/command-inputs.js +13 -3
  5. package/dist/cli/command-metadata.js +51 -16
  6. package/dist/cli/diagnostic-invocation.js +46 -0
  7. package/dist/cli/diagnostic.js +120 -0
  8. package/dist/cli/help.js +67 -1
  9. package/dist/cli/hook-ingress.js +27 -3
  10. package/dist/cli/memory-flow-admission.js +9 -3
  11. package/dist/cli/memory-init-invocation.js +10 -3
  12. package/dist/cli/memory-init.js +77 -68
  13. package/dist/cli/memory-storage-admission.js +3 -2
  14. package/dist/cli/memory-storage-qualification.js +105 -0
  15. package/dist/cli/memory-storage-transition.js +123 -0
  16. package/dist/cli/memory-upgrade.js +189 -0
  17. package/dist/cli/native-hook-ingress.js +14 -4
  18. package/dist/cli/native-hook-preparation.js +55 -0
  19. package/dist/cli/response-file.js +2 -1
  20. package/dist/cli/run-cli.js +79 -40
  21. package/dist/cli/setup.js +2 -1
  22. package/dist/cli.js +18 -0
  23. package/dist/domain/diagnostic-aspects.js +103 -0
  24. package/dist/domain/diagnostic-flows.js +8 -0
  25. package/dist/domain/stage-handler-identities.js +8 -0
  26. package/dist/harness-runtime/bin/dd-agy.mjs +3 -3
  27. package/dist/harness-runtime/bin/dd-grok.mjs +3 -3
  28. package/dist/harness-runtime/bin/dd-zcode.mjs +5 -5
  29. package/dist/harness-runtime/lib/agy-error-boundary.mjs +58 -0
  30. package/dist/harness-runtime/lib/agy-native-inspection.mjs +216 -0
  31. package/dist/harness-runtime/lib/agy-native_pb.js +117 -0
  32. package/dist/harness-runtime/lib/daemon-operations.d.mts +1 -0
  33. package/dist/harness-runtime/lib/daemon-operations.mjs +84 -10
  34. package/dist/harness-runtime/lib/dd-agy-daemon.mjs +366 -52
  35. package/dist/harness-runtime/lib/dd-agy.mjs +17 -3
  36. package/dist/harness-runtime/lib/dd-codex-daemon.mjs +27 -13
  37. package/dist/harness-runtime/lib/dd-codex.mjs +4 -0
  38. package/dist/harness-runtime/lib/dd-droid-daemon.mjs +4 -3
  39. package/dist/harness-runtime/lib/dd-droid.mjs +71 -29
  40. package/dist/harness-runtime/lib/dd-grok-daemon.mjs +73 -16
  41. package/dist/harness-runtime/lib/dd-grok.mjs +6 -3
  42. package/dist/harness-runtime/lib/dd-opencode-daemon.mjs +66 -20
  43. package/dist/harness-runtime/lib/dd-opencode.mjs +30 -3
  44. package/dist/harness-runtime/lib/dd-zcode-daemon.mjs +7 -6
  45. package/dist/harness-runtime/lib/dd-zcode.mjs +44 -14
  46. package/dist/harness-runtime/lib/delegation-instructions.mjs +1 -1
  47. package/dist/harness-runtime/lib/driver-recovery.mjs +9 -8
  48. package/dist/harness-runtime/lib/entity-references.d.mts +2 -0
  49. package/dist/harness-runtime/lib/entity-references.mjs +9 -0
  50. package/dist/harness-runtime/lib/managed-daemon.d.mts +1 -1
  51. package/dist/harness-runtime/lib/managed-daemon.mjs +24 -9
  52. package/dist/harness-runtime/lib/native-children.d.mts +8 -1
  53. package/dist/harness-runtime/lib/native-children.mjs +114 -8
  54. package/dist/harness-runtime/lib/native-hook-command.mjs +4 -4
  55. package/dist/harness-runtime/lib/native-request-proof.mjs +1 -1
  56. package/dist/harness-runtime/lib/observation-clock.d.mts +2 -2
  57. package/dist/harness-runtime/lib/observation-clock.mjs +7 -4
  58. package/dist/harness-runtime/lib/opencode-plugin.mjs +3 -3
  59. package/dist/harness-runtime/lib/operation-errors.mjs +1 -1
  60. package/dist/harness-runtime/lib/provider-overload-policy.d.mts +5 -3
  61. package/dist/harness-runtime/lib/provider-overload-policy.mjs +20 -7
  62. package/dist/harness-runtime/lib/runner-events.mjs +3 -1
  63. package/dist/harness-runtime/lib/setup-progress.mjs +8 -6
  64. package/dist/harness-runtime/lib/tool-observations.mjs +1 -1
  65. package/dist/schemas/diagnostic-stage-result.schema.json +354 -0
  66. package/dist/schemas/diagnostic-work-result.schema.json +296 -0
  67. package/dist/schemas/execution-bundle.schema.json +7 -1
  68. package/dist/schemas/mb-upgrade-stage-result.schema.json +863 -0
  69. package/dist/schemas/memory-repair-work-result.schema.json +9 -2
  70. package/dist/schemas/project-flow-pack-manifest.schema.json +1 -1
  71. package/dist/schemas/release-impact.schema.json +3 -3
  72. package/dist/schemas/stage-start-response.schema.json +4 -0
  73. package/dist/services/accepted-intent.js +13 -1
  74. package/dist/services/canon.js +43 -7
  75. package/dist/services/check-environment.js +21 -0
  76. package/dist/services/check-runner-evidence.js +267 -15
  77. package/dist/services/cli-operation-classifier.js +1 -1
  78. package/dist/services/cli-upgrade-allowlist.js +1 -1
  79. package/dist/services/code-checks.js +34 -19
  80. package/dist/services/compatibility-preflight.js +5 -2
  81. package/dist/services/controller-execution.js +10 -4
  82. package/dist/services/controller-fanout.js +34 -10
  83. package/dist/services/controller-memory-task.js +45 -0
  84. package/dist/services/controller-memory-worker.js +49 -0
  85. package/dist/services/dashboard.js +2 -2
  86. package/dist/services/descriptor-run-settlement.js +5 -3
  87. package/dist/services/descriptor-run-start.js +46 -20
  88. package/dist/services/descriptor-stage-coordinator.js +66 -13
  89. package/dist/services/descriptor-stage-settlement.js +2 -1
  90. package/dist/services/descriptor-stage-start.js +40 -4
  91. package/dist/services/diagnostic-evidence.js +73 -0
  92. package/dist/services/diagnostic-report-publication.js +72 -0
  93. package/dist/services/diagnostic-report.js +67 -0
  94. package/dist/services/diagnostic-run-bundle.js +74 -0
  95. package/dist/services/diagnostic-snapshot.js +159 -0
  96. package/dist/services/diagnostic-stage-handlers.js +108 -0
  97. package/dist/services/diagnostic-stage-results.js +132 -0
  98. package/dist/services/diagnostic-start.js +84 -0
  99. package/dist/services/diagnostic-work-batch.js +34 -0
  100. package/dist/services/diagnostic-work-collection.js +97 -0
  101. package/dist/services/diagnostic-work-registration.js +101 -0
  102. package/dist/services/diagnostic-work-results.js +93 -0
  103. package/dist/services/engines.js +64 -14
  104. package/dist/services/eval-snapshots.js +186 -119
  105. package/dist/services/execution-bundles.js +19 -7
  106. package/dist/services/external-work-launch.js +5 -3
  107. package/dist/services/flow-catalog.js +13 -4
  108. package/dist/services/frozen-engine.js +148 -11
  109. package/dist/services/hook-memory-routing.js +188 -0
  110. package/dist/services/hook-recovery-guard.js +14 -0
  111. package/dist/services/hook-session-observation.js +24 -0
  112. package/dist/services/hooks.js +27 -8
  113. package/dist/services/hybrid-contracts.js +4 -13
  114. package/dist/services/hybrid-digest.js +14 -0
  115. package/dist/services/installed-flow-source.js +47 -4
  116. package/dist/services/lifecycle-invocations.js +179 -38
  117. package/dist/services/memory-accepted-composition.js +5 -4
  118. package/dist/services/memory-completed-correction-wave.js +23 -15
  119. package/dist/services/memory-compose-handler.js +163 -21
  120. package/dist/services/memory-composition-acceptance.js +186 -10
  121. package/dist/services/memory-composition-graph.js +42 -11
  122. package/dist/services/memory-composition-registration.js +23 -8
  123. package/dist/services/memory-composition-work-custody.js +14 -9
  124. package/dist/services/memory-composition-work-results.js +15 -3
  125. package/dist/services/memory-corrected-review-batch.js +10 -3
  126. package/dist/services/memory-corrected-review-findings.js +4 -2
  127. package/dist/services/memory-corrected-review-inputs.js +10 -5
  128. package/dist/services/memory-corrected-review-receipt.js +17 -9
  129. package/dist/services/memory-corrected-review-registration.js +3 -2
  130. package/dist/services/memory-correction-check-receipt.js +20 -4
  131. package/dist/services/memory-custody-snapshot.js +36 -0
  132. package/dist/services/memory-finish-evidence.js +44 -22
  133. package/dist/services/memory-finish-handler.js +37 -23
  134. package/dist/services/memory-finish-integration.js +218 -48
  135. package/dist/services/memory-finish-readiness.js +66 -19
  136. package/dist/services/memory-git-integration.js +87 -18
  137. package/dist/services/memory-historical-corrected-review.js +21 -15
  138. package/dist/services/memory-historical-original-review.js +22 -9
  139. package/dist/services/memory-historical-repair-registration.js +14 -8
  140. package/dist/services/memory-historical-second-review.js +30 -11
  141. package/dist/services/memory-init-contexts.js +44 -2
  142. package/dist/services/memory-init-controller-start.js +27 -8
  143. package/dist/services/memory-init-git-policy.js +50 -21
  144. package/dist/services/memory-init-lint-capability.js +24 -6
  145. package/dist/services/memory-init-native-capacity.js +17 -4
  146. package/dist/services/memory-init-preflight.js +21 -17
  147. package/dist/services/memory-init-request-candidate.js +73 -17
  148. package/dist/services/memory-init-request-input.js +23 -7
  149. package/dist/services/memory-init-run-bundle.js +49 -12
  150. package/dist/services/memory-init-storage.js +17 -7
  151. package/dist/services/memory-init-worktrunk-capability.js +3 -7
  152. package/dist/services/memory-layer.js +71 -6
  153. package/dist/services/memory-lint-config.js +111 -2
  154. package/dist/services/memory-native-review-copy.js +5 -67
  155. package/dist/services/memory-normal-flow-entry.js +12 -0
  156. package/dist/services/memory-ordinary-defaults.js +22 -5
  157. package/dist/services/memory-preflight-handler.js +15 -0
  158. package/dist/services/memory-repair-current-view.js +1 -1
  159. package/dist/services/memory-repair-document-delta.js +70 -33
  160. package/dist/services/memory-repair-history.js +12 -7
  161. package/dist/services/memory-repair-physical-binding.js +1 -119
  162. package/dist/services/memory-repair-publication-plan.js +5 -3
  163. package/dist/services/memory-repair-publication-source.js +1 -1
  164. package/dist/services/memory-repair-publication.js +3 -2
  165. package/dist/services/memory-repair-registration.js +2 -1
  166. package/dist/services/memory-repair-work-baseline.js +4 -2
  167. package/dist/services/memory-repair-work-completion.js +32 -12
  168. package/dist/services/memory-repair-work-results.js +21 -2
  169. package/dist/services/memory-repair-workspace.js +11 -2
  170. package/dist/services/memory-repair-writer-copy.js +2 -1
  171. package/dist/services/memory-result-binding.js +78 -12
  172. package/dist/services/memory-retained-review-authority.js +17 -8
  173. package/dist/services/memory-retained-second-baseline.js +1 -1
  174. package/dist/services/memory-retained-second-repairs.js +12 -6
  175. package/dist/services/memory-review-correction-plan.js +47 -12
  176. package/dist/services/memory-review-handler.js +17 -17
  177. package/dist/services/memory-review-inputs.js +55 -9
  178. package/dist/services/memory-review-limits.js +10 -0
  179. package/dist/services/memory-review-receipt.js +2 -2
  180. package/dist/services/memory-review-registration.js +13 -4
  181. package/dist/services/memory-review-work-custody.js +3 -1
  182. package/dist/services/memory-review-work-input.js +26 -7
  183. package/dist/services/memory-review-work-results.js +26 -4
  184. package/dist/services/memory-second-corrected-review-inputs.js +5 -3
  185. package/dist/services/memory-second-correction-plan.js +2 -1
  186. package/dist/services/memory-second-repair-launch.js +4 -4
  187. package/dist/services/memory-second-repair-source.js +1 -1
  188. package/dist/services/memory-stage-dispatch.js +38 -1
  189. package/dist/services/memory-stage-results.js +113 -22
  190. package/dist/services/memory-tool-probe.js +25 -0
  191. package/dist/services/memory-upgrade-analysis-handler.js +243 -0
  192. package/dist/services/memory-upgrade-bootstrap-readiness.js +251 -0
  193. package/dist/services/memory-upgrade-evidence.js +100 -0
  194. package/dist/services/memory-upgrade-inventory.js +442 -0
  195. package/dist/services/memory-upgrade-publication-custody.js +73 -0
  196. package/dist/services/memory-upgrade-publication.js +413 -0
  197. package/dist/services/memory-upgrade-selection.js +430 -0
  198. package/dist/services/memory-upgrade-start.js +60 -0
  199. package/dist/services/memory-upgrade-storage-readiness.js +340 -0
  200. package/dist/services/memory-verification-acceptance.js +38 -17
  201. package/dist/services/memory-verification-checks.js +92 -27
  202. package/dist/services/memory-verification-receipt.js +6 -5
  203. package/dist/services/memory-verify-handler.js +15 -13
  204. package/dist/services/memory-work-result-json.js +12 -0
  205. package/dist/services/memory-work-result-preparation.js +5 -4
  206. package/dist/services/merge-server.js +4 -2
  207. package/dist/services/migrations.js +127 -98
  208. package/dist/services/native-daemon-history.js +2 -1
  209. package/dist/services/physical-startup.js +45 -0
  210. package/dist/services/project-registry.js +97 -0
  211. package/dist/services/project-summary.js +1 -1
  212. package/dist/services/projects.js +3 -93
  213. package/dist/services/prompt-delivery.js +71 -0
  214. package/dist/services/provider-capacity.js +112 -46
  215. package/dist/services/recovery-snapshot-database.js +32 -4
  216. package/dist/services/release-cache.js +8 -0
  217. package/dist/services/repair-intents.js +1 -12
  218. package/dist/services/repair-receipt.js +13 -0
  219. package/dist/services/run-control-worker.js +35 -18
  220. package/dist/services/run-control.js +2 -1
  221. package/dist/services/run-controller-adapter.js +2 -1
  222. package/dist/services/run-controller-process.js +4 -7
  223. package/dist/services/run-controller-recovery.js +8 -2
  224. package/dist/services/run-controller.js +215 -94
  225. package/dist/services/run-fork.js +1 -1
  226. package/dist/services/run-recovery-guard.js +30 -0
  227. package/dist/services/run-recovery.js +11 -44
  228. package/dist/services/run-start-custody.js +17 -5
  229. package/dist/services/runs.js +6 -1
  230. package/dist/services/runtime-scope-resume.js +12 -6
  231. package/dist/services/runtime-scope-stop.js +3 -2
  232. package/dist/services/runtime-scope-worker.js +46 -19
  233. package/dist/services/runtime-service.js +2 -1
  234. package/dist/services/sessions.js +3 -24
  235. package/dist/services/setup.js +63 -13
  236. package/dist/services/stage-handlers.js +22 -7
  237. package/dist/services/stage-pause.js +111 -44
  238. package/dist/services/version-status.js +1 -1
  239. package/dist/services/vnext-code.js +2 -2
  240. package/dist/services/vnext-fanout.js +51 -9
  241. package/dist/services/vnext-plan-review.js +3 -5
  242. package/dist/services/work-registry.js +117 -39
  243. package/dist/services/workspace-bootstrap.js +2 -1
  244. package/dist/services/workspace-files.js +1 -1
  245. package/dist/shared/entity-references.js +1 -8
  246. package/dist/storage/data-preservation.js +65 -0
  247. package/dist/storage/database.js +53 -19
  248. package/dist/storage/import-guard.js +84 -27
  249. package/dist/storage/memory-upgrade-schema-bootstrap.js +38 -0
  250. package/dist/storage/paired-writer-migration.js +424 -0
  251. package/dist/storage/writer-contract.js +11 -4
  252. package/dist/storage/writer-migration.js +313 -52
  253. package/dist/storage/writer-restore.js +217 -0
  254. package/package.json +13 -6
@@ -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);
@@ -8,6 +8,11 @@ import { ObservedMemorySources } from "./memory-source-observations.js";
8
8
  import { readWorkBatchCustody } from "./work-registry.js";
9
9
  import { readMemoryCompositionRegistration, readRegisteredMemoryComposition } from "./memory-composition-registration.js";
10
10
  import { validateMemoryCompositionWorkResult } from "./memory-composition-work-results.js";
11
+ import { readMemoryUpgradePublication } from "./memory-upgrade-publication-custody.js";
12
+ import { readMemoryUpgradeBootstrapReadiness } from "./memory-upgrade-bootstrap-readiness.js";
13
+ import { readUpgradeStorageReadiness } from "./memory-upgrade-storage-readiness.js";
14
+ import { readAcceptedStageData } from "./stage-report-renderer.js";
15
+ import { getProjectVersionStatus } from "./version-status.js";
11
16
  const hash = (bytes) => createHash("sha256").update(bytes).digest("hex");
12
17
  function refuse(reason) {
13
18
  throw new AppError("memory_composition_acceptance_invalid", "Composition lacks registered, settled and current output custody", 2, { reason, effect: "no_effect" });
@@ -77,17 +82,34 @@ export class PreparedMemoryCompositionAcceptance {
77
82
  #registrationSha256;
78
83
  #workers;
79
84
  #artifacts;
85
+ #semanticArtifacts;
86
+ #publication = null;
87
+ #migrationRecord = null;
88
+ #historicalEvidence = new Set();
80
89
  #observed;
81
90
  constructor(scope, original, registrationSha256, workers, artifacts, observed) {
82
91
  this.#scope = structuredClone(scope);
83
92
  this.#original = original;
84
93
  this.#registrationSha256 = registrationSha256;
85
94
  this.#workers = workers;
86
- this.#artifacts = artifacts;
95
+ this.#artifacts = structuredClone(artifacts);
96
+ this.#semanticArtifacts = structuredClone(artifacts);
87
97
  this.#observed = observed;
88
98
  Object.freeze(this);
89
99
  }
90
100
  static prepare(context, input) {
101
+ return this.#prepare(context, input, true);
102
+ }
103
+ /** Restore settled semantic Work independently of today's possibly partly
104
+ * published marker bytes. The publication owner still checks the before view
105
+ * before retaining its intent; this method does not accept current artifacts.
106
+ */
107
+ static prepareForUpgradePublication(context, input) {
108
+ if (input.scope.stage !== "upgrade")
109
+ refuse("upgrade_publication_scope_required");
110
+ return this.#prepare(context, input, false);
111
+ }
112
+ static #prepare(context, input, observe) {
91
113
  const registration = readMemoryCompositionRegistration(context, input.scope);
92
114
  const original = readRegisteredMemoryComposition(context, input), value = original.value();
93
115
  const works = value.details.works;
@@ -137,21 +159,127 @@ export class PreparedMemoryCompositionAcceptance {
137
159
  for (const record of current.values())
138
160
  for (const id of record.units)
139
161
  artifacts.set(id, [...(artifacts.get(id) ?? []), record.artifact]);
140
- const observed = ObservedMemorySources.capture(assigned, [...current.values()].map(record => ({ root: record.artifact.root, path: record.artifact.path })));
141
- const hashes = observed.hashes();
142
- if ([...current.values()].some(record => hashes.get(memorySourceKey(record.artifact.root, record.artifact.path)) !== record.artifact.sha256))
143
- refuse("current_output_bytes_changed");
144
- const prepared = new PreparedMemoryCompositionAcceptance(input.scope, original, registrationSha256, selected.map(item => ({ id: item.id, harness: item.harness, authority: item.authority, evidence: item.evidence })), artifacts, observed);
162
+ const prepared = new PreparedMemoryCompositionAcceptance(input.scope, original, registrationSha256, selected.map(item => ({ id: item.id, harness: item.harness, authority: item.authority, evidence: item.evidence })), artifacts, null);
163
+ if (input.scope.stage === "upgrade")
164
+ prepared.#historicalEvidence = new Set(input.binding.historicalEvidence);
165
+ if (observe) {
166
+ if (input.scope.stage === "upgrade")
167
+ prepared.#includePublication(context, input.binding);
168
+ prepared.#observed = ObservedMemorySources.capture(assigned, prepared.outputLocations());
169
+ const hashes = prepared.#observed.hashes();
170
+ if ([...prepared.#artifacts.values()].flat().some(artifact => hashes.get(memorySourceKey(artifact.root, artifact.path)) !== artifact.sha256))
171
+ refuse("current_output_bytes_changed");
172
+ }
145
173
  prepared.assertCustody(context);
146
174
  return prepared;
147
175
  }
176
+ #includePublication(context, binding) {
177
+ const frozen = binding.execution?.snapshot().scope;
178
+ if (!frozen)
179
+ refuse("upgrade_execution_required");
180
+ const scope = { requestId: frozen.requestId, projectRoot: frozen.projectRoot, workspaceRoot: frozen.workspace };
181
+ const ready = readMemoryUpgradeBootstrapReadiness(context, scope), storage = readUpgradeStorageReadiness(context, scope);
182
+ if (!ready || !storage?.selection || ready.authority.binding.storage_sha256 !== storage.sha256)
183
+ refuse("upgrade_bootstrap_required");
184
+ const identity = { scope: this.#scope, selection_sha256: storage.selection.sha256, execution_sha256: binding.executionSha256,
185
+ registration_sha256: this.#registrationSha256, bootstrap_sha256: hybridDigest(ready.authority), work_custody_sha256: this.custodySha256() };
186
+ const publication = readMemoryUpgradePublication(context, identity);
187
+ if (!publication?.completed)
188
+ refuse("completed_upgrade_publication_required");
189
+ const state = publication.plan.classify();
190
+ if (state.completed !== state.total)
191
+ refuse("upgrade_publication_not_current");
192
+ const actions = new Map(publication.plan.actions().map(action => [`.memory-bank/${action.path}`, action]));
193
+ for (const artifacts of this.#artifacts.values())
194
+ for (const artifact of artifacts) {
195
+ const action = artifact.root === "workspace" ? actions.get(artifact.path) : undefined;
196
+ if (!action)
197
+ continue;
198
+ // Only the exact CLI transition can advance a settled worker hash. No
199
+ // caller-provided allowlist may excuse an arbitrary changed document.
200
+ if (action.before?.sha256 !== artifact.sha256 || action.after?.kind !== "file")
201
+ refuse("unbound_semantic_output_transition");
202
+ artifact.sha256 = action.after.sha256;
203
+ }
204
+ const outputs = new Map(publication.plan.outputs().map(output => [`.memory-bank/${output.path}`, output]));
205
+ const recordPath = `.memory-bank/archive/mb-upgrade/${identity.selection_sha256}/migration.json`, record = outputs.get(recordPath);
206
+ if (!record)
207
+ refuse("upgrade_migration_record_missing");
208
+ this.#migrationRecord = { path: recordPath, sha256: record.sha256 };
209
+ const add = (unit, path, sha256) => {
210
+ const values = this.#artifacts.get(unit) ?? [], artifact = { root: "workspace", path, sha256 };
211
+ const prior = values.find(value => value.root === artifact.root && value.path === path);
212
+ if (prior && prior.sha256 !== sha256)
213
+ refuse("conflicting_mechanical_artifact");
214
+ if (!prior)
215
+ values.push(artifact);
216
+ this.#artifacts.set(unit, values);
217
+ };
218
+ const diff = readAcceptedStageData(context, { projectId: this.#scope.project_id, runId: this.#scope.run_id, stage: "diff-analysis" });
219
+ const changes = JSON.parse(diff.bytes.toString("utf8")).details.path_map;
220
+ const old = new Map(storage.selection.payload.current.inventory.inventory.entries.map(entry => [`.memory-bank/${entry.path}`, entry]));
221
+ const final = new Map(publication.plan.beforeFiles().map(entry => [entry.path, entry]));
222
+ for (const action of publication.plan.actions()) {
223
+ if (action.after)
224
+ final.set(action.path, action.after);
225
+ else
226
+ final.delete(action.path);
227
+ }
228
+ for (const entry of storage.selection.payload.current.inventory.inventory.entries) {
229
+ const name = `.memory-bank/${entry.path}`, actual = final.get(entry.path);
230
+ const semantic = changes.some(change => change.to === name && change.action === "semantic-work");
231
+ if (entry.target && (entry.ownership === "generated" || entry.ownership === "canonical" && !semantic)
232
+ && (actual?.kind !== "file" || actual.sha256 !== entry.target.sha256))
233
+ refuse("incomplete_canonical_publication");
234
+ if (entry.ownership === "canonical" && entry.action === "archive" && actual)
235
+ refuse("obsolete_canonical_path_active");
236
+ }
237
+ const status = getProjectVersionStatus({ projectRoot: frozen.workspace, rootSource: "explicit", canon: null });
238
+ if (status?.memory_bank.version !== storage.selection.release.canon.version
239
+ || status.flow_pack.canon_version_at_source_commit !== storage.selection.release.canon.version
240
+ || status.flow_pack.source_commit !== storage.selection.release.canon.commit)
241
+ refuse("upgrade_versions_inconsistent");
242
+ for (const change of changes) {
243
+ if (change.action === "retain" && change.from) {
244
+ const source = old.get(change.from)?.current;
245
+ if (source?.kind === "file")
246
+ add(change.unit_id, change.from, outputs.get(change.from)?.sha256 ?? source.sha256);
247
+ }
248
+ else if (["canonical", "generated"].includes(change.ownership) && change.action !== "semantic-work") {
249
+ if (change.to) {
250
+ const output = outputs.get(change.to) ?? final.get(change.to.slice(13));
251
+ if (!output || output.kind !== "file")
252
+ refuse("missing_mechanical_artifact");
253
+ add(change.unit_id, change.to, output.sha256);
254
+ }
255
+ if (change.from) {
256
+ const archive = `.memory-bank/archive/mb-upgrade/${identity.selection_sha256}/files/${change.from.slice(13)}.bin`, output = outputs.get(archive);
257
+ if (!output)
258
+ refuse("missing_recovery_artifact");
259
+ add(change.unit_id, archive, output.sha256);
260
+ }
261
+ }
262
+ }
263
+ for (const unit of this.#original.value().units)
264
+ if (unit.applicability === "applicable")
265
+ add(unit.id, recordPath, record.sha256);
266
+ this.#publication = { binding: identity, sha256: publication.plan.sha256 };
267
+ }
148
268
  /** Only these independently settled output locations may supplement baseline
149
269
  * evidence in the final result binding. The returned map is a defensive copy.
150
270
  */
151
- outputHashes() { return this.#observed.hashes(); }
271
+ outputHashes() { if (!this.#observed)
272
+ refuse("current_output_observation_required"); return this.#observed.hashes(); }
273
+ /** SQL-settled before-publication facts, not current filesystem witnesses. */
274
+ settledArtifacts() {
275
+ return [...this.#semanticArtifacts].flatMap(([unit_id, artifacts]) => artifacts.map(artifact => ({ unit_id, artifact: structuredClone(artifact) })));
276
+ }
277
+ custodySha256() {
278
+ return hybridDigest({ scope: this.#scope, registration_sha256: this.#registrationSha256, workers: this.#workers, artifacts: [...this.#semanticArtifacts] });
279
+ }
152
280
  historicalEvidence() {
153
281
  const units = this.#original.value().units;
154
- return new Set(units.flatMap(unit => unit.evidence.map(ref => `${memorySourceKey(ref.root, ref.path)}\0${ref.sha256}`)));
282
+ return new Set([...this.#historicalEvidence, ...units.flatMap(unit => unit.evidence.map(ref => `${memorySourceKey(ref.root, ref.path)}\0${ref.sha256}`))]);
155
283
  }
156
284
  resultEvidence() { return this.#workers.map(worker => structuredClone(worker.evidence)); }
157
285
  outputLocations() {
@@ -161,14 +289,48 @@ export class PreparedMemoryCompositionAcceptance {
161
289
  selected.set(memorySourceKey(artifact.root, artifact.path), { root: artifact.root, path: artifact.path });
162
290
  return [...selected.values()];
163
291
  }
292
+ /** Runtime projection from completed SQL publication, never an agent map.
293
+ * Keep substantive fields and immutable graph intact. Existing invented
294
+ * references are rejected rather than silently overwritten by this helper.
295
+ */
296
+ projectResult(bytes) {
297
+ if (!this.#migrationRecord)
298
+ return Buffer.from(bytes);
299
+ const value = object(Buffer.from(bytes).toString("utf8"), "invalid_result_json");
300
+ const reject = (pointer) => {
301
+ throw new AppError("memory_stage_result_invalid", "Use the selected upgrade outputs; do not invent or rerun publication", 2, { reason: "unselected_upgrade_output", phase: "prepare", effect: "no_effect", recoverable: true, result_sha256: hash(bytes), pointer });
302
+ };
303
+ if (Array.isArray(value.units))
304
+ for (const [index, unit] of value.units.entries()) {
305
+ if (!unit || typeof unit !== "object" || typeof unit.id !== "string" || !this.#original.mechanicalUnitIds().has(unit.id) && !this.#artifacts.has(unit.id))
306
+ continue;
307
+ const expected = this.#artifacts.get(unit.id) ?? [], before = this.#semanticArtifacts.get(unit.id) ?? [];
308
+ if (unit.artifacts !== undefined && !Array.isArray(unit.artifacts))
309
+ continue;
310
+ for (const reference of unit.artifacts ?? [])
311
+ if (![...expected, ...before].some(artifact => hybridDigest(artifact) === hybridDigest(reference)))
312
+ reject(`/units/${index}/artifacts`);
313
+ unit.artifacts = structuredClone(expected);
314
+ }
315
+ if (value.details && typeof value.details === "object" && !Array.isArray(value.details)) {
316
+ const details = value.details, original = this.#original.value().details;
317
+ if (details.migration_record !== undefined && ![original.migration_record, this.#migrationRecord].some(reference => hybridDigest(reference) === hybridDigest(details.migration_record)))
318
+ reject("/details/migration_record");
319
+ details.migration_record = { ...this.#migrationRecord };
320
+ }
321
+ return Buffer.from(JSON.stringify(value));
322
+ }
164
323
  assertResult(result) {
165
324
  if (!(result instanceof PreparedMemoryStageResult))
166
325
  refuse("prepared_result_required");
326
+ if (this.#scope.stage === "upgrade" && !this.#publication)
327
+ refuse("completed_upgrade_publication_required");
167
328
  const original = this.#original.value(), final = result.value(), value = final;
168
329
  const identity = ["schema_id", "run_id", "input_sha256", "predecessors"];
169
330
  if (hybridDigest(identity.map(field => original[field])) !== hybridDigest(identity.map(field => final[field])))
170
331
  refuse("foreign_result_identity");
171
- if (value.stage !== "compose" || value.outcome !== "accepted" || hybridDigest(value.details) !== hybridDigest(original.details)
332
+ const details = this.#migrationRecord ? { ...original.details, migration_record: this.#migrationRecord } : original.details;
333
+ if (value.stage !== this.#original.authoringStage() || value.outcome !== "accepted" || hybridDigest(value.details) !== hybridDigest(details)
172
334
  || value.findings.some(finding => finding.severity !== "minor" && finding.status !== "resolved"))
173
335
  refuse("composition_not_accepted_or_plan_changed");
174
336
  const inventory = new Map(original.units.map(unit => [unit.id, unit]));
@@ -193,7 +355,21 @@ export class PreparedMemoryCompositionAcceptance {
193
355
  for (const worker of this.#workers)
194
356
  if (settled(context, this.#scope, worker.id, worker.harness, this.#registrationSha256, assigned).authority !== worker.authority)
195
357
  refuse("work_settlement_changed");
358
+ if (this.#publication) {
359
+ const publication = readMemoryUpgradePublication(context, this.#publication.binding);
360
+ if (!publication?.completed || publication.plan.sha256 !== this.#publication.sha256)
361
+ refuse("publication_custody_changed");
362
+ }
196
363
  });
197
364
  }
198
- revalidate(context) { this.#observed.revalidate(); this.assertCustody(context); }
365
+ revalidate(context) {
366
+ if (this.#observed)
367
+ this.#observed.revalidate();
368
+ else {
369
+ const hashes = ObservedMemorySources.capture(roots(context, this.#scope), this.outputLocations()).hashes();
370
+ if (this.settledArtifacts().some(({ artifact }) => hashes.get(memorySourceKey(artifact.root, artifact.path)) !== artifact.sha256))
371
+ refuse("current_output_bytes_changed");
372
+ }
373
+ this.assertCustody(context);
374
+ }
199
375
  }
@@ -1,7 +1,7 @@
1
1
  import { AppError } from "../shared/errors.js";
2
2
  import { localEnvironmentAllowed } from "./workspace-files.js";
3
- function refuse(reason) {
4
- throw new AppError("memory_stage_result_invalid", "Memory composition graph cannot be dispatched", 2, { reason, phase: "prepare", effect: "no_effect" });
3
+ function refuse(reason, details = {}) {
4
+ throw new AppError("memory_stage_result_invalid", "Memory composition graph cannot be dispatched", 2, { ...details, reason, phase: "prepare", effect: "no_effect" });
5
5
  }
6
6
  // Portable scopes must remain safe on case-insensitive/normalizing filesystems.
7
7
  const physicalPath = (value) => value.normalize("NFC").toLowerCase();
@@ -9,8 +9,10 @@ const projectConfigurationPaths = [".memory-bank/dd-flow/project-execution.json"
9
9
  /** Exact project-owned defaults, never directory or case aliases of canon. */
10
10
  export function memoryProjectConfigurationPath(value) { return projectConfigurationPaths.includes(value); }
11
11
  /** Check both assigned directories and their concrete output descendants. */
12
- export function memoryCompositionOutputAllowed(value) {
13
- if (overlaps(value, ".memory-bank/dd-flow") && !memoryProjectConfigurationPath(value))
12
+ export function memoryCompositionOutputAllowed(value, upgradeWritePaths) {
13
+ if (upgradeWritePaths && !upgradeWritePaths.has(value))
14
+ return false;
15
+ if (!upgradeWritePaths && overlaps(value, ".memory-bank/dd-flow") && !memoryProjectConfigurationPath(value))
14
16
  return false;
15
17
  return value.split("/").every(part => localEnvironmentAllowed(part)
16
18
  && ![".git", "node_modules", ".dd-flow"].includes(physicalPath(part)));
@@ -23,7 +25,9 @@ function overlaps(left, right) {
23
25
  * semantic graph checks precede Work allocation, not merely worker dispatch.
24
26
  * They do not grant filesystem access or replace frozen profile/Work custody.
25
27
  */
26
- export function validateMemoryCompositionGraph(value) {
28
+ export function validateMemoryCompositionGraph(value, upgradePaths, mechanicalUnits) {
29
+ if (mechanicalUnits && !upgradePaths)
30
+ refuse("missing_upgrade_authority");
27
31
  const result = value;
28
32
  const unresolved = result.units.findIndex(unit => unit.applicability === "unknown");
29
33
  if (unresolved !== -1)
@@ -49,17 +53,24 @@ export function validateMemoryCompositionGraph(value) {
49
53
  // entry, not selected canon. Never grant their enclosing directory or
50
54
  // case/normalization aliases; the installed prompt/schema pack is sealed.
51
55
  const projectConfig = memoryProjectConfigurationPath(scope.path);
52
- if (scope.root === "workspace" && !projectConfig && [".memory-bank/mbb", ".memory-bank/dd-flow"].some(canonical => overlaps(scope.path, canonical)))
56
+ if (!upgradePaths && scope.root === "workspace" && !projectConfig && [".memory-bank/mbb", ".memory-bank/dd-flow"].some(canonical => overlaps(scope.path, canonical)))
53
57
  refuse("canonical_composition_write");
58
+ const selectedPaths = upgradePaths && scope.root === "workspace" ? new Set(work.unit_ids.flatMap(id => [...upgradePaths.get(id) ?? []])) : undefined;
54
59
  // A contained semantic scope is not permission to alter credentials,
55
60
  // repository metadata, installed dependencies or runtime state.
56
- if (!memoryCompositionOutputAllowed(scope.path))
57
- refuse("private_composition_write");
61
+ if (!memoryCompositionOutputAllowed(scope.path, selectedPaths))
62
+ refuse("private_composition_write", {
63
+ work_key: work.key, scope: { ...scope },
64
+ hint: selectedPaths && work.unit_ids.every(id => mechanicalUnits?.has(id))
65
+ ? "These units are mechanical: remove their agent Work and use memory upgrade publish after graph registration. Do not change accepted predecessor or ownership files."
66
+ : selectedPaths ? "This path is outside this Work's accepted upgrade authoring paths. Mechanical canonical/generated changes use memory upgrade publish, not agent workspace writes. Correct only the proposed scopes; keep accepted predecessor and ownership files unchanged."
67
+ : "Remove the private filesystem scope from the proposed Work; credentials, repository metadata and runtime state are not semantic outputs."
68
+ });
58
69
  if (scope.root === "run" && ["execution-bundle", "run.json"].some(authority => overlaps(scope.path, authority)))
59
70
  refuse("authority_composition_write");
60
71
  // Mechanical RUN files and another Work's evidence are never semantic outputs.
61
72
  if (scope.root === "run") {
62
- const namespace = `memory-artifacts/compose/${work.key}`;
73
+ const namespace = `memory-artifacts/${upgradePaths ? "upgrade" : "compose"}/${work.key}`;
63
74
  if (scope.path !== namespace && !scope.path.startsWith(`${namespace}/`))
64
75
  refuse("foreign_composition_run_write");
65
76
  }
@@ -107,15 +118,35 @@ export function validateMemoryCompositionGraph(value) {
107
118
  || assemblies.some(work => !declared.has(work.key)))
108
119
  refuse("invalid_assembly_membership");
109
120
  const applicable = new Set(result.units.filter(unit => unit.applicability !== "not-applicable").map(unit => unit.id));
121
+ for (const unit of mechanicalUnits ?? []) {
122
+ if (!applicable.has(unit) || upgradePaths?.has(unit))
123
+ refuse("invalid_mechanical_unit");
124
+ const assigned = works.filter(work => work.unit_ids.includes(unit));
125
+ if (assigned.length)
126
+ refuse("mechanical_unit_cannot_allocate_agent_work", {
127
+ unit_id: unit, work_keys: assigned.map(work => work.key).slice(0, 8),
128
+ hint: "Remove this unit from agent Work assignments, but keep the unit in the result inventory. CLI owns its mechanical publication through memory upgrade publish."
129
+ });
130
+ }
110
131
  for (const work of works)
111
132
  if (work.unit_ids.some(id => !applicable.has(id)))
112
133
  refuse("inapplicable_composition_work");
113
134
  for (const unit of applicable) {
135
+ if (mechanicalUnits?.has(unit))
136
+ continue;
114
137
  const authors = works.filter(work => work.unit_ids.includes(unit));
115
138
  const writers = authors.filter(work => work.role === "writer");
116
139
  const collectors = assemblies.filter(work => work.unit_ids.includes(unit));
117
- if (!authors.length || !collectors.length)
118
- refuse("incomplete_composition_coverage");
140
+ // Init assembles a new knowledge shelf. Upgrade may reconcile one existing
141
+ // document without changing its navigation; do not invent a collector Work.
142
+ // Explicit collectors still depend on every writer, in either flow.
143
+ if (!authors.length || !upgradePaths && !collectors.length)
144
+ refuse("incomplete_composition_coverage", {
145
+ unit_id: unit,
146
+ hint: upgradePaths
147
+ ? "Assign this applicable non-mechanical unit to Work. For preservation evidence only, group it into an existing evidence Work with its RUN-local output; keep accepted unit identities and workspace write scopes unchanged."
148
+ : "Assign this applicable unit to composition Work and its index-assembly collector. Keep accepted unit identities unchanged."
149
+ });
119
150
  if (collectors.some(collector => writers.some(writer => !dependsOn(collector.key, writer.key))))
120
151
  refuse("assembly_precedes_unit_writers");
121
152
  }