@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
@@ -5,7 +5,7 @@ import { channel } from "node:diagnostics_channel";
5
5
  import { createRequire } from "node:module";
6
6
  import { ensureDir } from "./paths.js";
7
7
  import { AppError } from "../shared/errors.js";
8
- import { executeStoreSchema, readStoreWriterContract, registerStoreWriter, registerReconciliationWriter, STORE_WRITER_VERSION } from "./writer-contract.js";
8
+ import { executeStoreSchema, readStoreWriterContract, registerStoreWriter, registerReconciliationWriter, bindStoreWriterAdmission, STORE_WRITER_VERSION } from "./writer-contract.js";
9
9
  import { assertImportAdmission } from "./import-guard.js";
10
10
  import { withStoreCleanup, rollbackStoreTransaction } from "./native-cleanup.js";
11
11
  const workContinuationStorageSql = `
@@ -25,7 +25,7 @@ function closeAfterOpenFailure(db, error) {
25
25
  db.close(); });
26
26
  }
27
27
  const require = createRequire(import.meta.url);
28
- const { DatabaseSync } = require("node:sqlite");
28
+ const { DatabaseSync, constants } = require("node:sqlite");
29
29
  const resourceDatabases = new Map();
30
30
  const resourceDatabaseIdentities = new WeakMap();
31
31
  function assertResourceDatabaseIdentity(database) {
@@ -400,6 +400,8 @@ export function getDatabase(ddFlowHome, mode = "initialize", importPermitId, hoo
400
400
  if (!exists || mode === "migrate")
401
401
  ensureDir(ddFlowHome);
402
402
  const db = mode === "read_existing" ? new DatabaseSync(dbPath, { readOnly: true }) : new DatabaseSync(dbPath);
403
+ if (mode !== "read_existing")
404
+ installWriterAdmission(db, () => assertImportAdmission(ddFlowHome, importPermitId), () => db.isTransaction, true);
403
405
  try {
404
406
  db.exec(`PRAGMA busy_timeout = ${deadline?.("store_open") ?? 4000}`);
405
407
  }
@@ -408,10 +410,12 @@ export function getDatabase(ddFlowHome, mode = "initialize", importPermitId, hoo
408
410
  }
409
411
  if (mode !== "read_existing") {
410
412
  try {
413
+ const admission = () => { if (!db.isTransaction)
414
+ assertImportAdmission(ddFlowHome, importPermitId); };
411
415
  if (mode === "reconcile")
412
- registerReconciliationWriter(db);
416
+ registerReconciliationWriter(db, admission);
413
417
  else
414
- registerStoreWriter(db);
418
+ registerStoreWriter(db, admission);
415
419
  }
416
420
  catch (error) {
417
421
  return closeAfterOpenFailure(db, error);
@@ -464,7 +468,7 @@ export function getDatabase(ddFlowHome, mode = "initialize", importPermitId, hoo
464
468
  }
465
469
  }
466
470
  db.exec("PRAGMA foreign_keys = ON");
467
- return databaseFacade(dbPath, mode !== "read_existing", db, undefined, deadline);
471
+ return databaseFacade(dbPath, mode !== "read_existing", db, undefined, deadline, mode === "read_existing" ? undefined : () => assertImportAdmission(ddFlowHome, importPermitId));
468
472
  }
469
473
  /**
470
474
  * Hook ingress is a hot, synchronous path. It may write receipts, but it
@@ -526,13 +530,15 @@ export function withResourceReadSnapshot(resourceHome, read) {
526
530
  return withStoreCleanup(operation, () => { if (native.isOpen)
527
531
  native.close(); });
528
532
  }
529
- export function getResourceDatabase(resourceHome, mode = "initialize", deadlineAt) {
533
+ export function getResourceDatabase(resourceHome, mode = "initialize", deadlineAt, importPermitId) {
530
534
  channel("dd-flow.sqlite.ingress").publish({ phase: "store_open", store_kind: "resource" });
531
535
  const deadline = requestDeadline(deadlineAt);
532
536
  deadline?.("resource_open");
533
537
  const dbPath = path.join(resourceHome, "runtime.sqlite");
538
+ if (mode !== "read_existing" && mode !== "read_isolated")
539
+ assertImportAdmission(resourceHome, importPermitId);
534
540
  if (mode === "retirement" && !fs.existsSync(dbPath))
535
- return getResourceDatabase(resourceHome, "initialize", deadlineAt);
541
+ return getResourceDatabase(resourceHome, "initialize", deadlineAt, importPermitId);
536
542
  const cached = resourceDatabases.get(dbPath);
537
543
  if (mode === "create_new") {
538
544
  if (cached)
@@ -564,30 +570,29 @@ export function getResourceDatabase(resourceHome, mode = "initialize", deadlineA
564
570
  if (mode === "prepared_existing" && !fs.existsSync(dbPath))
565
571
  throw new AppError("resource_storage_unprepared", "Prepare the resource registry before maintenance", 1);
566
572
  if (cached) {
573
+ if (importPermitId && resourceDatabaseIdentities.get(cached)?.importPermitId !== importPermitId)
574
+ throw new AppError("resource_writer_reopen_required", "Close the owned cached resource writer before opening a privileged maintenance connection", 1);
567
575
  if (mode === "prepared_existing" || mode === "retirement")
568
576
  assertResourceDatabaseIdentity(cached);
569
577
  const bounded = deadlineAt === undefined ? cached : cached.withDeadline(deadlineAt);
570
578
  const reader = { prepare: (sql) => ({ get: (...params) => bounded.get(sql, params), all: (...params) => bounded.all(sql, params), run: (...params) => bounded.run(sql, params) }) };
571
- if (mode === "prepared_existing" || mode === "retirement")
572
- assertResourceStoreReady(reader, mode === "retirement");
573
- else {
574
- const required = readStoreWriterContract(reader);
575
- if (required !== null && required !== STORE_WRITER_VERSION)
576
- throw new AppError("store_writer_incompatible", "Retirement writer cannot admit productive resource operations", 1, { required, supported: STORE_WRITER_VERSION });
577
- }
579
+ assertResourceStoreReady(reader, mode === "retirement");
578
580
  return bounded;
579
581
  }
580
582
  if (mode !== "prepared_existing" && mode !== "retirement")
581
583
  ensureDir(resourceHome);
582
584
  channel("dd-flow.sqlite.ingress").publish({ phase: "store_connection", store_kind: "resource" });
583
585
  const db = new DatabaseSync(dbPath);
586
+ installWriterAdmission(db, () => assertImportAdmission(resourceHome, importPermitId), () => db.isTransaction, true);
584
587
  try {
585
588
  channel("dd-flow.sqlite.ingress").publish({ phase: "store_writer_registration", store_kind: "resource" });
586
589
  db.exec(`PRAGMA busy_timeout = ${deadline?.("resource_open") ?? 4000}`);
590
+ const admission = () => { if (!db.isTransaction)
591
+ assertImportAdmission(resourceHome, importPermitId); };
587
592
  if (mode === "retirement")
588
- registerReconciliationWriter(db);
593
+ registerReconciliationWriter(db, admission);
589
594
  else
590
- registerStoreWriter(db);
595
+ registerStoreWriter(db, admission);
591
596
  }
592
597
  catch (error) {
593
598
  return closeAfterOpenFailure(db, error);
@@ -659,12 +664,12 @@ export function getResourceDatabase(resourceHome, mode = "initialize", deadlineA
659
664
  const database = databaseFacade(dbPath, true, db, () => {
660
665
  resourceDatabases.delete(dbPath);
661
666
  db.close?.();
662
- });
667
+ }, undefined, () => assertImportAdmission(resourceHome, importPermitId));
663
668
  try {
664
669
  const identity = fs.lstatSync(dbPath);
665
670
  if (!identity.isFile())
666
671
  throw new AppError("resource_storage_changed", "Resource registry target is not a regular file", 1);
667
- resourceDatabaseIdentities.set(database, { dev: identity.dev, ino: identity.ino });
672
+ resourceDatabaseIdentities.set(database, { dev: identity.dev, ino: identity.ino, ...(importPermitId ? { importPermitId } : {}) });
668
673
  }
669
674
  catch (error) {
670
675
  return closeAfterOpenFailure(db, error);
@@ -713,7 +718,25 @@ function emptyReadOnlyDatabase(dbPath) {
713
718
  all: () => []
714
719
  };
715
720
  }
716
- function databaseFacade(dbPath, writable, db, close = () => db.close?.(), deadline) {
721
+ function installWriterAdmission(db, assertAdmission, reserved, initializing = false) {
722
+ const writes = new Set([constants.SQLITE_INSERT, constants.SQLITE_UPDATE, constants.SQLITE_DELETE,
723
+ constants.SQLITE_CREATE_INDEX, constants.SQLITE_CREATE_TABLE, constants.SQLITE_CREATE_TEMP_INDEX, constants.SQLITE_CREATE_TEMP_TABLE,
724
+ constants.SQLITE_CREATE_TEMP_TRIGGER, constants.SQLITE_CREATE_TEMP_VIEW, constants.SQLITE_CREATE_TRIGGER, constants.SQLITE_CREATE_VIEW,
725
+ constants.SQLITE_DROP_INDEX, constants.SQLITE_DROP_TABLE, constants.SQLITE_DROP_TEMP_INDEX, constants.SQLITE_DROP_TEMP_TABLE,
726
+ constants.SQLITE_DROP_TEMP_TRIGGER, constants.SQLITE_DROP_TEMP_VIEW, constants.SQLITE_DROP_TRIGGER, constants.SQLITE_DROP_VIEW,
727
+ constants.SQLITE_ATTACH, constants.SQLITE_DETACH, constants.SQLITE_ALTER_TABLE, constants.SQLITE_REINDEX, constants.SQLITE_ANALYZE,
728
+ constants.SQLITE_CREATE_VTABLE, constants.SQLITE_DROP_VTABLE]);
729
+ const filePragmas = new Set(["journal_mode", "wal_checkpoint", "optimize", "incremental_vacuum", "user_version", "application_id", "writable_schema", "auto_vacuum", "page_size", "max_page_count", "encoding", "locking_mode"]);
730
+ db.setAuthorizer((action, first, second) => {
731
+ if (initializing && action === constants.SQLITE_TRANSACTION && first === "BEGIN")
732
+ assertAdmission();
733
+ if (!reserved() && (writes.has(action) || action === constants.SQLITE_PRAGMA
734
+ && filePragmas.has(first?.toLowerCase() ?? "") && (second !== null || ["wal_checkpoint", "optimize", "incremental_vacuum"].includes(first?.toLowerCase() ?? ""))))
735
+ assertAdmission();
736
+ return constants.SQLITE_OK;
737
+ });
738
+ }
739
+ function databaseFacade(dbPath, writable, db, close = () => db.close?.(), deadline, assertAdmission) {
717
740
  let scopedDeadline = deadline;
718
741
  let reserved = false;
719
742
  let poisoned = false;
@@ -727,6 +750,16 @@ function databaseFacade(dbPath, writable, db, close = () => db.close?.(), deadli
727
750
  let manualFrames = [];
728
751
  let readDepth = 0;
729
752
  let ownedWriterToken = null;
753
+ if (writable && assertAdmission) {
754
+ // SQLite identifies DML/DDL even behind comments, CTEs or RETURNING.
755
+ // An already reserved writer may finish; maintenance must acquire its
756
+ // own barriers and recheck drain before mutating either store.
757
+ installWriterAdmission(db, assertAdmission, () => reserved && db.isTransaction);
758
+ // Existing writer triggers also check at execution time. This closes the
759
+ // prepare-before-fence / step-after-fence window for protected DML.
760
+ bindStoreWriterAdmission(db, () => { if (!(reserved && db.isTransaction))
761
+ assertAdmission(); });
762
+ }
730
763
  const writerTiming = channel("dd-flow.sqlite.writer");
731
764
  const ingressPhase = channel("dd-flow.sqlite.ingress");
732
765
  const storeKind = path.basename(dbPath) === "runtime.sqlite" ? "resource" : "run";
@@ -754,6 +787,7 @@ function databaseFacade(dbPath, writable, db, close = () => db.close?.(), deadli
754
787
  throw new AppError("read_snapshot_write", "Cannot reserve a writer inside a read snapshot", 1);
755
788
  if (!writable)
756
789
  throw new AppError("read_only_storage", "Read-only access cannot start a write transaction", 1, { path: dbPath });
790
+ assertAdmission?.();
757
791
  bound("writer_reservation");
758
792
  db.exec("BEGIN IMMEDIATE");
759
793
  reserved = true;
@@ -2,6 +2,8 @@ import crypto from "node:crypto";
2
2
  import fs from "node:fs";
3
3
  import path from "node:path";
4
4
  import { AppError } from "../shared/errors.js";
5
+ import { acquireInstallationLock } from "../services/installation-lock.js";
6
+ import { withStoreCleanup } from "./native-cleanup.js";
5
7
  /** Outside the replaced runtime tree, so a copied source receipt cannot make
6
8
  * an incomplete target look ready. A failed target is diagnostic evidence. */
7
9
  export function importGuardPath(home) {
@@ -31,7 +33,7 @@ function readImportGuard(home) {
31
33
  if (guard.schema_id !== "dd-flow/import-guard@1" || guard.runtime_home !== canonicalHome(home)
32
34
  || !/^[0-9a-f-]{36}$/.test(guard.import_id) || !/^[a-f0-9]{64}$/.test(guard.source_manifest_sha256)
33
35
  || !path.isAbsolute(guard.project_root) || !path.isAbsolute(guard.workspace_root)
34
- || !["in_progress", "completed"].includes(guard.status)) {
36
+ || !["in_progress", "completed"].includes(guard.status) || guard.operation !== undefined && guard.operation !== "storage-migration") {
35
37
  throw new AppError("snapshot_import_incomplete", "Snapshot import guard does not identify this target", 1, { guard_path: file });
36
38
  }
37
39
  return guard;
@@ -39,41 +41,96 @@ function readImportGuard(home) {
39
41
  export function assertImportAdmission(home, permit) {
40
42
  const guard = readImportGuard(home);
41
43
  if (guard?.status === "in_progress" && guard.import_id !== permit)
42
- throw new AppError("snapshot_import_incomplete", "Snapshot import has not completed; preserve this target for diagnosis and restore into fresh targets", 1, { guard_path: importGuardPath(home), import_id: guard.import_id });
44
+ throw new AppError(guard.operation === "storage-migration" ? "storage_migration_incomplete" : "snapshot_import_incomplete", guard.operation === "storage-migration" ? "Storage maintenance requires reconciliation of its original request" : "Snapshot import has not completed; preserve this target for diagnosis and restore into fresh targets", 1, { guard_path: importGuardPath(home), import_id: guard.import_id });
43
45
  }
44
- function durableWrite(file, bytes, create) {
45
- const target = create ? file : `${file}.tmp-${crypto.randomUUID()}`;
46
- try {
47
- fs.writeFileSync(target, bytes, { flag: "wx", mode: 0o600 });
48
- const descriptor = fs.openSync(target, "r");
46
+ function durableWrite(file, bytes, create, expected) {
47
+ const lease = acquireInstallationLock(file + ".lock", crypto.createHash("sha256").update(file).digest("hex"));
48
+ return withStoreCleanup(() => {
49
+ lease.assertOwnership();
50
+ // A different request may have selected maintenance since the caller read
51
+ // a completed guard. Never rename over its now-live admission fence.
52
+ if (!create && JSON.stringify(JSON.parse(fs.readFileSync(file, "utf8"))) !== JSON.stringify(expected))
53
+ throw new AppError("storage_guard_changed", "Re-read the current maintenance owner before continuing", 1);
54
+ const target = create ? file : `${file}.tmp-${crypto.randomUUID()}`;
49
55
  try {
50
- fs.fsyncSync(descriptor);
56
+ fs.writeFileSync(target, bytes, { flag: "wx", mode: 0o600 });
57
+ const descriptor = fs.openSync(target, "r");
58
+ try {
59
+ fs.fsyncSync(descriptor);
60
+ }
61
+ finally {
62
+ fs.closeSync(descriptor);
63
+ }
64
+ if (!create)
65
+ fs.renameSync(target, file);
66
+ const directory = fs.openSync(path.dirname(file), "r");
67
+ try {
68
+ fs.fsyncSync(directory);
69
+ }
70
+ finally {
71
+ fs.closeSync(directory);
72
+ }
51
73
  }
52
74
  finally {
53
- fs.closeSync(descriptor);
54
- }
55
- if (!create)
56
- fs.renameSync(target, file);
57
- const directory = fs.openSync(path.dirname(file), "r");
58
- try {
59
- fs.fsyncSync(directory);
75
+ if (!create)
76
+ fs.rmSync(target, { force: true });
60
77
  }
61
- finally {
62
- fs.closeSync(directory);
63
- }
64
- }
65
- finally {
66
- if (!create)
67
- fs.rmSync(target, { force: true });
68
- }
78
+ }, () => lease.release());
69
79
  }
70
80
  export function beginSnapshotImport(input) {
71
81
  const importId = crypto.randomUUID();
72
82
  const guard = { schema_id: "dd-flow/import-guard@1", import_id: importId, status: "in_progress", source_manifest_sha256: input.sourceManifestSha256,
73
- runtime_home: canonicalHome(input.home), project_root: path.resolve(input.projectRoot), workspace_root: path.resolve(input.workspaceRoot) };
74
- durableWrite(importGuardPath(input.home), `${JSON.stringify(guard)}\n`, true);
83
+ runtime_home: canonicalHome(input.home), project_root: path.resolve(input.projectRoot), workspace_root: path.resolve(input.workspaceRoot),
84
+ ...(input.operation ? { operation: input.operation } : {}) };
85
+ durableWrite(importGuardPath(input.home), `${JSON.stringify(guard)}\n`, true, null);
75
86
  return importId;
76
87
  }
88
+ /** Reuse the import admission fence for one exact paired storage cutover.
89
+ * A completed older operation may be superseded; a live one never is. */
90
+ export function beginStorageMaintenance(input) {
91
+ if (!/^[0-9a-f-]{36}$/.test(input.importId) || !/^[a-f0-9]{64}$/.test(input.sourceManifestSha256))
92
+ throw new AppError("storage_migration_request_invalid", "Invalid retained maintenance identity", 2);
93
+ const guard = readImportGuard(input.home);
94
+ const next = { schema_id: "dd-flow/import-guard@1", import_id: input.importId, status: "in_progress",
95
+ source_manifest_sha256: input.sourceManifestSha256, runtime_home: canonicalHome(input.home),
96
+ project_root: path.resolve(input.projectRoot), workspace_root: path.resolve(input.workspaceRoot), operation: "storage-migration" };
97
+ if (guard?.import_id === input.importId && guard.source_manifest_sha256 === input.sourceManifestSha256
98
+ && guard.project_root === next.project_root && guard.workspace_root === next.workspace_root && guard.operation === next.operation)
99
+ return;
100
+ if (guard?.import_id === input.importId)
101
+ throw new AppError("storage_migration_request_conflict", "A maintenance identity cannot select another request", 1);
102
+ if (guard?.status === "in_progress")
103
+ assertImportAdmission(input.home);
104
+ durableWrite(importGuardPath(input.home), `${JSON.stringify(next)}\n`, !guard, guard);
105
+ }
106
+ /** Explicit recovery may take over only its original paired maintenance scope.
107
+ * Keeping the permit identity makes an old apply unable to reopen recovery. */
108
+ export function beginStorageRecovery(input) {
109
+ if (!/^[a-f0-9]{64}$/.test(input.originalSha256) || !/^[a-f0-9]{64}$/.test(input.recoverySha256))
110
+ throw new AppError("storage_recovery_scope_invalid", "Invalid retained recovery identity", 2);
111
+ const guard = readImportGuard(input.home);
112
+ if (!guard) {
113
+ beginStorageMaintenance({ ...input, sourceManifestSha256: input.originalSha256 });
114
+ return beginStorageRecovery(input);
115
+ }
116
+ if (guard.operation !== "storage-migration" || guard.import_id !== input.importId
117
+ || guard.project_root !== path.resolve(input.projectRoot) || guard.workspace_root !== path.resolve(input.workspaceRoot)
118
+ || ![input.originalSha256, input.recoverySha256].includes(guard.source_manifest_sha256))
119
+ throw new AppError("storage_recovery_scope_invalid", "Recovery cannot replace another maintenance scope", 1);
120
+ if (guard.source_manifest_sha256 === input.recoverySha256)
121
+ return;
122
+ durableWrite(importGuardPath(input.home), `${JSON.stringify({ ...guard, status: "in_progress", source_manifest_sha256: input.recoverySha256 })}\n`, false, guard);
123
+ }
124
+ export function completeStorageMaintenance(input) {
125
+ const guard = readImportGuard(input.home);
126
+ if (guard?.operation !== "storage-migration")
127
+ throw new AppError("storage_migration_incomplete", "Completion requires the original storage maintenance fence", 1);
128
+ if (guard?.status === "completed" && guard.operation === "storage-migration" && guard.import_id === input.importId
129
+ && guard.source_manifest_sha256 === input.sourceManifestSha256 && guard.project_root === path.resolve(input.projectRoot)
130
+ && guard.workspace_root === path.resolve(input.workspaceRoot))
131
+ return;
132
+ completeSnapshotImport(input.home, input.importId, input);
133
+ }
77
134
  /** A fork with its own matching intent may rebuild a completed import when
78
135
  * its final fork receipt was lost. Fence the existing home before removing any
79
136
  * copied files; an interrupted rebuild remains closed to productive opens. */
@@ -87,7 +144,7 @@ export function rearmCompletedSnapshotImport(input) {
87
144
  throw new AppError("snapshot_import_incomplete", "Existing import does not match this completed fork; preserve it for diagnosis", 1, { guard_path: importGuardPath(input.home) });
88
145
  }
89
146
  const importId = crypto.randomUUID();
90
- durableWrite(importGuardPath(input.home), `${JSON.stringify({ ...guard, import_id: importId, status: "in_progress" })}\n`, false);
147
+ durableWrite(importGuardPath(input.home), `${JSON.stringify({ ...guard, import_id: importId, status: "in_progress" })}\n`, false, guard);
91
148
  return importId;
92
149
  }
93
150
  export function completeSnapshotImport(home, importId, completed) {
@@ -98,5 +155,5 @@ export function completeSnapshotImport(home, importId, completed) {
98
155
  || guard.source_manifest_sha256 !== completed.sourceManifestSha256) {
99
156
  throw new AppError("snapshot_import_incomplete", "Import completion does not match its source and target paths", 1, { guard_path: importGuardPath(home) });
100
157
  }
101
- durableWrite(importGuardPath(home), `${JSON.stringify({ ...guard, status: "completed" })}\n`, false);
158
+ durableWrite(importGuardPath(home), `${JSON.stringify({ ...guard, status: "completed" })}\n`, false, guard);
102
159
  }
@@ -0,0 +1,38 @@
1
+ import { getDatabase, getResourceDatabase } from "./database.js";
2
+ import { withStoreCleanup } from "./native-cleanup.js";
3
+ import { PreparedMemoryInitRuntimeStorage } from "../services/memory-init-storage.js";
4
+ import { PreparedMemoryInitResourceStorage } from "../services/memory-init-resource-storage.js";
5
+ import { AppError } from "../shared/errors.js";
6
+ import fs from "node:fs";
7
+ import path from "node:path";
8
+ /** Fixed executing-engine recipe, never an operator command or a normal
9
+ * admission side effect. The paired migration owns backup, drain, permit,
10
+ * preservation verification and recovery for both stores around this step. */
11
+ export function bootstrapMemoryUpgradeSchema(runHome, resourceHome, permitId) {
12
+ try {
13
+ for (const file of [path.join(runHome, "db.sqlite"), path.join(resourceHome, "runtime.sqlite")]) {
14
+ const owner = fs.lstatSync(file);
15
+ if (!owner.isFile() || owner.nlink !== 1)
16
+ throw new AppError("memory_schema_bootstrap_unavailable", "Schema bootstrap requires the existing paired stores", 1);
17
+ }
18
+ const db = getDatabase(runHome, "initialize", permitId);
19
+ withStoreCleanup(() => {
20
+ const resources = getResourceDatabase(resourceHome, "initialize", undefined, permitId);
21
+ withStoreCleanup(() => {
22
+ const context = { ddFlowHome: runHome, db,
23
+ env: { DD_FLOW_HOME: runHome, DD_FLOW_RESOURCE_HOME: resourceHome }, now: () => new Date().toISOString() };
24
+ PreparedMemoryInitRuntimeStorage.prepareUpgrade(context);
25
+ PreparedMemoryInitResourceStorage.prepare(context);
26
+ }, () => resources.close?.());
27
+ }, () => db.close?.());
28
+ }
29
+ catch (error) {
30
+ // The writer cutover already happened. An admission failure does not mean
31
+ // no effect: retain both maintenance fences for exact replay or recovery.
32
+ if (error instanceof AppError) {
33
+ Object.assign(error.details, { effect: "unknown", next_action: "reconcile_exact_request" });
34
+ throw error;
35
+ }
36
+ throw new AppError("memory_schema_bootstrap_failed", "Reconcile the retained schema bootstrap before runtime admission", 1, { effect: "unknown", next_action: "reconcile_exact_request" });
37
+ }
38
+ }