gitswarm 0.0.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.
@@ -0,0 +1,569 @@
1
+ /**
2
+ * SQLite schema for standalone federation.
3
+ *
4
+ * This mirrors the subset of the PostgreSQL schema required for local
5
+ * multi-agent coordination. The full web app schema adds GitHub-specific
6
+ * tables, OAuth, WebSocket tracking, etc. that are not needed locally.
7
+ *
8
+ * v1: Base schema (agents, repos, patches, tasks, council, stages, activity)
9
+ * v2: git-cascade integration (stream_id refs, merge_mode, buffer fields, drop patches table)
10
+ */
11
+
12
+ /** v1 schema — initial tables. */
13
+ export const schemaV1 = `
14
+ -- ────────────────────────────────────────────────────────────────
15
+ -- Core: agents
16
+ -- ────────────────────────────────────────────────────────────────
17
+
18
+ CREATE TABLE IF NOT EXISTS agents (
19
+ id TEXT PRIMARY KEY DEFAULT (lower(hex(randomblob(16)))),
20
+ name TEXT UNIQUE NOT NULL,
21
+ description TEXT,
22
+ api_key_hash TEXT UNIQUE,
23
+ karma INTEGER DEFAULT 0,
24
+ status TEXT DEFAULT 'active' CHECK (status IN ('active','suspended','inactive')),
25
+ avatar_url TEXT,
26
+ metadata TEXT DEFAULT '{}',
27
+ created_at TEXT DEFAULT (datetime('now')),
28
+ updated_at TEXT DEFAULT (datetime('now'))
29
+ );
30
+
31
+ -- ────────────────────────────────────────────────────────────────
32
+ -- Federation: repos (local, not GitHub-bound)
33
+ -- ────────────────────────────────────────────────────────────────
34
+
35
+ CREATE TABLE IF NOT EXISTS repos (
36
+ id TEXT PRIMARY KEY DEFAULT (lower(hex(randomblob(16)))),
37
+ name TEXT NOT NULL,
38
+ description TEXT,
39
+ path TEXT,
40
+ stage TEXT DEFAULT 'seed' CHECK (stage IN ('seed','growth','established','mature')),
41
+ ownership_model TEXT DEFAULT 'solo' CHECK (ownership_model IN ('solo','guild','open')),
42
+ agent_access TEXT DEFAULT 'public',
43
+ min_karma INTEGER DEFAULT 0,
44
+ is_private INTEGER DEFAULT 0,
45
+ consensus_threshold REAL DEFAULT 0.66,
46
+ min_reviews INTEGER DEFAULT 1,
47
+ human_review_weight REAL DEFAULT 1.5,
48
+ contributor_count INTEGER DEFAULT 0,
49
+ patch_count INTEGER DEFAULT 0,
50
+ status TEXT DEFAULT 'active',
51
+ metadata TEXT DEFAULT '{}',
52
+ created_at TEXT DEFAULT (datetime('now')),
53
+ updated_at TEXT DEFAULT (datetime('now'))
54
+ );
55
+
56
+ -- ────────────────────────────────────────────────────────────────
57
+ -- Access control
58
+ -- ────────────────────────────────────────────────────────────────
59
+
60
+ CREATE TABLE IF NOT EXISTS repo_access (
61
+ id TEXT PRIMARY KEY DEFAULT (lower(hex(randomblob(16)))),
62
+ repo_id TEXT NOT NULL REFERENCES repos(id),
63
+ agent_id TEXT NOT NULL REFERENCES agents(id),
64
+ access_level TEXT DEFAULT 'read' CHECK (access_level IN ('none','read','write','maintain','admin')),
65
+ expires_at TEXT,
66
+ created_at TEXT DEFAULT (datetime('now')),
67
+ UNIQUE(repo_id, agent_id)
68
+ );
69
+
70
+ CREATE TABLE IF NOT EXISTS maintainers (
71
+ id TEXT PRIMARY KEY DEFAULT (lower(hex(randomblob(16)))),
72
+ repo_id TEXT NOT NULL REFERENCES repos(id),
73
+ agent_id TEXT NOT NULL REFERENCES agents(id),
74
+ role TEXT DEFAULT 'maintainer' CHECK (role IN ('owner','maintainer')),
75
+ created_at TEXT DEFAULT (datetime('now')),
76
+ UNIQUE(repo_id, agent_id)
77
+ );
78
+
79
+ CREATE TABLE IF NOT EXISTS branch_rules (
80
+ id TEXT PRIMARY KEY DEFAULT (lower(hex(randomblob(16)))),
81
+ repo_id TEXT NOT NULL REFERENCES repos(id),
82
+ branch_pattern TEXT NOT NULL,
83
+ direct_push TEXT DEFAULT 'maintainers' CHECK (direct_push IN ('none','maintainers','all')),
84
+ required_approvals INTEGER DEFAULT 1,
85
+ require_tests_pass INTEGER DEFAULT 0,
86
+ priority INTEGER DEFAULT 0,
87
+ created_at TEXT DEFAULT (datetime('now'))
88
+ );
89
+
90
+ -- ────────────────────────────────────────────────────────────────
91
+ -- Patches & reviews (coordination) — v1 only, replaced by streams in v2
92
+ -- ────────────────────────────────────────────────────────────────
93
+
94
+ CREATE TABLE IF NOT EXISTS patches (
95
+ id TEXT PRIMARY KEY DEFAULT (lower(hex(randomblob(16)))),
96
+ repo_id TEXT NOT NULL REFERENCES repos(id),
97
+ author_id TEXT NOT NULL REFERENCES agents(id),
98
+ title TEXT NOT NULL,
99
+ description TEXT,
100
+ source_branch TEXT,
101
+ target_branch TEXT DEFAULT 'main',
102
+ status TEXT DEFAULT 'open' CHECK (status IN ('open','merged','closed','draft')),
103
+ diff_summary TEXT,
104
+ metadata TEXT DEFAULT '{}',
105
+ created_at TEXT DEFAULT (datetime('now')),
106
+ updated_at TEXT DEFAULT (datetime('now'))
107
+ );
108
+
109
+ CREATE TABLE IF NOT EXISTS patch_reviews (
110
+ id TEXT PRIMARY KEY DEFAULT (lower(hex(randomblob(16)))),
111
+ patch_id TEXT NOT NULL REFERENCES patches(id),
112
+ reviewer_id TEXT NOT NULL REFERENCES agents(id),
113
+ verdict TEXT CHECK (verdict IN ('approve','request_changes','comment')),
114
+ feedback TEXT,
115
+ tested INTEGER DEFAULT 0,
116
+ is_human INTEGER DEFAULT 0,
117
+ reviewed_at TEXT DEFAULT (datetime('now')),
118
+ UNIQUE(patch_id, reviewer_id)
119
+ );
120
+
121
+ -- ────────────────────────────────────────────────────────────────
122
+ -- Tasks / bounties
123
+ -- ────────────────────────────────────────────────────────────────
124
+
125
+ CREATE TABLE IF NOT EXISTS tasks (
126
+ id TEXT PRIMARY KEY DEFAULT (lower(hex(randomblob(16)))),
127
+ repo_id TEXT NOT NULL REFERENCES repos(id),
128
+ title TEXT NOT NULL,
129
+ description TEXT,
130
+ status TEXT DEFAULT 'open' CHECK (status IN ('open','claimed','submitted','completed','cancelled','expired')),
131
+ priority TEXT DEFAULT 'medium' CHECK (priority IN ('low','medium','high','critical')),
132
+ amount INTEGER DEFAULT 0,
133
+ labels TEXT DEFAULT '[]',
134
+ difficulty TEXT,
135
+ created_by TEXT REFERENCES agents(id),
136
+ expires_at TEXT,
137
+ completed_at TEXT,
138
+ created_at TEXT DEFAULT (datetime('now')),
139
+ updated_at TEXT DEFAULT (datetime('now'))
140
+ );
141
+
142
+ CREATE TABLE IF NOT EXISTS task_claims (
143
+ id TEXT PRIMARY KEY DEFAULT (lower(hex(randomblob(16)))),
144
+ task_id TEXT NOT NULL REFERENCES tasks(id),
145
+ agent_id TEXT NOT NULL REFERENCES agents(id),
146
+ status TEXT DEFAULT 'active' CHECK (status IN ('active','submitted','approved','rejected','abandoned')),
147
+ patch_id TEXT REFERENCES patches(id),
148
+ submission_notes TEXT,
149
+ submitted_at TEXT,
150
+ reviewed_by TEXT REFERENCES agents(id),
151
+ reviewed_at TEXT,
152
+ review_notes TEXT,
153
+ claimed_at TEXT DEFAULT (datetime('now'))
154
+ );
155
+
156
+ -- ────────────────────────────────────────────────────────────────
157
+ -- Council governance
158
+ -- ────────────────────────────────────────────────────────────────
159
+
160
+ CREATE TABLE IF NOT EXISTS repo_councils (
161
+ id TEXT PRIMARY KEY DEFAULT (lower(hex(randomblob(16)))),
162
+ repo_id TEXT UNIQUE NOT NULL REFERENCES repos(id),
163
+ min_karma INTEGER DEFAULT 1000,
164
+ min_contributions INTEGER DEFAULT 5,
165
+ min_members INTEGER DEFAULT 3,
166
+ max_members INTEGER DEFAULT 9,
167
+ standard_quorum INTEGER DEFAULT 2,
168
+ critical_quorum INTEGER DEFAULT 3,
169
+ status TEXT DEFAULT 'forming' CHECK (status IN ('forming','active','dissolved')),
170
+ term_limit_months INTEGER DEFAULT 6,
171
+ election_interval_days INTEGER DEFAULT 90,
172
+ created_at TEXT DEFAULT (datetime('now')),
173
+ updated_at TEXT DEFAULT (datetime('now'))
174
+ );
175
+
176
+ CREATE TABLE IF NOT EXISTS council_members (
177
+ id TEXT PRIMARY KEY DEFAULT (lower(hex(randomblob(16)))),
178
+ council_id TEXT NOT NULL REFERENCES repo_councils(id),
179
+ agent_id TEXT NOT NULL REFERENCES agents(id),
180
+ role TEXT DEFAULT 'member' CHECK (role IN ('chair','member')),
181
+ votes_cast INTEGER DEFAULT 0,
182
+ proposals_made INTEGER DEFAULT 0,
183
+ term_expires_at TEXT,
184
+ joined_at TEXT DEFAULT (datetime('now')),
185
+ UNIQUE(council_id, agent_id)
186
+ );
187
+
188
+ CREATE TABLE IF NOT EXISTS council_proposals (
189
+ id TEXT PRIMARY KEY DEFAULT (lower(hex(randomblob(16)))),
190
+ council_id TEXT NOT NULL REFERENCES repo_councils(id),
191
+ title TEXT NOT NULL,
192
+ description TEXT,
193
+ proposal_type TEXT NOT NULL,
194
+ proposed_by TEXT NOT NULL REFERENCES agents(id),
195
+ quorum_required INTEGER DEFAULT 2,
196
+ votes_for INTEGER DEFAULT 0,
197
+ votes_against INTEGER DEFAULT 0,
198
+ votes_abstain INTEGER DEFAULT 0,
199
+ status TEXT DEFAULT 'open' CHECK (status IN ('open','passed','rejected','expired')),
200
+ action_data TEXT DEFAULT '{}',
201
+ executed INTEGER DEFAULT 0,
202
+ executed_at TEXT,
203
+ execution_result TEXT,
204
+ expires_at TEXT,
205
+ resolved_at TEXT,
206
+ proposed_at TEXT DEFAULT (datetime('now'))
207
+ );
208
+
209
+ CREATE TABLE IF NOT EXISTS council_votes (
210
+ id TEXT PRIMARY KEY DEFAULT (lower(hex(randomblob(16)))),
211
+ proposal_id TEXT NOT NULL REFERENCES council_proposals(id),
212
+ agent_id TEXT NOT NULL REFERENCES agents(id),
213
+ vote TEXT NOT NULL CHECK (vote IN ('for','against','abstain')),
214
+ comment TEXT,
215
+ voted_at TEXT DEFAULT (datetime('now')),
216
+ UNIQUE(proposal_id, agent_id)
217
+ );
218
+
219
+ -- ────────────────────────────────────────────────────────────────
220
+ -- Stage progression history
221
+ -- ────────────────────────────────────────────────────────────────
222
+
223
+ CREATE TABLE IF NOT EXISTS stage_history (
224
+ id TEXT PRIMARY KEY DEFAULT (lower(hex(randomblob(16)))),
225
+ repo_id TEXT NOT NULL REFERENCES repos(id),
226
+ from_stage TEXT NOT NULL,
227
+ to_stage TEXT NOT NULL,
228
+ contributor_count INTEGER,
229
+ patch_count INTEGER,
230
+ maintainer_count INTEGER,
231
+ transitioned_at TEXT DEFAULT (datetime('now'))
232
+ );
233
+
234
+ -- ────────────────────────────────────────────────────────────────
235
+ -- Activity log
236
+ -- ────────────────────────────────────────────────────────────────
237
+
238
+ CREATE TABLE IF NOT EXISTS activity_log (
239
+ id TEXT PRIMARY KEY DEFAULT (lower(hex(randomblob(16)))),
240
+ agent_id TEXT REFERENCES agents(id),
241
+ event_type TEXT NOT NULL,
242
+ target_type TEXT,
243
+ target_id TEXT,
244
+ metadata TEXT DEFAULT '{}',
245
+ created_at TEXT DEFAULT (datetime('now'))
246
+ );
247
+
248
+ -- ────────────────────────────────────────────────────────────────
249
+ -- Indexes
250
+ -- ────────────────────────────────────────────────────────────────
251
+
252
+ CREATE INDEX IF NOT EXISTS idx_repo_access_lookup ON repo_access(repo_id, agent_id);
253
+ CREATE INDEX IF NOT EXISTS idx_maintainers_lookup ON maintainers(repo_id, agent_id);
254
+ CREATE INDEX IF NOT EXISTS idx_patches_repo ON patches(repo_id);
255
+ CREATE INDEX IF NOT EXISTS idx_patches_author ON patches(author_id);
256
+ CREATE INDEX IF NOT EXISTS idx_reviews_patch ON patch_reviews(patch_id);
257
+ CREATE INDEX IF NOT EXISTS idx_tasks_repo ON tasks(repo_id);
258
+ CREATE INDEX IF NOT EXISTS idx_tasks_status ON tasks(status);
259
+ CREATE INDEX IF NOT EXISTS idx_claims_task ON task_claims(task_id);
260
+ CREATE INDEX IF NOT EXISTS idx_claims_agent ON task_claims(agent_id);
261
+ CREATE INDEX IF NOT EXISTS idx_proposals_council ON council_proposals(council_id);
262
+ CREATE INDEX IF NOT EXISTS idx_votes_proposal ON council_votes(proposal_id);
263
+ CREATE INDEX IF NOT EXISTS idx_activity_agent ON activity_log(agent_id);
264
+ CREATE INDEX IF NOT EXISTS idx_activity_type ON activity_log(event_type);
265
+ `;
266
+
267
+ /**
268
+ * v2 migration — git-cascade integration.
269
+ *
270
+ * - Adds merge_mode and buffer fields to repos
271
+ * - Adds stream_id to patch_reviews (replacing patch_id as primary ref)
272
+ * - Adds stream_id to task_claims (replacing patch_id)
273
+ * - Drops patches table (streams replace patches)
274
+ */
275
+ export const migrationV2 = `
276
+ -- ── Schema version tracking ─────────────────────────────────────
277
+ CREATE TABLE IF NOT EXISTS schema_version (
278
+ version INTEGER PRIMARY KEY,
279
+ applied_at TEXT DEFAULT (datetime('now'))
280
+ );
281
+
282
+ -- ── Repos: add merge_mode and buffer fields ─────────────────────
283
+ ALTER TABLE repos ADD COLUMN merge_mode TEXT DEFAULT 'review'
284
+ CHECK (merge_mode IN ('swarm','review','gated'));
285
+
286
+ ALTER TABLE repos ADD COLUMN buffer_branch TEXT DEFAULT 'buffer';
287
+ ALTER TABLE repos ADD COLUMN promote_target TEXT DEFAULT 'main';
288
+ ALTER TABLE repos ADD COLUMN auto_promote_on_green INTEGER DEFAULT 0;
289
+ ALTER TABLE repos ADD COLUMN auto_revert_on_red INTEGER DEFAULT 1;
290
+ ALTER TABLE repos ADD COLUMN stabilize_command TEXT;
291
+
292
+ -- ── patch_reviews: add stream_id, review_block_id ───────────────
293
+ -- SQLite doesn't support DROP CONSTRAINT, so we recreate the table.
294
+ CREATE TABLE IF NOT EXISTS patch_reviews_v2 (
295
+ id TEXT PRIMARY KEY DEFAULT (lower(hex(randomblob(16)))),
296
+ stream_id TEXT,
297
+ review_block_id TEXT,
298
+ patch_id TEXT,
299
+ reviewer_id TEXT NOT NULL REFERENCES agents(id),
300
+ verdict TEXT CHECK (verdict IN ('approve','request_changes','comment')),
301
+ feedback TEXT,
302
+ tested INTEGER DEFAULT 0,
303
+ is_human INTEGER DEFAULT 0,
304
+ reviewed_at TEXT DEFAULT (datetime('now')),
305
+ UNIQUE(stream_id, reviewer_id)
306
+ );
307
+
308
+ INSERT OR IGNORE INTO patch_reviews_v2 (id, patch_id, reviewer_id, verdict, feedback, tested, is_human, reviewed_at)
309
+ SELECT id, patch_id, reviewer_id, verdict, feedback, tested, is_human, reviewed_at FROM patch_reviews;
310
+
311
+ DROP TABLE IF EXISTS patch_reviews;
312
+ ALTER TABLE patch_reviews_v2 RENAME TO patch_reviews;
313
+
314
+ -- ── task_claims: add stream_id ──────────────────────────────────
315
+ ALTER TABLE task_claims ADD COLUMN stream_id TEXT;
316
+
317
+ -- ── Indexes for new columns ─────────────────────────────────────
318
+ CREATE INDEX IF NOT EXISTS idx_reviews_stream ON patch_reviews(stream_id);
319
+ CREATE INDEX IF NOT EXISTS idx_claims_stream ON task_claims(stream_id);
320
+
321
+ -- ── Record migration ────────────────────────────────────────────
322
+ INSERT INTO schema_version (version) VALUES (2);
323
+ `;
324
+
325
+ // Migration V3: sync_queue for offline Mode B event queuing
326
+ const migrationV3 = `
327
+ -- ── sync_queue: offline event queue for Mode B ──────────────────
328
+ CREATE TABLE IF NOT EXISTS sync_queue (
329
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
330
+ event_type TEXT NOT NULL,
331
+ payload TEXT NOT NULL,
332
+ created_at TEXT DEFAULT (datetime('now'))
333
+ );
334
+
335
+ INSERT INTO schema_version (version) VALUES (3);
336
+ `;
337
+
338
+ /**
339
+ * Migration V4: Cross-level integration fixes.
340
+ *
341
+ * 1. ID format standardization — convert 32-char hex IDs to 36-char UUIDs
342
+ * 2. Add policy-level streams table (git-cascade owns gc_streams for git
343
+ * mechanics; this table owns policy metadata for shared services)
344
+ * 3. Add org_id to repos for Mode B server sync
345
+ * 4. Add consensus_authority to repos for split-brain prevention
346
+ * 5. Add tracking columns to sync_queue for batch sync reliability
347
+ */
348
+ const migrationV4 = `
349
+ -- ── 1. ID format: convert 32-char hex to UUID with dashes ──────
350
+ -- Helper approach: update each table that uses hex IDs.
351
+ -- SQLite UPDATE with substr() inserts dashes into 32-char hex strings.
352
+ -- IDs that are already 36 chars (or any other length) are left unchanged.
353
+
354
+ UPDATE agents SET id =
355
+ substr(id,1,8)||'-'||substr(id,9,4)||'-'||substr(id,13,4)||'-'||
356
+ substr(id,17,4)||'-'||substr(id,21,12)
357
+ WHERE length(id) = 32;
358
+
359
+ UPDATE repos SET id =
360
+ substr(id,1,8)||'-'||substr(id,9,4)||'-'||substr(id,13,4)||'-'||
361
+ substr(id,17,4)||'-'||substr(id,21,12)
362
+ WHERE length(id) = 32;
363
+
364
+ UPDATE repo_access SET id =
365
+ substr(id,1,8)||'-'||substr(id,9,4)||'-'||substr(id,13,4)||'-'||
366
+ substr(id,17,4)||'-'||substr(id,21,12)
367
+ WHERE length(id) = 32;
368
+
369
+ UPDATE repo_access SET repo_id =
370
+ substr(repo_id,1,8)||'-'||substr(repo_id,9,4)||'-'||substr(repo_id,13,4)||'-'||
371
+ substr(repo_id,17,4)||'-'||substr(repo_id,21,12)
372
+ WHERE length(repo_id) = 32;
373
+
374
+ UPDATE repo_access SET agent_id =
375
+ substr(agent_id,1,8)||'-'||substr(agent_id,9,4)||'-'||substr(agent_id,13,4)||'-'||
376
+ substr(agent_id,17,4)||'-'||substr(agent_id,21,12)
377
+ WHERE length(agent_id) = 32;
378
+
379
+ UPDATE maintainers SET id =
380
+ substr(id,1,8)||'-'||substr(id,9,4)||'-'||substr(id,13,4)||'-'||
381
+ substr(id,17,4)||'-'||substr(id,21,12)
382
+ WHERE length(id) = 32;
383
+
384
+ UPDATE maintainers SET repo_id =
385
+ substr(repo_id,1,8)||'-'||substr(repo_id,9,4)||'-'||substr(repo_id,13,4)||'-'||
386
+ substr(repo_id,17,4)||'-'||substr(repo_id,21,12)
387
+ WHERE length(repo_id) = 32;
388
+
389
+ UPDATE maintainers SET agent_id =
390
+ substr(agent_id,1,8)||'-'||substr(agent_id,9,4)||'-'||substr(agent_id,13,4)||'-'||
391
+ substr(agent_id,17,4)||'-'||substr(agent_id,21,12)
392
+ WHERE length(agent_id) = 32;
393
+
394
+ UPDATE branch_rules SET id =
395
+ substr(id,1,8)||'-'||substr(id,9,4)||'-'||substr(id,13,4)||'-'||
396
+ substr(id,17,4)||'-'||substr(id,21,12)
397
+ WHERE length(id) = 32;
398
+
399
+ UPDATE branch_rules SET repo_id =
400
+ substr(repo_id,1,8)||'-'||substr(repo_id,9,4)||'-'||substr(repo_id,13,4)||'-'||
401
+ substr(repo_id,17,4)||'-'||substr(repo_id,21,12)
402
+ WHERE length(repo_id) = 32;
403
+
404
+ UPDATE patch_reviews SET id =
405
+ substr(id,1,8)||'-'||substr(id,9,4)||'-'||substr(id,13,4)||'-'||
406
+ substr(id,17,4)||'-'||substr(id,21,12)
407
+ WHERE length(id) = 32;
408
+
409
+ UPDATE patch_reviews SET reviewer_id =
410
+ substr(reviewer_id,1,8)||'-'||substr(reviewer_id,9,4)||'-'||substr(reviewer_id,13,4)||'-'||
411
+ substr(reviewer_id,17,4)||'-'||substr(reviewer_id,21,12)
412
+ WHERE length(reviewer_id) = 32;
413
+
414
+ UPDATE tasks SET id =
415
+ substr(id,1,8)||'-'||substr(id,9,4)||'-'||substr(id,13,4)||'-'||
416
+ substr(id,17,4)||'-'||substr(id,21,12)
417
+ WHERE length(id) = 32;
418
+
419
+ UPDATE tasks SET repo_id =
420
+ substr(repo_id,1,8)||'-'||substr(repo_id,9,4)||'-'||substr(repo_id,13,4)||'-'||
421
+ substr(repo_id,17,4)||'-'||substr(repo_id,21,12)
422
+ WHERE length(repo_id) = 32;
423
+
424
+ UPDATE task_claims SET id =
425
+ substr(id,1,8)||'-'||substr(id,9,4)||'-'||substr(id,13,4)||'-'||
426
+ substr(id,17,4)||'-'||substr(id,21,12)
427
+ WHERE length(id) = 32;
428
+
429
+ UPDATE task_claims SET task_id =
430
+ substr(task_id,1,8)||'-'||substr(task_id,9,4)||'-'||substr(task_id,13,4)||'-'||
431
+ substr(task_id,17,4)||'-'||substr(task_id,21,12)
432
+ WHERE length(task_id) = 32;
433
+
434
+ UPDATE task_claims SET agent_id =
435
+ substr(agent_id,1,8)||'-'||substr(agent_id,9,4)||'-'||substr(agent_id,13,4)||'-'||
436
+ substr(agent_id,17,4)||'-'||substr(agent_id,21,12)
437
+ WHERE length(agent_id) = 32;
438
+
439
+ UPDATE repo_councils SET id =
440
+ substr(id,1,8)||'-'||substr(id,9,4)||'-'||substr(id,13,4)||'-'||
441
+ substr(id,17,4)||'-'||substr(id,21,12)
442
+ WHERE length(id) = 32;
443
+
444
+ UPDATE repo_councils SET repo_id =
445
+ substr(repo_id,1,8)||'-'||substr(repo_id,9,4)||'-'||substr(repo_id,13,4)||'-'||
446
+ substr(repo_id,17,4)||'-'||substr(repo_id,21,12)
447
+ WHERE length(repo_id) = 32;
448
+
449
+ UPDATE council_members SET id =
450
+ substr(id,1,8)||'-'||substr(id,9,4)||'-'||substr(id,13,4)||'-'||
451
+ substr(id,17,4)||'-'||substr(id,21,12)
452
+ WHERE length(id) = 32;
453
+
454
+ UPDATE council_members SET council_id =
455
+ substr(council_id,1,8)||'-'||substr(council_id,9,4)||'-'||substr(council_id,13,4)||'-'||
456
+ substr(council_id,17,4)||'-'||substr(council_id,21,12)
457
+ WHERE length(council_id) = 32;
458
+
459
+ UPDATE council_members SET agent_id =
460
+ substr(agent_id,1,8)||'-'||substr(agent_id,9,4)||'-'||substr(agent_id,13,4)||'-'||
461
+ substr(agent_id,17,4)||'-'||substr(agent_id,21,12)
462
+ WHERE length(agent_id) = 32;
463
+
464
+ UPDATE council_proposals SET id =
465
+ substr(id,1,8)||'-'||substr(id,9,4)||'-'||substr(id,13,4)||'-'||
466
+ substr(id,17,4)||'-'||substr(id,21,12)
467
+ WHERE length(id) = 32;
468
+
469
+ UPDATE council_proposals SET council_id =
470
+ substr(council_id,1,8)||'-'||substr(council_id,9,4)||'-'||substr(council_id,13,4)||'-'||
471
+ substr(council_id,17,4)||'-'||substr(council_id,21,12)
472
+ WHERE length(council_id) = 32;
473
+
474
+ UPDATE council_proposals SET proposed_by =
475
+ substr(proposed_by,1,8)||'-'||substr(proposed_by,9,4)||'-'||substr(proposed_by,13,4)||'-'||
476
+ substr(proposed_by,17,4)||'-'||substr(proposed_by,21,12)
477
+ WHERE length(proposed_by) = 32;
478
+
479
+ UPDATE council_votes SET id =
480
+ substr(id,1,8)||'-'||substr(id,9,4)||'-'||substr(id,13,4)||'-'||
481
+ substr(id,17,4)||'-'||substr(id,21,12)
482
+ WHERE length(id) = 32;
483
+
484
+ UPDATE council_votes SET proposal_id =
485
+ substr(proposal_id,1,8)||'-'||substr(proposal_id,9,4)||'-'||substr(proposal_id,13,4)||'-'||
486
+ substr(proposal_id,17,4)||'-'||substr(proposal_id,21,12)
487
+ WHERE length(proposal_id) = 32;
488
+
489
+ UPDATE council_votes SET agent_id =
490
+ substr(agent_id,1,8)||'-'||substr(agent_id,9,4)||'-'||substr(agent_id,13,4)||'-'||
491
+ substr(agent_id,17,4)||'-'||substr(agent_id,21,12)
492
+ WHERE length(agent_id) = 32;
493
+
494
+ UPDATE stage_history SET id =
495
+ substr(id,1,8)||'-'||substr(id,9,4)||'-'||substr(id,13,4)||'-'||
496
+ substr(id,17,4)||'-'||substr(id,21,12)
497
+ WHERE length(id) = 32;
498
+
499
+ UPDATE stage_history SET repo_id =
500
+ substr(repo_id,1,8)||'-'||substr(repo_id,9,4)||'-'||substr(repo_id,13,4)||'-'||
501
+ substr(repo_id,17,4)||'-'||substr(repo_id,21,12)
502
+ WHERE length(repo_id) = 32;
503
+
504
+ UPDATE activity_log SET id =
505
+ substr(id,1,8)||'-'||substr(id,9,4)||'-'||substr(id,13,4)||'-'||
506
+ substr(id,17,4)||'-'||substr(id,21,12)
507
+ WHERE length(id) = 32;
508
+
509
+ -- ── 2. Policy-level streams table ──────────────────────────────
510
+ -- git-cascade owns gc_streams for git mechanics (branch, worktree, merge queue).
511
+ -- This table owns policy metadata that shared services (permissions, consensus) need.
512
+ CREATE TABLE IF NOT EXISTS streams (
513
+ id TEXT PRIMARY KEY,
514
+ repo_id TEXT NOT NULL REFERENCES repos(id),
515
+ agent_id TEXT NOT NULL REFERENCES agents(id),
516
+ name TEXT NOT NULL,
517
+ branch TEXT,
518
+ base_branch TEXT DEFAULT 'main',
519
+ parent_stream_id TEXT,
520
+ task_id TEXT,
521
+ status TEXT DEFAULT 'active'
522
+ CHECK (status IN ('active','in_review','merged','abandoned','reverted')),
523
+ source TEXT DEFAULT 'cli',
524
+ review_status TEXT DEFAULT 'pending'
525
+ CHECK (review_status IN ('pending','in_review','approved','changes_requested')),
526
+ metadata TEXT DEFAULT '{}',
527
+ created_at TEXT DEFAULT (datetime('now')),
528
+ updated_at TEXT DEFAULT (datetime('now'))
529
+ );
530
+
531
+ CREATE INDEX IF NOT EXISTS idx_streams_repo ON streams(repo_id);
532
+ CREATE INDEX IF NOT EXISTS idx_streams_agent ON streams(agent_id);
533
+ CREATE INDEX IF NOT EXISTS idx_streams_status ON streams(status);
534
+
535
+ -- Ensure gc_streams exists before backfill (git-cascade normally creates it;
536
+ -- this no-op skeleton lets the INSERT below work on fresh databases).
537
+ CREATE TABLE IF NOT EXISTS gc_streams (
538
+ id TEXT PRIMARY KEY, repoId TEXT, agentId TEXT, name TEXT,
539
+ branch TEXT, status TEXT, createdAt TEXT
540
+ );
541
+
542
+ -- Backfill from gc_streams if it has data
543
+ INSERT OR IGNORE INTO streams (id, repo_id, agent_id, name, branch, status, created_at)
544
+ SELECT s.id, s.repoId, s.agentId, s.name, s.branch, s.status, s.createdAt
545
+ FROM gc_streams s
546
+ WHERE s.repoId IS NOT NULL AND s.agentId IS NOT NULL;
547
+
548
+ -- ── 3. Repos: add org_id for Mode B server sync ────────────────
549
+ ALTER TABLE repos ADD COLUMN org_id TEXT;
550
+
551
+ -- ── 4. Repos: add consensus_authority for split-brain prevention ─
552
+ ALTER TABLE repos ADD COLUMN consensus_authority TEXT DEFAULT 'local'
553
+ CHECK (consensus_authority IN ('local','server'));
554
+
555
+ -- ── 5. sync_queue: add tracking columns for batch sync ──────────
556
+ ALTER TABLE sync_queue ADD COLUMN attempts INTEGER DEFAULT 0;
557
+ ALTER TABLE sync_queue ADD COLUMN last_error TEXT;
558
+
559
+ -- ── Record migration ────────────────────────────────────────────
560
+ INSERT INTO schema_version (version) VALUES (4);
561
+ `;
562
+
563
+ /** All migrations in order. */
564
+ export const migrations = [
565
+ { version: 1, sql: schemaV1 },
566
+ { version: 2, sql: migrationV2 },
567
+ { version: 3, sql: migrationV3 },
568
+ { version: 4, sql: migrationV4 },
569
+ ];