opencode-swarm 7.164.11 → 7.164.12
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/.opencode/skills/engineering-conventions/SKILL.md +1 -1
- package/dist/background/pending-delegations.d.ts +7 -2
- package/dist/background/pr-subscriptions.d.ts +3 -3
- package/dist/cli/{coder-settlement-0rfk8y11.js → coder-settlement-yh1q5fr3.js} +15 -16
- package/dist/cli/{config-doctor-j17b7qq2.js → config-doctor-bd4yzmh7.js} +3 -3
- package/dist/cli/{core-evhjjrtt.js → core-y0s1vmyc.js} +2 -2
- package/dist/cli/{curation-policy-vj6z5hvr.js → curation-policy-gs1qakn7.js} +5 -6
- package/dist/cli/{curator-vqd5yeck.js → curator-brqack0j.js} +48 -46
- package/dist/cli/{curator-drift-ez81vg2t.js → curator-drift-1sg9yk2m.js} +14 -12
- package/dist/cli/curator-llm-factory-dtsjc3zf.js +70 -0
- package/dist/cli/{dispatch-dqybvj8v.js → dispatch-c7xjt7js.js} +3 -3
- package/dist/cli/{evidence-summary-service-937x7rbg.js → evidence-summary-service-nqbcvp30.js} +24 -23
- package/dist/cli/{gate-evidence-q1cehr4k.js → gate-evidence-9stdtpej.js} +9 -10
- package/dist/cli/guardrail-explain-3ff1z3fm.js +71 -0
- package/dist/cli/{guardrail-log-3g93f3g2.js → guardrail-log-sf39x00b.js} +10 -10
- package/dist/cli/{guardrail-reset-qe2r5qzq.js → guardrail-reset-9wr6a60s.js} +48 -46
- package/dist/cli/hive-promoter-bqtj8261.js +85 -0
- package/dist/cli/{index-kv2tdkk5.js → index-03enxdaq.js} +1 -1
- package/dist/cli/{index-ggrp3v00.js → index-0byep4pe.js} +15 -17
- package/dist/cli/{index-2bgr671r.js → index-2bhz5k91.js} +20 -2
- package/dist/cli/{index-mmjamc9b.js → index-3fnym6ac.js} +5 -5
- package/dist/cli/{index-d9entpn3.js → index-40twgsyh.js} +4 -4
- package/dist/cli/{index-ggtmsm84.js → index-4mjmnysc.js} +1 -1
- package/dist/cli/{index-e90em0x4.js → index-4vb56w2k.js} +12 -17
- package/dist/cli/{index-r656jh9j.js → index-5wc5pbzc.js} +3700 -2609
- package/dist/cli/{index-w3gtqey0.js → index-6chhqq2n.js} +4 -4
- package/dist/cli/{index-qbw3yw2d.js → index-888pq4xx.js} +11 -11
- package/dist/cli/{index-3146wmgv.js → index-8d90gzaz.js} +1 -1
- package/dist/cli/{index-5jx22kwc.js → index-8vw2pjgr.js} +544 -15
- package/dist/cli/{index-b2nj04q6.js → index-9ss2m4rs.js} +1 -434
- package/dist/cli/{index-x43debgx.js → index-ad9064j9.js} +15 -17
- package/dist/cli/{index-5kcvrpkt.js → index-b25t91a3.js} +1 -1
- package/dist/cli/index-c45662wc.js +361 -0
- package/dist/cli/{index-aqppaaqx.js → index-cgewrpw5.js} +3 -2
- package/dist/cli/{index-zfs3zqeq.js → index-d3mpxsmy.js} +771 -21
- package/dist/cli/{index-y1yqmkv7.js → index-d7j3n5ra.js} +52 -50
- package/dist/cli/{index-4vb6qa73.js → index-ebce775n.js} +36 -2
- package/dist/cli/{index-dsg9dw3b.js → index-eed0gkv6.js} +2 -2
- package/dist/cli/index-es64amqt.js +675 -0
- package/dist/cli/{index-9gxbjkts.js → index-ewv8qa8q.js} +22 -19
- package/dist/cli/{index-1ww7wj8d.js → index-hb7bh4r2.js} +15 -15
- package/dist/cli/{index-kc2tgcm7.js → index-hg09xemc.js} +4 -4
- package/dist/cli/{index-w95vxjde.js → index-hvz4ad2t.js} +39 -8
- package/dist/cli/{index-77z98j76.js → index-k0s8n6m5.js} +314 -130
- package/dist/cli/{index-7m32fjb4.js → index-kxv193aq.js} +9 -9
- package/dist/cli/{index-hp22pzn5.js → index-m8xrbdmn.js} +2 -2
- package/dist/cli/{index-4gqxppcx.js → index-mee9maf5.js} +2 -2
- package/dist/cli/{index-r3mvsbtq.js → index-p612v1mv.js} +7 -1
- package/dist/cli/{index-r5q5gd2a.js → index-qgajv9rd.js} +2 -2
- package/dist/cli/{index-zjg4chy2.js → index-qqq04n70.js} +2 -2
- package/dist/cli/{index-ww6zm5hr.js → index-r2p0pgst.js} +3 -3
- package/dist/cli/{index-47kh2103.js → index-r69gzes2.js} +14 -14
- package/dist/cli/{index-vdn32nws.js → index-sphahrmq.js} +3 -3
- package/dist/cli/{index-rhqdsc1x.js → index-vw66q1rw.js} +9 -9
- package/dist/cli/{index-kch6yenm.js → index-wasdrhev.js} +7 -7
- package/dist/cli/{index-c5v1w13g.js → index-wknpkfm8.js} +807 -629
- package/dist/cli/{index-pp2nya9v.js → index-x39hjdf1.js} +2 -4
- package/dist/cli/{index-n8688d12.js → index-xxfk62zd.js} +2 -2
- package/dist/cli/index-yvqg6kn5.js +429 -0
- package/dist/cli/{index-nqsj0g5y.js → index-ztm0q26v.js} +8 -8
- package/dist/cli/index.js +50 -48
- package/dist/cli/{knowledge-escalator-j04g450p.js → knowledge-escalator-01vva51d.js} +17 -18
- package/dist/cli/{knowledge-events-53z45rs0.js → knowledge-events-dpfwh1hs.js} +15 -16
- package/dist/cli/{knowledge-link-a6kfg3qm.js → knowledge-link-2rvp966j.js} +4 -5
- package/dist/cli/{knowledge-store-whm0gagt.js → knowledge-store-aw58ae7b.js} +6 -7
- package/dist/cli/{knowledge-validator-h8m7065g.js → knowledge-validator-yhd31ryf.js} +8 -9
- package/dist/cli/{pending-delegations-6hkpe5ze.js → pending-delegations-fjetcz8e.js} +17 -8
- package/dist/cli/{pr-subscriptions-11zca1mh.js → pr-subscriptions-cwwtfs8w.js} +17 -8
- package/dist/cli/{pr-workflow-gate-6g29rkg7.js → pr-workflow-gate-5xwtg1k2.js} +50 -48
- package/dist/cli/{runner-5abw3t35.js → runner-mb51twxt.js} +6 -7
- package/dist/cli/{scan-cursor-mkfx410t.js → scan-cursor-j11qwf0v.js} +9 -10
- package/dist/cli/{schema-jbbam3ev.js → schema-0xp3bde3.js} +1 -1
- package/dist/cli/{scope-persistence-q1ct4k42.js → scope-persistence-brrk806y.js} +19 -12
- package/dist/cli/{skill-generator-cv8fg96x.js → skill-generator-j8qzmet9.js} +20 -21
- package/dist/cli/snapshot-coordination-init-h0xmtftq.js +84 -0
- package/dist/cli/{telemetry-mbqqamsj.js → telemetry-m84ap0pp.js} +1 -1
- package/dist/cli/{workspace-snapshot-eh18vgkn.js → workspace-snapshot-6z9rk7xw.js} +3 -3
- package/dist/cli/{worktree-collision-ownership-xgnn8dsq.js → worktree-collision-ownership-j9zykj18.js} +21 -12
- package/dist/cli/{worktree-isolation-k2dgfmeg.js → worktree-isolation-jd39xz6h.js} +48 -46
- package/dist/commands/close.d.ts +13 -6
- package/dist/commands/reset-session.d.ts +4 -0
- package/dist/db/coordination-store.d.ts +85 -0
- package/dist/db/durability.d.ts +22 -5
- package/dist/db/index.d.ts +1 -0
- package/dist/db/project-db.d.ts +12 -0
- package/dist/index.js +810 -705
- package/dist/memory/schema.d.ts +3 -3
- package/dist/pr-review/authorization.d.ts +5 -4
- package/dist/pr-review/persistence.d.ts +10 -1
- package/dist/scope/scope-binding.d.ts +10 -0
- package/dist/scope/scope-persistence.d.ts +10 -0
- package/dist/services/handoff-service.d.ts +2 -0
- package/dist/services/status-service.d.ts +4 -0
- package/dist/session/snapshot-coordination-init.d.ts +49 -0
- package/dist/session/snapshot-reader.d.ts +2 -0
- package/dist/session/snapshot-store.d.ts +42 -0
- package/dist/session/snapshot-writer.d.ts +4 -1
- package/dist/state.d.ts +6 -3
- package/dist/turbo/epic/state.d.ts +4 -20
- package/dist/utils/bun-compat.d.ts +10 -0
- package/dist/utils/external-tool-runner.d.ts +2 -0
- package/package.json +3 -2
- package/dist/cli/curator-llm-factory-sxcpmcqq.js +0 -68
- package/dist/cli/guardrail-explain-xv9aw1bf.js +0 -69
- package/dist/cli/hive-promoter-09yc9mp5.js +0 -83
- package/dist/cli/index-xnb3tk7x.js +0 -24
- package/dist/cli/{ast-diff-ekd9v10f.js → ast-diff-b0esr2kf.js} +3 -3
|
@@ -0,0 +1,675 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
import {
|
|
3
|
+
canonicalExistingFilesystemPath
|
|
4
|
+
} from "./index-ry16d01s.js";
|
|
5
|
+
import {
|
|
6
|
+
loadDatabaseCtor
|
|
7
|
+
} from "./index-n4fzmqz8.js";
|
|
8
|
+
import {
|
|
9
|
+
canonicalRootKeyFresh,
|
|
10
|
+
lexicalRootAliasKey
|
|
11
|
+
} from "./index-7qefgq4j.js";
|
|
12
|
+
import {
|
|
13
|
+
atomicWriteSwarmFileSync
|
|
14
|
+
} from "./index-ewv8qa8q.js";
|
|
15
|
+
|
|
16
|
+
// src/db/project-db.ts
|
|
17
|
+
import { existsSync, mkdirSync, unlinkSync } from "fs";
|
|
18
|
+
import { join, resolve } from "path";
|
|
19
|
+
|
|
20
|
+
// src/db/db-errors.ts
|
|
21
|
+
class ProjectDbError extends Error {
|
|
22
|
+
category;
|
|
23
|
+
constructor(category, message) {
|
|
24
|
+
super(message);
|
|
25
|
+
this.name = "ProjectDbError";
|
|
26
|
+
this.category = category;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
class DbWriteError extends Error {
|
|
31
|
+
category;
|
|
32
|
+
constructor(category, message) {
|
|
33
|
+
super(message);
|
|
34
|
+
this.name = "DbWriteError";
|
|
35
|
+
this.category = category;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
function classifyDbWriteError(err) {
|
|
39
|
+
const errno = err?.code;
|
|
40
|
+
if (errno === "ENOSPC")
|
|
41
|
+
return "disk_full";
|
|
42
|
+
if (errno === "EACCES" || errno === "EROFS" || errno === "EPERM")
|
|
43
|
+
return "read_only";
|
|
44
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
45
|
+
const upper = message.toUpperCase();
|
|
46
|
+
if (upper.includes("SQLITE_FULL") || upper.includes("DATABASE OR DISK IS FULL")) {
|
|
47
|
+
return "disk_full";
|
|
48
|
+
}
|
|
49
|
+
if (upper.includes("SQLITE_READONLY") || upper.includes("ATTEMPT TO WRITE A READONLY")) {
|
|
50
|
+
return "read_only";
|
|
51
|
+
}
|
|
52
|
+
if (upper.includes("SQLITE_CORRUPT") || upper.includes("DATABASE DISK IMAGE IS MALFORMED") || upper.includes("NOT A DATABASE")) {
|
|
53
|
+
return "corrupt";
|
|
54
|
+
}
|
|
55
|
+
if (upper.includes("SQLITE_BUSY") || upper.includes("DATABASE IS LOCKED")) {
|
|
56
|
+
return "busy";
|
|
57
|
+
}
|
|
58
|
+
return "unknown";
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// src/db/project-db.ts
|
|
62
|
+
var MIGRATION_FAILURE_ERROR_MAX_CHARS = 500;
|
|
63
|
+
var MIGRATION_FAILURE_MARKER = "db-migration-failure.json";
|
|
64
|
+
var MIGRATION_BUSY_RETRIES = 2;
|
|
65
|
+
var OPEN_CONTENTION_RETRIES = 4;
|
|
66
|
+
var MIGRATIONS = [
|
|
67
|
+
{
|
|
68
|
+
version: 1,
|
|
69
|
+
name: "create_project_constraints",
|
|
70
|
+
sql: `CREATE TABLE project_constraints (
|
|
71
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
72
|
+
constraint_type TEXT NOT NULL,
|
|
73
|
+
content TEXT NOT NULL,
|
|
74
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
75
|
+
)`
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
version: 2,
|
|
79
|
+
name: "create_qa_gate_profile",
|
|
80
|
+
sql: `CREATE TABLE qa_gate_profile (
|
|
81
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
82
|
+
plan_id TEXT NOT NULL UNIQUE,
|
|
83
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
84
|
+
project_type TEXT,
|
|
85
|
+
gates TEXT NOT NULL DEFAULT '{}',
|
|
86
|
+
locked_at TEXT,
|
|
87
|
+
locked_by_snapshot_seq INTEGER
|
|
88
|
+
)`
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
version: 3,
|
|
92
|
+
name: "create_qa_gate_profile_immutability_trigger",
|
|
93
|
+
sql: `CREATE TRIGGER IF NOT EXISTS trg_qa_gate_profile_no_update_after_lock
|
|
94
|
+
BEFORE UPDATE ON qa_gate_profile
|
|
95
|
+
WHEN OLD.locked_at IS NOT NULL
|
|
96
|
+
BEGIN
|
|
97
|
+
SELECT RAISE(ABORT, 'qa_gate_profile row is locked and cannot be modified after critic approval');
|
|
98
|
+
END`
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
version: 4,
|
|
102
|
+
name: "add_qa_gate_profile_raw_swarm",
|
|
103
|
+
sql: "ALTER TABLE qa_gate_profile ADD COLUMN raw_swarm TEXT"
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
version: 5,
|
|
107
|
+
name: "add_qa_gate_profile_raw_title",
|
|
108
|
+
sql: "ALTER TABLE qa_gate_profile ADD COLUMN raw_title TEXT"
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
version: 6,
|
|
112
|
+
name: "add_qa_gate_profile_identity_hash",
|
|
113
|
+
sql: "ALTER TABLE qa_gate_profile ADD COLUMN identity_hash TEXT"
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
version: 7,
|
|
117
|
+
name: "create_qa_gate_profile_identity_hash_index",
|
|
118
|
+
sql: `CREATE UNIQUE INDEX IF NOT EXISTS idx_qa_gate_profile_identity_hash
|
|
119
|
+
ON qa_gate_profile(identity_hash)
|
|
120
|
+
WHERE identity_hash IS NOT NULL`
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
version: 8,
|
|
124
|
+
name: "create_qa_gate_profile_identity",
|
|
125
|
+
sql: `CREATE TABLE qa_gate_profile_identity (
|
|
126
|
+
identity_hash TEXT PRIMARY KEY,
|
|
127
|
+
profile_id INTEGER NOT NULL UNIQUE REFERENCES qa_gate_profile(id) ON DELETE CASCADE,
|
|
128
|
+
raw_swarm TEXT NOT NULL,
|
|
129
|
+
raw_title TEXT NOT NULL,
|
|
130
|
+
readable_plan_id TEXT NOT NULL
|
|
131
|
+
)`
|
|
132
|
+
},
|
|
133
|
+
{
|
|
134
|
+
version: 9,
|
|
135
|
+
name: "create_qa_gate_profile_identity_readable_plan_index",
|
|
136
|
+
sql: `CREATE INDEX idx_qa_gate_profile_identity_readable_plan_id
|
|
137
|
+
ON qa_gate_profile_identity(readable_plan_id)`
|
|
138
|
+
},
|
|
139
|
+
{
|
|
140
|
+
version: 10,
|
|
141
|
+
name: "backfill_qa_gate_profile_identity_from_legacy_columns",
|
|
142
|
+
sql: `INSERT INTO qa_gate_profile_identity (
|
|
143
|
+
identity_hash,
|
|
144
|
+
profile_id,
|
|
145
|
+
raw_swarm,
|
|
146
|
+
raw_title,
|
|
147
|
+
readable_plan_id
|
|
148
|
+
)
|
|
149
|
+
SELECT
|
|
150
|
+
identity_hash,
|
|
151
|
+
id,
|
|
152
|
+
raw_swarm,
|
|
153
|
+
raw_title,
|
|
154
|
+
plan_id
|
|
155
|
+
FROM qa_gate_profile
|
|
156
|
+
WHERE identity_hash IS NOT NULL
|
|
157
|
+
AND raw_swarm IS NOT NULL
|
|
158
|
+
AND raw_title IS NOT NULL
|
|
159
|
+
AND NOT EXISTS (
|
|
160
|
+
SELECT 1
|
|
161
|
+
FROM qa_gate_profile_identity AS qi
|
|
162
|
+
WHERE qi.profile_id = qa_gate_profile.id
|
|
163
|
+
)`
|
|
164
|
+
},
|
|
165
|
+
{
|
|
166
|
+
version: 11,
|
|
167
|
+
name: "create_task_checkpoint_receipt",
|
|
168
|
+
sql: `CREATE TABLE task_checkpoint_receipt (
|
|
169
|
+
plan_identity_hash TEXT NOT NULL,
|
|
170
|
+
task_id TEXT NOT NULL,
|
|
171
|
+
label TEXT NOT NULL,
|
|
172
|
+
state TEXT NOT NULL CHECK(state IN ('pending', 'committed', 'logged')),
|
|
173
|
+
sha TEXT,
|
|
174
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
175
|
+
updated_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
176
|
+
PRIMARY KEY(plan_identity_hash, task_id)
|
|
177
|
+
)`
|
|
178
|
+
},
|
|
179
|
+
{
|
|
180
|
+
version: 12,
|
|
181
|
+
name: "add_task_checkpoint_receipt_completion_generation",
|
|
182
|
+
sql: `ALTER TABLE task_checkpoint_receipt
|
|
183
|
+
ADD COLUMN generation INTEGER NOT NULL DEFAULT 1;
|
|
184
|
+
ALTER TABLE task_checkpoint_receipt
|
|
185
|
+
ADD COLUMN completion_active INTEGER NOT NULL DEFAULT 1
|
|
186
|
+
CHECK(completion_active IN (0, 1))`
|
|
187
|
+
},
|
|
188
|
+
{
|
|
189
|
+
version: 13,
|
|
190
|
+
name: "bind_task_checkpoint_receipt_to_completion_ledger_seq",
|
|
191
|
+
sql: `ALTER TABLE task_checkpoint_receipt
|
|
192
|
+
ADD COLUMN completion_ledger_seq INTEGER
|
|
193
|
+
CHECK(completion_ledger_seq IS NULL OR completion_ledger_seq >= 0)`
|
|
194
|
+
},
|
|
195
|
+
{
|
|
196
|
+
version: 14,
|
|
197
|
+
name: "create_migration_failures",
|
|
198
|
+
sql: `CREATE TABLE IF NOT EXISTS migration_failures (
|
|
199
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
200
|
+
version INTEGER NOT NULL,
|
|
201
|
+
name TEXT NOT NULL,
|
|
202
|
+
error TEXT NOT NULL,
|
|
203
|
+
failed_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
204
|
+
)`
|
|
205
|
+
},
|
|
206
|
+
{
|
|
207
|
+
version: 15,
|
|
208
|
+
name: "create_insight_candidate_stream",
|
|
209
|
+
sql: `CREATE TABLE IF NOT EXISTS insight_candidate (
|
|
210
|
+
stream_id TEXT NOT NULL,
|
|
211
|
+
version INTEGER NOT NULL,
|
|
212
|
+
payload TEXT NOT NULL,
|
|
213
|
+
created_at TEXT NOT NULL,
|
|
214
|
+
consumed_at TEXT,
|
|
215
|
+
PRIMARY KEY(stream_id, version)
|
|
216
|
+
)`
|
|
217
|
+
},
|
|
218
|
+
{
|
|
219
|
+
version: 16,
|
|
220
|
+
name: "create_insight_candidate_pending_index",
|
|
221
|
+
sql: `CREATE INDEX IF NOT EXISTS idx_insight_candidate_pending
|
|
222
|
+
ON insight_candidate(stream_id, version)
|
|
223
|
+
WHERE consumed_at IS NULL`
|
|
224
|
+
},
|
|
225
|
+
{
|
|
226
|
+
version: 17,
|
|
227
|
+
name: "create_phase_report",
|
|
228
|
+
sql: `CREATE TABLE IF NOT EXISTS phase_report (
|
|
229
|
+
kind TEXT NOT NULL CHECK(kind IN ('curator_drift', 'design_doc_drift')),
|
|
230
|
+
phase INTEGER NOT NULL,
|
|
231
|
+
payload TEXT NOT NULL,
|
|
232
|
+
updated_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
233
|
+
PRIMARY KEY(kind, phase)
|
|
234
|
+
)`
|
|
235
|
+
},
|
|
236
|
+
{
|
|
237
|
+
version: 18,
|
|
238
|
+
name: "create_coordination_event",
|
|
239
|
+
sql: `CREATE TABLE IF NOT EXISTS coordination_event (
|
|
240
|
+
stream_id TEXT NOT NULL,
|
|
241
|
+
version INTEGER NOT NULL CHECK(version >= 1),
|
|
242
|
+
idempotency_key TEXT NOT NULL,
|
|
243
|
+
event_type TEXT NOT NULL,
|
|
244
|
+
generation INTEGER NOT NULL CHECK(generation >= 0),
|
|
245
|
+
payload TEXT NOT NULL,
|
|
246
|
+
created_at TEXT NOT NULL,
|
|
247
|
+
PRIMARY KEY(stream_id, version),
|
|
248
|
+
UNIQUE(stream_id, idempotency_key)
|
|
249
|
+
)`
|
|
250
|
+
},
|
|
251
|
+
{
|
|
252
|
+
version: 19,
|
|
253
|
+
name: "create_coordination_event_type_index",
|
|
254
|
+
sql: `CREATE INDEX IF NOT EXISTS idx_coordination_event_type
|
|
255
|
+
ON coordination_event(event_type, created_at)`
|
|
256
|
+
},
|
|
257
|
+
{
|
|
258
|
+
version: 20,
|
|
259
|
+
name: "create_coordination_state",
|
|
260
|
+
sql: `CREATE TABLE IF NOT EXISTS coordination_state (
|
|
261
|
+
namespace TEXT NOT NULL,
|
|
262
|
+
entity_key TEXT NOT NULL,
|
|
263
|
+
revision INTEGER NOT NULL CHECK(revision >= 1),
|
|
264
|
+
generation INTEGER NOT NULL CHECK(generation >= 0),
|
|
265
|
+
status TEXT NOT NULL,
|
|
266
|
+
payload TEXT NOT NULL,
|
|
267
|
+
updated_at TEXT NOT NULL,
|
|
268
|
+
PRIMARY KEY(namespace, entity_key)
|
|
269
|
+
)`
|
|
270
|
+
},
|
|
271
|
+
{
|
|
272
|
+
version: 21,
|
|
273
|
+
name: "create_coordination_state_status_index",
|
|
274
|
+
sql: `CREATE INDEX IF NOT EXISTS idx_coordination_state_status
|
|
275
|
+
ON coordination_state(namespace, status, updated_at)`
|
|
276
|
+
},
|
|
277
|
+
{
|
|
278
|
+
version: 22,
|
|
279
|
+
name: "create_coordination_lease",
|
|
280
|
+
sql: `CREATE TABLE IF NOT EXISTS coordination_lease (
|
|
281
|
+
namespace TEXT NOT NULL,
|
|
282
|
+
entity_key TEXT NOT NULL,
|
|
283
|
+
generation INTEGER NOT NULL CHECK(generation >= 0),
|
|
284
|
+
owner_token TEXT NOT NULL,
|
|
285
|
+
lease_expires_at TEXT NOT NULL,
|
|
286
|
+
payload TEXT NOT NULL,
|
|
287
|
+
updated_at TEXT NOT NULL,
|
|
288
|
+
PRIMARY KEY(namespace, entity_key)
|
|
289
|
+
)`
|
|
290
|
+
},
|
|
291
|
+
{
|
|
292
|
+
version: 23,
|
|
293
|
+
name: "create_coordination_lease_expiry_index",
|
|
294
|
+
sql: `CREATE INDEX IF NOT EXISTS idx_coordination_lease_expiry
|
|
295
|
+
ON coordination_lease(namespace, lease_expires_at)`
|
|
296
|
+
},
|
|
297
|
+
{
|
|
298
|
+
version: 24,
|
|
299
|
+
name: "create_coordination_import",
|
|
300
|
+
sql: `CREATE TABLE IF NOT EXISTS coordination_import (
|
|
301
|
+
source TEXT PRIMARY KEY,
|
|
302
|
+
imported_at TEXT NOT NULL,
|
|
303
|
+
source_digest TEXT NOT NULL,
|
|
304
|
+
row_count INTEGER NOT NULL CHECK(row_count >= 0)
|
|
305
|
+
)`
|
|
306
|
+
},
|
|
307
|
+
{
|
|
308
|
+
version: 25,
|
|
309
|
+
name: "create_coordination_event_created_index",
|
|
310
|
+
sql: `CREATE INDEX IF NOT EXISTS idx_coordination_event_created
|
|
311
|
+
ON coordination_event(created_at, stream_id, version)`
|
|
312
|
+
},
|
|
313
|
+
{
|
|
314
|
+
version: 26,
|
|
315
|
+
name: "create_coordination_event_idempotency_fence",
|
|
316
|
+
sql: `CREATE TABLE IF NOT EXISTS coordination_event_fence (
|
|
317
|
+
stream_id TEXT NOT NULL,
|
|
318
|
+
idempotency_key TEXT NOT NULL,
|
|
319
|
+
event_type TEXT NOT NULL,
|
|
320
|
+
generation INTEGER NOT NULL CHECK(generation >= 0),
|
|
321
|
+
payload_digest TEXT NOT NULL,
|
|
322
|
+
created_at TEXT NOT NULL,
|
|
323
|
+
PRIMARY KEY(stream_id, idempotency_key)
|
|
324
|
+
)`
|
|
325
|
+
},
|
|
326
|
+
{
|
|
327
|
+
version: 27,
|
|
328
|
+
name: "create_coordination_event_fence_stream_age_index",
|
|
329
|
+
sql: `CREATE INDEX IF NOT EXISTS idx_coordination_event_fence_stream_age
|
|
330
|
+
ON coordination_event_fence(stream_id, created_at)`
|
|
331
|
+
},
|
|
332
|
+
{
|
|
333
|
+
version: 28,
|
|
334
|
+
name: "create_coordination_event_fence_global_age_index",
|
|
335
|
+
sql: `CREATE INDEX IF NOT EXISTS idx_coordination_event_fence_global_age
|
|
336
|
+
ON coordination_event_fence(created_at, stream_id)`
|
|
337
|
+
}
|
|
338
|
+
];
|
|
339
|
+
var _projectDbs = new Map;
|
|
340
|
+
var _projectDbAliases = new Map;
|
|
341
|
+
var MAX_ALIASES_PER_PROJECT_DB = 128;
|
|
342
|
+
function lexicalAliasKey(directory) {
|
|
343
|
+
return lexicalRootAliasKey(directory);
|
|
344
|
+
}
|
|
345
|
+
function unbindAlias(alias, physicalKey) {
|
|
346
|
+
if (_projectDbAliases.get(alias) === physicalKey) {
|
|
347
|
+
_projectDbAliases.delete(alias);
|
|
348
|
+
}
|
|
349
|
+
_projectDbs.get(physicalKey)?.aliases.delete(alias);
|
|
350
|
+
}
|
|
351
|
+
function bindAlias(record, physicalKey, alias) {
|
|
352
|
+
const previousKey = _projectDbAliases.get(alias);
|
|
353
|
+
if (previousKey !== undefined && previousKey !== physicalKey) {
|
|
354
|
+
unbindAlias(alias, previousKey);
|
|
355
|
+
}
|
|
356
|
+
record.aliases.delete(alias);
|
|
357
|
+
record.aliases.add(alias);
|
|
358
|
+
_projectDbAliases.set(alias, physicalKey);
|
|
359
|
+
while (record.aliases.size > MAX_ALIASES_PER_PROJECT_DB) {
|
|
360
|
+
const evicted = [...record.aliases].find((candidate) => candidate !== record.primaryAlias && candidate !== alias);
|
|
361
|
+
if (evicted === undefined)
|
|
362
|
+
break;
|
|
363
|
+
unbindAlias(evicted, physicalKey);
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
function closeRecord(physicalKey, record) {
|
|
367
|
+
_internals.checkpointWalBestEffort(record.db);
|
|
368
|
+
record.db.close();
|
|
369
|
+
_projectDbs.delete(physicalKey);
|
|
370
|
+
for (const alias of [...record.aliases])
|
|
371
|
+
unbindAlias(alias, physicalKey);
|
|
372
|
+
}
|
|
373
|
+
function recordMigrationFailure(db, migration, err, markerDir) {
|
|
374
|
+
const errorText = String(err instanceof Error ? err.message : err).slice(0, MIGRATION_FAILURE_ERROR_MAX_CHARS);
|
|
375
|
+
try {
|
|
376
|
+
db.run("INSERT INTO migration_failures (version, name, error) VALUES (?, ?, ?)", [migration.version, migration.name, errorText]);
|
|
377
|
+
return;
|
|
378
|
+
} catch {}
|
|
379
|
+
if (!markerDir)
|
|
380
|
+
return;
|
|
381
|
+
try {
|
|
382
|
+
atomicWriteSwarmFileSync(join(markerDir, MIGRATION_FAILURE_MARKER), JSON.stringify({
|
|
383
|
+
schema_version: 1,
|
|
384
|
+
version: migration.version,
|
|
385
|
+
name: migration.name,
|
|
386
|
+
error: errorText,
|
|
387
|
+
failed_at: new Date().toISOString()
|
|
388
|
+
}, null, 2));
|
|
389
|
+
} catch {}
|
|
390
|
+
}
|
|
391
|
+
function removeMigrationFailureMarker(markerDir) {
|
|
392
|
+
const marker = join(markerDir, MIGRATION_FAILURE_MARKER);
|
|
393
|
+
if (!existsSync(marker))
|
|
394
|
+
return;
|
|
395
|
+
try {
|
|
396
|
+
unlinkSync(marker);
|
|
397
|
+
} catch {}
|
|
398
|
+
}
|
|
399
|
+
function isConcurrentMigrationApply(err) {
|
|
400
|
+
const msg = (err instanceof Error ? err.message : String(err)).toLowerCase();
|
|
401
|
+
if (msg.includes("unique constraint failed: schema_migrations"))
|
|
402
|
+
return true;
|
|
403
|
+
return msg.includes("already exists") && (msg.includes("table") || msg.includes("index") || msg.includes("trigger"));
|
|
404
|
+
}
|
|
405
|
+
function isSqliteBusy(err) {
|
|
406
|
+
const msg = (err instanceof Error ? err.message : String(err)).toLowerCase();
|
|
407
|
+
return msg.includes("sqlite_busy") || msg.includes("database is locked") || msg.includes("database is busy");
|
|
408
|
+
}
|
|
409
|
+
function isConcurrentOpenContention(err, dbPath) {
|
|
410
|
+
if (isSqliteBusy(err))
|
|
411
|
+
return true;
|
|
412
|
+
const msg = (err instanceof Error ? err.message : String(err)).toLowerCase();
|
|
413
|
+
return msg.includes("disk i/o error") && existsSync(dbPath);
|
|
414
|
+
}
|
|
415
|
+
function sleepSync(ms) {
|
|
416
|
+
try {
|
|
417
|
+
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
|
|
418
|
+
} catch {
|
|
419
|
+
const startedAt = Date.now();
|
|
420
|
+
while (Date.now() - startedAt < ms) {}
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
function currentVersionNowCovers(db, version) {
|
|
424
|
+
const row = db.query("SELECT MAX(version) as version FROM schema_migrations").get();
|
|
425
|
+
return (row?.version ?? 0) >= version;
|
|
426
|
+
}
|
|
427
|
+
function currentVersionNowCoversSafely(db, version) {
|
|
428
|
+
try {
|
|
429
|
+
return currentVersionNowCovers(db, version);
|
|
430
|
+
} catch {
|
|
431
|
+
return false;
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
function runProjectMigrations(db, markerDir) {
|
|
435
|
+
db.run(`CREATE TABLE IF NOT EXISTS schema_migrations (
|
|
436
|
+
version INTEGER PRIMARY KEY,
|
|
437
|
+
name TEXT NOT NULL,
|
|
438
|
+
applied_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
439
|
+
)`);
|
|
440
|
+
const row = db.query("SELECT MAX(version) as version FROM schema_migrations").get();
|
|
441
|
+
const currentVersion = row?.version ?? 0;
|
|
442
|
+
for (const migration of MIGRATIONS) {
|
|
443
|
+
if (migration.version <= currentVersion)
|
|
444
|
+
continue;
|
|
445
|
+
const apply = db.transaction(() => {
|
|
446
|
+
db.run(migration.sql);
|
|
447
|
+
db.run("INSERT INTO schema_migrations (version, name) VALUES (?, ?)", [
|
|
448
|
+
migration.version,
|
|
449
|
+
migration.name
|
|
450
|
+
]);
|
|
451
|
+
});
|
|
452
|
+
let lastError;
|
|
453
|
+
for (let attempt = 0;attempt <= MIGRATION_BUSY_RETRIES; attempt += 1) {
|
|
454
|
+
try {
|
|
455
|
+
apply();
|
|
456
|
+
lastError = undefined;
|
|
457
|
+
break;
|
|
458
|
+
} catch (err) {
|
|
459
|
+
lastError = err;
|
|
460
|
+
if (currentVersionNowCoversSafely(db, migration.version)) {
|
|
461
|
+
lastError = undefined;
|
|
462
|
+
break;
|
|
463
|
+
}
|
|
464
|
+
if (!isSqliteBusy(err) || attempt === MIGRATION_BUSY_RETRIES)
|
|
465
|
+
break;
|
|
466
|
+
sleepSync(10 * (attempt + 1));
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
if (lastError !== undefined) {
|
|
470
|
+
const err = lastError;
|
|
471
|
+
if (isConcurrentMigrationApply(err) && currentVersionNowCoversSafely(db, migration.version)) {
|
|
472
|
+
continue;
|
|
473
|
+
}
|
|
474
|
+
recordMigrationFailure(db, migration, err, markerDir);
|
|
475
|
+
throw new ProjectDbError("migration_failed", `swarm.db migration v${migration.version} (${migration.name}) failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
if (markerDir)
|
|
479
|
+
removeMigrationFailureMarker(markerDir);
|
|
480
|
+
}
|
|
481
|
+
function projectDbPath(directory) {
|
|
482
|
+
return join(canonicalRootKeyFresh(directory), ".swarm", "swarm.db");
|
|
483
|
+
}
|
|
484
|
+
function projectDbExists(directory) {
|
|
485
|
+
return existsSync(projectDbPath(directory));
|
|
486
|
+
}
|
|
487
|
+
function checkpointWalBestEffort(db) {
|
|
488
|
+
try {
|
|
489
|
+
const row = db.query("PRAGMA wal_checkpoint(TRUNCATE)").get();
|
|
490
|
+
if (row && row.busy === 1) {
|
|
491
|
+
db.run("PRAGMA wal_checkpoint(PASSIVE);");
|
|
492
|
+
}
|
|
493
|
+
} catch {
|
|
494
|
+
try {
|
|
495
|
+
db.run("PRAGMA wal_checkpoint(PASSIVE);");
|
|
496
|
+
} catch {}
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
function getProjectDb(directory) {
|
|
500
|
+
const materializedDirectory = resolve(directory);
|
|
501
|
+
try {
|
|
502
|
+
mkdirSync(materializedDirectory, { recursive: true });
|
|
503
|
+
} catch (err) {
|
|
504
|
+
throw new ProjectDbError("mkdir_failed", `Failed to create project root ${materializedDirectory}: ${err instanceof Error ? err.message : String(err)}`);
|
|
505
|
+
}
|
|
506
|
+
const key = canonicalRootKeyFresh(materializedDirectory);
|
|
507
|
+
const alias = lexicalAliasKey(directory);
|
|
508
|
+
const existing = _projectDbs.get(key);
|
|
509
|
+
if (existing) {
|
|
510
|
+
bindAlias(existing, key, alias);
|
|
511
|
+
return existing.db;
|
|
512
|
+
}
|
|
513
|
+
const swarmDir = join(key, ".swarm");
|
|
514
|
+
try {
|
|
515
|
+
mkdirSync(swarmDir, { recursive: true });
|
|
516
|
+
} catch (err) {
|
|
517
|
+
throw new ProjectDbError("mkdir_failed", `Failed to create .swarm/ for project ${key}: ${err instanceof Error ? err.message : String(err)}`);
|
|
518
|
+
}
|
|
519
|
+
let db;
|
|
520
|
+
try {
|
|
521
|
+
let Db;
|
|
522
|
+
try {
|
|
523
|
+
Db = loadDatabaseCtor();
|
|
524
|
+
} catch (err) {
|
|
525
|
+
throw new ProjectDbError("driver_unavailable", `Failed to resolve a SQLite driver for project ${key}: ${err instanceof Error ? err.message : String(err)}`);
|
|
526
|
+
}
|
|
527
|
+
const dbFile = join(swarmDir, "swarm.db");
|
|
528
|
+
for (let attempt = 0;attempt <= OPEN_CONTENTION_RETRIES; attempt += 1) {
|
|
529
|
+
db = new Db(dbFile);
|
|
530
|
+
try {
|
|
531
|
+
db.run("PRAGMA busy_timeout = 5000;");
|
|
532
|
+
db.run("PRAGMA journal_mode = WAL;");
|
|
533
|
+
db.run("PRAGMA synchronous = NORMAL;");
|
|
534
|
+
db.run("PRAGMA foreign_keys = ON;");
|
|
535
|
+
runProjectMigrations(db, swarmDir);
|
|
536
|
+
break;
|
|
537
|
+
} catch (err) {
|
|
538
|
+
try {
|
|
539
|
+
db.close();
|
|
540
|
+
} catch {}
|
|
541
|
+
db = undefined;
|
|
542
|
+
if (!isConcurrentOpenContention(err, dbFile) || attempt === OPEN_CONTENTION_RETRIES)
|
|
543
|
+
throw err;
|
|
544
|
+
sleepSync(25 * (attempt + 1));
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
if (!db)
|
|
548
|
+
throw new Error("SQLite initialization exhausted its busy retry budget");
|
|
549
|
+
} catch (err) {
|
|
550
|
+
if (db) {
|
|
551
|
+
try {
|
|
552
|
+
db.close();
|
|
553
|
+
} catch {}
|
|
554
|
+
}
|
|
555
|
+
if (err instanceof ProjectDbError)
|
|
556
|
+
throw err;
|
|
557
|
+
throw new ProjectDbError("open_failed", `Failed to open .swarm/swarm.db for project ${key}: ${err instanceof Error ? err.message : String(err)}`);
|
|
558
|
+
}
|
|
559
|
+
const record = {
|
|
560
|
+
db,
|
|
561
|
+
primaryAlias: alias,
|
|
562
|
+
aliases: new Set
|
|
563
|
+
};
|
|
564
|
+
_projectDbs.set(key, record);
|
|
565
|
+
bindAlias(record, key, alias);
|
|
566
|
+
return db;
|
|
567
|
+
}
|
|
568
|
+
function closeProjectDb(directory) {
|
|
569
|
+
const alias = lexicalAliasKey(directory);
|
|
570
|
+
const currentPath = canonicalExistingFilesystemPath(directory);
|
|
571
|
+
if (currentPath !== null) {
|
|
572
|
+
const currentKey = canonicalRootKeyFresh(directory);
|
|
573
|
+
const currentRecord = _projectDbs.get(currentKey);
|
|
574
|
+
if (currentRecord)
|
|
575
|
+
closeRecord(currentKey, currentRecord);
|
|
576
|
+
return;
|
|
577
|
+
}
|
|
578
|
+
const previousKey = _projectDbAliases.get(alias);
|
|
579
|
+
const previousRecord = previousKey === undefined ? undefined : _projectDbs.get(previousKey);
|
|
580
|
+
if (previousKey !== undefined && previousRecord) {
|
|
581
|
+
closeRecord(previousKey, previousRecord);
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
function closeAllProjectDbs() {
|
|
585
|
+
for (const record of _projectDbs.values()) {
|
|
586
|
+
try {
|
|
587
|
+
checkpointWalBestEffort(record.db);
|
|
588
|
+
record.db.close();
|
|
589
|
+
} catch {}
|
|
590
|
+
}
|
|
591
|
+
_projectDbs.clear();
|
|
592
|
+
_projectDbAliases.clear();
|
|
593
|
+
}
|
|
594
|
+
var _internals = {
|
|
595
|
+
checkpointWalBestEffort,
|
|
596
|
+
projectDbCount: () => _projectDbs.size,
|
|
597
|
+
projectDbAliasCount: () => _projectDbAliases.size
|
|
598
|
+
};
|
|
599
|
+
|
|
600
|
+
// src/db/durability.ts
|
|
601
|
+
var DURABILITY_CLASSES = {
|
|
602
|
+
qa_gate_profile: "full",
|
|
603
|
+
qa_gate_profile_identity: "full",
|
|
604
|
+
task_checkpoint_receipt: "full",
|
|
605
|
+
coordination_event: "full",
|
|
606
|
+
coordination_state: "full",
|
|
607
|
+
coordination_lease: "full",
|
|
608
|
+
coordination_import: "full",
|
|
609
|
+
insight_candidate: "normal",
|
|
610
|
+
phase_report: "normal",
|
|
611
|
+
project_constraints: "normal",
|
|
612
|
+
migration_failures: "normal",
|
|
613
|
+
schema_migrations: "normal"
|
|
614
|
+
};
|
|
615
|
+
function batchDurabilityClass(classes) {
|
|
616
|
+
for (const cls of classes) {
|
|
617
|
+
if (cls === "full")
|
|
618
|
+
return "full";
|
|
619
|
+
}
|
|
620
|
+
return "normal";
|
|
621
|
+
}
|
|
622
|
+
function applySynchronousForClass(db, cls) {
|
|
623
|
+
db.run(cls === "full" ? "PRAGMA synchronous = FULL;" : "PRAGMA synchronous = NORMAL;");
|
|
624
|
+
}
|
|
625
|
+
function withDurabilityClass(db, cls, fn) {
|
|
626
|
+
applySynchronousForClass(db, cls);
|
|
627
|
+
try {
|
|
628
|
+
return fn();
|
|
629
|
+
} finally {
|
|
630
|
+
applySynchronousForClass(db, "normal");
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
var transactionOwners = new WeakMap;
|
|
634
|
+
function withImmediateTransaction(db, cls, fn, hooks = {}) {
|
|
635
|
+
const owner = transactionOwners.get(db);
|
|
636
|
+
if (owner) {
|
|
637
|
+
if (cls === "full" && owner.durability !== "full") {
|
|
638
|
+
throw new Error("Cannot nest a FULL coordination transaction inside a NORMAL owner");
|
|
639
|
+
}
|
|
640
|
+
owner.depth += 1;
|
|
641
|
+
try {
|
|
642
|
+
return db.transaction(fn)();
|
|
643
|
+
} finally {
|
|
644
|
+
owner.depth -= 1;
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
if (db.inTransaction) {
|
|
648
|
+
throw new Error("Refusing to nest inside an unknown non-coordination-owned transaction");
|
|
649
|
+
}
|
|
650
|
+
hooks.beforeBegin?.(db);
|
|
651
|
+
applySynchronousForClass(db, cls);
|
|
652
|
+
transactionOwners.set(db, { durability: cls, depth: 1 });
|
|
653
|
+
db.run("BEGIN IMMEDIATE");
|
|
654
|
+
let committed = false;
|
|
655
|
+
try {
|
|
656
|
+
const result = fn();
|
|
657
|
+
hooks.beforeOuterCommit?.(db);
|
|
658
|
+
db.run("COMMIT");
|
|
659
|
+
committed = true;
|
|
660
|
+
hooks.afterOuterCommit?.(db);
|
|
661
|
+
return result;
|
|
662
|
+
} catch (err) {
|
|
663
|
+
if (!committed) {
|
|
664
|
+
try {
|
|
665
|
+
db.run("ROLLBACK");
|
|
666
|
+
} catch {}
|
|
667
|
+
}
|
|
668
|
+
throw err;
|
|
669
|
+
} finally {
|
|
670
|
+
transactionOwners.delete(db);
|
|
671
|
+
applySynchronousForClass(db, "normal");
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
export { DURABILITY_CLASSES, batchDurabilityClass, applySynchronousForClass, withDurabilityClass, withImmediateTransaction, DbWriteError, classifyDbWriteError, projectDbPath, projectDbExists, getProjectDb, closeProjectDb, closeAllProjectDbs };
|