@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
@@ -1,6 +1,8 @@
1
1
  import fs from "node:fs";
2
2
  import path from "node:path";
3
3
  import crypto from "node:crypto";
4
+ import { fileURLToPath } from "node:url";
5
+ import semver from "semver";
4
6
  import { AppError } from "../shared/errors.js";
5
7
  import { requireProjectByRoot } from "./projects.js";
6
8
  import { getProjectVersionStatus, resolveStatusProjectRoot } from "./version-status.js";
@@ -12,11 +14,16 @@ import { classifyCliOperation } from "./cli-operation-classifier.js";
12
14
  import { compatibilityContexts, selectEngine } from "./engines.js";
13
15
  import { getCliBuildInfo } from "./build-info.js";
14
16
  import { prepareJsonFile } from "../cli/input-preparation.js";
17
+ import { validateSchema } from "./schema-validation.js";
18
+ import { PreparedMemoryInitRuntimeStorage } from "./memory-init-storage.js";
19
+ import { PreparedMemoryInitResourceStorage } from "./memory-init-resource-storage.js";
20
+ import { AdmittedFrozenEngine } from "./frozen-engine.js";
21
+ import { resourceHome } from "./managed-processes.js";
15
22
  export function assessMigrationImpact(context, input) {
16
23
  const root = resolveStatusProjectRoot({ requestedRoot: input.projectRoot, cwd: process.cwd() });
17
24
  if (!root.root)
18
25
  throw new AppError("not_found", `Project root does not exist: ${input.projectRoot}`, 1);
19
- const canon = resolveCanonRoot(context);
26
+ const canon = upgradeCanon(context);
20
27
  if (!canon.ok || !canon.canon)
21
28
  throw new AppError("canon_unavailable", "Cannot resolve canonical Memory Bank for upgrade impact assessment", 1, { blockers: canon.blockers, bootstrap: canon.bootstrap });
22
29
  const status = getProjectVersionStatus({ projectRoot: root.root, rootSource: root.root_source, canon: { root: canon.canon.root, metadata: canon.canon.metadata } });
@@ -24,53 +31,65 @@ export function assessMigrationImpact(context, input) {
24
31
  const to = input.targetVersion ?? canon.canon.version;
25
32
  const forced = input.mode === "full_migration";
26
33
  const impacts = readImpacts(path.join(canon.canon.memorybank_root, "release-impact"));
27
- const chain = [];
34
+ const resolved = from && to ? adjacentMigrationChain(from, to, impacts) : null;
35
+ const chain = resolved?.status === "ok"
36
+ ? resolved.steps.map(step => impacts.find(item => item.from_version === step.from && item.to_version === step.to))
37
+ : [];
28
38
  const domains = new Set();
29
39
  const blockers = [];
30
40
  let mode = "patch";
31
41
  if (!from || !to)
32
42
  blockers.push("project or canonical Memory Bank version is unavailable");
33
- let current = from;
34
- while (current && to && current !== to) {
35
- const impact = impacts.find((item) => item.from_version === current);
36
- if (!impact) {
37
- blockers.push(`missing adjacent release impact from ${current}`);
38
- break;
39
- }
40
- chain.push(impact);
43
+ if (to !== canon.canon.version)
44
+ blockers.push("requested target version does not match the selected canon");
45
+ if (resolved?.status === "unsupported")
46
+ blockers.push(resolved.reason ?? "release impact chain is unsupported");
47
+ for (const impact of chain) {
41
48
  for (const domain of impact.domains)
42
49
  domains.add(domain);
43
50
  mode = stricterMode(mode, impact.minimum_upgrade_mode);
44
51
  if (impact.path_migration || impact.runtime_migration)
45
52
  mode = "full_migration";
46
- current = impact.to_version;
47
53
  }
48
- if (current && to && current !== to)
49
- blockers.push(`release impact chain does not reach ${to}`);
50
- if (status?.flow_pack.status !== "present" && from !== to)
54
+ if (status?.flow_pack.status !== "present")
51
55
  blockers.push("project flow-pack provenance is not present");
56
+ else {
57
+ try {
58
+ const file = path.resolve(root.root, status.flow_pack.manifest_path);
59
+ const prepared = prepareJsonFile(file, "Project flow-pack provenance");
60
+ validateSchema({ schemaName: "project-flow-pack-manifest", file, data: prepared.value,
61
+ schemaDir: fileURLToPath(new URL("../schemas/", import.meta.url)) });
62
+ if (!/^[a-f0-9]{40}$/.test(status.flow_pack.source_commit ?? "")
63
+ || status.flow_pack.canon_version_at_source_commit !== from)
64
+ blockers.push("project flow-pack provenance does not match its declared current version and commit");
65
+ }
66
+ catch (error) {
67
+ if (!(error instanceof AppError) || !["schema_validation", "invalid_json", "validation"].includes(error.code)
68
+ && !error.code.startsWith("input_file_"))
69
+ throw error;
70
+ blockers.push("project flow-pack provenance is invalid");
71
+ }
72
+ }
52
73
  if (blockers.length > 0)
53
74
  mode = "full_migration";
54
75
  if (forced)
55
76
  mode = "full_migration";
56
77
  return { ok: true, schema_id: "dd-flow/mb-upgrade-impact-assessment@1", project_root: root.root, source_version: from, target_version: to, mode, forced_full_migration: forced, chain, domains: [...domains].sort(), blockers, required_checks: [...new Set(chain.flatMap((item) => item.required_checks))], next_action: mode === "full_migration" ? "run full mb-upgrade contour" : "run selected mb-upgrade contour" };
57
78
  }
58
- function readImpacts(dir) {
79
+ export function readImpacts(dir) {
59
80
  if (!fs.existsSync(dir))
60
81
  return [];
61
- return fs.readdirSync(dir).filter((file) => file.endsWith(".json")).flatMap((file) => {
82
+ return fs.readdirSync(dir).filter(file => file.endsWith(".json")).sort().map(file => {
83
+ const source = path.join(dir, file);
62
84
  try {
63
- const value = JSON.parse(fs.readFileSync(path.join(dir, file), "utf8"));
64
- if (value.schema_id !== "dd-flow/release-impact@1")
65
- return [];
66
- if (typeof value.from_version !== "string" || typeof value.to_version !== "string" || !Array.isArray(value.domains) || !Array.isArray(value.required_checks))
67
- return [];
68
- if (!["patch", "targeted_minor", "full_migration"].includes(String(value.minimum_upgrade_mode)))
69
- return [];
70
- return [{ from_version: value.from_version, to_version: value.to_version, minimum_upgrade_mode: value.minimum_upgrade_mode, domains: value.domains.filter((item) => typeof item === "string"), path_migration: value.path_migration === true, runtime_migration: value.runtime_migration === true, required_checks: value.required_checks.filter((item) => typeof item === "string") }];
85
+ const prepared = prepareJsonFile(source, "Canonical release impact");
86
+ validateSchema({ schemaName: "release-impact", file: prepared.file, data: prepared.value,
87
+ schemaDir: fileURLToPath(new URL("../schemas/", import.meta.url)) });
88
+ return { ...prepared.value, source_reference: { path: prepared.file,
89
+ sha256: crypto.createHash("sha256").update(prepared.text).digest("hex") } };
71
90
  }
72
- catch {
73
- return [];
91
+ catch (error) {
92
+ throw new AppError("release_impact_invalid", "Canonical release impact is invalid; it cannot authorize a lightweight upgrade", 1, { file: source, effect: "no_effect", cause: error instanceof AppError ? error.code : "unreadable_release_impact" });
74
93
  }
75
94
  });
76
95
  }
@@ -267,7 +286,7 @@ function applyPreparedMigration(context, prepared, writeDb) {
267
286
  }
268
287
  return { ok: true, schema_id: "dd-flow/mb-upgrade-migration-apply@1", status: "applied", changed: true, backup: verifiedBackup, recovery: result };
269
288
  }
270
- export function assertUpgradeRunStorageReady(context, projectRoot) {
289
+ export function assertUpgradeRunStorageReady(context, projectRoot, engine) {
271
290
  const project = context.db.get("SELECT id FROM projects WHERE root = ?", [resolveProjectRoot(projectRoot)]);
272
291
  if (!project)
273
292
  throw new AppError("migration_not_applied", "mb-upgrade RUN requires a registered project with explicit migration apply", 1);
@@ -277,12 +296,57 @@ export function assertUpgradeRunStorageReady(context, projectRoot) {
277
296
  if (!source || !target)
278
297
  throw new AppError("migration_not_applied", "mb-upgrade RUN requires source and target Memory Bank versions", 1);
279
298
  const applied = context.db.get("SELECT value_json FROM runtime_config WHERE key = ?", [applyKey(project.id, source, target)]);
280
- if (!applied)
299
+ if (!applied || engine !== undefined) {
300
+ const assessment = assessMigrationImpact(context, { projectRoot });
301
+ const blockers = assessment.blockers;
302
+ const chain = assessment.chain;
303
+ if (!blockers.length && (engine !== undefined || chain.every(step => !step.runtime_migration))) {
304
+ const canon = upgradeCanon(context).canon;
305
+ const contract = prepareJsonFile(path.join(canon.flow_root, "flow-contract.json"), "Target flow contract");
306
+ let selectedEngineStorage;
307
+ if (engine !== undefined) {
308
+ if (!(engine instanceof AdmittedFrozenEngine))
309
+ throw new AppError("upgrade_storage_incompatible", "Upgrade requires an admitted selected engine", 1, { effect: "no_effect" });
310
+ const compatibility = prepareJsonFile(path.join(canon.flow_root, "compatibility.json"), "Target engine compatibility");
311
+ validateSchema({ schemaName: "compatibility", file: compatibility.file, data: compatibility.value,
312
+ schemaDir: fileURLToPath(new URL("../schemas/", import.meta.url)) });
313
+ const manifest = engine.manifest();
314
+ if (compatibility.value.memory_bank_version !== target || compatibility.value.dd_flow_cli.flow_contract !== contract.value.id
315
+ || manifest.package_name !== compatibility.value.dd_flow_cli.package_name || !semver.valid(compatibility.value.dd_flow_cli.min_version)
316
+ || semver.lt(manifest.package_version, compatibility.value.dd_flow_cli.min_version)
317
+ || manifest.package_name !== compatibility.value.engine.package_name
318
+ || !semver.satisfies(manifest.package_version, compatibility.value.engine.version_range, { includePrerelease: true }))
319
+ throw new AppError("upgrade_storage_incompatible", "Selected engine is incompatible with target canon", 1, { effect: "no_effect" });
320
+ selectedEngineStorage = engine.admitMemoryUpgradeStorage(context.ddFlowHome, resourceHome(context));
321
+ }
322
+ // Reuse the executing CLI's physical compatibility checks, not version text
323
+ // or a synthetic apply marker. This does not authorize storage migration.
324
+ const runOwner = fs.lstatSync(context.db.path);
325
+ if (!runOwner.isFile() || runOwner.nlink !== 1)
326
+ throw new AppError("upgrade_storage_incompatible", "Upgrade requires a regular single-linked runtime store", 1, { effect: "no_effect" });
327
+ const run = PreparedMemoryInitRuntimeStorage.prepareUpgrade(context);
328
+ const resource = PreparedMemoryInitResourceStorage.prepare(context);
329
+ const currentOwner = fs.lstatSync(context.db.path);
330
+ if (currentOwner.dev !== runOwner.dev || currentOwner.ino !== runOwner.ino)
331
+ throw new AppError("upgrade_storage_changed", "Runtime store changed during upgrade admission", 1, { effect: "no_effect" });
332
+ const manifest = prepareJsonFile(path.resolve(projectRoot, status.flow_pack.manifest_path), "Current flow-pack provenance");
333
+ return { schema_id: "dd-flow/mb-upgrade-storage-readiness@1", route: "verified-no-op",
334
+ source_version: source, target_version: target, chain,
335
+ current_provenance: { path: manifest.file, sha256: crypto.createHash("sha256").update(manifest.text).digest("hex") },
336
+ target: { root: canon.root, commit: canon.commit, contract_sha256: crypto.createHash("sha256").update(contract.text).digest("hex") },
337
+ ...(selectedEngineStorage ? { selected_engine_storage: selectedEngineStorage } : {}),
338
+ stores: [{ ...run.facts(), store_dev: runOwner.dev, store_ino: runOwner.ino }, resource.facts()].map(store => ({ store_kind: store.store_kind,
339
+ store_dev: store.store_dev, store_ino: store.store_ino,
340
+ store_path: store.store_path, schema_sha256: store.schema_sha256,
341
+ executing_writer_version: store.executing_writer_version })) };
342
+ }
281
343
  throw new AppError("migration_not_applied", "mb-upgrade RUN requires explicit migration apply before RUN creation", 1, {
282
344
  source_version: source,
283
345
  target_version: target,
346
+ blockers,
284
347
  next_action: "run migration plan, verify and explicit migration apply first"
285
348
  });
349
+ }
286
350
  const value = recordValue(parseJsonValue(applied.value_json));
287
351
  if (!value || !["applied", "verified_noop"].includes(stringValue(value.status) ?? "")) {
288
352
  throw new AppError("migration_not_applied", "mb-upgrade RUN has no successful explicit migration apply evidence", 1, {
@@ -290,6 +354,8 @@ export function assertUpgradeRunStorageReady(context, projectRoot) {
290
354
  target_version: target
291
355
  });
292
356
  }
357
+ return { schema_id: "dd-flow/mb-upgrade-storage-readiness@1", route: "existing-apply",
358
+ source_version: source, target_version: target, apply: value };
293
359
  }
294
360
  export function verifyMigrationReport(_context, input) {
295
361
  const filePath = path.resolve(input.file);
@@ -452,61 +518,45 @@ function hasActiveState(active) {
452
518
  function applyKey(projectId, source, target) {
453
519
  return `mb-upgrade.apply.${projectId}.${source}.${target}`;
454
520
  }
455
- function adjacentMigrationChain(source, target, impacts = []) {
456
- if (source === target)
457
- return { status: "ok", steps: [] };
458
- const sourceParts = parseSemver(source);
459
- const targetParts = parseSemver(target);
460
- if (!sourceParts || !targetParts) {
521
+ export function adjacentMigrationChain(source, target, impacts = []) {
522
+ if (semver.valid(source) !== source || semver.valid(target) !== target) {
461
523
  return { status: "unsupported", reason: "source or target version is not semver", steps: [] };
462
524
  }
463
- if (impacts.length > 0) {
464
- const steps = [];
465
- const visited = new Set();
466
- let current = source;
467
- while (current !== target) {
468
- if (visited.has(current))
469
- return { status: "unsupported", reason: "release impact chain contains a cycle", steps: [] };
470
- visited.add(current);
471
- const impact = impacts.find((item) => item.from_version === current);
472
- if (!impact)
473
- return { status: "unsupported", reason: `missing adjacent release impact from ${current}`, steps: [] };
474
- steps.push({
475
- id: `${impact.from_version}-to-${impact.to_version}`,
476
- from: impact.from_version,
477
- to: impact.to_version,
478
- status: "planned",
479
- minimum_upgrade_mode: impact.minimum_upgrade_mode,
480
- domains: impact.domains,
481
- path_migration: impact.path_migration,
482
- runtime_migration: impact.runtime_migration,
483
- required_checks: impact.required_checks
484
- });
485
- current = impact.to_version;
486
- const next = parseSemver(current);
487
- if (!next || compareParts(next, targetParts) > 0) {
488
- return { status: "unsupported", reason: `release impact chain overshoots target ${target}`, steps: [] };
489
- }
490
- }
491
- return { status: "ok", steps };
492
- }
493
- if (sourceParts.major !== targetParts.major) {
494
- return { status: "unsupported", reason: "major version migration requires canonical release-impact units", steps: [] };
495
- }
496
- if (targetParts.minor < sourceParts.minor || (targetParts.minor === sourceParts.minor && targetParts.patch < sourceParts.patch)) {
525
+ if (semver.gt(source, target))
497
526
  return { status: "unsupported", reason: "downgrade migrations are not supported", steps: [] };
498
- }
527
+ if (source === target)
528
+ return { status: "ok", steps: [] };
499
529
  const steps = [];
500
- let current = { ...sourceParts };
501
- while (compareParts(current, targetParts) < 0) {
502
- const next = nextAdjacent(current, targetParts);
530
+ const visited = new Set();
531
+ let current = source;
532
+ while (current !== target) {
533
+ if (visited.has(current))
534
+ return { status: "unsupported", reason: "release impact chain contains a cycle", steps: [] };
535
+ visited.add(current);
536
+ const matches = impacts.filter(item => item.from_version === current);
537
+ if (matches.length > 1)
538
+ return { status: "unsupported", reason: `ambiguous adjacent release impacts from ${current}`, steps: [] };
539
+ const impact = matches[0];
540
+ if (!impact)
541
+ return { status: "unsupported", reason: `missing adjacent release impact from ${current}`, steps: [] };
542
+ if (semver.valid(impact.to_version) !== impact.to_version || !semver.gt(impact.to_version, current)) {
543
+ return { status: "unsupported", reason: "release impact chain must advance through valid versions", steps: [] };
544
+ }
545
+ if (semver.gt(impact.to_version, target))
546
+ return { status: "unsupported", reason: `release impact chain overshoots target ${target}`, steps: [] };
503
547
  steps.push({
504
- id: `${formatParts(current)}-to-${formatParts(next)}`,
505
- from: formatParts(current),
506
- to: formatParts(next),
507
- status: "planned"
548
+ id: `${impact.from_version}-to-${impact.to_version}`,
549
+ from: impact.from_version,
550
+ to: impact.to_version,
551
+ status: "planned",
552
+ minimum_upgrade_mode: impact.minimum_upgrade_mode,
553
+ domains: impact.domains,
554
+ path_migration: impact.path_migration,
555
+ runtime_migration: impact.runtime_migration,
556
+ required_checks: impact.required_checks,
557
+ ...(impact.source_reference ? { source_reference: impact.source_reference } : {})
508
558
  });
509
- current = next;
559
+ current = impact.to_version;
510
560
  }
511
561
  return { status: "ok", steps };
512
562
  }
@@ -556,24 +606,3 @@ function recordValue(value) {
556
606
  function stringValue(value) {
557
607
  return typeof value === "string" && value.length > 0 ? value : null;
558
608
  }
559
- function parseSemver(value) {
560
- const match = value.match(/^([0-9]+)\.([0-9]+)\.([0-9]+)/);
561
- if (!match?.[1] || !match[2] || !match[3])
562
- return null;
563
- return { major: Number(match[1]), minor: Number(match[2]), patch: Number(match[3]) };
564
- }
565
- function compareParts(left, right) {
566
- if (left.major !== right.major)
567
- return left.major - right.major;
568
- if (left.minor !== right.minor)
569
- return left.minor - right.minor;
570
- return left.patch - right.patch;
571
- }
572
- function nextAdjacent(current, target) {
573
- if (current.minor < target.minor)
574
- return { major: current.major, minor: current.minor + 1, patch: 0 };
575
- return { major: current.major, minor: current.minor, patch: Math.min(current.patch + 1, target.patch) };
576
- }
577
- function formatParts(parts) {
578
- return `${parts.major}.${parts.minor}.${parts.patch}`;
579
- }
@@ -1,4 +1,5 @@
1
1
  import crypto from "node:crypto";
2
+ import { nativeShutdownConfirmed } from "../harness-runtime/lib/native-children.mjs";
2
3
  import fs from "node:fs";
3
4
  import path from "node:path";
4
5
  import { AppError } from "../shared/errors.js";
@@ -29,7 +30,7 @@ export function historicalDaemonSettlement(context, scopeId, record) {
29
30
  if (!state.daemon_id || path.basename(file) !== `${state.daemon_id}.json` || !owner || owner.budget?.scope_id !== scopeId || owner.owner_id !== record.owner_id || owner.role !== metadata.role || owner.state_dir !== metadata.state_dir || owner.adapter_executable !== metadata.adapter_executable || owner.dd_flow_home !== metadata.dd_flow_home || !retainedResourceHomeMatches(context, owner.resource_home) || (owner.project_id ?? null) !== record.project_id || (owner.run_id ?? null) !== record.run_id || (owner.work_id ?? null) !== record.work_id || !record.operation_id?.startsWith(`${owner.operation_id}:`) || state.resource_process?.lease_token !== record.lease_token || state.pid !== record.pid)
30
31
  throw new AppError("runtime_scope_owner_unknown", "Archived daemon does not match its registration", 1);
31
32
  const closed = state.shutdown_state === "clean" && state.active_tree === false
32
- ? retainedDaemonOperations(metadata.state_dir).find(operation => operation.daemon_id === state.daemon_id && operation.operation === "daemon.stop" && operation.state === "completed" && operation.result?.stopped === true && operation.result?.clean === true) : undefined;
33
+ ? retainedDaemonOperations(metadata.state_dir).find(operation => operation.daemon_id === state.daemon_id && operation.operation === "daemon.stop" && operation.state === "completed" && operation.result?.stopped === true && nativeShutdownConfirmed(operation.result, { daemonId: state.daemon_id ?? "" })) : undefined;
33
34
  const fresh = managedProcessStatus(context).find(item => item.id === record.id);
34
35
  if (!closed || !fresh || fresh.lease_token !== record.lease_token || fresh.pid !== record.pid || fresh.pid_started_at !== record.pid_started_at || fresh.metadata_json !== record.metadata_json || fresh.state !== "stopped" || fresh.termination_reason !== "daemon_stopped" || processTreeIsAlive(fresh) || fs.readFileSync(file, "utf8") !== bytes)
35
36
  throw new AppError("runtime_scope_outcome_unknown", "Previous daemon lacks stable clean shutdown evidence", 1);
@@ -0,0 +1,45 @@
1
+ import { setTimeout as delay } from "node:timers/promises";
2
+ import { AppError } from "../shared/errors.js";
3
+ import { createRenewalState, maintenanceRetryable, renewalDelay, renewalFailure, RENEWAL_POLICY } from "../harness-runtime/lib/lease-renewal.mjs";
4
+ import { processStartedAt } from "./managed-processes.js";
5
+ /** Birth and confirmation share one startup horizon. Retrying observations
6
+ * neither launches another child nor renews an unconfirmed resource lease. */
7
+ export function physicalStartup(databaseDeadlineAt) {
8
+ const deadline = performance.now() + RENEWAL_POLICY.startupMs;
9
+ const episode = createRenewalState();
10
+ const unavailable = () => new AppError("process_ownership_unknown", "Managed physical identity observation is unavailable", 1, { phase: "process_confirmation", effect: "no_effect", native_dispatch_started: false, reason: "physical_identity_unavailable" });
11
+ async function observe(operation, assertCurrent, leaseExpiresAt) {
12
+ let last;
13
+ const remaining = () => Math.max(0, Math.min(deadline - performance.now(), databaseDeadlineAt === undefined ? Infinity : databaseDeadlineAt - Date.now(), leaseExpiresAt === undefined ? Infinity : Date.parse(leaseExpiresAt) - Date.now() - RENEWAL_POLICY.safetyMs));
14
+ for (;;) {
15
+ assertCurrent();
16
+ if (!(remaining() > 0))
17
+ throw last ?? unavailable();
18
+ try {
19
+ const result = operation();
20
+ assertCurrent();
21
+ if (!(remaining() > 0))
22
+ throw new AppError("process_startup_expired", "Physical observation completed after its original admission horizon", 1, { phase: "process_confirmation", effect: "unknown", native_dispatch_started: false });
23
+ return result;
24
+ }
25
+ catch (error) {
26
+ // A stale owner/cancellation wins over a transient observation error.
27
+ assertCurrent();
28
+ const details = error instanceof AppError ? error.details : null;
29
+ if (!(error instanceof AppError) || error.code !== "process_ownership_unknown" || details?.reason !== "physical_identity_unavailable" || !maintenanceRetryable(error))
30
+ throw error;
31
+ last = error;
32
+ renewalFailure(episode);
33
+ if (!(remaining() > 0))
34
+ throw error;
35
+ await delay(Math.min(renewalDelay(episode), remaining()));
36
+ }
37
+ }
38
+ }
39
+ return { observe, birth: (assertCurrent) => observe(() => {
40
+ const started = processStartedAt(process.pid);
41
+ if (!started)
42
+ throw unavailable();
43
+ return { pid: process.pid, pid_started_at: started };
44
+ }, assertCurrent) };
45
+ }
@@ -0,0 +1,97 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import { AppError } from "../shared/errors.js";
4
+ import { projectRuntimeRoot, resolveProjectRoot } from "../storage/paths.js";
5
+ import { formatFullId, parseFullEntityId, slugFromRoot } from "../domain/entity-ids.js";
6
+ import { appendAudit } from "./audit.js";
7
+ /** Resolve filesystem identity before entering a composite SQL writer. */
8
+ export class PreparedProjectRegistration {
9
+ #root;
10
+ constructor(root) { this.#root = root; Object.freeze(this); }
11
+ static prepare(input) {
12
+ return new PreparedProjectRegistration(resolveProjectRoot(input.root));
13
+ }
14
+ root() { return this.#root; }
15
+ }
16
+ export function registerProject(context, input) {
17
+ return registerPreparedProject(context, PreparedProjectRegistration.prepare(input));
18
+ }
19
+ /** Metadata-only registration; the composite caller owns its SQL transaction. */
20
+ export function registerPreparedProject(context, prepared) {
21
+ if (!(prepared instanceof PreparedProjectRegistration))
22
+ throw new AppError("validation", "Project registration must be prepared", 2);
23
+ const root = prepared.root();
24
+ const existing = context.db.get("SELECT * FROM projects WHERE root = ?", [root]);
25
+ const now = context.now();
26
+ const project = existing ?? newProjectRecord(context, root, now);
27
+ if (existing) {
28
+ context.db.run("UPDATE projects SET status = 'active', updated_at = ? WHERE id = ?", [now, existing.id]);
29
+ }
30
+ else {
31
+ context.db.run("INSERT INTO projects (id, short_id, slug, root, status, state_root, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?)", [
32
+ project.id,
33
+ project.short_id,
34
+ project.slug,
35
+ project.root,
36
+ project.status,
37
+ project.state_root,
38
+ project.created_at,
39
+ project.updated_at
40
+ ]);
41
+ appendAudit(context, {
42
+ projectId: project.id,
43
+ eventType: "project.registered",
44
+ payload: { project_id: project.id, root }
45
+ });
46
+ }
47
+ return { ok: true, project: { ...project, status: "active", updated_at: now }, db_path: context.db.path };
48
+ }
49
+ export function normalizeStoredRoot(root) {
50
+ const absolute = path.resolve(root);
51
+ if (fs.existsSync(absolute)) {
52
+ return fs.realpathSync(absolute);
53
+ }
54
+ const parent = path.dirname(absolute);
55
+ if (fs.existsSync(parent)) {
56
+ return path.join(fs.realpathSync(parent), path.basename(absolute));
57
+ }
58
+ return absolute;
59
+ }
60
+ export function findProjectByRoot(context, root) {
61
+ return context.db.get("SELECT * FROM projects WHERE root = ?", [root]);
62
+ }
63
+ export function requireProjectByRoot(context, root) {
64
+ // Callers may receive a path through a native adapter or shell. Resolve
65
+ // aliases such as macOS `/var` -> `/private/var` before comparing with the
66
+ // canonical root persisted in the project registry.
67
+ const canonical = normalizeStoredRoot(root);
68
+ const project = findProjectByRoot(context, canonical);
69
+ if (!project) {
70
+ throw new AppError("not_found", `Project is not registered: ${canonical}`, 1);
71
+ }
72
+ return project;
73
+ }
74
+ export function newProjectRecord(context, root, now) {
75
+ const slug = slugFromRoot(root);
76
+ const id = nextFullProjectId(context, slug);
77
+ const { shortId } = parseFullEntityId(id);
78
+ return {
79
+ id,
80
+ short_id: shortId,
81
+ slug,
82
+ root,
83
+ status: "active",
84
+ state_root: projectRuntimeRoot(context.ddFlowHome, id),
85
+ created_at: now,
86
+ updated_at: now
87
+ };
88
+ }
89
+ export function nextFullProjectId(context, slug) {
90
+ const rows = context.db.all("SELECT id FROM projects WHERE id LIKE 'PRJ-%'");
91
+ const used = rows
92
+ .map((row) => /^PRJ-(\d{3})-/.exec(row.id)?.[1])
93
+ .filter((value) => Boolean(value))
94
+ .map((value) => Number(value));
95
+ const next = Math.max(0, ...used) + 1;
96
+ return formatFullId("PRJ", next, slug);
97
+ }
@@ -5,7 +5,7 @@ import { ensureDir } from "../storage/paths.js";
5
5
  import { getCliVersionReport } from "./build-info.js";
6
6
  import { projectDashboardHtmlPath, projectSummaryJsonPath } from "./config.js";
7
7
  import { queueForProject } from "./merge-queue.js";
8
- import { activeFlowSessionsForProject } from "./sessions.js";
8
+ import { activeFlowSessionsForProject } from "./hook-session-observation.js";
9
9
  import { getProjectVersionStatus } from "./version-status.js";
10
10
  export function projectSummaryPath(context, projectId) {
11
11
  return projectSummaryJsonPath(context, projectId);
@@ -1,58 +1,17 @@
1
- import fs from "node:fs";
2
1
  import path from "node:path";
3
2
  import { foreignKeyTransaction } from "../storage/foreign-key-transaction.js";
4
- import { formatFullId, isFullEntityId, isShortEntityId, parseFullEntityId, slugFromRoot } from "../domain/entity-ids.js";
3
+ import { isFullEntityId, isShortEntityId, parseFullEntityId } from "../domain/entity-ids.js";
5
4
  import { AppError } from "../shared/errors.js";
6
5
  import { projectRuntimeRoot, resolveProjectRoot } from "../storage/paths.js";
7
6
  import { appendAudit } from "./audit.js";
7
+ import { registerProject, registerPreparedProject, PreparedProjectRegistration, normalizeStoredRoot, findProjectByRoot, requireProjectByRoot, newProjectRecord } from "./project-registry.js";
8
+ export { registerProject, registerPreparedProject, PreparedProjectRegistration, findProjectByRoot, requireProjectByRoot };
8
9
  import { activeCodexSessionBindingsForProject, activeFlowSessionBindingsForProject, codexHomeProfilesForProject, codexHookEventsForProject, hookStatusForProject } from "./hooks.js";
9
10
  import { dashboardMarkdownPath, globalDashboardMarkdownPath, readProjectConfig } from "./config.js";
10
11
  import { loadProjectFlowContract } from "../domain/flow-contract.js";
11
12
  import { buildStaticFlowGuidance } from "./flow-guidance.js";
12
13
  import { normalizeProtocolLifecycle } from "./protocol-lifecycle.js";
13
14
  import { buildProjectSummary, projectRegistrySummary, readPublishedProjectSummary } from "./project-summary.js";
14
- /** Resolve filesystem identity before entering a composite SQL writer. */
15
- export class PreparedProjectRegistration {
16
- #root;
17
- constructor(root) { this.#root = root; Object.freeze(this); }
18
- static prepare(input) {
19
- return new PreparedProjectRegistration(resolveProjectRoot(input.root));
20
- }
21
- root() { return this.#root; }
22
- }
23
- export function registerProject(context, input) {
24
- return registerPreparedProject(context, PreparedProjectRegistration.prepare(input));
25
- }
26
- /** Metadata-only registration; the composite caller owns its SQL transaction. */
27
- export function registerPreparedProject(context, prepared) {
28
- if (!(prepared instanceof PreparedProjectRegistration))
29
- throw new AppError("validation", "Project registration must be prepared", 2);
30
- const root = prepared.root();
31
- const existing = context.db.get("SELECT * FROM projects WHERE root = ?", [root]);
32
- const now = context.now();
33
- const project = existing ?? newProjectRecord(context, root, now);
34
- if (existing) {
35
- context.db.run("UPDATE projects SET status = 'active', updated_at = ? WHERE id = ?", [now, existing.id]);
36
- }
37
- else {
38
- context.db.run("INSERT INTO projects (id, short_id, slug, root, status, state_root, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?)", [
39
- project.id,
40
- project.short_id,
41
- project.slug,
42
- project.root,
43
- project.status,
44
- project.state_root,
45
- project.created_at,
46
- project.updated_at
47
- ]);
48
- appendAudit(context, {
49
- projectId: project.id,
50
- eventType: "project.registered",
51
- payload: { project_id: project.id, root }
52
- });
53
- }
54
- return { ok: true, project: { ...project, status: "active", updated_at: now }, db_path: context.db.path };
55
- }
56
15
  export function prepareProjectArchive(context, input) {
57
16
  if (Boolean(input.idOrAlias) === Boolean(input.root))
58
17
  throw new AppError("usage", "Project archive requires exactly one project ID/alias or --root", 2);
@@ -92,17 +51,6 @@ export function archiveProject(context, input) {
92
51
  ...(result.changes === 1 ? {} : { skipped: true, reason: "already_archived" })
93
52
  };
94
53
  }
95
- function normalizeStoredRoot(root) {
96
- const absolute = path.resolve(root);
97
- if (fs.existsSync(absolute)) {
98
- return fs.realpathSync(absolute);
99
- }
100
- const parent = path.dirname(absolute);
101
- if (fs.existsSync(parent)) {
102
- return path.join(fs.realpathSync(parent), path.basename(absolute));
103
- }
104
- return absolute;
105
- }
106
54
  export function resolveProject(context, input) {
107
55
  const candidates = resolveProjectCandidates(context, input.idOrAlias);
108
56
  if (candidates.length === 1) {
@@ -244,35 +192,6 @@ export function getProjectStatus(context, input) {
244
192
  }
245
193
  };
246
194
  }
247
- export function findProjectByRoot(context, root) {
248
- return context.db.get("SELECT * FROM projects WHERE root = ?", [root]);
249
- }
250
- export function requireProjectByRoot(context, root) {
251
- // Callers may receive a path through a native adapter or shell. Resolve
252
- // aliases such as macOS `/var` -> `/private/var` before comparing with the
253
- // canonical root persisted in the project registry.
254
- const canonical = normalizeStoredRoot(root);
255
- const project = findProjectByRoot(context, canonical);
256
- if (!project) {
257
- throw new AppError("not_found", `Project is not registered: ${canonical}`, 1);
258
- }
259
- return project;
260
- }
261
- function newProjectRecord(context, root, now) {
262
- const slug = slugFromRoot(root);
263
- const id = nextFullProjectId(context, slug);
264
- const { shortId } = parseFullEntityId(id);
265
- return {
266
- id,
267
- short_id: shortId,
268
- slug,
269
- root,
270
- status: "active",
271
- state_root: projectRuntimeRoot(context.ddFlowHome, id),
272
- created_at: now,
273
- updated_at: now
274
- };
275
- }
276
195
  function backfilledTypedProjectRecord(context, project, typed, now) {
277
196
  return {
278
197
  ...project,
@@ -282,15 +201,6 @@ function backfilledTypedProjectRecord(context, project, typed, now) {
282
201
  updated_at: now
283
202
  };
284
203
  }
285
- function nextFullProjectId(context, slug) {
286
- const rows = context.db.all("SELECT id FROM projects WHERE id LIKE 'PRJ-%'");
287
- const used = rows
288
- .map((row) => /^PRJ-(\d{3})-/.exec(row.id)?.[1])
289
- .filter((value) => Boolean(value))
290
- .map((value) => Number(value));
291
- const next = Math.max(0, ...used) + 1;
292
- return formatFullId("PRJ", next, slug);
293
- }
294
204
  function typedProjectMetadata(project) {
295
205
  if (!isFullEntityId(project.id)) {
296
206
  return null;