@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
@@ -0,0 +1,220 @@
1
+ import { createHash } from "node:crypto";
2
+
3
+ import type { MemoryFile } from "../types.js";
4
+ import { isRecord } from "../store-contract.js";
5
+ import type { PropagationEvent } from "./dependency-propagation.js";
6
+ import type {
7
+ DependencyPropagationJob,
8
+ DependencyPropagationJobStatus,
9
+ } from "./dependency-propagation-delivery.js";
10
+
11
+ export const JOB_STATES: readonly DependencyPropagationJobStatus[] = [
12
+ "prepared",
13
+ "ready",
14
+ "leased",
15
+ "retryable",
16
+ "completed",
17
+ "dead_letter",
18
+ "canceled",
19
+ ];
20
+
21
+ const CAUSES: Record<PropagationEvent["cause"], true> = {
22
+ contradiction: true,
23
+ temporal_supersession: true,
24
+ consolidation_invalidate: true,
25
+ consolidation_merge: true,
26
+ };
27
+
28
+ export function isFiniteNumber(value: unknown): value is number {
29
+ return typeof value === "number" && Number.isFinite(value);
30
+ }
31
+
32
+ export function isPropagationEvent(value: unknown): value is PropagationEvent {
33
+ if (!isRecord(value) || !isRecord(value.oldMemory)) return false;
34
+ const oldMemory = value.oldMemory;
35
+ if (!isRecord(oldMemory.frontmatter)) return false;
36
+ const frontmatter = oldMemory.frontmatter;
37
+ if (typeof oldMemory.content !== "string" || typeof frontmatter.id !== "string") return false;
38
+ if (
39
+ !(value.replacementId === null || typeof value.replacementId === "string") ||
40
+ !(value.replacementContent === null || typeof value.replacementContent === "string") ||
41
+ typeof value.namespaceScope !== "string" ||
42
+ value.namespaceScope.length === 0 ||
43
+ typeof value.cause !== "string" ||
44
+ !Object.hasOwn(CAUSES, value.cause)
45
+ ) {
46
+ return false;
47
+ }
48
+ const temporalMutation = value.temporalMutation;
49
+ if (
50
+ temporalMutation !== undefined &&
51
+ (!isRecord(temporalMutation) ||
52
+ typeof temporalMutation.supersededAt !== "string" ||
53
+ (temporalMutation.invalidAt !== undefined && typeof temporalMutation.invalidAt !== "string") ||
54
+ !Array.isArray(temporalMutation.matchedKeys) ||
55
+ temporalMutation.matchedKeys.some((key) => typeof key !== "string"))
56
+ ) {
57
+ return false;
58
+ }
59
+ if (frontmatter.links !== undefined) {
60
+ if (!Array.isArray(frontmatter.links)) return false;
61
+ for (const link of frontmatter.links) {
62
+ if (!isRecord(link) || typeof link.targetId !== "string" || typeof link.linkType !== "string") return false;
63
+ }
64
+ }
65
+ return true;
66
+ }
67
+
68
+ export function validateJob(
69
+ value: unknown,
70
+ expectedState?: DependencyPropagationJobStatus,
71
+ ): DependencyPropagationJob | null {
72
+ if (!isRecord(value)) return null;
73
+ const status = value.status;
74
+ const event = value.event;
75
+ const attempts = value.attempts;
76
+ const createdAt = value.createdAt;
77
+ const updatedAt = value.updatedAt;
78
+ if (
79
+ typeof value.jobId !== "string" ||
80
+ value.jobId.length === 0 ||
81
+ typeof value.namespace !== "string" ||
82
+ value.namespace.length === 0 ||
83
+ typeof value.sourceId !== "string" ||
84
+ value.sourceId.length === 0 ||
85
+ !isPropagationEvent(event) ||
86
+ value.jobId !== eventJobId(event) ||
87
+ value.namespace !== event.namespaceScope ||
88
+ value.sourceId !== event.oldMemory.frontmatter.id ||
89
+ typeof status !== "string" ||
90
+ !JOB_STATES.includes(status as DependencyPropagationJobStatus) ||
91
+ (expectedState !== undefined && status !== expectedState) ||
92
+ typeof attempts !== "number" ||
93
+ !Number.isInteger(attempts) ||
94
+ attempts < 0 ||
95
+ !isFiniteNumber(createdAt) ||
96
+ !isFiniteNumber(updatedAt)
97
+ ) {
98
+ return null;
99
+ }
100
+ const revision = value.revision === undefined ? 0 : value.revision;
101
+ if (typeof revision !== "number" || !Number.isInteger(revision) || revision < 0) return null;
102
+ const reservationIdsValue = value.reservationIds;
103
+ if (!Array.isArray(reservationIdsValue)) return null;
104
+ const reservationIds = reservationIdsValue as unknown[];
105
+ if (
106
+ reservationIds.some(
107
+ (reservationId) => typeof reservationId !== "string" || reservationId.trim().length === 0,
108
+ ) || new Set(reservationIds).size !== reservationIds.length
109
+ ) {
110
+ return null;
111
+ }
112
+ const reservations = value.reservations;
113
+ if (
114
+ typeof reservations !== "number" ||
115
+ !Number.isInteger(reservations) ||
116
+ reservations < 0 ||
117
+ reservations !== reservationIds.length
118
+ ) {
119
+ return null;
120
+ }
121
+ const nextAttemptAt = value.nextAttemptAt;
122
+ const leaseOwner = value.leaseOwner;
123
+ const leaseExpiresAt = value.leaseExpiresAt;
124
+ const lastError = value.lastError;
125
+ const preparedReplacementFingerprint = value.preparedReplacementFingerprint;
126
+ if (nextAttemptAt !== undefined && !isFiniteNumber(nextAttemptAt)) return null;
127
+ if (leaseOwner !== undefined && typeof leaseOwner !== "string") return null;
128
+ if (leaseExpiresAt !== undefined && !isFiniteNumber(leaseExpiresAt)) return null;
129
+ if (lastError !== undefined && typeof lastError !== "string") return null;
130
+ if (
131
+ preparedReplacementFingerprint !== undefined &&
132
+ (typeof preparedReplacementFingerprint !== "string" || preparedReplacementFingerprint.length === 0)
133
+ ) {
134
+ return null;
135
+ }
136
+ if (status === "retryable" && nextAttemptAt === undefined) return null;
137
+ if (status === "leased" && (typeof leaseOwner !== "string" || leaseExpiresAt === undefined)) return null;
138
+ return {
139
+ jobId: value.jobId as string,
140
+ namespace: value.namespace as string,
141
+ sourceId: value.sourceId as string,
142
+ event,
143
+ status: status as DependencyPropagationJobStatus,
144
+ attempts,
145
+ createdAt,
146
+ updatedAt,
147
+ reservations,
148
+ reservationIds: [...(reservationIds as string[])],
149
+ revision,
150
+ ...(nextAttemptAt === undefined ? {} : { nextAttemptAt: nextAttemptAt as number }),
151
+ ...(leaseOwner === undefined ? {} : { leaseOwner: leaseOwner as string }),
152
+ ...(leaseExpiresAt === undefined ? {} : { leaseExpiresAt: leaseExpiresAt as number }),
153
+ ...(lastError === undefined ? {} : { lastError: lastError as string }),
154
+ ...(preparedReplacementFingerprint === undefined
155
+ ? {}
156
+ : { preparedReplacementFingerprint: preparedReplacementFingerprint as string }),
157
+ };
158
+ }
159
+ export function canonicalize(value: unknown): unknown {
160
+ if (Array.isArray(value)) return value.map((item) => canonicalize(item));
161
+ if (isRecord(value)) {
162
+ const result: Record<string, unknown> = {};
163
+ for (const key of Object.keys(value).sort()) {
164
+ if (value[key] === undefined) continue;
165
+ result[key] = canonicalize(value[key]);
166
+ }
167
+ return result;
168
+ }
169
+ return value === undefined ? null : value;
170
+ }
171
+
172
+ export function compareByteStable(left: string, right: string): number {
173
+ return Buffer.from(left, "utf8").compare(Buffer.from(right, "utf8"));
174
+ }
175
+
176
+ export function canonicalEvent(event: PropagationEvent): PropagationEvent {
177
+ return canonicalize(event) as PropagationEvent;
178
+ }
179
+
180
+ export function stableLinks(links: unknown): unknown {
181
+ if (!Array.isArray(links)) return links;
182
+ return [...links].sort((left, right) => {
183
+ const leftKey = JSON.stringify(canonicalize(left));
184
+ const rightKey = JSON.stringify(canonicalize(right));
185
+ return compareByteStable(leftKey, rightKey);
186
+ });
187
+ }
188
+
189
+ export function matchesPreparedSource(
190
+ current: MemoryFile,
191
+ prepared: Pick<MemoryFile, "content" | "frontmatter">,
192
+ ): boolean {
193
+ const { accessCount: _currentAccessCount, lastAccessed: _currentLastAccessed, ...currentSemantic } =
194
+ current.frontmatter;
195
+ const { accessCount: _preparedAccessCount, lastAccessed: _preparedLastAccessed, ...preparedSemantic } =
196
+ prepared.frontmatter;
197
+ const normalizedCurrent = { ...currentSemantic, links: stableLinks(currentSemantic.links) };
198
+ const normalizedPrepared = { ...preparedSemantic, links: stableLinks(preparedSemantic.links) };
199
+ return (
200
+ current.content === prepared.content &&
201
+ JSON.stringify(canonicalize(normalizedCurrent)) === JSON.stringify(canonicalize(normalizedPrepared))
202
+ );
203
+ }
204
+
205
+ export function eventJobId(event: PropagationEvent): string {
206
+ const { accessCount: _accessCount, lastAccessed: _lastAccessed, links, ...semanticFrontmatter } =
207
+ event.oldMemory.frontmatter;
208
+ const semanticEvent = {
209
+ ...event,
210
+ oldMemory: {
211
+ ...event.oldMemory,
212
+ frontmatter: { ...semanticFrontmatter, links: stableLinks(links) },
213
+ },
214
+ };
215
+ return createHash("sha256").update(JSON.stringify(canonicalize(semanticEvent))).digest("hex");
216
+ }
217
+
218
+ export function isNotFound(error: unknown): boolean {
219
+ return isRecord(error) && error.code === "ENOENT";
220
+ }
@@ -0,0 +1,237 @@
1
+ /**
2
+ * Recovery primary-mutation replay for durable dependency propagation.
3
+ *
4
+ * Extracted from the delivery module so the recovery concern — resolving a
5
+ * memory across hot/cold/archived stores and replaying the primary mutation
6
+ * (supersede / invalidate / temporal) that a durable job represents — lives in
7
+ * one cohesive place. These functions are stateless with respect to the
8
+ * delivery class: they operate on an injected storage handle plus pure helpers,
9
+ * so the delivery delegates here without wrapper indirection. The module
10
+ * imports only package-local, host-agnostic sources (no index backend).
11
+ */
12
+ import { createHash } from "node:crypto";
13
+
14
+ import { sanitizeMemoryContent } from "../sanitize.js";
15
+ import type { MemoryFile } from "../types.js";
16
+ import {
17
+ applyTemporalSupersessionPrimaryMutation,
18
+ type TemporalSupersessionStorage,
19
+ } from "../temporal-supersession.js";
20
+ import {
21
+ type DependencyPropagationStorage,
22
+ type PropagationEvent,
23
+ } from "./dependency-propagation.js";
24
+ import { canonicalize, matchesPreparedSource } from "./dependency-propagation-queue-state.js";
25
+
26
+ /**
27
+ * Storage capabilities the recovery replay needs beyond the base
28
+ * {@link DependencyPropagationStorage}: cold/archived memory reads, committed
29
+ * invalidation bookkeeping, and the supersede/invalidate primitives.
30
+ */
31
+ export type DependencyPropagationRecoveryStorage = DependencyPropagationStorage & {
32
+ readAllColdMemories?: () => Promise<MemoryFile[]>;
33
+ readArchivedMemories?: () => Promise<MemoryFile[]>;
34
+ hasCommittedInvalidation?: (memory: Pick<MemoryFile, "content" | "frontmatter">) => Promise<boolean>;
35
+ clearCommittedInvalidation?: (
36
+ memory: Pick<MemoryFile, "content" | "frontmatter">,
37
+ ) => Promise<void>;
38
+ invalidateMemory?: (
39
+ id: string,
40
+ snapshot?: Pick<MemoryFile, "content" | "frontmatter"> & Partial<Pick<MemoryFile, "path">>,
41
+ options?: { recordCommitProof?: boolean },
42
+ ) => Promise<boolean>;
43
+ updateMemoryIfUnchanged?: (
44
+ expected: MemoryFile,
45
+ content: string,
46
+ options?: {
47
+ supersedes?: string;
48
+ lineage?: string[];
49
+ actor?: string;
50
+ sourceConnector?: string;
51
+ },
52
+ ) => Promise<boolean>;
53
+ };
54
+
55
+ /**
56
+ * Content fingerprint captured at preparation time. Access-count /
57
+ * last-accessed bookkeeping is excluded so a replay can prove the survivor has
58
+ * not drifted.
59
+ */
60
+ export function memoryFingerprint(memory: Pick<MemoryFile, "content" | "frontmatter">): string {
61
+ const { accessCount: _accessCount, lastAccessed: _lastAccessed, ...frontmatter } = memory.frontmatter;
62
+ return createHash("sha256")
63
+ .update(JSON.stringify(canonicalize({ content: memory.content, frontmatter })))
64
+ .digest("hex");
65
+ }
66
+
67
+ /**
68
+ * Resolve a memory by id across hot, cold, and archived stores. Returns `null`
69
+ * when no source record survives anywhere — recovery then cannot replay.
70
+ */
71
+ export async function findRecoverySource(
72
+ storage: DependencyPropagationRecoveryStorage,
73
+ sourceId: string,
74
+ ): Promise<MemoryFile | null> {
75
+ const hot = await storage.getMemoryById(sourceId);
76
+ if (hot?.frontmatter.id === sourceId) return hot;
77
+ if (storage.readAllColdMemories) {
78
+ const cold = await storage.readAllColdMemories();
79
+ const coldMatch = cold.find((memory) => memory.frontmatter.id === sourceId);
80
+ if (coldMatch) return coldMatch;
81
+ }
82
+ if (storage.readArchivedMemories) {
83
+ const archived = await storage.readArchivedMemories();
84
+ const archivedMatch = archived.find((memory) => memory.frontmatter.id === sourceId);
85
+ if (archivedMatch) return archivedMatch;
86
+ }
87
+ return null;
88
+ }
89
+
90
+ /**
91
+ * Refresh a propagation event's replacement content from the current store for
92
+ * replay. Returns the event unchanged when it has no replacement; returns
93
+ * `null` when a replacement was expected but is no longer resolvable.
94
+ */
95
+ export async function readCurrentReplacementEvent(
96
+ storage: DependencyPropagationStorage,
97
+ event: PropagationEvent,
98
+ ): Promise<PropagationEvent | null> {
99
+ if (event.replacementId === null) return event;
100
+ const replacement = await findRecoverySource(
101
+ storage as DependencyPropagationRecoveryStorage,
102
+ event.replacementId,
103
+ );
104
+ if (!replacement) return null;
105
+ return {
106
+ ...event,
107
+ replacementContent: replacement.content,
108
+ };
109
+ }
110
+
111
+ /**
112
+ * Replay the primary mutation a durable job represents against the current
113
+ * store, by cause: temporal supersession, contradiction supersede,
114
+ * consolidation invalidate, or consolidation merge. Idempotent — returns
115
+ * `true` when the mutation is already applied or is applied now, `false` when
116
+ * the source/replacement no longer matches the prepared snapshot (so the caller
117
+ * keeps the job retryable instead of marking it complete).
118
+ */
119
+ export async function replayPrimaryMutation(
120
+ sourceStorage: DependencyPropagationStorage,
121
+ event: PropagationEvent,
122
+ preparedReplacementFingerprint?: string,
123
+ ): Promise<boolean> {
124
+ const storage = sourceStorage as DependencyPropagationRecoveryStorage;
125
+ const source = await findRecoverySource(storage, event.oldMemory.frontmatter.id);
126
+ const primaryApplied =
127
+ source?.frontmatter.status === "superseded" &&
128
+ source.frontmatter.supersededBy === event.replacementId;
129
+
130
+ if (event.cause === "temporal_supersession") {
131
+ if (!event.replacementId || !event.temporalMutation || !source) return false;
132
+ const temporalPrimaryApplied =
133
+ primaryApplied &&
134
+ source.frontmatter.supersededAt === event.temporalMutation.supersededAt &&
135
+ (event.temporalMutation.invalidAt === undefined ||
136
+ source.frontmatter.invalid_at === event.temporalMutation.invalidAt);
137
+ if (!temporalPrimaryApplied) {
138
+ const replacement = await findRecoverySource(storage, event.replacementId);
139
+ if (
140
+ event.replacementContent === null ||
141
+ !replacement ||
142
+ replacement.frontmatter.id !== event.replacementId ||
143
+ replacement.content !== event.replacementContent
144
+ )
145
+ return false;
146
+ if (!matchesPreparedSource(source, event.oldMemory)) return false;
147
+ }
148
+ return await applyTemporalSupersessionPrimaryMutation({
149
+ storage: sourceStorage as unknown as TemporalSupersessionStorage,
150
+ oldMemory: source,
151
+ replacementId: event.replacementId,
152
+ mutation: event.temporalMutation,
153
+ });
154
+ }
155
+
156
+ if (event.cause === "contradiction") {
157
+ if (!event.replacementId || !source) return false;
158
+ if (primaryApplied) return true;
159
+ const replacement = await findRecoverySource(storage, event.replacementId);
160
+ if (
161
+ event.replacementContent === null ||
162
+ !replacement ||
163
+ replacement.frontmatter.id !== event.replacementId ||
164
+ replacement.content !== event.replacementContent
165
+ )
166
+ return false;
167
+ if (!matchesPreparedSource(source, event.oldMemory)) return false;
168
+ return await storage.supersedeMemory(
169
+ event.oldMemory.frontmatter.id,
170
+ event.replacementId,
171
+ `dependency_propagation:${event.cause}`,
172
+ undefined,
173
+ { requireActive: true, acceptExactReplay: true, expectedSnapshot: source },
174
+ );
175
+ }
176
+
177
+ if (event.cause === "consolidation_invalidate") {
178
+ if (!source) {
179
+ return storage.hasCommittedInvalidation
180
+ ? await storage.hasCommittedInvalidation(event.oldMemory)
181
+ : false;
182
+ }
183
+ if (!storage.invalidateMemory || !matchesPreparedSource(source, event.oldMemory)) return false;
184
+ return await storage.invalidateMemory(source.frontmatter.id, source, {
185
+ recordCommitProof: true,
186
+ });
187
+ }
188
+
189
+ if (
190
+ !event.replacementId ||
191
+ event.replacementContent === null ||
192
+ !storage.updateMemoryIfUnchanged
193
+ ) {
194
+ return false;
195
+ }
196
+ const replacement = await findRecoverySource(storage, event.replacementId);
197
+ const persistedReplacementContent = sanitizeMemoryContent(event.replacementContent).text;
198
+ if (
199
+ replacement &&
200
+ replacement.frontmatter.supersedes === event.oldMemory.frontmatter.id &&
201
+ replacement.content !== persistedReplacementContent
202
+ ) {
203
+ return false;
204
+ }
205
+ const mergeAlreadyApplied =
206
+ replacement !== null &&
207
+ replacement.frontmatter.supersedes === event.oldMemory.frontmatter.id &&
208
+ replacement.content === persistedReplacementContent;
209
+ if (!source) {
210
+ if (!storage.hasCommittedInvalidation) return false;
211
+ return await storage.hasCommittedInvalidation(event.oldMemory);
212
+ }
213
+ if (
214
+ preparedReplacementFingerprint &&
215
+ source &&
216
+ !mergeAlreadyApplied &&
217
+ (!replacement || memoryFingerprint(replacement) !== preparedReplacementFingerprint)
218
+ ) {
219
+ return false;
220
+ }
221
+ if (!replacement || !matchesPreparedSource(source, event.oldMemory)) return false;
222
+ if (!mergeAlreadyApplied) {
223
+ const updated = await storage.updateMemoryIfUnchanged(
224
+ replacement,
225
+ event.replacementContent,
226
+ {
227
+ supersedes: event.oldMemory.frontmatter.id,
228
+ lineage: [event.replacementId, event.oldMemory.frontmatter.id],
229
+ },
230
+ );
231
+ if (!updated) return false;
232
+ }
233
+ if (!storage.invalidateMemory) return false;
234
+ return await storage.invalidateMemory(source.frontmatter.id, source, {
235
+ recordCommitProof: true,
236
+ });
237
+ }
@@ -0,0 +1,73 @@
1
+ /**
2
+ * Replay reconciliation for dependency-propagation recovery.
3
+ *
4
+ * Reconciles the index effects — reindex the survivor, deindex the superseded
5
+ * set — of a replayed consolidation merge so a recovery pass matches what a
6
+ * normal consolidation MERGE/INVALIDATE performs inline. Host-agnostic: it
7
+ * imports no index backend and never references the orchestrator. The namespace
8
+ * storage lookup, survivor reindex, embedding deindex, and query-aware-indexing
9
+ * gate all flow through core APIs plus the injected
10
+ * {@link DependencyPropagationReplayHost}. Cause-gating mirrors consolidation
11
+ * exactly: reindex runs only for a consolidation_merge survivor; deindex runs
12
+ * for consolidation_merge and consolidation_invalidate superseded memories,
13
+ * dropping embedding visibility first then the temporal index entry. Any
14
+ * rejection propagates so the delivery keeps recovery retryable rather than
15
+ * marking the job safely complete.
16
+ */
17
+ import { deindexMemoriesBatchAsync } from "../temporal-index.js";
18
+ import { resolveIndexingCapabilities } from "../capabilities.js";
19
+ import type { StorageManager } from "../storage.js";
20
+ import type { PluginConfig } from "../types.js";
21
+ import type { DependencyPropagationReplayReconciliationPort } from "./dependency-propagation-delivery.js";
22
+
23
+ /**
24
+ * Narrow host port: the index primitives a recovery replay needs. The host
25
+ * (orchestrator) supplies them; this module owns the cause-gating and
26
+ * deindex/reindex ordering and imports no index backend of its own.
27
+ */
28
+ export interface DependencyPropagationReplayHost {
29
+ /** Resolve the storage owning a propagation event's namespace scope. */
30
+ getStorage(namespace: string): Promise<StorageManager>;
31
+ /** Reindex/refresh a still-active survivor's index visibility. */
32
+ indexPersistedMemory(storage: StorageManager, memoryId: string): Promise<void>;
33
+ /** Remove a superseded memory's stale embedding index entry. */
34
+ removeFromIndex(memoryId: string): Promise<void>;
35
+ /** Host config; read for the query-aware-indexing gate. */
36
+ readonly config: PluginConfig;
37
+ }
38
+
39
+ /**
40
+ * Build the replay-reconciliation port bound to a host's index primitives. The
41
+ * returned port is handed to {@link DependencyPropagationDelivery} as
42
+ * `replayReconciliation`; it is inert until the delivery replays a job.
43
+ */
44
+ export function createDependencyPropagationReplayReconciliation(
45
+ host: DependencyPropagationReplayHost,
46
+ ): DependencyPropagationReplayReconciliationPort {
47
+ return {
48
+ reindex: async (event, survivorId) => {
49
+ if (event.cause !== "consolidation_merge") return;
50
+ const storage = await host.getStorage(event.namespaceScope);
51
+ await host.indexPersistedMemory(storage, survivorId);
52
+ },
53
+ deindex: async (event, memoryIds) => {
54
+ if (
55
+ event.cause !== "consolidation_merge" &&
56
+ event.cause !== "consolidation_invalidate"
57
+ ) {
58
+ return;
59
+ }
60
+ await Promise.all(
61
+ memoryIds.map((memoryId) => host.removeFromIndex(memoryId)),
62
+ );
63
+ if (!resolveIndexingCapabilities(host.config).queryAwareIndexing) return;
64
+ if (!event.oldMemory.path || !event.oldMemory.frontmatter.created) return;
65
+ const storage = await host.getStorage(event.namespaceScope);
66
+ await deindexMemoriesBatchAsync(storage.dir, [{
67
+ path: event.oldMemory.path,
68
+ createdAt: event.oldMemory.frontmatter.created,
69
+ tags: event.oldMemory.frontmatter.tags ?? [],
70
+ }]);
71
+ },
72
+ };
73
+ }