opencode-swarm 7.158.0 → 7.158.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.
- package/dist/background/pr-subscriptions.d.ts +31 -15
- package/dist/cli/{coder-settlement-5sexsmqz.js → coder-settlement-nrtcjr0h.js} +3 -3
- package/dist/cli/{config-doctor-ewn2kw6d.js → config-doctor-hs0x10ty.js} +2 -2
- package/dist/cli/{core-72wdmzps.js → core-a42bexs1.js} +1 -1
- package/dist/cli/{curator-llm-factory-r8q84m4g.js → curator-llm-factory-21vqqvn3.js} +20 -20
- package/dist/cli/{curator-g06sa98p.js → curator-yawrnk3t.js} +20 -20
- package/dist/cli/{evidence-summary-service-ttd0ntm0.js → evidence-summary-service-kmc92b95.js} +6 -6
- package/dist/cli/{gate-evidence-agnxf7md.js → gate-evidence-6tzhdxb2.js} +2 -2
- package/dist/cli/{guardrail-explain-qf4k176t.js → guardrail-explain-7xrdp18c.js} +21 -21
- package/dist/cli/{guardrail-log-g4qc595v.js → guardrail-log-f16sdc3p.js} +3 -3
- package/dist/cli/{guardrail-reset-zpxk0p1j.js → guardrail-reset-0ks08s0t.js} +20 -20
- package/dist/cli/{hive-promoter-zxy07gjg.js → hive-promoter-d661k3qq.js} +20 -20
- package/dist/cli/{index-6xxpwk5x.js → index-203fqkqk.js} +1 -1
- package/dist/cli/{index-fwe1wh6r.js → index-2j07670d.js} +1 -1
- package/dist/cli/{index-745dh1z1.js → index-4wy9kzjg.js} +22 -22
- package/dist/cli/{index-f036tggv.js → index-5erwyk1b.js} +3 -3
- package/dist/cli/{index-gm10kvne.js → index-6x9jq0cd.js} +2 -2
- package/dist/cli/{index-bv24pbaj.js → index-7qvfkgyk.js} +3 -3
- package/dist/cli/{index-rd324153.js → index-86vvhe8m.js} +1 -1
- package/dist/cli/{index-rac2gkvr.js → index-ac8rwjsg.js} +50 -50
- package/dist/cli/{index-vwx0by4y.js → index-azx50qge.js} +5 -5
- package/dist/cli/{index-nvta6kcv.js → index-dr2tfawg.js} +2 -2
- package/dist/cli/{index-rc93mm6e.js → index-ff8xjr1c.js} +1 -1
- package/dist/cli/{index-c57cyrwn.js → index-hrrzxz3q.js} +3 -3
- package/dist/cli/{index-663wnh64.js → index-jz216dhj.js} +1 -1
- package/dist/cli/{index-x4ftmdha.js → index-mc9qgkwd.js} +531 -100
- package/dist/cli/{index-5tchehv7.js → index-mfv7mfgg.js} +1 -1
- package/dist/cli/{index-f776m4r5.js → index-mt6qjp4c.js} +1 -1
- package/dist/cli/{index-pvs2zw1y.js → index-nphnjeh8.js} +2 -2
- package/dist/cli/{index-a1884hh4.js → index-t8g88esk.js} +1 -1
- package/dist/cli/{index-y3hdwh66.js → index-tc14d2zy.js} +3 -3
- package/dist/cli/{index-dmfbmnp7.js → index-tppnfxkm.js} +1 -1
- package/dist/cli/{index-x71shq8j.js → index-xscjecyq.js} +4 -4
- package/dist/cli/{index-1vxmqvpt.js → index-xzhk2hav.js} +1 -1
- package/dist/cli/{index-zawd71cg.js → index-ygxb3jw4.js} +3 -3
- package/dist/cli/{index-yfbp4q09.js → index-zy60xd05.js} +1 -1
- package/dist/cli/index.js +20 -20
- package/dist/cli/{knowledge-escalator-22zaj224.js → knowledge-escalator-9py5r6c9.js} +6 -6
- package/dist/cli/{knowledge-events-xbx1k408.js → knowledge-events-vcbece74.js} +4 -4
- package/dist/cli/{knowledge-store-fnm8xh9c.js → knowledge-store-0rdfjv20.js} +1 -1
- package/dist/cli/{knowledge-validator-t2jztjy1.js → knowledge-validator-nwjnjgf5.js} +2 -2
- package/dist/cli/{pending-delegations-29rtv1gb.js → pending-delegations-ht1vgaze.js} +1 -1
- package/dist/cli/{pr-review-reentry-authorization-25dep81m.js → pr-review-reentry-authorization-857b9rvv.js} +20 -20
- package/dist/cli/{pr-subscriptions-cgac3ydd.js → pr-subscriptions-j9j70bmd.js} +1 -1
- package/dist/cli/{scan-cursor-8acs9kwe.js → scan-cursor-59yj7tp9.js} +2 -2
- package/dist/cli/{schema-rjhassd0.js → schema-8a7fyp87.js} +1 -1
- package/dist/cli/{scope-persistence-jb1a5dzh.js → scope-persistence-prdxtcr3.js} +4 -4
- package/dist/cli/{skill-generator-frgppk1w.js → skill-generator-v2jkgp63.js} +7 -7
- package/dist/cli/{worktree-collision-ownership-f5sj6ffa.js → worktree-collision-ownership-6x03w8h7.js} +1 -1
- package/dist/cli/{worktree-isolation-5b0w6bha.js → worktree-isolation-s0setq2a.js} +20 -20
- package/dist/index.js +97 -96
- package/package.json +1 -1
|
@@ -29,12 +29,14 @@ var PR_SUBSCRIPTIONS_AUDIT_FILE = "pr-monitor/subscriptions.audit.jsonl";
|
|
|
29
29
|
var LEGACY_ARCHIVE_FILE = "pr-monitor/subscriptions.legacy.jsonl";
|
|
30
30
|
var FOREIGN_CHECKPOINT_FILE = "pr-monitor/subscriptions.checkpoint.foreign.json";
|
|
31
31
|
var CORRUPT_CHECKPOINT_FILE = "pr-monitor/subscriptions.checkpoint.corrupt.json";
|
|
32
|
+
var FOREIGN_LEGACY_FILE = "pr-monitor/subscriptions.legacy.foreign.jsonl";
|
|
32
33
|
var STORE_LOCK_AGENT = "pr-monitor";
|
|
33
34
|
var STORE_LOCK_TASK = "pr-subscriptions";
|
|
34
35
|
var PR_SUBSCRIPTION_LIMITS = {
|
|
35
36
|
readChunkBytes: 64 * 1024,
|
|
36
37
|
maxRecordBytes: 64 * 1024,
|
|
37
38
|
migrationChunkBytes: 1024 * 1024,
|
|
39
|
+
migrationMaxBytesPerOperation: 8 * 1024 * 1024,
|
|
38
40
|
legacySourceMaxBytes: 64 * 1024 * 1024,
|
|
39
41
|
defaultMaxActiveSubscriptions: 20,
|
|
40
42
|
maxCheckpointRecords: 512,
|
|
@@ -76,7 +78,7 @@ var RecordSchema = exports_external.object({
|
|
|
76
78
|
errorCount: exports_external.number().int().min(0),
|
|
77
79
|
customPollIntervalSeconds: exports_external.number().int().positive().optional(),
|
|
78
80
|
customFailureThreshold: exports_external.number().int().min(0).optional(),
|
|
79
|
-
customCooldownSeconds: exports_external.number().int().
|
|
81
|
+
customCooldownSeconds: exports_external.number().int().min(0).optional()
|
|
80
82
|
}).strict();
|
|
81
83
|
var TerminalSummarySchema = exports_external.object({
|
|
82
84
|
removed: exports_external.number().int().min(0),
|
|
@@ -88,6 +90,13 @@ var MigrationSchema = exports_external.object({
|
|
|
88
90
|
sourceBytes: exports_external.number().int().nonnegative(),
|
|
89
91
|
sourceMtimeMs: exports_external.number().nonnegative(),
|
|
90
92
|
corruptLines: exports_external.number().int().nonnegative(),
|
|
93
|
+
discardingOversizeLine: exports_external.boolean().optional().default(false),
|
|
94
|
+
baselineRecords: exports_external.record(exports_external.string(), RecordSchema).optional().default({}),
|
|
95
|
+
baselineTerminalSummary: TerminalSummarySchema.optional().default({
|
|
96
|
+
removed: 0,
|
|
97
|
+
expired: 0,
|
|
98
|
+
lastTerminalAt: null
|
|
99
|
+
}),
|
|
91
100
|
done: exports_external.boolean(),
|
|
92
101
|
archived: exports_external.boolean(),
|
|
93
102
|
startedAt: exports_external.number().int().nonnegative()
|
|
@@ -121,12 +130,31 @@ function auditPath(directory) {
|
|
|
121
130
|
function legacyArchivePath(directory) {
|
|
122
131
|
return validateSwarmPath(directory, LEGACY_ARCHIVE_FILE);
|
|
123
132
|
}
|
|
133
|
+
function legacyArchiveNextPath(directory) {
|
|
134
|
+
return `${legacyArchivePath(directory)}.next`;
|
|
135
|
+
}
|
|
136
|
+
function legacyArchivePreviousPath(directory) {
|
|
137
|
+
return `${legacyArchivePath(directory)}.previous`;
|
|
138
|
+
}
|
|
139
|
+
function fileExistsStrict(filePath) {
|
|
140
|
+
try {
|
|
141
|
+
fs.statSync(filePath);
|
|
142
|
+
return true;
|
|
143
|
+
} catch (err) {
|
|
144
|
+
if (err.code === "ENOENT")
|
|
145
|
+
return false;
|
|
146
|
+
throw err;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
124
149
|
function foreignSlotPath(directory) {
|
|
125
150
|
return validateSwarmPath(directory, FOREIGN_CHECKPOINT_FILE);
|
|
126
151
|
}
|
|
127
152
|
function corruptSlotPath(directory) {
|
|
128
153
|
return validateSwarmPath(directory, CORRUPT_CHECKPOINT_FILE);
|
|
129
154
|
}
|
|
155
|
+
function foreignLegacySlotPath(directory) {
|
|
156
|
+
return validateSwarmPath(directory, FOREIGN_LEGACY_FILE);
|
|
157
|
+
}
|
|
130
158
|
function ensureSwarmDir(directory) {
|
|
131
159
|
fs.mkdirSync(path.resolve(directory, ".swarm", "pr-monitor"), {
|
|
132
160
|
recursive: true
|
|
@@ -167,20 +195,22 @@ function renameWithRetry(from, to) {
|
|
|
167
195
|
}
|
|
168
196
|
throw lastError;
|
|
169
197
|
}
|
|
170
|
-
function foldLegacyRegion(filePath, startByte, maxBytes) {
|
|
198
|
+
function foldLegacyRegion(filePath, startByte, maxBytes, discardingOversizeLine = false) {
|
|
171
199
|
const folded = new Map;
|
|
172
200
|
let corruptLines = 0;
|
|
173
201
|
let consumedTo = startByte;
|
|
174
202
|
let fd;
|
|
175
203
|
try {
|
|
176
204
|
fd = fs.openSync(filePath, "r");
|
|
177
|
-
} catch {
|
|
205
|
+
} catch (err) {
|
|
178
206
|
return {
|
|
179
207
|
folded,
|
|
180
208
|
nextByte: startByte,
|
|
181
209
|
eof: true,
|
|
182
210
|
aborted: true,
|
|
183
|
-
corruptLines
|
|
211
|
+
corruptLines,
|
|
212
|
+
ioError: err instanceof Error ? err.message : String(err),
|
|
213
|
+
discardingOversizeLine
|
|
184
214
|
};
|
|
185
215
|
}
|
|
186
216
|
const processLineBytes = (line) => {
|
|
@@ -210,25 +240,25 @@ function foldLegacyRegion(filePath, startByte, maxBytes) {
|
|
|
210
240
|
folded.set(rec.correlationId, rec);
|
|
211
241
|
};
|
|
212
242
|
try {
|
|
213
|
-
const size =
|
|
243
|
+
const size = _internals.legacyFstatSync(fd).size;
|
|
214
244
|
if (startByte >= size) {
|
|
215
245
|
return {
|
|
216
246
|
folded,
|
|
217
247
|
nextByte: startByte,
|
|
218
248
|
eof: true,
|
|
219
249
|
aborted: false,
|
|
220
|
-
corruptLines
|
|
250
|
+
corruptLines,
|
|
251
|
+
ioError: null,
|
|
252
|
+
discardingOversizeLine: false
|
|
221
253
|
};
|
|
222
254
|
}
|
|
223
255
|
const chunk = Buffer.alloc(PR_SUBSCRIPTION_LIMITS.readChunkBytes);
|
|
224
256
|
let pending = [];
|
|
225
257
|
let pendingLen = 0;
|
|
226
|
-
let oversize =
|
|
258
|
+
let oversize = discardingOversizeLine;
|
|
227
259
|
let pos = startByte;
|
|
228
260
|
const flushPendingAtEof = () => {
|
|
229
|
-
if (oversize) {
|
|
230
|
-
corruptLines += 1;
|
|
231
|
-
} else if (pendingLen > 0) {
|
|
261
|
+
if (!oversize && pendingLen > 0) {
|
|
232
262
|
processLineBytes(Buffer.concat(pending));
|
|
233
263
|
}
|
|
234
264
|
consumedTo = Math.max(consumedTo, pos);
|
|
@@ -240,14 +270,16 @@ function foldLegacyRegion(filePath, startByte, maxBytes) {
|
|
|
240
270
|
nextByte: consumedTo,
|
|
241
271
|
eof: true,
|
|
242
272
|
aborted: false,
|
|
243
|
-
corruptLines
|
|
273
|
+
corruptLines,
|
|
274
|
+
ioError: null,
|
|
275
|
+
discardingOversizeLine: false
|
|
244
276
|
};
|
|
245
277
|
};
|
|
246
278
|
while (true) {
|
|
247
279
|
const want = Math.min(chunk.length, size - pos);
|
|
248
280
|
if (want <= 0)
|
|
249
281
|
return flushPendingAtEof();
|
|
250
|
-
const n =
|
|
282
|
+
const n = _internals.readSync(fd, chunk, 0, want, pos);
|
|
251
283
|
if (n <= 0)
|
|
252
284
|
return flushPendingAtEof();
|
|
253
285
|
const data = chunk.subarray(0, n);
|
|
@@ -257,14 +289,16 @@ function foldLegacyRegion(filePath, startByte, maxBytes) {
|
|
|
257
289
|
while (scan < data.length) {
|
|
258
290
|
const nl = data.indexOf(10, scan);
|
|
259
291
|
if (nl === -1) {
|
|
260
|
-
|
|
261
|
-
|
|
292
|
+
if (!oversize) {
|
|
293
|
+
pending.push(Buffer.from(data.subarray(scan)));
|
|
294
|
+
pendingLen += data.length - scan;
|
|
295
|
+
}
|
|
262
296
|
break;
|
|
263
297
|
}
|
|
264
298
|
const lineLen = pendingLen + (nl - scan);
|
|
265
|
-
if (oversize
|
|
299
|
+
if (!oversize && lineLen > PR_SUBSCRIPTION_LIMITS.maxRecordBytes) {
|
|
266
300
|
corruptLines += 1;
|
|
267
|
-
} else {
|
|
301
|
+
} else if (!oversize) {
|
|
268
302
|
const pieces = pending.length > 0 ? [...pending, data.subarray(scan, nl)] : [data.subarray(scan, nl)];
|
|
269
303
|
processLineBytes(Buffer.concat(pieces));
|
|
270
304
|
}
|
|
@@ -273,9 +307,21 @@ function foldLegacyRegion(filePath, startByte, maxBytes) {
|
|
|
273
307
|
oversize = false;
|
|
274
308
|
consumedTo = base + nl + 1;
|
|
275
309
|
scan = nl + 1;
|
|
310
|
+
if (consumedTo - startByte >= maxBytes) {
|
|
311
|
+
return {
|
|
312
|
+
folded,
|
|
313
|
+
nextByte: consumedTo,
|
|
314
|
+
eof: consumedTo >= size,
|
|
315
|
+
aborted: false,
|
|
316
|
+
corruptLines,
|
|
317
|
+
ioError: null,
|
|
318
|
+
discardingOversizeLine: false
|
|
319
|
+
};
|
|
320
|
+
}
|
|
276
321
|
}
|
|
277
322
|
if (!oversize && pendingLen > PR_SUBSCRIPTION_LIMITS.maxRecordBytes) {
|
|
278
323
|
oversize = true;
|
|
324
|
+
corruptLines += 1;
|
|
279
325
|
pending = [];
|
|
280
326
|
pendingLen = 0;
|
|
281
327
|
}
|
|
@@ -286,22 +332,37 @@ function foldLegacyRegion(filePath, startByte, maxBytes) {
|
|
|
286
332
|
nextByte: consumedTo,
|
|
287
333
|
eof,
|
|
288
334
|
aborted: false,
|
|
289
|
-
corruptLines
|
|
335
|
+
corruptLines,
|
|
336
|
+
ioError: null,
|
|
337
|
+
discardingOversizeLine: false
|
|
338
|
+
};
|
|
339
|
+
}
|
|
340
|
+
if (oversize && pos - startByte >= maxBytes) {
|
|
341
|
+
return {
|
|
342
|
+
folded,
|
|
343
|
+
nextByte: pos,
|
|
344
|
+
eof: pos >= size,
|
|
345
|
+
aborted: false,
|
|
346
|
+
corruptLines,
|
|
347
|
+
ioError: null,
|
|
348
|
+
discardingOversizeLine: pos < size
|
|
290
349
|
};
|
|
291
350
|
}
|
|
292
351
|
}
|
|
293
352
|
} catch (err) {
|
|
294
353
|
log(`[pr-monitor] foldLegacyRegion failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
295
354
|
return {
|
|
296
|
-
folded,
|
|
297
|
-
nextByte:
|
|
355
|
+
folded: new Map,
|
|
356
|
+
nextByte: startByte,
|
|
298
357
|
eof: true,
|
|
299
358
|
aborted: true,
|
|
300
|
-
corruptLines
|
|
359
|
+
corruptLines: 0,
|
|
360
|
+
ioError: err instanceof Error ? err.message : String(err),
|
|
361
|
+
discardingOversizeLine
|
|
301
362
|
};
|
|
302
363
|
} finally {
|
|
303
364
|
try {
|
|
304
|
-
|
|
365
|
+
_internals.legacyCloseSync(fd);
|
|
305
366
|
} catch {}
|
|
306
367
|
}
|
|
307
368
|
}
|
|
@@ -317,9 +378,11 @@ function mergeFoldedRecords(view, overlay) {
|
|
|
317
378
|
return replaced;
|
|
318
379
|
}
|
|
319
380
|
function readCheckpoint(directory) {
|
|
381
|
+
const filePath = checkpointPath(directory);
|
|
382
|
+
let fd;
|
|
320
383
|
let stat;
|
|
321
384
|
try {
|
|
322
|
-
|
|
385
|
+
fd = fs.openSync(filePath, "r");
|
|
323
386
|
} catch (err) {
|
|
324
387
|
if (err.code === "ENOENT") {
|
|
325
388
|
return { kind: "absent" };
|
|
@@ -329,20 +392,45 @@ function readCheckpoint(directory) {
|
|
|
329
392
|
reason: `unreadable (${err.code ?? "unknown"})`
|
|
330
393
|
};
|
|
331
394
|
}
|
|
332
|
-
|
|
395
|
+
try {
|
|
396
|
+
stat = fs.fstatSync(fd);
|
|
397
|
+
} catch (err) {
|
|
398
|
+
try {
|
|
399
|
+
fs.closeSync(fd);
|
|
400
|
+
} catch {}
|
|
333
401
|
return {
|
|
334
402
|
kind: "invalid",
|
|
335
|
-
reason: `
|
|
403
|
+
reason: `unreadable (${err.code ?? "unknown"})`
|
|
336
404
|
};
|
|
337
405
|
}
|
|
338
406
|
let raw;
|
|
339
407
|
try {
|
|
340
|
-
|
|
408
|
+
if (stat.size > PR_SUBSCRIPTION_LIMITS.checkpointHardReadBytes) {
|
|
409
|
+
return {
|
|
410
|
+
kind: "invalid",
|
|
411
|
+
reason: `checkpoint exceeds hard read ceiling (${stat.size} > ${PR_SUBSCRIPTION_LIMITS.checkpointHardReadBytes} bytes)`
|
|
412
|
+
};
|
|
413
|
+
}
|
|
414
|
+
_internals.afterCheckpointFstat(filePath);
|
|
415
|
+
const buffer = Buffer.alloc(stat.size);
|
|
416
|
+
let bytesRead = 0;
|
|
417
|
+
while (bytesRead < stat.size) {
|
|
418
|
+
const read = _internals.readSync(fd, buffer, bytesRead, stat.size - bytesRead, bytesRead);
|
|
419
|
+
if (read <= 0) {
|
|
420
|
+
throw new Error(`premature checkpoint EOF (${bytesRead}/${stat.size} bytes)`);
|
|
421
|
+
}
|
|
422
|
+
bytesRead += read;
|
|
423
|
+
}
|
|
424
|
+
raw = buffer.subarray(0, bytesRead).toString("utf-8");
|
|
341
425
|
} catch (err) {
|
|
342
426
|
return {
|
|
343
427
|
kind: "invalid",
|
|
344
428
|
reason: `unreadable (${err.code ?? "unknown"})`
|
|
345
429
|
};
|
|
430
|
+
} finally {
|
|
431
|
+
try {
|
|
432
|
+
fs.closeSync(fd);
|
|
433
|
+
} catch {}
|
|
346
434
|
}
|
|
347
435
|
let parsed;
|
|
348
436
|
try {
|
|
@@ -373,6 +461,23 @@ function readCheckpoint(directory) {
|
|
|
373
461
|
};
|
|
374
462
|
}
|
|
375
463
|
}
|
|
464
|
+
if (checkpoint.migration !== null) {
|
|
465
|
+
const baselineEntries = Object.entries(checkpoint.migration.baselineRecords);
|
|
466
|
+
if (baselineEntries.length > PR_SUBSCRIPTION_LIMITS.maxCheckpointRecords) {
|
|
467
|
+
return {
|
|
468
|
+
kind: "invalid",
|
|
469
|
+
reason: `migration baseline count ${baselineEntries.length} exceeds guard ${PR_SUBSCRIPTION_LIMITS.maxCheckpointRecords}`
|
|
470
|
+
};
|
|
471
|
+
}
|
|
472
|
+
for (const [key, rec] of baselineEntries) {
|
|
473
|
+
if (key !== rec.correlationId || rec.correlationId !== buildCorrelationId(rec.sessionID, rec.repoFullName, rec.prNumber)) {
|
|
474
|
+
return {
|
|
475
|
+
kind: "invalid",
|
|
476
|
+
reason: `migration baseline identity mismatch at key "${key}"`
|
|
477
|
+
};
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
}
|
|
376
481
|
return { kind: "ok", value: checkpoint };
|
|
377
482
|
}
|
|
378
483
|
function freshCheckpoint(directory) {
|
|
@@ -426,14 +531,14 @@ function quarantineCheckpoint(directory, slot) {
|
|
|
426
531
|
try {
|
|
427
532
|
fs.renameSync(to, `${to}.prev`);
|
|
428
533
|
} catch {}
|
|
429
|
-
renameWithRetry(from, to);
|
|
534
|
+
_internals.renameWithRetry(from, to);
|
|
430
535
|
return true;
|
|
431
536
|
} catch (err) {
|
|
432
537
|
log(`[pr-monitor] checkpoint quarantine (${slot}) failed: ${err instanceof Error ? err.message : String(err)} \u2014 the next checkpoint write replaces it`);
|
|
433
538
|
return false;
|
|
434
539
|
}
|
|
435
540
|
}
|
|
436
|
-
function flushAuditEvents(directory, events, sequence,
|
|
541
|
+
function flushAuditEvents(directory, events, sequence, _maintenance) {
|
|
437
542
|
if (events.length === 0)
|
|
438
543
|
return;
|
|
439
544
|
ensureSwarmDir(directory);
|
|
@@ -451,42 +556,58 @@ function flushAuditEvents(directory, events, sequence, maintenance) {
|
|
|
451
556
|
log(`[pr-monitor] audit append failed (non-fatal): ${err instanceof Error ? err.message : String(err)}`);
|
|
452
557
|
return;
|
|
453
558
|
}
|
|
454
|
-
compactAuditTail(directory, maintenance);
|
|
455
559
|
}
|
|
456
|
-
function
|
|
560
|
+
function storageErrorCode(err) {
|
|
561
|
+
if (typeof err === "object" && err !== null && "code" in err) {
|
|
562
|
+
const code = err.code;
|
|
563
|
+
if (typeof code === "string" && code.length > 0)
|
|
564
|
+
return code;
|
|
565
|
+
}
|
|
566
|
+
return "unknown";
|
|
567
|
+
}
|
|
568
|
+
function planAuditCompaction(directory) {
|
|
457
569
|
let size;
|
|
458
570
|
try {
|
|
459
571
|
size = fs.statSync(auditPath(directory)).size;
|
|
460
572
|
} catch {
|
|
461
|
-
return;
|
|
573
|
+
return null;
|
|
462
574
|
}
|
|
463
575
|
if (size <= PR_SUBSCRIPTION_LIMITS.auditMaxBytesLow)
|
|
464
|
-
return;
|
|
465
|
-
const windowBytes = 2 * PR_SUBSCRIPTION_LIMITS.auditMaxBytesHigh;
|
|
576
|
+
return null;
|
|
466
577
|
let content;
|
|
578
|
+
let omittedPrefix = false;
|
|
467
579
|
try {
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
580
|
+
const filePath = auditPath(directory);
|
|
581
|
+
const maxRead = PR_SUBSCRIPTION_LIMITS.auditMaxBytesHigh;
|
|
582
|
+
const start = Math.max(0, size - maxRead);
|
|
583
|
+
const length = Math.min(size, maxRead);
|
|
584
|
+
const buffer = Buffer.alloc(length);
|
|
585
|
+
const fd = fs.openSync(filePath, "r");
|
|
586
|
+
try {
|
|
587
|
+
let bytesRead = 0;
|
|
588
|
+
while (bytesRead < length) {
|
|
589
|
+
const read = _internals.readSync(fd, buffer, bytesRead, length - bytesRead, start + bytesRead);
|
|
590
|
+
if (read <= 0)
|
|
591
|
+
return null;
|
|
592
|
+
bytesRead += read;
|
|
479
593
|
}
|
|
480
|
-
|
|
481
|
-
|
|
594
|
+
content = buffer.toString("utf-8");
|
|
595
|
+
} finally {
|
|
596
|
+
fs.closeSync(fd);
|
|
597
|
+
}
|
|
598
|
+
if (start > 0) {
|
|
599
|
+
omittedPrefix = true;
|
|
600
|
+
const firstNewline = content.indexOf(`
|
|
601
|
+
`);
|
|
602
|
+
content = firstNewline >= 0 ? content.slice(firstNewline + 1) : "";
|
|
482
603
|
}
|
|
483
604
|
} catch {
|
|
484
|
-
return;
|
|
605
|
+
return null;
|
|
485
606
|
}
|
|
486
607
|
const all = content.split(`
|
|
487
608
|
`).filter((line) => line.trim().length > 0);
|
|
488
609
|
if (all.length <= PR_SUBSCRIPTION_LIMITS.auditMaxLinesHigh && size <= PR_SUBSCRIPTION_LIMITS.auditMaxBytesHigh) {
|
|
489
|
-
return;
|
|
610
|
+
return null;
|
|
490
611
|
}
|
|
491
612
|
const kept = [];
|
|
492
613
|
let bytes = 0;
|
|
@@ -498,14 +619,40 @@ function compactAuditTail(directory, maintenance) {
|
|
|
498
619
|
kept.unshift(all[i]);
|
|
499
620
|
bytes += lineBytes;
|
|
500
621
|
}
|
|
501
|
-
const dropped = all.length - kept.length;
|
|
502
|
-
|
|
503
|
-
|
|
622
|
+
const dropped = all.length - kept.length + (omittedPrefix ? 1 : 0);
|
|
623
|
+
return {
|
|
624
|
+
content: kept.length > 0 ? `${kept.join(`
|
|
504
625
|
`)}
|
|
505
|
-
` : ""
|
|
506
|
-
|
|
626
|
+
` : "",
|
|
627
|
+
dropped
|
|
628
|
+
};
|
|
629
|
+
}
|
|
630
|
+
function applyAuditCompaction(directory, plan) {
|
|
631
|
+
if (!plan)
|
|
632
|
+
return false;
|
|
633
|
+
try {
|
|
634
|
+
_internals.auditCompactionWrite(auditPath(directory), plan.content);
|
|
635
|
+
return true;
|
|
636
|
+
} catch (err) {
|
|
637
|
+
log(`[pr-monitor] audit compaction failed (non-fatal): ${storageErrorCode(err)}`);
|
|
638
|
+
return false;
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
function prepareAuditCompaction(directory) {
|
|
642
|
+
const plan = planAuditCompaction(directory);
|
|
643
|
+
return plan;
|
|
644
|
+
}
|
|
645
|
+
function persistCheckpointWithAuditCompaction(directory, checkpoint, plan) {
|
|
646
|
+
_internals.writeCheckpointFile(directory, checkpoint);
|
|
647
|
+
if (!applyAuditCompaction(directory, plan) || !plan)
|
|
648
|
+
return;
|
|
649
|
+
const previousDropped = checkpoint.maintenance.droppedAuditTransitions;
|
|
650
|
+
checkpoint.maintenance.droppedAuditTransitions += plan.dropped;
|
|
651
|
+
try {
|
|
652
|
+
_internals.writeCheckpointFile(directory, checkpoint);
|
|
507
653
|
} catch (err) {
|
|
508
|
-
|
|
654
|
+
checkpoint.maintenance.droppedAuditTransitions = previousDropped;
|
|
655
|
+
log(`[pr-monitor] audit compaction accounting failed (non-fatal): ${storageErrorCode(err)}`);
|
|
509
656
|
}
|
|
510
657
|
}
|
|
511
658
|
function auditStats(directory) {
|
|
@@ -565,20 +712,74 @@ function maybeArchiveLegacy(directory, checkpoint) {
|
|
|
565
712
|
if (!migration || !migration.done || migration.archived)
|
|
566
713
|
return null;
|
|
567
714
|
const legacy = storePath(directory);
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
715
|
+
let stat;
|
|
716
|
+
try {
|
|
717
|
+
stat = _internals.archiveStatSync(legacy);
|
|
718
|
+
} catch (err) {
|
|
719
|
+
if (err.code === "ENOENT") {
|
|
720
|
+
migration.archived = true;
|
|
721
|
+
} else {
|
|
722
|
+
log(`[pr-monitor] legacy archive inspection deferred: ${storageErrorCode(err)}`);
|
|
723
|
+
}
|
|
724
|
+
return null;
|
|
725
|
+
}
|
|
726
|
+
if (stat.size !== migration.sourceBytes || stat.mtimeMs !== migration.sourceMtimeMs)
|
|
727
|
+
return null;
|
|
728
|
+
_internals.beforeArchiveLegacy(directory);
|
|
729
|
+
try {
|
|
730
|
+
stat = _internals.archiveStatSync(legacy);
|
|
731
|
+
} catch {
|
|
571
732
|
return null;
|
|
572
733
|
}
|
|
573
|
-
if (size !== migration.sourceBytes)
|
|
734
|
+
if (stat.size !== migration.sourceBytes || stat.mtimeMs !== migration.sourceMtimeMs)
|
|
574
735
|
return null;
|
|
736
|
+
const archive = legacyArchivePath(directory);
|
|
737
|
+
const next = legacyArchiveNextPath(directory);
|
|
738
|
+
const previous = legacyArchivePreviousPath(directory);
|
|
575
739
|
try {
|
|
740
|
+
_internals.beforeArchiveRename(directory);
|
|
576
741
|
try {
|
|
577
|
-
fs.unlinkSync(
|
|
742
|
+
fs.unlinkSync(next);
|
|
743
|
+
} catch {}
|
|
744
|
+
_internals.renameWithRetry(legacy, next);
|
|
745
|
+
const archivedStat = _internals.archiveStatSync(next);
|
|
746
|
+
if (archivedStat.size !== migration.sourceBytes || archivedStat.mtimeMs !== migration.sourceMtimeMs) {
|
|
747
|
+
_internals.renameWithRetry(next, legacy);
|
|
748
|
+
return null;
|
|
749
|
+
}
|
|
750
|
+
const archivedAt = new Date;
|
|
751
|
+
fs.utimesSync(next, archivedAt, archivedAt);
|
|
752
|
+
let movedPrevious = false;
|
|
753
|
+
try {
|
|
754
|
+
fs.unlinkSync(previous);
|
|
578
755
|
} catch {}
|
|
579
|
-
|
|
756
|
+
try {
|
|
757
|
+
_internals.renameWithRetry(archive, previous);
|
|
758
|
+
movedPrevious = true;
|
|
759
|
+
} catch (err) {
|
|
760
|
+
if (err.code !== "ENOENT")
|
|
761
|
+
throw err;
|
|
762
|
+
}
|
|
763
|
+
try {
|
|
764
|
+
_internals.renameWithRetry(next, archive);
|
|
765
|
+
} catch (err) {
|
|
766
|
+
if (movedPrevious)
|
|
767
|
+
_internals.renameWithRetry(previous, archive);
|
|
768
|
+
_internals.renameWithRetry(next, legacy);
|
|
769
|
+
throw err;
|
|
770
|
+
}
|
|
771
|
+
if (movedPrevious) {
|
|
772
|
+
try {
|
|
773
|
+
fs.unlinkSync(previous);
|
|
774
|
+
} catch {}
|
|
775
|
+
}
|
|
580
776
|
} catch (err) {
|
|
581
|
-
|
|
777
|
+
try {
|
|
778
|
+
if (fs.existsSync(next) && !fs.existsSync(legacy)) {
|
|
779
|
+
_internals.renameWithRetry(next, legacy);
|
|
780
|
+
}
|
|
781
|
+
} catch {}
|
|
782
|
+
log(`[pr-monitor] legacy archive deferred: ${storageErrorCode(err)}`);
|
|
582
783
|
return null;
|
|
583
784
|
}
|
|
584
785
|
try {
|
|
@@ -588,6 +789,70 @@ function maybeArchiveLegacy(directory, checkpoint) {
|
|
|
588
789
|
migration.archived = true;
|
|
589
790
|
return { kind: "archive" };
|
|
590
791
|
}
|
|
792
|
+
function reconcileLegacyArchiveStaging(directory, migration) {
|
|
793
|
+
const legacy = storePath(directory);
|
|
794
|
+
const archive = legacyArchivePath(directory);
|
|
795
|
+
const next = legacyArchiveNextPath(directory);
|
|
796
|
+
const previous = legacyArchivePreviousPath(directory);
|
|
797
|
+
let legacyExists = fileExistsStrict(legacy);
|
|
798
|
+
let archiveExists = fileExistsStrict(archive);
|
|
799
|
+
let nextExists = fileExistsStrict(next);
|
|
800
|
+
let previousExists = fileExistsStrict(previous);
|
|
801
|
+
if (!nextExists && !previousExists)
|
|
802
|
+
return;
|
|
803
|
+
if (legacyExists) {
|
|
804
|
+
if (!archiveExists && previousExists) {
|
|
805
|
+
_internals.renameWithRetry(previous, archive);
|
|
806
|
+
archiveExists = true;
|
|
807
|
+
previousExists = false;
|
|
808
|
+
}
|
|
809
|
+
if (!archiveExists && nextExists) {
|
|
810
|
+
const candidate = fs.statSync(next);
|
|
811
|
+
if (migration.done && candidate.size === migration.sourceBytes && candidate.mtimeMs === migration.sourceMtimeMs) {
|
|
812
|
+
_internals.renameWithRetry(next, archive);
|
|
813
|
+
archiveExists = true;
|
|
814
|
+
nextExists = false;
|
|
815
|
+
}
|
|
816
|
+
}
|
|
817
|
+
if (archiveExists && previousExists)
|
|
818
|
+
fs.unlinkSync(previous);
|
|
819
|
+
if (nextExists)
|
|
820
|
+
fs.unlinkSync(next);
|
|
821
|
+
return;
|
|
822
|
+
}
|
|
823
|
+
if (archiveExists) {
|
|
824
|
+
if (nextExists) {
|
|
825
|
+
_internals.renameWithRetry(next, legacy);
|
|
826
|
+
migration.archived = false;
|
|
827
|
+
legacyExists = true;
|
|
828
|
+
nextExists = false;
|
|
829
|
+
}
|
|
830
|
+
if (previousExists)
|
|
831
|
+
fs.unlinkSync(previous);
|
|
832
|
+
return;
|
|
833
|
+
}
|
|
834
|
+
if (previousExists) {
|
|
835
|
+
_internals.renameWithRetry(previous, archive);
|
|
836
|
+
archiveExists = true;
|
|
837
|
+
previousExists = false;
|
|
838
|
+
if (nextExists) {
|
|
839
|
+
_internals.renameWithRetry(next, legacy);
|
|
840
|
+
migration.archived = false;
|
|
841
|
+
legacyExists = true;
|
|
842
|
+
nextExists = false;
|
|
843
|
+
}
|
|
844
|
+
return;
|
|
845
|
+
}
|
|
846
|
+
if (nextExists) {
|
|
847
|
+
const candidate = fs.statSync(next);
|
|
848
|
+
if (migration.done && candidate.size === migration.sourceBytes && candidate.mtimeMs === migration.sourceMtimeMs) {
|
|
849
|
+
_internals.renameWithRetry(next, archive);
|
|
850
|
+
} else {
|
|
851
|
+
_internals.renameWithRetry(next, legacy);
|
|
852
|
+
migration.archived = false;
|
|
853
|
+
}
|
|
854
|
+
}
|
|
855
|
+
}
|
|
591
856
|
function cleanupStaleLegacyArchive(directory) {
|
|
592
857
|
try {
|
|
593
858
|
const stat = fs.statSync(legacyArchivePath(directory));
|
|
@@ -605,7 +870,8 @@ function emitHealthTelemetry(trigger, checkpoint) {
|
|
|
605
870
|
compactions: checkpoint.maintenance.compactions,
|
|
606
871
|
corrupt_count: checkpoint.maintenance.corruptLegacyRecords,
|
|
607
872
|
dropped_audit_count: checkpoint.maintenance.droppedAuditTransitions,
|
|
608
|
-
checkpoint_bytes: Buffer.byteLength(JSON.stringify(checkpoint)
|
|
873
|
+
checkpoint_bytes: Buffer.byteLength(`${JSON.stringify(checkpoint)}
|
|
874
|
+
`, "utf-8"),
|
|
609
875
|
limit_bytes: PR_SUBSCRIPTION_LIMITS.maxCheckpointBytes,
|
|
610
876
|
recovery_resets: checkpoint.maintenance.resets
|
|
611
877
|
});
|
|
@@ -631,7 +897,7 @@ function warnMigrationCapacity(directory, records) {
|
|
|
631
897
|
log(`[pr-monitor] legacy subscriptions.jsonl folds to ${records} records (>checkpoint capacity ${PR_SUBSCRIPTION_LIMITS.maxCheckpointRecords}); migration refused \u2014 archive or split the file so its subscriptions can be re-subscribed, then remove it`);
|
|
632
898
|
}
|
|
633
899
|
function overlayLegacy(directory, checkpoint) {
|
|
634
|
-
|
|
900
|
+
let view = { ...checkpoint.records };
|
|
635
901
|
const legacy = storePath(directory);
|
|
636
902
|
let stat = null;
|
|
637
903
|
try {
|
|
@@ -660,10 +926,25 @@ function overlayLegacy(directory, checkpoint) {
|
|
|
660
926
|
}
|
|
661
927
|
const migration = checkpoint.migration;
|
|
662
928
|
if (migration === null || !migration.done) {
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
const
|
|
929
|
+
const baselineRecords = migration?.baselineRecords ?? {
|
|
930
|
+
...checkpoint.records
|
|
931
|
+
};
|
|
932
|
+
const sameGeneration = migration !== null && stat.size === migration.sourceBytes && stat.mtimeMs === migration.sourceMtimeMs;
|
|
933
|
+
const cursor = sameGeneration ? migration.scannedBytes : 0;
|
|
934
|
+
const discardingOversizeLine = sameGeneration ? migration.discardingOversizeLine : false;
|
|
935
|
+
if (!sameGeneration && migration !== null) {
|
|
936
|
+
view = { ...baselineRecords };
|
|
937
|
+
}
|
|
938
|
+
const fold2 = foldLegacyRegion(legacy, cursor, Number.MAX_SAFE_INTEGER, discardingOversizeLine);
|
|
939
|
+
if (fold2.ioError)
|
|
940
|
+
return {
|
|
941
|
+
view,
|
|
942
|
+
usedLegacy: false,
|
|
943
|
+
absorbed: 0,
|
|
944
|
+
corruptLines: 0,
|
|
945
|
+
overLimit: false,
|
|
946
|
+
aborted: true
|
|
947
|
+
};
|
|
667
948
|
const absorbed2 = mergeFoldedRecords(view, fold2.folded);
|
|
668
949
|
return {
|
|
669
950
|
view,
|
|
@@ -685,6 +966,15 @@ function overlayLegacy(directory, checkpoint) {
|
|
|
685
966
|
};
|
|
686
967
|
}
|
|
687
968
|
const fold = foldLegacyRegion(legacy, 0, Number.MAX_SAFE_INTEGER);
|
|
969
|
+
if (fold.ioError)
|
|
970
|
+
return {
|
|
971
|
+
view,
|
|
972
|
+
usedLegacy: false,
|
|
973
|
+
absorbed: 0,
|
|
974
|
+
corruptLines: 0,
|
|
975
|
+
overLimit: false,
|
|
976
|
+
aborted: true
|
|
977
|
+
};
|
|
688
978
|
const absorbed = mergeFoldedRecords(view, fold.folded);
|
|
689
979
|
return {
|
|
690
980
|
view,
|
|
@@ -713,31 +1003,38 @@ async function loadViewForRead(directory) {
|
|
|
713
1003
|
const size = fileSizeOrNull(legacy);
|
|
714
1004
|
if (size !== null && size <= PR_SUBSCRIPTION_LIMITS.legacySourceMaxBytes) {
|
|
715
1005
|
const fold2 = foldLegacyRegion(legacy, 0, Number.MAX_SAFE_INTEGER);
|
|
716
|
-
|
|
1006
|
+
if (!fold2.ioError)
|
|
1007
|
+
mergeFoldedRecords(view2, fold2.folded);
|
|
717
1008
|
} else if (size !== null) {
|
|
718
1009
|
warnLegacyOverLimit(directory, size);
|
|
719
1010
|
}
|
|
720
1011
|
return { view: view2, recoverySource: "corrupt-recovered" };
|
|
721
1012
|
}
|
|
722
1013
|
const legacyPath = storePath(directory);
|
|
723
|
-
|
|
724
|
-
|
|
1014
|
+
let legacyStat;
|
|
1015
|
+
try {
|
|
1016
|
+
legacyStat = fs.statSync(legacyPath);
|
|
1017
|
+
} catch {
|
|
725
1018
|
return { view: {}, recoverySource: "empty" };
|
|
726
1019
|
}
|
|
1020
|
+
const legacySize = legacyStat.size;
|
|
727
1021
|
if (legacySize > PR_SUBSCRIPTION_LIMITS.legacySourceMaxBytes) {
|
|
728
1022
|
warnLegacyOverLimit(directory, legacySize);
|
|
729
1023
|
return { view: {}, recoverySource: "legacy-log" };
|
|
730
1024
|
}
|
|
731
1025
|
const fold = foldLegacyRegion(legacyPath, 0, Number.MAX_SAFE_INTEGER);
|
|
732
1026
|
const view = {};
|
|
1027
|
+
if (fold.ioError)
|
|
1028
|
+
return { view, recoverySource: "legacy-log" };
|
|
733
1029
|
mergeFoldedRecords(view, fold.folded);
|
|
734
1030
|
if (!fold.aborted) {
|
|
735
|
-
|
|
1031
|
+
_internals.beforeBootstrapLock(directory);
|
|
1032
|
+
await bootstrapCheckpointFromLegacy(directory);
|
|
736
1033
|
}
|
|
737
1034
|
return { view, recoverySource: "legacy-log" };
|
|
738
1035
|
}
|
|
739
1036
|
var bootstrapAttempted = new Map;
|
|
740
|
-
async function bootstrapCheckpointFromLegacy(directory
|
|
1037
|
+
async function bootstrapCheckpointFromLegacy(directory) {
|
|
741
1038
|
if (bootstrapAttempted.has(directory))
|
|
742
1039
|
return;
|
|
743
1040
|
if (bootstrapAttempted.size >= 32) {
|
|
@@ -748,49 +1045,108 @@ async function bootstrapCheckpointFromLegacy(directory, foldedView, fold) {
|
|
|
748
1045
|
await withEvidenceLock(directory, PR_SUBSCRIPTIONS_FILE, STORE_LOCK_AGENT, STORE_LOCK_TASK, async () => {
|
|
749
1046
|
if (readCheckpoint(directory).kind !== "absent")
|
|
750
1047
|
return;
|
|
1048
|
+
let beforeFold;
|
|
1049
|
+
try {
|
|
1050
|
+
beforeFold = fs.statSync(storePath(directory));
|
|
1051
|
+
} catch {
|
|
1052
|
+
return;
|
|
1053
|
+
}
|
|
1054
|
+
const lockedFold = foldLegacyRegion(storePath(directory), 0, PR_SUBSCRIPTION_LIMITS.legacySourceMaxBytes);
|
|
1055
|
+
if (lockedFold.ioError)
|
|
1056
|
+
return;
|
|
1057
|
+
let afterFold;
|
|
1058
|
+
try {
|
|
1059
|
+
afterFold = fs.statSync(storePath(directory));
|
|
1060
|
+
} catch {
|
|
1061
|
+
return;
|
|
1062
|
+
}
|
|
1063
|
+
if (afterFold.size !== beforeFold.size || afterFold.mtimeMs !== beforeFold.mtimeMs)
|
|
1064
|
+
return;
|
|
1065
|
+
const foldedView = {};
|
|
1066
|
+
mergeFoldedRecords(foldedView, lockedFold.folded);
|
|
751
1067
|
const checkpoint = freshCheckpoint(directory);
|
|
752
1068
|
checkpoint.records = { ...foldedView };
|
|
753
1069
|
const terminals = Object.values(foldedView).filter((rec) => rec.status !== "active");
|
|
754
1070
|
checkpoint.terminalSummary.lastTerminalAt = terminals.length ? Math.max(...terminals.map((rec) => rec.updatedAt)) : null;
|
|
755
|
-
checkpoint.maintenance.corruptLegacyRecords =
|
|
756
|
-
let bootstrapMtime = 0;
|
|
757
|
-
try {
|
|
758
|
-
bootstrapMtime = fs.statSync(storePath(directory)).mtimeMs;
|
|
759
|
-
} catch {}
|
|
1071
|
+
checkpoint.maintenance.corruptLegacyRecords = lockedFold.corruptLines;
|
|
760
1072
|
checkpoint.migration = {
|
|
761
|
-
scannedBytes:
|
|
762
|
-
sourceBytes:
|
|
763
|
-
sourceMtimeMs:
|
|
764
|
-
corruptLines:
|
|
1073
|
+
scannedBytes: lockedFold.nextByte,
|
|
1074
|
+
sourceBytes: beforeFold.size,
|
|
1075
|
+
sourceMtimeMs: beforeFold.mtimeMs,
|
|
1076
|
+
corruptLines: lockedFold.corruptLines,
|
|
1077
|
+
discardingOversizeLine: false,
|
|
1078
|
+
baselineRecords: {},
|
|
1079
|
+
baselineTerminalSummary: {
|
|
1080
|
+
removed: 0,
|
|
1081
|
+
expired: 0,
|
|
1082
|
+
lastTerminalAt: null
|
|
1083
|
+
},
|
|
765
1084
|
done: true,
|
|
766
1085
|
archived: false,
|
|
767
1086
|
startedAt: Date.now()
|
|
768
1087
|
};
|
|
769
1088
|
checkpoint.sequence = 1;
|
|
770
1089
|
checkpoint.updatedAt = Date.now();
|
|
771
|
-
|
|
772
|
-
|
|
1090
|
+
const auditCompaction = prepareAuditCompaction(directory);
|
|
1091
|
+
persistCheckpointWithAuditCompaction(directory, checkpoint, auditCompaction);
|
|
1092
|
+
const archiveEvent = maybeArchiveLegacy(directory, checkpoint);
|
|
1093
|
+
if (archiveEvent) {
|
|
1094
|
+
checkpoint.sequence += 1;
|
|
1095
|
+
checkpoint.updatedAt = Date.now();
|
|
1096
|
+
_internals.writeCheckpointFile(directory, checkpoint);
|
|
1097
|
+
}
|
|
1098
|
+
flushAuditEvents(directory, [
|
|
1099
|
+
{ kind: "migrate-complete" },
|
|
1100
|
+
...archiveEvent ? [archiveEvent] : []
|
|
1101
|
+
], checkpoint.sequence, checkpoint.maintenance);
|
|
773
1102
|
emitHealthTelemetry("migrate-complete", checkpoint);
|
|
1103
|
+
if (archiveEvent)
|
|
1104
|
+
emitHealthTelemetry("archive", checkpoint);
|
|
774
1105
|
}, PR_SUBSCRIPTION_LIMITS.bootstrapLockTimeoutMs);
|
|
775
1106
|
} catch (err) {
|
|
776
|
-
log(`[pr-monitor] read-bootstrap skipped: ${
|
|
1107
|
+
log(`[pr-monitor] read-bootstrap skipped: ${storageErrorCode(err)}`);
|
|
777
1108
|
}
|
|
778
1109
|
}
|
|
779
1110
|
function loadViewForWrite(directory) {
|
|
780
1111
|
const audit = [];
|
|
781
1112
|
let dirty = false;
|
|
1113
|
+
let reboundForeign = false;
|
|
782
1114
|
let checkpoint;
|
|
783
1115
|
const read = readCheckpoint(directory);
|
|
784
1116
|
if (read.kind === "ok" && sameProjectRoot(read.value.rootPath, directory)) {
|
|
785
1117
|
checkpoint = cloneCheckpoint(read.value);
|
|
786
1118
|
} else if (read.kind === "ok") {
|
|
787
1119
|
const displaced = Object.keys(read.value.records).length;
|
|
788
|
-
|
|
1120
|
+
const legacy2 = storePath(directory);
|
|
1121
|
+
let foreignLegacyExists = false;
|
|
1122
|
+
try {
|
|
1123
|
+
_internals.statSync(legacy2);
|
|
1124
|
+
foreignLegacyExists = true;
|
|
1125
|
+
} catch (err) {
|
|
1126
|
+
if (err.code !== "ENOENT") {
|
|
1127
|
+
throw new Error(`PR subscription foreign legacy log could not be inspected; refusing to rebind (${storageErrorCode(err)})`);
|
|
1128
|
+
}
|
|
1129
|
+
}
|
|
1130
|
+
if (foreignLegacyExists) {
|
|
1131
|
+
const foreignLegacy = foreignLegacySlotPath(directory);
|
|
1132
|
+
try {
|
|
1133
|
+
try {
|
|
1134
|
+
fs.unlinkSync(foreignLegacy);
|
|
1135
|
+
} catch {}
|
|
1136
|
+
_internals.renameWithRetry(legacy2, foreignLegacy);
|
|
1137
|
+
} catch (err) {
|
|
1138
|
+
throw new Error(`PR subscription foreign legacy log could not be quarantined; refusing to rebind (${storageErrorCode(err)})`);
|
|
1139
|
+
}
|
|
1140
|
+
}
|
|
1141
|
+
if (!quarantineCheckpoint(directory, "foreign")) {
|
|
1142
|
+
throw new Error("PR subscription foreign checkpoint could not be quarantined; refusing to rebind");
|
|
1143
|
+
}
|
|
789
1144
|
checkpoint = freshCheckpoint(directory);
|
|
790
1145
|
checkpoint.maintenance.resets += read.value.maintenance.resets + 1;
|
|
791
1146
|
audit.push({ kind: "foreign-rebind" });
|
|
792
1147
|
dirty = true;
|
|
793
1148
|
log(`[pr-monitor] foreign checkpoint quarantined (${displaced} displaced records, recorded root ${read.value.rootPath}); store rebound to ${directory}`);
|
|
1149
|
+
reboundForeign = true;
|
|
794
1150
|
} else if (read.kind === "invalid") {
|
|
795
1151
|
quarantineCheckpoint(directory, "corrupt");
|
|
796
1152
|
checkpoint = freshCheckpoint(directory);
|
|
@@ -801,12 +1157,25 @@ function loadViewForWrite(directory) {
|
|
|
801
1157
|
} else {
|
|
802
1158
|
checkpoint = freshCheckpoint(directory);
|
|
803
1159
|
}
|
|
1160
|
+
if (!reboundForeign && checkpoint.migration !== null) {
|
|
1161
|
+
try {
|
|
1162
|
+
reconcileLegacyArchiveStaging(directory, checkpoint.migration);
|
|
1163
|
+
} catch (err) {
|
|
1164
|
+
throw new Error(`PR subscription legacy archive staging could not be reconciled; refusing mutation (${storageErrorCode(err)})`);
|
|
1165
|
+
}
|
|
1166
|
+
}
|
|
804
1167
|
const legacy = storePath(directory);
|
|
805
1168
|
let legacyStat = null;
|
|
806
1169
|
try {
|
|
807
|
-
legacyStat =
|
|
808
|
-
} catch {
|
|
1170
|
+
legacyStat = _internals.statSync(legacy);
|
|
1171
|
+
} catch (err) {
|
|
1172
|
+
if (err.code !== "ENOENT") {
|
|
1173
|
+
throw new Error(`PR subscription legacy store could not be inspected; refusing mutation (${storageErrorCode(err)})`);
|
|
1174
|
+
}
|
|
1175
|
+
}
|
|
809
1176
|
let view;
|
|
1177
|
+
if (reboundForeign)
|
|
1178
|
+
legacyStat = null;
|
|
810
1179
|
if (legacyStat === null) {
|
|
811
1180
|
view = { ...checkpoint.records };
|
|
812
1181
|
if (checkpoint.migration?.done && !checkpoint.migration.archived) {
|
|
@@ -817,25 +1186,44 @@ function loadViewForWrite(directory) {
|
|
|
817
1186
|
}
|
|
818
1187
|
if (legacyStat.size > PR_SUBSCRIPTION_LIMITS.legacySourceMaxBytes) {
|
|
819
1188
|
warnLegacyOverLimit(directory, legacyStat.size);
|
|
820
|
-
|
|
1189
|
+
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`);
|
|
821
1190
|
}
|
|
822
1191
|
const legacySize = legacyStat.size;
|
|
823
1192
|
const legacyMtime = legacyStat.mtimeMs;
|
|
824
1193
|
const migration = checkpoint.migration;
|
|
825
1194
|
if (migration === null || !migration.done) {
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
const
|
|
830
|
-
|
|
1195
|
+
const baselineRecords = migration?.baselineRecords ?? {
|
|
1196
|
+
...checkpoint.records
|
|
1197
|
+
};
|
|
1198
|
+
const baselineTerminalSummary = migration?.baselineTerminalSummary ?? {
|
|
1199
|
+
...checkpoint.terminalSummary
|
|
1200
|
+
};
|
|
1201
|
+
const sameGeneration = migration !== null && legacySize === migration.sourceBytes && legacyMtime === migration.sourceMtimeMs;
|
|
1202
|
+
let cursor = sameGeneration ? migration.scannedBytes : 0;
|
|
1203
|
+
const startedAt = sameGeneration ? migration.startedAt : Date.now();
|
|
1204
|
+
let corruptTotal = sameGeneration ? migration.corruptLines : 0;
|
|
1205
|
+
let discardingOversizeLine = sameGeneration ? migration.discardingOversizeLine : false;
|
|
1206
|
+
if (!sameGeneration && migration !== null) {
|
|
1207
|
+
checkpoint.records = { ...baselineRecords };
|
|
1208
|
+
checkpoint.terminalSummary = { ...baselineTerminalSummary };
|
|
1209
|
+
checkpoint.maintenance.corruptLegacyRecords = Math.max(0, checkpoint.maintenance.corruptLegacyRecords - migration.corruptLines);
|
|
1210
|
+
}
|
|
831
1211
|
view = { ...checkpoint.records };
|
|
832
1212
|
let capacityRefused = false;
|
|
1213
|
+
let foldedThisOperation = 0;
|
|
833
1214
|
while (true) {
|
|
834
|
-
const
|
|
1215
|
+
const chunkStart = cursor;
|
|
1216
|
+
const remainingOperationBytes = Math.max(1, PR_SUBSCRIPTION_LIMITS.migrationMaxBytesPerOperation - foldedThisOperation);
|
|
1217
|
+
const fold = foldLegacyRegion(legacy, cursor, Math.min(PR_SUBSCRIPTION_LIMITS.migrationChunkBytes, remainingOperationBytes), discardingOversizeLine);
|
|
1218
|
+
if (fold.ioError) {
|
|
1219
|
+
throw new Error("PR subscription legacy migration read failed (I/O error)");
|
|
1220
|
+
}
|
|
835
1221
|
mergeFoldedRecords(view, fold.folded);
|
|
836
1222
|
checkpoint.maintenance.corruptLegacyRecords += fold.corruptLines;
|
|
837
1223
|
corruptTotal += fold.corruptLines;
|
|
1224
|
+
discardingOversizeLine = fold.discardingOversizeLine;
|
|
838
1225
|
cursor = fold.nextByte;
|
|
1226
|
+
foldedThisOperation += Math.max(0, cursor - chunkStart);
|
|
839
1227
|
if (fold.aborted) {
|
|
840
1228
|
const progress = cloneCheckpoint(checkpoint);
|
|
841
1229
|
progress.records = { ...view };
|
|
@@ -844,6 +1232,9 @@ function loadViewForWrite(directory) {
|
|
|
844
1232
|
sourceBytes: legacySize,
|
|
845
1233
|
sourceMtimeMs: legacyMtime,
|
|
846
1234
|
corruptLines: corruptTotal,
|
|
1235
|
+
discardingOversizeLine,
|
|
1236
|
+
baselineRecords: { ...baselineRecords },
|
|
1237
|
+
baselineTerminalSummary: { ...baselineTerminalSummary },
|
|
847
1238
|
done: false,
|
|
848
1239
|
archived: false,
|
|
849
1240
|
startedAt
|
|
@@ -872,6 +1263,9 @@ function loadViewForWrite(directory) {
|
|
|
872
1263
|
sourceBytes: legacySize,
|
|
873
1264
|
sourceMtimeMs: legacyMtime,
|
|
874
1265
|
corruptLines: corruptTotal,
|
|
1266
|
+
discardingOversizeLine,
|
|
1267
|
+
baselineRecords: { ...baselineRecords },
|
|
1268
|
+
baselineTerminalSummary: { ...baselineTerminalSummary },
|
|
875
1269
|
done: false,
|
|
876
1270
|
archived: false,
|
|
877
1271
|
startedAt
|
|
@@ -881,6 +1275,9 @@ function loadViewForWrite(directory) {
|
|
|
881
1275
|
writeCheckpointFile(directory, progress);
|
|
882
1276
|
checkpoint.sequence = progress.sequence;
|
|
883
1277
|
dirty = true;
|
|
1278
|
+
if (foldedThisOperation >= PR_SUBSCRIPTION_LIMITS.migrationMaxBytesPerOperation) {
|
|
1279
|
+
throw new Error(`PR subscription legacy migration is in progress (${cursor}/${legacySize} bytes); retry the operation to continue`);
|
|
1280
|
+
}
|
|
884
1281
|
continue;
|
|
885
1282
|
}
|
|
886
1283
|
checkpoint.migration = {
|
|
@@ -888,6 +1285,13 @@ function loadViewForWrite(directory) {
|
|
|
888
1285
|
sourceBytes: legacySize,
|
|
889
1286
|
sourceMtimeMs: legacyMtime,
|
|
890
1287
|
corruptLines: corruptTotal,
|
|
1288
|
+
discardingOversizeLine: false,
|
|
1289
|
+
baselineRecords: {},
|
|
1290
|
+
baselineTerminalSummary: {
|
|
1291
|
+
removed: 0,
|
|
1292
|
+
expired: 0,
|
|
1293
|
+
lastTerminalAt: null
|
|
1294
|
+
},
|
|
891
1295
|
done: true,
|
|
892
1296
|
archived: false,
|
|
893
1297
|
startedAt
|
|
@@ -919,20 +1323,32 @@ function finalizeWrite(directory, loaded, opAudit) {
|
|
|
919
1323
|
const compactEvent = compactTerminalRecords(loaded.view, checkpoint);
|
|
920
1324
|
if (compactEvent)
|
|
921
1325
|
events.push(compactEvent);
|
|
922
|
-
if (!loaded.dirty && events.length === 0)
|
|
1326
|
+
if (!loaded.dirty && events.length === 0) {
|
|
1327
|
+
const archiveEvent2 = maybeArchiveLegacy(directory, checkpoint);
|
|
1328
|
+
if (!archiveEvent2)
|
|
1329
|
+
return;
|
|
1330
|
+
checkpoint.updatedAt = Date.now();
|
|
1331
|
+
checkpoint.sequence += 1;
|
|
1332
|
+
const auditCompaction2 = prepareAuditCompaction(directory);
|
|
1333
|
+
persistCheckpointWithAuditCompaction(directory, checkpoint, auditCompaction2);
|
|
1334
|
+
flushAuditEvents(directory, [archiveEvent2], checkpoint.sequence, checkpoint.maintenance);
|
|
1335
|
+
cleanupStaleLegacyArchive(directory);
|
|
1336
|
+
emitHealthTelemetry("archive", checkpoint);
|
|
923
1337
|
return;
|
|
1338
|
+
}
|
|
924
1339
|
checkpoint.records = loaded.view;
|
|
925
1340
|
checkpoint.updatedAt = Date.now();
|
|
926
1341
|
checkpoint.rootPath = path.resolve(directory);
|
|
927
1342
|
checkpoint.sequence += 1;
|
|
1343
|
+
const auditCompaction = prepareAuditCompaction(directory);
|
|
1344
|
+
persistCheckpointWithAuditCompaction(directory, checkpoint, auditCompaction);
|
|
928
1345
|
flushAuditEvents(directory, events, checkpoint.sequence, checkpoint.maintenance);
|
|
929
|
-
writeCheckpointFile(directory, checkpoint);
|
|
930
1346
|
const archiveEvent = maybeArchiveLegacy(directory, checkpoint);
|
|
931
1347
|
if (archiveEvent) {
|
|
932
1348
|
events.push(archiveEvent);
|
|
933
1349
|
checkpoint.sequence += 1;
|
|
1350
|
+
_internals.writeCheckpointFile(directory, checkpoint);
|
|
934
1351
|
flushAuditEvents(directory, [archiveEvent], checkpoint.sequence, checkpoint.maintenance);
|
|
935
|
-
writeCheckpointFile(directory, checkpoint);
|
|
936
1352
|
}
|
|
937
1353
|
cleanupStaleLegacyArchive(directory);
|
|
938
1354
|
for (const kind of [
|
|
@@ -1061,6 +1477,7 @@ async function updateSnapshot(directory, correlationId, updates) {
|
|
|
1061
1477
|
createdAt: match.createdAt,
|
|
1062
1478
|
updatedAt: Date.now()
|
|
1063
1479
|
};
|
|
1480
|
+
validateRecord(updated);
|
|
1064
1481
|
loaded.view[correlationId] = updated;
|
|
1065
1482
|
loaded.dirty = true;
|
|
1066
1483
|
finalizeWrite(directory, loaded, []);
|
|
@@ -1139,7 +1556,8 @@ async function getPrSubscriptionHealth(directory) {
|
|
|
1139
1556
|
if (legacySize !== null && !legacyOverLimit) {
|
|
1140
1557
|
const fold = foldLegacyRegion(storePath(directory), 0, Number.MAX_SAFE_INTEGER);
|
|
1141
1558
|
const view = {};
|
|
1142
|
-
|
|
1559
|
+
if (!fold.ioError)
|
|
1560
|
+
mergeFoldedRecords(view, fold.folded);
|
|
1143
1561
|
health = healthFromView(view, freshCheckpoint(directory));
|
|
1144
1562
|
health.corruptLegacyRecords = fold.corruptLines;
|
|
1145
1563
|
} else {
|
|
@@ -1152,7 +1570,8 @@ async function getPrSubscriptionHealth(directory) {
|
|
|
1152
1570
|
} else {
|
|
1153
1571
|
const fold = foldLegacyRegion(storePath(directory), 0, Number.MAX_SAFE_INTEGER);
|
|
1154
1572
|
const view = {};
|
|
1155
|
-
|
|
1573
|
+
if (!fold.ioError)
|
|
1574
|
+
mergeFoldedRecords(view, fold.folded);
|
|
1156
1575
|
health = healthFromView(view, freshCheckpoint(directory));
|
|
1157
1576
|
health.corruptLegacyRecords = fold.corruptLines;
|
|
1158
1577
|
}
|
|
@@ -1166,7 +1585,7 @@ async function getPrSubscriptionHealth(directory) {
|
|
|
1166
1585
|
applyFileStats(health, checkpointBytes, audit, archiveBytes);
|
|
1167
1586
|
return health;
|
|
1168
1587
|
} catch (err) {
|
|
1169
|
-
log(`[pr-monitor] getPrSubscriptionHealth failed: ${
|
|
1588
|
+
log(`[pr-monitor] getPrSubscriptionHealth failed: ${storageErrorCode(err)}`);
|
|
1170
1589
|
return emptyHealth();
|
|
1171
1590
|
}
|
|
1172
1591
|
}
|
|
@@ -1223,9 +1642,21 @@ function healthFromView(view, checkpoint) {
|
|
|
1223
1642
|
return health;
|
|
1224
1643
|
}
|
|
1225
1644
|
var _internals = {
|
|
1645
|
+
afterCheckpointFstat: (_filePath) => {},
|
|
1646
|
+
archiveStatSync: fs.statSync,
|
|
1647
|
+
auditCompactionWrite: atomicWriteSwarmFileSync,
|
|
1648
|
+
beforeBootstrapLock: (_directory) => {},
|
|
1649
|
+
beforeArchiveLegacy: (_directory) => {},
|
|
1650
|
+
beforeArchiveRename: (_directory) => {},
|
|
1226
1651
|
foldLegacyRegion,
|
|
1652
|
+
legacyCloseSync: fs.closeSync,
|
|
1653
|
+
legacyFstatSync: fs.fstatSync,
|
|
1227
1654
|
mergeFoldedRecords,
|
|
1228
|
-
|
|
1655
|
+
readSync: fs.readSync,
|
|
1656
|
+
renameWithRetry,
|
|
1657
|
+
sameProjectRoot,
|
|
1658
|
+
statSync: fs.statSync,
|
|
1659
|
+
writeCheckpointFile
|
|
1229
1660
|
};
|
|
1230
1661
|
|
|
1231
1662
|
export { PR_SUBSCRIPTIONS_FILE, PR_SUBSCRIPTIONS_CHECKPOINT_FILE, PR_SUBSCRIPTIONS_AUDIT_FILE, PR_SUBSCRIPTION_LIMITS, setOnSubscriptionCreated, buildCorrelationId, subscribe, unsubscribe, listActive, lookupByPr, updateSnapshot, sweepStale, getPrSubscriptionHealth, _internals };
|