ultra-builder-pro-cli 0.19.1 → 0.21.1
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.
- package/.ultra-template/docs/research/README.md +26 -5
- package/.ultra-template/specs/discovery.md +1 -1
- package/.ultra-template/tasks/tasks.json +1 -1
- package/CHANGELOG.md +90 -1
- package/README.md +62 -29
- package/adapters/_shared/codex-assets.cjs +69 -11
- package/adapters/_shared/interaction-contract.cjs +60 -5
- package/adapters/_shared/runtime-assets.cjs +41 -1
- package/adapters/claude.js +7 -5
- package/adapters/codex.js +1 -1
- package/adapters/kimi.js +2 -8
- package/adapters/opencode.js +30 -28
- package/bin/orchestrator.js +64 -24
- package/commands/ultra-research.md +1 -1
- package/docs/AGENT-CONTEXT.md +79 -45
- package/docs/ARCHITECTURE.md +75 -38
- package/docs/ARTIFACT-AUTHORITY.md +234 -0
- package/docs/DECISIONS.md +20 -10
- package/docs/LEGACY-CLI-CROSSWALK.md +15 -11
- package/docs/PLUGIN-ISOLATION-CONTRACT.md +4 -3
- package/docs/ROADMAP.md +21 -6
- package/docs/RUNTIME-COMPAT-MATRIX.md +30 -13
- package/docs/STATE-DB-ACCESS-POLICY.md +11 -9
- package/docs/WORKFLOW-LIFECYCLE.md +105 -54
- package/hooks/README.md +6 -5
- package/hooks/active_task_context.py +97 -12
- package/hooks/context_spine.py +16 -4
- package/hooks/health_check.py +5 -4
- package/hooks/pre_stop_check.py +5 -2
- package/hooks/runtime_paths.py +396 -0
- package/hooks/subagent_tracker.py +5 -2
- package/hooks/workflow_checkpoint.py +16 -3
- package/hooks/workflow_context.py +11 -3
- package/hooks/workflow_resume.py +7 -4
- package/mcp-server/breadcrumb.cjs +20 -2
- package/mcp-server/lib/archive-journal.cjs +1026 -111
- package/mcp-server/lib/archive-mutation-worker.py +365 -0
- package/mcp-server/lib/artifact-registry.cjs +1126 -0
- package/mcp-server/lib/baseline-workflow.cjs +9 -4
- package/mcp-server/lib/change-packet.cjs +818 -0
- package/mcp-server/lib/change-workflow.cjs +932 -176
- package/mcp-server/lib/context-paths.cjs +111 -0
- package/mcp-server/lib/context-spine.cjs +511 -45
- package/mcp-server/lib/decision-dialogue.cjs +427 -12
- package/mcp-server/lib/delivery-transaction.cjs +471 -0
- package/mcp-server/lib/doctor-backup-worker.cjs +328 -0
- package/mcp-server/lib/doctor.cjs +375 -12
- package/mcp-server/lib/git-bootstrap.cjs +459 -47
- package/mcp-server/lib/init-project.cjs +190 -95
- package/mcp-server/lib/plan-publication-worker.cjs +830 -0
- package/mcp-server/lib/plan-store.cjs +700 -5
- package/mcp-server/lib/project-breadcrumb.cjs +23 -4
- package/mcp-server/lib/projector.cjs +174 -40
- package/mcp-server/lib/runtime-paths.cjs +1687 -0
- package/mcp-server/lib/safe-project-file.cjs +385 -0
- package/mcp-server/lib/spec-learning.cjs +28 -12
- package/mcp-server/lib/state-db.cjs +397 -13
- package/mcp-server/lib/state-ops.cjs +248 -49
- package/mcp-server/lib/telemetry.cjs +12 -3
- package/mcp-server/lib/workflow-state.cjs +614 -62
- package/mcp-server/server.cjs +282 -33
- package/orchestrator/daemon.cjs +2 -10
- package/orchestrator/parallel-orchestrator.cjs +314 -50
- package/orchestrator/planner/plan-builder.cjs +1 -1
- package/orchestrator/recovery.cjs +125 -7
- package/orchestrator/session-close-journal-worker.cjs +5 -0
- package/orchestrator/session-close-journal.cjs +1014 -0
- package/orchestrator/session-runner.cjs +679 -69
- package/orchestrator/worktree-manager.cjs +186 -35
- package/package.json +2 -3
- package/skills/cc-collab/SKILL.md +4 -1
- package/skills/codex-collab/SKILL.md +5 -2
- package/skills/ultra-change/SKILL.md +38 -13
- package/skills/ultra-deliver/SKILL.md +23 -11
- package/skills/ultra-dev/SKILL.md +33 -6
- package/skills/ultra-doctor/SKILL.md +6 -2
- package/skills/ultra-init/SKILL.md +4 -2
- package/skills/ultra-plan/SKILL.md +68 -18
- package/skills/ultra-research/SKILL.md +45 -32
- package/skills/ultra-review/SKILL.md +9 -5
- package/skills/ultra-status/SKILL.md +9 -4
- package/skills/ultra-test/SKILL.md +15 -7
- package/skills/ultra-think/SKILL.md +6 -0
- package/skills/ultra-think/references/decision-dialogue.md +61 -14
- package/skills/ultra-verify/SKILL.md +4 -1
- package/spec/cli-protocol.md +19 -7
- package/spec/mcp-tools.yaml +531 -46
- package/spec/schemas/change-delta.v1.schema.json +153 -0
- package/spec/schemas/documentation-reconciliation.v1.schema.json +106 -0
- package/spec/schemas/mcp-tools.schema.json +1 -1
- package/spec/schemas/state-db.sql +57 -17
- package/spec/schemas/tasks.v4.5.schema.json +1 -1
- package/ultra-tools/cli.cjs +3 -2
- package/ultra-tools/commands/db.cjs +76 -25
- package/ultra-tools/commands/migrate.cjs +995 -80
- package/ultra-tools/commands/session.cjs +34 -8
- package/ultra-tools/commands/status.cjs +34 -7
- package/ultra-tools/commands/system.cjs +129 -22
- package/commands/learn.md +0 -10
- package/orchestrator/skill-miner.cjs +0 -133
- package/output-styles/coding-vibes.md +0 -67
- package/output-styles/structural-thinking.md +0 -61
- package/skills/learn/SKILL.md +0 -58
|
@@ -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 = '
|
|
12
|
+
const EXPECTED_VERSION = '20.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';
|
|
@@ -19,6 +19,9 @@ const AUTHORITY_SCHEMA_VERSION = '14.0';
|
|
|
19
19
|
const SEMANTIC_SCHEMA_VERSION = '15.0';
|
|
20
20
|
const DIALOGUE_SCHEMA_VERSION = '16.0';
|
|
21
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';
|
|
22
25
|
|
|
23
26
|
const MIGRATED_GAPS = Object.freeze([{
|
|
24
27
|
id: 'legacy-rebaseline-required',
|
|
@@ -42,6 +45,7 @@ const REQUIRED_TABLES = Object.freeze([
|
|
|
42
45
|
'circuit_breaker',
|
|
43
46
|
'changes',
|
|
44
47
|
'artifacts',
|
|
48
|
+
'artifact_edges',
|
|
45
49
|
'context_snapshots',
|
|
46
50
|
'spec_learning_candidates',
|
|
47
51
|
'trace_links',
|
|
@@ -54,6 +58,93 @@ const REQUIRED_TABLES = Object.freeze([
|
|
|
54
58
|
'decision_items',
|
|
55
59
|
]);
|
|
56
60
|
|
|
61
|
+
class ActiveSessionLeaseConflictError extends Error {
|
|
62
|
+
constructor(conflicts) {
|
|
63
|
+
super(
|
|
64
|
+
'state.db has duplicate active task leases; resolve the conflicting sessions before upgrade: '
|
|
65
|
+
+ conflicts.map((row) => `${row.task_id}=[${row.session_ids.join(',')}]`).join('; '),
|
|
66
|
+
);
|
|
67
|
+
this.name = 'ActiveSessionLeaseConflictError';
|
|
68
|
+
this.code = 'ACTIVE_SESSION_LEASE_CONFLICT';
|
|
69
|
+
this.details = { conflicts };
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
class ArtifactAuthorityConflictError extends Error {
|
|
74
|
+
constructor(conflicts) {
|
|
75
|
+
super(
|
|
76
|
+
'state.db has duplicate active artifact authority for canonical paths: '
|
|
77
|
+
+ conflicts.map((row) => `${row.path}=[${row.artifact_ids.join(',')}]`).join('; '),
|
|
78
|
+
);
|
|
79
|
+
this.name = 'ArtifactAuthorityConflictError';
|
|
80
|
+
this.code = 'ARTIFACT_AUTHORITY_CONFLICT';
|
|
81
|
+
this.details = { conflicts };
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function canonicalArtifactPath(value) {
|
|
86
|
+
const raw = String(value || '').trim().replaceAll('\\', '/');
|
|
87
|
+
if (!raw || path.posix.isAbsolute(raw)) {
|
|
88
|
+
throw new ArtifactAuthorityConflictError([{
|
|
89
|
+
path: raw || '(empty)',
|
|
90
|
+
artifact_ids: [],
|
|
91
|
+
reason: 'invalid_path',
|
|
92
|
+
}]);
|
|
93
|
+
}
|
|
94
|
+
const normalized = path.posix.normalize(raw).replace(/^\.\//, '');
|
|
95
|
+
if (normalized === '..' || normalized.startsWith('../') || normalized.includes('/../')) {
|
|
96
|
+
throw new ArtifactAuthorityConflictError([{
|
|
97
|
+
path: raw,
|
|
98
|
+
artifact_ids: [],
|
|
99
|
+
reason: 'path_escape',
|
|
100
|
+
}]);
|
|
101
|
+
}
|
|
102
|
+
return normalized;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function activeArtifactPathConflicts(db) {
|
|
106
|
+
if (!tableNames(db).includes('artifacts')) return [];
|
|
107
|
+
const columns = columnNames(db, 'artifacts');
|
|
108
|
+
if (!columns.has('id') || !columns.has('path')) return [];
|
|
109
|
+
const hasStatus = columns.has('status');
|
|
110
|
+
const rows = db.prepare(
|
|
111
|
+
`SELECT id, path FROM artifacts${hasStatus ? " WHERE status <> 'archived'" : ''}
|
|
112
|
+
ORDER BY id`,
|
|
113
|
+
).all();
|
|
114
|
+
const grouped = new Map();
|
|
115
|
+
for (const row of rows) {
|
|
116
|
+
const canonical = canonicalArtifactPath(row.path);
|
|
117
|
+
const ids = grouped.get(canonical) || [];
|
|
118
|
+
ids.push(row.id);
|
|
119
|
+
grouped.set(canonical, ids);
|
|
120
|
+
}
|
|
121
|
+
return [...grouped.entries()]
|
|
122
|
+
.filter(([, ids]) => ids.length > 1)
|
|
123
|
+
.map(([artifactPath, ids]) => ({ path: artifactPath, artifact_ids: ids }));
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function assertNoDuplicateActiveArtifactPaths(db) {
|
|
127
|
+
const conflicts = activeArtifactPathConflicts(db);
|
|
128
|
+
if (conflicts.length > 0) throw new ArtifactAuthorityConflictError(conflicts);
|
|
129
|
+
return true;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function normalizeStoredArtifactPaths(db) {
|
|
133
|
+
if (!tableNames(db).includes('artifacts')) return false;
|
|
134
|
+
assertNoDuplicateActiveArtifactPaths(db);
|
|
135
|
+
const rows = db.prepare('SELECT id, path FROM artifacts ORDER BY id').all();
|
|
136
|
+
let changed = false;
|
|
137
|
+
const update = db.prepare('UPDATE artifacts SET path = ? WHERE id = ?');
|
|
138
|
+
for (const row of rows) {
|
|
139
|
+
const canonical = canonicalArtifactPath(row.path);
|
|
140
|
+
if (canonical !== row.path) {
|
|
141
|
+
update.run(canonical, row.id);
|
|
142
|
+
changed = true;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
return changed;
|
|
146
|
+
}
|
|
147
|
+
|
|
57
148
|
function applyBaselineUpgrade(db, { legacyState = false } = {}) {
|
|
58
149
|
db.exec(`
|
|
59
150
|
CREATE TABLE IF NOT EXISTS baselines (
|
|
@@ -235,6 +326,7 @@ function tableNames(db) {
|
|
|
235
326
|
}
|
|
236
327
|
|
|
237
328
|
function applySchema(db) {
|
|
329
|
+
assertNoDuplicateActiveSessionLeases(db);
|
|
238
330
|
runScript(db, readSchemaSql());
|
|
239
331
|
}
|
|
240
332
|
|
|
@@ -328,7 +420,12 @@ function migrateAdaptiveWorkflowRuns(db, fromVersion = latestSchemaVersion(db))
|
|
|
328
420
|
const existing = db.prepare(
|
|
329
421
|
"SELECT 1 FROM migration_history WHERE to_version = '18.0' AND status = 'success' LIMIT 1",
|
|
330
422
|
).get();
|
|
331
|
-
if (
|
|
423
|
+
if ([
|
|
424
|
+
ADAPTIVE_SCHEMA_VERSION, DECISION_COMPLETION_SCHEMA_VERSION,
|
|
425
|
+
ARTIFACT_REGISTRY_SCHEMA_VERSION,
|
|
426
|
+
].includes(fromVersion) || existing) {
|
|
427
|
+
return false;
|
|
428
|
+
}
|
|
332
429
|
|
|
333
430
|
const obsoleteSteps = Object.freeze({
|
|
334
431
|
init: ['establish-baseline'],
|
|
@@ -455,6 +552,95 @@ function baselineSupportsUnbornGit(db) {
|
|
|
455
552
|
return typeof row?.sql === 'string' && row.sql.includes("'unborn'");
|
|
456
553
|
}
|
|
457
554
|
|
|
555
|
+
function decisionThreadsSupportCompletion(db) {
|
|
556
|
+
const row = db.prepare(
|
|
557
|
+
"SELECT sql FROM sqlite_master WHERE type = 'table' AND name = 'decision_threads'",
|
|
558
|
+
).get();
|
|
559
|
+
if (typeof row?.sql !== 'string' || !row.sql.includes("'completed'")) return false;
|
|
560
|
+
return columnNames(db, 'decision_threads').has('completed_at');
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
function upgradeDecisionThreadLifecycle(db) {
|
|
564
|
+
if (!tableNames(db).includes('decision_threads') || decisionThreadsSupportCompletion(db)) {
|
|
565
|
+
return false;
|
|
566
|
+
}
|
|
567
|
+
const foreignKeys = db.pragma('foreign_keys', { simple: true });
|
|
568
|
+
db.pragma('foreign_keys = OFF');
|
|
569
|
+
try {
|
|
570
|
+
db.transaction(() => {
|
|
571
|
+
db.exec(`
|
|
572
|
+
DROP TABLE IF EXISTS decision_threads_completion_upgrade;
|
|
573
|
+
CREATE TABLE decision_threads_completion_upgrade (
|
|
574
|
+
id TEXT PRIMARY KEY,
|
|
575
|
+
purpose TEXT NOT NULL,
|
|
576
|
+
mode TEXT NOT NULL DEFAULT 'guided'
|
|
577
|
+
CHECK (mode IN ('guided', 'fast', 'autonomous', 'diagnostic')),
|
|
578
|
+
status TEXT NOT NULL DEFAULT 'active'
|
|
579
|
+
CHECK (status IN ('active', 'checkpoint_ready', 'completed', 'confirmed', 'cancelled')),
|
|
580
|
+
baseline_id TEXT REFERENCES baselines(id) ON DELETE SET NULL,
|
|
581
|
+
change_id TEXT REFERENCES changes(id) ON DELETE CASCADE,
|
|
582
|
+
workflow_run_id TEXT REFERENCES workflow_runs(id) ON DELETE CASCADE,
|
|
583
|
+
summary_json TEXT NOT NULL DEFAULT '{}',
|
|
584
|
+
checkpoint_json TEXT NOT NULL DEFAULT '{}',
|
|
585
|
+
started_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')),
|
|
586
|
+
updated_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')),
|
|
587
|
+
completed_at TEXT,
|
|
588
|
+
confirmed_at TEXT,
|
|
589
|
+
CHECK (baseline_id IS NOT NULL OR change_id IS NOT NULL OR workflow_run_id IS NOT NULL)
|
|
590
|
+
);
|
|
591
|
+
INSERT INTO decision_threads_completion_upgrade (
|
|
592
|
+
id, purpose, mode, status, baseline_id, change_id, workflow_run_id,
|
|
593
|
+
summary_json, checkpoint_json, started_at, updated_at, completed_at, confirmed_at
|
|
594
|
+
)
|
|
595
|
+
SELECT
|
|
596
|
+
id, purpose, mode, status, baseline_id, change_id, workflow_run_id,
|
|
597
|
+
summary_json, checkpoint_json, started_at, updated_at, NULL, confirmed_at
|
|
598
|
+
FROM decision_threads;
|
|
599
|
+
DROP TABLE decision_threads;
|
|
600
|
+
ALTER TABLE decision_threads_completion_upgrade RENAME TO decision_threads;
|
|
601
|
+
CREATE INDEX decision_threads_status
|
|
602
|
+
ON decision_threads(status, updated_at);
|
|
603
|
+
CREATE INDEX decision_threads_authority
|
|
604
|
+
ON decision_threads(baseline_id, change_id, workflow_run_id, status);
|
|
605
|
+
`);
|
|
606
|
+
db.prepare(
|
|
607
|
+
`UPDATE decision_threads
|
|
608
|
+
SET status = 'completed',
|
|
609
|
+
summary_json = CASE
|
|
610
|
+
WHEN summary_json = '{}' THEN ?
|
|
611
|
+
ELSE json_set(summary_json, '$.completion_kind', 'migrated_settled_thread')
|
|
612
|
+
END,
|
|
613
|
+
completed_at = updated_at
|
|
614
|
+
WHERE status = 'active'
|
|
615
|
+
AND EXISTS (
|
|
616
|
+
SELECT 1 FROM decision_items WHERE decision_items.thread_id = decision_threads.id
|
|
617
|
+
)
|
|
618
|
+
AND NOT EXISTS (
|
|
619
|
+
SELECT 1 FROM decision_items
|
|
620
|
+
WHERE decision_items.thread_id = decision_threads.id AND status = 'open'
|
|
621
|
+
)
|
|
622
|
+
AND NOT EXISTS (
|
|
623
|
+
SELECT 1 FROM decision_items
|
|
624
|
+
WHERE decision_items.thread_id = decision_threads.id
|
|
625
|
+
AND blocking = 1 AND status = 'deferred'
|
|
626
|
+
)`,
|
|
627
|
+
).run(JSON.stringify({
|
|
628
|
+
text: 'Existing normalized decision state was completed during schema migration.',
|
|
629
|
+
completion_kind: 'migrated_settled_thread',
|
|
630
|
+
}));
|
|
631
|
+
const violations = db.pragma('foreign_key_check');
|
|
632
|
+
if (violations.length > 0) {
|
|
633
|
+
throw new Error(
|
|
634
|
+
`decision completion migration produced ${violations.length} foreign key violation(s)`,
|
|
635
|
+
);
|
|
636
|
+
}
|
|
637
|
+
})();
|
|
638
|
+
} finally {
|
|
639
|
+
db.pragma(`foreign_keys = ${foreignKeys ? 'ON' : 'OFF'}`);
|
|
640
|
+
}
|
|
641
|
+
return true;
|
|
642
|
+
}
|
|
643
|
+
|
|
458
644
|
function upgradeBaselineWorktreeConstraint(db, fromVersion = latestSchemaVersion(db)) {
|
|
459
645
|
if (!tableNames(db).includes('baselines') || baselineSupportsUnbornGit(db)) return false;
|
|
460
646
|
const previousVersion = fromVersion || 'unknown';
|
|
@@ -530,6 +716,7 @@ function upgradeBaselineWorktreeConstraint(db, fromVersion = latestSchemaVersion
|
|
|
530
716
|
}
|
|
531
717
|
|
|
532
718
|
function upgradeRuntimeConstraints(db, fromVersion = latestSchemaVersion(db)) {
|
|
719
|
+
assertNoDuplicateActiveSessionLeases(db);
|
|
533
720
|
if (tableSupportsKimi(db, 'events') && tableSupportsKimi(db, 'sessions')) return false;
|
|
534
721
|
const previousVersion = fromVersion || 'unknown';
|
|
535
722
|
const foreignKeys = db.pragma('foreign_keys', { simple: true });
|
|
@@ -581,6 +768,8 @@ function upgradeRuntimeConstraints(db, fromVersion = latestSchemaVersion(db)) {
|
|
|
581
768
|
ALTER TABLE sessions_runtime_upgrade RENAME TO sessions;
|
|
582
769
|
CREATE INDEX sessions_active ON sessions(status, task_id);
|
|
583
770
|
CREATE INDEX sessions_lease ON sessions(lease_expires_at) WHERE status = 'running';
|
|
771
|
+
CREATE UNIQUE INDEX sessions_one_active_task
|
|
772
|
+
ON sessions(task_id) WHERE status = 'running';
|
|
584
773
|
`);
|
|
585
774
|
db.prepare(
|
|
586
775
|
`INSERT INTO migration_history
|
|
@@ -602,7 +791,129 @@ function upgradeRuntimeConstraints(db, fromVersion = latestSchemaVersion(db)) {
|
|
|
602
791
|
return true;
|
|
603
792
|
}
|
|
604
793
|
|
|
794
|
+
function activeSessionLeaseConflicts(db) {
|
|
795
|
+
if (!tableNames(db).includes('sessions')) return [];
|
|
796
|
+
const columns = columnNames(db, 'sessions');
|
|
797
|
+
if (!['sid', 'task_id', 'status'].every((name) => columns.has(name))) return [];
|
|
798
|
+
const rows = db.prepare(
|
|
799
|
+
`SELECT task_id, sid
|
|
800
|
+
FROM sessions
|
|
801
|
+
WHERE status = 'running'
|
|
802
|
+
ORDER BY task_id, sid`,
|
|
803
|
+
).all();
|
|
804
|
+
const byTask = new Map();
|
|
805
|
+
for (const row of rows) {
|
|
806
|
+
const ids = byTask.get(row.task_id) || [];
|
|
807
|
+
ids.push(row.sid);
|
|
808
|
+
byTask.set(row.task_id, ids);
|
|
809
|
+
}
|
|
810
|
+
return [...byTask.entries()]
|
|
811
|
+
.filter(([, sessionIds]) => sessionIds.length > 1)
|
|
812
|
+
.map(([taskId, sessionIds]) => ({
|
|
813
|
+
task_id: taskId,
|
|
814
|
+
lease_count: sessionIds.length,
|
|
815
|
+
session_ids: sessionIds,
|
|
816
|
+
}));
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
function assertNoDuplicateActiveSessionLeases(db) {
|
|
820
|
+
const conflicts = activeSessionLeaseConflicts(db);
|
|
821
|
+
if (conflicts.length > 0) throw new ActiveSessionLeaseConflictError(conflicts);
|
|
822
|
+
return true;
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
function ensureActiveSessionLeaseIndex(db) {
|
|
826
|
+
if (!tableNames(db).includes('sessions')) return false;
|
|
827
|
+
assertNoDuplicateActiveSessionLeases(db);
|
|
828
|
+
const existing = db.prepare(
|
|
829
|
+
"SELECT 1 AS present FROM sqlite_master WHERE type = 'index' AND name = 'sessions_one_active_task'",
|
|
830
|
+
).get();
|
|
831
|
+
if (existing) return false;
|
|
832
|
+
db.exec(
|
|
833
|
+
`CREATE UNIQUE INDEX sessions_one_active_task
|
|
834
|
+
ON sessions(task_id) WHERE status = 'running'`,
|
|
835
|
+
);
|
|
836
|
+
return true;
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
function applyArtifactRegistryUpgrade(db) {
|
|
840
|
+
if (!tableNames(db).includes('artifacts')) return false;
|
|
841
|
+
assertNoDuplicateActiveArtifactPaths(db);
|
|
842
|
+
const columns = columnNames(db, 'artifacts');
|
|
843
|
+
let changed = false;
|
|
844
|
+
if (![
|
|
845
|
+
'owner_type', 'owner_id', 'digest', 'before_digest', 'after_digest',
|
|
846
|
+
'provenance_json', 'managed',
|
|
847
|
+
].every((column) => columns.has(column))) {
|
|
848
|
+
db.exec(`
|
|
849
|
+
DROP TABLE IF EXISTS artifacts_registry_upgrade;
|
|
850
|
+
CREATE TABLE artifacts_registry_upgrade (
|
|
851
|
+
id TEXT PRIMARY KEY,
|
|
852
|
+
owner_type TEXT NOT NULL
|
|
853
|
+
CHECK (owner_type IN ('project', 'baseline', 'change', 'task', 'workflow')),
|
|
854
|
+
owner_id TEXT NOT NULL,
|
|
855
|
+
change_id TEXT REFERENCES changes(id) ON DELETE SET NULL,
|
|
856
|
+
task_id TEXT REFERENCES tasks(id) ON DELETE SET NULL,
|
|
857
|
+
kind TEXT NOT NULL,
|
|
858
|
+
path TEXT NOT NULL,
|
|
859
|
+
digest TEXT,
|
|
860
|
+
content_hash TEXT,
|
|
861
|
+
before_digest TEXT,
|
|
862
|
+
after_digest TEXT,
|
|
863
|
+
provenance_json TEXT NOT NULL DEFAULT '{}',
|
|
864
|
+
metadata_json TEXT,
|
|
865
|
+
managed INTEGER NOT NULL DEFAULT 0 CHECK (managed IN (0, 1)),
|
|
866
|
+
status TEXT NOT NULL DEFAULT 'current'
|
|
867
|
+
CHECK (status IN ('current', 'stale', 'terminal', 'archived')),
|
|
868
|
+
created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')),
|
|
869
|
+
updated_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')),
|
|
870
|
+
UNIQUE(owner_type, owner_id, kind, path)
|
|
871
|
+
);
|
|
872
|
+
INSERT INTO artifacts_registry_upgrade
|
|
873
|
+
(id, owner_type, owner_id, change_id, task_id, kind, path, digest,
|
|
874
|
+
content_hash, before_digest, after_digest, provenance_json,
|
|
875
|
+
metadata_json, managed, status, created_at, updated_at)
|
|
876
|
+
SELECT id,
|
|
877
|
+
CASE WHEN task_id IS NOT NULL THEN 'task' ELSE 'change' END,
|
|
878
|
+
CASE WHEN task_id IS NOT NULL THEN task_id ELSE change_id END,
|
|
879
|
+
change_id, task_id, kind, path, content_hash, content_hash,
|
|
880
|
+
NULL, content_hash, '{"migration":"schema-20.0"}',
|
|
881
|
+
metadata_json, 0, status, created_at, updated_at
|
|
882
|
+
FROM artifacts;
|
|
883
|
+
DROP TABLE artifacts;
|
|
884
|
+
ALTER TABLE artifacts_registry_upgrade RENAME TO artifacts;
|
|
885
|
+
`);
|
|
886
|
+
changed = true;
|
|
887
|
+
}
|
|
888
|
+
if (normalizeStoredArtifactPaths(db)) changed = true;
|
|
889
|
+
db.exec(`
|
|
890
|
+
CREATE INDEX IF NOT EXISTS artifacts_change ON artifacts(change_id, kind);
|
|
891
|
+
CREATE INDEX IF NOT EXISTS artifacts_owner ON artifacts(owner_type, owner_id, status);
|
|
892
|
+
CREATE INDEX IF NOT EXISTS artifacts_path ON artifacts(path, status);
|
|
893
|
+
CREATE UNIQUE INDEX IF NOT EXISTS artifacts_one_active_path
|
|
894
|
+
ON artifacts(path) WHERE status <> 'archived';
|
|
895
|
+
CREATE TABLE IF NOT EXISTS artifact_edges (
|
|
896
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
897
|
+
source_type TEXT NOT NULL
|
|
898
|
+
CHECK (source_type IN ('artifact', 'project', 'baseline', 'change', 'task', 'workflow', 'external')),
|
|
899
|
+
source_id TEXT NOT NULL,
|
|
900
|
+
target_type TEXT NOT NULL
|
|
901
|
+
CHECK (target_type IN ('artifact', 'project', 'baseline', 'change', 'task', 'workflow', 'external')),
|
|
902
|
+
target_id TEXT NOT NULL,
|
|
903
|
+
relation TEXT NOT NULL,
|
|
904
|
+
created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')),
|
|
905
|
+
UNIQUE(source_type, source_id, target_type, target_id, relation)
|
|
906
|
+
);
|
|
907
|
+
CREATE INDEX IF NOT EXISTS artifact_edges_source
|
|
908
|
+
ON artifact_edges(source_type, source_id);
|
|
909
|
+
CREATE INDEX IF NOT EXISTS artifact_edges_target
|
|
910
|
+
ON artifact_edges(target_type, target_id);
|
|
911
|
+
`);
|
|
912
|
+
return changed;
|
|
913
|
+
}
|
|
914
|
+
|
|
605
915
|
function applyCompatibleUpgrades(db, fromVersion = latestSchemaVersion(db), { legacyState = false } = {}) {
|
|
916
|
+
assertNoDuplicateActiveSessionLeases(db);
|
|
606
917
|
const runtimeChanged = upgradeRuntimeConstraints(db, fromVersion);
|
|
607
918
|
if (tableNames(db).includes('baselines') && !baselineSupportsUnbornGit(db)) {
|
|
608
919
|
db.transaction(() => {
|
|
@@ -611,6 +922,7 @@ function applyCompatibleUpgrades(db, fromVersion = latestSchemaVersion(db), { le
|
|
|
611
922
|
})();
|
|
612
923
|
}
|
|
613
924
|
const gitStateChangedFrom = upgradeBaselineWorktreeConstraint(db, fromVersion);
|
|
925
|
+
const decisionCompletionChanged = upgradeDecisionThreadLifecycle(db);
|
|
614
926
|
db.transaction(() => {
|
|
615
927
|
applyCompatibleColumns(db);
|
|
616
928
|
const contextChanged = applyContextSpineUpgrade(db);
|
|
@@ -621,7 +933,8 @@ function applyCompatibleUpgrades(db, fromVersion = latestSchemaVersion(db), { le
|
|
|
621
933
|
fromVersion && ![
|
|
622
934
|
CONTEXT_SCHEMA_VERSION, BASELINE_SCHEMA_VERSION, ADOPTION_SCHEMA_VERSION,
|
|
623
935
|
WORKFLOW_SCHEMA_VERSION, AUTHORITY_SCHEMA_VERSION, SEMANTIC_SCHEMA_VERSION,
|
|
624
|
-
DIALOGUE_SCHEMA_VERSION, GIT_AUTHORITY_SCHEMA_VERSION,
|
|
936
|
+
DIALOGUE_SCHEMA_VERSION, GIT_AUTHORITY_SCHEMA_VERSION, ADAPTIVE_SCHEMA_VERSION,
|
|
937
|
+
DECISION_COMPLETION_SCHEMA_VERSION, ARTIFACT_REGISTRY_SCHEMA_VERSION,
|
|
625
938
|
].includes(fromVersion)
|
|
626
939
|
&& !contextMigration,
|
|
627
940
|
);
|
|
@@ -640,13 +953,15 @@ function applyCompatibleUpgrades(db, fromVersion = latestSchemaVersion(db), { le
|
|
|
640
953
|
applyBaselineColumns(db);
|
|
641
954
|
applyChangeColumns(db);
|
|
642
955
|
applySemanticAuthorityColumns(db);
|
|
956
|
+
const artifactRegistryChanged = applyArtifactRegistryUpgrade(db);
|
|
643
957
|
const baselineMigration = db.prepare(
|
|
644
958
|
"SELECT 1 FROM migration_history WHERE to_version = '11.0' AND status = 'success' LIMIT 1",
|
|
645
959
|
).get();
|
|
646
960
|
if (fromVersion && ![
|
|
647
961
|
BASELINE_SCHEMA_VERSION, ADOPTION_SCHEMA_VERSION, WORKFLOW_SCHEMA_VERSION,
|
|
648
962
|
AUTHORITY_SCHEMA_VERSION, SEMANTIC_SCHEMA_VERSION, DIALOGUE_SCHEMA_VERSION,
|
|
649
|
-
GIT_AUTHORITY_SCHEMA_VERSION,
|
|
963
|
+
GIT_AUTHORITY_SCHEMA_VERSION, ADAPTIVE_SCHEMA_VERSION,
|
|
964
|
+
DECISION_COMPLETION_SCHEMA_VERSION, ARTIFACT_REGISTRY_SCHEMA_VERSION,
|
|
650
965
|
].includes(fromVersion)
|
|
651
966
|
&& !baselineMigration) {
|
|
652
967
|
db.prepare(
|
|
@@ -715,7 +1030,11 @@ function applyCompatibleUpgrades(db, fromVersion = latestSchemaVersion(db), { le
|
|
|
715
1030
|
const workflowMigration = db.prepare(
|
|
716
1031
|
"SELECT 1 FROM migration_history WHERE to_version = '13.0' AND status = 'success' LIMIT 1",
|
|
717
1032
|
).get();
|
|
718
|
-
if (fromVersion &&
|
|
1033
|
+
if (fromVersion && ![
|
|
1034
|
+
WORKFLOW_SCHEMA_VERSION, AUTHORITY_SCHEMA_VERSION, SEMANTIC_SCHEMA_VERSION,
|
|
1035
|
+
DIALOGUE_SCHEMA_VERSION, GIT_AUTHORITY_SCHEMA_VERSION, ADAPTIVE_SCHEMA_VERSION,
|
|
1036
|
+
DECISION_COMPLETION_SCHEMA_VERSION, ARTIFACT_REGISTRY_SCHEMA_VERSION,
|
|
1037
|
+
].includes(fromVersion) && !workflowMigration) {
|
|
719
1038
|
db.prepare(
|
|
720
1039
|
`INSERT INTO migration_history
|
|
721
1040
|
(from_version, to_version, direction, status, notes)
|
|
@@ -729,7 +1048,11 @@ function applyCompatibleUpgrades(db, fromVersion = latestSchemaVersion(db), { le
|
|
|
729
1048
|
const authorityMigration = db.prepare(
|
|
730
1049
|
"SELECT 1 FROM migration_history WHERE to_version = '14.0' AND status = 'success' LIMIT 1",
|
|
731
1050
|
).get();
|
|
732
|
-
if (fromVersion &&
|
|
1051
|
+
if (fromVersion && ![
|
|
1052
|
+
AUTHORITY_SCHEMA_VERSION, SEMANTIC_SCHEMA_VERSION, DIALOGUE_SCHEMA_VERSION,
|
|
1053
|
+
GIT_AUTHORITY_SCHEMA_VERSION, ADAPTIVE_SCHEMA_VERSION, DECISION_COMPLETION_SCHEMA_VERSION,
|
|
1054
|
+
ARTIFACT_REGISTRY_SCHEMA_VERSION,
|
|
1055
|
+
].includes(fromVersion) && !authorityMigration) {
|
|
733
1056
|
db.prepare(
|
|
734
1057
|
`INSERT INTO migration_history
|
|
735
1058
|
(from_version, to_version, direction, status, notes)
|
|
@@ -743,7 +1066,11 @@ function applyCompatibleUpgrades(db, fromVersion = latestSchemaVersion(db), { le
|
|
|
743
1066
|
const semanticMigration = db.prepare(
|
|
744
1067
|
"SELECT 1 FROM migration_history WHERE to_version = '15.0' AND status = 'success' LIMIT 1",
|
|
745
1068
|
).get();
|
|
746
|
-
if (fromVersion &&
|
|
1069
|
+
if (fromVersion && ![
|
|
1070
|
+
SEMANTIC_SCHEMA_VERSION, DIALOGUE_SCHEMA_VERSION, GIT_AUTHORITY_SCHEMA_VERSION,
|
|
1071
|
+
ADAPTIVE_SCHEMA_VERSION, DECISION_COMPLETION_SCHEMA_VERSION,
|
|
1072
|
+
ARTIFACT_REGISTRY_SCHEMA_VERSION,
|
|
1073
|
+
].includes(fromVersion) && !semanticMigration) {
|
|
747
1074
|
db.prepare(
|
|
748
1075
|
`INSERT INTO migration_history
|
|
749
1076
|
(from_version, to_version, direction, status, notes)
|
|
@@ -757,7 +1084,10 @@ function applyCompatibleUpgrades(db, fromVersion = latestSchemaVersion(db), { le
|
|
|
757
1084
|
const dialogueMigration = db.prepare(
|
|
758
1085
|
"SELECT 1 FROM migration_history WHERE to_version = '16.0' AND status = 'success' LIMIT 1",
|
|
759
1086
|
).get();
|
|
760
|
-
if (fromVersion && ![
|
|
1087
|
+
if (fromVersion && ![
|
|
1088
|
+
DIALOGUE_SCHEMA_VERSION, GIT_AUTHORITY_SCHEMA_VERSION, ADAPTIVE_SCHEMA_VERSION,
|
|
1089
|
+
DECISION_COMPLETION_SCHEMA_VERSION, ARTIFACT_REGISTRY_SCHEMA_VERSION,
|
|
1090
|
+
].includes(fromVersion)
|
|
761
1091
|
&& !dialogueMigration) {
|
|
762
1092
|
db.prepare(
|
|
763
1093
|
`INSERT INTO migration_history
|
|
@@ -772,7 +1102,10 @@ function applyCompatibleUpgrades(db, fromVersion = latestSchemaVersion(db), { le
|
|
|
772
1102
|
const gitStateMigration = db.prepare(
|
|
773
1103
|
"SELECT 1 FROM migration_history WHERE to_version = '17.0' AND status = 'success' LIMIT 1",
|
|
774
1104
|
).get();
|
|
775
|
-
if (fromVersion && ![
|
|
1105
|
+
if (fromVersion && ![
|
|
1106
|
+
GIT_AUTHORITY_SCHEMA_VERSION, ADAPTIVE_SCHEMA_VERSION, DECISION_COMPLETION_SCHEMA_VERSION,
|
|
1107
|
+
ARTIFACT_REGISTRY_SCHEMA_VERSION,
|
|
1108
|
+
].includes(fromVersion)
|
|
776
1109
|
&& !gitStateMigration) {
|
|
777
1110
|
db.prepare(
|
|
778
1111
|
`INSERT INTO migration_history
|
|
@@ -788,26 +1121,65 @@ function applyCompatibleUpgrades(db, fromVersion = latestSchemaVersion(db), { le
|
|
|
788
1121
|
const adaptiveMigration = db.prepare(
|
|
789
1122
|
"SELECT 1 FROM migration_history WHERE to_version = '18.0' AND status = 'success' LIMIT 1",
|
|
790
1123
|
).get();
|
|
791
|
-
if (fromVersion &&
|
|
1124
|
+
if (fromVersion && ![
|
|
1125
|
+
ADAPTIVE_SCHEMA_VERSION, DECISION_COMPLETION_SCHEMA_VERSION,
|
|
1126
|
+
ARTIFACT_REGISTRY_SCHEMA_VERSION,
|
|
1127
|
+
].includes(fromVersion) && !adaptiveMigration) {
|
|
792
1128
|
db.prepare(
|
|
793
1129
|
`INSERT INTO migration_history
|
|
794
1130
|
(from_version, to_version, direction, status, notes)
|
|
795
1131
|
VALUES (?, ?, 'forward', 'success', ?)`,
|
|
796
1132
|
).run(
|
|
797
1133
|
fromVersion === GIT_AUTHORITY_SCHEMA_VERSION ? fromVersion : GIT_AUTHORITY_SCHEMA_VERSION,
|
|
798
|
-
|
|
1134
|
+
ADAPTIVE_SCHEMA_VERSION,
|
|
799
1135
|
adaptiveChanged
|
|
800
1136
|
? 'Migrate active rigid workflows to adaptive capability transitions and independent initialization'
|
|
801
1137
|
: 'Add adaptive capability transitions and independent initialization authority',
|
|
802
1138
|
);
|
|
803
1139
|
}
|
|
1140
|
+
const completionMigration = db.prepare(
|
|
1141
|
+
"SELECT 1 FROM migration_history WHERE to_version = '19.0' AND status = 'success' LIMIT 1",
|
|
1142
|
+
).get();
|
|
1143
|
+
if (fromVersion && ![
|
|
1144
|
+
DECISION_COMPLETION_SCHEMA_VERSION, ARTIFACT_REGISTRY_SCHEMA_VERSION,
|
|
1145
|
+
].includes(fromVersion) && !completionMigration) {
|
|
1146
|
+
db.prepare(
|
|
1147
|
+
`INSERT INTO migration_history
|
|
1148
|
+
(from_version, to_version, direction, status, notes)
|
|
1149
|
+
VALUES (?, ?, 'forward', 'success', ?)`,
|
|
1150
|
+
).run(
|
|
1151
|
+
ADAPTIVE_SCHEMA_VERSION,
|
|
1152
|
+
DECISION_COMPLETION_SCHEMA_VERSION,
|
|
1153
|
+
decisionCompletionChanged
|
|
1154
|
+
? 'Add non-ceremonial decision completion and migrate settled active threads'
|
|
1155
|
+
: 'Add non-ceremonial decision completion authority',
|
|
1156
|
+
);
|
|
1157
|
+
}
|
|
1158
|
+
const artifactRegistryMigration = db.prepare(
|
|
1159
|
+
"SELECT 1 FROM migration_history WHERE to_version = '20.0' AND status = 'success' LIMIT 1",
|
|
1160
|
+
).get();
|
|
1161
|
+
if (fromVersion && fromVersion !== ARTIFACT_REGISTRY_SCHEMA_VERSION
|
|
1162
|
+
&& !artifactRegistryMigration) {
|
|
1163
|
+
db.prepare(
|
|
1164
|
+
`INSERT INTO migration_history
|
|
1165
|
+
(from_version, to_version, direction, status, notes)
|
|
1166
|
+
VALUES (?, ?, 'forward', 'success', ?)`,
|
|
1167
|
+
).run(
|
|
1168
|
+
DECISION_COMPLETION_SCHEMA_VERSION,
|
|
1169
|
+
ARTIFACT_REGISTRY_SCHEMA_VERSION,
|
|
1170
|
+
artifactRegistryChanged
|
|
1171
|
+
? 'Migrate legacy change artifacts into the typed artifact registry and dependency graph'
|
|
1172
|
+
: 'Add the typed artifact registry, dependency graph, and orphan diagnostics',
|
|
1173
|
+
);
|
|
1174
|
+
}
|
|
804
1175
|
db.exec('CREATE INDEX IF NOT EXISTS tasks_change ON tasks(change_id) WHERE change_id IS NOT NULL');
|
|
805
1176
|
db.exec('CREATE INDEX IF NOT EXISTS events_change ON events(change_id, id)');
|
|
1177
|
+
ensureActiveSessionLeaseIndex(db);
|
|
806
1178
|
db.prepare(
|
|
807
1179
|
'INSERT OR IGNORE INTO schema_version (version, description) VALUES (?, ?)',
|
|
808
1180
|
).run(
|
|
809
1181
|
EXPECTED_VERSION,
|
|
810
|
-
'
|
|
1182
|
+
'Typed artifact registry, normalized dependency edges, and orphan diagnostics',
|
|
811
1183
|
);
|
|
812
1184
|
})();
|
|
813
1185
|
}
|
|
@@ -834,13 +1206,22 @@ function initStateDb(dbPath) {
|
|
|
834
1206
|
db = openStateDb(dbPath);
|
|
835
1207
|
const existing = new Set(tableNames(db));
|
|
836
1208
|
const fromVersion = latestSchemaVersion(db);
|
|
1209
|
+
assertNoDuplicateActiveArtifactPaths(db);
|
|
837
1210
|
const legacyState = existing.size > 0
|
|
838
1211
|
&& ![
|
|
839
1212
|
WORKFLOW_SCHEMA_VERSION, AUTHORITY_SCHEMA_VERSION, SEMANTIC_SCHEMA_VERSION,
|
|
840
|
-
DIALOGUE_SCHEMA_VERSION, GIT_AUTHORITY_SCHEMA_VERSION,
|
|
1213
|
+
DIALOGUE_SCHEMA_VERSION, GIT_AUTHORITY_SCHEMA_VERSION, ADAPTIVE_SCHEMA_VERSION,
|
|
1214
|
+
DECISION_COMPLETION_SCHEMA_VERSION, ARTIFACT_REGISTRY_SCHEMA_VERSION,
|
|
841
1215
|
].includes(fromVersion);
|
|
842
1216
|
backupPath = migrationBackup(db, dbPath, fromVersion, existing);
|
|
1217
|
+
// This must precede every ALTER, table rebuild, or index creation. Runtime
|
|
1218
|
+
// constraint upgrades rebuild sessions and would otherwise surface a
|
|
1219
|
+
// generic SQLITE_CONSTRAINT instead of the complete authority conflict.
|
|
1220
|
+
assertNoDuplicateActiveSessionLeases(db);
|
|
843
1221
|
applyCompatibleColumns(db);
|
|
1222
|
+
if (existing.has('artifacts')) {
|
|
1223
|
+
db.transaction(() => applyArtifactRegistryUpgrade(db))();
|
|
1224
|
+
}
|
|
844
1225
|
const missing = REQUIRED_TABLES.filter((t) => !existing.has(t));
|
|
845
1226
|
if (missing.length > 0) {
|
|
846
1227
|
applySchema(db);
|
|
@@ -884,4 +1265,7 @@ module.exports = {
|
|
|
884
1265
|
tableNames,
|
|
885
1266
|
runScript,
|
|
886
1267
|
MIGRATED_GAPS,
|
|
1268
|
+
ActiveSessionLeaseConflictError,
|
|
1269
|
+
activeSessionLeaseConflicts,
|
|
1270
|
+
assertNoDuplicateActiveSessionLeases,
|
|
887
1271
|
};
|