ultra-builder-pro-cli 0.25.1 → 0.26.0

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 (241) hide show
  1. package/.ultra-template/contexts/TEMPLATE.md +87 -0
  2. package/.ultra-template/decisions/.gitkeep +1 -0
  3. package/.ultra-template/evidence/.gitkeep +1 -0
  4. package/.ultra-template/north-star.md +46 -0
  5. package/.ultra-template/research/.gitkeep +1 -0
  6. package/.ultra-template/specs/discovery.md +2 -2
  7. package/.ultra-template/tasks.json +3 -0
  8. package/.ultra-template/test-report.json +27 -0
  9. package/CHANGELOG.md +40 -0
  10. package/README.md +217 -579
  11. package/adapters/_shared/delegate-profiles/kimi-read-only.md +15 -0
  12. package/adapters/_shared/delegate-profiles/kimi-write.md +17 -0
  13. package/adapters/_shared/file-ops.cjs +76 -2
  14. package/adapters/_shared/host-profile.cjs +97 -0
  15. package/adapters/_shared/plugin-core.cjs +204 -0
  16. package/adapters/_shared/runtime-assets.cjs +31 -96
  17. package/adapters/claude.js +92 -291
  18. package/adapters/codex.js +309 -842
  19. package/adapters/grok.js +143 -1469
  20. package/adapters/kimi.js +124 -1053
  21. package/adapters/opencode.js +230 -742
  22. package/bin/delegate-worker.cjs +323 -0
  23. package/bin/delegate.cjs +456 -0
  24. package/bin/install.js +38 -11
  25. package/docs/ARCHITECTURE.md +122 -216
  26. package/docs/ARTIFACT-AUTHORITY.md +90 -268
  27. package/docs/DECISIONS.md +75 -87
  28. package/docs/PHILOSOPHY.md +177 -0
  29. package/docs/PLUGIN-ISOLATION-CONTRACT.md +62 -39
  30. package/docs/RUNTIME-COMPAT-MATRIX.md +99 -156
  31. package/docs/SKILL-AUTHORING.md +94 -73
  32. package/docs/WORKFLOW-LIFECYCLE.md +93 -189
  33. package/hooks/README.md +17 -37
  34. package/hooks/_common.py +113 -0
  35. package/hooks/adapters/codex.py +9 -16
  36. package/hooks/adapters/grok.py +5 -5
  37. package/hooks/adapters/kimi.py +8 -14
  38. package/hooks/block_dangerous_commands.py +95 -0
  39. package/hooks/compact_context.py +65 -0
  40. package/hooks/mid_workflow_recall.py +25 -0
  41. package/hooks/post_edit_guard.py +105 -0
  42. package/hooks/session_context.py +30 -0
  43. package/package.json +17 -52
  44. package/skills/ultra-change/SKILL.md +97 -50
  45. package/skills/ultra-change/references/change-contract.md +40 -1
  46. package/skills/ultra-delegate/SKILL.md +82 -0
  47. package/skills/ultra-delegate/references/delegation-contract.md +51 -0
  48. package/skills/ultra-delegate/scripts/delegate_wait.py +86 -0
  49. package/skills/ultra-deliver/SKILL.md +61 -42
  50. package/skills/ultra-deliver/references/baseline-reconciliation.md +39 -20
  51. package/skills/ultra-dev/SKILL.md +96 -62
  52. package/skills/ultra-dev/references/debugging.md +22 -0
  53. package/skills/ultra-domain-modeling/SKILL.md +79 -0
  54. package/skills/ultra-grilling/SKILL.md +79 -0
  55. package/skills/ultra-init/SKILL.md +94 -37
  56. package/skills/ultra-init/references/brownfield-adoption.md +33 -83
  57. package/skills/ultra-init/scripts/init_project.cjs +84 -0
  58. package/skills/ultra-plan/SKILL.md +88 -68
  59. package/skills/ultra-research/SKILL.md +62 -48
  60. package/skills/ultra-research/references/00-problem-validation.md +5 -5
  61. package/skills/ultra-research/references/01-opportunity-discovery.md +3 -3
  62. package/skills/ultra-research/references/02-market-assessment.md +5 -4
  63. package/skills/ultra-research/references/03-competitive-landscape.md +3 -3
  64. package/skills/ultra-research/references/04-product-strategy.md +3 -3
  65. package/skills/ultra-research/references/05-assumptions-validation.md +5 -5
  66. package/skills/ultra-research/references/10-user-personas.md +3 -2
  67. package/skills/ultra-research/references/11-user-scenarios.md +3 -3
  68. package/skills/ultra-research/references/20-user-stories.md +4 -3
  69. package/skills/ultra-research/references/21-features-scope.md +3 -2
  70. package/skills/ultra-research/references/22-success-metrics.md +3 -3
  71. package/skills/ultra-research/references/30-architecture-context.md +3 -3
  72. package/skills/ultra-research/references/31-solution-strategy.md +3 -3
  73. package/skills/ultra-research/references/32-building-blocks.md +3 -3
  74. package/skills/ultra-research/references/40-deployment.md +3 -3
  75. package/skills/ultra-research/references/41-quality-risks.md +3 -3
  76. package/skills/ultra-research/references/99-synthesis.md +7 -7
  77. package/skills/ultra-review/SKILL.md +59 -32
  78. package/skills/ultra-review/references/code.md +17 -0
  79. package/skills/ultra-review/references/comments.md +12 -0
  80. package/skills/ultra-review/references/design.md +12 -0
  81. package/skills/ultra-review/references/errors.md +13 -0
  82. package/skills/ultra-review/references/spec.md +13 -0
  83. package/skills/ultra-review/references/tests.md +12 -0
  84. package/skills/ultra-review/references/worker-packet.md +40 -0
  85. package/skills/ultra-review/scripts/review_wait.py +26 -15
  86. package/skills/ultra-status/SKILL.md +61 -36
  87. package/skills/ultra-tdd/SKILL.md +81 -0
  88. package/skills/ultra-tdd/references/templates/README.md +20 -0
  89. package/skills/ultra-tdd/references/templates/feature-flag-default-audit.sh +79 -0
  90. package/skills/ultra-tdd/references/templates/persistence-real.ts +78 -0
  91. package/skills/ultra-tdd/references/templates/testcontainer-postgres.py +78 -0
  92. package/skills/ultra-tdd/references/templates/testcontainer-postgres.ts +73 -0
  93. package/skills/ultra-tdd/references/templates/vertical-slice.ts +66 -0
  94. package/skills/ultra-tdd/references/test-execution.md +18 -0
  95. package/skills/ultra-test/SKILL.md +70 -30
  96. package/skills/ultra-test/references/export-syntax.md +18 -0
  97. package/skills/ultra-test/scripts/worktree_digest.cjs +54 -0
  98. package/skills/ultra-think/SKILL.md +49 -27
  99. package/skills/ultra-think/references/autonomy-boundary.md +48 -0
  100. package/skills/ultra-think/references/interaction-boundary.md +18 -33
  101. package/.ultra-template/docs/research/README.md +0 -55
  102. package/.ultra-template/reports/templates/delivery-report.json +0 -17
  103. package/.ultra-template/reports/templates/test-report.json +0 -18
  104. package/.ultra-template/tasks/tasks.json +0 -15
  105. package/.ultra-template/templates/task-context.md +0 -19
  106. package/adapters/_shared/codex-assets.cjs +0 -725
  107. package/adapters/_shared/codex-ultra-tools-entry.cjs +0 -11
  108. package/adapters/_shared/interaction-contract.cjs +0 -129
  109. package/adapters/_shared/path-rewrite.cjs +0 -79
  110. package/adapters/_shared/runtime-native.cjs +0 -132
  111. package/adapters/_shared/settings-merge.cjs +0 -76
  112. package/agents/code-reviewer.md +0 -42
  113. package/agents/debugger.md +0 -40
  114. package/agents/review-code.md +0 -49
  115. package/agents/review-comments.md +0 -38
  116. package/agents/review-coordinator.md +0 -56
  117. package/agents/review-design.md +0 -40
  118. package/agents/review-errors.md +0 -43
  119. package/agents/review-spec.md +0 -39
  120. package/agents/review-tests.md +0 -40
  121. package/agents/tdd-runner.md +0 -38
  122. package/bin/orchestrator.js +0 -383
  123. package/commands/ultra-change.md +0 -10
  124. package/commands/ultra-deliver.md +0 -10
  125. package/commands/ultra-dev.md +0 -10
  126. package/commands/ultra-doctor.md +0 -10
  127. package/commands/ultra-init.md +0 -10
  128. package/commands/ultra-plan.md +0 -10
  129. package/commands/ultra-research.md +0 -10
  130. package/commands/ultra-review.md +0 -10
  131. package/commands/ultra-status.md +0 -10
  132. package/commands/ultra-test.md +0 -10
  133. package/commands/ultra-think.md +0 -10
  134. package/docs/AGENT-CONTEXT.md +0 -191
  135. package/docs/COMMIT-HASH-BACKFILL.md +0 -51
  136. package/docs/LEGACY-CLI-CROSSWALK.md +0 -175
  137. package/docs/ROADMAP.md +0 -101
  138. package/docs/STATE-DB-ACCESS-POLICY.md +0 -161
  139. package/hooks/active_task_context.py +0 -152
  140. package/hooks/context_envelope.py +0 -100
  141. package/hooks/health_check.py +0 -196
  142. package/hooks/pre_stop_check.py +0 -66
  143. package/hooks/runtime_paths.py +0 -396
  144. package/hooks/subagent_tracker.py +0 -88
  145. package/hooks/workflow_checkpoint.py +0 -76
  146. package/hooks/workflow_context.py +0 -48
  147. package/hooks/workflow_resume.py +0 -84
  148. package/mcp-server/hook-context.cjs +0 -38
  149. package/mcp-server/hook-event.cjs +0 -34
  150. package/mcp-server/lib/archive-journal.cjs +0 -1119
  151. package/mcp-server/lib/archive-mutation-worker.py +0 -365
  152. package/mcp-server/lib/artifact-registry.cjs +0 -1232
  153. package/mcp-server/lib/baseline-workflow.cjs +0 -1193
  154. package/mcp-server/lib/canonical-json.cjs +0 -19
  155. package/mcp-server/lib/change-authority.cjs +0 -26
  156. package/mcp-server/lib/change-packet.cjs +0 -790
  157. package/mcp-server/lib/change-workflow.cjs +0 -65
  158. package/mcp-server/lib/context-envelope.cjs +0 -581
  159. package/mcp-server/lib/context-paths.cjs +0 -142
  160. package/mcp-server/lib/decision-records.cjs +0 -423
  161. package/mcp-server/lib/delivery-transaction.cjs +0 -471
  162. package/mcp-server/lib/doctor-backup-worker.cjs +0 -328
  163. package/mcp-server/lib/doctor.cjs +0 -627
  164. package/mcp-server/lib/git-bootstrap.cjs +0 -651
  165. package/mcp-server/lib/init-project.cjs +0 -1021
  166. package/mcp-server/lib/kernel-change-workflow.cjs +0 -2029
  167. package/mcp-server/lib/legacy-change-workflow.cjs +0 -3032
  168. package/mcp-server/lib/managed-file-write.cjs +0 -172
  169. package/mcp-server/lib/plan-checkpoint.cjs +0 -177
  170. package/mcp-server/lib/plan-publication-worker.cjs +0 -830
  171. package/mcp-server/lib/plan-store.cjs +0 -794
  172. package/mcp-server/lib/prd-parser.cjs +0 -83
  173. package/mcp-server/lib/pricing.cjs +0 -44
  174. package/mcp-server/lib/project-context.cjs +0 -178
  175. package/mcp-server/lib/projector.cjs +0 -386
  176. package/mcp-server/lib/provider-refs.cjs +0 -42
  177. package/mcp-server/lib/runtime-paths.cjs +0 -1714
  178. package/mcp-server/lib/runtime-state.cjs +0 -222
  179. package/mcp-server/lib/safe-project-file.cjs +0 -385
  180. package/mcp-server/lib/stage-checkpoints.cjs +0 -463
  181. package/mcp-server/lib/state-authority.cjs +0 -95
  182. package/mcp-server/lib/state-db.cjs +0 -1580
  183. package/mcp-server/lib/state-ops.cjs +0 -1428
  184. package/mcp-server/lib/task-contract.cjs +0 -404
  185. package/mcp-server/lib/task-expander.cjs +0 -105
  186. package/mcp-server/lib/task-ledger.cjs +0 -1964
  187. package/mcp-server/lib/telemetry.cjs +0 -85
  188. package/mcp-server/lib/topo.cjs +0 -131
  189. package/mcp-server/lib/ultra-facade.cjs +0 -2684
  190. package/mcp-server/lib/worker-packet.cjs +0 -358
  191. package/mcp-server/server.cjs +0 -437
  192. package/orchestrator/auto-merge.cjs +0 -128
  193. package/orchestrator/daemon.cjs +0 -264
  194. package/orchestrator/dispatch-rules.cjs +0 -78
  195. package/orchestrator/parallel-orchestrator.cjs +0 -903
  196. package/orchestrator/planner/plan-builder.cjs +0 -125
  197. package/orchestrator/recovery.cjs +0 -188
  198. package/orchestrator/session-close-journal-worker.cjs +0 -5
  199. package/orchestrator/session-close-journal.cjs +0 -1014
  200. package/orchestrator/session-runner.cjs +0 -1153
  201. package/orchestrator/worktree-manager.cjs +0 -282
  202. package/settings.json +0 -7
  203. package/skills/cc-collab/SKILL.md +0 -42
  204. package/skills/code-review-expert/SKILL.md +0 -43
  205. package/skills/code-review-expert/references/correctness-reliability.md +0 -30
  206. package/skills/code-review-expert/references/design-boundaries.md +0 -26
  207. package/skills/code-review-expert/references/removal-plan.md +0 -24
  208. package/skills/codex-collab/SKILL.md +0 -49
  209. package/skills/codex-collab/evals/evals.json +0 -35
  210. package/skills/integration-rules/SKILL.md +0 -32
  211. package/skills/security-rules/SKILL.md +0 -38
  212. package/skills/testing-rules/SKILL.md +0 -32
  213. package/skills/ultra-doctor/SKILL.md +0 -41
  214. package/skills/ultra-plan/references/semantic-preflight.md +0 -20
  215. package/skills/ultra-verify/SKILL.md +0 -46
  216. package/skills/ultra-verify/evals/evals.json +0 -46
  217. package/skills/ultra-verify/references/cross-verify-modes.md +0 -28
  218. package/skills/ultra-verify/references/evidence-status.md +0 -21
  219. package/skills/ultra-verify/scripts/verify_wait.py +0 -94
  220. package/spec/cli-protocol.md +0 -102
  221. package/spec/mcp-tools.yaml +0 -312
  222. package/spec/schemas/baseline-reconciliation.v1.schema.json +0 -73
  223. package/spec/schemas/change-delta.v1.schema.json +0 -153
  224. package/spec/schemas/command-manifest.schema.json +0 -29
  225. package/spec/schemas/context-file.v4.5.schema.json +0 -54
  226. package/spec/schemas/delivery-report.v1.schema.json +0 -66
  227. package/spec/schemas/documentation-reconciliation.v1.schema.json +0 -106
  228. package/spec/schemas/mcp-tools.schema.json +0 -136
  229. package/spec/schemas/skill-manifest.schema.json +0 -24
  230. package/spec/schemas/state-db.sql +0 -641
  231. package/spec/schemas/task-ledger.v2.schema.json +0 -296
  232. package/spec/schemas/tasks.v4.5.schema.json +0 -96
  233. package/spec/schemas/test-report.v1.schema.json +0 -225
  234. package/ultra-tools/cli.cjs +0 -129
  235. package/ultra-tools/commands/db.cjs +0 -226
  236. package/ultra-tools/commands/legacy-memory.cjs +0 -159
  237. package/ultra-tools/commands/migrate.cjs +0 -1386
  238. package/ultra-tools/commands/session.cjs +0 -213
  239. package/ultra-tools/commands/status.cjs +0 -559
  240. package/ultra-tools/commands/system.cjs +0 -498
  241. package/ultra-tools/commands/task.cjs +0 -105
@@ -1,1580 +0,0 @@
1
- 'use strict';
2
-
3
- const fs = require('node:fs');
4
- const path = require('node:path');
5
-
6
- const Database = require('better-sqlite3');
7
-
8
- const REPO_ROOT = process.env.UBP_RUNTIME_ROOT
9
- ? path.resolve(process.env.UBP_RUNTIME_ROOT)
10
- : path.resolve(__dirname, '..', '..');
11
- const SCHEMA_FILE = path.join(REPO_ROOT, 'spec', 'schemas', 'state-db.sql');
12
- const EXPECTED_VERSION = '23.0';
13
- const KIMI_SCHEMA_VERSION = '9.1';
14
- const CONTEXT_SCHEMA_VERSION = '10.0';
15
- const BASELINE_SCHEMA_VERSION = '11.0';
16
- const ADOPTION_SCHEMA_VERSION = '12.0';
17
- const WORKFLOW_SCHEMA_VERSION = '13.0';
18
- const AUTHORITY_SCHEMA_VERSION = '14.0';
19
- const SEMANTIC_SCHEMA_VERSION = '15.0';
20
- const DIALOGUE_SCHEMA_VERSION = '16.0';
21
- const GIT_AUTHORITY_SCHEMA_VERSION = '17.0';
22
- const ADAPTIVE_SCHEMA_VERSION = '18.0';
23
- const DECISION_COMPLETION_SCHEMA_VERSION = '19.0';
24
- const ARTIFACT_REGISTRY_SCHEMA_VERSION = '20.0';
25
- const KERNEL_SCHEMA_VERSION = '21.0';
26
- const CHECKPOINT_NATIVE_SCHEMA_VERSION = '22.0';
27
- const SEMANTIC_KERNEL_SCHEMA_VERSION = '23.0';
28
-
29
- const MIGRATED_GAPS = Object.freeze([{
30
- id: 'legacy-rebaseline-required',
31
- category: 'baseline_blocker',
32
- status: 'open',
33
- blocking: true,
34
- summary: 'Legacy Ultra state requires evidence-backed brownfield re-adoption.',
35
- evidence_refs: [],
36
- owner: null,
37
- }]);
38
-
39
- const REQUIRED_TABLES = Object.freeze([
40
- 'baselines',
41
- 'tasks',
42
- 'events',
43
- 'sessions',
44
- 'schema_version',
45
- 'migration_history',
46
- 'telemetry',
47
- 'specs_refs',
48
- 'circuit_breaker',
49
- 'changes',
50
- 'artifacts',
51
- 'artifact_edges',
52
- 'context_snapshots',
53
- 'spec_learning_candidates',
54
- 'trace_links',
55
- 'incidents',
56
- 'projection_jobs',
57
- 'event_consumers',
58
- 'workflow_runs',
59
- 'workflow_steps',
60
- 'decision_threads',
61
- 'decision_items',
62
- 'stage_checkpoints',
63
- 'decision_records',
64
- 'context_envelopes',
65
- 'worker_packets',
66
- ]);
67
-
68
- class ActiveSessionLeaseConflictError extends Error {
69
- constructor(conflicts) {
70
- super(
71
- 'state.db has duplicate active task leases; resolve the conflicting sessions before upgrade: '
72
- + conflicts.map((row) => `${row.task_id}=[${row.session_ids.join(',')}]`).join('; '),
73
- );
74
- this.name = 'ActiveSessionLeaseConflictError';
75
- this.code = 'ACTIVE_SESSION_LEASE_CONFLICT';
76
- this.details = { conflicts };
77
- }
78
- }
79
-
80
- class ArtifactAuthorityConflictError extends Error {
81
- constructor(conflicts) {
82
- super(
83
- 'state.db has duplicate active artifact authority for canonical paths: '
84
- + conflicts.map((row) => `${row.path}=[${row.artifact_ids.join(',')}]`).join('; '),
85
- );
86
- this.name = 'ArtifactAuthorityConflictError';
87
- this.code = 'ARTIFACT_AUTHORITY_CONFLICT';
88
- this.details = { conflicts };
89
- }
90
- }
91
-
92
- function canonicalArtifactPath(value) {
93
- const raw = String(value || '').trim().replaceAll('\\', '/');
94
- if (!raw || path.posix.isAbsolute(raw)) {
95
- throw new ArtifactAuthorityConflictError([{
96
- path: raw || '(empty)',
97
- artifact_ids: [],
98
- reason: 'invalid_path',
99
- }]);
100
- }
101
- const normalized = path.posix.normalize(raw).replace(/^\.\//, '');
102
- if (normalized === '..' || normalized.startsWith('../') || normalized.includes('/../')) {
103
- throw new ArtifactAuthorityConflictError([{
104
- path: raw,
105
- artifact_ids: [],
106
- reason: 'path_escape',
107
- }]);
108
- }
109
- return normalized;
110
- }
111
-
112
- function activeArtifactPathConflicts(db) {
113
- if (!tableNames(db).includes('artifacts')) return [];
114
- const columns = columnNames(db, 'artifacts');
115
- if (!columns.has('id') || !columns.has('path')) return [];
116
- const hasStatus = columns.has('status');
117
- const rows = db.prepare(
118
- `SELECT id, path FROM artifacts${hasStatus ? " WHERE status <> 'archived'" : ''}
119
- ORDER BY id`,
120
- ).all();
121
- const grouped = new Map();
122
- for (const row of rows) {
123
- const canonical = canonicalArtifactPath(row.path);
124
- const ids = grouped.get(canonical) || [];
125
- ids.push(row.id);
126
- grouped.set(canonical, ids);
127
- }
128
- return [...grouped.entries()]
129
- .filter(([, ids]) => ids.length > 1)
130
- .map(([artifactPath, ids]) => ({ path: artifactPath, artifact_ids: ids }));
131
- }
132
-
133
- function assertNoDuplicateActiveArtifactPaths(db) {
134
- const conflicts = activeArtifactPathConflicts(db);
135
- if (conflicts.length > 0) throw new ArtifactAuthorityConflictError(conflicts);
136
- return true;
137
- }
138
-
139
- function normalizeStoredArtifactPaths(db) {
140
- if (!tableNames(db).includes('artifacts')) return false;
141
- assertNoDuplicateActiveArtifactPaths(db);
142
- const rows = db.prepare('SELECT id, path FROM artifacts ORDER BY id').all();
143
- let changed = false;
144
- const update = db.prepare('UPDATE artifacts SET path = ? WHERE id = ?');
145
- for (const row of rows) {
146
- const canonical = canonicalArtifactPath(row.path);
147
- if (canonical !== row.path) {
148
- update.run(canonical, row.id);
149
- changed = true;
150
- }
151
- }
152
- return changed;
153
- }
154
-
155
- function applyBaselineUpgrade(db, { legacyState = false } = {}) {
156
- db.exec(`
157
- CREATE TABLE IF NOT EXISTS baselines (
158
- id TEXT PRIMARY KEY,
159
- project_name TEXT NOT NULL,
160
- project_type TEXT,
161
- stack TEXT,
162
- mode TEXT NOT NULL CHECK (mode IN ('greenfield', 'brownfield', 'migrated')),
163
- status TEXT NOT NULL DEFAULT 'draft'
164
- CHECK (status IN ('draft', 'adopting', 'blocked', 'ready', 'superseded')),
165
- repository_root TEXT NOT NULL DEFAULT '.',
166
- scope_json TEXT NOT NULL DEFAULT '["."]',
167
- repository_revision TEXT,
168
- repository_branch TEXT,
169
- worktree_state TEXT NOT NULL DEFAULT 'unavailable'
170
- CHECK (worktree_state IN ('clean', 'dirty', 'unborn', 'unavailable')),
171
- worktree_digest TEXT,
172
- worktree_files_json TEXT NOT NULL DEFAULT '[]',
173
- worktree_accepted INTEGER NOT NULL DEFAULT 0 CHECK (worktree_accepted IN (0, 1)),
174
- known_red_accepted INTEGER NOT NULL DEFAULT 0 CHECK (known_red_accepted IN (0, 1)),
175
- spec_refs_json TEXT NOT NULL DEFAULT '[]',
176
- evidence_json TEXT NOT NULL DEFAULT '[]',
177
- verification_json TEXT NOT NULL DEFAULT '[]',
178
- unknowns_json TEXT NOT NULL DEFAULT '[]',
179
- gaps_json TEXT NOT NULL DEFAULT '[]',
180
- classification_json TEXT NOT NULL DEFAULT '{}',
181
- provider_refs_json TEXT NOT NULL DEFAULT '{}',
182
- research_run_id TEXT,
183
- research_checkpoint_id TEXT,
184
- approved_by TEXT,
185
- approval_note TEXT,
186
- started_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')),
187
- updated_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')),
188
- converged_at TEXT
189
- );
190
- CREATE INDEX IF NOT EXISTS baselines_status ON baselines(status, updated_at);
191
- `);
192
- if (legacyState && db.prepare('SELECT COUNT(*) AS count FROM baselines').get().count === 0) {
193
- db.prepare(
194
- `INSERT OR IGNORE INTO baselines
195
- (id, project_name, mode, status, gaps_json, approval_note)
196
- VALUES ('migrated-baseline', 'Migrated Ultra project', 'migrated', 'adopting', ?,
197
- 'Legacy project preserved; owner re-adoption is required')`,
198
- ).run(JSON.stringify(MIGRATED_GAPS));
199
- }
200
- }
201
-
202
- function addColumnIfMissing(db, table, columns, name, definition) {
203
- if (!columns.has(name)) {
204
- db.exec(`ALTER TABLE ${table} ADD COLUMN ${name} ${definition}`);
205
- columns.add(name);
206
- return true;
207
- }
208
- return false;
209
- }
210
-
211
- function applyBaselineColumns(db) {
212
- if (!tableNames(db).includes('baselines')) return false;
213
- const columns = columnNames(db, 'baselines');
214
- let changed = false;
215
- changed = addColumnIfMissing(db, 'baselines', columns, 'repository_branch', 'TEXT') || changed;
216
- changed = addColumnIfMissing(
217
- db, 'baselines', columns, 'worktree_state',
218
- "TEXT NOT NULL DEFAULT 'unavailable' CHECK (worktree_state IN ('clean', 'dirty', 'unborn', 'unavailable'))",
219
- ) || changed;
220
- changed = addColumnIfMissing(db, 'baselines', columns, 'worktree_digest', 'TEXT') || changed;
221
- changed = addColumnIfMissing(
222
- db, 'baselines', columns, 'worktree_files_json', "TEXT NOT NULL DEFAULT '[]'",
223
- ) || changed;
224
- changed = addColumnIfMissing(
225
- db, 'baselines', columns, 'worktree_accepted',
226
- 'INTEGER NOT NULL DEFAULT 0 CHECK (worktree_accepted IN (0, 1))',
227
- ) || changed;
228
- changed = addColumnIfMissing(
229
- db, 'baselines', columns, 'known_red_accepted',
230
- 'INTEGER NOT NULL DEFAULT 0 CHECK (known_red_accepted IN (0, 1))',
231
- ) || changed;
232
- changed = addColumnIfMissing(db, 'baselines', columns, 'gaps_json', "TEXT NOT NULL DEFAULT '[]'") || changed;
233
- changed = addColumnIfMissing(
234
- db, 'baselines', columns, 'classification_json', "TEXT NOT NULL DEFAULT '{}'",
235
- ) || changed;
236
- changed = addColumnIfMissing(db, 'baselines', columns, 'research_run_id', 'TEXT') || changed;
237
- changed = addColumnIfMissing(
238
- db,
239
- 'baselines',
240
- columns,
241
- 'research_checkpoint_id',
242
- 'TEXT',
243
- ) || changed;
244
- if (columns.has('research_checkpoint_id')
245
- && tableNames(db).includes('stage_checkpoints')) {
246
- const result = db.prepare(
247
- `UPDATE baselines
248
- SET research_checkpoint_id = research_run_id
249
- WHERE research_checkpoint_id IS NULL
250
- AND research_run_id IS NOT NULL
251
- AND EXISTS (
252
- SELECT 1 FROM stage_checkpoints
253
- WHERE stage_checkpoints.id = baselines.research_run_id
254
- AND stage_checkpoints.stage = 'research'
255
- )`,
256
- ).run();
257
- changed = result.changes > 0 || changed;
258
- }
259
- return changed;
260
- }
261
-
262
- function applyKernelIntegrityColumns(db) {
263
- const tables = new Set(tableNames(db));
264
- let changed = false;
265
- if (tables.has('context_envelopes')) {
266
- const columns = columnNames(db, 'context_envelopes');
267
- changed = addColumnIfMissing(
268
- db,
269
- 'context_envelopes',
270
- columns,
271
- 'file_digest',
272
- 'TEXT',
273
- ) || changed;
274
- }
275
- if (tables.has('worker_packets')) {
276
- const columns = columnNames(db, 'worker_packets');
277
- for (const [name, definition] of [
278
- ['file_digest', 'TEXT'],
279
- ['status', "TEXT NOT NULL DEFAULT 'assigned' CHECK (status IN ('pending', 'assigned', 'abandoned'))"],
280
- ['assigned_at', 'TEXT'],
281
- ['abandoned_at', 'TEXT'],
282
- ['abandon_reason', 'TEXT'],
283
- ]) {
284
- changed = addColumnIfMissing(
285
- db,
286
- 'worker_packets',
287
- columns,
288
- name,
289
- definition,
290
- ) || changed;
291
- }
292
- }
293
- return changed;
294
- }
295
-
296
- function applyChangeColumns(db) {
297
- if (!tableNames(db).includes('changes')) return false;
298
- const columns = columnNames(db, 'changes');
299
- let changed = false;
300
- changed = addColumnIfMissing(db, 'changes', columns, 'baseline_bypass_json', 'TEXT') || changed;
301
- changed = addColumnIfMissing(db, 'changes', columns, 'contract_json', "TEXT NOT NULL DEFAULT '{}'") || changed;
302
- changed = addColumnIfMissing(db, 'changes', columns, 'classification_json', "TEXT NOT NULL DEFAULT '{}'") || changed;
303
- changed = addColumnIfMissing(
304
- db, 'changes', columns, 'research_disposition_json', "TEXT NOT NULL DEFAULT '{}'",
305
- ) || changed;
306
- changed = addColumnIfMissing(
307
- db, 'changes', columns, 'alignment_thread_id',
308
- 'TEXT REFERENCES decision_threads(id) ON DELETE SET NULL',
309
- ) || changed;
310
- changed = addColumnIfMissing(
311
- db, 'changes', columns, 'supersedes_id',
312
- 'TEXT REFERENCES changes(id) ON DELETE SET NULL',
313
- ) || changed;
314
- return changed;
315
- }
316
-
317
- function upgradeSemanticKernelConstraints(db) {
318
- const tables = new Set(tableNames(db));
319
- if (!tables.has('changes') || !tables.has('tasks')) return false;
320
- const taskColumns = columnNames(db, 'tasks');
321
- const preservesLegacyComplexityHint = taskColumns.has('complexity_hint');
322
- const changesSql = db.prepare(
323
- "SELECT sql FROM sqlite_master WHERE type = 'table' AND name = 'changes'",
324
- ).get()?.sql || '';
325
- const tasksSql = db.prepare(
326
- "SELECT sql FROM sqlite_master WHERE type = 'table' AND name = 'tasks'",
327
- ).get()?.sql || '';
328
- const current = /length\s*\(\s*trim\s*\(\s*kind\s*\)\s*\)\s+BETWEEN\s+1\s+AND\s+80/i
329
- .test(changesSql)
330
- && /length\s*\(\s*trim\s*\(\s*type\s*\)\s*\)\s+BETWEEN\s+1\s+AND\s+80/i
331
- .test(tasksSql)
332
- && /length\s*\(\s*trim\s*\(\s*priority\s*\)\s*\)\s+BETWEEN\s+1\s+AND\s+80/i
333
- .test(tasksSql)
334
- && /length\s*\(\s*trim\s*\(\s*slice_kind\s*\)\s*\)\s+BETWEEN\s+1\s+AND\s+80/i
335
- .test(tasksSql)
336
- && columnNames(db, 'changes').has('supersedes_id');
337
- if (current) return false;
338
-
339
- const foreignKeys = db.pragma('foreign_keys', { simple: true });
340
- db.pragma('foreign_keys = OFF');
341
- try {
342
- db.transaction(() => {
343
- db.exec(`
344
- DROP TABLE IF EXISTS changes_semantic_kernel_upgrade;
345
- CREATE TABLE changes_semantic_kernel_upgrade (
346
- id TEXT PRIMARY KEY,
347
- title TEXT NOT NULL,
348
- kind TEXT NOT NULL CHECK (length(trim(kind)) BETWEEN 1 AND 80),
349
- status TEXT NOT NULL DEFAULT 'active'
350
- CHECK (status IN ('active', 'blocked', 'ready', 'archived', 'cancelled')),
351
- intent TEXT NOT NULL,
352
- docs_impact_json TEXT NOT NULL DEFAULT '{"status":"unknown","files":[],"rationale":null}',
353
- provider_refs_json TEXT NOT NULL DEFAULT '{}',
354
- baseline_bypass_json TEXT,
355
- contract_json TEXT NOT NULL DEFAULT '{}',
356
- classification_json TEXT NOT NULL DEFAULT '{}',
357
- research_disposition_json TEXT NOT NULL DEFAULT '{}',
358
- alignment_thread_id TEXT REFERENCES decision_threads(id) ON DELETE SET NULL,
359
- base_commit TEXT,
360
- supersedes_id TEXT REFERENCES changes_semantic_kernel_upgrade(id) ON DELETE SET NULL,
361
- artifact_root TEXT NOT NULL,
362
- created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')),
363
- updated_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')),
364
- closed_at TEXT
365
- );
366
- INSERT INTO changes_semantic_kernel_upgrade (
367
- id, title, kind, status, intent, docs_impact_json, provider_refs_json,
368
- baseline_bypass_json, contract_json, classification_json,
369
- research_disposition_json, alignment_thread_id, base_commit,
370
- supersedes_id, artifact_root, created_at, updated_at, closed_at
371
- )
372
- SELECT
373
- id, title, kind, status, intent, docs_impact_json, provider_refs_json,
374
- baseline_bypass_json, contract_json, classification_json,
375
- research_disposition_json, alignment_thread_id, base_commit,
376
- supersedes_id, artifact_root, created_at, updated_at, closed_at
377
- FROM changes;
378
-
379
- DROP TABLE IF EXISTS tasks_semantic_kernel_upgrade;
380
- CREATE TABLE tasks_semantic_kernel_upgrade (
381
- id TEXT PRIMARY KEY,
382
- title TEXT NOT NULL,
383
- type TEXT NOT NULL CHECK (length(trim(type)) BETWEEN 1 AND 80),
384
- priority TEXT NOT NULL CHECK (length(trim(priority)) BETWEEN 1 AND 80),
385
- complexity INTEGER CHECK (complexity BETWEEN 1 AND 10),
386
- estimated_days REAL CHECK (estimated_days IS NULL OR estimated_days > 0),
387
- status TEXT NOT NULL DEFAULT 'pending'
388
- CHECK (status IN ('pending', 'in_progress', 'completed', 'blocked', 'expanded')),
389
- deps TEXT,
390
- files_modified TEXT,
391
- session_id TEXT,
392
- stale INTEGER NOT NULL DEFAULT 0 CHECK (stale IN (0, 1)),
393
- ${preservesLegacyComplexityHint ? 'complexity_hint TEXT,' : ''}
394
- tag TEXT,
395
- trace_to TEXT,
396
- outcome TEXT,
397
- slice_kind TEXT CHECK (
398
- slice_kind IS NULL OR length(trim(slice_kind)) BETWEEN 1 AND 80
399
- ),
400
- public_seam TEXT,
401
- verification_command TEXT,
402
- acceptance_json TEXT NOT NULL DEFAULT '[]',
403
- context_refs_json TEXT NOT NULL DEFAULT '[]',
404
- docs_impact_json TEXT NOT NULL DEFAULT '{"status":"unknown","files":[],"rationale":null}',
405
- ownership_json TEXT NOT NULL DEFAULT '{}',
406
- context_file TEXT,
407
- completion_commit TEXT,
408
- change_id TEXT REFERENCES changes_semantic_kernel_upgrade(id) ON DELETE SET NULL,
409
- parent_id TEXT REFERENCES tasks_semantic_kernel_upgrade(id) ON DELETE SET NULL,
410
- created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')),
411
- updated_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now'))
412
- );
413
- INSERT INTO tasks_semantic_kernel_upgrade (
414
- id, title, type, priority, complexity, estimated_days, status, deps,
415
- files_modified, session_id, stale,
416
- ${preservesLegacyComplexityHint ? 'complexity_hint,' : ''}
417
- tag, trace_to, outcome, slice_kind,
418
- public_seam, verification_command, acceptance_json, context_refs_json,
419
- docs_impact_json, ownership_json, context_file, completion_commit,
420
- change_id, parent_id, created_at, updated_at
421
- )
422
- SELECT
423
- id, title, type, priority, complexity, estimated_days, status, deps,
424
- files_modified, session_id, stale,
425
- ${preservesLegacyComplexityHint ? 'complexity_hint,' : ''}
426
- tag, trace_to, outcome, slice_kind,
427
- public_seam, verification_command, acceptance_json, context_refs_json,
428
- docs_impact_json, ownership_json, context_file, completion_commit,
429
- change_id, parent_id, created_at, updated_at
430
- FROM tasks;
431
-
432
- DROP TABLE tasks;
433
- DROP TABLE changes;
434
- ALTER TABLE changes_semantic_kernel_upgrade RENAME TO changes;
435
- ALTER TABLE tasks_semantic_kernel_upgrade RENAME TO tasks;
436
- CREATE INDEX changes_status ON changes(status, created_at);
437
- CREATE INDEX changes_kind ON changes(kind, created_at);
438
- CREATE INDEX tasks_status ON tasks(status);
439
- CREATE INDEX tasks_tag ON tasks(tag);
440
- CREATE INDEX tasks_session ON tasks(session_id) WHERE session_id IS NOT NULL;
441
- CREATE INDEX tasks_stale ON tasks(stale) WHERE stale = 1;
442
- CREATE INDEX tasks_parent ON tasks(parent_id) WHERE parent_id IS NOT NULL;
443
- CREATE INDEX tasks_change ON tasks(change_id) WHERE change_id IS NOT NULL;
444
- `);
445
- const violations = db.pragma('foreign_key_check');
446
- if (violations.length > 0) {
447
- throw new Error(
448
- `semantic kernel migration produced ${violations.length} foreign key violation(s)`,
449
- );
450
- }
451
- })();
452
- } finally {
453
- db.pragma(`foreign_keys = ${foreignKeys ? 'ON' : 'OFF'}`);
454
- }
455
- return true;
456
- }
457
-
458
- function applySemanticAuthorityColumns(db) {
459
- let changed = false;
460
- if (tableNames(db).includes('workflow_steps')) {
461
- const columns = columnNames(db, 'workflow_steps');
462
- changed = addColumnIfMissing(
463
- db, 'workflow_steps', columns, 'semantic_records_json', "TEXT NOT NULL DEFAULT '[]'",
464
- ) || changed;
465
- }
466
- if (tableNames(db).includes('spec_learning_candidates')) {
467
- const columns = columnNames(db, 'spec_learning_candidates');
468
- for (const [name, definition] of [
469
- ['applied_ref', 'TEXT'],
470
- ['before_digest', 'TEXT'],
471
- ['after_digest', 'TEXT'],
472
- ['apply_evidence_json', "TEXT NOT NULL DEFAULT '[]'"],
473
- ]) {
474
- changed = addColumnIfMissing(db, 'spec_learning_candidates', columns, name, definition) || changed;
475
- }
476
- }
477
- return changed;
478
- }
479
-
480
- function quoteSqlString(value) {
481
- return `'${String(value).replaceAll("'", "''")}'`;
482
- }
483
-
484
- function migrationBackup(db, dbPath, fromVersion, existingTables) {
485
- if (existingTables.size === 0 || fromVersion === EXPECTED_VERSION) return null;
486
- const parent = path.dirname(dbPath);
487
- const backupDir = path.basename(parent) === '.ultra' ? path.join(parent, 'backups') : path.join(parent, 'backups');
488
- fs.mkdirSync(backupDir, { recursive: true });
489
- const stamp = new Date().toISOString().replace(/[:.]/g, '-');
490
- const version = String(fromVersion || 'unknown').replace(/[^a-zA-Z0-9_.-]/g, '_');
491
- const backupPath = path.join(backupDir, `state-pre-${version}-to-${EXPECTED_VERSION}-${stamp}.db`);
492
- db.exec(`VACUUM INTO ${quoteSqlString(backupPath)}`);
493
- return backupPath;
494
- }
495
-
496
- function readSchemaSql() {
497
- if (!fs.existsSync(SCHEMA_FILE)) {
498
- throw new Error(`state-db schema missing at ${SCHEMA_FILE}`);
499
- }
500
- return fs.readFileSync(SCHEMA_FILE, 'utf8');
501
- }
502
-
503
- function applyPragmas(db) {
504
- const journalMode = db.pragma('journal_mode = WAL', { simple: true });
505
- if (String(journalMode).toLowerCase() !== 'wal') {
506
- const error = new Error(
507
- `state.db requires SQLite WAL mode; storage returned ${String(journalMode || 'unknown')}`,
508
- );
509
- error.code = 'STATE_DB_WAL_UNAVAILABLE';
510
- throw error;
511
- }
512
- db.pragma('synchronous = NORMAL');
513
- db.pragma('busy_timeout = 5000');
514
- db.pragma('foreign_keys = ON');
515
- }
516
-
517
- function runScript(db, sql) {
518
- const runner = db.exec.bind(db);
519
- db.transaction(() => runner(sql))();
520
- }
521
-
522
- function openStateDb(dbPath) {
523
- if (!dbPath) throw new Error('openStateDb: dbPath required');
524
- fs.mkdirSync(path.dirname(dbPath), { recursive: true });
525
- const db = new Database(dbPath);
526
- applyPragmas(db);
527
- return db;
528
- }
529
-
530
- function tableNames(db) {
531
- return db
532
- .prepare("SELECT name FROM sqlite_master WHERE type = 'table' AND name NOT LIKE 'sqlite_%'")
533
- .all()
534
- .map((r) => r.name);
535
- }
536
-
537
- function applySchema(db) {
538
- assertNoDuplicateActiveSessionLeases(db);
539
- runScript(db, readSchemaSql());
540
- }
541
-
542
- function columnNames(db, table) {
543
- return new Set(db.prepare(`PRAGMA table_info(${table})`).all().map((row) => row.name));
544
- }
545
-
546
- function latestSchemaVersion(db) {
547
- if (!tableNames(db).includes('schema_version')) return null;
548
- return db.prepare(
549
- 'SELECT version FROM schema_version ORDER BY applied_at DESC, rowid DESC LIMIT 1',
550
- ).get()?.version || null;
551
- }
552
-
553
- function applyCompatibleColumns(db) {
554
- const tables = new Set(tableNames(db));
555
- if (!tables.has('tasks')) return;
556
- const taskColumns = columnNames(db, 'tasks');
557
- if (!taskColumns.has('estimated_days')) {
558
- db.exec('ALTER TABLE tasks ADD COLUMN estimated_days REAL CHECK (estimated_days IS NULL OR estimated_days > 0)');
559
- }
560
- if (!taskColumns.has('change_id')) {
561
- db.exec('ALTER TABLE tasks ADD COLUMN change_id TEXT REFERENCES changes(id) ON DELETE SET NULL');
562
- }
563
- const taskAdditions = [
564
- ['outcome', 'TEXT'],
565
- ['slice_kind', 'TEXT CHECK (slice_kind IS NULL OR length(trim(slice_kind)) BETWEEN 1 AND 80)'],
566
- ['public_seam', 'TEXT'],
567
- ['verification_command', 'TEXT'],
568
- ['acceptance_json', "TEXT NOT NULL DEFAULT '[]'"],
569
- ['context_refs_json', "TEXT NOT NULL DEFAULT '[]'"],
570
- ['docs_impact_json', "TEXT NOT NULL DEFAULT '{\"status\":\"unknown\",\"files\":[],\"rationale\":null}'"],
571
- ['ownership_json', "TEXT NOT NULL DEFAULT '{}'"],
572
- ];
573
- for (const [name, definition] of taskAdditions) {
574
- if (!taskColumns.has(name)) db.exec(`ALTER TABLE tasks ADD COLUMN ${name} ${definition}`);
575
- }
576
- if (tables.has('events')) {
577
- const eventColumns = columnNames(db, 'events');
578
- if (!eventColumns.has('change_id')) db.exec('ALTER TABLE events ADD COLUMN change_id TEXT');
579
- }
580
- }
581
-
582
- function applyContextSpineUpgrade(db) {
583
- const tables = new Set(tableNames(db));
584
- if (!tables.has('context_snapshots')) return false;
585
- let changed = !tables.has('spec_learning_candidates');
586
- const columns = columnNames(db, 'context_snapshots');
587
- const additions = [
588
- ['role', "TEXT NOT NULL DEFAULT 'plan' CHECK (role IN ('plan', 'implement', 'check', 'review'))"],
589
- ['gate', "TEXT NOT NULL DEFAULT 'alignment' CHECK (gate IN ('alignment', 'planning', 'implementation', 'verification', 'review', 'convergence', 'recovery'))"],
590
- ['next_action', "TEXT NOT NULL DEFAULT ''"],
591
- ['allowed_transitions_json', "TEXT NOT NULL DEFAULT '[]'"],
592
- ['required_transition', 'TEXT'],
593
- ['readiness', "TEXT NOT NULL DEFAULT 'ready' CHECK (readiness IN ('ready', 'blocked'))"],
594
- ['blockers_json', "TEXT NOT NULL DEFAULT '[]'"],
595
- ['context_json', "TEXT NOT NULL DEFAULT '{}'"],
596
- ['token_estimate', 'INTEGER NOT NULL DEFAULT 0 CHECK (token_estimate >= 0)'],
597
- ['token_budget', 'INTEGER NOT NULL DEFAULT 12000 CHECK (token_budget > 0)'],
598
- ];
599
- for (const [name, definition] of additions) {
600
- if (!columns.has(name)) {
601
- db.exec(`ALTER TABLE context_snapshots ADD COLUMN ${name} ${definition}`);
602
- changed = true;
603
- }
604
- }
605
- db.exec(`
606
- CREATE TABLE IF NOT EXISTS spec_learning_candidates (
607
- id TEXT PRIMARY KEY,
608
- change_id TEXT NOT NULL REFERENCES changes(id) ON DELETE CASCADE,
609
- task_id TEXT REFERENCES tasks(id) ON DELETE SET NULL,
610
- target_ref TEXT NOT NULL,
611
- summary TEXT NOT NULL,
612
- evidence_json TEXT NOT NULL DEFAULT '[]',
613
- status TEXT NOT NULL DEFAULT 'proposed'
614
- CHECK (status IN ('proposed', 'approved', 'rejected', 'applied')),
615
- resolution TEXT,
616
- proposed_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')),
617
- resolved_at TEXT,
618
- applied_at TEXT
619
- );
620
- CREATE INDEX IF NOT EXISTS spec_learning_change
621
- ON spec_learning_candidates(change_id, status, proposed_at);
622
- `);
623
- return changed;
624
- }
625
-
626
- function migrateAdaptiveWorkflowRuns(db, fromVersion = latestSchemaVersion(db)) {
627
- const tables = new Set(tableNames(db));
628
- if (!tables.has('workflow_runs') || !tables.has('workflow_steps')) return false;
629
- const existing = db.prepare(
630
- "SELECT 1 FROM migration_history WHERE to_version = '18.0' AND status = 'success' LIMIT 1",
631
- ).get();
632
- if ([
633
- ADAPTIVE_SCHEMA_VERSION, DECISION_COMPLETION_SCHEMA_VERSION,
634
- ARTIFACT_REGISTRY_SCHEMA_VERSION, KERNEL_SCHEMA_VERSION,
635
- CHECKPOINT_NATIVE_SCHEMA_VERSION, SEMANTIC_KERNEL_SCHEMA_VERSION,
636
- ].includes(fromVersion) || existing) {
637
- return false;
638
- }
639
-
640
- const obsoleteSteps = Object.freeze({
641
- init: ['establish-baseline'],
642
- change: ['plan-change', 'compile-context', 'verify-readiness'],
643
- plan: ['select-posture', 'approve-plan'],
644
- deliver: ['release-if-authorized'],
645
- });
646
- const activeRuns = db.prepare(
647
- `SELECT id, kind, status FROM workflow_runs
648
- WHERE status IN ('active', 'blocked', 'ready')`,
649
- ).all();
650
- const isCompleted = db.prepare(
651
- "SELECT status FROM workflow_steps WHERE run_id = ? AND step_id = ?",
652
- );
653
- const currentRequired = db.prepare(
654
- `SELECT step_id FROM workflow_steps
655
- WHERE run_id = ? AND required = 1 AND status NOT IN ('completed', 'skipped')
656
- ORDER BY position ASC LIMIT 1`,
657
- );
658
- const skipObsolete = db.prepare(
659
- `UPDATE workflow_steps
660
- SET required = 0,
661
- status = CASE WHEN status IN ('pending', 'in_progress', 'blocked') THEN 'skipped' ELSE status END,
662
- blockers_json = CASE WHEN status IN ('pending', 'in_progress', 'blocked') THEN '[]' ELSE blockers_json END,
663
- skip_reason = CASE
664
- WHEN status IN ('pending', 'in_progress', 'blocked')
665
- THEN 'Removed by schema 18 adaptive workflow migration; durable evidence is preserved.'
666
- ELSE skip_reason
667
- END,
668
- completed_at = CASE
669
- WHEN status IN ('pending', 'in_progress', 'blocked')
670
- THEN strftime('%Y-%m-%dT%H:%M:%fZ', 'now')
671
- ELSE completed_at
672
- END,
673
- updated_at = strftime('%Y-%m-%dT%H:%M:%fZ', 'now')
674
- WHERE run_id = ? AND step_id = ?`,
675
- );
676
- const completeRun = db.prepare(
677
- `UPDATE workflow_runs
678
- SET status = 'completed', current_step = NULL, blockers_json = '[]',
679
- definition_version = '2.0', summary_json = ?,
680
- updated_at = strftime('%Y-%m-%dT%H:%M:%fZ', 'now'),
681
- completed_at = COALESCE(completed_at, strftime('%Y-%m-%dT%H:%M:%fZ', 'now'))
682
- WHERE id = ?`,
683
- );
684
- for (const run of activeRuns) {
685
- for (const stepId of obsoleteSteps[run.kind] || []) skipObsolete.run(run.id, stepId);
686
-
687
- if (run.kind === 'init') {
688
- const scaffoldReady = ['inspect-authority', 'classify-repository', 'scaffold-authority']
689
- .every((stepId) => isCompleted.get(run.id, stepId)?.status === 'completed');
690
- if (scaffoldReady) {
691
- db.prepare(
692
- `UPDATE workflow_steps
693
- SET status = 'completed',
694
- evidence_json = CASE WHEN evidence_json = '[]' THEN ? ELSE evidence_json END,
695
- blockers_json = '[]',
696
- started_at = COALESCE(started_at, strftime('%Y-%m-%dT%H:%M:%fZ', 'now')),
697
- completed_at = COALESCE(completed_at, strftime('%Y-%m-%dT%H:%M:%fZ', 'now')),
698
- updated_at = strftime('%Y-%m-%dT%H:%M:%fZ', 'now')
699
- WHERE run_id = ? AND step_id = 'verify-initialization'
700
- AND status IN ('pending', 'in_progress', 'blocked')`,
701
- ).run(JSON.stringify([{
702
- kind: 'migration',
703
- ref: 'schema:18.0',
704
- summary: 'Existing scaffold and durable authority were preserved during adaptive migration.',
705
- }]), run.id);
706
- if (!currentRequired.get(run.id)) {
707
- completeRun.run(JSON.stringify({
708
- migrated: true,
709
- authority_basis: 'initialized_scaffold',
710
- research_started: false,
711
- }), run.id);
712
- continue;
713
- }
714
- }
715
- }
716
-
717
- if (run.kind === 'change') {
718
- const contractCaptured = ['bind-baseline', 'classify-change', 'record-intent']
719
- .every((stepId) => isCompleted.get(run.id, stepId)?.status === 'completed');
720
- if (contractCaptured && !currentRequired.get(run.id)) {
721
- completeRun.run(JSON.stringify({
722
- migrated: true,
723
- authority_basis: 'accepted_change_contract',
724
- }), run.id);
725
- continue;
726
- }
727
- }
728
-
729
- const next = currentRequired.get(run.id)?.step_id || null;
730
- db.prepare(
731
- `UPDATE workflow_runs
732
- SET definition_version = '2.0', current_step = ?, blockers_json = '[]',
733
- status = CASE
734
- WHEN ? IS NULL AND status IN ('active', 'blocked') THEN 'ready'
735
- WHEN ? IS NOT NULL AND status = 'blocked' THEN 'active'
736
- ELSE status
737
- END,
738
- updated_at = strftime('%Y-%m-%dT%H:%M:%fZ', 'now')
739
- WHERE id = ?`,
740
- ).run(next, next, next, run.id);
741
- }
742
- db.prepare(
743
- `UPDATE context_snapshots
744
- SET allowed_transitions_json = COALESCE(allowed_transitions_json, '[]'),
745
- required_transition = NULL
746
- WHERE allowed_transitions_json IS NULL OR required_transition IS NOT NULL`,
747
- ).run();
748
- return true;
749
- }
750
-
751
- function tableSupportsRuntime(db, table, runtime) {
752
- const row = db.prepare(
753
- "SELECT sql FROM sqlite_master WHERE type = 'table' AND name = ?",
754
- ).get(table);
755
- return typeof row?.sql === 'string' && row.sql.includes(`'${runtime}'`);
756
- }
757
-
758
- function baselineSupportsUnbornGit(db) {
759
- const row = db.prepare(
760
- "SELECT sql FROM sqlite_master WHERE type = 'table' AND name = 'baselines'",
761
- ).get();
762
- return typeof row?.sql === 'string' && row.sql.includes("'unborn'");
763
- }
764
-
765
- function decisionThreadsSupportCompletion(db) {
766
- const row = db.prepare(
767
- "SELECT sql FROM sqlite_master WHERE type = 'table' AND name = 'decision_threads'",
768
- ).get();
769
- if (typeof row?.sql !== 'string' || !row.sql.includes("'completed'")) return false;
770
- return columnNames(db, 'decision_threads').has('completed_at');
771
- }
772
-
773
- function upgradeDecisionThreadLifecycle(db) {
774
- if (!tableNames(db).includes('decision_threads') || decisionThreadsSupportCompletion(db)) {
775
- return false;
776
- }
777
- const foreignKeys = db.pragma('foreign_keys', { simple: true });
778
- db.pragma('foreign_keys = OFF');
779
- try {
780
- db.transaction(() => {
781
- db.exec(`
782
- DROP TABLE IF EXISTS decision_threads_completion_upgrade;
783
- CREATE TABLE decision_threads_completion_upgrade (
784
- id TEXT PRIMARY KEY,
785
- purpose TEXT NOT NULL,
786
- mode TEXT NOT NULL DEFAULT 'guided'
787
- CHECK (mode IN ('guided', 'fast', 'autonomous', 'diagnostic')),
788
- status TEXT NOT NULL DEFAULT 'active'
789
- CHECK (status IN ('active', 'checkpoint_ready', 'completed', 'confirmed', 'cancelled')),
790
- baseline_id TEXT REFERENCES baselines(id) ON DELETE SET NULL,
791
- change_id TEXT REFERENCES changes(id) ON DELETE CASCADE,
792
- workflow_run_id TEXT REFERENCES workflow_runs(id) ON DELETE CASCADE,
793
- summary_json TEXT NOT NULL DEFAULT '{}',
794
- checkpoint_json TEXT NOT NULL DEFAULT '{}',
795
- started_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')),
796
- updated_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')),
797
- completed_at TEXT,
798
- confirmed_at TEXT,
799
- CHECK (baseline_id IS NOT NULL OR change_id IS NOT NULL OR workflow_run_id IS NOT NULL)
800
- );
801
- INSERT INTO decision_threads_completion_upgrade (
802
- id, purpose, mode, status, baseline_id, change_id, workflow_run_id,
803
- summary_json, checkpoint_json, started_at, updated_at, completed_at, confirmed_at
804
- )
805
- SELECT
806
- id, purpose, mode, status, baseline_id, change_id, workflow_run_id,
807
- summary_json, checkpoint_json, started_at, updated_at, NULL, confirmed_at
808
- FROM decision_threads;
809
- DROP TABLE decision_threads;
810
- ALTER TABLE decision_threads_completion_upgrade RENAME TO decision_threads;
811
- CREATE INDEX decision_threads_status
812
- ON decision_threads(status, updated_at);
813
- CREATE INDEX decision_threads_authority
814
- ON decision_threads(baseline_id, change_id, workflow_run_id, status);
815
- `);
816
- db.prepare(
817
- `UPDATE decision_threads
818
- SET status = 'completed',
819
- summary_json = CASE
820
- WHEN summary_json = '{}' THEN ?
821
- ELSE json_set(summary_json, '$.completion_kind', 'migrated_settled_thread')
822
- END,
823
- completed_at = updated_at
824
- WHERE status = 'active'
825
- AND EXISTS (
826
- SELECT 1 FROM decision_items WHERE decision_items.thread_id = decision_threads.id
827
- )
828
- AND NOT EXISTS (
829
- SELECT 1 FROM decision_items
830
- WHERE decision_items.thread_id = decision_threads.id AND status = 'open'
831
- )
832
- AND NOT EXISTS (
833
- SELECT 1 FROM decision_items
834
- WHERE decision_items.thread_id = decision_threads.id
835
- AND blocking = 1 AND status = 'deferred'
836
- )`,
837
- ).run(JSON.stringify({
838
- text: 'Existing normalized decision state was completed during schema migration.',
839
- completion_kind: 'migrated_settled_thread',
840
- }));
841
- const violations = db.pragma('foreign_key_check');
842
- if (violations.length > 0) {
843
- throw new Error(
844
- `decision completion migration produced ${violations.length} foreign key violation(s)`,
845
- );
846
- }
847
- })();
848
- } finally {
849
- db.pragma(`foreign_keys = ${foreignKeys ? 'ON' : 'OFF'}`);
850
- }
851
- return true;
852
- }
853
-
854
- function upgradeBaselineWorktreeConstraint(db, fromVersion = latestSchemaVersion(db)) {
855
- if (!tableNames(db).includes('baselines') || baselineSupportsUnbornGit(db)) return false;
856
- const previousVersion = fromVersion || 'unknown';
857
- const foreignKeys = db.pragma('foreign_keys', { simple: true });
858
- db.pragma('foreign_keys = OFF');
859
- try {
860
- db.transaction(() => {
861
- db.exec(`
862
- DROP TABLE IF EXISTS baselines_git_upgrade;
863
- CREATE TABLE baselines_git_upgrade (
864
- id TEXT PRIMARY KEY,
865
- project_name TEXT NOT NULL,
866
- project_type TEXT,
867
- stack TEXT,
868
- mode TEXT NOT NULL CHECK (mode IN ('greenfield', 'brownfield', 'migrated')),
869
- status TEXT NOT NULL DEFAULT 'draft'
870
- CHECK (status IN ('draft', 'adopting', 'blocked', 'ready', 'superseded')),
871
- repository_root TEXT NOT NULL DEFAULT '.',
872
- scope_json TEXT NOT NULL DEFAULT '["."]',
873
- repository_revision TEXT,
874
- repository_branch TEXT,
875
- worktree_state TEXT NOT NULL DEFAULT 'unavailable'
876
- CHECK (worktree_state IN ('clean', 'dirty', 'unborn', 'unavailable')),
877
- worktree_digest TEXT,
878
- worktree_files_json TEXT NOT NULL DEFAULT '[]',
879
- worktree_accepted INTEGER NOT NULL DEFAULT 0 CHECK (worktree_accepted IN (0, 1)),
880
- known_red_accepted INTEGER NOT NULL DEFAULT 0 CHECK (known_red_accepted IN (0, 1)),
881
- spec_refs_json TEXT NOT NULL DEFAULT '[]',
882
- evidence_json TEXT NOT NULL DEFAULT '[]',
883
- verification_json TEXT NOT NULL DEFAULT '[]',
884
- unknowns_json TEXT NOT NULL DEFAULT '[]',
885
- gaps_json TEXT NOT NULL DEFAULT '[]',
886
- classification_json TEXT NOT NULL DEFAULT '{}',
887
- provider_refs_json TEXT NOT NULL DEFAULT '{}',
888
- research_run_id TEXT,
889
- research_checkpoint_id TEXT,
890
- approved_by TEXT,
891
- approval_note TEXT,
892
- started_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')),
893
- updated_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')),
894
- converged_at TEXT
895
- );
896
- INSERT INTO baselines_git_upgrade (
897
- id, project_name, project_type, stack, mode, status, repository_root, scope_json,
898
- repository_revision, repository_branch, worktree_state, worktree_digest,
899
- worktree_files_json, worktree_accepted, known_red_accepted, spec_refs_json,
900
- evidence_json, verification_json, unknowns_json, gaps_json, classification_json,
901
- provider_refs_json, research_run_id, research_checkpoint_id,
902
- approved_by, approval_note, started_at,
903
- updated_at, converged_at
904
- )
905
- SELECT
906
- id, project_name, project_type, stack, mode, status, repository_root, scope_json,
907
- repository_revision, repository_branch, worktree_state, worktree_digest,
908
- worktree_files_json, worktree_accepted, known_red_accepted, spec_refs_json,
909
- evidence_json, verification_json, unknowns_json, gaps_json, classification_json,
910
- provider_refs_json, research_run_id, research_checkpoint_id,
911
- approved_by, approval_note, started_at,
912
- updated_at, converged_at
913
- FROM baselines;
914
- DROP TABLE baselines;
915
- ALTER TABLE baselines_git_upgrade RENAME TO baselines;
916
- CREATE INDEX baselines_status ON baselines(status, updated_at);
917
- `);
918
- const violations = db.pragma('foreign_key_check');
919
- if (violations.length > 0) {
920
- throw new Error(
921
- `baseline Git-state migration produced ${violations.length} foreign key violation(s)`,
922
- );
923
- }
924
- })();
925
- } finally {
926
- db.pragma(`foreign_keys = ${foreignKeys ? 'ON' : 'OFF'}`);
927
- }
928
- return previousVersion;
929
- }
930
-
931
- function upgradeRuntimeConstraints(db, fromVersion = latestSchemaVersion(db)) {
932
- assertNoDuplicateActiveSessionLeases(db);
933
- const missingKimi = !tableSupportsRuntime(db, 'events', 'kimi')
934
- || !tableSupportsRuntime(db, 'sessions', 'kimi');
935
- const missingGrok = !tableSupportsRuntime(db, 'events', 'grok')
936
- || !tableSupportsRuntime(db, 'sessions', 'grok');
937
- if (!missingKimi && !missingGrok) return false;
938
- const previousVersion = fromVersion || 'unknown';
939
- const foreignKeys = db.pragma('foreign_keys', { simple: true });
940
- db.pragma('foreign_keys = OFF');
941
- try {
942
- db.transaction(() => {
943
- db.exec(`
944
- DROP TABLE IF EXISTS events_runtime_upgrade;
945
- CREATE TABLE events_runtime_upgrade (
946
- id INTEGER PRIMARY KEY AUTOINCREMENT,
947
- ts TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')),
948
- type TEXT NOT NULL,
949
- task_id TEXT,
950
- change_id TEXT,
951
- session_id TEXT,
952
- runtime TEXT CHECK (runtime IS NULL OR runtime IN ('claude', 'opencode', 'codex', 'kimi', 'grok')),
953
- payload_json TEXT
954
- );
955
- INSERT INTO events_runtime_upgrade
956
- (id, ts, type, task_id, change_id, session_id, runtime, payload_json)
957
- SELECT id, ts, type, task_id, change_id, session_id, runtime, payload_json FROM events;
958
- DROP TABLE events;
959
- ALTER TABLE events_runtime_upgrade RENAME TO events;
960
- CREATE INDEX events_ts_type ON events(ts, type);
961
- CREATE INDEX events_task ON events(task_id, id);
962
- CREATE INDEX events_session ON events(session_id, id);
963
- CREATE INDEX events_change ON events(change_id, id);
964
-
965
- DROP TABLE IF EXISTS sessions_runtime_upgrade;
966
- CREATE TABLE sessions_runtime_upgrade (
967
- sid TEXT PRIMARY KEY,
968
- task_id TEXT NOT NULL REFERENCES tasks(id),
969
- runtime TEXT NOT NULL CHECK (runtime IN ('claude', 'opencode', 'codex', 'kimi', 'grok')),
970
- pid INTEGER,
971
- worktree_path TEXT NOT NULL,
972
- artifact_dir TEXT NOT NULL,
973
- status TEXT NOT NULL DEFAULT 'running'
974
- CHECK (status IN ('running', 'completed', 'crashed', 'orphan')),
975
- lease_expires_at TEXT NOT NULL,
976
- heartbeat_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')),
977
- started_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now'))
978
- );
979
- INSERT INTO sessions_runtime_upgrade
980
- (sid, task_id, runtime, pid, worktree_path, artifact_dir, status,
981
- lease_expires_at, heartbeat_at, started_at)
982
- SELECT sid, task_id, runtime, pid, worktree_path, artifact_dir, status,
983
- lease_expires_at, heartbeat_at, started_at FROM sessions;
984
- DROP TABLE sessions;
985
- ALTER TABLE sessions_runtime_upgrade RENAME TO sessions;
986
- CREATE INDEX sessions_active ON sessions(status, task_id);
987
- CREATE INDEX sessions_lease ON sessions(lease_expires_at) WHERE status = 'running';
988
- CREATE UNIQUE INDEX sessions_one_active_task
989
- ON sessions(task_id) WHERE status = 'running';
990
- `);
991
- if (missingKimi) {
992
- db.prepare(
993
- `INSERT INTO migration_history
994
- (from_version, to_version, direction, status, notes)
995
- VALUES (?, ?, 'forward', 'success', ?)`,
996
- ).run(
997
- previousVersion,
998
- KIMI_SCHEMA_VERSION,
999
- 'Add Kimi to durable event and session runtime constraints',
1000
- );
1001
- }
1002
- if (missingGrok) {
1003
- db.prepare(
1004
- `INSERT INTO migration_history
1005
- (from_version, to_version, direction, status, notes)
1006
- VALUES (?, ?, 'forward', 'success', ?)`,
1007
- ).run(
1008
- missingKimi ? KIMI_SCHEMA_VERSION : previousVersion,
1009
- KERNEL_SCHEMA_VERSION,
1010
- 'Add Grok to durable event and session runtime constraints',
1011
- );
1012
- }
1013
- const violations = db.pragma('foreign_key_check');
1014
- if (violations.length > 0) {
1015
- throw new Error(`runtime constraint migration produced ${violations.length} foreign key violation(s)`);
1016
- }
1017
- })();
1018
- } finally {
1019
- db.pragma(`foreign_keys = ${foreignKeys ? 'ON' : 'OFF'}`);
1020
- }
1021
- return true;
1022
- }
1023
-
1024
- function activeSessionLeaseConflicts(db) {
1025
- if (!tableNames(db).includes('sessions')) return [];
1026
- const columns = columnNames(db, 'sessions');
1027
- if (!['sid', 'task_id', 'status'].every((name) => columns.has(name))) return [];
1028
- const rows = db.prepare(
1029
- `SELECT task_id, sid
1030
- FROM sessions
1031
- WHERE status = 'running'
1032
- ORDER BY task_id, sid`,
1033
- ).all();
1034
- const byTask = new Map();
1035
- for (const row of rows) {
1036
- const ids = byTask.get(row.task_id) || [];
1037
- ids.push(row.sid);
1038
- byTask.set(row.task_id, ids);
1039
- }
1040
- return [...byTask.entries()]
1041
- .filter(([, sessionIds]) => sessionIds.length > 1)
1042
- .map(([taskId, sessionIds]) => ({
1043
- task_id: taskId,
1044
- lease_count: sessionIds.length,
1045
- session_ids: sessionIds,
1046
- }));
1047
- }
1048
-
1049
- function assertNoDuplicateActiveSessionLeases(db) {
1050
- const conflicts = activeSessionLeaseConflicts(db);
1051
- if (conflicts.length > 0) throw new ActiveSessionLeaseConflictError(conflicts);
1052
- return true;
1053
- }
1054
-
1055
- function ensureActiveSessionLeaseIndex(db) {
1056
- if (!tableNames(db).includes('sessions')) return false;
1057
- assertNoDuplicateActiveSessionLeases(db);
1058
- const existing = db.prepare(
1059
- "SELECT 1 AS present FROM sqlite_master WHERE type = 'index' AND name = 'sessions_one_active_task'",
1060
- ).get();
1061
- if (existing) return false;
1062
- db.exec(
1063
- `CREATE UNIQUE INDEX sessions_one_active_task
1064
- ON sessions(task_id) WHERE status = 'running'`,
1065
- );
1066
- return true;
1067
- }
1068
-
1069
- function applyArtifactRegistryUpgrade(db) {
1070
- if (!tableNames(db).includes('artifacts')) return false;
1071
- assertNoDuplicateActiveArtifactPaths(db);
1072
- const columns = columnNames(db, 'artifacts');
1073
- let changed = false;
1074
- if (![
1075
- 'owner_type', 'owner_id', 'digest', 'before_digest', 'after_digest',
1076
- 'provenance_json', 'managed',
1077
- ].every((column) => columns.has(column))) {
1078
- db.exec(`
1079
- DROP TABLE IF EXISTS artifacts_registry_upgrade;
1080
- CREATE TABLE artifacts_registry_upgrade (
1081
- id TEXT PRIMARY KEY,
1082
- owner_type TEXT NOT NULL
1083
- CHECK (owner_type IN ('project', 'baseline', 'change', 'task', 'workflow')),
1084
- owner_id TEXT NOT NULL,
1085
- change_id TEXT REFERENCES changes(id) ON DELETE SET NULL,
1086
- task_id TEXT REFERENCES tasks(id) ON DELETE SET NULL,
1087
- kind TEXT NOT NULL,
1088
- path TEXT NOT NULL,
1089
- digest TEXT,
1090
- content_hash TEXT,
1091
- before_digest TEXT,
1092
- after_digest TEXT,
1093
- provenance_json TEXT NOT NULL DEFAULT '{}',
1094
- metadata_json TEXT,
1095
- managed INTEGER NOT NULL DEFAULT 0 CHECK (managed IN (0, 1)),
1096
- status TEXT NOT NULL DEFAULT 'current'
1097
- CHECK (status IN ('current', 'stale', 'terminal', 'archived')),
1098
- created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')),
1099
- updated_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')),
1100
- UNIQUE(owner_type, owner_id, kind, path)
1101
- );
1102
- INSERT INTO artifacts_registry_upgrade
1103
- (id, owner_type, owner_id, change_id, task_id, kind, path, digest,
1104
- content_hash, before_digest, after_digest, provenance_json,
1105
- metadata_json, managed, status, created_at, updated_at)
1106
- SELECT id,
1107
- CASE WHEN task_id IS NOT NULL THEN 'task' ELSE 'change' END,
1108
- CASE WHEN task_id IS NOT NULL THEN task_id ELSE change_id END,
1109
- change_id, task_id, kind, path, content_hash, content_hash,
1110
- NULL, content_hash, '{"migration":"schema-20.0"}',
1111
- metadata_json, 0, status, created_at, updated_at
1112
- FROM artifacts;
1113
- DROP TABLE artifacts;
1114
- ALTER TABLE artifacts_registry_upgrade RENAME TO artifacts;
1115
- `);
1116
- changed = true;
1117
- }
1118
- if (normalizeStoredArtifactPaths(db)) changed = true;
1119
- db.exec(`
1120
- CREATE INDEX IF NOT EXISTS artifacts_change ON artifacts(change_id, kind);
1121
- CREATE INDEX IF NOT EXISTS artifacts_owner ON artifacts(owner_type, owner_id, status);
1122
- CREATE INDEX IF NOT EXISTS artifacts_path ON artifacts(path, status);
1123
- CREATE UNIQUE INDEX IF NOT EXISTS artifacts_one_active_path
1124
- ON artifacts(path) WHERE status <> 'archived';
1125
- CREATE TABLE IF NOT EXISTS artifact_edges (
1126
- id INTEGER PRIMARY KEY AUTOINCREMENT,
1127
- source_type TEXT NOT NULL
1128
- CHECK (source_type IN ('artifact', 'project', 'baseline', 'change', 'task', 'workflow', 'external')),
1129
- source_id TEXT NOT NULL,
1130
- target_type TEXT NOT NULL
1131
- CHECK (target_type IN ('artifact', 'project', 'baseline', 'change', 'task', 'workflow', 'external')),
1132
- target_id TEXT NOT NULL,
1133
- relation TEXT NOT NULL,
1134
- created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')),
1135
- UNIQUE(source_type, source_id, target_type, target_id, relation)
1136
- );
1137
- CREATE INDEX IF NOT EXISTS artifact_edges_source
1138
- ON artifact_edges(source_type, source_id);
1139
- CREATE INDEX IF NOT EXISTS artifact_edges_target
1140
- ON artifact_edges(target_type, target_id);
1141
- `);
1142
- return changed;
1143
- }
1144
-
1145
- function applyCompatibleUpgrades(db, fromVersion = latestSchemaVersion(db), { legacyState = false } = {}) {
1146
- assertNoDuplicateActiveSessionLeases(db);
1147
- const runtimeChanged = upgradeRuntimeConstraints(db, fromVersion);
1148
- if (tableNames(db).includes('baselines') && !baselineSupportsUnbornGit(db)) {
1149
- db.transaction(() => {
1150
- applyBaselineUpgrade(db, { legacyState });
1151
- applyBaselineColumns(db);
1152
- })();
1153
- }
1154
- const gitStateChangedFrom = upgradeBaselineWorktreeConstraint(db, fromVersion);
1155
- const decisionCompletionChanged = upgradeDecisionThreadLifecycle(db);
1156
- db.transaction(() => {
1157
- applyCompatibleColumns(db);
1158
- applyChangeColumns(db);
1159
- })();
1160
- const semanticKernelChanged = upgradeSemanticKernelConstraints(db);
1161
- db.transaction(() => {
1162
- applyCompatibleColumns(db);
1163
- const contextChanged = applyContextSpineUpgrade(db);
1164
- const contextMigration = db.prepare(
1165
- "SELECT 1 FROM migration_history WHERE to_version = '10.0' AND status = 'success' LIMIT 1",
1166
- ).get();
1167
- const needsContextMigration = Boolean(
1168
- fromVersion && ![
1169
- CONTEXT_SCHEMA_VERSION, BASELINE_SCHEMA_VERSION, ADOPTION_SCHEMA_VERSION,
1170
- WORKFLOW_SCHEMA_VERSION, AUTHORITY_SCHEMA_VERSION, SEMANTIC_SCHEMA_VERSION,
1171
- DIALOGUE_SCHEMA_VERSION, GIT_AUTHORITY_SCHEMA_VERSION, ADAPTIVE_SCHEMA_VERSION,
1172
- DECISION_COMPLETION_SCHEMA_VERSION, ARTIFACT_REGISTRY_SCHEMA_VERSION,
1173
- KERNEL_SCHEMA_VERSION, CHECKPOINT_NATIVE_SCHEMA_VERSION,
1174
- SEMANTIC_KERNEL_SCHEMA_VERSION,
1175
- ].includes(fromVersion)
1176
- && !contextMigration,
1177
- );
1178
- if (contextChanged || needsContextMigration) {
1179
- db.prepare(
1180
- `INSERT INTO migration_history
1181
- (from_version, to_version, direction, status, notes)
1182
- VALUES (?, ?, 'forward', 'success', ?)`,
1183
- ).run(
1184
- runtimeChanged ? KIMI_SCHEMA_VERSION : (fromVersion || 'unknown'),
1185
- CONTEXT_SCHEMA_VERSION,
1186
- 'Add Context Spine role/gate readiness, execution contracts, breadcrumbs, and specification learning',
1187
- );
1188
- }
1189
- applyBaselineUpgrade(db, { legacyState });
1190
- applyBaselineColumns(db);
1191
- applyChangeColumns(db);
1192
- applySemanticAuthorityColumns(db);
1193
- applyKernelIntegrityColumns(db);
1194
- const artifactRegistryChanged = applyArtifactRegistryUpgrade(db);
1195
- const baselineMigration = db.prepare(
1196
- "SELECT 1 FROM migration_history WHERE to_version = '11.0' AND status = 'success' LIMIT 1",
1197
- ).get();
1198
- if (fromVersion && ![
1199
- BASELINE_SCHEMA_VERSION, ADOPTION_SCHEMA_VERSION, WORKFLOW_SCHEMA_VERSION,
1200
- AUTHORITY_SCHEMA_VERSION, SEMANTIC_SCHEMA_VERSION, DIALOGUE_SCHEMA_VERSION,
1201
- GIT_AUTHORITY_SCHEMA_VERSION, ADAPTIVE_SCHEMA_VERSION,
1202
- DECISION_COMPLETION_SCHEMA_VERSION, ARTIFACT_REGISTRY_SCHEMA_VERSION,
1203
- KERNEL_SCHEMA_VERSION, CHECKPOINT_NATIVE_SCHEMA_VERSION,
1204
- SEMANTIC_KERNEL_SCHEMA_VERSION,
1205
- ].includes(fromVersion)
1206
- && !baselineMigration) {
1207
- db.prepare(
1208
- `INSERT INTO migration_history
1209
- (from_version, to_version, direction, status, notes)
1210
- VALUES (?, ?, 'forward', 'success', ?)`,
1211
- ).run(
1212
- fromVersion === CONTEXT_SCHEMA_VERSION ? fromVersion : CONTEXT_SCHEMA_VERSION,
1213
- BASELINE_SCHEMA_VERSION,
1214
- 'Add authoritative greenfield and brownfield baseline adoption, evidence, convergence, and drift state',
1215
- );
1216
- }
1217
- if (legacyState) {
1218
- const legacyBaselines = db.prepare(
1219
- "SELECT id, mode, status, classification_json, gaps_json FROM baselines WHERE status != 'superseded'",
1220
- ).all();
1221
- for (const baseline of legacyBaselines) {
1222
- let classification = {};
1223
- let gaps = [];
1224
- try { classification = JSON.parse(baseline.classification_json || '{}'); } catch { classification = {}; }
1225
- try { gaps = JSON.parse(baseline.gaps_json || '[]'); } catch { gaps = []; }
1226
- if (!gaps.some((gap) => gap?.id === MIGRATED_GAPS[0].id)) gaps.push(MIGRATED_GAPS[0]);
1227
- classification.migration = {
1228
- previous_mode: baseline.mode,
1229
- previous_status: baseline.status,
1230
- from_schema: fromVersion || 'unknown',
1231
- requires_research_workflow: true,
1232
- };
1233
- db.prepare(
1234
- `UPDATE baselines SET mode = 'migrated', status = 'adopting',
1235
- classification_json = ?, gaps_json = ?, approved_by = NULL, approval_note = ?,
1236
- converged_at = NULL, research_run_id = NULL, research_checkpoint_id = NULL,
1237
- updated_at = strftime('%Y-%m-%dT%H:%M:%fZ', 'now')
1238
- WHERE id = ?`,
1239
- ).run(
1240
- JSON.stringify(classification), JSON.stringify(gaps),
1241
- 'Legacy project preserved; evidence-backed brownfield re-adoption is required',
1242
- baseline.id,
1243
- );
1244
- }
1245
- db.prepare(
1246
- `UPDATE baselines SET status = 'adopting', approved_by = NULL, approval_note = ?,
1247
- converged_at = NULL, worktree_state = 'unavailable', worktree_digest = NULL,
1248
- worktree_files_json = '[]', worktree_accepted = 0,
1249
- gaps_json = CASE WHEN gaps_json IS NULL OR gaps_json = '[]' THEN ? ELSE gaps_json END,
1250
- updated_at = strftime('%Y-%m-%dT%H:%M:%fZ', 'now')
1251
- WHERE mode = 'migrated' AND status != 'superseded'`,
1252
- ).run(
1253
- 'Legacy project preserved; owner re-adoption is required',
1254
- JSON.stringify(MIGRATED_GAPS),
1255
- );
1256
- const adoptionMigration = db.prepare(
1257
- "SELECT 1 FROM migration_history WHERE to_version = '12.0' AND status = 'success' LIMIT 1",
1258
- ).get();
1259
- if (!adoptionMigration) {
1260
- db.prepare(
1261
- `INSERT INTO migration_history
1262
- (from_version, to_version, direction, status, notes)
1263
- VALUES (?, ?, 'forward', 'success', ?)`,
1264
- ).run(
1265
- BASELINE_SCHEMA_VERSION,
1266
- ADOPTION_SCHEMA_VERSION,
1267
- 'Add repository snapshots, authoritative gap ledger, incident governance, and required re-adoption',
1268
- );
1269
- }
1270
- }
1271
- const workflowMigration = db.prepare(
1272
- "SELECT 1 FROM migration_history WHERE to_version = '13.0' AND status = 'success' LIMIT 1",
1273
- ).get();
1274
- if (fromVersion && ![
1275
- WORKFLOW_SCHEMA_VERSION, AUTHORITY_SCHEMA_VERSION, SEMANTIC_SCHEMA_VERSION,
1276
- DIALOGUE_SCHEMA_VERSION, GIT_AUTHORITY_SCHEMA_VERSION, ADAPTIVE_SCHEMA_VERSION,
1277
- DECISION_COMPLETION_SCHEMA_VERSION, ARTIFACT_REGISTRY_SCHEMA_VERSION,
1278
- KERNEL_SCHEMA_VERSION, CHECKPOINT_NATIVE_SCHEMA_VERSION,
1279
- SEMANTIC_KERNEL_SCHEMA_VERSION,
1280
- ].includes(fromVersion) && !workflowMigration) {
1281
- db.prepare(
1282
- `INSERT INTO migration_history
1283
- (from_version, to_version, direction, status, notes)
1284
- VALUES (?, ?, 'forward', 'success', ?)`,
1285
- ).run(
1286
- fromVersion === ADOPTION_SCHEMA_VERSION ? fromVersion : ADOPTION_SCHEMA_VERSION,
1287
- WORKFLOW_SCHEMA_VERSION,
1288
- 'Add durable cross-stage workflow runs, step evidence, output digests, blockers, and recovery',
1289
- );
1290
- }
1291
- const authorityMigration = db.prepare(
1292
- "SELECT 1 FROM migration_history WHERE to_version = '14.0' AND status = 'success' LIMIT 1",
1293
- ).get();
1294
- if (fromVersion && ![
1295
- AUTHORITY_SCHEMA_VERSION, SEMANTIC_SCHEMA_VERSION, DIALOGUE_SCHEMA_VERSION,
1296
- GIT_AUTHORITY_SCHEMA_VERSION, ADAPTIVE_SCHEMA_VERSION, DECISION_COMPLETION_SCHEMA_VERSION,
1297
- ARTIFACT_REGISTRY_SCHEMA_VERSION, KERNEL_SCHEMA_VERSION,
1298
- CHECKPOINT_NATIVE_SCHEMA_VERSION, SEMANTIC_KERNEL_SCHEMA_VERSION,
1299
- ].includes(fromVersion) && !authorityMigration) {
1300
- db.prepare(
1301
- `INSERT INTO migration_history
1302
- (from_version, to_version, direction, status, notes)
1303
- VALUES (?, ?, 'forward', 'success', ?)`,
1304
- ).run(
1305
- fromVersion === WORKFLOW_SCHEMA_VERSION ? fromVersion : WORKFLOW_SCHEMA_VERSION,
1306
- AUTHORITY_SCHEMA_VERSION,
1307
- 'Persist known-red acceptance and continuously revalidate ready baseline authority',
1308
- );
1309
- }
1310
- const semanticMigration = db.prepare(
1311
- "SELECT 1 FROM migration_history WHERE to_version = '15.0' AND status = 'success' LIMIT 1",
1312
- ).get();
1313
- if (fromVersion && ![
1314
- SEMANTIC_SCHEMA_VERSION, DIALOGUE_SCHEMA_VERSION, GIT_AUTHORITY_SCHEMA_VERSION,
1315
- ADAPTIVE_SCHEMA_VERSION, DECISION_COMPLETION_SCHEMA_VERSION,
1316
- ARTIFACT_REGISTRY_SCHEMA_VERSION, KERNEL_SCHEMA_VERSION,
1317
- CHECKPOINT_NATIVE_SCHEMA_VERSION, SEMANTIC_KERNEL_SCHEMA_VERSION,
1318
- ].includes(fromVersion) && !semanticMigration) {
1319
- db.prepare(
1320
- `INSERT INTO migration_history
1321
- (from_version, to_version, direction, status, notes)
1322
- VALUES (?, ?, 'forward', 'success', ?)`,
1323
- ).run(
1324
- fromVersion === AUTHORITY_SCHEMA_VERSION ? fromVersion : AUTHORITY_SCHEMA_VERSION,
1325
- SEMANTIC_SCHEMA_VERSION,
1326
- 'Add typed research semantics, complete change contracts, verified learning application, and reconciliation provenance',
1327
- );
1328
- }
1329
- const dialogueMigration = db.prepare(
1330
- "SELECT 1 FROM migration_history WHERE to_version = '16.0' AND status = 'success' LIMIT 1",
1331
- ).get();
1332
- if (fromVersion && ![
1333
- DIALOGUE_SCHEMA_VERSION, GIT_AUTHORITY_SCHEMA_VERSION, ADAPTIVE_SCHEMA_VERSION,
1334
- DECISION_COMPLETION_SCHEMA_VERSION, ARTIFACT_REGISTRY_SCHEMA_VERSION,
1335
- KERNEL_SCHEMA_VERSION, CHECKPOINT_NATIVE_SCHEMA_VERSION,
1336
- SEMANTIC_KERNEL_SCHEMA_VERSION,
1337
- ].includes(fromVersion)
1338
- && !dialogueMigration) {
1339
- db.prepare(
1340
- `INSERT INTO migration_history
1341
- (from_version, to_version, direction, status, notes)
1342
- VALUES (?, ?, 'forward', 'success', ?)`,
1343
- ).run(
1344
- fromVersion === SEMANTIC_SCHEMA_VERSION ? fromVersion : SEMANTIC_SCHEMA_VERSION,
1345
- DIALOGUE_SCHEMA_VERSION,
1346
- 'Add durable one-question decision dialogue, owner checkpoints, and workflow alignment gates',
1347
- );
1348
- }
1349
- const gitStateMigration = db.prepare(
1350
- "SELECT 1 FROM migration_history WHERE to_version = '17.0' AND status = 'success' LIMIT 1",
1351
- ).get();
1352
- if (fromVersion && ![
1353
- GIT_AUTHORITY_SCHEMA_VERSION, ADAPTIVE_SCHEMA_VERSION, DECISION_COMPLETION_SCHEMA_VERSION,
1354
- ARTIFACT_REGISTRY_SCHEMA_VERSION, KERNEL_SCHEMA_VERSION,
1355
- CHECKPOINT_NATIVE_SCHEMA_VERSION, SEMANTIC_KERNEL_SCHEMA_VERSION,
1356
- ].includes(fromVersion)
1357
- && !gitStateMigration) {
1358
- db.prepare(
1359
- `INSERT INTO migration_history
1360
- (from_version, to_version, direction, status, notes)
1361
- VALUES (?, ?, 'forward', 'success', ?)`,
1362
- ).run(
1363
- gitStateChangedFrom || DIALOGUE_SCHEMA_VERSION,
1364
- GIT_AUTHORITY_SCHEMA_VERSION,
1365
- 'Add explicit unborn Git authority and require an owner-authorized checkpoint before baseline recording',
1366
- );
1367
- }
1368
- const adaptiveChanged = migrateAdaptiveWorkflowRuns(db, fromVersion);
1369
- const adaptiveMigration = db.prepare(
1370
- "SELECT 1 FROM migration_history WHERE to_version = '18.0' AND status = 'success' LIMIT 1",
1371
- ).get();
1372
- if (fromVersion && ![
1373
- ADAPTIVE_SCHEMA_VERSION, DECISION_COMPLETION_SCHEMA_VERSION,
1374
- ARTIFACT_REGISTRY_SCHEMA_VERSION, KERNEL_SCHEMA_VERSION,
1375
- CHECKPOINT_NATIVE_SCHEMA_VERSION, SEMANTIC_KERNEL_SCHEMA_VERSION,
1376
- ].includes(fromVersion) && !adaptiveMigration) {
1377
- db.prepare(
1378
- `INSERT INTO migration_history
1379
- (from_version, to_version, direction, status, notes)
1380
- VALUES (?, ?, 'forward', 'success', ?)`,
1381
- ).run(
1382
- fromVersion === GIT_AUTHORITY_SCHEMA_VERSION ? fromVersion : GIT_AUTHORITY_SCHEMA_VERSION,
1383
- ADAPTIVE_SCHEMA_VERSION,
1384
- adaptiveChanged
1385
- ? 'Migrate active rigid workflows to adaptive capability transitions and independent initialization'
1386
- : 'Add adaptive capability transitions and independent initialization authority',
1387
- );
1388
- }
1389
- const completionMigration = db.prepare(
1390
- "SELECT 1 FROM migration_history WHERE to_version = '19.0' AND status = 'success' LIMIT 1",
1391
- ).get();
1392
- if (fromVersion && ![
1393
- DECISION_COMPLETION_SCHEMA_VERSION, ARTIFACT_REGISTRY_SCHEMA_VERSION,
1394
- KERNEL_SCHEMA_VERSION, CHECKPOINT_NATIVE_SCHEMA_VERSION,
1395
- SEMANTIC_KERNEL_SCHEMA_VERSION,
1396
- ].includes(fromVersion) && !completionMigration) {
1397
- db.prepare(
1398
- `INSERT INTO migration_history
1399
- (from_version, to_version, direction, status, notes)
1400
- VALUES (?, ?, 'forward', 'success', ?)`,
1401
- ).run(
1402
- ADAPTIVE_SCHEMA_VERSION,
1403
- DECISION_COMPLETION_SCHEMA_VERSION,
1404
- decisionCompletionChanged
1405
- ? 'Add non-ceremonial decision completion and migrate settled active threads'
1406
- : 'Add non-ceremonial decision completion authority',
1407
- );
1408
- }
1409
- const artifactRegistryMigration = db.prepare(
1410
- "SELECT 1 FROM migration_history WHERE to_version = '20.0' AND status = 'success' LIMIT 1",
1411
- ).get();
1412
- if (fromVersion && fromVersion !== ARTIFACT_REGISTRY_SCHEMA_VERSION
1413
- && fromVersion !== KERNEL_SCHEMA_VERSION
1414
- && fromVersion !== CHECKPOINT_NATIVE_SCHEMA_VERSION
1415
- && fromVersion !== SEMANTIC_KERNEL_SCHEMA_VERSION
1416
- && !artifactRegistryMigration) {
1417
- db.prepare(
1418
- `INSERT INTO migration_history
1419
- (from_version, to_version, direction, status, notes)
1420
- VALUES (?, ?, 'forward', 'success', ?)`,
1421
- ).run(
1422
- DECISION_COMPLETION_SCHEMA_VERSION,
1423
- ARTIFACT_REGISTRY_SCHEMA_VERSION,
1424
- artifactRegistryChanged
1425
- ? 'Migrate legacy change artifacts into the typed artifact registry and dependency graph'
1426
- : 'Add the typed artifact registry, dependency graph, and orphan diagnostics',
1427
- );
1428
- }
1429
- const kernelMigration = db.prepare(
1430
- "SELECT 1 FROM migration_history WHERE to_version = '21.0' AND status = 'success' LIMIT 1",
1431
- ).get();
1432
- if (fromVersion
1433
- && fromVersion !== KERNEL_SCHEMA_VERSION
1434
- && fromVersion !== CHECKPOINT_NATIVE_SCHEMA_VERSION
1435
- && fromVersion !== SEMANTIC_KERNEL_SCHEMA_VERSION
1436
- && !kernelMigration) {
1437
- db.prepare(
1438
- `INSERT INTO migration_history
1439
- (from_version, to_version, direction, status, notes)
1440
- VALUES (?, ?, 'forward', 'success', ?)`,
1441
- ).run(
1442
- ARTIFACT_REGISTRY_SCHEMA_VERSION,
1443
- KERNEL_SCHEMA_VERSION,
1444
- 'Add the seven-tool persistence kernel, reversible stage checkpoints, canonical context envelopes, normalized decisions, worker packets, and Grok runtime authority',
1445
- );
1446
- }
1447
- const checkpointNativeMigration = db.prepare(
1448
- "SELECT 1 FROM migration_history WHERE to_version = '22.0' AND status = 'success' LIMIT 1",
1449
- ).get();
1450
- if (fromVersion
1451
- && fromVersion !== CHECKPOINT_NATIVE_SCHEMA_VERSION
1452
- && fromVersion !== SEMANTIC_KERNEL_SCHEMA_VERSION
1453
- && !checkpointNativeMigration) {
1454
- db.prepare(
1455
- `INSERT INTO migration_history
1456
- (from_version, to_version, direction, status, notes)
1457
- VALUES (?, ?, 'forward', 'success', ?)`,
1458
- ).run(
1459
- KERNEL_SCHEMA_VERSION,
1460
- CHECKPOINT_NATIVE_SCHEMA_VERSION,
1461
- 'Bind Baseline research to Stage Checkpoints and add byte digests for Context Envelopes and Worker Packets',
1462
- );
1463
- }
1464
- const semanticKernelMigration = db.prepare(
1465
- "SELECT 1 FROM migration_history WHERE to_version = '23.0' AND status = 'success' LIMIT 1",
1466
- ).get();
1467
- if (fromVersion
1468
- && fromVersion !== SEMANTIC_KERNEL_SCHEMA_VERSION
1469
- && !semanticKernelMigration) {
1470
- db.prepare(
1471
- `INSERT INTO migration_history
1472
- (from_version, to_version, direction, status, notes)
1473
- VALUES (?, ?, 'forward', 'success', ?)`,
1474
- ).run(
1475
- CHECKPOINT_NATIVE_SCHEMA_VERSION,
1476
- SEMANTIC_KERNEL_SCHEMA_VERSION,
1477
- semanticKernelChanged
1478
- ? 'Move Task and Change business vocabulary out of SQLite enums and add immutable Change successor linkage'
1479
- : 'Record semantic-kernel vocabulary and Change successor authority',
1480
- );
1481
- }
1482
- db.exec('CREATE INDEX IF NOT EXISTS tasks_change ON tasks(change_id) WHERE change_id IS NOT NULL');
1483
- db.exec('CREATE INDEX IF NOT EXISTS events_change ON events(change_id, id)');
1484
- ensureActiveSessionLeaseIndex(db);
1485
- db.prepare(
1486
- 'INSERT OR IGNORE INTO schema_version (version, description) VALUES (?, ?)',
1487
- ).run(
1488
- EXPECTED_VERSION,
1489
- 'Persistence and safety kernel with open semantic vocabulary and immutable Change successors',
1490
- );
1491
- })();
1492
- }
1493
-
1494
- function ensureSchemaVersion(db) {
1495
- // schema_version is an audit trail — multiple rows across phase upgrades.
1496
- // Guard by checking whether the expected version row exists rather than
1497
- // relying on applied_at ordering (same-tick inserts from the seed block
1498
- // make `ORDER BY applied_at DESC LIMIT 1` non-deterministic).
1499
- const row = db.prepare('SELECT version FROM schema_version WHERE version = ?').get(EXPECTED_VERSION);
1500
- if (!row) {
1501
- const latest = db.prepare('SELECT version FROM schema_version ORDER BY applied_at DESC LIMIT 1').get();
1502
- throw new Error(
1503
- `state.db schema_version mismatch: expected '${EXPECTED_VERSION}', file has '${latest ? latest.version : '(empty)'}'`,
1504
- );
1505
- }
1506
- return row.version;
1507
- }
1508
-
1509
- function initStateDb(dbPath) {
1510
- let db;
1511
- let backupPath = null;
1512
- try {
1513
- db = openStateDb(dbPath);
1514
- const existing = new Set(tableNames(db));
1515
- const fromVersion = latestSchemaVersion(db);
1516
- assertNoDuplicateActiveArtifactPaths(db);
1517
- const legacyState = existing.size > 0
1518
- && ![
1519
- WORKFLOW_SCHEMA_VERSION, AUTHORITY_SCHEMA_VERSION, SEMANTIC_SCHEMA_VERSION,
1520
- DIALOGUE_SCHEMA_VERSION, GIT_AUTHORITY_SCHEMA_VERSION, ADAPTIVE_SCHEMA_VERSION,
1521
- DECISION_COMPLETION_SCHEMA_VERSION, ARTIFACT_REGISTRY_SCHEMA_VERSION,
1522
- KERNEL_SCHEMA_VERSION, CHECKPOINT_NATIVE_SCHEMA_VERSION,
1523
- SEMANTIC_KERNEL_SCHEMA_VERSION,
1524
- ].includes(fromVersion);
1525
- backupPath = migrationBackup(db, dbPath, fromVersion, existing);
1526
- // This must precede every ALTER, table rebuild, or index creation. Runtime
1527
- // constraint upgrades rebuild sessions and would otherwise surface a
1528
- // generic SQLITE_CONSTRAINT instead of the complete authority conflict.
1529
- assertNoDuplicateActiveSessionLeases(db);
1530
- applyCompatibleColumns(db);
1531
- if (existing.has('artifacts')) {
1532
- db.transaction(() => applyArtifactRegistryUpgrade(db))();
1533
- }
1534
- const missing = REQUIRED_TABLES.filter((t) => !existing.has(t));
1535
- if (missing.length > 0) {
1536
- applySchema(db);
1537
- }
1538
- applyCompatibleUpgrades(db, fromVersion, { legacyState });
1539
- const version = ensureSchemaVersion(db);
1540
- return {
1541
- db,
1542
- path: dbPath,
1543
- schema_version: version,
1544
- created: missing.length > 0,
1545
- backup_path: backupPath,
1546
- tables: tableNames(db).sort(),
1547
- };
1548
- } catch (error) {
1549
- if (backupPath && error && typeof error === 'object') {
1550
- error.migration_backup_path = backupPath;
1551
- }
1552
- closeStateDb(db);
1553
- throw error;
1554
- }
1555
- }
1556
-
1557
- function closeStateDb(db) {
1558
- if (db && typeof db.close === 'function') {
1559
- db.close();
1560
- }
1561
- }
1562
-
1563
- module.exports = {
1564
- EXPECTED_VERSION,
1565
- REQUIRED_TABLES,
1566
- SCHEMA_FILE,
1567
- openStateDb,
1568
- applySchema,
1569
- applyCompatibleUpgrades,
1570
- applyPragmas,
1571
- ensureSchemaVersion,
1572
- initStateDb,
1573
- closeStateDb,
1574
- tableNames,
1575
- runScript,
1576
- MIGRATED_GAPS,
1577
- ActiveSessionLeaseConflictError,
1578
- activeSessionLeaseConflicts,
1579
- assertNoDuplicateActiveSessionLeases,
1580
- };