@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
@@ -8,6 +8,7 @@ import { StorageManager, normalizeAttributePairs } from "./storage.js";
8
8
  import { sanitizeMemoryContent } from "./sanitize.js";
9
9
  import {
10
10
  applyTemporalSupersession,
11
+ applyTemporalSupersessionPrimaryMutation,
11
12
  computeSupersessionKey,
12
13
  lookupAttributeByNormalizedKey,
13
14
  normalizeSupersessionKey,
@@ -15,8 +16,10 @@ import {
15
16
  shouldSupersedeExisting,
16
17
  supersessionKeysForFact,
17
18
  } from "./temporal-supersession.js";
18
- import type { MemoryFrontmatter } from "./types.js";
19
-
19
+ import type { MemoryFile, MemoryFrontmatter, MemoryLifecycleEvent } from "./types.js";
20
+ import type { TemporalSupersessionStorage } from "./temporal-supersession.js";
21
+ import type { DependencyPropagationPreparationToken } from "./orchestration/dependency-propagation-delivery.js";
22
+ import type { PropagationEvent } from "./orchestration/dependency-propagation.js";
20
23
  const TEST_ENTITY = "project-x";
21
24
 
22
25
  async function makeStorage(prefix = "engram-temporal-supersession-"): Promise<{
@@ -324,6 +327,83 @@ test("applyTemporalSupersession: city update retires old fact, leaves unrelated
324
327
  }
325
328
  });
326
329
 
330
+ test("applyTemporalSupersession: invalid createdAt is a pre-mutation no-op", async () => {
331
+ let touched = false;
332
+ const storage = {
333
+ async readAllMemories(): Promise<MemoryFile[]> {
334
+ touched = true;
335
+ throw new Error("readAllMemories must not run");
336
+ },
337
+ async readAllColdMemories(): Promise<MemoryFile[]> {
338
+ touched = true;
339
+ throw new Error("readAllColdMemories must not run");
340
+ },
341
+ async readMemoryByPath(): Promise<MemoryFile | null> {
342
+ touched = true;
343
+ throw new Error("readMemoryByPath must not run");
344
+ },
345
+ async writeMemoryFrontmatter(): Promise<boolean> {
346
+ touched = true;
347
+ throw new Error("writeMemoryFrontmatter must not run");
348
+ },
349
+ async appendTombstone(): Promise<string | null> {
350
+ touched = true;
351
+ throw new Error("appendTombstone must not run");
352
+ },
353
+ async getMemoryTimeline(): Promise<MemoryLifecycleEvent[]> {
354
+ touched = true;
355
+ throw new Error("getMemoryTimeline must not run");
356
+ },
357
+ } as unknown as TemporalSupersessionStorage;
358
+
359
+ const result = await applyTemporalSupersession({
360
+ storage,
361
+ newMemoryId: "replacement",
362
+ entityRef: TEST_ENTITY,
363
+ structuredAttributes: { city: "NYC" },
364
+ createdAt: "not-a-timestamp",
365
+ enabled: true,
366
+ });
367
+
368
+ assert.deepEqual(result, { supersededIds: [], matchedKeys: [] });
369
+ assert.equal(touched, false, "invalid ordering input must not start mutation processing");
370
+ });
371
+ test("primary temporal replay refuses an adapter without a timeline", async () => {
372
+ const { storage, cleanup } = await makeStorage("engram-temporal-required-timeline-");
373
+ try {
374
+ const oldId = await writeFact(
375
+ storage,
376
+ "project X is based in Austin",
377
+ TEST_ENTITY,
378
+ { city: "Austin" },
379
+ );
380
+ const old = await storage.getMemoryById(oldId);
381
+ assert.ok(old);
382
+ const missingTimeline = new Proxy(storage, {
383
+ get(target, property, receiver) {
384
+ if (property === "getMemoryTimeline") return undefined;
385
+ return Reflect.get(target, property, receiver);
386
+ },
387
+ }) as unknown as TemporalSupersessionStorage;
388
+
389
+ const applied = await applyTemporalSupersessionPrimaryMutation({
390
+ storage: missingTimeline,
391
+ oldMemory: old,
392
+ replacementId: "replacement",
393
+ mutation: {
394
+ supersededAt: "2026-08-09T00:00:00.000Z",
395
+ matchedKeys: ["project-x::city"],
396
+ },
397
+ });
398
+
399
+ assert.equal(applied, false);
400
+ const unchanged = await storage.getMemoryById(oldId);
401
+ assert.equal(unchanged?.frontmatter.status ?? "active", "active");
402
+ } finally {
403
+ await cleanup();
404
+ }
405
+ });
406
+
327
407
  test("applyTemporalSupersession: retires child chunks with superseded parent", async () => {
328
408
  const { storage, cleanup } = await makeStorage("engram-temporal-chunk-expiry-");
329
409
  try {
@@ -2035,6 +2115,85 @@ test("applyTemporalSupersession: hash-dedup path — supersedes older conflictin
2035
2115
  await cleanup();
2036
2116
  }
2037
2117
  });
2118
+ test("applyTemporalSupersession: cold-only dedup replacement keeps propagation content replayable", async () => {
2119
+ const { storage, cleanup } = await makeStorage("engram-cold-dedup-replay-");
2120
+ try {
2121
+ const staleId = await writeFact(storage, "entity lives in Austin", TEST_ENTITY, { city: "Austin" });
2122
+ storage.invalidateAllMemoriesCacheForDir();
2123
+ const stale = (await storage.readAllMemories()).find((memory) => memory.frontmatter.id === staleId);
2124
+ assert.ok(stale);
2125
+ await storage.writeMemoryFrontmatter(stale, {
2126
+ created: "2026-01-01T00:00:00.000Z",
2127
+ updated: "2026-01-01T00:00:00.000Z",
2128
+ });
2129
+ storage.invalidateAllMemoriesCacheForDir();
2130
+ const replacementId = await writeFact(
2131
+ storage,
2132
+ "entity relocated to NYC",
2133
+ TEST_ENTITY,
2134
+ { city: "NYC" },
2135
+ );
2136
+ const coldPath = await migrateFactToCold(storage, replacementId);
2137
+ const coldReplacement = await storage.readMemoryByPath(coldPath);
2138
+ assert.ok(coldReplacement);
2139
+
2140
+ const preparedEvents: PropagationEvent[] = [];
2141
+ const delivery = {
2142
+ async prepare(event: PropagationEvent): Promise<DependencyPropagationPreparationToken> {
2143
+ preparedEvents.push(event);
2144
+ return {
2145
+ jobId: "cold-dedup-replay",
2146
+ revision: 0,
2147
+ ownsPreparedJob: true,
2148
+ reservationId: "cold-dedup-replay-reservation",
2149
+ };
2150
+ },
2151
+ async afterMutation(
2152
+ _token: DependencyPropagationPreparationToken | null,
2153
+ event: PropagationEvent,
2154
+ ): Promise<void> {
2155
+ preparedEvents.push(event);
2156
+ },
2157
+ async cancel(_token: DependencyPropagationPreparationToken | null): Promise<void> {},
2158
+ async deferPrepared(_token: DependencyPropagationPreparationToken | null): Promise<void> {},
2159
+ };
2160
+
2161
+ const result = await applyTemporalSupersession({
2162
+ storage,
2163
+ newMemoryId: replacementId,
2164
+ entityRef: TEST_ENTITY,
2165
+ structuredAttributes: { city: "NYC" },
2166
+ createdAt: new Date().toISOString(),
2167
+ enabled: true,
2168
+ namespaceScope: "default",
2169
+ dependencyPropagationDelivery: delivery,
2170
+ });
2171
+
2172
+ assert.deepEqual(result.supersededIds, [staleId]);
2173
+ assert.equal(preparedEvents.length, 2);
2174
+ for (const preparedEvent of preparedEvents) {
2175
+ assert.equal(preparedEvent.cause, "temporal_supersession");
2176
+ assert.equal(preparedEvent.namespaceScope, "default");
2177
+ assert.equal(preparedEvent.replacementId, replacementId);
2178
+ assert.equal(preparedEvent.replacementContent, coldReplacement.content);
2179
+ assert.equal(preparedEvent.oldMemory.frontmatter.id, staleId);
2180
+ assert.equal(preparedEvent.oldMemory.content, stale!.content);
2181
+ assert.ok(preparedEvent.temporalMutation, "temporal event must carry replay mutation data");
2182
+ assert.deepEqual(preparedEvent.temporalMutation?.matchedKeys, [`${TEST_ENTITY}::city`]);
2183
+ assert.ok(
2184
+ preparedEvent.temporalMutation &&
2185
+ Number.isFinite(Date.parse(preparedEvent.temporalMutation.supersededAt)),
2186
+ "temporal event must carry a durable supersededAt",
2187
+ );
2188
+ assert.ok(
2189
+ preparedEvent.temporalMutation?.invalidAt,
2190
+ "temporal event must carry the invalidation bound for replay",
2191
+ );
2192
+ }
2193
+ } finally {
2194
+ await cleanup();
2195
+ }
2196
+ });
2038
2197
 
2039
2198
  // ─── Regression: Finding UvU1 — hash-dedup path must use current write time ──
2040
2199
  //
@@ -2769,3 +2928,273 @@ test("normalizeAttributePairs + sanitizeMemoryContent: normalizedIncoming uses s
2769
2928
  await cleanup();
2770
2929
  }
2771
2930
  });
2931
+ function recoveryMemory(id: string, parentId?: string): MemoryFile {
2932
+ return {
2933
+ path: `/synthetic/temporal/${id}.md`,
2934
+ content: `content-${id}`,
2935
+ frontmatter: {
2936
+ id,
2937
+ category: "fact",
2938
+ created: "2026-01-01T00:00:00.000Z",
2939
+ updated: "2026-01-01T00:00:00.000Z",
2940
+ source: "synthetic",
2941
+ confidence: 0.9,
2942
+ confidenceTier: "explicit",
2943
+ tags: [],
2944
+ status: "active",
2945
+ ...(parentId ? { parentId } : {}),
2946
+ },
2947
+ } as MemoryFile;
2948
+ }
2949
+
2950
+ function recoveryStorage(options: {
2951
+ parent: MemoryFile;
2952
+ child: MemoryFile;
2953
+ replacement: MemoryFile;
2954
+ failHot?: boolean;
2955
+ failCold?: boolean;
2956
+ crashAfterParentWrite?: boolean;
2957
+ durableTombstones?: Set<string>;
2958
+ }) {
2959
+ const lifecycleEvents: Array<{
2960
+ eventId: string;
2961
+ memoryId: string;
2962
+ eventType: "superseded" | "updated";
2963
+ timestamp: string;
2964
+ actor: string;
2965
+ ruleVersion: string;
2966
+ relatedMemoryIds: string[];
2967
+ after: { status: "superseded" };
2968
+ correlationId?: string;
2969
+ }> = [];
2970
+ const durableTombstones = options.durableTombstones ?? new Set<string>();
2971
+ const tombstoneKeys = new Set<string>();
2972
+ const writes: string[] = [];
2973
+ const tombstones: string[] = [];
2974
+ let crashAfterParentWrite = options.crashAfterParentWrite ?? false;
2975
+ const storage = {
2976
+ async readAllMemories(): Promise<MemoryFile[]> {
2977
+ if (options.failHot) throw new Error("hot scan unavailable");
2978
+ return [options.parent, options.child, options.replacement];
2979
+ },
2980
+ async readAllColdMemories(): Promise<MemoryFile[]> {
2981
+ if (options.failCold) throw new Error("cold scan unavailable");
2982
+ return [];
2983
+ },
2984
+ async readMemoryByPath(memoryPath: string): Promise<MemoryFile | null> {
2985
+ return [options.parent, options.child, options.replacement].find(
2986
+ (memory) => memory.path === memoryPath,
2987
+ ) ?? null;
2988
+ },
2989
+ async writeMemoryFrontmatter(
2990
+ memory: MemoryFile,
2991
+ patch: Partial<MemoryFrontmatter>,
2992
+ lifecycle?: {
2993
+ actor?: string;
2994
+ reasonCode?: string;
2995
+ relatedMemoryIds?: string[];
2996
+ correlationId?: string;
2997
+ },
2998
+ ): Promise<boolean> {
2999
+ Object.assign(memory.frontmatter, patch);
3000
+ writes.push(memory.frontmatter.id);
3001
+ if (memory.frontmatter.id === options.parent.frontmatter.id && crashAfterParentWrite) {
3002
+ crashAfterParentWrite = false;
3003
+ throw new Error("crash after parent frontmatter write");
3004
+ }
3005
+ lifecycleEvents.push({
3006
+ eventId: `event-${lifecycleEvents.length + 1}`,
3007
+ memoryId: memory.frontmatter.id,
3008
+ eventType: memory.frontmatter.status === "superseded" ? "superseded" : "updated",
3009
+ timestamp: lifecycle?.reasonCode?.includes("structured")
3010
+ ? (memory.frontmatter.supersededAt ?? memory.frontmatter.updated)
3011
+ : memory.frontmatter.updated,
3012
+ actor: lifecycle?.actor ?? "synthetic",
3013
+ ruleVersion: "synthetic",
3014
+ relatedMemoryIds: lifecycle?.relatedMemoryIds ?? [],
3015
+ after: { status: "superseded" },
3016
+ ...(lifecycle?.correlationId ? { correlationId: lifecycle.correlationId } : {}),
3017
+ });
3018
+ return true;
3019
+ },
3020
+ async writeMemoryFrontmatterIfUnchanged(
3021
+ memory: MemoryFile,
3022
+ patch: Partial<MemoryFrontmatter>,
3023
+ lifecycle?: {
3024
+ actor?: string;
3025
+ reasonCode?: string;
3026
+ relatedMemoryIds?: string[];
3027
+ correlationId?: string;
3028
+ },
3029
+ ): Promise<boolean> {
3030
+ return storage.writeMemoryFrontmatter(memory, patch, lifecycle);
3031
+ },
3032
+ async appendTombstone(input: {
3033
+ sourceMemoryId: string;
3034
+ supersessionKey?: string;
3035
+ operationKey?: string;
3036
+ }): Promise<string> {
3037
+ const key = input.supersessionKey ?? "content";
3038
+ tombstoneKeys.add(key);
3039
+ durableTombstones.add(
3040
+ `${input.sourceMemoryId}|${key}|${input.operationKey ?? ""}`,
3041
+ );
3042
+ tombstones.push(key);
3043
+ return `tombstone-${tombstones.length}`;
3044
+ },
3045
+ async hasExactTombstone(input: {
3046
+ sourceMemoryId: string;
3047
+ supersessionKey?: string;
3048
+ operationKey?: string;
3049
+ }): Promise<boolean> {
3050
+ return durableTombstones.has(
3051
+ `${input.sourceMemoryId}|${input.supersessionKey ?? "content"}|${input.operationKey ?? ""}`,
3052
+ );
3053
+ },
3054
+ async getMemoryTimeline(memoryId: string) {
3055
+ return lifecycleEvents.filter((event) => event.memoryId === memoryId);
3056
+ },
3057
+ async getTombstoneStore() {
3058
+ return {
3059
+ lookup(query: { supersessionKey?: string }): unknown | null {
3060
+ return tombstoneKeys.has(query.supersessionKey ?? "content") ? { tombstoneId: "existing" } : null;
3061
+ },
3062
+ };
3063
+ },
3064
+ isTombstonesEnabled: () => true,
3065
+ };
3066
+ return { storage, lifecycleEvents, writes, tombstones };
3067
+ }
3068
+
3069
+ test("primary temporal recovery retains the prepared work when the hot scan fails", async () => {
3070
+ const parent = recoveryMemory("old");
3071
+ const child = recoveryMemory("old-child", "old");
3072
+ const replacement = recoveryMemory("new");
3073
+ const fixture = recoveryStorage({ parent, child, replacement, failHot: true });
3074
+ const mutation = {
3075
+ supersededAt: "2026-02-01T00:00:00.000Z",
3076
+ matchedKeys: ["project-x::city"],
3077
+ };
3078
+
3079
+ const completed = await applyTemporalSupersessionPrimaryMutation({
3080
+ storage: fixture.storage,
3081
+ oldMemory: parent,
3082
+ replacementId: replacement.frontmatter.id,
3083
+ mutation,
3084
+ });
3085
+
3086
+ assert.equal(completed, false);
3087
+ assert.equal(parent.frontmatter.status, "superseded");
3088
+ assert.equal(child.frontmatter.status, "active");
3089
+ assert.deepEqual(fixture.tombstones, []);
3090
+ });
3091
+
3092
+ test("primary temporal recovery retains the prepared work when the cold scan fails", async () => {
3093
+ const parent = recoveryMemory("old");
3094
+ const child = recoveryMemory("old-child", "old");
3095
+ const replacement = recoveryMemory("new");
3096
+ const fixture = recoveryStorage({ parent, child, replacement, failCold: true });
3097
+
3098
+ const completed = await applyTemporalSupersessionPrimaryMutation({
3099
+ storage: fixture.storage,
3100
+ oldMemory: parent,
3101
+ replacementId: replacement.frontmatter.id,
3102
+ mutation: {
3103
+ supersededAt: "2026-02-01T00:00:00.000Z",
3104
+ matchedKeys: ["project-x::city"],
3105
+ },
3106
+ });
3107
+
3108
+ assert.equal(completed, false);
3109
+ assert.equal(parent.frontmatter.status, "superseded");
3110
+ assert.equal(child.frontmatter.status, "active");
3111
+ assert.deepEqual(fixture.tombstones, []);
3112
+ });
3113
+
3114
+ test("primary temporal recovery replays the parent ledger, child expiry, and tombstone exactly once", async () => {
3115
+ const parent = recoveryMemory("old");
3116
+ const child = recoveryMemory("old-child", "old");
3117
+ const replacement = recoveryMemory("new");
3118
+ const fixture = recoveryStorage({
3119
+ parent,
3120
+ child,
3121
+ replacement,
3122
+ crashAfterParentWrite: true,
3123
+ });
3124
+ const mutation = {
3125
+ supersededAt: "2026-02-01T00:00:00.000Z",
3126
+ matchedKeys: ["project-x::city"],
3127
+ };
3128
+ const input = {
3129
+ storage: fixture.storage,
3130
+ oldMemory: parent,
3131
+ replacementId: replacement.frontmatter.id,
3132
+ mutation,
3133
+ };
3134
+
3135
+ await assert.rejects(applyTemporalSupersessionPrimaryMutation(input));
3136
+ assert.equal(parent.frontmatter.status, "superseded");
3137
+ assert.equal(child.frontmatter.status, "active");
3138
+
3139
+ assert.equal(await applyTemporalSupersessionPrimaryMutation(input), true);
3140
+ assert.equal(await applyTemporalSupersessionPrimaryMutation(input), true);
3141
+ assert.equal(child.frontmatter.status, "superseded");
3142
+ assert.deepEqual(fixture.writes, ["old", "old", "old-child"]);
3143
+ assert.deepEqual(fixture.tombstones, ["project-x::city"]);
3144
+ assert.equal(
3145
+ fixture.lifecycleEvents.filter((event) => event.memoryId === "old").length,
3146
+ 1,
3147
+ );
3148
+ assert.equal(
3149
+ fixture.lifecycleEvents.filter((event) => event.memoryId === "old-child").length,
3150
+ 1,
3151
+ );
3152
+ });
3153
+
3154
+ test("primary temporal recovery keeps both tombstones across a restart", async () => {
3155
+ const parent = recoveryMemory("old");
3156
+ parent.frontmatter.entityRef = TEST_ENTITY;
3157
+ parent.frontmatter.contentHash = "hash-old";
3158
+ const child = recoveryMemory("old-child", "old");
3159
+ const replacement = recoveryMemory("new");
3160
+ const durableTombstones = new Set<string>();
3161
+ const mutation = {
3162
+ supersededAt: "2026-02-01T00:00:00.000Z",
3163
+ matchedKeys: ["project-x::city", "project-x::tool"],
3164
+ };
3165
+
3166
+ const first = recoveryStorage({
3167
+ parent,
3168
+ child,
3169
+ replacement,
3170
+ durableTombstones,
3171
+ });
3172
+ assert.equal(
3173
+ await applyTemporalSupersessionPrimaryMutation({
3174
+ storage: first.storage,
3175
+ oldMemory: parent,
3176
+ replacementId: replacement.frontmatter.id,
3177
+ mutation,
3178
+ allCandidates: [child],
3179
+ }),
3180
+ true,
3181
+ );
3182
+ assert.deepEqual(first.tombstones.sort(), ["project-x::city", "project-x::tool"]);
3183
+
3184
+ const restarted = recoveryStorage({
3185
+ parent,
3186
+ child,
3187
+ replacement,
3188
+ durableTombstones,
3189
+ });
3190
+ assert.equal(
3191
+ await applyTemporalSupersessionPrimaryMutation({
3192
+ storage: restarted.storage,
3193
+ oldMemory: parent,
3194
+ replacementId: replacement.frontmatter.id,
3195
+ mutation,
3196
+ }),
3197
+ true,
3198
+ );
3199
+ assert.deepEqual(restarted.tombstones, []);
3200
+ });