@lwelliott/cortex-cli 1.0.3

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 (335) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +159 -0
  3. package/dist/cli/audit.d.ts +3 -0
  4. package/dist/cli/audit.d.ts.map +1 -0
  5. package/dist/cli/audit.js +78 -0
  6. package/dist/cli/audit.js.map +1 -0
  7. package/dist/cli/doc.d.ts +3 -0
  8. package/dist/cli/doc.d.ts.map +1 -0
  9. package/dist/cli/doc.js +291 -0
  10. package/dist/cli/doc.js.map +1 -0
  11. package/dist/cli/entity.d.ts +3 -0
  12. package/dist/cli/entity.d.ts.map +1 -0
  13. package/dist/cli/entity.js +135 -0
  14. package/dist/cli/entity.js.map +1 -0
  15. package/dist/cli/format-selector.d.ts +30 -0
  16. package/dist/cli/format-selector.d.ts.map +1 -0
  17. package/dist/cli/format-selector.js +101 -0
  18. package/dist/cli/format-selector.js.map +1 -0
  19. package/dist/cli/formatters/audit-formatter.d.ts +33 -0
  20. package/dist/cli/formatters/audit-formatter.d.ts.map +1 -0
  21. package/dist/cli/formatters/audit-formatter.js +204 -0
  22. package/dist/cli/formatters/audit-formatter.js.map +1 -0
  23. package/dist/cli/formatters/json.d.ts +10 -0
  24. package/dist/cli/formatters/json.d.ts.map +1 -0
  25. package/dist/cli/formatters/json.js +31 -0
  26. package/dist/cli/formatters/json.js.map +1 -0
  27. package/dist/cli/formatters/markdown.d.ts +12 -0
  28. package/dist/cli/formatters/markdown.d.ts.map +1 -0
  29. package/dist/cli/formatters/markdown.js +341 -0
  30. package/dist/cli/formatters/markdown.js.map +1 -0
  31. package/dist/cli/formatters/quiet.d.ts +15 -0
  32. package/dist/cli/formatters/quiet.d.ts.map +1 -0
  33. package/dist/cli/formatters/quiet.js +75 -0
  34. package/dist/cli/formatters/quiet.js.map +1 -0
  35. package/dist/cli/formatters/table.d.ts +11 -0
  36. package/dist/cli/formatters/table.d.ts.map +1 -0
  37. package/dist/cli/formatters/table.js +726 -0
  38. package/dist/cli/formatters/table.js.map +1 -0
  39. package/dist/cli/graph.d.ts +3 -0
  40. package/dist/cli/graph.d.ts.map +1 -0
  41. package/dist/cli/graph.js +209 -0
  42. package/dist/cli/graph.js.map +1 -0
  43. package/dist/cli/index.d.ts +3 -0
  44. package/dist/cli/index.d.ts.map +1 -0
  45. package/dist/cli/index.js +144 -0
  46. package/dist/cli/index.js.map +1 -0
  47. package/dist/cli/lesson.d.ts +3 -0
  48. package/dist/cli/lesson.d.ts.map +1 -0
  49. package/dist/cli/lesson.js +85 -0
  50. package/dist/cli/lesson.js.map +1 -0
  51. package/dist/cli/project-resolver.d.ts +17 -0
  52. package/dist/cli/project-resolver.d.ts.map +1 -0
  53. package/dist/cli/project-resolver.js +25 -0
  54. package/dist/cli/project-resolver.js.map +1 -0
  55. package/dist/cli/project.d.ts +12 -0
  56. package/dist/cli/project.d.ts.map +1 -0
  57. package/dist/cli/project.js +169 -0
  58. package/dist/cli/project.js.map +1 -0
  59. package/dist/cli/relation.d.ts +3 -0
  60. package/dist/cli/relation.d.ts.map +1 -0
  61. package/dist/cli/relation.js +128 -0
  62. package/dist/cli/relation.js.map +1 -0
  63. package/dist/cli/review.d.ts +3 -0
  64. package/dist/cli/review.d.ts.map +1 -0
  65. package/dist/cli/review.js +131 -0
  66. package/dist/cli/review.js.map +1 -0
  67. package/dist/cli/router.d.ts +38 -0
  68. package/dist/cli/router.d.ts.map +1 -0
  69. package/dist/cli/router.js +49 -0
  70. package/dist/cli/router.js.map +1 -0
  71. package/dist/cli/schema.d.ts +3 -0
  72. package/dist/cli/schema.d.ts.map +1 -0
  73. package/dist/cli/schema.js +94 -0
  74. package/dist/cli/schema.js.map +1 -0
  75. package/dist/cli/sprint.d.ts +12 -0
  76. package/dist/cli/sprint.d.ts.map +1 -0
  77. package/dist/cli/sprint.js +200 -0
  78. package/dist/cli/sprint.js.map +1 -0
  79. package/dist/cli/task.d.ts +12 -0
  80. package/dist/cli/task.d.ts.map +1 -0
  81. package/dist/cli/task.js +252 -0
  82. package/dist/cli/task.js.map +1 -0
  83. package/dist/cli/test-report.d.ts +3 -0
  84. package/dist/cli/test-report.d.ts.map +1 -0
  85. package/dist/cli/test-report.js +134 -0
  86. package/dist/cli/test-report.js.map +1 -0
  87. package/dist/cli/validate.d.ts +8 -0
  88. package/dist/cli/validate.d.ts.map +1 -0
  89. package/dist/cli/validate.js +86 -0
  90. package/dist/cli/validate.js.map +1 -0
  91. package/dist/config/config.d.ts +18 -0
  92. package/dist/config/config.d.ts.map +1 -0
  93. package/dist/config/config.js +70 -0
  94. package/dist/config/config.js.map +1 -0
  95. package/dist/config/registry.d.ts +52 -0
  96. package/dist/config/registry.d.ts.map +1 -0
  97. package/dist/config/registry.js +193 -0
  98. package/dist/config/registry.js.map +1 -0
  99. package/dist/config/resolver.d.ts +26 -0
  100. package/dist/config/resolver.d.ts.map +1 -0
  101. package/dist/config/resolver.js +87 -0
  102. package/dist/config/resolver.js.map +1 -0
  103. package/dist/db/builtin-schema-constants.d.ts +109 -0
  104. package/dist/db/builtin-schema-constants.d.ts.map +1 -0
  105. package/dist/db/builtin-schema-constants.js +32 -0
  106. package/dist/db/builtin-schema-constants.js.map +1 -0
  107. package/dist/db/connection.d.ts +34 -0
  108. package/dist/db/connection.d.ts.map +1 -0
  109. package/dist/db/connection.js +140 -0
  110. package/dist/db/connection.js.map +1 -0
  111. package/dist/db/generated/schema-types.d.ts +134 -0
  112. package/dist/db/generated/schema-types.d.ts.map +1 -0
  113. package/dist/db/generated/schema-types.js +31 -0
  114. package/dist/db/generated/schema-types.js.map +1 -0
  115. package/dist/db/locks.d.ts +106 -0
  116. package/dist/db/locks.d.ts.map +1 -0
  117. package/dist/db/locks.js +211 -0
  118. package/dist/db/locks.js.map +1 -0
  119. package/dist/db/migration.d.ts +65 -0
  120. package/dist/db/migration.d.ts.map +1 -0
  121. package/dist/db/migration.js +233 -0
  122. package/dist/db/migration.js.map +1 -0
  123. package/dist/db/retry.d.ts +26 -0
  124. package/dist/db/retry.d.ts.map +1 -0
  125. package/dist/db/retry.js +71 -0
  126. package/dist/db/retry.js.map +1 -0
  127. package/dist/db/schema.d.ts +21 -0
  128. package/dist/db/schema.d.ts.map +1 -0
  129. package/dist/db/schema.js +830 -0
  130. package/dist/db/schema.js.map +1 -0
  131. package/dist/models/common.d.ts +27 -0
  132. package/dist/models/common.d.ts.map +1 -0
  133. package/dist/models/common.js +4 -0
  134. package/dist/models/common.js.map +1 -0
  135. package/dist/models/document.d.ts +89 -0
  136. package/dist/models/document.d.ts.map +1 -0
  137. package/dist/models/document.js +5 -0
  138. package/dist/models/document.js.map +1 -0
  139. package/dist/models/entity-model.d.ts +30 -0
  140. package/dist/models/entity-model.d.ts.map +1 -0
  141. package/dist/models/entity-model.js +99 -0
  142. package/dist/models/entity-model.js.map +1 -0
  143. package/dist/models/entity.d.ts +88 -0
  144. package/dist/models/entity.d.ts.map +1 -0
  145. package/dist/models/entity.js +5 -0
  146. package/dist/models/entity.js.map +1 -0
  147. package/dist/models/graph-schema.d.ts +43 -0
  148. package/dist/models/graph-schema.d.ts.map +1 -0
  149. package/dist/models/graph-schema.js +76 -0
  150. package/dist/models/graph-schema.js.map +1 -0
  151. package/dist/models/project.d.ts +49 -0
  152. package/dist/models/project.d.ts.map +1 -0
  153. package/dist/models/project.js +18 -0
  154. package/dist/models/project.js.map +1 -0
  155. package/dist/models/relation.d.ts +35 -0
  156. package/dist/models/relation.d.ts.map +1 -0
  157. package/dist/models/relation.js +212 -0
  158. package/dist/models/relation.js.map +1 -0
  159. package/dist/models/review.d.ts +86 -0
  160. package/dist/models/review.d.ts.map +1 -0
  161. package/dist/models/review.js +5 -0
  162. package/dist/models/review.js.map +1 -0
  163. package/dist/models/sprint.d.ts +38 -0
  164. package/dist/models/sprint.d.ts.map +1 -0
  165. package/dist/models/sprint.js +4 -0
  166. package/dist/models/sprint.js.map +1 -0
  167. package/dist/models/task.d.ts +75 -0
  168. package/dist/models/task.d.ts.map +1 -0
  169. package/dist/models/task.js +4 -0
  170. package/dist/models/task.js.map +1 -0
  171. package/dist/models/validation.d.ts +25 -0
  172. package/dist/models/validation.d.ts.map +1 -0
  173. package/dist/models/validation.js +4 -0
  174. package/dist/models/validation.js.map +1 -0
  175. package/dist/services/audit-check-service.d.ts +16 -0
  176. package/dist/services/audit-check-service.d.ts.map +1 -0
  177. package/dist/services/audit-check-service.js +137 -0
  178. package/dist/services/audit-check-service.js.map +1 -0
  179. package/dist/services/audit-checks/dangling-relations-check.d.ts +7 -0
  180. package/dist/services/audit-checks/dangling-relations-check.d.ts.map +1 -0
  181. package/dist/services/audit-checks/dangling-relations-check.js +52 -0
  182. package/dist/services/audit-checks/dangling-relations-check.js.map +1 -0
  183. package/dist/services/audit-checks/duplicate-ids-check.d.ts +4 -0
  184. package/dist/services/audit-checks/duplicate-ids-check.d.ts.map +1 -0
  185. package/dist/services/audit-checks/duplicate-ids-check.js +49 -0
  186. package/dist/services/audit-checks/duplicate-ids-check.js.map +1 -0
  187. package/dist/services/audit-checks/fix-engine.d.ts +24 -0
  188. package/dist/services/audit-checks/fix-engine.d.ts.map +1 -0
  189. package/dist/services/audit-checks/fix-engine.js +114 -0
  190. package/dist/services/audit-checks/fix-engine.js.map +1 -0
  191. package/dist/services/audit-checks/fr-nfr-to-fs-nfs-check.d.ts +9 -0
  192. package/dist/services/audit-checks/fr-nfr-to-fs-nfs-check.d.ts.map +1 -0
  193. package/dist/services/audit-checks/fr-nfr-to-fs-nfs-check.js +56 -0
  194. package/dist/services/audit-checks/fr-nfr-to-fs-nfs-check.js.map +1 -0
  195. package/dist/services/audit-checks/fs-nfs-to-sad-ad-qa-check.d.ts +10 -0
  196. package/dist/services/audit-checks/fs-nfs-to-sad-ad-qa-check.d.ts.map +1 -0
  197. package/dist/services/audit-checks/fs-nfs-to-sad-ad-qa-check.js +57 -0
  198. package/dist/services/audit-checks/fs-nfs-to-sad-ad-qa-check.js.map +1 -0
  199. package/dist/services/audit-checks/fs-nfs-to-ts-check.d.ts +9 -0
  200. package/dist/services/audit-checks/fs-nfs-to-ts-check.d.ts.map +1 -0
  201. package/dist/services/audit-checks/fs-nfs-to-ts-check.js +56 -0
  202. package/dist/services/audit-checks/fs-nfs-to-ts-check.js.map +1 -0
  203. package/dist/services/audit-checks/inverse-consistency-check.d.ts +7 -0
  204. package/dist/services/audit-checks/inverse-consistency-check.d.ts.map +1 -0
  205. package/dist/services/audit-checks/inverse-consistency-check.js +54 -0
  206. package/dist/services/audit-checks/inverse-consistency-check.js.map +1 -0
  207. package/dist/services/audit-checks/mapping-check.d.ts +4 -0
  208. package/dist/services/audit-checks/mapping-check.d.ts.map +1 -0
  209. package/dist/services/audit-checks/mapping-check.js +74 -0
  210. package/dist/services/audit-checks/mapping-check.js.map +1 -0
  211. package/dist/services/audit-checks/orphan-documents-check.d.ts +9 -0
  212. package/dist/services/audit-checks/orphan-documents-check.d.ts.map +1 -0
  213. package/dist/services/audit-checks/orphan-documents-check.js +54 -0
  214. package/dist/services/audit-checks/orphan-documents-check.js.map +1 -0
  215. package/dist/services/audit-checks/orphan-entities-check.d.ts +4 -0
  216. package/dist/services/audit-checks/orphan-entities-check.d.ts.map +1 -0
  217. package/dist/services/audit-checks/orphan-entities-check.js +39 -0
  218. package/dist/services/audit-checks/orphan-entities-check.js.map +1 -0
  219. package/dist/services/audit-checks/sad-ad-qa-tc-to-src-check.d.ts +9 -0
  220. package/dist/services/audit-checks/sad-ad-qa-tc-to-src-check.d.ts.map +1 -0
  221. package/dist/services/audit-checks/sad-ad-qa-tc-to-src-check.js +56 -0
  222. package/dist/services/audit-checks/sad-ad-qa-tc-to-src-check.js.map +1 -0
  223. package/dist/services/audit-checks/sprint-consistency-check.d.ts +4 -0
  224. package/dist/services/audit-checks/sprint-consistency-check.d.ts.map +1 -0
  225. package/dist/services/audit-checks/sprint-consistency-check.js +80 -0
  226. package/dist/services/audit-checks/sprint-consistency-check.js.map +1 -0
  227. package/dist/services/audit-checks/src-filesystem-only-check.d.ts +10 -0
  228. package/dist/services/audit-checks/src-filesystem-only-check.d.ts.map +1 -0
  229. package/dist/services/audit-checks/src-filesystem-only-check.js +106 -0
  230. package/dist/services/audit-checks/src-filesystem-only-check.js.map +1 -0
  231. package/dist/services/audit-checks/ts-to-tc-check.d.ts +10 -0
  232. package/dist/services/audit-checks/ts-to-tc-check.d.ts.map +1 -0
  233. package/dist/services/audit-checks/ts-to-tc-check.js +58 -0
  234. package/dist/services/audit-checks/ts-to-tc-check.js.map +1 -0
  235. package/dist/services/audit-checks/type-mismatch-check.d.ts +4 -0
  236. package/dist/services/audit-checks/type-mismatch-check.d.ts.map +1 -0
  237. package/dist/services/audit-checks/type-mismatch-check.js +55 -0
  238. package/dist/services/audit-checks/type-mismatch-check.js.map +1 -0
  239. package/dist/services/audit-checks/types.d.ts +66 -0
  240. package/dist/services/audit-checks/types.d.ts.map +1 -0
  241. package/dist/services/audit-checks/types.js +30 -0
  242. package/dist/services/audit-checks/types.js.map +1 -0
  243. package/dist/services/audit-checks/us-to-fr-nfr-check.d.ts +9 -0
  244. package/dist/services/audit-checks/us-to-fr-nfr-check.d.ts.map +1 -0
  245. package/dist/services/audit-checks/us-to-fr-nfr-check.js +56 -0
  246. package/dist/services/audit-checks/us-to-fr-nfr-check.js.map +1 -0
  247. package/dist/services/doc-entity-mapping.d.ts +19 -0
  248. package/dist/services/doc-entity-mapping.d.ts.map +1 -0
  249. package/dist/services/doc-entity-mapping.js +35 -0
  250. package/dist/services/doc-entity-mapping.js.map +1 -0
  251. package/dist/services/document-service.d.ts +58 -0
  252. package/dist/services/document-service.d.ts.map +1 -0
  253. package/dist/services/document-service.js +409 -0
  254. package/dist/services/document-service.js.map +1 -0
  255. package/dist/services/graph-export-service.d.ts +12 -0
  256. package/dist/services/graph-export-service.d.ts.map +1 -0
  257. package/dist/services/graph-export-service.js +112 -0
  258. package/dist/services/graph-export-service.js.map +1 -0
  259. package/dist/services/graph-service.d.ts +21 -0
  260. package/dist/services/graph-service.d.ts.map +1 -0
  261. package/dist/services/graph-service.js +49 -0
  262. package/dist/services/graph-service.js.map +1 -0
  263. package/dist/services/graph-validation-service.d.ts +13 -0
  264. package/dist/services/graph-validation-service.d.ts.map +1 -0
  265. package/dist/services/graph-validation-service.js +171 -0
  266. package/dist/services/graph-validation-service.js.map +1 -0
  267. package/dist/services/project.d.ts +80 -0
  268. package/dist/services/project.d.ts.map +1 -0
  269. package/dist/services/project.js +256 -0
  270. package/dist/services/project.js.map +1 -0
  271. package/dist/services/review-service.d.ts +81 -0
  272. package/dist/services/review-service.d.ts.map +1 -0
  273. package/dist/services/review-service.js +242 -0
  274. package/dist/services/review-service.js.map +1 -0
  275. package/dist/services/schema-migration-service.d.ts +44 -0
  276. package/dist/services/schema-migration-service.d.ts.map +1 -0
  277. package/dist/services/schema-migration-service.js +894 -0
  278. package/dist/services/schema-migration-service.js.map +1 -0
  279. package/dist/services/sprint.d.ts +71 -0
  280. package/dist/services/sprint.d.ts.map +1 -0
  281. package/dist/services/sprint.js +238 -0
  282. package/dist/services/sprint.js.map +1 -0
  283. package/dist/services/stage-validator.d.ts +36 -0
  284. package/dist/services/stage-validator.d.ts.map +1 -0
  285. package/dist/services/stage-validator.js +78 -0
  286. package/dist/services/stage-validator.js.map +1 -0
  287. package/dist/services/task.d.ts +75 -0
  288. package/dist/services/task.d.ts.map +1 -0
  289. package/dist/services/task.js +487 -0
  290. package/dist/services/task.js.map +1 -0
  291. package/dist/services/validation.d.ts +24 -0
  292. package/dist/services/validation.d.ts.map +1 -0
  293. package/dist/services/validation.js +53 -0
  294. package/dist/services/validation.js.map +1 -0
  295. package/dist/state-machines/sprint-state.d.ts +25 -0
  296. package/dist/state-machines/sprint-state.d.ts.map +1 -0
  297. package/dist/state-machines/sprint-state.js +64 -0
  298. package/dist/state-machines/sprint-state.js.map +1 -0
  299. package/dist/state-machines/task-state.d.ts +31 -0
  300. package/dist/state-machines/task-state.d.ts.map +1 -0
  301. package/dist/state-machines/task-state.js +83 -0
  302. package/dist/state-machines/task-state.js.map +1 -0
  303. package/dist/utils/errors.d.ts +673 -0
  304. package/dist/utils/errors.d.ts.map +1 -0
  305. package/dist/utils/errors.js +1447 -0
  306. package/dist/utils/errors.js.map +1 -0
  307. package/dist/utils/format.d.ts +52 -0
  308. package/dist/utils/format.d.ts.map +1 -0
  309. package/dist/utils/format.js +65 -0
  310. package/dist/utils/format.js.map +1 -0
  311. package/dist/validators/board-integrity.d.ts +25 -0
  312. package/dist/validators/board-integrity.d.ts.map +1 -0
  313. package/dist/validators/board-integrity.js +102 -0
  314. package/dist/validators/board-integrity.js.map +1 -0
  315. package/dist/validators/board-shape.d.ts +13 -0
  316. package/dist/validators/board-shape.d.ts.map +1 -0
  317. package/dist/validators/board-shape.js +74 -0
  318. package/dist/validators/board-shape.js.map +1 -0
  319. package/dist/validators/owner-validator.d.ts +30 -0
  320. package/dist/validators/owner-validator.d.ts.map +1 -0
  321. package/dist/validators/owner-validator.js +70 -0
  322. package/dist/validators/owner-validator.js.map +1 -0
  323. package/dist/validators/spawn-legality.d.ts +19 -0
  324. package/dist/validators/spawn-legality.d.ts.map +1 -0
  325. package/dist/validators/spawn-legality.js +77 -0
  326. package/dist/validators/spawn-legality.js.map +1 -0
  327. package/dist/validators/state-field-checker.d.ts +8 -0
  328. package/dist/validators/state-field-checker.d.ts.map +1 -0
  329. package/dist/validators/state-field-checker.js +47 -0
  330. package/dist/validators/state-field-checker.js.map +1 -0
  331. package/dist/validators/state-field-recorder.d.ts +39 -0
  332. package/dist/validators/state-field-recorder.d.ts.map +1 -0
  333. package/dist/validators/state-field-recorder.js +62 -0
  334. package/dist/validators/state-field-recorder.js.map +1 -0
  335. package/package.json +53 -0
@@ -0,0 +1,894 @@
1
+ "use strict";
2
+ // Schema Migration Service — Sprint 31: 9-Phase Graph Schema Overhaul
3
+ // Implements SAD-0033: Schema Finalization — Drop entity_types Table, 18 Relation Types
4
+ // Implements SAD-0012: Schema-to-Graph CLI Migration (extended for Sprint 31)
5
+ // Implements FS-SS-031-0001 through FS-SS-031-0008 (all 8 Sprint 31 migration specs)
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.getAvailablePhases = getAvailablePhases;
8
+ exports.runPhase = runPhase;
9
+ exports.runMigration = runMigration;
10
+ exports.validatePreMigration = validatePreMigration;
11
+ exports.validatePostMigration = validatePostMigration;
12
+ const fs_1 = require("fs");
13
+ const path_1 = require("path");
14
+ const child_process_1 = require("child_process");
15
+ const retry_1 = require("../db/retry");
16
+ const errors_1 = require("../utils/errors");
17
+ // ─── Utility Functions ───
18
+ function measureElapsed(fn) {
19
+ const start = process.hrtime.bigint();
20
+ fn();
21
+ const end = process.hrtime.bigint();
22
+ return Number((end - start) / 1000000n);
23
+ }
24
+ // ─── Canonical Schema Definitions (post-Sprint-31 / builtin:3.0) ───
25
+ /** 18 canonical relation types (SAD-0033 §3, plan/project-schema.yaml) */
26
+ const NEW_RELATION_TYPES = {
27
+ // 8 NEW types (Phase 4)
28
+ decomposed_to: { from: ['SAD'], to: ['AD', 'QA'], cardinality: 'one_to_many' },
29
+ decomposes: { from: ['AD', 'QA'], to: ['SAD'], cardinality: 'many_to_one' },
30
+ divided_from: { from: ['TC'], to: ['TS'], cardinality: 'many_to_one' },
31
+ divided_into: { from: ['TC'], to: ['TS'], cardinality: 'many_to_one' },
32
+ fulfilled_by: { from: ['US'], to: ['FR', 'NFR'], cardinality: 'many_to_many' },
33
+ fulfills: { from: ['FR', 'NFR'], to: ['US'], cardinality: 'many_to_many' },
34
+ realized_by: { from: ['FR'], to: ['FS'], cardinality: 'many_to_many' },
35
+ realizes: { from: ['FS'], to: ['FR'], cardinality: 'many_to_many' },
36
+ };
37
+ /** 9 surviving relation types with UPDATED definitions (Phase 5) */
38
+ const SURVIVING_UPDATES = {
39
+ contained_in: { from: ['US', 'FR', 'NFR'], to: ['MDL'], cardinality: 'many_to_one' },
40
+ contains: { from: ['MDL'], to: ['US', 'FR', 'NFR'], cardinality: 'one_to_many' },
41
+ covered_by: { from: ['FS', 'NFS'], to: ['SAD', 'AD', 'QA'], cardinality: 'many_to_many' },
42
+ covers: { from: ['SAD', 'AD', 'QA'], to: ['FS', 'NFS'], cardinality: 'many_to_many' },
43
+ implemented_by: { from: ['SAD', 'AD', 'QA'], to: ['SRC'], cardinality: 'many_to_many' },
44
+ implements: { from: ['SRC'], to: ['SAD', 'AD', 'QA'], cardinality: 'many_to_many' },
45
+ verified_by: { from: ['FS', 'NFS'], to: ['TS'], cardinality: 'many_to_many' },
46
+ verifies: { from: ['TS'], to: ['FS', 'NFS'], cardinality: 'many_to_many' },
47
+ // depends_on: unchanged [MDL] → [MDL]; no update applied
48
+ };
49
+ /** depends_on + depended_on_by: unchanged from builtin:2.0 but listed for completeness */
50
+ const UNCHANGED_SURVIVORS = ['depends_on', 'depended_on_by'];
51
+ /** 17 relation types to DELETE in Phase 7 (SAD-0033 §3) */
52
+ const REMOVED_RELATION_TYPES = [
53
+ 'affected_by', 'affects', 'belongs_to', 'belongs_to_suite',
54
+ 'decided_by', 'decides', 'decomposed_from', 'decomposes_to',
55
+ 'governed_by', 'governs_inv', 'has_member', 'reviewed_by',
56
+ 'reviews', 'traced_from', 'traces_to', 'validated_by', 'validates',
57
+ ];
58
+ /** All 18 final relation type names */
59
+ const FINAL_RELATION_TYPE_NAMES = [
60
+ ...Object.keys(NEW_RELATION_TYPES),
61
+ ...Object.keys(SURVIVING_UPDATES),
62
+ ...UNCHANGED_SURVIVORS,
63
+ ];
64
+ // ─── Phase 1: Drop entity_types Table ───
65
+ function phase1_execute(db) {
66
+ const details = { entityTypesTableDropped: 0, entitiesTableRecreated: 0 };
67
+ // Check if entity_types table exists
68
+ const tableExists = db.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name='entity_types'").get();
69
+ if (tableExists) {
70
+ // SQLite does not support ALTER TABLE DROP CONSTRAINT.
71
+ // The entities table has a FK: type_name REFERENCES entity_types(name).
72
+ // We must recreate entities without the FK before dropping entity_types.
73
+ // (FK enforcement is already OFF for the entire migration.)
74
+ db.exec(`
75
+ CREATE TABLE entities_new (
76
+ id TEXT PRIMARY KEY,
77
+ type_name TEXT NOT NULL,
78
+ properties TEXT NOT NULL DEFAULT '{}',
79
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
80
+ updated_at TEXT NOT NULL DEFAULT (datetime('now'))
81
+ );
82
+ INSERT INTO entities_new SELECT id, type_name, properties, created_at, updated_at FROM entities;
83
+ DROP TABLE entities;
84
+ ALTER TABLE entities_new RENAME TO entities;
85
+ CREATE INDEX idx_entities_type ON entities(type_name);
86
+ `);
87
+ details.entitiesTableRecreated = 1;
88
+ db.exec('DROP TABLE IF EXISTS entity_types');
89
+ details.entityTypesTableDropped = 1;
90
+ }
91
+ else {
92
+ details.entityTypesTableDropped = 0;
93
+ details.entitiesTableRecreated = 0;
94
+ }
95
+ return details;
96
+ }
97
+ function phase1_validatePre(db) {
98
+ const tableExists = db.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name='entity_types'").get();
99
+ return !!tableExists;
100
+ }
101
+ function phase1_validatePost(db) {
102
+ const tableExists = db.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name='entity_types'").get();
103
+ return !tableExists;
104
+ }
105
+ // ─── Phase 2: Delete TR Graph Entities and Relations ───
106
+ function phase2_execute(db) {
107
+ const details = { trEntitiesDeleted: 0, trRelationsDeleted: 0, testReportRowsPreserved: 0 };
108
+ // Count TR entities
109
+ const trEntityCount = db.prepare("SELECT COUNT(*) as cnt FROM entities WHERE type_name = 'TR'").get().cnt;
110
+ if (trEntityCount === 0) {
111
+ return details; // Nothing to do — idempotent
112
+ }
113
+ // Delete all relations referencing TR entities
114
+ const relResult1 = db.prepare("DELETE FROM relations WHERE from_id IN (SELECT id FROM entities WHERE type_name = 'TR') OR to_id IN (SELECT id FROM entities WHERE type_name = 'TR')").run();
115
+ details.trRelationsDeleted = relResult1.changes;
116
+ // Also delete any relations referencing TR entities by ID
117
+ const trEntityIds = db.prepare("SELECT id FROM entities WHERE type_name = 'TR'").all();
118
+ for (const e of trEntityIds) {
119
+ db.prepare('DELETE FROM relations WHERE from_id = ? OR to_id = ?').run(e.id, e.id);
120
+ }
121
+ // Delete TR entities
122
+ const entityResult = db.prepare("DELETE FROM entities WHERE type_name = 'TR'").run();
123
+ details.trEntitiesDeleted = entityResult.changes;
124
+ // Count preserved test_reports rows
125
+ const testReportCount = db.prepare('SELECT COUNT(*) as cnt FROM test_reports').get().cnt;
126
+ details.testReportsRowsPreserved = testReportCount;
127
+ return details;
128
+ }
129
+ function phase2_validatePre(db) {
130
+ const trCount = db.prepare("SELECT COUNT(*) as cnt FROM entities WHERE type_name = 'TR'").get().cnt;
131
+ return trCount > 0;
132
+ }
133
+ function phase2_validatePost(db) {
134
+ const trCount = db.prepare("SELECT COUNT(*) as cnt FROM entities WHERE type_name = 'TR'").get().cnt;
135
+ return trCount === 0;
136
+ }
137
+ // ─── Phase 3: Migrate URD Documents to US/FR/NFR ───
138
+ function determineUrdReplacementType(title, content) {
139
+ const titleLower = (title || '').toLowerCase();
140
+ const contentLower = (content || '').toLowerCase();
141
+ // NFR pattern: non-functional, performance, NFR prefix
142
+ if (titleLower.includes('non-functional') || titleLower.includes('non functional') ||
143
+ titleLower.startsWith('nfr-') || titleLower.includes('nfr ') ||
144
+ contentLower.includes('non-functional requirement') ||
145
+ titleLower.includes('performance') ||
146
+ titleLower.includes('latency') || titleLower.includes('throughput') ||
147
+ titleLower.includes('concurrency')) {
148
+ return 'NFR';
149
+ }
150
+ // FR pattern: functional requirement, FR prefix
151
+ if (titleLower.includes('functional requirement') ||
152
+ titleLower.startsWith('fr-') || titleLower.includes('fr ') ||
153
+ contentLower.includes('functional requirement') ||
154
+ titleLower.includes(' bug ') || titleLower.includes('fix') ||
155
+ titleLower.includes('error') || titleLower.includes('validation') ||
156
+ titleLower.includes('command') || titleLower.includes('search')) {
157
+ return 'FR';
158
+ }
159
+ // US pattern: user story, US prefix
160
+ if (titleLower.includes('user story') || titleLower.includes('userstory') ||
161
+ titleLower.startsWith('us-') || titleLower.includes('us ')) {
162
+ return 'US';
163
+ }
164
+ // Default: US as catch-all (most URD docs are user story level)
165
+ return 'US';
166
+ }
167
+ function phase3_execute(db, _projectRoot) {
168
+ const details = {
169
+ urdDocsMigrated: 0,
170
+ migratedToUS: 0,
171
+ migratedToFR: 0,
172
+ migratedToNFR: 0,
173
+ };
174
+ // Find all URD documents
175
+ const urdDocs = db.prepare("SELECT d.id, d.title, d.type FROM documents d WHERE d.type = 'URD'").all();
176
+ if (urdDocs.length === 0) {
177
+ return details; // Nothing to do — idempotent
178
+ }
179
+ // Delete any existing URD entities (cascading relations)
180
+ db.prepare("DELETE FROM relations WHERE from_id IN (SELECT id FROM entities WHERE type_name = 'URD') OR to_id IN (SELECT id FROM entities WHERE type_name = 'URD')").run();
181
+ const urdEntities = db.prepare("SELECT id FROM entities WHERE type_name = 'URD'").all();
182
+ for (const e of urdEntities) {
183
+ db.prepare('DELETE FROM relations WHERE from_id = ? OR to_id = ?').run(e.id, e.id);
184
+ }
185
+ db.prepare("DELETE FROM entities WHERE type_name = 'URD'").run();
186
+ // Get content for each URD doc and determine replacement type
187
+ for (const doc of urdDocs) {
188
+ const contentRow = db.prepare('SELECT content FROM document_contents WHERE document_id = ?').get(doc.id);
189
+ const replacementType = determineUrdReplacementType(doc.title, contentRow?.content ?? null);
190
+ // Generate new ID: TYPE-NNNN where NNNN is next sequential number
191
+ const maxIdRow = db.prepare("SELECT MAX(CAST(SUBSTR(id, INSTR(id, '-') + 1) AS INTEGER)) as maxNum FROM documents WHERE type = ?").get(replacementType);
192
+ const nextNum = ((maxIdRow?.maxNum ?? 0) + 1);
193
+ const newId = `${replacementType}-${String(nextNum).padStart(4, '0')}`;
194
+ // Update document type and ID
195
+ db.prepare('UPDATE documents SET type = ?, id = ? WHERE id = ?').run(replacementType, newId, doc.id);
196
+ // Update content FK
197
+ db.prepare('UPDATE document_contents SET document_id = ? WHERE document_id = ?').run(newId, doc.id);
198
+ details.urdDocsMigrated++;
199
+ if (replacementType === 'US')
200
+ details.migratedToUS++;
201
+ else if (replacementType === 'FR')
202
+ details.migratedToFR++;
203
+ else
204
+ details.migratedToNFR++;
205
+ }
206
+ return details;
207
+ }
208
+ function phase3_validatePre(db) {
209
+ const urdCount = db.prepare("SELECT COUNT(*) as cnt FROM documents WHERE type = 'URD'").get().cnt;
210
+ return urdCount > 0;
211
+ }
212
+ function phase3_validatePost(db) {
213
+ const urdCount = db.prepare("SELECT COUNT(*) as cnt FROM documents WHERE type = 'URD'").get().cnt;
214
+ return urdCount === 0;
215
+ }
216
+ // ─── Phase 4: Add 8 New Relation Type Definitions ───
217
+ function phase4_execute(db) {
218
+ const details = { newRelationTypesAdded: 0, alreadyExisted: 0 };
219
+ const now = new Date().toISOString();
220
+ for (const [name, def] of Object.entries(NEW_RELATION_TYPES)) {
221
+ const existing = db.prepare('SELECT name FROM relation_types WHERE name = ?').get(name);
222
+ if (existing) {
223
+ details.alreadyExisted++;
224
+ continue;
225
+ }
226
+ db.prepare(`
227
+ INSERT INTO relation_types (name, from_types, to_types, cardinality, inverse_of, description, created_at, updated_at)
228
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)
229
+ `).run(name, JSON.stringify(def.from), JSON.stringify(def.to), def.cardinality, null, '', now, now);
230
+ details.newRelationTypesAdded++;
231
+ }
232
+ return details;
233
+ }
234
+ function phase4_validatePre(db) {
235
+ // Valid if not all 8 new types exist yet
236
+ let missing = 0;
237
+ for (const name of Object.keys(NEW_RELATION_TYPES)) {
238
+ const exists = db.prepare('SELECT name FROM relation_types WHERE name = ?').get(name);
239
+ if (!exists)
240
+ missing++;
241
+ }
242
+ return missing > 0;
243
+ }
244
+ function phase4_validatePost(db) {
245
+ let allPresent = true;
246
+ for (const name of Object.keys(NEW_RELATION_TYPES)) {
247
+ const exists = db.prepare('SELECT name FROM relation_types WHERE name = ?').get(name);
248
+ if (!exists)
249
+ allPresent = false;
250
+ }
251
+ return allPresent;
252
+ }
253
+ // ─── Phase 5: Update 9 Surviving Relation Type Definitions ───
254
+ function phase5_execute(db) {
255
+ const details = { relationTypesUpdated: 0 };
256
+ for (const [name, def] of Object.entries(SURVIVING_UPDATES)) {
257
+ const existing = db.prepare('SELECT name, from_types, to_types, cardinality FROM relation_types WHERE name = ?').get(name);
258
+ if (!existing)
259
+ continue;
260
+ const newFrom = JSON.stringify(def.from);
261
+ const newTo = JSON.stringify(def.to);
262
+ if (existing.from_types !== newFrom || existing.to_types !== newTo || existing.cardinality !== def.cardinality) {
263
+ db.prepare('UPDATE relation_types SET from_types = ?, to_types = ?, cardinality = ? WHERE name = ?').run(newFrom, newTo, def.cardinality, name);
264
+ details.relationTypesUpdated++;
265
+ }
266
+ }
267
+ return details;
268
+ }
269
+ function phase5_validatePre(_db) {
270
+ // Always allowed; idempotent
271
+ return true;
272
+ }
273
+ function phase5_validatePost(db) {
274
+ // Verify all surviving types match expected definitions
275
+ for (const [name, def] of Object.entries(SURVIVING_UPDATES)) {
276
+ const row = db.prepare('SELECT from_types, to_types FROM relation_types WHERE name = ?').get(name);
277
+ if (!row)
278
+ return false;
279
+ if (row.from_types !== JSON.stringify(def.from))
280
+ return false;
281
+ if (row.to_types !== JSON.stringify(def.to))
282
+ return false;
283
+ }
284
+ return true;
285
+ }
286
+ // ─── Phase 6: Post-Migration Traceability Rebuild ───
287
+ function phase6_execute(db) {
288
+ const details = {
289
+ fulfilledByCreated: 0,
290
+ fulfillsCreated: 0,
291
+ realizedByCreated: 0,
292
+ realizesCreated: 0,
293
+ oldEdgesRead: 0,
294
+ };
295
+ const now = new Date().toISOString();
296
+ // Step 1: Read old decomposed_from / decomposes_to edges (these will be deleted in Phase 7)
297
+ // These represent: US → decomposes_to → FR/NFR
298
+ const oldDecomposeEdges = db.prepare(`
299
+ SELECT DISTINCT r.from_id, r.to_id,
300
+ e_from.type_name as from_type, e_to.type_name as to_type
301
+ FROM relations r
302
+ JOIN entities e_from ON r.from_id = e_from.id
303
+ JOIN entities e_to ON r.to_id = e_to.id
304
+ WHERE r.rel_type IN ('decomposed_from', 'decomposes_to')
305
+ `).all();
306
+ details.oldEdgesRead += oldDecomposeEdges.length;
307
+ // Build fulfilled_by/fulfills edges from decomposition data
308
+ const fulfilledPairs = new Set();
309
+ for (const edge of oldDecomposeEdges) {
310
+ let usId = null;
311
+ let frNfrId = null;
312
+ if (edge.from_type === 'US' && (edge.to_type === 'FR' || edge.to_type === 'NFR')) {
313
+ usId = edge.from_id;
314
+ frNfrId = edge.to_id;
315
+ }
316
+ else if (edge.to_type === 'US' && (edge.from_type === 'FR' || edge.from_type === 'NFR')) {
317
+ usId = edge.to_id;
318
+ frNfrId = edge.from_id;
319
+ }
320
+ if (usId && frNfrId) {
321
+ const pairKey = `fulfilled:${usId}->${frNfrId}`;
322
+ if (!fulfilledPairs.has(pairKey)) {
323
+ fulfilledPairs.add(pairKey);
324
+ // Check if fulfilled_by already exists
325
+ const existingFb = db.prepare("SELECT id FROM relations WHERE rel_type = 'fulfilled_by' AND from_id = ? AND to_id = ?").get(usId, frNfrId);
326
+ if (!existingFb) {
327
+ db.prepare(`
328
+ INSERT INTO relations (rel_type, from_id, to_id, properties, created_at)
329
+ VALUES (?, ?, ?, ?, ?)
330
+ `).run('fulfilled_by', usId, frNfrId, '{}', now);
331
+ details.fulfilledByCreated++;
332
+ }
333
+ // Check if fulfills already exists
334
+ const existingFl = db.prepare("SELECT id FROM relations WHERE rel_type = 'fulfills' AND from_id = ? AND to_id = ?").get(frNfrId, usId);
335
+ if (!existingFl) {
336
+ db.prepare(`
337
+ INSERT INTO relations (rel_type, from_id, to_id, properties, created_at)
338
+ VALUES (?, ?, ?, ?, ?)
339
+ `).run('fulfills', frNfrId, usId, '{}', now);
340
+ details.fulfillsCreated++;
341
+ }
342
+ }
343
+ }
344
+ }
345
+ // Step 2: Read old traced_from / traces_to edges
346
+ // These represent: FR → traced_from → FS (FR requirement traces from FS specification)
347
+ const oldTraceEdges = db.prepare(`
348
+ SELECT DISTINCT r.from_id, r.to_id,
349
+ e_from.type_name as from_type, e_to.type_name as to_type
350
+ FROM relations r
351
+ JOIN entities e_from ON r.from_id = e_from.id
352
+ JOIN entities e_to ON r.to_id = e_to.id
353
+ WHERE r.rel_type IN ('traced_from', 'traces_to')
354
+ `).all();
355
+ details.oldEdgesRead += oldTraceEdges.length;
356
+ // Build realized_by/realizes edges from traceability data
357
+ const realizedPairs = new Set();
358
+ for (const edge of oldTraceEdges) {
359
+ let frId = null;
360
+ let fsId = null;
361
+ if (edge.from_type === 'FR' && edge.to_type === 'FS') {
362
+ frId = edge.from_id;
363
+ fsId = edge.to_id;
364
+ }
365
+ else if (edge.to_type === 'FR' && edge.from_type === 'FS') {
366
+ frId = edge.to_id;
367
+ fsId = edge.from_id;
368
+ }
369
+ else if (edge.from_type === 'FR' && (edge.to_type === 'FS' || edge.to_type === 'NFS')) {
370
+ frId = edge.from_id;
371
+ fsId = edge.to_id;
372
+ }
373
+ else if (edge.to_type === 'FR' && (edge.from_type === 'FS' || edge.from_type === 'NFS')) {
374
+ frId = edge.to_id;
375
+ fsId = edge.from_id;
376
+ }
377
+ if (frId && fsId) {
378
+ const pairKey = `realized:${frId}->${fsId}`;
379
+ if (!realizedPairs.has(pairKey)) {
380
+ realizedPairs.add(pairKey);
381
+ // Check if realized_by already exists
382
+ const existingRb = db.prepare("SELECT id FROM relations WHERE rel_type = 'realized_by' AND from_id = ? AND to_id = ?").get(frId, fsId);
383
+ if (!existingRb) {
384
+ db.prepare(`
385
+ INSERT INTO relations (rel_type, from_id, to_id, properties, created_at)
386
+ VALUES (?, ?, ?, ?, ?)
387
+ `).run('realized_by', frId, fsId, '{}', now);
388
+ details.realizedByCreated++;
389
+ }
390
+ // Check if realizes already exists
391
+ const existingRl = db.prepare("SELECT id FROM relations WHERE rel_type = 'realizes' AND from_id = ? AND to_id = ?").get(fsId, frId);
392
+ if (!existingRl) {
393
+ db.prepare(`
394
+ INSERT INTO relations (rel_type, from_id, to_id, properties, created_at)
395
+ VALUES (?, ?, ?, ?, ?)
396
+ `).run('realizes', fsId, frId, '{}', now);
397
+ details.realizesCreated++;
398
+ }
399
+ }
400
+ }
401
+ }
402
+ return details;
403
+ }
404
+ function phase6_validatePre(db) {
405
+ // Check if all 8 new relation types exist (Phase 4 must have been run)
406
+ for (const name of Object.keys(NEW_RELATION_TYPES)) {
407
+ const exists = db.prepare('SELECT name FROM relation_types WHERE name = ?').get(name);
408
+ if (!exists)
409
+ return false;
410
+ }
411
+ return true;
412
+ }
413
+ function phase6_validatePost() {
414
+ // Success if no errors thrown
415
+ return true;
416
+ }
417
+ // ─── Phase 7: Remove 17 Old Relation Types ───
418
+ function phase7_execute(db) {
419
+ const details = { relationTypesRemoved: 0, relationInstancesDeleted: 0, violatingRelationsDeleted: 0 };
420
+ // Step 0: Delete relation instances that violate the new schema type constraints.
421
+ // After Phase 5 updates the relation type definitions, some existing relation
422
+ // instances may have entity types that are no longer allowed.
423
+ const currentRelationTypes = db.prepare('SELECT name, from_types, to_types FROM relation_types').all();
424
+ for (const rt of currentRelationTypes) {
425
+ const allowedFrom = JSON.parse(rt.from_types);
426
+ const allowedTo = JSON.parse(rt.to_types);
427
+ // Delete relations where from_entity type is not in allowedFrom
428
+ const violatingFrom = db.prepare(`
429
+ DELETE FROM relations WHERE rel_type = ?
430
+ AND from_id IN (SELECT id FROM entities WHERE type_name NOT IN (${allowedFrom.map(() => '?').join(',')}))
431
+ `).run(rt.name, ...allowedFrom);
432
+ details.violatingRelationsDeleted += violatingFrom.changes;
433
+ // Delete relations where to_entity type is not in allowedTo
434
+ const violatingTo = db.prepare(`
435
+ DELETE FROM relations WHERE rel_type = ?
436
+ AND to_id IN (SELECT id FROM entities WHERE type_name NOT IN (${allowedTo.map(() => '?').join(',')}))
437
+ `).run(rt.name, ...allowedTo);
438
+ details.violatingRelationsDeleted += violatingTo.changes;
439
+ }
440
+ for (const typeName of REMOVED_RELATION_TYPES) {
441
+ // Step 1: Delete all relation instances of this type
442
+ const instResult = db.prepare('DELETE FROM relations WHERE rel_type = ?').run(typeName);
443
+ details.relationInstancesDeleted += instResult.changes;
444
+ // Step 2: Delete the relation type definition
445
+ const typeResult = db.prepare('DELETE FROM relation_types WHERE name = ?').run(typeName);
446
+ if (typeResult.changes > 0) {
447
+ details.relationTypesRemoved++;
448
+ }
449
+ }
450
+ return details;
451
+ }
452
+ function phase7_validatePre(db) {
453
+ let remaining = 0;
454
+ for (const name of REMOVED_RELATION_TYPES) {
455
+ const exists = db.prepare('SELECT name FROM relation_types WHERE name = ?').get(name);
456
+ if (exists)
457
+ remaining++;
458
+ }
459
+ return remaining > 0;
460
+ }
461
+ function phase7_validatePost(db) {
462
+ for (const name of REMOVED_RELATION_TYPES) {
463
+ const exists = db.prepare('SELECT name FROM relation_types WHERE name = ?').get(name);
464
+ if (exists)
465
+ return false;
466
+ }
467
+ return true;
468
+ }
469
+ // ─── Phase 8: YAML Finalization ───
470
+ // generateYamlContent removed — YAML is now source-of-truth, read-only by migration
471
+ function phase8_execute(db, projectRoot) {
472
+ const details = {};
473
+ // Step 1: Verify relation type count
474
+ const rtCount = db.prepare('SELECT COUNT(*) as cnt FROM relation_types').get().cnt;
475
+ details.relationTypeCount = rtCount;
476
+ // Step 2: Read plan/project-schema.yaml as source-of-truth
477
+ const planPath = (0, path_1.resolve)(projectRoot, 'plan', 'project-schema.yaml');
478
+ if (!(0, fs_1.existsSync)(planPath)) {
479
+ throw new errors_1.MigrationYamlWriteError('plan/project-schema.yaml', 'YAML schema file not found — cannot sync DB from YAML');
480
+ }
481
+ const yamlContent = (0, fs_1.readFileSync)(planPath, 'utf-8');
482
+ details.planYamlRead = planPath;
483
+ details.planYamlBytes = yamlContent.length;
484
+ // Step 3: Parse YAML and sync to DB (YAML is source-of-truth)
485
+ const yaml = require('yaml');
486
+ const schema = yaml.parse(yamlContent);
487
+ if (!schema || !schema.relation_types) {
488
+ throw new errors_1.MigrationValidationError('YAML schema missing relation_types key');
489
+ }
490
+ const updateStmt = db.prepare(`UPDATE relation_types SET from_types = ?, to_types = ?, cardinality = ?, updated_at = datetime('now') WHERE name = ?`);
491
+ let syncedCount = 0;
492
+ for (const [name, def] of Object.entries(schema.relation_types)) {
493
+ const fromTypes = JSON.stringify(def?.from ?? []);
494
+ const toTypes = JSON.stringify(def?.to ?? []);
495
+ const cardinality = def?.cardinality ?? 'many_to_many';
496
+ const result = updateStmt.run(fromTypes, toTypes, cardinality, name);
497
+ if (result.changes > 0)
498
+ syncedCount++;
499
+ }
500
+ details.relationTypesSynced = syncedCount;
501
+ // Step 4: Re-generate schema-types.ts (optional — skip if script unavailable)
502
+ const scriptPath = (0, path_1.resolve)(projectRoot, 'scripts', 'generate-schema-types.js');
503
+ if ((0, fs_1.existsSync)(scriptPath)) {
504
+ try {
505
+ const output = (0, child_process_1.execSync)(`node ${scriptPath}`, {
506
+ cwd: projectRoot,
507
+ encoding: 'utf-8',
508
+ timeout: 30_000,
509
+ });
510
+ details.scriptOutput = output.trim();
511
+ }
512
+ catch (err) {
513
+ // Script execution failure is non-fatal for yaml-finalization
514
+ const msg = err instanceof Error ? err.message : String(err);
515
+ details.scriptOutput = `Warning: generate-schema-types.js failed: ${msg}`;
516
+ }
517
+ }
518
+ else {
519
+ details.scriptOutput = 'generate-schema-types.js not found — skipped';
520
+ }
521
+ // Step 5: Update graph_schema metadata to builtin:3.0
522
+ const now = new Date().toISOString();
523
+ db.prepare('DELETE FROM graph_schema').run();
524
+ db.prepare('INSERT INTO graph_schema (source_file, version, bootstrapped_at, checksum) VALUES (?, ?, ?, ?)').run('builtin:3.0', 'builtin:3.0', now, null);
525
+ details.schemaVersion = 'builtin:3.0';
526
+ return details;
527
+ }
528
+ function phase8_validatePre(db) {
529
+ const rtCount = db.prepare('SELECT COUNT(*) as cnt FROM relation_types').get().cnt;
530
+ return rtCount === 18;
531
+ }
532
+ function phase8_validatePost(_db) {
533
+ return true; // Verified by Phase 9
534
+ }
535
+ // ─── Phase 9: Verification ───
536
+ function phase9_execute(db) {
537
+ const details = {};
538
+ // Re-enable FK enforcement — it was disabled at the start of the migration
539
+ db.exec('PRAGMA foreign_keys = ON');
540
+ // Check 1: Relation type count
541
+ const rtCount = db.prepare('SELECT COUNT(*) as cnt FROM relation_types').get().cnt;
542
+ details.relationTypeCount = rtCount;
543
+ // Check 2: Verify no entity_types table
544
+ const etTable = db.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name='entity_types'").get();
545
+ details.entityTypesTableExists = etTable ? 'YES' : 'NO';
546
+ // Check 3: Count remaining relation instances
547
+ const relCount = db.prepare('SELECT COUNT(*) as cnt FROM relations').get().cnt;
548
+ details.remainingRelationInstances = relCount;
549
+ // Check 4: Verify no removed relation types exist
550
+ let removedStillPresent = 0;
551
+ for (const name of REMOVED_RELATION_TYPES) {
552
+ const exists = db.prepare('SELECT name FROM relation_types WHERE name = ?').get(name);
553
+ if (exists)
554
+ removedStillPresent++;
555
+ }
556
+ details.removedTypesStillPresent = removedStillPresent;
557
+ // Check 5: Verify all final types exist
558
+ let finalTypesPresent = 0;
559
+ for (const name of FINAL_RELATION_TYPE_NAMES) {
560
+ const exists = db.prepare('SELECT name FROM relation_types WHERE name = ?').get(name);
561
+ if (exists)
562
+ finalTypesPresent++;
563
+ }
564
+ details.finalTypesPresent = finalTypesPresent;
565
+ // Check 6: Count URD documents
566
+ const urdCount = db.prepare("SELECT COUNT(*) as cnt FROM documents WHERE type = 'URD'").get().cnt;
567
+ details.urdDocsRemaining = urdCount;
568
+ // Check 7: Count TR entities
569
+ const trCount = db.prepare("SELECT COUNT(*) as cnt FROM entities WHERE type_name = 'TR'").get().cnt;
570
+ details.trEntitiesRemaining = trCount;
571
+ // Check 8: Dangling relations
572
+ const danglingCount = db.prepare(`
573
+ SELECT COUNT(*) as cnt FROM relations r
574
+ LEFT JOIN entities e1 ON r.from_id = e1.id
575
+ LEFT JOIN entities e2 ON r.to_id = e2.id
576
+ WHERE e1.id IS NULL OR e2.id IS NULL
577
+ `).get().cnt;
578
+ details.danglingRelations = danglingCount;
579
+ // Overall pass/fail
580
+ const failed = [];
581
+ if (rtCount !== 18)
582
+ failed.push(`Relation type count ${rtCount} != 18`);
583
+ if (etTable)
584
+ failed.push('entity_types table still exists');
585
+ if (removedStillPresent > 0)
586
+ failed.push(`${removedStillPresent} removed types still present`);
587
+ if (finalTypesPresent !== 18)
588
+ failed.push(`Only ${finalTypesPresent}/18 final types present`);
589
+ if (urdCount > 0)
590
+ failed.push(`${urdCount} URD docs remaining`);
591
+ if (trCount > 0)
592
+ failed.push(`${trCount} TR entities remaining`);
593
+ if (danglingCount > 0)
594
+ failed.push(`${danglingCount} dangling relations`);
595
+ if (failed.length > 0) {
596
+ throw new errors_1.MigrationValidationError(`Verification failed: ${failed.join('; ')}`);
597
+ }
598
+ details.verdict = 'PASS';
599
+ return details;
600
+ }
601
+ function phase9_validatePre(_db) {
602
+ return true; // Always runnable
603
+ }
604
+ function phase9_validatePost(_db) {
605
+ return true;
606
+ }
607
+ // ─── Phase Registry ───
608
+ const PHASES = [
609
+ {
610
+ name: 'entity-types-drop',
611
+ execute: (db, _) => {
612
+ const result = { phase: 'entity-types-drop', elapsedMs: 0, details: {} };
613
+ try {
614
+ result.elapsedMs = measureElapsed(() => {
615
+ result.details = phase1_execute(db);
616
+ });
617
+ }
618
+ catch (err) {
619
+ result.error = err instanceof Error ? err.message : String(err);
620
+ }
621
+ return result;
622
+ },
623
+ validatePre: phase1_validatePre,
624
+ validatePost: phase1_validatePost,
625
+ },
626
+ {
627
+ name: 'tr-relations-cleanup',
628
+ execute: (db, _) => {
629
+ const result = { phase: 'tr-relations-cleanup', elapsedMs: 0, details: {} };
630
+ try {
631
+ result.elapsedMs = measureElapsed(() => {
632
+ result.details = phase2_execute(db);
633
+ });
634
+ }
635
+ catch (err) {
636
+ result.error = err instanceof Error ? err.message : String(err);
637
+ }
638
+ return result;
639
+ },
640
+ validatePre: phase2_validatePre,
641
+ validatePost: phase2_validatePost,
642
+ },
643
+ {
644
+ name: 'urd-migration',
645
+ execute: (db, projectRoot) => {
646
+ const result = { phase: 'urd-migration', elapsedMs: 0, details: {} };
647
+ try {
648
+ result.elapsedMs = measureElapsed(() => {
649
+ result.details = phase3_execute(db, projectRoot);
650
+ });
651
+ }
652
+ catch (err) {
653
+ result.error = err instanceof Error ? err.message : String(err);
654
+ }
655
+ return result;
656
+ },
657
+ validatePre: phase3_validatePre,
658
+ validatePost: phase3_validatePost,
659
+ },
660
+ {
661
+ name: 'add-new-relations',
662
+ execute: (db, _) => {
663
+ const result = { phase: 'add-new-relations', elapsedMs: 0, details: {} };
664
+ try {
665
+ result.elapsedMs = measureElapsed(() => {
666
+ result.details = phase4_execute(db);
667
+ });
668
+ }
669
+ catch (err) {
670
+ result.error = err instanceof Error ? err.message : String(err);
671
+ }
672
+ return result;
673
+ },
674
+ validatePre: phase4_validatePre,
675
+ validatePost: phase4_validatePost,
676
+ },
677
+ {
678
+ name: 'update-surviving-relations',
679
+ execute: (db, _) => {
680
+ const result = { phase: 'update-surviving-relations', elapsedMs: 0, details: {} };
681
+ try {
682
+ result.elapsedMs = measureElapsed(() => {
683
+ result.details = phase5_execute(db);
684
+ });
685
+ }
686
+ catch (err) {
687
+ result.error = err instanceof Error ? err.message : String(err);
688
+ }
689
+ return result;
690
+ },
691
+ validatePre: phase5_validatePre,
692
+ validatePost: phase5_validatePost,
693
+ },
694
+ {
695
+ name: 'traceability-rebuild',
696
+ execute: (db, _) => {
697
+ const result = { phase: 'traceability-rebuild', elapsedMs: 0, details: {} };
698
+ try {
699
+ result.elapsedMs = measureElapsed(() => {
700
+ result.details = phase6_execute(db);
701
+ });
702
+ }
703
+ catch (err) {
704
+ result.error = err instanceof Error ? err.message : String(err);
705
+ }
706
+ return result;
707
+ },
708
+ validatePre: phase6_validatePre,
709
+ validatePost: (_db) => phase6_validatePost(),
710
+ },
711
+ {
712
+ name: 'remove-old-relations',
713
+ execute: (db, _) => {
714
+ const result = { phase: 'remove-old-relations', elapsedMs: 0, details: {} };
715
+ try {
716
+ result.elapsedMs = measureElapsed(() => {
717
+ result.details = phase7_execute(db);
718
+ });
719
+ }
720
+ catch (err) {
721
+ result.error = err instanceof Error ? err.message : String(err);
722
+ }
723
+ return result;
724
+ },
725
+ validatePre: phase7_validatePre,
726
+ validatePost: phase7_validatePost,
727
+ },
728
+ {
729
+ name: 'yaml-finalization',
730
+ execute: (db, projectRoot) => {
731
+ const result = { phase: 'yaml-finalization', elapsedMs: 0, details: {} };
732
+ try {
733
+ result.elapsedMs = measureElapsed(() => {
734
+ result.details = phase8_execute(db, projectRoot);
735
+ });
736
+ }
737
+ catch (err) {
738
+ result.error = err instanceof Error ? err.message : String(err);
739
+ }
740
+ return result;
741
+ },
742
+ validatePre: phase8_validatePre,
743
+ validatePost: phase8_validatePost,
744
+ },
745
+ {
746
+ name: 'verification',
747
+ execute: (db, _) => {
748
+ const result = { phase: 'verification', elapsedMs: 0, details: {} };
749
+ try {
750
+ result.elapsedMs = measureElapsed(() => {
751
+ result.details = phase9_execute(db);
752
+ });
753
+ }
754
+ catch (err) {
755
+ result.error = err instanceof Error ? err.message : String(err);
756
+ }
757
+ return result;
758
+ },
759
+ validatePre: phase9_validatePre,
760
+ validatePost: phase9_validatePost,
761
+ },
762
+ ];
763
+ const PHASE_NAME_MAP = new Map(PHASES.map(p => [p.name, p]));
764
+ // ─── Public API ───
765
+ function getAvailablePhases() {
766
+ return PHASES.map(p => p.name);
767
+ }
768
+ /**
769
+ * Run a single named phase. Throws if phase name is unknown.
770
+ */
771
+ function runPhase(db, phaseName, projectRoot) {
772
+ const phase = PHASE_NAME_MAP.get(phaseName);
773
+ if (!phase) {
774
+ throw new Error(`Unknown migration phase: ${phaseName}. Valid phases: ${getAvailablePhases().join(', ')}`);
775
+ }
776
+ return phase.execute(db, projectRoot);
777
+ }
778
+ /**
779
+ * Run the full 9-phase Sprint 31 migration on the given database.
780
+ * Phases execute in order. Each phase runs in its own transaction.
781
+ * Idempotent: phases skip work when preconditions are already met.
782
+ *
783
+ * @param db - Database connection
784
+ * @param projectRoot - Project root directory (for YAML/script paths in Phase 8)
785
+ * @returns MigrationResult with phase details and elapsed times
786
+ */
787
+ function runMigration(db, projectRoot) {
788
+ const phases = [];
789
+ const errors = [];
790
+ const totalStart = process.hrtime.bigint();
791
+ // SQLite schema migrations require FK enforcement to be OFF during the
792
+ // entire migration because we recreate tables, change IDs, and drop
793
+ // referenced tables. FK is re-enabled in Phase 9 (verification).
794
+ db.exec('PRAGMA foreign_keys = OFF');
795
+ // Phase ordering enforcement: check each phase's preconditions
796
+ // and skip if already applied
797
+ for (const phase of PHASES) {
798
+ const phaseName = phase.name;
799
+ // Check if phase needs to run (or is already applied)
800
+ // For destructive phases, validatePre checks if work needs to be done
801
+ // For additive phases, we check the post-condition instead
802
+ try {
803
+ // Run the phase in a transaction
804
+ (0, retry_1.withTransaction)(db, () => {
805
+ const result = phase.execute(db, projectRoot);
806
+ result.phase = phaseName; // Ensure phase name is set
807
+ phases.push(result);
808
+ if (result.error) {
809
+ errors.push(`${phaseName}: ${result.error}`);
810
+ }
811
+ else {
812
+ console.log(`Phase ${phaseName}: ${result.elapsedMs}ms`);
813
+ }
814
+ // Post-validation
815
+ if (!result.error && !phase.validatePost(db)) {
816
+ const msg = `Post-validation failed for phase ${phaseName}`;
817
+ result.error = msg;
818
+ errors.push(msg);
819
+ }
820
+ });
821
+ }
822
+ catch (err) {
823
+ const msg = err instanceof Error ? err.message : String(err);
824
+ phases.push({
825
+ phase: phaseName,
826
+ elapsedMs: 0,
827
+ details: {},
828
+ error: msg,
829
+ });
830
+ errors.push(`${phaseName}: ${msg}`);
831
+ console.error(`Phase ${phaseName} failed: ${msg}`);
832
+ }
833
+ // If a phase failed, stop further phases
834
+ if (phases[phases.length - 1].error) {
835
+ const totalElapsed = Number((process.hrtime.bigint() - totalStart) / 1000000n);
836
+ return { success: false, phases, totalElapsedMs: totalElapsed, errors };
837
+ }
838
+ }
839
+ const totalElapsed = Number((process.hrtime.bigint() - totalStart) / 1000000n);
840
+ console.log(`Total migration time: ${totalElapsed}ms`);
841
+ return {
842
+ success: errors.length === 0,
843
+ phases,
844
+ totalElapsedMs: totalElapsed,
845
+ errors,
846
+ };
847
+ }
848
+ /**
849
+ * Pre-migration validation: check that the database has the expected pre-migration state.
850
+ * (builtin:2.0 characteristics — entity_types table, 27+ relation types)
851
+ */
852
+ function validatePreMigration(db) {
853
+ const entityTypeTable = db.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name='entity_types'").get();
854
+ if (entityTypeTable) {
855
+ const entityTypeCount = db.prepare('SELECT COUNT(*) as cnt FROM entity_types').get().cnt;
856
+ console.log(`Pre-migration: entity_types table present with ${entityTypeCount} types`);
857
+ }
858
+ else {
859
+ console.log('Pre-migration: entity_types table already dropped');
860
+ }
861
+ const relationTypeCount = db.prepare('SELECT COUNT(*) as cnt FROM relation_types').get().cnt;
862
+ console.log(`Pre-migration: ${relationTypeCount} relation types present`);
863
+ }
864
+ /**
865
+ * Post-migration validation: verify the database is in builtin:3.0 state.
866
+ */
867
+ function validatePostMigration(db) {
868
+ // Verify entity_types table is dropped
869
+ const etTable = db.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name='entity_types'").get();
870
+ if (etTable) {
871
+ throw new errors_1.MigrationValidationError('Post-migration validation failed: entity_types table still exists');
872
+ }
873
+ // Verify relation type count
874
+ const rtCount = db.prepare('SELECT COUNT(*) as cnt FROM relation_types').get().cnt;
875
+ if (rtCount !== 18) {
876
+ throw new errors_1.MigrationValidationError(`Post-migration validation failed: expected 18 relation types, found ${rtCount}`);
877
+ }
878
+ // Verify no removed relation types exist
879
+ for (const removed of REMOVED_RELATION_TYPES) {
880
+ const exists = db.prepare('SELECT name FROM relation_types WHERE name = ?').get(removed);
881
+ if (exists) {
882
+ throw new errors_1.MigrationValidationError(`Post-migration validation failed: removed relation type "${removed}" still exists`);
883
+ }
884
+ }
885
+ // Verify all final types exist
886
+ for (const name of FINAL_RELATION_TYPE_NAMES) {
887
+ const exists = db.prepare('SELECT name FROM relation_types WHERE name = ?').get(name);
888
+ if (!exists) {
889
+ throw new errors_1.MigrationValidationError(`Post-migration validation failed: required relation type "${name}" not found`);
890
+ }
891
+ }
892
+ console.log('Post-migration validation: PASSED');
893
+ }
894
+ //# sourceMappingURL=schema-migration-service.js.map