@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,106 @@
1
+ import type Database from 'better-sqlite3';
2
+ /** Options for lock acquisition */
3
+ export interface LockOptions {
4
+ /** Lock name (e.g., "kanban:sprint-2026050301") */
5
+ name: string;
6
+ /** Unique identifier for the lock holder (e.g., PID + timestamp) */
7
+ owner: string;
8
+ /** Max wait time in ms (default: 5000) */
9
+ timeoutMs?: number;
10
+ /** Lock considered stale after this many ms (default: 60000) */
11
+ staleMs?: number;
12
+ /** Retry interval in ms (default: 50) */
13
+ retryIntervalMs?: number;
14
+ }
15
+ /** Handle returned after successful lock acquisition */
16
+ export interface LockHandle {
17
+ /** The lock name */
18
+ name: string;
19
+ /** The owner identifier */
20
+ owner: string;
21
+ /** Release the lock */
22
+ release: () => void;
23
+ }
24
+ /**
25
+ * Generate a unique owner string using PID and timestamp.
26
+ */
27
+ export declare function generateLockOwner(): string;
28
+ /**
29
+ * Clear stale locks for a given lock name.
30
+ * A lock is stale if COALESCE(last_heartbeat, acquired_at) is older than now - staleMs.
31
+ * Per FS-SS-003-0004 VR-003.
32
+ *
33
+ * @returns Number of stale locks cleared
34
+ */
35
+ export declare function clearStaleLocks(db: Database.Database, name: string, staleMs?: number): number;
36
+ /**
37
+ * Attempt to acquire a lock (single attempt, no retry).
38
+ * Uses TEXT (ISO 8601) for acquired_at per FS-SS-003-0003 Data Model.
39
+ *
40
+ * @returns true if acquired, false if lock is held by another owner
41
+ */
42
+ export declare function tryAcquireLock(db: Database.Database, name: string, owner: string): boolean;
43
+ /**
44
+ * Refresh/heartbeat a lock — update last_heartbeat timestamp.
45
+ * Per FS-SS-003-0004 VR-005: operations exceeding the stale threshold
46
+ * must explicitly extend their lock.
47
+ *
48
+ * @returns true if the lock was refreshed, false if not found or wrong owner
49
+ */
50
+ export declare function refreshLock(db: Database.Database, name: string, owner: string): boolean;
51
+ /**
52
+ * Get information about a lock (if held).
53
+ */
54
+ export declare function getLockInfo(db: Database.Database, name: string): {
55
+ owner: string;
56
+ acquiredAt: string;
57
+ lastHeartbeat: string | null;
58
+ } | null;
59
+ /**
60
+ * Acquire an advisory lock with timeout and stale detection.
61
+ * Implements the retry loop with configurable timeout.
62
+ *
63
+ * Per FS-SS-003-0003 VR-008: re-evaluates lock staleness at intervals no
64
+ * greater than the advisory lock acquisition timeout. The default retry
65
+ * interval (50ms) is well below the default timeout (5000ms), so each
66
+ * retry iteration satisfies this bound.
67
+ *
68
+ * @throws LockTimeoutError if the lock cannot be acquired within timeoutMs (ERR-LOCK-002)
69
+ */
70
+ export declare function acquireLock(db: Database.Database, opts: LockOptions): LockHandle;
71
+ /**
72
+ * Acquire multiple advisory locks in alphabetical order.
73
+ * Per FS-SS-003-0003 VR-006/VR-007: locks must be acquired in alphabetical
74
+ * order by lock name to prevent deadlocks.
75
+ *
76
+ * On partial failure (some locks acquired but a later one times out),
77
+ * all acquired locks are released before the error is thrown.
78
+ *
79
+ * @throws LockTimeoutError if any lock cannot be acquired
80
+ */
81
+ export declare function acquireLocks(db: Database.Database, lockNames: string[], owner: string, opts?: Omit<LockOptions, 'name' | 'owner'>): LockHandle[];
82
+ /**
83
+ * Release an advisory lock.
84
+ * Only the owner can release their own lock.
85
+ *
86
+ * @returns true if the lock was released, false if not found or wrong owner
87
+ */
88
+ export declare function releaseLock(db: Database.Database, name: string, owner: string): boolean;
89
+ /**
90
+ * Force-release a lock regardless of owner.
91
+ * Use with caution — only for administrative purposes.
92
+ */
93
+ export declare function forceReleaseLock(db: Database.Database, name: string): boolean;
94
+ /**
95
+ * Execute a function within a lock context, guaranteeing release in finally.
96
+ * Per FS-SS-003-0003 VR-003: locks must be released upon operation completion
97
+ * (success, failure, or exception).
98
+ *
99
+ * @param db - Database connection
100
+ * @param opts - Lock acquisition options
101
+ * @param fn - Function to execute while holding the lock
102
+ * @returns The return value of fn
103
+ * @throws LockTimeoutError if the lock cannot be acquired
104
+ */
105
+ export declare function withLock<T>(db: Database.Database, opts: LockOptions, fn: () => T): T;
106
+ //# sourceMappingURL=locks.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"locks.d.ts","sourceRoot":"","sources":["../../src/db/locks.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,QAAQ,MAAM,gBAAgB,CAAC;AAG3C,mCAAmC;AACnC,MAAM,WAAW,WAAW;IAC1B,mDAAmD;IACnD,IAAI,EAAE,MAAM,CAAC;IACb,oEAAoE;IACpE,KAAK,EAAE,MAAM,CAAC;IACd,0CAA0C;IAC1C,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,gEAAgE;IAChE,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,yCAAyC;IACzC,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED,wDAAwD;AACxD,MAAM,WAAW,UAAU;IACzB,oBAAoB;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,2BAA2B;IAC3B,KAAK,EAAE,MAAM,CAAC;IACd,uBAAuB;IACvB,OAAO,EAAE,MAAM,IAAI,CAAC;CACrB;AAMD;;GAEG;AACH,wBAAgB,iBAAiB,IAAI,MAAM,CAE1C;AASD;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,EAAE,EAAE,QAAQ,CAAC,QAAQ,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,GAAE,MAAyB,GAAG,MAAM,CAQ/G;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,EAAE,EAAE,QAAQ,CAAC,QAAQ,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAY1F;AAED;;;;;;GAMG;AACH,wBAAgB,WAAW,CAAC,EAAE,EAAE,QAAQ,CAAC,QAAQ,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAKvF;AAED;;GAEG;AACH,wBAAgB,WAAW,CAAC,EAAE,EAAE,QAAQ,CAAC,QAAQ,EAAE,IAAI,EAAE,MAAM,GAAG;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAC;IAAC,aAAa,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,GAAG,IAAI,CAY3I;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,WAAW,CAAC,EAAE,EAAE,QAAQ,CAAC,QAAQ,EAAE,IAAI,EAAE,WAAW,GAAG,UAAU,CA4ChF;AAED;;;;;;;;;GASG;AACH,wBAAgB,YAAY,CAAC,EAAE,EAAE,QAAQ,CAAC,QAAQ,EAAE,SAAS,EAAE,MAAM,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,IAAI,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,GAAG,UAAU,EAAE,CAkBhJ;AAED;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,EAAE,EAAE,QAAQ,CAAC,QAAQ,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAKvF;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,EAAE,EAAE,QAAQ,CAAC,QAAQ,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAK7E;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,QAAQ,CAAC,CAAC,EAAE,EAAE,EAAE,QAAQ,CAAC,QAAQ,EAAE,IAAI,EAAE,WAAW,EAAE,EAAE,EAAE,MAAM,CAAC,GAAG,CAAC,CAOpF"}
@@ -0,0 +1,211 @@
1
+ "use strict";
2
+ // Advisory lock implementation
3
+ // Implements AD-0006: Advisory Locks for Multi-Step Operations
4
+ // + FS-SS-003-0003 (lock management), FS-SS-003-0004 (stale detection),
5
+ // FS-SS-003-0007 (error reporting)
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.generateLockOwner = generateLockOwner;
8
+ exports.clearStaleLocks = clearStaleLocks;
9
+ exports.tryAcquireLock = tryAcquireLock;
10
+ exports.refreshLock = refreshLock;
11
+ exports.getLockInfo = getLockInfo;
12
+ exports.acquireLock = acquireLock;
13
+ exports.acquireLocks = acquireLocks;
14
+ exports.releaseLock = releaseLock;
15
+ exports.forceReleaseLock = forceReleaseLock;
16
+ exports.withLock = withLock;
17
+ const errors_1 = require("../utils/errors");
18
+ const DEFAULT_TIMEOUT_MS = 5000;
19
+ const DEFAULT_STALE_MS = 60000; // FS-SS-003-0004 VR-001: default 60 seconds
20
+ const DEFAULT_RETRY_INTERVAL_MS = 50;
21
+ /**
22
+ * Generate a unique owner string using PID and timestamp.
23
+ */
24
+ function generateLockOwner() {
25
+ return `${process.pid}:${Date.now()}`;
26
+ }
27
+ /**
28
+ * Get the current ISO 8601 timestamp.
29
+ */
30
+ function isoNow() {
31
+ return new Date().toISOString();
32
+ }
33
+ /**
34
+ * Clear stale locks for a given lock name.
35
+ * A lock is stale if COALESCE(last_heartbeat, acquired_at) is older than now - staleMs.
36
+ * Per FS-SS-003-0004 VR-003.
37
+ *
38
+ * @returns Number of stale locks cleared
39
+ */
40
+ function clearStaleLocks(db, name, staleMs = DEFAULT_STALE_MS) {
41
+ const cutoff = new Date(Date.now() - staleMs).toISOString();
42
+ const result = db
43
+ .prepare('DELETE FROM locks WHERE name = ? AND COALESCE(last_heartbeat, acquired_at) < ?')
44
+ .run(name, cutoff);
45
+ return result.changes;
46
+ }
47
+ /**
48
+ * Attempt to acquire a lock (single attempt, no retry).
49
+ * Uses TEXT (ISO 8601) for acquired_at per FS-SS-003-0003 Data Model.
50
+ *
51
+ * @returns true if acquired, false if lock is held by another owner
52
+ */
53
+ function tryAcquireLock(db, name, owner) {
54
+ try {
55
+ db.prepare('INSERT INTO locks (name, owner, acquired_at) VALUES (?, ?, ?)')
56
+ .run(name, owner, isoNow());
57
+ return true;
58
+ }
59
+ catch (err) {
60
+ // UNIQUE constraint violation means lock is already held
61
+ if (err instanceof Error && err.message.includes('UNIQUE constraint failed')) {
62
+ return false;
63
+ }
64
+ throw err;
65
+ }
66
+ }
67
+ /**
68
+ * Refresh/heartbeat a lock — update last_heartbeat timestamp.
69
+ * Per FS-SS-003-0004 VR-005: operations exceeding the stale threshold
70
+ * must explicitly extend their lock.
71
+ *
72
+ * @returns true if the lock was refreshed, false if not found or wrong owner
73
+ */
74
+ function refreshLock(db, name, owner) {
75
+ const result = db
76
+ .prepare('UPDATE locks SET last_heartbeat = ? WHERE name = ? AND owner = ?')
77
+ .run(isoNow(), name, owner);
78
+ return result.changes > 0;
79
+ }
80
+ /**
81
+ * Get information about a lock (if held).
82
+ */
83
+ function getLockInfo(db, name) {
84
+ const row = db
85
+ .prepare('SELECT owner, acquired_at, last_heartbeat FROM locks WHERE name = ?')
86
+ .get(name);
87
+ if (!row)
88
+ return null;
89
+ return {
90
+ owner: row.owner,
91
+ acquiredAt: row.acquired_at,
92
+ lastHeartbeat: row.last_heartbeat,
93
+ };
94
+ }
95
+ /**
96
+ * Acquire an advisory lock with timeout and stale detection.
97
+ * Implements the retry loop with configurable timeout.
98
+ *
99
+ * Per FS-SS-003-0003 VR-008: re-evaluates lock staleness at intervals no
100
+ * greater than the advisory lock acquisition timeout. The default retry
101
+ * interval (50ms) is well below the default timeout (5000ms), so each
102
+ * retry iteration satisfies this bound.
103
+ *
104
+ * @throws LockTimeoutError if the lock cannot be acquired within timeoutMs (ERR-LOCK-002)
105
+ */
106
+ function acquireLock(db, opts) {
107
+ const { name, owner, timeoutMs = DEFAULT_TIMEOUT_MS, staleMs = DEFAULT_STALE_MS, retryIntervalMs = DEFAULT_RETRY_INTERVAL_MS, } = opts;
108
+ const startTime = Date.now();
109
+ const deadline = startTime + timeoutMs;
110
+ while (true) {
111
+ // Clear stale locks before each attempt (FS-SS-003-0004 VR-002)
112
+ clearStaleLocks(db, name, staleMs);
113
+ // Try to acquire
114
+ if (tryAcquireLock(db, name, owner)) {
115
+ return {
116
+ name,
117
+ owner,
118
+ release: () => releaseLock(db, name, owner),
119
+ };
120
+ }
121
+ // Check timeout
122
+ const elapsed = Date.now() - startTime;
123
+ if (Date.now() >= deadline) {
124
+ // Query current lock owner for error reporting (FS-SS-003-0007 VR-002)
125
+ const info = getLockInfo(db, name);
126
+ const currentOwner = info?.owner ?? 'unknown';
127
+ throw new errors_1.LockTimeoutError(name, currentOwner, elapsed);
128
+ }
129
+ // Wait before retry
130
+ const sleepMs = Math.min(retryIntervalMs, deadline - Date.now());
131
+ if (sleepMs > 0) {
132
+ // Synchronous busy-wait (Node.js main-thread compatible)
133
+ const end = Date.now() + sleepMs;
134
+ while (Date.now() < end) {
135
+ // busy-wait
136
+ }
137
+ }
138
+ }
139
+ }
140
+ /**
141
+ * Acquire multiple advisory locks in alphabetical order.
142
+ * Per FS-SS-003-0003 VR-006/VR-007: locks must be acquired in alphabetical
143
+ * order by lock name to prevent deadlocks.
144
+ *
145
+ * On partial failure (some locks acquired but a later one times out),
146
+ * all acquired locks are released before the error is thrown.
147
+ *
148
+ * @throws LockTimeoutError if any lock cannot be acquired
149
+ */
150
+ function acquireLocks(db, lockNames, owner, opts) {
151
+ // Sort alphabetically to prevent deadlocks (VR-006)
152
+ const sorted = [...lockNames].sort();
153
+ const acquired = [];
154
+ try {
155
+ for (const name of sorted) {
156
+ const handle = acquireLock(db, { name, owner, ...opts });
157
+ acquired.push(handle);
158
+ }
159
+ return acquired;
160
+ }
161
+ catch (err) {
162
+ // Rollback: release all acquired locks on partial failure
163
+ for (const handle of acquired) {
164
+ handle.release();
165
+ }
166
+ throw err;
167
+ }
168
+ }
169
+ /**
170
+ * Release an advisory lock.
171
+ * Only the owner can release their own lock.
172
+ *
173
+ * @returns true if the lock was released, false if not found or wrong owner
174
+ */
175
+ function releaseLock(db, name, owner) {
176
+ const result = db
177
+ .prepare('DELETE FROM locks WHERE name = ? AND owner = ?')
178
+ .run(name, owner);
179
+ return result.changes > 0;
180
+ }
181
+ /**
182
+ * Force-release a lock regardless of owner.
183
+ * Use with caution — only for administrative purposes.
184
+ */
185
+ function forceReleaseLock(db, name) {
186
+ const result = db
187
+ .prepare('DELETE FROM locks WHERE name = ?')
188
+ .run(name);
189
+ return result.changes > 0;
190
+ }
191
+ /**
192
+ * Execute a function within a lock context, guaranteeing release in finally.
193
+ * Per FS-SS-003-0003 VR-003: locks must be released upon operation completion
194
+ * (success, failure, or exception).
195
+ *
196
+ * @param db - Database connection
197
+ * @param opts - Lock acquisition options
198
+ * @param fn - Function to execute while holding the lock
199
+ * @returns The return value of fn
200
+ * @throws LockTimeoutError if the lock cannot be acquired
201
+ */
202
+ function withLock(db, opts, fn) {
203
+ const handle = acquireLock(db, opts);
204
+ try {
205
+ return fn();
206
+ }
207
+ finally {
208
+ handle.release();
209
+ }
210
+ }
211
+ //# sourceMappingURL=locks.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"locks.js","sourceRoot":"","sources":["../../src/db/locks.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,65 @@
1
+ import type Database from 'better-sqlite3';
2
+ /**
3
+ * Compute SHA-256 checksum of migration SQL content.
4
+ */
5
+ export declare function computeChecksum(sql: string): string;
6
+ /**
7
+ * Get the current migration version from the database.
8
+ * Returns 0 if no migrations have been applied.
9
+ */
10
+ export declare function getCurrentVersion(db: Database.Database): number;
11
+ /**
12
+ * Verify schema integrity: all expected tables and indexes exist.
13
+ * Returns an array of missing table/index names, or empty if all present.
14
+ */
15
+ export declare function verifySchemaIntegrity(db: Database.Database): string[];
16
+ /**
17
+ * Verify migration checksums.
18
+ * By default (verifyAll=false), only checks the latest migration.
19
+ * With verifyAll=true, checks all applied migrations.
20
+ *
21
+ * @throws MigrationChecksumMismatchError on mismatch (ERR-MIG-003)
22
+ */
23
+ export declare function verifyChecksums(db: Database.Database, verifyAll?: boolean): void;
24
+ /**
25
+ * Run all pending migrations on the database.
26
+ * Each migration is applied in a transaction.
27
+ * After all migrations, a schema integrity check is performed.
28
+ * Idempotent: if all migrations are already applied, no action is taken.
29
+ *
30
+ * @param db - Database connection
31
+ * @param targetVersion - Optional target version (defaults to latest)
32
+ * @throws MigrationError if a migration fails (ERR-MIG-001)
33
+ * @throws MigrationIntegrityError if integrity check fails (ERR-MIG-002)
34
+ */
35
+ export declare function runMigrations(db: Database.Database, targetVersion?: number): void;
36
+ /**
37
+ * Initialize the builtin graph schema (relation types + graph_schema metadata).
38
+ *
39
+ * Implements AD-0048: Two-pass FK-safe insertion strategy.
40
+ *
41
+ * Pass 1: Insert all relation types WITHOUT inverse_of (avoids FK violations
42
+ * when the referenced inverse type row does not yet exist).
43
+ * Pass 2: UPDATE each type's inverse_of to its final value.
44
+ *
45
+ * Both passes run within a single transaction with FK enforcement active.
46
+ *
47
+ * @requires PRAGMA foreign_keys = ON — caller must ensure FK enforcement is enabled
48
+ * before invoking this function.
49
+ */
50
+ export declare function initializeBuiltinSchema(db: Database.Database): void;
51
+ /**
52
+ * Initialize a fresh database with the full schema.
53
+ * Also verifies the latest migration checksum on open (VR-006).
54
+ * Builtin schema is initialized after migrations (FS-SS-008-0007-A).
55
+ */
56
+ export declare function initializeSchema(db: Database.Database, verifyAll?: boolean, skipBuiltinSchema?: boolean): void;
57
+ /**
58
+ * Check if the database schema is up to date.
59
+ */
60
+ export declare function isSchemaUpToDate(db: Database.Database): boolean;
61
+ /**
62
+ * Get list of pending migration versions.
63
+ */
64
+ export declare function getPendingMigrations(db: Database.Database): number[];
65
+ //# sourceMappingURL=migration.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"migration.d.ts","sourceRoot":"","sources":["../../src/db/migration.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,QAAQ,MAAM,gBAAgB,CAAC;AAO3C;;GAEG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAEnD;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,EAAE,EAAE,QAAQ,CAAC,QAAQ,GAAG,MAAM,CAY/D;AAED;;;GAGG;AACH,wBAAgB,qBAAqB,CAAC,EAAE,EAAE,QAAQ,CAAC,QAAQ,GAAG,MAAM,EAAE,CAyBrE;AAED;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,EAAE,EAAE,QAAQ,CAAC,QAAQ,EAAE,SAAS,GAAE,OAAe,GAAG,IAAI,CAqBvF;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,aAAa,CAAC,EAAE,EAAE,QAAQ,CAAC,QAAQ,EAAE,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,CA+DjF;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,uBAAuB,CAAC,EAAE,EAAE,QAAQ,CAAC,QAAQ,GAAG,IAAI,CAiDnE;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,EAAE,EAAE,QAAQ,CAAC,QAAQ,EAAE,SAAS,GAAE,OAAe,EAAE,iBAAiB,GAAE,OAAe,GAAG,IAAI,CAY5H;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,EAAE,EAAE,QAAQ,CAAC,QAAQ,GAAG,OAAO,CAE/D;AAED;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,EAAE,EAAE,QAAQ,CAAC,QAAQ,GAAG,MAAM,EAAE,CAGpE"}
@@ -0,0 +1,233 @@
1
+ "use strict";
2
+ // Schema migration runner
3
+ // Implements AD-0002: Sequential Versioned Migrations
4
+ // + FS-SS-001-0002 VR-004 (integrity check), VR-006 (checksum verification)
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.computeChecksum = computeChecksum;
7
+ exports.getCurrentVersion = getCurrentVersion;
8
+ exports.verifySchemaIntegrity = verifySchemaIntegrity;
9
+ exports.verifyChecksums = verifyChecksums;
10
+ exports.runMigrations = runMigrations;
11
+ exports.initializeBuiltinSchema = initializeBuiltinSchema;
12
+ exports.initializeSchema = initializeSchema;
13
+ exports.isSchemaUpToDate = isSchemaUpToDate;
14
+ exports.getPendingMigrations = getPendingMigrations;
15
+ const crypto_1 = require("crypto");
16
+ const schema_1 = require("./schema");
17
+ const schema_types_1 = require("./generated/schema-types");
18
+ const errors_1 = require("../utils/errors");
19
+ const retry_1 = require("./retry");
20
+ /**
21
+ * Compute SHA-256 checksum of migration SQL content.
22
+ */
23
+ function computeChecksum(sql) {
24
+ return (0, crypto_1.createHash)('sha256').update(sql).digest('hex');
25
+ }
26
+ /**
27
+ * Get the current migration version from the database.
28
+ * Returns 0 if no migrations have been applied.
29
+ */
30
+ function getCurrentVersion(db) {
31
+ // Check if _migrations table exists
32
+ const tableExists = db
33
+ .prepare("SELECT name FROM sqlite_master WHERE type='table' AND name='_migrations'")
34
+ .get();
35
+ if (!tableExists) {
36
+ return 0;
37
+ }
38
+ const row = db.prepare('SELECT MAX(version) as v FROM _migrations').get();
39
+ return row?.v ?? 0;
40
+ }
41
+ /**
42
+ * Verify schema integrity: all expected tables and indexes exist.
43
+ * Returns an array of missing table/index names, or empty if all present.
44
+ */
45
+ function verifySchemaIntegrity(db) {
46
+ const existingTables = new Set(db.prepare("SELECT name FROM sqlite_master WHERE type='table'").all()
47
+ .map(r => r.name));
48
+ const existingIndexes = new Set(db.prepare("SELECT name FROM sqlite_master WHERE type='index'").all()
49
+ .map(r => r.name));
50
+ const missing = [];
51
+ for (const table of schema_1.EXPECTED_TABLES) {
52
+ if (!existingTables.has(table)) {
53
+ missing.push(`table:${table}`);
54
+ }
55
+ }
56
+ for (const index of schema_1.EXPECTED_INDEXES) {
57
+ if (!existingIndexes.has(index)) {
58
+ missing.push(`index:${index}`);
59
+ }
60
+ }
61
+ return missing;
62
+ }
63
+ /**
64
+ * Verify migration checksums.
65
+ * By default (verifyAll=false), only checks the latest migration.
66
+ * With verifyAll=true, checks all applied migrations.
67
+ *
68
+ * @throws MigrationChecksumMismatchError on mismatch (ERR-MIG-003)
69
+ */
70
+ function verifyChecksums(db, verifyAll = false) {
71
+ const currentVersion = getCurrentVersion(db);
72
+ if (currentVersion === 0)
73
+ return;
74
+ const rows = db
75
+ .prepare('SELECT version, checksum FROM _migrations ORDER BY version')
76
+ .all();
77
+ const versionsToCheck = verifyAll
78
+ ? rows
79
+ : rows.filter(r => r.version === currentVersion);
80
+ for (const row of versionsToCheck) {
81
+ const migration = schema_1.MIGRATIONS.find(m => m.version === row.version);
82
+ if (!migration)
83
+ continue; // Unknown version — can't verify
84
+ const expected = computeChecksum(migration.sql);
85
+ if (row.checksum !== expected) {
86
+ throw new errors_1.MigrationChecksumMismatchError(row.version, expected, row.checksum);
87
+ }
88
+ }
89
+ }
90
+ /**
91
+ * Run all pending migrations on the database.
92
+ * Each migration is applied in a transaction.
93
+ * After all migrations, a schema integrity check is performed.
94
+ * Idempotent: if all migrations are already applied, no action is taken.
95
+ *
96
+ * @param db - Database connection
97
+ * @param targetVersion - Optional target version (defaults to latest)
98
+ * @throws MigrationError if a migration fails (ERR-MIG-001)
99
+ * @throws MigrationIntegrityError if integrity check fails (ERR-MIG-002)
100
+ */
101
+ function runMigrations(db, targetVersion) {
102
+ const target = targetVersion ?? schema_1.LATEST_VERSION;
103
+ const current = getCurrentVersion(db);
104
+ if (current > target) {
105
+ throw new errors_1.MigrationVersionError(current, [`Database version (${current}) is ahead of target version (${target}). This application version may be outdated.`]);
106
+ }
107
+ if (current === target) {
108
+ return; // Already at target version
109
+ }
110
+ const pending = schema_1.MIGRATIONS.filter(m => m.version > current && m.version <= target)
111
+ .sort((a, b) => a.version - b.version);
112
+ let lastAppliedVersion = current;
113
+ // Disable FK enforcement during migrations (needed for DROP COLUMN on FK-referenced columns)
114
+ const fkWasOn = db.pragma('foreign_keys', { simple: true });
115
+ if (fkWasOn)
116
+ db.pragma('foreign_keys = OFF');
117
+ for (const migration of pending) {
118
+ const checksum = computeChecksum(migration.sql);
119
+ const transaction = db.transaction(() => {
120
+ try {
121
+ db.exec(migration.sql);
122
+ db.prepare('INSERT INTO _migrations (version, checksum) VALUES (?, ?)')
123
+ .run(migration.version, checksum);
124
+ }
125
+ catch (err) {
126
+ // Allow idempotent migrations: ignore "duplicate column" errors for ALTER TABLE ADD COLUMN
127
+ const msg = err instanceof Error ? err.message : String(err);
128
+ if (msg.includes('duplicate column name') || msg.includes('no such column')) {
129
+ // Column already exists (ADD) or already removed (DROP) — record migration as applied
130
+ db.prepare('INSERT INTO _migrations (version, checksum) VALUES (?, ?)')
131
+ .run(migration.version, checksum);
132
+ return;
133
+ }
134
+ throw new errors_1.MigrationError(`Migration v${migration.version} (${migration.description}) failed: ${err instanceof Error ? err.message : String(err)}`, { version: migration.version, description: migration.description, originalError: err });
135
+ }
136
+ });
137
+ transaction();
138
+ lastAppliedVersion = migration.version;
139
+ }
140
+ // Re-enable FK enforcement if it was on
141
+ if (fkWasOn)
142
+ db.pragma('foreign_keys = ON');
143
+ // VR-004: Schema integrity check after all migrations (only when fully migrated)
144
+ if (lastAppliedVersion >= schema_1.LATEST_VERSION || target >= schema_1.LATEST_VERSION) {
145
+ const missing = verifySchemaIntegrity(db);
146
+ if (missing.length > 0) {
147
+ throw new errors_1.MigrationIntegrityError(lastAppliedVersion, missing);
148
+ }
149
+ }
150
+ }
151
+ /**
152
+ * Initialize the builtin graph schema (relation types + graph_schema metadata).
153
+ *
154
+ * Implements AD-0048: Two-pass FK-safe insertion strategy.
155
+ *
156
+ * Pass 1: Insert all relation types WITHOUT inverse_of (avoids FK violations
157
+ * when the referenced inverse type row does not yet exist).
158
+ * Pass 2: UPDATE each type's inverse_of to its final value.
159
+ *
160
+ * Both passes run within a single transaction with FK enforcement active.
161
+ *
162
+ * @requires PRAGMA foreign_keys = ON — caller must ensure FK enforcement is enabled
163
+ * before invoking this function.
164
+ */
165
+ function initializeBuiltinSchema(db) {
166
+ // Defensive check: FK enforcement must be ON for the two-pass strategy to be meaningful
167
+ const fkState = db.pragma('foreign_keys', { simple: true });
168
+ if (fkState === 0) {
169
+ throw new Error('initializeBuiltinSchema requires foreign_keys = ON');
170
+ }
171
+ const now = new Date().toISOString();
172
+ (0, retry_1.withTransaction)(db, () => {
173
+ // builtin:3.0 (Sprint 31): entity_types table dropped.
174
+ // Entity types are derived dynamically from documents.type.
175
+ // No entity type initialization is performed here.
176
+ // Pass 1: Insert all relation types WITHOUT inverse_of
177
+ // This avoids FK violations when the inverse type row does not yet exist.
178
+ for (const rt of schema_types_1.RELATION_TYPES) {
179
+ db.prepare(`
180
+ INSERT OR IGNORE INTO relation_types (name, from_types, to_types, cardinality, inverse_of, description, created_at, updated_at)
181
+ VALUES (?, ?, ?, ?, NULL, ?, ?, ?)
182
+ `).run(rt.name, JSON.stringify([...rt.from_types]), JSON.stringify([...rt.to_types]), rt.cardinality, rt.description, now, now);
183
+ }
184
+ // Pass 2: UPDATE inverse_of for each type that has one
185
+ for (const rt of schema_types_1.RELATION_TYPES) {
186
+ if (rt.inverse_of) {
187
+ db.prepare(`
188
+ UPDATE relation_types SET inverse_of = ?, updated_at = ?
189
+ WHERE name = ? AND inverse_of IS NULL
190
+ `).run(rt.inverse_of, now, rt.name);
191
+ }
192
+ }
193
+ // Record builtin schema source — only if no row exists (preserve YAML bootstrap metadata)
194
+ const existingMeta = db.prepare('SELECT COUNT(*) as cnt FROM graph_schema').get();
195
+ if (existingMeta.cnt === 0) {
196
+ db.prepare(`
197
+ INSERT INTO graph_schema (source_file, version, bootstrapped_at, checksum)
198
+ VALUES ('builtin:3.0', 'builtin:3.0', ?, NULL)
199
+ `).run(now);
200
+ }
201
+ });
202
+ }
203
+ /**
204
+ * Initialize a fresh database with the full schema.
205
+ * Also verifies the latest migration checksum on open (VR-006).
206
+ * Builtin schema is initialized after migrations (FS-SS-008-0007-A).
207
+ */
208
+ function initializeSchema(db, verifyAll = false, skipBuiltinSchema = false) {
209
+ // Verify existing checksums before running migrations
210
+ const currentVersion = getCurrentVersion(db);
211
+ if (currentVersion > 0) {
212
+ verifyChecksums(db, verifyAll);
213
+ }
214
+ runMigrations(db);
215
+ // FS-SS-008-0007-A: Initialize builtin graph schema
216
+ if (!skipBuiltinSchema) {
217
+ initializeBuiltinSchema(db);
218
+ }
219
+ }
220
+ /**
221
+ * Check if the database schema is up to date.
222
+ */
223
+ function isSchemaUpToDate(db) {
224
+ return getCurrentVersion(db) >= schema_1.LATEST_VERSION;
225
+ }
226
+ /**
227
+ * Get list of pending migration versions.
228
+ */
229
+ function getPendingMigrations(db) {
230
+ const current = getCurrentVersion(db);
231
+ return schema_1.MIGRATIONS.filter(m => m.version > current).map(m => m.version);
232
+ }
233
+ //# sourceMappingURL=migration.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"migration.js","sourceRoot":"","sources":["../../src/db/migration.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,26 @@
1
+ import type Database from 'better-sqlite3';
2
+ export interface RetryOptions {
3
+ /** Maximum number of retries (default: 5) */
4
+ maxRetries?: number;
5
+ /** Base delay in ms for exponential backoff (default: 50) */
6
+ baseMs?: number;
7
+ /** Maximum delay in ms for exponential backoff (default: 1000) */
8
+ maxMs?: number;
9
+ /** Maximum total wait time in ms across all retries (default: 30000) */
10
+ maxTotalWaitMs?: number;
11
+ }
12
+ /**
13
+ * Execute a function with retry on SQLITE_BUSY using exponential backoff.
14
+ * Each retry doubles the delay, capped at maxMs.
15
+ * Total wait time is capped by maxTotalWaitMs per FS-SS-003-0005 VR-007.
16
+ *
17
+ * @param fn - The function to execute (typically a DB write operation)
18
+ * @param options - Retry configuration
19
+ * @throws DatabaseBusyError (ERR-LOCK-001) if all retries are exhausted
20
+ */
21
+ export declare function withRetry<T>(fn: () => T, options?: RetryOptions): T;
22
+ /**
23
+ * Execute a database operation within a transaction with retry on busy.
24
+ */
25
+ export declare function withTransaction<T>(db: Database.Database, fn: () => T, options?: RetryOptions): T;
26
+ //# sourceMappingURL=retry.d.ts.map