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
|
@@ -1,10 +1,21 @@
|
|
|
1
1
|
// @bun
|
|
2
2
|
import {
|
|
3
3
|
validateSwarmPath
|
|
4
|
-
} from "./index-
|
|
4
|
+
} from "./index-qqq04n70.js";
|
|
5
|
+
import {
|
|
6
|
+
deleteCoordinationState,
|
|
7
|
+
importCoordinationOnce,
|
|
8
|
+
listCoordinationStates,
|
|
9
|
+
transitionCoordinationState,
|
|
10
|
+
withCoordinationTransaction
|
|
11
|
+
} from "./index-c45662wc.js";
|
|
12
|
+
import {
|
|
13
|
+
getProjectDb,
|
|
14
|
+
projectDbExists
|
|
15
|
+
} from "./index-es64amqt.js";
|
|
5
16
|
import {
|
|
6
17
|
observeStoreHealth
|
|
7
|
-
} from "./index-
|
|
18
|
+
} from "./index-eed0gkv6.js";
|
|
8
19
|
import {
|
|
9
20
|
canonicalRootKey,
|
|
10
21
|
canonicalRootKeyFresh,
|
|
@@ -12,13 +23,13 @@ import {
|
|
|
12
23
|
} from "./index-7qefgq4j.js";
|
|
13
24
|
import {
|
|
14
25
|
withEvidenceLock
|
|
15
|
-
} from "./index-
|
|
26
|
+
} from "./index-2bhz5k91.js";
|
|
16
27
|
import {
|
|
17
28
|
atomicWriteSwarmFileSync
|
|
18
|
-
} from "./index-
|
|
29
|
+
} from "./index-ewv8qa8q.js";
|
|
19
30
|
import {
|
|
20
31
|
telemetry
|
|
21
|
-
} from "./index-
|
|
32
|
+
} from "./index-r2p0pgst.js";
|
|
22
33
|
import {
|
|
23
34
|
exports_external,
|
|
24
35
|
init_zod
|
|
@@ -29,6 +40,7 @@ import {
|
|
|
29
40
|
|
|
30
41
|
// src/background/pr-subscriptions.ts
|
|
31
42
|
init_zod();
|
|
43
|
+
import { createHash } from "crypto";
|
|
32
44
|
import * as fs from "fs";
|
|
33
45
|
import * as path from "path";
|
|
34
46
|
var PR_SUBSCRIPTIONS_FILE = "pr-monitor/subscriptions.jsonl";
|
|
@@ -38,6 +50,7 @@ var LEGACY_ARCHIVE_FILE = "pr-monitor/subscriptions.legacy.jsonl";
|
|
|
38
50
|
var FOREIGN_CHECKPOINT_FILE = "pr-monitor/subscriptions.checkpoint.foreign.json";
|
|
39
51
|
var CORRUPT_CHECKPOINT_FILE = "pr-monitor/subscriptions.checkpoint.corrupt.json";
|
|
40
52
|
var FOREIGN_LEGACY_FILE = "pr-monitor/subscriptions.legacy.foreign.jsonl";
|
|
53
|
+
var PR_SUBSCRIPTION_COORDINATION_NAMESPACE = "background.pr-subscription";
|
|
41
54
|
var STORE_LOCK_AGENT = "pr-monitor";
|
|
42
55
|
var STORE_LOCK_TASK = "pr-subscriptions";
|
|
43
56
|
var PR_SUBSCRIPTION_LIMITS = {
|
|
@@ -154,12 +167,482 @@ function fileExistsStrict(filePath) {
|
|
|
154
167
|
throw err;
|
|
155
168
|
}
|
|
156
169
|
}
|
|
170
|
+
function importedShadowPath(filePath) {
|
|
171
|
+
return `${filePath}.imported`;
|
|
172
|
+
}
|
|
173
|
+
function projectionMarkerPath(filePath) {
|
|
174
|
+
return `${filePath}.sqlite-projection`;
|
|
175
|
+
}
|
|
157
176
|
function foreignSlotPath(directory) {
|
|
158
177
|
return validateSwarmPath(directory, FOREIGN_CHECKPOINT_FILE);
|
|
159
178
|
}
|
|
160
179
|
function corruptSlotPath(directory) {
|
|
161
180
|
return validateSwarmPath(directory, CORRUPT_CHECKPOINT_FILE);
|
|
162
181
|
}
|
|
182
|
+
function readCoordinationSubscriptions(directory) {
|
|
183
|
+
if (!projectDbExists(directory))
|
|
184
|
+
return null;
|
|
185
|
+
try {
|
|
186
|
+
return Object.fromEntries(listCoordinationStates(directory, PR_SUBSCRIPTION_COORDINATION_NAMESPACE).map((row) => {
|
|
187
|
+
const parsed = RecordSchema.safeParse(JSON.parse(row.payload));
|
|
188
|
+
if (!parsed.success || parsed.data.correlationId !== row.entityKey || row.generation !== 1 || parsed.data.status !== row.status) {
|
|
189
|
+
throw new Error(`PR subscription coordination row failed schema or authority binding validation for ${row.entityKey}`);
|
|
190
|
+
}
|
|
191
|
+
return [row.entityKey, parsed.data];
|
|
192
|
+
}));
|
|
193
|
+
} catch (error) {
|
|
194
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
195
|
+
log(`[pr-monitor] coordination read failed closed: ${detail}`);
|
|
196
|
+
throw new Error(`PR subscription coordination authority is unreadable: ${detail}`);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
function projectionSequence(directory) {
|
|
200
|
+
const read = readCheckpoint(directory);
|
|
201
|
+
return read.kind === "ok" ? read.value.sequence : 0;
|
|
202
|
+
}
|
|
203
|
+
function writeCoordinationProjection(directory, view, baseCheckpoint) {
|
|
204
|
+
ensureSwarmDir(directory);
|
|
205
|
+
const prepared = prepareCoordinationPersistence(directory, view, baseCheckpoint);
|
|
206
|
+
assertCoordinationProjectionCapacity(directory, prepared.view, prepared.checkpoint);
|
|
207
|
+
const sequence = projectionSequence(directory) + 1;
|
|
208
|
+
const checkpoint = prepared.checkpoint;
|
|
209
|
+
checkpoint.sequence = sequence;
|
|
210
|
+
checkpoint.updatedAt = Date.now();
|
|
211
|
+
_internals.writeCheckpointFile(directory, checkpoint);
|
|
212
|
+
fs.writeFileSync(projectionMarkerPath(checkpointPath(directory)), "", "utf-8");
|
|
213
|
+
const legacyPath = storePath(directory);
|
|
214
|
+
if (fs.existsSync(legacyPath)) {
|
|
215
|
+
const legacyPayload = Object.values(checkpoint.records).map((entry) => JSON.stringify(entry)).join(`
|
|
216
|
+
`) + (Object.keys(checkpoint.records).length > 0 ? `
|
|
217
|
+
` : "");
|
|
218
|
+
writeLegacyStoreFile(legacyPath, legacyPayload);
|
|
219
|
+
fs.writeFileSync(projectionMarkerPath(legacyPath), "", "utf-8");
|
|
220
|
+
if (checkpoint.migration?.done === true) {
|
|
221
|
+
try {
|
|
222
|
+
const legacyStat = fs.statSync(legacyPath);
|
|
223
|
+
checkpoint.migration.sourceBytes = legacyStat.size;
|
|
224
|
+
checkpoint.migration.scannedBytes = legacyStat.size;
|
|
225
|
+
checkpoint.migration.sourceMtimeMs = legacyStat.mtimeMs;
|
|
226
|
+
_internals.writeCheckpointFile(directory, checkpoint);
|
|
227
|
+
} catch {}
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
function writeLegacyStoreFile(targetPath, content) {
|
|
232
|
+
atomicWriteSwarmFileSync(targetPath, content);
|
|
233
|
+
}
|
|
234
|
+
function hasLegacyReconciliationArtifacts(directory) {
|
|
235
|
+
return [
|
|
236
|
+
legacyArchiveNextPath(directory),
|
|
237
|
+
legacyArchivePreviousPath(directory)
|
|
238
|
+
].some((filePath) => fileExistsStrict(filePath));
|
|
239
|
+
}
|
|
240
|
+
function loadLegacyPrSubscriptionViewStrict(directory) {
|
|
241
|
+
const checkpoint = readCheckpoint(directory);
|
|
242
|
+
if (checkpoint.kind === "invalid") {
|
|
243
|
+
throw new Error("legacy checkpoint is invalid");
|
|
244
|
+
}
|
|
245
|
+
if (checkpoint.kind === "ok") {
|
|
246
|
+
if (!sameProjectRoot(checkpoint.value.rootPath, directory)) {
|
|
247
|
+
throw new Error("legacy checkpoint belongs to a different project root");
|
|
248
|
+
}
|
|
249
|
+
const overlaid = overlayLegacy(directory, checkpoint.value);
|
|
250
|
+
if (overlaid.overLimit) {
|
|
251
|
+
throw new Error("legacy subscription source exceeds migration ceiling");
|
|
252
|
+
}
|
|
253
|
+
if (overlaid.aborted) {
|
|
254
|
+
throw new Error("legacy subscription source is unreadable");
|
|
255
|
+
}
|
|
256
|
+
return overlaid.view;
|
|
257
|
+
}
|
|
258
|
+
const legacy = storePath(directory);
|
|
259
|
+
const stat = fileSizeOrNull(legacy);
|
|
260
|
+
if (stat === null)
|
|
261
|
+
return {};
|
|
262
|
+
if (stat > PR_SUBSCRIPTION_LIMITS.legacySourceMaxBytes) {
|
|
263
|
+
throw new Error("legacy subscription source exceeds migration ceiling");
|
|
264
|
+
}
|
|
265
|
+
const fold = foldLegacyRegion(legacy, 0, Number.MAX_SAFE_INTEGER);
|
|
266
|
+
if (fold.ioError || fold.aborted) {
|
|
267
|
+
throw new Error("PR subscription legacy migration read failed (I/O error)");
|
|
268
|
+
}
|
|
269
|
+
const view = {};
|
|
270
|
+
mergeFoldedRecords(view, fold.folded);
|
|
271
|
+
return view;
|
|
272
|
+
}
|
|
273
|
+
function buildCoordinationProjectionCheckpoint(directory, view, baseCheckpoint) {
|
|
274
|
+
const records = { ...view };
|
|
275
|
+
const terminals = Object.values(records).filter((entry) => entry.status !== "active");
|
|
276
|
+
const current = readCheckpoint(directory);
|
|
277
|
+
const checkpoint = baseCheckpoint ? cloneCheckpoint(baseCheckpoint) : current.kind === "ok" && sameProjectRoot(current.value.rootPath, directory) ? cloneCheckpoint(current.value) : freshCheckpoint(directory);
|
|
278
|
+
const priorSummary = { ...checkpoint.terminalSummary };
|
|
279
|
+
checkpoint.records = records;
|
|
280
|
+
checkpoint.terminalSummary = {
|
|
281
|
+
removed: priorSummary.removed,
|
|
282
|
+
expired: priorSummary.expired,
|
|
283
|
+
lastTerminalAt: terminals.length ? Math.max(priorSummary.lastTerminalAt ?? 0, ...terminals.map((entry) => entry.updatedAt)) : priorSummary.lastTerminalAt
|
|
284
|
+
};
|
|
285
|
+
checkpoint.rootPath = path.resolve(directory);
|
|
286
|
+
return checkpoint;
|
|
287
|
+
}
|
|
288
|
+
function assertCoordinationProjectionCapacity(directory, view, baseCheckpoint) {
|
|
289
|
+
const checkpoint = buildCoordinationProjectionCheckpoint(directory, view, baseCheckpoint);
|
|
290
|
+
const recordCount = Object.keys(checkpoint.records).length;
|
|
291
|
+
if (recordCount > PR_SUBSCRIPTION_LIMITS.maxCheckpointRecords) {
|
|
292
|
+
throw new Error(`PR subscription store over checkpoint capacity: ${recordCount} records > ${PR_SUBSCRIPTION_LIMITS.maxCheckpointRecords}. The folded state exceeds the bounded checkpoint \u2014 archive or split .swarm/pr-monitor/subscriptions.jsonl (subscriptions can be re-created with /swarm pr subscribe), then remove it.`);
|
|
293
|
+
}
|
|
294
|
+
const contentBytes = Buffer.byteLength(`${JSON.stringify(checkpoint)}
|
|
295
|
+
`, "utf-8");
|
|
296
|
+
if (contentBytes > PR_SUBSCRIPTION_LIMITS.checkpointHardReadBytes) {
|
|
297
|
+
throw new Error(`PR subscription store over checkpoint capacity: ${contentBytes} bytes > ${PR_SUBSCRIPTION_LIMITS.checkpointHardReadBytes}. The folded state exceeds the bounded checkpoint \u2014 archive or split .swarm/pr-monitor/subscriptions.jsonl (subscriptions can be re-created with /swarm pr subscribe), then remove it.`);
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
function prepareCoordinationPersistence(directory, view, baseCheckpoint) {
|
|
301
|
+
const preparedView = { ...view };
|
|
302
|
+
const checkpoint = buildCoordinationProjectionCheckpoint(directory, preparedView, baseCheckpoint);
|
|
303
|
+
const compacted = compactTerminalRecords(preparedView, checkpoint) !== null;
|
|
304
|
+
checkpoint.records = { ...preparedView };
|
|
305
|
+
return { view: preparedView, checkpoint, compacted };
|
|
306
|
+
}
|
|
307
|
+
function coordinationShadowDigest(directory) {
|
|
308
|
+
const hash = createHash("sha256");
|
|
309
|
+
for (const filePath of [
|
|
310
|
+
storePath(directory),
|
|
311
|
+
checkpointPath(directory),
|
|
312
|
+
auditPath(directory)
|
|
313
|
+
]) {
|
|
314
|
+
if (!fileExistsStrict(filePath))
|
|
315
|
+
continue;
|
|
316
|
+
hash.update(path.basename(filePath));
|
|
317
|
+
hash.update(fs.readFileSync(filePath));
|
|
318
|
+
}
|
|
319
|
+
return hash.digest("hex");
|
|
320
|
+
}
|
|
321
|
+
function isDeepStrictEqualByJson(left, right) {
|
|
322
|
+
return JSON.stringify(left) === JSON.stringify(right);
|
|
323
|
+
}
|
|
324
|
+
function coordinationProjectionMatches(directory, view) {
|
|
325
|
+
try {
|
|
326
|
+
const legacyPath = storePath(directory);
|
|
327
|
+
if (!fileExistsStrict(legacyPath)) {
|
|
328
|
+
const checkpointRead = readCheckpoint(directory);
|
|
329
|
+
if (checkpointRead.kind !== "ok")
|
|
330
|
+
return false;
|
|
331
|
+
const prepared = prepareCoordinationPersistence(directory, view, checkpointRead.value).checkpoint;
|
|
332
|
+
return isDeepStrictEqualByJson(checkpointRead.value.records, prepared.records) && isDeepStrictEqualByJson(checkpointRead.value.terminalSummary, prepared.terminalSummary);
|
|
333
|
+
}
|
|
334
|
+
return isDeepStrictEqualByJson(loadLegacyPrSubscriptionViewStrict(directory), view);
|
|
335
|
+
} catch {
|
|
336
|
+
return false;
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
function coordinationImportShadowState(directory) {
|
|
340
|
+
if (!projectDbExists(directory))
|
|
341
|
+
return "absent";
|
|
342
|
+
const statement = getProjectDb(directory).prepare("SELECT source_digest FROM coordination_import WHERE source = ?");
|
|
343
|
+
try {
|
|
344
|
+
const row = statement.get(PR_SUBSCRIPTIONS_CHECKPOINT_FILE);
|
|
345
|
+
if (!row)
|
|
346
|
+
return "absent";
|
|
347
|
+
return row.source_digest === coordinationShadowDigest(directory) ? "matches" : "mismatch";
|
|
348
|
+
} finally {
|
|
349
|
+
statement.finalize();
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
function legacyProjectionFingerprintMatches(directory, checkpoint) {
|
|
353
|
+
const migration = checkpoint.migration;
|
|
354
|
+
const legacyPath = storePath(directory);
|
|
355
|
+
if (!fileExistsStrict(legacyPath))
|
|
356
|
+
return true;
|
|
357
|
+
if (!fileExistsStrict(projectionMarkerPath(legacyPath)) || !migration?.done) {
|
|
358
|
+
return false;
|
|
359
|
+
}
|
|
360
|
+
try {
|
|
361
|
+
const stat = fs.statSync(legacyPath);
|
|
362
|
+
return stat.size === migration.sourceBytes && stat.mtimeMs === migration.sourceMtimeMs;
|
|
363
|
+
} catch {
|
|
364
|
+
return false;
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
function archiveShadowFileBestEffort(filePath) {
|
|
368
|
+
if (!fileExistsStrict(filePath))
|
|
369
|
+
return;
|
|
370
|
+
const importedPath = importedShadowPath(filePath);
|
|
371
|
+
if (fileExistsStrict(importedPath))
|
|
372
|
+
return;
|
|
373
|
+
try {
|
|
374
|
+
renameWithRetry(filePath, importedPath);
|
|
375
|
+
} catch (error) {
|
|
376
|
+
log(`[pr-monitor] shadow archive failed for ${path.basename(filePath)}: ${error instanceof Error ? error.message : String(error)}`);
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
function retireAuthoritativeLegacyShadow(directory) {
|
|
380
|
+
const legacyPath = storePath(directory);
|
|
381
|
+
if (!fileExistsStrict(legacyPath))
|
|
382
|
+
return true;
|
|
383
|
+
if (fileExistsStrict(projectionMarkerPath(legacyPath)))
|
|
384
|
+
return true;
|
|
385
|
+
const importedPath = importedShadowPath(legacyPath);
|
|
386
|
+
try {
|
|
387
|
+
if (!fileExistsStrict(importedPath)) {
|
|
388
|
+
_internals.renameWithRetry(legacyPath, importedPath);
|
|
389
|
+
} else {
|
|
390
|
+
fs.unlinkSync(legacyPath);
|
|
391
|
+
}
|
|
392
|
+
return !fileExistsStrict(legacyPath);
|
|
393
|
+
} catch (error) {
|
|
394
|
+
log(`[pr-monitor] authoritative legacy shadow retirement failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
395
|
+
return false;
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
function ensurePrSubscriptionCoordinationImported(directory, createIfNeeded) {
|
|
399
|
+
if (!createIfNeeded && !projectDbExists(directory))
|
|
400
|
+
return false;
|
|
401
|
+
const coordinationView = readCoordinationSubscriptions(directory);
|
|
402
|
+
if (coordinationView !== null && Object.keys(coordinationView).length > 0) {
|
|
403
|
+
const checkpointRead2 = readCheckpoint(directory);
|
|
404
|
+
const migrationPending = checkpointRead2.kind === "ok" && sameProjectRoot(checkpointRead2.value.rootPath, directory) && checkpointRead2.value.migration?.done === false;
|
|
405
|
+
if (migrationPending)
|
|
406
|
+
return false;
|
|
407
|
+
const legacyPath = storePath(directory);
|
|
408
|
+
if (fileExistsStrict(legacyPath) && !fileExistsStrict(projectionMarkerPath(legacyPath))) {
|
|
409
|
+
let legacyStat2 = null;
|
|
410
|
+
try {
|
|
411
|
+
legacyStat2 = _internals.statSync(legacyPath);
|
|
412
|
+
} catch (error) {
|
|
413
|
+
if (error.code !== "ENOENT") {
|
|
414
|
+
throw new Error(`PR subscription legacy migration read failed (I/O error); refusing mutation: ${error instanceof Error ? error.message : String(error)}`);
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
if (legacyStat2 && legacyStat2.size > PR_SUBSCRIPTION_LIMITS.migrationMaxBytesPerOperation && (checkpointRead2.kind !== "ok" || checkpointRead2.value.migration === null || checkpointRead2.value.migration.done === false)) {
|
|
418
|
+
return false;
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
if (fileExistsStrict(legacyPath)) {
|
|
422
|
+
let legacyStat2 = null;
|
|
423
|
+
try {
|
|
424
|
+
legacyStat2 = _internals.statSync(legacyPath);
|
|
425
|
+
} catch (error) {
|
|
426
|
+
if (error.code !== "ENOENT") {
|
|
427
|
+
throw new Error(`PR subscription legacy migration read failed (I/O error); refusing mutation: ${error instanceof Error ? error.message : String(error)}`);
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
if (legacyStat2 && legacyStat2.size > PR_SUBSCRIPTION_LIMITS.migrationMaxBytesPerOperation && (checkpointRead2.kind !== "ok" || checkpointRead2.value.migration === null || checkpointRead2.value.migration.done === false)) {
|
|
431
|
+
return false;
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
const projectionNeedsRepair = !fileExistsStrict(projectionMarkerPath(checkpointPath(directory))) || fileExistsStrict(legacyPath) && !fileExistsStrict(projectionMarkerPath(legacyPath)) || !coordinationProjectionMatches(directory, coordinationView);
|
|
435
|
+
if (projectionNeedsRepair) {
|
|
436
|
+
const checkpointProjectionPublished = fileExistsStrict(projectionMarkerPath(checkpointPath(directory)));
|
|
437
|
+
const settledProjection = checkpointProjectionPublished && checkpointRead2.kind === "ok" && (checkpointRead2.value.migration === null || checkpointRead2.value.migration.archived === true);
|
|
438
|
+
const importShadowState = coordinationImportShadowState(directory);
|
|
439
|
+
if (importShadowState === "mismatch" && !settledProjection || !checkpointProjectionPublished && importShadowState !== "matches" || hasLegacyReconciliationArtifacts(directory)) {
|
|
440
|
+
return false;
|
|
441
|
+
}
|
|
442
|
+
for (const filePath of [
|
|
443
|
+
storePath(directory),
|
|
444
|
+
checkpointPath(directory),
|
|
445
|
+
auditPath(directory)
|
|
446
|
+
]) {
|
|
447
|
+
archiveShadowFileBestEffort(filePath);
|
|
448
|
+
}
|
|
449
|
+
writeCoordinationProjection(directory, coordinationView);
|
|
450
|
+
}
|
|
451
|
+
return true;
|
|
452
|
+
}
|
|
453
|
+
const hasLegacyArtifacts = [
|
|
454
|
+
storePath(directory),
|
|
455
|
+
checkpointPath(directory),
|
|
456
|
+
auditPath(directory),
|
|
457
|
+
legacyArchivePath(directory)
|
|
458
|
+
].some((filePath) => fileExistsStrict(filePath));
|
|
459
|
+
if (!hasLegacyArtifacts)
|
|
460
|
+
return createIfNeeded || projectDbExists(directory);
|
|
461
|
+
const checkpointRead = readCheckpoint(directory);
|
|
462
|
+
const baseCheckpoint = checkpointRead.kind === "ok" && sameProjectRoot(checkpointRead.value.rootPath, directory) ? cloneCheckpoint(checkpointRead.value) : undefined;
|
|
463
|
+
if (checkpointRead.kind === "invalid" || checkpointRead.kind === "ok" && !sameProjectRoot(checkpointRead.value.rootPath, directory)) {
|
|
464
|
+
return false;
|
|
465
|
+
}
|
|
466
|
+
let legacyStat = null;
|
|
467
|
+
try {
|
|
468
|
+
legacyStat = _internals.statSync(storePath(directory));
|
|
469
|
+
} catch (error) {
|
|
470
|
+
if (error.code !== "ENOENT") {
|
|
471
|
+
throw new Error(`PR subscription legacy migration read failed (I/O error); refusing mutation: ${error instanceof Error ? error.message : String(error)}`);
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
if (legacyStat !== null && legacyStat.size > PR_SUBSCRIPTION_LIMITS.migrationMaxBytesPerOperation && (checkpointRead.kind !== "ok" || checkpointRead.value.migration === null || checkpointRead.value.migration.done === false)) {
|
|
475
|
+
return false;
|
|
476
|
+
}
|
|
477
|
+
const legacyView = loadLegacyPrSubscriptionViewStrict(directory);
|
|
478
|
+
const preparedImport = prepareCoordinationPersistence(directory, legacyView, baseCheckpoint);
|
|
479
|
+
if (legacyStat) {
|
|
480
|
+
preparedImport.checkpoint.migration = {
|
|
481
|
+
scannedBytes: legacyStat.size,
|
|
482
|
+
sourceBytes: legacyStat.size,
|
|
483
|
+
sourceMtimeMs: legacyStat.mtimeMs,
|
|
484
|
+
corruptLines: preparedImport.checkpoint.maintenance.corruptLegacyRecords,
|
|
485
|
+
discardingOversizeLine: false,
|
|
486
|
+
baselineRecords: {},
|
|
487
|
+
baselineTerminalSummary: {
|
|
488
|
+
removed: 0,
|
|
489
|
+
expired: 0,
|
|
490
|
+
lastTerminalAt: null
|
|
491
|
+
},
|
|
492
|
+
done: true,
|
|
493
|
+
archived: false,
|
|
494
|
+
startedAt: Date.now()
|
|
495
|
+
};
|
|
496
|
+
}
|
|
497
|
+
assertCoordinationProjectionCapacity(directory, preparedImport.view, preparedImport.checkpoint);
|
|
498
|
+
const outcome = importCoordinationOnce(directory, {
|
|
499
|
+
source: PR_SUBSCRIPTIONS_CHECKPOINT_FILE,
|
|
500
|
+
sourceDigest: coordinationShadowDigest(directory),
|
|
501
|
+
rowCount: Object.keys(preparedImport.view).length,
|
|
502
|
+
emptyNamespace: PR_SUBSCRIPTION_COORDINATION_NAMESPACE
|
|
503
|
+
}, () => {
|
|
504
|
+
for (const record of Object.values(preparedImport.view)) {
|
|
505
|
+
const payload = JSON.stringify(record);
|
|
506
|
+
const result = transitionCoordinationState(directory, {
|
|
507
|
+
namespace: PR_SUBSCRIPTION_COORDINATION_NAMESPACE,
|
|
508
|
+
entityKey: record.correlationId,
|
|
509
|
+
expectedRevision: null,
|
|
510
|
+
generation: 1,
|
|
511
|
+
status: record.status,
|
|
512
|
+
payload
|
|
513
|
+
});
|
|
514
|
+
if (result.outcome !== "applied") {
|
|
515
|
+
throw new Error(`subscription import failed for ${record.correlationId}: ${result.outcome}`);
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
});
|
|
519
|
+
const importedView = readCoordinationSubscriptions(directory) ?? preparedImport.view;
|
|
520
|
+
if (outcome !== "state_exists") {
|
|
521
|
+
for (const filePath of [
|
|
522
|
+
storePath(directory),
|
|
523
|
+
checkpointPath(directory),
|
|
524
|
+
auditPath(directory),
|
|
525
|
+
legacyArchivePath(directory)
|
|
526
|
+
]) {
|
|
527
|
+
archiveShadowFileBestEffort(filePath);
|
|
528
|
+
}
|
|
529
|
+
const importedLegacy = importedShadowPath(storePath(directory));
|
|
530
|
+
if (legacyStat) {
|
|
531
|
+
if (!fileExistsStrict(storePath(directory)) && fileExistsStrict(importedLegacy)) {
|
|
532
|
+
fs.copyFileSync(importedLegacy, storePath(directory));
|
|
533
|
+
} else if (fileExistsStrict(storePath(directory)) && !fileExistsStrict(importedLegacy)) {
|
|
534
|
+
fs.copyFileSync(storePath(directory), importedLegacy);
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
writeCoordinationProjection(directory, importedView, preparedImport.checkpoint);
|
|
539
|
+
if (outcome !== "state_exists" && preparedImport.checkpoint.migration) {
|
|
540
|
+
if (fileExistsStrict(storePath(directory))) {
|
|
541
|
+
const projected = _internals.statSync(storePath(directory));
|
|
542
|
+
legacyStat = projected;
|
|
543
|
+
preparedImport.checkpoint.migration.sourceBytes = projected.size;
|
|
544
|
+
preparedImport.checkpoint.migration.scannedBytes = projected.size;
|
|
545
|
+
preparedImport.checkpoint.migration.sourceMtimeMs = projected.mtimeMs;
|
|
546
|
+
}
|
|
547
|
+
const archiveEvent = maybeArchiveLegacy(directory, preparedImport.checkpoint);
|
|
548
|
+
if (archiveEvent) {
|
|
549
|
+
preparedImport.checkpoint.sequence += 1;
|
|
550
|
+
preparedImport.checkpoint.updatedAt = Date.now();
|
|
551
|
+
_internals.writeCheckpointFile(directory, preparedImport.checkpoint);
|
|
552
|
+
flushAuditEvents(directory, [{ kind: "migrate-complete" }, archiveEvent], preparedImport.checkpoint.sequence, preparedImport.checkpoint.maintenance);
|
|
553
|
+
emitHealthTelemetry(directory, "migrate-complete", preparedImport.checkpoint);
|
|
554
|
+
emitHealthTelemetry(directory, "archive", preparedImport.checkpoint);
|
|
555
|
+
} else {
|
|
556
|
+
if (legacyStat) {
|
|
557
|
+
try {
|
|
558
|
+
const currentLegacyStat = _internals.statSync(storePath(directory));
|
|
559
|
+
if (currentLegacyStat.size !== legacyStat.size || currentLegacyStat.mtimeMs !== legacyStat.mtimeMs) {
|
|
560
|
+
try {
|
|
561
|
+
fs.unlinkSync(projectionMarkerPath(storePath(directory)));
|
|
562
|
+
} catch (error) {
|
|
563
|
+
if (error.code !== "ENOENT") {
|
|
564
|
+
return false;
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
return false;
|
|
568
|
+
}
|
|
569
|
+
} catch {
|
|
570
|
+
return false;
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
flushAuditEvents(directory, [{ kind: "migrate-complete" }], preparedImport.checkpoint.sequence, preparedImport.checkpoint.maintenance);
|
|
574
|
+
emitHealthTelemetry(directory, "migrate-complete", preparedImport.checkpoint);
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
return true;
|
|
578
|
+
}
|
|
579
|
+
function canUseCoordinationWritePath(directory) {
|
|
580
|
+
if (hasLegacyReconciliationArtifacts(directory))
|
|
581
|
+
return false;
|
|
582
|
+
const checkpoint = readCheckpoint(directory);
|
|
583
|
+
if (checkpoint.kind === "ok" && checkpoint.value.migration?.done === false) {
|
|
584
|
+
return false;
|
|
585
|
+
}
|
|
586
|
+
const ensured = ensurePrSubscriptionCoordinationImported(directory, true);
|
|
587
|
+
return ensured;
|
|
588
|
+
}
|
|
589
|
+
function persistCoordinationSubscriptions(directory, view, writeProjection = true, events = []) {
|
|
590
|
+
const prepared = prepareCoordinationPersistence(directory, view);
|
|
591
|
+
if (writeProjection) {
|
|
592
|
+
writeCoordinationProjection(directory, prepared.view, prepared.checkpoint);
|
|
593
|
+
}
|
|
594
|
+
withCoordinationTransaction(directory, () => {
|
|
595
|
+
const current = new Map(listCoordinationStates(directory, PR_SUBSCRIPTION_COORDINATION_NAMESPACE).map((row) => [row.entityKey, row]));
|
|
596
|
+
const nextIds = new Set(Object.keys(prepared.view));
|
|
597
|
+
for (const [entityKey, row] of current) {
|
|
598
|
+
if (nextIds.has(entityKey))
|
|
599
|
+
continue;
|
|
600
|
+
const deleted = deleteCoordinationState(directory, PR_SUBSCRIPTION_COORDINATION_NAMESPACE, entityKey, row.revision);
|
|
601
|
+
if (!deleted) {
|
|
602
|
+
throw new Error(`subscription delete conflict for ${entityKey}`);
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
for (const record of Object.values(prepared.view)) {
|
|
606
|
+
const row = current.get(record.correlationId);
|
|
607
|
+
const payload = JSON.stringify(record);
|
|
608
|
+
const result = transitionCoordinationState(directory, {
|
|
609
|
+
namespace: PR_SUBSCRIPTION_COORDINATION_NAMESPACE,
|
|
610
|
+
entityKey: record.correlationId,
|
|
611
|
+
expectedRevision: row?.revision ?? null,
|
|
612
|
+
generation: 1,
|
|
613
|
+
status: record.status,
|
|
614
|
+
payload,
|
|
615
|
+
event: {
|
|
616
|
+
streamId: record.correlationId,
|
|
617
|
+
idempotencyKey: createHash("sha256").update(payload).digest("hex"),
|
|
618
|
+
eventType: record.status,
|
|
619
|
+
payload
|
|
620
|
+
}
|
|
621
|
+
});
|
|
622
|
+
if (result.outcome !== "applied" && result.outcome !== "duplicate") {
|
|
623
|
+
throw new Error(`subscription write failed for ${record.correlationId}: ${result.outcome}`);
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
});
|
|
627
|
+
if (events.length === 0)
|
|
628
|
+
return;
|
|
629
|
+
const checkpoint = readCheckpoint(directory);
|
|
630
|
+
if (checkpoint.kind !== "ok")
|
|
631
|
+
return;
|
|
632
|
+
const auditCompaction = prepareAuditCompaction(directory);
|
|
633
|
+
if (applyAuditCompaction(directory, auditCompaction) && auditCompaction) {
|
|
634
|
+
checkpoint.value.maintenance.droppedAuditTransitions += auditCompaction.dropped;
|
|
635
|
+
try {
|
|
636
|
+
_internals.writeCheckpointFile(directory, checkpoint.value);
|
|
637
|
+
} catch (error) {
|
|
638
|
+
log(`[pr-monitor] audit accounting write deferred: ${storageErrorCode(error)}`);
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
flushAuditEvents(directory, events, checkpoint.value.sequence, checkpoint.value.maintenance);
|
|
642
|
+
if (prepared.compacted) {
|
|
643
|
+
emitHealthTelemetry(directory, "compact", prepared.checkpoint);
|
|
644
|
+
}
|
|
645
|
+
}
|
|
163
646
|
function foreignLegacySlotPath(directory) {
|
|
164
647
|
return validateSwarmPath(directory, FOREIGN_LEGACY_FILE);
|
|
165
648
|
}
|
|
@@ -508,6 +991,25 @@ function validateRecord(record) {
|
|
|
508
991
|
throw new Error(`Invalid subscription record: ${result.error.message}`);
|
|
509
992
|
}
|
|
510
993
|
}
|
|
994
|
+
function snapshotUpdateHasChanges(match, updates) {
|
|
995
|
+
const immutableKeys = new Set([
|
|
996
|
+
"correlationId",
|
|
997
|
+
"sessionID",
|
|
998
|
+
"prNumber",
|
|
999
|
+
"repoFullName",
|
|
1000
|
+
"prUrl",
|
|
1001
|
+
"createdAt",
|
|
1002
|
+
"updatedAt"
|
|
1003
|
+
]);
|
|
1004
|
+
for (const [key, value] of Object.entries(updates)) {
|
|
1005
|
+
if (immutableKeys.has(key))
|
|
1006
|
+
continue;
|
|
1007
|
+
if (!Object.is(match[key], value)) {
|
|
1008
|
+
return true;
|
|
1009
|
+
}
|
|
1010
|
+
}
|
|
1011
|
+
return false;
|
|
1012
|
+
}
|
|
511
1013
|
function writeCheckpointFile(directory, checkpoint) {
|
|
512
1014
|
const recordCount = Object.keys(checkpoint.records).length;
|
|
513
1015
|
if (recordCount > PR_SUBSCRIPTION_LIMITS.maxCheckpointRecords) {
|
|
@@ -837,8 +1339,7 @@ function reconcileLegacyArchiveStaging(directory, migration) {
|
|
|
837
1339
|
archiveExists = true;
|
|
838
1340
|
previousExists = false;
|
|
839
1341
|
if (nextExists) {
|
|
840
|
-
|
|
841
|
-
migration.archived = false;
|
|
1342
|
+
fs.unlinkSync(next);
|
|
842
1343
|
legacyExists = true;
|
|
843
1344
|
nextExists = false;
|
|
844
1345
|
}
|
|
@@ -995,11 +1496,66 @@ function overlayLegacy(directory, checkpoint) {
|
|
|
995
1496
|
};
|
|
996
1497
|
}
|
|
997
1498
|
async function loadViewForRead(directory) {
|
|
1499
|
+
if (ensurePrSubscriptionCoordinationImported(directory, false)) {
|
|
1500
|
+
const coordinationView = readCoordinationSubscriptions(directory) ?? {};
|
|
1501
|
+
const checkpointRead = readCheckpoint(directory);
|
|
1502
|
+
if (checkpointRead.kind === "ok" && sameProjectRoot(checkpointRead.value.rootPath, directory) && checkpointRead.value.migration?.done === false) {
|
|
1503
|
+
const pendingCheckpoint = cloneCheckpoint(checkpointRead.value);
|
|
1504
|
+
pendingCheckpoint.records = {
|
|
1505
|
+
...pendingCheckpoint.records,
|
|
1506
|
+
...coordinationView
|
|
1507
|
+
};
|
|
1508
|
+
const overlaid = overlayLegacy(directory, pendingCheckpoint);
|
|
1509
|
+
return {
|
|
1510
|
+
view: overlaid.view,
|
|
1511
|
+
recoverySource: overlaid.usedLegacy ? "checkpoint+legacy" : "checkpoint"
|
|
1512
|
+
};
|
|
1513
|
+
}
|
|
1514
|
+
return {
|
|
1515
|
+
view: coordinationView,
|
|
1516
|
+
recoverySource: "checkpoint"
|
|
1517
|
+
};
|
|
1518
|
+
}
|
|
998
1519
|
const read = readCheckpoint(directory);
|
|
1520
|
+
const pendingCoordination = readCoordinationSubscriptions(directory);
|
|
1521
|
+
if (read.kind === "ok" && sameProjectRoot(read.value.rootPath, directory) && read.value.migration?.done === false && pendingCoordination !== null && Object.keys(pendingCoordination).length > 0) {
|
|
1522
|
+
const pendingCheckpoint = cloneCheckpoint(read.value);
|
|
1523
|
+
pendingCheckpoint.records = {
|
|
1524
|
+
...pendingCheckpoint.records,
|
|
1525
|
+
...pendingCoordination
|
|
1526
|
+
};
|
|
1527
|
+
const overlaid = overlayLegacy(directory, pendingCheckpoint);
|
|
1528
|
+
return {
|
|
1529
|
+
view: overlaid.view,
|
|
1530
|
+
recoverySource: overlaid.usedLegacy ? "checkpoint+legacy" : "checkpoint"
|
|
1531
|
+
};
|
|
1532
|
+
}
|
|
999
1533
|
if (read.kind === "ok") {
|
|
1000
1534
|
if (!sameProjectRoot(read.value.rootPath, directory)) {
|
|
1001
1535
|
return { view: {}, recoverySource: "foreign" };
|
|
1002
1536
|
}
|
|
1537
|
+
if (read.value.migration?.done === true) {
|
|
1538
|
+
if (!legacyProjectionFingerprintMatches(directory, read.value)) {
|
|
1539
|
+
if (fileExistsStrict(legacyArchivePath(directory))) {
|
|
1540
|
+
retireAuthoritativeLegacyShadow(directory);
|
|
1541
|
+
return {
|
|
1542
|
+
view: { ...read.value.records },
|
|
1543
|
+
recoverySource: "checkpoint"
|
|
1544
|
+
};
|
|
1545
|
+
}
|
|
1546
|
+
const overlaid2 = overlayLegacy(directory, read.value);
|
|
1547
|
+
if (overlaid2.usedLegacy && !overlaid2.aborted) {
|
|
1548
|
+
return {
|
|
1549
|
+
view: overlaid2.view,
|
|
1550
|
+
recoverySource: "checkpoint+legacy"
|
|
1551
|
+
};
|
|
1552
|
+
}
|
|
1553
|
+
}
|
|
1554
|
+
return {
|
|
1555
|
+
view: { ...read.value.records },
|
|
1556
|
+
recoverySource: "checkpoint"
|
|
1557
|
+
};
|
|
1558
|
+
}
|
|
1003
1559
|
const overlaid = overlayLegacy(directory, read.value);
|
|
1004
1560
|
return {
|
|
1005
1561
|
view: overlaid.view,
|
|
@@ -1123,6 +1679,7 @@ function loadViewForWrite(directory) {
|
|
|
1123
1679
|
let reboundForeign = false;
|
|
1124
1680
|
let checkpoint;
|
|
1125
1681
|
const read = readCheckpoint(directory);
|
|
1682
|
+
let archiveDeferredForExternalRewrite = false;
|
|
1126
1683
|
if (read.kind === "ok" && sameProjectRoot(read.value.rootPath, directory)) {
|
|
1127
1684
|
checkpoint = cloneCheckpoint(read.value);
|
|
1128
1685
|
} else if (read.kind === "ok") {
|
|
@@ -1188,7 +1745,9 @@ function loadViewForWrite(directory) {
|
|
|
1188
1745
|
legacyStat = null;
|
|
1189
1746
|
if (legacyStat === null) {
|
|
1190
1747
|
view = { ...checkpoint.records };
|
|
1191
|
-
|
|
1748
|
+
const importedShadowExists = fileExistsStrict(importedShadowPath(legacy));
|
|
1749
|
+
const archiveExists = fileExistsStrict(legacyArchivePath(directory));
|
|
1750
|
+
if (checkpoint.migration?.done && !checkpoint.migration.archived && (archiveExists || !importedShadowExists)) {
|
|
1192
1751
|
checkpoint.migration.archived = true;
|
|
1193
1752
|
dirty = true;
|
|
1194
1753
|
}
|
|
@@ -1198,6 +1757,21 @@ function loadViewForWrite(directory) {
|
|
|
1198
1757
|
warnLegacyOverLimit(directory, legacyStat.size);
|
|
1199
1758
|
throw new Error(`PR subscription legacy store exceeds the ${PR_SUBSCRIPTION_LIMITS.legacySourceMaxBytes}-byte migration ceiling; refusing mutation until the file is archived or split`);
|
|
1200
1759
|
}
|
|
1760
|
+
if (checkpoint.migration?.done === true && checkpoint.migration.archived && legacyProjectionFingerprintMatches(directory, checkpoint)) {
|
|
1761
|
+
checkpoint.migration.archived = false;
|
|
1762
|
+
dirty = true;
|
|
1763
|
+
}
|
|
1764
|
+
if (checkpoint.migration?.done === true && !legacyProjectionFingerprintMatches(directory, checkpoint)) {
|
|
1765
|
+
try {
|
|
1766
|
+
fs.unlinkSync(projectionMarkerPath(legacy));
|
|
1767
|
+
} catch (error) {
|
|
1768
|
+
if (error.code !== "ENOENT")
|
|
1769
|
+
throw error;
|
|
1770
|
+
}
|
|
1771
|
+
checkpoint.migration = null;
|
|
1772
|
+
dirty = true;
|
|
1773
|
+
archiveDeferredForExternalRewrite = true;
|
|
1774
|
+
}
|
|
1201
1775
|
const legacySize = legacyStat.size;
|
|
1202
1776
|
const legacyMtime = legacyStat.mtimeMs;
|
|
1203
1777
|
const migration = checkpoint.migration;
|
|
@@ -1265,7 +1839,8 @@ function loadViewForWrite(directory) {
|
|
|
1265
1839
|
warnMigrationCapacity(directory, Object.keys(view).length);
|
|
1266
1840
|
break;
|
|
1267
1841
|
}
|
|
1268
|
-
|
|
1842
|
+
const deferLargeInitialGeneration = !sameGeneration && legacySize > PR_SUBSCRIPTION_LIMITS.migrationMaxBytesPerOperation && foldedThisOperation >= PR_SUBSCRIPTION_LIMITS.migrationMaxBytesPerOperation;
|
|
1843
|
+
if (!fold.eof || deferLargeInitialGeneration) {
|
|
1269
1844
|
const progress = cloneCheckpoint(checkpoint);
|
|
1270
1845
|
progress.records = { ...view };
|
|
1271
1846
|
progress.migration = {
|
|
@@ -1303,7 +1878,7 @@ function loadViewForWrite(directory) {
|
|
|
1303
1878
|
lastTerminalAt: null
|
|
1304
1879
|
},
|
|
1305
1880
|
done: true,
|
|
1306
|
-
archived:
|
|
1881
|
+
archived: archiveDeferredForExternalRewrite,
|
|
1307
1882
|
startedAt
|
|
1308
1883
|
};
|
|
1309
1884
|
audit.push({ kind: "migrate-complete" });
|
|
@@ -1315,17 +1890,15 @@ function loadViewForWrite(directory) {
|
|
|
1315
1890
|
}
|
|
1316
1891
|
return { checkpoint, view, audit, dirty };
|
|
1317
1892
|
}
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
checkpoint.maintenance.corruptLegacyRecords += overlaid.corruptLines;
|
|
1326
|
-
dirty = true;
|
|
1893
|
+
if (!legacyProjectionFingerprintMatches(directory, checkpoint)) {
|
|
1894
|
+
const legacyPath = storePath(directory);
|
|
1895
|
+
if (fileExistsStrict(projectionMarkerPath(legacyPath))) {
|
|
1896
|
+
const overlaid = overlayLegacy(directory, checkpoint);
|
|
1897
|
+
return { checkpoint, view: overlaid.view, audit, dirty };
|
|
1898
|
+
}
|
|
1899
|
+
retireAuthoritativeLegacyShadow(directory);
|
|
1327
1900
|
}
|
|
1328
|
-
return { checkpoint, view, audit, dirty };
|
|
1901
|
+
return { checkpoint, view: { ...checkpoint.records }, audit, dirty };
|
|
1329
1902
|
}
|
|
1330
1903
|
function finalizeWrite(directory, loaded, opAudit) {
|
|
1331
1904
|
const { checkpoint } = loaded;
|
|
@@ -1352,6 +1925,23 @@ function finalizeWrite(directory, loaded, opAudit) {
|
|
|
1352
1925
|
checkpoint.sequence += 1;
|
|
1353
1926
|
const auditCompaction = prepareAuditCompaction(directory);
|
|
1354
1927
|
persistCheckpointWithAuditCompaction(directory, checkpoint, auditCompaction);
|
|
1928
|
+
if (checkpoint.migration?.done === true) {
|
|
1929
|
+
const legacyPath = storePath(directory);
|
|
1930
|
+
try {
|
|
1931
|
+
let legacyStat = fs.statSync(legacyPath);
|
|
1932
|
+
if (legacyStat.size !== checkpoint.migration.sourceBytes || legacyStat.mtimeMs !== checkpoint.migration.sourceMtimeMs) {
|
|
1933
|
+
const legacyPayload = Object.values(checkpoint.records).map((entry) => JSON.stringify(entry)).join(`
|
|
1934
|
+
`) + (Object.keys(checkpoint.records).length > 0 ? `
|
|
1935
|
+
` : "");
|
|
1936
|
+
writeLegacyStoreFile(legacyPath, legacyPayload);
|
|
1937
|
+
fs.writeFileSync(projectionMarkerPath(legacyPath), "", "utf-8");
|
|
1938
|
+
legacyStat = fs.statSync(legacyPath);
|
|
1939
|
+
}
|
|
1940
|
+
checkpoint.migration.sourceBytes = legacyStat.size;
|
|
1941
|
+
checkpoint.migration.scannedBytes = legacyStat.size;
|
|
1942
|
+
checkpoint.migration.sourceMtimeMs = legacyStat.mtimeMs;
|
|
1943
|
+
} catch {}
|
|
1944
|
+
}
|
|
1355
1945
|
flushAuditEvents(directory, events, checkpoint.sequence, checkpoint.maintenance);
|
|
1356
1946
|
const archiveEvent = maybeArchiveLegacy(directory, checkpoint);
|
|
1357
1947
|
if (archiveEvent) {
|
|
@@ -1361,6 +1951,10 @@ function finalizeWrite(directory, loaded, opAudit) {
|
|
|
1361
1951
|
flushAuditEvents(directory, [archiveEvent], checkpoint.sequence, checkpoint.maintenance);
|
|
1362
1952
|
}
|
|
1363
1953
|
cleanupStaleLegacyArchive(directory);
|
|
1954
|
+
const coordinationView = readCoordinationSubscriptions(directory);
|
|
1955
|
+
if (coordinationView !== null && !isDeepStrictEqualByJson(coordinationView, loaded.view)) {
|
|
1956
|
+
persistCoordinationSubscriptions(directory, { ...loaded.view });
|
|
1957
|
+
}
|
|
1364
1958
|
for (const kind of [
|
|
1365
1959
|
"compact",
|
|
1366
1960
|
"migrate-complete",
|
|
@@ -1392,6 +1986,42 @@ async function subscribe(directory, input) {
|
|
|
1392
1986
|
const correlationId = buildCorrelationId(input.sessionID, input.repoFullName, input.prNumber);
|
|
1393
1987
|
const now = Date.now();
|
|
1394
1988
|
return withEvidenceLock(directory, PR_SUBSCRIPTIONS_FILE, STORE_LOCK_AGENT, STORE_LOCK_TASK, async () => {
|
|
1989
|
+
if (canUseCoordinationWritePath(directory)) {
|
|
1990
|
+
const view = readCoordinationSubscriptions(directory) ?? {};
|
|
1991
|
+
const match2 = Object.values(view).find((r) => r.correlationId === correlationId && r.status === "active");
|
|
1992
|
+
if (match2) {
|
|
1993
|
+
onSubscriptionCreated?.(directory, match2);
|
|
1994
|
+
return match2;
|
|
1995
|
+
}
|
|
1996
|
+
if (input.maxSubscriptions === undefined || input.maxSubscriptions > 0) {
|
|
1997
|
+
const limit = input.maxSubscriptions !== undefined ? input.maxSubscriptions : PR_SUBSCRIPTION_LIMITS.defaultMaxActiveSubscriptions;
|
|
1998
|
+
const activeCount = Object.values(view).filter((r) => r.status === "active").length;
|
|
1999
|
+
if (activeCount >= limit) {
|
|
2000
|
+
throw new Error(`PR subscription limit reached: ${activeCount}/${limit}`);
|
|
2001
|
+
}
|
|
2002
|
+
}
|
|
2003
|
+
const record2 = {
|
|
2004
|
+
correlationId,
|
|
2005
|
+
sessionID: input.sessionID,
|
|
2006
|
+
prNumber: input.prNumber,
|
|
2007
|
+
repoFullName: input.repoFullName,
|
|
2008
|
+
prUrl: input.prUrl,
|
|
2009
|
+
lastCheckedAt: now,
|
|
2010
|
+
isWatching: true,
|
|
2011
|
+
hasUnaddressedEvents: false,
|
|
2012
|
+
status: "active",
|
|
2013
|
+
createdAt: now,
|
|
2014
|
+
updatedAt: now,
|
|
2015
|
+
errorCount: 0
|
|
2016
|
+
};
|
|
2017
|
+
validateRecord(record2);
|
|
2018
|
+
view[correlationId] = record2;
|
|
2019
|
+
persistCoordinationSubscriptions(directory, view, true, [
|
|
2020
|
+
{ kind: "subscribe", correlationId }
|
|
2021
|
+
]);
|
|
2022
|
+
onSubscriptionCreated?.(directory, record2);
|
|
2023
|
+
return record2;
|
|
2024
|
+
}
|
|
1395
2025
|
const loaded = loadViewForWrite(directory);
|
|
1396
2026
|
const match = Object.values(loaded.view).find((r) => r.correlationId === correlationId && r.status === "active");
|
|
1397
2027
|
if (match) {
|
|
@@ -1432,6 +2062,24 @@ async function unsubscribe(directory, correlationId) {
|
|
|
1432
2062
|
if (!correlationId)
|
|
1433
2063
|
return null;
|
|
1434
2064
|
return withEvidenceLock(directory, PR_SUBSCRIPTIONS_FILE, STORE_LOCK_AGENT, STORE_LOCK_TASK, async () => {
|
|
2065
|
+
if (canUseCoordinationWritePath(directory)) {
|
|
2066
|
+
const view = readCoordinationSubscriptions(directory) ?? {};
|
|
2067
|
+
const match2 = Object.values(view).find((r) => r.correlationId === correlationId && r.status === "active");
|
|
2068
|
+
if (!match2) {
|
|
2069
|
+
return null;
|
|
2070
|
+
}
|
|
2071
|
+
const removed2 = {
|
|
2072
|
+
...match2,
|
|
2073
|
+
status: "removed",
|
|
2074
|
+
isWatching: false,
|
|
2075
|
+
updatedAt: Date.now()
|
|
2076
|
+
};
|
|
2077
|
+
view[correlationId] = removed2;
|
|
2078
|
+
persistCoordinationSubscriptions(directory, view, true, [
|
|
2079
|
+
{ kind: "unsubscribe", correlationId }
|
|
2080
|
+
]);
|
|
2081
|
+
return removed2;
|
|
2082
|
+
}
|
|
1435
2083
|
const loaded = loadViewForWrite(directory);
|
|
1436
2084
|
const match = Object.values(loaded.view).find((r) => r.correlationId === correlationId && r.status === "active");
|
|
1437
2085
|
if (!match) {
|
|
@@ -1470,12 +2118,65 @@ async function updateSnapshot(directory, correlationId, updates) {
|
|
|
1470
2118
|
if (!correlationId)
|
|
1471
2119
|
return null;
|
|
1472
2120
|
return withEvidenceLock(directory, PR_SUBSCRIPTIONS_FILE, STORE_LOCK_AGENT, STORE_LOCK_TASK, async () => {
|
|
2121
|
+
const existingCoordination = readCoordinationSubscriptions(directory);
|
|
2122
|
+
if (existingCoordination !== null && !fileExistsStrict(storePath(directory)) && !hasLegacyReconciliationArtifacts(directory)) {
|
|
2123
|
+
const match2 = Object.values(existingCoordination).find((r) => r.correlationId === correlationId && r.status === "active");
|
|
2124
|
+
if (!match2)
|
|
2125
|
+
return null;
|
|
2126
|
+
if (!snapshotUpdateHasChanges(match2, updates)) {
|
|
2127
|
+
return match2;
|
|
2128
|
+
}
|
|
2129
|
+
const updated2 = {
|
|
2130
|
+
...match2,
|
|
2131
|
+
...updates,
|
|
2132
|
+
correlationId,
|
|
2133
|
+
sessionID: match2.sessionID,
|
|
2134
|
+
repoFullName: match2.repoFullName,
|
|
2135
|
+
prNumber: match2.prNumber,
|
|
2136
|
+
prUrl: match2.prUrl,
|
|
2137
|
+
createdAt: match2.createdAt,
|
|
2138
|
+
updatedAt: Date.now()
|
|
2139
|
+
};
|
|
2140
|
+
validateRecord(updated2);
|
|
2141
|
+
existingCoordination[correlationId] = updated2;
|
|
2142
|
+
persistCoordinationSubscriptions(directory, existingCoordination);
|
|
2143
|
+
return updated2;
|
|
2144
|
+
}
|
|
2145
|
+
if (canUseCoordinationWritePath(directory)) {
|
|
2146
|
+
const view = readCoordinationSubscriptions(directory) ?? {};
|
|
2147
|
+
const match2 = Object.values(view).find((r) => r.correlationId === correlationId && r.status === "active");
|
|
2148
|
+
if (!match2) {
|
|
2149
|
+
return null;
|
|
2150
|
+
}
|
|
2151
|
+
if (!snapshotUpdateHasChanges(match2, updates)) {
|
|
2152
|
+
return match2;
|
|
2153
|
+
}
|
|
2154
|
+
const updated2 = {
|
|
2155
|
+
...match2,
|
|
2156
|
+
...updates,
|
|
2157
|
+
correlationId,
|
|
2158
|
+
sessionID: match2.sessionID,
|
|
2159
|
+
repoFullName: match2.repoFullName,
|
|
2160
|
+
prNumber: match2.prNumber,
|
|
2161
|
+
prUrl: match2.prUrl,
|
|
2162
|
+
createdAt: match2.createdAt,
|
|
2163
|
+
updatedAt: Date.now()
|
|
2164
|
+
};
|
|
2165
|
+
validateRecord(updated2);
|
|
2166
|
+
view[correlationId] = updated2;
|
|
2167
|
+
persistCoordinationSubscriptions(directory, view);
|
|
2168
|
+
return updated2;
|
|
2169
|
+
}
|
|
1473
2170
|
const loaded = loadViewForWrite(directory);
|
|
1474
2171
|
const match = Object.values(loaded.view).find((r) => r.correlationId === correlationId && r.status === "active");
|
|
1475
2172
|
if (!match) {
|
|
1476
2173
|
finalizeWrite(directory, loaded, []);
|
|
1477
2174
|
return null;
|
|
1478
2175
|
}
|
|
2176
|
+
if (!snapshotUpdateHasChanges(match, updates)) {
|
|
2177
|
+
finalizeWrite(directory, loaded, []);
|
|
2178
|
+
return match;
|
|
2179
|
+
}
|
|
1479
2180
|
const updated = {
|
|
1480
2181
|
...match,
|
|
1481
2182
|
...updates,
|
|
@@ -1501,6 +2202,36 @@ async function sweepStale(directory, ttlDays, mergedPrs) {
|
|
|
1501
2202
|
const now = Date.now();
|
|
1502
2203
|
try {
|
|
1503
2204
|
return await withEvidenceLock(directory, PR_SUBSCRIPTIONS_FILE, STORE_LOCK_AGENT, STORE_LOCK_TASK, async () => {
|
|
2205
|
+
if (canUseCoordinationWritePath(directory)) {
|
|
2206
|
+
const view = readCoordinationSubscriptions(directory) ?? {};
|
|
2207
|
+
let swept2 = 0;
|
|
2208
|
+
const expiredCorrelationIds = [];
|
|
2209
|
+
const now2 = Date.now();
|
|
2210
|
+
for (const record of Object.values(view)) {
|
|
2211
|
+
if (record.status !== "active")
|
|
2212
|
+
continue;
|
|
2213
|
+
const prKey = `${record.repoFullName}::${record.prNumber}`;
|
|
2214
|
+
const isMerged = mergedPrs?.has(prKey) ?? false;
|
|
2215
|
+
const isStale = now2 - record.updatedAt > ttlMs;
|
|
2216
|
+
if (isMerged || isStale && !record.hasUnaddressedEvents) {
|
|
2217
|
+
view[record.correlationId] = {
|
|
2218
|
+
...record,
|
|
2219
|
+
status: "expired",
|
|
2220
|
+
isWatching: false,
|
|
2221
|
+
updatedAt: now2
|
|
2222
|
+
};
|
|
2223
|
+
expiredCorrelationIds.push(record.correlationId);
|
|
2224
|
+
swept2 += 1;
|
|
2225
|
+
}
|
|
2226
|
+
}
|
|
2227
|
+
if (swept2 > 0) {
|
|
2228
|
+
persistCoordinationSubscriptions(directory, view, true, expiredCorrelationIds.map((correlationId) => ({
|
|
2229
|
+
kind: "expired",
|
|
2230
|
+
correlationId
|
|
2231
|
+
})));
|
|
2232
|
+
}
|
|
2233
|
+
return swept2;
|
|
2234
|
+
}
|
|
1504
2235
|
const loaded = loadViewForWrite(directory);
|
|
1505
2236
|
const opAudit = [];
|
|
1506
2237
|
let swept = 0;
|
|
@@ -1552,7 +2283,18 @@ async function sweepStale(directory, ttlDays, mergedPrs) {
|
|
|
1552
2283
|
}
|
|
1553
2284
|
async function getPrSubscriptionHealth(directory) {
|
|
1554
2285
|
try {
|
|
2286
|
+
const coordinationView = readCoordinationSubscriptions(directory);
|
|
1555
2287
|
const read = readCheckpoint(directory);
|
|
2288
|
+
const migrationPending = read.kind === "ok" && sameProjectRoot(read.value.rootPath, directory) && read.value.migration?.done === false;
|
|
2289
|
+
if (coordinationView !== null && Object.keys(coordinationView).length > 0 && !migrationPending) {
|
|
2290
|
+
const audit2 = auditStats(directory);
|
|
2291
|
+
const archiveBytes2 = fileSizeOrNull(legacyArchivePath(directory)) ?? 0;
|
|
2292
|
+
const checkpointBytes2 = fileSizeOrNull(checkpointPath(directory)) ?? 0;
|
|
2293
|
+
const health2 = healthFromView(coordinationView, buildCoordinationProjectionCheckpoint(directory, coordinationView));
|
|
2294
|
+
health2.recoverySource = "checkpoint";
|
|
2295
|
+
applyFileStats(health2, checkpointBytes2, audit2, archiveBytes2);
|
|
2296
|
+
return health2;
|
|
2297
|
+
}
|
|
1556
2298
|
const audit = auditStats(directory);
|
|
1557
2299
|
const archiveBytes = fileSizeOrNull(legacyArchivePath(directory)) ?? 0;
|
|
1558
2300
|
const checkpointBytes = fileSizeOrNull(checkpointPath(directory)) ?? 0;
|
|
@@ -1587,7 +2329,15 @@ async function getPrSubscriptionHealth(directory) {
|
|
|
1587
2329
|
}
|
|
1588
2330
|
health.recoverySource = legacySize === null ? "empty" : "legacy-log";
|
|
1589
2331
|
} else {
|
|
1590
|
-
const
|
|
2332
|
+
const pendingCheckpoint = migrationPending && coordinationView !== null ? (() => {
|
|
2333
|
+
const merged = cloneCheckpoint(read.value);
|
|
2334
|
+
merged.records = {
|
|
2335
|
+
...merged.records,
|
|
2336
|
+
...coordinationView
|
|
2337
|
+
};
|
|
2338
|
+
return merged;
|
|
2339
|
+
})() : read.value;
|
|
2340
|
+
const overlaid = overlayLegacy(directory, pendingCheckpoint);
|
|
1591
2341
|
health = healthFromView(overlaid.view, read.value);
|
|
1592
2342
|
health.recoverySource = overlaid.usedLegacy ? "checkpoint+legacy" : "checkpoint";
|
|
1593
2343
|
}
|