@remnic/core 9.52.1 → 9.53.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (253) hide show
  1. package/dist/access-admin-ops-surface.d.ts +5 -5
  2. package/dist/access-admin-ops-surface.js +22 -22
  3. package/dist/access-authorization-probe.d.ts +5 -5
  4. package/dist/access-authorization-probe.js +23 -23
  5. package/dist/access-boundary.d.ts +5 -5
  6. package/dist/access-boundary.js +22 -22
  7. package/dist/access-cli.js +54 -54
  8. package/dist/access-extraction-force-flush.d.ts +5 -5
  9. package/dist/access-extraction-force-flush.js +22 -22
  10. package/dist/access-http-lcm-compaction.d.ts +5 -5
  11. package/dist/access-http-lifecycle-flush.d.ts +5 -5
  12. package/dist/access-http-offline-stream.d.ts +5 -5
  13. package/dist/access-http-query.js +22 -22
  14. package/dist/access-http.d.ts +5 -5
  15. package/dist/access-http.js +29 -29
  16. package/dist/access-identity-continuity-surface.d.ts +4 -4
  17. package/dist/access-identity-continuity-surface.js +22 -22
  18. package/dist/access-lcm-surface.d.ts +5 -5
  19. package/dist/access-lcm-surface.js +22 -22
  20. package/dist/access-mcp.d.ts +5 -5
  21. package/dist/access-mcp.js +27 -27
  22. package/dist/access-namespace-preflight.js +22 -22
  23. package/dist/access-observe-write-surface.d.ts +5 -5
  24. package/dist/access-observe-write-surface.js +22 -22
  25. package/dist/access-offline-manifest.d.ts +4 -4
  26. package/dist/access-offline-manifest.js +8 -4
  27. package/dist/access-operations-batch.js +24 -24
  28. package/dist/access-operations.d.ts +5 -5
  29. package/dist/access-operations.js +26 -26
  30. package/dist/access-recall-concurrency.d.ts +5 -5
  31. package/dist/access-recall-concurrency.js +22 -22
  32. package/dist/access-recall-response.d.ts +5 -5
  33. package/dist/access-recall-response.js +22 -22
  34. package/dist/access-recall-surface.d.ts +5 -5
  35. package/dist/access-recall-surface.js +22 -22
  36. package/dist/{access-service-Cbk3bNbD.d.ts → access-service-CgCjvbQu.d.ts} +3 -3
  37. package/dist/access-service-helpers.d.ts +5 -5
  38. package/dist/access-service.d.ts +5 -5
  39. package/dist/access-service.js +22 -22
  40. package/dist/access-surface-catalog.d.ts +5 -5
  41. package/dist/access-wearables-meetings-surface.d.ts +2 -2
  42. package/dist/active-recall.js +4 -4
  43. package/dist/{auto-sync-QFQGOEO2.js → auto-sync-JXEW7444.js} +6 -6
  44. package/dist/bootstrap.d.ts +4 -4
  45. package/dist/briefing.d.ts +1 -1
  46. package/dist/briefing.js +12 -12
  47. package/dist/buffer.d.ts +1 -1
  48. package/dist/causal-consolidation.js +13 -13
  49. package/dist/{chunk-MLXZD4WM.js → chunk-2GFK22OZ.js} +2 -2
  50. package/dist/{chunk-D5YOZ66N.js → chunk-3DWTDC54.js} +20 -20
  51. package/dist/{chunk-RANKJI5T.js → chunk-3JLPGJ43.js} +2 -2
  52. package/dist/chunk-5FVVY2EB.js +27 -0
  53. package/dist/chunk-5FVVY2EB.js.map +1 -0
  54. package/dist/{chunk-GXSOUIA4.js → chunk-5WQMORU5.js} +4 -4
  55. package/dist/{chunk-J272WKX2.js → chunk-6HPHEFXT.js} +2 -2
  56. package/dist/{chunk-B6LZ4IBH.js → chunk-6LAPLZOS.js} +2 -2
  57. package/dist/{chunk-WD5B5AIE.js → chunk-6R4G7N4S.js} +118 -173
  58. package/dist/chunk-6R4G7N4S.js.map +1 -0
  59. package/dist/{chunk-VXCNQYTC.js → chunk-APPFNGX6.js} +2 -2
  60. package/dist/{chunk-RMHMUUBW.js → chunk-AY43LBE4.js} +104 -48
  61. package/dist/{chunk-RMHMUUBW.js.map → chunk-AY43LBE4.js.map} +1 -1
  62. package/dist/{chunk-GXFSQ6SD.js → chunk-C4ZNGVK3.js} +2 -2
  63. package/dist/{chunk-JSEJH2YQ.js → chunk-CGVHPCP2.js} +2 -2
  64. package/dist/{chunk-BQIFZ2MP.js → chunk-CX5SW2EW.js} +41 -24
  65. package/dist/chunk-CX5SW2EW.js.map +1 -0
  66. package/dist/{chunk-TEMSWHQD.js → chunk-DZD576ZU.js} +5 -5
  67. package/dist/{chunk-AMTEYNYK.js → chunk-EVWWT3KE.js} +4 -4
  68. package/dist/{chunk-XAPMEU3K.js → chunk-FHFERRYE.js} +2 -2
  69. package/dist/{chunk-P2KBDJJ6.js → chunk-G2LUMAUF.js} +4 -3
  70. package/dist/{chunk-P2KBDJJ6.js.map → chunk-G2LUMAUF.js.map} +1 -1
  71. package/dist/{chunk-AXIDQG5A.js → chunk-G4T7L557.js} +5 -5
  72. package/dist/{chunk-7M6NYQWB.js → chunk-G7Z6EENO.js} +4 -4
  73. package/dist/{chunk-YXKVLYXU.js → chunk-HWRD23DW.js} +2 -2
  74. package/dist/{chunk-SOEN6KCF.js → chunk-IBKR2TFR.js} +2 -2
  75. package/dist/{chunk-R3DJVOSM.js → chunk-JDSVLVCG.js} +8 -1
  76. package/dist/chunk-JDSVLVCG.js.map +1 -0
  77. package/dist/{chunk-F4EU6VUC.js → chunk-JDZPNCMA.js} +2 -2
  78. package/dist/{chunk-DLG5V6ND.js → chunk-K4QS7LXU.js} +4 -7
  79. package/dist/chunk-K4QS7LXU.js.map +1 -0
  80. package/dist/{chunk-BAKXNQ3C.js → chunk-KRTY3ENI.js} +2 -2
  81. package/dist/{chunk-NK5RGJ65.js → chunk-L4DLLKPN.js} +2 -2
  82. package/dist/{chunk-VFSAWRMZ.js → chunk-LJS7KUPV.js} +2 -2
  83. package/dist/{chunk-JJRVPWFY.js → chunk-MLPTFODV.js} +166 -37
  84. package/dist/chunk-MLPTFODV.js.map +1 -0
  85. package/dist/{chunk-NOEMGE56.js → chunk-NH5IR2WM.js} +2 -2
  86. package/dist/{chunk-5V6IXHPI.js → chunk-NSVDUN76.js} +2 -2
  87. package/dist/{chunk-IARXXZU4.js → chunk-P7XI2AQ3.js} +2 -2
  88. package/dist/{chunk-W7Y5JDVB.js → chunk-PDDTI7IC.js} +4 -4
  89. package/dist/{chunk-QINJITDS.js → chunk-PX2LSHYM.js} +2 -2
  90. package/dist/{chunk-O5FFBO2M.js → chunk-QAVH6X2A.js} +2 -2
  91. package/dist/{chunk-HDIUYMSB.js → chunk-QWGE2PUH.js} +6 -6
  92. package/dist/{chunk-X57M2Q4I.js → chunk-RDJRFDJY.js} +3 -3
  93. package/dist/{chunk-RYLEDKQ2.js → chunk-RUMNY5DT.js} +3 -3
  94. package/dist/{chunk-BKIFTXHR.js → chunk-RZ4XGKOJ.js} +2 -2
  95. package/dist/{chunk-66SE3BNH.js → chunk-SUPEYLH2.js} +2 -2
  96. package/dist/{chunk-BF6SXNQJ.js → chunk-T2JHZWH6.js} +20 -20
  97. package/dist/{chunk-VHFXP27N.js → chunk-THPPCISA.js} +60 -23
  98. package/dist/chunk-THPPCISA.js.map +1 -0
  99. package/dist/{chunk-QPOHMO6M.js → chunk-UES4BHA2.js} +2 -2
  100. package/dist/{chunk-X7XEDOSL.js → chunk-VLL43QEQ.js} +8 -8
  101. package/dist/{chunk-QBSFOKY6.js → chunk-VOELBUS4.js} +4 -4
  102. package/dist/{chunk-2LY6GIIE.js → chunk-WDBX6VER.js} +2 -2
  103. package/dist/{chunk-BMEYMKWX.js → chunk-WM65LU67.js} +2 -2
  104. package/dist/{chunk-WLRD2TXF.js → chunk-WOPA3SGI.js} +4 -4
  105. package/dist/{chunk-5UZ6UCOZ.js → chunk-XUAMCLY4.js} +3 -3
  106. package/dist/{chunk-7HXZJAID.js → chunk-YGLY6QFV.js} +3 -3
  107. package/dist/{chunk-2OIQDTDC.js → chunk-ZPJ4QSGP.js} +2 -2
  108. package/dist/{chunk-3OY7KXHM.js → chunk-ZYLC7NFO.js} +3 -3
  109. package/dist/{cli-DMAFI0qt.d.ts → cli-D-HiEYhl.d.ts} +3 -3
  110. package/dist/cli.d.ts +6 -6
  111. package/dist/cli.js +41 -41
  112. package/dist/compounding/engine.d.ts +1 -1
  113. package/dist/compounding/engine.js +13 -13
  114. package/dist/config.js +4 -4
  115. package/dist/connectors/codex-materialize-runner.js +12 -12
  116. package/dist/connectors/index.js +12 -12
  117. package/dist/consolidation-provenance-check.d.ts +1 -1
  118. package/dist/consolidation-undo.d.ts +1 -1
  119. package/dist/content-hash.d.ts +12 -2
  120. package/dist/content-hash.js +9 -3
  121. package/dist/contradiction/index.d.ts +1 -1
  122. package/dist/contradiction/index.js +3 -3
  123. package/dist/convergence-refresh.d.ts +1 -1
  124. package/dist/convergence-refresh.js +14 -14
  125. package/dist/corpus-watermark.js +14 -14
  126. package/dist/entity-retrieval.d.ts +1 -1
  127. package/dist/entity-retrieval.js +12 -12
  128. package/dist/explicit-capture.d.ts +4 -4
  129. package/dist/explicit-capture.js +8 -8
  130. package/dist/external-wiki-access.d.ts +5 -5
  131. package/dist/external-wiki-access.js +23 -23
  132. package/dist/external-wiki-mcp-tools.d.ts +5 -5
  133. package/dist/extraction-normalization.js +2 -2
  134. package/dist/extraction-source-grounding.js +3 -3
  135. package/dist/extraction.js +16 -16
  136. package/dist/{forget-HDS4N6CG.js → forget-EHGGJZLX.js} +3 -2
  137. package/dist/{forget-HDS4N6CG.js.map → forget-EHGGJZLX.js.map} +1 -1
  138. package/dist/harmonic-construction.js +2 -2
  139. package/dist/harmonic-retrieval.js +4 -4
  140. package/dist/index.d.ts +12 -12
  141. package/dist/index.js +66 -66
  142. package/dist/maintenance/memory-governance.js +12 -12
  143. package/dist/maintenance/rebuild-memory-lifecycle-ledger.d.ts +1 -1
  144. package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +12 -12
  145. package/dist/maintenance/rebuild-memory-projection.d.ts +1 -1
  146. package/dist/maintenance/rebuild-memory-projection.js +13 -13
  147. package/dist/{maintenance-kBII2qUI.d.ts → maintenance-CN_Ct-Hr.d.ts} +1 -1
  148. package/dist/mcp-memory-inspector-app.d.ts +5 -5
  149. package/dist/memory-worth-outcomes.d.ts +1 -1
  150. package/dist/namespaces/migrate.d.ts +1 -1
  151. package/dist/namespaces/migrate.js +13 -13
  152. package/dist/namespaces/storage.d.ts +1 -1
  153. package/dist/namespaces/storage.js +12 -12
  154. package/dist/operator-doctor-corpus.js +15 -15
  155. package/dist/operator-toolkit.d.ts +1 -1
  156. package/dist/operator-toolkit.js +20 -20
  157. package/dist/orchestration/compression-guideline-coordinator.d.ts +1 -1
  158. package/dist/orchestration/maintenance.d.ts +2 -2
  159. package/dist/orchestration/maintenance.js +17 -17
  160. package/dist/{orchestrator-DHy5LtTy.d.ts → orchestrator-B3Xe8qu0.d.ts} +3 -3
  161. package/dist/orchestrator.d.ts +4 -4
  162. package/dist/orchestrator.js +54 -54
  163. package/dist/provenance.js +2 -2
  164. package/dist/reconcile/manifest.d.ts +3 -1
  165. package/dist/reconcile/manifest.js +7 -3
  166. package/dist/{resolution-MDPNOEG6.js → resolution-MMOD5WC4.js} +4 -4
  167. package/dist/resume-bundles.js +5 -5
  168. package/dist/salvage-envelope.js +3 -3
  169. package/dist/schemas.d.ts +38 -38
  170. package/dist/semantic-consolidation.js +13 -13
  171. package/dist/semantic-rule-promotion.js +12 -12
  172. package/dist/semantic-rule-verifier.js +12 -12
  173. package/dist/{service-PT2_z7xm.d.ts → service-CQGEqL1g.d.ts} +1 -1
  174. package/dist/source-agent-qualifier.js +14 -14
  175. package/dist/{storage-BmVudhk4.d.ts → storage-DmkCjo0l.d.ts} +36 -32
  176. package/dist/storage.d.ts +1 -1
  177. package/dist/storage.js +14 -14
  178. package/dist/structured-attributes.d.ts +3 -1
  179. package/dist/structured-attributes.js +3 -1
  180. package/dist/tier-migration.d.ts +1 -1
  181. package/dist/transfer/types.d.ts +12 -12
  182. package/dist/verified-recall.js +12 -12
  183. package/dist/write-envelope.js +2 -2
  184. package/package.json +2 -2
  185. package/src/access-offline-manifest.ts +1 -0
  186. package/src/access-service-offline-file-content.test.ts +1 -0
  187. package/src/content-hash.ts +34 -1
  188. package/src/dedup/index.test.ts +56 -38
  189. package/src/dedup/index.ts +85 -38
  190. package/src/lifecycle/tombstones.test.ts +763 -16
  191. package/src/lifecycle/tombstones.ts +217 -50
  192. package/src/reconcile/manifest.test.ts +158 -1
  193. package/src/reconcile/manifest.ts +76 -22
  194. package/src/storage/content-hash-index.ts +56 -40
  195. package/src/storage/entity-ref-repair.ts +3 -3
  196. package/src/storage/memory-read-store.ts +9 -0
  197. package/src/storage/tombstone-blocked-capture-index.ts +4 -9
  198. package/src/storage-contract/content-hash.test.ts +13 -0
  199. package/src/storage-fact-hash-index-defer.test.ts +1 -1
  200. package/src/storage.ts +107 -93
  201. package/src/structured-attributes.ts +11 -0
  202. package/dist/chunk-BQIFZ2MP.js.map +0 -1
  203. package/dist/chunk-DLG5V6ND.js.map +0 -1
  204. package/dist/chunk-JJRVPWFY.js.map +0 -1
  205. package/dist/chunk-R3DJVOSM.js.map +0 -1
  206. package/dist/chunk-VHFXP27N.js.map +0 -1
  207. package/dist/chunk-W5H6ZRPT.js +0 -14
  208. package/dist/chunk-W5H6ZRPT.js.map +0 -1
  209. package/dist/chunk-WD5B5AIE.js.map +0 -1
  210. /package/dist/{auto-sync-QFQGOEO2.js.map → auto-sync-JXEW7444.js.map} +0 -0
  211. /package/dist/{chunk-MLXZD4WM.js.map → chunk-2GFK22OZ.js.map} +0 -0
  212. /package/dist/{chunk-D5YOZ66N.js.map → chunk-3DWTDC54.js.map} +0 -0
  213. /package/dist/{chunk-RANKJI5T.js.map → chunk-3JLPGJ43.js.map} +0 -0
  214. /package/dist/{chunk-GXSOUIA4.js.map → chunk-5WQMORU5.js.map} +0 -0
  215. /package/dist/{chunk-J272WKX2.js.map → chunk-6HPHEFXT.js.map} +0 -0
  216. /package/dist/{chunk-B6LZ4IBH.js.map → chunk-6LAPLZOS.js.map} +0 -0
  217. /package/dist/{chunk-VXCNQYTC.js.map → chunk-APPFNGX6.js.map} +0 -0
  218. /package/dist/{chunk-GXFSQ6SD.js.map → chunk-C4ZNGVK3.js.map} +0 -0
  219. /package/dist/{chunk-JSEJH2YQ.js.map → chunk-CGVHPCP2.js.map} +0 -0
  220. /package/dist/{chunk-TEMSWHQD.js.map → chunk-DZD576ZU.js.map} +0 -0
  221. /package/dist/{chunk-AMTEYNYK.js.map → chunk-EVWWT3KE.js.map} +0 -0
  222. /package/dist/{chunk-XAPMEU3K.js.map → chunk-FHFERRYE.js.map} +0 -0
  223. /package/dist/{chunk-AXIDQG5A.js.map → chunk-G4T7L557.js.map} +0 -0
  224. /package/dist/{chunk-7M6NYQWB.js.map → chunk-G7Z6EENO.js.map} +0 -0
  225. /package/dist/{chunk-YXKVLYXU.js.map → chunk-HWRD23DW.js.map} +0 -0
  226. /package/dist/{chunk-SOEN6KCF.js.map → chunk-IBKR2TFR.js.map} +0 -0
  227. /package/dist/{chunk-F4EU6VUC.js.map → chunk-JDZPNCMA.js.map} +0 -0
  228. /package/dist/{chunk-BAKXNQ3C.js.map → chunk-KRTY3ENI.js.map} +0 -0
  229. /package/dist/{chunk-NK5RGJ65.js.map → chunk-L4DLLKPN.js.map} +0 -0
  230. /package/dist/{chunk-VFSAWRMZ.js.map → chunk-LJS7KUPV.js.map} +0 -0
  231. /package/dist/{chunk-NOEMGE56.js.map → chunk-NH5IR2WM.js.map} +0 -0
  232. /package/dist/{chunk-5V6IXHPI.js.map → chunk-NSVDUN76.js.map} +0 -0
  233. /package/dist/{chunk-IARXXZU4.js.map → chunk-P7XI2AQ3.js.map} +0 -0
  234. /package/dist/{chunk-W7Y5JDVB.js.map → chunk-PDDTI7IC.js.map} +0 -0
  235. /package/dist/{chunk-QINJITDS.js.map → chunk-PX2LSHYM.js.map} +0 -0
  236. /package/dist/{chunk-O5FFBO2M.js.map → chunk-QAVH6X2A.js.map} +0 -0
  237. /package/dist/{chunk-HDIUYMSB.js.map → chunk-QWGE2PUH.js.map} +0 -0
  238. /package/dist/{chunk-X57M2Q4I.js.map → chunk-RDJRFDJY.js.map} +0 -0
  239. /package/dist/{chunk-RYLEDKQ2.js.map → chunk-RUMNY5DT.js.map} +0 -0
  240. /package/dist/{chunk-BKIFTXHR.js.map → chunk-RZ4XGKOJ.js.map} +0 -0
  241. /package/dist/{chunk-66SE3BNH.js.map → chunk-SUPEYLH2.js.map} +0 -0
  242. /package/dist/{chunk-BF6SXNQJ.js.map → chunk-T2JHZWH6.js.map} +0 -0
  243. /package/dist/{chunk-QPOHMO6M.js.map → chunk-UES4BHA2.js.map} +0 -0
  244. /package/dist/{chunk-X7XEDOSL.js.map → chunk-VLL43QEQ.js.map} +0 -0
  245. /package/dist/{chunk-QBSFOKY6.js.map → chunk-VOELBUS4.js.map} +0 -0
  246. /package/dist/{chunk-2LY6GIIE.js.map → chunk-WDBX6VER.js.map} +0 -0
  247. /package/dist/{chunk-BMEYMKWX.js.map → chunk-WM65LU67.js.map} +0 -0
  248. /package/dist/{chunk-WLRD2TXF.js.map → chunk-WOPA3SGI.js.map} +0 -0
  249. /package/dist/{chunk-5UZ6UCOZ.js.map → chunk-XUAMCLY4.js.map} +0 -0
  250. /package/dist/{chunk-7HXZJAID.js.map → chunk-YGLY6QFV.js.map} +0 -0
  251. /package/dist/{chunk-2OIQDTDC.js.map → chunk-ZPJ4QSGP.js.map} +0 -0
  252. /package/dist/{chunk-3OY7KXHM.js.map → chunk-ZYLC7NFO.js.map} +0 -0
  253. /package/dist/{resolution-MDPNOEG6.js.map → resolution-MMOD5WC4.js.map} +0 -0
@@ -290,10 +290,14 @@ interface TombstoneEntry {
290
290
  reason: TombstoneReason;
291
291
  /** The memory that was retired. */
292
292
  sourceMemoryId: string;
293
- /** sha256 of the retired memory's rawContent (rule 23). */
293
+ /** sha256 of the retired identity; explicit contentHashSource may define it. */
294
294
  contentHash: string;
295
- /** `ContentHashIndex.normalizeContent(rawContent)` the pre-hash form. */
295
+ /** Current body hash retained alongside an ambiguous or explicit primary hash. */
296
+ currentContentHashAlias?: string;
297
+ /** Current normalized body used by the normalized lookup tier. */
296
298
  normalizedText: string;
299
+ /** Version of the current body identity fields. */
300
+ normalizerVersion?: number;
297
301
  entityRef?: string;
298
302
  /** Structured-attribute supersession key when one existed. */
299
303
  supersessionKey?: string;
@@ -327,7 +331,7 @@ interface TombstoneLookupQuery {
327
331
  /** Single supersession key (direct/unit callers). */
328
332
  supersessionKey?: string;
329
333
  /** All derived supersession keys (write chokepoint). The keyed tier is
330
- * checked for each; the first active match wins. */
334
+ * checked for each; the first active match wins. */
331
335
  supersessionKeys?: string[];
332
336
  namespace: string;
333
337
  }
@@ -346,6 +350,13 @@ interface TombstoneStoreOptions {
346
350
  * skipped entirely.
347
351
  */
348
352
  semanticSimilarity?: (a: string, b: string) => number;
353
+ /**
354
+ * Resolve source identity candidates for bounded legacy sourceMemoryIds.
355
+ * StorageManager reads the corpus before the tombstone write lock.
356
+ */
357
+ readonly sourceContentsForMemoryIds?: (sourceMemoryIds: readonly string[]) => Promise<ReadonlyMap<string, string | readonly string[]>>;
358
+ /** Maximum number of legacy entries considered during one load. */
359
+ readonly legacyMigrationLimit?: number;
349
360
  /**
350
361
  * Cross-process write-lock timings for the tombstone JSONL mutation lock
351
362
  * (issue #1639). The secure-store append is a read-merge-write (read
@@ -437,6 +448,13 @@ declare class TombstoneStore {
437
448
  /** Lazy load + build the in-memory index. Idempotent. */
438
449
  load(): Promise<void>;
439
450
  private loadInternal;
451
+ private parseEntries;
452
+ /**
453
+ * Re-index pre-Unicode records from retired source content.
454
+ * Source reads stay bounded, but every batch finishes before the store
455
+ * publishes its in-memory index. Missing sources remain eligible on restart.
456
+ */
457
+ private migrateLegacyEntries;
440
458
  private resetIndex;
441
459
  /**
442
460
  * Index an entry. Later entries (higher array index = newer createdAt)
@@ -448,15 +466,13 @@ declare class TombstoneStore {
448
466
  invalidate(): void;
449
467
  /**
450
468
  * Cross-process staleness probe (#1579). If the file's mtime advanced since
451
- * the last load/own-write, a peer process appended and our in-memory index is
452
- * stale — invalidate AND reload in place so the next lookup sees the new
453
- * entries. Best-effort: a stat error is swallowed (the loaded index is
454
- * retained rather than crashing the write path). No-op when no `stat` was
455
- * injected or when the mtime is unchanged. After our own append/revoke/rebuild
456
- * `markWritten` records the new mtime, so this probe does not fire for our
457
- * own writes (only for peer-process appends).
469
+ * the last load/own-write, a peer process appended and our in-memory index
470
+ * is stale — invalidate and reload before the next access. Mutation callers
471
+ * pass `migrate: false` to avoid nested serializer/lock acquisition.
458
472
  */
459
- ensureFreshAgainstDisk(): Promise<void>;
473
+ ensureFreshAgainstDisk(options?: {
474
+ migrate?: boolean;
475
+ }): Promise<void>;
460
476
  /**
461
477
  * Record the file mtime after THIS process writes (append / revoke / rebuild)
462
478
  * so `ensureFreshAgainstDisk` does not treat our own write as a peer append
@@ -497,6 +513,7 @@ declare class TombstoneStore {
497
513
  */
498
514
  revoke(tombstoneId: string, createdBy: TombstoneCreatedBy): Promise<string>;
499
515
  private serializeAppend;
516
+ private migrateLoadedLegacyEntries;
500
517
  /**
501
518
  * Acquire the cross-process tombstone write lock and run `task` under it
502
519
  * (issue #1639). Strict-fail on acquisition timeout: the secure-store append
@@ -592,6 +609,8 @@ interface ContentHashPathEntry {
592
609
  declare class ContentHashIndex {
593
610
  private hashes;
594
611
  private dirty;
612
+ /** True when load() found the pre-Unicode, markerless on-disk format. */
613
+ private formatMigrationRequired;
595
614
  /**
596
615
  * Hashes explicitly removed since the last successful save (issue #1909 review
597
616
  * round 8 thread 3). Tracked separately so `saveMergingWithDisk` can be
@@ -635,6 +654,8 @@ declare class ContentHashIndex {
635
654
  */
636
655
  private lastSyncedFingerprint;
637
656
  constructor(stateDir: string, secureStoreKeyProvider?: () => Buffer | null, secureStoreWriteKeyProvider?: () => Buffer | null, memoryDir?: string, lockOptions?: ContentHashIndexLockOptions);
657
+ /** Whether a caller with an authoritative corpus must rebuild this legacy index. */
658
+ get requiresFormatMigration(): boolean;
638
659
  /** Load existing hashes from disk. Safe to call multiple times. */
639
660
  load(): Promise<void>;
640
661
  /** Cheap `stat` of the durable index file; null when it does not exist yet. */
@@ -1646,26 +1667,9 @@ declare class StorageManager extends TombstoneBlockedCaptureIndexHost {
1646
1667
  * method itself never writes to disk.
1647
1668
  */
1648
1669
  private rebuildFactHashIndexFromCorpus;
1649
- /**
1650
- * The content-hash the corpus rebuild registers for `memory`, or null when the
1651
- * body carries a citation from an unknown/custom template that cannot be
1652
- * safely stripped. Shared by the authoritative rebuild and the fact-only
1653
- * corpus confirmation (`hasFactContentHash`) so both derive the identical
1654
- * hash for a given stored body.
1655
- *
1656
- * Preference order:
1657
- * 1. frontmatter.contentHash — the raw pre-citation hash writeMemory records
1658
- * for facts (issue #369 round 8); matches hasFactContentHash(rawFact).
1659
- * 2. Reconstruct from the stored body: strip the "[Attributes: …]" suffix
1660
- * writeMemory appends for structuredAttributes (registration hashed the
1661
- * raw canonical content WITHOUT it — a no-op when absent), then strip a
1662
- * recognised citation and hash the bare body, or hash a citation-free
1663
- * body as-is.
1664
- * 3. A body with a citation from an unknown/custom template → null: a
1665
- * false-negative miss beats a wrong hash that would permanently suppress
1666
- * legitimate duplicate writes.
1667
- */
1668
- private corpusRegisteredHash;
1670
+ private storedContentIdentityCandidates;
1671
+ /** Returns the current body identity while preserving explicit external identities. */
1672
+ private corpusRegisteredHashes;
1669
1673
  private get questionsDir();
1670
1674
  private get artifactsDir();
1671
1675
  private get identityDir();
@@ -1781,7 +1785,7 @@ declare class StorageManager extends TombstoneBlockedCaptureIndexHost {
1781
1785
  writeSealedMemory(envelope: SealedMemoryEnvelope, extras?: SealedWriteExtras): Promise<MemoryWriteResult>;
1782
1786
  hasFactContentHash(content: string): Promise<boolean>;
1783
1787
  private factContentHashPresentInCorpus;
1784
- private factContentHashForRemoval;
1788
+ private factContentHashesForRemoval;
1785
1789
  private addActiveFactContentHash;
1786
1790
  /**
1787
1791
  * Re-register a fact's contentHash in the dedup index after a tombstone
package/dist/storage.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import './types-CkiDUorT.js';
2
2
  import './corpus-read-cancellation.js';
3
3
  import './artifact-search.js';
4
- export { C as ContentHashIndex, t as ContentHashIndexLockOptions, u as FactHashIndexNotAuthoritativeError, v as MemoryLifecycleEventWriteOptions, M as MemoryWriteResult, R as ReextractJobRequest, w as SealedWriteExtras, S as StorageManager, W as WriteMemoryOptions, x as buildEntitySchemaCacheKey, y as compareEntityTimestamps, z as compileEntityFacts, A as countEntityStructuredFacts, B as fingerprintEntityStructuredFacts, D as isEntitySynthesisStale, E as isValidBufferSurpriseEvent, G as normalizeFrontmatterForPath, H as normalizeStructuredSectionFacts, p as parseEntityFile, I as parseFrontmatter, J as parseFrontmatterStringValue, s as serializeEntityFile } from './storage-BmVudhk4.js';
4
+ export { C as ContentHashIndex, t as ContentHashIndexLockOptions, u as FactHashIndexNotAuthoritativeError, v as MemoryLifecycleEventWriteOptions, M as MemoryWriteResult, R as ReextractJobRequest, w as SealedWriteExtras, S as StorageManager, W as WriteMemoryOptions, x as buildEntitySchemaCacheKey, y as compareEntityTimestamps, z as compileEntityFacts, A as countEntityStructuredFacts, B as fingerprintEntityStructuredFacts, D as isEntitySynthesisStale, E as isValidBufferSurpriseEvent, G as normalizeFrontmatterForPath, H as normalizeStructuredSectionFacts, p as parseEntityFile, I as parseFrontmatter, J as parseFrontmatterStringValue, s as serializeEntityFile } from './storage-DmkCjo0l.js';
5
5
  export { normalizeEntityName } from './entity-id-normalization.js';
6
6
  import './page-versioning.js';
7
7
  import './consolidation-operator.js';
package/dist/storage.js CHANGED
@@ -13,12 +13,11 @@ import {
13
13
  parseFrontmatter,
14
14
  parseFrontmatterStringValue,
15
15
  serializeEntityFile
16
- } from "./chunk-WD5B5AIE.js";
16
+ } from "./chunk-6R4G7N4S.js";
17
17
  import "./chunk-UHVXE24Y.js";
18
- import "./chunk-JJRVPWFY.js";
18
+ import "./chunk-MLPTFODV.js";
19
19
  import "./chunk-SXIMQLGV.js";
20
20
  import "./chunk-S2V7RUCL.js";
21
- import "./chunk-J6A3CX5N.js";
22
21
  import "./chunk-UU3G6GUP.js";
23
22
  import "./chunk-SCU65EZI.js";
24
23
  import "./chunk-WRJVB3C2.js";
@@ -27,13 +26,9 @@ import "./chunk-DM2T26WE.js";
27
26
  import "./chunk-LDXUBPMO.js";
28
27
  import "./chunk-JYIJNNN3.js";
29
28
  import "./chunk-AAD2ZS6P.js";
30
- import "./chunk-DLG5V6ND.js";
31
- import "./chunk-5V6IXHPI.js";
29
+ import "./chunk-K4QS7LXU.js";
30
+ import "./chunk-NSVDUN76.js";
32
31
  import "./chunk-BT7NVCML.js";
33
- import {
34
- normalizeAttributePairs,
35
- stripAttributesSuffix
36
- } from "./chunk-R3DJVOSM.js";
37
32
  import {
38
33
  normalizeEntityName
39
34
  } from "./chunk-YXZG5TVN.js";
@@ -53,18 +48,17 @@ import "./chunk-RGMVMVMF.js";
53
48
  import "./chunk-N7RWREIQ.js";
54
49
  import "./chunk-XMMESCPD.js";
55
50
  import "./chunk-42JKGUFJ.js";
56
- import "./chunk-QBSFOKY6.js";
57
- import "./chunk-5GKPFL3S.js";
51
+ import "./chunk-VOELBUS4.js";
58
52
  import "./chunk-ZIKENA2C.js";
59
53
  import "./chunk-PQVCH2AE.js";
60
54
  import "./chunk-6MKAMLQL.js";
61
- import "./chunk-JAKIDQB2.js";
62
55
  import "./chunk-W5BIQOAJ.js";
63
56
  import "./chunk-Z7L72ZHL.js";
64
57
  import "./chunk-4O4TKIXQ.js";
65
58
  import "./chunk-BJMBJZ2Y.js";
66
59
  import "./chunk-UKJAGEXH.js";
67
60
  import "./chunk-FP2373TW.js";
61
+ import "./chunk-J6A3CX5N.js";
68
62
  import "./chunk-7FGL6CGA.js";
69
63
  import "./chunk-EGGE52UU.js";
70
64
  import "./chunk-ZA53AZND.js";
@@ -73,11 +67,17 @@ import "./chunk-7RJUVTVF.js";
73
67
  import {
74
68
  ContentHashIndex,
75
69
  FactHashIndexNotAuthoritativeError
76
- } from "./chunk-BQIFZ2MP.js";
70
+ } from "./chunk-CX5SW2EW.js";
77
71
  import "./chunk-3CSX3ETE.js";
78
72
  import "./chunk-RL6NEVXT.js";
73
+ import {
74
+ normalizeAttributePairs,
75
+ stripAttributesSuffix
76
+ } from "./chunk-JDSVLVCG.js";
79
77
  import "./chunk-FFAZ2XFU.js";
80
- import "./chunk-W5H6ZRPT.js";
78
+ import "./chunk-5FVVY2EB.js";
79
+ import "./chunk-5GKPFL3S.js";
80
+ import "./chunk-JAKIDQB2.js";
81
81
  import "./chunk-P7FMDTKL.js";
82
82
  import "./chunk-2XRE6FLM.js";
83
83
  import "./chunk-EYIEWJNI.js";
@@ -25,6 +25,8 @@
25
25
  * returns the trimmed input unchanged — never truncates legitimate content.
26
26
  */
27
27
  declare function stripAttributesSuffix(content: string): string;
28
+ /** Return stored-body identity forms, from raw semantic content to full body. */
29
+ declare function storedContentIdentityCandidates(content: string, stripCitation: (value: string) => string): string[];
28
30
  /**
29
31
  * Canonical `key: value; …` rendering of structured attributes. Keys are
30
32
  * trimmed + lowercased, values trimmed, pairs sorted by normalized key.
@@ -48,4 +50,4 @@ interface AssembledPersistBody {
48
50
  */
49
51
  declare function assemblePersistedBody(content: string, structuredAttributes?: Record<string, string>): AssembledPersistBody;
50
52
 
51
- export { type AssembledPersistBody, assemblePersistedBody, normalizeAttributePairs, stripAttributesSuffix };
53
+ export { type AssembledPersistBody, assemblePersistedBody, normalizeAttributePairs, storedContentIdentityCandidates, stripAttributesSuffix };
@@ -1,14 +1,16 @@
1
1
  import {
2
2
  assemblePersistedBody,
3
3
  normalizeAttributePairs,
4
+ storedContentIdentityCandidates,
4
5
  stripAttributesSuffix
5
- } from "./chunk-R3DJVOSM.js";
6
+ } from "./chunk-JDSVLVCG.js";
6
7
  import "./chunk-5GKPFL3S.js";
7
8
  import "./chunk-JAKIDQB2.js";
8
9
  import "./chunk-PZ5AY32C.js";
9
10
  export {
10
11
  assemblePersistedBody,
11
12
  normalizeAttributePairs,
13
+ storedContentIdentityCandidates,
12
14
  stripAttributesSuffix
13
15
  };
14
16
  //# sourceMappingURL=structured-attributes.js.map
@@ -1,5 +1,5 @@
1
1
  import { M as MemoryFile } from './types-CkiDUorT.js';
2
- import { S as StorageManager } from './storage-BmVudhk4.js';
2
+ import { S as StorageManager } from './storage-DmkCjo0l.js';
3
3
  import { MemoryTier } from './tier-routing.js';
4
4
  import { SearchBackend } from './search/port.js';
5
5
  import './message-parts/index.js';
@@ -313,13 +313,13 @@ declare const CapsuleBlockSchema: z.ZodObject<{
313
313
  peerProfiles: boolean;
314
314
  }>;
315
315
  }, "strip", z.ZodTypeAny, {
316
- schemaVersion: string;
317
316
  includes: {
318
317
  procedural: boolean;
319
318
  taxonomy: boolean;
320
319
  identityAnchors: boolean;
321
320
  peerProfiles: boolean;
322
321
  };
322
+ schemaVersion: string;
323
323
  id: string;
324
324
  version: string;
325
325
  description: string;
@@ -334,13 +334,13 @@ declare const CapsuleBlockSchema: z.ZodObject<{
334
334
  directAnswerEnabled: boolean;
335
335
  };
336
336
  }, {
337
- schemaVersion: string;
338
337
  includes: {
339
338
  procedural: boolean;
340
339
  taxonomy: boolean;
341
340
  identityAnchors: boolean;
342
341
  peerProfiles: boolean;
343
342
  };
343
+ schemaVersion: string;
344
344
  id: string;
345
345
  version: string;
346
346
  description: string;
@@ -464,13 +464,13 @@ declare const ExportManifestV2Schema: z.ZodObject<{
464
464
  peerProfiles: boolean;
465
465
  }>;
466
466
  }, "strip", z.ZodTypeAny, {
467
- schemaVersion: string;
468
467
  includes: {
469
468
  procedural: boolean;
470
469
  taxonomy: boolean;
471
470
  identityAnchors: boolean;
472
471
  peerProfiles: boolean;
473
472
  };
473
+ schemaVersion: string;
474
474
  id: string;
475
475
  version: string;
476
476
  description: string;
@@ -485,13 +485,13 @@ declare const ExportManifestV2Schema: z.ZodObject<{
485
485
  directAnswerEnabled: boolean;
486
486
  };
487
487
  }, {
488
- schemaVersion: string;
489
488
  includes: {
490
489
  procedural: boolean;
491
490
  taxonomy: boolean;
492
491
  identityAnchors: boolean;
493
492
  peerProfiles: boolean;
494
493
  };
494
+ schemaVersion: string;
495
495
  id: string;
496
496
  version: string;
497
497
  description: string;
@@ -518,13 +518,13 @@ declare const ExportManifestV2Schema: z.ZodObject<{
518
518
  pluginVersion: string;
519
519
  includesTranscripts: boolean;
520
520
  capsule: {
521
- schemaVersion: string;
522
521
  includes: {
523
522
  procedural: boolean;
524
523
  taxonomy: boolean;
525
524
  identityAnchors: boolean;
526
525
  peerProfiles: boolean;
527
526
  };
527
+ schemaVersion: string;
528
528
  id: string;
529
529
  version: string;
530
530
  description: string;
@@ -551,13 +551,13 @@ declare const ExportManifestV2Schema: z.ZodObject<{
551
551
  pluginVersion: string;
552
552
  includesTranscripts: boolean;
553
553
  capsule: {
554
- schemaVersion: string;
555
554
  includes: {
556
555
  procedural: boolean;
557
556
  taxonomy: boolean;
558
557
  identityAnchors: boolean;
559
558
  peerProfiles: boolean;
560
559
  };
560
+ schemaVersion: string;
561
561
  id: string;
562
562
  version: string;
563
563
  description: string;
@@ -683,13 +683,13 @@ declare const ExportBundleV2Schema: z.ZodObject<{
683
683
  peerProfiles: boolean;
684
684
  }>;
685
685
  }, "strip", z.ZodTypeAny, {
686
- schemaVersion: string;
687
686
  includes: {
688
687
  procedural: boolean;
689
688
  taxonomy: boolean;
690
689
  identityAnchors: boolean;
691
690
  peerProfiles: boolean;
692
691
  };
692
+ schemaVersion: string;
693
693
  id: string;
694
694
  version: string;
695
695
  description: string;
@@ -704,13 +704,13 @@ declare const ExportBundleV2Schema: z.ZodObject<{
704
704
  directAnswerEnabled: boolean;
705
705
  };
706
706
  }, {
707
- schemaVersion: string;
708
707
  includes: {
709
708
  procedural: boolean;
710
709
  taxonomy: boolean;
711
710
  identityAnchors: boolean;
712
711
  peerProfiles: boolean;
713
712
  };
713
+ schemaVersion: string;
714
714
  id: string;
715
715
  version: string;
716
716
  description: string;
@@ -737,13 +737,13 @@ declare const ExportBundleV2Schema: z.ZodObject<{
737
737
  pluginVersion: string;
738
738
  includesTranscripts: boolean;
739
739
  capsule: {
740
- schemaVersion: string;
741
740
  includes: {
742
741
  procedural: boolean;
743
742
  taxonomy: boolean;
744
743
  identityAnchors: boolean;
745
744
  peerProfiles: boolean;
746
745
  };
746
+ schemaVersion: string;
747
747
  id: string;
748
748
  version: string;
749
749
  description: string;
@@ -770,13 +770,13 @@ declare const ExportBundleV2Schema: z.ZodObject<{
770
770
  pluginVersion: string;
771
771
  includesTranscripts: boolean;
772
772
  capsule: {
773
- schemaVersion: string;
774
773
  includes: {
775
774
  procedural: boolean;
776
775
  taxonomy: boolean;
777
776
  identityAnchors: boolean;
778
777
  peerProfiles: boolean;
779
778
  };
779
+ schemaVersion: string;
780
780
  id: string;
781
781
  version: string;
782
782
  description: string;
@@ -815,13 +815,13 @@ declare const ExportBundleV2Schema: z.ZodObject<{
815
815
  pluginVersion: string;
816
816
  includesTranscripts: boolean;
817
817
  capsule: {
818
- schemaVersion: string;
819
818
  includes: {
820
819
  procedural: boolean;
821
820
  taxonomy: boolean;
822
821
  identityAnchors: boolean;
823
822
  peerProfiles: boolean;
824
823
  };
824
+ schemaVersion: string;
825
825
  id: string;
826
826
  version: string;
827
827
  description: string;
@@ -854,13 +854,13 @@ declare const ExportBundleV2Schema: z.ZodObject<{
854
854
  pluginVersion: string;
855
855
  includesTranscripts: boolean;
856
856
  capsule: {
857
- schemaVersion: string;
858
857
  includes: {
859
858
  procedural: boolean;
860
859
  taxonomy: boolean;
861
860
  identityAnchors: boolean;
862
861
  peerProfiles: boolean;
863
862
  };
863
+ schemaVersion: string;
864
864
  id: string;
865
865
  version: string;
866
866
  description: string;
@@ -1,14 +1,13 @@
1
1
  import {
2
2
  compareVerifiedEpisodeResults,
3
3
  searchVerifiedEpisodes
4
- } from "./chunk-J272WKX2.js";
4
+ } from "./chunk-6HPHEFXT.js";
5
5
  import "./chunk-B7RWBZXW.js";
6
- import "./chunk-WD5B5AIE.js";
6
+ import "./chunk-6R4G7N4S.js";
7
7
  import "./chunk-UHVXE24Y.js";
8
- import "./chunk-JJRVPWFY.js";
8
+ import "./chunk-MLPTFODV.js";
9
9
  import "./chunk-SXIMQLGV.js";
10
10
  import "./chunk-S2V7RUCL.js";
11
- import "./chunk-J6A3CX5N.js";
12
11
  import "./chunk-UU3G6GUP.js";
13
12
  import "./chunk-SCU65EZI.js";
14
13
  import "./chunk-WRJVB3C2.js";
@@ -17,10 +16,9 @@ import "./chunk-DM2T26WE.js";
17
16
  import "./chunk-LDXUBPMO.js";
18
17
  import "./chunk-JYIJNNN3.js";
19
18
  import "./chunk-AAD2ZS6P.js";
20
- import "./chunk-DLG5V6ND.js";
21
- import "./chunk-5V6IXHPI.js";
19
+ import "./chunk-K4QS7LXU.js";
20
+ import "./chunk-NSVDUN76.js";
22
21
  import "./chunk-BT7NVCML.js";
23
- import "./chunk-R3DJVOSM.js";
24
22
  import "./chunk-YXZG5TVN.js";
25
23
  import "./chunk-5GINB5LC.js";
26
24
  import "./chunk-G7D6GZ5J.js";
@@ -38,12 +36,10 @@ import "./chunk-RGMVMVMF.js";
38
36
  import "./chunk-N7RWREIQ.js";
39
37
  import "./chunk-XMMESCPD.js";
40
38
  import "./chunk-42JKGUFJ.js";
41
- import "./chunk-QBSFOKY6.js";
42
- import "./chunk-5GKPFL3S.js";
39
+ import "./chunk-VOELBUS4.js";
43
40
  import "./chunk-ZIKENA2C.js";
44
41
  import "./chunk-PQVCH2AE.js";
45
42
  import "./chunk-6MKAMLQL.js";
46
- import "./chunk-JAKIDQB2.js";
47
43
  import "./chunk-W5BIQOAJ.js";
48
44
  import "./chunk-Z7L72ZHL.js";
49
45
  import "./chunk-4O4TKIXQ.js";
@@ -51,16 +47,20 @@ import "./chunk-ZBJMUXZH.js";
51
47
  import "./chunk-BJMBJZ2Y.js";
52
48
  import "./chunk-UKJAGEXH.js";
53
49
  import "./chunk-FP2373TW.js";
50
+ import "./chunk-J6A3CX5N.js";
54
51
  import "./chunk-7FGL6CGA.js";
55
52
  import "./chunk-EGGE52UU.js";
56
53
  import "./chunk-ZA53AZND.js";
57
54
  import "./chunk-GCASGYIO.js";
58
55
  import "./chunk-7RJUVTVF.js";
59
- import "./chunk-BQIFZ2MP.js";
56
+ import "./chunk-CX5SW2EW.js";
60
57
  import "./chunk-3CSX3ETE.js";
61
58
  import "./chunk-RL6NEVXT.js";
59
+ import "./chunk-JDSVLVCG.js";
62
60
  import "./chunk-FFAZ2XFU.js";
63
- import "./chunk-W5H6ZRPT.js";
61
+ import "./chunk-5FVVY2EB.js";
62
+ import "./chunk-5GKPFL3S.js";
63
+ import "./chunk-JAKIDQB2.js";
64
64
  import "./chunk-P7FMDTKL.js";
65
65
  import "./chunk-2XRE6FLM.js";
66
66
  import "./chunk-EYIEWJNI.js";
@@ -11,9 +11,9 @@ import {
11
11
  isMemoryCategory,
12
12
  isSealedMemoryEnvelope,
13
13
  sealedWriteToLegacyArgs
14
- } from "./chunk-5V6IXHPI.js";
14
+ } from "./chunk-NSVDUN76.js";
15
15
  import "./chunk-BT7NVCML.js";
16
- import "./chunk-R3DJVOSM.js";
16
+ import "./chunk-JDSVLVCG.js";
17
17
  import "./chunk-5GKPFL3S.js";
18
18
  import "./chunk-JAKIDQB2.js";
19
19
  import "./chunk-P7FMDTKL.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@remnic/core",
3
- "version": "9.52.1",
3
+ "version": "9.53.0",
4
4
  "description": "Framework-agnostic Remnic memory engine — orchestrator, storage, extraction, search, trust zones",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -3126,7 +3126,7 @@
3126
3126
  "core"
3127
3127
  ],
3128
3128
  "peerDependencies": {
3129
- "@remnic/coding-graph": "^9.52.1"
3129
+ "@remnic/coding-graph": "^9.53.0"
3130
3130
  },
3131
3131
  "peerDependenciesMeta": {
3132
3132
  "@remnic/coding-graph": {
@@ -47,6 +47,7 @@ export async function createOfflineSyncManifestStream(
47
47
  file,
48
48
  async ({ path }) => storage.readOfflineSyncFile(nodePath.join(storage.dir, path)),
49
49
  parseMemory,
50
+ orchestrator.config.inlineSourceAttributionFormat,
50
51
  );
51
52
  }
52
53
  })(),
@@ -194,6 +194,7 @@ test("offline manifest streams body-free active, archived, old, bad, and encrypt
194
194
  id: "active-memory",
195
195
  category: "fact",
196
196
  contentHash: activeContentHash,
197
+ normalizerVersion: 4,
197
198
  status: "active",
198
199
  });
199
200
  assert.notEqual(rowsByPath.get("facts/active.md")?.sha256, activeContentHash);
@@ -9,8 +9,18 @@
9
9
 
10
10
  import { createHash } from "node:crypto";
11
11
 
12
- /** Normalize content: lowercase, strip non-alphanumerics, collapse whitespace. */
12
+ /** Normalize content: preserve Unicode letters, marks, and numbers. */
13
13
  export function normalizeContent(content: string): string {
14
+ return content
15
+ .normalize("NFC")
16
+ .toLowerCase()
17
+ .replace(/[^\p{L}\p{M}\p{N}\s]/gu, " ")
18
+ .replace(/\s+/g, " ")
19
+ .trim();
20
+ }
21
+
22
+ /** Normalize content with the pre-Unicode ASCII-only normalizer. */
23
+ export function normalizeLegacyContent(content: string): string {
14
24
  return content
15
25
  .toLowerCase()
16
26
  .replace(/[^a-z0-9\s]/g, " ")
@@ -18,6 +28,29 @@ export function normalizeContent(content: string): string {
18
28
  .trim();
19
29
  }
20
30
 
31
+ /** Compute the SHA-256 hash used by pre-Unicode content indexes. */
32
+ export function computeLegacyContentHash(content: string): string {
33
+ return createHash("sha256").update(normalizeLegacyContent(content)).digest("hex");
34
+ }
35
+
36
+ /**
37
+ * Return true only when a persisted legacy hash can be proved to identify the
38
+ * same body under the current normalizer. Empty or changed legacy forms are
39
+ * ambiguous and must not be promoted to a current identity.
40
+ */
41
+ export function isUnambiguousLegacyContentHash(
42
+ content: string,
43
+ contentHash: string,
44
+ currentNormalizedText: string = normalizeContent(content),
45
+ ): boolean {
46
+ const legacyNormalizedText = normalizeLegacyContent(content);
47
+ return (
48
+ legacyNormalizedText.length > 0 &&
49
+ legacyNormalizedText === currentNormalizedText &&
50
+ createHash("sha256").update(legacyNormalizedText).digest("hex") === contentHash
51
+ );
52
+ }
53
+
21
54
  /** Normalize content and compute SHA-256 hash. */
22
55
  export function computeContentHash(content: string): string {
23
56
  return createHash("sha256").update(normalizeContent(content)).digest("hex");