@remnic/core 9.51.1 → 9.52.0
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/access-admin-ops-surface.d.ts +6 -5
- package/dist/access-admin-ops-surface.js +16 -16
- package/dist/access-authorization-probe.d.ts +6 -5
- package/dist/access-authorization-probe.js +17 -17
- package/dist/access-boundary.d.ts +6 -5
- package/dist/access-boundary.js +16 -16
- package/dist/access-cli.js +39 -39
- package/dist/access-extraction-force-flush.d.ts +6 -5
- package/dist/access-extraction-force-flush.js +16 -16
- package/dist/access-http-lcm-compaction.d.ts +6 -5
- package/dist/access-http-lifecycle-flush.d.ts +6 -5
- package/dist/access-http-offline-stream.d.ts +6 -5
- package/dist/access-http-query.js +16 -16
- package/dist/access-http.d.ts +6 -5
- package/dist/access-http.js +22 -22
- package/dist/access-identity-continuity-surface.d.ts +5 -4
- package/dist/access-identity-continuity-surface.js +16 -16
- package/dist/access-lcm-surface.d.ts +6 -5
- package/dist/access-lcm-surface.js +16 -16
- package/dist/access-mcp.d.ts +6 -5
- package/dist/access-mcp.js +20 -20
- package/dist/access-namespace-preflight.js +16 -16
- package/dist/access-observe-write-surface.d.ts +6 -5
- package/dist/access-observe-write-surface.js +16 -16
- package/dist/access-offline-manifest.d.ts +5 -4
- package/dist/access-operations-batch.js +17 -17
- package/dist/access-operations.d.ts +9 -8
- package/dist/access-operations.js +19 -19
- package/dist/access-recall-concurrency.d.ts +6 -5
- package/dist/access-recall-concurrency.js +16 -16
- package/dist/access-recall-response.d.ts +6 -5
- package/dist/access-recall-response.js +16 -16
- package/dist/access-recall-surface.d.ts +6 -5
- package/dist/access-recall-surface.js +16 -16
- package/dist/access-schema.d.ts +88 -88
- package/dist/{access-service-BY0KJ44c.d.ts → access-service-Cbk3bNbD.d.ts} +3 -3
- package/dist/access-service-helpers.d.ts +6 -5
- package/dist/access-service.d.ts +6 -5
- package/dist/access-service.js +16 -16
- package/dist/access-surface-catalog.d.ts +6 -5
- package/dist/access-wearables-meetings-surface.d.ts +2 -2
- package/dist/bootstrap.d.ts +5 -4
- package/dist/briefing.d.ts +1 -1
- package/dist/briefing.js +7 -7
- package/dist/buffer.d.ts +1 -1
- package/dist/{capsule-merge-XMFBXVDP.js → capsule-merge-HCJJMLPS.js} +4 -4
- package/dist/causal-consolidation.js +8 -8
- package/dist/{chunk-PWN32TAG.js → chunk-2ASAS3CQ.js} +2 -2
- package/dist/{chunk-WQM326GS.js → chunk-2LY6GIIE.js} +2 -2
- package/dist/{chunk-JL43BVDE.js → chunk-2OIQDTDC.js} +2 -2
- package/dist/{chunk-33SV3E43.js → chunk-3OY7KXHM.js} +3 -3
- package/dist/{chunk-AIUBBHSF.js → chunk-5UZ6UCOZ.js} +3 -3
- package/dist/chunk-5WITETWU.js +591 -0
- package/dist/chunk-5WITETWU.js.map +1 -0
- package/dist/{chunk-UT5BV5OG.js → chunk-7HXZJAID.js} +2 -2
- package/dist/{chunk-M7BXDKPR.js → chunk-7M6NYQWB.js} +3 -3
- package/dist/{chunk-TPTECFWR.js → chunk-B6LZ4IBH.js} +3 -3
- package/dist/{chunk-J5LLX6IL.js → chunk-BF6SXNQJ.js} +10 -10
- package/dist/{chunk-4PC4PRJE.js → chunk-BKIFTXHR.js} +2 -2
- package/dist/{chunk-VVG2BOKA.js → chunk-BMEYMKWX.js} +2 -2
- package/dist/{chunk-DCZBSE4F.js → chunk-D5YOZ66N.js} +18 -18
- package/dist/{chunk-BHXYAVUM.js → chunk-DLG5V6ND.js} +2 -2
- package/dist/{chunk-52UKFTUK.js → chunk-F4EU6VUC.js} +2 -2
- package/dist/{chunk-M4A44LUA.js → chunk-GSTIBXBW.js} +2 -2
- package/dist/{chunk-W5XVQFLM.js → chunk-GXFSQ6SD.js} +2 -2
- package/dist/{chunk-XADV5IZ3.js → chunk-HDIUYMSB.js} +5 -5
- package/dist/{chunk-FXTYIA46.js → chunk-J272WKX2.js} +2 -2
- package/dist/{chunk-ENTCVTQR.js → chunk-JJRVPWFY.js} +11 -1
- package/dist/chunk-JJRVPWFY.js.map +1 -0
- package/dist/{chunk-JYL3BVVZ.js → chunk-JSEJH2YQ.js} +2 -2
- package/dist/{chunk-5UDMFE3Q.js → chunk-MLXZD4WM.js} +2 -2
- package/dist/{chunk-PUSLMZBQ.js → chunk-NOEMGE56.js} +2 -2
- package/dist/{chunk-C5QLI33R.js → chunk-QINJITDS.js} +2 -2
- package/dist/{chunk-45BRYON6.js → chunk-QPOHMO6M.js} +2 -2
- package/dist/{chunk-ZQOCRIGA.js → chunk-QYN5PXDZ.js} +3 -3
- package/dist/{chunk-PA45XCUD.js → chunk-RANKJI5T.js} +2 -2
- package/dist/{chunk-ZHY5TMJG.js → chunk-RMHMUUBW.js} +2392 -628
- package/dist/chunk-RMHMUUBW.js.map +1 -0
- package/dist/{chunk-37ZO6LLJ.js → chunk-RYLEDKQ2.js} +2 -2
- package/dist/{chunk-CV6BBVYV.js → chunk-TEMSWHQD.js} +2 -2
- package/dist/{chunk-QGR6OVGT.js → chunk-VFSAWRMZ.js} +2 -2
- package/dist/{chunk-NLK2VZYZ.js → chunk-VXCNQYTC.js} +2 -2
- package/dist/{chunk-AZ2QBGYB.js → chunk-W7Y5JDVB.js} +1 -1
- package/dist/{chunk-VBHPOWBJ.js → chunk-WD5B5AIE.js} +1051 -591
- package/dist/chunk-WD5B5AIE.js.map +1 -0
- package/dist/{chunk-OYESWJ43.js → chunk-WLRD2TXF.js} +15 -10
- package/dist/chunk-WLRD2TXF.js.map +1 -0
- package/dist/{chunk-4F6X3RXW.js → chunk-X57M2Q4I.js} +2 -2
- package/dist/{chunk-6MTSFX7A.js → chunk-X7XEDOSL.js} +8 -8
- package/dist/{chunk-UEVLYVNW.js → chunk-YXKVLYXU.js} +2 -2
- package/dist/{chunk-OQAL6SIR.js → chunk-ZMSZVISA.js} +2 -2
- package/dist/{cli-CnOkH9hA.d.ts → cli-DMAFI0qt.d.ts} +3 -3
- package/dist/cli.d.ts +7 -6
- package/dist/cli.js +33 -33
- package/dist/compounding/engine.d.ts +1 -1
- package/dist/compounding/engine.js +7 -7
- package/dist/connectors/codex-materialize-runner.js +7 -7
- package/dist/connectors/index.js +7 -7
- package/dist/consolidation-provenance-check.d.ts +1 -1
- package/dist/consolidation-provenance-check.js +4 -4
- package/dist/consolidation-undo.d.ts +1 -1
- package/dist/consolidation-undo.js +3 -3
- package/dist/contradiction/index.d.ts +1 -1
- package/dist/convergence-refresh.d.ts +1 -1
- package/dist/convergence-refresh.js +9 -9
- package/dist/corpus-watermark.js +9 -9
- package/dist/dependency-propagation-delivery-D63r24pH.d.ts +41 -0
- package/dist/entity-retrieval.d.ts +1 -1
- package/dist/entity-retrieval.js +7 -7
- package/dist/explicit-capture.d.ts +5 -4
- package/dist/explicit-capture.js +4 -4
- package/dist/external-wiki-access.d.ts +6 -5
- package/dist/external-wiki-access.js +17 -17
- package/dist/external-wiki-mcp-tools.d.ts +6 -5
- package/dist/extraction.js +7 -7
- package/dist/{forget-UJDJHG6R.js → forget-HDS4N6CG.js} +3 -3
- package/dist/harmonic-retrieval.js +1 -1
- package/dist/index.d.ts +687 -686
- package/dist/index.js +39 -39
- package/dist/maintenance/memory-governance.js +7 -7
- package/dist/maintenance/rebuild-memory-lifecycle-ledger.d.ts +1 -1
- package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +7 -7
- package/dist/maintenance/rebuild-memory-projection.d.ts +1 -1
- package/dist/maintenance/rebuild-memory-projection.js +8 -8
- package/dist/{maintenance-CKwxpdRx.d.ts → maintenance-kBII2qUI.d.ts} +3 -2
- package/dist/mcp-memory-inspector-app.d.ts +6 -5
- package/dist/memory-worth-outcomes.d.ts +1 -1
- package/dist/namespaces/migrate.d.ts +1 -1
- package/dist/namespaces/migrate.js +8 -8
- package/dist/namespaces/storage.d.ts +1 -1
- package/dist/namespaces/storage.js +7 -7
- package/dist/operator-doctor-corpus.js +10 -10
- package/dist/operator-toolkit.d.ts +1 -1
- package/dist/operator-toolkit.js +15 -15
- package/dist/orchestration/compression-guideline-coordinator.d.ts +1 -1
- package/dist/orchestration/maintenance.d.ts +2 -2
- package/dist/orchestration/maintenance.js +12 -12
- package/dist/{orchestrator-BshGam-o.d.ts → orchestrator-DHy5LtTy.d.ts} +20 -35
- package/dist/orchestrator.d.ts +5 -4
- package/dist/orchestrator.js +39 -39
- package/dist/page-versioning.js +3 -3
- package/dist/schemas.d.ts +138 -138
- package/dist/semantic-consolidation.js +8 -8
- package/dist/semantic-rule-promotion.js +7 -7
- package/dist/semantic-rule-verifier.js +7 -7
- package/dist/{service-BIgVM7fL.d.ts → service-PT2_z7xm.d.ts} +1 -1
- package/dist/shared-context/manager.d.ts +8 -8
- package/dist/source-agent-qualifier.js +7 -7
- package/dist/{storage-CVfGWqCI.d.ts → storage-BmVudhk4.d.ts} +46 -27
- package/dist/storage.d.ts +1 -1
- package/dist/storage.js +6 -6
- package/dist/temporal-supersession.d.ts +51 -26
- package/dist/temporal-supersession.js +3 -1
- package/dist/tier-migration.d.ts +1 -1
- package/dist/transfer/capsule-import.js +4 -4
- package/dist/transfer/types.d.ts +66 -66
- package/dist/verified-recall.js +7 -7
- package/package.json +2 -2
- package/src/lifecycle/tombstones.ts +30 -0
- package/src/orchestration/consolidation-run.ts +324 -106
- package/src/orchestration/contradiction-linking-coordinator.ts +237 -64
- package/src/orchestration/dependency-propagation-delivery.test.ts +2353 -0
- package/src/orchestration/dependency-propagation-delivery.ts +1143 -0
- package/src/orchestration/dependency-propagation-queue-state.ts +220 -0
- package/src/orchestration/dependency-propagation-recovery-replay.ts +237 -0
- package/src/orchestration/dependency-propagation-replay-reconciliation.ts +73 -0
- package/src/orchestration/dependency-propagation-triggers.test.ts +1069 -11
- package/src/orchestration/dependency-propagation.test.ts +82 -31
- package/src/orchestration/dependency-propagation.ts +55 -17
- package/src/orchestration/extraction-persist-deps.ts +2 -2
- package/src/orchestration/extraction-persist.ts +14 -15
- package/src/orchestration/extraction-queue-coordinator.ts +23 -1
- package/src/orchestration/extraction-shutdown.ts +29 -0
- package/src/orchestration/maintenance.ts +13 -7
- package/src/orchestration/turn-ingestion.ts +5 -1
- package/src/orchestrator-background-writes.test.ts +265 -1
- package/src/orchestrator-extraction-queue.test.ts +39 -0
- package/src/orchestrator.ts +59 -58
- package/src/storage/committed-invalidation.ts +34 -0
- package/src/storage/deletion-revision-store.ts +529 -0
- package/src/storage/supersession-audit.ts +41 -0
- package/src/storage/supersession-side-effects.ts +52 -25
- package/src/storage-contract/failure-semantics.test.ts +12 -0
- package/src/storage-contract/round-trip.test.ts +184 -2
- package/src/storage-deletion-revisions.test.ts +213 -1
- package/src/storage-frontmatter-cas.test.ts +141 -0
- package/src/storage-memory-lifecycle-ledger.test.ts +372 -0
- package/src/storage-replay.test.ts +149 -0
- package/src/storage.ts +345 -358
- package/src/temporal-supersession.test.ts +431 -2
- package/src/temporal-supersession.ts +567 -107
- package/src/testing/subjects/dependency-propagation.test.ts +179 -38
- package/dist/chunk-ENTCVTQR.js.map +0 -1
- package/dist/chunk-EWMTOVMY.js +0 -484
- package/dist/chunk-EWMTOVMY.js.map +0 -1
- package/dist/chunk-OYESWJ43.js.map +0 -1
- package/dist/chunk-VBHPOWBJ.js.map +0 -1
- package/dist/chunk-ZHY5TMJG.js.map +0 -1
- /package/dist/{capsule-merge-XMFBXVDP.js.map → capsule-merge-HCJJMLPS.js.map} +0 -0
- /package/dist/{chunk-PWN32TAG.js.map → chunk-2ASAS3CQ.js.map} +0 -0
- /package/dist/{chunk-WQM326GS.js.map → chunk-2LY6GIIE.js.map} +0 -0
- /package/dist/{chunk-JL43BVDE.js.map → chunk-2OIQDTDC.js.map} +0 -0
- /package/dist/{chunk-33SV3E43.js.map → chunk-3OY7KXHM.js.map} +0 -0
- /package/dist/{chunk-AIUBBHSF.js.map → chunk-5UZ6UCOZ.js.map} +0 -0
- /package/dist/{chunk-UT5BV5OG.js.map → chunk-7HXZJAID.js.map} +0 -0
- /package/dist/{chunk-M7BXDKPR.js.map → chunk-7M6NYQWB.js.map} +0 -0
- /package/dist/{chunk-TPTECFWR.js.map → chunk-B6LZ4IBH.js.map} +0 -0
- /package/dist/{chunk-J5LLX6IL.js.map → chunk-BF6SXNQJ.js.map} +0 -0
- /package/dist/{chunk-4PC4PRJE.js.map → chunk-BKIFTXHR.js.map} +0 -0
- /package/dist/{chunk-VVG2BOKA.js.map → chunk-BMEYMKWX.js.map} +0 -0
- /package/dist/{chunk-DCZBSE4F.js.map → chunk-D5YOZ66N.js.map} +0 -0
- /package/dist/{chunk-BHXYAVUM.js.map → chunk-DLG5V6ND.js.map} +0 -0
- /package/dist/{chunk-52UKFTUK.js.map → chunk-F4EU6VUC.js.map} +0 -0
- /package/dist/{chunk-M4A44LUA.js.map → chunk-GSTIBXBW.js.map} +0 -0
- /package/dist/{chunk-W5XVQFLM.js.map → chunk-GXFSQ6SD.js.map} +0 -0
- /package/dist/{chunk-XADV5IZ3.js.map → chunk-HDIUYMSB.js.map} +0 -0
- /package/dist/{chunk-FXTYIA46.js.map → chunk-J272WKX2.js.map} +0 -0
- /package/dist/{chunk-JYL3BVVZ.js.map → chunk-JSEJH2YQ.js.map} +0 -0
- /package/dist/{chunk-5UDMFE3Q.js.map → chunk-MLXZD4WM.js.map} +0 -0
- /package/dist/{chunk-PUSLMZBQ.js.map → chunk-NOEMGE56.js.map} +0 -0
- /package/dist/{chunk-C5QLI33R.js.map → chunk-QINJITDS.js.map} +0 -0
- /package/dist/{chunk-45BRYON6.js.map → chunk-QPOHMO6M.js.map} +0 -0
- /package/dist/{chunk-ZQOCRIGA.js.map → chunk-QYN5PXDZ.js.map} +0 -0
- /package/dist/{chunk-PA45XCUD.js.map → chunk-RANKJI5T.js.map} +0 -0
- /package/dist/{chunk-37ZO6LLJ.js.map → chunk-RYLEDKQ2.js.map} +0 -0
- /package/dist/{chunk-CV6BBVYV.js.map → chunk-TEMSWHQD.js.map} +0 -0
- /package/dist/{chunk-QGR6OVGT.js.map → chunk-VFSAWRMZ.js.map} +0 -0
- /package/dist/{chunk-NLK2VZYZ.js.map → chunk-VXCNQYTC.js.map} +0 -0
- /package/dist/{chunk-AZ2QBGYB.js.map → chunk-W7Y5JDVB.js.map} +0 -0
- /package/dist/{chunk-4F6X3RXW.js.map → chunk-X57M2Q4I.js.map} +0 -0
- /package/dist/{chunk-6MTSFX7A.js.map → chunk-X7XEDOSL.js.map} +0 -0
- /package/dist/{chunk-UEVLYVNW.js.map → chunk-YXKVLYXU.js.map} +0 -0
- /package/dist/{chunk-OQAL6SIR.js.map → chunk-ZMSZVISA.js.map} +0 -0
- /package/dist/{forget-UJDJHG6R.js.map → forget-HDS4N6CG.js.map} +0 -0
|
@@ -14,7 +14,7 @@ import {
|
|
|
14
14
|
applyTombstoneResurrectionGate,
|
|
15
15
|
buildRetiredFactTombstoneInputs,
|
|
16
16
|
collectRetiredMemoriesForRebuild
|
|
17
|
-
} from "./chunk-
|
|
17
|
+
} from "./chunk-JJRVPWFY.js";
|
|
18
18
|
import {
|
|
19
19
|
withholdToolScopedFromSharedNamespace
|
|
20
20
|
} from "./chunk-S2V7RUCL.js";
|
|
@@ -61,7 +61,7 @@ import {
|
|
|
61
61
|
isQueuedReviewMemory,
|
|
62
62
|
parseTombstoneBlockedOfflineSyncMemory,
|
|
63
63
|
tombstoneBlocked
|
|
64
|
-
} from "./chunk-
|
|
64
|
+
} from "./chunk-DLG5V6ND.js";
|
|
65
65
|
import {
|
|
66
66
|
composeMemoryEnvelope,
|
|
67
67
|
isSealedMemoryEnvelope,
|
|
@@ -83,7 +83,7 @@ import {
|
|
|
83
83
|
} from "./chunk-G7D6GZ5J.js";
|
|
84
84
|
import {
|
|
85
85
|
createVersion
|
|
86
|
-
} from "./chunk-
|
|
86
|
+
} from "./chunk-ZMSZVISA.js";
|
|
87
87
|
import {
|
|
88
88
|
attachInFlightReader,
|
|
89
89
|
beginCoalescedScan,
|
|
@@ -105,12 +105,12 @@ import {
|
|
|
105
105
|
requestEntityCanonicalIdReconcileSync,
|
|
106
106
|
resolveHistoricalEntityCanonicalId,
|
|
107
107
|
withEntityCanonicalMutationLock
|
|
108
|
-
} from "./chunk-
|
|
108
|
+
} from "./chunk-GSTIBXBW.js";
|
|
109
109
|
import {
|
|
110
110
|
computeSupersessionKey,
|
|
111
111
|
normalizeSupersessionKey,
|
|
112
112
|
supersessionKeysForFact
|
|
113
|
-
} from "./chunk-
|
|
113
|
+
} from "./chunk-5WITETWU.js";
|
|
114
114
|
import {
|
|
115
115
|
clearMemoryCache,
|
|
116
116
|
getCachedEntities,
|
|
@@ -204,7 +204,8 @@ import {
|
|
|
204
204
|
raceAbort
|
|
205
205
|
} from "./chunk-BTTPKDIL.js";
|
|
206
206
|
import {
|
|
207
|
-
isPathInsideStorageRoot
|
|
207
|
+
isPathInsideStorageRoot,
|
|
208
|
+
resolveSafeStoragePath
|
|
208
209
|
} from "./chunk-FF4KLI5W.js";
|
|
209
210
|
import {
|
|
210
211
|
MAGIC_HEADER_SIZE,
|
|
@@ -229,21 +230,20 @@ import {
|
|
|
229
230
|
|
|
230
231
|
// src/storage.ts
|
|
231
232
|
import {
|
|
232
|
-
lstat as
|
|
233
|
-
readdir as
|
|
234
|
-
readFile as
|
|
233
|
+
lstat as lstat4,
|
|
234
|
+
readdir as readdir6,
|
|
235
|
+
readFile as readFile3,
|
|
235
236
|
realpath as realpath3,
|
|
236
|
-
rename as rename2,
|
|
237
237
|
stat as stat2,
|
|
238
238
|
writeFile as writeFile2,
|
|
239
|
-
mkdir,
|
|
240
|
-
unlink as
|
|
239
|
+
mkdir as mkdir2,
|
|
240
|
+
unlink as unlink5,
|
|
241
241
|
appendFile,
|
|
242
|
-
open
|
|
242
|
+
open as open2
|
|
243
243
|
} from "fs/promises";
|
|
244
244
|
import { appendFileSync, createReadStream, mkdirSync, statSync } from "fs";
|
|
245
|
-
import { createHash as
|
|
246
|
-
import
|
|
245
|
+
import { createHash as createHash7 } from "crypto";
|
|
246
|
+
import path11 from "path";
|
|
247
247
|
|
|
248
248
|
// src/storage/memory-read-store.ts
|
|
249
249
|
import { lstat, readdir, realpath, stat } from "fs/promises";
|
|
@@ -762,6 +762,29 @@ var MemoryReadStore = class {
|
|
|
762
762
|
}
|
|
763
763
|
};
|
|
764
764
|
|
|
765
|
+
// src/storage/committed-invalidation.ts
|
|
766
|
+
async function clearProofFailOpen(callbacks) {
|
|
767
|
+
await callbacks.clearProof?.().catch((error) => {
|
|
768
|
+
log.warn(`failed to clear invalidation proof for ${callbacks.memoryId}: ${error}`);
|
|
769
|
+
});
|
|
770
|
+
}
|
|
771
|
+
async function runCommittedInvalidation(callbacks) {
|
|
772
|
+
let deletionCompleted = false;
|
|
773
|
+
try {
|
|
774
|
+
await callbacks.recordProof?.();
|
|
775
|
+
if (!await callbacks.deleteMemory()) {
|
|
776
|
+
await clearProofFailOpen(callbacks);
|
|
777
|
+
return false;
|
|
778
|
+
}
|
|
779
|
+
deletionCompleted = true;
|
|
780
|
+
await callbacks.afterDelete();
|
|
781
|
+
return true;
|
|
782
|
+
} catch (error) {
|
|
783
|
+
if (!deletionCompleted) await clearProofFailOpen(callbacks);
|
|
784
|
+
throw error;
|
|
785
|
+
}
|
|
786
|
+
}
|
|
787
|
+
|
|
765
788
|
// src/storage/profile-header.ts
|
|
766
789
|
var LAST_UPDATED_HEADER = /^\*Last updated:[^*]*\*$/;
|
|
767
790
|
var PROFILE_TITLE = /^ {0,3}#(?:[ \t]+|$)/;
|
|
@@ -2045,9 +2068,425 @@ ${rows.join("\n")}
|
|
|
2045
2068
|
}
|
|
2046
2069
|
};
|
|
2047
2070
|
|
|
2071
|
+
// src/storage/deletion-revision-store.ts
|
|
2072
|
+
import { createHash, randomUUID } from "crypto";
|
|
2073
|
+
import { lstat as lstat2, mkdir, open, readFile, rename, unlink as unlink2 } from "fs/promises";
|
|
2074
|
+
import path3 from "path";
|
|
2075
|
+
var DELETION_REVISION_MAX_MTIME_MS = 864e13;
|
|
2076
|
+
var DELETION_REVISION_LOCK_STALE_MS = 6e4;
|
|
2077
|
+
var DELETION_REVISION_LOCK_MAX_WAIT_MS = 12e4;
|
|
2078
|
+
var INVALIDATION_COMMIT_MAX_TIMESTAMP_MS = 864e13;
|
|
2079
|
+
var INVALIDATION_PROOF_QUARANTINE_MAX_IDS = 1024;
|
|
2080
|
+
function canonicalizeInvalidationEvidence(value) {
|
|
2081
|
+
if (Array.isArray(value)) return value.map((item) => canonicalizeInvalidationEvidence(item));
|
|
2082
|
+
if (value && typeof value === "object") {
|
|
2083
|
+
const result = {};
|
|
2084
|
+
for (const key of Object.keys(value).sort()) {
|
|
2085
|
+
result[key] = canonicalizeInvalidationEvidence(value[key]);
|
|
2086
|
+
}
|
|
2087
|
+
return result;
|
|
2088
|
+
}
|
|
2089
|
+
return value;
|
|
2090
|
+
}
|
|
2091
|
+
function invalidationCommitFingerprint(memory) {
|
|
2092
|
+
const frontmatter = { ...memory.frontmatter };
|
|
2093
|
+
delete frontmatter.accessCount;
|
|
2094
|
+
delete frontmatter.lastAccessed;
|
|
2095
|
+
return createHash("sha256").update(
|
|
2096
|
+
JSON.stringify(
|
|
2097
|
+
canonicalizeInvalidationEvidence({
|
|
2098
|
+
content: memory.content,
|
|
2099
|
+
frontmatter
|
|
2100
|
+
})
|
|
2101
|
+
)
|
|
2102
|
+
).digest("hex");
|
|
2103
|
+
}
|
|
2104
|
+
function isValidInvalidationCommitTimestamp(value) {
|
|
2105
|
+
return typeof value === "number" && Number.isFinite(value) && value >= 0 && value <= INVALIDATION_COMMIT_MAX_TIMESTAMP_MS;
|
|
2106
|
+
}
|
|
2107
|
+
function isValidInvalidationCommitFingerprint(value) {
|
|
2108
|
+
return typeof value === "string" && /^[a-f0-9]{64}$/.test(value);
|
|
2109
|
+
}
|
|
2110
|
+
function isValidMemoryId(value) {
|
|
2111
|
+
return typeof value === "string" && value.length > 0 && !value.includes("\0");
|
|
2112
|
+
}
|
|
2113
|
+
function isValidDeletionRevisionPath(value) {
|
|
2114
|
+
return typeof value === "string" && value.length > 0 && !value.includes("\\") && !value.includes("\0") && !path3.posix.isAbsolute(value) && path3.posix.normalize(value) === value && value !== "." && value !== ".." && !value.startsWith("../");
|
|
2115
|
+
}
|
|
2116
|
+
function deletionRevisionPathIdentity(value) {
|
|
2117
|
+
return value.normalize("NFC").toUpperCase().toLowerCase();
|
|
2118
|
+
}
|
|
2119
|
+
var DeletionRevisionStore = class {
|
|
2120
|
+
options;
|
|
2121
|
+
invalidationProofQuarantine = /* @__PURE__ */ new Set();
|
|
2122
|
+
invalidationProofQuarantineFull = false;
|
|
2123
|
+
invalidationProofQuarantineFullAt = null;
|
|
2124
|
+
constructor(options) {
|
|
2125
|
+
this.options = options;
|
|
2126
|
+
}
|
|
2127
|
+
quarantineInvalidationProof(memoryId) {
|
|
2128
|
+
if (this.invalidationProofQuarantineFull) {
|
|
2129
|
+
const nextBoundary = Date.now() + 1;
|
|
2130
|
+
this.invalidationProofQuarantineFullAt = Math.max(
|
|
2131
|
+
this.invalidationProofQuarantineFullAt ?? 0,
|
|
2132
|
+
nextBoundary
|
|
2133
|
+
);
|
|
2134
|
+
return;
|
|
2135
|
+
}
|
|
2136
|
+
if (this.invalidationProofQuarantine.has(memoryId)) return;
|
|
2137
|
+
if (this.invalidationProofQuarantine.size >= INVALIDATION_PROOF_QUARANTINE_MAX_IDS) {
|
|
2138
|
+
this.invalidationProofQuarantineFull = true;
|
|
2139
|
+
this.invalidationProofQuarantineFullAt = Date.now() + 1;
|
|
2140
|
+
return;
|
|
2141
|
+
}
|
|
2142
|
+
this.invalidationProofQuarantine.add(memoryId);
|
|
2143
|
+
}
|
|
2144
|
+
async resolveConfiguredStoragePath(targetPath) {
|
|
2145
|
+
const baseDir = path3.resolve(this.options.baseDir);
|
|
2146
|
+
const relativePath = path3.relative(baseDir, path3.resolve(targetPath));
|
|
2147
|
+
return resolveSafeStoragePath(baseDir, relativePath);
|
|
2148
|
+
}
|
|
2149
|
+
async resolveManagedStoragePath(filePath, method) {
|
|
2150
|
+
const assertedPath = this.options.assertManagedStoragePath(filePath, method);
|
|
2151
|
+
const target = await this.resolveConfiguredStoragePath(assertedPath);
|
|
2152
|
+
const relativePath = path3.relative(path3.resolve(this.options.baseDir), target).split(path3.sep).join("/");
|
|
2153
|
+
return { target, relativePath };
|
|
2154
|
+
}
|
|
2155
|
+
parseDeletionRevisionMetadata(raw) {
|
|
2156
|
+
let parsed;
|
|
2157
|
+
try {
|
|
2158
|
+
parsed = JSON.parse(raw);
|
|
2159
|
+
} catch {
|
|
2160
|
+
throw new Error("Deletion revision metadata is invalid.");
|
|
2161
|
+
}
|
|
2162
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
2163
|
+
throw new Error("Deletion revision metadata is invalid.");
|
|
2164
|
+
}
|
|
2165
|
+
const root = parsed;
|
|
2166
|
+
if (Object.keys(root).sort().join(",") !== "deletions,version" || root.version !== 1 || !Array.isArray(root.deletions)) {
|
|
2167
|
+
throw new Error("Deletion revision metadata is invalid.");
|
|
2168
|
+
}
|
|
2169
|
+
const revisions = /* @__PURE__ */ new Map();
|
|
2170
|
+
const pathByIdentity = /* @__PURE__ */ new Map();
|
|
2171
|
+
for (const rawEntry of root.deletions) {
|
|
2172
|
+
if (!rawEntry || typeof rawEntry !== "object" || Array.isArray(rawEntry)) {
|
|
2173
|
+
throw new Error("Deletion revision metadata is invalid.");
|
|
2174
|
+
}
|
|
2175
|
+
const entry = rawEntry;
|
|
2176
|
+
if (Object.keys(entry).sort().join(",") !== "mtimeMs,path" || !isValidDeletionRevisionPath(entry.path) || typeof entry.mtimeMs !== "number" || !Number.isFinite(entry.mtimeMs) || entry.mtimeMs < 0 || entry.mtimeMs > DELETION_REVISION_MAX_MTIME_MS || revisions.has(entry.path)) {
|
|
2177
|
+
throw new Error("Deletion revision metadata is invalid.");
|
|
2178
|
+
}
|
|
2179
|
+
const identity = deletionRevisionPathIdentity(entry.path);
|
|
2180
|
+
if (pathByIdentity.has(identity)) {
|
|
2181
|
+
throw new Error("Deletion revision metadata is invalid.");
|
|
2182
|
+
}
|
|
2183
|
+
pathByIdentity.set(identity, entry.path);
|
|
2184
|
+
revisions.set(entry.path, entry.mtimeMs);
|
|
2185
|
+
}
|
|
2186
|
+
return new Map([...revisions.entries()].sort(([left], [right]) => left < right ? -1 : left > right ? 1 : 0));
|
|
2187
|
+
}
|
|
2188
|
+
async readDeletionRevisionMetadata() {
|
|
2189
|
+
const metadataPath = await this.resolveConfiguredStoragePath(this.options.deletionRevisionMetadataPath);
|
|
2190
|
+
let raw;
|
|
2191
|
+
try {
|
|
2192
|
+
raw = await readFile(metadataPath, "utf8");
|
|
2193
|
+
} catch (error) {
|
|
2194
|
+
if (isErrnoCode(error, "ENOENT")) return /* @__PURE__ */ new Map();
|
|
2195
|
+
throw new Error("Deletion revision metadata is unavailable.");
|
|
2196
|
+
}
|
|
2197
|
+
return this.parseDeletionRevisionMetadata(raw);
|
|
2198
|
+
}
|
|
2199
|
+
async writeDeletionRevisionMetadata(revisions, lock) {
|
|
2200
|
+
const metadataPath = await this.resolveConfiguredStoragePath(this.options.deletionRevisionMetadataPath);
|
|
2201
|
+
const deletions = [...revisions.entries()].sort(([left], [right]) => left < right ? -1 : left > right ? 1 : 0).map(([entryPath, mtimeMs]) => ({ path: entryPath, mtimeMs }));
|
|
2202
|
+
const metadata = { version: 1, deletions };
|
|
2203
|
+
const temporaryPath = await this.resolveConfiguredStoragePath(
|
|
2204
|
+
`${metadataPath}.${process.pid}.${randomUUID()}.tmp`
|
|
2205
|
+
);
|
|
2206
|
+
let handle = null;
|
|
2207
|
+
try {
|
|
2208
|
+
handle = await open(temporaryPath, "wx", 384);
|
|
2209
|
+
await handle.writeFile(`${JSON.stringify(metadata)}
|
|
2210
|
+
`, "utf8");
|
|
2211
|
+
await handle.sync();
|
|
2212
|
+
await handle.close();
|
|
2213
|
+
handle = null;
|
|
2214
|
+
if (!await lock.refresh()) {
|
|
2215
|
+
throw new Error("Deletion revision metadata lock was lost.");
|
|
2216
|
+
}
|
|
2217
|
+
const renameTarget = await this.resolveConfiguredStoragePath(this.options.deletionRevisionMetadataPath);
|
|
2218
|
+
await rename(temporaryPath, renameTarget);
|
|
2219
|
+
} finally {
|
|
2220
|
+
if (handle !== null) {
|
|
2221
|
+
await handle.close().catch((error) => {
|
|
2222
|
+
log.warn("failed to close deletion revision metadata temporary file", error);
|
|
2223
|
+
});
|
|
2224
|
+
}
|
|
2225
|
+
const cleanupPath = await this.resolveConfiguredStoragePath(temporaryPath).catch((error) => {
|
|
2226
|
+
log.warn("failed to resolve deletion revision metadata temporary file for cleanup", error);
|
|
2227
|
+
return null;
|
|
2228
|
+
});
|
|
2229
|
+
if (cleanupPath !== null) {
|
|
2230
|
+
await unlink2(cleanupPath).catch((error) => {
|
|
2231
|
+
if (!isErrnoCode(error, "ENOENT")) {
|
|
2232
|
+
log.warn("failed to clean up deletion revision metadata temporary file", error);
|
|
2233
|
+
}
|
|
2234
|
+
});
|
|
2235
|
+
}
|
|
2236
|
+
}
|
|
2237
|
+
}
|
|
2238
|
+
async withDeletionRevisionLock(task) {
|
|
2239
|
+
const lockPath2 = await this.resolveConfiguredStoragePath(this.options.deletionRevisionLockPath);
|
|
2240
|
+
return withHeldFileLock(
|
|
2241
|
+
lockPath2,
|
|
2242
|
+
{
|
|
2243
|
+
staleMs: DELETION_REVISION_LOCK_STALE_MS,
|
|
2244
|
+
maxWaitMs: DELETION_REVISION_LOCK_MAX_WAIT_MS
|
|
2245
|
+
},
|
|
2246
|
+
async (acquired, lock) => {
|
|
2247
|
+
if (!acquired) throw new Error("Deletion revision metadata lock is unavailable.");
|
|
2248
|
+
return task(lock);
|
|
2249
|
+
}
|
|
2250
|
+
);
|
|
2251
|
+
}
|
|
2252
|
+
async readDeletionRevisions() {
|
|
2253
|
+
return this.withDeletionRevisionLock(async () => this.readDeletionRevisionMetadata());
|
|
2254
|
+
}
|
|
2255
|
+
parseInvalidationCommitMetadata(raw) {
|
|
2256
|
+
let parsed;
|
|
2257
|
+
try {
|
|
2258
|
+
parsed = JSON.parse(raw);
|
|
2259
|
+
} catch {
|
|
2260
|
+
throw new Error("Invalidation commit metadata is invalid.");
|
|
2261
|
+
}
|
|
2262
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
2263
|
+
throw new Error("Invalidation commit metadata is invalid.");
|
|
2264
|
+
}
|
|
2265
|
+
const root = parsed;
|
|
2266
|
+
if (Object.keys(root).sort().join(",") !== "commits,version" || root.version !== 1 || !Array.isArray(root.commits)) {
|
|
2267
|
+
throw new Error("Invalidation commit metadata is invalid.");
|
|
2268
|
+
}
|
|
2269
|
+
const commits = /* @__PURE__ */ new Map();
|
|
2270
|
+
for (const rawEntry of root.commits) {
|
|
2271
|
+
if (!rawEntry || typeof rawEntry !== "object" || Array.isArray(rawEntry)) {
|
|
2272
|
+
throw new Error("Invalidation commit metadata is invalid.");
|
|
2273
|
+
}
|
|
2274
|
+
const entry = rawEntry;
|
|
2275
|
+
if (Object.keys(entry).sort().join(",") !== "committedAt,fingerprint,memoryId" || !isValidMemoryId(entry.memoryId) || !isValidInvalidationCommitFingerprint(entry.fingerprint) || !isValidInvalidationCommitTimestamp(entry.committedAt) || commits.has(entry.memoryId)) {
|
|
2276
|
+
throw new Error("Invalidation commit metadata is invalid.");
|
|
2277
|
+
}
|
|
2278
|
+
commits.set(entry.memoryId, {
|
|
2279
|
+
fingerprint: entry.fingerprint,
|
|
2280
|
+
committedAt: entry.committedAt
|
|
2281
|
+
});
|
|
2282
|
+
}
|
|
2283
|
+
return commits;
|
|
2284
|
+
}
|
|
2285
|
+
async readInvalidationCommitMetadata() {
|
|
2286
|
+
const metadataPath = await this.resolveConfiguredStoragePath(this.options.invalidationCommitMetadataPath);
|
|
2287
|
+
let raw;
|
|
2288
|
+
try {
|
|
2289
|
+
raw = await readFile(metadataPath, "utf8");
|
|
2290
|
+
} catch (error) {
|
|
2291
|
+
if (isErrnoCode(error, "ENOENT")) return /* @__PURE__ */ new Map();
|
|
2292
|
+
throw new Error("Invalidation commit metadata is unavailable.");
|
|
2293
|
+
}
|
|
2294
|
+
return this.parseInvalidationCommitMetadata(raw);
|
|
2295
|
+
}
|
|
2296
|
+
async writeInvalidationCommitMetadata(commits, lock) {
|
|
2297
|
+
const metadataPath = await this.resolveConfiguredStoragePath(this.options.invalidationCommitMetadataPath);
|
|
2298
|
+
const entries = [...commits.entries()].sort(([left], [right]) => left < right ? -1 : left > right ? 1 : 0).map(([memoryId, value]) => ({
|
|
2299
|
+
memoryId,
|
|
2300
|
+
fingerprint: value.fingerprint,
|
|
2301
|
+
committedAt: value.committedAt
|
|
2302
|
+
}));
|
|
2303
|
+
const metadata = { version: 1, commits: entries };
|
|
2304
|
+
const temporaryPath = await this.resolveConfiguredStoragePath(
|
|
2305
|
+
`${metadataPath}.${process.pid}.${randomUUID()}.tmp`
|
|
2306
|
+
);
|
|
2307
|
+
const metadataDir = await this.resolveConfiguredStoragePath(path3.dirname(metadataPath));
|
|
2308
|
+
let handle = null;
|
|
2309
|
+
let renamed = false;
|
|
2310
|
+
try {
|
|
2311
|
+
await mkdir(metadataDir, { recursive: true });
|
|
2312
|
+
handle = await open(temporaryPath, "wx", 384);
|
|
2313
|
+
await handle.writeFile(`${JSON.stringify(metadata)}
|
|
2314
|
+
`, "utf8");
|
|
2315
|
+
await handle.sync();
|
|
2316
|
+
await handle.close();
|
|
2317
|
+
handle = null;
|
|
2318
|
+
if (!await lock.refresh()) {
|
|
2319
|
+
throw new Error("Invalidation commit metadata lock was lost.");
|
|
2320
|
+
}
|
|
2321
|
+
const renameTarget = await this.resolveConfiguredStoragePath(this.options.invalidationCommitMetadataPath);
|
|
2322
|
+
await rename(temporaryPath, renameTarget);
|
|
2323
|
+
renamed = true;
|
|
2324
|
+
} finally {
|
|
2325
|
+
if (handle !== null) {
|
|
2326
|
+
await handle.close().catch((error) => {
|
|
2327
|
+
log.warn("failed to close invalidation commit metadata temporary file", error);
|
|
2328
|
+
});
|
|
2329
|
+
}
|
|
2330
|
+
const cleanupPath = await this.resolveConfiguredStoragePath(temporaryPath).catch((error) => {
|
|
2331
|
+
log.warn("failed to resolve invalidation commit metadata temporary file for cleanup", error);
|
|
2332
|
+
return null;
|
|
2333
|
+
});
|
|
2334
|
+
if (cleanupPath !== null) {
|
|
2335
|
+
await unlink2(cleanupPath).catch((error) => {
|
|
2336
|
+
if (!isErrnoCode(error, "ENOENT")) {
|
|
2337
|
+
log.warn(
|
|
2338
|
+
`failed to clean up invalidation commit metadata temporary file after ${renamed ? "commit" : "failure"}`,
|
|
2339
|
+
error
|
|
2340
|
+
);
|
|
2341
|
+
}
|
|
2342
|
+
});
|
|
2343
|
+
}
|
|
2344
|
+
}
|
|
2345
|
+
}
|
|
2346
|
+
async hasCommittedInvalidation(memory) {
|
|
2347
|
+
const memoryId = memory.frontmatter.id;
|
|
2348
|
+
const fingerprint = invalidationCommitFingerprint(memory);
|
|
2349
|
+
return this.withDeletionRevisionLock(async () => {
|
|
2350
|
+
const entry = (await this.readInvalidationCommitMetadata()).get(memoryId);
|
|
2351
|
+
if (entry?.fingerprint !== fingerprint) return false;
|
|
2352
|
+
if (this.invalidationProofQuarantine.has(memoryId)) return false;
|
|
2353
|
+
if (!this.invalidationProofQuarantineFull) return true;
|
|
2354
|
+
const fullAt = this.invalidationProofQuarantineFullAt;
|
|
2355
|
+
return fullAt !== null && entry.committedAt >= fullAt;
|
|
2356
|
+
});
|
|
2357
|
+
}
|
|
2358
|
+
async recordCommittedInvalidation(memory) {
|
|
2359
|
+
const fingerprint = invalidationCommitFingerprint(memory);
|
|
2360
|
+
await this.withDeletionRevisionLock(async (lock) => {
|
|
2361
|
+
const fullAt = this.invalidationProofQuarantineFullAt;
|
|
2362
|
+
const committedAt = fullAt === null ? Date.now() : Math.max(Date.now(), fullAt + 1);
|
|
2363
|
+
const commits = await this.readInvalidationCommitMetadata();
|
|
2364
|
+
commits.set(memory.frontmatter.id, {
|
|
2365
|
+
fingerprint,
|
|
2366
|
+
committedAt
|
|
2367
|
+
});
|
|
2368
|
+
await this.writeInvalidationCommitMetadata(commits, lock);
|
|
2369
|
+
this.invalidationProofQuarantine.delete(memory.frontmatter.id);
|
|
2370
|
+
});
|
|
2371
|
+
}
|
|
2372
|
+
async clearCommittedInvalidation(memory) {
|
|
2373
|
+
const memoryId = memory.frontmatter.id;
|
|
2374
|
+
this.quarantineInvalidationProof(memoryId);
|
|
2375
|
+
const fingerprint = invalidationCommitFingerprint(memory);
|
|
2376
|
+
let cleared = false;
|
|
2377
|
+
try {
|
|
2378
|
+
await this.withDeletionRevisionLock(async (lock) => {
|
|
2379
|
+
const commits = await this.readInvalidationCommitMetadata();
|
|
2380
|
+
const current = commits.get(memoryId);
|
|
2381
|
+
if (current?.fingerprint !== fingerprint) return;
|
|
2382
|
+
commits.delete(memoryId);
|
|
2383
|
+
await this.writeInvalidationCommitMetadata(commits, lock);
|
|
2384
|
+
});
|
|
2385
|
+
cleared = true;
|
|
2386
|
+
} finally {
|
|
2387
|
+
if (cleared) this.invalidationProofQuarantine.delete(memoryId);
|
|
2388
|
+
}
|
|
2389
|
+
}
|
|
2390
|
+
async recordReplicatedDeletionRevision(filePath, mtimeMs) {
|
|
2391
|
+
const { relativePath } = await this.resolveManagedStoragePath(
|
|
2392
|
+
filePath,
|
|
2393
|
+
"storage.recordReplicatedDeletionRevision"
|
|
2394
|
+
);
|
|
2395
|
+
if (typeof mtimeMs !== "number" || !Number.isFinite(mtimeMs) || mtimeMs < 0 || mtimeMs > DELETION_REVISION_MAX_MTIME_MS) {
|
|
2396
|
+
throw new Error("Deletion revision timestamp is invalid.");
|
|
2397
|
+
}
|
|
2398
|
+
if (!isValidDeletionRevisionPath(relativePath)) {
|
|
2399
|
+
throw new Error("Deletion revision path is invalid.");
|
|
2400
|
+
}
|
|
2401
|
+
await this.withDeletionRevisionLock(async (lock) => {
|
|
2402
|
+
const revisions = await this.readDeletionRevisionMetadata();
|
|
2403
|
+
const identity = deletionRevisionPathIdentity(relativePath);
|
|
2404
|
+
let existingPath;
|
|
2405
|
+
for (const candidatePath of revisions.keys()) {
|
|
2406
|
+
if (deletionRevisionPathIdentity(candidatePath) === identity) {
|
|
2407
|
+
existingPath = candidatePath;
|
|
2408
|
+
break;
|
|
2409
|
+
}
|
|
2410
|
+
}
|
|
2411
|
+
const existingMtimeMs = existingPath === void 0 ? void 0 : revisions.get(existingPath);
|
|
2412
|
+
if (existingMtimeMs !== void 0 && existingMtimeMs >= mtimeMs) return;
|
|
2413
|
+
const updated = new Map(revisions);
|
|
2414
|
+
if (existingPath !== void 0) updated.delete(existingPath);
|
|
2415
|
+
updated.set(relativePath, mtimeMs);
|
|
2416
|
+
await this.writeDeletionRevisionMetadata(updated, lock);
|
|
2417
|
+
});
|
|
2418
|
+
}
|
|
2419
|
+
async writeManagedStorageFile(filePath, write) {
|
|
2420
|
+
const { relativePath } = await this.resolveManagedStoragePath(filePath, "storage.writeManagedStorageFile");
|
|
2421
|
+
if (!isValidDeletionRevisionPath(relativePath)) {
|
|
2422
|
+
throw new Error("Deletion revision path is invalid.");
|
|
2423
|
+
}
|
|
2424
|
+
await this.withDeletionRevisionLock(async (lock) => {
|
|
2425
|
+
const before = await this.readDeletionRevisionMetadata();
|
|
2426
|
+
const identity = deletionRevisionPathIdentity(relativePath);
|
|
2427
|
+
const existingPath = [...before.keys()].find(
|
|
2428
|
+
(candidatePath) => deletionRevisionPathIdentity(candidatePath) === identity
|
|
2429
|
+
);
|
|
2430
|
+
await write();
|
|
2431
|
+
if (existingPath === void 0) return;
|
|
2432
|
+
const updated = new Map(before);
|
|
2433
|
+
updated.delete(existingPath);
|
|
2434
|
+
await this.writeDeletionRevisionMetadata(updated, lock);
|
|
2435
|
+
});
|
|
2436
|
+
}
|
|
2437
|
+
async deleteManagedStorageFile(filePath, deletionMtimeMs) {
|
|
2438
|
+
const { target, relativePath } = await this.resolveManagedStoragePath(
|
|
2439
|
+
filePath,
|
|
2440
|
+
"storage.deleteManagedStorageFile"
|
|
2441
|
+
);
|
|
2442
|
+
if (deletionMtimeMs !== void 0 && deletionMtimeMs !== null && (typeof deletionMtimeMs !== "number" || !Number.isFinite(deletionMtimeMs) || deletionMtimeMs < 0 || deletionMtimeMs > DELETION_REVISION_MAX_MTIME_MS)) {
|
|
2443
|
+
throw new Error("Deletion revision timestamp is invalid.");
|
|
2444
|
+
}
|
|
2445
|
+
return this.withDeletionRevisionLock(async (lock) => {
|
|
2446
|
+
const safeTarget = await this.resolveConfiguredStoragePath(target);
|
|
2447
|
+
try {
|
|
2448
|
+
await lstat2(safeTarget);
|
|
2449
|
+
} catch (error) {
|
|
2450
|
+
if (isErrnoCode(error, "ENOENT")) return false;
|
|
2451
|
+
throw error;
|
|
2452
|
+
}
|
|
2453
|
+
if (!isValidDeletionRevisionPath(relativePath)) {
|
|
2454
|
+
throw new Error("Deletion revision path is invalid.");
|
|
2455
|
+
}
|
|
2456
|
+
const revision = deletionMtimeMs === null ? void 0 : deletionMtimeMs ?? Date.now();
|
|
2457
|
+
const before = await this.readDeletionRevisionMetadata();
|
|
2458
|
+
const identity = deletionRevisionPathIdentity(relativePath);
|
|
2459
|
+
let existingPath;
|
|
2460
|
+
for (const candidatePath of before.keys()) {
|
|
2461
|
+
if (deletionRevisionPathIdentity(candidatePath) === identity) {
|
|
2462
|
+
existingPath = candidatePath;
|
|
2463
|
+
break;
|
|
2464
|
+
}
|
|
2465
|
+
}
|
|
2466
|
+
const existing = existingPath === void 0 ? void 0 : before.get(existingPath);
|
|
2467
|
+
const changed = existingPath !== void 0 && existingPath !== relativePath || (revision === void 0 ? existing !== void 0 : existing !== revision);
|
|
2468
|
+
if (changed) {
|
|
2469
|
+
const updated = new Map(before);
|
|
2470
|
+
if (existingPath !== void 0) updated.delete(existingPath);
|
|
2471
|
+
if (revision !== void 0) updated.set(relativePath, revision);
|
|
2472
|
+
await this.writeDeletionRevisionMetadata(updated, lock);
|
|
2473
|
+
}
|
|
2474
|
+
try {
|
|
2475
|
+
const unlinkTarget = await this.resolveConfiguredStoragePath(target);
|
|
2476
|
+
await unlink2(unlinkTarget);
|
|
2477
|
+
return true;
|
|
2478
|
+
} catch (error) {
|
|
2479
|
+
if (changed) await this.writeDeletionRevisionMetadata(before, lock);
|
|
2480
|
+
if (isErrnoCode(error, "ENOENT")) return false;
|
|
2481
|
+
throw error;
|
|
2482
|
+
}
|
|
2483
|
+
});
|
|
2484
|
+
}
|
|
2485
|
+
};
|
|
2486
|
+
|
|
2048
2487
|
// src/storage/identity-continuity-store.ts
|
|
2049
2488
|
import { readdir as readdir3 } from "fs/promises";
|
|
2050
|
-
import
|
|
2489
|
+
import path4 from "path";
|
|
2051
2490
|
var IdentityContinuityStore = class {
|
|
2052
2491
|
constructor(deps) {
|
|
2053
2492
|
this.deps = deps;
|
|
@@ -2072,7 +2511,7 @@ var IdentityContinuityStore = class {
|
|
|
2072
2511
|
const date = nowIso.slice(0, 10);
|
|
2073
2512
|
const id = this.deps.generateId("incident");
|
|
2074
2513
|
const incident = createContinuityIncidentRecord(id, input, nowIso);
|
|
2075
|
-
const filePath =
|
|
2514
|
+
const filePath = path4.join(this.deps.identityIncidentsDir, `${date}-${id}.md`);
|
|
2076
2515
|
await this.deps.writeStorageSecureFile(filePath, serializeContinuityIncident(incident));
|
|
2077
2516
|
return { ...incident, filePath };
|
|
2078
2517
|
}
|
|
@@ -2085,7 +2524,7 @@ var IdentityContinuityStore = class {
|
|
|
2085
2524
|
const incidents = [];
|
|
2086
2525
|
for (const file of candidates) {
|
|
2087
2526
|
if (incidents.length >= cappedLimit) break;
|
|
2088
|
-
const filePath =
|
|
2527
|
+
const filePath = path4.join(this.deps.identityIncidentsDir, file);
|
|
2089
2528
|
try {
|
|
2090
2529
|
const raw = await this.deps.readStorageSecureFile(filePath);
|
|
2091
2530
|
const parsed = parseContinuityIncident(raw);
|
|
@@ -2116,7 +2555,7 @@ var IdentityContinuityStore = class {
|
|
|
2116
2555
|
await this.deps.ensureDirectories();
|
|
2117
2556
|
const safeKey = this.sanitizeIdentityAuditKey(key);
|
|
2118
2557
|
const dir = period === "weekly" ? this.deps.identityAuditsWeeklyDir : this.deps.identityAuditsMonthlyDir;
|
|
2119
|
-
const filePath =
|
|
2558
|
+
const filePath = path4.join(dir, `${safeKey}.md`);
|
|
2120
2559
|
await this.deps.writeStorageSecureFile(filePath, content);
|
|
2121
2560
|
return filePath;
|
|
2122
2561
|
}
|
|
@@ -2124,7 +2563,7 @@ var IdentityContinuityStore = class {
|
|
|
2124
2563
|
try {
|
|
2125
2564
|
const safeKey = this.sanitizeIdentityAuditKey(key);
|
|
2126
2565
|
const dir = period === "weekly" ? this.deps.identityAuditsWeeklyDir : this.deps.identityAuditsMonthlyDir;
|
|
2127
|
-
return await this.deps.readStorageSecureFile(
|
|
2566
|
+
return await this.deps.readStorageSecureFile(path4.join(dir, `${safeKey}.md`));
|
|
2128
2567
|
} catch (err) {
|
|
2129
2568
|
if (err instanceof Error && err.message === "Invalid identity audit key") return null;
|
|
2130
2569
|
if (!isErrnoCode(err, "ENOENT")) throw err;
|
|
@@ -2180,12 +2619,12 @@ var IdentityContinuityStore = class {
|
|
|
2180
2619
|
const fileNames = await this.readContinuityIncidentFileNames();
|
|
2181
2620
|
const directMatch = fileNames.find((name) => name.endsWith(`-${id}.md`));
|
|
2182
2621
|
if (directMatch) {
|
|
2183
|
-
const directPath =
|
|
2622
|
+
const directPath = path4.join(this.deps.identityIncidentsDir, directMatch);
|
|
2184
2623
|
const parsed = await this.readContinuityIncidentFile(directPath);
|
|
2185
2624
|
if (parsed?.id === id) return directPath;
|
|
2186
2625
|
}
|
|
2187
2626
|
for (const fileName of fileNames) {
|
|
2188
|
-
const filePath =
|
|
2627
|
+
const filePath = path4.join(this.deps.identityIncidentsDir, fileName);
|
|
2189
2628
|
const parsed = await this.readContinuityIncidentFile(filePath);
|
|
2190
2629
|
if (parsed?.id === id) return filePath;
|
|
2191
2630
|
}
|
|
@@ -2201,19 +2640,19 @@ var IdentityContinuityStore = class {
|
|
|
2201
2640
|
};
|
|
2202
2641
|
|
|
2203
2642
|
// src/storage/entity-canonical-id-migration.ts
|
|
2204
|
-
import { createHash, randomUUID } from "crypto";
|
|
2643
|
+
import { createHash as createHash2, randomUUID as randomUUID2 } from "crypto";
|
|
2205
2644
|
import {
|
|
2206
2645
|
access,
|
|
2207
|
-
lstat as
|
|
2208
|
-
readFile,
|
|
2646
|
+
lstat as lstat3,
|
|
2647
|
+
readFile as readFile2,
|
|
2209
2648
|
readdir as readdir4,
|
|
2210
2649
|
realpath as realpath2,
|
|
2211
|
-
rename,
|
|
2650
|
+
rename as rename2,
|
|
2212
2651
|
stat as statFile,
|
|
2213
|
-
unlink as
|
|
2652
|
+
unlink as unlink3,
|
|
2214
2653
|
writeFile
|
|
2215
2654
|
} from "fs/promises";
|
|
2216
|
-
import
|
|
2655
|
+
import path5 from "path";
|
|
2217
2656
|
import { z } from "zod";
|
|
2218
2657
|
var ENTITY_CANONICAL_ID_MIGRATION_VERSION = 1;
|
|
2219
2658
|
var ENTITY_CANONICAL_ID_MIGRATION_LOCK_STALE_MS = 6e4;
|
|
@@ -2238,7 +2677,7 @@ var EntityCanonicalIdMigrationStateSchema = z.object({
|
|
|
2238
2677
|
async function fingerprintPath(filePath) {
|
|
2239
2678
|
let fileStat;
|
|
2240
2679
|
try {
|
|
2241
|
-
fileStat = await
|
|
2680
|
+
fileStat = await lstat3(filePath);
|
|
2242
2681
|
} catch (error) {
|
|
2243
2682
|
if (!isErrnoCode(error, "ENOENT")) throw error;
|
|
2244
2683
|
}
|
|
@@ -2247,16 +2686,16 @@ async function fingerprintPath(filePath) {
|
|
|
2247
2686
|
async function fingerprintEntityEntries(entitiesDir) {
|
|
2248
2687
|
let names;
|
|
2249
2688
|
try {
|
|
2250
|
-
names = (await readdir4(entitiesDir)).filter((name) =>
|
|
2689
|
+
names = (await readdir4(entitiesDir)).filter((name) => path5.extname(name) === ".md").sort();
|
|
2251
2690
|
} catch (error) {
|
|
2252
2691
|
if (isErrnoCode(error, "ENOENT")) return "missing";
|
|
2253
2692
|
throw error;
|
|
2254
2693
|
}
|
|
2255
|
-
const hash =
|
|
2694
|
+
const hash = createHash2("sha256");
|
|
2256
2695
|
for (const name of names) {
|
|
2257
2696
|
hash.update(`${name.length}:${name}:`);
|
|
2258
2697
|
try {
|
|
2259
|
-
const entry = await
|
|
2698
|
+
const entry = await lstat3(path5.join(entitiesDir, name), { bigint: true });
|
|
2260
2699
|
hash.update(`${entry.dev}:${entry.ino}:${entry.size}:${entry.mtimeNs}:${entry.ctimeNs}`);
|
|
2261
2700
|
} catch (error) {
|
|
2262
2701
|
if (!isErrnoCode(error, "ENOENT")) throw error;
|
|
@@ -2276,9 +2715,9 @@ async function getFingerprint(baseDir, entitiesDir, getEntityMutationVersion) {
|
|
|
2276
2715
|
] = await Promise.all([
|
|
2277
2716
|
fingerprintPath(entitiesDir),
|
|
2278
2717
|
fingerprintEntityEntries(entitiesDir),
|
|
2279
|
-
fingerprintPath(
|
|
2280
|
-
fingerprintPath(
|
|
2281
|
-
fingerprintPath(
|
|
2718
|
+
fingerprintPath(path5.join(baseDir, "config", "aliases.json")),
|
|
2719
|
+
fingerprintPath(path5.join(baseDir, "state", ENTITY_CANONICAL_ID_RECONCILE_MARKER)),
|
|
2720
|
+
fingerprintPath(path5.join(baseDir, "state", ENTITY_CANONICAL_ID_RECONCILE_CONSUMING_MARKER))
|
|
2282
2721
|
]);
|
|
2283
2722
|
return [
|
|
2284
2723
|
entityDirectoryFingerprint,
|
|
@@ -2292,7 +2731,7 @@ async function getFingerprint(baseDir, entitiesDir, getEntityMutationVersion) {
|
|
|
2292
2731
|
async function validateRoots(baseDir, entitiesDir, stateDir, archiveDir) {
|
|
2293
2732
|
let baseStat;
|
|
2294
2733
|
try {
|
|
2295
|
-
baseStat = await
|
|
2734
|
+
baseStat = await lstat3(baseDir);
|
|
2296
2735
|
} catch (error) {
|
|
2297
2736
|
if (isErrnoCode(error, "ENOENT")) return;
|
|
2298
2737
|
throw error;
|
|
@@ -2304,14 +2743,14 @@ async function validateRoots(baseDir, entitiesDir, stateDir, archiveDir) {
|
|
|
2304
2743
|
const roots = [
|
|
2305
2744
|
entitiesDir,
|
|
2306
2745
|
stateDir,
|
|
2307
|
-
...RECALL_FALLBACK_DIRS.map((directory) =>
|
|
2308
|
-
|
|
2746
|
+
...RECALL_FALLBACK_DIRS.map((directory) => path5.join(baseDir, directory)),
|
|
2747
|
+
path5.join(baseDir, "cold"),
|
|
2309
2748
|
archiveDir
|
|
2310
2749
|
];
|
|
2311
2750
|
for (const root of roots) {
|
|
2312
2751
|
let rootStat;
|
|
2313
2752
|
try {
|
|
2314
|
-
rootStat = await
|
|
2753
|
+
rootStat = await lstat3(root);
|
|
2315
2754
|
} catch (error) {
|
|
2316
2755
|
if (isErrnoCode(error, "ENOENT")) continue;
|
|
2317
2756
|
throw error;
|
|
@@ -2323,13 +2762,13 @@ async function validateRoots(baseDir, entitiesDir, stateDir, archiveDir) {
|
|
|
2323
2762
|
}
|
|
2324
2763
|
}
|
|
2325
2764
|
function statePath(deps) {
|
|
2326
|
-
return
|
|
2765
|
+
return path5.join(deps.stateDir, ENTITY_CANONICAL_ID_MIGRATION_FILE);
|
|
2327
2766
|
}
|
|
2328
2767
|
async function assertSafeMigrationStateFile(deps) {
|
|
2329
2768
|
const migrationStatePath = statePath(deps);
|
|
2330
2769
|
let fileStat;
|
|
2331
2770
|
try {
|
|
2332
|
-
fileStat = await
|
|
2771
|
+
fileStat = await lstat3(migrationStatePath);
|
|
2333
2772
|
} catch (error) {
|
|
2334
2773
|
if (isErrnoCode(error, "ENOENT")) return;
|
|
2335
2774
|
throw error;
|
|
@@ -2348,7 +2787,7 @@ async function assertSafeMigrationStateFile(deps) {
|
|
|
2348
2787
|
async function assertSafeTombstoneFile(deps, tombstonePath) {
|
|
2349
2788
|
let fileStat;
|
|
2350
2789
|
try {
|
|
2351
|
-
fileStat = await
|
|
2790
|
+
fileStat = await lstat3(tombstonePath);
|
|
2352
2791
|
} catch (error) {
|
|
2353
2792
|
if (isErrnoCode(error, "ENOENT")) return false;
|
|
2354
2793
|
throw error;
|
|
@@ -2366,13 +2805,13 @@ async function assertSafeTombstoneFile(deps, tombstonePath) {
|
|
|
2366
2805
|
return true;
|
|
2367
2806
|
}
|
|
2368
2807
|
function lockPath(deps) {
|
|
2369
|
-
return
|
|
2808
|
+
return path5.join(deps.stateDir, "entity-canonical-id-migration.lock");
|
|
2370
2809
|
}
|
|
2371
2810
|
async function readState(deps) {
|
|
2372
2811
|
await assertSafeMigrationStateFile(deps);
|
|
2373
2812
|
let raw;
|
|
2374
2813
|
try {
|
|
2375
|
-
raw = await
|
|
2814
|
+
raw = await readFile2(statePath(deps), "utf-8");
|
|
2376
2815
|
} catch (error) {
|
|
2377
2816
|
if (isErrnoCode(error, "ENOENT")) return null;
|
|
2378
2817
|
throw error;
|
|
@@ -2395,13 +2834,13 @@ async function readState(deps) {
|
|
|
2395
2834
|
}
|
|
2396
2835
|
async function writeState(deps, state) {
|
|
2397
2836
|
const destination = statePath(deps);
|
|
2398
|
-
const temporary = `${destination}.${process.pid}.${
|
|
2837
|
+
const temporary = `${destination}.${process.pid}.${randomUUID2()}.tmp`;
|
|
2399
2838
|
try {
|
|
2400
2839
|
await writeFile(temporary, `${JSON.stringify(state, null, 2)}
|
|
2401
2840
|
`, "utf-8");
|
|
2402
|
-
await
|
|
2841
|
+
await rename2(temporary, destination);
|
|
2403
2842
|
} finally {
|
|
2404
|
-
await
|
|
2843
|
+
await unlink3(temporary).catch(() => void 0);
|
|
2405
2844
|
}
|
|
2406
2845
|
}
|
|
2407
2846
|
async function publishState(deps, state) {
|
|
@@ -2430,7 +2869,7 @@ async function sameFileIdentity(leftPath, rightPath) {
|
|
|
2430
2869
|
async function assertNotSymlink(filePath, label) {
|
|
2431
2870
|
let stat3;
|
|
2432
2871
|
try {
|
|
2433
|
-
stat3 = await
|
|
2872
|
+
stat3 = await lstat3(filePath);
|
|
2434
2873
|
} catch (error) {
|
|
2435
2874
|
if (isErrnoCode(error, "ENOENT")) return;
|
|
2436
2875
|
throw error;
|
|
@@ -2442,7 +2881,7 @@ async function assertNotSymlink(filePath, label) {
|
|
|
2442
2881
|
async function assertNotMemorySymlink(filePath) {
|
|
2443
2882
|
let stat3;
|
|
2444
2883
|
try {
|
|
2445
|
-
stat3 = await
|
|
2884
|
+
stat3 = await lstat3(filePath);
|
|
2446
2885
|
} catch (error) {
|
|
2447
2886
|
if (isErrnoCode(error, "ENOENT")) return;
|
|
2448
2887
|
throw error;
|
|
@@ -2458,13 +2897,13 @@ async function assertEntityContentUnchanged(deps, filePath, expectedContent, leg
|
|
|
2458
2897
|
}
|
|
2459
2898
|
}
|
|
2460
2899
|
async function readSafeEntityEntries(entitiesDir) {
|
|
2461
|
-
const rootStat = await
|
|
2900
|
+
const rootStat = await lstat3(entitiesDir);
|
|
2462
2901
|
if (rootStat.isSymbolicLink() || !rootStat.isDirectory()) {
|
|
2463
2902
|
throw new Error("Refusing entity canonical-id migration through a symlinked or non-directory entities root.");
|
|
2464
2903
|
}
|
|
2465
2904
|
const entries = await readdir4(entitiesDir);
|
|
2466
2905
|
for (const entry of entries) {
|
|
2467
|
-
await assertNotSymlink(
|
|
2906
|
+
await assertNotSymlink(path5.join(entitiesDir, entry), entry);
|
|
2468
2907
|
}
|
|
2469
2908
|
return entries;
|
|
2470
2909
|
}
|
|
@@ -2526,7 +2965,7 @@ async function rewriteReferences(deps, mappings, refreshLock) {
|
|
|
2526
2965
|
const seenPaths = /* @__PURE__ */ new Set();
|
|
2527
2966
|
let rewroteAny = false;
|
|
2528
2967
|
for (const memory of [...hotMemories, ...coldMemories, ...archivedMemories]) {
|
|
2529
|
-
const memoryPath =
|
|
2968
|
+
const memoryPath = path5.resolve(memory.path);
|
|
2530
2969
|
if (seenPaths.has(memoryPath)) continue;
|
|
2531
2970
|
seenPaths.add(memoryPath);
|
|
2532
2971
|
const previousEntityRef = memory.frontmatter.entityRef;
|
|
@@ -2558,7 +2997,7 @@ async function rewriteReferences(deps, mappings, refreshLock) {
|
|
|
2558
2997
|
latestCanonicalId
|
|
2559
2998
|
);
|
|
2560
2999
|
rewroteAny = true;
|
|
2561
|
-
rewroteColdMemory ||= latestMemory.path.includes(`${
|
|
3000
|
+
rewroteColdMemory ||= latestMemory.path.includes(`${path5.sep}cold${path5.sep}`);
|
|
2562
3001
|
}
|
|
2563
3002
|
if (!rewroteAny) return { rewroteColdMemory };
|
|
2564
3003
|
}
|
|
@@ -2597,7 +3036,7 @@ function rewriteTombstoneLine(line, mappings, normalizedMappings) {
|
|
|
2597
3036
|
return changed ? JSON.stringify(tombstone) : line;
|
|
2598
3037
|
}
|
|
2599
3038
|
async function rewriteTombstoneReferences(deps, mappings, refreshLock) {
|
|
2600
|
-
const tombstonePath =
|
|
3039
|
+
const tombstonePath = path5.join(deps.stateDir, "tombstones.jsonl");
|
|
2601
3040
|
const normalizedMappings = /* @__PURE__ */ new Map();
|
|
2602
3041
|
for (const [legacyId, canonicalId] of Object.entries(mappings)) {
|
|
2603
3042
|
const normalizedLegacyId = normalizeSupersessionKey(legacyId);
|
|
@@ -2606,7 +3045,7 @@ async function rewriteTombstoneReferences(deps, mappings, refreshLock) {
|
|
|
2606
3045
|
}
|
|
2607
3046
|
}
|
|
2608
3047
|
await withHeldFileLock(
|
|
2609
|
-
|
|
3048
|
+
path5.join(deps.stateDir, "tombstones.lock"),
|
|
2610
3049
|
{
|
|
2611
3050
|
staleMs: TOMBSTONE_LOCK_STALE_MS,
|
|
2612
3051
|
maxWaitMs: TOMBSTONE_LOCK_MAX_WAIT_MS
|
|
@@ -2794,7 +3233,7 @@ async function migrateEntityFilePair(deps, legacyId, canonicalId, refreshMigrati
|
|
|
2794
3233
|
await deps.snapshotBeforeWrite(legacyPath, "write");
|
|
2795
3234
|
await assertNotSymlink(legacyPath, legacyId);
|
|
2796
3235
|
await assertEntityContentUnchanged(deps, legacyPath, legacyContent, legacyId);
|
|
2797
|
-
await
|
|
3236
|
+
await unlink3(legacyPath);
|
|
2798
3237
|
return { deferred: false, progressed: true };
|
|
2799
3238
|
}
|
|
2800
3239
|
if (!legacyExists) return { deferred: false, progressed: false };
|
|
@@ -2818,13 +3257,13 @@ async function migrateEntityFilePair(deps, legacyId, canonicalId, refreshMigrati
|
|
|
2818
3257
|
await deps.snapshotBeforeWrite(legacyPath, "write");
|
|
2819
3258
|
await assertNotSymlink(legacyPath, legacyId);
|
|
2820
3259
|
await assertEntityContentUnchanged(deps, legacyPath, entityContent, legacyId);
|
|
2821
|
-
await
|
|
3260
|
+
await unlink3(legacyPath);
|
|
2822
3261
|
} catch (error) {
|
|
2823
3262
|
if (!canonicalExists && canonicalFingerprintAfterWrite !== null) {
|
|
2824
3263
|
try {
|
|
2825
3264
|
if (await fingerprintPath(canonicalPath) === canonicalFingerprintAfterWrite) {
|
|
2826
3265
|
await assertNotSymlink(canonicalPath, canonicalId);
|
|
2827
|
-
await
|
|
3266
|
+
await unlink3(canonicalPath);
|
|
2828
3267
|
}
|
|
2829
3268
|
} catch {
|
|
2830
3269
|
}
|
|
@@ -2845,12 +3284,12 @@ async function migrateLegacyEntityCanonicalIds(deps) {
|
|
|
2845
3284
|
if (!acquired) throw new Error("Timed out waiting for active entity canonical-id migration.");
|
|
2846
3285
|
await deps.validateMemoryScanRoots?.();
|
|
2847
3286
|
await readSafeEntityEntries(deps.entitiesDir);
|
|
2848
|
-
const reconcileMarkerPath =
|
|
2849
|
-
const consumingMarkerPath =
|
|
3287
|
+
const reconcileMarkerPath = path5.join(deps.stateDir, ENTITY_CANONICAL_ID_RECONCILE_MARKER);
|
|
3288
|
+
const consumingMarkerPath = path5.join(deps.stateDir, ENTITY_CANONICAL_ID_RECONCILE_CONSUMING_MARKER);
|
|
2850
3289
|
let reconcileRequested = await fileExists(consumingMarkerPath);
|
|
2851
3290
|
if (await fileExists(reconcileMarkerPath)) {
|
|
2852
3291
|
try {
|
|
2853
|
-
await
|
|
3292
|
+
await rename2(reconcileMarkerPath, consumingMarkerPath);
|
|
2854
3293
|
reconcileRequested = true;
|
|
2855
3294
|
} catch (error) {
|
|
2856
3295
|
if (!isErrnoCode(error, "ENOENT")) throw error;
|
|
@@ -2873,7 +3312,7 @@ async function migrateLegacyEntityCanonicalIds(deps) {
|
|
|
2873
3312
|
);
|
|
2874
3313
|
}
|
|
2875
3314
|
if (reconcileRequested) {
|
|
2876
|
-
await
|
|
3315
|
+
await unlink3(consumingMarkerPath).catch((error) => {
|
|
2877
3316
|
if (!isErrnoCode(error, "ENOENT")) throw error;
|
|
2878
3317
|
});
|
|
2879
3318
|
}
|
|
@@ -3019,7 +3458,7 @@ async function migrateLegacyEntityCanonicalIds(deps) {
|
|
|
3019
3458
|
}
|
|
3020
3459
|
|
|
3021
3460
|
// src/storage/entity-ref-repair.ts
|
|
3022
|
-
import { unlink as
|
|
3461
|
+
import { unlink as unlink4 } from "fs/promises";
|
|
3023
3462
|
var EntityRefRepair = class {
|
|
3024
3463
|
constructor(deps) {
|
|
3025
3464
|
this.deps = deps;
|
|
@@ -3081,7 +3520,7 @@ ${body}
|
|
|
3081
3520
|
if (opts.onFailRestore) {
|
|
3082
3521
|
await this.restore(opts.onFailRestore);
|
|
3083
3522
|
} else if (opts.onFailRemove) {
|
|
3084
|
-
await
|
|
3523
|
+
await unlink4(opts.onFailRemove).catch(() => void 0);
|
|
3085
3524
|
this.deps.invalidateAllMemoriesCache();
|
|
3086
3525
|
}
|
|
3087
3526
|
throw err;
|
|
@@ -3242,7 +3681,7 @@ import {
|
|
|
3242
3681
|
readFileSync,
|
|
3243
3682
|
realpathSync
|
|
3244
3683
|
} from "fs";
|
|
3245
|
-
import
|
|
3684
|
+
import path6 from "path";
|
|
3246
3685
|
function resolveSafeAliasRoots(baseDir, configDir) {
|
|
3247
3686
|
let baseStat;
|
|
3248
3687
|
try {
|
|
@@ -3269,8 +3708,8 @@ function resolveSafeAliasRoots(baseDir, configDir) {
|
|
|
3269
3708
|
return memoryRoot;
|
|
3270
3709
|
}
|
|
3271
3710
|
function readEntityAliasConfigSync(baseDir) {
|
|
3272
|
-
const configDir =
|
|
3273
|
-
const aliasPath =
|
|
3711
|
+
const configDir = path6.join(baseDir, "config");
|
|
3712
|
+
const aliasPath = path6.join(configDir, "aliases.json");
|
|
3274
3713
|
let memoryRoot = resolveSafeAliasRoots(baseDir, configDir);
|
|
3275
3714
|
if (memoryRoot === void 0) return void 0;
|
|
3276
3715
|
let aliasPreflight;
|
|
@@ -3331,9 +3770,9 @@ function decodeYamlScalar(value) {
|
|
|
3331
3770
|
}
|
|
3332
3771
|
|
|
3333
3772
|
// src/orchestration/qmd-result-resolver.ts
|
|
3334
|
-
import
|
|
3773
|
+
import path7 from "path";
|
|
3335
3774
|
function qmdCollectionPathParts(resultPath) {
|
|
3336
|
-
if (!resultPath ||
|
|
3775
|
+
if (!resultPath || path7.isAbsolute(resultPath)) return null;
|
|
3337
3776
|
const normalized = resultPath.replace(/\\/g, "/").replace(/^\/+/, "");
|
|
3338
3777
|
const slashIndex = normalized.indexOf("/");
|
|
3339
3778
|
if (slashIndex <= 0 || slashIndex >= normalized.length - 1) return null;
|
|
@@ -3346,9 +3785,9 @@ function qmdCollectionPathParts(resultPath) {
|
|
|
3346
3785
|
}
|
|
3347
3786
|
function qmdResultPathCandidates(storageDir, resultPath) {
|
|
3348
3787
|
const candidates = /* @__PURE__ */ new Set();
|
|
3349
|
-
const storageRoot =
|
|
3788
|
+
const storageRoot = path7.resolve(storageDir);
|
|
3350
3789
|
const addCandidate = (candidate) => {
|
|
3351
|
-
const resolved =
|
|
3790
|
+
const resolved = path7.resolve(candidate);
|
|
3352
3791
|
if (isPathInsideStorageRoot(storageRoot, resolved)) {
|
|
3353
3792
|
candidates.add(resolved);
|
|
3354
3793
|
}
|
|
@@ -3356,18 +3795,18 @@ function qmdResultPathCandidates(storageDir, resultPath) {
|
|
|
3356
3795
|
const addRelativeCandidates = (relativePath) => {
|
|
3357
3796
|
const normalized = relativePath.replace(/\\/g, "/").replace(/^\/+/, "");
|
|
3358
3797
|
if (!normalized) return;
|
|
3359
|
-
addCandidate(
|
|
3798
|
+
addCandidate(path7.join(storageRoot, normalized));
|
|
3360
3799
|
if (/^\d{4}-\d{2}-\d{2}\//.test(normalized)) {
|
|
3361
|
-
addCandidate(
|
|
3800
|
+
addCandidate(path7.join(storageRoot, "facts", normalized));
|
|
3362
3801
|
}
|
|
3363
3802
|
};
|
|
3364
|
-
if (
|
|
3803
|
+
if (path7.isAbsolute(resultPath)) {
|
|
3365
3804
|
addCandidate(resultPath);
|
|
3366
|
-
const relative2 =
|
|
3367
|
-
if (relative2 && !relative2.startsWith("..") && !
|
|
3368
|
-
const normalized = relative2.split(
|
|
3805
|
+
const relative2 = path7.relative(storageRoot, path7.resolve(resultPath));
|
|
3806
|
+
if (relative2 && !relative2.startsWith("..") && !path7.isAbsolute(relative2)) {
|
|
3807
|
+
const normalized = relative2.split(path7.sep).join("/");
|
|
3369
3808
|
if (/^\d{4}-\d{2}-\d{2}\//.test(normalized)) {
|
|
3370
|
-
addCandidate(
|
|
3809
|
+
addCandidate(path7.join(storageRoot, "facts", normalized));
|
|
3371
3810
|
}
|
|
3372
3811
|
}
|
|
3373
3812
|
} else {
|
|
@@ -3402,7 +3841,7 @@ var QmdResultResolver = class {
|
|
|
3402
3841
|
const seenStorageDirs = /* @__PURE__ */ new Set();
|
|
3403
3842
|
const addStorage = (storage) => {
|
|
3404
3843
|
const storageDir = storageDirOrNull(storage);
|
|
3405
|
-
const storageKey = storageDir ?
|
|
3844
|
+
const storageKey = storageDir ? path7.resolve(storageDir) : `storage-without-dir-${storages.length}`;
|
|
3406
3845
|
if (seenStorageDirs.has(storageKey)) return;
|
|
3407
3846
|
seenStorageDirs.add(storageKey);
|
|
3408
3847
|
storages.push(storage);
|
|
@@ -3432,7 +3871,7 @@ var QmdResultResolver = class {
|
|
|
3432
3871
|
continue;
|
|
3433
3872
|
}
|
|
3434
3873
|
try {
|
|
3435
|
-
const coldRoot =
|
|
3874
|
+
const coldRoot = path7.join(storageDir, "cold");
|
|
3436
3875
|
for (const candidate of qmdResultPathCandidates(
|
|
3437
3876
|
coldRoot,
|
|
3438
3877
|
parts.relativePath
|
|
@@ -3491,9 +3930,9 @@ var QmdResultResolver = class {
|
|
|
3491
3930
|
error: err.message
|
|
3492
3931
|
});
|
|
3493
3932
|
}
|
|
3494
|
-
if (!
|
|
3933
|
+
if (!path7.isAbsolute(resultPath)) return null;
|
|
3495
3934
|
}
|
|
3496
|
-
if (
|
|
3935
|
+
if (path7.isAbsolute(resultPath)) {
|
|
3497
3936
|
if (!fallbackStorageDir) {
|
|
3498
3937
|
return await fallbackStorage.readMemoryByPath(resultPath);
|
|
3499
3938
|
}
|
|
@@ -3533,7 +3972,7 @@ var QmdResultResolver = class {
|
|
|
3533
3972
|
);
|
|
3534
3973
|
if (!memory) return null;
|
|
3535
3974
|
let ownerNamespace = null;
|
|
3536
|
-
if (
|
|
3975
|
+
if (path7.isAbsolute(memory.path)) {
|
|
3537
3976
|
const ownerStorage = await this.storageForAbsoluteQmdResultPath(
|
|
3538
3977
|
memory.path,
|
|
3539
3978
|
fallbackStorage,
|
|
@@ -3557,17 +3996,17 @@ var QmdResultResolver = class {
|
|
|
3557
3996
|
};
|
|
3558
3997
|
}
|
|
3559
3998
|
async storageForAbsoluteQmdResultPath(resultPath, fallbackStorage, recallNamespaces = []) {
|
|
3560
|
-
const resolvedPath =
|
|
3999
|
+
const resolvedPath = path7.resolve(resultPath);
|
|
3561
4000
|
const config = this.getConfig();
|
|
3562
|
-
const memoryRoot =
|
|
3563
|
-
const namespacesRoot =
|
|
4001
|
+
const memoryRoot = path7.resolve(config.memoryDir);
|
|
4002
|
+
const namespacesRoot = path7.join(memoryRoot, "namespaces");
|
|
3564
4003
|
const fallbackStorageDir = storageDirOrNull(fallbackStorage);
|
|
3565
4004
|
const matches = [];
|
|
3566
4005
|
const seenDirs = /* @__PURE__ */ new Set();
|
|
3567
4006
|
const maybeAddStorage = (storage, namespace) => {
|
|
3568
4007
|
const storageDir = storageDirOrNull(storage);
|
|
3569
4008
|
if (!storageDir) return;
|
|
3570
|
-
const candidateRoot =
|
|
4009
|
+
const candidateRoot = path7.resolve(storageDir);
|
|
3571
4010
|
if (seenDirs.has(candidateRoot)) return;
|
|
3572
4011
|
if (!isPathInsideStorageRoot(candidateRoot, resolvedPath)) return;
|
|
3573
4012
|
if (candidateRoot === memoryRoot && isPathInsideStorageRoot(namespacesRoot, resolvedPath)) {
|
|
@@ -3585,7 +4024,7 @@ var QmdResultResolver = class {
|
|
|
3585
4024
|
candidateNamespaces.add(ns);
|
|
3586
4025
|
}
|
|
3587
4026
|
if (isPathInsideStorageRoot(namespacesRoot, resolvedPath)) {
|
|
3588
|
-
const relativeToNamespaces =
|
|
4027
|
+
const relativeToNamespaces = path7.relative(namespacesRoot, resolvedPath);
|
|
3589
4028
|
const [namespaceSegment] = relativeToNamespaces.split(/[\\/]/);
|
|
3590
4029
|
if (namespaceSegment) {
|
|
3591
4030
|
candidateNamespaces.add(
|
|
@@ -4129,7 +4568,7 @@ function collectSpeakers(tagged) {
|
|
|
4129
4568
|
}
|
|
4130
4569
|
|
|
4131
4570
|
// src/wearables/fusion/fuse.ts
|
|
4132
|
-
import { createHash as
|
|
4571
|
+
import { createHash as createHash3 } from "crypto";
|
|
4133
4572
|
var FUSION_ID_PREFIX = "fusion";
|
|
4134
4573
|
var FUSION_ALGO_VERSION = "2026-07-12-1";
|
|
4135
4574
|
function canonicalInputsKey(date, inputs) {
|
|
@@ -4158,7 +4597,7 @@ function canonicalInputsKey(date, inputs) {
|
|
|
4158
4597
|
])
|
|
4159
4598
|
])
|
|
4160
4599
|
];
|
|
4161
|
-
return
|
|
4600
|
+
return createHash3("sha256").update(JSON.stringify(record), "utf-8").digest("hex");
|
|
4162
4601
|
}
|
|
4163
4602
|
function configFingerprint(options, sources) {
|
|
4164
4603
|
const proximityGapMs = options.proximityGapMs ?? DEFAULT_PROXIMITY_GAP_MS;
|
|
@@ -4173,7 +4612,7 @@ function canonicalDayKey(date, inputs, options, algoVersion = FUSION_ALGO_VERSIO
|
|
|
4173
4612
|
options,
|
|
4174
4613
|
inputs.map((conv) => conv.source)
|
|
4175
4614
|
);
|
|
4176
|
-
return
|
|
4615
|
+
return createHash3("sha256").update(inputsKey).update("\n").update(fingerprint).update("\n").update(algoVersion).digest("hex");
|
|
4177
4616
|
}
|
|
4178
4617
|
function buildProvenance(cluster, proximityGapMs, windowToleranceMs) {
|
|
4179
4618
|
return {
|
|
@@ -4233,11 +4672,11 @@ function fuseDay(date, inputs, options = {}) {
|
|
|
4233
4672
|
}
|
|
4234
4673
|
|
|
4235
4674
|
// src/wearables/fusion/store.ts
|
|
4236
|
-
import { createHash as
|
|
4237
|
-
import * as
|
|
4675
|
+
import { createHash as createHash4 } from "crypto";
|
|
4676
|
+
import * as path8 from "path";
|
|
4238
4677
|
var FUSION_KIND = "wearable-fusion";
|
|
4239
4678
|
function hashFusionBody(conversations) {
|
|
4240
|
-
return
|
|
4679
|
+
return createHash4("sha256").update(JSON.stringify(conversations), "utf-8").digest("hex");
|
|
4241
4680
|
}
|
|
4242
4681
|
function composeFusionDayMeta(date, conversations, sources, contentHash, fusedAt) {
|
|
4243
4682
|
return {
|
|
@@ -4421,7 +4860,7 @@ var FusionArtifactStore = class {
|
|
|
4421
4860
|
`invalid wearable fusion date '${String(date)}' \u2014 expected YYYY-MM-DD`
|
|
4422
4861
|
);
|
|
4423
4862
|
}
|
|
4424
|
-
return
|
|
4863
|
+
return path8.join(this.wearablesDir, FUSION_DIR_NAME, `${date}.md`);
|
|
4425
4864
|
}
|
|
4426
4865
|
async writeFusedDay(date, serialized) {
|
|
4427
4866
|
const filePath = this.fusedDayPath(date);
|
|
@@ -4458,7 +4897,7 @@ var FusionArtifactStore = class {
|
|
|
4458
4897
|
}
|
|
4459
4898
|
/** List dates with stored fused artifacts, newest first. */
|
|
4460
4899
|
async listFusedDays() {
|
|
4461
|
-
const fusionDir =
|
|
4900
|
+
const fusionDir = path8.join(this.wearablesDir, FUSION_DIR_NAME);
|
|
4462
4901
|
await this.assertPathContained(fusionDir);
|
|
4463
4902
|
let entries;
|
|
4464
4903
|
try {
|
|
@@ -4502,10 +4941,10 @@ var FusionArtifactStore = class {
|
|
|
4502
4941
|
try {
|
|
4503
4942
|
rootReal = await this.io.realpath(this.memoryDir);
|
|
4504
4943
|
} catch {
|
|
4505
|
-
rootReal =
|
|
4944
|
+
rootReal = path8.resolve(this.memoryDir);
|
|
4506
4945
|
}
|
|
4507
4946
|
await this.assertNoEscape(rootReal, this.wearablesDir);
|
|
4508
|
-
await this.assertNoEscape(rootReal,
|
|
4947
|
+
await this.assertNoEscape(rootReal, path8.dirname(targetPath));
|
|
4509
4948
|
await this.assertNoEscape(rootReal, targetPath);
|
|
4510
4949
|
await this.assertFusionDirNotSymlinked();
|
|
4511
4950
|
}
|
|
@@ -4519,7 +4958,7 @@ var FusionArtifactStore = class {
|
|
|
4519
4958
|
* (a fresh write creates a real directory lexically under the root).
|
|
4520
4959
|
*/
|
|
4521
4960
|
async assertFusionDirNotSymlinked() {
|
|
4522
|
-
const fusionDir =
|
|
4961
|
+
const fusionDir = path8.join(this.wearablesDir, FUSION_DIR_NAME);
|
|
4523
4962
|
let stat3;
|
|
4524
4963
|
try {
|
|
4525
4964
|
stat3 = await this.io.lstat(fusionDir);
|
|
@@ -4654,8 +5093,8 @@ function reconstructFusionInputs(date, dayTranscripts) {
|
|
|
4654
5093
|
}
|
|
4655
5094
|
|
|
4656
5095
|
// src/meetings/store.ts
|
|
4657
|
-
import { createHash as
|
|
4658
|
-
import * as
|
|
5096
|
+
import { createHash as createHash5 } from "crypto";
|
|
5097
|
+
import * as path9 from "path";
|
|
4659
5098
|
var MEETINGS_DIR_NAME = "meetings";
|
|
4660
5099
|
var MEETING_KIND = "meeting";
|
|
4661
5100
|
var MEETING_FORMAT_VERSION = 1;
|
|
@@ -4685,7 +5124,7 @@ function canonicalContent(base, fused) {
|
|
|
4685
5124
|
});
|
|
4686
5125
|
}
|
|
4687
5126
|
function composeMeetingRecord(base, fused) {
|
|
4688
|
-
const contentHash =
|
|
5127
|
+
const contentHash = createHash5("sha256").update(canonicalContent(base, fused), "utf-8").digest("hex");
|
|
4689
5128
|
return {
|
|
4690
5129
|
...base,
|
|
4691
5130
|
...fused,
|
|
@@ -4824,7 +5263,7 @@ var MeetingRecordStore = class {
|
|
|
4824
5263
|
constructor(memoryDir, io) {
|
|
4825
5264
|
this.memoryDir = memoryDir;
|
|
4826
5265
|
this.io = io;
|
|
4827
|
-
this.meetingsDir =
|
|
5266
|
+
this.meetingsDir = path9.join(memoryDir, MEETINGS_DIR_NAME);
|
|
4828
5267
|
}
|
|
4829
5268
|
memoryDir;
|
|
4830
5269
|
io;
|
|
@@ -4840,7 +5279,7 @@ var MeetingRecordStore = class {
|
|
|
4840
5279
|
if (idDate !== date) {
|
|
4841
5280
|
throw new Error(`meeting id '${id}' does not belong to date '${date}'`);
|
|
4842
5281
|
}
|
|
4843
|
-
return
|
|
5282
|
+
return path9.join(this.meetingsDir, date, `${id}.md`);
|
|
4844
5283
|
}
|
|
4845
5284
|
/** Idempotently persist a record: skip the write when contentHash is unchanged. */
|
|
4846
5285
|
async saveMeetingRecord(record) {
|
|
@@ -4871,7 +5310,7 @@ var MeetingRecordStore = class {
|
|
|
4871
5310
|
if (!isValidTranscriptDate(date)) {
|
|
4872
5311
|
throw new Error(`invalid meeting date '${String(date)}' \u2014 expected YYYY-MM-DD`);
|
|
4873
5312
|
}
|
|
4874
|
-
const dayDir =
|
|
5313
|
+
const dayDir = path9.join(this.meetingsDir, date);
|
|
4875
5314
|
await this.assertPathContained(dayDir);
|
|
4876
5315
|
let entries;
|
|
4877
5316
|
try {
|
|
@@ -4939,10 +5378,10 @@ var MeetingRecordStore = class {
|
|
|
4939
5378
|
try {
|
|
4940
5379
|
rootReal = await this.io.realpath(this.memoryDir);
|
|
4941
5380
|
} catch {
|
|
4942
|
-
rootReal =
|
|
5381
|
+
rootReal = path9.resolve(this.memoryDir);
|
|
4943
5382
|
}
|
|
4944
5383
|
await this.assertNoEscape(rootReal, this.meetingsDir);
|
|
4945
|
-
await this.assertNoEscape(rootReal,
|
|
5384
|
+
await this.assertNoEscape(rootReal, path9.dirname(targetPath));
|
|
4946
5385
|
await this.assertNoEscape(rootReal, targetPath);
|
|
4947
5386
|
await this.assertNotSymlink(this.meetingsDir, "directory");
|
|
4948
5387
|
const dayDir = this.dayDirWithin(targetPath);
|
|
@@ -4952,11 +5391,11 @@ var MeetingRecordStore = class {
|
|
|
4952
5391
|
/** The `<meetings>/<date>` directory on the path to `targetPath`, or null when
|
|
4953
5392
|
* `targetPath` is the meetings root itself. */
|
|
4954
5393
|
dayDirWithin(targetPath) {
|
|
4955
|
-
const rel =
|
|
4956
|
-
if (rel === "" || rel.startsWith("..") ||
|
|
4957
|
-
const first = rel.split(
|
|
5394
|
+
const rel = path9.relative(this.meetingsDir, targetPath);
|
|
5395
|
+
if (rel === "" || rel.startsWith("..") || path9.isAbsolute(rel)) return null;
|
|
5396
|
+
const first = rel.split(path9.sep)[0];
|
|
4958
5397
|
if (first === void 0 || first.length === 0) return null;
|
|
4959
|
-
return
|
|
5398
|
+
return path9.join(this.meetingsDir, first);
|
|
4960
5399
|
}
|
|
4961
5400
|
async assertNotSymlink(target, kind) {
|
|
4962
5401
|
let stat3;
|
|
@@ -4967,7 +5406,7 @@ var MeetingRecordStore = class {
|
|
|
4967
5406
|
throw err;
|
|
4968
5407
|
}
|
|
4969
5408
|
if (stat3.isSymbolicLink) {
|
|
4970
|
-
const label =
|
|
5409
|
+
const label = path9.relative(this.memoryDir, target) || MEETINGS_DIR_NAME;
|
|
4971
5410
|
throw new Error(
|
|
4972
5411
|
`meetings ${kind} '${label}' is a symbolic link \u2014 refusing to follow it even when it resolves inside the memory dir (AGENTS.md pattern #3)`
|
|
4973
5412
|
);
|
|
@@ -5011,15 +5450,15 @@ function citationTemplateLiteralParts(template) {
|
|
|
5011
5450
|
const parts = [];
|
|
5012
5451
|
let cursor = 0;
|
|
5013
5452
|
while (cursor < template.length) {
|
|
5014
|
-
const
|
|
5015
|
-
if (
|
|
5453
|
+
const open3 = template.indexOf("{", cursor);
|
|
5454
|
+
if (open3 === -1) {
|
|
5016
5455
|
parts.push(template.slice(cursor));
|
|
5017
5456
|
break;
|
|
5018
5457
|
}
|
|
5019
|
-
parts.push(template.slice(cursor,
|
|
5020
|
-
const close = template.indexOf("}",
|
|
5458
|
+
parts.push(template.slice(cursor, open3));
|
|
5459
|
+
const close = template.indexOf("}", open3 + 1);
|
|
5021
5460
|
if (close === -1) {
|
|
5022
|
-
cursor =
|
|
5461
|
+
cursor = open3 + 1;
|
|
5023
5462
|
} else {
|
|
5024
5463
|
cursor = close + 1;
|
|
5025
5464
|
}
|
|
@@ -5070,6 +5509,27 @@ function stripCitationMarkersForHashRemoval(value, template) {
|
|
|
5070
5509
|
return removed ? trimLeadingSpacesAndTabs(result) : value;
|
|
5071
5510
|
}
|
|
5072
5511
|
|
|
5512
|
+
// src/storage/supersession-audit.ts
|
|
5513
|
+
import { readdir as readdir5 } from "fs/promises";
|
|
5514
|
+
import path10 from "path";
|
|
5515
|
+
async function hasSupersessionAudit(lookup, oldMemoryId, newMemoryId, auditBody) {
|
|
5516
|
+
let entries;
|
|
5517
|
+
try {
|
|
5518
|
+
entries = await readdir5(lookup.correctionsDir, { withFileTypes: true });
|
|
5519
|
+
} catch (error) {
|
|
5520
|
+
if (isErrnoCode(error, "ENOENT")) return false;
|
|
5521
|
+
throw error;
|
|
5522
|
+
}
|
|
5523
|
+
for (const entry of entries) {
|
|
5524
|
+
if (!entry.isFile() || !entry.name.endsWith(".md")) continue;
|
|
5525
|
+
const memory = await lookup.readMemoryByPath(path10.join(lookup.correctionsDir, entry.name));
|
|
5526
|
+
if (memory?.frontmatter.category === "correction" && (memory.frontmatter.sourceMemoryId === oldMemoryId || memory.frontmatter.sourceMemoryId === void 0 && memory.frontmatter.lineage?.includes(oldMemoryId)) && memory.frontmatter.lineage?.includes(newMemoryId) && memory.content === auditBody) {
|
|
5527
|
+
return true;
|
|
5528
|
+
}
|
|
5529
|
+
}
|
|
5530
|
+
return false;
|
|
5531
|
+
}
|
|
5532
|
+
|
|
5073
5533
|
// src/storage/supersession-side-effects.ts
|
|
5074
5534
|
async function runSupersessionSideEffects(options) {
|
|
5075
5535
|
const {
|
|
@@ -5077,22 +5537,18 @@ async function runSupersessionSideEffects(options) {
|
|
|
5077
5537
|
newMemoryId,
|
|
5078
5538
|
reason,
|
|
5079
5539
|
now,
|
|
5540
|
+
operationId,
|
|
5541
|
+
exactReplay,
|
|
5080
5542
|
currentBefore,
|
|
5081
5543
|
updatedFm,
|
|
5082
|
-
citationTemplate
|
|
5083
|
-
isColdOrArchiveTierPath,
|
|
5084
|
-
invalidateColdMemoriesCache,
|
|
5085
|
-
bumpMemoryCorpusVersion,
|
|
5086
|
-
appendLifecycleEvent,
|
|
5087
|
-
bumpMemoryStatusVersion,
|
|
5088
|
-
appendTombstone,
|
|
5089
|
-
writeSealedMemory
|
|
5544
|
+
citationTemplate
|
|
5090
5545
|
} = options;
|
|
5091
5546
|
try {
|
|
5092
|
-
if (isColdOrArchiveTierPath(currentBefore.path)) invalidateColdMemoriesCache();
|
|
5093
|
-
|
|
5094
|
-
|
|
5095
|
-
|
|
5547
|
+
if (options.isColdOrArchiveTierPath(currentBefore.path)) options.invalidateColdMemoriesCache();
|
|
5548
|
+
if (exactReplay) options.invalidateAllMemoriesCache();
|
|
5549
|
+
else options.bumpMemoryCorpusVersion();
|
|
5550
|
+
await options.appendLifecycleEvent();
|
|
5551
|
+
options.bumpMemoryStatusVersion();
|
|
5096
5552
|
log.debug(`superseded memory ${oldMemoryId} by ${newMemoryId}: ${reason}`);
|
|
5097
5553
|
if (currentBefore.frontmatter.category === "fact") {
|
|
5098
5554
|
for (const input of buildRetiredFactTombstoneInputs(
|
|
@@ -5110,28 +5566,48 @@ async function runSupersessionSideEffects(options) {
|
|
|
5110
5566
|
supersessionKeysForFact
|
|
5111
5567
|
}
|
|
5112
5568
|
)) {
|
|
5113
|
-
|
|
5569
|
+
const exactInput = { ...input, operationKey: operationId };
|
|
5570
|
+
const exists = await options.hasExactTombstone(exactInput) || await options.hasExactTombstone(input);
|
|
5571
|
+
if (!exists) await options.appendTombstone(exactInput);
|
|
5114
5572
|
}
|
|
5115
5573
|
}
|
|
5116
|
-
const
|
|
5117
|
-
{
|
|
5118
|
-
content: `Superseded: ${currentBefore.content}
|
|
5574
|
+
const auditBody = `Superseded: ${currentBefore.content}
|
|
5119
5575
|
|
|
5120
|
-
Reason: ${reason}
|
|
5121
|
-
|
|
5122
|
-
|
|
5123
|
-
|
|
5576
|
+
Reason: ${reason}`;
|
|
5577
|
+
const auditExists = await hasSupersessionAudit(
|
|
5578
|
+
{
|
|
5579
|
+
correctionsDir: options.correctionsDir,
|
|
5580
|
+
readMemoryByPath: options.readMemoryByPath
|
|
5124
5581
|
},
|
|
5125
|
-
|
|
5582
|
+
oldMemoryId,
|
|
5583
|
+
newMemoryId,
|
|
5584
|
+
auditBody
|
|
5126
5585
|
);
|
|
5127
|
-
|
|
5586
|
+
if (!auditExists) {
|
|
5587
|
+
const auditEnvelope = composeMemoryEnvelope(
|
|
5588
|
+
{
|
|
5589
|
+
content: auditBody,
|
|
5590
|
+
category: "correction",
|
|
5591
|
+
confidence: 1,
|
|
5592
|
+
tags: ["supersession", "auto-resolved"]
|
|
5593
|
+
},
|
|
5594
|
+
{ source: "contradiction-detection" }
|
|
5595
|
+
);
|
|
5596
|
+
await options.writeSealedMemory(auditEnvelope, {
|
|
5597
|
+
lineage: [oldMemoryId, newMemoryId],
|
|
5598
|
+
sourceMemoryId: oldMemoryId
|
|
5599
|
+
});
|
|
5600
|
+
}
|
|
5601
|
+
options.invalidateAllMemoriesCache();
|
|
5602
|
+
return true;
|
|
5128
5603
|
} catch (err) {
|
|
5129
|
-
log.error(`
|
|
5604
|
+
log.error(`supersession side effects failed for ${oldMemoryId}:`, err);
|
|
5605
|
+
return false;
|
|
5130
5606
|
}
|
|
5131
5607
|
}
|
|
5132
5608
|
|
|
5133
5609
|
// src/storage/entity-timeline.ts
|
|
5134
|
-
import { createHash as
|
|
5610
|
+
import { createHash as createHash6 } from "crypto";
|
|
5135
5611
|
function parseEntityTimelineBullet(bullet, fallbackTimestamp) {
|
|
5136
5612
|
const trimmed = bullet.trim();
|
|
5137
5613
|
if (!trimmed) return null;
|
|
@@ -5503,7 +5979,7 @@ function fingerprintEntityStructuredFacts(entity) {
|
|
|
5503
5979
|
(left, right) => left.key.localeCompare(right.key) || left.title.localeCompare(right.title) || left.facts.join("\n").localeCompare(right.facts.join("\n"))
|
|
5504
5980
|
);
|
|
5505
5981
|
if (normalizedSections.length === 0) return void 0;
|
|
5506
|
-
return
|
|
5982
|
+
return createHash6("sha256").update(JSON.stringify(normalizedSections)).digest("hex");
|
|
5507
5983
|
}
|
|
5508
5984
|
function isEntitySynthesisStale(entity) {
|
|
5509
5985
|
const structuredFactCount = countEntityStructuredFacts(entity);
|
|
@@ -5525,15 +6001,6 @@ function isEntitySynthesisStale(entity) {
|
|
|
5525
6001
|
}
|
|
5526
6002
|
|
|
5527
6003
|
// src/storage.ts
|
|
5528
|
-
var DELETION_REVISION_MAX_MTIME_MS = 864e13;
|
|
5529
|
-
var DELETION_REVISION_LOCK_STALE_MS = 6e4;
|
|
5530
|
-
var DELETION_REVISION_LOCK_MAX_WAIT_MS = 12e4;
|
|
5531
|
-
function isValidDeletionRevisionPath(value) {
|
|
5532
|
-
return typeof value === "string" && value.length > 0 && !value.includes("\\") && !value.includes("\0") && !path9.posix.isAbsolute(value) && path9.posix.normalize(value) === value && value !== "." && value !== ".." && !value.startsWith("../");
|
|
5533
|
-
}
|
|
5534
|
-
function deletionRevisionPathIdentity(value) {
|
|
5535
|
-
return value.normalize("NFC").toUpperCase().toLowerCase().replace(/\u00df/g, "ss");
|
|
5536
|
-
}
|
|
5537
6004
|
function assertMemoryWorthCounter(field, value) {
|
|
5538
6005
|
if (typeof value !== "number" || !Number.isFinite(value)) {
|
|
5539
6006
|
throw new Error(`${field} must be a finite number, got ${String(value)}`);
|
|
@@ -6059,17 +6526,17 @@ function inferEntityTypeFromContent(content) {
|
|
|
6059
6526
|
}
|
|
6060
6527
|
var KNOWN_ENTITY_FILENAME_PREFIXES = /* @__PURE__ */ new Set(["company", "other", "person", "place", "project", "tool", "topic"]);
|
|
6061
6528
|
function inferEntityTypeFromFilename(pathRel) {
|
|
6062
|
-
const basename =
|
|
6529
|
+
const basename = path11.basename(pathRel, ".md").toLowerCase();
|
|
6063
6530
|
const separator = basename.indexOf("-");
|
|
6064
6531
|
if (separator <= 0) return void 0;
|
|
6065
6532
|
const candidate = basename.slice(0, separator);
|
|
6066
6533
|
return KNOWN_ENTITY_FILENAME_PREFIXES.has(candidate) ? candidate : void 0;
|
|
6067
6534
|
}
|
|
6068
6535
|
function normalizeFrontmatterForPath(frontmatter, pathRel, content = "") {
|
|
6069
|
-
const normalizedPath = pathRel.split(
|
|
6536
|
+
const normalizedPath = pathRel.split(path11.sep).join("/");
|
|
6070
6537
|
let normalizedFrontmatter = frontmatter;
|
|
6071
6538
|
if (normalizedPath === "entities" || normalizedPath.startsWith("entities/") || normalizedPath.includes("/entities/")) {
|
|
6072
|
-
const basename =
|
|
6539
|
+
const basename = path11.basename(pathRel, ".md");
|
|
6073
6540
|
const inferredType = inferEntityTypeFromContent(content) || inferEntityTypeFromFilename(pathRel) || "entity";
|
|
6074
6541
|
const existingTags = Array.isArray(frontmatter.tags) ? frontmatter.tags : [];
|
|
6075
6542
|
normalizedFrontmatter = {
|
|
@@ -6534,12 +7001,20 @@ var StorageManager3 = class _StorageManager extends TombstoneBlockedCaptureIndex
|
|
|
6534
7001
|
super();
|
|
6535
7002
|
this.baseDir = baseDir;
|
|
6536
7003
|
this.entitySchemas = entitySchemas;
|
|
7004
|
+
this.deletionRevisionStore = new DeletionRevisionStore({
|
|
7005
|
+
baseDir,
|
|
7006
|
+
deletionRevisionMetadataPath: path11.join(baseDir, ".offline-sync", "deletion-revisions.v1.json"),
|
|
7007
|
+
invalidationCommitMetadataPath: path11.join(baseDir, ".offline-sync", "invalidation-commits.v1.json"),
|
|
7008
|
+
deletionRevisionLockPath: path11.join(baseDir, ".offline-sync", "deletion-revisions.v1.json.lock"),
|
|
7009
|
+
assertManagedStoragePath: (filePath, method) => this.assertManagedStoragePath(filePath, method)
|
|
7010
|
+
});
|
|
6537
7011
|
this.hotMemoriesCacheEnabled = hotMemoriesCacheEnabledOverride ?? _StorageManager.hotMemoriesCacheDefaultByDir.get(baseDir) ?? _StorageManager.hotMemoriesCacheDefault;
|
|
6538
7012
|
this.loadAliasesSync();
|
|
6539
7013
|
}
|
|
6540
7014
|
baseDir;
|
|
6541
7015
|
entitySchemas;
|
|
6542
7016
|
knowledgeIndexCache = null;
|
|
7017
|
+
deletionRevisionStore;
|
|
6543
7018
|
artifactIndexCache = null;
|
|
6544
7019
|
projectionLedgerLagManager = new ProjectionLedgerLagManager();
|
|
6545
7020
|
static loadedMemorySnapshots = /* @__PURE__ */ new WeakMap();
|
|
@@ -6718,8 +7193,8 @@ var StorageManager3 = class _StorageManager extends TombstoneBlockedCaptureIndex
|
|
|
6718
7193
|
}
|
|
6719
7194
|
/** Whether `filePath` resolves to `stateDir` itself or a path contained in it. */
|
|
6720
7195
|
isStateFilePath(filePath) {
|
|
6721
|
-
const rel =
|
|
6722
|
-
return rel === "" || !rel.startsWith("..") && !
|
|
7196
|
+
const rel = path11.relative(this.stateDir, path11.resolve(filePath));
|
|
7197
|
+
return rel === "" || !rel.startsWith("..") && !path11.isAbsolute(rel);
|
|
6723
7198
|
}
|
|
6724
7199
|
// ── Tombstone store (issue #1579) ───────────────────────────────────────
|
|
6725
7200
|
// Instance-scoped (rule 11) + namespace-scoped (rule 42) tombstone index.
|
|
@@ -6834,8 +7309,8 @@ var StorageManager3 = class _StorageManager extends TombstoneBlockedCaptureIndex
|
|
|
6834
7309
|
* /srv/cold/remnic), skipping the patch and stranding a stale hot entry.
|
|
6835
7310
|
*/
|
|
6836
7311
|
isColdOrArchiveTierPath(p) {
|
|
6837
|
-
const rel =
|
|
6838
|
-
return rel === "cold" || rel === "archive" || rel.startsWith(`cold${
|
|
7312
|
+
const rel = path11.relative(this.baseDir, p);
|
|
7313
|
+
return rel === "cold" || rel === "archive" || rel.startsWith(`cold${path11.sep}`) || rel.startsWith(`archive${path11.sep}`);
|
|
6839
7314
|
}
|
|
6840
7315
|
/**
|
|
6841
7316
|
* Mark the secure-store as required for this storage instance.
|
|
@@ -6922,7 +7397,7 @@ var StorageManager3 = class _StorageManager extends TombstoneBlockedCaptureIndex
|
|
|
6922
7397
|
void 0,
|
|
6923
7398
|
this.baseDir
|
|
6924
7399
|
);
|
|
6925
|
-
const rel =
|
|
7400
|
+
const rel = path11.relative(this.baseDir, filePath).split(path11.sep).join("/");
|
|
6926
7401
|
return `${rel}:${version.versionId}`;
|
|
6927
7402
|
} catch (err) {
|
|
6928
7403
|
log.warn(
|
|
@@ -7002,12 +7477,12 @@ var StorageManager3 = class _StorageManager extends TombstoneBlockedCaptureIndex
|
|
|
7002
7477
|
}
|
|
7003
7478
|
identityFilePath(workspaceDir, namespace) {
|
|
7004
7479
|
const rawNamespace = typeof namespace === "string" ? namespace.trim() : "";
|
|
7005
|
-
if (!rawNamespace) return
|
|
7480
|
+
if (!rawNamespace) return path11.join(workspaceDir, "IDENTITY.md");
|
|
7006
7481
|
const safeNamespace = rawNamespace.replace(/[^a-zA-Z0-9._-]/g, "-");
|
|
7007
|
-
return
|
|
7482
|
+
return path11.join(workspaceDir, `IDENTITY.${safeNamespace}.md`);
|
|
7008
7483
|
}
|
|
7009
7484
|
versionFilePath(kind) {
|
|
7010
|
-
return
|
|
7485
|
+
return path11.join(this.stateDir, `.${kind}-version.log`);
|
|
7011
7486
|
}
|
|
7012
7487
|
bumpSharedVersion(kind, fallbackMap) {
|
|
7013
7488
|
const filePath = this.versionFilePath(kind);
|
|
@@ -7111,7 +7586,7 @@ var StorageManager3 = class _StorageManager extends TombstoneBlockedCaptureIndex
|
|
|
7111
7586
|
// memories.
|
|
7112
7587
|
// -------------------------------------------------------------------------
|
|
7113
7588
|
get wearablesDir() {
|
|
7114
|
-
return
|
|
7589
|
+
return path11.join(this.baseDir, WEARABLES_DIR_NAME);
|
|
7115
7590
|
}
|
|
7116
7591
|
/**
|
|
7117
7592
|
* Resolve the on-disk path for a source/day transcript. Throws on
|
|
@@ -7127,7 +7602,7 @@ var StorageManager3 = class _StorageManager extends TombstoneBlockedCaptureIndex
|
|
|
7127
7602
|
if (!isValidTranscriptDate(date)) {
|
|
7128
7603
|
throw new Error(`invalid wearable transcript date '${String(date)}' \u2014 expected YYYY-MM-DD`);
|
|
7129
7604
|
}
|
|
7130
|
-
return
|
|
7605
|
+
return path11.join(this.wearablesDir, sourceId, `${date}.md`);
|
|
7131
7606
|
}
|
|
7132
7607
|
async writeWearableDayTranscript(sourceId, date, serialized) {
|
|
7133
7608
|
const targetPath = this.wearableTranscriptPath(sourceId, date);
|
|
@@ -7153,10 +7628,10 @@ var StorageManager3 = class _StorageManager extends TombstoneBlockedCaptureIndex
|
|
|
7153
7628
|
return this._fusionStore = new FusionArtifactStore(this.wearablesDir, this.baseDir, {
|
|
7154
7629
|
writeFile: (p, c) => this.writeStorageSecureFile(p, c),
|
|
7155
7630
|
readFile: (p) => readMaybeEncryptedFile(p, this._secureStoreKey, this.baseDir),
|
|
7156
|
-
readDir: (d) =>
|
|
7157
|
-
deleteFile: (p) =>
|
|
7631
|
+
readDir: (d) => readdir6(d),
|
|
7632
|
+
deleteFile: (p) => unlink5(p),
|
|
7158
7633
|
realpath: (p) => realpath3(p),
|
|
7159
|
-
lstat: (p) =>
|
|
7634
|
+
lstat: (p) => lstat4(p).then((st) => ({ isSymbolicLink: st.isSymbolicLink() }))
|
|
7160
7635
|
});
|
|
7161
7636
|
}
|
|
7162
7637
|
_meetingRecordStore;
|
|
@@ -7171,10 +7646,10 @@ var StorageManager3 = class _StorageManager extends TombstoneBlockedCaptureIndex
|
|
|
7171
7646
|
return this._meetingRecordStore = new MeetingRecordStore(this.baseDir, {
|
|
7172
7647
|
writeFile: (p, c) => this.writeStorageSecureFile(p, c),
|
|
7173
7648
|
readFile: (p) => readMaybeEncryptedFile(p, this._secureStoreKey, this.baseDir),
|
|
7174
|
-
readDir: (d) =>
|
|
7175
|
-
deleteFile: (p) =>
|
|
7649
|
+
readDir: (d) => readdir6(d),
|
|
7650
|
+
deleteFile: (p) => unlink5(p),
|
|
7176
7651
|
realpath: (p) => realpath3(p),
|
|
7177
|
-
lstat: (p) =>
|
|
7652
|
+
lstat: (p) => lstat4(p).then((st) => ({ isSymbolicLink: st.isSymbolicLink() }))
|
|
7178
7653
|
});
|
|
7179
7654
|
}
|
|
7180
7655
|
/**
|
|
@@ -7243,218 +7718,87 @@ var StorageManager3 = class _StorageManager extends TombstoneBlockedCaptureIndex
|
|
|
7243
7718
|
});
|
|
7244
7719
|
}
|
|
7245
7720
|
get factsDir() {
|
|
7246
|
-
return
|
|
7247
|
-
}
|
|
7248
|
-
get correctionsDir() {
|
|
7249
|
-
return path9.join(this.baseDir, "corrections");
|
|
7250
|
-
}
|
|
7251
|
-
get proceduresDir() {
|
|
7252
|
-
return path9.join(this.baseDir, "procedures");
|
|
7253
|
-
}
|
|
7254
|
-
get reasoningTracesDir() {
|
|
7255
|
-
return path9.join(this.baseDir, "reasoning-traces");
|
|
7256
|
-
}
|
|
7257
|
-
get entitiesDir() {
|
|
7258
|
-
return path9.join(this.baseDir, "entities");
|
|
7721
|
+
return path11.join(this.baseDir, "facts");
|
|
7259
7722
|
}
|
|
7260
|
-
|
|
7261
|
-
|
|
7262
|
-
const filePath = path9.resolve(this.entitiesDir, `${name}.md`);
|
|
7263
|
-
const relative2 = path9.relative(this.entitiesDir, filePath);
|
|
7264
|
-
if (relative2 === "" || relative2.startsWith("..") || path9.isAbsolute(relative2)) {
|
|
7265
|
-
return null;
|
|
7266
|
-
}
|
|
7267
|
-
return filePath;
|
|
7268
|
-
}
|
|
7269
|
-
readStorageSecureFile(filePath) {
|
|
7270
|
-
return readMaybeEncryptedFile(filePath, this._secureStoreKey, this.baseDir);
|
|
7271
|
-
}
|
|
7272
|
-
writeStorageSecureFile(filePath, content, forceEncrypt = false) {
|
|
7273
|
-
const writeKey = this.resolveWriteKey(forceEncrypt);
|
|
7274
|
-
return writeMaybeEncryptedFile(filePath, content, writeKey, {}, this.baseDir).then(() => {
|
|
7275
|
-
this.notifyCatalogWriteForPath(filePath);
|
|
7276
|
-
});
|
|
7277
|
-
}
|
|
7278
|
-
assertManagedStoragePath(filePath, method) {
|
|
7279
|
-
const resolved = path9.resolve(filePath);
|
|
7280
|
-
const base = path9.resolve(this.baseDir);
|
|
7281
|
-
const rel = path9.relative(base, resolved);
|
|
7282
|
-
if (rel === "" || rel === ".." || rel.startsWith(`..${path9.sep}`) || path9.isAbsolute(rel)) {
|
|
7283
|
-
throw new Error(`${method}: file path escapes memory dir`);
|
|
7284
|
-
}
|
|
7285
|
-
return resolved;
|
|
7286
|
-
}
|
|
7287
|
-
parseDeletionRevisionMetadata(raw) {
|
|
7288
|
-
let parsed;
|
|
7289
|
-
try {
|
|
7290
|
-
parsed = JSON.parse(raw);
|
|
7291
|
-
} catch {
|
|
7292
|
-
throw new Error("Deletion revision metadata is invalid.");
|
|
7293
|
-
}
|
|
7294
|
-
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
7295
|
-
throw new Error("Deletion revision metadata is invalid.");
|
|
7296
|
-
}
|
|
7297
|
-
const root = parsed;
|
|
7298
|
-
if (Object.keys(root).sort().join(",") !== "deletions,version" || root.version !== 1 || !Array.isArray(root.deletions)) {
|
|
7299
|
-
throw new Error("Deletion revision metadata is invalid.");
|
|
7300
|
-
}
|
|
7301
|
-
const revisions = /* @__PURE__ */ new Map();
|
|
7302
|
-
const pathByIdentity = /* @__PURE__ */ new Map();
|
|
7303
|
-
for (const rawEntry of root.deletions) {
|
|
7304
|
-
if (!rawEntry || typeof rawEntry !== "object" || Array.isArray(rawEntry)) {
|
|
7305
|
-
throw new Error("Deletion revision metadata is invalid.");
|
|
7306
|
-
}
|
|
7307
|
-
const entry = rawEntry;
|
|
7308
|
-
if (Object.keys(entry).sort().join(",") !== "mtimeMs,path" || !isValidDeletionRevisionPath(entry.path) || typeof entry.mtimeMs !== "number" || !Number.isFinite(entry.mtimeMs) || entry.mtimeMs < 0 || entry.mtimeMs > DELETION_REVISION_MAX_MTIME_MS || revisions.has(entry.path)) {
|
|
7309
|
-
throw new Error("Deletion revision metadata is invalid.");
|
|
7310
|
-
}
|
|
7311
|
-
const identity = deletionRevisionPathIdentity(entry.path);
|
|
7312
|
-
if (pathByIdentity.has(identity)) {
|
|
7313
|
-
throw new Error("Deletion revision metadata is invalid.");
|
|
7314
|
-
}
|
|
7315
|
-
pathByIdentity.set(identity, entry.path);
|
|
7316
|
-
revisions.set(entry.path, entry.mtimeMs);
|
|
7317
|
-
}
|
|
7318
|
-
return new Map([...revisions.entries()].sort(([left], [right]) => left < right ? -1 : left > right ? 1 : 0));
|
|
7723
|
+
get correctionsDir() {
|
|
7724
|
+
return path11.join(this.baseDir, "corrections");
|
|
7319
7725
|
}
|
|
7320
|
-
|
|
7321
|
-
|
|
7322
|
-
try {
|
|
7323
|
-
raw = await readFile2(this.deletionRevisionMetadataPath, "utf8");
|
|
7324
|
-
} catch (error) {
|
|
7325
|
-
if (isErrnoCode(error, "ENOENT")) return /* @__PURE__ */ new Map();
|
|
7326
|
-
throw new Error("Deletion revision metadata is unavailable.");
|
|
7327
|
-
}
|
|
7328
|
-
return this.parseDeletionRevisionMetadata(raw);
|
|
7726
|
+
get proceduresDir() {
|
|
7727
|
+
return path11.join(this.baseDir, "procedures");
|
|
7329
7728
|
}
|
|
7330
|
-
|
|
7331
|
-
|
|
7332
|
-
|
|
7333
|
-
|
|
7334
|
-
|
|
7335
|
-
|
|
7336
|
-
|
|
7337
|
-
|
|
7338
|
-
|
|
7339
|
-
|
|
7340
|
-
|
|
7341
|
-
|
|
7342
|
-
if (!await lock.refresh()) {
|
|
7343
|
-
throw new Error("Deletion revision metadata lock was lost.");
|
|
7344
|
-
}
|
|
7345
|
-
await rename2(temporaryPath, this.deletionRevisionMetadataPath);
|
|
7346
|
-
} finally {
|
|
7347
|
-
if (handle !== null) await handle.close().catch(() => void 0);
|
|
7348
|
-
await unlink4(temporaryPath).catch((error) => {
|
|
7349
|
-
if (!isErrnoCode(error, "ENOENT")) throw error;
|
|
7350
|
-
});
|
|
7729
|
+
get reasoningTracesDir() {
|
|
7730
|
+
return path11.join(this.baseDir, "reasoning-traces");
|
|
7731
|
+
}
|
|
7732
|
+
get entitiesDir() {
|
|
7733
|
+
return path11.join(this.baseDir, "entities");
|
|
7734
|
+
}
|
|
7735
|
+
resolveEntityFilePath(name) {
|
|
7736
|
+
if (typeof name !== "string") return null;
|
|
7737
|
+
const filePath = path11.resolve(this.entitiesDir, `${name}.md`);
|
|
7738
|
+
const relative2 = path11.relative(this.entitiesDir, filePath);
|
|
7739
|
+
if (relative2 === "" || relative2.startsWith("..") || path11.isAbsolute(relative2)) {
|
|
7740
|
+
return null;
|
|
7351
7741
|
}
|
|
7742
|
+
return filePath;
|
|
7352
7743
|
}
|
|
7353
|
-
|
|
7354
|
-
return
|
|
7355
|
-
|
|
7356
|
-
|
|
7357
|
-
|
|
7358
|
-
|
|
7359
|
-
|
|
7360
|
-
|
|
7361
|
-
|
|
7362
|
-
|
|
7363
|
-
|
|
7744
|
+
readStorageSecureFile(filePath) {
|
|
7745
|
+
return readMaybeEncryptedFile(filePath, this._secureStoreKey, this.baseDir);
|
|
7746
|
+
}
|
|
7747
|
+
async readDependencyPropagationQueueFile(filePath) {
|
|
7748
|
+
const target = await resolveSafeStoragePath(
|
|
7749
|
+
this.baseDir,
|
|
7750
|
+
path11.relative(
|
|
7751
|
+
this.baseDir,
|
|
7752
|
+
this.assertManagedStoragePath(filePath, "storage.readDependencyPropagationQueueFile")
|
|
7753
|
+
)
|
|
7754
|
+
);
|
|
7755
|
+
return this.readStorageSecureFile(target);
|
|
7756
|
+
}
|
|
7757
|
+
async writeDependencyPropagationQueueFile(filePath, content) {
|
|
7758
|
+
const target = await resolveSafeStoragePath(
|
|
7759
|
+
this.baseDir,
|
|
7760
|
+
path11.relative(
|
|
7761
|
+
this.baseDir,
|
|
7762
|
+
this.assertManagedStoragePath(filePath, "storage.writeDependencyPropagationQueueFile")
|
|
7763
|
+
)
|
|
7364
7764
|
);
|
|
7765
|
+
return this.writeStorageSecureFile(target, content);
|
|
7766
|
+
}
|
|
7767
|
+
writeStorageSecureFile(filePath, content, forceEncrypt = false) {
|
|
7768
|
+
const writeKey = this.resolveWriteKey(forceEncrypt);
|
|
7769
|
+
return writeMaybeEncryptedFile(filePath, content, writeKey, {}, this.baseDir).then(() => {
|
|
7770
|
+
this.notifyCatalogWriteForPath(filePath);
|
|
7771
|
+
});
|
|
7772
|
+
}
|
|
7773
|
+
assertManagedStoragePath(filePath, method) {
|
|
7774
|
+
const resolved = path11.resolve(filePath);
|
|
7775
|
+
const base = path11.resolve(this.baseDir);
|
|
7776
|
+
const rel = path11.relative(base, resolved);
|
|
7777
|
+
if (rel === "" || rel === ".." || rel.startsWith(`..${path11.sep}`) || path11.isAbsolute(rel)) {
|
|
7778
|
+
throw new Error(`${method}: file path escapes memory dir`);
|
|
7779
|
+
}
|
|
7780
|
+
return resolved;
|
|
7365
7781
|
}
|
|
7366
7782
|
async readDeletionRevisions() {
|
|
7367
|
-
return this.
|
|
7783
|
+
return this.deletionRevisionStore.readDeletionRevisions();
|
|
7784
|
+
}
|
|
7785
|
+
async hasCommittedInvalidation(memory) {
|
|
7786
|
+
return this.deletionRevisionStore.hasCommittedInvalidation(memory);
|
|
7787
|
+
}
|
|
7788
|
+
async recordCommittedInvalidation(memory) {
|
|
7789
|
+
return this.deletionRevisionStore.recordCommittedInvalidation(memory);
|
|
7790
|
+
}
|
|
7791
|
+
async clearCommittedInvalidation(memory) {
|
|
7792
|
+
return this.deletionRevisionStore.clearCommittedInvalidation(memory);
|
|
7368
7793
|
}
|
|
7369
7794
|
async recordReplicatedDeletionRevision(filePath, mtimeMs) {
|
|
7370
|
-
|
|
7371
|
-
if (typeof mtimeMs !== "number" || !Number.isFinite(mtimeMs) || mtimeMs < 0 || mtimeMs > DELETION_REVISION_MAX_MTIME_MS) {
|
|
7372
|
-
throw new Error("Deletion revision timestamp is invalid.");
|
|
7373
|
-
}
|
|
7374
|
-
const relativePath = path9.relative(this.baseDir, target).split(path9.sep).join("/");
|
|
7375
|
-
if (!isValidDeletionRevisionPath(relativePath)) {
|
|
7376
|
-
throw new Error("Deletion revision path is invalid.");
|
|
7377
|
-
}
|
|
7378
|
-
await this.withDeletionRevisionLock(async (lock) => {
|
|
7379
|
-
const revisions = await this.readDeletionRevisionMetadata();
|
|
7380
|
-
const identity = deletionRevisionPathIdentity(relativePath);
|
|
7381
|
-
let existingPath;
|
|
7382
|
-
for (const candidatePath of revisions.keys()) {
|
|
7383
|
-
if (deletionRevisionPathIdentity(candidatePath) === identity) {
|
|
7384
|
-
existingPath = candidatePath;
|
|
7385
|
-
break;
|
|
7386
|
-
}
|
|
7387
|
-
}
|
|
7388
|
-
const existingMtimeMs = existingPath === void 0 ? void 0 : revisions.get(existingPath);
|
|
7389
|
-
if (existingMtimeMs !== void 0 && existingMtimeMs >= mtimeMs) return;
|
|
7390
|
-
const updated = new Map(revisions);
|
|
7391
|
-
if (existingPath !== void 0) updated.delete(existingPath);
|
|
7392
|
-
updated.set(relativePath, mtimeMs);
|
|
7393
|
-
await this.writeDeletionRevisionMetadata(updated, lock);
|
|
7394
|
-
});
|
|
7795
|
+
return this.deletionRevisionStore.recordReplicatedDeletionRevision(filePath, mtimeMs);
|
|
7395
7796
|
}
|
|
7396
7797
|
async writeManagedStorageFile(filePath, write) {
|
|
7397
|
-
|
|
7398
|
-
const relativePath = path9.relative(this.baseDir, target).split(path9.sep).join("/");
|
|
7399
|
-
if (!isValidDeletionRevisionPath(relativePath)) {
|
|
7400
|
-
throw new Error("Deletion revision path is invalid.");
|
|
7401
|
-
}
|
|
7402
|
-
await this.withDeletionRevisionLock(async (lock) => {
|
|
7403
|
-
const before = await this.readDeletionRevisionMetadata();
|
|
7404
|
-
const identity = deletionRevisionPathIdentity(relativePath);
|
|
7405
|
-
const existingPath = [...before.keys()].find(
|
|
7406
|
-
(candidatePath) => deletionRevisionPathIdentity(candidatePath) === identity
|
|
7407
|
-
);
|
|
7408
|
-
await write();
|
|
7409
|
-
if (existingPath === void 0) return;
|
|
7410
|
-
const updated = new Map(before);
|
|
7411
|
-
updated.delete(existingPath);
|
|
7412
|
-
await this.writeDeletionRevisionMetadata(updated, lock);
|
|
7413
|
-
});
|
|
7798
|
+
return this.deletionRevisionStore.writeManagedStorageFile(filePath, write);
|
|
7414
7799
|
}
|
|
7415
7800
|
async deleteManagedStorageFile(filePath, deletionMtimeMs) {
|
|
7416
|
-
|
|
7417
|
-
if (deletionMtimeMs !== void 0 && deletionMtimeMs !== null && (typeof deletionMtimeMs !== "number" || !Number.isFinite(deletionMtimeMs) || deletionMtimeMs < 0 || deletionMtimeMs > DELETION_REVISION_MAX_MTIME_MS)) {
|
|
7418
|
-
throw new Error("Deletion revision timestamp is invalid.");
|
|
7419
|
-
}
|
|
7420
|
-
return this.withDeletionRevisionLock(async (lock) => {
|
|
7421
|
-
try {
|
|
7422
|
-
await lstat3(target);
|
|
7423
|
-
} catch (error) {
|
|
7424
|
-
if (isErrnoCode(error, "ENOENT")) return false;
|
|
7425
|
-
throw error;
|
|
7426
|
-
}
|
|
7427
|
-
const relativePath = path9.relative(this.baseDir, target).split(path9.sep).join("/");
|
|
7428
|
-
if (!isValidDeletionRevisionPath(relativePath)) {
|
|
7429
|
-
throw new Error("Deletion revision path is invalid.");
|
|
7430
|
-
}
|
|
7431
|
-
const revision = deletionMtimeMs === null ? void 0 : deletionMtimeMs ?? Date.now();
|
|
7432
|
-
const before = await this.readDeletionRevisionMetadata();
|
|
7433
|
-
const identity = deletionRevisionPathIdentity(relativePath);
|
|
7434
|
-
let existingPath;
|
|
7435
|
-
for (const candidatePath of before.keys()) {
|
|
7436
|
-
if (deletionRevisionPathIdentity(candidatePath) === identity) {
|
|
7437
|
-
existingPath = candidatePath;
|
|
7438
|
-
break;
|
|
7439
|
-
}
|
|
7440
|
-
}
|
|
7441
|
-
const existing = existingPath === void 0 ? void 0 : before.get(existingPath);
|
|
7442
|
-
const changed = existingPath !== void 0 && existingPath !== relativePath || (revision === void 0 ? existing !== void 0 : existing !== revision);
|
|
7443
|
-
if (changed) {
|
|
7444
|
-
const updated = new Map(before);
|
|
7445
|
-
if (existingPath !== void 0) updated.delete(existingPath);
|
|
7446
|
-
if (revision !== void 0) updated.set(relativePath, revision);
|
|
7447
|
-
await this.writeDeletionRevisionMetadata(updated, lock);
|
|
7448
|
-
}
|
|
7449
|
-
try {
|
|
7450
|
-
await unlink4(target);
|
|
7451
|
-
return true;
|
|
7452
|
-
} catch (error) {
|
|
7453
|
-
if (changed) await this.writeDeletionRevisionMetadata(before, lock);
|
|
7454
|
-
if (isErrnoCode(error, "ENOENT")) return false;
|
|
7455
|
-
throw error;
|
|
7456
|
-
}
|
|
7457
|
-
});
|
|
7801
|
+
return this.deletionRevisionStore.deleteManagedStorageFile(filePath, deletionMtimeMs);
|
|
7458
7802
|
}
|
|
7459
7803
|
async readOfflineSyncFile(filePath) {
|
|
7460
7804
|
const target = this.assertManagedStoragePath(filePath, "storage.readOfflineSyncFile");
|
|
@@ -7463,7 +7807,7 @@ var StorageManager3 = class _StorageManager extends TombstoneBlockedCaptureIndex
|
|
|
7463
7807
|
async digestOfflineSyncFile(filePath) {
|
|
7464
7808
|
const target = this.assertManagedStoragePath(filePath, "storage.digestOfflineSyncFile");
|
|
7465
7809
|
const st = await stat2(target);
|
|
7466
|
-
const relPath =
|
|
7810
|
+
const relPath = path11.relative(this.baseDir, target).split(path11.sep).join("/");
|
|
7467
7811
|
const cache = await this.loadOfflineSyncDigestCache();
|
|
7468
7812
|
const cached = cache.get(relPath);
|
|
7469
7813
|
if (cached && cached.statBytes === st.size && cached.mtimeMs === st.mtimeMs && cached.ctimeMs === st.ctimeMs && !cached.encrypted) {
|
|
@@ -7477,11 +7821,11 @@ var StorageManager3 = class _StorageManager extends TombstoneBlockedCaptureIndex
|
|
|
7477
7821
|
if (encrypted) {
|
|
7478
7822
|
const content = await readMaybeEncryptedFileBuffer(target, this._secureStoreKey, this.baseDir);
|
|
7479
7823
|
digest = {
|
|
7480
|
-
sha256:
|
|
7824
|
+
sha256: createHash7("sha256").update(content).digest("hex"),
|
|
7481
7825
|
bytes: content.byteLength
|
|
7482
7826
|
};
|
|
7483
7827
|
} else {
|
|
7484
|
-
const hash =
|
|
7828
|
+
const hash = createHash7("sha256");
|
|
7485
7829
|
let bytes = 0;
|
|
7486
7830
|
for await (const rawChunk of createReadStream(target)) {
|
|
7487
7831
|
const chunk = Buffer.isBuffer(rawChunk) ? rawChunk : Buffer.from(rawChunk);
|
|
@@ -7509,7 +7853,7 @@ var StorageManager3 = class _StorageManager extends TombstoneBlockedCaptureIndex
|
|
|
7509
7853
|
async readOfflineSyncDigestCache() {
|
|
7510
7854
|
const cache = /* @__PURE__ */ new Map();
|
|
7511
7855
|
try {
|
|
7512
|
-
const raw = await
|
|
7856
|
+
const raw = await readFile3(this.offlineSyncDigestCachePath, "utf-8");
|
|
7513
7857
|
const parsed = JSON.parse(raw);
|
|
7514
7858
|
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return cache;
|
|
7515
7859
|
const entries = parsed.entries;
|
|
@@ -7524,7 +7868,7 @@ var StorageManager3 = class _StorageManager extends TombstoneBlockedCaptureIndex
|
|
|
7524
7868
|
const bytes = typeof record.bytes === "number" ? record.bytes : NaN;
|
|
7525
7869
|
const sha256 = typeof record.sha256 === "string" ? record.sha256 : "";
|
|
7526
7870
|
const encrypted = record.encrypted === true;
|
|
7527
|
-
if (cachePath.length === 0 || cachePath === ".." || cachePath.startsWith("../") ||
|
|
7871
|
+
if (cachePath.length === 0 || cachePath === ".." || cachePath.startsWith("../") || path11.isAbsolute(cachePath) || !Number.isFinite(statBytes) || !Number.isFinite(mtimeMs) || !Number.isFinite(ctimeMs) || !Number.isFinite(bytes) || !/^[a-f0-9]{64}$/i.test(sha256)) {
|
|
7528
7872
|
continue;
|
|
7529
7873
|
}
|
|
7530
7874
|
cache.set(cachePath, { statBytes, mtimeMs, ctimeMs, encrypted, sha256, bytes });
|
|
@@ -7566,7 +7910,7 @@ var StorageManager3 = class _StorageManager extends TombstoneBlockedCaptureIndex
|
|
|
7566
7910
|
const cache = this.offlineSyncDigestCache;
|
|
7567
7911
|
if (!cache) return;
|
|
7568
7912
|
const entries = [...cache.entries()].sort(([a], [b]) => a.localeCompare(b)).map(([entryPath, entry]) => ({ path: entryPath, ...entry }));
|
|
7569
|
-
await
|
|
7913
|
+
await mkdir2(path11.dirname(this.offlineSyncDigestCachePath), { recursive: true });
|
|
7570
7914
|
await writeFile2(this.offlineSyncDigestCachePath, `${JSON.stringify({ version: 1, entries })}
|
|
7571
7915
|
`, "utf-8");
|
|
7572
7916
|
}).catch((err) => {
|
|
@@ -7576,7 +7920,7 @@ var StorageManager3 = class _StorageManager extends TombstoneBlockedCaptureIndex
|
|
|
7576
7920
|
});
|
|
7577
7921
|
}
|
|
7578
7922
|
async offlineSyncFileIsEncrypted(filePath) {
|
|
7579
|
-
const handle = await
|
|
7923
|
+
const handle = await open2(filePath, "r");
|
|
7580
7924
|
try {
|
|
7581
7925
|
const header = Buffer.alloc(MAGIC_HEADER_SIZE);
|
|
7582
7926
|
const { bytesRead } = await handle.read(header, 0, header.length, 0);
|
|
@@ -7635,7 +7979,7 @@ var StorageManager3 = class _StorageManager extends TombstoneBlockedCaptureIndex
|
|
|
7635
7979
|
}
|
|
7636
7980
|
let handle;
|
|
7637
7981
|
try {
|
|
7638
|
-
handle = await
|
|
7982
|
+
handle = await open2(filePath, "r");
|
|
7639
7983
|
} catch (err) {
|
|
7640
7984
|
if (isErrnoCode(err, "ENOENT")) return false;
|
|
7641
7985
|
throw err;
|
|
@@ -7655,7 +7999,7 @@ var StorageManager3 = class _StorageManager extends TombstoneBlockedCaptureIndex
|
|
|
7655
7999
|
}
|
|
7656
8000
|
async appendStorageSecureFileUnlocked(filePath, content) {
|
|
7657
8001
|
const writeKey = this.resolveWriteKey();
|
|
7658
|
-
await
|
|
8002
|
+
await mkdir2(path11.dirname(filePath), { recursive: true });
|
|
7659
8003
|
if (writeKey === null) {
|
|
7660
8004
|
try {
|
|
7661
8005
|
if (await this.isEncryptedFileHeader(filePath)) {
|
|
@@ -7681,19 +8025,13 @@ var StorageManager3 = class _StorageManager extends TombstoneBlockedCaptureIndex
|
|
|
7681
8025
|
this.notifyCatalogWriteForPath(filePath);
|
|
7682
8026
|
}
|
|
7683
8027
|
get stateDir() {
|
|
7684
|
-
return
|
|
8028
|
+
return path11.join(this.baseDir, "state");
|
|
7685
8029
|
}
|
|
7686
8030
|
get offlineSyncDigestCachePath() {
|
|
7687
|
-
return
|
|
7688
|
-
}
|
|
7689
|
-
get deletionRevisionMetadataPath() {
|
|
7690
|
-
return path9.join(this.baseDir, ".offline-sync", "deletion-revisions.v1.json");
|
|
7691
|
-
}
|
|
7692
|
-
get deletionRevisionLockPath() {
|
|
7693
|
-
return `${this.deletionRevisionMetadataPath}.lock`;
|
|
8031
|
+
return path11.join(this.baseDir, ".offline-sync", "digest-cache.v1.json");
|
|
7694
8032
|
}
|
|
7695
8033
|
get entitySynthesisQueuePath() {
|
|
7696
|
-
return
|
|
8034
|
+
return path11.join(this.stateDir, "entity-synthesis-queue.json");
|
|
7697
8035
|
}
|
|
7698
8036
|
// ── Tombstone store access (issue #1579) ─────────────────────────────────
|
|
7699
8037
|
/**
|
|
@@ -7703,7 +8041,7 @@ var StorageManager3 = class _StorageManager extends TombstoneBlockedCaptureIndex
|
|
|
7703
8041
|
* helpers are injected into the store).
|
|
7704
8042
|
*/
|
|
7705
8043
|
get tombstonesPath() {
|
|
7706
|
-
return
|
|
8044
|
+
return path11.join(this.stateDir, "tombstones.jsonl");
|
|
7707
8045
|
}
|
|
7708
8046
|
/**
|
|
7709
8047
|
* Configure the tombstone invariant for this storage instance. Installed
|
|
@@ -7800,6 +8138,16 @@ var StorageManager3 = class _StorageManager extends TombstoneBlockedCaptureIndex
|
|
|
7800
8138
|
return null;
|
|
7801
8139
|
}
|
|
7802
8140
|
}
|
|
8141
|
+
async hasExactTombstone(input) {
|
|
8142
|
+
if (!this.tombstonesConfig.enabled) return false;
|
|
8143
|
+
try {
|
|
8144
|
+
const store = await this.getTombstoneStore();
|
|
8145
|
+
return await store.hasExactEntry(input);
|
|
8146
|
+
} catch (err) {
|
|
8147
|
+
log.warn(`exact tombstone proof failed (memory=${input.sourceMemoryId}): ${err}`);
|
|
8148
|
+
return false;
|
|
8149
|
+
}
|
|
8150
|
+
}
|
|
7803
8151
|
/**
|
|
7804
8152
|
* Revoke a tombstone (re-allow the content). Used by the review-queue
|
|
7805
8153
|
* approval path when an operator approves a blocked memory.
|
|
@@ -8000,55 +8348,55 @@ var StorageManager3 = class _StorageManager extends TombstoneBlockedCaptureIndex
|
|
|
8000
8348
|
return null;
|
|
8001
8349
|
}
|
|
8002
8350
|
get questionsDir() {
|
|
8003
|
-
return
|
|
8351
|
+
return path11.join(this.baseDir, "questions");
|
|
8004
8352
|
}
|
|
8005
8353
|
get artifactsDir() {
|
|
8006
|
-
return
|
|
8354
|
+
return path11.join(this.baseDir, "artifacts");
|
|
8007
8355
|
}
|
|
8008
8356
|
get identityDir() {
|
|
8009
|
-
return
|
|
8357
|
+
return path11.join(this.baseDir, "identity");
|
|
8010
8358
|
}
|
|
8011
8359
|
get identityAnchorPath() {
|
|
8012
|
-
return
|
|
8360
|
+
return path11.join(this.identityDir, "identity-anchor.md");
|
|
8013
8361
|
}
|
|
8014
8362
|
get identityIncidentsDir() {
|
|
8015
|
-
return
|
|
8363
|
+
return path11.join(this.identityDir, "incidents");
|
|
8016
8364
|
}
|
|
8017
8365
|
get identityAuditsWeeklyDir() {
|
|
8018
|
-
return
|
|
8366
|
+
return path11.join(this.identityDir, "audits", "weekly");
|
|
8019
8367
|
}
|
|
8020
8368
|
get identityAuditsMonthlyDir() {
|
|
8021
|
-
return
|
|
8369
|
+
return path11.join(this.identityDir, "audits", "monthly");
|
|
8022
8370
|
}
|
|
8023
8371
|
get identityImprovementLoopsPath() {
|
|
8024
|
-
return
|
|
8372
|
+
return path11.join(this.identityDir, "improvement-loops.md");
|
|
8025
8373
|
}
|
|
8026
8374
|
get identityReflectionsPath() {
|
|
8027
|
-
return
|
|
8375
|
+
return path11.join(this.identityDir, "reflections.md");
|
|
8028
8376
|
}
|
|
8029
8377
|
get profilePath() {
|
|
8030
|
-
return
|
|
8378
|
+
return path11.join(this.baseDir, "profile.md");
|
|
8031
8379
|
}
|
|
8032
8380
|
get memoryActionsPath() {
|
|
8033
|
-
return
|
|
8381
|
+
return path11.join(this.stateDir, "memory-actions.jsonl");
|
|
8034
8382
|
}
|
|
8035
8383
|
get memoryLifecycleLedgerPath() {
|
|
8036
|
-
return
|
|
8384
|
+
return path11.join(this.stateDir, "memory-lifecycle-ledger.jsonl");
|
|
8037
8385
|
}
|
|
8038
8386
|
get compressionGuidelinesPath() {
|
|
8039
|
-
return
|
|
8387
|
+
return path11.join(this.stateDir, "compression-guidelines.md");
|
|
8040
8388
|
}
|
|
8041
8389
|
get compressionGuidelineDraftPath() {
|
|
8042
|
-
return
|
|
8390
|
+
return path11.join(this.stateDir, "compression-guidelines.draft.md");
|
|
8043
8391
|
}
|
|
8044
8392
|
get compressionGuidelineStatePath() {
|
|
8045
|
-
return
|
|
8393
|
+
return path11.join(this.stateDir, "compression-guideline-state.json");
|
|
8046
8394
|
}
|
|
8047
8395
|
get compressionGuidelineDraftStatePath() {
|
|
8048
|
-
return
|
|
8396
|
+
return path11.join(this.stateDir, "compression-guideline-draft-state.json");
|
|
8049
8397
|
}
|
|
8050
8398
|
get behaviorSignalsPath() {
|
|
8051
|
-
return
|
|
8399
|
+
return path11.join(this.stateDir, "behavior-signals.jsonl");
|
|
8052
8400
|
}
|
|
8053
8401
|
/**
|
|
8054
8402
|
* In-memory dedup key set for behavior-signals appends (issue #1909),
|
|
@@ -8080,7 +8428,7 @@ var StorageManager3 = class _StorageManager extends TombstoneBlockedCaptureIndex
|
|
|
8080
8428
|
* governance sweeps can treat it uniformly.
|
|
8081
8429
|
*/
|
|
8082
8430
|
get bufferSurpriseLedgerPath() {
|
|
8083
|
-
return
|
|
8431
|
+
return path11.join(this.stateDir, "buffer-surprise-ledger.jsonl");
|
|
8084
8432
|
}
|
|
8085
8433
|
/**
|
|
8086
8434
|
* Entity alias table loaded from THIS store's config/aliases.json.
|
|
@@ -8141,7 +8489,7 @@ var StorageManager3 = class _StorageManager extends TombstoneBlockedCaptureIndex
|
|
|
8141
8489
|
this.loadAliasesSync();
|
|
8142
8490
|
}
|
|
8143
8491
|
loadAliasesSync() {
|
|
8144
|
-
const aliasPath =
|
|
8492
|
+
const aliasPath = path11.join(this.baseDir, "config", "aliases.json");
|
|
8145
8493
|
this.userAliases = {};
|
|
8146
8494
|
const raw = readEntityAliasConfigSync(this.baseDir);
|
|
8147
8495
|
if (raw === void 0) return;
|
|
@@ -8176,22 +8524,22 @@ var StorageManager3 = class _StorageManager extends TombstoneBlockedCaptureIndex
|
|
|
8176
8524
|
return completionFingerprint;
|
|
8177
8525
|
}
|
|
8178
8526
|
async ensureDirectories() {
|
|
8179
|
-
await
|
|
8527
|
+
await mkdir2(this.baseDir, { recursive: true });
|
|
8180
8528
|
await validateRoots(this.baseDir, this.entitiesDir, this.stateDir, this.archiveDir);
|
|
8181
8529
|
const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
8182
|
-
await
|
|
8183
|
-
await
|
|
8184
|
-
await
|
|
8185
|
-
await
|
|
8186
|
-
await
|
|
8187
|
-
await
|
|
8188
|
-
await
|
|
8189
|
-
await
|
|
8190
|
-
await
|
|
8191
|
-
await
|
|
8192
|
-
await
|
|
8193
|
-
await
|
|
8194
|
-
await
|
|
8530
|
+
await mkdir2(path11.join(this.factsDir, today), { recursive: true });
|
|
8531
|
+
await mkdir2(path11.join(this.proceduresDir, today), { recursive: true });
|
|
8532
|
+
await mkdir2(path11.join(this.reasoningTracesDir, today), { recursive: true });
|
|
8533
|
+
await mkdir2(this.correctionsDir, { recursive: true });
|
|
8534
|
+
await mkdir2(this.entitiesDir, { recursive: true });
|
|
8535
|
+
await mkdir2(this.stateDir, { recursive: true });
|
|
8536
|
+
await mkdir2(this.questionsDir, { recursive: true });
|
|
8537
|
+
await mkdir2(this.artifactsDir, { recursive: true });
|
|
8538
|
+
await mkdir2(this.identityDir, { recursive: true });
|
|
8539
|
+
await mkdir2(this.identityIncidentsDir, { recursive: true });
|
|
8540
|
+
await mkdir2(this.identityAuditsWeeklyDir, { recursive: true });
|
|
8541
|
+
await mkdir2(this.identityAuditsMonthlyDir, { recursive: true });
|
|
8542
|
+
await mkdir2(path11.join(this.baseDir, "config"), { recursive: true });
|
|
8195
8543
|
await this.entityCanonicalIdMigration.ensure();
|
|
8196
8544
|
if (this.hotMemoriesCacheEnabled && this.getMemoryCorpusVersion() === 0) {
|
|
8197
8545
|
this.bumpMemoryCorpusVersion();
|
|
@@ -8212,12 +8560,12 @@ var StorageManager3 = class _StorageManager extends TombstoneBlockedCaptureIndex
|
|
|
8212
8560
|
*/
|
|
8213
8561
|
async resolveCategoryWritePath(category, id, today) {
|
|
8214
8562
|
if (category === "correction") {
|
|
8215
|
-
await
|
|
8216
|
-
return
|
|
8563
|
+
await mkdir2(this.correctionsDir, { recursive: true });
|
|
8564
|
+
return path11.join(this.correctionsDir, `${id}.md`);
|
|
8217
8565
|
}
|
|
8218
|
-
const datedDir =
|
|
8219
|
-
await
|
|
8220
|
-
return
|
|
8566
|
+
const datedDir = path11.join(getCategoryDir(this.baseDir, category), today);
|
|
8567
|
+
await mkdir2(datedDir, { recursive: true });
|
|
8568
|
+
return path11.join(datedDir, `${id}.md`);
|
|
8221
8569
|
}
|
|
8222
8570
|
async findExistingTombstoneBlockedMemory(content, category, sourceConnector) {
|
|
8223
8571
|
const identity = buildExplicitCaptureDedupKey(content, category, sourceConnector);
|
|
@@ -8379,7 +8727,7 @@ ${sanitized.text}
|
|
|
8379
8727
|
}
|
|
8380
8728
|
});
|
|
8381
8729
|
} catch (err) {
|
|
8382
|
-
await
|
|
8730
|
+
await unlink5(filePath).catch(() => void 0);
|
|
8383
8731
|
this.invalidateAllMemoriesCache();
|
|
8384
8732
|
await this.rebuildTombstoneBlockedCaptureAfterInvalidationForPath(filePath);
|
|
8385
8733
|
throw err;
|
|
@@ -8584,8 +8932,8 @@ ${sanitized.text}
|
|
|
8584
8932
|
await this.ensureDirectories();
|
|
8585
8933
|
const now = /* @__PURE__ */ new Date();
|
|
8586
8934
|
const day = now.toISOString().slice(0, 10);
|
|
8587
|
-
const dir =
|
|
8588
|
-
await
|
|
8935
|
+
const dir = path11.join(this.artifactsDir, day);
|
|
8936
|
+
await mkdir2(dir, { recursive: true });
|
|
8589
8937
|
const id = `artifact-${Date.now()}-${Math.random().toString(36).slice(2, 6)}`;
|
|
8590
8938
|
const fm = {
|
|
8591
8939
|
id,
|
|
@@ -8610,7 +8958,7 @@ ${sanitized.text}
|
|
|
8610
8958
|
log.warn(`artifact content rejected for ${id}; violations=${sanitized.violations.join(", ")}`);
|
|
8611
8959
|
return "";
|
|
8612
8960
|
}
|
|
8613
|
-
const filePath =
|
|
8961
|
+
const filePath = path11.join(dir, `${id}.md`);
|
|
8614
8962
|
await this.writeStorageSecureFile(filePath, `${serializeFrontmatter(fm)}
|
|
8615
8963
|
|
|
8616
8964
|
${sanitized.text}
|
|
@@ -8791,6 +9139,11 @@ ${sanitized.text}
|
|
|
8791
9139
|
clearInFlightReads();
|
|
8792
9140
|
_StorageManager.questionsCache.clear();
|
|
8793
9141
|
_StorageManager.coldMemoriesCache.clear();
|
|
9142
|
+
_StorageManager.artifactWriteVersionByDir.clear();
|
|
9143
|
+
_StorageManager.memoryStatusVersionByDir.clear();
|
|
9144
|
+
_StorageManager.memoryCorpusVersionByDir.clear();
|
|
9145
|
+
_StorageManager.entityMutationVersionByDir.clear();
|
|
9146
|
+
_StorageManager.coldWriteVersionByDir.clear();
|
|
8794
9147
|
clearMemoryCache();
|
|
8795
9148
|
_StorageManager.hotMemoriesCacheDefaultByDir.clear();
|
|
8796
9149
|
_StorageManager.hotMemoriesCacheTtlByDir.clear();
|
|
@@ -8893,7 +9246,7 @@ ${sanitized.text}
|
|
|
8893
9246
|
* per-process in-memory cache safe across process boundaries.
|
|
8894
9247
|
*/
|
|
8895
9248
|
invalidateColdMemoriesCache() {
|
|
8896
|
-
const coldRoot =
|
|
9249
|
+
const coldRoot = path11.join(this.baseDir, "cold");
|
|
8897
9250
|
_StorageManager.coldMemoriesCache.delete(coldRoot);
|
|
8898
9251
|
this.bumpColdWriteVersion();
|
|
8899
9252
|
invalidateAllForDir(this.baseDir);
|
|
@@ -9013,7 +9366,7 @@ ${sanitized.text}
|
|
|
9013
9366
|
const correctionPaths = [];
|
|
9014
9367
|
const factPaths = [];
|
|
9015
9368
|
for (const filePath of filePaths) {
|
|
9016
|
-
if (filePath === this.correctionsDir || filePath.startsWith(`${this.correctionsDir}${
|
|
9369
|
+
if (filePath === this.correctionsDir || filePath.startsWith(`${this.correctionsDir}${path11.sep}`)) {
|
|
9017
9370
|
correctionPaths.push(filePath);
|
|
9018
9371
|
} else {
|
|
9019
9372
|
factPaths.push(filePath);
|
|
@@ -9074,9 +9427,9 @@ ${sanitized.text}
|
|
|
9074
9427
|
const root = this.archiveDir;
|
|
9075
9428
|
const readDir = async (dir) => {
|
|
9076
9429
|
try {
|
|
9077
|
-
const entries = await
|
|
9430
|
+
const entries = await readdir6(dir, { withFileTypes: true });
|
|
9078
9431
|
for (const entry of entries) {
|
|
9079
|
-
const fullPath =
|
|
9432
|
+
const fullPath = path11.join(dir, entry.name);
|
|
9080
9433
|
if (entry.isDirectory()) {
|
|
9081
9434
|
await readDir(fullPath);
|
|
9082
9435
|
} else if (entry.name.endsWith(".md")) {
|
|
@@ -9101,10 +9454,15 @@ ${sanitized.text}
|
|
|
9101
9454
|
}
|
|
9102
9455
|
} catch (err) {
|
|
9103
9456
|
if (err instanceof SecureStoreLockedError) throw err;
|
|
9457
|
+
if (!isErrnoCode(err, "ENOENT")) {
|
|
9458
|
+
log.warn(`skipping unreadable archived memory ${fullPath}: ${err}`);
|
|
9459
|
+
}
|
|
9104
9460
|
}
|
|
9105
9461
|
}
|
|
9106
9462
|
}
|
|
9107
|
-
} catch {
|
|
9463
|
+
} catch (error) {
|
|
9464
|
+
if (error instanceof SecureStoreLockedError) throw error;
|
|
9465
|
+
if (!isErrnoCode(error, "ENOENT")) throw error;
|
|
9108
9466
|
}
|
|
9109
9467
|
};
|
|
9110
9468
|
await readDir(root);
|
|
@@ -9114,7 +9472,7 @@ ${sanitized.text}
|
|
|
9114
9472
|
return this.rememberMemorySnapshot(await this.memoryReadStore.readMemoryByPath(filePath));
|
|
9115
9473
|
}
|
|
9116
9474
|
resolveTierRootDir(tier) {
|
|
9117
|
-
return tier === "cold" ?
|
|
9475
|
+
return tier === "cold" ? path11.join(this.baseDir, "cold") : this.baseDir;
|
|
9118
9476
|
}
|
|
9119
9477
|
resolveMemoryDateDir(memory) {
|
|
9120
9478
|
const preferred = memory.frontmatter.created || memory.frontmatter.updated;
|
|
@@ -9129,13 +9487,13 @@ ${sanitized.text}
|
|
|
9129
9487
|
buildTierMemoryPath(memory, tier) {
|
|
9130
9488
|
const root = this.resolveTierRootDir(tier);
|
|
9131
9489
|
if (this.isArtifactMemory(memory)) {
|
|
9132
|
-
return
|
|
9490
|
+
return path11.join(root, "artifacts", this.resolveMemoryDateDir(memory), `${memory.frontmatter.id}.md`);
|
|
9133
9491
|
}
|
|
9134
9492
|
if (memory.frontmatter.category === "correction") {
|
|
9135
|
-
return
|
|
9493
|
+
return path11.join(root, "corrections", `${memory.frontmatter.id}.md`);
|
|
9136
9494
|
}
|
|
9137
9495
|
const dir = categoryDirName(memory.frontmatter.category);
|
|
9138
|
-
return
|
|
9496
|
+
return path11.join(root, dir, this.resolveMemoryDateDir(memory), `${memory.frontmatter.id}.md`);
|
|
9139
9497
|
}
|
|
9140
9498
|
async writeMemoryFileAtomic(targetPath, memory) {
|
|
9141
9499
|
const refIdsAtWrite = this.currentHistoricalIds();
|
|
@@ -9151,7 +9509,7 @@ ${memory.content}
|
|
|
9151
9509
|
{},
|
|
9152
9510
|
this.baseDir
|
|
9153
9511
|
);
|
|
9154
|
-
const priorDest = await
|
|
9512
|
+
const priorDest = await readFile3(targetPath).catch(() => null);
|
|
9155
9513
|
await persist();
|
|
9156
9514
|
if (typeof rawRef === "string") {
|
|
9157
9515
|
const refBeforeRepair = frontmatter.entityRef;
|
|
@@ -9166,7 +9524,7 @@ ${memory.content}
|
|
|
9166
9524
|
});
|
|
9167
9525
|
} catch (err) {
|
|
9168
9526
|
if (priorDest === null) {
|
|
9169
|
-
await
|
|
9527
|
+
await unlink5(targetPath).catch(() => void 0);
|
|
9170
9528
|
} else {
|
|
9171
9529
|
await writeFile2(targetPath, priorDest).catch(() => void 0);
|
|
9172
9530
|
}
|
|
@@ -9213,8 +9571,8 @@ ${memory.content}
|
|
|
9213
9571
|
memory.frontmatter.entityRef = current.frontmatter.entityRef;
|
|
9214
9572
|
}
|
|
9215
9573
|
durable = true;
|
|
9216
|
-
const sourcePath =
|
|
9217
|
-
const destPath =
|
|
9574
|
+
const sourcePath = path11.resolve(current.path);
|
|
9575
|
+
const destPath = path11.resolve(targetPath);
|
|
9218
9576
|
if (sourcePath !== destPath) {
|
|
9219
9577
|
await recordArchiveDelete2(this.baseDir, current.path, () => this.deleteManagedStorageFile(current.path));
|
|
9220
9578
|
this.invalidateAllMemoriesCache();
|
|
@@ -9238,8 +9596,8 @@ ${memory.content}
|
|
|
9238
9596
|
}
|
|
9239
9597
|
async migrateMemoryToTier(memory, targetTier) {
|
|
9240
9598
|
const targetPath = this.buildTierMemoryPath(memory, targetTier);
|
|
9241
|
-
const sourcePath =
|
|
9242
|
-
const destPath =
|
|
9599
|
+
const sourcePath = path11.resolve(memory.path);
|
|
9600
|
+
const destPath = path11.resolve(targetPath);
|
|
9243
9601
|
if (sourcePath === destPath) {
|
|
9244
9602
|
return { changed: false, targetPath };
|
|
9245
9603
|
}
|
|
@@ -9249,7 +9607,7 @@ ${memory.content}
|
|
|
9249
9607
|
return { changed, targetPath };
|
|
9250
9608
|
}
|
|
9251
9609
|
get archiveDir() {
|
|
9252
|
-
return
|
|
9610
|
+
return path11.join(this.baseDir, "archive");
|
|
9253
9611
|
}
|
|
9254
9612
|
/**
|
|
9255
9613
|
* Archive a memory by moving it from facts/ to archive/YYYY-MM-DD/.
|
|
@@ -9262,8 +9620,8 @@ ${memory.content}
|
|
|
9262
9620
|
try {
|
|
9263
9621
|
const now = lifecycle?.at ?? /* @__PURE__ */ new Date();
|
|
9264
9622
|
const today = now.toISOString().slice(0, 10);
|
|
9265
|
-
const destDir =
|
|
9266
|
-
await
|
|
9623
|
+
const destDir = path11.join(this.archiveDir, today);
|
|
9624
|
+
await mkdir2(destDir, { recursive: true });
|
|
9267
9625
|
const refIdsAtWrite = this.currentHistoricalIds();
|
|
9268
9626
|
const updatedFm = canonicalizeEntityRefOption(
|
|
9269
9627
|
{
|
|
@@ -9278,7 +9636,7 @@ ${memory.content}
|
|
|
9278
9636
|
|
|
9279
9637
|
${current.content}
|
|
9280
9638
|
`;
|
|
9281
|
-
const destPath =
|
|
9639
|
+
const destPath = path11.join(destDir, path11.basename(current.path));
|
|
9282
9640
|
const priorDest = await this.readStorageSecureFile(destPath).catch(() => null);
|
|
9283
9641
|
await recordArchiveWrite(this.baseDir, destPath, () => this.writeStorageSecureFile(destPath, fileContent));
|
|
9284
9642
|
if (typeof current.frontmatter.entityRef === "string") {
|
|
@@ -9295,7 +9653,7 @@ ${current.content}
|
|
|
9295
9653
|
await recordArchiveDelete2(
|
|
9296
9654
|
this.baseDir,
|
|
9297
9655
|
destPath,
|
|
9298
|
-
() =>
|
|
9656
|
+
() => unlink5(destPath).then(
|
|
9299
9657
|
() => true,
|
|
9300
9658
|
() => false
|
|
9301
9659
|
)
|
|
@@ -9388,26 +9746,31 @@ ${current.content}
|
|
|
9388
9746
|
}
|
|
9389
9747
|
return null;
|
|
9390
9748
|
}
|
|
9391
|
-
async invalidateMemory(id) {
|
|
9392
|
-
const
|
|
9393
|
-
|
|
9394
|
-
if (!memory) return false;
|
|
9749
|
+
async invalidateMemory(id, expectedSnapshot, options) {
|
|
9750
|
+
const memory = expectedSnapshot?.path ? await this.readMemoryByPath(expectedSnapshot.path) : (await this.readAllMemories()).find((candidate) => candidate.frontmatter.id === id) ?? null;
|
|
9751
|
+
if (!memory || memory.frontmatter.id !== id) return false;
|
|
9395
9752
|
return await this.runTombstoneBlockedInvalidation(memory, async (current, rebuildMarker, markDurable) => {
|
|
9396
|
-
|
|
9397
|
-
|
|
9398
|
-
|
|
9399
|
-
|
|
9400
|
-
|
|
9401
|
-
|
|
9402
|
-
|
|
9403
|
-
|
|
9404
|
-
|
|
9753
|
+
if (expectedSnapshot && invalidationCommitFingerprint(current) !== invalidationCommitFingerprint(expectedSnapshot)) {
|
|
9754
|
+
return false;
|
|
9755
|
+
}
|
|
9756
|
+
return runCommittedInvalidation({
|
|
9757
|
+
memoryId: id,
|
|
9758
|
+
recordProof: options?.recordCommitProof === true ? () => this.recordCommittedInvalidation(current) : void 0,
|
|
9759
|
+
clearProof: options?.recordCommitProof === true ? () => this.clearCommittedInvalidation(current) : void 0,
|
|
9760
|
+
deleteMemory: () => recordArchiveDelete(this.baseDir, current.path, () => this.deleteManagedStorageFile(current.path)),
|
|
9761
|
+
afterDelete: async () => {
|
|
9762
|
+
markDurable();
|
|
9763
|
+
markProjectedMemoryPathInvalid(this.baseDir, id);
|
|
9764
|
+
this.invalidateAllMemoriesCache();
|
|
9765
|
+
if (this.isColdOrArchiveTierPath(current.path)) this.invalidateColdMemoriesCache();
|
|
9766
|
+
await this.rebuildTombstoneBlockedCaptureAfterInvalidation(rebuildMarker);
|
|
9767
|
+
this.bumpMemoryStatusVersion();
|
|
9768
|
+
log.debug(`invalidated memory ${id}`);
|
|
9769
|
+
}
|
|
9770
|
+
});
|
|
9405
9771
|
});
|
|
9406
9772
|
}
|
|
9407
|
-
async
|
|
9408
|
-
const memories = await this.readAllMemories();
|
|
9409
|
-
const memory = memories.find((m) => m.frontmatter.id === id);
|
|
9410
|
-
if (!memory) return false;
|
|
9773
|
+
async updateMemoryFromCurrent(memory, newContent, options) {
|
|
9411
9774
|
const mergedLineage = [...memory.frontmatter.lineage ?? [], ...options?.lineage ?? []].filter(
|
|
9412
9775
|
(v, i, a) => a.indexOf(v) === i
|
|
9413
9776
|
);
|
|
@@ -9424,7 +9787,7 @@ ${current.content}
|
|
|
9424
9787
|
);
|
|
9425
9788
|
const sanitized = sanitizeMemoryContent(newContent);
|
|
9426
9789
|
if (!sanitized.clean) {
|
|
9427
|
-
log.warn(`updated memory content sanitized for ${id}; violations=${sanitized.violations.join(", ")}`);
|
|
9790
|
+
log.warn(`updated memory content sanitized for ${memory.frontmatter.id}; violations=${sanitized.violations.join(", ")}`);
|
|
9428
9791
|
}
|
|
9429
9792
|
const fileContent = `${serializeFrontmatter(updated)}
|
|
9430
9793
|
|
|
@@ -9432,6 +9795,7 @@ ${sanitized.text}
|
|
|
9432
9795
|
`;
|
|
9433
9796
|
await this.writeTombstoneBlockedUpdate(memory, fileContent, updated, sanitized.text, async () => {
|
|
9434
9797
|
this.invalidateAllMemoriesCache();
|
|
9798
|
+
if (this.isColdOrArchiveTierPath(memory.path)) this.invalidateColdMemoriesCache();
|
|
9435
9799
|
});
|
|
9436
9800
|
if (typeof memory.frontmatter.entityRef === "string") {
|
|
9437
9801
|
await this.entityRefRepair.repair(
|
|
@@ -9445,7 +9809,7 @@ ${sanitized.text}
|
|
|
9445
9809
|
}
|
|
9446
9810
|
await this.patchHotMemoriesCache({ addedPath: memory.path });
|
|
9447
9811
|
await this.appendGeneratedMemoryLifecycleEventFailOpen("storage.updateMemory", {
|
|
9448
|
-
memoryId: id,
|
|
9812
|
+
memoryId: memory.frontmatter.id,
|
|
9449
9813
|
eventType: "updated",
|
|
9450
9814
|
timestamp: updated.updated,
|
|
9451
9815
|
actor: options?.actor ?? "storage.updateMemory",
|
|
@@ -9456,9 +9820,36 @@ ${sanitized.text}
|
|
|
9456
9820
|
...(updated.lineage ?? []).filter(Boolean)
|
|
9457
9821
|
]
|
|
9458
9822
|
});
|
|
9459
|
-
log.debug(`updated memory ${id}`);
|
|
9823
|
+
log.debug(`updated memory ${memory.frontmatter.id}`);
|
|
9460
9824
|
return true;
|
|
9461
9825
|
}
|
|
9826
|
+
async updateMemory(id, newContent, options) {
|
|
9827
|
+
const memory = (await this.readAllMemories()).find((candidate) => candidate.frontmatter.id === id);
|
|
9828
|
+
if (!memory) return false;
|
|
9829
|
+
return await this.updateMemoryFromCurrent(memory, newContent, options);
|
|
9830
|
+
}
|
|
9831
|
+
async updateMemoryIfUnchanged(expected, newContent, options) {
|
|
9832
|
+
const sanitized = sanitizeMemoryContent(newContent);
|
|
9833
|
+
const oldIdentity = buildExplicitCaptureDedupKey(
|
|
9834
|
+
expected.content,
|
|
9835
|
+
expected.frontmatter.category,
|
|
9836
|
+
expected.frontmatter.sourceConnector
|
|
9837
|
+
);
|
|
9838
|
+
const newIdentity = buildExplicitCaptureDedupKey(
|
|
9839
|
+
sanitized.text,
|
|
9840
|
+
expected.frontmatter.category,
|
|
9841
|
+
expected.frontmatter.sourceConnector
|
|
9842
|
+
);
|
|
9843
|
+
return await this.withTombstoneBlockedCaptureWriteLock(
|
|
9844
|
+
async () => {
|
|
9845
|
+
const current = await this.readMemoryByPath(expected.path);
|
|
9846
|
+
if (!current || current.frontmatter.id !== expected.frontmatter.id) return false;
|
|
9847
|
+
if (invalidationCommitFingerprint(current) !== invalidationCommitFingerprint(expected)) return false;
|
|
9848
|
+
return await this.updateMemoryFromCurrent(current, newContent, options);
|
|
9849
|
+
},
|
|
9850
|
+
[buildCapturePathLockIdentity(expected.path), oldIdentity, newIdentity]
|
|
9851
|
+
);
|
|
9852
|
+
}
|
|
9462
9853
|
/**
|
|
9463
9854
|
* Update frontmatter fields without changing memory content.
|
|
9464
9855
|
* Returns false when the memory is not found.
|
|
@@ -9478,7 +9869,7 @@ ${memory.content}
|
|
|
9478
9869
|
`;
|
|
9479
9870
|
await this.writeTombstoneBlockedFrontmatter(memory, fileContent, updated, async () => {
|
|
9480
9871
|
this.invalidateAllMemoriesCache();
|
|
9481
|
-
if (memory.path.includes(`${
|
|
9872
|
+
if (memory.path.includes(`${path11.sep}cold${path11.sep}`)) {
|
|
9482
9873
|
this.invalidateColdMemoriesCache();
|
|
9483
9874
|
}
|
|
9484
9875
|
});
|
|
@@ -9489,7 +9880,7 @@ ${memory.content}
|
|
|
9489
9880
|
});
|
|
9490
9881
|
}
|
|
9491
9882
|
await this.patchHotMemoriesCache({ addedPath: memory.path });
|
|
9492
|
-
if (memory.path.includes(`${
|
|
9883
|
+
if (memory.path.includes(`${path11.sep}cold${path11.sep}`)) {
|
|
9493
9884
|
this.invalidateColdMemoriesCache();
|
|
9494
9885
|
}
|
|
9495
9886
|
try {
|
|
@@ -9524,6 +9915,17 @@ ${memory.content}
|
|
|
9524
9915
|
}
|
|
9525
9916
|
return true;
|
|
9526
9917
|
}
|
|
9918
|
+
/**
|
|
9919
|
+
* Update frontmatter only when the persisted memory still matches the
|
|
9920
|
+
* expected semantic snapshot.
|
|
9921
|
+
*/
|
|
9922
|
+
async writeMemoryFrontmatterIfUnchanged(expected, patch, lifecycle) {
|
|
9923
|
+
return await this.withTombstoneBlockedMemoryPathLock(expected.path, async (current) => {
|
|
9924
|
+
if (!current) return false;
|
|
9925
|
+
if (invalidationCommitFingerprint(current) !== invalidationCommitFingerprint(expected)) return false;
|
|
9926
|
+
return await this.writeMemoryFrontmatter(current, patch, lifecycle);
|
|
9927
|
+
});
|
|
9928
|
+
}
|
|
9527
9929
|
/**
|
|
9528
9930
|
* Update frontmatter by memory ID.
|
|
9529
9931
|
* Prefer writeMemoryFrontmatter(memory, patch) in batch loops to avoid full-corpus rescans.
|
|
@@ -9559,7 +9961,7 @@ ${memory.content}
|
|
|
9559
9961
|
return deleted;
|
|
9560
9962
|
}
|
|
9561
9963
|
async loadBuffer() {
|
|
9562
|
-
const bufferPath =
|
|
9964
|
+
const bufferPath = path11.join(this.stateDir, "buffer.json");
|
|
9563
9965
|
try {
|
|
9564
9966
|
const raw = await this.readStorageSecureFile(bufferPath);
|
|
9565
9967
|
return JSON.parse(raw);
|
|
@@ -9571,11 +9973,11 @@ ${memory.content}
|
|
|
9571
9973
|
}
|
|
9572
9974
|
async saveBuffer(state) {
|
|
9573
9975
|
await this.ensureDirectories();
|
|
9574
|
-
const bufferPath =
|
|
9976
|
+
const bufferPath = path11.join(this.stateDir, "buffer.json");
|
|
9575
9977
|
await this.writeStorageSecureFile(bufferPath, JSON.stringify(state, null, 2));
|
|
9576
9978
|
}
|
|
9577
9979
|
async loadMeta() {
|
|
9578
|
-
const metaPath =
|
|
9980
|
+
const metaPath = path11.join(this.stateDir, "meta.json");
|
|
9579
9981
|
try {
|
|
9580
9982
|
const raw = await this.readStorageSecureFile(metaPath);
|
|
9581
9983
|
const parsed = JSON.parse(raw);
|
|
@@ -9608,7 +10010,7 @@ ${memory.content}
|
|
|
9608
10010
|
}
|
|
9609
10011
|
async saveMeta(state) {
|
|
9610
10012
|
await this.ensureDirectories();
|
|
9611
|
-
const metaPath =
|
|
10013
|
+
const metaPath = path11.join(this.stateDir, "meta.json");
|
|
9612
10014
|
await this.writeStorageSecureFile(metaPath, JSON.stringify(state, null, 2));
|
|
9613
10015
|
}
|
|
9614
10016
|
async appendMemoryActionEvents(events) {
|
|
@@ -9672,7 +10074,7 @@ ${memory.content}
|
|
|
9672
10074
|
},
|
|
9673
10075
|
(payload) => this.appendStorageSecureFile(target, payload),
|
|
9674
10076
|
async () => {
|
|
9675
|
-
await
|
|
10077
|
+
await mkdir2(path11.dirname(target), { recursive: true });
|
|
9676
10078
|
}
|
|
9677
10079
|
);
|
|
9678
10080
|
}
|
|
@@ -9770,7 +10172,7 @@ ${memory.content}
|
|
|
9770
10172
|
async appendReextractJobs(events) {
|
|
9771
10173
|
if (events.length === 0) return 0;
|
|
9772
10174
|
await this.ensureDirectories();
|
|
9773
|
-
const filePath =
|
|
10175
|
+
const filePath = path11.join(this.stateDir, "reextract-jobs.jsonl");
|
|
9774
10176
|
const lines = events.map((event) => JSON.stringify(event)).join("\n") + "\n";
|
|
9775
10177
|
try {
|
|
9776
10178
|
await this.appendStorageSecureFile(filePath, lines);
|
|
@@ -9783,7 +10185,7 @@ ${memory.content}
|
|
|
9783
10185
|
}
|
|
9784
10186
|
async readReextractJobs(limit = 200) {
|
|
9785
10187
|
const safeLimit = Number.isFinite(limit) ? Math.max(1, Math.min(1e3, Math.floor(limit))) : 200;
|
|
9786
|
-
const filePath =
|
|
10188
|
+
const filePath = path11.join(this.stateDir, "reextract-jobs.jsonl");
|
|
9787
10189
|
try {
|
|
9788
10190
|
const raw = await this.readStorageSecureFile(filePath);
|
|
9789
10191
|
const lines = raw.split("\n").filter((line) => line.trim().length > 0);
|
|
@@ -9932,7 +10334,7 @@ ${memory.content}
|
|
|
9932
10334
|
return false;
|
|
9933
10335
|
}
|
|
9934
10336
|
if (draftState.contentHash) {
|
|
9935
|
-
const contentHash =
|
|
10337
|
+
const contentHash = createHash7("sha256").update(draftContent).digest("hex");
|
|
9936
10338
|
if (contentHash !== draftState.contentHash) return false;
|
|
9937
10339
|
}
|
|
9938
10340
|
await this.writeCompressionGuidelines(draftContent);
|
|
@@ -9941,8 +10343,8 @@ ${memory.content}
|
|
|
9941
10343
|
activationState: "active"
|
|
9942
10344
|
});
|
|
9943
10345
|
await Promise.all([
|
|
9944
|
-
|
|
9945
|
-
|
|
10346
|
+
unlink5(this.compressionGuidelineDraftPath).catch(() => void 0),
|
|
10347
|
+
unlink5(this.compressionGuidelineDraftStatePath).catch(() => void 0)
|
|
9946
10348
|
]);
|
|
9947
10349
|
return true;
|
|
9948
10350
|
}
|
|
@@ -9994,7 +10396,7 @@ ${memory.content}
|
|
|
9994
10396
|
return `${prefix}-${ts}-${rand}`;
|
|
9995
10397
|
}
|
|
9996
10398
|
async writeQuestion(question, context, priority) {
|
|
9997
|
-
await
|
|
10399
|
+
await mkdir2(this.questionsDir, { recursive: true });
|
|
9998
10400
|
const id = this.generateId("q");
|
|
9999
10401
|
const frontmatter = {
|
|
10000
10402
|
id,
|
|
@@ -10010,7 +10412,7 @@ ${question}
|
|
|
10010
10412
|
|
|
10011
10413
|
**Context:** ${context}
|
|
10012
10414
|
`;
|
|
10013
|
-
const filePath =
|
|
10415
|
+
const filePath = path11.join(this.questionsDir, `${id}.md`);
|
|
10014
10416
|
await this.writeStorageSecureFile(filePath, content);
|
|
10015
10417
|
log.debug(`wrote question ${id} to ${filePath}`);
|
|
10016
10418
|
this.invalidateQuestionsCache();
|
|
@@ -10028,7 +10430,7 @@ ${question}
|
|
|
10028
10430
|
if (!match) return null;
|
|
10029
10431
|
const frontmatterStr = match[1];
|
|
10030
10432
|
const body = match[2].trim();
|
|
10031
|
-
const id = this.extractFrontmatterValue(frontmatterStr, "id") ??
|
|
10433
|
+
const id = this.extractFrontmatterValue(frontmatterStr, "id") ?? path11.basename(filePath, ".md");
|
|
10032
10434
|
const created = this.extractFrontmatterValue(frontmatterStr, "created") ?? "";
|
|
10033
10435
|
const priority = parseFloat(this.extractFrontmatterValue(frontmatterStr, "priority") ?? "0.5");
|
|
10034
10436
|
const resolved = this.extractFrontmatterValue(frontmatterStr, "resolved") === "true";
|
|
@@ -10062,7 +10464,7 @@ ${question}
|
|
|
10062
10464
|
async readIdentity(workspaceDir, namespace) {
|
|
10063
10465
|
const identityPath = this.identityFilePath(workspaceDir, namespace);
|
|
10064
10466
|
try {
|
|
10065
|
-
return await
|
|
10467
|
+
return await readFile3(identityPath, "utf-8");
|
|
10066
10468
|
} catch {
|
|
10067
10469
|
return "";
|
|
10068
10470
|
}
|
|
@@ -10080,15 +10482,15 @@ ${question}
|
|
|
10080
10482
|
const identityPath = this.identityFilePath(workspaceDir, opts?.namespace);
|
|
10081
10483
|
let existing = "";
|
|
10082
10484
|
try {
|
|
10083
|
-
existing = await
|
|
10485
|
+
existing = await readFile3(identityPath, "utf-8");
|
|
10084
10486
|
} catch {
|
|
10085
10487
|
}
|
|
10086
10488
|
const hygiene = opts?.hygiene;
|
|
10087
|
-
const rotateEnabled = hygiene?.enabled === true && hygiene.rotateEnabled === true && Array.isArray(hygiene.rotatePaths) && hygiene.rotatePaths.includes(
|
|
10489
|
+
const rotateEnabled = hygiene?.enabled === true && hygiene.rotateEnabled === true && Array.isArray(hygiene.rotatePaths) && hygiene.rotatePaths.includes(path11.basename(identityPath));
|
|
10088
10490
|
if (rotateEnabled) {
|
|
10089
10491
|
const maxBytes = hygiene.rotateMaxBytes;
|
|
10090
10492
|
if (existing.length > maxBytes) {
|
|
10091
|
-
const archiveDir =
|
|
10493
|
+
const archiveDir = path11.join(workspaceDir, hygiene.archiveDir);
|
|
10092
10494
|
const { newContent } = await rotateMarkdownFileToArchive({
|
|
10093
10495
|
filePath: identityPath,
|
|
10094
10496
|
archiveDir,
|
|
@@ -10140,7 +10542,7 @@ ${reflection}
|
|
|
10140
10542
|
}
|
|
10141
10543
|
}
|
|
10142
10544
|
async writeIdentityReflections(content) {
|
|
10143
|
-
await
|
|
10545
|
+
await mkdir2(this.identityDir, { recursive: true });
|
|
10144
10546
|
await this.writeStorageSecureFile(this.identityReflectionsPath, content);
|
|
10145
10547
|
}
|
|
10146
10548
|
async appendIdentityReflection(reflection) {
|
|
@@ -10172,7 +10574,7 @@ ${reflection}
|
|
|
10172
10574
|
|
|
10173
10575
|
${reflection}
|
|
10174
10576
|
`;
|
|
10175
|
-
await
|
|
10577
|
+
await mkdir2(this.identityDir, { recursive: true });
|
|
10176
10578
|
await this.writeStorageSecureFile(this.identityReflectionsPath, `${existing.trimEnd()}${section}`);
|
|
10177
10579
|
log.debug(`appended namespace-local reflection to ${this.identityReflectionsPath}`);
|
|
10178
10580
|
}
|
|
@@ -10228,7 +10630,7 @@ ${reflection}
|
|
|
10228
10630
|
const rightTs = latestEntityTimelineTimestamp(right.entity) ?? "";
|
|
10229
10631
|
return compareEntityTimestamps(rightTs, leftTs);
|
|
10230
10632
|
}).map(({ entityName }) => entityName);
|
|
10231
|
-
await
|
|
10633
|
+
await mkdir2(this.stateDir, { recursive: true });
|
|
10232
10634
|
await this.writeStorageSecureFile(
|
|
10233
10635
|
this.entitySynthesisQueuePath,
|
|
10234
10636
|
JSON.stringify(
|
|
@@ -10248,7 +10650,7 @@ ${reflection}
|
|
|
10248
10650
|
if (queue.length === 0) return;
|
|
10249
10651
|
const removals = new Set(entityNames);
|
|
10250
10652
|
const nextQueue = queue.filter((name) => !removals.has(name));
|
|
10251
|
-
await
|
|
10653
|
+
await mkdir2(this.stateDir, { recursive: true });
|
|
10252
10654
|
await this.writeStorageSecureFile(
|
|
10253
10655
|
this.entitySynthesisQueuePath,
|
|
10254
10656
|
JSON.stringify(
|
|
@@ -10348,14 +10750,14 @@ ${reflection}
|
|
|
10348
10750
|
if (entries.length === 0) return 0;
|
|
10349
10751
|
const memories = await this.readAllMemories();
|
|
10350
10752
|
const memoryMap = new Map(memories.map((m) => [m.frontmatter.id, m]));
|
|
10351
|
-
const memoryPathMap = new Map(memories.map((m) => [
|
|
10753
|
+
const memoryPathMap = new Map(memories.map((m) => [path11.resolve(m.path), m]));
|
|
10352
10754
|
let updated = 0;
|
|
10353
10755
|
const prevVersion = this.getMemoryCorpusVersion();
|
|
10354
10756
|
const keyId = this.hotCacheKeyId();
|
|
10355
10757
|
const warm = this.hotMemoriesCacheEnabled && getCachedMemories(this.baseDir, prevVersion, keyId, this.hotCacheTtlMs()) !== null;
|
|
10356
10758
|
const appliedPatches = /* @__PURE__ */ new Map();
|
|
10357
10759
|
for (const entry of entries) {
|
|
10358
|
-
const memory = entry.memoryPath ? memoryPathMap.get(
|
|
10760
|
+
const memory = entry.memoryPath ? memoryPathMap.get(path11.resolve(entry.memoryPath)) : memoryMap.get(entry.memoryId);
|
|
10359
10761
|
if (!memory) continue;
|
|
10360
10762
|
try {
|
|
10361
10763
|
const applied = await this.withTombstoneBlockedMemoryPathLock(memory.path, async (current) => {
|
|
@@ -10386,7 +10788,7 @@ ${current.content}
|
|
|
10386
10788
|
if (warm) {
|
|
10387
10789
|
updateCacheOnWrite(this.baseDir, { ...applied.current, frontmatter: applied.newFm }, keyId);
|
|
10388
10790
|
}
|
|
10389
|
-
appliedPatches.set(
|
|
10791
|
+
appliedPatches.set(path11.resolve(memory.path), {
|
|
10390
10792
|
accessCount: entry.newCount,
|
|
10391
10793
|
lastAccessed: entry.lastAccessed
|
|
10392
10794
|
});
|
|
@@ -10402,7 +10804,7 @@ ${current.content}
|
|
|
10402
10804
|
const cur = getCachedMemories(this.baseDir, prevVersion, keyId);
|
|
10403
10805
|
if (cur) {
|
|
10404
10806
|
const reapplied = cur.map((m) => {
|
|
10405
|
-
const patch = appliedPatches.get(
|
|
10807
|
+
const patch = appliedPatches.get(path11.resolve(m.path));
|
|
10406
10808
|
return patch ? {
|
|
10407
10809
|
...m,
|
|
10408
10810
|
frontmatter: { ...m.frontmatter, accessCount: patch.accessCount, lastAccessed: patch.lastAccessed }
|
|
@@ -10437,7 +10839,7 @@ ${current.content}
|
|
|
10437
10839
|
const pathsById = /* @__PURE__ */ new Map();
|
|
10438
10840
|
const filePathsById = /* @__PURE__ */ new Map();
|
|
10439
10841
|
for (const filePath of filePaths) {
|
|
10440
|
-
const memoryId =
|
|
10842
|
+
const memoryId = path11.basename(filePath, ".md");
|
|
10441
10843
|
if (!wantedIds.has(memoryId)) continue;
|
|
10442
10844
|
const paths = filePathsById.get(memoryId) ?? [];
|
|
10443
10845
|
paths.push(filePath);
|
|
@@ -10449,7 +10851,7 @@ ${current.content}
|
|
|
10449
10851
|
const preferredMatches = [];
|
|
10450
10852
|
for (const preferredPath of preferred) {
|
|
10451
10853
|
const directCandidates = new Set(qmdResultPathCandidates(this.baseDir, preferredPath));
|
|
10452
|
-
const directMatch = existingPaths.find((filePath) => directCandidates.has(
|
|
10854
|
+
const directMatch = existingPaths.find((filePath) => directCandidates.has(path11.resolve(filePath)));
|
|
10453
10855
|
if (directMatch) {
|
|
10454
10856
|
preferredMatches.push(directMatch);
|
|
10455
10857
|
continue;
|
|
@@ -10461,7 +10863,7 @@ ${current.content}
|
|
|
10461
10863
|
candidates.add(candidate);
|
|
10462
10864
|
}
|
|
10463
10865
|
}
|
|
10464
|
-
const match = existingPaths.find((filePath) => candidates.has(
|
|
10866
|
+
const match = existingPaths.find((filePath) => candidates.has(path11.resolve(filePath)));
|
|
10465
10867
|
if (match) preferredMatches.push(match);
|
|
10466
10868
|
}
|
|
10467
10869
|
if (preferredMatches.length > 0) {
|
|
@@ -10621,104 +11023,177 @@ ${sanitized.text}
|
|
|
10621
11023
|
const memories = await this.readAllMemories();
|
|
10622
11024
|
return memories.filter((m) => m.frontmatter.parentId === parentId).sort((a, b) => (a.frontmatter.chunkIndex ?? 0) - (b.frontmatter.chunkIndex ?? 0));
|
|
10623
11025
|
}
|
|
10624
|
-
async supersedeMemory(oldMemoryId, newMemoryId, reason) {
|
|
10625
|
-
const
|
|
10626
|
-
|
|
10627
|
-
|
|
10628
|
-
)
|
|
10629
|
-
|
|
10630
|
-
|
|
10631
|
-
|
|
10632
|
-
|
|
11026
|
+
async supersedeMemory(oldMemoryId, newMemoryId, reason, supersessionMetadata, options = {}) {
|
|
11027
|
+
const matchesSupersession = (memory) => memory.frontmatter.status === "superseded" && memory.frontmatter.supersededBy === newMemoryId && (supersessionMetadata === void 0 || memory.frontmatter.supersessionCause === supersessionMetadata.supersessionCause && memory.frontmatter.invalidatedBy === supersessionMetadata.invalidatedBy);
|
|
11028
|
+
const isExactReplay = (memory) => options.acceptExactReplay === true && matchesSupersession(memory);
|
|
11029
|
+
let oldMemory;
|
|
11030
|
+
if (options.expectedSnapshot?.path) {
|
|
11031
|
+
const selected = await this.readMemoryByPath(options.expectedSnapshot.path);
|
|
11032
|
+
if (!selected || selected.frontmatter.id !== oldMemoryId) {
|
|
11033
|
+
return false;
|
|
11034
|
+
}
|
|
11035
|
+
if (invalidationCommitFingerprint(selected) !== invalidationCommitFingerprint(options.expectedSnapshot) && !isExactReplay(selected)) {
|
|
11036
|
+
return false;
|
|
11037
|
+
}
|
|
11038
|
+
oldMemory = selected;
|
|
11039
|
+
} else {
|
|
11040
|
+
const memories = [
|
|
11041
|
+
...await this.readAllMemories(),
|
|
11042
|
+
...await this.readAllColdMemories(),
|
|
11043
|
+
...await this.readArchivedMemories()
|
|
11044
|
+
];
|
|
11045
|
+
const candidates = memories.filter((memory) => memory.frontmatter.id === oldMemoryId);
|
|
11046
|
+
oldMemory = candidates.find(
|
|
11047
|
+
(memory) => inferMemoryStatus(memory.frontmatter, toMemoryPathRel(this.baseDir, memory.path)) === "active"
|
|
11048
|
+
) ?? candidates.find(isExactReplay) ?? candidates[0];
|
|
11049
|
+
if (oldMemory && options.expectedSnapshot && invalidationCommitFingerprint(oldMemory) !== invalidationCommitFingerprint(options.expectedSnapshot) && !isExactReplay(oldMemory)) {
|
|
11050
|
+
return false;
|
|
11051
|
+
}
|
|
10633
11052
|
}
|
|
10634
11053
|
if (!oldMemory) return false;
|
|
10635
|
-
const
|
|
11054
|
+
const requireActiveAtWrite = options.requireActive === true || inferMemoryStatus(oldMemory.frontmatter, toMemoryPathRel(this.baseDir, oldMemory.path)) === "active";
|
|
11055
|
+
const operationId = createHash7("sha256").update(
|
|
11056
|
+
JSON.stringify({
|
|
11057
|
+
oldMemoryId,
|
|
11058
|
+
newMemoryId,
|
|
11059
|
+
reason,
|
|
11060
|
+
supersessionMetadata: supersessionMetadata ?? null
|
|
11061
|
+
})
|
|
11062
|
+
).digest("hex");
|
|
11063
|
+
const initialNow = (/* @__PURE__ */ new Date()).toISOString();
|
|
10636
11064
|
let currentBefore = oldMemory;
|
|
10637
11065
|
let updatedFm = oldMemory.frontmatter;
|
|
10638
|
-
|
|
10639
|
-
|
|
10640
|
-
|
|
10641
|
-
return false;
|
|
10642
|
-
|
|
10643
|
-
|
|
10644
|
-
|
|
10645
|
-
|
|
10646
|
-
|
|
10647
|
-
|
|
10648
|
-
|
|
10649
|
-
|
|
11066
|
+
let exactReplay = false;
|
|
11067
|
+
try {
|
|
11068
|
+
const written = await this.withTombstoneBlockedMemoryPathLock(oldMemory.path, async (current) => {
|
|
11069
|
+
if (current?.frontmatter.id !== oldMemoryId) return false;
|
|
11070
|
+
if (matchesSupersession(current)) {
|
|
11071
|
+
currentBefore = current;
|
|
11072
|
+
updatedFm = current.frontmatter;
|
|
11073
|
+
return options.acceptExactReplay === true ? "exact-replay" : false;
|
|
11074
|
+
}
|
|
11075
|
+
if (options.expectedSnapshot && invalidationCommitFingerprint(current) !== invalidationCommitFingerprint(options.expectedSnapshot)) {
|
|
11076
|
+
return false;
|
|
11077
|
+
}
|
|
11078
|
+
if (current.frontmatter.supersededBy !== void 0 || requireActiveAtWrite && inferMemoryStatus(current.frontmatter, toMemoryPathRel(this.baseDir, current.path)) !== "active") {
|
|
11079
|
+
return false;
|
|
11080
|
+
}
|
|
11081
|
+
currentBefore = current;
|
|
11082
|
+
const refIdsAtWrite = this.currentHistoricalIds();
|
|
11083
|
+
updatedFm = canonicalizeEntityRefOption(
|
|
11084
|
+
{
|
|
11085
|
+
...current.frontmatter,
|
|
11086
|
+
...supersessionMetadata,
|
|
11087
|
+
status: "superseded",
|
|
11088
|
+
supersededBy: newMemoryId,
|
|
11089
|
+
supersededAt: initialNow,
|
|
11090
|
+
updated: initialNow
|
|
11091
|
+
},
|
|
11092
|
+
refIdsAtWrite
|
|
11093
|
+
);
|
|
11094
|
+
const fileContent = `${serializeFrontmatter(updatedFm)}
|
|
10650
11095
|
|
|
10651
11096
|
${current.content}
|
|
10652
11097
|
`;
|
|
10653
|
-
|
|
10654
|
-
|
|
11098
|
+
await this.writeTombstoneBlockedFrontmatter(current, fileContent, updatedFm);
|
|
11099
|
+
if (this.isColdOrArchiveTierPath(current.path)) this.invalidateColdMemoriesCache();
|
|
11100
|
+
if (typeof current.frontmatter.entityRef === "string") {
|
|
11101
|
+
await this.entityRefRepair.repair(
|
|
11102
|
+
current.path,
|
|
11103
|
+
updatedFm,
|
|
11104
|
+
current.frontmatter.entityRef,
|
|
11105
|
+
refIdsAtWrite,
|
|
11106
|
+
current.content,
|
|
11107
|
+
{ onFailRestore: current }
|
|
11108
|
+
);
|
|
11109
|
+
}
|
|
11110
|
+
return true;
|
|
11111
|
+
});
|
|
11112
|
+
if (!written) return false;
|
|
11113
|
+
exactReplay = written === "exact-replay";
|
|
11114
|
+
if (exactReplay && typeof updatedFm.entityRef === "string") {
|
|
10655
11115
|
await this.entityRefRepair.repair(
|
|
10656
|
-
|
|
11116
|
+
currentBefore.path,
|
|
10657
11117
|
updatedFm,
|
|
10658
|
-
|
|
10659
|
-
|
|
10660
|
-
|
|
10661
|
-
{ onFailRestore:
|
|
11118
|
+
updatedFm.entityRef,
|
|
11119
|
+
this.currentHistoricalIds(),
|
|
11120
|
+
currentBefore.content,
|
|
11121
|
+
{ onFailRestore: currentBefore }
|
|
10662
11122
|
);
|
|
10663
11123
|
}
|
|
10664
|
-
|
|
10665
|
-
|
|
11124
|
+
const supersededAt = updatedFm.supersededAt ?? initialNow;
|
|
11125
|
+
const beforeState = this.summarizeLifecycleState(
|
|
11126
|
+
currentBefore.frontmatter,
|
|
11127
|
+
currentBefore.path
|
|
11128
|
+
);
|
|
11129
|
+
const afterState = this.summarizeLifecycleState(updatedFm, currentBefore.path);
|
|
11130
|
+
const sideEffectsComplete = await runSupersessionSideEffects({
|
|
11131
|
+
oldMemoryId,
|
|
11132
|
+
newMemoryId,
|
|
11133
|
+
reason,
|
|
11134
|
+
now: supersededAt,
|
|
11135
|
+
operationId,
|
|
11136
|
+
exactReplay,
|
|
11137
|
+
currentBefore,
|
|
11138
|
+
updatedFm,
|
|
11139
|
+
citationTemplate: this.citationTemplate,
|
|
11140
|
+
correctionsDir: this.correctionsDir,
|
|
11141
|
+
readMemoryByPath: (filePath) => this.readMemoryByPath(filePath),
|
|
11142
|
+
isColdOrArchiveTierPath: (memoryPath) => this.isColdOrArchiveTierPath(memoryPath),
|
|
11143
|
+
invalidateColdMemoriesCache: () => this.invalidateColdMemoriesCache(),
|
|
11144
|
+
invalidateAllMemoriesCache: () => this.invalidateAllMemoriesCache(),
|
|
11145
|
+
bumpMemoryCorpusVersion: () => this.bumpMemoryCorpusVersion(),
|
|
11146
|
+
appendLifecycleEvent: async () => {
|
|
11147
|
+
let lifecycleEvents = [];
|
|
11148
|
+
try {
|
|
11149
|
+
lifecycleEvents = await this.getMemoryTimeline(oldMemoryId, 200);
|
|
11150
|
+
} catch (error) {
|
|
11151
|
+
log.warn(`supersession lifecycle replay check failed for ${oldMemoryId}: ${error}`);
|
|
11152
|
+
}
|
|
11153
|
+
if (lifecycleEvents.some(
|
|
11154
|
+
(event) => event.memoryId === oldMemoryId && event.eventType === "superseded" && (event.correlationId === operationId || event.correlationId === void 0 && event.reasonCode === reason && event.timestamp === supersededAt && event.relatedMemoryIds?.includes(newMemoryId))
|
|
11155
|
+
)) {
|
|
11156
|
+
return;
|
|
11157
|
+
}
|
|
11158
|
+
await this.appendGeneratedMemoryLifecycleEventFailOpen("storage.supersedeMemory", {
|
|
11159
|
+
memoryId: oldMemoryId,
|
|
11160
|
+
eventType: "superseded",
|
|
11161
|
+
timestamp: supersededAt,
|
|
11162
|
+
actor: "storage.supersedeMemory",
|
|
11163
|
+
reasonCode: reason,
|
|
11164
|
+
before: exactReplay ? { ...beforeState, status: "active" } : beforeState,
|
|
11165
|
+
after: afterState,
|
|
11166
|
+
relatedMemoryIds: [newMemoryId],
|
|
11167
|
+
correlationId: operationId
|
|
11168
|
+
});
|
|
11169
|
+
},
|
|
11170
|
+
bumpMemoryStatusVersion: () => this.bumpMemoryStatusVersion(),
|
|
11171
|
+
hasExactTombstone: (input) => this.hasExactTombstone(input),
|
|
11172
|
+
appendTombstone: (input) => this.appendTombstone(input),
|
|
11173
|
+
writeSealedMemory: (envelope, extras) => this.writeSealedMemory(envelope, extras)
|
|
11174
|
+
});
|
|
11175
|
+
return sideEffectsComplete || options.acceptExactReplay !== true;
|
|
11176
|
+
} catch (err) {
|
|
10666
11177
|
log.error(`failed to supersede memory ${oldMemoryId}:`, err);
|
|
10667
11178
|
return false;
|
|
10668
|
-
}
|
|
10669
|
-
if (!written) return false;
|
|
10670
|
-
await runSupersessionSideEffects({
|
|
10671
|
-
oldMemoryId,
|
|
10672
|
-
newMemoryId,
|
|
10673
|
-
reason,
|
|
10674
|
-
now,
|
|
10675
|
-
currentBefore,
|
|
10676
|
-
updatedFm,
|
|
10677
|
-
citationTemplate: this.citationTemplate,
|
|
10678
|
-
isColdOrArchiveTierPath: (memoryPath) => this.isColdOrArchiveTierPath(memoryPath),
|
|
10679
|
-
invalidateColdMemoriesCache: () => this.invalidateColdMemoriesCache(),
|
|
10680
|
-
bumpMemoryCorpusVersion: () => this.bumpMemoryCorpusVersion(),
|
|
10681
|
-
appendLifecycleEvent: () => this.appendGeneratedMemoryLifecycleEventFailOpen("storage.supersedeMemory", {
|
|
10682
|
-
memoryId: oldMemoryId,
|
|
10683
|
-
eventType: "superseded",
|
|
10684
|
-
timestamp: now,
|
|
10685
|
-
actor: "storage.supersedeMemory",
|
|
10686
|
-
reasonCode: reason,
|
|
10687
|
-
before: this.summarizeLifecycleState(currentBefore.frontmatter, currentBefore.path),
|
|
10688
|
-
after: this.summarizeLifecycleState(updatedFm, currentBefore.path),
|
|
10689
|
-
relatedMemoryIds: [newMemoryId]
|
|
10690
|
-
}),
|
|
10691
|
-
bumpMemoryStatusVersion: () => this.bumpMemoryStatusVersion(),
|
|
10692
|
-
appendTombstone: (input) => this.appendTombstone(input),
|
|
10693
|
-
writeSealedMemory: (envelope, extras) => this.writeSealedMemory(envelope, extras)
|
|
10694
|
-
});
|
|
10695
|
-
return true;
|
|
11179
|
+
}
|
|
10696
11180
|
}
|
|
10697
|
-
// ---------------------------------------------------------------------------
|
|
10698
|
-
// Memory Summarization (Phase 4A)
|
|
10699
|
-
// ---------------------------------------------------------------------------
|
|
10700
11181
|
get summariesDir() {
|
|
10701
|
-
return
|
|
11182
|
+
return path11.join(this.baseDir, "summaries");
|
|
10702
11183
|
}
|
|
10703
|
-
/**
|
|
10704
|
-
* Write a memory summary.
|
|
10705
|
-
*/
|
|
10706
11184
|
async writeSummary(summary) {
|
|
10707
|
-
await
|
|
10708
|
-
const filePath =
|
|
11185
|
+
await mkdir2(this.summariesDir, { recursive: true });
|
|
11186
|
+
const filePath = path11.join(this.summariesDir, `${summary.id}.json`);
|
|
10709
11187
|
await this.writeStorageSecureFile(filePath, JSON.stringify(summary, null, 2));
|
|
10710
11188
|
log.debug(`wrote summary ${summary.id}`);
|
|
10711
11189
|
}
|
|
10712
|
-
/**
|
|
10713
|
-
* Get all summaries.
|
|
10714
|
-
*/
|
|
10715
11190
|
async readSummaries() {
|
|
10716
11191
|
try {
|
|
10717
|
-
const files = await
|
|
11192
|
+
const files = await readdir6(this.summariesDir);
|
|
10718
11193
|
const summaries = [];
|
|
10719
11194
|
for (const file of files) {
|
|
10720
11195
|
if (!file.endsWith(".json")) continue;
|
|
10721
|
-
const filePath =
|
|
11196
|
+
const filePath = path11.join(this.summariesDir, file);
|
|
10722
11197
|
const raw = await this.readStorageSecureFile(filePath);
|
|
10723
11198
|
summaries.push(JSON.parse(raw));
|
|
10724
11199
|
}
|
|
@@ -10729,9 +11204,6 @@ ${current.content}
|
|
|
10729
11204
|
return [];
|
|
10730
11205
|
}
|
|
10731
11206
|
}
|
|
10732
|
-
/**
|
|
10733
|
-
* Archive memories (mark as archived, not delete).
|
|
10734
|
-
*/
|
|
10735
11207
|
async archiveMemories(memoryIds, summaryId) {
|
|
10736
11208
|
const memories = await this.readAllMemories();
|
|
10737
11209
|
const memoryMap = new Map(memories.map((m) => [m.frontmatter.id, m]));
|
|
@@ -10790,26 +11262,17 @@ ${current.content}
|
|
|
10790
11262
|
}
|
|
10791
11263
|
return archived;
|
|
10792
11264
|
}
|
|
10793
|
-
// ---------------------------------------------------------------------------
|
|
10794
|
-
// Topic Extraction (Phase 4B)
|
|
10795
|
-
// ---------------------------------------------------------------------------
|
|
10796
|
-
/**
|
|
10797
|
-
* Save topic scores to meta.json.
|
|
10798
|
-
*/
|
|
10799
11265
|
async saveTopics(topics) {
|
|
10800
|
-
const metaPath =
|
|
10801
|
-
await
|
|
11266
|
+
const metaPath = path11.join(this.stateDir, "topics.json");
|
|
11267
|
+
await mkdir2(this.stateDir, { recursive: true });
|
|
10802
11268
|
await this.writeStorageSecureFile(
|
|
10803
11269
|
metaPath,
|
|
10804
11270
|
JSON.stringify({ topics, updatedAt: (/* @__PURE__ */ new Date()).toISOString() }, null, 2)
|
|
10805
11271
|
);
|
|
10806
11272
|
log.debug(`saved ${topics.length} topic scores`);
|
|
10807
11273
|
}
|
|
10808
|
-
/**
|
|
10809
|
-
* Load topic scores from meta.json.
|
|
10810
|
-
*/
|
|
10811
11274
|
async loadTopics() {
|
|
10812
|
-
const metaPath =
|
|
11275
|
+
const metaPath = path11.join(this.stateDir, "topics.json");
|
|
10813
11276
|
try {
|
|
10814
11277
|
const raw = await this.readStorageSecureFile(metaPath);
|
|
10815
11278
|
return JSON.parse(raw);
|
|
@@ -10819,9 +11282,6 @@ ${current.content}
|
|
|
10819
11282
|
return { topics: [], updatedAt: null };
|
|
10820
11283
|
}
|
|
10821
11284
|
}
|
|
10822
|
-
/**
|
|
10823
|
-
* Add links to an existing memory.
|
|
10824
|
-
*/
|
|
10825
11285
|
async addLinksToMemory(memoryId, links, lifecycle) {
|
|
10826
11286
|
const memories = await this.readAllMemories();
|
|
10827
11287
|
const memory = memories.find((m) => m.frontmatter.id === memoryId);
|
|
@@ -10927,4 +11387,4 @@ export {
|
|
|
10927
11387
|
isValidBufferSurpriseEvent,
|
|
10928
11388
|
StorageManager3 as StorageManager
|
|
10929
11389
|
};
|
|
10930
|
-
//# sourceMappingURL=chunk-
|
|
11390
|
+
//# sourceMappingURL=chunk-WD5B5AIE.js.map
|