@ouro.bot/cli 0.1.0-alpha.690 → 0.1.0-alpha.691
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/changelog.json
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"_note": "This changelog is maintained as part of the PR/version-bump workflow. Agent-curated, not auto-generated. Agents read this file directly via read_file to understand what changed between versions.",
|
|
3
3
|
"versions": [
|
|
4
|
+
{
|
|
5
|
+
"version": "0.1.0-alpha.691",
|
|
6
|
+
"changes": [
|
|
7
|
+
"Bound context-loss Sentinel per-receipt detail files while preserving append-only history, so long-lived agents cannot accumulate unbounded duplicate recovery JSON in their bundles."
|
|
8
|
+
]
|
|
9
|
+
},
|
|
4
10
|
{
|
|
5
11
|
"version": "0.1.0-alpha.690",
|
|
6
12
|
"changes": [
|
|
@@ -47,6 +47,7 @@ const flight_recorder_1 = require("../arc/flight-recorder");
|
|
|
47
47
|
const runtime_1 = require("../nerves/runtime");
|
|
48
48
|
const context_loss_gauntlet_1 = require("./context-loss-gauntlet");
|
|
49
49
|
const provider_visibility_1 = require("./provider-visibility");
|
|
50
|
+
const CONTEXT_LOSS_SENTINEL_RECEIPT_FILE_LIMIT = 512;
|
|
50
51
|
const REQUIRED_LANES = ["outward", "inner"];
|
|
51
52
|
function logicalLocator(...parts) {
|
|
52
53
|
return path.posix.join(...parts);
|
|
@@ -324,6 +325,43 @@ function readReceiptFile(filePath, label, issues) {
|
|
|
324
325
|
}
|
|
325
326
|
return parsed.value;
|
|
326
327
|
}
|
|
328
|
+
function receiptIdFromFileName(fileName) {
|
|
329
|
+
return fileName.endsWith(".json") ? fileName.slice(0, -".json".length) : null;
|
|
330
|
+
}
|
|
331
|
+
function receiptOrderFromDetailFile(filePath, fileName) {
|
|
332
|
+
try {
|
|
333
|
+
const parsed = JSON.parse(fs.readFileSync(filePath, "utf-8"));
|
|
334
|
+
if (isReceipt(parsed))
|
|
335
|
+
return orderFromReceipt(parsed);
|
|
336
|
+
}
|
|
337
|
+
catch {
|
|
338
|
+
// Malformed legacy detail files should not block retention cleanup.
|
|
339
|
+
}
|
|
340
|
+
const stat = fs.statSync(filePath);
|
|
341
|
+
return {
|
|
342
|
+
generatedAt: new Date(stat.mtimeMs).toISOString(),
|
|
343
|
+
id: receiptIdFromFileName(fileName),
|
|
344
|
+
};
|
|
345
|
+
}
|
|
346
|
+
function pruneReceiptDetailFiles(paths, protectedReceiptIds) {
|
|
347
|
+
const fileNames = fs.readdirSync(paths.receiptsDir)
|
|
348
|
+
.filter((entry) => receiptIdFromFileName(entry) !== null);
|
|
349
|
+
if (fileNames.length <= CONTEXT_LOSS_SENTINEL_RECEIPT_FILE_LIMIT)
|
|
350
|
+
return;
|
|
351
|
+
const candidates = fileNames
|
|
352
|
+
.map((fileName) => ({
|
|
353
|
+
fileName,
|
|
354
|
+
filePath: path.join(paths.receiptsDir, fileName),
|
|
355
|
+
receiptId: receiptIdFromFileName(fileName),
|
|
356
|
+
order: receiptOrderFromDetailFile(path.join(paths.receiptsDir, fileName), fileName),
|
|
357
|
+
}))
|
|
358
|
+
.filter((entry) => !protectedReceiptIds.has(entry.receiptId))
|
|
359
|
+
.sort((left, right) => compareOrder(left.order, right.order));
|
|
360
|
+
const removalCount = fileNames.length - CONTEXT_LOSS_SENTINEL_RECEIPT_FILE_LIMIT;
|
|
361
|
+
for (const entry of candidates.slice(0, removalCount)) {
|
|
362
|
+
fs.rmSync(entry.filePath, { force: true });
|
|
363
|
+
}
|
|
364
|
+
}
|
|
327
365
|
function isNonEmptyText(value) {
|
|
328
366
|
return typeof value === "string" && value.trim().length > 0;
|
|
329
367
|
}
|
|
@@ -806,19 +844,24 @@ async function persistReceipt(agentRoot, receipt, lockTimeoutMs) {
|
|
|
806
844
|
const coalesced = coalescedDaemonHealthReceipt(agentRoot, existingLatest, existingReady, receipt, watermark);
|
|
807
845
|
if (coalesced)
|
|
808
846
|
return coalesced;
|
|
809
|
-
|
|
847
|
+
const receiptPath = path.join(paths.receiptsDir, `${receipt.id}.json`);
|
|
848
|
+
atomicWriteJson(receiptPath, receipt);
|
|
849
|
+
setReceiptFileMtime(receiptPath, receipt);
|
|
810
850
|
appendHistory(paths, receipt);
|
|
851
|
+
let latestAfterWrite = existingLatest ?? receipt;
|
|
811
852
|
if (shouldReplaceLatestReceipt(existingLatest, receipt, latestOrder)) {
|
|
812
853
|
atomicWriteJson(paths.latest, receipt);
|
|
813
854
|
updateWatermarkOrder(watermark, "latest", receipt);
|
|
814
855
|
writeWatermark(agentRoot, watermark);
|
|
815
856
|
setReceiptFileMtime(paths.latest, receipt);
|
|
857
|
+
latestAfterWrite = receipt;
|
|
816
858
|
recordBlockedReceiptEvent(agentRoot, receipt);
|
|
817
859
|
recordRecoveredReceiptEvent(agentRoot, receipt);
|
|
818
860
|
}
|
|
819
861
|
else if (existingLatest && nextReady === receipt) {
|
|
820
862
|
const updatedLatest = syncLatestReadyState(existingLatest, nextReady);
|
|
821
863
|
atomicWriteJson(paths.latest, updatedLatest);
|
|
864
|
+
latestAfterWrite = updatedLatest;
|
|
822
865
|
}
|
|
823
866
|
if (receipt.verdict === "ready" && nextReady === receipt) {
|
|
824
867
|
atomicWriteJson(paths.latestReady, receipt);
|
|
@@ -826,6 +869,10 @@ async function persistReceipt(agentRoot, receipt, lockTimeoutMs) {
|
|
|
826
869
|
writeWatermark(agentRoot, watermark);
|
|
827
870
|
setReceiptFileMtime(paths.latestReady, receipt);
|
|
828
871
|
}
|
|
872
|
+
const protectedReceiptIds = new Set([receipt.id, latestAfterWrite.id]);
|
|
873
|
+
if (nextReady)
|
|
874
|
+
protectedReceiptIds.add(nextReady.id);
|
|
875
|
+
pruneReceiptDetailFiles(paths, protectedReceiptIds);
|
|
829
876
|
return receipt;
|
|
830
877
|
});
|
|
831
878
|
}
|