@remnic/core 9.17.1 → 9.18.0

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 (377) hide show
  1. package/dist/access-admin-ops-surface.d.ts +8 -8
  2. package/dist/access-admin-ops-surface.js +18 -18
  3. package/dist/access-boundary.d.ts +8 -8
  4. package/dist/access-boundary.js +18 -18
  5. package/dist/access-cli.js +52 -52
  6. package/dist/access-http.d.ts +8 -8
  7. package/dist/access-http.js +23 -23
  8. package/dist/access-identity-continuity-surface.d.ts +6 -6
  9. package/dist/access-identity-continuity-surface.js +18 -18
  10. package/dist/access-lcm-surface.d.ts +8 -8
  11. package/dist/access-lcm-surface.js +18 -18
  12. package/dist/access-mcp.d.ts +8 -8
  13. package/dist/access-mcp.js +22 -22
  14. package/dist/access-memory-search-fanout.d.ts +2 -2
  15. package/dist/access-memory-search-fanout.js +3 -3
  16. package/dist/{access-namespace-preflight-_yxq3PYy.d.ts → access-namespace-preflight-IbQ0O8Kf.d.ts} +1 -1
  17. package/dist/access-namespace-preflight.d.ts +3 -3
  18. package/dist/access-namespace-preflight.js +18 -18
  19. package/dist/access-observe-write-surface.d.ts +8 -8
  20. package/dist/access-observe-write-surface.js +18 -18
  21. package/dist/access-operations-batch.js +20 -20
  22. package/dist/access-operations.d.ts +11 -11
  23. package/dist/access-operations.js +21 -21
  24. package/dist/access-recall-concurrency.d.ts +8 -8
  25. package/dist/access-recall-concurrency.js +18 -18
  26. package/dist/access-recall-response.d.ts +8 -8
  27. package/dist/access-recall-response.js +18 -18
  28. package/dist/access-recall-surface.d.ts +8 -8
  29. package/dist/access-recall-surface.js +18 -18
  30. package/dist/access-schema.d.ts +76 -76
  31. package/dist/{access-service-CSm242ks.d.ts → access-service-BqjsJRn_.d.ts} +6 -6
  32. package/dist/access-service.d.ts +8 -8
  33. package/dist/access-service.js +18 -18
  34. package/dist/access-surface-catalog.d.ts +8 -8
  35. package/dist/action-confidence.d.ts +1 -1
  36. package/dist/active-memory-bridge.d.ts +1 -1
  37. package/dist/active-memory-bridge.js +3 -3
  38. package/dist/active-recall.d.ts +1 -1
  39. package/dist/active-recall.js +1 -1
  40. package/dist/behavior-learner.d.ts +1 -1
  41. package/dist/behavior-signals.d.ts +1 -1
  42. package/dist/bootstrap.d.ts +6 -6
  43. package/dist/briefing.d.ts +2 -2
  44. package/dist/briefing.js +6 -6
  45. package/dist/buffer-surprise-report.d.ts +1 -1
  46. package/dist/buffer.d.ts +2 -2
  47. package/dist/buffer.js +2 -2
  48. package/dist/bulk-import/index.d.ts +3 -3
  49. package/dist/calibration.d.ts +1 -1
  50. package/dist/capabilities.d.ts +4 -3
  51. package/dist/capabilities.js +1 -1
  52. package/dist/{catalog-9qLG_2OV.d.ts → catalog-D_ToaAZM.d.ts} +1 -1
  53. package/dist/causal-behavior.d.ts +1 -1
  54. package/dist/causal-consolidation.d.ts +1 -1
  55. package/dist/causal-consolidation.js +7 -7
  56. package/dist/causal-trajectory-graph.d.ts +1 -1
  57. package/dist/{chunk-BV5WMSIJ.js → chunk-2AJ72VVS.js} +3 -3
  58. package/dist/{chunk-755Q3DGF.js → chunk-2H7OEVPO.js} +2 -2
  59. package/dist/{chunk-TG6IHVE2.js → chunk-354T54RR.js} +2 -2
  60. package/dist/{chunk-347WD2LE.js → chunk-3CSX3ETE.js} +2 -1
  61. package/dist/chunk-3CSX3ETE.js.map +1 -0
  62. package/dist/{chunk-EA2H5LML.js → chunk-44DUJETW.js} +2 -2
  63. package/dist/{chunk-SESF64QR.js → chunk-52H7OB7Y.js} +4 -4
  64. package/dist/{chunk-KQED7RCV.js → chunk-6P6ZR6JR.js} +22 -22
  65. package/dist/{chunk-C2RMQRPN.js → chunk-6YJLLDWO.js} +3 -3
  66. package/dist/{chunk-QDTEH6BS.js → chunk-7JXXI3GW.js} +2 -2
  67. package/dist/{chunk-B6P3QDBF.js → chunk-7KXE7KN5.js} +2 -2
  68. package/dist/{chunk-2OH3ZRYH.js → chunk-7NHIHUBW.js} +7 -6
  69. package/dist/chunk-7NHIHUBW.js.map +1 -0
  70. package/dist/{chunk-LMVAIZR5.js → chunk-A2KGXUOU.js} +5 -9
  71. package/dist/chunk-A2KGXUOU.js.map +1 -0
  72. package/dist/{chunk-52EFQMQC.js → chunk-AKWX5E6R.js} +30 -30
  73. package/dist/{chunk-WIUYMCPL.js → chunk-ALPBE5EC.js} +15 -15
  74. package/dist/{chunk-VZC7ORJE.js → chunk-B3R5CC35.js} +2 -2
  75. package/dist/{chunk-JECBQBEX.js → chunk-B7GROTXD.js} +2 -2
  76. package/dist/{chunk-6WAFYNVT.js → chunk-BJJAX3IZ.js} +2 -2
  77. package/dist/{chunk-7O2C2OOF.js → chunk-CNNQVI2A.js} +7 -7
  78. package/dist/{chunk-S2FSBU4H.js → chunk-FCCMFVS6.js} +2 -2
  79. package/dist/{chunk-F7BWFIDK.js → chunk-FO3WM76R.js} +65 -4
  80. package/dist/chunk-FO3WM76R.js.map +1 -0
  81. package/dist/{chunk-V57NLO5P.js → chunk-HKF5VASQ.js} +11 -4
  82. package/dist/chunk-HKF5VASQ.js.map +1 -0
  83. package/dist/{chunk-7ON2JW73.js → chunk-IKMAO55B.js} +2 -2
  84. package/dist/{chunk-B57BIBXI.js → chunk-IU3XJAQM.js} +3 -3
  85. package/dist/{chunk-WQ2I6ODB.js → chunk-JEMLK7ED.js} +3 -3
  86. package/dist/{chunk-RFDD3B7W.js → chunk-JGUXPJUQ.js} +2 -2
  87. package/dist/{chunk-BGBPDAIN.js → chunk-JHIEQCMG.js} +6 -6
  88. package/dist/{chunk-N7DK67GZ.js → chunk-KPEIWAZY.js} +1 -1
  89. package/dist/{chunk-N7DK67GZ.js.map → chunk-KPEIWAZY.js.map} +1 -1
  90. package/dist/{chunk-I6ODVWQB.js → chunk-LTGSOACX.js} +2 -2
  91. package/dist/{chunk-AWT7SCWQ.js → chunk-LU22QKFA.js} +2 -2
  92. package/dist/{chunk-QCLQ4BGA.js → chunk-MJYYXODM.js} +3 -3
  93. package/dist/{chunk-7VZWZVYG.js → chunk-MQVINOJQ.js} +3 -3
  94. package/dist/{chunk-7YJLCNPF.js → chunk-NEFMSUC2.js} +204 -170
  95. package/dist/chunk-NEFMSUC2.js.map +1 -0
  96. package/dist/{chunk-K2B2TMF3.js → chunk-NGO4QKMY.js} +2 -2
  97. package/dist/{chunk-7LXSPIVG.js → chunk-OR3OQHHS.js} +4 -4
  98. package/dist/{chunk-ANEUPF6O.js → chunk-QH6CS6XJ.js} +2 -2
  99. package/dist/{chunk-SCCO46KZ.js → chunk-RDQGLJRR.js} +3 -2
  100. package/dist/chunk-RDQGLJRR.js.map +1 -0
  101. package/dist/{chunk-QDNE35FU.js → chunk-SB4OKS6C.js} +3 -3
  102. package/dist/{chunk-IZHXSLO7.js → chunk-SGCWBLHN.js} +2 -2
  103. package/dist/{chunk-B6NCHYAK.js → chunk-SQEDDWAM.js} +4 -4
  104. package/dist/{chunk-XSBUROEB.js → chunk-UGH6VPPO.js} +2 -2
  105. package/dist/{chunk-GBT4TFV7.js → chunk-UHOUNNY3.js} +9 -9
  106. package/dist/{chunk-7PK4W4RN.js → chunk-UL6WTQN4.js} +3 -3
  107. package/dist/{chunk-KGKYTCDO.js → chunk-UQXIW7OZ.js} +3 -3
  108. package/dist/{chunk-WIS5JTJR.js → chunk-UVCY7VDX.js} +2 -2
  109. package/dist/{chunk-BIRPR5N7.js → chunk-VMALXCR5.js} +2 -2
  110. package/dist/{chunk-L3HAUO7K.js → chunk-WIP3MQZY.js} +2 -2
  111. package/dist/{chunk-AE7WMV4V.js → chunk-WMLHJ5UW.js} +2 -2
  112. package/dist/{chunk-G2WCVFDC.js → chunk-WMMT6W7C.js} +4 -4
  113. package/dist/{chunk-AFH7PIFU.js → chunk-XGYCI23S.js} +2 -2
  114. package/dist/{chunk-6QYST5XN.js → chunk-XY7QPJDA.js} +2 -2
  115. package/dist/{chunk-BZ52BJ6C.js → chunk-YWJBAHTY.js} +2 -2
  116. package/dist/{chunk-BCNVNUQG.js → chunk-ZHWEUG7Y.js} +10 -1
  117. package/dist/{chunk-BCNVNUQG.js.map → chunk-ZHWEUG7Y.js.map} +1 -1
  118. package/dist/{cli-BLZHYY2A.d.ts → cli-D2g5mRgf.d.ts} +5 -5
  119. package/dist/cli.d.ts +10 -10
  120. package/dist/cli.js +36 -36
  121. package/dist/compounding/engine.d.ts +2 -2
  122. package/dist/compounding/engine.js +7 -7
  123. package/dist/compounding/preference-consolidator.d.ts +1 -1
  124. package/dist/compression-optimizer.d.ts +1 -1
  125. package/dist/config.d.ts +1 -1
  126. package/dist/config.js +1 -1
  127. package/dist/connectors/codex-materialize-runner.d.ts +1 -1
  128. package/dist/connectors/codex-materialize-runner.js +6 -6
  129. package/dist/connectors/codex-materialize.d.ts +1 -1
  130. package/dist/connectors/index.d.ts +1 -1
  131. package/dist/connectors/index.js +7 -7
  132. package/dist/consolidation-provenance-check.d.ts +2 -2
  133. package/dist/consolidation-undo.d.ts +2 -2
  134. package/dist/contradiction/index.d.ts +2 -2
  135. package/dist/contradiction/index.js +2 -2
  136. package/dist/{contradiction-scan-RJBABNRH.js → contradiction-scan-HMF7CNKD.js} +3 -3
  137. package/dist/conversation-index/backend.d.ts +1 -1
  138. package/dist/conversation-index/chunker.d.ts +1 -1
  139. package/dist/conversation-index/faiss-adapter.d.ts +1 -1
  140. package/dist/conversation-index/indexer.d.ts +1 -1
  141. package/dist/conversation-index/search.d.ts +1 -1
  142. package/dist/day-summary.d.ts +1 -1
  143. package/dist/day-summary.js +2 -2
  144. package/dist/delinearize.d.ts +1 -1
  145. package/dist/direct-answer-wiring.d.ts +1 -1
  146. package/dist/direct-answer.d.ts +1 -1
  147. package/dist/embedding-fallback.d.ts +1 -1
  148. package/dist/embedding-fallback.js +2 -2
  149. package/dist/enrichment/index.d.ts +1 -1
  150. package/dist/entity-retrieval.d.ts +2 -2
  151. package/dist/entity-retrieval.js +6 -6
  152. package/dist/entity-schema.d.ts +1 -1
  153. package/dist/explicit-capture.d.ts +6 -6
  154. package/dist/explicit-capture.js +2 -2
  155. package/dist/extraction-faithfulness.d.ts +1 -1
  156. package/dist/extraction-judge-telemetry.d.ts +1 -1
  157. package/dist/extraction-judge-training.d.ts +1 -1
  158. package/dist/extraction-judge.d.ts +1 -1
  159. package/dist/extraction.d.ts +1 -1
  160. package/dist/extraction.js +4 -4
  161. package/dist/fallback-llm.d.ts +1 -1
  162. package/dist/{first-start-migration-CSRHCJIQ.js → first-start-migration-LEXL3XYL.js} +2 -2
  163. package/dist/graph-dashboard-diff.d.ts +1 -1
  164. package/dist/graph-dashboard-key.d.ts +1 -1
  165. package/dist/graph-dashboard-parser.d.ts +1 -1
  166. package/dist/graph-edge-reinforcement.d.ts +1 -1
  167. package/dist/graph-snapshot.d.ts +1 -1
  168. package/dist/graph.d.ts +1 -1
  169. package/dist/importance.d.ts +1 -1
  170. package/dist/importers/index.d.ts +1 -1
  171. package/dist/in-flight-reads.d.ts +1 -1
  172. package/dist/index.d.ts +418 -418
  173. package/dist/index.js +52 -52
  174. package/dist/intent.d.ts +1 -1
  175. package/dist/lcm/engine.d.ts +1 -1
  176. package/dist/lcm/index.d.ts +1 -1
  177. package/dist/lcm/tools.d.ts +1 -1
  178. package/dist/lifecycle.d.ts +1 -1
  179. package/dist/live-connectors-runner.d.ts +1 -1
  180. package/dist/local-llm.d.ts +1 -1
  181. package/dist/local-llm.js +2 -2
  182. package/dist/local-model-endpoint.d.ts +1 -1
  183. package/dist/maintenance/memory-governance.d.ts +1 -1
  184. package/dist/maintenance/memory-governance.js +6 -6
  185. package/dist/maintenance/rebuild-memory-lifecycle-ledger.d.ts +2 -2
  186. package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +6 -6
  187. package/dist/maintenance/rebuild-memory-projection.d.ts +26 -8
  188. package/dist/maintenance/rebuild-memory-projection.js +7 -7
  189. package/dist/mcp-memory-inspector-app.d.ts +8 -8
  190. package/dist/memory-action-policy.d.ts +1 -1
  191. package/dist/memory-cache.d.ts +1 -1
  192. package/dist/memory-lifecycle-ledger-utils.d.ts +1 -1
  193. package/dist/memory-projection-store.d.ts +3 -2
  194. package/dist/memory-projection-store.js +3 -1
  195. package/dist/memory-provenance.d.ts +1 -1
  196. package/dist/memory-worth-outcomes.d.ts +2 -2
  197. package/dist/models-json.d.ts +1 -1
  198. package/dist/namespaces/migrate.d.ts +3 -3
  199. package/dist/namespaces/migrate.js +10 -10
  200. package/dist/namespaces/principal.d.ts +1 -1
  201. package/dist/namespaces/principal.js +2 -2
  202. package/dist/namespaces/search.d.ts +1 -1
  203. package/dist/namespaces/search.js +4 -4
  204. package/dist/namespaces/storage.d.ts +3 -3
  205. package/dist/namespaces/storage.js +6 -6
  206. package/dist/native-knowledge.d.ts +1 -1
  207. package/dist/offline-sync-impression-drain.d.ts +1 -1
  208. package/dist/offline-sync-impression-drain.js +4 -4
  209. package/dist/operator-toolkit.d.ts +2 -2
  210. package/dist/operator-toolkit.js +16 -16
  211. package/dist/orchestration/compression-guideline-coordinator.d.ts +2 -2
  212. package/dist/orchestration/compression-guideline-coordinator.js +2 -2
  213. package/dist/orchestration/maintenance.d.ts +4 -3
  214. package/dist/orchestration/maintenance.js +15 -11
  215. package/dist/{orchestrator-CplS9U3U.d.ts → orchestrator-DrILRi-n.d.ts} +5 -5
  216. package/dist/orchestrator.d.ts +6 -6
  217. package/dist/orchestrator.js +52 -52
  218. package/dist/patterns-cli.d.ts +1 -1
  219. package/dist/{pipeline-DB4CbFqo.d.ts → pipeline-BYo4b_y9.d.ts} +1 -1
  220. package/dist/policy-runtime.d.ts +1 -1
  221. package/dist/proactive-contention.d.ts +1 -1
  222. package/dist/provenance.d.ts +1 -1
  223. package/dist/{purge-IRF47ZTX.js → purge-YSGX5BOG.js} +2 -2
  224. package/dist/{qmd-BxZmOLHq.d.ts → qmd-BIrL8e69.d.ts} +1 -1
  225. package/dist/qmd-preflight.d.ts +2 -2
  226. package/dist/qmd-recall-cache.d.ts +1 -1
  227. package/dist/qmd.d.ts +2 -2
  228. package/dist/recall-concurrency-config.d.ts +1 -1
  229. package/dist/recall-disclosure-escalation.d.ts +1 -1
  230. package/dist/recall-explain-renderer.d.ts +1 -1
  231. package/dist/recall-explain-renderer.js +3 -3
  232. package/dist/recall-memory-map.d.ts +1 -1
  233. package/dist/recall-planner-llm.d.ts +1 -1
  234. package/dist/recall-state.d.ts +1 -1
  235. package/dist/recall-tag-filter.d.ts +1 -1
  236. package/dist/recall-timings.d.ts +1 -1
  237. package/dist/recall-xray-cli.d.ts +1 -1
  238. package/dist/recall-xray-cli.js +4 -4
  239. package/dist/recall-xray-renderer.d.ts +1 -1
  240. package/dist/recall-xray-renderer.js +3 -3
  241. package/dist/recall-xray.d.ts +1 -1
  242. package/dist/recall-xray.js +2 -2
  243. package/dist/replay/normalizers/chatgpt.d.ts +1 -1
  244. package/dist/replay/normalizers/claude.d.ts +1 -1
  245. package/dist/replay/normalizers/openclaw.d.ts +1 -1
  246. package/dist/replay/normalizers/shared.d.ts +1 -1
  247. package/dist/replay/runner.d.ts +1 -1
  248. package/dist/replay/types.d.ts +1 -1
  249. package/dist/resolve-auth-token.d.ts +1 -1
  250. package/dist/resume-bundles.js +2 -2
  251. package/dist/retrieval-agents.d.ts +2 -2
  252. package/dist/retrieval-tiers.d.ts +1 -1
  253. package/dist/routing/engine.d.ts +1 -1
  254. package/dist/routing/store.d.ts +1 -1
  255. package/dist/salvage-envelope.d.ts +1 -1
  256. package/dist/schemas.d.ts +54 -54
  257. package/dist/{scope-profiles-NsQQo1Lt.d.ts → scope-profiles-BiQ9yXP4.d.ts} +1 -1
  258. package/dist/search/embed-helper.d.ts +1 -1
  259. package/dist/search/embed-helper.js +2 -2
  260. package/dist/search/factory.d.ts +1 -1
  261. package/dist/search/factory.js +3 -3
  262. package/dist/search/index.d.ts +1 -1
  263. package/dist/search/index.js +3 -3
  264. package/dist/search/lancedb-backend.d.ts +1 -1
  265. package/dist/search/meilisearch-backend.d.ts +1 -1
  266. package/dist/search/noop-backend.d.ts +1 -1
  267. package/dist/search/orama-backend.d.ts +1 -1
  268. package/dist/search/port.d.ts +1 -1
  269. package/dist/search/remote-backend.d.ts +1 -1
  270. package/dist/{semantic-consolidation-DW85h30n.d.ts → semantic-consolidation-C-cSATcR.d.ts} +1 -1
  271. package/dist/semantic-consolidation.d.ts +2 -2
  272. package/dist/semantic-consolidation.js +7 -7
  273. package/dist/semantic-rule-promotion.js +6 -6
  274. package/dist/semantic-rule-verifier.d.ts +1 -1
  275. package/dist/semantic-rule-verifier.js +6 -6
  276. package/dist/session-observer-bands.d.ts +1 -1
  277. package/dist/session-observer-state.d.ts +1 -1
  278. package/dist/shared-context/manager.d.ts +11 -11
  279. package/dist/shared-context/manager.js +2 -2
  280. package/dist/signal.d.ts +1 -1
  281. package/dist/{storage-o5BTAa8d.d.ts → storage-ByTv05qM.d.ts} +1 -1
  282. package/dist/storage-guards.d.ts +6 -2
  283. package/dist/storage-guards.js +3 -1
  284. package/dist/storage.d.ts +2 -2
  285. package/dist/storage.js +5 -5
  286. package/dist/summarizer.d.ts +1 -1
  287. package/dist/summarizer.js +3 -3
  288. package/dist/summary-snapshot.d.ts +1 -1
  289. package/dist/temporal-supersession.d.ts +2 -2
  290. package/dist/temporal-timeline-recall.d.ts +1 -1
  291. package/dist/temporal-validity.d.ts +1 -1
  292. package/dist/threading.d.ts +1 -1
  293. package/dist/tier-migration.d.ts +2 -2
  294. package/dist/tier-routing.d.ts +1 -1
  295. package/dist/{tier-stats-QEJSPFV2.js → tier-stats-XKD7SXYY.js} +2 -2
  296. package/dist/topics.d.ts +1 -1
  297. package/dist/transcript.d.ts +1 -1
  298. package/dist/transfer/types.d.ts +66 -66
  299. package/dist/trust-score-stage.d.ts +1 -1
  300. package/dist/trust-score.d.ts +1 -1
  301. package/dist/{types-V3dT-pen.d.ts → types-e0_MI3Iq.d.ts} +16 -0
  302. package/dist/types.d.ts +1 -1
  303. package/dist/types.js +1 -1
  304. package/dist/utility-runtime.d.ts +1 -1
  305. package/dist/verified-recall.js +6 -6
  306. package/dist/write-envelope.d.ts +1 -1
  307. package/package.json +2 -2
  308. package/src/capabilities.test.ts +1 -0
  309. package/src/capabilities.ts +4 -1
  310. package/src/config.test.ts +22 -0
  311. package/src/config.ts +5 -0
  312. package/src/maintenance/projection-support.ts +56 -0
  313. package/src/maintenance/rebuild-memory-lifecycle-ledger.ts +2 -7
  314. package/src/maintenance/rebuild-memory-projection.ts +7 -10
  315. package/src/memory-projection-store.ts +1 -1
  316. package/src/orchestration/maintenance.test.ts +116 -0
  317. package/src/orchestration/maintenance.ts +16 -0
  318. package/src/orchestration/projection-rebuild-schedule.ts +115 -0
  319. package/src/storage-guards.ts +19 -2
  320. package/src/storage-memory-lifecycle-ledger.test.ts +32 -0
  321. package/src/storage.ts +15 -1
  322. package/src/testing/subjects/memory-projection.test.ts +78 -0
  323. package/src/types.ts +18 -0
  324. package/dist/chunk-2OH3ZRYH.js.map +0 -1
  325. package/dist/chunk-347WD2LE.js.map +0 -1
  326. package/dist/chunk-7YJLCNPF.js.map +0 -1
  327. package/dist/chunk-F7BWFIDK.js.map +0 -1
  328. package/dist/chunk-LMVAIZR5.js.map +0 -1
  329. package/dist/chunk-SCCO46KZ.js.map +0 -1
  330. package/dist/chunk-V57NLO5P.js.map +0 -1
  331. /package/dist/{chunk-BV5WMSIJ.js.map → chunk-2AJ72VVS.js.map} +0 -0
  332. /package/dist/{chunk-755Q3DGF.js.map → chunk-2H7OEVPO.js.map} +0 -0
  333. /package/dist/{chunk-TG6IHVE2.js.map → chunk-354T54RR.js.map} +0 -0
  334. /package/dist/{chunk-EA2H5LML.js.map → chunk-44DUJETW.js.map} +0 -0
  335. /package/dist/{chunk-SESF64QR.js.map → chunk-52H7OB7Y.js.map} +0 -0
  336. /package/dist/{chunk-KQED7RCV.js.map → chunk-6P6ZR6JR.js.map} +0 -0
  337. /package/dist/{chunk-C2RMQRPN.js.map → chunk-6YJLLDWO.js.map} +0 -0
  338. /package/dist/{chunk-QDTEH6BS.js.map → chunk-7JXXI3GW.js.map} +0 -0
  339. /package/dist/{chunk-B6P3QDBF.js.map → chunk-7KXE7KN5.js.map} +0 -0
  340. /package/dist/{chunk-52EFQMQC.js.map → chunk-AKWX5E6R.js.map} +0 -0
  341. /package/dist/{chunk-WIUYMCPL.js.map → chunk-ALPBE5EC.js.map} +0 -0
  342. /package/dist/{chunk-VZC7ORJE.js.map → chunk-B3R5CC35.js.map} +0 -0
  343. /package/dist/{chunk-JECBQBEX.js.map → chunk-B7GROTXD.js.map} +0 -0
  344. /package/dist/{chunk-6WAFYNVT.js.map → chunk-BJJAX3IZ.js.map} +0 -0
  345. /package/dist/{chunk-7O2C2OOF.js.map → chunk-CNNQVI2A.js.map} +0 -0
  346. /package/dist/{chunk-S2FSBU4H.js.map → chunk-FCCMFVS6.js.map} +0 -0
  347. /package/dist/{chunk-7ON2JW73.js.map → chunk-IKMAO55B.js.map} +0 -0
  348. /package/dist/{chunk-B57BIBXI.js.map → chunk-IU3XJAQM.js.map} +0 -0
  349. /package/dist/{chunk-WQ2I6ODB.js.map → chunk-JEMLK7ED.js.map} +0 -0
  350. /package/dist/{chunk-RFDD3B7W.js.map → chunk-JGUXPJUQ.js.map} +0 -0
  351. /package/dist/{chunk-BGBPDAIN.js.map → chunk-JHIEQCMG.js.map} +0 -0
  352. /package/dist/{chunk-I6ODVWQB.js.map → chunk-LTGSOACX.js.map} +0 -0
  353. /package/dist/{chunk-AWT7SCWQ.js.map → chunk-LU22QKFA.js.map} +0 -0
  354. /package/dist/{chunk-QCLQ4BGA.js.map → chunk-MJYYXODM.js.map} +0 -0
  355. /package/dist/{chunk-7VZWZVYG.js.map → chunk-MQVINOJQ.js.map} +0 -0
  356. /package/dist/{chunk-K2B2TMF3.js.map → chunk-NGO4QKMY.js.map} +0 -0
  357. /package/dist/{chunk-7LXSPIVG.js.map → chunk-OR3OQHHS.js.map} +0 -0
  358. /package/dist/{chunk-ANEUPF6O.js.map → chunk-QH6CS6XJ.js.map} +0 -0
  359. /package/dist/{chunk-QDNE35FU.js.map → chunk-SB4OKS6C.js.map} +0 -0
  360. /package/dist/{chunk-IZHXSLO7.js.map → chunk-SGCWBLHN.js.map} +0 -0
  361. /package/dist/{chunk-B6NCHYAK.js.map → chunk-SQEDDWAM.js.map} +0 -0
  362. /package/dist/{chunk-XSBUROEB.js.map → chunk-UGH6VPPO.js.map} +0 -0
  363. /package/dist/{chunk-GBT4TFV7.js.map → chunk-UHOUNNY3.js.map} +0 -0
  364. /package/dist/{chunk-7PK4W4RN.js.map → chunk-UL6WTQN4.js.map} +0 -0
  365. /package/dist/{chunk-KGKYTCDO.js.map → chunk-UQXIW7OZ.js.map} +0 -0
  366. /package/dist/{chunk-WIS5JTJR.js.map → chunk-UVCY7VDX.js.map} +0 -0
  367. /package/dist/{chunk-BIRPR5N7.js.map → chunk-VMALXCR5.js.map} +0 -0
  368. /package/dist/{chunk-L3HAUO7K.js.map → chunk-WIP3MQZY.js.map} +0 -0
  369. /package/dist/{chunk-AE7WMV4V.js.map → chunk-WMLHJ5UW.js.map} +0 -0
  370. /package/dist/{chunk-G2WCVFDC.js.map → chunk-WMMT6W7C.js.map} +0 -0
  371. /package/dist/{chunk-AFH7PIFU.js.map → chunk-XGYCI23S.js.map} +0 -0
  372. /package/dist/{chunk-6QYST5XN.js.map → chunk-XY7QPJDA.js.map} +0 -0
  373. /package/dist/{chunk-BZ52BJ6C.js.map → chunk-YWJBAHTY.js.map} +0 -0
  374. /package/dist/{contradiction-scan-RJBABNRH.js.map → contradiction-scan-HMF7CNKD.js.map} +0 -0
  375. /package/dist/{first-start-migration-CSRHCJIQ.js.map → first-start-migration-LEXL3XYL.js.map} +0 -0
  376. /package/dist/{purge-IRF47ZTX.js.map → purge-YSGX5BOG.js.map} +0 -0
  377. /package/dist/{tier-stats-QEJSPFV2.js.map → tier-stats-XKD7SXYY.js.map} +0 -0
@@ -0,0 +1,56 @@
1
+ /**
2
+ * Projection maintenance support (issue #2119) — helpers shared by the
3
+ * scheduled projection rebuild, the CLI rebuild, and the ledger rebuild.
4
+ * Lives beside the rebuild ops so the at-ceiling store/rebuild modules stay
5
+ * within their structural ratchets.
6
+ */
7
+
8
+ import path from "node:path";
9
+ import { openProjectionReadonly } from "../memory-projection-store.js";
10
+
11
+ /** Read the projection meta rebuiltAt timestamp; null when absent/unopenable. */
12
+ export function readProjectionRebuiltAt(memoryDir: string): string | null {
13
+ const db = openProjectionReadonly(memoryDir);
14
+ if (!db) return null;
15
+ try {
16
+ const row = db.prepare("SELECT value FROM meta WHERE key = 'rebuiltAt'").get() as
17
+ | { value?: unknown }
18
+ | undefined;
19
+ return typeof row?.value === "string" && row.value.length > 0 ? row.value : null;
20
+ } catch {
21
+ return null;
22
+ } finally {
23
+ db.close();
24
+ }
25
+ }
26
+
27
+ /**
28
+ * Assert an optionally injected storage (e.g. the daemon's live unlocked
29
+ * secure-store instance) is rooted at `memoryDir`, and hand it back. Shared
30
+ * by the projection and lifecycle-ledger rebuilds so the containment guard
31
+ * cannot drift between them; callers construct their own StorageManager when
32
+ * nothing was injected (keeps this module off the direct-storage-import
33
+ * ratchet, issue #1533).
34
+ */
35
+ export function assertInjectedStorageRooted<T extends { dir: string }>(
36
+ label: string,
37
+ memoryDir: string,
38
+ injected: T | undefined,
39
+ ): T | undefined {
40
+ if (injected && path.resolve(injected.dir) !== path.resolve(memoryDir)) {
41
+ throw new Error(
42
+ `${label}: storage.dir (${injected.dir}) must match memoryDir (${memoryDir})`,
43
+ );
44
+ }
45
+ return injected;
46
+ }
47
+
48
+ export interface SkippedDuplicateMemory {
49
+ memoryId: string;
50
+ keptPath: string;
51
+ skippedPath: string;
52
+ }
53
+
54
+ export interface SkippedBlankIdMemory {
55
+ path: string;
56
+ }
@@ -1,6 +1,7 @@
1
1
  import path from "node:path";
2
2
  import { stat } from "node:fs/promises";
3
3
  import { StorageManager } from "../storage.js";
4
+ import { assertInjectedStorageRooted } from "./projection-support.js";
4
5
  import { log } from "../logger.js";
5
6
  import { displayErrorDetail } from "../runtime/better-sqlite.js";
6
7
  import type { MemoryLifecycleEvent } from "../types.js";
@@ -205,13 +206,7 @@ export async function rebuildMemoryLifecycleLedger(
205
206
  const dryRun = options.dryRun !== false;
206
207
  const now = options.now ?? new Date();
207
208
  const outputPath = path.join(options.memoryDir, "state", "memory-lifecycle-ledger.jsonl");
208
- const storage = options.storage ?? new StorageManager(options.memoryDir);
209
- if (options.storage && path.resolve(storage.dir) !== path.resolve(options.memoryDir)) {
210
- throw new Error(
211
- `rebuildMemoryLifecycleLedger: storage.dir (${storage.dir}) must match `
212
- + `memoryDir (${options.memoryDir})`,
213
- );
214
- }
209
+ const storage = assertInjectedStorageRooted("rebuildMemoryLifecycleLedger", options.memoryDir, options.storage) ?? new StorageManager(options.memoryDir);
215
210
  const secureRewrite = options.storage !== undefined;
216
211
  const tiers = [
217
212
  await storage.readAllMemories(),
@@ -46,6 +46,8 @@ import { commitPreparedFileAtomically } from "./atomic-file.js";
46
46
 
47
47
  export interface RebuildMemoryProjectionOptions {
48
48
  memoryDir: string;
49
+ /** Reuse the caller's live StorageManager (must be rooted at memoryDir). */
50
+ storage?: StorageManager;
49
51
  defaultNamespace?: string;
50
52
  dryRun?: boolean;
51
53
  now?: Date;
@@ -53,15 +55,9 @@ export interface RebuildMemoryProjectionOptions {
53
55
  updatedBefore?: string;
54
56
  }
55
57
 
56
- export interface SkippedDuplicateMemory {
57
- memoryId: string;
58
- keptPath: string;
59
- skippedPath: string;
60
- }
61
-
62
- export interface SkippedBlankIdMemory {
63
- path: string;
64
- }
58
+ export type { SkippedBlankIdMemory, SkippedDuplicateMemory } from "./projection-support.js";
59
+ import { assertInjectedStorageRooted } from "./projection-support.js";
60
+ import type { SkippedBlankIdMemory, SkippedDuplicateMemory } from "./projection-support.js";
65
61
 
66
62
  export interface SkippedDuplicateTimelineEvent {
67
63
  eventId: string;
@@ -550,6 +546,7 @@ function deduplicateTimelineEvents(
550
546
  async function loadAuthoritativeProjectionSnapshot(options: {
551
547
  memoryDir: string;
552
548
  defaultNamespace?: string;
549
+ storage?: StorageManager;
553
550
  updatedAfter?: string;
554
551
  updatedBefore?: string;
555
552
  }): Promise<{
@@ -581,7 +578,7 @@ async function loadAuthoritativeProjectionSnapshot(options: {
581
578
  skippedDuplicateTimelineEvents: SkippedDuplicateTimelineEvent[];
582
579
  skippedBlankIdTimelineEvents: SkippedBlankIdTimelineEvent[];
583
580
  }> {
584
- const storage = new StorageManager(options.memoryDir);
581
+ const storage = assertInjectedStorageRooted("rebuildMemoryProjection", options.memoryDir, options.storage) ?? new StorageManager(options.memoryDir);
585
582
  // Force a fresh disk read — projection verify/rebuild must see the true
586
583
  // on-disk state, not a potentially stale in-process cache.
587
584
  storage.invalidateAllMemoriesCacheForDir();
@@ -366,7 +366,7 @@ function logProjectionOpenFailure(memoryDir: string, error: unknown): void {
366
366
  );
367
367
  }
368
368
 
369
- function openProjectionReadonly(memoryDir: string): BetterSqlite3Database | null {
369
+ export function openProjectionReadonly(memoryDir: string): BetterSqlite3Database | null {
370
370
  const dbPath = getMemoryProjectionPath(memoryDir);
371
371
  // A MISSING file is a normal fallback (cold install, never built). A file
372
372
  // that EXISTS but cannot open (wrong ABI / corrupt / bad perms) is a real
@@ -18,10 +18,22 @@ import test from "node:test";
18
18
  import assert from "node:assert/strict";
19
19
  import os from "node:os";
20
20
  import path from "node:path";
21
+ import { readProjectionRebuiltAt } from "../maintenance/projection-support.js";
21
22
  import { lstat, mkdir, mkdtemp, readdir, readFile, rm, stat, symlink, writeFile } from "node:fs/promises";
22
23
  import { execFileSync } from "node:child_process";
23
24
 
24
25
  import { MaintenanceScheduler } from "./maintenance.js";
26
+ import {
27
+ createProjectionRebuildScheduleState,
28
+ maybeRebuildMemoryProjectionScheduled,
29
+ type ProjectionRebuildScheduleState,
30
+ } from "./projection-rebuild-schedule.js";
31
+ import { rebuildMemoryProjection } from "../maintenance/rebuild-memory-projection.js";
32
+ import {
33
+ probeProjectionHealth,
34
+ readProjectedMemoryBrowse,
35
+
36
+ } from "../memory-projection-store.js";
25
37
  import type { PluginConfig } from "../types.js";
26
38
  import type { SearchBackend } from "../search/port.js";
27
39
  import type {
@@ -2836,3 +2848,107 @@ test("catalog-disabled fallback pending drain skips unsafe spill entries (symlin
2836
2848
  await rm(outside, { recursive: true, force: true });
2837
2849
  }
2838
2850
  });
2851
+
2852
+ // ───────────────────────────────────────────────────────────────────────────
2853
+ // Memory-projection scheduled rebuild (issue #2119)
2854
+ // ───────────────────────────────────────────────────────────────────────────
2855
+
2856
+
2857
+ async function seedMemoryDirWithOneFact(): Promise<string> {
2858
+ const memoryDir = await mkdtemp(path.join(os.tmpdir(), "remnic-projrebuild-"));
2859
+ await mkdir(path.join(memoryDir, "facts", "2026-07-01"), { recursive: true });
2860
+ await writeFile(
2861
+ path.join(memoryDir, "facts", "2026-07-01", "fact-1.md"),
2862
+ `---
2863
+ id: fact-1
2864
+ category: fact
2865
+ created: 2026-07-01T00:00:00.000Z
2866
+ updated: 2026-07-01T01:00:00.000Z
2867
+ source: test
2868
+ confidence: 0.8
2869
+ confidenceTier: implied
2870
+ tags: ["alpha"]
2871
+ ---
2872
+
2873
+ alpha
2874
+ `,
2875
+ "utf-8",
2876
+ );
2877
+ return memoryDir;
2878
+ }
2879
+ /** Build the pure-function state the extracted scheduled rebuild owns. */
2880
+ function projectionScheduleState(): ProjectionRebuildScheduleState {
2881
+ return createProjectionRebuildScheduleState();
2882
+ }
2883
+
2884
+ test("scheduled projection rebuild runs when enabled and writes a populated projection (#2119)", async () => {
2885
+ const memoryDir = await seedMemoryDirWithOneFact();
2886
+ try {
2887
+ // No projection built yet — the exact issue-#2119 cold state where timeline
2888
+ // consumers would fall back to full-corpus scans.
2889
+ assert.equal(probeProjectionHealth(memoryDir).state, "absent");
2890
+ await maybeRebuildMemoryProjectionScheduled({
2891
+ config: fixtureConfig({
2892
+ memoryDir,
2893
+ projectionRebuildEnabled: true,
2894
+ projectionRebuildIntervalMs: 6 * 60 * 60 * 1000,
2895
+ }),
2896
+ state: projectionScheduleState(),
2897
+ });
2898
+ // The projection now exists, is openable, and carries the seeded memory.
2899
+ assert.equal(probeProjectionHealth(memoryDir).state, "openable");
2900
+ assert.equal(readProjectedMemoryBrowse(memoryDir, { limit: 5, offset: 0 })?.total, 1);
2901
+ assert.ok(readProjectionRebuiltAt(memoryDir), "rebuiltAt meta must be stamped");
2902
+ } finally {
2903
+ await rm(memoryDir, { recursive: true, force: true });
2904
+ }
2905
+ });
2906
+
2907
+ test("scheduled projection rebuild is a no-op when disabled by config (#2119)", async () => {
2908
+ const memoryDir = await seedMemoryDirWithOneFact();
2909
+ try {
2910
+ await maybeRebuildMemoryProjectionScheduled({
2911
+ config: fixtureConfig({
2912
+ memoryDir,
2913
+ projectionRebuildEnabled: false,
2914
+ projectionRebuildIntervalMs: 6 * 60 * 60 * 1000,
2915
+ }),
2916
+ state: projectionScheduleState(),
2917
+ });
2918
+ // Gate off: no projection is created, so the CLI/cron path remains the only
2919
+ // way to build it.
2920
+ assert.equal(probeProjectionHealth(memoryDir).state, "absent");
2921
+ } finally {
2922
+ await rm(memoryDir, { recursive: true, force: true });
2923
+ }
2924
+ });
2925
+
2926
+ test("scheduled projection rebuild skips when the projection was rebuilt within the interval (#2119)", async () => {
2927
+ const memoryDir = await seedMemoryDirWithOneFact();
2928
+ try {
2929
+ // Seed a projection whose rebuiltAt is one minute ago — inside the 6h
2930
+ // interval, so a scheduled pass must NOT rebuild it again.
2931
+ const oneMinuteAgo = new Date(Date.now() - 60_000);
2932
+ await rebuildMemoryProjection({ memoryDir, dryRun: false, now: oneMinuteAgo });
2933
+ const before = readProjectionRebuiltAt(memoryDir);
2934
+ assert.equal(before, oneMinuteAgo.toISOString());
2935
+
2936
+ await maybeRebuildMemoryProjectionScheduled({
2937
+ config: fixtureConfig({
2938
+ memoryDir,
2939
+ projectionRebuildEnabled: true,
2940
+ projectionRebuildIntervalMs: 6 * 60 * 60 * 1000,
2941
+ }),
2942
+ state: projectionScheduleState(),
2943
+ });
2944
+ // Fresh-within-interval: rebuiltAt is unchanged (no rebuild happened) and no
2945
+ // backup was written (a rebuild would have archived the prior projection).
2946
+ assert.equal(readProjectionRebuiltAt(memoryDir), before, "projection must not be rebuilt when fresh");
2947
+ await assert.rejects(
2948
+ () => readdir(path.join(memoryDir, "archive", "memory-projection")),
2949
+ "no backup means no rebuild occurred",
2950
+ );
2951
+ } finally {
2952
+ await rm(memoryDir, { recursive: true, force: true });
2953
+ }
2954
+ });
@@ -51,6 +51,10 @@ import {
51
51
  graphEdgeDecayCadenceToCronExpr,
52
52
  } from "../maintenance/memory-governance-cron.js";
53
53
  import { rebuildMemoryLifecycleLedger } from "../maintenance/rebuild-memory-lifecycle-ledger.js";
54
+ import {
55
+ createProjectionRebuildScheduleState,
56
+ maybeRebuildMemoryProjectionScheduled,
57
+ } from "./projection-rebuild-schedule.js";
54
58
  import {
55
59
  drainPendingLifecycleLedgerIfAny,
56
60
  pendingLifecycleLedgerDir,
@@ -134,6 +138,9 @@ export class MaintenanceScheduler {
134
138
  // fixture.
135
139
  private lifecycleLedgerMaxBytes = STATE_FILE_MAX_DECRYPT_BYTES;
136
140
 
141
+ // ── Memory-projection scheduled-rebuild state (issue #2119) ──
142
+ private readonly projectionRebuild = createProjectionRebuildScheduleState();
143
+
137
144
  // ── Activity (screen-capture) sync scheduler (issue #1900) ──
138
145
  // The full arm/re-arm/teardown lifecycle lives in ActivitySyncRegistrar.
139
146
  private readonly activityRegistrar: ActivitySyncRegistrar;
@@ -500,6 +507,15 @@ export class MaintenanceScheduler {
500
507
  void this.maybeCompactMemoryLifecycleLedger().catch((err) =>
501
508
  log.debug(`lifecycle ledger auto-compaction check failed (non-fatal): ${err}`),
502
509
  );
510
+ // Memory-projection scheduled rebuild (#2119): interval-throttled,
511
+ // single-flighted, never awaited, never throws — see the module docblock.
512
+ void maybeRebuildMemoryProjectionScheduled({
513
+ config: this.deps.config,
514
+ getStorage: this.deps.getStorage,
515
+ state: this.projectionRebuild,
516
+ }).catch((err) =>
517
+ log.debug(`memory projection scheduled rebuild check failed (non-fatal): ${err}`),
518
+ );
503
519
  }
504
520
 
505
521
  /** Internal: run a single QMD maintenance pass under singleflight guard. */
@@ -0,0 +1,115 @@
1
+ /**
2
+ * Scheduled rebuild of the root memory projection (issue #2119).
3
+ *
4
+ * The projection (`state/memory-projection.sqlite`) is otherwise rebuild-only
5
+ * (manual CLI / tests), so it goes stale as the lifecycle ledger grows and
6
+ * timeline/browse consumers silently fall back to full-corpus scans. The
7
+ * MaintenanceScheduler fires this alongside ledger compaction on every
8
+ * maintenance request — interval-throttled, single-flighted, never awaited,
9
+ * never throws.
10
+ *
11
+ * `projectionRebuildEnabled` gates it (via the lifecycle capabilities
12
+ * resolver, default true); `projectionRebuildIntervalMs` is the cadence.
13
+ *
14
+ * Skipped when the projection was rebuilt within the interval. The freshness
15
+ * check reads the on-disk `rebuiltAt` meta (not just an in-process timestamp)
16
+ * so a daemon restart or an operator's cron `remnic rebuild-memory-projection`
17
+ * both suppress a redundant rebuild. On a real rebuild it logs the lag it just
18
+ * cured (age of the projection before the rebuild) plus the fresh row counts,
19
+ * so operators can see the projection was drifting. Failures are non-fatal
20
+ * and leave the throttle un-advanced to retry.
21
+ *
22
+ * Scope: the root memoryDir projection — the one `getMemoryTimeline` /
23
+ * browse / current-state serve from `baseDir`. Per-namespace projections are
24
+ * not yet scheduled here (they are still served by their own rebuild paths);
25
+ * the fallback WARN surfaces staleness for any namespace that lags.
26
+ */
27
+ import { log } from "../logger.js";
28
+ import type { PluginConfig } from "../types.js";
29
+ import { resolveMemoryLifecycleCapabilities } from "../capabilities.js";
30
+ import {
31
+ rebuildMemoryProjection,
32
+ type RebuildMemoryProjectionOptions,
33
+ } from "../maintenance/rebuild-memory-projection.js";
34
+ import { readProjectionRebuiltAt } from "../maintenance/projection-support.js";
35
+
36
+ type InjectedStorage = NonNullable<RebuildMemoryProjectionOptions["storage"]>;
37
+
38
+ export interface ProjectionRebuildScheduleState {
39
+ inFlight: boolean;
40
+ lastRebuildAtMs: number;
41
+ /** Timestamp of the most recent FAILURE; gates a short retry backoff that is
42
+ * separate from the success interval so a transient cause (e.g. a locked
43
+ * secure store) retries promptly once cleared instead of waiting a full
44
+ * interval. */
45
+ lastFailureAtMs: number;
46
+ }
47
+
48
+ /** Bounded retry cadence after a failure — short enough to self-heal promptly
49
+ * once a transient cause clears, long enough to keep a persistently-failing
50
+ * rebuild off every maintenance tick. */
51
+ const FAILURE_RETRY_BACKOFF_MS = 5 * 60 * 1000;
52
+
53
+ export function createProjectionRebuildScheduleState(): ProjectionRebuildScheduleState {
54
+ return { inFlight: false, lastRebuildAtMs: 0, lastFailureAtMs: 0 };
55
+ }
56
+
57
+ export async function maybeRebuildMemoryProjectionScheduled(opts: {
58
+ config: PluginConfig;
59
+ /** The daemon's live root storage, when available (secure-store unlock). */
60
+ getStorage?: () => InjectedStorage | undefined;
61
+ state: ProjectionRebuildScheduleState;
62
+ }): Promise<void> {
63
+ const { config, state } = opts;
64
+ if (!resolveMemoryLifecycleCapabilities(config).projectionRebuild) return;
65
+ if (state.inFlight) return;
66
+ const intervalMs = config.projectionRebuildIntervalMs;
67
+ const now = Date.now();
68
+ if (now - state.lastRebuildAtMs < intervalMs) return;
69
+ if (now - state.lastFailureAtMs < FAILURE_RETRY_BACKOFF_MS) return;
70
+
71
+ const rootStorage = opts.getStorage?.();
72
+ const memoryDir = rootStorage ? rootStorage.dir : config.memoryDir;
73
+
74
+ // On-disk freshness check: survives restarts and cross-process CLI rebuilds.
75
+ const rebuiltAt = readProjectionRebuiltAt(memoryDir);
76
+ const rebuiltAtMs = rebuiltAt ? Date.parse(rebuiltAt) : Number.NaN;
77
+ if (Number.isFinite(rebuiltAtMs) && now - rebuiltAtMs < intervalMs) {
78
+ // Fresh already — adopt its timestamp so the next pass short-circuits on
79
+ // the cheap in-process guard without re-opening the projection each time.
80
+ state.lastRebuildAtMs = rebuiltAtMs;
81
+ return;
82
+ }
83
+
84
+ state.inFlight = true;
85
+ try {
86
+ const result = await rebuildMemoryProjection({
87
+ memoryDir,
88
+ // Reuse the daemon's live storage so secure-store deployments rebuild
89
+ // through the unlocked instance instead of a fresh locked one.
90
+ ...(rootStorage ? { storage: rootStorage } : {}),
91
+ defaultNamespace: config.defaultNamespace,
92
+ dryRun: false,
93
+ now: new Date(now),
94
+ });
95
+ state.lastRebuildAtMs = Date.now();
96
+ const lag = Number.isFinite(rebuiltAtMs)
97
+ ? `${Math.max(0, Math.round((now - rebuiltAtMs) / 60_000))}m stale`
98
+ : "never previously built";
99
+ log.info(
100
+ `memory projection rebuilt (scheduled) for ${memoryDir}: was ${lag}; `
101
+ + `${result.currentRows} current, ${result.timelineRows} timeline, `
102
+ + `${result.entityMentionRows} entity-mention rows`,
103
+ );
104
+ } catch (err) {
105
+ // Non-fatal. Arm a SHORT backoff (not the full success interval) so a
106
+ // transient cause (locked secure store, momentarily-unavailable storage)
107
+ // retries promptly once it clears, while a persistently-failing rebuild
108
+ // still stays off every maintenance tick. lastRebuildAtMs is left as-is so
109
+ // the success interval is not falsely advanced.
110
+ state.lastFailureAtMs = Date.now();
111
+ log.warn(`memory projection scheduled rebuild failed (non-fatal) for ${memoryDir}: ${err}`);
112
+ } finally {
113
+ state.inFlight = false;
114
+ }
115
+ }
@@ -18,13 +18,30 @@ export function assertMemoryFrontmatterId(
18
18
  }
19
19
  }
20
20
 
21
- export function warnProjectionFallback(memoryDir: string, consumer: string): null {
21
+ export function warnProjectionFallback(
22
+ memoryDir: string,
23
+ consumer: string,
24
+ // Lazy so the (potentially I/O-bound) lag computation runs only when this
25
+ // call actually logs, not on every rate-limited-suppressed fallback (#2119).
26
+ detail?: () => string | undefined,
27
+ ): null {
22
28
  const key = `${memoryDir}\0${consumer}`;
23
29
  const now = Date.now();
24
30
  const warnedAt = projectionFallbackWarnedAt.get(key) ?? 0;
25
31
  if (now - warnedAt >= PROJECTION_FALLBACK_WARN_INTERVAL_MS) {
26
32
  projectionFallbackWarnedAt.set(key, now);
27
- log.warn(`storage.${consumer}: memory projection absent or empty; falling back to full corpus`);
33
+ const suffix = detail?.();
34
+ log.warn(
35
+ `storage.${consumer}: memory projection absent or empty; falling back to full corpus`
36
+ + (suffix ? ` (${suffix})` : ""),
37
+ );
28
38
  }
29
39
  return null;
30
40
  }
41
+
42
+ /** @internal Test seam (#2119): clear the fallback-warn rate-limit dedup map so
43
+ * a focused test can prove the once-per-interval spam suppression from a clean
44
+ * slate regardless of prior tests in the same process. */
45
+ export function __resetProjectionFallbackWarnSuppressionForTest(): void {
46
+ projectionFallbackWarnedAt.clear();
47
+ }
@@ -6,6 +6,8 @@ import test from "node:test";
6
6
  import { randomUUID } from "node:crypto";
7
7
 
8
8
  import { StorageManager } from "./storage.js";
9
+ import { initLogger, resetLogger } from "./logger.js";
10
+ import { __resetProjectionFallbackWarnSuppressionForTest } from "./storage-guards.js";
9
11
  import { encryptFileBody, filePathAad, isEncryptedFile, readMaybeEncryptedFile, readMaybeEncryptedFileBuffer } from "./secure-store/secure-fs.js";
10
12
  import type { MemoryLifecycleEvent } from "./types.js";
11
13
  import {
@@ -1271,3 +1273,33 @@ test("compaction buffer reader skips an oversized unterminated row instead of th
1271
1273
  "valid rows returned; the oversized row is skipped without throwing",
1272
1274
  );
1273
1275
  });
1276
+
1277
+ test("getMemoryTimeline fallback WARN is loud with lag and rate-limited when the projection is missing (#2119)", async () => {
1278
+ const warns: string[] = [];
1279
+ initLogger({ info() {}, warn: (m) => warns.push(m), error() {}, debug() {} }, false);
1280
+ __resetProjectionFallbackWarnSuppressionForTest();
1281
+ try {
1282
+ const rows = [
1283
+ JSON.stringify(lifecycleEvent("t1", "memory-target", "2026-01-01T00:01:00.000Z")),
1284
+ JSON.stringify(lifecycleEvent("t2", "memory-target", "2026-01-01T00:02:00.000Z")),
1285
+ ];
1286
+ await withLifecycleLedger(rows, async (storage) => {
1287
+ // No projection sqlite exists in this temp dir, so both calls take the
1288
+ // ledger fallback — the exact issue-#2119 regression path.
1289
+ const first = await storage.getMemoryTimeline("memory-target", 5);
1290
+ const second = await storage.getMemoryTimeline("memory-target", 5);
1291
+ assert.equal(first.length, 2);
1292
+ assert.equal(second.length, 2);
1293
+
1294
+ const timelineWarns = warns.filter((w) => w.includes("storage.getMemoryTimeline"));
1295
+ // Rate-limited: two fallbacks, but only ONE WARN in the interval (no spam).
1296
+ assert.equal(timelineWarns.length, 1, "fallback WARN must fire once, not per call");
1297
+ // Loud: the single WARN names the fallback AND the projection lag.
1298
+ assert.match(timelineWarns[0]!, /falling back to full corpus/);
1299
+ assert.match(timelineWarns[0]!, /projection never rebuilt/);
1300
+ });
1301
+ } finally {
1302
+ resetLogger();
1303
+ __resetProjectionFallbackWarnSuppressionForTest();
1304
+ }
1305
+ });
package/src/storage.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import { readProjectionRebuiltAt } from "./maintenance/projection-support.js";
1
2
  import {
2
3
  access,
3
4
  lstat,
@@ -6584,7 +6585,20 @@ export class StorageManager {
6584
6585
 
6585
6586
  const projected = readProjectedMemoryTimeline(this.baseDir, memoryId, cappedLimit);
6586
6587
  if (projected && projected.length > 0) return projected;
6587
- warnProjectionFallback(this.baseDir, "getMemoryTimeline");
6588
+ // Loud, rate-limited staleness telemetry (#2119): a fallback here means the
6589
+ // projection is missing/empty/stale, so surface HOW stale it is. The lag is
6590
+ // computed lazily — only when warnProjectionFallback actually logs (once per
6591
+ // interval) — so a hot fallback path pays for a projection meta read at most
6592
+ // once per suppression window, not per call.
6593
+ warnProjectionFallback(this.baseDir, "getMemoryTimeline", () => {
6594
+ const rebuiltAt = readProjectionRebuiltAt(this.baseDir);
6595
+ if (!rebuiltAt) return "projection never rebuilt";
6596
+ const ageMs = Date.now() - Date.parse(rebuiltAt);
6597
+ if (!Number.isFinite(ageMs)) return `projection rebuiltAt=${rebuiltAt}`;
6598
+ const ageMin = Math.max(0, Math.round(ageMs / 60_000));
6599
+ const age = ageMin >= 120 ? `${Math.round(ageMin / 60)}h` : `${ageMin}m`;
6600
+ return `projection ${age} stale, last rebuilt ${rebuiltAt}`;
6601
+ });
6588
6602
  return readBoundedLifecycleEventsFromLedger(
6589
6603
  this.memoryLifecycleLedgerPath, (p) => this.readStorageSecureFile(p), cappedLimit, memoryId);
6590
6604
  }
@@ -0,0 +1,78 @@
1
+ /**
2
+ * Memory-projection rebuild subject for the scenario-matrix harness
3
+ * (issue #2119). Each canonical row runs the REAL rebuild path — memory scan,
4
+ * sqlite build, atomic install, rebuiltAt meta — plus the scheduled-rebuild
5
+ * wrapper's freshness short-circuit. The matrix dimensions are orthogonal to
6
+ * this rebuild-only subsystem, so the same durable projection invariant
7
+ * applies to every row.
8
+ */
9
+ import assert from "node:assert/strict";
10
+ import { mkdir, mkdtemp, rm, writeFile } from "node:fs/promises";
11
+ import { existsSync } from "node:fs";
12
+ import path from "node:path";
13
+ import os from "node:os";
14
+ import { rebuildMemoryProjection } from "../../maintenance/rebuild-memory-projection.js";
15
+ import { readProjectionRebuiltAt } from "../../maintenance/projection-support.js";
16
+ import {
17
+ type LifecycleSubject,
18
+ type MatrixRow,
19
+ runLifecycleMatrix,
20
+ } from "../lifecycle-matrix.js";
21
+
22
+ interface MemoryProjectionState {
23
+ memoryDir: string;
24
+ memoryId: string;
25
+ }
26
+
27
+ function memoryFixture(row: MatrixRow, memoryId: string): string {
28
+ return [
29
+ "---",
30
+ `id: ${memoryId}`,
31
+ "category: fact",
32
+ "confidence: 0.9",
33
+ "created: 2026-01-01T00:00:00.000Z",
34
+ "updated: 2026-01-01T00:00:00.000Z",
35
+ "status: active",
36
+ "---",
37
+ "",
38
+ `Matrix fixture memory for lifecycle row ${row.id}.`,
39
+ "",
40
+ ].join("\n");
41
+ }
42
+
43
+ const subject: LifecycleSubject<MemoryProjectionState> = {
44
+ async setup(row): Promise<MemoryProjectionState> {
45
+ const memoryDir = await mkdtemp(path.join(os.tmpdir(), "remnic-projection-subject-"));
46
+ const memoryId = `matrix-projection-${row.id}`;
47
+ const factDir = path.join(memoryDir, "facts", "2026-01-01");
48
+ await mkdir(factDir, { recursive: true });
49
+ await writeFile(path.join(factDir, `${memoryId}.md`), memoryFixture(row, memoryId), "utf8");
50
+ return { memoryDir, memoryId };
51
+ },
52
+
53
+ async exercise(state): Promise<void> {
54
+ const result = await rebuildMemoryProjection({
55
+ memoryDir: state.memoryDir,
56
+ dryRun: false,
57
+ now: new Date("2026-01-01T12:00:00.000Z"),
58
+ });
59
+ assert.equal(result.dryRun, false);
60
+ assert.equal(result.scannedMemories, 1);
61
+ assert.equal(result.currentRows, 1);
62
+ assert.ok(existsSync(result.outputPath), "projection sqlite installed");
63
+ },
64
+
65
+ async invariants(state): Promise<void> {
66
+ // The on-disk rebuiltAt meta is the scheduled rebuild's cross-process
67
+ // freshness signal (daemon restart / operator CLI both rely on it).
68
+ const rebuiltAt = readProjectionRebuiltAt(state.memoryDir);
69
+ assert.ok(rebuiltAt, "rebuiltAt meta written by the rebuild");
70
+ assert.ok(Number.isFinite(Date.parse(rebuiltAt)), "rebuiltAt parses as a timestamp");
71
+ },
72
+
73
+ async teardown(state): Promise<void> {
74
+ await rm(state.memoryDir, { recursive: true, force: true });
75
+ },
76
+ };
77
+
78
+ runLifecycleMatrix("memory-projection", subject);
package/src/types.ts CHANGED
@@ -2121,6 +2121,24 @@ export interface PluginConfig extends BoundedJsonlStateConfig {
2121
2121
  /** Confidence threshold for the "below visibility" telemetry counter. Default 0.2. */
2122
2122
  graphEdgeDecayVisibilityThreshold: number;
2123
2123
 
2124
+ // Issue #2119 — maintenance-scheduled memory-projection rebuild.
2125
+ /**
2126
+ * Enable the maintenance-scheduled rebuild of `state/memory-projection.sqlite`.
2127
+ * The projection is otherwise rebuild-only (manual CLI / tests), so it rots as
2128
+ * the lifecycle ledger grows and timeline/browse consumers silently regress to
2129
+ * full-corpus fallback scans. Default true — mirrors lifecycle-ledger
2130
+ * auto-compaction, which is likewise enabled-by-default to keep a state file
2131
+ * healthy. Set false to disable (operators can still cron the CLI rebuild).
2132
+ */
2133
+ projectionRebuildEnabled: boolean;
2134
+ /**
2135
+ * Minimum interval between scheduled projection rebuilds. A rebuild is skipped
2136
+ * when the projection's `rebuiltAt` meta is younger than this, so a burst of
2137
+ * maintenance requests cannot fan into back-to-back rebuilds. Floored at 60s.
2138
+ * Default 6h (matches memoryLifecycleLedgerCompactMinIntervalMs).
2139
+ */
2140
+ projectionRebuildIntervalMs: number;
2141
+
2124
2142
  /**
2125
2143
  * Issue #681 PR 3/3 — minimum edge confidence to traverse during spreading
2126
2144
  * activation. Edges below are pruned (no activation, no downstream neighbors);