@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
@@ -12,12 +12,52 @@
12
12
  * cached `readAllMemories()` path so cost is amortized with the rest of the
13
13
  * write pipeline.
14
14
  */
15
- import type { MemoryFile, MemoryFrontmatter, PluginConfig } from "./types.js";
16
- import type { ExtractionEngine } from "./extraction.js";
17
- import type { StorageManager } from "./storage.js";
15
+ import type { MemoryFile, MemoryFrontmatter, MemoryLifecycleEvent } from "./types.js";
16
+
17
+ import { createHash } from "node:crypto";
18
18
  import { log } from "./logger.js";
19
19
  import { effectiveValidAt } from "./temporal-validity.js";
20
- import { propagateInvalidation } from "./orchestration/dependency-propagation.js";
20
+ import type {
21
+ DependencyPropagationDeliveryPort,
22
+ DependencyPropagationPreparationToken,
23
+ } from "./orchestration/dependency-propagation-delivery.js";
24
+ import type { PropagationEvent } from "./orchestration/dependency-propagation.js";
25
+ export interface TemporalSupersessionStorage {
26
+ readAllMemories(): Promise<MemoryFile[]>;
27
+ readAllColdMemories(): Promise<MemoryFile[]>;
28
+ readMemoryByPath(memoryPath: string): Promise<MemoryFile | null>;
29
+ writeMemoryFrontmatterIfUnchanged?: (
30
+ memory: MemoryFile,
31
+ patch: Partial<MemoryFrontmatter>,
32
+ lifecycle?: {
33
+ actor?: string;
34
+ reasonCode?: string;
35
+ relatedMemoryIds?: string[];
36
+ correlationId?: string;
37
+ },
38
+ ) => Promise<boolean>;
39
+ appendTombstone(input: {
40
+ reason: "supersession";
41
+ createdBy: "supersession";
42
+ sourceMemoryId: string;
43
+ rawContent: string;
44
+ entityRef?: string;
45
+ supersessionKey?: string;
46
+ createdAt?: string;
47
+ contentHash?: string;
48
+ operationKey?: string;
49
+ }): Promise<string | null>;
50
+ hasExactTombstone?: (input: {
51
+ sourceMemoryId: string;
52
+ contentHash?: string;
53
+ entityRef?: string;
54
+ supersessionKey?: string;
55
+ createdAt?: string;
56
+ operationKey?: string;
57
+ }) => Promise<boolean>;
58
+ getMemoryTimeline: (memoryId: string, limit?: number) => Promise<MemoryLifecycleEvent[]>;
59
+ isTombstonesEnabled?: () => boolean;
60
+ }
21
61
 
22
62
  /**
23
63
  * Shared normalization for supersession key components.
@@ -171,15 +211,254 @@ export function shouldSupersedeExisting(args: {
171
211
  function normalizeValue(v: string): string {
172
212
  return v.trim().toLowerCase();
173
213
  }
214
+ function temporalSupersessionOperationKey(
215
+ sourceMemoryId: string,
216
+ replacementId: string,
217
+ mutation: TemporalSupersessionMutation,
218
+ ): string {
219
+ return createHash("sha256")
220
+ .update(
221
+ JSON.stringify({
222
+ sourceMemoryId,
223
+ replacementId,
224
+ cause: "temporal_supersession",
225
+ supersededAt: mutation.supersededAt,
226
+ invalidAt: mutation.invalidAt ?? null,
227
+ matchedKeys: [...mutation.matchedKeys].sort(compareByteStable),
228
+ }),
229
+ )
230
+ .digest("hex");
231
+ }
232
+ type TemporalMemorySnapshot = Pick<MemoryFile, "content" | "frontmatter">;
233
+ function compareByteStable(left: string, right: string): number {
234
+ return Buffer.from(left, "utf8").compare(Buffer.from(right, "utf8"));
235
+ }
236
+
237
+ function canonicalTemporalValue(value: unknown): unknown {
238
+ if (Array.isArray(value)) return value.map((entry) => canonicalTemporalValue(entry));
239
+ if (value && typeof value === "object") {
240
+ return Object.fromEntries(
241
+ Object.entries(value as Record<string, unknown>)
242
+ .sort(([left], [right]) => compareByteStable(left, right))
243
+ .map(([key, entry]) => [key, canonicalTemporalValue(entry)]),
244
+ );
245
+ }
246
+ return value;
247
+ }
248
+
249
+ function temporalSnapshotKey(memory: TemporalMemorySnapshot): string {
250
+ return JSON.stringify(
251
+ canonicalTemporalValue({
252
+ content: memory.content,
253
+ frontmatter: memory.frontmatter,
254
+ }),
255
+ );
256
+ }
257
+
258
+ function temporalMutationPatch(
259
+ replacementId: string,
260
+ mutation: TemporalSupersessionMutation,
261
+ ): Partial<MemoryFrontmatter> {
262
+ return {
263
+ status: "superseded",
264
+ supersededBy: replacementId,
265
+ supersededAt: mutation.supersededAt,
266
+ updated: mutation.supersededAt,
267
+ ...(mutation.invalidAt ? { invalid_at: mutation.invalidAt } : {}),
268
+ };
269
+ }
270
+
271
+ type TemporalPrimaryCommitState = "committed" | "not-committed" | "unknown";
272
+
273
+ async function classifyTemporalPrimaryMutation(args: {
274
+ storage: TemporalSupersessionStorage;
275
+ path: string;
276
+ before: TemporalMemorySnapshot;
277
+ patch: Partial<MemoryFrontmatter>;
278
+ }): Promise<TemporalPrimaryCommitState> {
279
+ let current: MemoryFile | null;
280
+ try {
281
+ current = await args.storage.readMemoryByPath(args.path);
282
+ } catch {
283
+ return "unknown";
284
+ }
285
+ if (!current) return "unknown";
286
+ const beforeKey = temporalSnapshotKey(args.before);
287
+ const committedKey = temporalSnapshotKey({
288
+ content: args.before.content,
289
+ frontmatter: { ...args.before.frontmatter, ...args.patch },
290
+ });
291
+ const currentKey = temporalSnapshotKey(current);
292
+ if (currentKey === committedKey) return "committed";
293
+ if (currentKey === beforeKey) return "not-committed";
294
+ return "unknown";
295
+ }
296
+ type PreparedTemporalPropagation = {
297
+ delivery: DependencyPropagationDeliveryPort;
298
+ event: PropagationEvent;
299
+ token: DependencyPropagationPreparationToken | null;
300
+ };
301
+
302
+ async function settleTemporalPropagationAfterPrimaryFailure(
303
+ pending: PreparedTemporalPropagation,
304
+ commitState: TemporalPrimaryCommitState,
305
+ memoryId: string,
306
+ ): Promise<void> {
307
+ let token = pending.token;
308
+ if (commitState === "not-committed") {
309
+ if (token !== null) {
310
+ try {
311
+ await pending.delivery.cancel(token);
312
+ } catch (error) {
313
+ log.warn(
314
+ `temporal-supersession: dependency propagation cancellation failed for ${memoryId}: ${error}`,
315
+ );
316
+ }
317
+ }
318
+ return;
319
+ }
320
+ if (token === null && (commitState === "committed" || commitState === "unknown")) {
321
+ try {
322
+ token = await pending.delivery.prepare(pending.event);
323
+ } catch (error) {
324
+ log.warn(
325
+ `temporal-supersession: dependency propagation recovery preparation failed for ${memoryId}: ${error}`,
326
+ );
327
+ }
328
+ }
329
+ if (token !== null) {
330
+ try {
331
+ await pending.delivery.deferPrepared(token);
332
+ } catch (error) {
333
+ log.warn(`temporal-supersession: dependency propagation defer failed for ${memoryId}: ${error}`);
334
+ }
335
+ return;
336
+ }
337
+ if (commitState === "committed") {
338
+ try {
339
+ await pending.delivery.afterMutation(null, pending.event);
340
+ } catch (error) {
341
+ log.warn(`temporal-supersession: dependency propagation fallback failed for ${memoryId}: ${error}`);
342
+ }
343
+ }
344
+ }
345
+
346
+
347
+ async function hasTemporalLifecycleEffect(args: {
348
+ storage: TemporalSupersessionStorage;
349
+ memoryId: string;
350
+ timestamp: string;
351
+ operationKey: string;
352
+ reasonCode: string;
353
+ relatedMemoryIds: string[];
354
+ }): Promise<boolean> {
355
+ const events = await args.storage.getMemoryTimeline(args.memoryId, 200);
356
+ return events.some((event) => {
357
+ if (
358
+ event.memoryId !== args.memoryId ||
359
+ (event.eventType !== "superseded" && event.eventType !== "updated") ||
360
+ event.timestamp !== args.timestamp ||
361
+ event.after?.status !== "superseded"
362
+ ) {
363
+ return false;
364
+ }
365
+ if (event.correlationId !== undefined) return event.correlationId === args.operationKey;
366
+ // Rows written before correlation IDs existed can be replayed only with
367
+ // their complete legacy identity. New rows always take the exact branch.
368
+ return (
369
+ event.reasonCode === args.reasonCode &&
370
+ Array.isArray(event.relatedMemoryIds) &&
371
+ event.relatedMemoryIds.length === args.relatedMemoryIds.length &&
372
+ event.relatedMemoryIds.every((memoryId, index) => memoryId === args.relatedMemoryIds[index])
373
+ );
374
+ });
375
+ }
376
+
377
+ async function hasTemporalTombstoneEffect(args: {
378
+ storage: TemporalSupersessionStorage;
379
+ oldMemory: MemoryFile;
380
+ supersessionKey?: string;
381
+ operationKey: string;
382
+ createdAt: string;
383
+ }): Promise<boolean> {
384
+ if (!args.storage.hasExactTombstone) return false;
385
+ return args.storage.hasExactTombstone({
386
+ sourceMemoryId: args.oldMemory.frontmatter.id,
387
+ ...(args.oldMemory.frontmatter.contentHash
388
+ ? { contentHash: args.oldMemory.frontmatter.contentHash }
389
+ : {}),
390
+ ...(args.oldMemory.frontmatter.entityRef
391
+ ? { entityRef: args.oldMemory.frontmatter.entityRef }
392
+ : {}),
393
+ ...(args.supersessionKey ? { supersessionKey: args.supersessionKey } : {}),
394
+ createdAt: args.createdAt,
395
+ operationKey: args.operationKey,
396
+ });
397
+ }
398
+
399
+
400
+ async function ensureTemporalLifecycleEffect(args: {
401
+ storage: TemporalSupersessionStorage;
402
+ memory: MemoryFile;
403
+ replacementId: string;
404
+ mutation: TemporalSupersessionMutation;
405
+ operationKey: string;
406
+ child?: boolean;
407
+ }): Promise<boolean> {
408
+ const reasonCode = args.child
409
+ ? "structured-attribute-update-child-chunk"
410
+ : "structured-attribute-update";
411
+ const relatedMemoryIds = args.child
412
+ ? [args.replacementId, args.memory.frontmatter.parentId ?? ""].filter(Boolean)
413
+ : [args.replacementId];
414
+ const lifecycleEffect = await hasTemporalLifecycleEffect({
415
+ storage: args.storage,
416
+ memoryId: args.memory.frontmatter.id,
417
+ timestamp: args.mutation.supersededAt,
418
+ operationKey: args.operationKey,
419
+ reasonCode,
420
+ relatedMemoryIds,
421
+ });
422
+ if (lifecycleEffect === undefined) return false;
423
+ if (lifecycleEffect === true) return true;
424
+ if (lifecycleEffect === false || args.memory.frontmatter.status !== "superseded") {
425
+ if (!args.storage.writeMemoryFrontmatterIfUnchanged) return false;
426
+ const wrote = await args.storage.writeMemoryFrontmatterIfUnchanged(
427
+ args.memory,
428
+ {
429
+ status: "superseded",
430
+ supersededBy: args.replacementId,
431
+ supersededAt: args.mutation.supersededAt,
432
+ updated: args.mutation.supersededAt,
433
+ ...(args.mutation.invalidAt && !args.memory.frontmatter.invalid_at
434
+ ? { invalid_at: args.mutation.invalidAt }
435
+ : {}),
436
+ },
437
+ {
438
+ actor: "temporal-supersession",
439
+ reasonCode: args.child
440
+ ? "structured-attribute-update-child-chunk"
441
+ : "structured-attribute-update",
442
+ relatedMemoryIds: args.child
443
+ ? [args.replacementId, args.memory.frontmatter.parentId ?? ""].filter(Boolean)
444
+ : [args.replacementId],
445
+ correlationId: args.operationKey,
446
+ },
447
+ );
448
+ return wrote;
449
+ }
450
+ return true;
451
+ }
174
452
 
175
453
  async function expireChildChunksForSupersededParent(args: {
176
- storage: StorageManager;
454
+ storage: TemporalSupersessionStorage;
177
455
  allCandidates: MemoryFile[];
178
456
  parentId: string;
179
457
  newMemoryId: string;
180
458
  supersededAt: string;
181
459
  invalidAt?: string;
182
- }): Promise<void> {
460
+ operationKey: string;
461
+ }): Promise<boolean> {
183
462
  const processedChunkIds = new Set<string>();
184
463
  const chunks = args.allCandidates.filter(
185
464
  (candidate) => candidate.frontmatter.parentId === args.parentId,
@@ -194,31 +473,161 @@ async function expireChildChunksForSupersededParent(args: {
194
473
  if (!freshChunk) continue;
195
474
  processedChunkIds.add(chunkKey);
196
475
  const freshStatus = freshChunk.frontmatter.status ?? "active";
197
- if (freshStatus !== "active" || freshChunk.frontmatter.supersededBy) continue;
198
-
199
- await args.storage.writeMemoryFrontmatter(
200
- freshChunk,
201
- {
202
- status: "superseded",
203
- supersededBy: args.newMemoryId,
476
+ if (
477
+ freshStatus !== "active" &&
478
+ !(
479
+ freshStatus === "superseded" &&
480
+ freshChunk.frontmatter.supersededBy === args.newMemoryId
481
+ )
482
+ ) {
483
+ continue;
484
+ }
485
+ const completed = await ensureTemporalLifecycleEffect({
486
+ storage: args.storage,
487
+ memory: freshChunk,
488
+ replacementId: args.newMemoryId,
489
+ mutation: {
204
490
  supersededAt: args.supersededAt,
205
- updated: args.supersededAt,
206
- ...(args.invalidAt && !freshChunk.frontmatter.invalid_at
207
- ? { invalid_at: args.invalidAt }
208
- : {}),
209
- },
210
- {
211
- actor: "temporal-supersession",
212
- reasonCode: "structured-attribute-update-child-chunk",
213
- relatedMemoryIds: [args.newMemoryId, args.parentId],
491
+ ...(args.invalidAt ? { invalidAt: args.invalidAt } : {}),
492
+ matchedKeys: [],
214
493
  },
215
- );
494
+ operationKey: `${args.operationKey}:child:${freshChunk.frontmatter.id}`,
495
+ child: true,
496
+ });
497
+ if (!completed) return false;
216
498
  } catch (err) {
217
499
  log.warn(
218
500
  `temporal-supersession: failed to expire child chunk ${chunk.frontmatter.id} for parent ${args.parentId}: ${err}`,
219
501
  );
502
+ return false;
503
+ }
504
+ }
505
+ return true;
506
+ }
507
+
508
+ export interface TemporalSupersessionMutation {
509
+ supersededAt: string;
510
+ invalidAt?: string;
511
+ matchedKeys: string[];
512
+ }
513
+
514
+ export async function applyTemporalSupersessionPrimaryMutation(args: {
515
+ storage: TemporalSupersessionStorage;
516
+ oldMemory: MemoryFile;
517
+ replacementId: string;
518
+ mutation: TemporalSupersessionMutation;
519
+ allCandidates?: MemoryFile[];
520
+ }): Promise<boolean> {
521
+ const { storage, oldMemory, replacementId, mutation } = args;
522
+ if (typeof storage.getMemoryTimeline !== "function") {
523
+ log.warn("temporal-supersession: required memory timeline adapter is missing");
524
+ return false;
525
+ }
526
+ const operationKey = temporalSupersessionOperationKey(
527
+ oldMemory.frontmatter.id,
528
+ replacementId,
529
+ mutation,
530
+ );
531
+ const alreadyApplied =
532
+ oldMemory.frontmatter.status === "superseded" &&
533
+ oldMemory.frontmatter.supersededBy === replacementId &&
534
+ oldMemory.frontmatter.supersededAt === mutation.supersededAt &&
535
+ (mutation.invalidAt === undefined || oldMemory.frontmatter.invalid_at === mutation.invalidAt);
536
+ const lifecycleEffect = alreadyApplied
537
+ ? await hasTemporalLifecycleEffect({
538
+ storage,
539
+ memoryId: oldMemory.frontmatter.id,
540
+ timestamp: mutation.supersededAt,
541
+ operationKey,
542
+ reasonCode: "structured-attribute-update",
543
+ relatedMemoryIds: [replacementId],
544
+ })
545
+ : undefined;
546
+ if (alreadyApplied && lifecycleEffect === undefined) return false;
547
+ if (!alreadyApplied || lifecycleEffect === false) {
548
+ if (!storage.writeMemoryFrontmatterIfUnchanged) return false;
549
+ const wrote = await storage.writeMemoryFrontmatterIfUnchanged(
550
+ oldMemory,
551
+ temporalMutationPatch(replacementId, mutation),
552
+ {
553
+ actor: "temporal-supersession",
554
+ reasonCode: "structured-attribute-update",
555
+ relatedMemoryIds: [replacementId],
556
+ correlationId: operationKey,
557
+ },
558
+ );
559
+ if (!wrote) return false;
560
+ }
561
+
562
+ let allCandidates = args.allCandidates;
563
+ if (!allCandidates) {
564
+ let hotMemories: MemoryFile[];
565
+ let coldMemories: MemoryFile[];
566
+ try {
567
+ hotMemories = await storage.readAllMemories();
568
+ } catch (error) {
569
+ log.warn(`temporal-supersession: recovery hot-memory scan failed: ${error}`);
570
+ return false;
571
+ }
572
+ try {
573
+ coldMemories = await storage.readAllColdMemories();
574
+ } catch (error) {
575
+ log.warn(`temporal-supersession: recovery cold-memory scan failed: ${error}`);
576
+ return false;
577
+ }
578
+ allCandidates = [...hotMemories, ...coldMemories];
579
+ }
580
+ const childrenCompleted = await expireChildChunksForSupersededParent({
581
+ storage,
582
+ allCandidates,
583
+ parentId: oldMemory.frontmatter.id,
584
+ newMemoryId: replacementId,
585
+ supersededAt: mutation.supersededAt,
586
+ invalidAt: mutation.invalidAt ?? oldMemory.frontmatter.invalid_at,
587
+ operationKey,
588
+ });
589
+ if (!childrenCompleted) return false;
590
+
591
+ const keysToTombstone =
592
+ mutation.matchedKeys.length > 0 ? mutation.matchedKeys : [undefined];
593
+ for (const key of keysToTombstone) {
594
+ const tombstoneOperationKey = `${operationKey}:tombstone:${key ?? "content"}`;
595
+ if (
596
+ await hasTemporalTombstoneEffect({
597
+ storage,
598
+ oldMemory,
599
+ supersessionKey: key,
600
+ operationKey: tombstoneOperationKey,
601
+ createdAt: mutation.supersededAt,
602
+ })
603
+ ) {
604
+ continue;
605
+ }
606
+ try {
607
+ const tombstoneId = await storage.appendTombstone({
608
+ reason: "supersession",
609
+ createdBy: "supersession",
610
+ sourceMemoryId: oldMemory.frontmatter.id,
611
+ ...(oldMemory.frontmatter.contentHash
612
+ ? { contentHash: oldMemory.frontmatter.contentHash }
613
+ : {}),
614
+ rawContent: oldMemory.content,
615
+ ...(oldMemory.frontmatter.entityRef ? { entityRef: oldMemory.frontmatter.entityRef } : {}),
616
+ ...(key ? { supersessionKey: key } : {}),
617
+ createdAt: mutation.supersededAt,
618
+ operationKey: tombstoneOperationKey,
619
+ });
620
+ if (tombstoneId === null && storage.isTombstonesEnabled?.() !== false) {
621
+ return false;
622
+ }
623
+ } catch (error) {
624
+ log.warn(
625
+ `temporal-supersession: tombstone emit failed for ${oldMemory.frontmatter.id}${key ? ` (key=${key})` : ""} : ${error}`,
626
+ );
627
+ return false;
220
628
  }
221
629
  }
630
+ return true;
222
631
  }
223
632
 
224
633
  export interface TemporalSupersessionResult {
@@ -232,7 +641,7 @@ export interface TemporalSupersessionResult {
232
641
  * on disk, and supersession is a best-effort hygiene step.
233
642
  */
234
643
  export async function applyTemporalSupersession(args: {
235
- storage: StorageManager;
644
+ storage: TemporalSupersessionStorage;
236
645
  newMemoryId: string;
237
646
  entityRef?: string;
238
647
  structuredAttributes?: Record<string, string>;
@@ -246,16 +655,29 @@ export async function applyTemporalSupersession(args: {
246
655
  * stale relative to the incoming promotion event (PR #402 Finding Uyui).
247
656
  */
248
657
  useCallerTimestamp?: boolean;
249
- /** Optional orchestration dependencies for one-hop propagation. */
250
- extraction?: ExtractionEngine;
251
- config?: PluginConfig;
658
+ /** Namespace used by the dependency propagation worker. */
252
659
  namespaceScope?: string;
660
+ /** Optional durable delivery for one-hop propagation. */
661
+ dependencyPropagationDelivery?: DependencyPropagationDeliveryPort;
253
662
  }): Promise<TemporalSupersessionResult> {
254
663
  const empty: TemporalSupersessionResult = { supersededIds: [], matchedKeys: [] };
664
+ const createdAtMs = typeof args.createdAt === "string" ? Date.parse(args.createdAt) : Number.NaN;
665
+ if (!Number.isFinite(createdAtMs)) {
666
+ log.warn(`temporal-supersession: refusing mutation with invalid createdAt: ${String(args.createdAt)}`);
667
+ return empty;
668
+ }
669
+ // Validate the caller's ordering anchor before any corpus scan or durable
670
+ // side-effect preparation. A malformed timestamp must be a true no-op:
671
+ // otherwise a later monotonic clamp could manufacture a 1970 mutation and
672
+ // leave a prepared propagation job without a replayable temporal identity.
255
673
  if (!args.enabled) return empty;
256
674
  if (!args.entityRef) return empty;
257
675
  if (!args.structuredAttributes) return empty;
258
676
  if (Object.keys(args.structuredAttributes).length === 0) return empty;
677
+ if (typeof args.storage.getMemoryTimeline !== "function") {
678
+ log.warn("temporal-supersession: required memory timeline adapter is missing");
679
+ return empty;
680
+ }
259
681
 
260
682
  const newKeys = supersessionKeysForFact({
261
683
  entityRef: args.entityRef,
@@ -335,6 +757,11 @@ export async function applyTemporalSupersession(args: {
335
757
  // supersessions — the P1 finding is precisely that stale cold facts leak
336
758
  // when hot has no hits.
337
759
  const allCandidates: MemoryFile[] = [...hotMemories, ...coldMemories];
760
+ // Resolve the replacement from the combined hot and cold snapshot. A
761
+ // hash-dedup promotion can point at a cold-only memory, and replay needs its
762
+ // captured content rather than a null replacement payload.
763
+ const replacementMemory =
764
+ allCandidates.find((memory) => memory.frontmatter.id === args.newMemoryId) ?? newMemoryFile;
338
765
 
339
766
  for (const memory of allCandidates) {
340
767
  if (memory.frontmatter.id === args.newMemoryId) continue;
@@ -371,6 +798,14 @@ export async function applyTemporalSupersession(args: {
371
798
  continue;
372
799
  }
373
800
 
801
+ let preparedPropagation: {
802
+ delivery: DependencyPropagationDeliveryPort;
803
+ event: PropagationEvent;
804
+ token: DependencyPropagationPreparationToken | null;
805
+ } | null = null;
806
+ let primaryPath: string | null = null;
807
+ let primaryBefore: TemporalMemorySnapshot | null = null;
808
+ let primaryPatch: Partial<MemoryFrontmatter> | null = null;
374
809
  try {
375
810
  // CAS-style re-read immediately before the write. `readAllMemories()`
376
811
  // is a snapshot — with concurrent writers, another run may have already
@@ -405,6 +840,11 @@ export async function applyTemporalSupersession(args: {
405
840
  );
406
841
  continue;
407
842
  }
843
+ primaryPath = fresh.path;
844
+ primaryBefore = {
845
+ content: fresh.content,
846
+ frontmatter: { ...fresh.frontmatter },
847
+ };
408
848
 
409
849
  // Finding 2 fix: the `supersededAt` / `updated` timestamps written to the
410
850
  // old fact must never run backwards relative to its own persisted
@@ -446,99 +886,119 @@ export async function applyTemporalSupersession(args: {
446
886
  if (args.useCallerTimestamp) {
447
887
  invalidAtPatch = persistedCreatedAt;
448
888
  } else {
449
- const newValidAt = newMemoryFile?.frontmatter.valid_at?.trim();
889
+ const newValidAt = replacementMemory?.frontmatter.valid_at?.trim();
450
890
  invalidAtPatch =
451
891
  newValidAt && newValidAt.length > 0 ? newValidAt : persistedCreatedAt;
452
892
  }
453
893
  }
454
- const wrote = await args.storage.writeMemoryFrontmatter(
455
- fresh,
456
- {
457
- status: "superseded",
458
- supersededBy: args.newMemoryId,
459
- supersededAt,
460
- updated: supersededAt,
461
- ...(invalidAtPatch ? { invalid_at: invalidAtPatch } : {}),
462
- },
463
- {
464
- actor: "temporal-supersession",
465
- reasonCode: "structured-attribute-update",
466
- relatedMemoryIds: [args.newMemoryId],
467
- },
468
- );
469
- if (wrote) {
470
- supersededIds.push(memory.frontmatter.id);
471
- for (const key of decision.matchedKeys) matchedKeys.add(key);
472
- await expireChildChunksForSupersededParent({
894
+ const mutation: TemporalSupersessionMutation = {
895
+ supersededAt,
896
+ ...(invalidAtPatch ? { invalidAt: invalidAtPatch } : {}),
897
+ matchedKeys: decision.matchedKeys,
898
+ };
899
+ primaryPatch = temporalMutationPatch(args.newMemoryId, mutation);
900
+ const propagationEvent: PropagationEvent | null =
901
+ args.dependencyPropagationDelivery && args.namespaceScope
902
+ ? {
903
+ oldMemory: {
904
+ content: fresh.content,
905
+ frontmatter: {
906
+ ...fresh.frontmatter,
907
+ ...(fresh.frontmatter.links
908
+ ? { links: fresh.frontmatter.links.map((link) => ({ ...link })) }
909
+ : {}),
910
+ },
911
+ },
912
+ replacementId: args.newMemoryId,
913
+ replacementContent: replacementMemory?.content ?? null,
914
+ cause: "temporal_supersession",
915
+ namespaceScope: args.namespaceScope,
916
+ temporalMutation: mutation,
917
+ }
918
+ : null;
919
+ if (propagationEvent && args.dependencyPropagationDelivery) {
920
+ preparedPropagation = {
921
+ delivery: args.dependencyPropagationDelivery,
922
+ event: propagationEvent,
923
+ token: null,
924
+ };
925
+ try {
926
+ preparedPropagation.token =
927
+ await args.dependencyPropagationDelivery.prepare(propagationEvent);
928
+ } catch (prepareErr) {
929
+ log.warn(
930
+ `temporal-supersession: dependency propagation preparation failed for ${fresh.frontmatter.id}: ${prepareErr}`,
931
+ );
932
+ }
933
+ }
934
+ const wrote = await applyTemporalSupersessionPrimaryMutation({
935
+ storage: args.storage,
936
+ oldMemory: fresh,
937
+ replacementId: args.newMemoryId,
938
+ mutation,
939
+ allCandidates,
940
+ });
941
+ if (!wrote) {
942
+ const commitState = await classifyTemporalPrimaryMutation({
473
943
  storage: args.storage,
474
- allCandidates,
475
- parentId: fresh.frontmatter.id,
476
- newMemoryId: args.newMemoryId,
477
- supersededAt,
478
- invalidAt: invalidAtPatch ?? fresh.frontmatter.invalid_at,
944
+ path: fresh.path,
945
+ before: primaryBefore,
946
+ patch: primaryPatch,
479
947
  });
480
- // Issue #1579 — emit a tombstone for the superseded memory so it
481
- // cannot resurrect through re-extraction / import / consolidation /
482
- // dreams / pattern-reinforcement. The supersession key is the first
483
- // matched key (the write-time supersession identity). Best-effort:
484
- // a tombstone append failure is logged but never fails supersession
485
- // (gotcha #13).
486
- // Issue #1579 thread ObteS: persist ALL matched supersession keys, not
487
- // just the first. A later paraphrased re-observation may derive the
488
- // same entity but place the tombstoned attribute at a different
489
- // position in structuredAttributes; without an entry for every matched
490
- // key, the keyed tier would miss and the fact could resurrect as
491
- // active. Pass the canonical contentHash from the retired memory's
492
- // frontmatter so the tombstone's exact tier matches re-extraction
493
- // (review: citation-hash alignment). The rawContent is the stored body;
494
- // the StorageManager.appendTombstone chokepoint strips citation
495
- // annotations for the normalized-text tier.
496
- const keysToTombstone =
497
- decision.matchedKeys.length > 0 ? decision.matchedKeys : [undefined];
498
- for (const key of keysToTombstone) {
499
- try {
500
- await args.storage.appendTombstone({
501
- reason: "supersession",
502
- createdBy: "supersession",
503
- sourceMemoryId: fresh.frontmatter.id,
504
- ...(fresh.frontmatter.contentHash
505
- ? { contentHash: fresh.frontmatter.contentHash }
506
- : {}),
507
- rawContent: fresh.content,
508
- ...(fresh.frontmatter.entityRef ? { entityRef: fresh.frontmatter.entityRef } : {}),
509
- ...(key ? { supersessionKey: key } : {}),
510
- createdAt: supersededAt,
511
- });
512
- } catch (tombErr) {
948
+ if (preparedPropagation) {
949
+ await settleTemporalPropagationAfterPrimaryFailure(
950
+ preparedPropagation,
951
+ commitState,
952
+ fresh.frontmatter.id,
953
+ );
954
+ if (commitState !== "not-committed") {
513
955
  log.warn(
514
- `temporal-supersession: tombstone emit failed for ${fresh.frontmatter.id}${key ? ` (key=${key})` : ""} : ${tombErr}`,
956
+ `temporal-supersession: retaining dependency propagation recovery for ${fresh.frontmatter.id} after ${commitState} primary mutation`,
515
957
  );
516
958
  }
517
959
  }
518
- if (args.extraction && args.config && args.namespaceScope) {
519
- try {
520
- await propagateInvalidation(
521
- {
522
- storage: args.storage,
523
- extraction: args.extraction,
524
- config: args.config,
525
- },
526
- {
527
- oldMemory: fresh,
528
- replacementId: args.newMemoryId,
529
- replacementContent: newMemoryFile?.content ?? null,
530
- cause: "temporal_supersession",
531
- namespaceScope: args.namespaceScope,
532
- },
533
- );
534
- } catch (propagationErr) {
535
- log.warn(
536
- `temporal-supersession: dependency propagation failed for ${fresh.frontmatter.id}: ${propagationErr}`,
537
- );
538
- }
960
+ preparedPropagation = null;
961
+ continue;
962
+ }
963
+ if (preparedPropagation) {
964
+ const completedPropagation = preparedPropagation;
965
+ preparedPropagation = null;
966
+ try {
967
+ await completedPropagation.delivery.afterMutation(
968
+ completedPropagation.token,
969
+ completedPropagation.event,
970
+ );
971
+ } catch (afterMutationErr) {
972
+ log.warn(
973
+ `temporal-supersession: dependency propagation delivery failed for ${memory.frontmatter.id}: ${afterMutationErr}`,
974
+ );
539
975
  }
540
976
  }
977
+ supersededIds.push(memory.frontmatter.id);
978
+ for (const key of decision.matchedKeys) matchedKeys.add(key);
541
979
  } catch (err) {
980
+ let commitState: TemporalPrimaryCommitState = "unknown";
981
+ if (primaryPath && primaryBefore && primaryPatch) {
982
+ commitState = await classifyTemporalPrimaryMutation({
983
+ storage: args.storage,
984
+ path: primaryPath,
985
+ before: primaryBefore,
986
+ patch: primaryPatch,
987
+ });
988
+ }
989
+ if (preparedPropagation) {
990
+ await settleTemporalPropagationAfterPrimaryFailure(
991
+ preparedPropagation,
992
+ commitState,
993
+ memory.frontmatter.id,
994
+ );
995
+ if (commitState !== "not-committed") {
996
+ log.warn(
997
+ `temporal-supersession: retaining dependency propagation recovery for ${memory.frontmatter.id} after ${commitState} primary mutation`,
998
+ );
999
+ }
1000
+ }
1001
+ preparedPropagation = null;
542
1002
  log.warn(
543
1003
  `temporal-supersession: failed to mark ${memory.frontmatter.id} superseded: ${err}`,
544
1004
  );