@remnic/core 9.17.1 → 9.19.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 (379) 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-BsawJa-L.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-DLuVnV6a.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-O6P8694A.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-KGKYTCDO.js → chunk-3LZSS77C.js} +3 -3
  63. package/dist/{chunk-EA2H5LML.js → chunk-44DUJETW.js} +2 -2
  64. package/dist/{chunk-WQ2I6ODB.js → chunk-5MFHOX5T.js} +3 -3
  65. package/dist/{chunk-JECBQBEX.js → chunk-B7GROTXD.js} +2 -2
  66. package/dist/{chunk-6WAFYNVT.js → chunk-BJJAX3IZ.js} +2 -2
  67. package/dist/{chunk-IZHXSLO7.js → chunk-CH3BRJ3P.js} +2 -2
  68. package/dist/{chunk-L3HAUO7K.js → chunk-D5H3B36B.js} +2 -2
  69. package/dist/{chunk-AWT7SCWQ.js → chunk-DQCUBRTZ.js} +2 -2
  70. package/dist/{chunk-WIUYMCPL.js → chunk-DR65EEXG.js} +15 -15
  71. package/dist/{chunk-7LXSPIVG.js → chunk-EZFVVEQG.js} +4 -4
  72. package/dist/{chunk-S2FSBU4H.js → chunk-FCCMFVS6.js} +2 -2
  73. package/dist/{chunk-2OH3ZRYH.js → chunk-FL7MNSH3.js} +7 -6
  74. package/dist/chunk-FL7MNSH3.js.map +1 -0
  75. package/dist/{chunk-QDTEH6BS.js → chunk-HCNG42P3.js} +2 -2
  76. package/dist/{chunk-V57NLO5P.js → chunk-HKF5VASQ.js} +11 -4
  77. package/dist/chunk-HKF5VASQ.js.map +1 -0
  78. package/dist/{chunk-7YJLCNPF.js → chunk-HRREDJSL.js} +204 -170
  79. package/dist/chunk-HRREDJSL.js.map +1 -0
  80. package/dist/{chunk-7ON2JW73.js → chunk-IKMAO55B.js} +2 -2
  81. package/dist/{chunk-B57BIBXI.js → chunk-IU3XJAQM.js} +3 -3
  82. package/dist/{chunk-RFDD3B7W.js → chunk-JGUXPJUQ.js} +2 -2
  83. package/dist/{chunk-KQED7RCV.js → chunk-KOFVOVJG.js} +22 -22
  84. package/dist/{chunk-7PK4W4RN.js → chunk-KUF7OIFK.js} +3 -3
  85. package/dist/{chunk-QCLQ4BGA.js → chunk-MJYYXODM.js} +3 -3
  86. package/dist/{chunk-C2RMQRPN.js → chunk-MLW3J7IC.js} +3 -3
  87. package/dist/{chunk-7VZWZVYG.js → chunk-MQVINOJQ.js} +3 -3
  88. package/dist/{chunk-BCNVNUQG.js → chunk-OFQUY5NZ.js} +12 -4
  89. package/dist/chunk-OFQUY5NZ.js.map +1 -0
  90. package/dist/{chunk-52EFQMQC.js → chunk-OOJQ6ID5.js} +30 -30
  91. package/dist/{chunk-F7BWFIDK.js → chunk-OZSL2QZC.js} +65 -4
  92. package/dist/chunk-OZSL2QZC.js.map +1 -0
  93. package/dist/{chunk-LMVAIZR5.js → chunk-PLRDZ2OO.js} +5 -9
  94. package/dist/chunk-PLRDZ2OO.js.map +1 -0
  95. package/dist/{chunk-BIRPR5N7.js → chunk-Q55XM4SU.js} +2 -2
  96. package/dist/{chunk-7O2C2OOF.js → chunk-QEEMLGCO.js} +7 -7
  97. package/dist/{chunk-ANEUPF6O.js → chunk-QH6CS6XJ.js} +2 -2
  98. package/dist/{chunk-SCCO46KZ.js → chunk-RDQGLJRR.js} +3 -2
  99. package/dist/chunk-RDQGLJRR.js.map +1 -0
  100. package/dist/{chunk-BGBPDAIN.js → chunk-RPOOJPMB.js} +6 -6
  101. package/dist/{chunk-I6ODVWQB.js → chunk-S5HCKQ7Q.js} +2 -2
  102. package/dist/{chunk-QDNE35FU.js → chunk-SB4OKS6C.js} +3 -3
  103. package/dist/{chunk-B6NCHYAK.js → chunk-SQEDDWAM.js} +4 -4
  104. package/dist/{chunk-K2B2TMF3.js → chunk-U7D5HWNG.js} +2 -2
  105. package/dist/{chunk-XSBUROEB.js → chunk-UGH6VPPO.js} +2 -2
  106. package/dist/{chunk-G2WCVFDC.js → chunk-UVSDFM7R.js} +4 -4
  107. package/dist/{chunk-AFH7PIFU.js → chunk-VSVX777Y.js} +2 -2
  108. package/dist/{chunk-B6P3QDBF.js → chunk-W3DJOT22.js} +2 -2
  109. package/dist/{chunk-AE7WMV4V.js → chunk-WMLHJ5UW.js} +2 -2
  110. package/dist/{chunk-N7DK67GZ.js → chunk-WYL5ODHJ.js} +1 -1
  111. package/dist/chunk-WYL5ODHJ.js.map +1 -0
  112. package/dist/{chunk-GBT4TFV7.js → chunk-X6E5WAGX.js} +9 -9
  113. package/dist/{chunk-VZC7ORJE.js → chunk-X7GOH7Y6.js} +2 -2
  114. package/dist/{chunk-6QYST5XN.js → chunk-XY7QPJDA.js} +2 -2
  115. package/dist/{chunk-WIS5JTJR.js → chunk-Y4JHE2ML.js} +2 -2
  116. package/dist/{chunk-SESF64QR.js → chunk-YUWFASAK.js} +4 -4
  117. package/dist/{chunk-BZ52BJ6C.js → chunk-YWJBAHTY.js} +2 -2
  118. package/dist/{cli-BLZHYY2A.d.ts → cli-0dQHR6e6.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 +420 -420
  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-B8ickLkd.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-B669unGm.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-D0PVXmea.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 +76 -76
  257. package/dist/{scope-profiles-NsQQo1Lt.d.ts → scope-profiles-BOp3gSzp.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-C7SZNzUJ.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-CJ_Fia4H.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-GeWGXq9E.d.ts} +18 -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 +33 -0
  311. package/src/config.ts +7 -3
  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 +20 -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-BCNVNUQG.js.map +0 -1
  328. package/dist/chunk-F7BWFIDK.js.map +0 -1
  329. package/dist/chunk-LMVAIZR5.js.map +0 -1
  330. package/dist/chunk-N7DK67GZ.js.map +0 -1
  331. package/dist/chunk-SCCO46KZ.js.map +0 -1
  332. package/dist/chunk-V57NLO5P.js.map +0 -1
  333. /package/dist/{chunk-BV5WMSIJ.js.map → chunk-2AJ72VVS.js.map} +0 -0
  334. /package/dist/{chunk-755Q3DGF.js.map → chunk-2H7OEVPO.js.map} +0 -0
  335. /package/dist/{chunk-TG6IHVE2.js.map → chunk-354T54RR.js.map} +0 -0
  336. /package/dist/{chunk-KGKYTCDO.js.map → chunk-3LZSS77C.js.map} +0 -0
  337. /package/dist/{chunk-EA2H5LML.js.map → chunk-44DUJETW.js.map} +0 -0
  338. /package/dist/{chunk-WQ2I6ODB.js.map → chunk-5MFHOX5T.js.map} +0 -0
  339. /package/dist/{chunk-JECBQBEX.js.map → chunk-B7GROTXD.js.map} +0 -0
  340. /package/dist/{chunk-6WAFYNVT.js.map → chunk-BJJAX3IZ.js.map} +0 -0
  341. /package/dist/{chunk-IZHXSLO7.js.map → chunk-CH3BRJ3P.js.map} +0 -0
  342. /package/dist/{chunk-L3HAUO7K.js.map → chunk-D5H3B36B.js.map} +0 -0
  343. /package/dist/{chunk-AWT7SCWQ.js.map → chunk-DQCUBRTZ.js.map} +0 -0
  344. /package/dist/{chunk-WIUYMCPL.js.map → chunk-DR65EEXG.js.map} +0 -0
  345. /package/dist/{chunk-7LXSPIVG.js.map → chunk-EZFVVEQG.js.map} +0 -0
  346. /package/dist/{chunk-S2FSBU4H.js.map → chunk-FCCMFVS6.js.map} +0 -0
  347. /package/dist/{chunk-QDTEH6BS.js.map → chunk-HCNG42P3.js.map} +0 -0
  348. /package/dist/{chunk-7ON2JW73.js.map → chunk-IKMAO55B.js.map} +0 -0
  349. /package/dist/{chunk-B57BIBXI.js.map → chunk-IU3XJAQM.js.map} +0 -0
  350. /package/dist/{chunk-RFDD3B7W.js.map → chunk-JGUXPJUQ.js.map} +0 -0
  351. /package/dist/{chunk-KQED7RCV.js.map → chunk-KOFVOVJG.js.map} +0 -0
  352. /package/dist/{chunk-7PK4W4RN.js.map → chunk-KUF7OIFK.js.map} +0 -0
  353. /package/dist/{chunk-QCLQ4BGA.js.map → chunk-MJYYXODM.js.map} +0 -0
  354. /package/dist/{chunk-C2RMQRPN.js.map → chunk-MLW3J7IC.js.map} +0 -0
  355. /package/dist/{chunk-7VZWZVYG.js.map → chunk-MQVINOJQ.js.map} +0 -0
  356. /package/dist/{chunk-52EFQMQC.js.map → chunk-OOJQ6ID5.js.map} +0 -0
  357. /package/dist/{chunk-BIRPR5N7.js.map → chunk-Q55XM4SU.js.map} +0 -0
  358. /package/dist/{chunk-7O2C2OOF.js.map → chunk-QEEMLGCO.js.map} +0 -0
  359. /package/dist/{chunk-ANEUPF6O.js.map → chunk-QH6CS6XJ.js.map} +0 -0
  360. /package/dist/{chunk-BGBPDAIN.js.map → chunk-RPOOJPMB.js.map} +0 -0
  361. /package/dist/{chunk-I6ODVWQB.js.map → chunk-S5HCKQ7Q.js.map} +0 -0
  362. /package/dist/{chunk-QDNE35FU.js.map → chunk-SB4OKS6C.js.map} +0 -0
  363. /package/dist/{chunk-B6NCHYAK.js.map → chunk-SQEDDWAM.js.map} +0 -0
  364. /package/dist/{chunk-K2B2TMF3.js.map → chunk-U7D5HWNG.js.map} +0 -0
  365. /package/dist/{chunk-XSBUROEB.js.map → chunk-UGH6VPPO.js.map} +0 -0
  366. /package/dist/{chunk-G2WCVFDC.js.map → chunk-UVSDFM7R.js.map} +0 -0
  367. /package/dist/{chunk-AFH7PIFU.js.map → chunk-VSVX777Y.js.map} +0 -0
  368. /package/dist/{chunk-B6P3QDBF.js.map → chunk-W3DJOT22.js.map} +0 -0
  369. /package/dist/{chunk-AE7WMV4V.js.map → chunk-WMLHJ5UW.js.map} +0 -0
  370. /package/dist/{chunk-GBT4TFV7.js.map → chunk-X6E5WAGX.js.map} +0 -0
  371. /package/dist/{chunk-VZC7ORJE.js.map → chunk-X7GOH7Y6.js.map} +0 -0
  372. /package/dist/{chunk-6QYST5XN.js.map → chunk-XY7QPJDA.js.map} +0 -0
  373. /package/dist/{chunk-WIS5JTJR.js.map → chunk-Y4JHE2ML.js.map} +0 -0
  374. /package/dist/{chunk-SESF64QR.js.map → chunk-YUWFASAK.js.map} +0 -0
  375. /package/dist/{chunk-BZ52BJ6C.js.map → chunk-YWJBAHTY.js.map} +0 -0
  376. /package/dist/{contradiction-scan-RJBABNRH.js.map → contradiction-scan-HMF7CNKD.js.map} +0 -0
  377. /package/dist/{first-start-migration-CSRHCJIQ.js.map → first-start-migration-LEXL3XYL.js.map} +0 -0
  378. /package/dist/{purge-IRF47ZTX.js.map → purge-YSGX5BOG.js.map} +0 -0
  379. /package/dist/{tier-stats-QEJSPFV2.js.map → tier-stats-XKD7SXYY.js.map} +0 -0
@@ -2349,6 +2349,28 @@ test("parseConfig bounded-state knobs coerce valid strings, preserve 0, and reje
2349
2349
  assert.throws(() => parseConfig({ recallImpressionsRotateKeep: "1000000" }), /between 1 and 1000/);
2350
2350
  }));
2351
2351
 
2352
+ test("parseConfig projection-rebuild knobs: defaults, boolean coercion, numeric override, and 60s floor (#2119)", () => {
2353
+ const defaults = parseConfig({});
2354
+ // Enabled by default — the projection must stay fresh without operator action,
2355
+ // mirroring lifecycle-ledger auto-compaction's enabled-by-default posture.
2356
+ assert.equal(defaults.projectionRebuildEnabled, true);
2357
+ assert.equal(defaults.projectionRebuildIntervalMs, 6 * 60 * 60 * 1000);
2358
+
2359
+ // Boolean coerced via the shared helper (string "false" must disable, not read
2360
+ // as truthy — gotcha #36).
2361
+ assert.equal(parseConfig({ projectionRebuildEnabled: "false" }).projectionRebuildEnabled, false);
2362
+ assert.equal(parseConfig({ projectionRebuildEnabled: false }).projectionRebuildEnabled, false);
2363
+
2364
+ // Numeric override is honored; a sub-floor value is clamped to the 60s floor.
2365
+ assert.equal(parseConfig({ projectionRebuildIntervalMs: 3600000 }).projectionRebuildIntervalMs, 3600000);
2366
+ // String forms are coerced (CLI/--config inputs arrive as strings, gotcha #17)...
2367
+ assert.equal(parseConfig({ projectionRebuildIntervalMs: "3600000" }).projectionRebuildIntervalMs, 3600000);
2368
+ // ...and invalid values are REJECTED, never silently reinterpreted (pattern #39):
2369
+ assert.throws(() => parseConfig({ projectionRebuildIntervalMs: 1000 }), /projectionRebuildIntervalMs/);
2370
+ assert.throws(() => parseConfig({ projectionRebuildIntervalMs: 3600000.5 }), /projectionRebuildIntervalMs/);
2371
+ assert.throws(() => parseConfig({ projectionRebuildIntervalMs: "abc" }), /projectionRebuildIntervalMs/);
2372
+ });
2373
+
2352
2374
  test("parseConfig forwards activity source settings", () => {
2353
2375
  const config = parseConfig({
2354
2376
  activity: {
@@ -2374,3 +2396,14 @@ test("parseConfig forwards activity source settings", () => {
2374
2396
  maxMemoriesPerDay: 0,
2375
2397
  });
2376
2398
  });
2399
+
2400
+ test("parseConfig forwards bridgeMode as a raw passthrough (validation is in resolveBridgeMode)", () => {
2401
+ assert.equal(parseConfig({}).bridgeMode, "embedded");
2402
+ assert.equal(parseConfig({ bridgeMode: "delegate" }).bridgeMode, "delegate");
2403
+ assert.equal(parseConfig({ bridgeMode: "embedded" }).bridgeMode, "embedded");
2404
+ // Invalid values pass through unchanged — resolveBridgeMode rejects them.
2405
+ assert.equal(parseConfig({ bridgeMode: "daemon" }).bridgeMode, "daemon");
2406
+ // Non-strings stringify so they REACH the downstream validator instead of
2407
+ // silently defaulting to embedded.
2408
+ assert.equal(parseConfig({ bridgeMode: true }).bridgeMode, "true");
2409
+ });
package/src/config.ts CHANGED
@@ -2339,14 +2339,13 @@ export function parseConfig(
2339
2339
  wearables,
2340
2340
  activity,
2341
2341
  provenance,
2342
- // At-rest encryption (issue #690 PR 3/4)
2343
- // coerceBool handles CLI string inputs: `--config secureStoreEnabled=true`
2344
- // arrives as the string "true" which `=== true` would reject (CLAUDE.md #36).
2342
+ // At-rest encryption (#690): coerceBool handles CLI string "true" (#36).
2345
2343
  secureStoreEnabled: coerceBool(cfg.secureStoreEnabled) === true,
2346
2344
  secureStoreEncryptOnWrite: coerceBool(cfg.secureStoreEncryptOnWrite) !== false, // default: true
2347
2345
  codingMode,
2348
2346
  heartbeat,
2349
2347
  slotBehavior,
2348
+ bridgeMode: cfg.bridgeMode === undefined ? "embedded" : String(cfg.bridgeMode),
2350
2349
  codexCompat,
2351
2350
  codingKnowledge: parseCodingKnowledgeConfig(cfg.codingKnowledge),
2352
2351
  chat: parseChatConfig(cfg.chat),
@@ -3566,6 +3565,11 @@ export function parseConfig(
3566
3565
  Number.isFinite(cfg.graphEdgeDecayVisibilityThreshold)
3567
3566
  ? Math.max(0, Math.min(1, cfg.graphEdgeDecayVisibilityThreshold))
3568
3567
  : 0.2,
3568
+ // Issue #2119 — maintenance-scheduled memory-projection rebuild.
3569
+ // Enabled-by-default (gotcha #36 coercion); interval via the strict shared parser (strings coerced, non-integer/sub-60s rejected — pattern #39).
3570
+ projectionRebuildEnabled: coerceBooleanLike(cfg.projectionRebuildEnabled) ?? true,
3571
+ projectionRebuildIntervalMs: parseIntegerAtLeast(
3572
+ cfg.projectionRebuildIntervalMs, 6 * 60 * 60 * 1000, 60_000, "projectionRebuildIntervalMs"),
3569
3573
  ...parseBoundedJsonlStateConfig(cfg, parseIntegerAtLeast),
3570
3574
  // Issue #681 PR 3/3 — confidence-aware traversal. Floor clamps to [0,1]; a
3571
3575
  // documented 0 iterations disables PageRank refinement (BFS scores pass through).
@@ -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
  }