@substrate-ai/core 0.20.95 → 0.20.97
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/dist/persistence/core-schema.d.ts +34 -0
- package/dist/persistence/core-schema.d.ts.map +1 -0
- package/dist/persistence/core-schema.js +237 -0
- package/dist/persistence/core-schema.js.map +1 -0
- package/dist/persistence/index.d.ts +8 -1
- package/dist/persistence/index.d.ts.map +1 -1
- package/dist/persistence/index.js +11 -1
- package/dist/persistence/index.js.map +1 -1
- package/dist/persistence/monitor-schema.d.ts +17 -0
- package/dist/persistence/monitor-schema.d.ts.map +1 -0
- package/dist/persistence/monitor-schema.js +72 -0
- package/dist/persistence/monitor-schema.js.map +1 -0
- package/dist/persistence/pipeline-schema.d.ts +15 -0
- package/dist/persistence/pipeline-schema.d.ts.map +1 -0
- package/dist/persistence/pipeline-schema.js +160 -0
- package/dist/persistence/pipeline-schema.js.map +1 -0
- package/dist/persistence/repo-map-schema.d.ts +17 -0
- package/dist/persistence/repo-map-schema.d.ts.map +1 -0
- package/dist/persistence/repo-map-schema.js +45 -0
- package/dist/persistence/repo-map-schema.js.map +1 -0
- package/dist/persistence/schema.d.ts +26 -14
- package/dist/persistence/schema.d.ts.map +1 -1
- package/dist/persistence/schema.js +59 -724
- package/dist/persistence/schema.js.map +1 -1
- package/dist/persistence/state-schema.d.ts +20 -0
- package/dist/persistence/state-schema.d.ts.map +1 -0
- package/dist/persistence/state-schema.js +130 -0
- package/dist/persistence/state-schema.js.map +1 -0
- package/dist/persistence/telemetry-schema.d.ts +16 -0
- package/dist/persistence/telemetry-schema.d.ts.map +1 -0
- package/dist/persistence/telemetry-schema.js +129 -0
- package/dist/persistence/telemetry-schema.js.map +1 -0
- package/dist/persistence/work-graph-schema.d.ts +25 -0
- package/dist/persistence/work-graph-schema.d.ts.map +1 -0
- package/dist/persistence/work-graph-schema.js +82 -0
- package/dist/persistence/work-graph-schema.js.map +1 -0
- package/package.json +1 -1
|
@@ -1,736 +1,71 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Composition-root for persistence schema initialization.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* `initSchema(adapter)` calls the per-subsystem `initXxxSchema` functions in
|
|
5
|
+
* the correct order (tables before views; dependencies before dependents).
|
|
6
6
|
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
7
|
+
* Subsystem ownership (Ship 5, 2026-05 — per-subsystem schema split):
|
|
8
|
+
* - core-schema.ts — sessions, tasks, plans, execution log, cost entries, signals, schema_migrations + ready_tasks/session_cost_summary views
|
|
9
|
+
* - pipeline-schema.ts — pipeline_runs, decisions, requirements, constraints, artifacts, token_usage, run_metrics, story_metrics
|
|
10
|
+
* - monitor-schema.ts — task_metrics, performance_aggregates, routing_recommendations (main DB; monitor.db is separate)
|
|
11
|
+
* - state-schema.ts — stories, contracts, metrics, dispatch_log, build_results, review_verdicts, _schema_version (legacy)
|
|
12
|
+
* - repo-map-schema.ts — repo_map_symbols, repo_map_meta
|
|
13
|
+
* - telemetry-schema.ts — turn_analysis, efficiency_scores, recommendations, category_stats, consumer_stats
|
|
14
|
+
* - work-graph-schema.ts — wg_stories, story_dependencies, ready_stories view
|
|
9
15
|
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
* -
|
|
15
|
-
*
|
|
16
|
-
* -
|
|
17
|
-
*
|
|
18
|
-
* - Telemetry: turn_analysis, efficiency_scores, recommendations, category_stats, consumer_stats
|
|
16
|
+
* All per-subsystem inits are idempotent (CREATE TABLE IF NOT EXISTS, INSERT
|
|
17
|
+
* IGNORE seeds, try/catch ALTER ADD COLUMN migrations).
|
|
18
|
+
*
|
|
19
|
+
* Composition root order matters for views:
|
|
20
|
+
* - `ready_tasks` and `session_cost_summary` (initCoreViews) reference
|
|
21
|
+
* `tasks` + `sessions` — must run AFTER initCoreSchema.
|
|
22
|
+
* - `ready_stories` (in initWorkGraphSchema) references wg_stories +
|
|
23
|
+
* story_dependencies, both defined inside the same function — self-contained.
|
|
19
24
|
*/
|
|
25
|
+
import { initCoreSchema, initCoreViews } from './core-schema.js';
|
|
26
|
+
import { initPipelineSchema } from './pipeline-schema.js';
|
|
27
|
+
import { initMonitorSchema } from './monitor-schema.js';
|
|
28
|
+
import { initStateSchema } from './state-schema.js';
|
|
29
|
+
import { initRepoMapSchema } from './repo-map-schema.js';
|
|
30
|
+
import { initTelemetrySchema } from './telemetry-schema.js';
|
|
31
|
+
import { initWorkGraphSchema } from './work-graph-schema.js';
|
|
20
32
|
/**
|
|
21
33
|
* Initialize all persistence tables on the given adapter.
|
|
22
34
|
* Idempotent — safe to call multiple times.
|
|
23
35
|
*/
|
|
24
36
|
export async function initSchema(adapter) {
|
|
25
|
-
//
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
)
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
session_id VARCHAR(255) NOT NULL,
|
|
48
|
-
name TEXT NOT NULL,
|
|
49
|
-
description TEXT,
|
|
50
|
-
prompt TEXT NOT NULL,
|
|
51
|
-
status VARCHAR(32) NOT NULL DEFAULT 'pending',
|
|
52
|
-
agent VARCHAR(128),
|
|
53
|
-
model TEXT,
|
|
54
|
-
billing_mode VARCHAR(32),
|
|
55
|
-
worktree_path TEXT,
|
|
56
|
-
worktree_branch TEXT,
|
|
57
|
-
worktree_cleaned_at TEXT,
|
|
58
|
-
worker_id TEXT,
|
|
59
|
-
budget_usd DOUBLE,
|
|
60
|
-
cost_usd DOUBLE NOT NULL DEFAULT 0.0,
|
|
61
|
-
input_tokens INTEGER NOT NULL DEFAULT 0,
|
|
62
|
-
output_tokens INTEGER NOT NULL DEFAULT 0,
|
|
63
|
-
result TEXT,
|
|
64
|
-
error TEXT,
|
|
65
|
-
exit_code INTEGER,
|
|
66
|
-
retry_count INTEGER NOT NULL DEFAULT 0,
|
|
67
|
-
max_retries INTEGER NOT NULL DEFAULT 2,
|
|
68
|
-
timeout_ms INTEGER,
|
|
69
|
-
task_type TEXT,
|
|
70
|
-
metadata TEXT,
|
|
71
|
-
merge_status TEXT,
|
|
72
|
-
merged_files TEXT,
|
|
73
|
-
conflict_files TEXT,
|
|
74
|
-
budget_exceeded INTEGER NOT NULL DEFAULT 0,
|
|
75
|
-
started_at TEXT,
|
|
76
|
-
completed_at TEXT,
|
|
77
|
-
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
78
|
-
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
|
|
79
|
-
)
|
|
80
|
-
`);
|
|
81
|
-
await adapter.exec('CREATE INDEX IF NOT EXISTS idx_tasks_session ON tasks(session_id)');
|
|
82
|
-
await adapter.exec('CREATE INDEX IF NOT EXISTS idx_tasks_status ON tasks(status)');
|
|
83
|
-
await adapter.exec('CREATE INDEX IF NOT EXISTS idx_tasks_agent ON tasks(agent)');
|
|
84
|
-
await adapter.exec('CREATE INDEX IF NOT EXISTS idx_tasks_session_status ON tasks(session_id, status)');
|
|
85
|
-
await adapter.exec(`
|
|
86
|
-
CREATE TABLE IF NOT EXISTS task_dependencies (
|
|
87
|
-
task_id VARCHAR(255) NOT NULL,
|
|
88
|
-
depends_on VARCHAR(255) NOT NULL,
|
|
89
|
-
PRIMARY KEY (task_id, depends_on),
|
|
90
|
-
CHECK (task_id != depends_on)
|
|
91
|
-
)
|
|
92
|
-
`);
|
|
93
|
-
await adapter.exec('CREATE INDEX IF NOT EXISTS idx_deps_depends_on ON task_dependencies(depends_on)');
|
|
94
|
-
await adapter.exec(`
|
|
95
|
-
CREATE TABLE IF NOT EXISTS execution_log (
|
|
96
|
-
id INTEGER PRIMARY KEY AUTO_INCREMENT,
|
|
97
|
-
session_id VARCHAR(255) NOT NULL,
|
|
98
|
-
task_id VARCHAR(255),
|
|
99
|
-
event VARCHAR(128) NOT NULL,
|
|
100
|
-
old_status VARCHAR(32),
|
|
101
|
-
new_status VARCHAR(32),
|
|
102
|
-
agent VARCHAR(128),
|
|
103
|
-
cost_usd DOUBLE,
|
|
104
|
-
data TEXT,
|
|
105
|
-
timestamp DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
|
|
106
|
-
)
|
|
107
|
-
`);
|
|
108
|
-
await adapter.exec('CREATE INDEX IF NOT EXISTS idx_log_session ON execution_log(session_id)');
|
|
109
|
-
await adapter.exec('CREATE INDEX IF NOT EXISTS idx_log_task ON execution_log(task_id)');
|
|
110
|
-
await adapter.exec('CREATE INDEX IF NOT EXISTS idx_log_event ON execution_log(event)');
|
|
111
|
-
await adapter.exec('CREATE INDEX IF NOT EXISTS idx_log_timestamp ON execution_log(timestamp)');
|
|
112
|
-
// -- Cost entries (migration 001 + 002) -----------------------------------
|
|
113
|
-
await adapter.exec(`
|
|
114
|
-
CREATE TABLE IF NOT EXISTS cost_entries (
|
|
115
|
-
id INTEGER PRIMARY KEY AUTO_INCREMENT,
|
|
116
|
-
session_id VARCHAR(255) NOT NULL,
|
|
117
|
-
task_id VARCHAR(255),
|
|
118
|
-
agent VARCHAR(128) NOT NULL,
|
|
119
|
-
billing_mode VARCHAR(32) NOT NULL,
|
|
120
|
-
category VARCHAR(64) NOT NULL DEFAULT 'execution',
|
|
121
|
-
provider VARCHAR(64) NOT NULL DEFAULT 'unknown',
|
|
122
|
-
input_tokens INTEGER NOT NULL DEFAULT 0,
|
|
123
|
-
output_tokens INTEGER NOT NULL DEFAULT 0,
|
|
124
|
-
estimated_cost DOUBLE NOT NULL DEFAULT 0.0,
|
|
125
|
-
actual_cost DOUBLE,
|
|
126
|
-
savings_usd DOUBLE NOT NULL DEFAULT 0.0,
|
|
127
|
-
model TEXT,
|
|
128
|
-
timestamp DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
|
|
129
|
-
)
|
|
130
|
-
`);
|
|
131
|
-
await adapter.exec('CREATE INDEX IF NOT EXISTS idx_cost_session ON cost_entries(session_id)');
|
|
132
|
-
await adapter.exec('CREATE INDEX IF NOT EXISTS idx_cost_task ON cost_entries(task_id)');
|
|
133
|
-
await adapter.exec('CREATE INDEX IF NOT EXISTS idx_cost_category ON cost_entries(category)');
|
|
134
|
-
await adapter.exec('CREATE INDEX IF NOT EXISTS idx_cost_entries_session_task ON cost_entries(session_id, task_id)');
|
|
135
|
-
await adapter.exec('CREATE INDEX IF NOT EXISTS idx_cost_entries_provider ON cost_entries(provider)');
|
|
136
|
-
await adapter.exec('CREATE INDEX IF NOT EXISTS idx_cost_session_agent ON cost_entries(session_id, agent)');
|
|
137
|
-
await adapter.exec('CREATE INDEX IF NOT EXISTS idx_cost_agent ON cost_entries(agent)');
|
|
138
|
-
// -- Session signals (migration 004) --------------------------------------
|
|
139
|
-
await adapter.exec(`
|
|
140
|
-
CREATE TABLE IF NOT EXISTS session_signals (
|
|
141
|
-
id INTEGER PRIMARY KEY AUTO_INCREMENT,
|
|
142
|
-
session_id VARCHAR(255) NOT NULL,
|
|
143
|
-
\`signal\` VARCHAR(16) NOT NULL CHECK(\`signal\` IN ('pause', 'resume', 'cancel')),
|
|
144
|
-
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
145
|
-
processed_at TEXT
|
|
146
|
-
)
|
|
147
|
-
`);
|
|
148
|
-
// -- Plans (migration 005 + 006) ------------------------------------------
|
|
149
|
-
await adapter.exec(`
|
|
150
|
-
CREATE TABLE IF NOT EXISTS plans (
|
|
151
|
-
id VARCHAR(255) PRIMARY KEY,
|
|
152
|
-
description TEXT NOT NULL,
|
|
153
|
-
task_count INTEGER NOT NULL DEFAULT 0,
|
|
154
|
-
estimated_cost_usd DOUBLE NOT NULL DEFAULT 0.0,
|
|
155
|
-
planning_agent VARCHAR(128) NOT NULL,
|
|
156
|
-
plan_yaml TEXT NOT NULL,
|
|
157
|
-
status VARCHAR(32) NOT NULL DEFAULT 'draft',
|
|
158
|
-
current_version INTEGER NOT NULL DEFAULT 1,
|
|
159
|
-
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
160
|
-
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
|
|
161
|
-
)
|
|
162
|
-
`);
|
|
163
|
-
await adapter.exec('CREATE INDEX IF NOT EXISTS idx_plans_status ON plans(status)');
|
|
164
|
-
await adapter.exec('CREATE INDEX IF NOT EXISTS idx_plans_created ON plans(created_at)');
|
|
165
|
-
await adapter.exec(`
|
|
166
|
-
CREATE TABLE IF NOT EXISTS plan_versions (
|
|
167
|
-
plan_id VARCHAR(255) NOT NULL,
|
|
168
|
-
version INTEGER NOT NULL,
|
|
169
|
-
task_graph_yaml TEXT NOT NULL,
|
|
170
|
-
feedback_used TEXT,
|
|
171
|
-
planning_cost_usd DOUBLE NOT NULL DEFAULT 0.0,
|
|
172
|
-
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
173
|
-
PRIMARY KEY (plan_id, version)
|
|
174
|
-
)
|
|
175
|
-
`);
|
|
176
|
-
await adapter.exec('CREATE INDEX IF NOT EXISTS idx_plan_versions_plan_id ON plan_versions(plan_id)');
|
|
177
|
-
// -- Pipeline runs + decisions (migration 007 + 008 final shapes) ---------
|
|
178
|
-
await adapter.exec(`
|
|
179
|
-
CREATE TABLE IF NOT EXISTS pipeline_runs (
|
|
180
|
-
id VARCHAR(255) PRIMARY KEY,
|
|
181
|
-
methodology VARCHAR(128) NOT NULL,
|
|
182
|
-
current_phase VARCHAR(64),
|
|
183
|
-
status VARCHAR(32) NOT NULL DEFAULT 'running'
|
|
184
|
-
CHECK(status IN ('running','paused','completed','failed','stopped')),
|
|
185
|
-
config_json TEXT,
|
|
186
|
-
token_usage_json TEXT,
|
|
187
|
-
parent_run_id VARCHAR(255),
|
|
188
|
-
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
189
|
-
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
|
|
190
|
-
)
|
|
191
|
-
`);
|
|
192
|
-
await adapter.exec('CREATE INDEX IF NOT EXISTS idx_pipeline_runs_status ON pipeline_runs(status)');
|
|
193
|
-
await adapter.exec('CREATE INDEX IF NOT EXISTS idx_pipeline_runs_parent_run_id ON pipeline_runs(parent_run_id)');
|
|
194
|
-
await adapter.exec(`
|
|
195
|
-
CREATE TABLE IF NOT EXISTS decisions (
|
|
196
|
-
id VARCHAR(255) PRIMARY KEY,
|
|
197
|
-
pipeline_run_id VARCHAR(255),
|
|
198
|
-
phase VARCHAR(64) NOT NULL,
|
|
199
|
-
category VARCHAR(64) NOT NULL,
|
|
200
|
-
\`key\` VARCHAR(255) NOT NULL,
|
|
201
|
-
value TEXT NOT NULL,
|
|
202
|
-
rationale TEXT,
|
|
203
|
-
superseded_by VARCHAR(255),
|
|
204
|
-
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
205
|
-
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
|
|
206
|
-
)
|
|
207
|
-
`);
|
|
208
|
-
await adapter.exec('CREATE INDEX IF NOT EXISTS idx_decisions_phase ON decisions(phase)');
|
|
209
|
-
await adapter.exec('CREATE INDEX IF NOT EXISTS idx_decisions_key ON decisions(phase, `key`)');
|
|
210
|
-
await adapter.exec('CREATE INDEX IF NOT EXISTS idx_decisions_superseded_by ON decisions(superseded_by)');
|
|
211
|
-
await adapter.exec(`
|
|
212
|
-
CREATE TABLE IF NOT EXISTS requirements (
|
|
213
|
-
id VARCHAR(255) PRIMARY KEY,
|
|
214
|
-
pipeline_run_id VARCHAR(255),
|
|
215
|
-
source VARCHAR(128) NOT NULL,
|
|
216
|
-
type VARCHAR(32) NOT NULL CHECK(type IN ('functional','non_functional','constraint')),
|
|
217
|
-
description TEXT NOT NULL,
|
|
218
|
-
priority VARCHAR(16) NOT NULL CHECK(priority IN ('must','should','could','wont')),
|
|
219
|
-
status VARCHAR(32) NOT NULL DEFAULT 'active',
|
|
220
|
-
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
|
|
221
|
-
)
|
|
222
|
-
`);
|
|
223
|
-
await adapter.exec('CREATE INDEX IF NOT EXISTS idx_requirements_type ON requirements(type)');
|
|
224
|
-
await adapter.exec('CREATE INDEX IF NOT EXISTS idx_requirements_status ON requirements(status)');
|
|
225
|
-
await adapter.exec(`
|
|
226
|
-
CREATE TABLE IF NOT EXISTS constraints (
|
|
227
|
-
id VARCHAR(255) PRIMARY KEY,
|
|
228
|
-
pipeline_run_id VARCHAR(255),
|
|
229
|
-
category VARCHAR(64) NOT NULL,
|
|
230
|
-
description TEXT NOT NULL,
|
|
231
|
-
source VARCHAR(128) NOT NULL,
|
|
232
|
-
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
|
|
233
|
-
)
|
|
234
|
-
`);
|
|
235
|
-
await adapter.exec(`
|
|
236
|
-
CREATE TABLE IF NOT EXISTS artifacts (
|
|
237
|
-
id VARCHAR(255) PRIMARY KEY,
|
|
238
|
-
pipeline_run_id VARCHAR(255),
|
|
239
|
-
phase VARCHAR(64) NOT NULL,
|
|
240
|
-
type VARCHAR(128) NOT NULL,
|
|
241
|
-
path TEXT NOT NULL,
|
|
242
|
-
content_hash TEXT,
|
|
243
|
-
summary TEXT,
|
|
244
|
-
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
|
|
245
|
-
)
|
|
246
|
-
`);
|
|
247
|
-
await adapter.exec('CREATE INDEX IF NOT EXISTS idx_artifacts_phase ON artifacts(phase)');
|
|
248
|
-
await adapter.exec(`
|
|
249
|
-
CREATE TABLE IF NOT EXISTS token_usage (
|
|
250
|
-
id INTEGER PRIMARY KEY AUTO_INCREMENT,
|
|
251
|
-
pipeline_run_id VARCHAR(255),
|
|
252
|
-
phase VARCHAR(64) NOT NULL,
|
|
253
|
-
agent VARCHAR(128) NOT NULL,
|
|
254
|
-
input_tokens INTEGER NOT NULL DEFAULT 0,
|
|
255
|
-
output_tokens INTEGER NOT NULL DEFAULT 0,
|
|
256
|
-
cost_usd DOUBLE NOT NULL DEFAULT 0.0,
|
|
257
|
-
metadata TEXT,
|
|
258
|
-
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
|
|
259
|
-
)
|
|
260
|
-
`);
|
|
261
|
-
await adapter.exec('CREATE INDEX IF NOT EXISTS idx_token_usage_run ON token_usage(pipeline_run_id)');
|
|
262
|
-
// -- Run metrics (migration 010) ------------------------------------------
|
|
263
|
-
await adapter.exec(`
|
|
264
|
-
CREATE TABLE IF NOT EXISTS run_metrics (
|
|
265
|
-
run_id VARCHAR(255) PRIMARY KEY,
|
|
266
|
-
methodology VARCHAR(128) NOT NULL,
|
|
267
|
-
status VARCHAR(32) NOT NULL DEFAULT 'running',
|
|
268
|
-
started_at TEXT NOT NULL,
|
|
269
|
-
completed_at TEXT,
|
|
270
|
-
wall_clock_seconds DOUBLE DEFAULT 0,
|
|
271
|
-
total_input_tokens INTEGER DEFAULT 0,
|
|
272
|
-
total_output_tokens INTEGER DEFAULT 0,
|
|
273
|
-
total_cost_usd DOUBLE DEFAULT 0,
|
|
274
|
-
stories_attempted INTEGER DEFAULT 0,
|
|
275
|
-
stories_succeeded INTEGER DEFAULT 0,
|
|
276
|
-
stories_failed INTEGER DEFAULT 0,
|
|
277
|
-
stories_escalated INTEGER DEFAULT 0,
|
|
278
|
-
total_review_cycles INTEGER DEFAULT 0,
|
|
279
|
-
total_dispatches INTEGER DEFAULT 0,
|
|
280
|
-
concurrency_setting INTEGER DEFAULT 1,
|
|
281
|
-
max_concurrent_actual INTEGER DEFAULT 1,
|
|
282
|
-
restarts INTEGER DEFAULT 0,
|
|
283
|
-
is_baseline INTEGER DEFAULT 0,
|
|
284
|
-
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
|
|
285
|
-
)
|
|
286
|
-
`);
|
|
287
|
-
await adapter.exec(`
|
|
288
|
-
CREATE TABLE IF NOT EXISTS story_metrics (
|
|
289
|
-
id INTEGER PRIMARY KEY AUTO_INCREMENT,
|
|
290
|
-
run_id VARCHAR(255) NOT NULL,
|
|
291
|
-
story_key VARCHAR(255) NOT NULL,
|
|
292
|
-
result VARCHAR(32) NOT NULL DEFAULT 'pending',
|
|
293
|
-
phase_durations_json TEXT,
|
|
294
|
-
started_at TEXT,
|
|
295
|
-
completed_at TEXT,
|
|
296
|
-
wall_clock_seconds DOUBLE DEFAULT 0,
|
|
297
|
-
input_tokens INTEGER DEFAULT 0,
|
|
298
|
-
output_tokens INTEGER DEFAULT 0,
|
|
299
|
-
cost_usd DOUBLE DEFAULT 0,
|
|
300
|
-
review_cycles INTEGER DEFAULT 0,
|
|
301
|
-
dispatches INTEGER DEFAULT 0,
|
|
302
|
-
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
303
|
-
UNIQUE(run_id, story_key)
|
|
304
|
-
)
|
|
305
|
-
`);
|
|
306
|
-
// -- story_metrics agent/model columns (mesh telemetry enrichment) ---------
|
|
307
|
-
for (const col of ['primary_agent_id VARCHAR(64)', 'primary_model VARCHAR(128)', 'dispatch_agents_json TEXT']) {
|
|
308
|
-
try {
|
|
309
|
-
await adapter.exec(`ALTER TABLE story_metrics ADD COLUMN ${col}`);
|
|
310
|
-
}
|
|
311
|
-
catch { /* column already exists */ }
|
|
312
|
-
}
|
|
313
|
-
// -- Monitor tables (from 001-monitor-schema) -----------------------------
|
|
314
|
-
await adapter.exec(`
|
|
315
|
-
CREATE TABLE IF NOT EXISTS task_metrics (
|
|
316
|
-
task_id VARCHAR(255) NOT NULL,
|
|
317
|
-
agent VARCHAR(128) NOT NULL,
|
|
318
|
-
task_type VARCHAR(128) NOT NULL,
|
|
319
|
-
outcome VARCHAR(16) NOT NULL CHECK(outcome IN ('success', 'failure')),
|
|
320
|
-
failure_reason TEXT,
|
|
321
|
-
input_tokens INTEGER NOT NULL DEFAULT 0,
|
|
322
|
-
output_tokens INTEGER NOT NULL DEFAULT 0,
|
|
323
|
-
duration_ms INTEGER NOT NULL DEFAULT 0,
|
|
324
|
-
cost DOUBLE NOT NULL DEFAULT 0.0,
|
|
325
|
-
estimated_cost DOUBLE NOT NULL DEFAULT 0.0,
|
|
326
|
-
billing_mode VARCHAR(32) NOT NULL DEFAULT 'api',
|
|
327
|
-
retries INTEGER NOT NULL DEFAULT 0,
|
|
328
|
-
recorded_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
329
|
-
PRIMARY KEY (task_id, recorded_at)
|
|
330
|
-
)
|
|
331
|
-
`);
|
|
332
|
-
await adapter.exec('CREATE INDEX IF NOT EXISTS idx_tm_agent ON task_metrics(agent)');
|
|
333
|
-
await adapter.exec('CREATE INDEX IF NOT EXISTS idx_tm_task_type ON task_metrics(task_type)');
|
|
334
|
-
await adapter.exec('CREATE INDEX IF NOT EXISTS idx_tm_recorded_at ON task_metrics(recorded_at)');
|
|
335
|
-
await adapter.exec('CREATE INDEX IF NOT EXISTS idx_tm_agent_type ON task_metrics(agent, task_type)');
|
|
336
|
-
await adapter.exec(`
|
|
337
|
-
CREATE TABLE IF NOT EXISTS performance_aggregates (
|
|
338
|
-
agent VARCHAR(255) NOT NULL,
|
|
339
|
-
task_type VARCHAR(255) NOT NULL,
|
|
340
|
-
total_tasks INTEGER NOT NULL DEFAULT 0,
|
|
341
|
-
successful_tasks INTEGER NOT NULL DEFAULT 0,
|
|
342
|
-
failed_tasks INTEGER NOT NULL DEFAULT 0,
|
|
343
|
-
total_input_tokens INTEGER NOT NULL DEFAULT 0,
|
|
344
|
-
total_output_tokens INTEGER NOT NULL DEFAULT 0,
|
|
345
|
-
total_duration_ms INTEGER NOT NULL DEFAULT 0,
|
|
346
|
-
total_cost DOUBLE NOT NULL DEFAULT 0.0,
|
|
347
|
-
total_retries INTEGER NOT NULL DEFAULT 0,
|
|
348
|
-
last_updated DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
349
|
-
PRIMARY KEY (agent, task_type)
|
|
350
|
-
)
|
|
351
|
-
`);
|
|
352
|
-
await adapter.exec(`
|
|
353
|
-
CREATE TABLE IF NOT EXISTS routing_recommendations (
|
|
354
|
-
id INTEGER PRIMARY KEY AUTO_INCREMENT,
|
|
355
|
-
task_type VARCHAR(128) NOT NULL,
|
|
356
|
-
current_agent VARCHAR(128) NOT NULL,
|
|
357
|
-
recommended_agent VARCHAR(128) NOT NULL,
|
|
358
|
-
reason TEXT,
|
|
359
|
-
confidence DOUBLE NOT NULL DEFAULT 0.0,
|
|
360
|
-
supporting_data TEXT,
|
|
361
|
-
generated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
362
|
-
expires_at TEXT
|
|
363
|
-
)
|
|
364
|
-
`);
|
|
365
|
-
// -- Telemetry tables (migration 011) -------------------------------------
|
|
366
|
-
await adapter.exec(`
|
|
367
|
-
CREATE TABLE IF NOT EXISTS turn_analysis (
|
|
368
|
-
story_key VARCHAR(64) NOT NULL,
|
|
369
|
-
span_id VARCHAR(128) NOT NULL,
|
|
370
|
-
turn_number INTEGER NOT NULL,
|
|
371
|
-
name VARCHAR(255) NOT NULL DEFAULT '',
|
|
372
|
-
timestamp BIGINT NOT NULL DEFAULT 0,
|
|
373
|
-
source VARCHAR(32) NOT NULL DEFAULT '',
|
|
374
|
-
model VARCHAR(64),
|
|
375
|
-
input_tokens INTEGER NOT NULL DEFAULT 0,
|
|
376
|
-
output_tokens INTEGER NOT NULL DEFAULT 0,
|
|
377
|
-
cache_read_tokens INTEGER NOT NULL DEFAULT 0,
|
|
378
|
-
fresh_tokens INTEGER NOT NULL DEFAULT 0,
|
|
379
|
-
cache_hit_rate DOUBLE NOT NULL DEFAULT 0,
|
|
380
|
-
cost_usd DOUBLE NOT NULL DEFAULT 0,
|
|
381
|
-
duration_ms INTEGER NOT NULL DEFAULT 0,
|
|
382
|
-
context_size INTEGER NOT NULL DEFAULT 0,
|
|
383
|
-
context_delta INTEGER NOT NULL DEFAULT 0,
|
|
384
|
-
tool_name VARCHAR(128),
|
|
385
|
-
is_context_spike BOOLEAN NOT NULL DEFAULT 0,
|
|
386
|
-
child_spans_json TEXT NOT NULL DEFAULT '[]',
|
|
387
|
-
task_type VARCHAR(64),
|
|
388
|
-
phase VARCHAR(64),
|
|
389
|
-
dispatch_id VARCHAR(64),
|
|
390
|
-
PRIMARY KEY (story_key, span_id)
|
|
391
|
-
)
|
|
392
|
-
`);
|
|
393
|
-
await adapter.exec('CREATE INDEX IF NOT EXISTS idx_turn_analysis_story ON turn_analysis (story_key, turn_number)');
|
|
394
|
-
// Migration: add dispatch context columns for existing repos (Story 30-1)
|
|
395
|
-
for (const col of ['task_type', 'phase', 'dispatch_id']) {
|
|
396
|
-
try {
|
|
397
|
-
await adapter.exec(`ALTER TABLE turn_analysis ADD COLUMN ${col} VARCHAR(64)`);
|
|
398
|
-
}
|
|
399
|
-
catch { /* column already exists */ }
|
|
400
|
-
}
|
|
401
|
-
await adapter.exec(`
|
|
402
|
-
CREATE TABLE IF NOT EXISTS efficiency_scores (
|
|
403
|
-
story_key VARCHAR(64) NOT NULL,
|
|
404
|
-
timestamp BIGINT NOT NULL,
|
|
405
|
-
composite_score INTEGER NOT NULL DEFAULT 0,
|
|
406
|
-
cache_hit_sub_score DOUBLE NOT NULL DEFAULT 0,
|
|
407
|
-
io_ratio_sub_score DOUBLE NOT NULL DEFAULT 0,
|
|
408
|
-
context_management_sub_score DOUBLE NOT NULL DEFAULT 0,
|
|
409
|
-
avg_cache_hit_rate DOUBLE NOT NULL DEFAULT 0,
|
|
410
|
-
avg_io_ratio DOUBLE NOT NULL DEFAULT 0,
|
|
411
|
-
context_spike_count INTEGER NOT NULL DEFAULT 0,
|
|
412
|
-
total_turns INTEGER NOT NULL DEFAULT 0,
|
|
413
|
-
per_model_json TEXT NOT NULL DEFAULT '[]',
|
|
414
|
-
per_source_json TEXT NOT NULL DEFAULT '[]',
|
|
415
|
-
dispatch_id TEXT,
|
|
416
|
-
task_type TEXT,
|
|
417
|
-
phase TEXT,
|
|
418
|
-
PRIMARY KEY (story_key, timestamp)
|
|
419
|
-
)
|
|
420
|
-
`);
|
|
421
|
-
// Migration: add dispatch context columns for existing repos (Story 30-3)
|
|
422
|
-
for (const col of ['dispatch_id', 'task_type', 'phase']) {
|
|
423
|
-
try {
|
|
424
|
-
await adapter.exec(`ALTER TABLE efficiency_scores ADD COLUMN ${col} TEXT`);
|
|
425
|
-
}
|
|
426
|
-
catch { /* column already exists */ }
|
|
427
|
-
}
|
|
428
|
-
await adapter.exec(`
|
|
429
|
-
CREATE TABLE IF NOT EXISTS recommendations (
|
|
430
|
-
id VARCHAR(16) NOT NULL,
|
|
431
|
-
story_key VARCHAR(64) NOT NULL,
|
|
432
|
-
sprint_id VARCHAR(64),
|
|
433
|
-
rule_id VARCHAR(64) NOT NULL,
|
|
434
|
-
severity VARCHAR(16) NOT NULL,
|
|
435
|
-
title TEXT NOT NULL,
|
|
436
|
-
description TEXT NOT NULL,
|
|
437
|
-
potential_savings_tokens INTEGER,
|
|
438
|
-
potential_savings_usd DOUBLE,
|
|
439
|
-
action_target TEXT,
|
|
440
|
-
generated_at VARCHAR(32) NOT NULL,
|
|
441
|
-
PRIMARY KEY (id)
|
|
442
|
-
)
|
|
443
|
-
`);
|
|
444
|
-
await adapter.exec(`
|
|
445
|
-
CREATE TABLE IF NOT EXISTS category_stats (
|
|
446
|
-
story_key VARCHAR(100) NOT NULL,
|
|
447
|
-
category VARCHAR(30) NOT NULL,
|
|
448
|
-
total_tokens BIGINT NOT NULL DEFAULT 0,
|
|
449
|
-
percentage DECIMAL(6,3) NOT NULL DEFAULT 0,
|
|
450
|
-
event_count INTEGER NOT NULL DEFAULT 0,
|
|
451
|
-
avg_tokens_per_event DECIMAL(12,2) NOT NULL DEFAULT 0,
|
|
452
|
-
trend VARCHAR(10) NOT NULL DEFAULT 'stable',
|
|
453
|
-
PRIMARY KEY (story_key, category)
|
|
454
|
-
)
|
|
455
|
-
`);
|
|
456
|
-
await adapter.exec(`
|
|
457
|
-
CREATE TABLE IF NOT EXISTS consumer_stats (
|
|
458
|
-
story_key VARCHAR(100) NOT NULL,
|
|
459
|
-
consumer_key VARCHAR(300) NOT NULL,
|
|
460
|
-
category VARCHAR(30) NOT NULL,
|
|
461
|
-
total_tokens BIGINT NOT NULL DEFAULT 0,
|
|
462
|
-
percentage DECIMAL(6,3) NOT NULL DEFAULT 0,
|
|
463
|
-
event_count INTEGER NOT NULL DEFAULT 0,
|
|
464
|
-
top_invocations_json TEXT,
|
|
465
|
-
PRIMARY KEY (story_key, consumer_key)
|
|
466
|
-
)
|
|
467
|
-
`);
|
|
468
|
-
// -- Work-graph tables (Epic 31-1) ----------------------------------------
|
|
469
|
-
// Pre-v0.20.90, these lived ONLY in src/modules/state/schema.sql which is
|
|
470
|
-
// applied at `substrate init --dolt` time. That left projects whose
|
|
471
|
-
// .substrate/state/.dolt was initialized BEFORE Epic 31-1 (~2026-04)
|
|
472
|
-
// permanently missing wg_stories + story_dependencies — substrate run's
|
|
473
|
-
// initSchema didn't know about them, so the auto-migration never fired.
|
|
474
|
-
// Empirical discovery 2026-05-11: ynab (initialized 2026-03-10 at
|
|
475
|
-
// _schema_version=5) was missing all three of these despite the operator
|
|
476
|
-
// having upgraded substrate many times. Moving the DDL here makes
|
|
477
|
-
// initSchema the single contract for "what tables substrate needs."
|
|
478
|
-
await adapter.exec(`
|
|
479
|
-
CREATE TABLE IF NOT EXISTS wg_stories (
|
|
480
|
-
story_key VARCHAR(20) NOT NULL,
|
|
481
|
-
epic VARCHAR(20) NOT NULL,
|
|
482
|
-
title VARCHAR(255),
|
|
483
|
-
status VARCHAR(30) NOT NULL DEFAULT 'planned',
|
|
484
|
-
spec_path VARCHAR(500),
|
|
485
|
-
created_at DATETIME,
|
|
486
|
-
updated_at DATETIME,
|
|
487
|
-
completed_at DATETIME,
|
|
488
|
-
PRIMARY KEY (story_key)
|
|
489
|
-
)
|
|
490
|
-
`);
|
|
491
|
-
await adapter.exec('CREATE INDEX IF NOT EXISTS idx_wg_stories_epic ON wg_stories (epic)');
|
|
492
|
-
await adapter.exec(`
|
|
493
|
-
CREATE TABLE IF NOT EXISTS story_dependencies (
|
|
494
|
-
story_key VARCHAR(50) NOT NULL,
|
|
495
|
-
depends_on VARCHAR(50) NOT NULL,
|
|
496
|
-
dependency_type VARCHAR(50) NOT NULL DEFAULT 'blocks',
|
|
497
|
-
source VARCHAR(50) NOT NULL DEFAULT 'explicit',
|
|
498
|
-
created_at DATETIME,
|
|
499
|
-
PRIMARY KEY (story_key, depends_on)
|
|
500
|
-
)
|
|
501
|
-
`);
|
|
502
|
-
// Migration: story_dependencies.created_at was added in v0.12.0. For
|
|
503
|
-
// projects whose schema predates that, ensure the column exists. Idempotent
|
|
504
|
-
// try/catch — silently no-ops if the column is already present.
|
|
505
|
-
try {
|
|
506
|
-
await adapter.exec('ALTER TABLE story_dependencies ADD COLUMN created_at DATETIME');
|
|
507
|
-
}
|
|
508
|
-
catch { /* column already exists */ }
|
|
509
|
-
// -- Views ----------------------------------------------------------------
|
|
510
|
-
// NOTE: Views use JOINs and aggregation. They work with Dolt
|
|
511
|
-
// but NOT with InMemoryDatabaseAdapter. For InMemory backend, views are
|
|
512
|
-
// skipped silently (CREATE VIEW is an unknown statement to InMemory).
|
|
513
|
-
// ready_stories view (Epic 31-1) — stories ready to dispatch (planned/ready
|
|
514
|
-
// status AND all blocking dependencies are complete). InMemoryDatabaseAdapter
|
|
515
|
-
// explicitly no-ops CREATE VIEW (memory-adapter.ts:120-123), so this works
|
|
516
|
-
// on both backends without a try/catch wrapper — and a wrapper would hide
|
|
517
|
-
// genuine Dolt-side errors. Uses `CREATE VIEW IF NOT EXISTS` rather than
|
|
518
|
-
// `CREATE OR REPLACE` to match the existing ready_tasks pattern and avoid
|
|
519
|
-
// re-running the view definition on every initSchema call.
|
|
520
|
-
await adapter.exec(`
|
|
521
|
-
CREATE VIEW IF NOT EXISTS ready_stories AS
|
|
522
|
-
SELECT s.* FROM wg_stories s
|
|
523
|
-
WHERE s.status IN ('planned', 'ready')
|
|
524
|
-
AND NOT EXISTS (
|
|
525
|
-
SELECT 1 FROM story_dependencies d
|
|
526
|
-
JOIN wg_stories dep ON dep.story_key = d.depends_on
|
|
527
|
-
WHERE d.story_key = s.story_key
|
|
528
|
-
AND d.dependency_type = 'blocks'
|
|
529
|
-
AND dep.status <> 'complete'
|
|
530
|
-
)
|
|
531
|
-
`);
|
|
532
|
-
await adapter.exec(`
|
|
533
|
-
CREATE VIEW IF NOT EXISTS ready_tasks AS
|
|
534
|
-
SELECT t.* FROM tasks t
|
|
535
|
-
WHERE t.status = 'pending'
|
|
536
|
-
AND NOT EXISTS (
|
|
537
|
-
SELECT 1 FROM task_dependencies td
|
|
538
|
-
JOIN tasks dep ON dep.id = td.depends_on
|
|
539
|
-
WHERE td.task_id = t.id
|
|
540
|
-
AND dep.status NOT IN ('completed', 'cancelled')
|
|
541
|
-
)
|
|
542
|
-
`);
|
|
543
|
-
await adapter.exec(`
|
|
544
|
-
CREATE VIEW IF NOT EXISTS session_cost_summary AS
|
|
545
|
-
SELECT
|
|
546
|
-
s.id AS session_id,
|
|
547
|
-
s.name AS session_name,
|
|
548
|
-
COUNT(DISTINCT t.id) AS total_tasks,
|
|
549
|
-
SUM(CASE WHEN t.status = 'completed' THEN 1 ELSE 0 END) AS completed_tasks,
|
|
550
|
-
SUM(CASE WHEN t.status = 'failed' THEN 1 ELSE 0 END) AS failed_tasks,
|
|
551
|
-
SUM(CASE WHEN t.status = 'running' THEN 1 ELSE 0 END) AS running_tasks,
|
|
552
|
-
COALESCE(SUM(t.cost_usd), 0) AS total_cost_usd,
|
|
553
|
-
SUM(CASE WHEN t.billing_mode = 'subscription' THEN t.cost_usd ELSE 0 END) AS subscription_cost_usd,
|
|
554
|
-
SUM(CASE WHEN t.billing_mode = 'api' THEN t.cost_usd ELSE 0 END) AS api_cost_usd,
|
|
555
|
-
s.planning_cost_usd
|
|
556
|
-
FROM sessions s
|
|
557
|
-
LEFT JOIN tasks t ON t.session_id = s.id
|
|
558
|
-
GROUP BY s.id
|
|
559
|
-
`);
|
|
560
|
-
// -- Schema migration tracking table (for future use) --------------------
|
|
561
|
-
await adapter.exec(`
|
|
562
|
-
CREATE TABLE IF NOT EXISTS schema_migrations (
|
|
563
|
-
version INTEGER PRIMARY KEY,
|
|
564
|
-
name TEXT NOT NULL,
|
|
565
|
-
applied_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
|
|
566
|
-
)
|
|
567
|
-
`);
|
|
568
|
-
// -- Legacy state tables (ported from schema.sql in Ship 3, 2026-05) ------
|
|
569
|
-
// These were previously created only at `substrate init --dolt` time via
|
|
570
|
-
// schema.sql. Ship 3 moved them into initSchema so initSchema is the single
|
|
571
|
-
// contract for "what tables substrate needs," and substrate init --dolt
|
|
572
|
-
// no longer applies a separate DDL file. The Ship 2 regression gate
|
|
573
|
-
// (test/persistence/full-init-integration.test.ts) enforces that this
|
|
574
|
-
// set + the rest of initSchema produces the same table union schema.sql did.
|
|
575
|
-
//
|
|
576
|
-
// Functional status of these tables: stories/contracts/metrics/dispatch_log/
|
|
577
|
-
// build_results/review_verdicts had ZERO rows in every audited production
|
|
578
|
-
// project (ynab, quant). They survive because Ship 7 will decide their
|
|
579
|
-
// fate; deleting them outright would break operator commands that still
|
|
580
|
-
// expect them and risk an inconsistent post-init schema for existing repos.
|
|
581
|
-
await adapter.exec(`
|
|
582
|
-
CREATE TABLE IF NOT EXISTS stories (
|
|
583
|
-
story_key VARCHAR(100) NOT NULL,
|
|
584
|
-
sprint VARCHAR(50),
|
|
585
|
-
status VARCHAR(30) NOT NULL DEFAULT 'PENDING',
|
|
586
|
-
phase VARCHAR(30) NOT NULL DEFAULT 'PENDING',
|
|
587
|
-
ac_results JSON,
|
|
588
|
-
error_message TEXT,
|
|
589
|
-
created_at DATETIME,
|
|
590
|
-
updated_at DATETIME,
|
|
591
|
-
completed_at DATETIME,
|
|
592
|
-
PRIMARY KEY (story_key)
|
|
593
|
-
)
|
|
594
|
-
`);
|
|
595
|
-
await adapter.exec(`
|
|
596
|
-
CREATE TABLE IF NOT EXISTS contracts (
|
|
597
|
-
story_key VARCHAR(100) NOT NULL,
|
|
598
|
-
name VARCHAR(200) NOT NULL,
|
|
599
|
-
direction VARCHAR(20) NOT NULL,
|
|
600
|
-
schema_path VARCHAR(500),
|
|
601
|
-
transport VARCHAR(200),
|
|
602
|
-
recorded_at DATETIME,
|
|
603
|
-
PRIMARY KEY (story_key, name, direction)
|
|
604
|
-
)
|
|
605
|
-
`);
|
|
606
|
-
await adapter.exec(`
|
|
607
|
-
CREATE TABLE IF NOT EXISTS metrics (
|
|
608
|
-
story_key VARCHAR(100) NOT NULL,
|
|
609
|
-
task_type VARCHAR(100) NOT NULL,
|
|
610
|
-
recorded_at DATETIME NOT NULL,
|
|
611
|
-
model VARCHAR(100),
|
|
612
|
-
tokens_in BIGINT NOT NULL DEFAULT 0,
|
|
613
|
-
tokens_out BIGINT NOT NULL DEFAULT 0,
|
|
614
|
-
cache_read_tokens BIGINT NOT NULL DEFAULT 0,
|
|
615
|
-
cost_usd DECIMAL(10,6) NOT NULL DEFAULT 0,
|
|
616
|
-
wall_clock_ms BIGINT NOT NULL DEFAULT 0,
|
|
617
|
-
review_cycles INT NOT NULL DEFAULT 0,
|
|
618
|
-
stall_count INT NOT NULL DEFAULT 0,
|
|
619
|
-
result VARCHAR(30),
|
|
620
|
-
PRIMARY KEY (story_key, task_type, recorded_at)
|
|
621
|
-
)
|
|
622
|
-
`);
|
|
623
|
-
await adapter.exec(`
|
|
624
|
-
CREATE TABLE IF NOT EXISTS dispatch_log (
|
|
625
|
-
story_key VARCHAR(100) NOT NULL,
|
|
626
|
-
dispatched_at DATETIME NOT NULL,
|
|
627
|
-
branch VARCHAR(200),
|
|
628
|
-
worker_id VARCHAR(100),
|
|
629
|
-
result VARCHAR(30),
|
|
630
|
-
PRIMARY KEY (story_key, dispatched_at)
|
|
631
|
-
)
|
|
632
|
-
`);
|
|
633
|
-
await adapter.exec(`
|
|
634
|
-
CREATE TABLE IF NOT EXISTS build_results (
|
|
635
|
-
story_key VARCHAR(100) NOT NULL,
|
|
636
|
-
timestamp DATETIME NOT NULL,
|
|
637
|
-
command VARCHAR(500),
|
|
638
|
-
exit_code INT,
|
|
639
|
-
stdout_hash VARCHAR(64),
|
|
640
|
-
PRIMARY KEY (story_key, timestamp)
|
|
641
|
-
)
|
|
642
|
-
`);
|
|
643
|
-
await adapter.exec(`
|
|
644
|
-
CREATE TABLE IF NOT EXISTS review_verdicts (
|
|
645
|
-
story_key VARCHAR(100) NOT NULL,
|
|
646
|
-
timestamp DATETIME NOT NULL,
|
|
647
|
-
verdict VARCHAR(30),
|
|
648
|
-
issues_count INT NOT NULL DEFAULT 0,
|
|
649
|
-
notes TEXT,
|
|
650
|
-
PRIMARY KEY (story_key, timestamp)
|
|
651
|
-
)
|
|
652
|
-
`);
|
|
653
|
-
await adapter.exec(`
|
|
654
|
-
CREATE TABLE IF NOT EXISTS _schema_version (
|
|
655
|
-
version INT NOT NULL,
|
|
656
|
-
applied_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
657
|
-
description VARCHAR(500),
|
|
658
|
-
PRIMARY KEY (version)
|
|
659
|
-
)
|
|
660
|
-
`);
|
|
661
|
-
// _schema_version seed rows — preserved verbatim from schema.sql for
|
|
662
|
-
// backward-compat with operators inspecting the version table. Ship 7
|
|
663
|
-
// will decide whether to keep the table or delete it.
|
|
664
|
-
try {
|
|
665
|
-
await adapter.exec(`INSERT IGNORE INTO _schema_version (version, description) VALUES (1, 'Initial substrate state schema')`);
|
|
666
|
-
}
|
|
667
|
-
catch { /* InMemory adapter does not support INSERT IGNORE */ }
|
|
668
|
-
try {
|
|
669
|
-
await adapter.exec(`INSERT IGNORE INTO _schema_version (version, description) VALUES (2, 'Add turn_analysis table (Epic 27-4)')`);
|
|
670
|
-
}
|
|
671
|
-
catch { /* same */ }
|
|
672
|
-
try {
|
|
673
|
-
await adapter.exec(`INSERT IGNORE INTO _schema_version (version, description) VALUES (3, 'Add category_stats and consumer_stats tables (Epic 27-5)')`);
|
|
674
|
-
}
|
|
675
|
-
catch { /* same */ }
|
|
676
|
-
try {
|
|
677
|
-
await adapter.exec(`INSERT IGNORE INTO _schema_version (version, description) VALUES (4, 'Add recommendations table (Epic 27-7)')`);
|
|
678
|
-
}
|
|
679
|
-
catch { /* same */ }
|
|
680
|
-
try {
|
|
681
|
-
await adapter.exec(`INSERT IGNORE INTO _schema_version (version, description) VALUES (5, 'Add repo_map_symbols and repo_map_meta tables (Epic 28-2)')`);
|
|
682
|
-
}
|
|
683
|
-
catch { /* same */ }
|
|
684
|
-
try {
|
|
685
|
-
await adapter.exec(`INSERT IGNORE INTO _schema_version (version, description) VALUES (6, 'Add dependencies JSON column to repo_map_symbols (Epic 28-3)')`);
|
|
686
|
-
}
|
|
687
|
-
catch { /* same */ }
|
|
688
|
-
try {
|
|
689
|
-
await adapter.exec(`INSERT IGNORE INTO _schema_version (version, description) VALUES (7, 'Add wg_stories, story_dependencies tables and ready_stories view (Epic 31-1)')`);
|
|
690
|
-
}
|
|
691
|
-
catch { /* same */ }
|
|
692
|
-
try {
|
|
693
|
-
await adapter.exec(`INSERT IGNORE INTO _schema_version (version, description) VALUES (8, 'Add task_type, phase, dispatch_id columns to turn_analysis (Story 30-1)')`);
|
|
694
|
-
}
|
|
695
|
-
catch { /* same */ }
|
|
696
|
-
try {
|
|
697
|
-
await adapter.exec(`INSERT IGNORE INTO _schema_version (version, description) VALUES (9, 'Add dispatch_id, task_type, phase columns to efficiency_scores (Story 30-3)')`);
|
|
698
|
-
}
|
|
699
|
-
catch { /* same */ }
|
|
700
|
-
// repo_map_symbols + repo_map_meta — actively used by src/modules/repo-map/storage.ts
|
|
701
|
-
await adapter.exec(`
|
|
702
|
-
CREATE TABLE IF NOT EXISTS repo_map_symbols (
|
|
703
|
-
id BIGINT AUTO_INCREMENT NOT NULL,
|
|
704
|
-
file_path VARCHAR(1000) NOT NULL,
|
|
705
|
-
symbol_name VARCHAR(500) NOT NULL,
|
|
706
|
-
symbol_kind VARCHAR(20) NOT NULL,
|
|
707
|
-
signature TEXT,
|
|
708
|
-
line_number INT NOT NULL DEFAULT 0,
|
|
709
|
-
exported TINYINT(1) NOT NULL DEFAULT 0,
|
|
710
|
-
file_hash VARCHAR(64) NOT NULL,
|
|
711
|
-
dependencies JSON,
|
|
712
|
-
PRIMARY KEY (id)
|
|
713
|
-
)
|
|
714
|
-
`);
|
|
715
|
-
await adapter.exec('CREATE INDEX IF NOT EXISTS idx_repo_map_symbols_file ON repo_map_symbols (file_path)');
|
|
716
|
-
await adapter.exec('CREATE INDEX IF NOT EXISTS idx_repo_map_symbols_kind ON repo_map_symbols (symbol_kind)');
|
|
717
|
-
await adapter.exec(`
|
|
718
|
-
CREATE TABLE IF NOT EXISTS repo_map_meta (
|
|
719
|
-
id INT NOT NULL DEFAULT 1,
|
|
720
|
-
commit_sha VARCHAR(64),
|
|
721
|
-
updated_at DATETIME,
|
|
722
|
-
file_count INT NOT NULL DEFAULT 0,
|
|
723
|
-
PRIMARY KEY (id)
|
|
724
|
-
)
|
|
725
|
-
`);
|
|
726
|
-
// -- Telemetry indexes (ported from schema.sql in Ship 3) ----------------
|
|
727
|
-
// These indexes were declared in schema.sql but not in the original initSchema.
|
|
728
|
-
// The Ship 2 regression gate doesn't check for individual indexes by name,
|
|
729
|
-
// but read-heavy operator commands (`substrate metrics`) depend on them
|
|
730
|
-
// for query performance against story_key columns.
|
|
731
|
-
await adapter.exec('CREATE INDEX IF NOT EXISTS idx_efficiency_story ON efficiency_scores (story_key, timestamp DESC)');
|
|
732
|
-
await adapter.exec('CREATE INDEX IF NOT EXISTS idx_recommendations_story ON recommendations (story_key, severity)');
|
|
733
|
-
await adapter.exec('CREATE INDEX IF NOT EXISTS idx_category_stats_story ON category_stats (story_key, total_tokens)');
|
|
734
|
-
await adapter.exec('CREATE INDEX IF NOT EXISTS idx_consumer_stats_story ON consumer_stats (story_key, total_tokens)');
|
|
37
|
+
// 1. Core tables (sessions, tasks, plans, ...) — must be first so the
|
|
38
|
+
// views below can reference them.
|
|
39
|
+
await initCoreSchema(adapter);
|
|
40
|
+
// 2. Pipeline state (pipeline_runs, decisions, requirements, ...).
|
|
41
|
+
await initPipelineSchema(adapter);
|
|
42
|
+
// 3. Monitor tables on the main DB (task_metrics, performance_aggregates,
|
|
43
|
+
// routing_recommendations). `.substrate/monitor.db` is a separate DB
|
|
44
|
+
// managed independently by monitor-database.ts.
|
|
45
|
+
await initMonitorSchema(adapter);
|
|
46
|
+
// 4. Legacy state tables (Ship 1 excised the corresponding writes;
|
|
47
|
+
// Ship 7 will decide their final fate). Kept for now to preserve
|
|
48
|
+
// backward-compat with operator commands that still expect them.
|
|
49
|
+
await initStateSchema(adapter);
|
|
50
|
+
// 5. Repo-map tables (used by src/modules/repo-map/storage.ts).
|
|
51
|
+
await initRepoMapSchema(adapter);
|
|
52
|
+
// 6. Telemetry tables (turn_analysis, efficiency_scores, ...).
|
|
53
|
+
await initTelemetrySchema(adapter);
|
|
54
|
+
// 7. Work-graph tables + ready_stories view (Epic 31-1).
|
|
55
|
+
await initWorkGraphSchema(adapter);
|
|
56
|
+
// 8. Core views (ready_tasks, session_cost_summary) — must run LAST so
|
|
57
|
+
// sessions/tasks/task_dependencies all exist.
|
|
58
|
+
await initCoreViews(adapter);
|
|
735
59
|
}
|
|
60
|
+
// Re-export the per-subsystem init functions so consumers can call individual
|
|
61
|
+
// subsystems if they don't need the full schema. Operator CLI commands that
|
|
62
|
+
// only touch one subsystem (e.g. `substrate epic-status` only needs the
|
|
63
|
+
// work-graph tables) can import the narrow init function directly.
|
|
64
|
+
export { initCoreSchema, initCoreViews } from './core-schema.js';
|
|
65
|
+
export { initPipelineSchema } from './pipeline-schema.js';
|
|
66
|
+
export { initMonitorSchema } from './monitor-schema.js';
|
|
67
|
+
export { initStateSchema } from './state-schema.js';
|
|
68
|
+
export { initRepoMapSchema } from './repo-map-schema.js';
|
|
69
|
+
export { initTelemetrySchema } from './telemetry-schema.js';
|
|
70
|
+
export { initWorkGraphSchema } from './work-graph-schema.js';
|
|
736
71
|
//# sourceMappingURL=schema.js.map
|