ultra-builder-pro-cli 0.20.0 → 0.22.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.
- package/.ultra-template/docs/research/README.md +26 -5
- package/.ultra-template/specs/discovery.md +1 -1
- package/.ultra-template/tasks/tasks.json +10 -3
- package/.ultra-template/templates/task-context.md +19 -0
- package/CHANGELOG.md +106 -0
- package/README.md +215 -41
- package/adapters/_shared/codex-assets.cjs +69 -11
- package/adapters/_shared/interaction-contract.cjs +36 -3
- 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 +43 -30
- package/bin/orchestrator.js +64 -24
- package/docs/AGENT-CONTEXT.md +89 -49
- package/docs/ARCHITECTURE.md +112 -45
- package/docs/ARTIFACT-AUTHORITY.md +274 -0
- package/docs/COMMIT-HASH-BACKFILL.md +33 -90
- package/docs/DECISIONS.md +14 -6
- package/docs/LEGACY-CLI-CROSSWALK.md +16 -6
- package/docs/PLUGIN-ISOLATION-CONTRACT.md +6 -4
- package/docs/ROADMAP.md +20 -6
- package/docs/RUNTIME-COMPAT-MATRIX.md +29 -16
- package/docs/STATE-DB-ACCESS-POLICY.md +43 -13
- package/docs/WORKFLOW-LIFECYCLE.md +105 -50
- package/hooks/README.md +10 -8
- package/hooks/active_task_context.py +105 -18
- 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 +1127 -0
- package/mcp-server/lib/baseline-workflow.cjs +69 -28
- 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 +142 -0
- package/mcp-server/lib/context-spine.cjs +511 -45
- package/mcp-server/lib/decision-dialogue.cjs +345 -3
- 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 +401 -11
- package/mcp-server/lib/git-bootstrap.cjs +458 -47
- package/mcp-server/lib/init-project.cjs +240 -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 +211 -42
- 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-authority.cjs +19 -3
- package/mcp-server/lib/state-db.cjs +263 -9
- package/mcp-server/lib/state-ops.cjs +254 -50
- package/mcp-server/lib/task-ledger.cjs +1342 -0
- package/mcp-server/lib/telemetry.cjs +12 -3
- package/mcp-server/lib/workflow-state.cjs +610 -58
- package/mcp-server/server.cjs +423 -42
- 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 +34 -8
- package/skills/ultra-deliver/SKILL.md +26 -11
- package/skills/ultra-dev/SKILL.md +52 -10
- package/skills/ultra-doctor/SKILL.md +16 -3
- package/skills/ultra-init/SKILL.md +22 -11
- package/skills/ultra-plan/SKILL.md +58 -18
- package/skills/ultra-research/SKILL.md +37 -35
- package/skills/ultra-review/SKILL.md +14 -8
- package/skills/ultra-status/SKILL.md +18 -9
- package/skills/ultra-test/SKILL.md +11 -5
- package/skills/ultra-think/SKILL.md +6 -2
- package/skills/ultra-think/references/decision-dialogue.md +17 -6
- package/skills/ultra-verify/SKILL.md +4 -1
- package/spec/cli-protocol.md +21 -7
- package/spec/mcp-tools.yaml +629 -44
- package/spec/schemas/change-delta.v1.schema.json +153 -0
- package/spec/schemas/context-file.v4.5.schema.json +1 -1
- 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 +51 -14
- package/spec/schemas/task-ledger.v1.schema.json +217 -0
- 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 +1000 -82
- package/ultra-tools/commands/session.cjs +34 -8
- package/ultra-tools/commands/status.cjs +32 -6
- package/ultra-tools/commands/system.cjs +129 -22
- package/.ultra-template/tasks/contexts/TEMPLATE.md +0 -19
- 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';
|
|
@@ -21,6 +21,7 @@ const DIALOGUE_SCHEMA_VERSION = '16.0';
|
|
|
21
21
|
const GIT_AUTHORITY_SCHEMA_VERSION = '17.0';
|
|
22
22
|
const ADAPTIVE_SCHEMA_VERSION = '18.0';
|
|
23
23
|
const DECISION_COMPLETION_SCHEMA_VERSION = '19.0';
|
|
24
|
+
const ARTIFACT_REGISTRY_SCHEMA_VERSION = '20.0';
|
|
24
25
|
|
|
25
26
|
const MIGRATED_GAPS = Object.freeze([{
|
|
26
27
|
id: 'legacy-rebaseline-required',
|
|
@@ -44,6 +45,7 @@ const REQUIRED_TABLES = Object.freeze([
|
|
|
44
45
|
'circuit_breaker',
|
|
45
46
|
'changes',
|
|
46
47
|
'artifacts',
|
|
48
|
+
'artifact_edges',
|
|
47
49
|
'context_snapshots',
|
|
48
50
|
'spec_learning_candidates',
|
|
49
51
|
'trace_links',
|
|
@@ -56,6 +58,93 @@ const REQUIRED_TABLES = Object.freeze([
|
|
|
56
58
|
'decision_items',
|
|
57
59
|
]);
|
|
58
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
|
+
|
|
59
148
|
function applyBaselineUpgrade(db, { legacyState = false } = {}) {
|
|
60
149
|
db.exec(`
|
|
61
150
|
CREATE TABLE IF NOT EXISTS baselines (
|
|
@@ -237,6 +326,7 @@ function tableNames(db) {
|
|
|
237
326
|
}
|
|
238
327
|
|
|
239
328
|
function applySchema(db) {
|
|
329
|
+
assertNoDuplicateActiveSessionLeases(db);
|
|
240
330
|
runScript(db, readSchemaSql());
|
|
241
331
|
}
|
|
242
332
|
|
|
@@ -330,7 +420,10 @@ function migrateAdaptiveWorkflowRuns(db, fromVersion = latestSchemaVersion(db))
|
|
|
330
420
|
const existing = db.prepare(
|
|
331
421
|
"SELECT 1 FROM migration_history WHERE to_version = '18.0' AND status = 'success' LIMIT 1",
|
|
332
422
|
).get();
|
|
333
|
-
if ([
|
|
423
|
+
if ([
|
|
424
|
+
ADAPTIVE_SCHEMA_VERSION, DECISION_COMPLETION_SCHEMA_VERSION,
|
|
425
|
+
ARTIFACT_REGISTRY_SCHEMA_VERSION,
|
|
426
|
+
].includes(fromVersion) || existing) {
|
|
334
427
|
return false;
|
|
335
428
|
}
|
|
336
429
|
|
|
@@ -623,6 +716,7 @@ function upgradeBaselineWorktreeConstraint(db, fromVersion = latestSchemaVersion
|
|
|
623
716
|
}
|
|
624
717
|
|
|
625
718
|
function upgradeRuntimeConstraints(db, fromVersion = latestSchemaVersion(db)) {
|
|
719
|
+
assertNoDuplicateActiveSessionLeases(db);
|
|
626
720
|
if (tableSupportsKimi(db, 'events') && tableSupportsKimi(db, 'sessions')) return false;
|
|
627
721
|
const previousVersion = fromVersion || 'unknown';
|
|
628
722
|
const foreignKeys = db.pragma('foreign_keys', { simple: true });
|
|
@@ -674,6 +768,8 @@ function upgradeRuntimeConstraints(db, fromVersion = latestSchemaVersion(db)) {
|
|
|
674
768
|
ALTER TABLE sessions_runtime_upgrade RENAME TO sessions;
|
|
675
769
|
CREATE INDEX sessions_active ON sessions(status, task_id);
|
|
676
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';
|
|
677
773
|
`);
|
|
678
774
|
db.prepare(
|
|
679
775
|
`INSERT INTO migration_history
|
|
@@ -695,7 +791,129 @@ function upgradeRuntimeConstraints(db, fromVersion = latestSchemaVersion(db)) {
|
|
|
695
791
|
return true;
|
|
696
792
|
}
|
|
697
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
|
+
|
|
698
915
|
function applyCompatibleUpgrades(db, fromVersion = latestSchemaVersion(db), { legacyState = false } = {}) {
|
|
916
|
+
assertNoDuplicateActiveSessionLeases(db);
|
|
699
917
|
const runtimeChanged = upgradeRuntimeConstraints(db, fromVersion);
|
|
700
918
|
if (tableNames(db).includes('baselines') && !baselineSupportsUnbornGit(db)) {
|
|
701
919
|
db.transaction(() => {
|
|
@@ -716,7 +934,7 @@ function applyCompatibleUpgrades(db, fromVersion = latestSchemaVersion(db), { le
|
|
|
716
934
|
CONTEXT_SCHEMA_VERSION, BASELINE_SCHEMA_VERSION, ADOPTION_SCHEMA_VERSION,
|
|
717
935
|
WORKFLOW_SCHEMA_VERSION, AUTHORITY_SCHEMA_VERSION, SEMANTIC_SCHEMA_VERSION,
|
|
718
936
|
DIALOGUE_SCHEMA_VERSION, GIT_AUTHORITY_SCHEMA_VERSION, ADAPTIVE_SCHEMA_VERSION,
|
|
719
|
-
DECISION_COMPLETION_SCHEMA_VERSION,
|
|
937
|
+
DECISION_COMPLETION_SCHEMA_VERSION, ARTIFACT_REGISTRY_SCHEMA_VERSION,
|
|
720
938
|
].includes(fromVersion)
|
|
721
939
|
&& !contextMigration,
|
|
722
940
|
);
|
|
@@ -735,6 +953,7 @@ function applyCompatibleUpgrades(db, fromVersion = latestSchemaVersion(db), { le
|
|
|
735
953
|
applyBaselineColumns(db);
|
|
736
954
|
applyChangeColumns(db);
|
|
737
955
|
applySemanticAuthorityColumns(db);
|
|
956
|
+
const artifactRegistryChanged = applyArtifactRegistryUpgrade(db);
|
|
738
957
|
const baselineMigration = db.prepare(
|
|
739
958
|
"SELECT 1 FROM migration_history WHERE to_version = '11.0' AND status = 'success' LIMIT 1",
|
|
740
959
|
).get();
|
|
@@ -742,7 +961,7 @@ function applyCompatibleUpgrades(db, fromVersion = latestSchemaVersion(db), { le
|
|
|
742
961
|
BASELINE_SCHEMA_VERSION, ADOPTION_SCHEMA_VERSION, WORKFLOW_SCHEMA_VERSION,
|
|
743
962
|
AUTHORITY_SCHEMA_VERSION, SEMANTIC_SCHEMA_VERSION, DIALOGUE_SCHEMA_VERSION,
|
|
744
963
|
GIT_AUTHORITY_SCHEMA_VERSION, ADAPTIVE_SCHEMA_VERSION,
|
|
745
|
-
DECISION_COMPLETION_SCHEMA_VERSION,
|
|
964
|
+
DECISION_COMPLETION_SCHEMA_VERSION, ARTIFACT_REGISTRY_SCHEMA_VERSION,
|
|
746
965
|
].includes(fromVersion)
|
|
747
966
|
&& !baselineMigration) {
|
|
748
967
|
db.prepare(
|
|
@@ -814,7 +1033,7 @@ function applyCompatibleUpgrades(db, fromVersion = latestSchemaVersion(db), { le
|
|
|
814
1033
|
if (fromVersion && ![
|
|
815
1034
|
WORKFLOW_SCHEMA_VERSION, AUTHORITY_SCHEMA_VERSION, SEMANTIC_SCHEMA_VERSION,
|
|
816
1035
|
DIALOGUE_SCHEMA_VERSION, GIT_AUTHORITY_SCHEMA_VERSION, ADAPTIVE_SCHEMA_VERSION,
|
|
817
|
-
DECISION_COMPLETION_SCHEMA_VERSION,
|
|
1036
|
+
DECISION_COMPLETION_SCHEMA_VERSION, ARTIFACT_REGISTRY_SCHEMA_VERSION,
|
|
818
1037
|
].includes(fromVersion) && !workflowMigration) {
|
|
819
1038
|
db.prepare(
|
|
820
1039
|
`INSERT INTO migration_history
|
|
@@ -832,6 +1051,7 @@ function applyCompatibleUpgrades(db, fromVersion = latestSchemaVersion(db), { le
|
|
|
832
1051
|
if (fromVersion && ![
|
|
833
1052
|
AUTHORITY_SCHEMA_VERSION, SEMANTIC_SCHEMA_VERSION, DIALOGUE_SCHEMA_VERSION,
|
|
834
1053
|
GIT_AUTHORITY_SCHEMA_VERSION, ADAPTIVE_SCHEMA_VERSION, DECISION_COMPLETION_SCHEMA_VERSION,
|
|
1054
|
+
ARTIFACT_REGISTRY_SCHEMA_VERSION,
|
|
835
1055
|
].includes(fromVersion) && !authorityMigration) {
|
|
836
1056
|
db.prepare(
|
|
837
1057
|
`INSERT INTO migration_history
|
|
@@ -849,6 +1069,7 @@ function applyCompatibleUpgrades(db, fromVersion = latestSchemaVersion(db), { le
|
|
|
849
1069
|
if (fromVersion && ![
|
|
850
1070
|
SEMANTIC_SCHEMA_VERSION, DIALOGUE_SCHEMA_VERSION, GIT_AUTHORITY_SCHEMA_VERSION,
|
|
851
1071
|
ADAPTIVE_SCHEMA_VERSION, DECISION_COMPLETION_SCHEMA_VERSION,
|
|
1072
|
+
ARTIFACT_REGISTRY_SCHEMA_VERSION,
|
|
852
1073
|
].includes(fromVersion) && !semanticMigration) {
|
|
853
1074
|
db.prepare(
|
|
854
1075
|
`INSERT INTO migration_history
|
|
@@ -865,7 +1086,7 @@ function applyCompatibleUpgrades(db, fromVersion = latestSchemaVersion(db), { le
|
|
|
865
1086
|
).get();
|
|
866
1087
|
if (fromVersion && ![
|
|
867
1088
|
DIALOGUE_SCHEMA_VERSION, GIT_AUTHORITY_SCHEMA_VERSION, ADAPTIVE_SCHEMA_VERSION,
|
|
868
|
-
DECISION_COMPLETION_SCHEMA_VERSION,
|
|
1089
|
+
DECISION_COMPLETION_SCHEMA_VERSION, ARTIFACT_REGISTRY_SCHEMA_VERSION,
|
|
869
1090
|
].includes(fromVersion)
|
|
870
1091
|
&& !dialogueMigration) {
|
|
871
1092
|
db.prepare(
|
|
@@ -883,6 +1104,7 @@ function applyCompatibleUpgrades(db, fromVersion = latestSchemaVersion(db), { le
|
|
|
883
1104
|
).get();
|
|
884
1105
|
if (fromVersion && ![
|
|
885
1106
|
GIT_AUTHORITY_SCHEMA_VERSION, ADAPTIVE_SCHEMA_VERSION, DECISION_COMPLETION_SCHEMA_VERSION,
|
|
1107
|
+
ARTIFACT_REGISTRY_SCHEMA_VERSION,
|
|
886
1108
|
].includes(fromVersion)
|
|
887
1109
|
&& !gitStateMigration) {
|
|
888
1110
|
db.prepare(
|
|
@@ -901,6 +1123,7 @@ function applyCompatibleUpgrades(db, fromVersion = latestSchemaVersion(db), { le
|
|
|
901
1123
|
).get();
|
|
902
1124
|
if (fromVersion && ![
|
|
903
1125
|
ADAPTIVE_SCHEMA_VERSION, DECISION_COMPLETION_SCHEMA_VERSION,
|
|
1126
|
+
ARTIFACT_REGISTRY_SCHEMA_VERSION,
|
|
904
1127
|
].includes(fromVersion) && !adaptiveMigration) {
|
|
905
1128
|
db.prepare(
|
|
906
1129
|
`INSERT INTO migration_history
|
|
@@ -917,7 +1140,9 @@ function applyCompatibleUpgrades(db, fromVersion = latestSchemaVersion(db), { le
|
|
|
917
1140
|
const completionMigration = db.prepare(
|
|
918
1141
|
"SELECT 1 FROM migration_history WHERE to_version = '19.0' AND status = 'success' LIMIT 1",
|
|
919
1142
|
).get();
|
|
920
|
-
if (fromVersion &&
|
|
1143
|
+
if (fromVersion && ![
|
|
1144
|
+
DECISION_COMPLETION_SCHEMA_VERSION, ARTIFACT_REGISTRY_SCHEMA_VERSION,
|
|
1145
|
+
].includes(fromVersion) && !completionMigration) {
|
|
921
1146
|
db.prepare(
|
|
922
1147
|
`INSERT INTO migration_history
|
|
923
1148
|
(from_version, to_version, direction, status, notes)
|
|
@@ -930,13 +1155,31 @@ function applyCompatibleUpgrades(db, fromVersion = latestSchemaVersion(db), { le
|
|
|
930
1155
|
: 'Add non-ceremonial decision completion authority',
|
|
931
1156
|
);
|
|
932
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
|
+
}
|
|
933
1175
|
db.exec('CREATE INDEX IF NOT EXISTS tasks_change ON tasks(change_id) WHERE change_id IS NOT NULL');
|
|
934
1176
|
db.exec('CREATE INDEX IF NOT EXISTS events_change ON events(change_id, id)');
|
|
1177
|
+
ensureActiveSessionLeaseIndex(db);
|
|
935
1178
|
db.prepare(
|
|
936
1179
|
'INSERT OR IGNORE INTO schema_version (version, description) VALUES (?, ?)',
|
|
937
1180
|
).run(
|
|
938
1181
|
EXPECTED_VERSION,
|
|
939
|
-
'
|
|
1182
|
+
'Typed artifact registry, normalized dependency edges, and orphan diagnostics',
|
|
940
1183
|
);
|
|
941
1184
|
})();
|
|
942
1185
|
}
|
|
@@ -963,14 +1206,22 @@ function initStateDb(dbPath) {
|
|
|
963
1206
|
db = openStateDb(dbPath);
|
|
964
1207
|
const existing = new Set(tableNames(db));
|
|
965
1208
|
const fromVersion = latestSchemaVersion(db);
|
|
1209
|
+
assertNoDuplicateActiveArtifactPaths(db);
|
|
966
1210
|
const legacyState = existing.size > 0
|
|
967
1211
|
&& ![
|
|
968
1212
|
WORKFLOW_SCHEMA_VERSION, AUTHORITY_SCHEMA_VERSION, SEMANTIC_SCHEMA_VERSION,
|
|
969
1213
|
DIALOGUE_SCHEMA_VERSION, GIT_AUTHORITY_SCHEMA_VERSION, ADAPTIVE_SCHEMA_VERSION,
|
|
970
|
-
DECISION_COMPLETION_SCHEMA_VERSION,
|
|
1214
|
+
DECISION_COMPLETION_SCHEMA_VERSION, ARTIFACT_REGISTRY_SCHEMA_VERSION,
|
|
971
1215
|
].includes(fromVersion);
|
|
972
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);
|
|
973
1221
|
applyCompatibleColumns(db);
|
|
1222
|
+
if (existing.has('artifacts')) {
|
|
1223
|
+
db.transaction(() => applyArtifactRegistryUpgrade(db))();
|
|
1224
|
+
}
|
|
974
1225
|
const missing = REQUIRED_TABLES.filter((t) => !existing.has(t));
|
|
975
1226
|
if (missing.length > 0) {
|
|
976
1227
|
applySchema(db);
|
|
@@ -1014,4 +1265,7 @@ module.exports = {
|
|
|
1014
1265
|
tableNames,
|
|
1015
1266
|
runScript,
|
|
1016
1267
|
MIGRATED_GAPS,
|
|
1268
|
+
ActiveSessionLeaseConflictError,
|
|
1269
|
+
activeSessionLeaseConflicts,
|
|
1270
|
+
assertNoDuplicateActiveSessionLeases,
|
|
1017
1271
|
};
|