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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (254) hide show
  1. package/CHANGELOG.md +130 -0
  2. package/README.md +13 -8
  3. package/dist/build-info.json +7 -7
  4. package/dist/cli/command-inputs.js +13 -3
  5. package/dist/cli/command-metadata.js +51 -16
  6. package/dist/cli/diagnostic-invocation.js +46 -0
  7. package/dist/cli/diagnostic.js +120 -0
  8. package/dist/cli/help.js +67 -1
  9. package/dist/cli/hook-ingress.js +27 -3
  10. package/dist/cli/memory-flow-admission.js +9 -3
  11. package/dist/cli/memory-init-invocation.js +10 -3
  12. package/dist/cli/memory-init.js +77 -68
  13. package/dist/cli/memory-storage-admission.js +3 -2
  14. package/dist/cli/memory-storage-qualification.js +105 -0
  15. package/dist/cli/memory-storage-transition.js +123 -0
  16. package/dist/cli/memory-upgrade.js +189 -0
  17. package/dist/cli/native-hook-ingress.js +14 -4
  18. package/dist/cli/native-hook-preparation.js +55 -0
  19. package/dist/cli/response-file.js +2 -1
  20. package/dist/cli/run-cli.js +79 -40
  21. package/dist/cli/setup.js +2 -1
  22. package/dist/cli.js +18 -0
  23. package/dist/domain/diagnostic-aspects.js +103 -0
  24. package/dist/domain/diagnostic-flows.js +8 -0
  25. package/dist/domain/stage-handler-identities.js +8 -0
  26. package/dist/harness-runtime/bin/dd-agy.mjs +3 -3
  27. package/dist/harness-runtime/bin/dd-grok.mjs +3 -3
  28. package/dist/harness-runtime/bin/dd-zcode.mjs +5 -5
  29. package/dist/harness-runtime/lib/agy-error-boundary.mjs +58 -0
  30. package/dist/harness-runtime/lib/agy-native-inspection.mjs +216 -0
  31. package/dist/harness-runtime/lib/agy-native_pb.js +117 -0
  32. package/dist/harness-runtime/lib/daemon-operations.d.mts +1 -0
  33. package/dist/harness-runtime/lib/daemon-operations.mjs +84 -10
  34. package/dist/harness-runtime/lib/dd-agy-daemon.mjs +366 -52
  35. package/dist/harness-runtime/lib/dd-agy.mjs +17 -3
  36. package/dist/harness-runtime/lib/dd-codex-daemon.mjs +27 -13
  37. package/dist/harness-runtime/lib/dd-codex.mjs +4 -0
  38. package/dist/harness-runtime/lib/dd-droid-daemon.mjs +4 -3
  39. package/dist/harness-runtime/lib/dd-droid.mjs +71 -29
  40. package/dist/harness-runtime/lib/dd-grok-daemon.mjs +73 -16
  41. package/dist/harness-runtime/lib/dd-grok.mjs +6 -3
  42. package/dist/harness-runtime/lib/dd-opencode-daemon.mjs +66 -20
  43. package/dist/harness-runtime/lib/dd-opencode.mjs +30 -3
  44. package/dist/harness-runtime/lib/dd-zcode-daemon.mjs +7 -6
  45. package/dist/harness-runtime/lib/dd-zcode.mjs +44 -14
  46. package/dist/harness-runtime/lib/delegation-instructions.mjs +1 -1
  47. package/dist/harness-runtime/lib/driver-recovery.mjs +9 -8
  48. package/dist/harness-runtime/lib/entity-references.d.mts +2 -0
  49. package/dist/harness-runtime/lib/entity-references.mjs +9 -0
  50. package/dist/harness-runtime/lib/managed-daemon.d.mts +1 -1
  51. package/dist/harness-runtime/lib/managed-daemon.mjs +24 -9
  52. package/dist/harness-runtime/lib/native-children.d.mts +8 -1
  53. package/dist/harness-runtime/lib/native-children.mjs +114 -8
  54. package/dist/harness-runtime/lib/native-hook-command.mjs +4 -4
  55. package/dist/harness-runtime/lib/native-request-proof.mjs +1 -1
  56. package/dist/harness-runtime/lib/observation-clock.d.mts +2 -2
  57. package/dist/harness-runtime/lib/observation-clock.mjs +7 -4
  58. package/dist/harness-runtime/lib/opencode-plugin.mjs +3 -3
  59. package/dist/harness-runtime/lib/operation-errors.mjs +1 -1
  60. package/dist/harness-runtime/lib/provider-overload-policy.d.mts +5 -3
  61. package/dist/harness-runtime/lib/provider-overload-policy.mjs +20 -7
  62. package/dist/harness-runtime/lib/runner-events.mjs +3 -1
  63. package/dist/harness-runtime/lib/setup-progress.mjs +8 -6
  64. package/dist/harness-runtime/lib/tool-observations.mjs +1 -1
  65. package/dist/schemas/diagnostic-stage-result.schema.json +354 -0
  66. package/dist/schemas/diagnostic-work-result.schema.json +296 -0
  67. package/dist/schemas/execution-bundle.schema.json +7 -1
  68. package/dist/schemas/mb-upgrade-stage-result.schema.json +863 -0
  69. package/dist/schemas/memory-repair-work-result.schema.json +9 -2
  70. package/dist/schemas/project-flow-pack-manifest.schema.json +1 -1
  71. package/dist/schemas/release-impact.schema.json +3 -3
  72. package/dist/schemas/stage-start-response.schema.json +4 -0
  73. package/dist/services/accepted-intent.js +13 -1
  74. package/dist/services/canon.js +43 -7
  75. package/dist/services/check-environment.js +21 -0
  76. package/dist/services/check-runner-evidence.js +267 -15
  77. package/dist/services/cli-operation-classifier.js +1 -1
  78. package/dist/services/cli-upgrade-allowlist.js +1 -1
  79. package/dist/services/code-checks.js +34 -19
  80. package/dist/services/compatibility-preflight.js +5 -2
  81. package/dist/services/controller-execution.js +10 -4
  82. package/dist/services/controller-fanout.js +34 -10
  83. package/dist/services/controller-memory-task.js +45 -0
  84. package/dist/services/controller-memory-worker.js +49 -0
  85. package/dist/services/dashboard.js +2 -2
  86. package/dist/services/descriptor-run-settlement.js +5 -3
  87. package/dist/services/descriptor-run-start.js +46 -20
  88. package/dist/services/descriptor-stage-coordinator.js +66 -13
  89. package/dist/services/descriptor-stage-settlement.js +2 -1
  90. package/dist/services/descriptor-stage-start.js +40 -4
  91. package/dist/services/diagnostic-evidence.js +73 -0
  92. package/dist/services/diagnostic-report-publication.js +72 -0
  93. package/dist/services/diagnostic-report.js +67 -0
  94. package/dist/services/diagnostic-run-bundle.js +74 -0
  95. package/dist/services/diagnostic-snapshot.js +159 -0
  96. package/dist/services/diagnostic-stage-handlers.js +108 -0
  97. package/dist/services/diagnostic-stage-results.js +132 -0
  98. package/dist/services/diagnostic-start.js +84 -0
  99. package/dist/services/diagnostic-work-batch.js +34 -0
  100. package/dist/services/diagnostic-work-collection.js +97 -0
  101. package/dist/services/diagnostic-work-registration.js +101 -0
  102. package/dist/services/diagnostic-work-results.js +93 -0
  103. package/dist/services/engines.js +64 -14
  104. package/dist/services/eval-snapshots.js +186 -119
  105. package/dist/services/execution-bundles.js +19 -7
  106. package/dist/services/external-work-launch.js +5 -3
  107. package/dist/services/flow-catalog.js +13 -4
  108. package/dist/services/frozen-engine.js +148 -11
  109. package/dist/services/hook-memory-routing.js +188 -0
  110. package/dist/services/hook-recovery-guard.js +14 -0
  111. package/dist/services/hook-session-observation.js +24 -0
  112. package/dist/services/hooks.js +27 -8
  113. package/dist/services/hybrid-contracts.js +4 -13
  114. package/dist/services/hybrid-digest.js +14 -0
  115. package/dist/services/installed-flow-source.js +47 -4
  116. package/dist/services/lifecycle-invocations.js +179 -38
  117. package/dist/services/memory-accepted-composition.js +5 -4
  118. package/dist/services/memory-completed-correction-wave.js +23 -15
  119. package/dist/services/memory-compose-handler.js +163 -21
  120. package/dist/services/memory-composition-acceptance.js +186 -10
  121. package/dist/services/memory-composition-graph.js +42 -11
  122. package/dist/services/memory-composition-registration.js +23 -8
  123. package/dist/services/memory-composition-work-custody.js +14 -9
  124. package/dist/services/memory-composition-work-results.js +15 -3
  125. package/dist/services/memory-corrected-review-batch.js +10 -3
  126. package/dist/services/memory-corrected-review-findings.js +4 -2
  127. package/dist/services/memory-corrected-review-inputs.js +10 -5
  128. package/dist/services/memory-corrected-review-receipt.js +17 -9
  129. package/dist/services/memory-corrected-review-registration.js +3 -2
  130. package/dist/services/memory-correction-check-receipt.js +20 -4
  131. package/dist/services/memory-custody-snapshot.js +36 -0
  132. package/dist/services/memory-finish-evidence.js +44 -22
  133. package/dist/services/memory-finish-handler.js +37 -23
  134. package/dist/services/memory-finish-integration.js +218 -48
  135. package/dist/services/memory-finish-readiness.js +66 -19
  136. package/dist/services/memory-git-integration.js +87 -18
  137. package/dist/services/memory-historical-corrected-review.js +21 -15
  138. package/dist/services/memory-historical-original-review.js +22 -9
  139. package/dist/services/memory-historical-repair-registration.js +14 -8
  140. package/dist/services/memory-historical-second-review.js +30 -11
  141. package/dist/services/memory-init-contexts.js +44 -2
  142. package/dist/services/memory-init-controller-start.js +27 -8
  143. package/dist/services/memory-init-git-policy.js +50 -21
  144. package/dist/services/memory-init-lint-capability.js +24 -6
  145. package/dist/services/memory-init-native-capacity.js +17 -4
  146. package/dist/services/memory-init-preflight.js +21 -17
  147. package/dist/services/memory-init-request-candidate.js +73 -17
  148. package/dist/services/memory-init-request-input.js +23 -7
  149. package/dist/services/memory-init-run-bundle.js +49 -12
  150. package/dist/services/memory-init-storage.js +17 -7
  151. package/dist/services/memory-init-worktrunk-capability.js +3 -7
  152. package/dist/services/memory-layer.js +71 -6
  153. package/dist/services/memory-lint-config.js +111 -2
  154. package/dist/services/memory-native-review-copy.js +5 -67
  155. package/dist/services/memory-normal-flow-entry.js +12 -0
  156. package/dist/services/memory-ordinary-defaults.js +22 -5
  157. package/dist/services/memory-preflight-handler.js +15 -0
  158. package/dist/services/memory-repair-current-view.js +1 -1
  159. package/dist/services/memory-repair-document-delta.js +70 -33
  160. package/dist/services/memory-repair-history.js +12 -7
  161. package/dist/services/memory-repair-physical-binding.js +1 -119
  162. package/dist/services/memory-repair-publication-plan.js +5 -3
  163. package/dist/services/memory-repair-publication-source.js +1 -1
  164. package/dist/services/memory-repair-publication.js +3 -2
  165. package/dist/services/memory-repair-registration.js +2 -1
  166. package/dist/services/memory-repair-work-baseline.js +4 -2
  167. package/dist/services/memory-repair-work-completion.js +32 -12
  168. package/dist/services/memory-repair-work-results.js +21 -2
  169. package/dist/services/memory-repair-workspace.js +11 -2
  170. package/dist/services/memory-repair-writer-copy.js +2 -1
  171. package/dist/services/memory-result-binding.js +78 -12
  172. package/dist/services/memory-retained-review-authority.js +17 -8
  173. package/dist/services/memory-retained-second-baseline.js +1 -1
  174. package/dist/services/memory-retained-second-repairs.js +12 -6
  175. package/dist/services/memory-review-correction-plan.js +47 -12
  176. package/dist/services/memory-review-handler.js +17 -17
  177. package/dist/services/memory-review-inputs.js +55 -9
  178. package/dist/services/memory-review-limits.js +10 -0
  179. package/dist/services/memory-review-receipt.js +2 -2
  180. package/dist/services/memory-review-registration.js +13 -4
  181. package/dist/services/memory-review-work-custody.js +3 -1
  182. package/dist/services/memory-review-work-input.js +26 -7
  183. package/dist/services/memory-review-work-results.js +26 -4
  184. package/dist/services/memory-second-corrected-review-inputs.js +5 -3
  185. package/dist/services/memory-second-correction-plan.js +2 -1
  186. package/dist/services/memory-second-repair-launch.js +4 -4
  187. package/dist/services/memory-second-repair-source.js +1 -1
  188. package/dist/services/memory-stage-dispatch.js +38 -1
  189. package/dist/services/memory-stage-results.js +113 -22
  190. package/dist/services/memory-tool-probe.js +25 -0
  191. package/dist/services/memory-upgrade-analysis-handler.js +243 -0
  192. package/dist/services/memory-upgrade-bootstrap-readiness.js +251 -0
  193. package/dist/services/memory-upgrade-evidence.js +100 -0
  194. package/dist/services/memory-upgrade-inventory.js +442 -0
  195. package/dist/services/memory-upgrade-publication-custody.js +73 -0
  196. package/dist/services/memory-upgrade-publication.js +413 -0
  197. package/dist/services/memory-upgrade-selection.js +430 -0
  198. package/dist/services/memory-upgrade-start.js +60 -0
  199. package/dist/services/memory-upgrade-storage-readiness.js +340 -0
  200. package/dist/services/memory-verification-acceptance.js +38 -17
  201. package/dist/services/memory-verification-checks.js +92 -27
  202. package/dist/services/memory-verification-receipt.js +6 -5
  203. package/dist/services/memory-verify-handler.js +15 -13
  204. package/dist/services/memory-work-result-json.js +12 -0
  205. package/dist/services/memory-work-result-preparation.js +5 -4
  206. package/dist/services/merge-server.js +4 -2
  207. package/dist/services/migrations.js +127 -98
  208. package/dist/services/native-daemon-history.js +2 -1
  209. package/dist/services/physical-startup.js +45 -0
  210. package/dist/services/project-registry.js +97 -0
  211. package/dist/services/project-summary.js +1 -1
  212. package/dist/services/projects.js +3 -93
  213. package/dist/services/prompt-delivery.js +71 -0
  214. package/dist/services/provider-capacity.js +112 -46
  215. package/dist/services/recovery-snapshot-database.js +32 -4
  216. package/dist/services/release-cache.js +8 -0
  217. package/dist/services/repair-intents.js +1 -12
  218. package/dist/services/repair-receipt.js +13 -0
  219. package/dist/services/run-control-worker.js +35 -18
  220. package/dist/services/run-control.js +2 -1
  221. package/dist/services/run-controller-adapter.js +2 -1
  222. package/dist/services/run-controller-process.js +4 -7
  223. package/dist/services/run-controller-recovery.js +8 -2
  224. package/dist/services/run-controller.js +215 -94
  225. package/dist/services/run-fork.js +1 -1
  226. package/dist/services/run-recovery-guard.js +30 -0
  227. package/dist/services/run-recovery.js +11 -44
  228. package/dist/services/run-start-custody.js +17 -5
  229. package/dist/services/runs.js +6 -1
  230. package/dist/services/runtime-scope-resume.js +12 -6
  231. package/dist/services/runtime-scope-stop.js +3 -2
  232. package/dist/services/runtime-scope-worker.js +46 -19
  233. package/dist/services/runtime-service.js +2 -1
  234. package/dist/services/sessions.js +3 -24
  235. package/dist/services/setup.js +63 -13
  236. package/dist/services/stage-handlers.js +22 -7
  237. package/dist/services/stage-pause.js +111 -44
  238. package/dist/services/version-status.js +1 -1
  239. package/dist/services/vnext-code.js +2 -2
  240. package/dist/services/vnext-fanout.js +51 -9
  241. package/dist/services/vnext-plan-review.js +3 -5
  242. package/dist/services/work-registry.js +117 -39
  243. package/dist/services/workspace-bootstrap.js +2 -1
  244. package/dist/services/workspace-files.js +1 -1
  245. package/dist/shared/entity-references.js +1 -8
  246. package/dist/storage/data-preservation.js +65 -0
  247. package/dist/storage/database.js +53 -19
  248. package/dist/storage/import-guard.js +84 -27
  249. package/dist/storage/memory-upgrade-schema-bootstrap.js +38 -0
  250. package/dist/storage/paired-writer-migration.js +424 -0
  251. package/dist/storage/writer-contract.js +11 -4
  252. package/dist/storage/writer-migration.js +313 -52
  253. package/dist/storage/writer-restore.js +217 -0
  254. package/package.json +13 -6
package/dist/cli/help.js CHANGED
@@ -2,6 +2,70 @@ import { AppError } from "../shared/errors.js";
2
2
  import { commandInputs, commandAliases } from "./command-inputs.js";
3
3
  import { commandKeyForPositions, globalOptionDocumentation, optionsForCommandTree, routerNativeRoute } from "./command-metadata.js";
4
4
  const helpPages = new Map([
5
+ ["flow mb-audit", `dd-flow flow mb-audit - read-only Memory Bank audit
6
+
7
+ Usage:
8
+ dd-flow flow mb-audit --project-root /absolute/project --request-id ID --intake-file /absolute/request.txt [--profile ID] --json
9
+
10
+ Describe quick/full or focused audit scope in the original intake. Stages are
11
+ preflight → audit → review → report. Applicable aspects use assigned read-only
12
+ Work and independent critics. Missing evidence and active DEFs remain visible.
13
+ Reports and runtime state live in DD_FLOW_HOME; project files are not repaired.
14
+ The snapshot includes selected dirty/untracked source, excluding private inputs.
15
+
16
+ Profiles, engine, canon and objective are frozen before dispatch. Exact replay
17
+ may omit intake/profile selectors; supplied selectors must match the original.
18
+ --prepare-only creates the snapshot and RUN without Sessions; --detach returns
19
+ the controller receipt. Neither is a dry run. Default execution is managed,
20
+ with fresh stage Sessions and external Work (concurrency 1).
21
+ Use existing run drive status and run control commands for observation/recovery.`],
22
+ ["flow project-review", `dd-flow review / flow project-review - read-only project-conformance review
23
+
24
+ Usage:
25
+ dd-flow review --project-root /absolute/project --request-id ID --intake-file /absolute/request.txt [--profile ID] --json
26
+
27
+ Both spellings share validation and effects. Describe the review focus and
28
+ constraints in the original intake. Stages are preflight → analyze → review →
29
+ report. Compare specs, features, ADRs, scenarios and policy with actual code and
30
+ evidence. This is not SDLC CODE-REVIEW and never implicitly repairs the project.
31
+ All baseline aspects retain applicability and coverage; independent criticism,
32
+ rejected findings and uncertainty remain in runtime evidence.
33
+
34
+ Execution uses an isolated read-only snapshot and frozen global canon. No local
35
+ Memory Bank installation is required. Profiles and the original request remain
36
+ pinned on replay. --prepare-only creates runtime state without Sessions;
37
+ --detach returns the accepted controller receipt. Default execution is managed,
38
+ with fresh stage Sessions and external Work. Use run drive status / run control
39
+ to observe, answer, stop or reconcile the exact retained RUN.`],
40
+ ["runtime storage qualify", `dd-flow runtime storage qualify - pre-probe selected-engine capability
41
+
42
+ --operation memory-upgrade checks existing storage with the executing engine.
43
+ Compatible stores use ordinary operation admission. Otherwise, drained source
44
+ stores are copied with committed WAL to a private temporary directory; actual
45
+ migration and target operation admission run only on those copies.
46
+
47
+ Requires explicit canonical DD_FLOW_HOME and DD_FLOW_RESOURCE_HOME. The result
48
+ is capability only: it does not authorize writes, create a RUN, publish migration
49
+ evidence for real stores or start a model. Original storage is never migrated.
50
+ Public upgrade still requires a separately retained plan and explicit apply.`],
51
+ ["runtime storage transition", `dd-flow runtime storage transition - selected-engine pre-context storage transport
52
+
53
+ Normal project entry remains dd-flow upgrade. This transport executes only in
54
+ the immutable entrypoint selected by the original retained upgrade request.
55
+ It requires explicit DD_FLOW_HOME and DD_FLOW_RESOURCE_HOME and never initializes
56
+ storage, chooses another engine, starts a model or allocates a RUN.
57
+
58
+ --action plan retains both store snapshots and returns exact apply/status commands.
59
+ apply requires the retained --expected-sha256 and --offline; both backups are
60
+ verified before the first schema change. Unknown effects require exact-request
61
+ status/reconciliation, not a new selector or replacement plan.
62
+
63
+ recovery-plan requires the migration SHA, --recovery-id and --offline. Selecting
64
+ recovery fences both stores and aborts the old apply; recover and recovery-status
65
+ use the returned recovery SHA. Restoration is never automatic.
66
+
67
+ The offline switch acknowledges external drain; logical and physical registered
68
+ owners are checked independently. No owner is terminated by this transport.`],
5
69
  ["flow mb-init", `dd-flow init / flow mb-init - managed project Memory Bank initialization
6
70
 
7
71
  Usage:
@@ -332,7 +396,7 @@ Examples:
332
396
  ],
333
397
  [
334
398
  "canon",
335
- `dd-flow canon - register and resolve the canonical Memory Bank checkout
399
+ `dd-flow canon - register and resolve the canonical Memory Bank source
336
400
 
337
401
  Usage:
338
402
  dd-flow canon register --root <canonical-dd-memorybank-root> --json
@@ -341,6 +405,8 @@ Usage:
341
405
 
342
406
  Discovery order is explicit --root, then DD_MEMORYBANK, then the globally registered runtime value in DD_FLOW_HOME, then known nearby dd-memorybank checkouts from the current working directory. Explicit --root is the only source considered for that command. If multiple different valid canonical checkouts are found, resolve fails closed with blockers so the caller chooses intentionally.
343
407
 
408
+ Without an explicit, environment or registered source, the release installed by setup in DD_FLOW_HOME takes precedence over nearby checkouts. Its commit comes from the retained release manifest, not ancestor Git history. A degraded installation is reported; it never silently falls back to a nearby checkout.
409
+
344
410
  A valid canonical root contains dd-flow/README.md, mbb/index.md, protocol/index.md, and canonical-only entrypoints such as dd-flow/mb-init.md, dd-flow/mb-upgrade.md, and dd-flow/mb-distill.md. Current canon runs mb-upgrade review as the 05-review stage inside mb-upgrade, not as a top-level mb-upgrade-review.md entrypoint.
345
411
 
346
412
  Examples:
@@ -1,10 +1,11 @@
1
1
  import { fileURLToPath } from "node:url";
2
2
  import { performance } from "node:perf_hooks";
3
+ import { channel } from "node:diagnostics_channel";
3
4
  import { parseCommandArgs, parseLifecycleCommand, lifecycleCommandFromPayload, continuationCommand } from "../services/lifecycle-command.js";
4
5
  import { errorRecord, AppError } from "../shared/errors.js";
5
6
  import { parseJsonObject } from "../shared/json.js";
6
7
  const workerFile = fileURLToPath(import.meta.url);
7
- const ingressBudgetMs = 20_000; // Native host timeout is 30s; this includes cold module/DB open.
8
+ const ingressBudgetMs = 20_000; // Shared admission budget; host separately allows cold CLI startup/cleanup.
8
9
  const { hookBudget, readNativeHookInput } = await import("../harness-runtime/lib/native-hook-command.mjs");
9
10
  /** Lightweight entry: unrelated hooks never load the router or open storage. */
10
11
  export async function runHookIngress(args) {
@@ -46,15 +47,32 @@ export async function runHookIngress(args) {
46
47
  }
47
48
  // A bounded CLI subprocess, not a detached writer: its successful reply follows COMMIT.
48
49
  if (process.argv[1] === workerFile) {
50
+ const started = performance.now();
51
+ const timing = channel("dd-flow.sqlite.ingress");
52
+ let previousPhase;
53
+ const trace = (value) => {
54
+ const record = value;
55
+ if (record.phase === previousPhase)
56
+ return;
57
+ previousPhase = record.phase;
58
+ process.stderr.write(`${JSON.stringify({ kind: "native_hook_phase", phase: record.phase, elapsed_ms: Math.round(performance.now() - started), ...(record.remaining_ms !== undefined ? { remaining_ms: record.remaining_ms } : {}) })}\n`);
59
+ };
60
+ timing.subscribe(trace);
49
61
  try {
50
- const stdin = await readNativeHookInput(process.stdin, hookBudget(ingressBudgetMs, process.env.DD_FLOW_HOOK_DEADLINE_AT ? Number(process.env.DD_FLOW_HOOK_DEADLINE_AT) : null));
62
+ const budget = hookBudget(ingressBudgetMs, process.env.DD_FLOW_HOOK_DEADLINE_AT ? Number(process.env.DD_FLOW_HOOK_DEADLINE_AT) : null);
63
+ const phase = (name) => trace({ phase: name, remaining_ms: budget.remaining(name) });
64
+ phase("input");
65
+ const stdin = await readNativeHookInput(process.stdin, budget);
51
66
  const payload = parseJsonObject(stdin, "native hook stdin");
52
- const started = performance.now();
67
+ phase("verification");
53
68
  const { verifyCodexHookDelivery } = await import("../services/codex-hook-delivery.js");
54
69
  const proof = await verifyCodexHookDelivery(process.env, payload);
55
70
  const verified = performance.now();
71
+ phase("import");
56
72
  const [{ createContext }, { handleCodexHook }] = await Promise.all([import("../runtime/context.js"), import("../services/hooks.js")]);
73
+ phase("imported");
57
74
  const loaded = performance.now();
75
+ phase("store_open");
58
76
  const context = createContext(process.env, "hook");
59
77
  const opened = performance.now();
60
78
  try {
@@ -62,7 +80,10 @@ if (process.argv[1] === workerFile) {
62
80
  context.codexHookProof = proof;
63
81
  const parsed = parseCommandArgs(process.argv.slice(5));
64
82
  const projectRoot = parsed.options.get("project-root")?.[0];
83
+ phase("receipt");
65
84
  const result = handleCodexHook(context, { event: "PreToolUse", ...(projectRoot ? { projectRoot } : {}), stdin });
85
+ phase("committed");
86
+ phase("ack");
66
87
  process.stdout.write(`${JSON.stringify({ ...(result.hookSpecificOutput ? { hookSpecificOutput: result.hookSpecificOutput } : {}), timing: {
67
88
  verify_ms: Math.round(verified - started), load_ms: Math.round(loaded - verified), open_ms: Math.round(opened - loaded), receipt_ms: Math.round(performance.now() - opened)
68
89
  } })}\n`);
@@ -75,4 +96,7 @@ if (process.argv[1] === workerFile) {
75
96
  process.stderr.write(`${JSON.stringify({ ok: false, error: errorRecord(error) })}\n`);
76
97
  process.exitCode = 1;
77
98
  }
99
+ finally {
100
+ timing.unsubscribe(trace);
101
+ }
78
102
  }
@@ -6,6 +6,7 @@ import { hybridDigest } from "../services/hybrid-contracts.js";
6
6
  import { AppError, errorRecord } from "../shared/errors.js";
7
7
  import { writePreparationError } from "./error-output.js";
8
8
  import { memoryInitStartupInputSchemas } from "../services/memory-init-startup-custody.js";
9
+ import { isDiagnosticFlow } from "../domain/diagnostic-flows.js";
9
10
  /** Executing engine's real handler admission only. The caller owns independent
10
11
  * release/source selection; this command never opens storage or creates a RUN. */
11
12
  export async function runMemoryFlowAdmissionCli(args, io = process) {
@@ -20,8 +21,10 @@ export async function runMemoryFlowAdmissionCli(args, io = process) {
20
21
  throw new AppError("usage", "Flow admission requires an exact source snapshot and independent digest", 2, { effect: "no_effect" });
21
22
  }
22
23
  const selected = input;
24
+ const operation = route.parsed.options.get("operation")?.[0] ?? "";
25
+ const flowId = isDiagnosticFlow(operation) ? operation : operation === "memory-upgrade" ? "mb-upgrade" : "mb-init";
23
26
  const startupSchema = memoryInitStartupInputSchemas.find(schema => schema === selected.startup_input_schema);
24
- if (Object.hasOwn(selected, "startup_input_schema") && startupSchema === undefined) {
27
+ if (Object.hasOwn(selected, "startup_input_schema") && (startupSchema === undefined || flowId !== "mb-init")) {
25
28
  throw new AppError("usage", "Executing engine does not support the requested startup input schema", 2, { effect: "no_effect" });
26
29
  }
27
30
  if (typeof selected.source_sha256 !== "string" || !/^[a-f0-9]{64}$/.test(selected.source_sha256)) {
@@ -29,9 +32,12 @@ export async function runMemoryFlowAdmissionCli(args, io = process) {
29
32
  }
30
33
  const source = PreparedInstalledFlowSource.restoreRetained(selected.source, selected.source_sha256);
31
34
  const flow = source.resolveRegisteredFlow();
32
- const facts = { schema_id: startupSchema === undefined ? "dd-flow/memory-init-engine-flow@1" : "dd-flow/memory-init-engine-flow@2",
35
+ if (flow.descriptor.id !== flowId)
36
+ throw new AppError("usage", "Flow source does not match the requested engine admission operation", 2, { effect: "no_effect" });
37
+ const facts = { schema_id: isDiagnosticFlow(flowId) ? "dd-flow/diagnostic-engine-flow@1" : flowId === "mb-upgrade" ? "dd-flow/memory-upgrade-engine-flow@1"
38
+ : startupSchema === undefined ? "dd-flow/memory-init-engine-flow@1" : "dd-flow/memory-init-engine-flow@2",
33
39
  ...(startupSchema === undefined ? {} : { startup_input_schema: startupSchema }),
34
- flow_id: "mb-init", source_sha256: source.sha256,
40
+ flow_id: flowId, source_sha256: source.sha256,
35
41
  descriptor_sha256: source.descriptorReference().sha256, stages: flow.descriptor.stages.map(stage => stage.id) };
36
42
  io.stdout.write(`${JSON.stringify({ ok: true, action: "runtime_flow_admit", facts, sha256: hybridDigest(facts) }, null, output.json ? undefined : 2)}\n`);
37
43
  return 0;
@@ -3,15 +3,22 @@ import { AppError } from "../shared/errors.js";
3
3
  import { hybridDigest } from "../services/hybrid-contracts.js";
4
4
  import { prepareCommandRoute } from "./command-inputs.js";
5
5
  const stages = new Set(["preflight", "discover", "compose", "verify", "review", "finish"]);
6
+ const upgradeStages = new Set(["preflight", "diff-analysis", "upgrade", "verify", "review", "finish"]);
6
7
  function refuse(message) {
7
8
  throw new AppError("usage", message, 2, { phase: "prepare", effect: "no_effect" });
8
9
  }
9
10
  /** Pure invocation preparation. File bytes, stored replay and productive
10
11
  * authority remain with their existing owners; no ambient defaults here. */
11
12
  export function prepareMemoryInitInvocation(args) {
13
+ return prepareMemoryInvocation(args, "mb-init");
14
+ }
15
+ export function prepareMemoryUpgradeInvocation(args) {
16
+ return prepareMemoryInvocation(args, "mb-upgrade");
17
+ }
18
+ function prepareMemoryInvocation(args, flowId) {
12
19
  const route = prepareCommandRoute(args);
13
- if (!["init", "flow mb-init"].includes(route.key))
14
- refuse("Use init or flow mb-init");
20
+ if (!(flowId === "mb-init" ? ["init", "flow mb-init"] : ["upgrade", "flow mb-upgrade"]).includes(route.key))
21
+ refuse(`Use ${flowId === "mb-init" ? "init" : "upgrade"} or flow ${flowId}`);
15
22
  const options = route.parsed.options;
16
23
  const value = (name) => options.get(name)?.[0];
17
24
  const scope = { projectRoot: value("project-root"), workspaceRoot: value("workspace-root"), requestId: value("request-id") };
@@ -38,7 +45,7 @@ export function prepareMemoryInitInvocation(args) {
38
45
  execution.rootProfile = profile;
39
46
  for (const assignment of options.get("stage-profile") ?? []) {
40
47
  const at = assignment.indexOf("="), stage = assignment.slice(0, at), profile = assignment.slice(at + 1);
41
- if (at < 1 || !stages.has(stage) || !profile.trim() || execution.stageProfiles?.[stage] !== undefined)
48
+ if (at < 1 || !(flowId === "mb-init" ? stages : upgradeStages).has(stage) || !profile.trim() || execution.stageProfiles?.[stage] !== undefined)
42
49
  refuse("--stage-profile requires a unique stage=profile assignment");
43
50
  (execution.stageProfiles ??= {})[stage] = profile;
44
51
  }
@@ -13,7 +13,7 @@ import { PreparedMemoryInitLintCapability } from "../services/memory-init-lint-c
13
13
  import { PreparedMemoryInitWorktrunkCapability } from "../services/memory-init-worktrunk-capability.js";
14
14
  import { produceMemoryInitNativeCapacity } from "../services/memory-init-native-capacity.js";
15
15
  import { openMemoryInitStores, assertMemoryInitStorePrerequisites } from "../services/memory-init-stores.js";
16
- import { prepareMemoryInitControllerStart } from "../services/memory-init-controller-start.js";
16
+ import { prepareMemoryInitControllerStart, prepareMemoryUpgradeControllerStart } from "../services/memory-init-controller-start.js";
17
17
  import { runControllerStatus } from "../services/run-controller.js";
18
18
  import { requireRun } from "../services/run-controller-state.js";
19
19
  import { requireProjectByRoot } from "../services/projects.js";
@@ -28,6 +28,8 @@ import { onboardExecutor } from "../services/executor-onboarding.js";
28
28
  import { readOperatorProfiles, readOperatorProfileInput } from "../services/operator-profiles.js";
29
29
  import { resolveDdFlowHome } from "../storage/paths.js";
30
30
  import { askExecutorChoice } from "./executor-onboarding.js";
31
+ import { isDiagnosticFlow } from "../domain/diagnostic-flows.js";
32
+ import { prepareDiagnosticControllerStart } from "../services/memory-init-controller-start.js";
31
33
  /** Backpressure bounds the observing client's output; closing it never controls the RUN. */
32
34
  export async function writeMemoryInitObservation(stdout, text) {
33
35
  const disconnected = () => Boolean(stdout.destroyed);
@@ -159,73 +161,7 @@ export async function runMemoryInitCli(args, env = process.env, io = process) {
159
161
  // Beyond this boundary, absence of a success receipt is not no-effect.
160
162
  effects = true;
161
163
  const started = startMemoryInitRun(context, invocation.scope, prepared ? () => prepared : undefined);
162
- if (invocation.prepareOnly) {
163
- const run = requireRun(context, requireProjectByRoot(context, invocation.scope.projectRoot).id, started.runId);
164
- const response = { ok: true, action: "flow_mb_init_prepared", flow_id: "mb-init", ...started,
165
- run_id: started.runId, request_id: invocation.scope.requestId, run_root: run.run_root,
166
- project_root: run.project_root, workspace_root: run.workspace_root };
167
- await writeMemoryInitObservation(io.stdout, `${JSON.stringify(writeResponseFile(response, output), null, output.json || output.progressJsonl ? undefined : 2)}\n`);
168
- return 0;
169
- }
170
- const launch = prepareMemoryInitControllerStart(context, invocation.scope);
171
- // Use the existing RUN-bound router: a global CLI must not launch its own
172
- // controller code in place of the independently frozen RUN executable.
173
- const receiptChunks = [];
174
- let receiptSize = 0, oversizedReceipt = false;
175
- const capture = new Writable({ write(chunk, _, next) {
176
- receiptSize += Buffer.byteLength(chunk);
177
- if (receiptSize > 1024 * 1024)
178
- oversizedReceipt = true;
179
- else
180
- receiptChunks.push(Buffer.from(chunk));
181
- next();
182
- } });
183
- const routed = await routeArgsThroughEngine(context, ["run", "drive", "launch", "--project-root", launch.input.projectRoot,
184
- "--run", launch.input.runId, "--request-id", launch.input.requestId, "--context-file", launch.input.contextFile,
185
- "--context-sha256", launch.input.contextSha256, "--json"], { stdout: capture, stderr: io.stderr }, undefined, { ...env, DD_FLOW_ENGINE_MODE: undefined }, invocation.scope.projectRoot);
186
- if (!routed?.routed || routed.inProcess || routed.exitCode !== 0 || oversizedReceipt)
187
- throw new AppError("memory_init_controller_launch_unknown", "Observe or reconcile the exact RUN-bound controller launch", 1, { effect: "unknown" });
188
- let receipt;
189
- try {
190
- receipt = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(Buffer.concat(receiptChunks)));
191
- }
192
- catch {
193
- throw new AppError("memory_init_controller_launch_unknown", "Selected engine returned no valid controller receipt", 1, { effect: "unknown" });
194
- }
195
- const accepted = receipt.controller;
196
- if (receipt.ok !== true || !accepted || accepted.run_id !== started.runId || accepted.request_id !== invocation.scope.requestId)
197
- throw new AppError("memory_init_controller_launch_unknown", "Selected engine returned a foreign controller receipt", 1, { effect: "unknown" });
198
- const emit = (result) => writeMemoryInitObservation(io.stdout, `${JSON.stringify(result, null, output.json || output.progressJsonl ? undefined : 2)}\n`);
199
- if (output.progressJsonl && !await emit({ ok: true, action: "flow_accepted", flow_id: "mb-init", ...started, ...receipt }))
200
- return 0;
201
- let result = receipt;
202
- if (!invocation.detach) {
203
- let after = 0;
204
- let recentEvents = [];
205
- for (;;) {
206
- // The client has no ownership of the detached controller process.
207
- if (io.stdout.destroyed)
208
- return 0;
209
- result = runControllerStatus(context, { projectRoot: invocation.scope.projectRoot, runId: started.runId, after });
210
- const events = result.events, controller = result.controller;
211
- after = result.next_cursor;
212
- recentEvents = [...recentEvents, ...events].slice(-100);
213
- if (output.progressJsonl && events.length && !await emit({ ok: true, action: "flow_progress", flow_id: "mb-init", ...result }))
214
- return 0;
215
- if (!controller)
216
- throw new AppError("memory_init_controller_receipt_missing", "Observe or reconcile the retained controller", 1, { effect: "unknown" });
217
- if (["completed", "cancelled", "superseded", "recovery_required", "failed", "waiting_for_user", "waiting_for_context"].includes(controller.status)) {
218
- result = { ...result, events: recentEvents };
219
- break;
220
- }
221
- await delay(250);
222
- }
223
- }
224
- const response = { ...result, action: "flow_mb_init", flow_id: "mb-init", run_id: started.runId, request_id: invocation.scope.requestId };
225
- if (!await emit(writeResponseFile(response, output)))
226
- return 0;
227
- const status = result.controller?.status;
228
- return ["recovery_required", "failed", "cancelled", "superseded"].includes(status ?? "") ? 1 : 0;
164
+ return await launchAndObserveMemoryFlow(context, invocation, started, "mb-init", output, env, io);
229
165
  }
230
166
  catch (error) {
231
167
  const record = errorRecord(error);
@@ -238,3 +174,76 @@ export async function runMemoryInitCli(args, env = process.env, io = process) {
238
174
  context?.db.close?.();
239
175
  }
240
176
  }
177
+ /** Shared foreground observer only; the existing RUN-bound controller owns
178
+ * physical Sessions. Neither alias gains another orchestration lifecycle. */
179
+ export async function launchAndObserveMemoryFlow(context, invocation, started, flowId, output, env, io) {
180
+ const action = `flow_${flowId.replaceAll("-", "_")}`;
181
+ if (invocation.prepareOnly) {
182
+ const run = requireRun(context, requireProjectByRoot(context, invocation.scope.projectRoot).id, started.runId);
183
+ const response = { ok: true, action: `${action}_prepared`, flow_id: flowId, ...started,
184
+ run_id: started.runId, request_id: invocation.scope.requestId, run_root: run.run_root,
185
+ project_root: run.project_root, workspace_root: run.workspace_root };
186
+ await writeMemoryInitObservation(io.stdout, `${JSON.stringify(writeResponseFile(response, output), null, output.json || output.progressJsonl ? undefined : 2)}\n`);
187
+ return 0;
188
+ }
189
+ const launch = isDiagnosticFlow(flowId) ? prepareDiagnosticControllerStart(context, invocation.scope, flowId)
190
+ : (flowId === "mb-init" ? prepareMemoryInitControllerStart : prepareMemoryUpgradeControllerStart)(context, invocation.scope);
191
+ // Use the existing RUN-bound router: a global CLI must not launch its own
192
+ // controller code in place of the independently frozen RUN executable.
193
+ const receiptChunks = [];
194
+ let receiptSize = 0, oversizedReceipt = false;
195
+ const capture = new Writable({ write(chunk, _, next) {
196
+ receiptSize += Buffer.byteLength(chunk);
197
+ if (receiptSize > 1024 * 1024)
198
+ oversizedReceipt = true;
199
+ else
200
+ receiptChunks.push(Buffer.from(chunk));
201
+ next();
202
+ } });
203
+ const routed = await routeArgsThroughEngine(context, ["run", "drive", "launch", "--project-root", launch.input.projectRoot,
204
+ "--run", launch.input.runId, "--request-id", launch.input.requestId, "--context-file", launch.input.contextFile,
205
+ "--context-sha256", launch.input.contextSha256, "--json"], { stdout: capture, stderr: io.stderr }, undefined, { ...env, DD_FLOW_ENGINE_MODE: undefined }, invocation.scope.projectRoot);
206
+ if (!routed?.routed || routed.inProcess || routed.exitCode !== 0 || oversizedReceipt)
207
+ throw new AppError("memory_init_controller_launch_unknown", "Observe or reconcile the exact RUN-bound controller launch", 1, { effect: "unknown" });
208
+ let receipt;
209
+ try {
210
+ receipt = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(Buffer.concat(receiptChunks)));
211
+ }
212
+ catch {
213
+ throw new AppError("memory_init_controller_launch_unknown", "Selected engine returned no valid controller receipt", 1, { effect: "unknown" });
214
+ }
215
+ const accepted = receipt.controller;
216
+ if (receipt.ok !== true || !accepted || accepted.run_id !== started.runId || accepted.request_id !== invocation.scope.requestId)
217
+ throw new AppError("memory_init_controller_launch_unknown", "Selected engine returned a foreign controller receipt", 1, { effect: "unknown" });
218
+ const emit = (result) => writeMemoryInitObservation(io.stdout, `${JSON.stringify(result, null, output.json || output.progressJsonl ? undefined : 2)}\n`);
219
+ if (output.progressJsonl && !await emit({ ok: true, action: "flow_accepted", flow_id: flowId, ...started, ...receipt }))
220
+ return 0;
221
+ let result = receipt;
222
+ if (!invocation.detach) {
223
+ let after = 0;
224
+ let recentEvents = [];
225
+ for (;;) {
226
+ // The client has no ownership of the detached controller process.
227
+ if (io.stdout.destroyed)
228
+ return 0;
229
+ result = runControllerStatus(context, { projectRoot: invocation.scope.projectRoot, runId: started.runId, after });
230
+ const events = result.events, controller = result.controller;
231
+ after = result.next_cursor;
232
+ recentEvents = [...recentEvents, ...events].slice(-100);
233
+ if (output.progressJsonl && events.length && !await emit({ ok: true, action: "flow_progress", flow_id: flowId, ...result }))
234
+ return 0;
235
+ if (!controller)
236
+ throw new AppError("memory_init_controller_receipt_missing", "Observe or reconcile the retained controller", 1, { effect: "unknown" });
237
+ if (["completed", "cancelled", "superseded", "recovery_required", "failed", "waiting_for_user", "waiting_for_context"].includes(controller.status)) {
238
+ result = { ...result, events: recentEvents };
239
+ break;
240
+ }
241
+ await delay(250);
242
+ }
243
+ }
244
+ const response = { ...result, action, flow_id: flowId, run_id: started.runId, request_id: invocation.scope.requestId };
245
+ if (!await emit(writeResponseFile(response, output)))
246
+ return 0;
247
+ const status = result.controller?.status;
248
+ return ["recovery_required", "failed", "cancelled", "superseded"].includes(status ?? "") ? 1 : 0;
249
+ }
@@ -17,6 +17,7 @@ export function runMemoryStorageAdmissionCli(args, env = process.env, io = proce
17
17
  if (route.key !== "runtime storage admit" || output.responseFile || output.progressJsonl) {
18
18
  throw new AppError("usage", "Storage admission accepts stdout output only", 2, { effect: "no_effect" });
19
19
  }
20
+ const operation = route.parsed.options.get("operation")[0];
20
21
  const home = env.DD_FLOW_HOME;
21
22
  if (!home || !path.isAbsolute(home) || path.resolve(home) !== home || fs.realpathSync(home) !== home) {
22
23
  throw new AppError("usage", "Storage admission requires an explicit canonical absolute DD_FLOW_HOME", 2, { effect: "no_effect" });
@@ -27,7 +28,7 @@ export function runMemoryStorageAdmissionCli(args, env = process.env, io = proce
27
28
  const resource = PreparedMemoryInitResourceStorage.prepare({ ddFlowHome: home, env });
28
29
  const context = createContext(env, "prepared_existing");
29
30
  const facts = withStoreCleanup(() => {
30
- const run = PreparedMemoryInitRuntimeStorage.prepare(context);
31
+ const run = operation === "memory-upgrade" ? PreparedMemoryInitRuntimeStorage.prepareUpgrade(context) : PreparedMemoryInitRuntimeStorage.prepare(context);
31
32
  const resourceWriter = getResourceDatabase(path.dirname(resource.facts().store_path), "prepared_existing");
32
33
  return withStoreCleanup(() => {
33
34
  if (resourceWriter.get("SELECT dd_flow_writer_version() AS version")?.version !== resource.facts().executing_writer_version) {
@@ -38,7 +39,7 @@ export function runMemoryStorageAdmissionCli(args, env = process.env, io = proce
38
39
  if (!current.isFile() || current.nlink !== 1 || current.dev !== owner.dev || current.ino !== owner.ino) {
39
40
  throw new AppError("run_storage_changed", "RUN store changed during admission", 1, { effect: "no_effect" });
40
41
  }
41
- return { schema_id: "dd-flow/memory-init-engine-storage@1", operation: "memory-init", route: "verified-no-op", outcome: "no-op",
42
+ return { schema_id: `dd-flow/${operation}-engine-storage@1`, operation, route: "verified-no-op", outcome: "no-op",
42
43
  run: { ...run.facts(), store_dev: owner.dev, store_ino: owner.ino }, resource: resource.facts() };
43
44
  }, () => resourceWriter.close?.());
44
45
  }, () => context.db.close?.());
@@ -0,0 +1,105 @@
1
+ import fs from "node:fs";
2
+ import os from "node:os";
3
+ import path from "node:path";
4
+ import { Writable } from "node:stream";
5
+ import { DatabaseSync } from "node:sqlite";
6
+ import { getDatabase, getResourceDatabase } from "../storage/database.js";
7
+ import { canonicalPath } from "../storage/paths.js";
8
+ import { prepareCommandRoute } from "./command-inputs.js";
9
+ import { parseOutputOptions } from "./output-options.js";
10
+ import { runMemoryStorageAdmissionCli } from "./memory-storage-admission.js";
11
+ import { writePreparationError } from "./error-output.js";
12
+ import { AppError, errorRecord } from "../shared/errors.js";
13
+ import { hybridDigest } from "../services/hybrid-contracts.js";
14
+ import { captureStoreMigrationState } from "../storage/writer-migration.js";
15
+ import { PreparedPairedWriterMigration, observeDrainedMigrationStores } from "../storage/paired-writer-migration.js";
16
+ import { assertImportAdmission } from "../storage/import-guard.js";
17
+ /** Capability only. Exercise the actual target migration/admission on private
18
+ * WAL-aware copies before paying for a native probe. Never grants real-store
19
+ * write admission, productive migration proof or a runtime permit. */
20
+ export function runMemoryStorageQualificationCli(args, env = process.env, io = process) {
21
+ let temporary;
22
+ try {
23
+ const output = parseOutputOptions(args), route = prepareCommandRoute(output.args);
24
+ if (route.key !== "runtime storage qualify" || output.responseFile || output.progressJsonl)
25
+ throw new AppError("usage", "Storage qualification accepts stdout output only", 2, { effect: "no_effect" });
26
+ const home = env.DD_FLOW_HOME, resource = env.DD_FLOW_RESOURCE_HOME;
27
+ for (const root of [home, resource])
28
+ if (!root || !path.isAbsolute(root) || canonicalPath(root, false) !== root)
29
+ throw new AppError("usage", "Qualification requires both explicit canonical storage Homes", 2, { effect: "no_effect" });
30
+ for (const root of new Set([home, resource]))
31
+ assertImportAdmission(root);
32
+ const stores = [{ database: path.join(home, "db.sqlite") }, { database: path.join(resource, "runtime.sqlite") }];
33
+ const admit = (selected) => {
34
+ let stdout = "", stderr = "";
35
+ const collect = (write) => new Writable({ write(chunk, _encoding, done) { write(chunk.toString()); done(); } });
36
+ const code = runMemoryStorageAdmissionCli(["runtime", "storage", "admit", "--operation", "memory-upgrade", "--json"], selected, { stdout: collect(chunk => { stdout += chunk; }), stderr: collect(chunk => { stderr += chunk; }) });
37
+ return { code, stdout, stderr };
38
+ };
39
+ const current = admit(env);
40
+ let selectedRoute = "verified-no-op";
41
+ if (current.code !== 0) {
42
+ // No simulation of a live runtime: the normal admission above remains
43
+ // sufficient for compatible stores; changed stores must already drain.
44
+ temporary = fs.realpathSync(fs.mkdtempSync(path.join(os.tmpdir(), "dd-upgrade-capability-")));
45
+ const runHome = path.join(temporary, "run"), resourceHome = path.join(temporary, "resource");
46
+ fs.mkdirSync(runHome);
47
+ fs.mkdirSync(resourceHome);
48
+ const copies = [path.join(runHome, "db.sqlite"), path.join(resourceHome, "runtime.sqlite")];
49
+ stores.forEach((store, index) => {
50
+ if (!fs.existsSync(store.database)) {
51
+ if (["-wal", "-shm", "-journal"].some(suffix => fs.existsSync(store.database + suffix)))
52
+ throw new AppError("storage_capability_invalid", "Missing source store retains sidecars", 1, { effect: "no_effect" });
53
+ const fresh = index === 0 ? getDatabase(runHome, "create_new") : getResourceDatabase(resourceHome, "create_new");
54
+ fresh.close?.();
55
+ return;
56
+ }
57
+ const owner = fs.lstatSync(store.database);
58
+ if (!owner.isFile() || owner.nlink !== 1)
59
+ throw new AppError("storage_capability_invalid", "Source store must have one regular physical owner", 1, { effect: "no_effect" });
60
+ const reader = new DatabaseSync(store.database, { readOnly: true });
61
+ try {
62
+ reader.exec(`VACUUM INTO '${copies[index].replaceAll("'", "''")}'`);
63
+ const copy = new DatabaseSync(copies[index], { readOnly: true });
64
+ try {
65
+ if (hybridDigest(captureStoreMigrationState(reader)) !== hybridDigest(captureStoreMigrationState(copy)))
66
+ throw new AppError("storage_capability_invalid", "Source changed during private qualification", 1, { effect: "no_effect" });
67
+ }
68
+ finally {
69
+ copy.close();
70
+ }
71
+ const after = fs.lstatSync(store.database);
72
+ if (after.dev !== owner.dev || after.ino !== owner.ino || after.nlink !== 1)
73
+ throw new AppError("storage_capability_invalid", "Source store owner changed", 1, { effect: "no_effect" });
74
+ }
75
+ finally {
76
+ reader.close();
77
+ }
78
+ });
79
+ let checked = admit({ ...env, DD_FLOW_HOME: runHome, DD_FLOW_RESOURCE_HOME: resourceHome });
80
+ if (checked.code !== 0) {
81
+ observeDrainedMigrationStores(stores.filter(store => fs.existsSync(store.database)));
82
+ PreparedPairedWriterMigration.prepare({ requestId: "capability-only", requestFile: path.join(temporary, "plan.json"), projectRoot: temporary,
83
+ workspaceRoot: temporary, runHome, resourceHome, selectionSha256: hybridDigest(stores), bootstrapSchema: "memory-upgrade@1" }).apply();
84
+ checked = admit({ ...env, DD_FLOW_HOME: runHome, DD_FLOW_RESOURCE_HOME: resourceHome });
85
+ selectedRoute = "migration";
86
+ }
87
+ if (checked.code !== 0)
88
+ throw new AppError("storage_capability_invalid", "Target cannot admit its privately migrated copies", 1, { effect: "no_effect", admission: JSON.parse(checked.stderr) });
89
+ }
90
+ const facts = { schema_id: "dd-flow/memory-upgrade-storage-capability@1", operation: "memory-upgrade", route: selectedRoute,
91
+ source_stores: stores.map(store => store.database), productive_admission: false };
92
+ io.stdout.write(`${JSON.stringify({ ok: true, action: "runtime_storage_qualify", facts, sha256: hybridDigest(facts) })}\n`);
93
+ return 0;
94
+ }
95
+ catch (error) {
96
+ const record = errorRecord(error);
97
+ record.details = { ...record.details, effect: "no_effect" };
98
+ writePreparationError(io, args, record);
99
+ return record.exitCode;
100
+ }
101
+ finally {
102
+ if (temporary)
103
+ fs.rmSync(temporary, { recursive: true, force: true });
104
+ }
105
+ }