@remnic/core 9.69.44 → 9.69.46

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 (533) hide show
  1. package/dist/access-admin-ops-surface.d.ts +14 -14
  2. package/dist/access-admin-ops-surface.js +36 -36
  3. package/dist/access-audit.js +2 -2
  4. package/dist/access-authorization-probe.d.ts +14 -14
  5. package/dist/access-authorization-probe.js +37 -37
  6. package/dist/access-boundary.d.ts +14 -14
  7. package/dist/access-boundary.js +36 -36
  8. package/dist/access-cli.js +90 -87
  9. package/dist/access-cli.js.map +1 -1
  10. package/dist/access-coding-context-resolution.d.ts +1 -1
  11. package/dist/access-extraction-force-flush.d.ts +14 -14
  12. package/dist/access-extraction-force-flush.js +36 -36
  13. package/dist/access-health-types.d.ts +1 -1
  14. package/dist/access-http-briefing.d.ts +14 -14
  15. package/dist/access-http-briefing.js +37 -37
  16. package/dist/access-http-lcm-compaction.d.ts +14 -14
  17. package/dist/access-http-lifecycle-flush.d.ts +14 -14
  18. package/dist/access-http-offline-stream.d.ts +14 -14
  19. package/dist/access-http-query.d.ts +2 -2
  20. package/dist/access-http-query.js +38 -38
  21. package/dist/access-http.d.ts +15 -15
  22. package/dist/access-http.js +52 -52
  23. package/dist/access-identity-continuity-surface.d.ts +11 -11
  24. package/dist/access-identity-continuity-surface.js +36 -36
  25. package/dist/access-lcm-surface.d.ts +14 -14
  26. package/dist/access-lcm-surface.js +36 -36
  27. package/dist/access-mcp-shared-context-tools.d.ts +14 -14
  28. package/dist/access-mcp.d.ts +14 -14
  29. package/dist/access-mcp.js +44 -44
  30. package/dist/access-memory-search-fanout.d.ts +2 -2
  31. package/dist/{access-namespace-preflight-CE-RrXKu.d.ts → access-namespace-preflight-DDnsN22V.d.ts} +1 -1
  32. package/dist/access-namespace-preflight.d.ts +3 -3
  33. package/dist/access-namespace-preflight.js +36 -36
  34. package/dist/access-observe-transcript.d.ts +11 -11
  35. package/dist/access-observe-write-category.d.ts +3 -3
  36. package/dist/access-observe-write-category.js +4 -4
  37. package/dist/access-observe-write-surface.d.ts +14 -14
  38. package/dist/access-observe-write-surface.js +36 -36
  39. package/dist/access-offline-manifest.d.ts +11 -11
  40. package/dist/access-offline-manifest.js +3 -3
  41. package/dist/access-operations-batch.js +37 -37
  42. package/dist/access-operations.d.ts +14 -14
  43. package/dist/access-operations.js +43 -43
  44. package/dist/access-recall-concurrency.d.ts +14 -14
  45. package/dist/access-recall-concurrency.js +36 -36
  46. package/dist/access-recall-response.d.ts +14 -14
  47. package/dist/access-recall-response.js +36 -36
  48. package/dist/access-recall-surface.d.ts +14 -14
  49. package/dist/access-recall-surface.js +36 -36
  50. package/dist/access-schema.d.ts +114 -114
  51. package/dist/access-schema.js +3 -3
  52. package/dist/{access-service-Cww3xAAy.d.ts → access-service-DGynYlbE.d.ts} +73 -73
  53. package/dist/access-service-helpers.d.ts +14 -14
  54. package/dist/access-service.d.ts +14 -14
  55. package/dist/access-service.js +36 -36
  56. package/dist/access-surface-catalog.d.ts +14 -14
  57. package/dist/access-surface-catalog.js +38 -38
  58. package/dist/access-wearables-meetings-surface.d.ts +4 -4
  59. package/dist/action-confidence.d.ts +1 -1
  60. package/dist/active-memory-bridge.d.ts +1 -1
  61. package/dist/active-recall.d.ts +1 -1
  62. package/dist/activity/digest.d.ts +1 -1
  63. package/dist/ambient-provenance.d.ts +1 -1
  64. package/dist/artifact-search.d.ts +1 -1
  65. package/dist/{auto-sync-QZD4RD6H.js → auto-sync-DG5KEOF5.js} +5 -5
  66. package/dist/background-generation.d.ts +1 -1
  67. package/dist/behavior-learner.d.ts +1 -1
  68. package/dist/behavior-signals.d.ts +1 -1
  69. package/dist/bootstrap.d.ts +11 -11
  70. package/dist/briefing-window.d.ts +2 -2
  71. package/dist/briefing.d.ts +2 -2
  72. package/dist/briefing.js +12 -12
  73. package/dist/buffer-surprise-report.d.ts +1 -1
  74. package/dist/buffer-turn-helpers.d.ts +1 -1
  75. package/dist/buffer.d.ts +2 -2
  76. package/dist/buffer.js +3 -3
  77. package/dist/bulk-import/index.d.ts +3 -3
  78. package/dist/calibration.d.ts +1 -1
  79. package/dist/calibration.js +2 -2
  80. package/dist/capabilities.d.ts +1 -1
  81. package/dist/{catalog-Ya3DDn_T.d.ts → catalog-Dl2hYqpS.d.ts} +1 -1
  82. package/dist/causal-behavior.d.ts +1 -1
  83. package/dist/causal-consolidation.d.ts +1 -1
  84. package/dist/causal-consolidation.js +13 -13
  85. package/dist/causal-trajectory-graph.d.ts +1 -1
  86. package/dist/{chunk-MOL7JK4F.js → chunk-3NYM6JA4.js} +2 -2
  87. package/dist/{chunk-WNMVUWI7.js → chunk-3UQIS2GC.js} +2 -2
  88. package/dist/{chunk-JDL5BYYF.js → chunk-47C6DF43.js} +3 -3
  89. package/dist/{chunk-ZBSP4BSN.js → chunk-4ECS33D4.js} +2 -2
  90. package/dist/{chunk-6EGIQEKC.js → chunk-4HTQ76O4.js} +2 -2
  91. package/dist/{chunk-7L54ARML.js → chunk-4XTSKGLX.js} +2 -2
  92. package/dist/{chunk-SUS64JER.js → chunk-5EHPB6RL.js} +2 -2
  93. package/dist/{chunk-J2MGTGKC.js → chunk-5ZXGH2ZL.js} +20 -3
  94. package/dist/chunk-5ZXGH2ZL.js.map +1 -0
  95. package/dist/{chunk-VDLNQVYW.js → chunk-62NYUFNH.js} +7 -7
  96. package/dist/{chunk-Y77UTH7F.js → chunk-66QOUYNS.js} +83 -64
  97. package/dist/chunk-66QOUYNS.js.map +1 -0
  98. package/dist/{chunk-SZV2L2IG.js → chunk-7BNB7KHB.js} +4 -4
  99. package/dist/chunk-AAQA6ZUP.js +41 -0
  100. package/dist/chunk-AAQA6ZUP.js.map +1 -0
  101. package/dist/{chunk-RJ7Z7YXK.js → chunk-AG6PANBG.js} +14 -20
  102. package/dist/chunk-AG6PANBG.js.map +1 -0
  103. package/dist/{chunk-IAGEEVQL.js → chunk-AN2O4KZY.js} +2 -2
  104. package/dist/{chunk-JUOJGZPV.js → chunk-AYP2FZFA.js} +2 -2
  105. package/dist/{chunk-XK76RJBN.js → chunk-AYZHHMTK.js} +2 -2
  106. package/dist/{chunk-LQVXSRAJ.js → chunk-B4EGAHHS.js} +2 -2
  107. package/dist/{chunk-HKQAI6XI.js → chunk-CFIHVWYZ.js} +2 -2
  108. package/dist/{chunk-DABSYX7V.js → chunk-CWMMKFO7.js} +3 -3
  109. package/dist/{chunk-WLHPCAVD.js → chunk-D5DTZBTV.js} +6 -6
  110. package/dist/{chunk-2HDLDBJW.js → chunk-DDBLZJRV.js} +2 -2
  111. package/dist/{chunk-F2FJTOV6.js → chunk-DO6R3LIE.js} +2 -2
  112. package/dist/{chunk-SHEHQBCG.js → chunk-DUJYRVUD.js} +638 -45
  113. package/dist/chunk-DUJYRVUD.js.map +1 -0
  114. package/dist/{chunk-LD22AAJI.js → chunk-EAZ3YZKN.js} +2 -2
  115. package/dist/{chunk-46FVFU7S.js → chunk-EC5SVM6H.js} +2 -2
  116. package/dist/{chunk-GKKLI23D.js → chunk-EEHQY7ER.js} +2 -2
  117. package/dist/{chunk-UHSOPTNB.js → chunk-EP6BA7VV.js} +50 -15
  118. package/dist/chunk-EP6BA7VV.js.map +1 -0
  119. package/dist/{chunk-BPXJINHW.js → chunk-EUP4AXIW.js} +2 -2
  120. package/dist/{chunk-2WMXZYMS.js → chunk-F5V7KRQS.js} +2 -2
  121. package/dist/{chunk-4AX5OYKC.js → chunk-FDDCZDBI.js} +2 -2
  122. package/dist/{chunk-XLPZN3A5.js → chunk-FMEP2RB5.js} +2 -2
  123. package/dist/{chunk-D7UUB33A.js → chunk-FSH6KRKO.js} +2 -2
  124. package/dist/{chunk-3254BYQV.js → chunk-GGY7WKMB.js} +1 -1
  125. package/dist/{chunk-ROXDOKLM.js → chunk-GYYLZOSO.js} +4 -4
  126. package/dist/{chunk-3K64PLY3.js → chunk-HXDXR2FH.js} +23 -23
  127. package/dist/{chunk-XOCGC3FJ.js → chunk-HYWPUYLO.js} +2 -2
  128. package/dist/{chunk-KQAFEZQX.js → chunk-IPLYGWQF.js} +5 -5
  129. package/dist/{chunk-M6FKXOYV.js → chunk-IXMWTNNY.js} +2 -2
  130. package/dist/{chunk-ZYSOT6HD.js → chunk-J3IWHCMF.js} +2 -2
  131. package/dist/{chunk-3YQ7VLP3.js → chunk-J5OXWG66.js} +2 -2
  132. package/dist/{chunk-H2N6SLTR.js → chunk-JIWZVOQM.js} +2 -2
  133. package/dist/{chunk-C3YEB7CR.js → chunk-JOP7W24J.js} +4 -4
  134. package/dist/{chunk-F6PTJLEM.js → chunk-K4KE5MYE.js} +1 -1
  135. package/dist/{chunk-IOTYIECA.js → chunk-KF57IQFX.js} +13 -13
  136. package/dist/{chunk-JL4TQYM5.js → chunk-L272A5QW.js} +33 -2
  137. package/dist/{chunk-JL4TQYM5.js.map → chunk-L272A5QW.js.map} +1 -1
  138. package/dist/chunk-M4DB6ZNN.js +87 -0
  139. package/dist/chunk-M4DB6ZNN.js.map +1 -0
  140. package/dist/{chunk-ZVQ5HF77.js → chunk-N46SNYMC.js} +2 -2
  141. package/dist/{chunk-YR5QT4SR.js → chunk-NAFPHFDM.js} +2 -2
  142. package/dist/{chunk-VHANUT45.js → chunk-ORQ7AZMU.js} +7 -7
  143. package/dist/{chunk-EOXCOZ6L.js → chunk-OV7GJ4KW.js} +2 -2
  144. package/dist/{chunk-VJXMFBVR.js → chunk-P7S7BVBJ.js} +2 -2
  145. package/dist/{chunk-GMU7MAHS.js → chunk-QUFJ3NJE.js} +260 -796
  146. package/dist/chunk-QUFJ3NJE.js.map +1 -0
  147. package/dist/{chunk-7F47R4H4.js → chunk-S3SND7SO.js} +2 -2
  148. package/dist/{chunk-5AJZ7EUW.js → chunk-S7KDHFF2.js} +2 -2
  149. package/dist/{chunk-4WMGRLTV.js → chunk-SRRZZHA5.js} +2 -2
  150. package/dist/{chunk-KDLOKVX7.js → chunk-SW42MMAH.js} +2 -2
  151. package/dist/{chunk-2YIUUXNK.js → chunk-T5NI5K46.js} +2 -2
  152. package/dist/{chunk-QCTXQ2HW.js → chunk-TE6B2UU6.js} +2 -2
  153. package/dist/{chunk-JDOBQPJY.js → chunk-TNQR7N52.js} +2 -2
  154. package/dist/{chunk-7K5Q6COX.js → chunk-TVVEYCNW.js} +4 -4
  155. package/dist/{chunk-T3F2HWIG.js → chunk-TZ44E732.js} +2 -2
  156. package/dist/{chunk-PF7LQ22G.js → chunk-U4ESGVZ2.js} +3 -3
  157. package/dist/{chunk-WJPR2XUN.js → chunk-UEJY6ZTQ.js} +2 -2
  158. package/dist/{chunk-K3IVJ5ML.js → chunk-UGGOQKO6.js} +26 -26
  159. package/dist/{chunk-OCX7OWOC.js → chunk-UOREPQ3C.js} +2 -2
  160. package/dist/{chunk-R3H2AXO2.js → chunk-VGRVJ6Z6.js} +2 -2
  161. package/dist/{chunk-IWHNT35B.js → chunk-WOBMSMUK.js} +3 -3
  162. package/dist/{chunk-DDGQDNQW.js → chunk-XLZZLUWL.js} +2 -2
  163. package/dist/{chunk-KORQWGUA.js → chunk-Y3XHIWVP.js} +2 -2
  164. package/dist/{chunk-BZDK5EOD.js → chunk-YFVIA5A6.js} +2 -2
  165. package/dist/{chunk-GR76OKET.js → chunk-YSBYNCCN.js} +2 -2
  166. package/dist/{chunk-OGAKUA7I.js → chunk-YUQPUK6H.js} +2 -2
  167. package/dist/{chunk-TRTLWUDD.js → chunk-ZTHGFTMW.js} +2 -2
  168. package/dist/{cli-Cl7bKIWJ.d.ts → cli-Cik3H7q1.d.ts} +5 -5
  169. package/dist/cli.d.ts +16 -16
  170. package/dist/cli.js +67 -67
  171. package/dist/coding/export-okf-codegraph.d.ts +1 -1
  172. package/dist/coding/export-okf-codegraph.js +11 -11
  173. package/dist/coding/pre-action-gate.d.ts +1 -1
  174. package/dist/compounding/engine.d.ts +2 -2
  175. package/dist/compounding/engine.js +12 -12
  176. package/dist/compounding/preference-consolidator.d.ts +1 -1
  177. package/dist/compression-optimizer.d.ts +1 -1
  178. package/dist/config.d.ts +1 -1
  179. package/dist/connectors/codex-materialize-runner.d.ts +1 -1
  180. package/dist/connectors/codex-materialize-runner.js +12 -12
  181. package/dist/connectors/codex-materialize.d.ts +1 -1
  182. package/dist/connectors/index.d.ts +1 -1
  183. package/dist/connectors/index.js +12 -12
  184. package/dist/consolidation-provenance-check.d.ts +2 -2
  185. package/dist/consolidation-undo.d.ts +2 -2
  186. package/dist/contradiction/index.d.ts +4 -4
  187. package/dist/converge-config.d.ts +1 -1
  188. package/dist/convergence-refresh.d.ts +2 -2
  189. package/dist/convergence-refresh.js +14 -14
  190. package/dist/conversation-index/backend.d.ts +1 -1
  191. package/dist/conversation-index/chunker.d.ts +1 -1
  192. package/dist/conversation-index/faiss-adapter.d.ts +1 -1
  193. package/dist/conversation-index/indexer.d.ts +1 -1
  194. package/dist/conversation-index/search.d.ts +1 -1
  195. package/dist/corpus-watermark.d.ts +1 -1
  196. package/dist/corpus-watermark.js +14 -14
  197. package/dist/cross-lingual-recall.d.ts +1 -1
  198. package/dist/day-summary.d.ts +1 -1
  199. package/dist/deep-recall-http-glue.d.ts +14 -14
  200. package/dist/deep-recall-http-glue.js +4 -4
  201. package/dist/deep-recall-mcp-tools.d.ts +14 -14
  202. package/dist/deep-recall-policy-llm.d.ts +2 -2
  203. package/dist/deep-recall-policy-llm.js +3 -3
  204. package/dist/deep-recall-seeds.d.ts +1 -1
  205. package/dist/delinearize.d.ts +1 -1
  206. package/dist/dependency-propagation-config.d.ts +1 -1
  207. package/dist/{dependency-propagation-delivery-CggS9BFT.d.ts → dependency-propagation-delivery-BXaUoVUf.d.ts} +1 -1
  208. package/dist/direct-answer-wiring.d.ts +1 -1
  209. package/dist/direct-answer.d.ts +1 -1
  210. package/dist/embedding-fallback.d.ts +1 -1
  211. package/dist/enrichment/index.d.ts +1 -1
  212. package/dist/entity-origin-fields.d.ts +1 -1
  213. package/dist/entity-retrieval.d.ts +2 -2
  214. package/dist/entity-retrieval.js +12 -12
  215. package/dist/entity-schema.d.ts +1 -1
  216. package/dist/episodic-context.d.ts +1 -1
  217. package/dist/eval-trajectory-contracts.d.ts +1 -1
  218. package/dist/eval-trajectory-store.d.ts +1 -1
  219. package/dist/eval-trajectory.d.ts +1 -1
  220. package/dist/explicit-capture.d.ts +11 -11
  221. package/dist/explicit-capture.js +2 -2
  222. package/dist/external-wiki-access.d.ts +14 -14
  223. package/dist/external-wiki-access.js +37 -37
  224. package/dist/external-wiki-collection-registration.d.ts +1 -1
  225. package/dist/external-wiki-collection.d.ts +1 -1
  226. package/dist/external-wiki-mcp-tools.d.ts +14 -14
  227. package/dist/extraction-error-classification.d.ts +1 -1
  228. package/dist/extraction-faithfulness.d.ts +2 -2
  229. package/dist/extraction-faithfulness.js +3 -3
  230. package/dist/extraction-judge-telemetry.d.ts +2 -2
  231. package/dist/extraction-judge-training.d.ts +2 -2
  232. package/dist/extraction-judge.d.ts +2 -2
  233. package/dist/extraction-judge.js +3 -3
  234. package/dist/extraction-liveness.d.ts +1 -1
  235. package/dist/extraction-normalization.d.ts +1 -1
  236. package/dist/extraction-prompt.d.ts +1 -1
  237. package/dist/extraction-source-grounding-rules.d.ts +1 -1
  238. package/dist/extraction-source-grounding.d.ts +1 -1
  239. package/dist/extraction-span-config.d.ts +1 -1
  240. package/dist/extraction-span-materialize.d.ts +1 -1
  241. package/dist/extraction-span-wire.d.ts +23 -4
  242. package/dist/extraction-span-wire.js +7 -8
  243. package/dist/extraction-span-wire.js.map +1 -1
  244. package/dist/extraction.d.ts +2 -2
  245. package/dist/extraction.js +19 -17
  246. package/dist/fallback-llm-codex-subscription.d.ts +1 -1
  247. package/dist/fallback-llm-codex-subscription.js +1 -1
  248. package/dist/fallback-llm.d.ts +1 -1
  249. package/dist/fallback-llm.js +2 -2
  250. package/dist/{generic-recall-paths-CoXPSnCw.d.ts → generic-recall-paths-BkVVEoeO.d.ts} +1 -1
  251. package/dist/graph-append-error.d.ts +1 -1
  252. package/dist/graph-dashboard-diff.d.ts +1 -1
  253. package/dist/graph-dashboard-key.d.ts +1 -1
  254. package/dist/graph-dashboard-parser.d.ts +1 -1
  255. package/dist/graph-edge-reinforcement.d.ts +1 -1
  256. package/dist/graph-jsonl.d.ts +1 -1
  257. package/dist/graph-path-reconstruction.d.ts +1 -1
  258. package/dist/graph-path-scoring.d.ts +1 -1
  259. package/dist/graph-snapshot.d.ts +1 -1
  260. package/dist/graph.d.ts +1 -1
  261. package/dist/harmonic-retrieval.js +1 -1
  262. package/dist/importance.d.ts +1 -1
  263. package/dist/importers/index.d.ts +1 -1
  264. package/dist/importers/test-utils.d.ts +1 -1
  265. package/dist/in-flight-reads.d.ts +1 -1
  266. package/dist/{index-fdw_oK5a.d.ts → index-Zb6SUV4R.d.ts} +3 -3
  267. package/dist/index.d.ts +703 -703
  268. package/dist/index.js +90 -87
  269. package/dist/intent.d.ts +1 -1
  270. package/dist/lcm/engine.d.ts +1 -1
  271. package/dist/lcm/index.d.ts +1 -1
  272. package/dist/lcm/index.js +3 -3
  273. package/dist/lcm/tools.d.ts +1 -1
  274. package/dist/lifecycle.d.ts +1 -1
  275. package/dist/live-connectors-runner.d.ts +1 -1
  276. package/dist/{local-llm-3-jtw52a.d.ts → local-llm-DdrhmecM.d.ts} +1 -1
  277. package/dist/local-llm-helpers.d.ts +2 -2
  278. package/dist/local-llm.d.ts +2 -2
  279. package/dist/local-model-endpoint.d.ts +1 -1
  280. package/dist/location/index.d.ts +5 -5
  281. package/dist/location/index.js +14 -14
  282. package/dist/maintenance/memory-governance.d.ts +1 -1
  283. package/dist/maintenance/memory-governance.js +12 -12
  284. package/dist/maintenance/rebuild-memory-lifecycle-ledger.d.ts +2 -2
  285. package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +12 -12
  286. package/dist/maintenance/rebuild-memory-projection.d.ts +2 -2
  287. package/dist/maintenance/rebuild-memory-projection.js +13 -13
  288. package/dist/{maintenance-DgeeH0a1.d.ts → maintenance-Du_VRvVg.d.ts} +3 -3
  289. package/dist/mcp-memory-inspector-app.d.ts +14 -14
  290. package/dist/memory-action-policy.d.ts +1 -1
  291. package/dist/memory-age.d.ts +100 -0
  292. package/dist/memory-age.js +22 -0
  293. package/dist/memory-age.js.map +1 -0
  294. package/dist/memory-cache.d.ts +1 -1
  295. package/dist/memory-controller.d.ts +1 -1
  296. package/dist/memory-lifecycle-ledger-utils.d.ts +1 -1
  297. package/dist/memory-projection-store.d.ts +1 -1
  298. package/dist/memory-provenance.d.ts +1 -1
  299. package/dist/memory-snapshot.d.ts +1 -1
  300. package/dist/memory-subject-cli.d.ts +14 -14
  301. package/dist/memory-subject.d.ts +14 -14
  302. package/dist/memory-worth-outcomes.d.ts +2 -2
  303. package/dist/models-json.d.ts +1 -1
  304. package/dist/namespaces/migrate.d.ts +3 -3
  305. package/dist/namespaces/migrate.js +13 -13
  306. package/dist/namespaces/principal.d.ts +1 -1
  307. package/dist/namespaces/search.d.ts +1 -1
  308. package/dist/namespaces/search.js +3 -3
  309. package/dist/namespaces/storage.d.ts +3 -3
  310. package/dist/namespaces/storage.js +12 -12
  311. package/dist/native-knowledge.d.ts +1 -1
  312. package/dist/offline-sync-impression-drain.d.ts +1 -1
  313. package/dist/operator-doctor-corpus.d.ts +1 -1
  314. package/dist/operator-doctor-corpus.js +15 -15
  315. package/dist/operator-doctor-replica.d.ts +1 -1
  316. package/dist/operator-toolkit.d.ts +3 -3
  317. package/dist/operator-toolkit.js +20 -20
  318. package/dist/orchestration/compression-guideline-coordinator.d.ts +2 -2
  319. package/dist/orchestration/maintenance.d.ts +4 -4
  320. package/dist/orchestration/maintenance.js +20 -20
  321. package/dist/{orchestrator-VXWKdatm.d.ts → orchestrator-D9U3Zgjp.d.ts} +10 -10
  322. package/dist/orchestrator.d.ts +11 -11
  323. package/dist/orchestrator.js +90 -87
  324. package/dist/patterns-cli.d.ts +1 -1
  325. package/dist/{pipeline-CwXZBKR5.d.ts → pipeline-4lqKJZ_0.d.ts} +1 -1
  326. package/dist/policy-runtime.d.ts +1 -1
  327. package/dist/proactive-contention.d.ts +2 -2
  328. package/dist/provenance.d.ts +1 -1
  329. package/dist/{public-http-DdmM4scJ.d.ts → public-http-AabioFLh.d.ts} +1 -1
  330. package/dist/{qmd-DOLTPIQg.d.ts → qmd-DnoWaXKb.d.ts} +1 -1
  331. package/dist/qmd-preflight.d.ts +2 -2
  332. package/dist/qmd-recall-cache.d.ts +1 -1
  333. package/dist/qmd.d.ts +2 -2
  334. package/dist/recall-concurrency-config.d.ts +1 -1
  335. package/dist/recall-disclosure-escalation.d.ts +1 -1
  336. package/dist/recall-explain-renderer.d.ts +1 -1
  337. package/dist/recall-memory-map.d.ts +1 -1
  338. package/dist/recall-navigate-access.d.ts +1 -1
  339. package/dist/recall-navigate-disclosure.d.ts +1 -1
  340. package/dist/recall-navigate.d.ts +1 -1
  341. package/dist/recall-navigation-http-glue.d.ts +14 -14
  342. package/dist/recall-navigation-mcp-tools.d.ts +14 -14
  343. package/dist/recall-navigation-operations.js +37 -37
  344. package/dist/recall-navigation-renderer.d.ts +1 -1
  345. package/dist/recall-navigation.d.ts +1 -1
  346. package/dist/recall-navigation.js +36 -36
  347. package/dist/recall-planner-llm.d.ts +1 -1
  348. package/dist/recall-planner-llm.js +2 -2
  349. package/dist/recall-state-view-admission.d.ts +2 -2
  350. package/dist/recall-state-view-admission.js +3 -3
  351. package/dist/recall-state-view-anchors.d.ts +1 -1
  352. package/dist/recall-state.d.ts +1 -1
  353. package/dist/recall-tag-filter.d.ts +1 -1
  354. package/dist/recall-timings.d.ts +1 -1
  355. package/dist/recall-xray-cli.d.ts +1 -1
  356. package/dist/recall-xray-renderer.d.ts +1 -1
  357. package/dist/recall-xray.d.ts +1 -1
  358. package/dist/reconcile/cursor.d.ts +1 -1
  359. package/dist/reconcile/manifest.d.ts +1 -1
  360. package/dist/reconcile/manifest.js +2 -2
  361. package/dist/reconcile/plan.d.ts +1 -1
  362. package/dist/replay/normalizers/chatgpt.d.ts +1 -1
  363. package/dist/replay/normalizers/claude.d.ts +1 -1
  364. package/dist/replay/normalizers/openclaw.d.ts +1 -1
  365. package/dist/replay/normalizers/shared.d.ts +1 -1
  366. package/dist/replay/runner.d.ts +1 -1
  367. package/dist/replay/types.d.ts +1 -1
  368. package/dist/replica-divergence.d.ts +1 -1
  369. package/dist/replica-peers-config.d.ts +1 -1
  370. package/dist/resolve-auth-token.d.ts +1 -1
  371. package/dist/retrieval-agents.d.ts +2 -2
  372. package/dist/retrieval-tiers.d.ts +1 -1
  373. package/dist/routing/engine.d.ts +1 -1
  374. package/dist/routing/store.d.ts +1 -1
  375. package/dist/salvage-envelope.d.ts +1 -1
  376. package/dist/schemas.d.ts +160 -160
  377. package/dist/{scope-profiles-C564-hrv.d.ts → scope-profiles-crazXegZ.d.ts} +1 -1
  378. package/dist/search/embed-helper.d.ts +1 -1
  379. package/dist/search/factory.d.ts +1 -1
  380. package/dist/search/factory.js +2 -2
  381. package/dist/search/index.d.ts +1 -1
  382. package/dist/search/index.js +4 -4
  383. package/dist/search/lancedb-backend.d.ts +1 -1
  384. package/dist/search/meilisearch-backend.d.ts +1 -1
  385. package/dist/search/noop-backend.d.ts +1 -1
  386. package/dist/search/orama-backend.d.ts +1 -1
  387. package/dist/search/port.d.ts +1 -1
  388. package/dist/search/remote-backend.d.ts +1 -1
  389. package/dist/{semantic-consolidation-g6x-ShY8.d.ts → semantic-consolidation-B09JI9WM.d.ts} +1 -1
  390. package/dist/semantic-consolidation.d.ts +2 -2
  391. package/dist/semantic-consolidation.js +13 -13
  392. package/dist/semantic-rule-promotion.js +12 -12
  393. package/dist/semantic-rule-verifier.d.ts +1 -1
  394. package/dist/semantic-rule-verifier.js +12 -12
  395. package/dist/{service-CaCOk0rd.d.ts → service-ByjbCuj7.d.ts} +2 -2
  396. package/dist/session-observer-bands.d.ts +1 -1
  397. package/dist/session-observer-state.d.ts +1 -1
  398. package/dist/shared-context/manager.d.ts +7 -7
  399. package/dist/signal.d.ts +1 -1
  400. package/dist/source-agent-qualifier.d.ts +1 -1
  401. package/dist/source-agent-qualifier.js +15 -15
  402. package/dist/source-attribution.d.ts +7 -4
  403. package/dist/source-attribution.js +1 -1
  404. package/dist/standup/build.js +12 -12
  405. package/dist/{storage-C9nsZX5Q.d.ts → storage-CbdF077z.d.ts} +17 -3
  406. package/dist/storage.d.ts +2 -2
  407. package/dist/storage.js +11 -11
  408. package/dist/subject-config.d.ts +1 -1
  409. package/dist/summarizer.d.ts +1 -1
  410. package/dist/summarizer.js +3 -3
  411. package/dist/summary-snapshot.d.ts +1 -1
  412. package/dist/support-passport/index.d.ts +17 -17
  413. package/dist/support-passport/index.js +39 -39
  414. package/dist/{tagging-BA4RFdH3.d.ts → tagging-CF7DNV7_.d.ts} +2 -2
  415. package/dist/temporal-supersession.d.ts +2 -2
  416. package/dist/temporal-timeline-recall.d.ts +1 -1
  417. package/dist/temporal-validity.d.ts +1 -1
  418. package/dist/threading.d.ts +1 -1
  419. package/dist/tier-migration.d.ts +2 -2
  420. package/dist/tier-routing.d.ts +1 -1
  421. package/dist/topics.d.ts +1 -1
  422. package/dist/transcript.d.ts +1 -1
  423. package/dist/transfer/autodetect.js +1 -1
  424. package/dist/transfer/backup.js +1 -1
  425. package/dist/transfer/capsule-export.js +2 -2
  426. package/dist/transfer/export-okf.d.ts +1 -1
  427. package/dist/transfer/export-okf.js +12 -12
  428. package/dist/transfer/types.d.ts +78 -78
  429. package/dist/trust-score-stage.d.ts +1 -1
  430. package/dist/trust-score.d.ts +1 -1
  431. package/dist/{types-Sps-lg5I.d.ts → types-nmhnJpxx.d.ts} +1 -1
  432. package/dist/types.d.ts +1 -1
  433. package/dist/utility-runtime.d.ts +1 -1
  434. package/dist/verified-recall.js +12 -12
  435. package/dist/who-knows-cli.d.ts +14 -14
  436. package/dist/who-knows-cli.js +13 -13
  437. package/dist/who-knows.d.ts +14 -14
  438. package/dist/who-knows.js +12 -12
  439. package/dist/write-envelope.d.ts +1 -1
  440. package/package.json +2 -2
  441. package/src/background-generation.test.ts +77 -0
  442. package/src/dedup/merge.test.ts +185 -8
  443. package/src/extraction-span-wire.test.ts +17 -1
  444. package/src/extraction-span-wire.ts +46 -3
  445. package/src/extraction.ts +2 -11
  446. package/src/memory-age.test.ts +106 -0
  447. package/src/memory-age.ts +196 -0
  448. package/src/orchestration/recall-search-pipeline.test.ts +207 -1
  449. package/src/orchestration/recall-search-pipeline.ts +13 -9
  450. package/src/orchestration/semantic-merge-commit-effects.ts +31 -9
  451. package/src/orchestration/semantic-merge-persist.ts +16 -5
  452. package/src/providers/codex-subscription.test.ts +109 -3
  453. package/src/providers/codex-subscription.ts +57 -17
  454. package/src/source-attribution.test.ts +27 -0
  455. package/src/source-attribution.ts +41 -5
  456. package/src/storage/cas-revision-host.ts +22 -0
  457. package/src/storage/cas-revision-store.test.ts +44 -1
  458. package/src/storage/cas-revision-store.ts +23 -0
  459. package/src/storage/tombstone-blocked-capture-sync.ts +26 -1
  460. package/src/storage-frontmatter-cas.test.ts +49 -1
  461. package/src/storage.ts +1 -1
  462. package/src/summarizer.ts +18 -0
  463. package/dist/chunk-GMU7MAHS.js.map +0 -1
  464. package/dist/chunk-J2MGTGKC.js.map +0 -1
  465. package/dist/chunk-RJ7Z7YXK.js.map +0 -1
  466. package/dist/chunk-SHEHQBCG.js.map +0 -1
  467. package/dist/chunk-UHSOPTNB.js.map +0 -1
  468. package/dist/chunk-Y77UTH7F.js.map +0 -1
  469. /package/dist/{auto-sync-QZD4RD6H.js.map → auto-sync-DG5KEOF5.js.map} +0 -0
  470. /package/dist/{chunk-MOL7JK4F.js.map → chunk-3NYM6JA4.js.map} +0 -0
  471. /package/dist/{chunk-WNMVUWI7.js.map → chunk-3UQIS2GC.js.map} +0 -0
  472. /package/dist/{chunk-JDL5BYYF.js.map → chunk-47C6DF43.js.map} +0 -0
  473. /package/dist/{chunk-ZBSP4BSN.js.map → chunk-4ECS33D4.js.map} +0 -0
  474. /package/dist/{chunk-6EGIQEKC.js.map → chunk-4HTQ76O4.js.map} +0 -0
  475. /package/dist/{chunk-7L54ARML.js.map → chunk-4XTSKGLX.js.map} +0 -0
  476. /package/dist/{chunk-SUS64JER.js.map → chunk-5EHPB6RL.js.map} +0 -0
  477. /package/dist/{chunk-VDLNQVYW.js.map → chunk-62NYUFNH.js.map} +0 -0
  478. /package/dist/{chunk-SZV2L2IG.js.map → chunk-7BNB7KHB.js.map} +0 -0
  479. /package/dist/{chunk-IAGEEVQL.js.map → chunk-AN2O4KZY.js.map} +0 -0
  480. /package/dist/{chunk-JUOJGZPV.js.map → chunk-AYP2FZFA.js.map} +0 -0
  481. /package/dist/{chunk-XK76RJBN.js.map → chunk-AYZHHMTK.js.map} +0 -0
  482. /package/dist/{chunk-LQVXSRAJ.js.map → chunk-B4EGAHHS.js.map} +0 -0
  483. /package/dist/{chunk-HKQAI6XI.js.map → chunk-CFIHVWYZ.js.map} +0 -0
  484. /package/dist/{chunk-DABSYX7V.js.map → chunk-CWMMKFO7.js.map} +0 -0
  485. /package/dist/{chunk-WLHPCAVD.js.map → chunk-D5DTZBTV.js.map} +0 -0
  486. /package/dist/{chunk-2HDLDBJW.js.map → chunk-DDBLZJRV.js.map} +0 -0
  487. /package/dist/{chunk-F2FJTOV6.js.map → chunk-DO6R3LIE.js.map} +0 -0
  488. /package/dist/{chunk-LD22AAJI.js.map → chunk-EAZ3YZKN.js.map} +0 -0
  489. /package/dist/{chunk-46FVFU7S.js.map → chunk-EC5SVM6H.js.map} +0 -0
  490. /package/dist/{chunk-GKKLI23D.js.map → chunk-EEHQY7ER.js.map} +0 -0
  491. /package/dist/{chunk-BPXJINHW.js.map → chunk-EUP4AXIW.js.map} +0 -0
  492. /package/dist/{chunk-2WMXZYMS.js.map → chunk-F5V7KRQS.js.map} +0 -0
  493. /package/dist/{chunk-4AX5OYKC.js.map → chunk-FDDCZDBI.js.map} +0 -0
  494. /package/dist/{chunk-XLPZN3A5.js.map → chunk-FMEP2RB5.js.map} +0 -0
  495. /package/dist/{chunk-D7UUB33A.js.map → chunk-FSH6KRKO.js.map} +0 -0
  496. /package/dist/{chunk-3254BYQV.js.map → chunk-GGY7WKMB.js.map} +0 -0
  497. /package/dist/{chunk-ROXDOKLM.js.map → chunk-GYYLZOSO.js.map} +0 -0
  498. /package/dist/{chunk-3K64PLY3.js.map → chunk-HXDXR2FH.js.map} +0 -0
  499. /package/dist/{chunk-XOCGC3FJ.js.map → chunk-HYWPUYLO.js.map} +0 -0
  500. /package/dist/{chunk-KQAFEZQX.js.map → chunk-IPLYGWQF.js.map} +0 -0
  501. /package/dist/{chunk-M6FKXOYV.js.map → chunk-IXMWTNNY.js.map} +0 -0
  502. /package/dist/{chunk-ZYSOT6HD.js.map → chunk-J3IWHCMF.js.map} +0 -0
  503. /package/dist/{chunk-3YQ7VLP3.js.map → chunk-J5OXWG66.js.map} +0 -0
  504. /package/dist/{chunk-H2N6SLTR.js.map → chunk-JIWZVOQM.js.map} +0 -0
  505. /package/dist/{chunk-C3YEB7CR.js.map → chunk-JOP7W24J.js.map} +0 -0
  506. /package/dist/{chunk-F6PTJLEM.js.map → chunk-K4KE5MYE.js.map} +0 -0
  507. /package/dist/{chunk-IOTYIECA.js.map → chunk-KF57IQFX.js.map} +0 -0
  508. /package/dist/{chunk-ZVQ5HF77.js.map → chunk-N46SNYMC.js.map} +0 -0
  509. /package/dist/{chunk-YR5QT4SR.js.map → chunk-NAFPHFDM.js.map} +0 -0
  510. /package/dist/{chunk-VHANUT45.js.map → chunk-ORQ7AZMU.js.map} +0 -0
  511. /package/dist/{chunk-EOXCOZ6L.js.map → chunk-OV7GJ4KW.js.map} +0 -0
  512. /package/dist/{chunk-VJXMFBVR.js.map → chunk-P7S7BVBJ.js.map} +0 -0
  513. /package/dist/{chunk-7F47R4H4.js.map → chunk-S3SND7SO.js.map} +0 -0
  514. /package/dist/{chunk-5AJZ7EUW.js.map → chunk-S7KDHFF2.js.map} +0 -0
  515. /package/dist/{chunk-4WMGRLTV.js.map → chunk-SRRZZHA5.js.map} +0 -0
  516. /package/dist/{chunk-KDLOKVX7.js.map → chunk-SW42MMAH.js.map} +0 -0
  517. /package/dist/{chunk-2YIUUXNK.js.map → chunk-T5NI5K46.js.map} +0 -0
  518. /package/dist/{chunk-QCTXQ2HW.js.map → chunk-TE6B2UU6.js.map} +0 -0
  519. /package/dist/{chunk-JDOBQPJY.js.map → chunk-TNQR7N52.js.map} +0 -0
  520. /package/dist/{chunk-7K5Q6COX.js.map → chunk-TVVEYCNW.js.map} +0 -0
  521. /package/dist/{chunk-T3F2HWIG.js.map → chunk-TZ44E732.js.map} +0 -0
  522. /package/dist/{chunk-PF7LQ22G.js.map → chunk-U4ESGVZ2.js.map} +0 -0
  523. /package/dist/{chunk-WJPR2XUN.js.map → chunk-UEJY6ZTQ.js.map} +0 -0
  524. /package/dist/{chunk-K3IVJ5ML.js.map → chunk-UGGOQKO6.js.map} +0 -0
  525. /package/dist/{chunk-OCX7OWOC.js.map → chunk-UOREPQ3C.js.map} +0 -0
  526. /package/dist/{chunk-R3H2AXO2.js.map → chunk-VGRVJ6Z6.js.map} +0 -0
  527. /package/dist/{chunk-IWHNT35B.js.map → chunk-WOBMSMUK.js.map} +0 -0
  528. /package/dist/{chunk-DDGQDNQW.js.map → chunk-XLZZLUWL.js.map} +0 -0
  529. /package/dist/{chunk-KORQWGUA.js.map → chunk-Y3XHIWVP.js.map} +0 -0
  530. /package/dist/{chunk-BZDK5EOD.js.map → chunk-YFVIA5A6.js.map} +0 -0
  531. /package/dist/{chunk-GR76OKET.js.map → chunk-YSBYNCCN.js.map} +0 -0
  532. /package/dist/{chunk-OGAKUA7I.js.map → chunk-YUQPUK6H.js.map} +0 -0
  533. /package/dist/{chunk-TRTLWUDD.js.map → chunk-ZTHGFTMW.js.map} +0 -0
@@ -769,7 +769,6 @@ async function harness(
769
769
  registerFactContentHash: async (id: string, hash: string, _expectedContent: string) => {
770
770
  calls.hashRegistrations.push({ id, hash });
771
771
  },
772
- readCasRevision: async (p: string) => (p === targetPath ? casRevision : undefined),
773
772
  readCasRevisionStatus: async (p: string): Promise<CasRevisionReadStatus> => {
774
773
  if (p !== targetPath) return { status: "absent" };
775
774
  if (overrides.receiptStatus === "unavailable") {
@@ -3460,7 +3459,10 @@ async function postEffectsHarness(options: {
3460
3459
  : null,
3461
3460
  // #2807: static token — these fixtures drive the rollback paths, not
3462
3461
  // ownership attribution.
3463
- readCasRevision: async () => "2026-08-20T00:00:00.000Z",
3462
+ readCasRevisionStatus: async (): Promise<CasRevisionReadStatus> => ({
3463
+ status: "present",
3464
+ revision: "2026-08-20T00:00:00.000Z",
3465
+ }),
3464
3466
  } as unknown as StorageManager;
3465
3467
  const deps = {
3466
3468
  config: parseConfig({ memoryDir: dir }),
@@ -3729,6 +3731,12 @@ test("runMergedTargetPostEffects: a writer committing mid-rebuild keeps its edge
3729
3731
  content: buildGraphEdgeCalls > 0 ? NEWER_BODY : MERGED,
3730
3732
  }
3731
3733
  : null,
3734
+ // #2807: B's mid-rebuild commit mints a NEW token — the identity the
3735
+ // revision guard compares (public `updated` no longer carries it).
3736
+ readCasRevisionStatus: async (): Promise<CasRevisionReadStatus> =>
3737
+ buildGraphEdgeCalls > 0
3738
+ ? { status: "present", revision: "2026-08-22T01:00:00.001Z" }
3739
+ : { status: "present", revision: "2026-08-20T00:00:00.000Z" },
3732
3740
  } as unknown as StorageManager;
3733
3741
  const deps = {
3734
3742
  ...h.deps,
@@ -3800,8 +3808,10 @@ test("runMergedTargetPostEffects: a stale writer's rollback preserves the newer
3800
3808
  : null,
3801
3809
  // #2807: B's mid-rebuild commit mints a NEW token — the identity the
3802
3810
  // revision guard compares (public `updated` no longer carries it).
3803
- readCasRevision: async () =>
3804
- buildStarted ? "2026-08-22T01:00:00.001Z" : "2026-08-20T00:00:00.000Z",
3811
+ readCasRevisionStatus: async (): Promise<CasRevisionReadStatus> =>
3812
+ buildStarted
3813
+ ? { status: "present", revision: "2026-08-22T01:00:00.001Z" }
3814
+ : { status: "present", revision: "2026-08-20T00:00:00.000Z" },
3805
3815
  } as unknown as StorageManager;
3806
3816
  const graphConfig = parseConfig({ memoryDir: h.dir });
3807
3817
  const graphIndex = new GraphIndex(h.dir, graphConfig);
@@ -3849,6 +3859,170 @@ test("runMergedTargetPostEffects: a stale writer's rollback preserves the newer
3849
3859
  );
3850
3860
  });
3851
3861
 
3862
+ // ── #2837: the revision guard must FAIL CLOSED. The old guard read the
3863
+ // receipt fail-open (readCasRevision), so a rewrite whose BOTH sidecar reads
3864
+ // were unavailable compared undefined against undefined, passed, and
3865
+ // installed replacement edges no receipt ever verified. ────────────────────
3866
+
3867
+ test("runMergedTargetPostEffects: both receipt reads unavailable skips the graph rewrite instead of installing unchecked (#2837)", async () => {
3868
+ const h = await postEffectsHarness();
3869
+ const OTHER = "facts/2026-08-19/other.md";
3870
+ const priorEntity: GraphEdge = { from: h.targetRelPath, to: OTHER, type: "entity", weight: 1, label: "entity-billing-service", ts: "2026-08-19T00:00:00.000Z" };
3871
+ await seedGraphFile(h.dir, "entity", [priorEntity]);
3872
+ let built = false;
3873
+ const storage = {
3874
+ dir: h.dir,
3875
+ getMemoryByIdIncludingArchived: async (id: string) =>
3876
+ id === "fact-target"
3877
+ ? {
3878
+ path: path.join(h.dir, h.targetRelPath),
3879
+ frontmatter: { id, category: "fact", entityRef: "entity-billing-service" },
3880
+ content: MERGED,
3881
+ }
3882
+ : null,
3883
+ // The fail-open collapse the OLD guard trusted: an unreadable sidecar
3884
+ // reads as undefined on both sides of the remove-and-rebuild.
3885
+ readCasRevision: async () => undefined,
3886
+ readCasRevisionStatus: async (): Promise<CasRevisionReadStatus> => ({
3887
+ status: "unavailable",
3888
+ reason: "simulated sidecar outage",
3889
+ }),
3890
+ } as unknown as StorageManager;
3891
+ const deps = {
3892
+ ...h.deps,
3893
+ buildGraphEdge: async () => {
3894
+ built = true;
3895
+ },
3896
+ } as unknown as ExtractionPersistDeps;
3897
+ const graphContext = { allMemsForGraph: [], memoryPathById: new Map(), previousPersistedRelPath: "facts/2026-08-18/prev.md" };
3898
+ await runMergedTargetPostEffects(
3899
+ deps,
3900
+ storage,
3901
+ { targetId: "fact-target", mergedContent: MERGED },
3902
+ {
3903
+ category: "fact",
3904
+ incomingContent: INCOMING,
3905
+ incomingConfidence: 0.9,
3906
+ namespace: "default",
3907
+ graphCaps: ALL_GRAPH_CAPS,
3908
+ graphContext,
3909
+ threadIdForEdge: undefined,
3910
+ threadEpisodeIdsForGraph: undefined,
3911
+ },
3912
+ );
3913
+ assert.equal(built, false, "the graph effect must be SKIPPED while receipt identity is unavailable");
3914
+ assert.deepEqual(
3915
+ await readGraphFile(h.dir, "entity"),
3916
+ [priorEntity],
3917
+ "the prior edges survive untouched — nothing was removed for an install that could never be verified",
3918
+ );
3919
+ assert.equal(graphContext.previousPersistedRelPath, "facts/2026-08-18/prev.md", "a skipped rewrite must not advance the adjacency chain");
3920
+ });
3921
+
3922
+ test("runMergedTargetPostEffects: a receipt that becomes unavailable mid-rebuild rolls the install back (#2837)", async () => {
3923
+ const h = await postEffectsHarness();
3924
+ const OTHER = "facts/2026-08-19/other.md";
3925
+ const priorEntity: GraphEdge = { from: h.targetRelPath, to: OTHER, type: "entity", weight: 1, label: "entity-billing-service", ts: "2026-08-19T00:00:00.000Z" };
3926
+ await seedGraphFile(h.dir, "entity", [priorEntity]);
3927
+ let buildStarted = false;
3928
+ const storage = {
3929
+ dir: h.dir,
3930
+ getMemoryByIdIncludingArchived: async (id: string) =>
3931
+ id === "fact-target"
3932
+ ? {
3933
+ path: path.join(h.dir, h.targetRelPath),
3934
+ frontmatter: { id, category: "fact", entityRef: "entity-billing-service" },
3935
+ content: MERGED,
3936
+ }
3937
+ : null,
3938
+ readCasRevision: async () => undefined,
3939
+ readCasRevisionStatus: async (): Promise<CasRevisionReadStatus> =>
3940
+ buildStarted
3941
+ ? { status: "unavailable", reason: "simulated transient sidecar read failure" }
3942
+ : { status: "present", revision: "2026-08-20T00:00:00.000Z" },
3943
+ } as unknown as StorageManager;
3944
+ const rebuilt: GraphEdge = { from: h.targetRelPath, to: OTHER, type: "entity", weight: 1, label: "entity-billing-service-v3", ts: "2026-08-23T00:00:00.000Z" };
3945
+ const deps = {
3946
+ ...h.deps,
3947
+ buildGraphEdge: async () => {
3948
+ buildStarted = true;
3949
+ await appendEdge(h.dir, { ...rebuilt });
3950
+ return [{ ...rebuilt }];
3951
+ },
3952
+ } as unknown as ExtractionPersistDeps;
3953
+ const graphContext = { allMemsForGraph: [], memoryPathById: new Map(), previousPersistedRelPath: "facts/2026-08-18/prev.md" };
3954
+ await runMergedTargetPostEffects(
3955
+ deps,
3956
+ storage,
3957
+ { targetId: "fact-target", mergedContent: MERGED },
3958
+ {
3959
+ category: "fact",
3960
+ incomingContent: INCOMING,
3961
+ incomingConfidence: 0.9,
3962
+ namespace: "default",
3963
+ graphCaps: ALL_GRAPH_CAPS,
3964
+ graphContext,
3965
+ threadIdForEdge: undefined,
3966
+ threadEpisodeIdsForGraph: undefined,
3967
+ },
3968
+ );
3969
+ assert.deepEqual(
3970
+ await readGraphFile(h.dir, "entity"),
3971
+ [priorEntity],
3972
+ "an unverifiable post-build receipt must roll the remove-and-rebuild back to the prior edges",
3973
+ );
3974
+ assert.equal(graphContext.previousPersistedRelPath, "facts/2026-08-18/prev.md", "a rolled-back rewrite must not advance the adjacency chain");
3975
+ });
3976
+ test("runMergedTargetPostEffects: a genuinely absent receipt on both reads still installs (#2837)", async () => {
3977
+ const h = await postEffectsHarness();
3978
+ const OTHER = "facts/2026-08-19/other.md";
3979
+ const priorEntity: GraphEdge = { from: h.targetRelPath, to: OTHER, type: "entity", weight: 1, label: "entity-billing-service", ts: "2026-08-19T00:00:00.000Z" };
3980
+ await seedGraphFile(h.dir, "entity", [priorEntity]);
3981
+ const storage = {
3982
+ dir: h.dir,
3983
+ getMemoryByIdIncludingArchived: async (id: string) =>
3984
+ id === "fact-target"
3985
+ ? {
3986
+ path: path.join(h.dir, h.targetRelPath),
3987
+ frontmatter: { id, category: "fact", entityRef: "entity-billing-service" },
3988
+ content: MERGED,
3989
+ }
3990
+ : null,
3991
+ readCasRevision: async () => undefined,
3992
+ readCasRevisionStatus: async (): Promise<CasRevisionReadStatus> => ({ status: "absent" }),
3993
+ } as unknown as StorageManager;
3994
+ const rebuilt: GraphEdge = { from: h.targetRelPath, to: OTHER, type: "entity", weight: 1, label: "entity-billing-service-v3", ts: "2026-08-23T00:00:00.000Z" };
3995
+ const deps = {
3996
+ ...h.deps,
3997
+ buildGraphEdge: async () => {
3998
+ await appendEdge(h.dir, { ...rebuilt });
3999
+ return [{ ...rebuilt }];
4000
+ },
4001
+ } as unknown as ExtractionPersistDeps;
4002
+ const graphContext = { allMemsForGraph: [], memoryPathById: new Map(), previousPersistedRelPath: "facts/2026-08-18/prev.md" };
4003
+ await runMergedTargetPostEffects(
4004
+ deps,
4005
+ storage,
4006
+ { targetId: "fact-target", mergedContent: MERGED },
4007
+ {
4008
+ category: "fact",
4009
+ incomingContent: INCOMING,
4010
+ incomingConfidence: 0.9,
4011
+ namespace: "default",
4012
+ graphCaps: ALL_GRAPH_CAPS,
4013
+ graphContext,
4014
+ threadIdForEdge: undefined,
4015
+ threadEpisodeIdsForGraph: undefined,
4016
+ },
4017
+ );
4018
+ assert.deepEqual(
4019
+ await readGraphFile(h.dir, "entity"),
4020
+ [rebuilt],
4021
+ "a target that never minted a receipt (pre-sidecar legacy) still installs its rebuilt edges",
4022
+ );
4023
+ assert.equal(graphContext.previousPersistedRelPath, h.targetRelPath, "the verified install advances the batch's adjacency chain");
4024
+ });
4025
+
3852
4026
  // ── Round N+16 (A): the N+14 surgical rollback leaned on build()'s RETURN
3853
4027
  // value. A build that THROWS mid-append (the entity rows landed, a later
3854
4028
  // append failed) loses that return, and the rollback fell back to the
@@ -4194,11 +4368,12 @@ test("applySemanticMergeAtPersist: a timestamp-less retirement between the conte
4194
4368
  // identity the rollback compares.
4195
4369
  const receipt = h.calls.commitRevisions[0];
4196
4370
  assert.ok(receipt, "the content CAS stamped a receipt");
4197
- const standingToken = await h.storage.readCasRevision(h.target.path);
4198
- assert.ok(standingToken, "the retirement minted a new token");
4371
+ const standingStatus = await h.storage.readCasRevisionStatus(h.target.path);
4372
+ assert.ok(standingStatus.status === "present", "the retirement minted a standing receipt");
4373
+ const standingToken = standingStatus.status === "present" ? standingStatus.revision : undefined;
4199
4374
  assert.notEqual(standingToken, receipt, "the timestamp-less retirement retired the CAS's receipt token");
4200
4375
  assert.ok(
4201
- new Date(standingToken).getTime() > new Date(receipt).getTime(),
4376
+ standingToken !== undefined && new Date(standingToken).getTime() > new Date(receipt).getTime(),
4202
4377
  "the timestamp-less retirement advanced the token strictly past the CAS receipt",
4203
4378
  );
4204
4379
  assert.deepEqual(
@@ -4324,7 +4499,9 @@ test("applySemanticMergeAtPersist: the monotonic receipt survives the metadata p
4324
4499
  T.toISOString(),
4325
4500
  "#2807: the metadata patch stamps business time (mergePatch.updated) verbatim — never a revision",
4326
4501
  );
4327
- const standingToken = await h.storage.readCasRevision(h.target.path);
4502
+ const standingRead = await h.storage.readCasRevisionStatus(h.target.path);
4503
+ assert.equal(standingRead.status, "present", "the standing receipt is readable after the metadata patches");
4504
+ const standingToken = standingRead.status === "present" ? standingRead.revision : undefined;
4328
4505
  const [aReceipt, bReceipt, cReceipt2] = h.calls.commitRevisions;
4329
4506
  assert.ok(aReceipt && bReceipt && cReceipt2);
4330
4507
  assert.ok(aReceipt < bReceipt && bReceipt < cReceipt2, "A, B, C receipts are unique and strictly increasing");
@@ -1,6 +1,6 @@
1
1
  import assert from "node:assert/strict";
2
2
  import { test } from "node:test";
3
- import { applySpanMode } from "./extraction-span-wire.js";
3
+ import { applySpanMode, extractionProviderJsonSchema } from "./extraction-span-wire.js";
4
4
 
5
5
  const TEXT = "Alice moved to Seattle";
6
6
 
@@ -18,3 +18,19 @@ test("applySpanMode: enabled slices half-open [start, end)", () => {
18
18
  end: 11,
19
19
  });
20
20
  });
21
+
22
+ function factProperties(spanMode: "off" | "shadow" | "on") {
23
+ const schema = extractionProviderJsonSchema(spanMode);
24
+ const properties = schema.properties as
25
+ | Record<string, { items?: { properties?: Record<string, unknown> } }>
26
+ | undefined;
27
+ return properties?.facts?.items?.properties ?? {};
28
+ }
29
+
30
+ test("provider schema omits span in off mode and keeps it in on/shadow (issue #2952)", () => {
31
+ assert.equal("span" in factProperties("off"), false);
32
+ assert.equal("span" in factProperties("on"), true);
33
+ assert.equal("span" in factProperties("shadow"), true);
34
+ assert.deepEqual(extractionProviderJsonSchema("on"), extractionProviderJsonSchema("shadow"));
35
+ assert.notEqual(extractionProviderJsonSchema("off"), extractionProviderJsonSchema("on"));
36
+ });
@@ -1,11 +1,14 @@
1
1
  /**
2
- * Extraction seam for span mode (issue #2333 leftover slice).
2
+ * Extraction seam for span mode (issue #2333 leftover slice + #2952).
3
3
  *
4
- * Default off. Callers pass `enabled`. This module does not touch
5
- * extraction.ts; Phase A still gates production wiring.
4
+ * Default off. Callers pass `enabled` to `applySpanMode`. The provider JSON
5
+ * schema is mode-aware so off-mode structured output cannot advertise `span`.
6
6
  */
7
7
 
8
+ import { zodToJsonSchema } from "zod-to-json-schema";
9
+ import type { ExtractionSpanConfig } from "./extraction-span-config.js";
8
10
  import { parseSpanOffsets, type ParsedSpan } from "./extraction-span.js";
11
+ import { ExtractionProviderOutputSchema } from "./schemas.js";
9
12
 
10
13
  export function applySpanMode(opts: {
11
14
  enabled?: boolean | 0 | 1;
@@ -15,3 +18,43 @@ export function applySpanMode(opts: {
15
18
  }): ParsedSpan {
16
19
  return parseSpanOffsets(opts.text, { start: opts.start, end: opts.end }, opts.enabled);
17
20
  }
21
+
22
+ const JSON_SCHEMA_OPTS = { $refStrategy: "none" as const, effectStrategy: "input" as const };
23
+
24
+ const EXTRACTION_PROVIDER_JSON_SCHEMA_WITH_SPAN = zodToJsonSchema(
25
+ ExtractionProviderOutputSchema,
26
+ JSON_SCHEMA_OPTS,
27
+ ) as Record<string, unknown>;
28
+
29
+ type FactItems = {
30
+ properties?: Record<string, unknown>;
31
+ required?: string[];
32
+ };
33
+
34
+ function omitFactSpan(schema: Record<string, unknown>): Record<string, unknown> {
35
+ const clone = structuredClone(schema) as {
36
+ properties?: { facts?: { items?: FactItems } };
37
+ };
38
+ const items = clone.properties?.facts?.items;
39
+ if (!items?.properties || !("span" in items.properties)) {
40
+ throw new Error("extraction provider schema is missing facts.items.properties.span");
41
+ }
42
+ delete items.properties.span;
43
+ if (Array.isArray(items.required)) {
44
+ items.required = items.required.filter((key) => key !== "span");
45
+ }
46
+ return clone as Record<string, unknown>;
47
+ }
48
+
49
+ const EXTRACTION_PROVIDER_JSON_SCHEMA_WITHOUT_SPAN = omitFactSpan(
50
+ EXTRACTION_PROVIDER_JSON_SCHEMA_WITH_SPAN,
51
+ );
52
+
53
+ /** Provider JSON schema: `span` is present in on/shadow, absent in off. */
54
+ export function extractionProviderJsonSchema(
55
+ spanMode: ExtractionSpanConfig["spanMode"] = "off",
56
+ ): Record<string, unknown> {
57
+ return spanMode === "off"
58
+ ? EXTRACTION_PROVIDER_JSON_SCHEMA_WITHOUT_SPAN
59
+ : EXTRACTION_PROVIDER_JSON_SCHEMA_WITH_SPAN;
60
+ }
package/src/extraction.ts CHANGED
@@ -7,7 +7,6 @@ import { shouldRunProactivePass } from "./proactive-contention.js";
7
7
  import { FallbackLlmClient, fallbackLlmRuntimeContextFromConfig, gatewayTaskChainOptions } from "./fallback-llm.js";
8
8
  import { revalidateDependentsViaLlm, type RevalidationFastChatCompletion } from "./orchestration/dependency-revalidation.js";
9
9
  import {
10
- ExtractionProviderOutputSchema,
11
10
  ExtractionResultSchema,
12
11
  ConsolidationResultSchema,
13
12
  IdentityConsolidationResultSchema,
@@ -53,9 +52,9 @@ import {
53
52
  eventTimePromptInstruction,
54
53
  spanModePromptSection,
55
54
  } from "./extraction-prompt.js";
56
- import { zodToJsonSchema } from "zod-to-json-schema";
57
55
  import { containsExtractionPlaceholder, extractionAttributes, extractionCueAnchors, extractionText, isPlainRecord } from "./extraction-normalization.js";
58
56
  import { applyExtractionSpanMaterialization, bindLocalExtractionPrompt, buildSpanMaterializeTurns, stripUntrustedFactSpans, truncateLocalExtractionConversation, type SpanMaterializeTurn } from "./extraction-span-materialize.js";
57
+ import { extractionProviderJsonSchema } from "./extraction-span-wire.js";
59
58
  import { resolveLocalLlmCapabilities, resolveMemoryLifecycleCapabilities, resolvePipelineProcessingCapabilities, resolveRecallAuxiliaryCapabilities } from "./capabilities.js";
60
59
  type ExtractionQuestion = ExtractionResult["questions"][number];
61
60
  type ExtractedFactResult = ExtractionResult["facts"][number];
@@ -64,14 +63,6 @@ type ExtractedRelationshipResult = NonNullable<ExtractionResult["relationships"]
64
63
 
65
64
  const PROACTIVE_MIN_CONFIDENCE = 0.8;
66
65
 
67
- const EXTRACTION_PROVIDER_JSON_SCHEMA = zodToJsonSchema(
68
- ExtractionProviderOutputSchema,
69
- {
70
- $refStrategy: "none",
71
- effectStrategy: "input",
72
- },
73
- ) as Record<string, unknown>;
74
-
75
66
  function extractionEntityType(value: unknown): ExtractedEntityResult["type"] | undefined {
76
67
  const type = extractionText(value);
77
68
  if (
@@ -1353,7 +1344,7 @@ export class ExtractionEngine {
1353
1344
  timeoutMs: this.config.taskLlmTimeoutMs ?? this.config.localLlmTimeoutMs,
1354
1345
  jsonSchema: {
1355
1346
  name: "extraction_result",
1356
- schema: EXTRACTION_PROVIDER_JSON_SCHEMA,
1347
+ schema: extractionProviderJsonSchema(this.config.extraction?.spanMode ?? "off"),
1357
1348
  strict: false,
1358
1349
  },
1359
1350
  signal,
@@ -0,0 +1,106 @@
1
+ import assert from "node:assert/strict";
2
+ import test from "node:test";
3
+
4
+ import {
5
+ applyRecencyBoost,
6
+ assessMtimeTrust,
7
+ collectMtimeFallbackContext,
8
+ resolveMemoryAge,
9
+ utcDayKey,
10
+ } from "./memory-age.js";
11
+
12
+ const DAY_MS = 86_400_000;
13
+ const NOW = Date.parse("2026-08-25T12:00:00.000Z");
14
+
15
+ function spreadDays(count: number, startMs = NOW): number[] {
16
+ return Array.from({ length: count }, (_unused, i) => startMs + i * DAY_MS);
17
+ }
18
+
19
+ test("resolveMemoryAge: content dates win over a fresh mtime (#2976)", () => {
20
+ const created = new Date(NOW - 40 * DAY_MS).toISOString();
21
+ const resolved = resolveMemoryAge({ created });
22
+ assert.equal(resolved.source, "content");
23
+ assert.equal(resolved.referenceMs, Date.parse(created), "ages by the content date, not the fresh mtime");
24
+ });
25
+
26
+ test("resolveMemoryAge: created wins over updated; updated backs a missing created", () => {
27
+ const created = new Date(NOW - 90 * DAY_MS).toISOString();
28
+ const updated = new Date(NOW - 1 * DAY_MS).toISOString();
29
+ assert.equal(resolveMemoryAge({ created, updated }).referenceMs, Date.parse(created));
30
+ assert.equal(resolveMemoryAge({ created: undefined, updated }).referenceMs, Date.parse(updated));
31
+ });
32
+
33
+ test("resolveMemoryAge: unparsable content dates never produce a reference", () => {
34
+ for (const bad of ["not-a-date", "", "2026-13-45"] as const) {
35
+ const resolved = resolveMemoryAge({ created: bad, updated: bad });
36
+ assert.equal(resolved.referenceMs, null, `unparsable date ${JSON.stringify(bad)} must not resolve`);
37
+ assert.equal(resolved.source, "unknown");
38
+ }
39
+ });
40
+
41
+ test("resolveMemoryAge: mtime backs age only when trusted and no content date exists", () => {
42
+ const mtimeMs = NOW - 2 * DAY_MS;
43
+ const trusted = assessMtimeTrust([mtimeMs, NOW - 9 * DAY_MS]);
44
+ assert.equal(resolveMemoryAge({}, mtimeMs, trusted).source, "mtime");
45
+ assert.equal(resolveMemoryAge({ created: "garbage" }, mtimeMs, trusted).source, "mtime");
46
+ assert.equal(resolveMemoryAge({ created: new Date(NOW).toISOString() }, mtimeMs, trusted).source, "content");
47
+ const distrusted = assessMtimeTrust([mtimeMs, mtimeMs, NOW - 9 * DAY_MS, NOW - 11 * DAY_MS]);
48
+ assert.equal(distrusted.bulkTouchDays.has(utcDayKey(mtimeMs)), true, "two of four same-day files cluster");
49
+ const refused = resolveMemoryAge({}, mtimeMs, distrusted);
50
+ assert.equal(refused.source, "unknown", "an untrusted mtime is unknown age, never fresh");
51
+ assert.equal(refused.referenceMs, null);
52
+ });
53
+
54
+ test("assessMtimeTrust: the measured distill-kura shape (50 of 214 files on one day) is a bulk touch", () => {
55
+ const bulkDay = NOW - 400 * DAY_MS;
56
+ const sample = [...Array<number>(50).fill(bulkDay), ...spreadDays(164, NOW - 200 * DAY_MS)];
57
+ const trust = assessMtimeTrust(sample);
58
+ assert.equal(trust.bulkTouchDays.size, 1);
59
+ assert.equal(trust.bulkTouchDays.has(utcDayKey(bulkDay)), true);
60
+ assert.equal(trust.isTrusted(bulkDay), false);
61
+ assert.equal(trust.isTrusted(sample[sample.length - 1]!), true);
62
+ });
63
+
64
+ test("assessMtimeTrust: the 20% boundary is inclusive", () => {
65
+ // 43 of 214 = 20.09% -> flagged; 42 of 214 = 19.6% -> not flagged.
66
+ const flagged = assessMtimeTrust([...Array<number>(43).fill(NOW), ...spreadDays(171, NOW - 400 * DAY_MS)]);
67
+ assert.equal(flagged.bulkTouchDays.has(utcDayKey(NOW)), true, "43/214 on one day is >= 20%");
68
+ const clear = assessMtimeTrust([...Array<number>(42).fill(NOW), ...spreadDays(172, NOW - 400 * DAY_MS)]);
69
+ assert.equal(clear.bulkTouchDays.has(utcDayKey(NOW)), false, "42/214 on one day is < 20%");
70
+ });
71
+
72
+ test("assessMtimeTrust: a single file never forms a cluster (min 2)", () => {
73
+ const trust = assessMtimeTrust([NOW]);
74
+ assert.equal(trust.bulkTouchDays.size, 0);
75
+ assert.equal(trust.isTrusted(NOW), true);
76
+ const pair = assessMtimeTrust([NOW, NOW]);
77
+ assert.equal(pair.bulkTouchDays.size, 1, "two files on one day meet the min-2 rule");
78
+ });
79
+
80
+ test("applyRecencyBoost: exact blend, unknown age untouched, weight 0 untouched, future clamped", () => {
81
+ const memory = {
82
+ path: "facts/a.md",
83
+ frontmatter: { created: new Date(NOW - 14 * DAY_MS).toISOString() },
84
+ };
85
+ const expected = 1 * 0.5 + Math.pow(0.5, 14 / 7) * 0.5;
86
+ assert.ok(Math.abs(applyRecencyBoost(1, memory, 0.5, NOW) - expected) < 1e-12);
87
+ // Unknown age (no dates, no trusted mtime) leaves the score alone.
88
+ assert.equal(applyRecencyBoost(0.7, { path: "facts/b.md", frontmatter: {} }, 0.5, NOW), 0.7);
89
+ assert.equal(applyRecencyBoost(0.7, memory, 0, NOW), 0.7);
90
+ // A corrupt future date clamps to age zero instead of boosting past 1.
91
+ const future = {
92
+ path: "facts/c.md",
93
+ frontmatter: { created: new Date(NOW + 30 * DAY_MS).toISOString() },
94
+ };
95
+ assert.equal(applyRecencyBoost(1, future, 0.5, NOW), 1);
96
+ });
97
+
98
+ test("collectMtimeFallbackContext: fully dated memories skip it entirely (no stats)", async () => {
99
+ // The paths do not exist: any stat would surface below as a null mtime
100
+ // instead of the documented null short-circuit.
101
+ const context = await collectMtimeFallbackContext([
102
+ { path: "/nonexistent/facts/a.md", created: new Date(NOW).toISOString() },
103
+ { path: "/nonexistent/facts/b.md", updated: new Date(NOW).toISOString() },
104
+ ]);
105
+ assert.equal(context, null);
106
+ });
@@ -0,0 +1,196 @@
1
+ /**
2
+ * Shared memory-age resolution (issue #2976).
3
+ *
4
+ * "Age is not mtime" (distill-kura `weave.py` `age_days`): `cp -r`, a
5
+ * restore, a checkout, or a bulk import resets file mtimes, so any freshness
6
+ * mechanism keyed on mtime either switches itself off or boosts everything
7
+ * equally. Remnic therefore resolves a memory's age from dates written
8
+ * INSIDE the memory first:
9
+ *
10
+ * 1. frontmatter `created` — the recall ranking reference before #2976,
11
+ * kept first so every dated memory scores exactly as it did;
12
+ * 2. frontmatter `updated` — the content-revision date;
13
+ * 3. file mtime — only when no content date exists AND the caller's trust
14
+ * assessment accepts it (see `assessMtimeTrust`);
15
+ * 4. otherwise UNKNOWN: no reference instant, and downstream scoring must
16
+ * treat unknown age as never-fresh (no recency boost), never as
17
+ * "just written".
18
+ *
19
+ * Bi-temporal `valid_at`/`observedAt` are deliberately NOT in the ladder:
20
+ * they date when the fact was true, not when the memory was written, so a
21
+ * fresh correction about an old event would be misaged by them.
22
+ *
23
+ * Bulk-touch heuristic (distill-kura measured 50/214 files sharing one
24
+ * bulk-touch day, mtime understating true age by a median of 11 days, worst
25
+ * case 425): a UTC calendar day shared by >= max(BULK_TOUCH_MIN_FILES,
26
+ * BULK_TOUCH_STORE_FRACTION of the sampled files) is a bulk-touch day, and
27
+ * mtimes falling on a bulk-touch day are untrusted — those ages are unknown,
28
+ * not fresh. The sample is the scored result set of the calling pipeline
29
+ * (bounded I/O), which observes a bulk-touched store as a same-day cluster.
30
+ */
31
+
32
+ import { stat } from "node:fs/promises";
33
+
34
+ /** Minimum files sharing one calendar day before that day is bulk-touched. */
35
+ export const BULK_TOUCH_MIN_FILES = 2;
36
+ /** Fraction of the sampled files on one day that marks a bulk touch. */
37
+ export const BULK_TOUCH_STORE_FRACTION = 0.2;
38
+
39
+ /** Recency half-life (days) used by `applyRecencyBoost`. */
40
+ export const RECENCY_HALF_LIFE_DAYS = 7;
41
+
42
+ /** How the resolved age reference was obtained. */
43
+ export type MemoryAgeSource = "content" | "mtime" | "unknown";
44
+
45
+ export interface ResolvedMemoryAge {
46
+ /** Reference instant for age computations, or null when age is unknown. */
47
+ referenceMs: number | null;
48
+ source: MemoryAgeSource;
49
+ }
50
+
51
+ /** Content dates written inside the memory (frontmatter). */
52
+ export interface MemoryAgeDates {
53
+ created?: string | null;
54
+ updated?: string | null;
55
+ }
56
+
57
+ /** Minimal memory shape the resolver needs; `path` keys the mtime fallback. */
58
+ export interface MemoryAgeSubject extends MemoryAgeDates {
59
+ path: string;
60
+ }
61
+
62
+ /** MemoryFile slice `applyRecencyBoost` reads; dates live in frontmatter. */
63
+ export interface RecencyBoostSubject {
64
+ path: string;
65
+ frontmatter: MemoryAgeDates;
66
+ }
67
+
68
+ /** Result of assessing a sample of mtimes for bulk-touch clustering. */
69
+ export interface MtimeTrust {
70
+ /** UTC day keys ("YYYY-MM-DD") whose mtime counts mark a bulk touch. */
71
+ bulkTouchDays: ReadonlySet<string>;
72
+ isTrusted(mtimeMs: number): boolean;
73
+ }
74
+
75
+ /** Per-recall mtime fallback context (null when no scored memory needs it). */
76
+ export interface MtimeFallbackContext {
77
+ mtimeMsByPath: Map<string, number | null>;
78
+ trust: MtimeTrust;
79
+ }
80
+
81
+ function finiteMs(value: string | null | undefined): number | null {
82
+ if (typeof value !== "string") return null;
83
+ const ms = Date.parse(value);
84
+ return Number.isFinite(ms) ? ms : null;
85
+ }
86
+
87
+ /**
88
+ * Resolve the reference instant for a memory's age. Content dates win over
89
+ * mtime; an mtime is consulted only when no content date parses and the
90
+ * trust assessment (when provided) accepts it; otherwise age is unknown.
91
+ */
92
+ export function resolveMemoryAge(
93
+ subject: MemoryAgeDates,
94
+ mtimeMs?: number | null,
95
+ trust?: MtimeTrust | null,
96
+ ): ResolvedMemoryAge {
97
+ const contentMs = finiteMs(subject.created) ?? finiteMs(subject.updated);
98
+ if (contentMs !== null) return { referenceMs: contentMs, source: "content" };
99
+ if (
100
+ typeof mtimeMs === "number" &&
101
+ Number.isFinite(mtimeMs) &&
102
+ trust?.isTrusted(mtimeMs) !== false
103
+ ) {
104
+ return { referenceMs: mtimeMs, source: "mtime" };
105
+ }
106
+ return { referenceMs: null, source: "unknown" };
107
+ }
108
+
109
+ /** UTC calendar-day key ("YYYY-MM-DD") for a millisecond instant. */
110
+ export function utcDayKey(ms: number): string {
111
+ return new Date(ms).toISOString().slice(0, 10);
112
+ }
113
+
114
+ /**
115
+ * Assess a sample of mtimes: days shared by >= max(BULK_TOUCH_MIN_FILES,
116
+ * BULK_TOUCH_STORE_FRACTION of the sample) are bulk-touch days, and their
117
+ * mtimes are untrusted for age. An empty or scattered sample trusts nothing
118
+ * less than it did before — only clustering changes anything.
119
+ */
120
+ export function assessMtimeTrust(sampleMtimesMs: readonly number[]): MtimeTrust {
121
+ const counts = new Map<string, number>();
122
+ for (const ms of sampleMtimesMs) {
123
+ if (!Number.isFinite(ms)) continue;
124
+ const key = utcDayKey(ms);
125
+ counts.set(key, (counts.get(key) ?? 0) + 1);
126
+ }
127
+ const threshold = Math.max(
128
+ BULK_TOUCH_MIN_FILES,
129
+ sampleMtimesMs.length * BULK_TOUCH_STORE_FRACTION,
130
+ );
131
+ const bulkTouchDays = new Set<string>();
132
+ for (const [day, count] of counts) {
133
+ if (count >= threshold) bulkTouchDays.add(day);
134
+ }
135
+ return {
136
+ bulkTouchDays,
137
+ isTrusted: (mtimeMs: number) => Number.isFinite(mtimeMs) && !bulkTouchDays.has(utcDayKey(mtimeMs)),
138
+ };
139
+ }
140
+
141
+ /**
142
+ * Build the mtime fallback context for a recall boost pass. Returns null —
143
+ * with zero stat calls — when every scored memory already has a content
144
+ * date, so the common path pays nothing. Otherwise stats the scored files
145
+ * (bounded by the result count) and assesses their mtimes for bulk-touch
146
+ * clustering. Unreadable files map to null mtimes (unknown age, never fresh).
147
+ */
148
+ export async function collectMtimeFallbackContext(
149
+ memories: ReadonlyArray<MemoryAgeSubject>,
150
+ ): Promise<MtimeFallbackContext | null> {
151
+ const needsFallback = memories.some(
152
+ (m) => finiteMs(m.created) === null && finiteMs(m.updated) === null,
153
+ );
154
+ if (!needsFallback) return null;
155
+ const stats = await Promise.all(
156
+ memories.map(async (m) => {
157
+ try {
158
+ return (await stat(m.path)).mtimeMs;
159
+ } catch {
160
+ return null;
161
+ }
162
+ }),
163
+ );
164
+ const mtimeMsByPath = new Map<string, number | null>(
165
+ memories.map((m, i) => [m.path, stats[i] ?? null]),
166
+ );
167
+ const trust = assessMtimeTrust(
168
+ stats.filter((ms): ms is number => ms !== null && Number.isFinite(ms)),
169
+ );
170
+ return { mtimeMsByPath, trust };
171
+ }
172
+
173
+ /**
174
+ * Apply the exponential recency blend to one score. Age resolves per
175
+ * `resolveMemoryAge`; unknown age leaves the score untouched (never fresh).
176
+ * A future reference instant clamps to age zero rather than boosting above
177
+ * the recency ceiling (corrupt dates are not super-fresh).
178
+ */
179
+ export function applyRecencyBoost(
180
+ score: number,
181
+ memory: RecencyBoostSubject,
182
+ recencyWeight: number,
183
+ nowMs: number,
184
+ fallback?: MtimeFallbackContext | null,
185
+ ): number {
186
+ if (recencyWeight <= 0) return score;
187
+ const resolved = resolveMemoryAge(
188
+ memory.frontmatter,
189
+ fallback?.mtimeMsByPath.get(memory.path) ?? null,
190
+ fallback?.trust,
191
+ );
192
+ if (resolved.referenceMs === null) return score;
193
+ const ageDays = Math.max(0, (nowMs - resolved.referenceMs) / 86_400_000);
194
+ const recencyScore = Math.pow(0.5, ageDays / RECENCY_HALF_LIFE_DAYS);
195
+ return score * (1 - recencyWeight) + recencyScore * recencyWeight;
196
+ }