@graphrefly/graphrefly 0.47.0 → 0.47.2

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 (187) hide show
  1. package/dist/base/composition/index.cjs +24 -16
  2. package/dist/base/composition/index.cjs.map +1 -1
  3. package/dist/base/composition/index.js +6 -6
  4. package/dist/base/index.cjs +142 -86
  5. package/dist/base/index.cjs.map +1 -1
  6. package/dist/base/index.js +11 -11
  7. package/dist/base/io/index.cjs +114 -68
  8. package/dist/base/io/index.cjs.map +1 -1
  9. package/dist/base/io/index.js +5 -5
  10. package/dist/base/sources/browser/index.cjs +13 -9
  11. package/dist/base/sources/browser/index.cjs.map +1 -1
  12. package/dist/base/sources/browser/index.js +13 -9
  13. package/dist/base/sources/browser/index.js.map +1 -1
  14. package/dist/base/sources/event/index.cjs +1 -1
  15. package/dist/base/sources/event/index.cjs.map +1 -1
  16. package/dist/base/sources/event/index.js +1 -1
  17. package/dist/base/sources/index.cjs +21 -13
  18. package/dist/base/sources/index.cjs.map +1 -1
  19. package/dist/base/sources/index.js +3 -3
  20. package/dist/base/sources/node/index.cjs +43 -37
  21. package/dist/base/sources/node/index.cjs.map +1 -1
  22. package/dist/base/sources/node/index.js +43 -37
  23. package/dist/base/sources/node/index.js.map +1 -1
  24. package/dist/{chunk-VLAGJZSL.js → chunk-3O3NKZJW.js} +2 -2
  25. package/dist/{chunk-YJ4U2D2C.js → chunk-446I4EGD.js} +9 -7
  26. package/dist/chunk-446I4EGD.js.map +1 -0
  27. package/dist/{chunk-DKNHAICT.js → chunk-5GVURVIG.js} +14 -8
  28. package/dist/chunk-5GVURVIG.js.map +1 -0
  29. package/dist/{chunk-2OB3CEJS.js → chunk-6MRSX3YK.js} +2 -2
  30. package/dist/{chunk-EVYY4X5A.js → chunk-6ZLCPUXS.js} +2 -2
  31. package/dist/{chunk-ZVXXDWIB.js → chunk-7AVQIGF6.js} +514 -33
  32. package/dist/chunk-7AVQIGF6.js.map +1 -0
  33. package/dist/{chunk-7EGRP2VX.js → chunk-7BULJTL6.js} +2 -2
  34. package/dist/{chunk-7EGRP2VX.js.map → chunk-7BULJTL6.js.map} +1 -1
  35. package/dist/{chunk-FW23JYNQ.js → chunk-CEVNQ74M.js} +2 -2
  36. package/dist/{chunk-CGHORL6G.js → chunk-DDTS7F5O.js} +7 -5
  37. package/dist/chunk-DDTS7F5O.js.map +1 -0
  38. package/dist/{chunk-OCUDSN63.js → chunk-EL5VHUGK.js} +79 -47
  39. package/dist/chunk-EL5VHUGK.js.map +1 -0
  40. package/dist/{chunk-4GYMCUDZ.js → chunk-EP4WVQLX.js} +5 -5
  41. package/dist/{chunk-SOOKUYVM.js → chunk-F7EKHR32.js} +13 -9
  42. package/dist/chunk-F7EKHR32.js.map +1 -0
  43. package/dist/{chunk-JKTC747G.js → chunk-FQSQONOU.js} +4 -4
  44. package/dist/{chunk-JGFRAFDL.js → chunk-FVINAAKA.js} +3 -3
  45. package/dist/{chunk-RAGGHLCV.js → chunk-GUNIRPEJ.js} +8 -6
  46. package/dist/{chunk-RAGGHLCV.js.map → chunk-GUNIRPEJ.js.map} +1 -1
  47. package/dist/{chunk-BU3SEFA5.js → chunk-IOJDYUA7.js} +2 -2
  48. package/dist/{chunk-Y52CS6YA.js → chunk-JA67ZQG2.js} +2 -2
  49. package/dist/{chunk-Y52CS6YA.js.map → chunk-JA67ZQG2.js.map} +1 -1
  50. package/dist/{chunk-DM4OMPWK.js → chunk-KNU73RZW.js} +2 -2
  51. package/dist/{chunk-GWRNLJNW.js → chunk-KRFGO5QH.js} +19 -15
  52. package/dist/{chunk-GWRNLJNW.js.map → chunk-KRFGO5QH.js.map} +1 -1
  53. package/dist/{chunk-Z4YXAUDN.js → chunk-KUFXLAEY.js} +11 -7
  54. package/dist/{chunk-Z4YXAUDN.js.map → chunk-KUFXLAEY.js.map} +1 -1
  55. package/dist/{chunk-Z6EGP5D7.js → chunk-LDCSZ72P.js} +2 -2
  56. package/dist/{chunk-5IMMNARC.js → chunk-MS3WPRJR.js} +37 -25
  57. package/dist/chunk-MS3WPRJR.js.map +1 -0
  58. package/dist/{chunk-CXANAIZU.js → chunk-N65E26UL.js} +3 -3
  59. package/dist/{chunk-O3MT7DYI.js → chunk-N6MNJNHB.js} +2 -2
  60. package/dist/{chunk-Q3EYOCZB.js → chunk-NPRP3MCV.js} +111 -2
  61. package/dist/chunk-NPRP3MCV.js.map +1 -0
  62. package/dist/{chunk-A7KV5UK4.js → chunk-OXD5LFQP.js} +2 -2
  63. package/dist/{chunk-ZT4WMQW4.js → chunk-PTWADEH3.js} +9 -7
  64. package/dist/chunk-PTWADEH3.js.map +1 -0
  65. package/dist/{chunk-IHTWQEDR.js → chunk-QFE5BQH7.js} +2 -2
  66. package/dist/{chunk-IHTWQEDR.js.map → chunk-QFE5BQH7.js.map} +1 -1
  67. package/dist/{chunk-22SG74BD.js → chunk-R6ZCSXKX.js} +3 -3
  68. package/dist/{chunk-PZWISPIQ.js → chunk-S7HN5FHL.js} +17 -11
  69. package/dist/chunk-S7HN5FHL.js.map +1 -0
  70. package/dist/{chunk-RJOG4IJU.js → chunk-T7SP3EYR.js} +18 -12
  71. package/dist/chunk-T7SP3EYR.js.map +1 -0
  72. package/dist/{chunk-4S53H2KR.js → chunk-VAZXUK6G.js} +2 -2
  73. package/dist/{chunk-IJRR6YAI.js → chunk-VLDRAMP7.js} +18 -12
  74. package/dist/chunk-VLDRAMP7.js.map +1 -0
  75. package/dist/{chunk-TNX5ZGDJ.js → chunk-VNXAF2KE.js} +4 -4
  76. package/dist/{chunk-EHRRQ4IC.js → chunk-VP3TIUDF.js} +2 -2
  77. package/dist/{chunk-6XZYT4SW.js → chunk-WGDEBIP4.js} +5 -5
  78. package/dist/{chunk-E5OZPDIW.js → chunk-X7BA5PWG.js} +7 -5
  79. package/dist/chunk-X7BA5PWG.js.map +1 -0
  80. package/dist/compat/index.cjs +1 -1
  81. package/dist/compat/index.cjs.map +1 -1
  82. package/dist/compat/index.js +2 -2
  83. package/dist/compat/nestjs/index.cjs +1 -1
  84. package/dist/compat/nestjs/index.cjs.map +1 -1
  85. package/dist/compat/nestjs/index.js +2 -2
  86. package/dist/index.cjs +1657 -982
  87. package/dist/index.cjs.map +1 -1
  88. package/dist/index.d.cts +2 -2
  89. package/dist/index.d.ts +2 -2
  90. package/dist/index.js +58 -36
  91. package/dist/index.js.map +1 -1
  92. package/dist/presets/ai/index.cjs +42 -26
  93. package/dist/presets/ai/index.cjs.map +1 -1
  94. package/dist/presets/ai/index.js +11 -11
  95. package/dist/presets/harness/index.cjs +53 -33
  96. package/dist/presets/harness/index.cjs.map +1 -1
  97. package/dist/presets/harness/index.js +22 -22
  98. package/dist/presets/index.cjs +76 -48
  99. package/dist/presets/index.cjs.map +1 -1
  100. package/dist/presets/index.js +28 -28
  101. package/dist/presets/inspect/index.cjs.map +1 -1
  102. package/dist/presets/inspect/index.js +4 -4
  103. package/dist/presets/resilience/index.cjs +35 -23
  104. package/dist/presets/resilience/index.cjs.map +1 -1
  105. package/dist/presets/resilience/index.js +5 -5
  106. package/dist/solutions/index.cjs +71 -45
  107. package/dist/solutions/index.cjs.map +1 -1
  108. package/dist/solutions/index.js +24 -24
  109. package/dist/{timeout-U5O4ESK3.js → timeout-BEABACRP.js} +2 -2
  110. package/dist/utils/ai/browser.cjs.map +1 -1
  111. package/dist/utils/ai/browser.js +9 -9
  112. package/dist/utils/ai/index.cjs +41 -25
  113. package/dist/utils/ai/index.cjs.map +1 -1
  114. package/dist/utils/ai/index.js +18 -18
  115. package/dist/utils/ai/node.js +3 -3
  116. package/dist/utils/domain-templates/index.cjs +1 -1
  117. package/dist/utils/domain-templates/index.cjs.map +1 -1
  118. package/dist/utils/domain-templates/index.js +2 -2
  119. package/dist/utils/graphspec/index.cjs +1 -1
  120. package/dist/utils/graphspec/index.cjs.map +1 -1
  121. package/dist/utils/graphspec/index.js +2 -2
  122. package/dist/utils/harness/index.cjs +16 -10
  123. package/dist/utils/harness/index.cjs.map +1 -1
  124. package/dist/utils/harness/index.js +1 -1
  125. package/dist/utils/index.cjs +1069 -452
  126. package/dist/utils/index.cjs.map +1 -1
  127. package/dist/utils/index.d.cts +2 -2
  128. package/dist/utils/index.d.ts +2 -2
  129. package/dist/utils/index.js +45 -23
  130. package/dist/utils/inspect/index.cjs.map +1 -1
  131. package/dist/utils/inspect/index.js +2 -2
  132. package/dist/utils/memory/index.cjs +513 -37
  133. package/dist/utils/memory/index.cjs.map +1 -1
  134. package/dist/utils/memory/index.d.cts +641 -3
  135. package/dist/utils/memory/index.d.ts +641 -3
  136. package/dist/utils/memory/index.js +19 -1
  137. package/dist/utils/messaging/index.cjs +109 -0
  138. package/dist/utils/messaging/index.cjs.map +1 -1
  139. package/dist/utils/messaging/index.d.cts +115 -2
  140. package/dist/utils/messaging/index.d.ts +115 -2
  141. package/dist/utils/messaging/index.js +5 -1
  142. package/dist/utils/orchestration/index.cjs +5 -3
  143. package/dist/utils/orchestration/index.cjs.map +1 -1
  144. package/dist/utils/orchestration/index.js +2 -2
  145. package/dist/utils/process/index.js +2 -2
  146. package/dist/utils/reduction/index.cjs +1 -1
  147. package/dist/utils/reduction/index.cjs.map +1 -1
  148. package/dist/utils/reduction/index.js +1 -1
  149. package/dist/utils/resilience/index.cjs +35 -23
  150. package/dist/utils/resilience/index.cjs.map +1 -1
  151. package/dist/utils/resilience/index.js +4 -4
  152. package/dist/utils/surface/index.cjs +1 -1
  153. package/dist/utils/surface/index.cjs.map +1 -1
  154. package/dist/utils/surface/index.js +3 -3
  155. package/package.json +1 -1
  156. package/dist/chunk-5IMMNARC.js.map +0 -1
  157. package/dist/chunk-CGHORL6G.js.map +0 -1
  158. package/dist/chunk-DKNHAICT.js.map +0 -1
  159. package/dist/chunk-E5OZPDIW.js.map +0 -1
  160. package/dist/chunk-IJRR6YAI.js.map +0 -1
  161. package/dist/chunk-OCUDSN63.js.map +0 -1
  162. package/dist/chunk-PZWISPIQ.js.map +0 -1
  163. package/dist/chunk-Q3EYOCZB.js.map +0 -1
  164. package/dist/chunk-RJOG4IJU.js.map +0 -1
  165. package/dist/chunk-SOOKUYVM.js.map +0 -1
  166. package/dist/chunk-YJ4U2D2C.js.map +0 -1
  167. package/dist/chunk-ZT4WMQW4.js.map +0 -1
  168. package/dist/chunk-ZVXXDWIB.js.map +0 -1
  169. /package/dist/{chunk-VLAGJZSL.js.map → chunk-3O3NKZJW.js.map} +0 -0
  170. /package/dist/{chunk-2OB3CEJS.js.map → chunk-6MRSX3YK.js.map} +0 -0
  171. /package/dist/{chunk-EVYY4X5A.js.map → chunk-6ZLCPUXS.js.map} +0 -0
  172. /package/dist/{chunk-FW23JYNQ.js.map → chunk-CEVNQ74M.js.map} +0 -0
  173. /package/dist/{chunk-4GYMCUDZ.js.map → chunk-EP4WVQLX.js.map} +0 -0
  174. /package/dist/{chunk-JKTC747G.js.map → chunk-FQSQONOU.js.map} +0 -0
  175. /package/dist/{chunk-JGFRAFDL.js.map → chunk-FVINAAKA.js.map} +0 -0
  176. /package/dist/{chunk-BU3SEFA5.js.map → chunk-IOJDYUA7.js.map} +0 -0
  177. /package/dist/{chunk-DM4OMPWK.js.map → chunk-KNU73RZW.js.map} +0 -0
  178. /package/dist/{chunk-Z6EGP5D7.js.map → chunk-LDCSZ72P.js.map} +0 -0
  179. /package/dist/{chunk-CXANAIZU.js.map → chunk-N65E26UL.js.map} +0 -0
  180. /package/dist/{chunk-O3MT7DYI.js.map → chunk-N6MNJNHB.js.map} +0 -0
  181. /package/dist/{chunk-A7KV5UK4.js.map → chunk-OXD5LFQP.js.map} +0 -0
  182. /package/dist/{chunk-22SG74BD.js.map → chunk-R6ZCSXKX.js.map} +0 -0
  183. /package/dist/{chunk-4S53H2KR.js.map → chunk-VAZXUK6G.js.map} +0 -0
  184. /package/dist/{chunk-TNX5ZGDJ.js.map → chunk-VNXAF2KE.js.map} +0 -0
  185. /package/dist/{chunk-EHRRQ4IC.js.map → chunk-VP3TIUDF.js.map} +0 -0
  186. /package/dist/{chunk-6XZYT4SW.js.map → chunk-WGDEBIP4.js.map} +0 -0
  187. /package/dist/{timeout-U5O4ESK3.js.map → timeout-BEABACRP.js.map} +0 -0
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/utils/memory/index.ts","../src/utils/memory/fact-store.ts","../src/utils/memory/persistent-fact-store.ts","../src/utils/memory/recipes/admission-llm-judge.ts","../src/utils/memory/recipes/bitemporal-query.ts","../src/utils/memory/recipes/_shared.ts","../src/utils/memory/recipes/consolidation-rem.ts","../src/utils/memory/recipes/decay-exponential.ts","../src/utils/memory/recipes/influence-analysis.ts","../src/utils/memory/recipes/invalidation-tracer.ts","../src/utils/memory/recipes/scoring-by-outcome.ts","../src/utils/memory/recipes/shard-by-tenant.ts"],"sourcesContent":["/**\n * Memory patterns (roadmap §4.3) — public-face Phase-4 primitives audited under\n * `archive/docs/SESSION-public-face-blocks-review.md` (Wave A, locked 2026-04-25).\n *\n * Three primitives (the pure `decay` helper was promoted to `extra/utils/decay.ts`\n * per Tier 2.2 and is no longer re-exported here; `lightCollection` was folded\n * into `collection({ranked:false})` per Tier 2.3 and is no longer a separate\n * factory):\n * - {@link collection} / {@link CollectionGraph} — keyed memory store with\n * optional decay-aware ranking. Pass `{ ranked: false }` for the previous\n * `lightCollection` shape (Map + LRU + audit, no scoring).\n * - {@link vectorIndex} / {@link VectorIndexGraph} — reactive vector store with\n * optional HNSW backend, retention, and reactive {@link VectorIndexGraph.searchNode}.\n * - {@link knowledgeGraph} / {@link KnowledgeGraph} — entities + typed edges with\n * symmetric adjacency indexes and reactive {@link KnowledgeGraph.relatedNode}.\n *\n * **No imperative reads.** Per the API-style policy locked 2026-04-25, public-face\n * primitives expose reactive reads only — `itemNode` / `hasNode` / `searchNode` /\n * `relatedNode`. One-shot snapshots use `node.cache` after `awaitSettled`, or\n * `firstValueFrom(node)`.\n *\n * **Audit logs.** Every imperative mutation (`upsert / remove / clear / link /\n * unlink / rescore / reindex`) is wrapped via {@link mutate} and appends a\n * typed record to a public `events` log on the bundle / graph.\n *\n * @module\n */\n\nimport { monotonicNs, type Node, NodeImpl, node, wallClockNs } from \"@graphrefly/pure-ts/core\";\nimport type { ReactiveLogBundle } from \"@graphrefly/pure-ts/extra\";\nimport { fromTimer, keepalive, reactiveMap } from \"@graphrefly/pure-ts/extra\";\nimport { Graph } from \"@graphrefly/pure-ts/graph\";\nimport { domainMeta } from \"../../base/meta/domain-meta.js\";\nimport {\n\ttype BaseAuditRecord,\n\tbumpCursor,\n\tcreateAuditLog,\n\tmutate,\n\tregisterCursor,\n} from \"../../base/mutation/index.js\";\nimport type { NodeOrValue } from \"../../base/resilience/_internal.js\";\nimport { decay } from \"../../base/utils/decay.js\";\n\n// ── Shared helpers ───────────────────────────────────────────────────────\n\nconst NS_PER_SEC = 1_000_000_000;\n\nfunction memoryMeta(kind: string, extra?: Record<string, unknown>): Record<string, unknown> {\n\treturn domainMeta(\"memory\", kind, extra);\n}\n\n/**\n * Coerce a value-or-Node argument into a `Node<T>`. Pass-through if already a\n * Node; otherwise wraps in `state(value, {name})`. Used by reactive read\n * factories (`itemNode` / `searchNode` / `relatedNode`) so callers can supply\n * a static value without manually creating a state node.\n *\n * Heuristic: anything that is a `NodeImpl` instance is a Node; everything else\n * is treated as a raw value to wrap.\n */\nfunction toNode<T>(v: T | Node<T>, name?: string): Node<T> {\n\tif (v instanceof NodeImpl) return v as Node<T>;\n\treturn node<T>([], { initial: v as T, ...(name ? { name } : undefined) });\n}\n\nfunction ageSeconds(now: number, lastNs: number): number {\n\treturn (now - lastNs) / NS_PER_SEC;\n}\n\n// `decay` was promoted to `extra/utils/decay.ts` per Tier 2.2 — it is no longer\n// re-exported from this module. Import from `@graphrefly/graphrefly/extra` (or\n// `../../extra/utils/decay.js` internally) instead.\n\n/**\n * Cosine similarity over `(a, b)`. When lengths differ, the shorter is\n * implicitly zero-padded to the longer length. Returns `0` if either vector\n * has zero norm. Public utility — used by {@link VectorIndexGraph.searchNode}\n * and exposed for downstream consumers (e.g. `patterns/ai/memory/`) that need\n * the same scoring at the boundary.\n *\n * **Numeric guards.** Returns `0` for non-finite results (overflow producing\n * `Infinity`/`NaN` from very-large vectors, or `NaN` propagating from any\n * `NaN`/`Infinity` component). Without this guard, downstream sort\n * comparators would order NaN-scored rows arbitrarily.\n *\n * **Depth.** This is a per-call computation; no internal caching. For very\n * large indexes (>10k) consider precomputing norms or using HNSW.\n *\n * @category memory\n */\nexport function cosineSimilarity(a: readonly number[], b: readonly number[]): number {\n\tconst n = Math.max(a.length, b.length);\n\tlet dot = 0;\n\tlet na = 0;\n\tlet nb = 0;\n\tfor (let i = 0; i < n; i += 1) {\n\t\tconst av = a[i] ?? 0;\n\t\tconst bv = b[i] ?? 0;\n\t\tdot += av * bv;\n\t\tna += av * av;\n\t\tnb += bv * bv;\n\t}\n\tif (na === 0 || nb === 0) return 0;\n\tconst score = dot / Math.sqrt(na * nb);\n\treturn Number.isFinite(score) ? score : 0;\n}\n\n/**\n * Equality predicate for {@link VectorIndexGraph.searchNode} results. Compares\n * `id` AND `score` AND `meta` reference per position so that score-only changes\n * (re-upsert with new vector keeping the same top-K order) propagate to\n * downstream subscribers. The previous id-only comparator silently dropped\n * those updates.\n */\nfunction searchResultsEqual<TMeta>(\n\ta: readonly VectorSearchResult<TMeta>[] | undefined,\n\tb: readonly VectorSearchResult<TMeta>[] | undefined,\n): boolean {\n\tif (a === b) return true;\n\tif (a == null || b == null) return false;\n\tif (a.length !== b.length) return false;\n\tfor (let i = 0; i < a.length; i += 1) {\n\t\tconst x = a[i]!;\n\t\tconst y = b[i]!;\n\t\tif (x.id !== y.id || x.score !== y.score || x.meta !== y.meta) return false;\n\t}\n\treturn true;\n}\n\n// ── Common types ─────────────────────────────────────────────────────────\n\n/** Public alias for the `Node | value` shape accepted by reactive read factories. */\nexport type { NodeOrValue } from \"../../base/resilience/_internal.js\";\n\n// ── Unit 2 (Tier 2.3 fold): collection (formerly lightCollection + collection)\n//\n// Pre-Tier-2.3 the module shipped `lightCollection` (no Graph, no ranking,\n// just LRU + audit) alongside `collection` (Graph-mounted with timer-driven\n// decay-aware ranking). Per the consolidation plan §1 Rule 4, the two are\n// folded into a single `collection({ranked: true|false})` factory: when\n// `ranked: false`, no `ranked` derived / refresh tick / scoring is wired.\n// `LightCollectionEntry` is gone — `CollectionEntry<T>` is the unified entry\n// shape (`baseScore` reads `0` in unranked mode).\n\nexport type CollectionEntry<T> = {\n\treadonly id: string;\n\treadonly value: T;\n\treadonly createdAtNs: number;\n\treadonly lastAccessNs: number;\n\treadonly baseScore: number;\n};\n\nexport type RankedCollectionEntry<T> = CollectionEntry<T> & {\n\treadonly score: number;\n};\n\nexport type CollectionScoreFn<T> = (value: T) => number;\n\nexport type CollectionOptions<T> = {\n\tmaxSize?: number;\n\t/**\n\t * Whether to expose a live decay-aware `ranked` node + `rescore` mutator.\n\t * Default `true`. Pass `false` to fold in the previous `lightCollection`\n\t * shape — entries are still keyed/audited/LRU-evicted, but the timer-driven\n\t * `ranked` + scoring machinery is skipped. `ranked` then resolves to a\n\t * static empty array Node and `rescore()` is a no-op (so callers writing\n\t * type-generic code don't need to special-case the unranked path).\n\t */\n\tranked?: boolean;\n\t/**\n\t * Produces a base score at insert/update time. Static fn or a reactive\n\t * `Node<(value: T) => number>` — when supplied as a Node, `ranked` re-derives\n\t * whenever the score fn changes, but `baseScore` on each entry is only\n\t * recomputed via {@link CollectionGraph.rescore}. Default `() => 1`.\n\t *\n\t * Ignored when `ranked: false` (entries record `baseScore: 0`).\n\t */\n\tscore?: CollectionScoreFn<T> | Node<CollectionScoreFn<T>>;\n\t/**\n\t * Exponential decay rate per second. `0` disables decay (default). When\n\t * positive, `ranked` becomes fully reactive on time via a `fromTimer` source\n\t * (cadence auto-derived from `decayRate` unless overridden via\n\t * `refreshIntervalMs`). Half-life: `ratePerSecond = Math.LN2 / halfLifeSeconds`.\n\t *\n\t * Ignored when `ranked: false`.\n\t */\n\tdecayRate?: number;\n\t/** Minimum score floor after decay. Default `0`. */\n\tminScore?: number;\n\t/**\n\t * Override for the `ranked` refresh tick cadence (milliseconds). When\n\t * unset and `decayRate > 0`, defaults to `1000 * Math.LN2 / (10 * decayRate)`\n\t * — roughly one tick per 10% of the half-life (~10% staleness budget).\n\t */\n\trefreshIntervalMs?: number;\n};\n\nexport interface CollectionAuditRecord extends BaseAuditRecord {\n\treadonly action: \"upsert\" | \"remove\" | \"clear\" | \"rescore\";\n\treadonly id?: string;\n}\n\nexport type CollectionGraph<T> = Graph & {\n\treadonly events: ReactiveLogBundle<CollectionAuditRecord>;\n\treadonly items: Node<ReadonlyMap<string, CollectionEntry<T>>>;\n\t/**\n\t * Live decay-aware ranking, sorted by score descending. When the\n\t * collection was constructed with `ranked: false`, this is a static\n\t * empty-array Node (kept for type uniformity).\n\t */\n\treadonly ranked: Node<readonly RankedCollectionEntry<T>[]>;\n\treadonly size: Node<number>;\n\tupsert: (id: string, value: T, opts?: { score?: number }) => void;\n\tremove: (id: string) => void;\n\tclear: () => void;\n\t/**\n\t * Recompute every entry's `baseScore` via the latest score fn. O(N). Useful\n\t * when a reactive `score` Node has emitted a new fn and the caller wants\n\t * existing entries re-scored without an explicit re-upsert.\n\t *\n\t * No-op (still records an audit entry) when constructed with\n\t * `ranked: false`.\n\t */\n\trescore: () => void;\n\titemNode: (id: NodeOrValue<string>) => Node<CollectionEntry<T> | undefined>;\n\t/** Reactive `true` once the entry exists; tracks upsert / remove. */\n\thasNode: (id: NodeOrValue<string>) => Node<boolean>;\n};\n\nfunction rankedEqual<T>(\n\ta: readonly RankedCollectionEntry<T>[] | undefined,\n\tb: readonly RankedCollectionEntry<T>[] | undefined,\n): boolean {\n\tif (a === b) return true;\n\tif (a == null || b == null) return false;\n\tif (a.length !== b.length) return false;\n\tfor (let i = 0; i < a.length; i += 1) {\n\t\tconst x = a[i]!;\n\t\tconst y = b[i]!;\n\t\t// Compare value reference too — if `upsert(id, newValue)` runs and\n\t\t// `score(newValue) === score(oldValue)` AND timestamps coincide\n\t\t// (rare on platforms where consecutive `monotonicNs()` calls in the\n\t\t// same microtask collide), the prior comparator suppressed the\n\t\t// emission and consumers reading `entry.value` saw stale data.\n\t\t// Value identity catches it cheaply (`value !== value` only on NaN\n\t\t// payloads, which behave correctly here).\n\t\tif (\n\t\t\tx.id !== y.id ||\n\t\t\tx.score !== y.score ||\n\t\t\tx.lastAccessNs !== y.lastAccessNs ||\n\t\t\tx.value !== y.value\n\t\t)\n\t\t\treturn false;\n\t}\n\treturn true;\n}\n\n/**\n * Scored memory store with live decay-aware ranking.\n *\n * Topology (mounted on the returned graph):\n * - `items` — `reactiveMap<id, CollectionEntry<T>>` (with `retention` configured\n * for score-based eviction when `maxSize` is set).\n * - `ranked` — `Node<readonly RankedCollectionEntry<T>[]>`, sorted by live\n * decayed score. **Lazy** — does NOT compute until subscribed (no internal\n * keepalive). Use `keepalive(coll.ranked)` for eager activation.\n * - `size` — `Node<number>`, count of entries.\n * - `_refreshTick` — `fromTimer`-driven `monotonicNs()` source, mounted only\n * when `decayRate > 0`. Drives `ranked`'s time-dependent re-derivation.\n * - `_seq` — sequence cursor for the audit log.\n * - `events` — bounded reactive log of every mutation.\n *\n * **Time as a reactive dep.** When `decayRate > 0`, `ranked`'s deps are\n * `[items, refreshTick]` — the tick payload IS `monotonicNs()`, so the fn is\n * pure of deps and dry-run-reproducible with a mocked clock.\n *\n * **Lazy timer.** With no subscriber to `ranked`, the timer source does not\n * fire — the activation chain is downstream-driven. To keep the timer warm\n * without consuming results, register `graph.addDisposer(keepalive(coll.ranked))`.\n *\n * **Eviction at write-time.** Score-based retention runs on every successful\n * `upsert / remove / clear` (it is mutation-driven, not tick-driven). The\n * retention scorer reads `monotonicNs()` to compute decayed scores at eviction\n * time — this is a deliberate impurity vs. `ReactiveMapRetention.score`'s\n * \"pure of `(key, value)`\" docstring: write-time is the right moment to evict\n * stale-by-decay entries.\n *\n * **No imperative reads.** Subscribe to `items` / `ranked` for live snapshots,\n * or use `itemNode(id)` for single-key reactive reads.\n *\n * **`rescore` ordering caveat.** `rescore()` reads `items.entries.cache`\n * (the post-emission snapshot) and writes via `setMany`. When called\n * stand-alone it sees the latest committed state. When wrapped inside a\n * user-level `batch(() => { coll.upsert(...); coll.rescore(); })`, the\n * `cache` snapshot reflects state BEFORE the batch — so a just-staged\n * upsert is invisible to the rescore scan. If you need rescore to include\n * the staged upsert, either call `rescore()` after the batch settles or\n * pass the new `baseScore` directly via `upsert(id, value, { score })`.\n *\n * **Audit no-op records.** Like `lightCollection`, mutations record audit\n * entries even when the impl was a no-op (e.g., `rescore()` on an empty\n * store). Intentional — the framework records attempts.\n *\n * @category memory\n */\nexport function collection<T>(name: string, opts: CollectionOptions<T> = {}): CollectionGraph<T> {\n\tconst maxSize = opts.maxSize;\n\tconst ranked = opts.ranked ?? true;\n\t// `decayRate` / `score` / `refreshIntervalMs` are no-ops when ranked is off\n\t// (they only feed the `ranked` derived). The audit + LRU paths still run.\n\tconst decayRate = ranked ? (opts.decayRate ?? 0) : 0;\n\tconst minScore = opts.minScore ?? 0;\n\tif (maxSize !== undefined && maxSize < 1) {\n\t\tthrow new RangeError(\"collection: maxSize must be >= 1\");\n\t}\n\n\t// Resolve score fn — supports static fn or reactive Node<fn>. When\n\t// `ranked: false` the score is constant `0` and `readScoreFn` is unused\n\t// (the upsert path takes the `_opts.score ?? readScoreFn()(value)` branch\n\t// only when ranking is requested).\n\tconst scoreFnDefault: CollectionScoreFn<T> = () => (ranked ? 1 : 0);\n\tconst scoreInput = opts.score ?? scoreFnDefault;\n\tconst scoreNode: Node<CollectionScoreFn<T>> | undefined =\n\t\tranked && scoreInput instanceof NodeImpl\n\t\t\t? (scoreInput as Node<CollectionScoreFn<T>>)\n\t\t\t: undefined;\n\tconst readScoreFn = (): CollectionScoreFn<T> => {\n\t\tif (scoreNode) return scoreNode.cache ?? scoreFnDefault;\n\t\treturn scoreInput as CollectionScoreFn<T>;\n\t};\n\n\tconst graph = new Graph(name);\n\n\t// Score-based retention scorer for `reactiveMap`. When unranked the base\n\t// score is `0`, so retention falls back to LRU-by-`lastAccessNs` (the\n\t// older the access, the lower the decayed score → first to evict).\n\tconst retentionScore = (_k: string, v: CollectionEntry<T>): number =>\n\t\tranked\n\t\t\t? decay(v.baseScore, ageSeconds(monotonicNs(), v.lastAccessNs), decayRate, minScore)\n\t\t\t: v.lastAccessNs;\n\n\tconst items = reactiveMap<string, CollectionEntry<T>>({\n\t\tname: \"items\",\n\t\t...(maxSize !== undefined ? { retention: { score: retentionScore, maxSize } } : {}),\n\t});\n\n\tgraph.add(items.entries, { name: \"items\" });\n\n\t// Refresh tick — only mounted when ranking + decay are configured. Tick\n\t// payload is `monotonicNs()`, so `rankedNode`'s fn is pure-of-deps and\n\t// dry-run-reproducible.\n\tlet refreshTick: Node<number> | undefined;\n\tif (ranked && decayRate > 0) {\n\t\tconst intervalMs = opts.refreshIntervalMs ?? Math.max(1, (1000 * Math.LN2) / (10 * decayRate));\n\t\tconst tickCounter = fromTimer(intervalMs, { period: intervalMs });\n\t\t// Map each tick to the wall-clock `monotonicNs` — the tick payload IS\n\t\t// the time stamp downstream consumers use. Reading the central clock\n\t\t// inside this fn is sanctioned: this derived's purpose is to publish\n\t\t// \"now\" reactively (cf. spec §5.11 — central timer), and downstream\n\t\t// `rankedNode` reads it from its dep array, never from the clock\n\t\t// directly.\n\t\t//\n\t\t// `initial: monotonicNs()` seeds the cache with construction-time\n\t\t// `now` so push-on-subscribe delivers DATA to `rankedNode` before the\n\t\t// first tick fires — without this, `rankedNode` would stall in pending\n\t\t// status until ~`refreshIntervalMs` after first activation, and a\n\t\t// caller reading `rankedNode.cache` immediately after `upsert` would\n\t\t// see `undefined`.\n\t\trefreshTick = node(\n\t\t\t[tickCounter],\n\t\t\t(_batchData, actions) => {\n\t\t\t\tactions.emit(monotonicNs());\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"refresh_tick_ns\",\n\t\t\t\tdescribeKind: \"derived\",\n\t\t\t\tinitial: monotonicNs(),\n\t\t\t\tmeta: memoryMeta(\"clock\"),\n\t\t\t},\n\t\t);\n\t\tgraph.add(refreshTick, { name: \"refresh_tick_ns\" });\n\t}\n\n\t// `rankedNode` derived — pure of (items, refreshTick?, scoreNode?). When\n\t// `ranked: false`, `rankedNode` is a static empty-array node so the\n\t// public type stays uniform without re-running the sort.\n\tlet rankedNode: Node<readonly RankedCollectionEntry<T>[]>;\n\tif (ranked) {\n\t\tconst rankedDeps: Node<unknown>[] = [items.entries];\n\t\tif (refreshTick) rankedDeps.push(refreshTick);\n\t\tif (scoreNode) rankedDeps.push(scoreNode);\n\t\trankedNode = node(\n\t\t\trankedDeps,\n\t\t\t(batchData, actions, ctx) => {\n\t\t\t\tconst values = batchData.map((batch, i) =>\n\t\t\t\t\tbatch != null && batch.length > 0 ? batch.at(-1) : ctx.prevData[i],\n\t\t\t\t);\n\t\t\t\tconst snapshot = values[0] as ReadonlyMap<string, CollectionEntry<T>> | undefined;\n\t\t\t\tlet now: number;\n\t\t\t\tif (refreshTick) {\n\t\t\t\t\tconst tickValue = values[1] as number | undefined;\n\t\t\t\t\tnow = typeof tickValue === \"number\" ? tickValue : monotonicNs();\n\t\t\t\t} else {\n\t\t\t\t\tnow = monotonicNs();\n\t\t\t\t}\n\t\t\t\tif (!snapshot || snapshot.size === 0) {\n\t\t\t\t\tactions.emit([] as readonly RankedCollectionEntry<T>[]);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tconst out: RankedCollectionEntry<T>[] = [];\n\t\t\t\tfor (const entry of snapshot.values()) {\n\t\t\t\t\tout.push({\n\t\t\t\t\t\t...entry,\n\t\t\t\t\t\tscore: decay(entry.baseScore, ageSeconds(now, entry.lastAccessNs), decayRate, minScore),\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t\tout.sort((a, b) => b.score - a.score || b.lastAccessNs - a.lastAccessNs);\n\t\t\t\tactions.emit(out as readonly RankedCollectionEntry<T>[]);\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"ranked\",\n\t\t\t\tdescribeKind: \"derived\",\n\t\t\t\tequals: rankedEqual,\n\t\t\t\tmeta: memoryMeta(\"ranked\"),\n\t\t\t},\n\t\t) as Node<readonly RankedCollectionEntry<T>[]>;\n\t\tgraph.add(rankedNode, { name: \"ranked\" });\n\t} else {\n\t\trankedNode = node<readonly RankedCollectionEntry<T>[]>([], {\n\t\t\tinitial: [] as readonly RankedCollectionEntry<T>[],\n\t\t\tname: \"ranked\",\n\t\t\tdescribeKind: \"state\",\n\t\t\tmeta: memoryMeta(\"ranked_disabled\"),\n\t\t}) as Node<readonly RankedCollectionEntry<T>[]>;\n\t\tgraph.add(rankedNode, { name: \"ranked\" });\n\t}\n\n\tconst size = node(\n\t\t[items.entries],\n\t\t(batchData, actions, ctx) => {\n\t\t\tconst data = batchData.map((batch, i) =>\n\t\t\t\tbatch != null && batch.length > 0 ? batch.at(-1) : ctx.prevData[i],\n\t\t\t);\n\t\t\tconst snapshot = data[0] as ReadonlyMap<string, CollectionEntry<T>> | undefined;\n\t\t\tactions.emit(((snapshot ?? new Map()) as ReadonlyMap<string, CollectionEntry<T>>).size);\n\t\t},\n\t\t{\n\t\t\tname: \"size\",\n\t\t\tdescribeKind: \"derived\",\n\t\t\tinitial: 0,\n\t\t\tmeta: memoryMeta(\"size\"),\n\t\t},\n\t);\n\tgraph.add(size, { name: \"size\" });\n\t// Keepalive only on `size` (cheap; pure of items). `ranked` is intentionally\n\t// lazy so the refresh timer doesn't fire when nothing consumes the ranking.\n\tgraph.addDisposer(keepalive(size));\n\n\t// Audit log + seq cursor.\n\tconst events = createAuditLog<CollectionAuditRecord>({\n\t\tname: \"events\",\n\t\tretainedLimit: 1024,\n\t\tgraph,\n\t});\n\tconst seqCursor = registerCursor(graph, \"seq\", 0);\n\n\tconst upsertImpl = (id: string, value: T, _opts?: { score?: number }): void => {\n\t\tconst now = monotonicNs();\n\t\tconst prev = items.get(id);\n\t\tconst baseScore = _opts?.score ?? readScoreFn()(value);\n\t\titems.set(id, {\n\t\t\tid,\n\t\t\tvalue,\n\t\t\tbaseScore,\n\t\t\tcreatedAtNs: prev?.createdAtNs ?? now,\n\t\t\tlastAccessNs: now,\n\t\t});\n\t};\n\tconst removeImpl = (id: string): void => {\n\t\tif (!items.has(id)) return;\n\t\titems.delete(id);\n\t};\n\tconst clearImpl = (): void => {\n\t\tif (items.size === 0) return;\n\t\titems.clear();\n\t};\n\tconst rescoreImpl = (): void => {\n\t\t// `ranked: false` short-circuit — there's no live `ranked` node to\n\t\t// re-derive and `baseScore` is held at its insertion-time value, so\n\t\t// rescore is a no-op. The audit record is still emitted so consumers\n\t\t// see the attempt.\n\t\tif (!ranked) return;\n\t\tconst fn = readScoreFn();\n\t\tconst snapshot = items.entries.cache as ReadonlyMap<string, CollectionEntry<T>> | undefined;\n\t\tif (!snapshot || snapshot.size === 0) return;\n\t\tconst updates: Array<[string, CollectionEntry<T>]> = [];\n\t\tfor (const entry of snapshot.values()) {\n\t\t\tupdates.push([entry.id, { ...entry, baseScore: fn(entry.value) }]);\n\t\t}\n\t\titems.setMany(updates);\n\t};\n\n\tconst upsert = mutate(upsertImpl, {\n\t\tframe: \"inline\",\n\t\tlog: events,\n\t\tseq: seqCursor,\n\t\tonSuccessRecord: ([id], _r, m) => ({ action: \"upsert\" as const, id, t_ns: m.t_ns, seq: m.seq }),\n\t});\n\tconst remove = mutate(removeImpl, {\n\t\tframe: \"inline\",\n\t\tlog: events,\n\t\tseq: seqCursor,\n\t\tonSuccessRecord: ([id], _r, m) => ({ action: \"remove\" as const, id, t_ns: m.t_ns, seq: m.seq }),\n\t});\n\tconst clear = mutate(clearImpl, {\n\t\tframe: \"inline\",\n\t\tlog: events,\n\t\tseq: seqCursor,\n\t\tonSuccessRecord: (_args, _r, m) => ({ action: \"clear\" as const, t_ns: m.t_ns, seq: m.seq }),\n\t});\n\tconst rescore = mutate(rescoreImpl, {\n\t\tframe: \"inline\",\n\t\tlog: events,\n\t\tseq: seqCursor,\n\t\tonSuccessRecord: (_args, _r, m) => ({ action: \"rescore\" as const, t_ns: m.t_ns, seq: m.seq }),\n\t});\n\n\tfunction itemNode(id: NodeOrValue<string>): Node<CollectionEntry<T> | undefined> {\n\t\tconst idN = toNode(id, \"id\");\n\t\treturn node(\n\t\t\t[items.entries, idN],\n\t\t\t(batchData, actions, ctx) => {\n\t\t\t\tconst data = batchData.map((batch, i) =>\n\t\t\t\t\tbatch != null && batch.length > 0 ? batch.at(-1) : ctx.prevData[i],\n\t\t\t\t);\n\t\t\t\tconst map = data[0] as ReadonlyMap<string, CollectionEntry<T>> | undefined;\n\t\t\t\tconst key = data[1] as string;\n\t\t\t\tactions.emit(map?.get(key));\n\t\t\t},\n\t\t\t{\n\t\t\t\tdescribeKind: \"derived\",\n\t\t\t\tmeta: memoryMeta(\"collection_item\"),\n\t\t\t},\n\t\t);\n\t}\n\n\tfunction hasNode(id: NodeOrValue<string>): Node<boolean> {\n\t\tconst idN = toNode(id, \"id\");\n\t\treturn node(\n\t\t\t[items.entries, idN],\n\t\t\t(batchData, actions, ctx) => {\n\t\t\t\tconst data = batchData.map((batch, i) =>\n\t\t\t\t\tbatch != null && batch.length > 0 ? batch.at(-1) : ctx.prevData[i],\n\t\t\t\t);\n\t\t\t\tconst map = data[0] as ReadonlyMap<string, CollectionEntry<T>> | undefined;\n\t\t\t\tconst key = data[1] as string;\n\t\t\t\tactions.emit(map?.has(key) ?? false);\n\t\t\t},\n\t\t\t{\n\t\t\t\tdescribeKind: \"derived\",\n\t\t\t\tmeta: memoryMeta(\"collection_has\"),\n\t\t\t},\n\t\t);\n\t}\n\n\tconst out = Object.assign(graph, {\n\t\tevents,\n\t\titems: items.entries,\n\t\tranked: rankedNode,\n\t\tsize,\n\t\tupsert,\n\t\tremove,\n\t\tclear,\n\t\trescore,\n\t\titemNode,\n\t\thasNode,\n\t}) as CollectionGraph<T>;\n\treturn out;\n}\n\n// ── Unit 4: vectorIndex ──────────────────────────────────────────────────\n\nexport type VectorBackend = \"flat\" | \"hnsw\";\n\nexport type VectorRecord<TMeta> = {\n\treadonly id: string;\n\treadonly vector: readonly number[];\n\treadonly meta?: TMeta;\n\t/** Wall-clock-monotonic timestamp at last upsert; used for the default LRU retention. */\n\treadonly upsertedAtNs: number;\n};\n\nexport type VectorSearchResult<TMeta> = {\n\treadonly id: string;\n\treadonly score: number;\n\treadonly meta?: TMeta;\n};\n\nexport type HnswAdapter<TMeta> = {\n\tupsert: (id: string, vector: readonly number[], meta?: TMeta) => void;\n\tremove: (id: string) => void;\n\tclear: () => void;\n\tsearch: (query: readonly number[], k: number) => ReadonlyArray<VectorSearchResult<TMeta>>;\n\t/** Optional adapter teardown. Called from `graph.destroy()` via `addDisposer`. */\n\tdispose?: () => void;\n};\n\nexport type VectorIndexOptions<TMeta> = {\n\tname?: string;\n\tbackend?: VectorBackend;\n\tdimension?: number;\n\t/**\n\t * Strict-dimension default. When `true` (default) AND `dimension` is unset,\n\t * mixed-length upserts throw `RangeError`. Set `false` to opt into the\n\t * lenient zero-padding behavior of {@link VectorIndexGraph.searchNode}.\n\t */\n\tstrictDimension?: boolean;\n\t/** Optional dependency seam for HNSW. */\n\thnswFactory?: () => HnswAdapter<TMeta>;\n\t/** Maximum live entries (LRU-by-upsert-time when set; user-overridable via `retentionScore`). */\n\tmaxSize?: number;\n\t/** Custom retention scorer. Higher score = kept. Defaults to `r => r.upsertedAtNs`. */\n\tretentionScore?: (record: VectorRecord<TMeta>) => number;\n};\n\nexport interface VectorIndexAuditRecord extends BaseAuditRecord {\n\treadonly action: \"upsert\" | \"remove\" | \"clear\" | \"reindex\" | \"evict\";\n\treadonly id?: string;\n}\n\nexport type VectorIndexGraph<TMeta> = Graph & {\n\treadonly backend: VectorBackend;\n\treadonly events: ReactiveLogBundle<VectorIndexAuditRecord>;\n\treadonly entries: Node<ReadonlyMap<string, VectorRecord<TMeta>>>;\n\tupsert: (id: string, vector: readonly number[], meta?: TMeta) => void;\n\tremove: (id: string) => void;\n\tclear: () => void;\n\t/** Re-push every live entry into the optional HNSW adapter. No-op for `flat`. */\n\treindex: () => void;\n\t/**\n\t * Reactive top-K search. Re-derives whenever entries / query / k change.\n\t * Lazy. Use `firstValueFrom(searchNode(...))` for one-shot reads.\n\t */\n\tsearchNode: (\n\t\tquery: Node<readonly number[]>,\n\t\tk?: NodeOrValue<number>,\n\t) => Node<readonly VectorSearchResult<TMeta>[]>;\n};\n\n/**\n * Reactive vector store with optional HNSW backend.\n *\n * **Storage on `reactiveMap`.** `entries` is a `reactiveMap<id, VectorRecord<TMeta>>`\n * with optional score-based retention (`maxSize` + LRU-by-`upsertedAtNs` by\n * default; user can supply a custom `retentionScore`). On retention eviction,\n * the HNSW adapter (if configured) is also notified via `adapter.remove(id)`.\n *\n * **Reactive search.** `searchNode(queryNode, k)` returns a `Node<readonly\n * VectorSearchResult<TMeta>[]>` that re-derives on entries / query / k change.\n * Lazy — only computes when subscribed. Imperative `search()` is intentionally\n * not exposed (no-imperative-reads policy). Use `firstValueFrom(searchNode(...))`\n * for one-shot reads.\n *\n * **Strict dimension.** Default `strictDimension: true` — if `dimension` is\n * unset and an upsert produces a vector of a different length than the first\n * upserted, throws `RangeError`. Pass `strictDimension: false` to opt into\n * the lenient zero-padding fallback (the previous default).\n *\n * **Adapter lifecycle.** When the HNSW adapter exposes a `dispose()` method,\n * it is bound to the graph's teardown via `addDisposer`. When retention\n * evicts an entry, `adapter.remove(id)` is invoked synchronously inside the\n * retention `onArchive` callback.\n *\n * **Cosine zero-pad.** The flat backend uses cosine similarity over the\n * pairwise max-length zero-pad. Mixing dimensions silently degrades scores\n * unless strict mode catches it at upsert time. For embedding-model vectors,\n * L2-normalize at the source — `vectorIndex` does not normalize.\n *\n * @category memory\n */\nexport function vectorIndex<TMeta>(opts: VectorIndexOptions<TMeta> = {}): VectorIndexGraph<TMeta> {\n\tconst backend = opts.backend ?? \"flat\";\n\tconst dimension = opts.dimension;\n\tconst strictDimension = opts.strictDimension ?? true;\n\tconst maxSize = opts.maxSize;\n\tconst userRetentionScore = opts.retentionScore;\n\n\tlet hnsw: HnswAdapter<TMeta> | undefined;\n\tif (backend === \"hnsw\") {\n\t\thnsw = opts.hnswFactory?.();\n\t\tif (!hnsw) {\n\t\t\tthrow new Error(\n\t\t\t\t'vectorIndex backend \"hnsw\" requires an optional dependency adapter; install your HNSW package and provide `hnswFactory`.',\n\t\t\t);\n\t\t}\n\t}\n\n\tconst graph = new Graph(opts.name ?? \"vector_index\");\n\n\t// Track an inferred dimension when the user didn't lock it but strict mode\n\t// is on — first upsert sets it; subsequent mismatches throw.\n\tlet inferredDimension: number | undefined;\n\tfunction assertDimension(vector: readonly number[]): void {\n\t\tif (dimension !== undefined) {\n\t\t\tif (vector.length !== dimension) {\n\t\t\t\tthrow new RangeError(\n\t\t\t\t\t`vector dimension mismatch: expected ${dimension}, got ${vector.length}`,\n\t\t\t\t);\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\t\tif (!strictDimension) return;\n\t\tif (inferredDimension === undefined) {\n\t\t\tinferredDimension = vector.length;\n\t\t\treturn;\n\t\t}\n\t\tif (vector.length !== inferredDimension) {\n\t\t\tthrow new RangeError(\n\t\t\t\t`vector dimension mismatch: inferred ${inferredDimension} from first upsert, got ${vector.length}. ` +\n\t\t\t\t\t`Pass \\`strictDimension: false\\` to opt into zero-pad behavior, or set an explicit \\`dimension\\`.`,\n\t\t\t);\n\t\t}\n\t}\n\n\tconst baseRetentionScore = userRetentionScore ?? ((r: VectorRecord<TMeta>) => r.upsertedAtNs);\n\t// `clearInProgress` lets us short-circuit the per-entry `onArchive` →\n\t// `hnsw.remove(id)` cascade when the user calls `clearImpl()`. Retention\n\t// fires `onArchive` for every evicted entry; followed by an explicit\n\t// `hnsw.clear()` we'd double-touch the adapter. Inside `clearImpl` we\n\t// flip this flag, then call `hnsw.clear()` once at the end. (G fix.)\n\tlet clearInProgress = false;\n\n\t// `clearAuditPending` defers the per-entry `evict` audit emission when a\n\t// `clear()` is in flight — those evictions are reported as a single\n\t// `clear` action, not a flurry of `evict` records.\n\tconst events = createAuditLog<VectorIndexAuditRecord>({\n\t\tname: \"events\",\n\t\tretainedLimit: 1024,\n\t\tgraph,\n\t});\n\tconst seqCursor = registerCursor(graph, \"seq\", 0);\n\n\tconst entries = reactiveMap<string, VectorRecord<TMeta>>({\n\t\tname: \"entries\",\n\t\t...(maxSize !== undefined\n\t\t\t? {\n\t\t\t\t\tretention: {\n\t\t\t\t\t\tscore: (_k, v) => baseRetentionScore(v),\n\t\t\t\t\t\tmaxSize,\n\t\t\t\t\t\tonArchive: (key) => {\n\t\t\t\t\t\t\tif (clearInProgress) return;\n\t\t\t\t\t\t\tif (backend === \"hnsw\") hnsw!.remove(key);\n\t\t\t\t\t\t\t// E1: surface retention-driven evictions in the audit log\n\t\t\t\t\t\t\t// so replay consumers can reconstruct the live snapshot\n\t\t\t\t\t\t\t// from `events` alone. `seq` is bumped via the cursor;\n\t\t\t\t\t\t\t// the `t_ns` matches `wallClockNs()` for consistency\n\t\t\t\t\t\t\t// with `lightMutation`'s record stamping.\n\t\t\t\t\t\t\tevents.append({\n\t\t\t\t\t\t\t\taction: \"evict\" as const,\n\t\t\t\t\t\t\t\tid: key,\n\t\t\t\t\t\t\t\tt_ns: wallClockNs(),\n\t\t\t\t\t\t\t\tseq: bumpCursor(seqCursor),\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t}\n\t\t\t: {}),\n\t});\n\tgraph.add(entries.entries, { name: \"entries\" });\n\t// F1: keep `entries` warm so downstream consumers reading\n\t// `vectors.entries.cache` (e.g. `patterns/ai/memory/runRetrieval`) don't\n\t// rely on an external subscriber to activate the node. State nodes are\n\t// ROM and retain `.cache` regardless of subscribers — this `keepalive`\n\t// is defense-in-depth and matches the kg's adjacency keepalive pattern.\n\tgraph.addDisposer(keepalive(entries.entries));\n\n\t// HNSW dispose runs BEFORE state-node teardown via standard disposer\n\t// ordering (disposers drain first, then `[[TEARDOWN]]` propagates per\n\t// `Graph.destroy()`). This is the right ordering: free the adapter's\n\t// native resources before the reactive layer tears down.\n\tif (hnsw?.dispose) {\n\t\tconst disposeAdapter = hnsw.dispose.bind(hnsw);\n\t\tgraph.addDisposer(() => disposeAdapter());\n\t}\n\n\tconst upsertImpl = (id: string, vector: readonly number[], meta?: TMeta): void => {\n\t\tassertDimension(vector);\n\t\t// B1: mutate HNSW first so a throw aborts the reactive write. With\n\t\t// the prior order (entries.set then hnsw.upsert), an adapter throw\n\t\t// would leave entries holding a row HNSW didn't index. Now: HNSW\n\t\t// commits first; if it throws, entries is untouched and audit log\n\t\t// records the failure.\n\t\tif (backend === \"hnsw\") hnsw!.upsert(id, vector, meta);\n\t\t// Defensive copies: vector via `[...vector]`; meta via shallow spread\n\t\t// when it's a non-null object (Array.isArray covered first since arrays\n\t\t// are objects). Primitives, `null`, functions etc. pass through\n\t\t// unchanged. Documented depth limitation: nested objects in `meta` are\n\t\t// shared by reference.\n\t\tconst copiedMeta: TMeta | undefined = (() => {\n\t\t\tif (meta === undefined) return undefined;\n\t\t\tif (meta === null || typeof meta !== \"object\") return meta;\n\t\t\treturn Array.isArray(meta) ? ([...meta] as unknown as TMeta) : ({ ...meta } as TMeta);\n\t\t})();\n\t\tconst record: VectorRecord<TMeta> = {\n\t\t\tid,\n\t\t\tvector: [...vector],\n\t\t\t...(copiedMeta !== undefined ? { meta: copiedMeta } : {}),\n\t\t\tupsertedAtNs: monotonicNs(),\n\t\t};\n\t\tentries.set(id, record);\n\t};\n\tconst removeImpl = (id: string): void => {\n\t\tif (!entries.has(id)) return;\n\t\t// B1: HNSW first, then entries.\n\t\tif (backend === \"hnsw\") hnsw!.remove(id);\n\t\tentries.delete(id);\n\t};\n\tconst clearImpl = (): void => {\n\t\tif (entries.size === 0) return;\n\t\t// B1 + G: mark the clear-in-progress flag so retention `onArchive`\n\t\t// suppresses per-entry HNSW removes AND per-entry `evict` audit\n\t\t// records. Then call `entries.clear()` (drains the backend through\n\t\t// retention archival without side effects), and finally call\n\t\t// `hnsw.clear()` once. Reset `inferredDimension` so a fresh start\n\t\t// re-infers from the next upsert.\n\t\tclearInProgress = true;\n\t\ttry {\n\t\t\tentries.clear();\n\t\t\tif (backend === \"hnsw\") hnsw!.clear();\n\t\t} finally {\n\t\t\tclearInProgress = false;\n\t\t}\n\t\tinferredDimension = undefined;\n\t};\n\tconst reindexImpl = (): void => {\n\t\tif (backend !== \"hnsw\") return;\n\t\tconst snapshot = entries.entries.cache as ReadonlyMap<string, VectorRecord<TMeta>> | undefined;\n\t\tif (!snapshot) return;\n\t\thnsw!.clear();\n\t\tfor (const r of snapshot.values()) {\n\t\t\thnsw!.upsert(r.id, r.vector, r.meta);\n\t\t}\n\t};\n\n\t// `freeze: false` for `upsert` — deep-freezing a 768-dim vector is a\n\t// measurable hot-path tax, and the wrapper does its own defensive copy\n\t// (`vector: [...vector]`) before persisting. See §B.2 of the audit lock.\n\tconst upsert = mutate(upsertImpl, {\n\t\tframe: \"inline\",\n\t\tlog: events,\n\t\tfreeze: false,\n\t\tseq: seqCursor,\n\t\tonSuccessRecord: ([id], _r, m) => ({ action: \"upsert\" as const, id, t_ns: m.t_ns, seq: m.seq }),\n\t});\n\tconst remove = mutate(removeImpl, {\n\t\tframe: \"inline\",\n\t\tlog: events,\n\t\tseq: seqCursor,\n\t\tonSuccessRecord: ([id], _r, m) => ({ action: \"remove\" as const, id, t_ns: m.t_ns, seq: m.seq }),\n\t});\n\tconst clear = mutate(clearImpl, {\n\t\tframe: \"inline\",\n\t\tlog: events,\n\t\tseq: seqCursor,\n\t\tonSuccessRecord: (_args, _r, m) => ({ action: \"clear\" as const, t_ns: m.t_ns, seq: m.seq }),\n\t});\n\tconst reindex = mutate(reindexImpl, {\n\t\tframe: \"inline\",\n\t\tlog: events,\n\t\tseq: seqCursor,\n\t\tonSuccessRecord: (_args, _r, m) => ({ action: \"reindex\" as const, t_ns: m.t_ns, seq: m.seq }),\n\t});\n\n\tfunction searchNode(\n\t\tquery: Node<readonly number[]>,\n\t\tk: NodeOrValue<number> = 5,\n\t): Node<readonly VectorSearchResult<TMeta>[]> {\n\t\tconst kN = toNode<number>(k, \"k\");\n\t\treturn node(\n\t\t\t[entries.entries, query, kN],\n\t\t\t(batchData, actions, ctx) => {\n\t\t\t\tconst values = batchData.map((batch, i) =>\n\t\t\t\t\tbatch != null && batch.length > 0 ? batch.at(-1) : ctx.prevData[i],\n\t\t\t\t);\n\t\t\t\tconst snapshot = values[0] as ReadonlyMap<string, VectorRecord<TMeta>> | undefined;\n\t\t\t\tconst q = values[1] as readonly number[] | undefined;\n\t\t\t\tconst kRaw = values[2] as number;\n\t\t\t\t// Auto-fix: `Math.max(0, Math.floor(k))` — `| 0` is a 32-bit\n\t\t\t\t// signed truncation that collapses Infinity to 0 and wraps\n\t\t\t\t// values > 2^31. Use a proper floor with a non-negative floor.\n\t\t\t\tconst kVal = Number.isFinite(kRaw) ? Math.max(0, Math.floor(kRaw)) : 0;\n\t\t\t\tif (!snapshot || snapshot.size === 0 || kVal <= 0) {\n\t\t\t\t\tactions.emit([] as readonly VectorSearchResult<TMeta>[]);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\t// Auto-fix: defensive guard for unset / empty query — earlier\n\t\t\t\t// the fn would TypeError on `q.length` reading `undefined`,\n\t\t\t\t// or compute meaningless all-zero scores against an empty\n\t\t\t\t// vector. With strict-dimension OR an explicit `dimension`,\n\t\t\t\t// also reject mismatched-length queries (the imperative path\n\t\t\t\t// used to throw; reactive deriveds shouldn't throw, so\n\t\t\t\t// degrade to empty results).\n\t\t\t\tif (q == null || q.length === 0) {\n\t\t\t\t\tactions.emit([] as readonly VectorSearchResult<TMeta>[]);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tconst expectedDim = dimension ?? (strictDimension ? inferredDimension : undefined);\n\t\t\t\tif (expectedDim !== undefined && q.length !== expectedDim) {\n\t\t\t\t\tactions.emit([] as readonly VectorSearchResult<TMeta>[]);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tif (backend === \"hnsw\") {\n\t\t\t\t\t// Defensive copy of the adapter's return — HNSW libs\n\t\t\t\t\t// sometimes hand back internal buffers; downstream\n\t\t\t\t\t// subscribers must not be able to corrupt adapter state.\n\t\t\t\t\tconst adapterResults = hnsw!.search(q, kVal);\n\t\t\t\t\tactions.emit([...adapterResults] as readonly VectorSearchResult<TMeta>[]);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tconst ranked = [...snapshot.values()]\n\t\t\t\t\t.map((row) => {\n\t\t\t\t\t\tconst result: VectorSearchResult<TMeta> = {\n\t\t\t\t\t\t\tid: row.id,\n\t\t\t\t\t\t\tscore: cosineSimilarity(q, row.vector),\n\t\t\t\t\t\t\t...(row.meta !== undefined ? { meta: row.meta } : {}),\n\t\t\t\t\t\t};\n\t\t\t\t\t\treturn result;\n\t\t\t\t\t})\n\t\t\t\t\t.sort((a, b) => b.score - a.score)\n\t\t\t\t\t.slice(0, kVal);\n\t\t\t\tactions.emit(ranked as readonly VectorSearchResult<TMeta>[]);\n\t\t\t},\n\t\t\t{\n\t\t\t\tdescribeKind: \"derived\",\n\t\t\t\t// A1: include `score` in equality. The previous id-only\n\t\t\t\t// comparator suppressed re-emissions when the same set of\n\t\t\t\t// IDs/order had different scores (re-upsert with new\n\t\t\t\t// vector; query change preserving ranking order).\n\t\t\t\tequals: (a, b) => searchResultsEqual(a, b),\n\t\t\t\tmeta: memoryMeta(\"vector_search\"),\n\t\t\t},\n\t\t) as Node<readonly VectorSearchResult<TMeta>[]>;\n\t}\n\n\tconst out = Object.assign(graph, {\n\t\tbackend,\n\t\tevents,\n\t\tentries: entries.entries,\n\t\tupsert,\n\t\tremove,\n\t\tclear,\n\t\treindex,\n\t\tsearchNode,\n\t}) as VectorIndexGraph<TMeta>;\n\treturn out;\n}\n\n// ── Unit 5: knowledgeGraph ───────────────────────────────────────────────\n\nexport type KnowledgeEdge<TRelation extends string = string> = {\n\treadonly from: string;\n\treadonly to: string;\n\treadonly relation: TRelation;\n\treadonly weight: number;\n};\n\nexport type KnowledgeGraphOptions = {\n\t/** Cap on entity count (LRU-by-upsert-time when set). */\n\tentitiesMaxSize?: number;\n\t/** Cap on edge count (LRU-by-upsert-time when set). */\n\tedgesMaxSize?: number;\n\t/**\n\t * Orphan-entity garbage collection. `\"keep\"` (default) leaves entities\n\t * untouched when their last edge is unlinked; `\"remove\"` deletes the\n\t * entity post-`unlink` if no edges reference it.\n\t */\n\torphanGC?: \"keep\" | \"remove\";\n};\n\nexport interface KnowledgeGraphAuditRecord extends BaseAuditRecord {\n\treadonly action: \"upsertEntity\" | \"removeEntity\" | \"link\" | \"unlink\" | \"orphanRemove\";\n\treadonly id?: string;\n\treadonly from?: string;\n\treadonly to?: string;\n\treadonly relation?: string;\n\t/** Edge weight at the time of the `link`. Omitted for non-edge actions. */\n\treadonly weight?: number;\n}\n\nexport type KnowledgeGraph<TEntity, TRelation extends string = string> = Graph & {\n\treadonly events: ReactiveLogBundle<KnowledgeGraphAuditRecord>;\n\treadonly entities: Node<ReadonlyMap<string, TEntity>>;\n\treadonly edges: Node<ReadonlyMap<string, KnowledgeEdge<TRelation>>>;\n\treadonly adjacencyOut: Node<ReadonlyMap<string, readonly KnowledgeEdge<TRelation>[]>>;\n\treadonly adjacencyIn: Node<ReadonlyMap<string, readonly KnowledgeEdge<TRelation>[]>>;\n\treadonly entityCount: Node<number>;\n\treadonly edgeCount: Node<number>;\n\tupsertEntity: (id: string, value: TEntity) => void;\n\tremoveEntity: (id: string) => void;\n\tlink: (from: string, to: string, relation: TRelation, weight?: number) => void;\n\tunlink: (from: string, to: string, relation?: TRelation) => void;\n\trelatedNode: (\n\t\tid: NodeOrValue<string>,\n\t\trelation?: NodeOrValue<TRelation>,\n\t) => Node<readonly KnowledgeEdge<TRelation>[]>;\n};\n\nconst TRIPLE_SEP = \"\u0000\";\nfunction tripleKey(from: string, to: string, relation: string): string {\n\treturn `${from}${TRIPLE_SEP}${to}${TRIPLE_SEP}${relation}`;\n}\n\nfunction buildAdjacency<TRelation extends string>(\n\tedges: ReadonlyMap<string, KnowledgeEdge<TRelation>> | undefined,\n\tside: \"from\" | \"to\",\n): ReadonlyMap<string, readonly KnowledgeEdge<TRelation>[]> {\n\tif (!edges || edges.size === 0) return new Map();\n\tconst buckets = new Map<string, KnowledgeEdge<TRelation>[]>();\n\tfor (const edge of edges.values()) {\n\t\tconst key = side === \"from\" ? edge.from : edge.to;\n\t\tlet bucket = buckets.get(key);\n\t\tif (!bucket) {\n\t\t\tbucket = [];\n\t\t\tbuckets.set(key, bucket);\n\t\t}\n\t\tbucket.push(edge);\n\t}\n\tconst out = new Map<string, readonly KnowledgeEdge<TRelation>[]>();\n\tfor (const [key, bucket] of buckets) out.set(key, Object.freeze(bucket));\n\treturn out;\n}\n\nfunction adjacencyEqual<TRelation extends string>(\n\ta: ReadonlyMap<string, readonly KnowledgeEdge<TRelation>[]> | undefined,\n\tb: ReadonlyMap<string, readonly KnowledgeEdge<TRelation>[]> | undefined,\n): boolean {\n\tif (a === b) return true;\n\tif (a == null || b == null) return false;\n\tif (a.size !== b.size) return false;\n\tfor (const [k, av] of a) {\n\t\tconst bv = b.get(k);\n\t\tif (!bv || av.length !== bv.length) return false;\n\t\tfor (let i = 0; i < av.length; i += 1) {\n\t\t\tconst ae = av[i]!;\n\t\t\tconst be = bv[i]!;\n\t\t\tif (\n\t\t\t\tae.from !== be.from ||\n\t\t\t\tae.to !== be.to ||\n\t\t\t\tae.relation !== be.relation ||\n\t\t\t\tae.weight !== be.weight\n\t\t\t)\n\t\t\t\treturn false;\n\t\t}\n\t}\n\treturn true;\n}\n\n/**\n * Reactive knowledge graph: entities + typed edges + symmetric adjacency.\n *\n * Topology (mounted on the returned graph):\n * - `entities` — `reactiveMap<id, TEntity>` (optional `entitiesMaxSize` LRU).\n * - `edges` — `reactiveMap<tripleKey, KnowledgeEdge<TRelation>>` keyed by\n * `${from}\u0000${to}\u0000${relation}` (optional `edgesMaxSize` LRU).\n * Entity IDs / relations must NOT contain `\u0000`.\n * - `adjacencyOut` — `Node<ReadonlyMap<from, readonly edge[]>>`. **Full O(E)\n * rebuild on every `link` / `unlink` mutation.** (Prior JSDoc claim of\n * \"O(E) build\" referred to a single rebuild — the per-mutation cost is\n * O(E), not O(1) amortized. For very large graphs with frequent edge\n * churn, consider batching via `reactiveMap.setMany`.)\n * - `adjacencyIn` — `Node<ReadonlyMap<to, readonly edge[]>>`. Same O(E) per\n * mutation rebuild characteristic.\n * - `entityCount` / `edgeCount` — observability deriveds.\n * - `events` — bounded reactive audit log.\n *\n * **`link()` semantics.** Calling `link(a, b, rel, w)` twice with different\n * weights replaces the weight on the existing edge (keyed by the triple).\n * `unlink` then `link` re-creates the edge (and bumps `lastUpsertNs` for\n * retention purposes).\n *\n * **Edge weight convention.** Higher weight = stronger relation. Default `1`.\n *\n * **Orphan GC.** `orphanGC: \"remove\"` deletes an entity from `entities` after\n * an `unlink` that empties its adjacency on both sides. Default `\"keep\"`.\n *\n * **No imperative reads.** Use `relatedNode(id, relation?)` for reactive reads.\n *\n * @category memory\n */\nexport function knowledgeGraph<TEntity, TRelation extends string = string>(\n\tname: string,\n\topts: KnowledgeGraphOptions = {},\n): KnowledgeGraph<TEntity, TRelation> {\n\tconst orphanGC = opts.orphanGC ?? \"keep\";\n\tif (opts.entitiesMaxSize !== undefined && opts.entitiesMaxSize < 1) {\n\t\tthrow new RangeError(\"knowledgeGraph: entitiesMaxSize must be >= 1\");\n\t}\n\tif (opts.edgesMaxSize !== undefined && opts.edgesMaxSize < 1) {\n\t\tthrow new RangeError(\"knowledgeGraph: edgesMaxSize must be >= 1\");\n\t}\n\n\tconst graph = new Graph(name);\n\n\tconst entitiesMap = reactiveMap<string, TEntity>({\n\t\tname: \"entities\",\n\t\t...(opts.entitiesMaxSize !== undefined ? { maxSize: opts.entitiesMaxSize } : {}),\n\t});\n\tconst edgesMap = reactiveMap<string, KnowledgeEdge<TRelation>>({\n\t\tname: \"edges\",\n\t\t...(opts.edgesMaxSize !== undefined ? { maxSize: opts.edgesMaxSize } : {}),\n\t});\n\tgraph.add(entitiesMap.entries, { name: \"entities\" });\n\tgraph.add(edgesMap.entries, { name: \"edges\" });\n\n\tconst adjacencyOut = node(\n\t\t[edgesMap.entries],\n\t\t(batchData, actions, ctx) => {\n\t\t\tconst data = batchData.map((batch, i) =>\n\t\t\t\tbatch != null && batch.length > 0 ? batch.at(-1) : ctx.prevData[i],\n\t\t\t);\n\t\t\tconst snapshot = data[0] as ReadonlyMap<string, KnowledgeEdge<TRelation>> | undefined;\n\t\t\tactions.emit(buildAdjacency<TRelation>(snapshot, \"from\"));\n\t\t},\n\t\t{\n\t\t\tname: \"adjacencyOut\",\n\t\t\tdescribeKind: \"derived\",\n\t\t\tinitial: new Map() as ReadonlyMap<string, readonly KnowledgeEdge<TRelation>[]>,\n\t\t\tequals: adjacencyEqual,\n\t\t\tmeta: memoryMeta(\"adjacency_out\"),\n\t\t},\n\t) as Node<ReadonlyMap<string, readonly KnowledgeEdge<TRelation>[]>>;\n\tconst adjacencyIn = node(\n\t\t[edgesMap.entries],\n\t\t(batchData, actions, ctx) => {\n\t\t\tconst data = batchData.map((batch, i) =>\n\t\t\t\tbatch != null && batch.length > 0 ? batch.at(-1) : ctx.prevData[i],\n\t\t\t);\n\t\t\tconst snapshot = data[0] as ReadonlyMap<string, KnowledgeEdge<TRelation>> | undefined;\n\t\t\tactions.emit(buildAdjacency<TRelation>(snapshot, \"to\"));\n\t\t},\n\t\t{\n\t\t\tname: \"adjacencyIn\",\n\t\t\tdescribeKind: \"derived\",\n\t\t\tinitial: new Map() as ReadonlyMap<string, readonly KnowledgeEdge<TRelation>[]>,\n\t\t\tequals: adjacencyEqual,\n\t\t\tmeta: memoryMeta(\"adjacency_in\"),\n\t\t},\n\t) as Node<ReadonlyMap<string, readonly KnowledgeEdge<TRelation>[]>>;\n\tgraph.add(adjacencyOut, { name: \"adjacencyOut\" });\n\tgraph.add(adjacencyIn, { name: \"adjacencyIn\" });\n\tgraph.addDisposer(keepalive(adjacencyOut));\n\tgraph.addDisposer(keepalive(adjacencyIn));\n\n\tconst entityCount = node(\n\t\t[entitiesMap.entries],\n\t\t(batchData, actions, ctx) => {\n\t\t\tconst data = batchData.map((batch, i) =>\n\t\t\t\tbatch != null && batch.length > 0 ? batch.at(-1) : ctx.prevData[i],\n\t\t\t);\n\t\t\tconst m = data[0] as ReadonlyMap<string, TEntity> | undefined;\n\t\t\tactions.emit(((m ?? new Map()) as ReadonlyMap<string, TEntity>).size);\n\t\t},\n\t\t{ name: \"entityCount\", describeKind: \"derived\", initial: 0, meta: memoryMeta(\"entity_count\") },\n\t);\n\tconst edgeCount = node(\n\t\t[edgesMap.entries],\n\t\t(batchData, actions, ctx) => {\n\t\t\tconst data = batchData.map((batch, i) =>\n\t\t\t\tbatch != null && batch.length > 0 ? batch.at(-1) : ctx.prevData[i],\n\t\t\t);\n\t\t\tconst m = data[0] as ReadonlyMap<string, KnowledgeEdge<TRelation>> | undefined;\n\t\t\tactions.emit(((m ?? new Map()) as ReadonlyMap<string, KnowledgeEdge<TRelation>>).size);\n\t\t},\n\t\t{ name: \"edgeCount\", describeKind: \"derived\", initial: 0, meta: memoryMeta(\"edge_count\") },\n\t);\n\tgraph.add(entityCount, { name: \"entityCount\" });\n\tgraph.add(edgeCount, { name: \"edgeCount\" });\n\tgraph.addDisposer(keepalive(entityCount));\n\tgraph.addDisposer(keepalive(edgeCount));\n\n\tconst events = createAuditLog<KnowledgeGraphAuditRecord>({\n\t\tname: \"events\",\n\t\tretainedLimit: 1024,\n\t\tgraph,\n\t});\n\tconst seqCursor = registerCursor(graph, \"seq\", 0);\n\n\t/**\n\t * O(1) orphan check via the kept-warm `adjacency*` deriveds. Reading\n\t * `adjacencyOut.cache` / `adjacencyIn.cache` is safe here because both\n\t * are activated via `addDisposer(keepalive(...))` at construction time\n\t * (a derived's RAM cache only persists with at least one subscriber, and\n\t * the keepalive registers exactly that). The previous implementation\n\t * scanned `edgesMap.entries.cache` post-`deleteMany`, which depended on\n\t * the (sync) snapshot-emit timing of `reactiveMap` — fragile. The\n\t * `adjacency*.cache` approach is both faster (O(1) vs O(E) per check)\n\t * and timing-robust because the reactiveMap snapshot has already\n\t * propagated through the derived chain by the time we read.\n\t */\n\tfunction entityHasReferences(id: string): boolean {\n\t\tconst out = adjacencyOut.cache as\n\t\t\t| ReadonlyMap<string, readonly KnowledgeEdge<TRelation>[]>\n\t\t\t| undefined;\n\t\tconst inb = adjacencyIn.cache as\n\t\t\t| ReadonlyMap<string, readonly KnowledgeEdge<TRelation>[]>\n\t\t\t| undefined;\n\t\tif ((out?.get(id)?.length ?? 0) > 0) return true;\n\t\tif ((inb?.get(id)?.length ?? 0) > 0) return true;\n\t\treturn false;\n\t}\n\n\t/**\n\t * Apply orphan GC to a list of candidate entity ids. Used by both\n\t * {@link unlinkImpl} (post-edge-removal) and {@link removeEntityImpl}\n\t * (post-cascade) so semantics are consistent. Each removed entity\n\t * records a separate `orphanRemove` audit entry with its own monotonic\n\t * `seq` value (D1 fix — the previous bare `events.append(...)` skipped\n\t * the cursor advance, leaving gaps in the audit replay sequence).\n\t */\n\tfunction applyOrphanGC(candidates: readonly string[]): void {\n\t\tif (orphanGC !== \"remove\") return;\n\t\tfor (const candidate of candidates) {\n\t\t\tif (!entitiesMap.has(candidate)) continue;\n\t\t\tif (entityHasReferences(candidate)) continue;\n\t\t\tentitiesMap.delete(candidate);\n\t\t\tevents.append({\n\t\t\t\taction: \"orphanRemove\" as const,\n\t\t\t\tid: candidate,\n\t\t\t\tt_ns: wallClockNs(),\n\t\t\t\tseq: bumpCursor(seqCursor),\n\t\t\t});\n\t\t}\n\t}\n\n\tconst upsertEntityImpl = (id: string, value: TEntity): void => {\n\t\tentitiesMap.set(id, value);\n\t};\n\tconst removeEntityImpl = (id: string): void => {\n\t\tconst snapshot = edgesMap.entries.cache as\n\t\t\t| ReadonlyMap<string, KnowledgeEdge<TRelation>>\n\t\t\t| undefined;\n\t\t// Collect both the edge-keys to drop AND the entity ids those edges\n\t\t// reference (other than `id` itself) — the latter become orphan-GC\n\t\t// candidates after the cascade. (C1 fix — the previous impl only\n\t\t// applied orphan GC inside `unlink`, so cascading entity removal\n\t\t// could leave dangling orphans.)\n\t\tconst cascadedNeighbors = new Set<string>();\n\t\tif (snapshot) {\n\t\t\tconst toDrop: string[] = [];\n\t\t\tfor (const [key, edge] of snapshot) {\n\t\t\t\tif (edge.from === id || edge.to === id) {\n\t\t\t\t\ttoDrop.push(key);\n\t\t\t\t\tif (edge.from !== id) cascadedNeighbors.add(edge.from);\n\t\t\t\t\tif (edge.to !== id) cascadedNeighbors.add(edge.to);\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (toDrop.length > 0) edgesMap.deleteMany(toDrop);\n\t\t}\n\t\tif (entitiesMap.has(id)) entitiesMap.delete(id);\n\t\tapplyOrphanGC([...cascadedNeighbors]);\n\t};\n\tconst linkImpl = (from: string, to: string, relation: TRelation, weight = 1): void => {\n\t\tedgesMap.set(tripleKey(from, to, relation), { from, to, relation, weight });\n\t};\n\tconst unlinkImpl = (from: string, to: string, relation?: TRelation): void => {\n\t\tif (relation !== undefined) {\n\t\t\tedgesMap.delete(tripleKey(from, to, relation));\n\t\t} else {\n\t\t\tconst snapshot = edgesMap.entries.cache as\n\t\t\t\t| ReadonlyMap<string, KnowledgeEdge<TRelation>>\n\t\t\t\t| undefined;\n\t\t\tif (!snapshot) return;\n\t\t\tconst toDrop: string[] = [];\n\t\t\tfor (const [key, edge] of snapshot) {\n\t\t\t\tif (edge.from === from && edge.to === to) toDrop.push(key);\n\t\t\t}\n\t\t\tif (toDrop.length > 0) edgesMap.deleteMany(toDrop);\n\t\t}\n\t\tapplyOrphanGC([from, to]);\n\t};\n\n\tconst upsertEntity = mutate(upsertEntityImpl, {\n\t\tframe: \"inline\",\n\t\tlog: events,\n\t\tseq: seqCursor,\n\t\tonSuccessRecord: ([id], _r, m) => ({\n\t\t\taction: \"upsertEntity\" as const,\n\t\t\tid,\n\t\t\tt_ns: m.t_ns,\n\t\t\tseq: m.seq,\n\t\t}),\n\t});\n\tconst removeEntity = mutate(removeEntityImpl, {\n\t\tframe: \"inline\",\n\t\tlog: events,\n\t\tseq: seqCursor,\n\t\tonSuccessRecord: ([id], _r, m) => ({\n\t\t\taction: \"removeEntity\" as const,\n\t\t\tid,\n\t\t\tt_ns: m.t_ns,\n\t\t\tseq: m.seq,\n\t\t}),\n\t});\n\tconst link = mutate(linkImpl, {\n\t\tframe: \"inline\",\n\t\tlog: events,\n\t\tseq: seqCursor,\n\t\tonSuccessRecord: ([from, to, relation, weight], _r, m) => ({\n\t\t\taction: \"link\" as const,\n\t\t\tfrom,\n\t\t\tto,\n\t\t\trelation: relation as string,\n\t\t\tweight: weight ?? 1,\n\t\t\tt_ns: m.t_ns,\n\t\t\tseq: m.seq,\n\t\t}),\n\t});\n\tconst unlink = mutate(unlinkImpl, {\n\t\tframe: \"inline\",\n\t\tlog: events,\n\t\tseq: seqCursor,\n\t\tonSuccessRecord: ([from, to, relation], _r, m) => ({\n\t\t\taction: \"unlink\" as const,\n\t\t\tfrom,\n\t\t\tto,\n\t\t\t...(relation !== undefined ? { relation: relation as string } : {}),\n\t\t\tt_ns: m.t_ns,\n\t\t\tseq: m.seq,\n\t\t}),\n\t});\n\n\tfunction relatedNode(\n\t\tid: NodeOrValue<string>,\n\t\trelation?: NodeOrValue<TRelation>,\n\t): Node<readonly KnowledgeEdge<TRelation>[]> {\n\t\tconst idN = toNode(id, \"id\");\n\t\t// `relation` is OPTIONAL. We deliberately do NOT include it as a dep\n\t\t// when omitted — `state(undefined)` would be a SENTINEL and the\n\t\t// derived's first-run gate would never open. Callers pass a Node\n\t\t// when they want reactive filtering; pass a value to lock the\n\t\t// filter; omit to disable filtering.\n\t\tconst relN = relation !== undefined ? toNode(relation, \"relation\") : undefined;\n\t\tconst deps: Node<unknown>[] = relN\n\t\t\t? [adjacencyOut, adjacencyIn, idN, relN]\n\t\t\t: [adjacencyOut, adjacencyIn, idN];\n\t\treturn node(\n\t\t\tdeps,\n\t\t\t(batchData, actions, ctx) => {\n\t\t\t\tconst values = batchData.map((batch, i) =>\n\t\t\t\t\tbatch != null && batch.length > 0 ? batch.at(-1) : ctx.prevData[i],\n\t\t\t\t);\n\t\t\t\tconst out = values[0] as ReadonlyMap<string, readonly KnowledgeEdge<TRelation>[]>;\n\t\t\t\tconst inb = values[1] as ReadonlyMap<string, readonly KnowledgeEdge<TRelation>[]>;\n\t\t\t\tconst key = values[2] as string;\n\t\t\t\tconst rel = relN ? (values[3] as TRelation | undefined) : undefined;\n\t\t\t\tconst outE = out?.get(key) ?? [];\n\t\t\t\tconst inE = inb?.get(key) ?? [];\n\t\t\t\t// Concatenate, then dedupe by triple key (a self-loop would appear in both).\n\t\t\t\tconst seen = new Set<string>();\n\t\t\t\tconst acc: KnowledgeEdge<TRelation>[] = [];\n\t\t\t\tfor (const edge of outE) {\n\t\t\t\t\tconst k = tripleKey(edge.from, edge.to, edge.relation);\n\t\t\t\t\tif (seen.has(k)) continue;\n\t\t\t\t\tif (rel !== undefined && edge.relation !== rel) continue;\n\t\t\t\t\tseen.add(k);\n\t\t\t\t\tacc.push(edge);\n\t\t\t\t}\n\t\t\t\tfor (const edge of inE) {\n\t\t\t\t\tconst k = tripleKey(edge.from, edge.to, edge.relation);\n\t\t\t\t\tif (seen.has(k)) continue;\n\t\t\t\t\tif (rel !== undefined && edge.relation !== rel) continue;\n\t\t\t\t\tseen.add(k);\n\t\t\t\t\tacc.push(edge);\n\t\t\t\t}\n\t\t\t\tactions.emit(acc as readonly KnowledgeEdge<TRelation>[]);\n\t\t\t},\n\t\t\t{\n\t\t\t\tdescribeKind: \"derived\",\n\t\t\t\tequals: (a, b) => {\n\t\t\t\t\tconst av = a as readonly KnowledgeEdge<TRelation>[] | undefined;\n\t\t\t\t\tconst bv = b as readonly KnowledgeEdge<TRelation>[] | undefined;\n\t\t\t\t\tif (av === bv) return true;\n\t\t\t\t\tif (av == null || bv == null) return false;\n\t\t\t\t\tif (av.length !== bv.length) return false;\n\t\t\t\t\tfor (let i = 0; i < av.length; i += 1) {\n\t\t\t\t\t\tconst x = av[i]!;\n\t\t\t\t\t\tconst y = bv[i]!;\n\t\t\t\t\t\tif (\n\t\t\t\t\t\t\tx.from !== y.from ||\n\t\t\t\t\t\t\tx.to !== y.to ||\n\t\t\t\t\t\t\tx.relation !== y.relation ||\n\t\t\t\t\t\t\tx.weight !== y.weight\n\t\t\t\t\t\t)\n\t\t\t\t\t\t\treturn false;\n\t\t\t\t\t}\n\t\t\t\t\treturn true;\n\t\t\t\t},\n\t\t\t\tmeta: memoryMeta(\"related\"),\n\t\t\t},\n\t\t) as Node<readonly KnowledgeEdge<TRelation>[]>;\n\t}\n\n\tconst out = Object.assign(graph, {\n\t\tevents,\n\t\tentities: entitiesMap.entries,\n\t\tedges: edgesMap.entries,\n\t\tadjacencyOut,\n\t\tadjacencyIn,\n\t\tentityCount,\n\t\tedgeCount,\n\t\tupsertEntity,\n\t\tremoveEntity,\n\t\tlink,\n\t\tunlink,\n\t\trelatedNode,\n\t}) as KnowledgeGraph<TEntity, TRelation>;\n\treturn out;\n}\n\n// ── DS-14.7: reactiveFactStore (static-topology MEME L2/L3 substrate) ─────\n// Lives in its own file (`fact-store.ts`) — re-exported here so the\n// `utils/memory` barrel stays the single import surface alongside\n// collection / vectorIndex / knowledgeGraph.\nexport {\n\ttype AdmissionFilter,\n\ttype CascadeEvent,\n\ttype CascadeOverflow,\n\ttype CascadeReason,\n\ttype DecayPolicy,\n\ttype DependentsIndex,\n\ttype FactId,\n\ttype FactStore,\n\ttype FactStoreAuditRecord,\n\ttype MemoryAnswer,\n\ttype MemoryFragment,\n\ttype MemoryQuery,\n\ttype OutcomeSignal,\n\ttype ReactiveFactStoreConfig,\n\ttype ReactiveFactStoreGraph,\n\ttype ReviewRequest,\n\treactiveFactStore,\n\ttype ScoringPolicy,\n\ttype ShardKey,\n\ttype StoreReadHandle,\n} from \"./fact-store.js\";\n// DS-14.7 follow-up #2 (persistence half) — durable, event-sourced fact store\n// with substrate-owned replay/dedup (memo:Re Story 6.4 back-derivation).\nexport {\n\ttype PersistentReactiveFactStoreConfig,\n\ttype PersistentReactiveFactStoreGraph,\n\tpersistentReactiveFactStore,\n} from \"./persistent-fact-store.js\";\n// DS-14.7 follow-up #1: recipe library — 8 shipped compositions over the\n// four extension faces. Re-exported here so `utils/memory` stays the single\n// import surface.\nexport * from \"./recipes/index.js\";\n","/**\n * DS-14.7 — Reactive Fact Store / Live Knowledge Graph (locked 2026-05-13).\n *\n * Static-topology agent-memory substrate that satisfies MEME L2 (cascade\n * invalidation) and L3 (obsolescence reasoning) plus Hassabis's\n * filter/consolidate/continual-learning frame, **without** materializing one\n * reactive node per fact. ~12 fixed operator nodes never grow regardless of how\n * many facts the store holds; facts live as columnar DATA inside an indexed\n * `state<FactStore>` (optionally sharded), and cascade is implemented as\n * bounded recursive message emission. Termination rests on a per-root\n * semantic contract — a fact only drives the cascade when it transitions to\n * obsolete (`validTo` set), and each obsolete root emits its cascade exactly\n * once across all waves — plus an empty-array fixpoint short-circuit. The\n * cascade is modeled as DATA arrays (NOT INVALIDATE messages), so spec §1.4's\n * idempotent-within-a-wave INVALIDATE guarantee does NOT govern this loop;\n * convergence is the per-root contract, not a spec §1.4 diamond-merge.\n *\n * Canonical design: `archive/docs/SESSION-DS-14.7-reactive-fact-store.md`\n * (9Q walk complete; Q9-open items 1–9 all resolved).\n *\n * Locked decisions baked in here:\n * - `cascadeMaxIterations` default **8**; overflow emits a per-batch summary\n * `{ droppedCount, sample, rootFactId }` to `cascadeOverflow` (Q9-open-4).\n * The overflow `sample` is capped at `OVERFLOW_SAMPLE_SIZE` (8),\n * independent of `cascadeMaxIterations`.\n * - `shardBy` default hash-mod **4**; caller override; `dependentsIndex`\n * unsharded for v1 (Q9-open-1).\n * - `MemoryFragment` adds `embedding? / parent_fragment_id? / provenance?`\n * (Q9-open-3).\n * - `dependentsIndex` updates synchronous + atomic with `factStore` commit\n * (Q9-open-2).\n * - Scoring contract `(fragment, storeReadHandle) => number` — read-only\n * handle, no mutation exposure (Q9-open-5).\n * - Consolidator emits to a dedicated `consolidated` topic that the pattern\n * default-wires back to `ingest`; caller can intercept (Q9-open-6).\n * - Query surface = structured `MemoryQuery` via the `query` topic (default);\n * function-shaped is caller-side `derived` over `factStore` (Q9-open-7).\n * - Bi-temporal is pattern-layer only — no DS-14 envelope shape change\n * (Q9-open-9); `simpleFactStore()` deferred to v1.1 (Q9-open-8 — NOT built).\n *\n * **Cascade cycle visibility.** `cascadeProcessor` stays synchronous (preserves\n * spec §1.4 batch-dedupe — LLM-driven dependency extraction lives UPSTREAM of\n * the cascade topic, never inside the recursion). Every cascade message carries\n * a `causalReason` field and the cycle nodes are tagged `meta.cycle:\"cascade\"`\n * so `describe()` / `explain()` surface the otherwise-invisible\n * `dependentsIndex` fn-body lookup.\n *\n * @module\n */\n\nimport { monotonicNs, type Node, node, wallClockNs } from \"@graphrefly/pure-ts/core\";\nimport type { ReactiveLogBundle } from \"@graphrefly/pure-ts/extra\";\nimport { keepalive, reactiveLog } from \"@graphrefly/pure-ts/extra\";\nimport { Graph } from \"@graphrefly/pure-ts/graph\";\nimport { domainMeta } from \"../../base/meta/domain-meta.js\";\nimport {\n\ttype BaseAuditRecord,\n\tbumpCursor,\n\tcreateAuditLog,\n\tregisterCursor,\n} from \"../../base/mutation/index.js\";\n\n// ── Public types ─────────────────────────────────────────────────────────\n\n/** Stable identity for a stored fact. */\nexport type FactId = string;\n\n/** Shard partition key (string | number — any hashable scalar). */\nexport type ShardKey = string | number;\n\n/**\n * A single stored memory fact. Pattern convention only — NOT a spec primitive\n * and NOT a DS-14 envelope field (bi-temporal stays pattern-layer per\n * Q9-open-9). Each field is a reactive lever (see design PART 2.3):\n * `validTo` set → cascade fires; `confidence < θ` → review; `sources` →\n * `dependentsIndex` edges feeding cascade.\n */\nexport interface MemoryFragment<T> {\n\treadonly id: FactId;\n\treadonly payload: T;\n\t/** Transaction time (when learned). `bigint` ns (e.g. `BigInt(monotonicNs())`). */\n\treadonly t_ns: bigint;\n\t/** Valid-time start. `undefined` = unbounded past. */\n\treadonly validFrom?: bigint;\n\t/** Valid-time end. Setting this is the MEME L3 obsolescence lever. */\n\treadonly validTo?: bigint;\n\t/** Confidence 0..1. Dropping below the review threshold emits a review. */\n\treadonly confidence: number;\n\treadonly tags: readonly string[];\n\t/** Dependency edges — fact IDs this fact is derived from / depends on. */\n\treadonly sources: readonly FactId[];\n\t/** Optional dense embedding (recipes use it for retrieval). */\n\treadonly embedding?: readonly number[];\n\t/** Version-chain pointer — consolidator emits successor fragments. */\n\treadonly parent_fragment_id?: FactId;\n\t/** Free-form provenance string for audit. */\n\treadonly provenance?: string;\n}\n\n/**\n * Columnar in-memory store. Held as DATA inside a `state<FactStore<T>>` node\n * (one per shard). `byId` is the authoritative map; the typed companions are\n * kept for the recipe layer (`bitemporal-query`, `influence-analysis`) — v1\n * stores fragments directly and lazily projects columns on demand.\n */\nexport interface FactStore<T> {\n\treadonly byId: ReadonlyMap<FactId, MemoryFragment<T>>;\n}\n\n/** Reverse dependency index: fact → IDs that depend on it. Unsharded (v1). */\nexport type DependentsIndex = ReadonlyMap<FactId, readonly FactId[]>;\n\n/** Read-only projection passed to scoring policies (no mutation surface). */\nexport interface StoreReadHandle<T> {\n\tget(id: FactId): MemoryFragment<T> | undefined;\n\thas(id: FactId): boolean;\n\treadonly size: number;\n\tvalues(): IterableIterator<MemoryFragment<T>>;\n}\n\nexport type ScoringPolicy<T> = (fragment: MemoryFragment<T>, store: StoreReadHandle<T>) => number;\nexport type DecayPolicy = (confidence: number, ageNs: bigint) => number;\nexport type AdmissionFilter<T> = (fragment: MemoryFragment<T>) => boolean;\n\n/** Outcome / RL signal — write-back lever for continual learning. */\nexport interface OutcomeSignal {\n\treadonly factId: FactId;\n\treadonly reward: number;\n}\n\n/** Structured query (Q9-open-7 default surface). Serializable + inspectable. */\nexport interface MemoryQuery {\n\t/** Match any of these tags (OR). Omit for no tag filter. */\n\treadonly tags?: readonly string[];\n\t/** Bi-temporal \"as of\" — only facts valid at this instant. */\n\treadonly asOf?: bigint;\n\t/** Minimum confidence (inclusive). */\n\treadonly minConfidence?: number;\n\t/** Cap results (sorted by confidence desc, then t_ns desc). */\n\treadonly limit?: number;\n}\n\nexport interface MemoryAnswer<T> {\n\treadonly query: MemoryQuery;\n\treadonly results: readonly MemoryFragment<T>[];\n}\n\nexport type CascadeReason = \"cascade\" | \"obsolete\" | \"manual\";\n\n/** A single cascade invalidation message flowing through the cascade cycle. */\nexport interface CascadeEvent {\n\treadonly factId: FactId;\n\treadonly rootFactId: FactId;\n\treadonly reason: CascadeReason;\n\t/**\n\t * The triggering root fact's own `validTo`. Cascade-invalidated dependents\n\t * inherit this (NOT a fresh wall-clock read), so the store is a\n\t * deterministic rebuildable projection: replaying the same ingest stream\n\t * yields byte-identical `validTo` on cascade-invalidated fragments. Each\n\t * dependent inherits the `validTo` of the *nearest obsolete fact that\n\t * drove its invalidation*: in a pure chain A→B→C where only A is\n\t * explicitly obsoleted, B and C both inherit A's `validTo` (B has no own\n\t * `validTo`, so when B becomes a cascade root the detector reads\n\t * `B.validTo` === A's value). If an intermediate B is itself explicitly\n\t * obsoleted with its *own* `validTo`, B's dependents inherit B's value,\n\t * not A's — \"stale from when its driving source was invalidated.\"\n\t * Deterministic either way. Always defined: only obsolete facts\n\t * (`validTo` set) drive the cascade as roots.\n\t */\n\treadonly rootValidTo: bigint;\n\t/** Cascade recursion depth (1 = first wave). Bounded by `cascadeMaxIterations`. */\n\treadonly iteration: number;\n\t/**\n\t * Human-readable causal chain — makes the `dependentsIndex` fn-body lookup\n\t * visible in `explain()` output even though it is not a topology edge\n\t * (design Q3 / COMPOSITION-GUIDE §24 mitigation).\n\t */\n\treadonly causalReason: string;\n}\n\n/** Per-batch overflow summary (Q9-open-4 — never per-message). */\nexport interface CascadeOverflow {\n\treadonly droppedCount: number;\n\treadonly sample: readonly FactId[];\n\treadonly rootFactId: FactId;\n}\n\nexport interface ReviewRequest {\n\treadonly factId: FactId;\n\treadonly confidence: number;\n\treadonly threshold: number;\n}\n\nexport interface FactStoreAuditRecord extends BaseAuditRecord {\n\treadonly action: \"ingest\" | \"invalidate\" | \"outcome\" | \"consolidate\" | \"decay\" | \"overflow\";\n\treadonly id?: FactId;\n\treadonly reason?: CascadeReason;\n}\n\nexport interface ReactiveFactStoreConfig<T> {\n\t// ① Function hooks (no reactive policy needed).\n\treadonly extractDependencies: (f: MemoryFragment<T>) => readonly FactId[];\n\t/** Shard partition fn. Default: FNV-1a hash of `id` mod `shardCount`. */\n\treadonly shardBy?: (f: MemoryFragment<T>) => ShardKey;\n\t/** Shard count for the default hash-mod sharder. Default 4 (§3.2). */\n\treadonly shardCount?: number;\n\n\t// ② Node<Policy> hooks (reactive — policy itself can evolve).\n\treadonly scoring?: Node<ScoringPolicy<T>>;\n\t/**\n\t * Forgetting-curve policy `(confidence, ageNs) => confidence'` (MEME L1\n\t * time-drift / Hassabis forgetting curve, DS-14.7 PART 4.1 face ②). `ageNs`\n\t * is the fact's monotonic age (`monotonicNs() - t_ns`, a `bigint`\n\t * nanosecond duration — `Number()` it for float math; precision degrades\n\t * for ages beyond `Number.MAX_SAFE_INTEGER` ns ≈ 104 days of process\n\t * uptime). Reactive: the policy itself can evolve (e.g.\n\t * `derived([outcome], …)` for continual learning — spec §1.4\n\t * push-on-update).\n\t *\n\t * **Activation contract.** The decay processor exists only when\n\t * {@link decayTrigger} is *configured*. Once it is, **both** a\n\t * `decayTrigger` tick **and** a `decay` policy emit drive a full pass\n\t * (push-on-update — the `outcomeProcessor`+`scoring` precedent). A policy\n\t * Node's *initial* value therefore runs a pass on first resolution: if you\n\t * build `decay` via `derived(...)`, expect a store-wide pass when it first\n\t * settles. With no `decayTrigger` configured the face is fully inert\n\t * (mirrors {@link consolidate}/{@link consolidateTrigger}).\n\t *\n\t * Each pass applies the current policy to every *live* fact (`validTo`\n\t * unset), clamps to `[0, 1]`, and writes back only facts whose confidence\n\t * actually changed (a fact obsoleted earlier in the same tick is never\n\t * resurrected). A drop below {@link reviewThreshold} surfaces via the\n\t * `review` topic; decay never sets `validTo`, so it cannot itself drive the\n\t * cascade.\n\t *\n\t * **Quiescence is the policy's responsibility.** The only no-op guard is\n\t * exact-equality (`next === confidence`). A policy with no reachable\n\t * fixpoint (e.g. plain geometric `c => c * 0.5`) therefore re-decays and\n\t * re-emits **every** live fact on **every** tick forever (silent CPU/GC +\n\t * audit-log churn — never an error). Bound it in the policy: clamp to a\n\t * floor and return `confidence` unchanged once at/below it (so the\n\t * exact-equality guard quiesces the store). The {@link decayExponential}\n\t * recipe is the ergonomic alternative that owns its own `fromTimer` +\n\t * `floor`/`epsilon` and writes through the `ingest` face (no\n\t * `decay`/`decayTrigger` wiring). Use this face when you want the store to\n\t * own decay as a reactive, swappable policy and you are providing the floor.\n\t */\n\treadonly decay?: Node<DecayPolicy>;\n\treadonly admissionFilter?: Node<AdmissionFilter<T>>;\n\n\t// ③ Topic inputs (caller wires upstream sources).\n\treadonly ingest: Node<MemoryFragment<T>>;\n\treadonly outcome?: Node<OutcomeSignal>;\n\treadonly query?: Node<MemoryQuery>;\n\t/**\n\t * Decay trigger — a reactive timer/cron Node (e.g. `fromTimer(...)` /\n\t * `fromCron(...)`). Configuring it creates the decay processor; each tick\n\t * then re-applies the current {@link decay} policy to every live fact (a\n\t * `decay` policy emit also drives a pass — see {@link decay}'s \"Activation\n\t * contract\"). A tick before any `decay` policy has emitted is a no-op.\n\t * Without this field the `decay` face is fully inert (mirrors\n\t * {@link consolidateTrigger}/{@link consolidate}).\n\t */\n\treadonly decayTrigger?: Node<unknown>;\n\t/**\n\t * Consolidator trigger — a reactive timer/cron Node (e.g. `fromCron(...)`).\n\t * When supplied, the `consolidated` node maps each tick to summarized\n\t * fragments emitted on the `consolidated` topic and default-wired back to\n\t * the internal ingest path.\n\t */\n\treadonly consolidateTrigger?: Node<unknown>;\n\t/**\n\t * Consolidation summarizer. Reads a store snapshot, returns successor\n\t * fragments (typically with `parent_fragment_id` set). Default: no-op\n\t * (emits nothing) so the cron tick is observable without forcing a policy.\n\t */\n\treadonly consolidate?: (store: StoreReadHandle<T>) => readonly MemoryFragment<T>[];\n\n\t// Invariants.\n\t/** Cascade recursion cap (§3.1). Default 8. */\n\treadonly cascadeMaxIterations?: number;\n\t/** Confidence below which a {@link ReviewRequest} is emitted. Default 0.3. */\n\treadonly reviewThreshold?: number;\n\n\t// Persistence.\n\t/**\n\t * Record every committed fragment (post-admission-filter, with full\n\t * payload) into a replayable {@link ReactiveFactStoreGraph.ingestLog}.\n\t * Default `false` (the log retains every fragment in memory — opt in only\n\t * when you intend to persist + replay).\n\t *\n\t * Enables the canonical **rebuildable-projection** recipe: the store is a\n\t * deterministic projection of its ingest stream (cascade `validTo` is\n\t * derived from the triggering root, not wall-clock; consolidator successors\n\t * are re-derived from replayed state, not logged — the projection is still\n\t * deterministic), so persisting the ingest log and replaying it on restart\n\t * reconstructs a byte-identical store. Pair with the BigInt-safe codec\n\t * ({@link MemoryFragment} carries `bigint` time fields). The tier MUST be\n\t * `mode:\"append\"` (the default) — `attachStorage` ships per-wave deltas, so\n\t * an `\"overwrite\"` tier would truncate the log (and `attachStorage` now\n\t * throws on one).\n\t *\n\t * **First run — persist:**\n\t * ```ts\n\t * import { reactiveFactStore } from \"@graphrefly/graphrefly\";\n\t * import { appendLogStorage, bigintJsonCodecFor } from \"@graphrefly/pure-ts/extra\";\n\t *\n\t * const tier = appendLogStorage(backend, {\n\t * name: \"facts-ingest\",\n\t * codec: bigintJsonCodecFor<readonly MemoryFragment<Doc>[]>(),\n\t * });\n\t * const mem = reactiveFactStore<Doc>({ ingest, extractDependencies, recordIngest: true });\n\t * mem.ingestLog!.attachStorage([tier]); // forwards every committed fragment\n\t * ```\n\t *\n\t * **Restart — replay (rebuild the projection):** read the persisted entries\n\t * and feed them through `config.ingest`. Replay is just the reactive input —\n\t * there is no imperative restore primitive.\n\t * ```ts\n\t * const { entries } = await tier.loadEntries!();\n\t * const mem = reactiveFactStore<Doc>({ ingest, extractDependencies });\n\t * for (const f of entries) ingest.emit(f); // identical store rebuilt\n\t * ```\n\t *\n\t * **Do NOT do both at once.** On restart, either replay through `ingest`\n\t * (above) **or** rely on `attachStorage`'s auto-restore — never both:\n\t * `attachStorage` restores the persisted entries into a fresh `ingestLog`,\n\t * and a manual replay loop would then re-append + re-persist them (doubling\n\t * the durable log every restart). If you want continued persistence on the\n\t * rebuilt store, set `recordIngest:true` and call `attachStorage([tier])`\n\t * **after** the replay loop completes (the log already holds the replayed\n\t * fragments, so `attachStorage` ships no spurious deltas).\n\t *\n\t * **Memory:** the in-memory log is unbounded (every committed fragment is\n\t * retained for the store's lifetime). For a long-lived high-volume store,\n\t * `ingestLog.trimHead(n)` after a confirmed `tier.flush()` bounds it (the\n\t * durable tier remains the full record).\n\t */\n\treadonly recordIngest?: boolean;\n}\n\nexport interface ReactiveFactStoreGraph<T> extends Graph {\n\t// ④ Topic outputs (caller subscribes for custom processing).\n\t/** Per-shard `state<FactStore<T>>` nodes (length = shard count). */\n\treadonly shards: readonly Node<FactStore<T>>[];\n\t/** Unified read view across all shards (derived). */\n\treadonly factStore: Node<FactStore<T>>;\n\treadonly dependentsIndex: Node<DependentsIndex>;\n\treadonly answer: Node<MemoryAnswer<T> | null>;\n\treadonly cascade: Node<readonly CascadeEvent[]>;\n\treadonly cascadeOverflow: Node<CascadeOverflow | null>;\n\treadonly review: Node<ReviewRequest | null>;\n\treadonly consolidated: Node<readonly MemoryFragment<T>[]>;\n\treadonly events: ReactiveLogBundle<FactStoreAuditRecord>;\n\t/**\n\t * Payload-carrying, replayable log of every committed fragment. Present iff\n\t * {@link ReactiveFactStoreConfig.recordIngest} is `true`. Unlike\n\t * {@link ReactiveFactStoreGraph.events} (action-only audit), each entry is\n\t * the full {@link MemoryFragment} — `attachStorage` it for a durable,\n\t * replayable projection source (see `recordIngest` docs for the recipe).\n\t */\n\treadonly ingestLog?: ReactiveLogBundle<MemoryFragment<T>>;\n\t/** Reactive read: a single fact by id (SENTINEL until the fact exists). */\n\titemNode(id: FactId): Node<MemoryFragment<T> | undefined>;\n}\n\n// ── Constants ────────────────────────────────────────────────────────────\n\n/**\n * Max number of dropped cascade-target ids included in a {@link CascadeOverflow}\n * `sample`. Deliberately decoupled from `cascadeMaxIterations` — it bounds the\n * diagnostic payload size, not the recursion budget.\n */\nconst OVERFLOW_SAMPLE_SIZE = 8;\n\n// ── Helpers ──────────────────────────────────────────────────────────────\n\nfunction factMeta(kind: string, extra?: Record<string, unknown>): Record<string, unknown> {\n\treturn domainMeta(\"memory\", kind, extra);\n}\n\n/** Deterministic, universal-safe FNV-1a 32-bit string hash (no `node:crypto`). */\nfunction fnv1a(s: string): number {\n\tlet h = 0x811c9dc5;\n\tfor (let i = 0; i < s.length; i += 1) {\n\t\th ^= s.charCodeAt(i);\n\t\t// 32-bit FNV prime multiply via shifts (avoids BigInt / float drift).\n\t\th = (h + ((h << 1) + (h << 4) + (h << 7) + (h << 8) + (h << 24))) >>> 0;\n\t}\n\treturn h >>> 0;\n}\n\nfunction makeReadHandle<T>(byId: ReadonlyMap<FactId, MemoryFragment<T>>): StoreReadHandle<T> {\n\treturn {\n\t\tget: (id) => byId.get(id),\n\t\thas: (id) => byId.has(id),\n\t\tget size() {\n\t\t\treturn byId.size;\n\t\t},\n\t\tvalues: () => byId.values(),\n\t};\n}\n\n/** Bi-temporal validity test: is `f` valid at instant `asOf`? */\nfunction currentlyValid<T>(f: MemoryFragment<T>, asOf?: bigint): boolean {\n\tif (asOf === undefined) return f.validTo === undefined;\n\tif (f.validFrom !== undefined && asOf < f.validFrom) return false;\n\tif (f.validTo !== undefined && asOf >= f.validTo) return false;\n\treturn true;\n}\n\nfunction lastOf<X>(batch: readonly unknown[] | undefined, prev: unknown): X | undefined {\n\treturn batch != null && batch.length > 0 ? (batch.at(-1) as X) : (prev as X | undefined);\n}\n\n// ── Factory ──────────────────────────────────────────────────────────────\n\n/**\n * Build a static-topology reactive fact store (DS-14.7 architecture C).\n *\n * Topology (~12 fixed nodes — never grows with fact count):\n * - `shards[0..N]` — `state<FactStore<T>>` columnar stores (default 4 shards).\n * - `factStore` — derived union read view across shards.\n * - `dependentsIndex` — `state<DependentsIndex>` reverse-dep map, unsharded,\n * updated synchronously + atomically with each commit (Q9-open-2).\n * - `extractOp` — derived: ingest → admission-filtered fragment + dep edges.\n * - `invalidationDetector` — derived: scans committed store for `validTo`-set\n * / low-confidence facts, resolves dependents via `dependentsIndex`, emits\n * cascade messages.\n * - `cascade` — topic node carrying `CascadeEvent[]`.\n * - `cascadeProcessor` — derived, **synchronous**, `meta.cycle:\"cascade\"`:\n * dedupes by factId, writes invalidations back to shards, recurses until\n * fixpoint OR `cascadeMaxIterations` → `cascadeOverflow`.\n * - `cascadeOverflow` — per-batch overflow summary node.\n * - `queryOp` / `answer` — structured `MemoryQuery` → results (SENTINEL-safe).\n * - `outcomeProcessor` — outcome signal → confidence write-back.\n * - `consolidated` — cron-tick → summarized fragments on the\n * `consolidated` topic,\n * default-wired back into the ingest path.\n * - `review` — low-confidence proactive-verification requests.\n *\n * The cascade cycle (`invalidationDetector → cascade → cascadeProcessor →\n * shards → invalidationDetector`) is a real, bounded reactive cycle. Both\n * `invalidationDetector` and `cascadeProcessor` are tagged\n * `meta.cycle:\"cascade\"` and every cascade message carries `causalReason`, so\n * `describe()` / `explain()` surface the otherwise-invisible\n * `dependentsIndex` lookup (COMPOSITION-GUIDE §24).\n *\n * @category memory\n */\nexport function reactiveFactStore<T>(\n\tconfig: ReactiveFactStoreConfig<T>,\n): ReactiveFactStoreGraph<T> {\n\tconst shardCount = Math.max(1, config.shardCount ?? 4);\n\tconst maxIterations = Math.max(1, config.cascadeMaxIterations ?? 8);\n\tconst reviewThreshold = config.reviewThreshold ?? 0.3;\n\tconst shardBy = config.shardBy ?? ((f: MemoryFragment<T>) => fnv1a(String(f.id)) % shardCount);\n\n\t// Cascade recursion depth counter. Reset to 0 on every external ingest\n\t// (a fresh root = a fresh cascade budget) and on a true fixpoint (detector\n\t// emits `[]`). Bounded by `maxIterations`; overflow stops the recursion.\n\t// This counter is a *backstop* for pathological cycles only — primary\n\t// termination is the per-root semantic contract below (F1/F5).\n\tlet cascadeIteration = 0;\n\n\t// F1/F5 — per-root cascade dedupe across waves. A fact only enters the\n\t// cascade as a root when it transitions to obsolete (`validTo` set), and\n\t// a given obsolete root emits its cascade exactly ONCE across all waves.\n\t// `processedRoots` tracks root ids that have already emitted; a root\n\t// re-detected in a later wave WITHOUT a new obsolescence transition does\n\t// not re-emit. This bounds the loop by the finite set of newly-obsolete\n\t// roots per external change, independent of `cascadeIteration` resets\n\t// (which an ingest / consolidator wire-back would otherwise defeat).\n\tconst processedRoots = new Set<FactId>();\n\n\tconst graph = new Graph(\"reactive_fact_store\") as ReactiveFactStoreGraph<T>;\n\n\tconst events = createAuditLog<FactStoreAuditRecord>({\n\t\tname: \"events\",\n\t\tretainedLimit: 1024,\n\t\tgraph,\n\t});\n\tconst seqCursor = registerCursor(graph, \"seq\", 0);\n\n\t// Opt-in payload-carrying ingest log (rebuildable-projection source).\n\t// Fed from `ingestAudit` (every committed fragment, post-admission).\n\tconst ingestLog: ReactiveLogBundle<MemoryFragment<T>> | undefined = config.recordIngest\n\t\t? reactiveLog<MemoryFragment<T>>([], { name: \"ingest_log\" })\n\t\t: undefined;\n\tif (ingestLog) graph.addDisposer(() => ingestLog.dispose());\n\n\t// ── shards: state<FactStore<T>> ──────────────────────────────────────\n\tconst emptyStore = (): FactStore<T> => ({ byId: new Map() });\n\tconst shards: Node<FactStore<T>>[] = [];\n\tfor (let s = 0; s < shardCount; s += 1) {\n\t\tconst shard = node<FactStore<T>>([], {\n\t\t\tinitial: emptyStore(),\n\t\t\tname: `shard_${s}`,\n\t\t\tdescribeKind: \"state\",\n\t\t\tmeta: factMeta(\"factstore\", { shard: s }),\n\t\t});\n\t\tgraph.add(shard, { name: `shard_${s}` });\n\t\tgraph.addDisposer(keepalive(shard));\n\t\tshards.push(shard);\n\t}\n\n\tconst shardIndexFor = (f: MemoryFragment<T>): number => {\n\t\tconst key = shardBy(f);\n\t\tconst n = typeof key === \"number\" ? key : fnv1a(String(key));\n\t\tconst idx = ((n % shardCount) + shardCount) % shardCount;\n\t\treturn idx;\n\t};\n\n\t// Resolve which shard a given id lives in by scanning current snapshots\n\t// (cascade write-backs reference ids without re-deriving the fragment).\n\tconst findShardOf = (id: FactId): number => {\n\t\tfor (let s = 0; s < shardCount; s += 1) {\n\t\t\tconst fs = shards[s]!.cache as FactStore<T> | undefined;\n\t\t\tif (fs?.byId.has(id)) return s;\n\t\t}\n\t\treturn -1;\n\t};\n\n\tconst allFacts = (): Map<FactId, MemoryFragment<T>> => {\n\t\tconst out = new Map<FactId, MemoryFragment<T>>();\n\t\tfor (const sh of shards) {\n\t\t\tconst fs = sh.cache as FactStore<T> | undefined;\n\t\t\tif (!fs) continue;\n\t\t\tfor (const [k, v] of fs.byId) out.set(k, v);\n\t\t}\n\t\treturn out;\n\t};\n\n\tconst commitFragment = (f: MemoryFragment<T>): void => {\n\t\tconst idx = shardIndexFor(f);\n\t\tconst cur = (shards[idx]!.cache as FactStore<T> | undefined) ?? emptyStore();\n\t\tconst next = new Map(cur.byId);\n\t\tnext.set(f.id, f);\n\t\tshards[idx]!.emit({ byId: next });\n\t};\n\n\tconst replaceFragment = (\n\t\tid: FactId,\n\t\tmut: (prev: MemoryFragment<T>) => MemoryFragment<T>,\n\t): boolean => {\n\t\tconst idx = findShardOf(id);\n\t\tif (idx < 0) return false;\n\t\tconst cur = shards[idx]!.cache as FactStore<T>;\n\t\tconst prev = cur.byId.get(id);\n\t\tif (!prev) return false;\n\t\tconst next = new Map(cur.byId);\n\t\tnext.set(id, mut(prev));\n\t\tshards[idx]!.emit({ byId: next });\n\t\treturn true;\n\t};\n\n\t// ── dependentsIndex: state<DependentsIndex>, unsharded ───────────────\n\tconst dependentsIndex = node<DependentsIndex>([], {\n\t\tinitial: new Map() as DependentsIndex,\n\t\tname: \"dependents_index\",\n\t\tdescribeKind: \"state\",\n\t\tmeta: factMeta(\"factstore\", { role: \"dependents_index\" }),\n\t});\n\tgraph.add(dependentsIndex, { name: \"dependents_index\" });\n\tgraph.addDisposer(keepalive(dependentsIndex));\n\n\t// Synchronous + atomic with the commit (Q9-open-2): add reverse edges\n\t// `source → [..., fact.id]` for every dependency the fragment declares.\n\tconst indexFragment = (f: MemoryFragment<T>, deps: readonly FactId[]): void => {\n\t\tconst cur = dependentsIndex.cache as DependentsIndex;\n\t\tconst next = new Map<FactId, FactId[]>();\n\t\tfor (const [k, v] of cur) next.set(k, [...v]);\n\t\tfor (const src of deps) {\n\t\t\tconst bucket = next.get(src) ?? [];\n\t\t\tif (!bucket.includes(f.id)) bucket.push(f.id);\n\t\t\tnext.set(src, bucket);\n\t\t}\n\t\tdependentsIndex.emit(next as DependentsIndex);\n\t};\n\n\t// ── factStore: unified read view (derived union over shards) ─────────\n\tconst factStore = node<FactStore<T>>(\n\t\tshards,\n\t\t(batchData, actions, ctx) => {\n\t\t\tvoid batchData;\n\t\t\tvoid ctx;\n\t\t\tactions.emit({ byId: allFacts() });\n\t\t},\n\t\t{\n\t\t\tname: \"fact_store\",\n\t\t\tdescribeKind: \"derived\",\n\t\t\tinitial: emptyStore(),\n\t\t\tmeta: factMeta(\"factstore\", { role: \"read_view\" }),\n\t\t\t// F10a: `allFacts()` builds a fresh Map every detector retrigger.\n\t\t\t// Fragments are immutable (replaced wholesale on mutation), so a\n\t\t\t// same-size + per-key-identity check is a sound structural equality\n\t\t\t// that stops `factStore` (and its `review` dependent) from re-firing\n\t\t\t// every cascade wave when nothing actually changed.\n\t\t\tequals: (a: FactStore<T>, b: FactStore<T>) => {\n\t\t\t\tif (a === b) return true;\n\t\t\t\tif (a.byId.size !== b.byId.size) return false;\n\t\t\t\tfor (const [k, v] of a.byId) {\n\t\t\t\t\tif (b.byId.get(k) !== v) return false;\n\t\t\t\t}\n\t\t\t\treturn true;\n\t\t\t},\n\t\t},\n\t);\n\tgraph.add(factStore, { name: \"fact_store\" });\n\tgraph.addDisposer(keepalive(factStore));\n\n\t// ── extractOp: ingest → admission filter → commit + index ────────────\n\tconst extractOp = node<MemoryFragment<T> | null>(\n\t\tconfig.admissionFilter ? [config.ingest, config.admissionFilter] : [config.ingest],\n\t\t(batchData, actions, ctx) => {\n\t\t\tconst f = lastOf<MemoryFragment<T>>(batchData[0], ctx.prevData[0]);\n\t\t\tif (f == null) {\n\t\t\t\tactions.emit(null);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (config.admissionFilter) {\n\t\t\t\tconst filter = lastOf<AdmissionFilter<T>>(batchData[1], ctx.prevData[1]);\n\t\t\t\tif (filter && !filter(f)) {\n\t\t\t\t\tactions.emit(null);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t}\n\t\t\tconst deps = config.extractDependencies(f);\n\t\t\t// External ingest = a fresh cascade root → reset the depth budget.\n\t\t\tcascadeIteration = 0;\n\t\t\t// F1/F5: a (re-)ingested id is a fresh fact version. Clear it from\n\t\t\t// `processedRoots` so a NEW obsolescence transition on this id\n\t\t\t// (e.g. re-ingest with `validTo` set) re-drives the cascade exactly\n\t\t\t// once, rather than being permanently suppressed.\n\t\t\tprocessedRoots.delete(f.id);\n\t\t\t// Synchronous + atomic: commit fragment, then index its dep edges.\n\t\t\tcommitFragment(f);\n\t\t\tindexFragment(f, deps);\n\t\t\tactions.emit(f);\n\t\t},\n\t\t{\n\t\t\tname: \"extract_op\",\n\t\t\tdescribeKind: \"derived\",\n\t\t\tmeta: factMeta(\"extract\"),\n\t\t},\n\t);\n\tgraph.add(extractOp, { name: \"extract_op\" });\n\tgraph.addDisposer(keepalive(extractOp));\n\n\t// ── invalidationDetector: store → cascade messages ───────────────────\n\t// F1/F5 per-root semantic contract: a fact drives the cascade as a root\n\t// ONLY when it is obsolete (`validTo` set). A low-confidence-but-still-live\n\t// fact (no `validTo`) does NOT by itself emit a cascade — it surfaces via\n\t// the `review` topic instead, so it cannot perpetually re-emit a cascade\n\t// every detector pass. Each obsolete root emits its cascade exactly once\n\t// across all waves (`processedRoots` dedupe): a root re-detected in a later\n\t// wave without a fresh obsolescence transition is skipped. Termination is\n\t// therefore bounded by the finite set of newly-obsolete roots per external\n\t// change — robust against `cascadeIteration` resets from ingest /\n\t// consolidator wire-back. The empty-array emit is the fixpoint\n\t// short-circuit; `cascadeMaxIterations` remains a backstop for pathological\n\t// LLM-extracted cycles (A→B→A) only.\n\tconst invalidationDetector = node<readonly CascadeEvent[]>(\n\t\t[...shards],\n\t\t(batchData, actions, ctx) => {\n\t\t\tvoid batchData;\n\t\t\tvoid ctx;\n\t\t\tconst facts = allFacts();\n\t\t\tconst index = dependentsIndex.cache as DependentsIndex;\n\t\t\tconst out: CascadeEvent[] = [];\n\t\t\tconst seen = new Set<FactId>();\n\t\t\tfor (const f of facts.values()) {\n\t\t\t\t// F1/F5(a): ONLY obsolete facts drive the cascade as roots. A\n\t\t\t\t// low-confidence-but-still-live fact is handled by `review`,\n\t\t\t\t// never by the cascade loop.\n\t\t\t\tconst obsolete = f.validTo !== undefined;\n\t\t\t\tif (!obsolete) continue;\n\t\t\t\t// F1/F5(b): each obsolete root emits its cascade exactly once\n\t\t\t\t// across all waves. A root re-detected later (still obsolete,\n\t\t\t\t// no fresh transition) is skipped — convergence is independent\n\t\t\t\t// of `cascadeIteration` resets.\n\t\t\t\tif (processedRoots.has(f.id)) continue;\n\t\t\t\tconst dependents = index.get(f.id) ?? [];\n\t\t\t\tfor (const dep of dependents) {\n\t\t\t\t\t// F3: only cascade onto dependents that are still live. A\n\t\t\t\t\t// non-existent fact (phantom edge — `extractDependencies`\n\t\t\t\t\t// named an un-ingested FactId) is NOT a live dependent;\n\t\t\t\t\t// neither is a dependent already carrying `validTo`.\n\t\t\t\t\tconst depFact = facts.get(dep);\n\t\t\t\t\tif (!depFact || depFact.validTo !== undefined) continue;\n\t\t\t\t\tconst k = `${f.id}->${dep}`;\n\t\t\t\t\tif (seen.has(k)) continue;\n\t\t\t\t\tseen.add(k);\n\t\t\t\t\tout.push({\n\t\t\t\t\t\tfactId: dep,\n\t\t\t\t\t\trootFactId: f.id,\n\t\t\t\t\t\treason: \"obsolete\",\n\t\t\t\t\t\t// `obsolete` guard above guarantees `f.validTo` is set.\n\t\t\t\t\t\trootValidTo: f.validTo as bigint,\n\t\t\t\t\t\titeration: cascadeIteration + 1,\n\t\t\t\t\t\tcausalReason: `dependentsIndex[${f.id}] → ${dep} (obsolete: validTo set)`,\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t\t// Mark the root processed once considered (whether or not it\n\t\t\t\t// had a still-live dependent) — it must not re-drive a later\n\t\t\t\t// wave without a fresh obsolescence transition.\n\t\t\t\tprocessedRoots.add(f.id);\n\t\t\t}\n\t\t\tif (out.length === 0) {\n\t\t\t\t// True fixpoint — reset the depth counter so the next external\n\t\t\t\t// root starts a fresh cascade budget.\n\t\t\t\tcascadeIteration = 0;\n\t\t\t}\n\t\t\tactions.emit(out);\n\t\t},\n\t\t{\n\t\t\tname: \"invalidation_detector\",\n\t\t\tdescribeKind: \"derived\",\n\t\t\tinitial: [] as readonly CascadeEvent[],\n\t\t\tmeta: factMeta(\"invalidation\", { cycle: \"cascade\" }),\n\t\t},\n\t);\n\tgraph.add(invalidationDetector, { name: \"invalidation_detector\" });\n\tgraph.addDisposer(keepalive(invalidationDetector));\n\n\t// ── cascade topic node ───────────────────────────────────────────────\n\tconst cascade = node<readonly CascadeEvent[]>(\n\t\t[invalidationDetector],\n\t\t(batchData, actions, ctx) => {\n\t\t\tconst evts = lastOf<readonly CascadeEvent[]>(batchData[0], ctx.prevData[0]) ?? [];\n\t\t\tactions.emit(evts);\n\t\t},\n\t\t{\n\t\t\tname: \"cascade\",\n\t\t\tdescribeKind: \"derived\",\n\t\t\tinitial: [] as readonly CascadeEvent[],\n\t\t\tmeta: factMeta(\"cascade_topic\", { cycle: \"cascade\" }),\n\t\t},\n\t);\n\tgraph.add(cascade, { name: \"cascade\" });\n\tgraph.addDisposer(keepalive(cascade));\n\n\t// ── cascadeOverflow (per-batch summary, Q9-open-4) ───────────────────\n\tconst cascadeOverflow = node<CascadeOverflow | null>([], {\n\t\tinitial: null,\n\t\tname: \"cascade_overflow\",\n\t\tdescribeKind: \"state\",\n\t\tmeta: factMeta(\"cascade_overflow\"),\n\t});\n\tgraph.add(cascadeOverflow, { name: \"cascade_overflow\" });\n\tgraph.addDisposer(keepalive(cascadeOverflow));\n\n\t// ── cascadeProcessor (SYNCHRONOUS, meta.cycle:\"cascade\") ─────────────\n\t// Per-wave dedupe by target factId, mark each dependent obsolete\n\t// (write-back → re-triggers invalidationDetector), bounded by\n\t// `cascadeMaxIterations`. NOTE: this is DATA-array dedupe, NOT spec §1.4\n\t// INVALIDATE idempotency — termination comes from the detector's\n\t// obsolete-only + per-root-once contract plus the empty-array fixpoint\n\t// short-circuit, not a spec §1.4 guarantee.\n\tconst cascadeProcessor = node<readonly CascadeEvent[]>(\n\t\t[cascade],\n\t\t(batchData, actions, ctx) => {\n\t\t\tconst evts = lastOf<readonly CascadeEvent[]>(batchData[0], ctx.prevData[0]) ?? [];\n\t\t\tif (evts.length === 0) {\n\t\t\t\tactions.emit([]);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\t// Dedupe by target factId (diamond-merge at message granularity).\n\t\t\tconst byId = new Map<FactId, CascadeEvent>();\n\t\t\tfor (const e of evts) if (!byId.has(e.factId)) byId.set(e.factId, e);\n\n\t\t\tcascadeIteration += 1;\n\t\t\tif (cascadeIteration > maxIterations) {\n\t\t\t\t// Cap hit (pathological dependency web / cycle). Emit a\n\t\t\t\t// per-batch overflow summary (Q9-open-4) and STOP the recursion\n\t\t\t\t// definitively: do NOT write back (no shard mutation → detector\n\t\t\t\t// does not re-fire) and settle the cascade topic with `[]` so\n\t\t\t\t// the cycle breaks. `cascadeIteration` stays above the cap until\n\t\t\t\t// the next external ingest resets it (via extractOp), so a\n\t\t\t\t// degenerate cycle cannot immediately re-enter.\n\t\t\t\tconst sample = [...byId.keys()].slice(0, OVERFLOW_SAMPLE_SIZE);\n\t\t\t\tconst rootFactId = evts[0]?.rootFactId ?? \"\";\n\t\t\t\tcascadeOverflow.emit({\n\t\t\t\t\tdroppedCount: byId.size,\n\t\t\t\t\tsample,\n\t\t\t\t\trootFactId,\n\t\t\t\t});\n\t\t\t\tevents.append({\n\t\t\t\t\taction: \"overflow\",\n\t\t\t\t\treason: \"cascade\",\n\t\t\t\t\tid: rootFactId,\n\t\t\t\t\tt_ns: wallClockNs(),\n\t\t\t\t\tseq: bumpCursor(seqCursor),\n\t\t\t\t});\n\t\t\t\tactions.emit([]);\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// Write-back: mark each dependent obsolete iff not already. Each\n\t\t\t// shard `emit` re-triggers `invalidationDetector` (it deps on\n\t\t\t// `[...shards]`) — that IS the recursion edge. No separate trigger\n\t\t\t// node is needed; the detector's \"still live\" predicate plus the\n\t\t\t// empty-emit fixpoint reset terminate the cycle.\n\t\t\t// Deterministic: the dependent inherits the triggering root's own\n\t\t\t// `validTo` (NOT a fresh `monotonicNs()` read), so replaying the\n\t\t\t// same ingest stream yields byte-identical `validTo`. Transitive\n\t\t\t// chains inherit the original root's time.\n\t\t\tfor (const [id, e] of byId) {\n\t\t\t\treplaceFragment(id, (prev) =>\n\t\t\t\t\tprev.validTo !== undefined ? prev : { ...prev, validTo: e.rootValidTo },\n\t\t\t\t);\n\t\t\t}\n\t\t\tactions.emit([...byId.values()]);\n\t\t},\n\t\t{\n\t\t\tname: \"cascade_processor\",\n\t\t\tdescribeKind: \"derived\",\n\t\t\tinitial: [] as readonly CascadeEvent[],\n\t\t\tmeta: factMeta(\"cascade_processor\", { cycle: \"cascade\" }),\n\t\t},\n\t);\n\tgraph.add(cascadeProcessor, { name: \"cascade_processor\" });\n\tgraph.addDisposer(keepalive(cascadeProcessor));\n\n\t// ── review: low-confidence proactive verification ────────────────────\n\tconst review = node<ReviewRequest | null>(\n\t\t[factStore],\n\t\t(batchData, actions, ctx) => {\n\t\t\tconst fs = lastOf<FactStore<T>>(batchData[0], ctx.prevData[0]);\n\t\t\tif (fs == null) {\n\t\t\t\tactions.emit(null);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tfor (const f of fs.byId.values()) {\n\t\t\t\tif (f.confidence < reviewThreshold && f.validTo === undefined) {\n\t\t\t\t\tactions.emit({\n\t\t\t\t\t\tfactId: f.id,\n\t\t\t\t\t\tconfidence: f.confidence,\n\t\t\t\t\t\tthreshold: reviewThreshold,\n\t\t\t\t\t});\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t}\n\t\t\tactions.emit(null);\n\t\t},\n\t\t{\n\t\t\tname: \"review\",\n\t\t\tdescribeKind: \"derived\",\n\t\t\tinitial: null,\n\t\t\tmeta: factMeta(\"review\"),\n\t\t\t// F10a: dedupe on the requested factId (null === no request) so a\n\t\t\t// stable low-confidence fact does not re-emit a review every wave.\n\t\t\tequals: (a: ReviewRequest | null, b: ReviewRequest | null) =>\n\t\t\t\t(a?.factId ?? null) === (b?.factId ?? null),\n\t\t},\n\t);\n\tgraph.add(review, { name: \"review\" });\n\tgraph.addDisposer(keepalive(review));\n\n\t// ── outcomeProcessor: RL signal → confidence write-back ──────────────\n\tif (config.outcome) {\n\t\tconst outcomeProcessor = node<OutcomeSignal | null>(\n\t\t\tconfig.scoring ? [config.outcome, config.scoring] : [config.outcome],\n\t\t\t(batchData, actions, ctx) => {\n\t\t\t\tconst sig = lastOf<OutcomeSignal>(batchData[0], ctx.prevData[0]);\n\t\t\t\tif (sig == null) {\n\t\t\t\t\tactions.emit(null);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\treplaceFragment(sig.factId, (prev) => {\n\t\t\t\t\tlet nextConf = prev.confidence;\n\t\t\t\t\tif (config.scoring) {\n\t\t\t\t\t\tconst policy = lastOf<ScoringPolicy<T>>(batchData[1], ctx.prevData[1]);\n\t\t\t\t\t\tif (policy) {\n\t\t\t\t\t\t\tnextConf = policy(prev, makeReadHandle(allFacts()));\n\t\t\t\t\t\t}\n\t\t\t\t\t} else {\n\t\t\t\t\t\tnextConf = Math.max(0, Math.min(1, prev.confidence + sig.reward));\n\t\t\t\t\t}\n\t\t\t\t\treturn { ...prev, confidence: nextConf };\n\t\t\t\t});\n\t\t\t\tactions.emit(sig);\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"outcome_processor\",\n\t\t\t\tdescribeKind: \"derived\",\n\t\t\t\tinitial: null,\n\t\t\t\tmeta: factMeta(\"outcome\"),\n\t\t\t},\n\t\t);\n\t\tgraph.add(outcomeProcessor, { name: \"outcome_processor\" });\n\t\tgraph.addDisposer(keepalive(outcomeProcessor));\n\t}\n\n\t// ── queryOp / answer (structured MemoryQuery, SENTINEL-safe) ─────────\n\t// Per COMPOSITION-GUIDE §3/§10: `answer` emits `null` while there has been\n\t// no query yet (SENTINEL on the query dep). Downstream consumers use the\n\t// `=== null` guard.\n\tconst answer = node<MemoryAnswer<T> | null>(\n\t\tconfig.query ? [config.query, factStore] : [factStore],\n\t\t(batchData, actions, ctx) => {\n\t\t\tif (!config.query) {\n\t\t\t\tactions.emit(null);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tconst q = lastOf<MemoryQuery>(batchData[0], ctx.prevData[0]);\n\t\t\tconst fs = lastOf<FactStore<T>>(batchData[1], ctx.prevData[1]);\n\t\t\tif (q == null) {\n\t\t\t\t// No query has been issued yet — null per the SENTINEL guard.\n\t\t\t\tactions.emit(null);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tconst store = fs ?? emptyStore();\n\t\t\tlet results = [...store.byId.values()].filter((f) => {\n\t\t\t\tif (q.tags && q.tags.length > 0 && !q.tags.some((t) => f.tags.includes(t))) {\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t\tif (q.minConfidence !== undefined && f.confidence < q.minConfidence) return false;\n\t\t\t\tif (!currentlyValid(f, q.asOf)) return false;\n\t\t\t\treturn true;\n\t\t\t});\n\t\t\tresults.sort((a, b) => b.confidence - a.confidence || Number(b.t_ns - a.t_ns));\n\t\t\tif (q.limit !== undefined) results = results.slice(0, Math.max(0, q.limit));\n\t\t\tactions.emit({ query: q, results });\n\t\t},\n\t\t{\n\t\t\tname: \"answer\",\n\t\t\tdescribeKind: \"derived\",\n\t\t\tinitial: null,\n\t\t\tmeta: factMeta(\"query\", { role: \"output\" }),\n\t\t},\n\t);\n\tgraph.add(answer, { name: \"answer\" });\n\tgraph.addDisposer(keepalive(answer));\n\n\t// ── consolidator (cron-fed) → consolidated topic → wired back ────────\n\tconst consolidated = node<readonly MemoryFragment<T>[]>(\n\t\tconfig.consolidateTrigger ? [config.consolidateTrigger] : [],\n\t\t(batchData, actions, ctx) => {\n\t\t\tvoid batchData;\n\t\t\tvoid ctx;\n\t\t\tif (!config.consolidateTrigger || !config.consolidate) {\n\t\t\t\tactions.emit([]);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tconst fragments = config.consolidate(makeReadHandle(allFacts()));\n\t\t\t// Default wire-back into the ingest path (Q9-open-6): the pattern\n\t\t\t// commits + indexes successor fragments; callers that need to gate\n\t\t\t// can subscribe to `consolidated` and intercept.\n\t\t\tfor (const f of fragments) {\n\t\t\t\tconst deps = config.extractDependencies(f);\n\t\t\t\t// F1/F5: wired-back successor is a fresh fact version — same\n\t\t\t\t// processedRoots reset as the ingest path so a later obsolescence\n\t\t\t\t// of this id can re-cascade exactly once.\n\t\t\t\tprocessedRoots.delete(f.id);\n\t\t\t\tcommitFragment(f);\n\t\t\t\tindexFragment(f, deps);\n\t\t\t\tevents.append({\n\t\t\t\t\taction: \"consolidate\",\n\t\t\t\t\tid: f.id,\n\t\t\t\t\tt_ns: wallClockNs(),\n\t\t\t\t\tseq: bumpCursor(seqCursor),\n\t\t\t\t});\n\t\t\t}\n\t\t\tactions.emit(fragments);\n\t\t},\n\t\t{\n\t\t\tname: \"consolidated\",\n\t\t\tdescribeKind: \"derived\",\n\t\t\tinitial: [] as readonly MemoryFragment<T>[],\n\t\t\tmeta: factMeta(\"consolidator\"),\n\t\t},\n\t);\n\tgraph.add(consolidated, { name: \"consolidated\" });\n\tgraph.addDisposer(keepalive(consolidated));\n\n\t// ── decayProcessor: trigger tick → apply DecayPolicy → confidence drift ─\n\t// Face ② `decay` (DS-14.7 PART 4.1 / §5.8). Deps on the caller's reactive\n\t// trigger (+ the policy Node so a policy swap is push-on-update, the\n\t// `outcomeProcessor`/`extractOp` ②-face precedent). Reads the store\n\t// advisory off `allFacts()` (COMPOSITION-GUIDE-GRAPH §7 — NOT a shards dep,\n\t// no within-wave cycle); write-back via `replaceFragment` re-triggers\n\t// `invalidationDetector` exactly like `consolidated`/`cascadeProcessor`\n\t// (already-bounded recursion edge). Decay only mutates `confidence`, never\n\t// `validTo`, so it cannot itself become a cascade root — a confidence drop\n\t// below `reviewThreshold` surfaces through `review` instead.\n\tif (config.decayTrigger) {\n\t\tconst decayProcessor = node<readonly MemoryFragment<T>[]>(\n\t\t\tconfig.decay ? [config.decayTrigger, config.decay] : [config.decayTrigger],\n\t\t\t(batchData, actions, ctx) => {\n\t\t\t\tconst policy = config.decay\n\t\t\t\t\t? lastOf<DecayPolicy>(batchData[1], ctx.prevData[1])\n\t\t\t\t\t: undefined;\n\t\t\t\t// Inert until a policy exists (SENTINEL guard) or if `decay` was\n\t\t\t\t// never supplied — `decayTrigger` alone is documented no-op.\n\t\t\t\tif (!policy) {\n\t\t\t\t\tactions.emit([]);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\t// `ageNs` is a DURATION → monotonic clock (CLAUDE.md \"Time utility\n\t\t\t\t// rule\"); `MemoryFragment.t_ns` is monotonic-stamped (field JSDoc\n\t\t\t\t// + fixture). Wall-clock here would mix domains and hand the\n\t\t\t\t// policy a ~epoch-sized garbage age. The `decay` audit record\n\t\t\t\t// below keeps `wallClockNs()` (wall-clock attribution, like every\n\t\t\t\t// other audit append).\n\t\t\t\tconst now = BigInt(monotonicNs());\n\t\t\t\tconst changes: { id: FactId; next: number }[] = [];\n\t\t\t\tfor (const f of allFacts().values()) {\n\t\t\t\t\tif (f.validTo !== undefined) continue; // obsolete — don't drift\n\t\t\t\t\tconst ageNs = now - f.t_ns;\n\t\t\t\t\tconst raw = policy(f.confidence, ageNs);\n\t\t\t\t\tif (!Number.isFinite(raw)) continue; // policy overflow guard\n\t\t\t\t\tconst next = raw < 0 ? 0 : raw > 1 ? 1 : raw;\n\t\t\t\t\tif (next === f.confidence) continue; // no-op → quiescent store\n\t\t\t\t\tchanges.push({ id: f.id, next });\n\t\t\t\t}\n\t\t\t\tconst decayed: MemoryFragment<T>[] = [];\n\t\t\t\tfor (const { id, next } of changes) {\n\t\t\t\t\t// Resurrection guard: re-read the LIVE shard — a fact obsoleted\n\t\t\t\t\t// by an in-flight cascade earlier this same tick must not be\n\t\t\t\t\t// re-confidence'd from the pre-cascade snapshot.\n\t\t\t\t\tconst idx = findShardOf(id);\n\t\t\t\t\tconst fs = idx < 0 ? undefined : (shards[idx]!.cache as FactStore<T> | undefined);\n\t\t\t\t\tconst live = fs?.byId.get(id);\n\t\t\t\t\tif (!live || live.validTo !== undefined) continue;\n\t\t\t\t\treplaceFragment(id, (prev) =>\n\t\t\t\t\t\tprev.validTo !== undefined ? prev : { ...prev, confidence: next },\n\t\t\t\t\t);\n\t\t\t\t\tdecayed.push({ ...live, confidence: next });\n\t\t\t\t}\n\t\t\t\tif (decayed.length > 0) {\n\t\t\t\t\tevents.append({\n\t\t\t\t\t\taction: \"decay\",\n\t\t\t\t\t\tt_ns: wallClockNs(),\n\t\t\t\t\t\tseq: bumpCursor(seqCursor),\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t\tactions.emit(decayed);\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"decay_processor\",\n\t\t\t\tdescribeKind: \"derived\",\n\t\t\t\tinitial: [] as readonly MemoryFragment<T>[],\n\t\t\t\tmeta: factMeta(\"decay\"),\n\t\t\t},\n\t\t);\n\t\tgraph.add(decayProcessor, { name: \"decay_processor\" });\n\t\tgraph.addDisposer(keepalive(decayProcessor));\n\t}\n\n\t// ── ingest audit (records every committed fragment) ──────────────────\n\tconst ingestAudit = node<MemoryFragment<T> | null>(\n\t\t[extractOp],\n\t\t(batchData, actions, ctx) => {\n\t\t\tconst f = lastOf<MemoryFragment<T> | null>(batchData[0], ctx.prevData[0]);\n\t\t\tif (f != null) {\n\t\t\t\tevents.append({\n\t\t\t\t\taction: \"ingest\",\n\t\t\t\t\tid: f.id,\n\t\t\t\t\tt_ns: wallClockNs(),\n\t\t\t\t\tseq: bumpCursor(seqCursor),\n\t\t\t\t});\n\t\t\t\t// Payload-carrying replay log (opt-in). Append the full\n\t\t\t\t// committed fragment so the store is a rebuildable projection.\n\t\t\t\tingestLog?.append(f);\n\t\t\t}\n\t\t\tactions.emit(f ?? null);\n\t\t},\n\t\t{\n\t\t\tname: \"_ingest_audit\",\n\t\t\tdescribeKind: \"derived\",\n\t\t\tinitial: null,\n\t\t\tmeta: factMeta(\"audit\"),\n\t\t},\n\t);\n\tgraph.add(ingestAudit, { name: \"_ingest_audit\" });\n\tgraph.addDisposer(keepalive(ingestAudit));\n\n\t// ── itemNode reactive read ───────────────────────────────────────────\n\tfunction itemNode(id: FactId): Node<MemoryFragment<T> | undefined> {\n\t\treturn node<MemoryFragment<T> | undefined>(\n\t\t\t[factStore],\n\t\t\t(batchData, actions, ctx) => {\n\t\t\t\tconst fs = lastOf<FactStore<T>>(batchData[0], ctx.prevData[0]);\n\t\t\t\tactions.emit(fs?.byId.get(id));\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: `item_${id}`,\n\t\t\t\tdescribeKind: \"derived\",\n\t\t\t\tmeta: factMeta(\"item\"),\n\t\t\t},\n\t\t);\n\t}\n\n\tconst out = Object.assign(graph, {\n\t\tshards: shards as readonly Node<FactStore<T>>[],\n\t\tfactStore,\n\t\tdependentsIndex,\n\t\tanswer,\n\t\tcascade,\n\t\tcascadeOverflow,\n\t\treview,\n\t\tconsolidated,\n\t\tevents,\n\t\t...(ingestLog ? { ingestLog } : {}),\n\t\titemNode,\n\t}) as ReactiveFactStoreGraph<T>;\n\treturn out;\n}\n","/**\n * Promotion 1 (memo:Re Story 6.4 back-derivation; DS-14.7 follow-up #2 —\n * the persistence half of `simpleFactStore()`). Design-review-locked\n * 2026-05-16.\n *\n * memo:Re hand-rolled `createPersistentMemoryStore` (217 LOC): a\n * `reactiveLog.attach(ingest)` side-log + an `appendLogStorage` tier +\n * paginated replay + a `persistedCount` suffix-cursor for replay-dedup +\n * flush/dispose lifecycle. Its code-review's *only High* finding (flush\n * partial-failure double-append) + several Meds (concurrent-flush race,\n * `persistedCount` baseline fragility, `loadAllHistory` silent truncation)\n * were all artifacts of the first consumer hand-rolling substrate-general\n * orchestration. This factory owns log↔store↔replay↔dedup correctly so the\n * whole silent-corruption bug class disappears for every future consumer.\n *\n * **Key design properties (locked):**\n * - **Synchronous factory.** Returns the `Graph` immediately like every other\n * `utils/memory` factory — `utils/` is composed by presets/solutions and\n * must stay reactive (no `await` in a construction path). The inherently-IO\n * durable load + replay is the ONE async boundary and it lives in a single\n * isolated reactive source node (`fromAny` over a paginated `loadEntries`\n * async-iterator), per spec §5.10 — not in the factory signature.\n * - **Substrate-owned durable cursor.** Persistence is wired AFTER the replay\n * source drains; `ReactiveLogBundle.attachStorage` initialises its\n * delivered-cursor to `ingestLog.size` at attach time, so the replayed\n * history is NOT re-persisted and only post-replay live fragments ship.\n * The cursor is entirely internal — the consumer tracks nothing (no\n * `persistedCount`). Observability is the read-only `position` Node.\n * - **Flush delegated** to the already-QA-hardened `appendLogStorage.flush`\n * (reject-on-prior-failure + rollback-epoch + chained-drain) — the High\n * double-append dissolves because nothing here hand-rolls `appendEntries`\n * + a cursor.\n * - **No silent partial-history loss.** Replay reads the durable history in a\n * single `tier.loadEntries()` call — the substrate append-log tier returns\n * the COMPLETE log (it does not paginate / windowed-truncate), so memo:Re's\n * `loadAllHistory` partial-page-truncation bug class is absent by\n * construction (not \"detected\"). Any `loadEntries` rejection propagates as\n * `ERROR` on the replay source (observable), not a swallowed partial load.\n * (Real cursor pagination for very large logs is a deferred substrate\n * enhancement — see `docs/optimizations.md`.)\n *\n * Determinism: `reactiveFactStore`'s cascade `validTo` is derived from the\n * triggering root (not wall-clock), so replaying the persisted ingest stream\n * rebuilds a byte-identical store — the rebuildable-projection contract\n * documented on {@link ReactiveFactStoreConfig.recordIngest}.\n *\n * Only the bytes-`StorageBackend` adapter stays userland; the BigInt-safe\n * codec is upstream (`bigintJsonCodecFor`, the default here).\n *\n * @module\n */\n\nimport { COMPLETE, type Node, node } from \"@graphrefly/pure-ts/core\";\nimport {\n\ttype AppendLogStorageTier,\n\tappendLogStorage,\n\tbigintJsonCodecFor,\n\ttype Codec,\n\tfromAny,\n\tkeepalive,\n\ttype StorageBackend,\n} from \"@graphrefly/pure-ts/extra\";\nimport { domainMeta } from \"../../base/meta/domain-meta.js\";\nimport {\n\ttype MemoryFragment,\n\ttype ReactiveFactStoreConfig,\n\ttype ReactiveFactStoreGraph,\n\treactiveFactStore,\n} from \"./fact-store.js\";\n\nfunction persistMeta(kind: string): Record<string, unknown> {\n\treturn domainMeta(\"memory\", kind);\n}\n\nexport interface PersistentReactiveFactStoreConfig<T>\n\t// `admissionFilter` is intentionally omitted (QA-A): `ingestLog` records the\n\t// POST-admission stream, so replaying it through a `config.ingest` that\n\t// re-applies admission would double-filter — and any stateful/non-\n\t// deterministic filter desyncs the durable log from the rebuilt store\n\t// (silent corruption). Apply admission ONCE, upstream of `config.ingest`\n\t// (e.g. a `.filter` before emitting); the durable log is the post-admission\n\t// truth. Use the non-persistent `reactiveFactStore` if you need the\n\t// reactive `admissionFilter` face.\n\textends Omit<ReactiveFactStoreConfig<T>, \"recordIngest\" | \"admissionFilter\"> {\n\t/**\n\t * Bytes backend the durable ingest log is persisted through. The ONLY\n\t * userland piece — e.g. memo:Re's Drizzle/Expo-SQLite `StorageBackend`,\n\t * or `memoryBackend()` for tests.\n\t */\n\treadonly storage: StorageBackend;\n\t/** Backend key / tier name. Default `\"fact_store_ingest\"`. */\n\treadonly persistName?: string;\n\t/** Codec for the durable bucket. Default `bigintJsonCodecFor` (BigInt-safe). */\n\treadonly codec?: Codec<readonly MemoryFragment<T>[]>;\n}\n\nexport interface PersistentReactiveFactStoreGraph<T> extends ReactiveFactStoreGraph<T> {\n\t/**\n\t * Reactive count of durably-persisted fragments. `0` until startup replay\n\t * completes; thereafter the committed-fragment count (replayed history —\n\t * loaded FROM the durable tier — plus live fragments shipped by the\n\t * substrate-owned `attachStorage` cursor; call {@link flush} to force them\n\t * physically durable). Observability only — the cursor is internal.\n\t */\n\treadonly position: Node<number>;\n\t/**\n\t * Reactive count of fragments rebuilt from durable history at startup.\n\t * `0` until the first replayed fragment; final value once replay\n\t * `COMPLETE`s.\n\t */\n\treadonly replayedCount: Node<number>;\n\t/** The durable append-log tier (shared backend; e.g. for projector cursors). */\n\treadonly tier: AppendLogStorageTier<MemoryFragment<T>>;\n\t/**\n\t * Force-drain the durable tier. Delegates to the QA-hardened\n\t * `appendLogStorage.flush` (rejects if a prior in-flight write failed;\n\t * honours the rollback epoch). Resolves once all shipped fragments are\n\t * physically durable.\n\t *\n\t * **Pre-attach-live durability (QA-E):** a fragment emitted into\n\t * `config.ingest` in the same synchronous tick as construction (before the\n\t * async replay drains) is shipped by a one-shot, fire-and-forget\n\t * reconciliation write whose rejection is NOT surfaced inline. Call\n\t * `flush()` after construction settles to (a) confirm that slice is\n\t * physically durable and (b) surface any write failure as a rejection. The\n\t * normal pattern — observe `replayedCount`/`position` before feeding live\n\t * ingest — avoids the window entirely.\n\t */\n\tflush(): Promise<void>;\n}\n\n/**\n * Build a durable, event-sourced {@link reactiveFactStore} that owns\n * log↔store↔replay↔dedup correctly. Synchronous factory; the only async is\n * an isolated internal replay source. See module docstring for the locked\n * design rationale.\n *\n * @example\n * ```ts\n * import { persistentReactiveFactStore } from \"@graphrefly/graphrefly\";\n * import { memoryBackend } from \"@graphrefly/pure-ts/extra\";\n *\n * const ingest = node<MemoryFragment<Doc>>([], { initial: undefined });\n * const mem = persistentReactiveFactStore<Doc>({\n * ingest,\n * extractDependencies: (f) => f.sources,\n * storage: memoryBackend(),\n * });\n * // Restart is automatic: the durable history is replayed through `ingest`\n * // on construction; observe `mem.replayedCount` / `mem.position`.\n * ingest.emit(myFragment); // live — persisted (not re-persisted)\n * await mem.flush(); // force physically durable\n * ```\n *\n * @category memory\n */\nexport function persistentReactiveFactStore<T>(\n\tconfig: PersistentReactiveFactStoreConfig<T>,\n): PersistentReactiveFactStoreGraph<T> {\n\tconst persistName = config.persistName ?? \"fact_store_ingest\";\n\tconst codec = config.codec ?? bigintJsonCodecFor<readonly MemoryFragment<T>[]>();\n\tconst tier = appendLogStorage<MemoryFragment<T>>(config.storage, {\n\t\tname: persistName,\n\t\tcodec,\n\t});\n\n\t// `recordIngest:true` is implied — the rebuildable-projection source.\n\tconst store = reactiveFactStore<T>({ ...config, recordIngest: true });\n\tconst ingestLog = store.ingestLog!;\n\n\t// Durable-history async iterator. The substrate `appendLogStorage` tier's\n\t// `loadEntries()` returns the COMPLETE log in one call (it does not honor\n\t// `cursor`/`pageSize` windowing), so a single read yields the full history\n\t// — no partial-page-truncation risk (QA-B). A `loadEntries` rejection\n\t// propagates as ERROR on the replay source (not swallowed).\n\tasync function* loadHistory(): AsyncGenerator<MemoryFragment<T>> {\n\t\tif (typeof tier.loadEntries !== \"function\") return;\n\t\tconst page = await tier.loadEntries();\n\t\tfor (const f of page.entries) yield f;\n\t}\n\n\t// Replay = a reactive async source feeding `config.ingest` (spec §5.10 —\n\t// the ONLY async lives in this one source node; the factory is sync).\n\tconst replaySource = fromAny<MemoryFragment<T>>(loadHistory(), {\n\t\tname: \"_replay_source\",\n\t\tmeta: persistMeta(\"persist_replay_source\"),\n\t});\n\tstore.add(replaySource, { name: \"_replay_source\" });\n\n\t// replayPump: re-feed each replayed fragment through `config.ingest`\n\t// (the documented rebuildable-projection replay — `.emit` into a source is\n\t// the sanctioned reactive entry, mirroring the `decay` recipe precedent).\n\t// Emits the running replayed count (consumers observe `replayedCount`).\n\tlet replayed = 0;\n\tconst replayPump = node<number>(\n\t\t[replaySource],\n\t\t(batchData, actions) => {\n\t\t\tconst b = batchData[0] as readonly MemoryFragment<T>[] | undefined;\n\t\t\tif (b != null && b.length > 0) {\n\t\t\t\tfor (const f of b) {\n\t\t\t\t\tconfig.ingest.emit(f);\n\t\t\t\t\treplayed += 1;\n\t\t\t\t}\n\t\t\t\tactions.emit(replayed);\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\tname: \"_replay_pump\",\n\t\t\tdescribeKind: \"derived\",\n\t\t\tinitial: 0,\n\t\t\tmeta: persistMeta(\"persist_replay_pump\"),\n\t\t},\n\t);\n\tstore.add(replayPump, { name: \"_replay_pump\" });\n\tstore.addDisposer(keepalive(replayPump));\n\n\t// `attached` flips reactively when the replay source COMPLETEs — gates\n\t// `position` so it can advance from 0 the instant replay drains.\n\tconst attached = node<boolean>([], {\n\t\tinitial: false,\n\t\tname: \"_storage_attached\",\n\t\tdescribeKind: \"state\",\n\t\tmeta: persistMeta(\"persist_attached\"),\n\t});\n\tstore.add(attached, { name: \"_storage_attached\" });\n\tstore.addDisposer(keepalive(attached));\n\n\t// Wire durable persistence AFTER replay drains. By COMPLETE, `ingestLog`\n\t// holds the full replayed history (synchronous per-wave propagation), so\n\t// `attachStorage` initialises its delivered-cursor to `ingestLog.size` →\n\t// the replayed history is NOT re-persisted; only live post-replay\n\t// fragments ship. The cursor is the substrate's `delivered` map; the\n\t// consumer tracks nothing. Catching the replay source's COMPLETE terminal\n\t// is reactive (spec §2.2 terminal propagation); `attachStorage` is the\n\t// IO-sink wiring, analogous to its own internal `fromTimer`/teardown-drain\n\t// wiring (§24 internal subscription, invisible in describe).\n\t//\n\t// **Pre-attach live-ingest window.** A caller that emits a live fragment\n\t// in the same synchronous tick as construction feeds `config.ingest`\n\t// BEFORE the async replay drains, so `ingestLog.size` at COMPLETE is\n\t// `replayed + (pre-attach live)`. `attachStorage`'s delivered-cursor\n\t// starts at `ingestLog.size` and can't tell that slice from replayed\n\t// history → it would never ship it (silent loss). Ship that one slice\n\t// once here (the same `appendEntries` API attachStorage uses — a one-shot\n\t// reconciliation, NOT a maintained consumer cursor); steady-state\n\t// delta-shipping stays the substrate-owned `delivered` cursor.\n\tlet detachStorage: (() => void) | undefined;\n\tconst replaySub = replaySource.subscribe((msgs) => {\n\t\tfor (const m of msgs) {\n\t\t\tif (m[0] === COMPLETE && detachStorage === undefined) {\n\t\t\t\tconst sizeAtAttach = ingestLog.size;\n\t\t\t\tdetachStorage = ingestLog.attachStorage([tier]);\n\t\t\t\tif (sizeAtAttach > replayed) {\n\t\t\t\t\tconst slice: MemoryFragment<T>[] = [];\n\t\t\t\t\tfor (let i = replayed; i < sizeAtAttach; i += 1) {\n\t\t\t\t\t\tconst v = ingestLog.at(i);\n\t\t\t\t\t\tif (v === undefined) {\n\t\t\t\t\t\t\t// QA-F: the slice is contiguous + fully-present by\n\t\t\t\t\t\t\t// invariant (`ingestLog` indices [replayed,size) are the\n\t\t\t\t\t\t\t// pre-attach live fragments). A hole here is a real bug —\n\t\t\t\t\t\t\t// fail loud rather than silently gap the durable log\n\t\t\t\t\t\t\t// (matches this file's \"no silent loss\" contract).\n\t\t\t\t\t\t\tthrow new Error(\n\t\t\t\t\t\t\t\t`persistentReactiveFactStore: ingestLog hole at index ${i} ` +\n\t\t\t\t\t\t\t\t\t`in reconciliation slice [${replayed}, ${sizeAtAttach}); ` +\n\t\t\t\t\t\t\t\t\t`pre-attach-live durability cannot be guaranteed.`,\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tslice.push(v);\n\t\t\t\t\t}\n\t\t\t\t\tif (slice.length > 0) {\n\t\t\t\t\t\tconst r = tier.appendEntries(slice);\n\t\t\t\t\t\tif (r instanceof Promise) r.catch(() => {});\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tattached.emit(true);\n\t\t\t}\n\t\t}\n\t});\n\tstore.addDisposer(() => {\n\t\treplaySub();\n\t\tdetachStorage?.();\n\t});\n\n\t// position: durably-persisted fragment count. `0` until replay completes;\n\t// then the `ingestLog` size (replayed history was loaded FROM the tier; new\n\t// fragments are shipped by the substrate-owned attachStorage cursor).\n\tconst position = node<number>(\n\t\t[ingestLog.entries, attached],\n\t\t(batchData, actions, ctx) => {\n\t\t\tconst eb = batchData[0];\n\t\t\tconst arr = (eb != null && eb.length > 0 ? eb.at(-1) : ctx.prevData[0]) as\n\t\t\t\t| readonly MemoryFragment<T>[]\n\t\t\t\t| undefined;\n\t\t\tconst ab = batchData[1];\n\t\t\tconst isAttached = (ab != null && ab.length > 0 ? ab.at(-1) : ctx.prevData[1]) as\n\t\t\t\t| boolean\n\t\t\t\t| undefined;\n\t\t\tactions.emit(isAttached === true ? (arr?.length ?? 0) : 0);\n\t\t},\n\t\t{\n\t\t\tname: \"_durable_position\",\n\t\t\tdescribeKind: \"derived\",\n\t\t\tinitial: 0,\n\t\t\tmeta: persistMeta(\"persist_position\"),\n\t\t},\n\t);\n\tstore.add(position, { name: \"_durable_position\" });\n\tstore.addDisposer(keepalive(position));\n\n\tconst out = Object.assign(store, {\n\t\tposition,\n\t\treplayedCount: replayPump,\n\t\ttier,\n\t\tasync flush(): Promise<void> {\n\t\t\tawait tier.flush?.();\n\t\t},\n\t}) as PersistentReactiveFactStoreGraph<T>;\n\treturn out;\n}\n","/**\n * Recipe — **LLM gatekeeper** admission filter.\n *\n * `ReactiveFactStoreConfig.admissionFilter` is a **synchronous** face — it runs\n * inside `extract_op` at ingest time, so it cannot itself `await` an LLM (spec\n * §5.10 forbids raw async in the reactive layer; the LLM call belongs in a\n * source / `promptNode` upstream). This recipe adapts a **precomputed verdict\n * stream** to the sync face: the caller runs the judge upstream (a `promptNode`\n * over the candidate fragment stream) and feeds its `Map<FactId, boolean>`\n * verdicts in; the recipe returns a `Node<AdmissionFilter<T>>` that consults\n * the latest verdicts synchronously.\n *\n * ```ts\n * // upstream: async LLM judge produces verdicts (id → admit?)\n * const verdicts = promptNode(adapter, [candidates], judgeFragmentsFn); // Node<Map<FactId,boolean>>\n * const mem = reactiveFactStore<Doc>({\n * ingest, extractDependencies,\n * admissionFilter: admissionLlmJudge<Doc>(verdicts), // ② sync-face adapter\n * });\n * ```\n *\n * **Deny-by-default.** A fragment with no verdict yet is rejected\n * (`defaultVerdict` default `false`) — the store never admits an unjudged fact.\n * Set `defaultVerdict: true` for an allow-then-prune posture instead.\n *\n * **⚠️ The filter only gates admission AT ingest, synchronously.**\n * - A fragment ingested **before** its verdict lands is rejected (with the\n * default `false`) and is **permanently lost** — there is no buffering or\n * retry; a later verdict admitting that id does NOT retroactively ingest it.\n * If you cannot guarantee verdict-before-fragment ordering, buffer/replay\n * the candidate stream upstream (e.g. gate ingest behind the verdict Node).\n * - \"Prune\" in `defaultVerdict: true` is a misnomer for *post-hoc removal*: a\n * verdict flipping `true → false` after a fact is committed does nothing\n * (the fact stays). It only means \"admit unjudged, reject explicitly-denied\n * at ingest\". True pruning requires a separate obsoletion path.\n *\n * @module\n */\n\nimport { type Node, node } from \"@graphrefly/pure-ts/core\";\nimport type { AdmissionFilter, FactId } from \"../fact-store.js\";\n\nexport interface AdmissionLlmJudgeOptions {\n\t/** Verdict for a fragment the judge hasn't ruled on yet. Default `false` (strict gate). */\n\treadonly defaultVerdict?: boolean;\n\t/** Node name. Default `admission_llm_judge`. */\n\treadonly name?: string;\n}\n\n/**\n * Adapt an upstream LLM-verdict stream to the synchronous `admissionFilter`\n * face. `verdicts` is a Node carrying the current `factId → admit?` map (e.g.\n * a `promptNode` accumulating judgements).\n *\n * @category memory\n */\nexport function admissionLlmJudge<T>(\n\tverdicts: Node<ReadonlyMap<FactId, boolean>>,\n\topts: AdmissionLlmJudgeOptions = {},\n): Node<AdmissionFilter<T>> {\n\tconst dflt = opts.defaultVerdict ?? false;\n\tconst buildFilter =\n\t\t(m: ReadonlyMap<FactId, boolean>): AdmissionFilter<T> =>\n\t\t(f) =>\n\t\t\tm.get(f.id) ?? dflt;\n\n\treturn node<AdmissionFilter<T>>(\n\t\t[verdicts],\n\t\t(batchData, actions, ctx) => {\n\t\t\tconst m =\n\t\t\t\t(batchData[0] as readonly ReadonlyMap<FactId, boolean>[] | undefined)?.at(-1) ??\n\t\t\t\t(ctx.prevData[0] as ReadonlyMap<FactId, boolean> | undefined) ??\n\t\t\t\tnew Map<FactId, boolean>();\n\t\t\tactions.emit(buildFilter(m));\n\t\t},\n\t\t{\n\t\t\tname: opts.name ?? \"admission_llm_judge\",\n\t\t\tdescribeKind: \"derived\",\n\t\t\t// Before any verdict arrives, apply the default policy.\n\t\t\tinitial: buildFilter(new Map<FactId, boolean>()),\n\t\t},\n\t);\n}\n","/**\n * Recipe — **\"as of t\" historical view** (MEME L3 obsolescence reasoning).\n *\n * `reactiveFactStore`'s built-in `query`/`answer` face answers a structured\n * `MemoryQuery` (which already supports `asOf`), but a common need is a\n * *standing, reactive* historical projection that re-derives whenever either\n * the store changes **or** the as-of instant moves (e.g. a scrubber in a debug\n * UI). This recipe is that projection over face ④ + a reactive `asOf` input:\n * `derived([asOf, mem.factStore])` → only fragments valid at `asOf`\n * (bi-temporal `[validFrom, validTo)` test), giving Graphiti/Zep-style\n * \"what did we believe at time T\" without committing anything to the spec.\n *\n * ```ts\n * const asOf = node<bigint>([], { initial: undefined });\n * const view = bitemporalQuery(mem, asOf, { tags: [\"policy\"] });\n * asOf.emit(lastTuesdayNs); // view re-derives to the policy facts valid then\n * ```\n *\n * Pass `asOf` SENTINEL (no emit yet) → the view is the **currently-valid** set\n * (live facts, `validTo` unset), so it's useful before any scrub too.\n *\n * @module\n */\n\nimport { type Node, node } from \"@graphrefly/pure-ts/core\";\nimport type { FactStore, MemoryFragment, ReactiveFactStoreGraph } from \"../fact-store.js\";\nimport { lastOf, validAt } from \"./_shared.js\";\n\nexport interface BitemporalQueryOptions {\n\t/** Restrict to fragments carrying any of these tags (OR). */\n\treadonly tags?: readonly string[];\n\t/** Minimum confidence (inclusive). */\n\treadonly minConfidence?: number;\n\t/** Node name. Default `bitemporal_query`. */\n\treadonly name?: string;\n}\n\n/**\n * Build a standing bi-temporal historical view over a {@link reactiveFactStore}.\n * Emits the fragments valid at the latest `asOf` (sorted confidence desc, then\n * `t_ns` desc — same order as the built-in `answer`).\n *\n * @category memory\n */\nexport function bitemporalQuery<T>(\n\tmem: ReactiveFactStoreGraph<T>,\n\tasOf: Node<bigint>,\n\topts: BitemporalQueryOptions = {},\n): Node<readonly MemoryFragment<T>[]> {\n\t// Wrap the caller's `asOf` to a non-SENTINEL `bigint | null` (initial\n\t// `null` = \"no scrub yet ⇒ currently-valid\"). Without this, an `asOf` that\n\t// hasn't emitted is SENTINEL and the first-run gate (COMPOSITION-GUIDE-GRAPH\n\t// §10 cascading SENTINEL) would silence the whole view until the first\n\t// scrub. `null` is DATA, so the gate opens immediately; a later `asOf.emit`\n\t// still re-triggers the view (it remains a reactive dep).\n\tconst asOfOrNull = node<bigint | null>(\n\t\t[asOf],\n\t\t(b, a, c) => a.emit(lastOf<bigint>(b[0], c.prevData[0]) ?? null),\n\t\t{ name: `${opts.name ?? \"bitemporal_query\"}_asof`, describeKind: \"derived\", initial: null },\n\t);\n\n\treturn node<readonly MemoryFragment<T>[]>(\n\t\t[asOfOrNull, mem.factStore],\n\t\t(batchData, actions, ctx) => {\n\t\t\tconst raw = lastOf<bigint | null>(batchData[0], ctx.prevData[0]);\n\t\t\tconst at = raw ?? undefined; // null/SENTINEL ⇒ currently-valid\n\t\t\tconst fs = lastOf<FactStore<T>>(batchData[1], ctx.prevData[1]);\n\t\t\tif (fs == null) {\n\t\t\t\tactions.emit([]);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tconst results = [...fs.byId.values()].filter((f) => {\n\t\t\t\tif (!validAt(f, at)) return false;\n\t\t\t\tif (opts.tags && opts.tags.length > 0 && !opts.tags.some((t) => f.tags.includes(t))) {\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t\tif (opts.minConfidence !== undefined && f.confidence < opts.minConfidence) return false;\n\t\t\t\treturn true;\n\t\t\t});\n\t\t\tresults.sort((a, b) => b.confidence - a.confidence || Number(b.t_ns - a.t_ns));\n\t\t\tactions.emit(results);\n\t\t},\n\t\t{\n\t\t\tname: opts.name ?? \"bitemporal_query\",\n\t\t\tdescribeKind: \"derived\",\n\t\t\tinitial: [] as readonly MemoryFragment<T>[],\n\t\t},\n\t);\n}\n","/**\n * Shared internals for the {@link reactiveFactStore} recipe library\n * (DS-14.7 follow-up #1). Not part of the public surface — recipes re-export\n * only their own factory.\n *\n * @module\n */\n\nimport type { MemoryFragment } from \"../fact-store.js\";\n\n/**\n * Last-value-of-wave helper, mirroring the `lastOf` in `fact-store.ts`: prefer\n * the most recent value emitted on dep slot `i` this wave, else fall back to\n * the dep's prior cached value (`ctx.prevData[i]`). Returns `undefined` at\n * SENTINEL (dep has never emitted DATA).\n */\nexport function lastOf<X>(batch: readonly unknown[] | undefined, prev: unknown): X | undefined {\n\treturn batch != null && batch.length > 0 ? (batch.at(-1) as X) : (prev as X | undefined);\n}\n\n/**\n * Bi-temporal validity test — re-implemented locally so recipes stay\n * self-contained (the `fact-store.ts` `currentlyValid` is private; widening its\n * export for two recipe consumers would creep the substrate surface). Semantics\n * are identical: with no `asOf`, \"currently valid\" === not obsolete\n * (`validTo` unset); with `asOf`, the instant must fall in `[validFrom, validTo)`.\n */\nexport function validAt<T>(f: MemoryFragment<T>, asOf?: bigint): boolean {\n\tif (asOf === undefined) return f.validTo === undefined;\n\tif (f.validFrom !== undefined && asOf < f.validFrom) return false;\n\tif (f.validTo !== undefined && asOf >= f.validTo) return false;\n\treturn true;\n}\n","/**\n * Recipe — **REM-style replay consolidation**.\n *\n * Hassabis's sleep-consolidation frame: periodically replay the\n * highest-confidence × most-recent facts and synthesize a compressed successor\n * fragment (version-chained via `parent_fragment_id`). Builds the two config\n * fields the `consolidate` extension face needs — a reactive `consolidateTrigger`\n * (a `fromTimer` source, spec §5.8-compliant) and the `consolidate(store)`\n * summarizer — so the caller just spreads them in:\n *\n * ```ts\n * const mem = reactiveFactStore<Doc>({\n * ingest, extractDependencies,\n * ...consolidationRem<Doc>({\n * periodMs: 60_000,\n * topK: 8,\n * recentWindowNs: 3_600_000_000_000n, // 1h\n * summarize: (frags) => mergeDocs(frags), // domain-specific\n * }),\n * });\n * ```\n *\n * The pattern default-wires consolidator output back into ingest (Q9-open-6),\n * so the successor fragment becomes a first-class fact; the originals are left\n * intact (callers obsolete them via their own `validTo` policy if desired —\n * keeping that out of the recipe preserves \"the recipe never decides what to\n * forget\").\n *\n * **Selection order & assumptions.** `recentWindowNs` filters the *pool* first\n * (facts within `recentWindowNs` of the newest live fact's `t_ns`); the pool\n * is then sorted by `confidence desc, t_ns desc` and `topK`-sliced — so an\n * old-but-in-window high-confidence fact can beat a brand-new low-confidence\n * one (recency gates the pool, confidence picks the winners). If every fact\n * falls outside the window the pool is empty and `summarize` is not called\n * (the consolidator emits `[]` — indistinguishable from an empty store).\n * `recentWindowNs` math assumes all fragments' `t_ns` come from the **same\n * clock** (`monotonicNs` xor `wallClockNs`, not mixed) — the window is\n * meaningless across mixed clocks.\n *\n * @module\n */\n\nimport type { Node } from \"@graphrefly/pure-ts/core\";\nimport { fromTimer } from \"@graphrefly/pure-ts/extra\";\nimport type { MemoryFragment, StoreReadHandle } from \"../fact-store.js\";\n\nexport interface ConsolidationRemOptions<T> {\n\t/** Replay period in milliseconds. */\n\treadonly periodMs: number;\n\t/** How many top facts to replay per pass. */\n\treadonly topK: number;\n\t/**\n\t * Synthesize successor fragment(s) from the replayed set. Domain-specific —\n\t * required (the pattern's default `consolidate` is a no-op). Set\n\t * `parent_fragment_id` on the result to chain versions.\n\t */\n\treadonly summarize: (replayed: readonly MemoryFragment<T>[]) => readonly MemoryFragment<T>[];\n\t/**\n\t * Only replay facts whose `t_ns` is within this many ns of the most-recent\n\t * fact's `t_ns` (recency gate). Omit to consider all live facts.\n\t */\n\treadonly recentWindowNs?: bigint;\n}\n\nexport interface ConsolidationRemConfig<T> {\n\treadonly consolidateTrigger: Node<number>;\n\treadonly consolidate: (store: StoreReadHandle<T>) => readonly MemoryFragment<T>[];\n}\n\n/**\n * Build the `{ consolidateTrigger, consolidate }` pair for a REM-replay\n * consolidation policy. Spread into {@link reactiveFactStore}'s config.\n *\n * @category memory\n */\nexport function consolidationRem<T>(opts: ConsolidationRemOptions<T>): ConsolidationRemConfig<T> {\n\t// Raw `fromTimer` is the trigger — the factory's `consolidated` node deps on\n\t// it and is keepalive'd, so it stays hot. (An intermediate equals-defaulted\n\t// wrapper would collapse the timer's first tick `0` against an `initial: 0`\n\t// via Object.is dedupe — timer ticks are events, not values.)\n\tconst consolidateTrigger = fromTimer(opts.periodMs, { period: opts.periodMs });\n\n\tconst consolidate = (store: StoreReadHandle<T>): readonly MemoryFragment<T>[] => {\n\t\t// Live facts only (obsolete ones aren't worth replaying).\n\t\tconst live = [...store.values()].filter((f) => f.validTo === undefined);\n\t\tif (live.length === 0) return [];\n\t\tlet pool = live;\n\t\tif (opts.recentWindowNs !== undefined) {\n\t\t\tconst newest = live.reduce((m, f) => (f.t_ns > m ? f.t_ns : m), live[0]!.t_ns);\n\t\t\tconst cutoff = newest - opts.recentWindowNs;\n\t\t\tpool = live.filter((f) => f.t_ns >= cutoff);\n\t\t}\n\t\tpool.sort((a, b) => b.confidence - a.confidence || Number(b.t_ns - a.t_ns));\n\t\tconst replayed = pool.slice(0, Math.max(0, opts.topK));\n\t\treturn replayed.length > 0 ? opts.summarize(replayed) : [];\n\t};\n\n\treturn { consolidateTrigger, consolidate };\n}\n","/**\n * Recipe — **standard forgetting curve** (Hassabis confidence drift).\n *\n * Periodically decays every live fact's `confidence` toward a floor on an\n * exponential half-life schedule, re-ingesting the drifted fragment (MEME L1\n * direct-replace). The periodic trigger is a reactive `fromTimer` source\n * (spec §5.8 no-polling / §5.10 no raw `setTimeout` — `fromTimer` is the\n * sanctioned reactive timer) and the only reactive dep, so there is no\n * within-wave feedback loop (COMPOSITION-GUIDE-GRAPH §7): the store snapshot is\n * read **advisory** off `mem.factStore.cache`, never as a reactive dep; the\n * re-ingest write commits **synchronously this tick** (graphrefly push is\n * synchronous) and the next tick decays from there.\n *\n * > **Recipe vs the `decay` face.** `ReactiveFactStoreConfig.decay:\n * > Node<DecayPolicy>` (DS-14.7 PART 4.1 face ②) **is wired** in the factory —\n * > pair it with `decayTrigger` and the store owns decay as a reactive,\n * > swappable policy. This recipe is the **ergonomic alternative**: it owns its\n * > own `fromTimer` and writes through the already-wired `ingest` face, so the\n * > caller wires no `decay`/`decayTrigger` and the recipe composes whether or\n * > not the face is also used. Both deliver the §5.8 \"`fromTimer` for periodic\n * > confidence drift\" behavior; pick the face for a caller-controlled policy\n * > Node, this recipe for a self-contained drop-in forgetting curve.\n *\n * **Convergence (conditional — read this).** Per-id decay is computed against\n * the elapsed time since this fact was last decayed (recipe-owned closure\n * clock — `t_ns` provenance is preserved, never overwritten; a re-ingested\n * *new version* — fresher `t_ns` than the last tick — restarts from its own\n * `t_ns`, not the stale prior-version tick). A fragment is skipped (no\n * re-ingest) when it is obsolete (`validTo` set), already at/below `floor`, or\n * the drift is below `epsilon`. Quiescence (\"timer keeps ticking, recipe emits\n * `[]`, no churn\") therefore holds **only when `epsilon > 0` AND `floor` is\n * reachable** — with `epsilon <= 0` and a finite half-life the geometric\n * drift toward `floor` is always `> 0`, so the loop re-ingests every live fact\n * forever (silent CPU/GC churn, never an error). `epsilon` is clamped to a\n * tiny positive minimum to make accidental non-quiescence impossible.\n * Obsoletion safety: the obsolete guard is re-checked against the **live**\n * store immediately before re-ingest, so a fact obsoleted by an in-flight\n * cascade earlier in the same tick is never resurrected by a stale snapshot\n * read.\n *\n * @module\n */\n\nimport { type Node, node, wallClockNs } from \"@graphrefly/pure-ts/core\";\nimport { fromTimer, keepalive } from \"@graphrefly/pure-ts/extra\";\nimport type { FactId, MemoryFragment, ReactiveFactStoreGraph } from \"../fact-store.js\";\n\nexport interface DecayExponentialOptions {\n\t/** Half-life in nanoseconds — confidence halves every `halfLifeNs` of fact age. */\n\treadonly halfLifeNs: bigint;\n\t/** Timer period in milliseconds (how often the forgetting pass runs). */\n\treadonly periodMs: number;\n\t/** Confidence floor; facts at/below it are left untouched. Default `0`. */\n\treadonly floor?: number;\n\t/**\n\t * Minimum confidence drift to bother re-ingesting. Default `1e-4`. Clamped\n\t * to a tiny positive minimum (`<= 0` would prevent quiescence — see the\n\t * module \"Convergence\" note).\n\t */\n\treadonly epsilon?: number;\n\t/** Node name. Default `decay_exponential`. */\n\treadonly name?: string;\n}\n\n/**\n * Wire an exponential-decay forgetting loop onto a {@link reactiveFactStore}.\n * Self-adds a driver Node to the store's graph (`describe()`-visible) and\n * returns it; each emission is the batch of fragments decayed that tick (also\n * fed back through `ingest`).\n *\n * @category memory\n */\nexport function decayExponential<T>(\n\tmem: ReactiveFactStoreGraph<T>,\n\tingest: Node<MemoryFragment<T>>,\n\topts: DecayExponentialOptions,\n): Node<readonly MemoryFragment<T>[]> {\n\tconst floor = opts.floor ?? 0;\n\t// Clamp to a tiny positive min: epsilon <= 0 would make the geometric\n\t// drift never fall below it ⇒ the loop never quiesces (module note).\n\tconst epsilon = Math.max(opts.epsilon ?? 1e-4, Number.EPSILON);\n\tconst half = Number(opts.halfLifeNs);\n\t// Recipe-owned per-id \"last decayed at\" clock — keeps `t_ns` provenance\n\t// intact while still giving correct per-interval exponential drift.\n\tconst lastTick = new Map<FactId, bigint>();\n\n\tconst timer = fromTimer(opts.periodMs, { period: opts.periodMs });\n\n\tconst driver = node<readonly MemoryFragment<T>[]>(\n\t\t[timer],\n\t\t(_batchData, actions) => {\n\t\t\tconst fs = mem.factStore.cache as\n\t\t\t\t| { byId: ReadonlyMap<FactId, MemoryFragment<T>> }\n\t\t\t\t| undefined;\n\t\t\tif (!fs) {\n\t\t\t\tactions.emit([]);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tconst now = BigInt(wallClockNs());\n\t\t\tconst decayed: MemoryFragment<T>[] = [];\n\t\t\tconst liveIds = new Set<FactId>();\n\t\t\tfor (const f of fs.byId.values()) {\n\t\t\t\tliveIds.add(f.id);\n\t\t\t\tif (f.validTo !== undefined) continue; // obsolete — dead, don't drift\n\t\t\t\tif (f.confidence <= floor) continue; // already forgotten\n\t\t\t\t// Version-aware \"since\": if our last tick predates the fact's own\n\t\t\t\t// t_ns, this is a freshly re-ingested version — restart from its\n\t\t\t\t// t_ns, not the stale prior-version tick (which would over-decay\n\t\t\t\t// across time the new version didn't exist).\n\t\t\t\tconst lt = lastTick.get(f.id);\n\t\t\t\tconst since = lt !== undefined && lt >= f.t_ns ? lt : f.t_ns;\n\t\t\t\tconst elapsed = Number(now - since);\n\t\t\t\tif (elapsed <= 0) continue;\n\t\t\t\tconst factor = 0.5 ** (half > 0 ? elapsed / half : 0);\n\t\t\t\tif (!Number.isFinite(factor)) continue; // bigint→Number overflow guard\n\t\t\t\tlet next = f.confidence * factor;\n\t\t\t\tif (next < floor) next = floor;\n\t\t\t\tif (f.confidence - next < epsilon) continue; // drift too small\n\t\t\t\t// Resurrection guard: re-read the LIVE store — a fact obsoleted by\n\t\t\t\t// an in-flight cascade triggered by an earlier re-ingest this same\n\t\t\t\t// tick must not be re-ingested from this (pre-cascade) snapshot.\n\t\t\t\tconst liveNow = (\n\t\t\t\t\tmem.factStore.cache as { byId: ReadonlyMap<FactId, MemoryFragment<T>> } | undefined\n\t\t\t\t)?.byId.get(f.id);\n\t\t\t\tif (liveNow && liveNow.validTo !== undefined) continue;\n\t\t\t\tlastTick.set(f.id, now);\n\t\t\t\tconst drifted: MemoryFragment<T> = { ...f, confidence: next };\n\t\t\t\tdecayed.push(drifted);\n\t\t\t\tingest.emit(drifted); // MEME L1 direct-replace (the wired face)\n\t\t\t}\n\t\t\t// Prune the closure clock of ids no longer in the store (bounds the\n\t\t\t// Map to live facts; obsoleted-but-present ids stay until removed).\n\t\t\tif (lastTick.size > liveIds.size) {\n\t\t\t\tfor (const id of lastTick.keys()) if (!liveIds.has(id)) lastTick.delete(id);\n\t\t\t}\n\t\t\tactions.emit(decayed);\n\t\t},\n\t\t{\n\t\t\tname: opts.name ?? \"decay_exponential\",\n\t\t\tdescribeKind: \"derived\",\n\t\t\tinitial: [] as readonly MemoryFragment<T>[],\n\t\t},\n\t);\n\n\tmem.add(driver, { name: opts.name ?? \"decay_exponential\" });\n\tmem.addDisposer(keepalive(timer));\n\tmem.addDisposer(keepalive(driver));\n\treturn driver;\n}\n","/**\n * Recipe — **write-time influence analysis** (MEME write-time `reachable`).\n *\n * The store's `dependents_index` is a one-hop reverse-dependency map; \"if I\n * obsolete fact X, what *transitively* gets invalidated?\" is its closure. This\n * recipe exposes that closure as a reactive projection over face ④\n * (`mem.dependentsIndex`), so a writer can see blast-radius **before**\n * committing an obsolescence — the same reachability the cascade loop walks,\n * surfaced for inspection.\n *\n * ```ts\n * const inf = influenceAnalysis(mem);\n * inf.influenceOf(\"home\").subscribe(ids => console.log(\"obsoleting home hits\", ids));\n * inf.ranked.subscribe(rows => rows.slice(0,5)); // most-influential facts\n * ```\n *\n * Pure observer — never writes back, so it cannot perturb cascade\n * convergence. **Single-apply per store** (it adds an `${name}_ranked` node);\n * apply twice on the same `mem` only with a distinct `opts.name`.\n *\n * **Cost.** `ranked` runs a BFS closure per index key on every\n * `dependents_index` emit — O(keys · (V+E)), NOT O(maxRanked); `maxRanked`\n * caps only the emitted slice, not the computation. Acceptable because\n * `dependents_index` is metadata (≪ the fact store, per DS-14.7 Q9-open-1);\n * for a pathologically dense index prefer `influenceOf(id)` (single-root BFS)\n * over subscribing `ranked`. In a cyclic reverse-dep graph\n * (LLM-extracted `A→B→A`) `closureOf` still terminates (`seen` set) but every\n * SCC member reports the same closure size — a *reachability* count, not the\n * cascade's actual obsolete-only propagation reach (which `processedRoots`\n * bounds further).\n *\n * @module\n */\n\nimport { type Node, node } from \"@graphrefly/pure-ts/core\";\nimport { keepalive } from \"@graphrefly/pure-ts/extra\";\nimport type { DependentsIndex, FactId, ReactiveFactStoreGraph } from \"../fact-store.js\";\nimport { lastOf } from \"./_shared.js\";\n\nexport interface InfluenceRow {\n\treadonly factId: FactId;\n\t/** Size of the transitive dependent closure (blast radius if obsoleted). */\n\treadonly influence: number;\n}\n\nexport interface InfluenceAnalysisOptions {\n\t/** Cap on rows emitted by `ranked` (top-N by influence). Default `64`. */\n\treadonly maxRanked?: number;\n\t/** Node name prefix. Default `influence`. */\n\treadonly name?: string;\n}\n\nexport interface InfluenceAnalysis {\n\t/** Reactive transitive dependent closure of `rootId` (excludes the root). */\n\tinfluenceOf(rootId: FactId): Node<readonly FactId[]>;\n\t/** Facts ranked by transitive-closure size (desc), capped at `maxRanked`. */\n\treadonly ranked: Node<readonly InfluenceRow[]>;\n}\n\n/** BFS the reverse-dep index from `root`; returns reachable ids (root excluded). */\nfunction closureOf(index: DependentsIndex, root: FactId): FactId[] {\n\tconst seen = new Set<FactId>();\n\tconst queue: FactId[] = [root];\n\twhile (queue.length > 0) {\n\t\tconst cur = queue.shift()!;\n\t\tfor (const dep of index.get(cur) ?? []) {\n\t\t\tif (seen.has(dep) || dep === root) continue;\n\t\t\tseen.add(dep);\n\t\t\tqueue.push(dep);\n\t\t}\n\t}\n\treturn [...seen];\n}\n\n/**\n * Attach influence/blast-radius analysis to a {@link reactiveFactStore}.\n *\n * @category memory\n */\nexport function influenceAnalysis<T>(\n\tmem: ReactiveFactStoreGraph<T>,\n\topts: InfluenceAnalysisOptions = {},\n): InfluenceAnalysis {\n\tconst prefix = opts.name ?? \"influence\";\n\tconst maxRanked = Math.max(1, opts.maxRanked ?? 64);\n\n\tconst ranked = node<readonly InfluenceRow[]>(\n\t\t[mem.dependentsIndex],\n\t\t(batchData, actions, ctx) => {\n\t\t\tconst index = lastOf<DependentsIndex>(batchData[0], ctx.prevData[0]);\n\t\t\tif (index == null) {\n\t\t\t\tactions.emit([]);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tconst rows: InfluenceRow[] = [];\n\t\t\tfor (const key of index.keys()) {\n\t\t\t\trows.push({ factId: key, influence: closureOf(index, key).length });\n\t\t\t}\n\t\t\trows.sort((a, b) => b.influence - a.influence);\n\t\t\tactions.emit(rows.slice(0, maxRanked));\n\t\t},\n\t\t{\n\t\t\tname: `${prefix}_ranked`,\n\t\t\tdescribeKind: \"derived\",\n\t\t\tinitial: [] as readonly InfluenceRow[],\n\t\t},\n\t);\n\tmem.add(ranked, { name: `${prefix}_ranked` });\n\tmem.addDisposer(keepalive(ranked));\n\n\t// Memoize per-rootId: a recipe node is added to `mem` under a unique name,\n\t// and `Graph.add` throws on a duplicate name — so `influenceOf(\"a\")` called\n\t// twice (two call sites, or a re-derive) MUST return the same node, not\n\t// re-add. Also bounds growth (one node per distinct queried root, not per\n\t// call) for the store's lifetime.\n\tconst builtFor = new Map<FactId, Node<readonly FactId[]>>();\n\tfunction influenceOf(rootId: FactId): Node<readonly FactId[]> {\n\t\tconst existing = builtFor.get(rootId);\n\t\tif (existing) return existing;\n\t\tconst n = node<readonly FactId[]>(\n\t\t\t[mem.dependentsIndex],\n\t\t\t(batchData, actions, ctx) => {\n\t\t\t\tconst index = lastOf<DependentsIndex>(batchData[0], ctx.prevData[0]);\n\t\t\t\tactions.emit(index == null ? [] : closureOf(index, rootId));\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: `${prefix}_of_${rootId}`,\n\t\t\t\tdescribeKind: \"derived\",\n\t\t\t\tinitial: [] as readonly FactId[],\n\t\t\t},\n\t\t);\n\t\tmem.add(n, { name: `${prefix}_of_${rootId}` });\n\t\tmem.addDisposer(keepalive(n));\n\t\tbuiltFor.set(rootId, n);\n\t\treturn n;\n\t}\n\n\treturn { influenceOf, ranked };\n}\n","/**\n * Recipe — **cascade-event tracer** (debugging the invisible edge).\n *\n * The `dependents_index` lookup that drives cascade invalidation is a fn-body\n * map read, not a topology edge — `describe()` / `explain()` can't see *why*\n * fact C got invalidated. This recipe is the pure-observer (face ④) companion\n * that subscribes `mem.cascade` + `mem.cascadeOverflow` and keeps a bounded\n * ring of human-readable trace entries (each carries the `causalReason` string\n * the store stamps for exactly this purpose), so a developer can answer\n * \"what obsoleted this?\" without instrumenting the store.\n *\n * ```ts\n * const trace = invalidationTracer(mem, { limit: 256 });\n * trace.subscribe((entries) => entries.forEach(e => log(e.causalReason)));\n * ```\n *\n * Read-only: it never writes back into the store, so it cannot perturb cascade\n * convergence.\n *\n * @module\n */\n\nimport { type Node, node } from \"@graphrefly/pure-ts/core\";\nimport { keepalive } from \"@graphrefly/pure-ts/extra\";\nimport type {\n\tCascadeEvent,\n\tCascadeOverflow,\n\tCascadeReason,\n\tFactId,\n\tReactiveFactStoreGraph,\n} from \"../fact-store.js\";\n\nexport interface InvalidationTraceEntry {\n\treadonly kind: \"cascade\" | \"overflow\";\n\treadonly factId: FactId;\n\treadonly rootFactId: FactId;\n\treadonly reason: CascadeReason | \"overflow\";\n\treadonly iteration?: number;\n\treadonly causalReason: string;\n}\n\nexport interface InvalidationTracerOptions {\n\t/** Ring-buffer size (most-recent N trace entries retained). Default `256`. */\n\treadonly limit?: number;\n\t/** Node name. Default `invalidation_tracer`. */\n\treadonly name?: string;\n}\n\n/**\n * Attach a bounded cascade-event tracer to a {@link reactiveFactStore}.\n * Self-adds a `describe()`-visible observer Node and returns it; each emission\n * is the current trace ring (oldest → newest).\n *\n * @category memory\n */\nexport function invalidationTracer<T>(\n\tmem: ReactiveFactStoreGraph<T>,\n\topts: InvalidationTracerOptions = {},\n): Node<readonly InvalidationTraceEntry[]> {\n\tconst limit = Math.max(1, opts.limit ?? 256);\n\tconst ring: InvalidationTraceEntry[] = []; // sole-owner bounded fold\n\n\tconst push = (e: InvalidationTraceEntry): void => {\n\t\tring.push(e);\n\t\tif (ring.length > limit) ring.splice(0, ring.length - limit);\n\t};\n\n\tconst tracer = node<readonly InvalidationTraceEntry[]>(\n\t\t[mem.cascade, mem.cascadeOverflow],\n\t\t(batchData, actions) => {\n\t\t\tconst cascadeWaves = (batchData[0] as readonly (readonly CascadeEvent[])[] | undefined) ?? [];\n\t\t\tfor (const wave of cascadeWaves) {\n\t\t\t\tfor (const ev of wave) {\n\t\t\t\t\tpush({\n\t\t\t\t\t\tkind: \"cascade\",\n\t\t\t\t\t\tfactId: ev.factId,\n\t\t\t\t\t\trootFactId: ev.rootFactId,\n\t\t\t\t\t\treason: ev.reason,\n\t\t\t\t\t\titeration: ev.iteration,\n\t\t\t\t\t\tcausalReason: ev.causalReason,\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}\n\t\t\tconst overflows = (batchData[1] as readonly (CascadeOverflow | null)[] | undefined) ?? [];\n\t\t\tfor (const ov of overflows) {\n\t\t\t\tif (ov == null) continue;\n\t\t\t\tpush({\n\t\t\t\t\tkind: \"overflow\",\n\t\t\t\t\tfactId: ov.sample[0] ?? \"\",\n\t\t\t\t\trootFactId: ov.rootFactId,\n\t\t\t\t\treason: \"overflow\",\n\t\t\t\t\tcausalReason: `cascade overflow: ${ov.droppedCount} dropped (root ${ov.rootFactId})`,\n\t\t\t\t});\n\t\t\t}\n\t\t\tactions.emit([...ring]);\n\t\t},\n\t\t{\n\t\t\tname: opts.name ?? \"invalidation_tracer\",\n\t\t\tdescribeKind: \"derived\",\n\t\t\tinitial: [] as readonly InvalidationTraceEntry[],\n\t\t},\n\t);\n\n\tmem.add(tracer, { name: opts.name ?? \"invalidation_tracer\" });\n\tmem.addDisposer(keepalive(tracer));\n\treturn tracer;\n}\n","/**\n * Recipe — **self-evolving scoring policy** (continual learning).\n *\n * Closes Hassabis's continual-learning frame against the `scoring` extension\n * face (②): an outcome / RL signal stream feeds back into the policy that\n * `reactiveFactStore` applies on `outcome` write-back, so the agent's memory\n * *learns* which facts proved useful.\n *\n * ```ts\n * const outcomes = node<OutcomeSignal>([], { initial: undefined });\n * const mem = reactiveFactStore<Doc>({\n * ingest, extractDependencies,\n * outcome: outcomes, // ③ topic input (RL signal)\n * scoring: scoringByOutcome(outcomes), // ② policy that evolves with it\n * });\n * ```\n *\n * The returned `Node<ScoringPolicy<T>>` re-emits a fresh policy closure every\n * time an `OutcomeSignal` arrives; the closure scores a fragment as\n * `clamp01(base(f) + learningRate · Σ reward(f.id))`. The reward accumulator is\n * a recipe-owned closure Map (sole-owner mutation inside the derived fn — the\n * COMPOSITION-GUIDE-sanctioned pattern for a fold the node alone advances),\n * keyed by `factId`, so learning is cumulative across episodes and survives\n * policy re-emission.\n *\n * **Contract (load-bearing — do not \"normalize\"):**\n * - The fn folds **every signal in the wave** (`for (const sig of batchData[0])`),\n * NOT `lastOf` last-only like the other recipes — a batched wave carrying N\n * `OutcomeSignal`s must accumulate all N. Replacing this with `lastOf` would\n * silently drop all-but-last reward in a batch.\n * - `outcomes` MUST be a non-replaying event source (each physical signal\n * delivered once). The fold has no per-signal idempotency key (two identical\n * `{factId, reward}` are legitimately distinct events); a source that\n * push-on-subscribe **re-delivers** a cached signal to a re-subscribe would\n * double-count it. The normal wiring (single keepalive'd consumer via the\n * factory's `outcomeProcessor`, subscribed at construction before any emit)\n * is safe.\n * - The policy node carries **no `equals`** by design: every emit is a fresh\n * closure identity so the factory's `outcomeProcessor` always re-fires and\n * re-reads the freshly-folded `acc` (the closure reads `acc` lazily at call\n * time). Adding an `equals` would make outcome write-back lag one signal.\n * - `acc` retains one entry per rewarded `factId` for the store's lifetime\n * (same bounded-growth class as `ingestLog`; acceptable — it is metadata).\n *\n * @module\n */\n\nimport { type Node, node } from \"@graphrefly/pure-ts/core\";\nimport type { FactId, MemoryFragment, OutcomeSignal, ScoringPolicy } from \"../fact-store.js\";\n\nexport interface ScoringByOutcomeOptions<T> {\n\t/**\n\t * Base score before accumulated reward. Default: the fragment's own\n\t * `confidence` (so an un-rewarded fact keeps its ingested confidence).\n\t */\n\treadonly base?: (f: MemoryFragment<T>) => number;\n\t/** Multiplier on accumulated reward. Default `1`. */\n\treadonly learningRate?: number;\n\t/** Node name (collision-safe if you build more than one). Default `scoring_by_outcome`. */\n\treadonly name?: string;\n}\n\nconst clamp01 = (n: number): number => (n < 0 ? 0 : n > 1 ? 1 : n);\n\n/**\n * Build a continual-learning {@link ScoringPolicy} Node from an\n * {@link OutcomeSignal} stream. Pass the SAME `outcomes` Node as both\n * `config.outcome` and (via this recipe) `config.scoring`.\n *\n * @category memory\n */\nexport function scoringByOutcome<T>(\n\toutcomes: Node<OutcomeSignal>,\n\topts: ScoringByOutcomeOptions<T> = {},\n): Node<ScoringPolicy<T>> {\n\tconst base = opts.base ?? ((f: MemoryFragment<T>) => f.confidence);\n\tconst learningRate = opts.learningRate ?? 1;\n\t// Sole-owner reward accumulator (recipe-level fold; only this node writes it).\n\tconst acc = new Map<FactId, number>();\n\n\tconst buildPolicy = (): ScoringPolicy<T> => (fragment) =>\n\t\tclamp01(base(fragment) + learningRate * (acc.get(fragment.id) ?? 0));\n\n\treturn node<ScoringPolicy<T>>(\n\t\t[outcomes],\n\t\t(batchData, actions) => {\n\t\t\t// Apply every signal in the wave (batched outcomes accumulate in order).\n\t\t\t// Empty wave (push-on-subscribe / SENTINEL) → re-emit the current policy\n\t\t\t// so a late subscriber still gets a usable scorer.\n\t\t\tconst wave = (batchData[0] as readonly OutcomeSignal[] | undefined) ?? [];\n\t\t\tfor (const sig of wave) acc.set(sig.factId, (acc.get(sig.factId) ?? 0) + sig.reward);\n\t\t\tactions.emit(buildPolicy());\n\t\t},\n\t\t{\n\t\t\tname: opts.name ?? \"scoring_by_outcome\",\n\t\t\tdescribeKind: \"derived\",\n\t\t\t// Usable scorer before any outcome arrives (base-only).\n\t\t\tinitial: buildPolicy(),\n\t\t},\n\t);\n}\n","/**\n * Recipe — **multi-tenant isolation** sharding.\n *\n * Two postures over the `shardBy` + `shardCount` faces (plain-fn face ①):\n *\n * - **Strict isolation** (`tenants` listed): one shard per tenant, fixed\n * `tenant → index` mapping. A tenant's facts never share a shard with\n * another's — the strongest in-process isolation the static-topology store\n * offers (per-shard `state<FactStore>` nodes). Unknown tenants fall to a\n * dedicated overflow shard (last index).\n * - **Soft partition** (no `tenants`): hash the tenant key into `shardCount`\n * buckets — even load, tenants *may* co-reside (collision), but cross-tenant\n * reads still require an explicit query (no accidental leakage of the *index*,\n * just shared physical storage).\n *\n * **Caveats.** Soft \"even load\" holds only at sufficient tenant cardinality —\n * with few tenants vs `shardCount`, FNV-1a-mod can collide several onto one\n * shard (use **strict** mode for a guaranteed-per-tenant shard). `tenantOf`\n * must return a defined, stable key: a `undefined`/`null` return is coerced to\n * the string `\"undefined\"` by the factory's hash and silently pools all\n * tenant-less facts together. Strict mode's overflow shard intentionally pools\n * **all** unconfigured tenants together (isolated from configured ones, not\n * from each other).\n *\n * ```ts\n * const mem = reactiveFactStore<Doc>({\n * ingest, extractDependencies,\n * ...shardByTenant<Doc>((f) => f.payload.tenantId, {\n * tenants: [\"acme\", \"globex\"], // strict: 3 shards (2 + overflow)\n * }),\n * });\n * ```\n *\n * @module\n */\n\nimport type { MemoryFragment, ShardKey } from \"../fact-store.js\";\n\nexport interface ShardByTenantOptions {\n\t/**\n\t * Known tenants for strict 1-shard-per-tenant isolation. Omit for soft\n\t * hash partitioning. The strict layout adds one trailing **overflow** shard\n\t * for tenants not in this list (so an unconfigured tenant is still isolated\n\t * from the configured ones, just pooled together).\n\t */\n\treadonly tenants?: readonly string[];\n\t/** Bucket count for the soft (non-strict) posture. Default `4`. */\n\treadonly shardCount?: number;\n}\n\nexport interface ShardByTenantConfig<T> {\n\treadonly shardBy: (f: MemoryFragment<T>) => ShardKey;\n\treadonly shardCount: number;\n}\n\n/**\n * Build the `{ shardBy, shardCount }` pair for tenant-isolated sharding.\n * `tenantOf` extracts the tenant key from a fragment. Spread into\n * {@link reactiveFactStore}'s config.\n *\n * @category memory\n */\nexport function shardByTenant<T>(\n\ttenantOf: (f: MemoryFragment<T>) => string,\n\topts: ShardByTenantOptions = {},\n): ShardByTenantConfig<T> {\n\tif (opts.tenants && opts.tenants.length > 0) {\n\t\tconst idx = new Map(opts.tenants.map((t, i) => [t, i] as const));\n\t\tconst overflow = opts.tenants.length; // trailing shard for unknown tenants\n\t\treturn {\n\t\t\tshardBy: (f) => idx.get(tenantOf(f)) ?? overflow,\n\t\t\tshardCount: opts.tenants.length + 1,\n\t\t};\n\t}\n\tconst shardCount = Math.max(1, opts.shardCount ?? 4);\n\t// Return the tenant string itself — the factory's default FNV-1a hash-mod\n\t// sharder buckets it deterministically into `shardCount`.\n\treturn { shardBy: (f) => tenantOf(f), shardCount };\n}\n"],"mappings":"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","names":["monotonicNs","node","wallClockNs","fromTimer","keepalive","Graph","out","node","keepalive","node","keepalive","node","node","lastOf","node","lastOf","node","wallClockNs","fromTimer","keepalive","node","keepalive","node","lastOf","keepalive","node","keepalive","node","node","Graph","monotonicNs","fromTimer","out","keepalive","wallClockNs"]}
@@ -62,7 +62,7 @@ function fromCron(expr, opts) {
62
62
  };
63
63
  check();
64
64
  const id = setInterval(check, tickMs);
65
- return () => clearInterval(id);
65
+ return { onDeactivation: () => clearInterval(id) };
66
66
  },
67
67
  { ...sourceOpts(rest), name: rest.name ?? `cron:${expr}` }
68
68
  );
@@ -73,4 +73,4 @@ export {
73
73
  matchesCron,
74
74
  fromCron
75
75
  };
76
- //# sourceMappingURL=chunk-7EGRP2VX.js.map
76
+ //# sourceMappingURL=chunk-7BULJTL6.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/base/sources/event/cron.ts"],"sourcesContent":["/**\n * Cron-based reactive sources and schedule types.\n *\n * Merged from extra/cron.ts + extra/sources/event.ts (fromCron) during cleave A2.\n * `fromCron` relocated here from `dom.ts` (post-cleave /qa A1): it uses zero DOM\n * APIs (only `setInterval` + `new Date()`), so it belongs on the universal\n * `event/index.ts` barrel, not the browser-only `dom.ts` subpath.\n */\n\nimport { type Node, type NodeOptions, node, wallClockNs } from \"@graphrefly/pure-ts/core\";\n\ntype ExtraOpts = Omit<NodeOptions, \"describeKind\">;\n\nfunction sourceOpts<T = unknown>(opts?: ExtraOpts): NodeOptions<T> {\n\treturn { describeKind: \"producer\", ...opts } as NodeOptions<T>;\n}\n\n/** Options for {@link fromCron}. */\nexport type FromCronOptions = ExtraOpts & {\n\t/** Polling interval in ms. Default `60_000`. */\n\ttickMs?: number;\n\t/** Output format: `\"timestamp_ns\"` (default) emits wall-clock nanoseconds; `\"date\"` emits a `Date` object. */\n\toutput?: \"timestamp_ns\" | \"date\";\n};\n\n/**\n * Minimal 5-field cron parser and matcher (minute hour day-of-month month day-of-week).\n * Ported from callbag-recharge `extra/cron.ts` for `fromCron` (roadmap §2.3).\n */\nexport interface CronSchedule {\n\tminutes: Set<number>;\n\thours: Set<number>;\n\tdaysOfMonth: Set<number>;\n\tmonths: Set<number>;\n\tdaysOfWeek: Set<number>;\n}\n\nfunction parseField(field: string, min: number, max: number): Set<number> {\n\tconst result = new Set<number>();\n\tfor (const part of field.split(\",\")) {\n\t\tconst [range, stepStr] = part.split(\"/\");\n\t\tconst step = stepStr ? Number.parseInt(stepStr, 10) : 1;\n\t\tif (Number.isNaN(step) || step < 1) throw new Error(`Invalid cron step: ${part}`);\n\t\tlet start: number;\n\t\tlet end: number;\n\t\tif (range === \"*\") {\n\t\t\tstart = min;\n\t\t\tend = max;\n\t\t} else if (range.includes(\"-\")) {\n\t\t\tconst [a, b] = range.split(\"-\");\n\t\t\tstart = Number.parseInt(a, 10);\n\t\t\tend = Number.parseInt(b, 10);\n\t\t} else {\n\t\t\tstart = Number.parseInt(range, 10);\n\t\t\tend = start;\n\t\t}\n\t\tif (Number.isNaN(start) || Number.isNaN(end)) throw new Error(`Invalid cron field: ${field}`);\n\t\tif (start < min || end > max)\n\t\t\tthrow new Error(`Cron field out of range: ${field} (${min}-${max})`);\n\t\tif (start > end) throw new Error(`Invalid cron range: ${start}-${end} in ${field}`);\n\t\tfor (let i = start; i <= end; i += step) result.add(i);\n\t}\n\treturn result;\n}\n\n/**\n * Parses a standard 5-field cron expression into a {@link CronSchedule}.\n *\n * Supports `*`, ranges (`1-5`), steps (`*\\/5`, `0-30/10`), and comma-separated\n * lists. Fields are: minute (0–59), hour (0–23), day-of-month (1–31),\n * month (1–12), day-of-week (0–6, Sunday = 0).\n *\n * @param expr - Five-field whitespace-separated cron string (e.g. `\"0 9 * * 1-5\"`).\n * @returns Parsed {@link CronSchedule} with one `Set<number>` per field.\n * @throws Error when the expression does not have exactly 5 fields, contains\n * out-of-range values, or uses an invalid step.\n *\n * @example\n * ```ts\n * import { parseCron } from \"@graphrefly/graphrefly-ts\";\n *\n * const sched = parseCron(\"0 9 * * 1-5\"); // weekdays at 09:00\n * sched.hours; // Set { 9 }\n * sched.daysOfWeek; // Set { 1, 2, 3, 4, 5 }\n * ```\n */\nexport function parseCron(expr: string): CronSchedule {\n\tconst parts = expr.trim().split(/\\s+/);\n\tif (parts.length !== 5) throw new Error(`Invalid cron: expected 5 fields, got ${parts.length}`);\n\treturn {\n\t\tminutes: parseField(parts[0], 0, 59),\n\t\thours: parseField(parts[1], 0, 23),\n\t\tdaysOfMonth: parseField(parts[2], 1, 31),\n\t\tmonths: parseField(parts[3], 1, 12),\n\t\tdaysOfWeek: parseField(parts[4], 0, 6),\n\t};\n}\n\n/**\n * Returns `true` if `date` satisfies every field of `schedule`.\n *\n * @param schedule - Parsed schedule from {@link parseCron}.\n * @param date - Moment to test (local time via `getMinutes`, `getHours`, etc.).\n * @returns `true` when all five cron fields match the given date.\n *\n * @example\n * ```ts\n * import { parseCron, matchesCron } from \"@graphrefly/graphrefly-ts\";\n *\n * const sched = parseCron(\"30 8 * * 1\"); // Mondays at 08:30\n * const monday = new Date(\"2026-03-30T08:30:00\"); // a Monday\n * matchesCron(sched, monday); // true\n * ```\n */\nexport function matchesCron(schedule: CronSchedule, date: Date): boolean {\n\treturn (\n\t\tschedule.minutes.has(date.getMinutes()) &&\n\t\tschedule.hours.has(date.getHours()) &&\n\t\tschedule.daysOfMonth.has(date.getDate()) &&\n\t\tschedule.months.has(date.getMonth() + 1) &&\n\t\tschedule.daysOfWeek.has(date.getDay())\n\t);\n}\n\n/**\n * Polls on an interval; when the current minute matches a 5-field cron expression, emits once (see {@link parseCron}).\n *\n * @param expr - Cron string (`min hour dom month dow`).\n * @param opts - Producer options plus `tickMs` (default `60_000`) and `output` (`timestamp_ns` default, or `date` for `Date` values).\n * @returns `Node<number>` (nanosecond timestamp) or `Node<Date>` when `output: \"date\"`.\n *\n * @example\n * ```ts\n * import { fromCron } from \"@graphrefly/graphrefly\";\n *\n * fromCron(\"0 9 * * 1\");\n * ```\n *\n * @category extra\n */\nexport function fromCron(expr: string, opts?: FromCronOptions & { output: \"date\" }): Node<Date>;\nexport function fromCron(expr: string, opts?: FromCronOptions): Node<number>;\nexport function fromCron(expr: string, opts?: FromCronOptions): Node<number | Date> {\n\tconst schedule: CronSchedule = parseCron(expr);\n\tconst { tickMs: tickOpt, output, ...rest } = opts ?? {};\n\tconst tickMs = tickOpt ?? 60_000;\n\tconst emitDate = output === \"date\";\n\treturn node<number | Date>(\n\t\t(_data, a) => {\n\t\t\tlet lastFiredKey = -1;\n\t\t\tconst check = () => {\n\t\t\t\tconst now = new Date();\n\t\t\t\tconst key =\n\t\t\t\t\tnow.getFullYear() * 100_000_000 +\n\t\t\t\t\t(now.getMonth() + 1) * 1_000_000 +\n\t\t\t\t\tnow.getDate() * 10_000 +\n\t\t\t\t\tnow.getHours() * 100 +\n\t\t\t\t\tnow.getMinutes();\n\t\t\t\tif (key !== lastFiredKey && matchesCron(schedule, now)) {\n\t\t\t\t\tlastFiredKey = key;\n\t\t\t\t\ta.emit(emitDate ? now : wallClockNs());\n\t\t\t\t}\n\t\t\t};\n\t\t\tcheck();\n\t\t\tconst id = setInterval(check, tickMs);\n\t\t\treturn () => clearInterval(id);\n\t\t},\n\t\t{ ...sourceOpts(rest), name: rest.name ?? `cron:${expr}` },\n\t);\n}\n"],"mappings":";AASA,SAAsC,MAAM,mBAAmB;AAI/D,SAAS,WAAwB,MAAkC;AAClE,SAAO,EAAE,cAAc,YAAY,GAAG,KAAK;AAC5C;AAsBA,SAAS,WAAW,OAAe,KAAa,KAA0B;AACzE,QAAM,SAAS,oBAAI,IAAY;AAC/B,aAAW,QAAQ,MAAM,MAAM,GAAG,GAAG;AACpC,UAAM,CAAC,OAAO,OAAO,IAAI,KAAK,MAAM,GAAG;AACvC,UAAM,OAAO,UAAU,OAAO,SAAS,SAAS,EAAE,IAAI;AACtD,QAAI,OAAO,MAAM,IAAI,KAAK,OAAO,EAAG,OAAM,IAAI,MAAM,sBAAsB,IAAI,EAAE;AAChF,QAAI;AACJ,QAAI;AACJ,QAAI,UAAU,KAAK;AAClB,cAAQ;AACR,YAAM;AAAA,IACP,WAAW,MAAM,SAAS,GAAG,GAAG;AAC/B,YAAM,CAAC,GAAG,CAAC,IAAI,MAAM,MAAM,GAAG;AAC9B,cAAQ,OAAO,SAAS,GAAG,EAAE;AAC7B,YAAM,OAAO,SAAS,GAAG,EAAE;AAAA,IAC5B,OAAO;AACN,cAAQ,OAAO,SAAS,OAAO,EAAE;AACjC,YAAM;AAAA,IACP;AACA,QAAI,OAAO,MAAM,KAAK,KAAK,OAAO,MAAM,GAAG,EAAG,OAAM,IAAI,MAAM,uBAAuB,KAAK,EAAE;AAC5F,QAAI,QAAQ,OAAO,MAAM;AACxB,YAAM,IAAI,MAAM,4BAA4B,KAAK,KAAK,GAAG,IAAI,GAAG,GAAG;AACpE,QAAI,QAAQ,IAAK,OAAM,IAAI,MAAM,uBAAuB,KAAK,IAAI,GAAG,OAAO,KAAK,EAAE;AAClF,aAAS,IAAI,OAAO,KAAK,KAAK,KAAK,KAAM,QAAO,IAAI,CAAC;AAAA,EACtD;AACA,SAAO;AACR;AAuBO,SAAS,UAAU,MAA4B;AACrD,QAAM,QAAQ,KAAK,KAAK,EAAE,MAAM,KAAK;AACrC,MAAI,MAAM,WAAW,EAAG,OAAM,IAAI,MAAM,wCAAwC,MAAM,MAAM,EAAE;AAC9F,SAAO;AAAA,IACN,SAAS,WAAW,MAAM,CAAC,GAAG,GAAG,EAAE;AAAA,IACnC,OAAO,WAAW,MAAM,CAAC,GAAG,GAAG,EAAE;AAAA,IACjC,aAAa,WAAW,MAAM,CAAC,GAAG,GAAG,EAAE;AAAA,IACvC,QAAQ,WAAW,MAAM,CAAC,GAAG,GAAG,EAAE;AAAA,IAClC,YAAY,WAAW,MAAM,CAAC,GAAG,GAAG,CAAC;AAAA,EACtC;AACD;AAkBO,SAAS,YAAY,UAAwB,MAAqB;AACxE,SACC,SAAS,QAAQ,IAAI,KAAK,WAAW,CAAC,KACtC,SAAS,MAAM,IAAI,KAAK,SAAS,CAAC,KAClC,SAAS,YAAY,IAAI,KAAK,QAAQ,CAAC,KACvC,SAAS,OAAO,IAAI,KAAK,SAAS,IAAI,CAAC,KACvC,SAAS,WAAW,IAAI,KAAK,OAAO,CAAC;AAEvC;AAoBO,SAAS,SAAS,MAAc,MAA6C;AACnF,QAAM,WAAyB,UAAU,IAAI;AAC7C,QAAM,EAAE,QAAQ,SAAS,QAAQ,GAAG,KAAK,IAAI,QAAQ,CAAC;AACtD,QAAM,SAAS,WAAW;AAC1B,QAAM,WAAW,WAAW;AAC5B,SAAO;AAAA,IACN,CAAC,OAAO,MAAM;AACb,UAAI,eAAe;AACnB,YAAM,QAAQ,MAAM;AACnB,cAAM,MAAM,oBAAI,KAAK;AACrB,cAAM,MACL,IAAI,YAAY,IAAI,OACnB,IAAI,SAAS,IAAI,KAAK,MACvB,IAAI,QAAQ,IAAI,MAChB,IAAI,SAAS,IAAI,MACjB,IAAI,WAAW;AAChB,YAAI,QAAQ,gBAAgB,YAAY,UAAU,GAAG,GAAG;AACvD,yBAAe;AACf,YAAE,KAAK,WAAW,MAAM,YAAY,CAAC;AAAA,QACtC;AAAA,MACD;AACA,YAAM;AACN,YAAM,KAAK,YAAY,OAAO,MAAM;AACpC,aAAO,MAAM,cAAc,EAAE;AAAA,IAC9B;AAAA,IACA,EAAE,GAAG,WAAW,IAAI,GAAG,MAAM,KAAK,QAAQ,QAAQ,IAAI,GAAG;AAAA,EAC1D;AACD;","names":[]}
1
+ {"version":3,"sources":["../src/base/sources/event/cron.ts"],"sourcesContent":["/**\n * Cron-based reactive sources and schedule types.\n *\n * Merged from extra/cron.ts + extra/sources/event.ts (fromCron) during cleave A2.\n * `fromCron` relocated here from `dom.ts` (post-cleave /qa A1): it uses zero DOM\n * APIs (only `setInterval` + `new Date()`), so it belongs on the universal\n * `event/index.ts` barrel, not the browser-only `dom.ts` subpath.\n */\n\nimport { type Node, type NodeOptions, node, wallClockNs } from \"@graphrefly/pure-ts/core\";\n\ntype ExtraOpts = Omit<NodeOptions, \"describeKind\">;\n\nfunction sourceOpts<T = unknown>(opts?: ExtraOpts): NodeOptions<T> {\n\treturn { describeKind: \"producer\", ...opts } as NodeOptions<T>;\n}\n\n/** Options for {@link fromCron}. */\nexport type FromCronOptions = ExtraOpts & {\n\t/** Polling interval in ms. Default `60_000`. */\n\ttickMs?: number;\n\t/** Output format: `\"timestamp_ns\"` (default) emits wall-clock nanoseconds; `\"date\"` emits a `Date` object. */\n\toutput?: \"timestamp_ns\" | \"date\";\n};\n\n/**\n * Minimal 5-field cron parser and matcher (minute hour day-of-month month day-of-week).\n * Ported from callbag-recharge `extra/cron.ts` for `fromCron` (roadmap §2.3).\n */\nexport interface CronSchedule {\n\tminutes: Set<number>;\n\thours: Set<number>;\n\tdaysOfMonth: Set<number>;\n\tmonths: Set<number>;\n\tdaysOfWeek: Set<number>;\n}\n\nfunction parseField(field: string, min: number, max: number): Set<number> {\n\tconst result = new Set<number>();\n\tfor (const part of field.split(\",\")) {\n\t\tconst [range, stepStr] = part.split(\"/\");\n\t\tconst step = stepStr ? Number.parseInt(stepStr, 10) : 1;\n\t\tif (Number.isNaN(step) || step < 1) throw new Error(`Invalid cron step: ${part}`);\n\t\tlet start: number;\n\t\tlet end: number;\n\t\tif (range === \"*\") {\n\t\t\tstart = min;\n\t\t\tend = max;\n\t\t} else if (range.includes(\"-\")) {\n\t\t\tconst [a, b] = range.split(\"-\");\n\t\t\tstart = Number.parseInt(a, 10);\n\t\t\tend = Number.parseInt(b, 10);\n\t\t} else {\n\t\t\tstart = Number.parseInt(range, 10);\n\t\t\tend = start;\n\t\t}\n\t\tif (Number.isNaN(start) || Number.isNaN(end)) throw new Error(`Invalid cron field: ${field}`);\n\t\tif (start < min || end > max)\n\t\t\tthrow new Error(`Cron field out of range: ${field} (${min}-${max})`);\n\t\tif (start > end) throw new Error(`Invalid cron range: ${start}-${end} in ${field}`);\n\t\tfor (let i = start; i <= end; i += step) result.add(i);\n\t}\n\treturn result;\n}\n\n/**\n * Parses a standard 5-field cron expression into a {@link CronSchedule}.\n *\n * Supports `*`, ranges (`1-5`), steps (`*\\/5`, `0-30/10`), and comma-separated\n * lists. Fields are: minute (0–59), hour (0–23), day-of-month (1–31),\n * month (1–12), day-of-week (0–6, Sunday = 0).\n *\n * @param expr - Five-field whitespace-separated cron string (e.g. `\"0 9 * * 1-5\"`).\n * @returns Parsed {@link CronSchedule} with one `Set<number>` per field.\n * @throws Error when the expression does not have exactly 5 fields, contains\n * out-of-range values, or uses an invalid step.\n *\n * @example\n * ```ts\n * import { parseCron } from \"@graphrefly/graphrefly-ts\";\n *\n * const sched = parseCron(\"0 9 * * 1-5\"); // weekdays at 09:00\n * sched.hours; // Set { 9 }\n * sched.daysOfWeek; // Set { 1, 2, 3, 4, 5 }\n * ```\n */\nexport function parseCron(expr: string): CronSchedule {\n\tconst parts = expr.trim().split(/\\s+/);\n\tif (parts.length !== 5) throw new Error(`Invalid cron: expected 5 fields, got ${parts.length}`);\n\treturn {\n\t\tminutes: parseField(parts[0], 0, 59),\n\t\thours: parseField(parts[1], 0, 23),\n\t\tdaysOfMonth: parseField(parts[2], 1, 31),\n\t\tmonths: parseField(parts[3], 1, 12),\n\t\tdaysOfWeek: parseField(parts[4], 0, 6),\n\t};\n}\n\n/**\n * Returns `true` if `date` satisfies every field of `schedule`.\n *\n * @param schedule - Parsed schedule from {@link parseCron}.\n * @param date - Moment to test (local time via `getMinutes`, `getHours`, etc.).\n * @returns `true` when all five cron fields match the given date.\n *\n * @example\n * ```ts\n * import { parseCron, matchesCron } from \"@graphrefly/graphrefly-ts\";\n *\n * const sched = parseCron(\"30 8 * * 1\"); // Mondays at 08:30\n * const monday = new Date(\"2026-03-30T08:30:00\"); // a Monday\n * matchesCron(sched, monday); // true\n * ```\n */\nexport function matchesCron(schedule: CronSchedule, date: Date): boolean {\n\treturn (\n\t\tschedule.minutes.has(date.getMinutes()) &&\n\t\tschedule.hours.has(date.getHours()) &&\n\t\tschedule.daysOfMonth.has(date.getDate()) &&\n\t\tschedule.months.has(date.getMonth() + 1) &&\n\t\tschedule.daysOfWeek.has(date.getDay())\n\t);\n}\n\n/**\n * Polls on an interval; when the current minute matches a 5-field cron expression, emits once (see {@link parseCron}).\n *\n * @param expr - Cron string (`min hour dom month dow`).\n * @param opts - Producer options plus `tickMs` (default `60_000`) and `output` (`timestamp_ns` default, or `date` for `Date` values).\n * @returns `Node<number>` (nanosecond timestamp) or `Node<Date>` when `output: \"date\"`.\n *\n * @example\n * ```ts\n * import { fromCron } from \"@graphrefly/graphrefly\";\n *\n * fromCron(\"0 9 * * 1\");\n * ```\n *\n * @category extra\n */\nexport function fromCron(expr: string, opts?: FromCronOptions & { output: \"date\" }): Node<Date>;\nexport function fromCron(expr: string, opts?: FromCronOptions): Node<number>;\nexport function fromCron(expr: string, opts?: FromCronOptions): Node<number | Date> {\n\tconst schedule: CronSchedule = parseCron(expr);\n\tconst { tickMs: tickOpt, output, ...rest } = opts ?? {};\n\tconst tickMs = tickOpt ?? 60_000;\n\tconst emitDate = output === \"date\";\n\treturn node<number | Date>(\n\t\t(_data, a) => {\n\t\t\tlet lastFiredKey = -1;\n\t\t\tconst check = () => {\n\t\t\t\tconst now = new Date();\n\t\t\t\tconst key =\n\t\t\t\t\tnow.getFullYear() * 100_000_000 +\n\t\t\t\t\t(now.getMonth() + 1) * 1_000_000 +\n\t\t\t\t\tnow.getDate() * 10_000 +\n\t\t\t\t\tnow.getHours() * 100 +\n\t\t\t\t\tnow.getMinutes();\n\t\t\t\tif (key !== lastFiredKey && matchesCron(schedule, now)) {\n\t\t\t\t\tlastFiredKey = key;\n\t\t\t\t\ta.emit(emitDate ? now : wallClockNs());\n\t\t\t\t}\n\t\t\t};\n\t\t\tcheck();\n\t\t\tconst id = setInterval(check, tickMs);\n\t\t\treturn { onDeactivation: () => clearInterval(id) };\n\t\t},\n\t\t{ ...sourceOpts(rest), name: rest.name ?? `cron:${expr}` },\n\t);\n}\n"],"mappings":"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","names":[]}
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  TopicGraph
3
- } from "./chunk-Q3EYOCZB.js";
3
+ } from "./chunk-NPRP3MCV.js";
4
4
  import {
5
5
  domainMeta
6
6
  } from "./chunk-FMPF42Q4.js";
@@ -451,4 +451,4 @@ export {
451
451
  policyGate,
452
452
  complianceSnapshot
453
453
  };
454
- //# sourceMappingURL=chunk-FW23JYNQ.js.map
454
+ //# sourceMappingURL=chunk-CEVNQ74M.js.map