@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
@@ -1,4 +1,5 @@
1
1
  import path from "node:path";
2
+ import fs from "node:fs";
2
3
  import { spawnSync } from "node:child_process";
3
4
  import { AppError } from "../shared/errors.js";
4
5
  import { hybridDigest } from "./hybrid-contracts.js";
@@ -15,6 +16,51 @@ function validScope(scope) {
15
16
  || Object.keys(scope).sort().join() !== "projectRoot,requestId,workspaceRoot")
16
17
  refuse("invalid_request_scope");
17
18
  }
19
+ /** Shared read-only policy observation for init and upgrade. The caller owns
20
+ * retained input custody; these facts grant neither dispatch nor push. */
21
+ export function observeMemoryGitPolicySelection(environment, input) {
22
+ validScope(input.scope);
23
+ const { scope, selection } = input;
24
+ if (!selection || Object.keys(selection).sort().join() !== "integrationBranch,route"
25
+ || !["integration_branch_direct", "feature_worktree"].includes(selection.route)
26
+ || typeof selection.integrationBranch !== "string" || !selection.integrationBranch || selection.integrationBranch.startsWith("-") || selection.integrationBranch.length > 1024
27
+ || [...selection.integrationBranch].some(character => character.charCodeAt(0) <= 32 || character.charCodeAt(0) === 127))
28
+ refuse("explicit_policy_required");
29
+ if (typeof input.gitExecutable !== "string" || !path.isAbsolute(input.gitExecutable))
30
+ refuse("explicit_git_required");
31
+ const env = Object.fromEntries(Object.entries(environment).filter(([key]) => !key.startsWith("GIT_")));
32
+ const git = (directory, args, optional = false) => {
33
+ const result = spawnSync(input.gitExecutable, ["-c", "core.fsmonitor=false", "-C", directory, ...args], { env: { ...env, GIT_OPTIONAL_LOCKS: "0" }, encoding: "utf8", timeout: 5000, maxBuffer: 64 * 1024, shell: false, windowsHide: true });
34
+ if (result.error || result.signal || !optional && result.status !== 0)
35
+ refuse("git_policy_observation_failed");
36
+ return result.status === 0 ? result.stdout.trim() : null;
37
+ };
38
+ const valid = spawnSync(input.gitExecutable, ["check-ref-format", `refs/heads/${selection.integrationBranch}`], { env: { ...env, GIT_OPTIONAL_LOCKS: "0" }, timeout: 5000, maxBuffer: 64 * 1024, shell: false, windowsHide: true });
39
+ if (valid.error || valid.signal || valid.status !== 0)
40
+ refuse("invalid_integration_branch");
41
+ const common = (directory) => {
42
+ if (git(directory, ["rev-parse", "--is-bare-repository"]) !== "false"
43
+ || fs.realpathSync(git(directory, ["rev-parse", "--show-toplevel"])) !== directory)
44
+ refuse("not_repository_root");
45
+ return fs.realpathSync(path.resolve(directory, git(directory, ["rev-parse", "--git-common-dir"])));
46
+ };
47
+ const repository = common(scope.projectRoot);
48
+ if (common(scope.workspaceRoot) !== repository)
49
+ refuse("foreign_workspace_repository");
50
+ const branch = git(scope.workspaceRoot, ["symbolic-ref", "--short", "HEAD"]);
51
+ if (!branch)
52
+ refuse("workspace_branch_unavailable");
53
+ if (selection.route === "integration_branch_direct"
54
+ ? scope.projectRoot !== scope.workspaceRoot || branch !== selection.integrationBranch
55
+ : scope.projectRoot === scope.workspaceRoot || branch === selection.integrationBranch)
56
+ refuse("route_workspace_mismatch");
57
+ if (selection.route === "feature_worktree"
58
+ && !/^[a-f0-9]{40,64}$/.test(git(scope.projectRoot, ["rev-parse", "--verify", `refs/heads/${selection.integrationBranch}^{commit}`], true) ?? ""))
59
+ refuse("integration_branch_unavailable");
60
+ const head = git(scope.workspaceRoot, ["rev-parse", "--verify", "HEAD"], true);
61
+ return { repository, workspace_branch: branch, initial_head: head, source: "explicit_request",
62
+ route: selection.route, integration_branch: selection.integrationBranch, push_authorized: false };
63
+ }
18
64
  /** SQL preservation, not fresh policy inference, Git execution or permission. */
19
65
  export function readMemoryInitGitPolicy(context, scope) {
20
66
  validScope(scope);
@@ -75,32 +121,15 @@ export class PreparedMemoryInitGitPolicy {
75
121
  static prepareFresh(environment, input) {
76
122
  if (!(input.target instanceof PreparedMemoryInitTarget))
77
123
  refuse("prepared_target_required");
78
- const selection = input.selection;
79
- if (!selection || Object.keys(selection).sort().join() !== "integrationBranch,route"
80
- || !["integration_branch_direct", "feature_worktree"].includes(selection.route)
81
- || typeof selection.integrationBranch !== "string" || !selection.integrationBranch || selection.integrationBranch.startsWith("-") || selection.integrationBranch.length > 1024
82
- || [...selection.integrationBranch].some(character => character.charCodeAt(0) <= 32 || character.charCodeAt(0) === 127))
83
- refuse("explicit_policy_required");
84
124
  input.target.revalidate();
85
125
  const facts = input.target.facts(), scope = { requestId: facts.request_id, projectRoot: facts.project_root, workspaceRoot: facts.workspace_root };
86
126
  if (!facts.tool_probes)
87
127
  refuse("fresh_target_required");
88
- const env = Object.fromEntries(Object.entries(environment).filter(([key]) => !key.startsWith("GIT_")));
89
- const result = spawnSync(facts.tools.git, ["check-ref-format", `refs/heads/${selection.integrationBranch}`], { env: { ...env, GIT_OPTIONAL_LOCKS: "0" }, encoding: "utf8", timeout: 5000, maxBuffer: 64 * 1024, shell: false, windowsHide: true });
90
- if (result.error || result.signal || result.status !== 0)
91
- refuse("invalid_integration_branch");
92
- if (selection.route === "integration_branch_direct"
93
- ? scope.projectRoot !== scope.workspaceRoot || facts.branch !== selection.integrationBranch
94
- : scope.projectRoot === scope.workspaceRoot || facts.branch === selection.integrationBranch)
95
- refuse("route_workspace_mismatch");
96
- if (selection.route === "feature_worktree") {
97
- const branch = spawnSync(facts.tools.git, ["-C", scope.projectRoot, "rev-parse", "--verify", `refs/heads/${selection.integrationBranch}^{commit}`], { env: { ...env, GIT_OPTIONAL_LOCKS: "0" }, encoding: "utf8", timeout: 5000, maxBuffer: 64 * 1024, shell: false, windowsHide: true });
98
- if (branch.error || branch.signal || branch.status !== 0 || !/^[a-f0-9]{40,64}$/.test(branch.stdout.trim()))
99
- refuse("integration_branch_unavailable");
100
- }
128
+ const policy = observeMemoryGitPolicySelection(environment, { scope, gitExecutable: facts.tools.git, selection: input.selection });
129
+ if (policy.repository !== facts.repository || policy.workspace_branch !== facts.branch || policy.initial_head !== facts.head)
130
+ refuse("target_changed_after_preparation");
101
131
  const receipt = { schema_id: "dd-flow/memory-init-git-policy@1", scope, target_sha256: input.target.sha256,
102
- repository: facts.repository, workspace_branch: facts.branch, initial_head: facts.head, source: "explicit_request",
103
- route: selection.route, integration_branch: selection.integrationBranch, push_authorized: false };
132
+ ...policy };
104
133
  return new PreparedMemoryInitGitPolicy(token, input.target, receipt);
105
134
  }
106
135
  value() { return structuredClone(this.#receipt); }
@@ -54,6 +54,12 @@ export class PreparedMemoryInitLintCapability {
54
54
  const selected = target.facts(), executable = selected.tools["mb-lint"], probe = selected.tool_probes?.["mb-lint"];
55
55
  if (!probe)
56
56
  refuse("missing_selected_tool_probe");
57
+ return PreparedMemoryInitLintCapability.prepareExecutable(executable, probe, env);
58
+ }
59
+ static prepareExecutable(executable, probe, env, options = {}) {
60
+ if (!path.isAbsolute(executable) || !probe || !/^[a-f0-9]{64}$/.test(probe.executable_sha256)
61
+ || !/^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/.test(probe.version))
62
+ refuse("invalid_selected_tool");
57
63
  const checksum = () => {
58
64
  try {
59
65
  return hash(readBoundedReleaseFile(executable, 64 * 1024 * 1024));
@@ -97,11 +103,11 @@ export class PreparedMemoryInitLintCapability {
97
103
  }
98
104
  };
99
105
  const probeEnv = Object.fromEntries(Object.entries(env).filter(([key]) => !key.startsWith("GIT_")));
100
- const run = (status, rule) => {
106
+ const run = (status, rule, config) => {
101
107
  ownDirectories();
102
- const result = spawnSync(executable, ["--root", scratch, "--files", logical, "--format", "json"], { cwd: scratch, env: { ...probeEnv, GIT_OPTIONAL_LOCKS: "0" }, shell: false, windowsHide: true, timeout: 5000, maxBuffer: 256 * 1024 });
108
+ const result = spawnSync(executable, ["--root", scratch, "--files", logical, "--format", "json", ...(config ? ["--config", config] : [])], { cwd: scratch, env: { ...probeEnv, GIT_OPTIONAL_LOCKS: "0" }, shell: false, windowsHide: true, timeout: 5000, maxBuffer: 256 * 1024 });
103
109
  if (result.error || result.signal || result.status !== status)
104
- refuse("lint_probe_execution_failed");
110
+ refuse(config ? "temporary_configuration_not_supported" : "lint_probe_execution_failed");
105
111
  ownDirectories();
106
112
  const actual = fs.lstatSync(file);
107
113
  if (!actual.isFile() || actual.nlink !== 1 || !same(actual, fileOwner)
@@ -136,6 +142,15 @@ export class PreparedMemoryInitLintCapability {
136
142
  const fields = run(1, "frontmatter/required-fields");
137
143
  writeProbe(`${validDocument}\n[Missing target](missing-probe-target.md)\n`);
138
144
  const links = run(1, "links/markdown-target-exists");
145
+ if (options.temporaryConfiguration) {
146
+ // Upgrade's active-document profile needs an external config with exact
147
+ // finding exclusions; a version response or basic JSON lint is insufficient.
148
+ const config = path.join(scratch, "upgrade-lint.json");
149
+ fs.writeFileSync(config, JSON.stringify({ ignoreFindings: [{
150
+ ruleId: "links/markdown-target-exists", file: logical, target: "missing-probe-target.md"
151
+ }] }), { flag: "wx", mode: 0o600 });
152
+ run(0, undefined, config);
153
+ }
139
154
  if (checksum() !== probe.executable_sha256)
140
155
  refuse("selected_executable_changed");
141
156
  prepared = new PreparedMemoryInitLintCapability({ schema_id: "dd-flow/memory-init-lint-capability@1",
@@ -170,6 +185,9 @@ export class PreparedMemoryInitLintCapability {
170
185
  if (!probe || selected.tools["mb-lint"] !== this.#facts.executable
171
186
  || probe.version !== this.#facts.version || probe.executable_sha256 !== this.#facts.executable_sha256)
172
187
  refuse("lint_capability_changed");
188
+ this.revalidateExecutable();
189
+ }
190
+ revalidateExecutable() {
173
191
  // This instance already passed the functional probes. Publication checks
174
192
  // preserve its selected executable, not repeat qualification after effects.
175
193
  let checksum;
@@ -12,6 +12,7 @@ import { readBoundedReleaseFile } from "./release-source.js";
12
12
  import { acquireInstallationLock } from "./installation-lock.js";
13
13
  import { inspectDaemonOperation } from "../harness-runtime/lib/daemon-operations.mjs";
14
14
  import { nativeCommandFingerprint } from "../harness-runtime/lib/native-request-proof.mjs";
15
+ import { validateMemoryInitStartupScope } from "./memory-init-startup-custody.js";
15
16
  import { continueProviderCapacity } from "./provider-capacity.js";
16
17
  import { CAPACITY_POLICY, MAX_OVERLOAD_RETRIES, refusalObservation, terminalProviderOverload } from "../harness-runtime/lib/provider-overload-policy.mjs";
17
18
  const sha = (bytes) => createHash("sha256").update(bytes).digest("hex");
@@ -127,6 +128,8 @@ export async function produceMemoryInitNativeCapacity(input) {
127
128
  };
128
129
  if (result)
129
130
  return await publish(result);
131
+ if (input.restoreOnly)
132
+ refuse("completed_probe_required", "unknown");
130
133
  // An independent bounded technical workspace cannot modify the target.
131
134
  const workspace = path.join(root, "project"), stateDir = path.join(root, "daemon");
132
135
  fs.mkdirSync(workspace, { recursive: true, mode: 0o700 });
@@ -294,3 +297,13 @@ export async function produceMemoryInitNativeCapacity(input) {
294
297
  lease.release();
295
298
  }
296
299
  }
300
+ /** Recover exact completed probe bytes for a retained raw request whose
301
+ * resolved selection was not published. Never starts or repairs a probe. */
302
+ export async function restoreCompletedMemoryNativeCapacity(input) {
303
+ validateMemoryInitStartupScope(input.scope);
304
+ const prior = read(path.join(input.home, "memory-init-native-probes", hybridDigest(input.scope), "input.json"));
305
+ if (!prior || hybridDigest(prior.scope) !== hybridDigest(input.scope))
306
+ refuse("original_probe_required", "unknown");
307
+ return produceMemoryInitNativeCapacity({ ...input, profile: prior.profile, maximum: Number(prior.maximum),
308
+ nativeConfigHome: String(prior.native_config_home), requestSha256: String(prior.request_sha256), allowLiveProbe: true, restoreOnly: true });
309
+ }
@@ -55,7 +55,7 @@ function noMemoryBank(directory) {
55
55
  function probeTools(tools, env) {
56
56
  const facts = Object.create(null);
57
57
  const probeEnv = Object.fromEntries(Object.entries(env).filter(([key]) => !key.startsWith("GIT_")));
58
- for (const name of ["git", "wt", "mb-lint"]) {
58
+ for (const name of Object.keys(tools)) {
59
59
  const diagnostics = { name, executable: tools[name], args: ["--version"], timeout_ms: 5000, phase: "executable_read" };
60
60
  try {
61
61
  const digest = () => createHash("sha256").update(readBoundedReleaseFile(tools[name], 64 * 1024 * 1024)).digest("hex");
@@ -90,6 +90,16 @@ function probeTools(tools, env) {
90
90
  }
91
91
  return facts;
92
92
  }
93
+ /** Shared deterministic executable selection; no project initialization or
94
+ * tool installation. Upgrade freezes this before content synchronization. */
95
+ export function inspectMemoryLintExecutable(env) {
96
+ const selected = executable("mb-lint", env);
97
+ return { executable: selected, probe: probeTools({ "mb-lint": selected }, env)["mb-lint"] };
98
+ }
99
+ export function inspectMemoryGitExecutable(env) {
100
+ const selected = executable("git", env);
101
+ return { executable: selected, probe: probeTools({ git: selected }, env).git };
102
+ }
93
103
  function inspect(input, env, ownedPublication = false, qualifyTools = true, observe, lintReservation, sourceTools) {
94
104
  if (!/^[A-Za-z0-9][A-Za-z0-9_.-]{0,95}$/.test(input.requestId))
95
105
  refuse("invalid_request_id");
@@ -7,15 +7,21 @@ import { assertImportAdmission } from "../storage/import-guard.js";
7
7
  import { hybridDigest } from "./hybrid-contracts.js";
8
8
  import { readBoundedReleaseFile } from "./release-source.js";
9
9
  import { validateMemoryInitObjective, validateMemoryInitStartupScope } from "./memory-init-startup-custody.js";
10
- import { prepareMemoryInitRequestInput } from "./memory-init-request-input.js";
10
+ import { prepareMemoryInitRequestInput, prepareMemoryUpgradeRequestInput } from "./memory-init-request-input.js";
11
11
  const preparedCandidates = new WeakMap();
12
12
  /** Only a genuinely retained candidate can supply fresh startup input. Copied
13
13
  * objects and caller-authored snapshots never acquire candidate authority. */
14
14
  export function captureMemoryInitRequestCandidate(candidate, home, scope, objective) {
15
- const capture = candidate && typeof candidate === "object" ? preparedCandidates.get(candidate) : undefined;
16
- if (!capture)
15
+ const witness = candidate && typeof candidate === "object" ? preparedCandidates.get(candidate) : undefined;
16
+ if (!witness || witness.operation !== "memory-init")
17
17
  refuse("unprepared_request_candidate");
18
- return capture(home, scope, objective);
18
+ return witness.capture(home, scope, objective);
19
+ }
20
+ export function captureMemoryUpgradeRequestCandidate(candidate, home, scope, objective) {
21
+ const witness = candidate && typeof candidate === "object" ? preparedCandidates.get(candidate) : undefined;
22
+ if (!witness || witness.operation !== "memory-upgrade")
23
+ refuse("unprepared_request_candidate");
24
+ return witness.capture(home, scope, objective);
19
25
  }
20
26
  const sha = (bytes) => createHash("sha256").update(bytes).digest("hex");
21
27
  function refuse(reason) {
@@ -47,10 +53,42 @@ function syncDirectory(file) {
47
53
  fs.closeSync(fd);
48
54
  }
49
55
  }
56
+ const candidateKey = (scope, operation) => hybridDigest(operation === "memory-upgrade"
57
+ ? { projectRoot: scope.projectRoot, requestId: scope.requestId } : scope);
50
58
  /** Read-only preparation and exact byte capture. A candidate is not startup,
51
59
  * storage, engine or dispatch authority. Retain only after prerequisite checks;
52
60
  * no missing candidate component is reconstructed from ambient selections. */
53
61
  export function prepareMemoryInitRequestCandidate(home, scope, selection, intake) {
62
+ return prepareMemoryRequestCandidate(home, scope, selection, intake, "memory-init");
63
+ }
64
+ export function prepareMemoryUpgradeRequestCandidate(home, scope, selection, intake) {
65
+ return prepareMemoryRequestCandidate(home, scope, selection, intake, "memory-upgrade");
66
+ }
67
+ /** Exact original input recovery before an incompatible SQLite store is opened.
68
+ * No profile, installed release, model, provider or missing component is chosen. */
69
+ export function readMemoryUpgradeRequestCandidate(home, scope) {
70
+ validateMemoryInitStartupScope(scope);
71
+ directory(home);
72
+ const namespace = path.join(home, "memory-upgrade-request-candidates"), key = candidateKey(scope, "memory-upgrade");
73
+ if (!exists(namespace))
74
+ return null;
75
+ directory(namespace);
76
+ const root = path.join(namespace, key), marker = path.join(namespace, `${key}.json`);
77
+ if (!exists(root) && !exists(marker))
78
+ return null;
79
+ if (!exists(root) || !exists(marker))
80
+ refuse("candidate_custody_missing");
81
+ directory(root);
82
+ const selection = readBoundedReleaseFile(path.join(root, "selection.json"), 256 * 1024);
83
+ const intake = readBoundedReleaseFile(path.join(root, "intake.txt"), 64 * 1024);
84
+ const candidate = prepareMemoryUpgradeRequestCandidate(home, scope, selection, intake);
85
+ // Existing-candidate retain verifies only; it cannot fill partial publication.
86
+ if (candidate.retain().created)
87
+ refuse("candidate_custody_missing");
88
+ return { candidate, objective: candidate.objective,
89
+ input: captureMemoryUpgradeRequestCandidate(candidate, home, scope, candidate.objective) };
90
+ }
91
+ function prepareMemoryRequestCandidate(home, scope, selection, intake, operation) {
54
92
  validateMemoryInitStartupScope(scope);
55
93
  scope = structuredClone(scope);
56
94
  const selectionBytes = Buffer.from(selection), intakeBytes = Buffer.from(intake);
@@ -64,17 +102,21 @@ export function prepareMemoryInitRequestCandidate(home, scope, selection, intake
64
102
  return refuse("invalid_intake_utf8");
65
103
  }
66
104
  validateMemoryInitObjective(objective);
105
+ // Original-input validity is not installed-target or storage admission.
106
+ if (operation === "memory-upgrade")
107
+ prepareMemoryUpgradeRequestInput(scope, selectionBytes, intakeBytes, objective);
67
108
  const homeOwner = directory(home);
68
109
  if ([scope.projectRoot, scope.workspaceRoot].some(root => {
69
110
  const relative = path.relative(root, home);
70
111
  return relative === "" || !path.isAbsolute(relative) && relative !== ".." && !relative.startsWith(`..${path.sep}`);
71
112
  }))
72
113
  refuse("candidate_home_inside_project");
73
- assertImportAdmission(home);
74
- const namespace = path.join(home, "memory-init-request-candidates"), key = hybridDigest(scope);
114
+ if (operation === "memory-init")
115
+ assertImportAdmission(home);
116
+ const namespace = path.join(home, `${operation}-request-candidates`), key = candidateKey(scope, operation);
75
117
  const root = path.join(namespace, key), marker = path.join(namespace, `${key}.json`);
76
118
  const inputFile = path.join(root, "intake.txt"), selectionFile = path.join(root, "selection.json");
77
- const facts = { schema_id: "dd-flow/memory-init-request-candidate@1", home, home_owner: homeOwner, scope,
119
+ const facts = { schema_id: `dd-flow/${operation}-request-candidate@1`, home, home_owner: homeOwner, scope,
78
120
  selection_sha256: sha(selectionBytes), intake_sha256: sha(intakeBytes) };
79
121
  const verify = () => {
80
122
  if (hybridDigest(directory(home)) !== hybridDigest(homeOwner))
@@ -84,7 +126,16 @@ export function prepareMemoryInitRequestCandidate(home, scope, selection, intake
84
126
  refuse("candidate_custody_missing");
85
127
  const expected = { ...facts, owner: directory(root) };
86
128
  const bytes = readBoundedReleaseFile(marker, 16 * 1024);
87
- if (!bytes.equals(Buffer.from(JSON.stringify(expected))))
129
+ let observed;
130
+ try {
131
+ observed = JSON.parse(bytes.toString("utf8"));
132
+ }
133
+ catch {
134
+ return refuse("candidate_request_changed");
135
+ }
136
+ // Metadata key order is not request identity. Original intake/selection
137
+ // remain byte-bound below; a reader must not rewrite their marker.
138
+ if (hybridDigest(observed) !== hybridDigest(expected))
88
139
  refuse("candidate_request_changed");
89
140
  if (!exists(inputFile) || !exists(selectionFile))
90
141
  refuse("candidate_input_missing");
@@ -95,6 +146,10 @@ export function prepareMemoryInitRequestCandidate(home, scope, selection, intake
95
146
  if (exists(namespace))
96
147
  directory(namespace);
97
148
  const found = exists(root) || exists(marker);
149
+ // Migration reconciliation must be able to read its original upgrade input
150
+ // while ordinary writers are fenced. It gains no permit or fresh write.
151
+ if (!found && operation === "memory-upgrade")
152
+ assertImportAdmission(home);
98
153
  if (found)
99
154
  verify();
100
155
  let retained = false;
@@ -102,7 +157,7 @@ export function prepareMemoryInitRequestCandidate(home, scope, selection, intake
102
157
  const candidate = {
103
158
  objective,
104
159
  retain: () => {
105
- if (found) {
160
+ if (found || operation === "memory-upgrade" && retained) {
106
161
  verify();
107
162
  return complete(false);
108
163
  }
@@ -145,13 +200,14 @@ export function prepareMemoryInitRequestCandidate(home, scope, selection, intake
145
200
  }
146
201
  }
147
202
  };
148
- preparedCandidates.set(candidate, (expectedHome, expectedScope, expectedObjective) => {
149
- if (!retained)
150
- refuse("request_candidate_not_retained");
151
- if (home !== expectedHome || hybridDigest(scope) !== hybridDigest(expectedScope) || objective !== expectedObjective)
152
- refuse("foreign_request_candidate");
153
- verify();
154
- return prepareMemoryInitRequestInput(scope, selectionBytes, intakeBytes, objective);
155
- });
203
+ preparedCandidates.set(candidate, { operation, capture: (expectedHome, expectedScope, expectedObjective) => {
204
+ if (!retained)
205
+ refuse("request_candidate_not_retained");
206
+ if (home !== expectedHome || hybridDigest(scope) !== hybridDigest(expectedScope) || objective !== expectedObjective)
207
+ refuse("foreign_request_candidate");
208
+ verify();
209
+ return operation === "memory-upgrade" ? prepareMemoryUpgradeRequestInput(scope, selectionBytes, intakeBytes, objective)
210
+ : prepareMemoryInitRequestInput(scope, selectionBytes, intakeBytes, objective);
211
+ } });
156
212
  return Object.freeze(candidate);
157
213
  }
@@ -26,9 +26,9 @@ function text(bytes) {
26
26
  }
27
27
  /** Data-only decoding for SQL custody/recovery. No Home, files, engine registry,
28
28
  * installed canon, current profiles or provider is consulted. */
29
- export function readMemoryInitRequestInput(value, scope, objective) {
29
+ function readMemoryRequestInput(value, scope, objective, operation) {
30
30
  if (!exact(value, ["schema_id", "scope", "intake_base64", "intake_sha256", "selection_base64", "selection_sha256"])
31
- || !object(value) || value.schema_id !== "dd-flow/memory-init-request-input@1"
31
+ || !object(value) || value.schema_id !== `dd-flow/${operation}-request-input@1`
32
32
  || hybridDigest(value.scope) !== hybridDigest(scope))
33
33
  refuse("invalid_input_scope");
34
34
  const intake = decode(value.intake_base64, value.intake_sha256, 64 * 1024);
@@ -44,8 +44,10 @@ export function readMemoryInitRequestInput(value, scope, objective) {
44
44
  throw error;
45
45
  return refuse("invalid_selection_json");
46
46
  }
47
- if (!exact(selection, ["schema_id", "scope", "execution", "git_policy", "objective", "engine_checksum", "receipts"])
48
- || !object(selection) || selection.schema_id !== "dd-flow/memory-init-request-selection@1"
47
+ if (!exact(selection, ["schema_id", "scope", "execution", "git_policy", "objective", "engine_checksum", "receipts",
48
+ ...(operation === "memory-upgrade" ? ["target_installation_sha256"] : [])])
49
+ || !object(selection) || selection.schema_id !== `dd-flow/${operation}-request-selection@1`
50
+ || operation === "memory-upgrade" && !digest(selection.target_installation_sha256)
49
51
  || hybridDigest(selection.scope) !== hybridDigest(scope) || selection.objective !== objective
50
52
  || !object(selection.execution) || !exact(selection.git_policy, ["route", "integrationBranch"])
51
53
  || !object(selection.git_policy) || !["integration_branch_direct", "feature_worktree"].includes(String(selection.git_policy.route))
@@ -55,12 +57,26 @@ export function readMemoryInitRequestInput(value, scope, objective) {
55
57
  refuse("invalid_original_selection");
56
58
  return { intake, selectionBytes, selection: selection };
57
59
  }
58
- export function prepareMemoryInitRequestInput(scope, selection, intake, objective) {
60
+ function prepareMemoryRequestInput(scope, selection, intake, objective, operation) {
59
61
  if (!selection.byteLength || selection.byteLength > 256 * 1024 || !intake.byteLength || intake.byteLength > 64 * 1024)
60
62
  refuse("unbounded_original_input");
61
- const value = { schema_id: "dd-flow/memory-init-request-input@1", scope: structuredClone(scope),
63
+ const value = { schema_id: `dd-flow/${operation}-request-input@1`, scope: structuredClone(scope),
62
64
  intake_base64: Buffer.from(intake).toString("base64"), intake_sha256: sha(intake),
63
65
  selection_base64: Buffer.from(selection).toString("base64"), selection_sha256: sha(selection) };
64
- readMemoryInitRequestInput(value, scope, objective);
66
+ readMemoryRequestInput(value, scope, objective, operation);
65
67
  return value;
66
68
  }
69
+ export function readMemoryInitRequestInput(value, scope, objective) {
70
+ const read = readMemoryRequestInput(value, scope, objective, "memory-init");
71
+ return { ...read, selection: read.selection };
72
+ }
73
+ export function readMemoryUpgradeRequestInput(value, scope, objective) {
74
+ const read = readMemoryRequestInput(value, scope, objective, "memory-upgrade");
75
+ return { ...read, selection: read.selection };
76
+ }
77
+ export function prepareMemoryInitRequestInput(scope, selection, intake, objective) {
78
+ return prepareMemoryRequestInput(scope, selection, intake, objective, "memory-init");
79
+ }
80
+ export function prepareMemoryUpgradeRequestInput(scope, selection, intake, objective) {
81
+ return prepareMemoryRequestInput(scope, selection, intake, objective, "memory-upgrade");
82
+ }
@@ -9,6 +9,9 @@ import { readMemoryInitRunAllocation } from "./memory-init-run-allocation.js";
9
9
  import { readMemoryInitProjectBootstrap } from "./memory-init-project-bootstrap.js";
10
10
  import { bundledHandlerContext } from "./stage-handlers.js";
11
11
  import { hybridDigest } from "./hybrid-contracts.js";
12
+ import { readMemoryUpgradeBootstrapReadiness } from "./memory-upgrade-bootstrap-readiness.js";
13
+ import { readUpgradeStorageReadiness } from "./memory-upgrade-storage-readiness.js";
14
+ import { nextDescriptorRunId } from "./descriptor-run-start.js";
12
15
  const bytes = (value) => Buffer.from(JSON.stringify(value));
13
16
  const hash = (value) => createHash("sha256").update(value).digest("hex");
14
17
  function refuse(reason) {
@@ -29,6 +32,48 @@ export function prepareMemoryInitRunBundle(context, input) {
29
32
  if (!ready || !allocation || !bootstrap || allocation.project_id !== ready.projectId
30
33
  || ready.authority.binding.execution_sha256 !== input.execution.sha256)
31
34
  refuse("bootstrap_selection_changed");
35
+ input.source.revalidate();
36
+ const flow = input.source.resolveRegisteredFlow(), canon = input.source.canonProvenance();
37
+ const release = JSON.parse(Buffer.from(ready.authority.binding.installation_manifest_base64, "base64").toString("utf8"));
38
+ if (input.source.installationSha256() !== ready.authority.binding.installation_manifest_sha256
39
+ || flow.sha256 !== snapshot.settings.descriptor_sha256 || snapshot.settings.flow_id !== "mb-init"
40
+ || canon.version !== release.canon.version || canon.commit !== release.canon.commit
41
+ || canon.archive_sha256 !== release.canon.archive.sha256
42
+ || !input.engine.manifestBytes().equals(Buffer.from(ready.authority.binding.engine_manifest_base64, "base64")))
43
+ refuse("frozen_source_changed");
44
+ return prepareMemoryRunBundle(input, ready, { runId: allocation.run_id, boundAt: bootstrap.receipt.created_at,
45
+ flowId: "mb-init", projectPack: { sha256: ready.authority.binding.pack_sha256, source_commit: canon.commit } });
46
+ }
47
+ /** Upgrade executes the target canon but starts with the original project pack.
48
+ * It must not claim that publication has already happened at RUN allocation. */
49
+ export function prepareMemoryUpgradeRunBundle(context, input) {
50
+ if (!(input.source instanceof PreparedInstalledFlowSource) || !(input.execution instanceof FrozenGenericExecution)
51
+ || !(input.engine instanceof AdmittedFrozenEngine))
52
+ refuse("unprepared_selection");
53
+ const snapshot = input.execution.snapshot(), scope = { requestId: snapshot.scope.requestId,
54
+ projectRoot: snapshot.scope.projectRoot, workspaceRoot: snapshot.scope.workspace };
55
+ const ready = readMemoryUpgradeBootstrapReadiness(context, scope), storage = readUpgradeStorageReadiness(context, scope);
56
+ if (!ready || !storage?.selection || !ready.hasFlowCapability || !ready.hasGitPolicy || !ready.hasLintCapability
57
+ || !ready.hasPreservationBaseline || ready.authority.binding.execution_sha256 !== input.execution.sha256
58
+ || ready.authority.binding.selection_sha256 !== storage.selection.sha256)
59
+ refuse("bootstrap_selection_changed");
60
+ const p = storage.selection.payload, canon = input.source.canonProvenance();
61
+ input.source.revalidate();
62
+ if (input.source.sha256 !== p.source.sha256 || input.source.installationSha256() !== p.release.manifest_sha256
63
+ || snapshot.settings.flow_id !== "mb-upgrade" || input.source.resolveRegisteredFlow().sha256 !== snapshot.settings.descriptor_sha256
64
+ || canon.version !== storage.selection.release.canon.version || canon.commit !== storage.selection.release.canon.commit
65
+ || canon.archive_sha256 !== storage.selection.release.canon.archive.sha256
66
+ || !input.engine.manifestBytes().equals(Buffer.from(ready.authority.binding.engine_manifest_base64, "base64")))
67
+ refuse("frozen_source_changed");
68
+ return prepareMemoryRunBundle(input, ready, {
69
+ runId: nextDescriptorRunId(context, { projectRoot: scope.projectRoot, requestId: scope.requestId, slug: "mb-upgrade" }),
70
+ boundAt: ready.createdAt, flowId: "mb-upgrade",
71
+ projectPack: { sha256: ready.authority.receipt.project_current.pack_sha256, source_commit: p.current.source_commit }
72
+ });
73
+ }
74
+ function prepareMemoryRunBundle(input, ready, selected) {
75
+ const snapshot = input.execution.snapshot(), scope = { requestId: snapshot.scope.requestId,
76
+ projectRoot: snapshot.scope.projectRoot, workspaceRoot: snapshot.scope.workspace };
32
77
  const { bootstrapIdentity, receipts: bootstrapReceipts } = ready.admission;
33
78
  if (!bootstrapIdentity || !bootstrapReceipts)
34
79
  refuse("bootstrap_admission_missing");
@@ -43,22 +88,14 @@ export function prepareMemoryInitRunBundle(context, input) {
43
88
  refuse("execution_receipt_collision");
44
89
  receipts.set(reference.sha256, structuredClone(fact));
45
90
  }
46
- input.source.revalidate();
47
91
  const flow = input.source.resolveRegisteredFlow(), canon = input.source.canonProvenance();
48
- const release = JSON.parse(Buffer.from(ready.authority.binding.installation_manifest_base64, "base64").toString("utf8"));
49
- if (input.source.installationSha256() !== ready.authority.binding.installation_manifest_sha256
50
- || flow.sha256 !== snapshot.settings.descriptor_sha256 || snapshot.settings.flow_id !== "mb-init"
51
- || canon.version !== release.canon.version || canon.commit !== release.canon.commit
52
- || canon.archive_sha256 !== release.canon.archive.sha256
53
- || !input.engine.manifestBytes().equals(Buffer.from(ready.authority.binding.engine_manifest_base64, "base64")))
54
- refuse("frozen_source_changed");
55
- const manifest = input.engine.manifest(), runId = allocation.run_id;
92
+ const manifest = input.engine.manifest(), runId = selected.runId;
56
93
  if (typeof manifest.integrity.checksum !== "string")
57
94
  refuse("missing_engine_checksum");
58
95
  // The accepted bootstrap timestamp makes reconstruction stable after a lost
59
96
  // reply. Current time is not a new selector for this request's engine binding.
60
97
  const binding = { schema_id: "dd-flow/run-engine-binding@1", run_id: runId, project_root: scope.projectRoot,
61
- bound_at: bootstrap.receipt.created_at, source: "run_creation", reason: "Request-bound memory init bootstrap",
98
+ bound_at: selected.boundAt, source: "run_creation", reason: selected.flowId === "mb-init" ? "Request-bound memory init bootstrap" : "Request-bound memory upgrade bootstrap",
62
99
  engine: { package_name: manifest.package_name, package_version: manifest.package_version, engine_version: manifest.engine_version,
63
100
  integrity_checksum: manifest.integrity.checksum, snapshot_root: manifest.snapshot_root }, probe: { status: "not_required", command: null } };
64
101
  const blobs = input.source.blobs();
@@ -84,8 +121,8 @@ export function prepareMemoryInitRunBundle(context, input) {
84
121
  for (const evidence of ready.authority.evidence)
85
122
  put(evidence.path, Buffer.from(evidence.base64, "base64"));
86
123
  const receipt = put("receipts/bootstrap.json", bytes(ready.authority.receipt));
87
- const provenance = { canon, project_pack: { sha256: ready.authority.binding.pack_sha256, source_commit: canon.commit } };
88
- const bundle = { schema_id: "dd-flow/execution-bundle@1", run_id: runId, flow_id: "mb-init",
124
+ const provenance = { canon, project_pack: selected.projectPack };
125
+ const bundle = { schema_id: "dd-flow/execution-bundle@1", run_id: runId, flow_id: selected.flowId,
89
126
  descriptor: input.source.descriptorReference(), execution_settings: execution, profile_settings: profile,
90
127
  engine_binding: engineBinding, engine_manifest: engineManifest, bootstrap_receipts: [receipt], ...provenance,
91
128
  inventory: [...blobs].map(([logical, content]) => ({ path: logical, sha256: hash(content) })) };
@@ -18,7 +18,7 @@ const primaryKeys = {
18
18
  };
19
19
  function refuse(reason) { throw new AppError("memory_init_storage_incompatible", "Executing CLI cannot admit memory-init runtime storage without migration or repair", 1, { reason, effect: "no_effect" }); }
20
20
  const normalize = (sql) => sql.replace(/\s+/g, " ").trim().replace(/;$/, "");
21
- function inspect(context) {
21
+ function inspect(context, upgrade = false) {
22
22
  if (!context.db.writable || !path.isAbsolute(context.ddFlowHome) || path.resolve(context.db.path) !== path.join(path.resolve(context.ddFlowHome), "db.sqlite"))
23
23
  refuse("foreign_or_readonly_store");
24
24
  const contract = context.db.all("SELECT id, version FROM dd_flow_writer_contract");
@@ -26,14 +26,15 @@ function inspect(context) {
26
26
  || context.db.get("SELECT dd_flow_writer_version() AS version")?.version !== STORE_WRITER_VERSION)
27
27
  refuse("writer_contract_mismatch");
28
28
  const schema = {};
29
- for (const [table, required] of Object.entries(footprint)) {
29
+ const selectedFootprint = upgrade ? { ...footprint, runtime_config: ["key", "value_json", "created_at", "updated_at"] } : footprint;
30
+ for (const [table, required] of Object.entries(selectedFootprint)) {
30
31
  const columns = context.db.all(`PRAGMA table_info("${table}")`);
31
32
  if (required.some(name => !columns.some(column => column.name === name)))
32
33
  refuse(`missing_operation_column:${table}`);
33
34
  if (required.some(name => columns.find(column => column.name === name)?.type.toUpperCase() !== (table === "audit_events" && ["id", "forced"].includes(name) ? "INTEGER" : "TEXT")))
34
35
  refuse(`operation_column_type_mismatch:${table}`);
35
36
  const key = columns.filter(column => column.pk > 0).sort((a, b) => a.pk - b.pk).map(column => column.name);
36
- if (JSON.stringify(key) !== JSON.stringify(primaryKeys[table]))
37
+ if (JSON.stringify(key) !== JSON.stringify(table === "runtime_config" ? ["key"] : primaryKeys[table]))
37
38
  refuse(`operation_primary_key_mismatch:${table}`);
38
39
  const fences = [];
39
40
  for (const action of ["INSERT", "UPDATE", "DELETE"]) {
@@ -56,14 +57,23 @@ function inspect(context) {
56
57
  export class PreparedMemoryInitRuntimeStorage {
57
58
  sha256;
58
59
  #facts;
59
- constructor(facts) { this.#facts = facts; this.sha256 = hybridDigest(facts); Object.freeze(this); }
60
+ #upgrade;
61
+ constructor(facts, upgrade) { this.#facts = facts; this.#upgrade = upgrade; this.sha256 = hybridDigest(facts); Object.freeze(this); }
60
62
  static prepare(context) {
63
+ return this.prepareFor(context, false);
64
+ }
65
+ /** Upgrade reuses the RUN/Work footprint and additionally needs retained
66
+ * request custody. This remains a non-migrating connection observation. */
67
+ static prepareUpgrade(context) {
68
+ return this.prepareFor(context, true);
69
+ }
70
+ static prepareFor(context, upgrade) {
61
71
  try {
62
72
  return context.db.readSnapshot(() => {
63
- const facts = inspect(context), integrity = context.db.all("PRAGMA quick_check");
73
+ const facts = inspect(context, upgrade), integrity = context.db.all("PRAGMA quick_check");
64
74
  if (integrity.length !== 1 || integrity[0]?.quick_check !== "ok" || context.db.all("PRAGMA foreign_key_check").length)
65
75
  refuse("storage_integrity_failed");
66
- return new PreparedMemoryInitRuntimeStorage(facts);
76
+ return new PreparedMemoryInitRuntimeStorage(facts, upgrade);
67
77
  });
68
78
  }
69
79
  catch (error) {
@@ -75,7 +85,7 @@ export class PreparedMemoryInitRuntimeStorage {
75
85
  facts() { return { ...this.#facts }; }
76
86
  /** Writer-safe metadata fence; expensive integrity scan remains preparation. */
77
87
  assertCompatibility(context) {
78
- if (hybridDigest(inspect(context)) !== this.sha256)
88
+ if (hybridDigest(inspect(context, this.#upgrade)) !== this.sha256)
79
89
  refuse("storage_changed_after_preparation");
80
90
  }
81
91
  }