@remnic/core 9.51.1 → 9.52.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (234) hide show
  1. package/dist/access-admin-ops-surface.d.ts +6 -5
  2. package/dist/access-admin-ops-surface.js +16 -16
  3. package/dist/access-authorization-probe.d.ts +6 -5
  4. package/dist/access-authorization-probe.js +17 -17
  5. package/dist/access-boundary.d.ts +6 -5
  6. package/dist/access-boundary.js +16 -16
  7. package/dist/access-cli.js +39 -39
  8. package/dist/access-extraction-force-flush.d.ts +6 -5
  9. package/dist/access-extraction-force-flush.js +16 -16
  10. package/dist/access-http-lcm-compaction.d.ts +6 -5
  11. package/dist/access-http-lifecycle-flush.d.ts +6 -5
  12. package/dist/access-http-offline-stream.d.ts +6 -5
  13. package/dist/access-http-query.js +16 -16
  14. package/dist/access-http.d.ts +6 -5
  15. package/dist/access-http.js +22 -22
  16. package/dist/access-identity-continuity-surface.d.ts +5 -4
  17. package/dist/access-identity-continuity-surface.js +16 -16
  18. package/dist/access-lcm-surface.d.ts +6 -5
  19. package/dist/access-lcm-surface.js +16 -16
  20. package/dist/access-mcp.d.ts +6 -5
  21. package/dist/access-mcp.js +20 -20
  22. package/dist/access-namespace-preflight.js +16 -16
  23. package/dist/access-observe-write-surface.d.ts +6 -5
  24. package/dist/access-observe-write-surface.js +16 -16
  25. package/dist/access-offline-manifest.d.ts +5 -4
  26. package/dist/access-operations-batch.js +17 -17
  27. package/dist/access-operations.d.ts +9 -8
  28. package/dist/access-operations.js +19 -19
  29. package/dist/access-recall-concurrency.d.ts +6 -5
  30. package/dist/access-recall-concurrency.js +16 -16
  31. package/dist/access-recall-response.d.ts +6 -5
  32. package/dist/access-recall-response.js +16 -16
  33. package/dist/access-recall-surface.d.ts +6 -5
  34. package/dist/access-recall-surface.js +16 -16
  35. package/dist/access-schema.d.ts +88 -88
  36. package/dist/{access-service-BY0KJ44c.d.ts → access-service-Cbk3bNbD.d.ts} +3 -3
  37. package/dist/access-service-helpers.d.ts +6 -5
  38. package/dist/access-service.d.ts +6 -5
  39. package/dist/access-service.js +16 -16
  40. package/dist/access-surface-catalog.d.ts +6 -5
  41. package/dist/access-wearables-meetings-surface.d.ts +2 -2
  42. package/dist/bootstrap.d.ts +5 -4
  43. package/dist/briefing.d.ts +1 -1
  44. package/dist/briefing.js +7 -7
  45. package/dist/buffer.d.ts +1 -1
  46. package/dist/{capsule-merge-XMFBXVDP.js → capsule-merge-HCJJMLPS.js} +4 -4
  47. package/dist/causal-consolidation.js +8 -8
  48. package/dist/{chunk-PWN32TAG.js → chunk-2ASAS3CQ.js} +2 -2
  49. package/dist/{chunk-WQM326GS.js → chunk-2LY6GIIE.js} +2 -2
  50. package/dist/{chunk-JL43BVDE.js → chunk-2OIQDTDC.js} +2 -2
  51. package/dist/{chunk-33SV3E43.js → chunk-3OY7KXHM.js} +3 -3
  52. package/dist/{chunk-AIUBBHSF.js → chunk-5UZ6UCOZ.js} +3 -3
  53. package/dist/chunk-5WITETWU.js +591 -0
  54. package/dist/chunk-5WITETWU.js.map +1 -0
  55. package/dist/{chunk-UT5BV5OG.js → chunk-7HXZJAID.js} +2 -2
  56. package/dist/{chunk-M7BXDKPR.js → chunk-7M6NYQWB.js} +3 -3
  57. package/dist/{chunk-TPTECFWR.js → chunk-B6LZ4IBH.js} +3 -3
  58. package/dist/{chunk-J5LLX6IL.js → chunk-BF6SXNQJ.js} +10 -10
  59. package/dist/{chunk-4PC4PRJE.js → chunk-BKIFTXHR.js} +2 -2
  60. package/dist/{chunk-VVG2BOKA.js → chunk-BMEYMKWX.js} +2 -2
  61. package/dist/{chunk-DCZBSE4F.js → chunk-D5YOZ66N.js} +18 -18
  62. package/dist/{chunk-BHXYAVUM.js → chunk-DLG5V6ND.js} +2 -2
  63. package/dist/{chunk-52UKFTUK.js → chunk-F4EU6VUC.js} +2 -2
  64. package/dist/{chunk-M4A44LUA.js → chunk-GSTIBXBW.js} +2 -2
  65. package/dist/{chunk-W5XVQFLM.js → chunk-GXFSQ6SD.js} +2 -2
  66. package/dist/{chunk-XADV5IZ3.js → chunk-HDIUYMSB.js} +5 -5
  67. package/dist/{chunk-FXTYIA46.js → chunk-J272WKX2.js} +2 -2
  68. package/dist/{chunk-ENTCVTQR.js → chunk-JJRVPWFY.js} +11 -1
  69. package/dist/chunk-JJRVPWFY.js.map +1 -0
  70. package/dist/{chunk-JYL3BVVZ.js → chunk-JSEJH2YQ.js} +2 -2
  71. package/dist/{chunk-5UDMFE3Q.js → chunk-MLXZD4WM.js} +2 -2
  72. package/dist/{chunk-PUSLMZBQ.js → chunk-NOEMGE56.js} +2 -2
  73. package/dist/{chunk-C5QLI33R.js → chunk-QINJITDS.js} +2 -2
  74. package/dist/{chunk-45BRYON6.js → chunk-QPOHMO6M.js} +2 -2
  75. package/dist/{chunk-ZQOCRIGA.js → chunk-QYN5PXDZ.js} +3 -3
  76. package/dist/{chunk-PA45XCUD.js → chunk-RANKJI5T.js} +2 -2
  77. package/dist/{chunk-ZHY5TMJG.js → chunk-RMHMUUBW.js} +2392 -628
  78. package/dist/chunk-RMHMUUBW.js.map +1 -0
  79. package/dist/{chunk-37ZO6LLJ.js → chunk-RYLEDKQ2.js} +2 -2
  80. package/dist/{chunk-CV6BBVYV.js → chunk-TEMSWHQD.js} +2 -2
  81. package/dist/{chunk-QGR6OVGT.js → chunk-VFSAWRMZ.js} +2 -2
  82. package/dist/{chunk-NLK2VZYZ.js → chunk-VXCNQYTC.js} +2 -2
  83. package/dist/{chunk-AZ2QBGYB.js → chunk-W7Y5JDVB.js} +1 -1
  84. package/dist/{chunk-VBHPOWBJ.js → chunk-WD5B5AIE.js} +1051 -591
  85. package/dist/chunk-WD5B5AIE.js.map +1 -0
  86. package/dist/{chunk-OYESWJ43.js → chunk-WLRD2TXF.js} +15 -10
  87. package/dist/chunk-WLRD2TXF.js.map +1 -0
  88. package/dist/{chunk-4F6X3RXW.js → chunk-X57M2Q4I.js} +2 -2
  89. package/dist/{chunk-6MTSFX7A.js → chunk-X7XEDOSL.js} +8 -8
  90. package/dist/{chunk-UEVLYVNW.js → chunk-YXKVLYXU.js} +2 -2
  91. package/dist/{chunk-OQAL6SIR.js → chunk-ZMSZVISA.js} +2 -2
  92. package/dist/{cli-CnOkH9hA.d.ts → cli-DMAFI0qt.d.ts} +3 -3
  93. package/dist/cli.d.ts +7 -6
  94. package/dist/cli.js +33 -33
  95. package/dist/compounding/engine.d.ts +1 -1
  96. package/dist/compounding/engine.js +7 -7
  97. package/dist/connectors/codex-materialize-runner.js +7 -7
  98. package/dist/connectors/index.js +7 -7
  99. package/dist/consolidation-provenance-check.d.ts +1 -1
  100. package/dist/consolidation-provenance-check.js +4 -4
  101. package/dist/consolidation-undo.d.ts +1 -1
  102. package/dist/consolidation-undo.js +3 -3
  103. package/dist/contradiction/index.d.ts +1 -1
  104. package/dist/convergence-refresh.d.ts +1 -1
  105. package/dist/convergence-refresh.js +9 -9
  106. package/dist/corpus-watermark.js +9 -9
  107. package/dist/dependency-propagation-delivery-D63r24pH.d.ts +41 -0
  108. package/dist/entity-retrieval.d.ts +1 -1
  109. package/dist/entity-retrieval.js +7 -7
  110. package/dist/explicit-capture.d.ts +5 -4
  111. package/dist/explicit-capture.js +4 -4
  112. package/dist/external-wiki-access.d.ts +6 -5
  113. package/dist/external-wiki-access.js +17 -17
  114. package/dist/external-wiki-mcp-tools.d.ts +6 -5
  115. package/dist/extraction.js +7 -7
  116. package/dist/{forget-UJDJHG6R.js → forget-HDS4N6CG.js} +3 -3
  117. package/dist/harmonic-retrieval.js +1 -1
  118. package/dist/index.d.ts +687 -686
  119. package/dist/index.js +39 -39
  120. package/dist/maintenance/memory-governance.js +7 -7
  121. package/dist/maintenance/rebuild-memory-lifecycle-ledger.d.ts +1 -1
  122. package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +7 -7
  123. package/dist/maintenance/rebuild-memory-projection.d.ts +1 -1
  124. package/dist/maintenance/rebuild-memory-projection.js +8 -8
  125. package/dist/{maintenance-CKwxpdRx.d.ts → maintenance-kBII2qUI.d.ts} +3 -2
  126. package/dist/mcp-memory-inspector-app.d.ts +6 -5
  127. package/dist/memory-worth-outcomes.d.ts +1 -1
  128. package/dist/namespaces/migrate.d.ts +1 -1
  129. package/dist/namespaces/migrate.js +8 -8
  130. package/dist/namespaces/storage.d.ts +1 -1
  131. package/dist/namespaces/storage.js +7 -7
  132. package/dist/operator-doctor-corpus.js +10 -10
  133. package/dist/operator-toolkit.d.ts +1 -1
  134. package/dist/operator-toolkit.js +15 -15
  135. package/dist/orchestration/compression-guideline-coordinator.d.ts +1 -1
  136. package/dist/orchestration/maintenance.d.ts +2 -2
  137. package/dist/orchestration/maintenance.js +12 -12
  138. package/dist/{orchestrator-BshGam-o.d.ts → orchestrator-DHy5LtTy.d.ts} +20 -35
  139. package/dist/orchestrator.d.ts +5 -4
  140. package/dist/orchestrator.js +39 -39
  141. package/dist/page-versioning.js +3 -3
  142. package/dist/schemas.d.ts +138 -138
  143. package/dist/semantic-consolidation.js +8 -8
  144. package/dist/semantic-rule-promotion.js +7 -7
  145. package/dist/semantic-rule-verifier.js +7 -7
  146. package/dist/{service-BIgVM7fL.d.ts → service-PT2_z7xm.d.ts} +1 -1
  147. package/dist/shared-context/manager.d.ts +8 -8
  148. package/dist/source-agent-qualifier.js +7 -7
  149. package/dist/{storage-CVfGWqCI.d.ts → storage-BmVudhk4.d.ts} +46 -27
  150. package/dist/storage.d.ts +1 -1
  151. package/dist/storage.js +6 -6
  152. package/dist/temporal-supersession.d.ts +51 -26
  153. package/dist/temporal-supersession.js +3 -1
  154. package/dist/tier-migration.d.ts +1 -1
  155. package/dist/transfer/capsule-import.js +4 -4
  156. package/dist/transfer/types.d.ts +66 -66
  157. package/dist/verified-recall.js +7 -7
  158. package/package.json +2 -2
  159. package/src/lifecycle/tombstones.ts +30 -0
  160. package/src/orchestration/consolidation-run.ts +324 -106
  161. package/src/orchestration/contradiction-linking-coordinator.ts +237 -64
  162. package/src/orchestration/dependency-propagation-delivery.test.ts +2353 -0
  163. package/src/orchestration/dependency-propagation-delivery.ts +1143 -0
  164. package/src/orchestration/dependency-propagation-queue-state.ts +220 -0
  165. package/src/orchestration/dependency-propagation-recovery-replay.ts +237 -0
  166. package/src/orchestration/dependency-propagation-replay-reconciliation.ts +73 -0
  167. package/src/orchestration/dependency-propagation-triggers.test.ts +1069 -11
  168. package/src/orchestration/dependency-propagation.test.ts +82 -31
  169. package/src/orchestration/dependency-propagation.ts +55 -17
  170. package/src/orchestration/extraction-persist-deps.ts +2 -2
  171. package/src/orchestration/extraction-persist.ts +14 -15
  172. package/src/orchestration/extraction-queue-coordinator.ts +23 -1
  173. package/src/orchestration/extraction-shutdown.ts +29 -0
  174. package/src/orchestration/maintenance.ts +13 -7
  175. package/src/orchestration/turn-ingestion.ts +5 -1
  176. package/src/orchestrator-background-writes.test.ts +265 -1
  177. package/src/orchestrator-extraction-queue.test.ts +39 -0
  178. package/src/orchestrator.ts +59 -58
  179. package/src/storage/committed-invalidation.ts +34 -0
  180. package/src/storage/deletion-revision-store.ts +529 -0
  181. package/src/storage/supersession-audit.ts +41 -0
  182. package/src/storage/supersession-side-effects.ts +52 -25
  183. package/src/storage-contract/failure-semantics.test.ts +12 -0
  184. package/src/storage-contract/round-trip.test.ts +184 -2
  185. package/src/storage-deletion-revisions.test.ts +213 -1
  186. package/src/storage-frontmatter-cas.test.ts +141 -0
  187. package/src/storage-memory-lifecycle-ledger.test.ts +372 -0
  188. package/src/storage-replay.test.ts +149 -0
  189. package/src/storage.ts +345 -358
  190. package/src/temporal-supersession.test.ts +431 -2
  191. package/src/temporal-supersession.ts +567 -107
  192. package/src/testing/subjects/dependency-propagation.test.ts +179 -38
  193. package/dist/chunk-ENTCVTQR.js.map +0 -1
  194. package/dist/chunk-EWMTOVMY.js +0 -484
  195. package/dist/chunk-EWMTOVMY.js.map +0 -1
  196. package/dist/chunk-OYESWJ43.js.map +0 -1
  197. package/dist/chunk-VBHPOWBJ.js.map +0 -1
  198. package/dist/chunk-ZHY5TMJG.js.map +0 -1
  199. /package/dist/{capsule-merge-XMFBXVDP.js.map → capsule-merge-HCJJMLPS.js.map} +0 -0
  200. /package/dist/{chunk-PWN32TAG.js.map → chunk-2ASAS3CQ.js.map} +0 -0
  201. /package/dist/{chunk-WQM326GS.js.map → chunk-2LY6GIIE.js.map} +0 -0
  202. /package/dist/{chunk-JL43BVDE.js.map → chunk-2OIQDTDC.js.map} +0 -0
  203. /package/dist/{chunk-33SV3E43.js.map → chunk-3OY7KXHM.js.map} +0 -0
  204. /package/dist/{chunk-AIUBBHSF.js.map → chunk-5UZ6UCOZ.js.map} +0 -0
  205. /package/dist/{chunk-UT5BV5OG.js.map → chunk-7HXZJAID.js.map} +0 -0
  206. /package/dist/{chunk-M7BXDKPR.js.map → chunk-7M6NYQWB.js.map} +0 -0
  207. /package/dist/{chunk-TPTECFWR.js.map → chunk-B6LZ4IBH.js.map} +0 -0
  208. /package/dist/{chunk-J5LLX6IL.js.map → chunk-BF6SXNQJ.js.map} +0 -0
  209. /package/dist/{chunk-4PC4PRJE.js.map → chunk-BKIFTXHR.js.map} +0 -0
  210. /package/dist/{chunk-VVG2BOKA.js.map → chunk-BMEYMKWX.js.map} +0 -0
  211. /package/dist/{chunk-DCZBSE4F.js.map → chunk-D5YOZ66N.js.map} +0 -0
  212. /package/dist/{chunk-BHXYAVUM.js.map → chunk-DLG5V6ND.js.map} +0 -0
  213. /package/dist/{chunk-52UKFTUK.js.map → chunk-F4EU6VUC.js.map} +0 -0
  214. /package/dist/{chunk-M4A44LUA.js.map → chunk-GSTIBXBW.js.map} +0 -0
  215. /package/dist/{chunk-W5XVQFLM.js.map → chunk-GXFSQ6SD.js.map} +0 -0
  216. /package/dist/{chunk-XADV5IZ3.js.map → chunk-HDIUYMSB.js.map} +0 -0
  217. /package/dist/{chunk-FXTYIA46.js.map → chunk-J272WKX2.js.map} +0 -0
  218. /package/dist/{chunk-JYL3BVVZ.js.map → chunk-JSEJH2YQ.js.map} +0 -0
  219. /package/dist/{chunk-5UDMFE3Q.js.map → chunk-MLXZD4WM.js.map} +0 -0
  220. /package/dist/{chunk-PUSLMZBQ.js.map → chunk-NOEMGE56.js.map} +0 -0
  221. /package/dist/{chunk-C5QLI33R.js.map → chunk-QINJITDS.js.map} +0 -0
  222. /package/dist/{chunk-45BRYON6.js.map → chunk-QPOHMO6M.js.map} +0 -0
  223. /package/dist/{chunk-ZQOCRIGA.js.map → chunk-QYN5PXDZ.js.map} +0 -0
  224. /package/dist/{chunk-PA45XCUD.js.map → chunk-RANKJI5T.js.map} +0 -0
  225. /package/dist/{chunk-37ZO6LLJ.js.map → chunk-RYLEDKQ2.js.map} +0 -0
  226. /package/dist/{chunk-CV6BBVYV.js.map → chunk-TEMSWHQD.js.map} +0 -0
  227. /package/dist/{chunk-QGR6OVGT.js.map → chunk-VFSAWRMZ.js.map} +0 -0
  228. /package/dist/{chunk-NLK2VZYZ.js.map → chunk-VXCNQYTC.js.map} +0 -0
  229. /package/dist/{chunk-AZ2QBGYB.js.map → chunk-W7Y5JDVB.js.map} +0 -0
  230. /package/dist/{chunk-4F6X3RXW.js.map → chunk-X57M2Q4I.js.map} +0 -0
  231. /package/dist/{chunk-6MTSFX7A.js.map → chunk-X7XEDOSL.js.map} +0 -0
  232. /package/dist/{chunk-UEVLYVNW.js.map → chunk-YXKVLYXU.js.map} +0 -0
  233. /package/dist/{chunk-OQAL6SIR.js.map → chunk-ZMSZVISA.js.map} +0 -0
  234. /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-ENTCVTQR.js";
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-BHXYAVUM.js";
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-OQAL6SIR.js";
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-M4A44LUA.js";
108
+ } from "./chunk-GSTIBXBW.js";
109
109
  import {
110
110
  computeSupersessionKey,
111
111
  normalizeSupersessionKey,
112
112
  supersessionKeysForFact
113
- } from "./chunk-EWMTOVMY.js";
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 lstat3,
233
- readdir as readdir5,
234
- readFile as readFile2,
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 unlink4,
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 createHash6, randomUUID as randomUUID2 } from "crypto";
246
- import path9 from "path";
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 path3 from "path";
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 = path3.join(this.deps.identityIncidentsDir, `${date}-${id}.md`);
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 = path3.join(this.deps.identityIncidentsDir, file);
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 = path3.join(dir, `${safeKey}.md`);
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(path3.join(dir, `${safeKey}.md`));
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 = path3.join(this.deps.identityIncidentsDir, directMatch);
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 = path3.join(this.deps.identityIncidentsDir, fileName);
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 lstat2,
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 unlink2,
2652
+ unlink as unlink3,
2214
2653
  writeFile
2215
2654
  } from "fs/promises";
2216
- import path4 from "path";
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 lstat2(filePath);
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) => path4.extname(name) === ".md").sort();
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 = createHash("sha256");
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 lstat2(path4.join(entitiesDir, name), { bigint: true });
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(path4.join(baseDir, "config", "aliases.json")),
2280
- fingerprintPath(path4.join(baseDir, "state", ENTITY_CANONICAL_ID_RECONCILE_MARKER)),
2281
- fingerprintPath(path4.join(baseDir, "state", ENTITY_CANONICAL_ID_RECONCILE_CONSUMING_MARKER))
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 lstat2(baseDir);
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) => path4.join(baseDir, directory)),
2308
- path4.join(baseDir, "cold"),
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 lstat2(root);
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 path4.join(deps.stateDir, ENTITY_CANONICAL_ID_MIGRATION_FILE);
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 lstat2(migrationStatePath);
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 lstat2(tombstonePath);
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 path4.join(deps.stateDir, "entity-canonical-id-migration.lock");
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 readFile(statePath(deps), "utf-8");
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}.${randomUUID()}.tmp`;
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 rename(temporary, destination);
2841
+ await rename2(temporary, destination);
2403
2842
  } finally {
2404
- await unlink2(temporary).catch(() => void 0);
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 lstat2(filePath);
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 lstat2(filePath);
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 lstat2(entitiesDir);
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(path4.join(entitiesDir, entry), entry);
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 = path4.resolve(memory.path);
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(`${path4.sep}cold${path4.sep}`);
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 = path4.join(deps.stateDir, "tombstones.jsonl");
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
- path4.join(deps.stateDir, "tombstones.lock"),
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 unlink2(legacyPath);
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 unlink2(legacyPath);
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 unlink2(canonicalPath);
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 = path4.join(deps.stateDir, ENTITY_CANONICAL_ID_RECONCILE_MARKER);
2849
- const consumingMarkerPath = path4.join(deps.stateDir, ENTITY_CANONICAL_ID_RECONCILE_CONSUMING_MARKER);
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 rename(reconcileMarkerPath, consumingMarkerPath);
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 unlink2(consumingMarkerPath).catch((error) => {
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 unlink3 } from "fs/promises";
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 unlink3(opts.onFailRemove).catch(() => void 0);
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 path5 from "path";
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 = path5.join(baseDir, "config");
3273
- const aliasPath = path5.join(configDir, "aliases.json");
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 path6 from "path";
3773
+ import path7 from "path";
3335
3774
  function qmdCollectionPathParts(resultPath) {
3336
- if (!resultPath || path6.isAbsolute(resultPath)) return null;
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 = path6.resolve(storageDir);
3788
+ const storageRoot = path7.resolve(storageDir);
3350
3789
  const addCandidate = (candidate) => {
3351
- const resolved = path6.resolve(candidate);
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(path6.join(storageRoot, normalized));
3798
+ addCandidate(path7.join(storageRoot, normalized));
3360
3799
  if (/^\d{4}-\d{2}-\d{2}\//.test(normalized)) {
3361
- addCandidate(path6.join(storageRoot, "facts", normalized));
3800
+ addCandidate(path7.join(storageRoot, "facts", normalized));
3362
3801
  }
3363
3802
  };
3364
- if (path6.isAbsolute(resultPath)) {
3803
+ if (path7.isAbsolute(resultPath)) {
3365
3804
  addCandidate(resultPath);
3366
- const relative2 = path6.relative(storageRoot, path6.resolve(resultPath));
3367
- if (relative2 && !relative2.startsWith("..") && !path6.isAbsolute(relative2)) {
3368
- const normalized = relative2.split(path6.sep).join("/");
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(path6.join(storageRoot, "facts", normalized));
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 ? path6.resolve(storageDir) : `storage-without-dir-${storages.length}`;
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 = path6.join(storageDir, "cold");
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 (!path6.isAbsolute(resultPath)) return null;
3933
+ if (!path7.isAbsolute(resultPath)) return null;
3495
3934
  }
3496
- if (path6.isAbsolute(resultPath)) {
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 (path6.isAbsolute(memory.path)) {
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 = path6.resolve(resultPath);
3999
+ const resolvedPath = path7.resolve(resultPath);
3561
4000
  const config = this.getConfig();
3562
- const memoryRoot = path6.resolve(config.memoryDir);
3563
- const namespacesRoot = path6.join(memoryRoot, "namespaces");
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 = path6.resolve(storageDir);
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 = path6.relative(namespacesRoot, resolvedPath);
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 createHash2 } from "crypto";
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 createHash2("sha256").update(JSON.stringify(record), "utf-8").digest("hex");
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 createHash2("sha256").update(inputsKey).update("\n").update(fingerprint).update("\n").update(algoVersion).digest("hex");
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 createHash3 } from "crypto";
4237
- import * as path7 from "path";
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 createHash3("sha256").update(JSON.stringify(conversations), "utf-8").digest("hex");
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 path7.join(this.wearablesDir, FUSION_DIR_NAME, `${date}.md`);
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 = path7.join(this.wearablesDir, FUSION_DIR_NAME);
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 = path7.resolve(this.memoryDir);
4944
+ rootReal = path8.resolve(this.memoryDir);
4506
4945
  }
4507
4946
  await this.assertNoEscape(rootReal, this.wearablesDir);
4508
- await this.assertNoEscape(rootReal, path7.dirname(targetPath));
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 = path7.join(this.wearablesDir, FUSION_DIR_NAME);
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 createHash4 } from "crypto";
4658
- import * as path8 from "path";
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 = createHash4("sha256").update(canonicalContent(base, fused), "utf-8").digest("hex");
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 = path8.join(memoryDir, MEETINGS_DIR_NAME);
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 path8.join(this.meetingsDir, date, `${id}.md`);
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 = path8.join(this.meetingsDir, date);
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 = path8.resolve(this.memoryDir);
5381
+ rootReal = path9.resolve(this.memoryDir);
4943
5382
  }
4944
5383
  await this.assertNoEscape(rootReal, this.meetingsDir);
4945
- await this.assertNoEscape(rootReal, path8.dirname(targetPath));
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 = path8.relative(this.meetingsDir, targetPath);
4956
- if (rel === "" || rel.startsWith("..") || path8.isAbsolute(rel)) return null;
4957
- const first = rel.split(path8.sep)[0];
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 path8.join(this.meetingsDir, first);
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 = path8.relative(this.memoryDir, target) || MEETINGS_DIR_NAME;
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 open2 = template.indexOf("{", cursor);
5015
- if (open2 === -1) {
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, open2));
5020
- const close = template.indexOf("}", open2 + 1);
5458
+ parts.push(template.slice(cursor, open3));
5459
+ const close = template.indexOf("}", open3 + 1);
5021
5460
  if (close === -1) {
5022
- cursor = open2 + 1;
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
- bumpMemoryCorpusVersion();
5094
- await appendLifecycleEvent();
5095
- bumpMemoryStatusVersion();
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
- await appendTombstone(input);
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 auditEnvelope = composeMemoryEnvelope(
5117
- {
5118
- content: `Superseded: ${currentBefore.content}
5574
+ const auditBody = `Superseded: ${currentBefore.content}
5119
5575
 
5120
- Reason: ${reason}`,
5121
- category: "correction",
5122
- confidence: 1,
5123
- tags: ["supersession", "auto-resolved"]
5576
+ Reason: ${reason}`;
5577
+ const auditExists = await hasSupersessionAudit(
5578
+ {
5579
+ correctionsDir: options.correctionsDir,
5580
+ readMemoryByPath: options.readMemoryByPath
5124
5581
  },
5125
- { source: "contradiction-detection" }
5582
+ oldMemoryId,
5583
+ newMemoryId,
5584
+ auditBody
5126
5585
  );
5127
- await writeSealedMemory(auditEnvelope, { lineage: [oldMemoryId, newMemoryId] });
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(`failed to supersede memory ${oldMemoryId}:`, err);
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 createHash5 } from "crypto";
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 createHash5("sha256").update(JSON.stringify(normalizedSections)).digest("hex");
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 = path9.basename(pathRel, ".md").toLowerCase();
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(path9.sep).join("/");
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 = path9.basename(pathRel, ".md");
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 = path9.relative(this.stateDir, path9.resolve(filePath));
6722
- return rel === "" || !rel.startsWith("..") && !path9.isAbsolute(rel);
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 = path9.relative(this.baseDir, p);
6838
- return rel === "cold" || rel === "archive" || rel.startsWith(`cold${path9.sep}`) || rel.startsWith(`archive${path9.sep}`);
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 = path9.relative(this.baseDir, filePath).split(path9.sep).join("/");
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 path9.join(workspaceDir, "IDENTITY.md");
7480
+ if (!rawNamespace) return path11.join(workspaceDir, "IDENTITY.md");
7006
7481
  const safeNamespace = rawNamespace.replace(/[^a-zA-Z0-9._-]/g, "-");
7007
- return path9.join(workspaceDir, `IDENTITY.${safeNamespace}.md`);
7482
+ return path11.join(workspaceDir, `IDENTITY.${safeNamespace}.md`);
7008
7483
  }
7009
7484
  versionFilePath(kind) {
7010
- return path9.join(this.stateDir, `.${kind}-version.log`);
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 path9.join(this.baseDir, WEARABLES_DIR_NAME);
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 path9.join(this.wearablesDir, sourceId, `${date}.md`);
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) => readdir5(d),
7157
- deleteFile: (p) => unlink4(p),
7631
+ readDir: (d) => readdir6(d),
7632
+ deleteFile: (p) => unlink5(p),
7158
7633
  realpath: (p) => realpath3(p),
7159
- lstat: (p) => lstat3(p).then((st) => ({ isSymbolicLink: st.isSymbolicLink() }))
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) => readdir5(d),
7175
- deleteFile: (p) => unlink4(p),
7649
+ readDir: (d) => readdir6(d),
7650
+ deleteFile: (p) => unlink5(p),
7176
7651
  realpath: (p) => realpath3(p),
7177
- lstat: (p) => lstat3(p).then((st) => ({ isSymbolicLink: st.isSymbolicLink() }))
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 path9.join(this.baseDir, "facts");
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
- resolveEntityFilePath(name) {
7261
- if (typeof name !== "string") return null;
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
- async readDeletionRevisionMetadata() {
7321
- let raw;
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
- async writeDeletionRevisionMetadata(revisions, lock) {
7331
- const deletions = [...revisions.entries()].sort(([left], [right]) => left < right ? -1 : left > right ? 1 : 0).map(([entryPath, mtimeMs]) => ({ path: entryPath, mtimeMs }));
7332
- const metadata = { version: 1, deletions };
7333
- const temporaryPath = `${this.deletionRevisionMetadataPath}.${process.pid}.${randomUUID2()}.tmp`;
7334
- let handle = null;
7335
- try {
7336
- handle = await open(temporaryPath, "wx", 384);
7337
- await handle.writeFile(`${JSON.stringify(metadata)}
7338
- `, "utf8");
7339
- await handle.sync();
7340
- await handle.close();
7341
- handle = null;
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
- async withDeletionRevisionLock(task) {
7354
- return withHeldFileLock(
7355
- this.deletionRevisionLockPath,
7356
- {
7357
- staleMs: DELETION_REVISION_LOCK_STALE_MS,
7358
- maxWaitMs: DELETION_REVISION_LOCK_MAX_WAIT_MS
7359
- },
7360
- async (acquired, lock) => {
7361
- if (!acquired) throw new Error("Deletion revision metadata lock is unavailable.");
7362
- return task(lock);
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.withDeletionRevisionLock(async () => this.readDeletionRevisionMetadata());
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
- const target = this.assertManagedStoragePath(filePath, "storage.recordReplicatedDeletionRevision");
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
- const target = this.assertManagedStoragePath(filePath, "storage.writeManagedStorageFile");
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
- const target = this.assertManagedStoragePath(filePath, "storage.deleteManagedStorageFile");
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 = path9.relative(this.baseDir, target).split(path9.sep).join("/");
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: createHash6("sha256").update(content).digest("hex"),
7824
+ sha256: createHash7("sha256").update(content).digest("hex"),
7481
7825
  bytes: content.byteLength
7482
7826
  };
7483
7827
  } else {
7484
- const hash = createHash6("sha256");
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 readFile2(this.offlineSyncDigestCachePath, "utf-8");
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("../") || path9.isAbsolute(cachePath) || !Number.isFinite(statBytes) || !Number.isFinite(mtimeMs) || !Number.isFinite(ctimeMs) || !Number.isFinite(bytes) || !/^[a-f0-9]{64}$/i.test(sha256)) {
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 mkdir(path9.dirname(this.offlineSyncDigestCachePath), { recursive: true });
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 open(filePath, "r");
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 open(filePath, "r");
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 mkdir(path9.dirname(filePath), { recursive: true });
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 path9.join(this.baseDir, "state");
8028
+ return path11.join(this.baseDir, "state");
7685
8029
  }
7686
8030
  get offlineSyncDigestCachePath() {
7687
- return path9.join(this.baseDir, ".offline-sync", "digest-cache.v1.json");
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 path9.join(this.stateDir, "entity-synthesis-queue.json");
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 path9.join(this.stateDir, "tombstones.jsonl");
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 path9.join(this.baseDir, "questions");
8351
+ return path11.join(this.baseDir, "questions");
8004
8352
  }
8005
8353
  get artifactsDir() {
8006
- return path9.join(this.baseDir, "artifacts");
8354
+ return path11.join(this.baseDir, "artifacts");
8007
8355
  }
8008
8356
  get identityDir() {
8009
- return path9.join(this.baseDir, "identity");
8357
+ return path11.join(this.baseDir, "identity");
8010
8358
  }
8011
8359
  get identityAnchorPath() {
8012
- return path9.join(this.identityDir, "identity-anchor.md");
8360
+ return path11.join(this.identityDir, "identity-anchor.md");
8013
8361
  }
8014
8362
  get identityIncidentsDir() {
8015
- return path9.join(this.identityDir, "incidents");
8363
+ return path11.join(this.identityDir, "incidents");
8016
8364
  }
8017
8365
  get identityAuditsWeeklyDir() {
8018
- return path9.join(this.identityDir, "audits", "weekly");
8366
+ return path11.join(this.identityDir, "audits", "weekly");
8019
8367
  }
8020
8368
  get identityAuditsMonthlyDir() {
8021
- return path9.join(this.identityDir, "audits", "monthly");
8369
+ return path11.join(this.identityDir, "audits", "monthly");
8022
8370
  }
8023
8371
  get identityImprovementLoopsPath() {
8024
- return path9.join(this.identityDir, "improvement-loops.md");
8372
+ return path11.join(this.identityDir, "improvement-loops.md");
8025
8373
  }
8026
8374
  get identityReflectionsPath() {
8027
- return path9.join(this.identityDir, "reflections.md");
8375
+ return path11.join(this.identityDir, "reflections.md");
8028
8376
  }
8029
8377
  get profilePath() {
8030
- return path9.join(this.baseDir, "profile.md");
8378
+ return path11.join(this.baseDir, "profile.md");
8031
8379
  }
8032
8380
  get memoryActionsPath() {
8033
- return path9.join(this.stateDir, "memory-actions.jsonl");
8381
+ return path11.join(this.stateDir, "memory-actions.jsonl");
8034
8382
  }
8035
8383
  get memoryLifecycleLedgerPath() {
8036
- return path9.join(this.stateDir, "memory-lifecycle-ledger.jsonl");
8384
+ return path11.join(this.stateDir, "memory-lifecycle-ledger.jsonl");
8037
8385
  }
8038
8386
  get compressionGuidelinesPath() {
8039
- return path9.join(this.stateDir, "compression-guidelines.md");
8387
+ return path11.join(this.stateDir, "compression-guidelines.md");
8040
8388
  }
8041
8389
  get compressionGuidelineDraftPath() {
8042
- return path9.join(this.stateDir, "compression-guidelines.draft.md");
8390
+ return path11.join(this.stateDir, "compression-guidelines.draft.md");
8043
8391
  }
8044
8392
  get compressionGuidelineStatePath() {
8045
- return path9.join(this.stateDir, "compression-guideline-state.json");
8393
+ return path11.join(this.stateDir, "compression-guideline-state.json");
8046
8394
  }
8047
8395
  get compressionGuidelineDraftStatePath() {
8048
- return path9.join(this.stateDir, "compression-guideline-draft-state.json");
8396
+ return path11.join(this.stateDir, "compression-guideline-draft-state.json");
8049
8397
  }
8050
8398
  get behaviorSignalsPath() {
8051
- return path9.join(this.stateDir, "behavior-signals.jsonl");
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 path9.join(this.stateDir, "buffer-surprise-ledger.jsonl");
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 = path9.join(this.baseDir, "config", "aliases.json");
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 mkdir(this.baseDir, { recursive: true });
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 mkdir(path9.join(this.factsDir, today), { recursive: true });
8183
- await mkdir(path9.join(this.proceduresDir, today), { recursive: true });
8184
- await mkdir(path9.join(this.reasoningTracesDir, today), { recursive: true });
8185
- await mkdir(this.correctionsDir, { recursive: true });
8186
- await mkdir(this.entitiesDir, { recursive: true });
8187
- await mkdir(this.stateDir, { recursive: true });
8188
- await mkdir(this.questionsDir, { recursive: true });
8189
- await mkdir(this.artifactsDir, { recursive: true });
8190
- await mkdir(this.identityDir, { recursive: true });
8191
- await mkdir(this.identityIncidentsDir, { recursive: true });
8192
- await mkdir(this.identityAuditsWeeklyDir, { recursive: true });
8193
- await mkdir(this.identityAuditsMonthlyDir, { recursive: true });
8194
- await mkdir(path9.join(this.baseDir, "config"), { recursive: true });
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 mkdir(this.correctionsDir, { recursive: true });
8216
- return path9.join(this.correctionsDir, `${id}.md`);
8563
+ await mkdir2(this.correctionsDir, { recursive: true });
8564
+ return path11.join(this.correctionsDir, `${id}.md`);
8217
8565
  }
8218
- const datedDir = path9.join(getCategoryDir(this.baseDir, category), today);
8219
- await mkdir(datedDir, { recursive: true });
8220
- return path9.join(datedDir, `${id}.md`);
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 unlink4(filePath).catch(() => void 0);
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 = path9.join(this.artifactsDir, day);
8588
- await mkdir(dir, { recursive: true });
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 = path9.join(dir, `${id}.md`);
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 = path9.join(this.baseDir, "cold");
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}${path9.sep}`)) {
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 readdir5(dir, { withFileTypes: true });
9430
+ const entries = await readdir6(dir, { withFileTypes: true });
9078
9431
  for (const entry of entries) {
9079
- const fullPath = path9.join(dir, entry.name);
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" ? path9.join(this.baseDir, "cold") : this.baseDir;
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 path9.join(root, "artifacts", this.resolveMemoryDateDir(memory), `${memory.frontmatter.id}.md`);
9490
+ return path11.join(root, "artifacts", this.resolveMemoryDateDir(memory), `${memory.frontmatter.id}.md`);
9133
9491
  }
9134
9492
  if (memory.frontmatter.category === "correction") {
9135
- return path9.join(root, "corrections", `${memory.frontmatter.id}.md`);
9493
+ return path11.join(root, "corrections", `${memory.frontmatter.id}.md`);
9136
9494
  }
9137
9495
  const dir = categoryDirName(memory.frontmatter.category);
9138
- return path9.join(root, dir, this.resolveMemoryDateDir(memory), `${memory.frontmatter.id}.md`);
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 readFile2(targetPath).catch(() => null);
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 unlink4(targetPath).catch(() => void 0);
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 = path9.resolve(current.path);
9217
- const destPath = path9.resolve(targetPath);
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 = path9.resolve(memory.path);
9242
- const destPath = path9.resolve(targetPath);
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 path9.join(this.baseDir, "archive");
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 = path9.join(this.archiveDir, today);
9266
- await mkdir(destDir, { recursive: true });
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 = path9.join(destDir, path9.basename(current.path));
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
- () => unlink4(destPath).then(
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 memories = await this.readAllMemories();
9393
- const memory = memories.find((candidate) => candidate.frontmatter.id === id);
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
- const deleted = await recordArchiveDelete(this.baseDir, current.path, () => this.deleteManagedStorageFile(current.path));
9397
- if (!deleted) return false;
9398
- markDurable();
9399
- markProjectedMemoryPathInvalid(this.baseDir, id);
9400
- this.invalidateAllMemoriesCache();
9401
- await this.rebuildTombstoneBlockedCaptureAfterInvalidation(rebuildMarker);
9402
- this.bumpMemoryStatusVersion();
9403
- log.debug(`invalidated memory ${id}`);
9404
- return true;
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 updateMemory(id, newContent, options) {
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(`${path9.sep}cold${path9.sep}`)) {
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(`${path9.sep}cold${path9.sep}`)) {
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 = path9.join(this.stateDir, "buffer.json");
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 = path9.join(this.stateDir, "buffer.json");
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 = path9.join(this.stateDir, "meta.json");
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 = path9.join(this.stateDir, "meta.json");
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 mkdir(path9.dirname(target), { recursive: true });
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 = path9.join(this.stateDir, "reextract-jobs.jsonl");
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 = path9.join(this.stateDir, "reextract-jobs.jsonl");
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 = createHash6("sha256").update(draftContent).digest("hex");
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
- unlink4(this.compressionGuidelineDraftPath).catch(() => void 0),
9945
- unlink4(this.compressionGuidelineDraftStatePath).catch(() => void 0)
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 mkdir(this.questionsDir, { recursive: true });
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 = path9.join(this.questionsDir, `${id}.md`);
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") ?? path9.basename(filePath, ".md");
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 readFile2(identityPath, "utf-8");
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 readFile2(identityPath, "utf-8");
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(path9.basename(identityPath));
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 = path9.join(workspaceDir, hygiene.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 mkdir(this.identityDir, { recursive: true });
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 mkdir(this.identityDir, { recursive: true });
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 mkdir(this.stateDir, { recursive: true });
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 mkdir(this.stateDir, { recursive: true });
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) => [path9.resolve(m.path), 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(path9.resolve(entry.memoryPath)) : memoryMap.get(entry.memoryId);
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(path9.resolve(memory.path), {
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(path9.resolve(m.path));
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 = path9.basename(filePath, ".md");
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(path9.resolve(filePath)));
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(path9.resolve(filePath)));
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 memories = await this.readAllMemories();
10626
- let oldMemory = memories.find(
10627
- (memory) => memory.frontmatter.id === oldMemoryId && inferMemoryStatus(memory.frontmatter, toMemoryPathRel(this.baseDir, memory.path)) === "active"
10628
- );
10629
- if (!oldMemory) {
10630
- oldMemory = (await this.readAllColdMemories()).find(
10631
- (memory) => memory.frontmatter.id === oldMemoryId && inferMemoryStatus(memory.frontmatter, toMemoryPathRel(this.baseDir, memory.path)) === "active"
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 now = (/* @__PURE__ */ new Date()).toISOString();
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
- const written = await this.withTombstoneBlockedMemoryPathLock(oldMemory.path, async (current) => {
10639
- if (current?.frontmatter.id !== oldMemoryId) return false;
10640
- if (current.frontmatter.supersededBy !== void 0 || inferMemoryStatus(current.frontmatter, toMemoryPathRel(this.baseDir, current.path)) !== "active") {
10641
- return false;
10642
- }
10643
- currentBefore = current;
10644
- const refIdsAtWrite = this.currentHistoricalIds();
10645
- updatedFm = canonicalizeEntityRefOption(
10646
- { ...current.frontmatter, status: "superseded", supersededBy: newMemoryId, supersededAt: now, updated: now },
10647
- refIdsAtWrite
10648
- );
10649
- const fileContent = `${serializeFrontmatter(updatedFm)}
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
- await this.writeTombstoneBlockedFrontmatter(current, fileContent, updatedFm);
10654
- if (typeof current.frontmatter.entityRef === "string") {
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
- current.path,
11116
+ currentBefore.path,
10657
11117
  updatedFm,
10658
- current.frontmatter.entityRef,
10659
- refIdsAtWrite,
10660
- current.content,
10661
- { onFailRestore: current }
11118
+ updatedFm.entityRef,
11119
+ this.currentHistoricalIds(),
11120
+ currentBefore.content,
11121
+ { onFailRestore: currentBefore }
10662
11122
  );
10663
11123
  }
10664
- return true;
10665
- }).catch((err) => {
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 path9.join(this.baseDir, "summaries");
11182
+ return path11.join(this.baseDir, "summaries");
10702
11183
  }
10703
- /**
10704
- * Write a memory summary.
10705
- */
10706
11184
  async writeSummary(summary) {
10707
- await mkdir(this.summariesDir, { recursive: true });
10708
- const filePath = path9.join(this.summariesDir, `${summary.id}.json`);
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 readdir5(this.summariesDir);
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 = path9.join(this.summariesDir, file);
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 = path9.join(this.stateDir, "topics.json");
10801
- await mkdir(this.stateDir, { recursive: true });
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 = path9.join(this.stateDir, "topics.json");
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-VBHPOWBJ.js.map
11390
+ //# sourceMappingURL=chunk-WD5B5AIE.js.map