ultra-builder-pro-cli 0.24.2 → 0.25.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 (40) hide show
  1. package/CHANGELOG.md +42 -0
  2. package/adapters/claude.js +104 -0
  3. package/adapters/codex.js +363 -40
  4. package/adapters/grok.js +1254 -58
  5. package/adapters/kimi.js +682 -13
  6. package/adapters/opencode.js +132 -11
  7. package/docs/AGENT-CONTEXT.md +14 -0
  8. package/docs/ARTIFACT-AUTHORITY.md +13 -0
  9. package/docs/COMMIT-HASH-BACKFILL.md +25 -18
  10. package/docs/RUNTIME-COMPAT-MATRIX.md +18 -2
  11. package/mcp-server/lib/artifact-registry.cjs +181 -51
  12. package/mcp-server/lib/baseline-workflow.cjs +160 -23
  13. package/mcp-server/lib/change-workflow.cjs +21 -16
  14. package/mcp-server/lib/context-envelope.cjs +124 -51
  15. package/mcp-server/lib/decision-records.cjs +164 -78
  16. package/mcp-server/lib/kernel-change-workflow.cjs +2029 -0
  17. package/mcp-server/lib/legacy-change-workflow.cjs +294 -71
  18. package/mcp-server/lib/managed-file-write.cjs +56 -0
  19. package/mcp-server/lib/prd-parser.cjs +2 -2
  20. package/mcp-server/lib/stage-checkpoints.cjs +163 -9
  21. package/mcp-server/lib/state-db.cjs +186 -8
  22. package/mcp-server/lib/state-ops.cjs +214 -31
  23. package/mcp-server/lib/task-contract.cjs +404 -0
  24. package/mcp-server/lib/task-expander.cjs +2 -2
  25. package/mcp-server/lib/task-ledger.cjs +175 -62
  26. package/mcp-server/lib/ultra-facade.cjs +1786 -169
  27. package/mcp-server/lib/worker-packet.cjs +2 -2
  28. package/mcp-server/server.cjs +23 -4
  29. package/orchestrator/parallel-orchestrator.cjs +114 -27
  30. package/orchestrator/session-runner.cjs +25 -136
  31. package/package.json +1 -1
  32. package/skills/ultra-change/SKILL.md +6 -1
  33. package/skills/ultra-dev/SKILL.md +9 -4
  34. package/skills/ultra-plan/SKILL.md +13 -4
  35. package/skills/ultra-plan/references/semantic-preflight.md +6 -4
  36. package/spec/mcp-tools.yaml +114 -9
  37. package/spec/schemas/context-file.v4.5.schema.json +2 -2
  38. package/spec/schemas/state-db.sql +10 -6
  39. package/spec/schemas/task-ledger.v2.schema.json +23 -8
  40. package/spec/schemas/tasks.v4.5.schema.json +34 -12
@@ -109,8 +109,64 @@ function writeManagedJson(rootDir, relativePath, value) {
109
109
  return writeManagedFile(rootDir, relativePath, `${JSON.stringify(value, null, 2)}\n`);
110
110
  }
111
111
 
112
+ function snapshotManagedFile(rootDir, relativePath) {
113
+ const relative = normalizeProjectRelative(relativePath);
114
+ const directoryRelative = path.posix.dirname(relative);
115
+ const basename = path.posix.basename(relative);
116
+ const prepared = ensureDirectory(rootDir, directoryRelative);
117
+ const target = path.join(prepared.directory, basename);
118
+ const stat = fs.lstatSync(target, { throwIfNoEntry: false });
119
+ if (!stat) return { path: relative, existed: false, bytes: null };
120
+ if (!stat.isFile() || stat.isSymbolicLink()) {
121
+ throw new ManagedFileWriteError(
122
+ 'PATH_AUTHORITY_VIOLATION',
123
+ `managed artifact target is unsafe: ${relative}`,
124
+ );
125
+ }
126
+ return {
127
+ path: relative,
128
+ existed: true,
129
+ bytes: fs.readFileSync(target),
130
+ };
131
+ }
132
+
133
+ function removeManagedFile(rootDir, relativePath) {
134
+ const relative = normalizeProjectRelative(relativePath);
135
+ const directoryRelative = path.posix.dirname(relative);
136
+ const basename = path.posix.basename(relative);
137
+ const prepared = ensureDirectory(rootDir, directoryRelative);
138
+ const target = path.join(prepared.directory, basename);
139
+ const stat = fs.lstatSync(target, { throwIfNoEntry: false });
140
+ if (!stat) return false;
141
+ if (!stat.isFile() || stat.isSymbolicLink()) {
142
+ throw new ManagedFileWriteError(
143
+ 'PATH_AUTHORITY_VIOLATION',
144
+ `managed artifact rollback target is unsafe: ${relative}`,
145
+ );
146
+ }
147
+ fs.unlinkSync(target);
148
+ return true;
149
+ }
150
+
151
+ function restoreManagedFile(rootDir, snapshot) {
152
+ if (!snapshot || typeof snapshot.path !== 'string') {
153
+ throw new ManagedFileWriteError(
154
+ 'PATH_AUTHORITY_VIOLATION',
155
+ 'managed artifact rollback snapshot is invalid',
156
+ );
157
+ }
158
+ if (snapshot.existed) {
159
+ return writeManagedFile(rootDir, snapshot.path, snapshot.bytes);
160
+ }
161
+ removeManagedFile(rootDir, snapshot.path);
162
+ return { path: snapshot.path, removed: true };
163
+ }
164
+
112
165
  module.exports = {
113
166
  ManagedFileWriteError,
167
+ removeManagedFile,
168
+ restoreManagedFile,
169
+ snapshotManagedFile,
114
170
  writeManagedFile,
115
171
  writeManagedJson,
116
172
  };
@@ -19,8 +19,8 @@ const TASK_LIST_SCHEMA = Object.freeze({
19
19
  properties: {
20
20
  id: { type: 'string', pattern: '^[a-zA-Z0-9_\\-]+$' },
21
21
  title: { type: 'string', minLength: 3, maxLength: 200 },
22
- type: { type: 'string', enum: ['architecture', 'feature', 'bugfix'] },
23
- priority: { type: 'string', enum: ['P0', 'P1', 'P2', 'P3'] },
22
+ type: { type: 'string', minLength: 1, maxLength: 80 },
23
+ priority: { type: 'string', minLength: 1, maxLength: 80 },
24
24
  complexity: { type: 'integer', minimum: 1, maximum: 10 },
25
25
  deps: { type: 'array', items: { type: 'string' } },
26
26
  files_modified: { type: 'array', items: { type: 'string' } },
@@ -55,10 +55,10 @@ function normalizeDiagnostics(value) {
55
55
  if (!item || typeof item !== 'object' || Array.isArray(item)) {
56
56
  throw new StageCheckpointError('VALIDATION_ERROR', `diagnostics[${index}] must be an object`);
57
57
  }
58
- const code = String(item.code || '').trim();
59
- if (!code) {
58
+ if (typeof item.code !== 'string' || !item.code.trim()) {
60
59
  throw new StageCheckpointError('VALIDATION_ERROR', `diagnostics[${index}].code is required`);
61
60
  }
61
+ const code = item.code.trim();
62
62
  const severity = item.severity || 'needs_attention';
63
63
  if (!['warning', 'needs_attention', 'hard_conflict'].includes(severity)) {
64
64
  throw new StageCheckpointError(
@@ -66,15 +66,79 @@ function normalizeDiagnostics(value) {
66
66
  `diagnostics[${index}].severity is invalid`,
67
67
  );
68
68
  }
69
+ if (item.message !== undefined && typeof item.message !== 'string') {
70
+ throw new StageCheckpointError(
71
+ 'VALIDATION_ERROR',
72
+ `diagnostics[${index}].message must be a string`,
73
+ );
74
+ }
69
75
  return {
70
76
  code,
71
77
  severity,
72
- ...(item.message ? { message: String(item.message) } : {}),
78
+ ...(item.message ? { message: item.message } : {}),
73
79
  ...(item.details === undefined ? {} : { details: item.details }),
74
80
  };
75
81
  });
76
82
  }
77
83
 
84
+ function normalizePayload(value) {
85
+ if (!value || typeof value !== 'object' || Array.isArray(value)) {
86
+ throw new StageCheckpointError('VALIDATION_ERROR', 'checkpoint payload must be an object');
87
+ }
88
+ return value;
89
+ }
90
+
91
+ function normalizeEvidence(value) {
92
+ if (!Array.isArray(value)) {
93
+ throw new StageCheckpointError('VALIDATION_ERROR', 'evidence must be an array');
94
+ }
95
+ return value.map((item, index) => {
96
+ if (!item || typeof item !== 'object' || Array.isArray(item)) {
97
+ throw new StageCheckpointError('VALIDATION_ERROR', `evidence[${index}] must be an object`);
98
+ }
99
+ return item;
100
+ });
101
+ }
102
+
103
+ function idempotencyConflict(idempotencyKey, priorDigest, requestedDigest) {
104
+ return new StageCheckpointError(
105
+ 'IDEMPOTENCY_KEY_CONFLICT',
106
+ `idempotency key ${idempotencyKey} was already used for another checkpoint request`,
107
+ {
108
+ idempotency_key: idempotencyKey,
109
+ prior_request_digest: priorDigest,
110
+ requested_request_digest: requestedDigest,
111
+ },
112
+ );
113
+ }
114
+
115
+ function draftRequestDigest({
116
+ stage,
117
+ scopeType,
118
+ scopeId,
119
+ payload,
120
+ }) {
121
+ const requestPayload = stage === 'plan' && payload && typeof payload === 'object'
122
+ ? Object.fromEntries(Object.entries(payload).filter(([key]) => key !== 'plan'))
123
+ : payload;
124
+ return canonical.digest({
125
+ operation: 'checkpoint:draft',
126
+ stage,
127
+ scope_type: scopeType,
128
+ scope_id: scopeId,
129
+ payload: requestPayload,
130
+ });
131
+ }
132
+
133
+ function checkpointDraftRequestDigest(checkpoint) {
134
+ return draftRequestDigest({
135
+ stage: checkpoint.stage,
136
+ scopeType: checkpoint.scope_type,
137
+ scopeId: checkpoint.scope_id,
138
+ payload: checkpoint.payload,
139
+ });
140
+ }
141
+
78
142
  function checkpointDigest(value) {
79
143
  return canonical.digest({
80
144
  stage: value.stage,
@@ -151,6 +215,28 @@ function existingByIdempotency(db, idempotencyKey) {
151
215
  );
152
216
  }
153
217
 
218
+ function findIdempotentDraft(db, input = {}) {
219
+ validateStage(input.stage);
220
+ const scope = normalizeScope(input.scope);
221
+ const idempotencyKey = String(input.idempotency_key || '').trim();
222
+ if (!idempotencyKey) {
223
+ throw new StageCheckpointError('VALIDATION_ERROR', 'idempotency_key is required');
224
+ }
225
+ const existing = existingByIdempotency(db, idempotencyKey);
226
+ if (!existing) return null;
227
+ const requestedDigest = draftRequestDigest({
228
+ stage: input.stage,
229
+ scopeType: scope.type,
230
+ scopeId: scope.id,
231
+ payload: normalizePayload(input.payload),
232
+ });
233
+ const priorDigest = checkpointDraftRequestDigest(existing);
234
+ if (priorDigest !== requestedDigest) {
235
+ throw idempotencyConflict(idempotencyKey, priorDigest, requestedDigest);
236
+ }
237
+ return existing;
238
+ }
239
+
154
240
  function saveDraft(db, input = {}) {
155
241
  validateStage(input.stage);
156
242
  const scope = normalizeScope(input.scope);
@@ -158,12 +244,23 @@ function saveDraft(db, input = {}) {
158
244
  if (!idempotencyKey) {
159
245
  throw new StageCheckpointError('VALIDATION_ERROR', 'idempotency_key is required');
160
246
  }
247
+ const payload = normalizePayload(input.payload);
248
+ const evidence = normalizeEvidence(input.evidence);
249
+ const diagnostics = normalizeDiagnostics(input.diagnostics);
250
+ const requestedDigest = draftRequestDigest({
251
+ stage: input.stage,
252
+ scopeType: scope.type,
253
+ scopeId: scope.id,
254
+ payload,
255
+ });
161
256
  const existingRetry = existingByIdempotency(db, idempotencyKey);
162
- if (existingRetry) return existingRetry;
163
- const payload = input.payload && typeof input.payload === 'object' && !Array.isArray(input.payload)
164
- ? input.payload : {};
165
- const evidence = Array.isArray(input.evidence) ? input.evidence : [];
166
- const diagnostics = normalizeDiagnostics(input.diagnostics || []);
257
+ if (existingRetry) {
258
+ const priorDigest = checkpointDraftRequestDigest(existingRetry);
259
+ if (priorDigest !== requestedDigest) {
260
+ throw idempotencyConflict(idempotencyKey, priorDigest, requestedDigest);
261
+ }
262
+ return existingRetry;
263
+ }
167
264
  return ops.tx(db, () => {
168
265
  const draft = db.prepare(
169
266
  `SELECT * FROM stage_checkpoints
@@ -241,6 +338,33 @@ function saveDraft(db, input = {}) {
241
338
  });
242
339
  }
243
340
 
341
+ function acceptanceReceipt(db, idempotencyKey) {
342
+ if (!idempotencyKey) return null;
343
+ const rows = db.prepare(
344
+ `SELECT payload_json FROM events
345
+ WHERE type = 'ultra_checkpoint_accepted'
346
+ ORDER BY id DESC`,
347
+ ).all();
348
+ for (const row of rows) {
349
+ const payload = parseJson(row.payload_json, {});
350
+ if (payload.idempotency_key === idempotencyKey) return payload;
351
+ }
352
+ return null;
353
+ }
354
+
355
+ function acceptanceReceiptForCheckpoint(db, checkpointId) {
356
+ const rows = db.prepare(
357
+ `SELECT payload_json FROM events
358
+ WHERE type = 'ultra_checkpoint_accepted'
359
+ ORDER BY id DESC`,
360
+ ).all();
361
+ for (const row of rows) {
362
+ const payload = parseJson(row.payload_json, {});
363
+ if (payload.checkpoint_id === checkpointId && payload.idempotency_key) return payload;
364
+ }
365
+ return null;
366
+ }
367
+
244
368
  function acceptDraft(db, input = {}) {
245
369
  const idempotencyKey = String(input.idempotency_key || '').trim();
246
370
  if (!idempotencyKey) {
@@ -249,7 +373,34 @@ function acceptDraft(db, input = {}) {
249
373
  return ops.tx(db, () => {
250
374
  const row = db.prepare('SELECT * FROM stage_checkpoints WHERE id = ?').get(input.id);
251
375
  if (!row) throw new StageCheckpointError('CHECKPOINT_NOT_FOUND', `checkpoint not found: ${input.id}`);
252
- if (row.status === 'accepted') return rowToCheckpoint(row);
376
+ const checkpoint = rowToCheckpoint(row);
377
+ const requestedDigest = canonical.digest({
378
+ operation: 'checkpoint:accept',
379
+ checkpoint_id: checkpoint.id,
380
+ checkpoint_digest: checkpoint.digest,
381
+ });
382
+ const prior = acceptanceReceipt(db, idempotencyKey);
383
+ if (prior) {
384
+ if (prior.request_digest !== requestedDigest) {
385
+ throw idempotencyConflict(
386
+ idempotencyKey,
387
+ prior.request_digest || null,
388
+ requestedDigest,
389
+ );
390
+ }
391
+ return checkpoint;
392
+ }
393
+ if (row.status === 'accepted') {
394
+ const checkpointReceipt = acceptanceReceiptForCheckpoint(db, checkpoint.id);
395
+ if (checkpointReceipt) {
396
+ throw idempotencyConflict(
397
+ idempotencyKey,
398
+ checkpointReceipt.request_digest || null,
399
+ requestedDigest,
400
+ );
401
+ }
402
+ return checkpoint;
403
+ }
253
404
  if (row.status !== 'draft') {
254
405
  throw new StageCheckpointError(
255
406
  'CHECKPOINT_NOT_MUTABLE',
@@ -279,6 +430,8 @@ function acceptDraft(db, input = {}) {
279
430
  stage: accepted.stage,
280
431
  revision: accepted.revision,
281
432
  digest: accepted.digest,
433
+ idempotency_key: idempotencyKey,
434
+ request_digest: requestedDigest,
282
435
  },
283
436
  });
284
437
  return accepted;
@@ -303,6 +456,7 @@ module.exports = {
303
456
  readCheckpoint,
304
457
  listCheckpoints,
305
458
  saveDraft,
459
+ findIdempotentDraft,
306
460
  acceptDraft,
307
461
  currentCheckpoint,
308
462
  checkpointDigest,
@@ -9,7 +9,7 @@ const REPO_ROOT = process.env.UBP_RUNTIME_ROOT
9
9
  ? path.resolve(process.env.UBP_RUNTIME_ROOT)
10
10
  : path.resolve(__dirname, '..', '..');
11
11
  const SCHEMA_FILE = path.join(REPO_ROOT, 'spec', 'schemas', 'state-db.sql');
12
- const EXPECTED_VERSION = '22.0';
12
+ const EXPECTED_VERSION = '23.0';
13
13
  const KIMI_SCHEMA_VERSION = '9.1';
14
14
  const CONTEXT_SCHEMA_VERSION = '10.0';
15
15
  const BASELINE_SCHEMA_VERSION = '11.0';
@@ -24,6 +24,7 @@ const DECISION_COMPLETION_SCHEMA_VERSION = '19.0';
24
24
  const ARTIFACT_REGISTRY_SCHEMA_VERSION = '20.0';
25
25
  const KERNEL_SCHEMA_VERSION = '21.0';
26
26
  const CHECKPOINT_NATIVE_SCHEMA_VERSION = '22.0';
27
+ const SEMANTIC_KERNEL_SCHEMA_VERSION = '23.0';
27
28
 
28
29
  const MIGRATED_GAPS = Object.freeze([{
29
30
  id: 'legacy-rebaseline-required',
@@ -306,9 +307,154 @@ function applyChangeColumns(db) {
306
307
  db, 'changes', columns, 'alignment_thread_id',
307
308
  'TEXT REFERENCES decision_threads(id) ON DELETE SET NULL',
308
309
  ) || changed;
310
+ changed = addColumnIfMissing(
311
+ db, 'changes', columns, 'supersedes_id',
312
+ 'TEXT REFERENCES changes(id) ON DELETE SET NULL',
313
+ ) || changed;
309
314
  return changed;
310
315
  }
311
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
+
312
458
  function applySemanticAuthorityColumns(db) {
313
459
  let changed = false;
314
460
  if (tableNames(db).includes('workflow_steps')) {
@@ -416,7 +562,7 @@ function applyCompatibleColumns(db) {
416
562
  }
417
563
  const taskAdditions = [
418
564
  ['outcome', 'TEXT'],
419
- ['slice_kind', "TEXT CHECK (slice_kind IS NULL OR slice_kind IN ('tracer_bullet', 'expand_contract', 'integration_checkpoint'))"],
565
+ ['slice_kind', 'TEXT CHECK (slice_kind IS NULL OR length(trim(slice_kind)) BETWEEN 1 AND 80)'],
420
566
  ['public_seam', 'TEXT'],
421
567
  ['verification_command', 'TEXT'],
422
568
  ['acceptance_json', "TEXT NOT NULL DEFAULT '[]'"],
@@ -486,7 +632,7 @@ function migrateAdaptiveWorkflowRuns(db, fromVersion = latestSchemaVersion(db))
486
632
  if ([
487
633
  ADAPTIVE_SCHEMA_VERSION, DECISION_COMPLETION_SCHEMA_VERSION,
488
634
  ARTIFACT_REGISTRY_SCHEMA_VERSION, KERNEL_SCHEMA_VERSION,
489
- CHECKPOINT_NATIVE_SCHEMA_VERSION,
635
+ CHECKPOINT_NATIVE_SCHEMA_VERSION, SEMANTIC_KERNEL_SCHEMA_VERSION,
490
636
  ].includes(fromVersion) || existing) {
491
637
  return false;
492
638
  }
@@ -1007,6 +1153,11 @@ function applyCompatibleUpgrades(db, fromVersion = latestSchemaVersion(db), { le
1007
1153
  }
1008
1154
  const gitStateChangedFrom = upgradeBaselineWorktreeConstraint(db, fromVersion);
1009
1155
  const decisionCompletionChanged = upgradeDecisionThreadLifecycle(db);
1156
+ db.transaction(() => {
1157
+ applyCompatibleColumns(db);
1158
+ applyChangeColumns(db);
1159
+ })();
1160
+ const semanticKernelChanged = upgradeSemanticKernelConstraints(db);
1010
1161
  db.transaction(() => {
1011
1162
  applyCompatibleColumns(db);
1012
1163
  const contextChanged = applyContextSpineUpgrade(db);
@@ -1020,6 +1171,7 @@ function applyCompatibleUpgrades(db, fromVersion = latestSchemaVersion(db), { le
1020
1171
  DIALOGUE_SCHEMA_VERSION, GIT_AUTHORITY_SCHEMA_VERSION, ADAPTIVE_SCHEMA_VERSION,
1021
1172
  DECISION_COMPLETION_SCHEMA_VERSION, ARTIFACT_REGISTRY_SCHEMA_VERSION,
1022
1173
  KERNEL_SCHEMA_VERSION, CHECKPOINT_NATIVE_SCHEMA_VERSION,
1174
+ SEMANTIC_KERNEL_SCHEMA_VERSION,
1023
1175
  ].includes(fromVersion)
1024
1176
  && !contextMigration,
1025
1177
  );
@@ -1049,6 +1201,7 @@ function applyCompatibleUpgrades(db, fromVersion = latestSchemaVersion(db), { le
1049
1201
  GIT_AUTHORITY_SCHEMA_VERSION, ADAPTIVE_SCHEMA_VERSION,
1050
1202
  DECISION_COMPLETION_SCHEMA_VERSION, ARTIFACT_REGISTRY_SCHEMA_VERSION,
1051
1203
  KERNEL_SCHEMA_VERSION, CHECKPOINT_NATIVE_SCHEMA_VERSION,
1204
+ SEMANTIC_KERNEL_SCHEMA_VERSION,
1052
1205
  ].includes(fromVersion)
1053
1206
  && !baselineMigration) {
1054
1207
  db.prepare(
@@ -1123,6 +1276,7 @@ function applyCompatibleUpgrades(db, fromVersion = latestSchemaVersion(db), { le
1123
1276
  DIALOGUE_SCHEMA_VERSION, GIT_AUTHORITY_SCHEMA_VERSION, ADAPTIVE_SCHEMA_VERSION,
1124
1277
  DECISION_COMPLETION_SCHEMA_VERSION, ARTIFACT_REGISTRY_SCHEMA_VERSION,
1125
1278
  KERNEL_SCHEMA_VERSION, CHECKPOINT_NATIVE_SCHEMA_VERSION,
1279
+ SEMANTIC_KERNEL_SCHEMA_VERSION,
1126
1280
  ].includes(fromVersion) && !workflowMigration) {
1127
1281
  db.prepare(
1128
1282
  `INSERT INTO migration_history
@@ -1141,7 +1295,7 @@ function applyCompatibleUpgrades(db, fromVersion = latestSchemaVersion(db), { le
1141
1295
  AUTHORITY_SCHEMA_VERSION, SEMANTIC_SCHEMA_VERSION, DIALOGUE_SCHEMA_VERSION,
1142
1296
  GIT_AUTHORITY_SCHEMA_VERSION, ADAPTIVE_SCHEMA_VERSION, DECISION_COMPLETION_SCHEMA_VERSION,
1143
1297
  ARTIFACT_REGISTRY_SCHEMA_VERSION, KERNEL_SCHEMA_VERSION,
1144
- CHECKPOINT_NATIVE_SCHEMA_VERSION,
1298
+ CHECKPOINT_NATIVE_SCHEMA_VERSION, SEMANTIC_KERNEL_SCHEMA_VERSION,
1145
1299
  ].includes(fromVersion) && !authorityMigration) {
1146
1300
  db.prepare(
1147
1301
  `INSERT INTO migration_history
@@ -1160,7 +1314,7 @@ function applyCompatibleUpgrades(db, fromVersion = latestSchemaVersion(db), { le
1160
1314
  SEMANTIC_SCHEMA_VERSION, DIALOGUE_SCHEMA_VERSION, GIT_AUTHORITY_SCHEMA_VERSION,
1161
1315
  ADAPTIVE_SCHEMA_VERSION, DECISION_COMPLETION_SCHEMA_VERSION,
1162
1316
  ARTIFACT_REGISTRY_SCHEMA_VERSION, KERNEL_SCHEMA_VERSION,
1163
- CHECKPOINT_NATIVE_SCHEMA_VERSION,
1317
+ CHECKPOINT_NATIVE_SCHEMA_VERSION, SEMANTIC_KERNEL_SCHEMA_VERSION,
1164
1318
  ].includes(fromVersion) && !semanticMigration) {
1165
1319
  db.prepare(
1166
1320
  `INSERT INTO migration_history
@@ -1179,6 +1333,7 @@ function applyCompatibleUpgrades(db, fromVersion = latestSchemaVersion(db), { le
1179
1333
  DIALOGUE_SCHEMA_VERSION, GIT_AUTHORITY_SCHEMA_VERSION, ADAPTIVE_SCHEMA_VERSION,
1180
1334
  DECISION_COMPLETION_SCHEMA_VERSION, ARTIFACT_REGISTRY_SCHEMA_VERSION,
1181
1335
  KERNEL_SCHEMA_VERSION, CHECKPOINT_NATIVE_SCHEMA_VERSION,
1336
+ SEMANTIC_KERNEL_SCHEMA_VERSION,
1182
1337
  ].includes(fromVersion)
1183
1338
  && !dialogueMigration) {
1184
1339
  db.prepare(
@@ -1197,7 +1352,7 @@ function applyCompatibleUpgrades(db, fromVersion = latestSchemaVersion(db), { le
1197
1352
  if (fromVersion && ![
1198
1353
  GIT_AUTHORITY_SCHEMA_VERSION, ADAPTIVE_SCHEMA_VERSION, DECISION_COMPLETION_SCHEMA_VERSION,
1199
1354
  ARTIFACT_REGISTRY_SCHEMA_VERSION, KERNEL_SCHEMA_VERSION,
1200
- CHECKPOINT_NATIVE_SCHEMA_VERSION,
1355
+ CHECKPOINT_NATIVE_SCHEMA_VERSION, SEMANTIC_KERNEL_SCHEMA_VERSION,
1201
1356
  ].includes(fromVersion)
1202
1357
  && !gitStateMigration) {
1203
1358
  db.prepare(
@@ -1217,7 +1372,7 @@ function applyCompatibleUpgrades(db, fromVersion = latestSchemaVersion(db), { le
1217
1372
  if (fromVersion && ![
1218
1373
  ADAPTIVE_SCHEMA_VERSION, DECISION_COMPLETION_SCHEMA_VERSION,
1219
1374
  ARTIFACT_REGISTRY_SCHEMA_VERSION, KERNEL_SCHEMA_VERSION,
1220
- CHECKPOINT_NATIVE_SCHEMA_VERSION,
1375
+ CHECKPOINT_NATIVE_SCHEMA_VERSION, SEMANTIC_KERNEL_SCHEMA_VERSION,
1221
1376
  ].includes(fromVersion) && !adaptiveMigration) {
1222
1377
  db.prepare(
1223
1378
  `INSERT INTO migration_history
@@ -1237,6 +1392,7 @@ function applyCompatibleUpgrades(db, fromVersion = latestSchemaVersion(db), { le
1237
1392
  if (fromVersion && ![
1238
1393
  DECISION_COMPLETION_SCHEMA_VERSION, ARTIFACT_REGISTRY_SCHEMA_VERSION,
1239
1394
  KERNEL_SCHEMA_VERSION, CHECKPOINT_NATIVE_SCHEMA_VERSION,
1395
+ SEMANTIC_KERNEL_SCHEMA_VERSION,
1240
1396
  ].includes(fromVersion) && !completionMigration) {
1241
1397
  db.prepare(
1242
1398
  `INSERT INTO migration_history
@@ -1256,6 +1412,7 @@ function applyCompatibleUpgrades(db, fromVersion = latestSchemaVersion(db), { le
1256
1412
  if (fromVersion && fromVersion !== ARTIFACT_REGISTRY_SCHEMA_VERSION
1257
1413
  && fromVersion !== KERNEL_SCHEMA_VERSION
1258
1414
  && fromVersion !== CHECKPOINT_NATIVE_SCHEMA_VERSION
1415
+ && fromVersion !== SEMANTIC_KERNEL_SCHEMA_VERSION
1259
1416
  && !artifactRegistryMigration) {
1260
1417
  db.prepare(
1261
1418
  `INSERT INTO migration_history
@@ -1275,6 +1432,7 @@ function applyCompatibleUpgrades(db, fromVersion = latestSchemaVersion(db), { le
1275
1432
  if (fromVersion
1276
1433
  && fromVersion !== KERNEL_SCHEMA_VERSION
1277
1434
  && fromVersion !== CHECKPOINT_NATIVE_SCHEMA_VERSION
1435
+ && fromVersion !== SEMANTIC_KERNEL_SCHEMA_VERSION
1278
1436
  && !kernelMigration) {
1279
1437
  db.prepare(
1280
1438
  `INSERT INTO migration_history
@@ -1291,6 +1449,7 @@ function applyCompatibleUpgrades(db, fromVersion = latestSchemaVersion(db), { le
1291
1449
  ).get();
1292
1450
  if (fromVersion
1293
1451
  && fromVersion !== CHECKPOINT_NATIVE_SCHEMA_VERSION
1452
+ && fromVersion !== SEMANTIC_KERNEL_SCHEMA_VERSION
1294
1453
  && !checkpointNativeMigration) {
1295
1454
  db.prepare(
1296
1455
  `INSERT INTO migration_history
@@ -1302,6 +1461,24 @@ function applyCompatibleUpgrades(db, fromVersion = latestSchemaVersion(db), { le
1302
1461
  'Bind Baseline research to Stage Checkpoints and add byte digests for Context Envelopes and Worker Packets',
1303
1462
  );
1304
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
+ }
1305
1482
  db.exec('CREATE INDEX IF NOT EXISTS tasks_change ON tasks(change_id) WHERE change_id IS NOT NULL');
1306
1483
  db.exec('CREATE INDEX IF NOT EXISTS events_change ON events(change_id, id)');
1307
1484
  ensureActiveSessionLeaseIndex(db);
@@ -1309,7 +1486,7 @@ function applyCompatibleUpgrades(db, fromVersion = latestSchemaVersion(db), { le
1309
1486
  'INSERT OR IGNORE INTO schema_version (version, description) VALUES (?, ?)',
1310
1487
  ).run(
1311
1488
  EXPECTED_VERSION,
1312
- 'Checkpoint-native baseline authority plus byte-bound Context Envelopes and Worker Packets',
1489
+ 'Persistence and safety kernel with open semantic vocabulary and immutable Change successors',
1313
1490
  );
1314
1491
  })();
1315
1492
  }
@@ -1343,6 +1520,7 @@ function initStateDb(dbPath) {
1343
1520
  DIALOGUE_SCHEMA_VERSION, GIT_AUTHORITY_SCHEMA_VERSION, ADAPTIVE_SCHEMA_VERSION,
1344
1521
  DECISION_COMPLETION_SCHEMA_VERSION, ARTIFACT_REGISTRY_SCHEMA_VERSION,
1345
1522
  KERNEL_SCHEMA_VERSION, CHECKPOINT_NATIVE_SCHEMA_VERSION,
1523
+ SEMANTIC_KERNEL_SCHEMA_VERSION,
1346
1524
  ].includes(fromVersion);
1347
1525
  backupPath = migrationBackup(db, dbPath, fromVersion, existing);
1348
1526
  // This must precede every ALTER, table rebuild, or index creation. Runtime