@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
@@ -6,6 +6,9 @@ import { startMemoryVerification, finishMemoryVerification } from "./memory-veri
6
6
  import { startMemoryReview, finishMemoryReview } from "./memory-review-handler.js";
7
7
  import { startMemoryFinish, finishMemoryFinish } from "./memory-finish-handler.js";
8
8
  import { memoryInitHandlerIdentities as ids } from "../domain/stage-handler-identities.js";
9
+ import { memoryUpgradeHandlerIdentities as upgradeIds } from "../domain/stage-handler-identities.js";
10
+ import { startMemoryUpgradeAnalysis, finishMemoryUpgradeAnalysis } from "./memory-upgrade-analysis-handler.js";
11
+ import { parseMemoryWorkResultProposal } from "./memory-work-result-json.js";
9
12
  // Engine-owned functions only; this is dispatch, not descriptor admission.
10
13
  export const memoryInitStageHandlers = Object.freeze({
11
14
  preflight: Object.freeze({ ...ids.preflight, entry: startMemoryPreflight, finish: finishMemoryPreflight }),
@@ -15,6 +18,18 @@ export const memoryInitStageHandlers = Object.freeze({
15
18
  review: Object.freeze({ ...ids.review, entry: startMemoryReview, finish: finishMemoryReview }),
16
19
  finish: Object.freeze({ ...ids.finish, entry: startMemoryFinish, finish: finishMemoryFinish })
17
20
  });
21
+ export const memoryUpgradeStageHandlers = Object.freeze({
22
+ preflight: Object.freeze({ ...upgradeIds.preflight,
23
+ entry: (context, input) => startMemoryUpgradeAnalysis(context, { ...input, stage: "preflight" }),
24
+ finish: (context, input) => finishMemoryUpgradeAnalysis(context, { ...input, stage: "preflight" }) }),
25
+ "diff-analysis": Object.freeze({ ...upgradeIds["diff-analysis"],
26
+ entry: (context, input) => startMemoryUpgradeAnalysis(context, { ...input, stage: "diff-analysis" }),
27
+ finish: (context, input) => finishMemoryUpgradeAnalysis(context, { ...input, stage: "diff-analysis" }) }),
28
+ upgrade: Object.freeze({ ...upgradeIds.upgrade, entry: startMemoryComposition, finish: finishMemoryComposition }),
29
+ verify: Object.freeze({ ...upgradeIds.verify, entry: startMemoryVerification, finish: finishMemoryVerification }),
30
+ review: Object.freeze({ ...upgradeIds.review, entry: startMemoryReview, finish: finishMemoryReview }),
31
+ finish: Object.freeze({ ...upgradeIds.finish, entry: startMemoryFinish, finish: finishMemoryFinish })
32
+ });
18
33
  function stage(input) {
19
34
  if (!Object.hasOwn(memoryInitStageHandlers, input.stage) || !(input.bytes instanceof Uint8Array) || input.bytes.length > 1024 * 1024)
20
35
  throw new AppError("memory_stage_dispatch_invalid", "Memory stage dispatch requires a bounded declared stage input", 2, { effect: "no_effect", phase: "prepare" });
@@ -24,5 +39,27 @@ export function startMemoryStage(context, input) {
24
39
  return memoryInitStageHandlers[stage(input)].entry(context, input);
25
40
  }
26
41
  export function finishMemoryStage(context, input) {
27
- return memoryInitStageHandlers[stage(input)].finish(context, input);
42
+ const selected = stage(input);
43
+ validateFinishProposal(input.bytes);
44
+ return memoryInitStageHandlers[selected].finish(context, input);
45
+ }
46
+ // Syntax belongs to the submitted proposal, not retained composition/receipt
47
+ // custody. Validate it before any stage-specific projection parses the bytes.
48
+ function validateFinishProposal(bytes) {
49
+ if (!bytes.length)
50
+ throw new AppError("memory_stage_dispatch_invalid", "Memory finish requires a nonempty result proposal", 2, { effect: "no_effect", phase: "prepare" });
51
+ parseMemoryWorkResultProposal(bytes, "memory_stage_result_invalid");
52
+ }
53
+ function upgradeStage(input) {
54
+ if (!Object.hasOwn(memoryUpgradeStageHandlers, input.stage) || !(input.bytes instanceof Uint8Array) || input.bytes.length > 1024 * 1024)
55
+ throw new AppError("memory_stage_dispatch_invalid", "Memory stage dispatch requires a bounded declared stage input", 2, { effect: "no_effect", phase: "prepare" });
56
+ return input.stage;
57
+ }
58
+ export function startMemoryUpgradeStage(context, input) {
59
+ return memoryUpgradeStageHandlers[upgradeStage(input)].entry(context, input);
60
+ }
61
+ export function finishMemoryUpgradeStage(context, input) {
62
+ const selected = upgradeStage(input);
63
+ validateFinishProposal(input.bytes);
64
+ return memoryUpgradeStageHandlers[selected].finish(context, input);
28
65
  }
@@ -3,19 +3,38 @@ import { createHash } from "node:crypto";
3
3
  import { Ajv2020 } from "ajv/dist/2020.js";
4
4
  import { AppError } from "../shared/errors.js";
5
5
  import { hybridDigest } from "./hybrid-contracts.js";
6
- import { validateMemoryCompositionGraph } from "./memory-composition-graph.js";
6
+ import { validateMemoryCompositionGraph, memoryCompositionOutputAllowed } from "./memory-composition-graph.js";
7
7
  import { FrozenGenericExecution } from "./generic-execution.js";
8
- const stages = ["preflight", "discover", "compose", "verify", "review", "finish"];
8
+ const memoryStages = {
9
+ "mb-init": ["preflight", "discover", "compose", "verify", "review", "finish"],
10
+ "mb-upgrade": ["preflight", "diff-analysis", "upgrade", "verify", "review", "finish"]
11
+ };
9
12
  const sha = (value) => /^[a-f0-9]{64}$/.test(value);
10
- function refuse(reason) {
11
- throw new AppError("memory_stage_result_invalid", "Memory stage result is not bound to its retained input", 2, { reason, phase: "prepare", effect: "no_effect" });
13
+ // Proposal relationships/scopes can be corrected before any acceptance or Work
14
+ // allocation. Missing/foreign runtime bindings and identities remain fatal.
15
+ const correctableProposalReasons = new Set([
16
+ "unsafe_reference_path",
17
+ "foreign_predecessor_reference", "duplicate_unit", "changed_unit_coverage", "duplicate_finding", "foreign_finding_unit",
18
+ "duplicate_fact", "foreign_fact_unit", "foreign_unit_reference", "unobserved_or_changed_source",
19
+ "duplicate_preservation_invariant", "weakened_upgrade_mode", "changed_preservation_invariants", "missing_applicable_unit_preservation",
20
+ "duplicate_preservation_assessment", "incomplete_preservation_assessment", "foreign_preservation_unit", "unreviewable_preservation_unit", "unapproved_composition_profile",
21
+ "unresolved_composition_applicability", "duplicate_composition_work", "unknown_composition_dependency", "duplicate_composition_scope",
22
+ "unbounded_composition_write", "foreign_composition_write", "canonical_composition_write", "private_composition_write",
23
+ "authority_composition_write", "foreign_composition_run_write", "cyclic_composition_dependency", "invalid_assembly_membership",
24
+ "mechanical_unit_cannot_allocate_agent_work", "inapplicable_composition_work", "incomplete_composition_coverage",
25
+ "assembly_precedes_unit_writers", "unordered_composition_writers", "unordered_composition_read_write"
26
+ ]);
27
+ function refuse(reason, details = {}) {
28
+ throw new AppError("memory_stage_result_invalid", reason === "missing_applicable_unit_preservation"
29
+ ? "Every applicable upgrade unit requires a source-backed preservation invariant. In preflight add the invariant; in diff-analysis keep the accepted invariants unchanged and assign the planned work to their existing units"
30
+ : "Memory stage result is not bound to its retained input", 2, { ...details, reason, phase: "prepare", effect: "no_effect" });
12
31
  }
13
- function contained(logical, allowRoot = false) {
32
+ function contained(logical, allowRoot = false, proposal = false) {
14
33
  if (allowRoot && logical === ".")
15
34
  return;
16
35
  if (!logical || path.posix.isAbsolute(logical) || path.win32.isAbsolute(logical) || logical.includes("\\")
17
36
  || logical.includes("\0") || logical.split("/").some(segment => !segment || segment === "." || segment === ".."))
18
- refuse("unsafe_reference_path");
37
+ refuse("unsafe_reference_path", { recoverable: proposal });
19
38
  }
20
39
  export function memorySourceKey(root, logical) { return `${root}\0${logical}`; }
21
40
  function unique(values, reason) {
@@ -34,23 +53,36 @@ export class PreparedMemoryStageResult {
34
53
  #routes;
35
54
  #executionSha256;
36
55
  #preflight;
37
- constructor(bytes, value, routes, executionSha256, preflight) {
56
+ #upgradeWritePaths;
57
+ #mechanicalUnits;
58
+ #preservationInvariants;
59
+ #upgradeEvidenceIndex;
60
+ constructor(bytes, value, routes, executionSha256, preflight, upgradeWritePaths, mechanicalUnits, preservationInvariants, upgradeEvidenceIndex) {
38
61
  this.#bytes = Buffer.from(bytes);
39
62
  this.#value = structuredClone(value);
40
63
  this.#routes = structuredClone(routes);
41
64
  this.#executionSha256 = executionSha256;
42
65
  this.#preflight = structuredClone(preflight);
66
+ this.#upgradeWritePaths = upgradeWritePaths ? new Set(upgradeWritePaths) : null;
67
+ this.#mechanicalUnits = new Set(mechanicalUnits);
68
+ this.#preservationInvariants = structuredClone(preservationInvariants);
69
+ this.#upgradeEvidenceIndex = structuredClone(upgradeEvidenceIndex);
43
70
  this.sha256 = createHash("sha256").update(bytes).digest("hex");
44
71
  Object.freeze(this);
45
72
  }
46
73
  static prepare(input, flow, binding) {
47
74
  try {
48
- if (input.byteLength < 1 || input.byteLength > 1024 * 1024 || flow.descriptor.id !== "mb-init"
75
+ const stages = memoryStages[flow.descriptor.id], upgrade = flow.descriptor.id === "mb-upgrade";
76
+ if (input.byteLength < 1 || input.byteLength > 1024 * 1024 || !stages
49
77
  || !stages.includes(binding.stage) || !binding.runId || !sha(binding.inputSha256))
50
78
  refuse("invalid_input_binding");
79
+ if (upgrade && (!binding.upgrade || !["patch", "targeted_minor", "full_migration"].includes(binding.upgrade.minimumMode)))
80
+ refuse("missing_upgrade_impact_binding");
81
+ if (binding.upgrade?.evidenceIndex && (binding.upgrade.evidenceIndex.path !== "upgrade-baseline.json" || !sha(binding.upgrade.evidenceIndex.sha256)))
82
+ refuse("invalid_upgrade_evidence_binding");
51
83
  const bytes = Buffer.from(input), value = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(bytes));
52
84
  const schema = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(flow.stageBlob(binding.stage, "result_schema")));
53
- if (schema.$id !== `dd-flow/mb-init-${binding.stage}-result@1`)
85
+ if (schema.$id !== (upgrade ? "dd-flow/mb-upgrade-stage-result@1" : `dd-flow/mb-init-${binding.stage}-result@1`))
54
86
  refuse("invalid_result_schema");
55
87
  const validate = new Ajv2020({ strict: true, strictRequired: false, validateFormats: false }).compile(schema);
56
88
  if (!validate(value))
@@ -71,7 +103,7 @@ export class PreparedMemoryStageResult {
71
103
  refuse("foreign_predecessor_reference");
72
104
  const units = unique(value.units.map(unit => unit.id), "duplicate_unit");
73
105
  if (binding.unitIds === null) {
74
- if (!["preflight", "discover"].includes(binding.stage))
106
+ if (!["preflight", upgrade ? "diff-analysis" : "discover"].includes(binding.stage))
75
107
  refuse("missing_unit_inventory");
76
108
  }
77
109
  else {
@@ -97,11 +129,11 @@ export class PreparedMemoryStageResult {
97
129
  return;
98
130
  const record = node;
99
131
  if (typeof record.unit_id === "string" && !units.has(record.unit_id))
100
- refuse("foreign_unit_reference");
132
+ refuse("foreign_unit_reference", { unit_id: record.unit_id.slice(0, 256) });
101
133
  if (Array.isArray(record.unit_ids) && record.unit_ids.some(id => typeof id !== "string" || !units.has(id)))
102
- refuse("foreign_unit_reference");
134
+ refuse("foreign_unit_reference", { unit_ids: record.unit_ids.filter(id => typeof id !== "string" || !units.has(id)).slice(0, 8).map(id => String(id).slice(0, 256)) });
103
135
  if (typeof record.path === "string" && (typeof record.root === "string" || typeof record.sha256 === "string"))
104
- contained(record.path, typeof record.root === "string" && record.sha256 === undefined);
136
+ contained(record.path, typeof record.root === "string" && record.sha256 === undefined, true);
105
137
  if (typeof record.root === "string" && typeof record.path === "string" && typeof record.sha256 === "string") {
106
138
  const key = memorySourceKey(record.root, record.path);
107
139
  if (binding.sources.get(key) !== record.sha256 && !(role === "evidence" && binding.historicalEvidence?.has(`${key}\0${record.sha256}`)))
@@ -110,15 +142,60 @@ export class PreparedMemoryStageResult {
110
142
  Object.entries(record).forEach(([key, child]) => visit(child, key === "evidence" || key === "artifacts" ? key : role));
111
143
  };
112
144
  visit(value);
145
+ if (upgrade) {
146
+ const invariants = value.details.preservation_invariants;
147
+ if (invariants)
148
+ unique(invariants.map(item => String(item.id)), "duplicate_preservation_invariant");
149
+ const expected = binding.upgrade.preservationInvariants;
150
+ if (binding.stage !== "preflight" && !expected)
151
+ refuse("missing_upgrade_preservation_binding");
152
+ if (binding.stage === "diff-analysis") {
153
+ const modes = ["patch", "targeted_minor", "full_migration"];
154
+ if (modes.indexOf(String(value.details.mode)) < modes.indexOf(binding.upgrade.minimumMode))
155
+ refuse("weakened_upgrade_mode");
156
+ const ordered = (items) => [...items].sort((a, b) => String(a.id).localeCompare(String(b.id), "en"));
157
+ if (hybridDigest(ordered(invariants)) !== hybridDigest(ordered(expected)))
158
+ refuse("changed_preservation_invariants");
159
+ for (const change of value.details.path_map) {
160
+ if (change.from !== null)
161
+ contained(change.from, false, true);
162
+ if (change.to !== null)
163
+ contained(change.to, false, true);
164
+ }
165
+ }
166
+ const unreviewable = (invariants ?? expected ?? []).filter(item => !value.units.some(unit => unit.id === item.unit_id && unit.applicability === "applicable"));
167
+ if (unreviewable.length)
168
+ refuse("unreviewable_preservation_unit", {
169
+ unit_ids: [...new Set(unreviewable.map(item => item.unit_id))].slice(0, 8),
170
+ invariant_ids: unreviewable.map(item => item.id).slice(0, 8),
171
+ hint: binding.stage === "preflight"
172
+ ? "Each preservation criterion needs an applicable assessment unit. Bind a no-migration criterion to an applicable provenance/readiness unit; this does not require migration Work or an apply receipt."
173
+ : "Keep each retained preservation criterion assigned to an applicable assessment unit. Correct proposed applicability, not accepted invariant identities or predecessor bytes."
174
+ });
175
+ if (binding.stage === "preflight" || binding.stage === "diff-analysis") {
176
+ const uncovered = value.units.filter(unit => unit.applicability === "applicable" && !invariants.some(item => item.unit_id === unit.id));
177
+ if (uncovered.length)
178
+ refuse("missing_applicable_unit_preservation", { unit_ids: uncovered.map(unit => unit.id).slice(0, 8) });
179
+ }
180
+ if (binding.stage === "verify") {
181
+ const assessments = value.details.preservation_assessments;
182
+ const selected = unique(assessments.map(item => item.invariant_id), "duplicate_preservation_assessment");
183
+ if (selected.size !== expected.length || expected.some(item => !selected.has(String(item.id))))
184
+ refuse("incomplete_preservation_assessment");
185
+ if (assessments.some(item => expected.find(original => original.id === item.invariant_id)?.unit_id !== item.unit_id))
186
+ refuse("foreign_preservation_unit");
187
+ }
188
+ }
113
189
  let routes = null;
114
- if (binding.stage === "compose") {
115
- validateMemoryCompositionGraph(value);
190
+ const authoringPaths = upgrade && binding.stage === "upgrade" ? binding.upgrade?.authoringPaths : undefined;
191
+ if (binding.stage === "compose" || authoringPaths) {
192
+ validateMemoryCompositionGraph(value, authoringPaths, authoringPaths ? binding.upgrade?.mechanicalUnits : undefined);
116
193
  if (!(binding.execution instanceof FrozenGenericExecution) || binding.execution.settings.flow_id !== flow.descriptor.id
117
194
  || binding.execution.settings.descriptor_sha256 !== flow.sha256 || binding.execution.sha256 !== binding.executionSha256)
118
195
  refuse("missing_or_foreign_composition_execution");
119
196
  const works = value.details.works;
120
197
  try {
121
- routes = works.map(work => ({ key: work.key, execution: binding.execution.work("compose", work.profile_id ?? undefined) }));
198
+ routes = works.map(work => ({ key: work.key, execution: binding.execution.work(binding.stage, work.profile_id ?? undefined) }));
122
199
  }
123
200
  catch {
124
201
  refuse("unapproved_composition_profile");
@@ -141,16 +218,29 @@ export class PreparedMemoryStageResult {
141
218
  }
142
219
  }
143
220
  }
144
- return new PreparedMemoryStageResult(bytes, value, routes, routes ? binding.executionSha256 : null, binding.preflight ?? null);
221
+ return new PreparedMemoryStageResult(bytes, value, routes, routes ? binding.executionSha256 : null, binding.preflight ?? null, authoringPaths ? new Set([...authoringPaths.values()].flatMap(paths => [...paths])) : null, authoringPaths ? binding.upgrade?.mechanicalUnits ?? new Set() : new Set(), upgrade ? binding.upgrade?.preservationInvariants ?? [] : [], upgrade ? binding.upgrade?.evidenceIndex ?? null : null);
145
222
  }
146
223
  catch (error) {
147
- if (error instanceof AppError && error.code === "memory_stage_result_invalid")
224
+ if (error instanceof AppError && error.code === "memory_stage_result_invalid") {
225
+ if (correctableProposalReasons.has(String(error.details.reason)) && error.details.recoverable !== false)
226
+ throw new AppError(error.code, `Correct the proposed result (${error.details.reason}) without changing retained RUN, predecessor or execution bindings`, error.exitCode, { ...error.details, recoverable: true, result_sha256: createHash("sha256").update(input).digest("hex") });
148
227
  throw error;
228
+ }
149
229
  return refuse("invalid_result_or_binding");
150
230
  }
151
231
  }
152
232
  bytes() { return Buffer.from(this.#bytes); }
233
+ mechanicalUnitIds() { return new Set(this.#mechanicalUnits); }
234
+ preservationInvariants() { return structuredClone(this.#preservationInvariants); }
153
235
  value() { return structuredClone(this.#value); }
236
+ authoringStage() {
237
+ if (!this.#routes || !["compose", "upgrade"].includes(this.#value.stage))
238
+ refuse("not_a_composition_result");
239
+ return this.#value.stage;
240
+ }
241
+ allowsCompositionOutput(root, logical) {
242
+ return memoryCompositionOutputAllowed(logical, root === "workspace" && this.#upgradeWritePaths ? this.#upgradeWritePaths : undefined);
243
+ }
154
244
  compositionRoutes() {
155
245
  if (!this.#routes)
156
246
  refuse("not_a_composition_result");
@@ -161,8 +251,7 @@ export class PreparedMemoryStageResult {
161
251
  * this result's predecessor/source custody before passing the batch to its writer.
162
252
  */
163
253
  compositionBatch(binding) {
164
- if (!this.#routes || this.#value.stage !== "compose")
165
- refuse("not_a_composition_result");
254
+ const stage = this.authoringStage();
166
255
  // Registration assigns unfinished authoring; only final acceptance attests
167
256
  // settled outputs. A repairable blueprint is not a blocked dispatch.
168
257
  if (this.#value.outcome === "blocked")
@@ -184,12 +273,14 @@ export class PreparedMemoryStageResult {
184
273
  if (typeof work.task !== "string" || !work.task.trim() || work.task.length > 16384)
185
274
  refuse("missing_composition_work_task");
186
275
  const packet = { schema_id: "dd-flow/memory-composition-work-packet@1", run_id: binding.runId,
187
- stage: "compose", key: work.key, role: work.role, unit_ids: work.unit_ids,
276
+ stage, key: work.key, role: work.role, unit_ids: work.unit_ids,
188
277
  task: work.task, depends_on: work.depends_on, result_schema: binding.resultSchema,
189
278
  read_scopes: work.read_scopes, write_scopes: work.write_scopes,
190
279
  compose_result_sha256: this.sha256, compose_input_sha256: binding.inputSha256,
191
280
  execution_sha256: binding.executionSha256, predecessors: this.#value.predecessors };
192
- const scopedPacket = { ...packet, ...(this.#preflight ? { preflight: structuredClone(this.#preflight) } : {}) };
281
+ const scopedPacket = { ...packet, ...(this.#preflight ? { preflight: structuredClone(this.#preflight) } : {}),
282
+ ...(this.#upgradeEvidenceIndex ? { upgrade_baseline: { root: "run", ...this.#upgradeEvidenceIndex },
283
+ preservation_invariants: this.#preservationInvariants.filter(item => work.unit_ids.includes(String(item.unit_id))) } : {}) };
193
284
  return { key: work.key, task: work.task, depends_on: [...work.depends_on],
194
285
  launch_policy: "fresh_agent_required", result_schema: binding.resultSchema,
195
286
  payload: structuredClone({ memory_composition: { ...scopedPacket, packet_sha256: hybridDigest(scopedPacket) },
@@ -0,0 +1,243 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import { createHash } from "node:crypto";
4
+ import { AppError } from "../shared/errors.js";
5
+ import { requireProjectByRoot } from "./projects.js";
6
+ import { resolveProjectRoot } from "../storage/paths.js";
7
+ import { assertPreparedRunBundleSqlAuthority, readFlowRunExecutionBundle } from "./runs.js";
8
+ import { hybridDigest } from "./hybrid-contracts.js";
9
+ import { PreparedMemoryUpgradeBootstrapReadiness, readMemoryUpgradeBootstrapReadiness } from "./memory-upgrade-bootstrap-readiness.js";
10
+ import { readUpgradeStorageReadiness, resolveUpgradeHistoricalScope } from "./memory-upgrade-storage-readiness.js";
11
+ import { PreparedDescriptorStageStart } from "./descriptor-stage-start.js";
12
+ import { PreparedDescriptorStageCoordinator, readDescriptorStageInput } from "./descriptor-stage-coordinator.js";
13
+ import { assertMemoryOrdinaryUpgradeConfigurationCoverage } from "./memory-ordinary-defaults.js";
14
+ import { PreparedDescriptorStageSettlement } from "./descriptor-stage-settlement.js";
15
+ import { PreparedMemoryResultBinding } from "./memory-result-binding.js";
16
+ import { PreparedMemoryStageResult } from "./memory-stage-results.js";
17
+ import { ObservedMemorySources } from "./memory-source-observations.js";
18
+ import { readAcceptedStageData } from "./stage-report-renderer.js";
19
+ import { localEnvironmentAllowed, workspaceGitFiles } from "./workspace-files.js";
20
+ import { validateUpgradePathMap } from "./memory-upgrade-inventory.js";
21
+ import { readMemoryUpgradeEvidence, readIssuedMemoryUpgradeTarget } from "./memory-upgrade-evidence.js";
22
+ function refuse(reason) {
23
+ throw new AppError("memory_upgrade_analysis_handler_invalid", "Upgrade analysis requires its accepted bootstrap and original project", 2, { reason, effect: "no_effect" });
24
+ }
25
+ function rejectResult(bytes, reason, reference) {
26
+ const message = reason === "missing_impact_checks" ? "Include every required check from the issued adjacent release-impact chain in the proposed analysis result"
27
+ : reason === "symlink_source_reference" ? "Cite the issued run upgrade-baseline.json project_link_metadata for link identity, and a selected regular target file for contents; direct symlink sources are not admitted"
28
+ : reason === "analysis_not_accepted" ? "Accepted analysis cannot carry unresolved major/blocking findings. Represent planned upgrade work in units, path_map and required_checks; for a real blocker use the issued pause command. Do not mark unresolved work resolved merely to pass this gate"
29
+ : "Correct the result evidence using issued stage inputs and old/target references; controller response views and logs are not admitted evidence";
30
+ throw new AppError("memory_stage_result_invalid", message, 2, { reason, phase: "prepare", effect: "no_effect", recoverable: true,
31
+ result_sha256: createHash("sha256").update(bytes).digest("hex"),
32
+ ...(reference ? { reference: { root: String(reference.root).slice(0, 32), path: String(reference.path).slice(0, 1024) } } : {}) });
33
+ }
34
+ function selected(context, input) {
35
+ if (!["preflight", "diff-analysis"].includes(input.stage) || !(input.bytes instanceof Uint8Array) || input.bytes.length > 1024 * 1024)
36
+ refuse("invalid_stage_input");
37
+ const bundle = readFlowRunExecutionBundle(context, input);
38
+ if (!bundle || bundle.descriptor.descriptor.id !== "mb-upgrade"
39
+ || bundle.descriptor.descriptor.entry_stage !== "preflight" || bundle.descriptor.successor("preflight", "accepted") !== "diff-analysis")
40
+ refuse("registered_upgrade_bundle_required");
41
+ const project = requireProjectByRoot(context, resolveProjectRoot(input.projectRoot));
42
+ const scope = { projectId: project.id, runId: input.runId, stage: input.stage };
43
+ const run = assertPreparedRunBundleSqlAuthority(context, scope, bundle), index = JSON.parse(run.index_json);
44
+ const frozen = bundle.execution.snapshot().scope;
45
+ const bootstrapScope = { requestId: frozen.requestId, projectRoot: frozen.projectRoot, workspaceRoot: frozen.workspace };
46
+ const ready = readMemoryUpgradeBootstrapReadiness(context, bootstrapScope);
47
+ if (!ready?.hasPreservationBaseline || ready.projectId !== project.id || ready.authority.binding.execution_sha256 !== bundle.execution.sha256)
48
+ refuse("accepted_upgrade_bootstrap_required");
49
+ const predecessor = input.stage === "diff-analysis" ? readAcceptedStageData(context, { ...scope, stage: "preflight" }) : null;
50
+ const authority = hybridDigest(ready.authority);
51
+ const fence = (writer) => {
52
+ const current = readMemoryUpgradeBootstrapReadiness(writer, bootstrapScope);
53
+ if (!current || hybridDigest(current.authority) !== authority
54
+ || predecessor && readAcceptedStageData(writer, { ...scope, stage: "preflight" }).authority_sha256 !== predecessor.authority_sha256)
55
+ refuse("analysis_dependencies_changed");
56
+ };
57
+ return { bundle, scope, bootstrapScope, predecessor, fence, accepted: index.stage_runs.find((stage) => stage.stage === input.stage)?.status === "done" };
58
+ }
59
+ /** Agent references select current observations from the CLI inventory, not
60
+ * arbitrary local files. Canonical/receipt inputs come from the retained RUN
61
+ * bundle; old document evidence is independently supplied by the SQL binding.
62
+ */
63
+ function sources(context, input, state) {
64
+ let value;
65
+ try {
66
+ value = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(input.bytes));
67
+ }
68
+ catch {
69
+ return rejectResult(input.bytes, "invalid_result_json");
70
+ }
71
+ const storage = readUpgradeStorageReadiness(context, state.bootstrapScope);
72
+ if (!storage?.selection)
73
+ refuse("retained_selection_required");
74
+ const run = assertPreparedRunBundleSqlAuthority(context, state.scope, state.bundle);
75
+ const workspace = run.workspace_root ?? run.project_root;
76
+ const projectIsWorkspace = workspace === run.project_root;
77
+ const allowed = new Set([...(workspaceGitFiles(workspace, ["--cached", "--others", "--exclude-standard"]) ?? []),
78
+ ...storage.selection.payload.current.inventory.inventory.entries.filter(entry => entry.current?.kind === "file" || entry.target).map(entry => `.memory-bank/${entry.path}`)]);
79
+ const allowedRun = new Set(state.bundle.manifest.inventory.map(entry => `execution-bundle/${entry.path}`));
80
+ const oldEvidence = readMemoryUpgradeEvidence(context, state.bootstrapScope);
81
+ const oldRunHashes = new Map([[oldEvidence.reference.path, oldEvidence.reference.sha256],
82
+ ...oldEvidence.index.entries.map(entry => [entry.retained.path, entry.retained.sha256])]);
83
+ const target = readIssuedMemoryUpgradeTarget(context, state.bootstrapScope, state.scope.projectId);
84
+ if (target)
85
+ for (const ref of [target.reference, ...target.index.entries.map(entry => entry.retained)]) {
86
+ allowedRun.add(ref.path);
87
+ oldRunHashes.set(ref.path, ref.sha256);
88
+ }
89
+ const inputStage = JSON.parse(run.index_json).stage_runs.find((stage) => stage.stage === state.scope.stage);
90
+ const inputPath = `${state.bundle.descriptor.stage(state.scope.stage).directory}/stage-input.json`;
91
+ if (typeof inputStage?.input_sha256 === "string")
92
+ allowedRun.add(inputPath);
93
+ allowedRun.add(oldEvidence.reference.path);
94
+ for (const entry of oldEvidence.index.entries)
95
+ allowedRun.add(entry.retained.path);
96
+ if (state.predecessor)
97
+ allowedRun.add(state.predecessor.result.path);
98
+ const position = state.bundle.descriptor.descriptor.stages.findIndex(stage => stage.id === state.scope.stage);
99
+ for (const stage of state.bundle.descriptor.descriptor.stages.slice(0, position)) {
100
+ const accepted = readAcceptedStageData(context, { ...state.scope, stage: stage.id });
101
+ allowedRun.add(accepted.result.path);
102
+ // Accepted invariants can cite the packet that originally established
103
+ // their scope. Retain that exact issued input, not arbitrary RUN files.
104
+ if (typeof accepted.input_sha256 === "string") {
105
+ const logical = `${stage.directory}/stage-input.json`;
106
+ allowedRun.add(logical);
107
+ oldRunHashes.set(logical, accepted.input_sha256);
108
+ }
109
+ }
110
+ const historical = new Set(oldEvidence.index.entries.map(entry => `${entry.source.path}\0${entry.source.sha256}`));
111
+ const locations = new Map(), pending = [{ value, role: "source" }];
112
+ let nodes = 0;
113
+ while (pending.length) {
114
+ if (++nodes > 100_000)
115
+ rejectResult(input.bytes, "unbounded_result_tree");
116
+ const item = pending.pop(), node = item.value;
117
+ if (Array.isArray(node)) {
118
+ for (const child of node)
119
+ pending.push({ value: child, role: item.role });
120
+ continue;
121
+ }
122
+ if (!node || typeof node !== "object")
123
+ continue;
124
+ const ref = node;
125
+ if (typeof ref.root === "string" && typeof ref.path === "string" && typeof ref.sha256 === "string") {
126
+ if (ref.root === "workspace" || ref.root === "project" && projectIsWorkspace) {
127
+ if (!allowed.has(ref.path) || ref.path.split("/").some(part => [".git", "node_modules", ".dd-flow"].includes(part) || !localEnvironmentAllowed(part)))
128
+ rejectResult(input.bytes, "unselected_project_source", ref);
129
+ if (!(item.role === "evidence" && historical.has(`${ref.path}\0${ref.sha256}`))) {
130
+ // Historical retained bytes need not exist in today's tree.
131
+ let parent = workspace;
132
+ for (const part of ref.path.split("/").slice(0, -1)) {
133
+ parent = path.join(parent, part);
134
+ if (!fs.lstatSync(parent).isDirectory())
135
+ rejectResult(input.bytes, "symlink_source_reference", ref);
136
+ }
137
+ if (fs.lstatSync(path.join(workspace, ref.path), { throwIfNoEntry: false })?.isSymbolicLink())
138
+ rejectResult(input.bytes, "symlink_source_reference", ref);
139
+ }
140
+ }
141
+ else if (ref.root === "run") {
142
+ if (!allowedRun.has(ref.path))
143
+ rejectResult(input.bytes, "unselected_run_source", ref);
144
+ if (ref.path === inputPath && ref.sha256 !== inputStage?.input_sha256)
145
+ rejectResult(input.bytes, "changed_stage_input", ref);
146
+ if (oldRunHashes.has(ref.path) && oldRunHashes.get(ref.path) !== ref.sha256)
147
+ rejectResult(input.bytes, "changed_old_evidence", ref);
148
+ }
149
+ else
150
+ rejectResult(input.bytes, "unselected_source_root", ref);
151
+ // The accepted old tree remains evidence after a canonical path is
152
+ // replaced or archived. It never qualifies a current output artifact.
153
+ if (!(item.role === "evidence" && ["workspace", "project"].includes(ref.root) && historical.has(`${ref.path}\0${ref.sha256}`)))
154
+ locations.set(`${ref.root}\0${ref.path}`, { root: ref.root, path: ref.path });
155
+ }
156
+ for (const [key, child] of Object.entries(ref))
157
+ pending.push({ value: child, role: key === "evidence" || key === "artifacts" ? key : item.role });
158
+ }
159
+ return ObservedMemorySources.capture({ workspace, ...(projectIsWorkspace ? { project: workspace } : {}), run: run.run_root ?? run.run_dir }, [...locations.values()]);
160
+ }
161
+ /** Shared upgrade observation path for registered authoring/review consumers.
162
+ * The source witness grants no Stage, Work or canonical-write authority. */
163
+ export function captureMemoryUpgradeSources(context, bundle, stage, bytes) {
164
+ if (bundle.descriptor.descriptor.id !== "mb-upgrade" || !(bytes instanceof Uint8Array) || bytes.length > 1024 * 1024)
165
+ refuse("invalid_stage_input");
166
+ bundle.descriptor.stage(stage);
167
+ const frozen = bundle.execution.snapshot().scope;
168
+ const bootstrapScope = { requestId: frozen.requestId, projectRoot: frozen.projectRoot, workspaceRoot: frozen.workspace };
169
+ const { projectId } = resolveUpgradeHistoricalScope(context, bootstrapScope);
170
+ const scope = { projectId, runId: bundle.manifest.run_id, stage };
171
+ const ready = readMemoryUpgradeBootstrapReadiness(context, bootstrapScope);
172
+ if (!ready?.hasPreservationBaseline || ready.projectId !== projectId || ready.authority.binding.execution_sha256 !== bundle.execution.sha256)
173
+ refuse("accepted_upgrade_bootstrap_required");
174
+ const edges = bundle.descriptor.descriptor.edges.filter(edge => edge.to === stage && edge.on === "accepted");
175
+ if (edges.length !== (stage === bundle.descriptor.descriptor.entry_stage ? 0 : 1))
176
+ refuse("invalid_stage_predecessor");
177
+ const predecessor = edges.length ? readAcceptedStageData(context, { ...scope, stage: edges[0].from }) : null;
178
+ return sources(context, { bytes }, { bundle, scope, bootstrapScope, predecessor });
179
+ }
180
+ /** Upgrade analysis uses the registered six-stage group. Generic lifecycle
181
+ * owns Work, prompts and dispatch; RUN admission still owns bootstrap readiness.
182
+ */
183
+ export function startMemoryUpgradeAnalysis(context, input) {
184
+ const state = selected(context, input);
185
+ if (state.accepted)
186
+ refuse("use_accepted_finish_replay");
187
+ PreparedMemoryUpgradeBootstrapReadiness.assertForRetainedRun(context, state.bundle);
188
+ const start = PreparedDescriptorStageStart.prepare(context, input);
189
+ const stage = start.apply(context, state.fence);
190
+ const coordinator = PreparedDescriptorStageCoordinator.prepare(context, input);
191
+ PreparedMemoryUpgradeBootstrapReadiness.assertForRetainedRun(context, state.bundle);
192
+ return { ...stage, ...coordinator.apply(context, state.fence) };
193
+ }
194
+ export function finishMemoryUpgradeAnalysis(context, input) {
195
+ const state = selected(context, input);
196
+ if (state.accepted) {
197
+ // Historical acknowledgement must not reinterpret old artifacts after the
198
+ // upgrade has intentionally changed them. SQL settlement owns acceptance.
199
+ const binding = PreparedMemoryResultBinding.prepare(context, { ...state.scope, flow: state.bundle.descriptor,
200
+ execution: state.bundle.execution, bundle: state.bundle, sources: ObservedMemorySources.capture({}, []), acceptedReplay: true });
201
+ if (!binding.acceptedBytes()?.equals(input.bytes))
202
+ refuse("accepted_result_changed");
203
+ const settlement = PreparedDescriptorStageSettlement.prepare(context, input);
204
+ return settlement.apply(context, writer => { state.fence(writer); binding.assertCustody(writer); });
205
+ }
206
+ const observed = sources(context, input, state);
207
+ const binding = PreparedMemoryResultBinding.prepare(context, { ...state.scope, flow: state.bundle.descriptor,
208
+ execution: state.bundle.execution, bundle: state.bundle, sources: observed, acceptedReplay: state.accepted });
209
+ const accepted = binding.acceptedBytes();
210
+ if (accepted && !accepted.equals(input.bytes))
211
+ refuse("accepted_result_changed");
212
+ const result = PreparedMemoryStageResult.prepare(input.bytes, state.bundle.descriptor, binding.binding()), value = result.value();
213
+ if (value.outcome !== "accepted" || value.findings.some(finding => finding.severity !== "minor" && finding.status !== "resolved"))
214
+ rejectResult(input.bytes, "analysis_not_accepted");
215
+ const details = value.details;
216
+ if (input.stage === "diff-analysis") {
217
+ const defaults = readDescriptorStageInput(context, state.scope, state.bundle).input.ordinary_defaults;
218
+ if (defaults) {
219
+ assertMemoryOrdinaryUpgradeConfigurationCoverage(value, defaults);
220
+ if (defaults.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')='diff-analysis' LIMIT 1", [state.scope.projectId, state.scope.runId])) {
221
+ throw new AppError("memory_stage_result_invalid", "Resolve the ordinary configuration choice through the existing diff-analysis pause/answer path before accepting the upgrade plan", 2, { phase: "prepare", effect: "no_effect", recoverable: true, reason: "ordinary_configuration_choice_required", missing: defaults.missing });
222
+ }
223
+ }
224
+ const storage = readUpgradeStorageReadiness(context, state.bootstrapScope);
225
+ const checks = details.required_checks;
226
+ if (!storage?.selection || storage.selection.payload.impact.chain.steps.some(step => !Array.isArray(step.required_checks)
227
+ || step.required_checks.some(check => typeof check !== "string" || !checks.includes(check))))
228
+ rejectResult(input.bytes, "missing_impact_checks");
229
+ try {
230
+ validateUpgradePathMap(storage.selection.payload.current.inventory.inventory, details.path_map);
231
+ }
232
+ catch (error) {
233
+ if (!(error instanceof AppError) || error.code !== "upgrade_inventory_invalid")
234
+ throw error;
235
+ throw new AppError("memory_stage_result_invalid", "Correct the proposed upgrade path map before it becomes an immutable accepted plan", 2, { reason: "invalid_upgrade_path_map", cause: error.details.reason, pointer: "/details/path_map", phase: "prepare", effect: "no_effect",
236
+ recoverable: true, result_sha256: result.sha256 });
237
+ }
238
+ }
239
+ const settlement = PreparedDescriptorStageSettlement.prepare(context, { ...input, bytes: result.bytes() });
240
+ PreparedMemoryUpgradeBootstrapReadiness.assertForRetainedRun(context, state.bundle);
241
+ binding.revalidate(context);
242
+ return settlement.apply(context, writer => { state.fence(writer); binding.assertCustody(writer); });
243
+ }