@remnic/core 9.4.0 → 9.4.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (329) hide show
  1. package/dist/access-admin-ops-surface.d.ts +5 -5
  2. package/dist/access-admin-ops-surface.js +22 -21
  3. package/dist/access-boundary.d.ts +5 -5
  4. package/dist/access-boundary.js +23 -22
  5. package/dist/access-cli.js +63 -62
  6. package/dist/access-cli.js.map +1 -1
  7. package/dist/access-http.d.ts +5 -5
  8. package/dist/access-http.js +31 -30
  9. package/dist/access-identity-continuity-surface.d.ts +4 -4
  10. package/dist/access-identity-continuity-surface.js +22 -21
  11. package/dist/access-lcm-surface.d.ts +5 -5
  12. package/dist/access-lcm-surface.js +22 -21
  13. package/dist/access-mcp.d.ts +5 -5
  14. package/dist/access-mcp.js +27 -26
  15. package/dist/access-observe-write-surface.d.ts +5 -5
  16. package/dist/access-observe-write-surface.js +22 -21
  17. package/dist/access-operations-batch.js +24 -23
  18. package/dist/access-operations.d.ts +9 -9
  19. package/dist/access-operations.js +26 -25
  20. package/dist/access-recall-surface.d.ts +5 -5
  21. package/dist/access-recall-surface.js +22 -21
  22. package/dist/access-schema.d.ts +68 -68
  23. package/dist/access-schema.js +3 -3
  24. package/dist/{access-service-CdW6B2qQ.d.ts → access-service-m-2VO_Ig.d.ts} +3 -3
  25. package/dist/access-service.d.ts +5 -5
  26. package/dist/access-service.js +22 -21
  27. package/dist/access-surface-catalog.d.ts +5 -5
  28. package/dist/action-confidence.d.ts +1 -1
  29. package/dist/active-memory-bridge.d.ts +1 -1
  30. package/dist/active-recall.d.ts +1 -1
  31. package/dist/active-recall.js +1 -1
  32. package/dist/adapters/index.js +4 -4
  33. package/dist/adapters/registry.js +2 -2
  34. package/dist/{auto-sync-M47VTCFI.js → auto-sync-HMHQHUAG.js} +4 -4
  35. package/dist/behavior-learner.d.ts +1 -1
  36. package/dist/behavior-signals.d.ts +1 -1
  37. package/dist/bootstrap.d.ts +4 -4
  38. package/dist/briefing.d.ts +1 -1
  39. package/dist/briefing.js +5 -5
  40. package/dist/buffer-surprise-report.d.ts +1 -1
  41. package/dist/buffer.d.ts +1 -1
  42. package/dist/calibration.d.ts +1 -1
  43. package/dist/capabilities.d.ts +1 -1
  44. package/dist/{catalog-bflYNWsM.d.ts → catalog-BxWokdC2.d.ts} +1 -1
  45. package/dist/causal-behavior.d.ts +1 -1
  46. package/dist/causal-consolidation.d.ts +1 -1
  47. package/dist/causal-consolidation.js +6 -6
  48. package/dist/causal-trajectory-graph.d.ts +1 -1
  49. package/dist/{chunk-TKUQSXU4.js → chunk-27FLVMDC.js} +12 -12
  50. package/dist/{chunk-XXEOGY77.js → chunk-2FOD2YY3.js} +7 -7
  51. package/dist/{chunk-6Y4JTNX7.js → chunk-2MR3MFQB.js} +2 -2
  52. package/dist/{chunk-AU7Q3LSC.js → chunk-2QSZNTDO.js} +4 -4
  53. package/dist/{chunk-64ZYXSCO.js → chunk-2U2A5HF4.js} +10 -4
  54. package/dist/chunk-2U2A5HF4.js.map +1 -0
  55. package/dist/{chunk-RBGL4MKQ.js → chunk-3F4N4WDH.js} +75 -110
  56. package/dist/chunk-3F4N4WDH.js.map +1 -0
  57. package/dist/{chunk-4NMATTXV.js → chunk-4BFRBP45.js} +2 -2
  58. package/dist/{chunk-L4UZBJNM.js → chunk-4P4626RY.js} +4 -4
  59. package/dist/{chunk-XZJMIJLW.js → chunk-5QZDMSSN.js} +29 -29
  60. package/dist/chunk-64UDOIMI.js +121 -0
  61. package/dist/chunk-64UDOIMI.js.map +1 -0
  62. package/dist/{chunk-H2EJNV2Z.js → chunk-66FLU5BT.js} +2 -2
  63. package/dist/{chunk-MWH5MAFP.js → chunk-6BISDEN6.js} +5 -5
  64. package/dist/{chunk-VBVSN2TV.js → chunk-6ZIETXZ2.js} +2 -2
  65. package/dist/{chunk-42BBIR33.js → chunk-7N2RUBAP.js} +2 -2
  66. package/dist/{chunk-UQI2NHTY.js → chunk-7QWMTOIF.js} +37 -37
  67. package/dist/{chunk-QICQ35K5.js → chunk-7Z2DZNWN.js} +17 -8
  68. package/dist/{chunk-QICQ35K5.js.map → chunk-7Z2DZNWN.js.map} +1 -1
  69. package/dist/{chunk-JI5AVXYO.js → chunk-AKUCU7CI.js} +5 -5
  70. package/dist/{chunk-FT37KV7R.js → chunk-BGTAL46Y.js} +2 -2
  71. package/dist/{chunk-7VFZTJ7K.js → chunk-BV2TIZ4F.js} +2 -2
  72. package/dist/{chunk-FWPHPN3V.js → chunk-CLX5VDNY.js} +5 -5
  73. package/dist/{chunk-TH2KIPP7.js → chunk-CZAA5N5S.js} +2 -2
  74. package/dist/{chunk-7BAJAXGB.js → chunk-CZAZX7JP.js} +4 -4
  75. package/dist/{chunk-APMGGGTY.js → chunk-DGYCWCQ7.js} +2 -2
  76. package/dist/{chunk-GEVIE5NV.js → chunk-DR6PEJU3.js} +4 -4
  77. package/dist/{chunk-2TCZE3C2.js → chunk-EPGWWCZF.js} +2 -2
  78. package/dist/{chunk-OUXPVWTZ.js → chunk-FLVL4L3L.js} +5 -5
  79. package/dist/{chunk-YPL66L3U.js → chunk-FZGGZAAU.js} +2 -2
  80. package/dist/{chunk-XOE5DYQL.js → chunk-HRCACVHL.js} +2 -2
  81. package/dist/{chunk-KS4PRHRD.js → chunk-IC6QX5TC.js} +2 -2
  82. package/dist/{chunk-H25TAODC.js → chunk-ILFJ7AVS.js} +4 -4
  83. package/dist/{chunk-UQMCR5RW.js → chunk-JLR3JSTL.js} +3 -3
  84. package/dist/{chunk-5H56JAHW.js → chunk-KCJ5H6XQ.js} +2 -2
  85. package/dist/{chunk-H5IT5JL7.js → chunk-LOR6ZM5R.js} +3 -3
  86. package/dist/{chunk-VBBMTSOV.js → chunk-M7GGMGMZ.js} +6 -6
  87. package/dist/{chunk-G47FEGGK.js → chunk-NG7YBCJO.js} +4 -4
  88. package/dist/{chunk-QGS7Z7YU.js → chunk-OHDJ34CC.js} +2 -2
  89. package/dist/{chunk-UAOZJX5J.js → chunk-P6SYWE4G.js} +53 -6
  90. package/dist/chunk-P6SYWE4G.js.map +1 -0
  91. package/dist/{chunk-LLMZK53N.js → chunk-PUBTHW5O.js} +2 -2
  92. package/dist/{chunk-AGNBY3VG.js → chunk-PYHOAXTI.js} +2 -2
  93. package/dist/{chunk-SXN3LCUT.js → chunk-RHOZ5LLO.js} +100 -4
  94. package/dist/chunk-RHOZ5LLO.js.map +1 -0
  95. package/dist/{chunk-6KYMPV2O.js → chunk-RL6NEVXT.js} +45 -2
  96. package/dist/chunk-RL6NEVXT.js.map +1 -0
  97. package/dist/{chunk-7WV3F5DQ.js → chunk-V7IZKJI3.js} +2 -2
  98. package/dist/{chunk-IGF4DII6.js → chunk-X4A62ET7.js} +9 -9
  99. package/dist/{chunk-OH6BKCPB.js → chunk-XN3TUUX2.js} +2 -2
  100. package/dist/{chunk-5K5VEDFY.js → chunk-YDSWRD33.js} +4 -4
  101. package/dist/{chunk-ABGDGBDT.js → chunk-YPEMNNNY.js} +2 -2
  102. package/dist/{chunk-YM7IEUO7.js → chunk-ZPOROUTX.js} +2 -2
  103. package/dist/{cli-Dfnc66gs.d.ts → cli-EiBBOt3r.d.ts} +3 -3
  104. package/dist/cli.d.ts +6 -6
  105. package/dist/cli.js +51 -50
  106. package/dist/compounding/engine.d.ts +1 -1
  107. package/dist/compounding/engine.js +5 -5
  108. package/dist/compounding/preference-consolidator.d.ts +1 -1
  109. package/dist/compression-optimizer.d.ts +1 -1
  110. package/dist/config.d.ts +1 -1
  111. package/dist/config.js +1 -1
  112. package/dist/connectors/codex-materialize-runner.d.ts +1 -1
  113. package/dist/connectors/codex-materialize-runner.js +5 -5
  114. package/dist/connectors/codex-materialize.d.ts +1 -1
  115. package/dist/connectors/index.d.ts +1 -1
  116. package/dist/connectors/index.js +5 -5
  117. package/dist/consolidation-provenance-check.d.ts +1 -1
  118. package/dist/consolidation-undo.d.ts +1 -1
  119. package/dist/contradiction/index.d.ts +1 -1
  120. package/dist/conversation-index/backend.d.ts +1 -1
  121. package/dist/conversation-index/backend.js +2 -2
  122. package/dist/conversation-index/chunker.d.ts +1 -1
  123. package/dist/conversation-index/faiss-adapter.d.ts +1 -1
  124. package/dist/conversation-index/indexer.d.ts +1 -1
  125. package/dist/conversation-index/search.d.ts +1 -1
  126. package/dist/day-summary.d.ts +1 -1
  127. package/dist/delinearize.d.ts +1 -1
  128. package/dist/direct-answer-wiring.d.ts +1 -1
  129. package/dist/direct-answer.d.ts +1 -1
  130. package/dist/embedding-fallback.d.ts +1 -1
  131. package/dist/enrichment/index.d.ts +1 -1
  132. package/dist/entity-retrieval.d.ts +1 -1
  133. package/dist/entity-retrieval.js +5 -5
  134. package/dist/entity-schema.d.ts +1 -1
  135. package/dist/explicit-capture.d.ts +4 -4
  136. package/dist/extraction-faithfulness.d.ts +1 -1
  137. package/dist/extraction-judge-telemetry.d.ts +1 -1
  138. package/dist/extraction-judge-training.d.ts +1 -1
  139. package/dist/extraction-judge.d.ts +1 -1
  140. package/dist/extraction.d.ts +1 -1
  141. package/dist/fallback-llm.d.ts +1 -1
  142. package/dist/graph-dashboard-diff.d.ts +1 -1
  143. package/dist/graph-dashboard-key.d.ts +1 -1
  144. package/dist/graph-dashboard-parser.d.ts +1 -1
  145. package/dist/graph-edge-reinforcement.d.ts +1 -1
  146. package/dist/graph-snapshot.d.ts +1 -1
  147. package/dist/graph.d.ts +1 -1
  148. package/dist/identity-continuity.d.ts +1 -1
  149. package/dist/importance.d.ts +1 -1
  150. package/dist/index.d.ts +17 -12
  151. package/dist/index.js +79 -78
  152. package/dist/intent.d.ts +1 -1
  153. package/dist/lcm/engine.d.ts +1 -1
  154. package/dist/lcm/engine.js +4 -4
  155. package/dist/lcm/index.d.ts +1 -1
  156. package/dist/lcm/index.js +6 -6
  157. package/dist/lcm/schema.js +2 -2
  158. package/dist/lcm/tools.d.ts +1 -1
  159. package/dist/lifecycle.d.ts +1 -1
  160. package/dist/live-connectors-runner.d.ts +1 -1
  161. package/dist/local-llm.d.ts +1 -1
  162. package/dist/local-model-endpoint.d.ts +1 -1
  163. package/dist/maintenance/memory-governance.d.ts +1 -1
  164. package/dist/maintenance/memory-governance.js +5 -5
  165. package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +5 -5
  166. package/dist/maintenance/rebuild-memory-projection.js +6 -6
  167. package/dist/mcp-memory-inspector-app.d.ts +5 -5
  168. package/dist/memory-action-policy.d.ts +1 -1
  169. package/dist/memory-cache.d.ts +1 -1
  170. package/dist/memory-lifecycle-ledger-utils.d.ts +1 -1
  171. package/dist/memory-projection-store.d.ts +23 -3
  172. package/dist/memory-projection-store.js +9 -2
  173. package/dist/memory-provenance.d.ts +1 -1
  174. package/dist/memory-worth-outcomes.d.ts +1 -1
  175. package/dist/models-json.d.ts +1 -1
  176. package/dist/namespaces/migrate.d.ts +2 -2
  177. package/dist/namespaces/migrate.js +17 -16
  178. package/dist/namespaces/principal.d.ts +1 -1
  179. package/dist/namespaces/search.d.ts +1 -1
  180. package/dist/namespaces/search.js +11 -10
  181. package/dist/namespaces/storage.d.ts +2 -2
  182. package/dist/namespaces/storage.js +5 -5
  183. package/dist/native-knowledge.d.ts +1 -1
  184. package/dist/operator-toolkit.d.ts +11 -2
  185. package/dist/operator-toolkit.js +23 -20
  186. package/dist/orchestration/compression-guideline-coordinator.d.ts +1 -1
  187. package/dist/orchestration/maintenance.d.ts +2 -2
  188. package/dist/orchestration/maintenance.js +7 -7
  189. package/dist/{orchestrator-Y9wyG4jK.d.ts → orchestrator-B2Y28Z9t.d.ts} +3 -3
  190. package/dist/orchestrator.d.ts +4 -4
  191. package/dist/orchestrator.js +63 -62
  192. package/dist/patterns-cli.d.ts +1 -1
  193. package/dist/policy-runtime.d.ts +1 -1
  194. package/dist/provenance.d.ts +1 -1
  195. package/dist/{purge-IHRX4Z7X.js → purge-NDWV6WYD.js} +4 -3
  196. package/dist/{purge-IHRX4Z7X.js.map → purge-NDWV6WYD.js.map} +1 -1
  197. package/dist/qmd-Db9mOkzB.d.ts +330 -0
  198. package/dist/qmd-preflight.d.ts +5 -0
  199. package/dist/qmd-preflight.js +31 -0
  200. package/dist/qmd-preflight.js.map +1 -0
  201. package/dist/qmd-recall-cache.d.ts +1 -1
  202. package/dist/qmd.d.ts +3 -305
  203. package/dist/qmd.js +10 -8
  204. package/dist/recall-disclosure-escalation.d.ts +1 -1
  205. package/dist/recall-explain-renderer.d.ts +1 -1
  206. package/dist/recall-planner-llm.d.ts +1 -1
  207. package/dist/recall-state.d.ts +1 -1
  208. package/dist/recall-tag-filter.d.ts +1 -1
  209. package/dist/recall-timings.d.ts +1 -1
  210. package/dist/recall-xray-cli.d.ts +1 -1
  211. package/dist/recall-xray-renderer.d.ts +1 -1
  212. package/dist/recall-xray.d.ts +1 -1
  213. package/dist/resolve-auth-token.d.ts +1 -1
  214. package/dist/resume-bundles.js +2 -2
  215. package/dist/retrieval-agents.d.ts +2 -2
  216. package/dist/retrieval-tiers.d.ts +1 -1
  217. package/dist/routing/engine.d.ts +1 -1
  218. package/dist/routing/store.d.ts +1 -1
  219. package/dist/runtime/better-sqlite.d.ts +17 -1
  220. package/dist/runtime/better-sqlite.js +5 -3
  221. package/dist/schemas.d.ts +84 -84
  222. package/dist/search/embed-helper.d.ts +1 -1
  223. package/dist/search/factory.d.ts +1 -1
  224. package/dist/search/factory.js +10 -9
  225. package/dist/search/index.d.ts +1 -1
  226. package/dist/search/index.js +15 -14
  227. package/dist/search/lancedb-backend.d.ts +1 -1
  228. package/dist/search/lancedb-backend.js +2 -2
  229. package/dist/search/meilisearch-backend.d.ts +1 -1
  230. package/dist/search/meilisearch-backend.js +2 -2
  231. package/dist/search/noop-backend.d.ts +1 -1
  232. package/dist/search/orama-backend.d.ts +1 -1
  233. package/dist/search/orama-backend.js +2 -2
  234. package/dist/search/port.d.ts +1 -1
  235. package/dist/search/remote-backend.d.ts +1 -1
  236. package/dist/{semantic-consolidation-bfXJNKVf.d.ts → semantic-consolidation-BDVIbLRw.d.ts} +1 -1
  237. package/dist/semantic-consolidation.d.ts +2 -2
  238. package/dist/semantic-consolidation.js +6 -6
  239. package/dist/semantic-rule-promotion.js +5 -5
  240. package/dist/semantic-rule-verifier.d.ts +1 -1
  241. package/dist/semantic-rule-verifier.js +5 -5
  242. package/dist/session-observer-bands.d.ts +1 -1
  243. package/dist/session-observer-state.d.ts +1 -1
  244. package/dist/session-transcript-migration.js +2 -2
  245. package/dist/shared-context/manager.d.ts +9 -9
  246. package/dist/signal.d.ts +1 -1
  247. package/dist/storage.d.ts +1 -1
  248. package/dist/storage.js +4 -4
  249. package/dist/summarizer.d.ts +1 -1
  250. package/dist/summary-snapshot.d.ts +1 -1
  251. package/dist/temporal-supersession.d.ts +1 -1
  252. package/dist/temporal-validity.d.ts +1 -1
  253. package/dist/threading.d.ts +1 -1
  254. package/dist/tier-migration.d.ts +1 -1
  255. package/dist/tier-routing.d.ts +1 -1
  256. package/dist/topics.d.ts +1 -1
  257. package/dist/transcript.d.ts +1 -1
  258. package/dist/transfer/autodetect.js +1 -1
  259. package/dist/transfer/backup.js +1 -1
  260. package/dist/transfer/capsule-export.js +2 -2
  261. package/dist/transfer/export-sqlite.js +2 -2
  262. package/dist/transfer/import-sqlite.js +2 -2
  263. package/dist/transfer/types.d.ts +66 -66
  264. package/dist/trust-score-stage.d.ts +1 -1
  265. package/dist/trust-score.d.ts +1 -1
  266. package/dist/{types-D1iBqhJA.d.ts → types-ljkd82rn.d.ts} +13 -1
  267. package/dist/types.d.ts +1 -1
  268. package/dist/utility-runtime.d.ts +1 -1
  269. package/dist/verified-recall.js +5 -5
  270. package/package.json +2 -2
  271. package/src/memory-projection-open-failure.test.ts +322 -0
  272. package/src/memory-projection-store.ts +179 -2
  273. package/src/operator-toolkit.ts +64 -0
  274. package/src/qmd-preflight.test.ts +197 -0
  275. package/src/qmd-preflight.ts +167 -0
  276. package/src/qmd.ts +115 -129
  277. package/src/runtime/better-sqlite.test.ts +54 -0
  278. package/src/runtime/better-sqlite.ts +83 -3
  279. package/src/wearables/cleanup.test.ts +56 -0
  280. package/src/wearables/cleanup.ts +22 -1
  281. package/src/wearables/config.test.ts +25 -0
  282. package/src/wearables/config.ts +31 -4
  283. package/src/wearables/types.ts +12 -1
  284. package/dist/chunk-64ZYXSCO.js.map +0 -1
  285. package/dist/chunk-6KYMPV2O.js.map +0 -1
  286. package/dist/chunk-RBGL4MKQ.js.map +0 -1
  287. package/dist/chunk-SXN3LCUT.js.map +0 -1
  288. package/dist/chunk-UAOZJX5J.js.map +0 -1
  289. /package/dist/{auto-sync-M47VTCFI.js.map → auto-sync-HMHQHUAG.js.map} +0 -0
  290. /package/dist/{chunk-TKUQSXU4.js.map → chunk-27FLVMDC.js.map} +0 -0
  291. /package/dist/{chunk-XXEOGY77.js.map → chunk-2FOD2YY3.js.map} +0 -0
  292. /package/dist/{chunk-6Y4JTNX7.js.map → chunk-2MR3MFQB.js.map} +0 -0
  293. /package/dist/{chunk-AU7Q3LSC.js.map → chunk-2QSZNTDO.js.map} +0 -0
  294. /package/dist/{chunk-4NMATTXV.js.map → chunk-4BFRBP45.js.map} +0 -0
  295. /package/dist/{chunk-L4UZBJNM.js.map → chunk-4P4626RY.js.map} +0 -0
  296. /package/dist/{chunk-XZJMIJLW.js.map → chunk-5QZDMSSN.js.map} +0 -0
  297. /package/dist/{chunk-H2EJNV2Z.js.map → chunk-66FLU5BT.js.map} +0 -0
  298. /package/dist/{chunk-MWH5MAFP.js.map → chunk-6BISDEN6.js.map} +0 -0
  299. /package/dist/{chunk-VBVSN2TV.js.map → chunk-6ZIETXZ2.js.map} +0 -0
  300. /package/dist/{chunk-42BBIR33.js.map → chunk-7N2RUBAP.js.map} +0 -0
  301. /package/dist/{chunk-UQI2NHTY.js.map → chunk-7QWMTOIF.js.map} +0 -0
  302. /package/dist/{chunk-JI5AVXYO.js.map → chunk-AKUCU7CI.js.map} +0 -0
  303. /package/dist/{chunk-FT37KV7R.js.map → chunk-BGTAL46Y.js.map} +0 -0
  304. /package/dist/{chunk-7VFZTJ7K.js.map → chunk-BV2TIZ4F.js.map} +0 -0
  305. /package/dist/{chunk-FWPHPN3V.js.map → chunk-CLX5VDNY.js.map} +0 -0
  306. /package/dist/{chunk-TH2KIPP7.js.map → chunk-CZAA5N5S.js.map} +0 -0
  307. /package/dist/{chunk-7BAJAXGB.js.map → chunk-CZAZX7JP.js.map} +0 -0
  308. /package/dist/{chunk-APMGGGTY.js.map → chunk-DGYCWCQ7.js.map} +0 -0
  309. /package/dist/{chunk-GEVIE5NV.js.map → chunk-DR6PEJU3.js.map} +0 -0
  310. /package/dist/{chunk-2TCZE3C2.js.map → chunk-EPGWWCZF.js.map} +0 -0
  311. /package/dist/{chunk-OUXPVWTZ.js.map → chunk-FLVL4L3L.js.map} +0 -0
  312. /package/dist/{chunk-YPL66L3U.js.map → chunk-FZGGZAAU.js.map} +0 -0
  313. /package/dist/{chunk-XOE5DYQL.js.map → chunk-HRCACVHL.js.map} +0 -0
  314. /package/dist/{chunk-KS4PRHRD.js.map → chunk-IC6QX5TC.js.map} +0 -0
  315. /package/dist/{chunk-H25TAODC.js.map → chunk-ILFJ7AVS.js.map} +0 -0
  316. /package/dist/{chunk-UQMCR5RW.js.map → chunk-JLR3JSTL.js.map} +0 -0
  317. /package/dist/{chunk-5H56JAHW.js.map → chunk-KCJ5H6XQ.js.map} +0 -0
  318. /package/dist/{chunk-H5IT5JL7.js.map → chunk-LOR6ZM5R.js.map} +0 -0
  319. /package/dist/{chunk-VBBMTSOV.js.map → chunk-M7GGMGMZ.js.map} +0 -0
  320. /package/dist/{chunk-G47FEGGK.js.map → chunk-NG7YBCJO.js.map} +0 -0
  321. /package/dist/{chunk-QGS7Z7YU.js.map → chunk-OHDJ34CC.js.map} +0 -0
  322. /package/dist/{chunk-LLMZK53N.js.map → chunk-PUBTHW5O.js.map} +0 -0
  323. /package/dist/{chunk-AGNBY3VG.js.map → chunk-PYHOAXTI.js.map} +0 -0
  324. /package/dist/{chunk-7WV3F5DQ.js.map → chunk-V7IZKJI3.js.map} +0 -0
  325. /package/dist/{chunk-IGF4DII6.js.map → chunk-X4A62ET7.js.map} +0 -0
  326. /package/dist/{chunk-OH6BKCPB.js.map → chunk-XN3TUUX2.js.map} +0 -0
  327. /package/dist/{chunk-5K5VEDFY.js.map → chunk-YDSWRD33.js.map} +0 -0
  328. /package/dist/{chunk-ABGDGBDT.js.map → chunk-YPEMNNNY.js.map} +0 -0
  329. /package/dist/{chunk-YM7IEUO7.js.map → chunk-ZPOROUTX.js.map} +0 -0
@@ -0,0 +1,322 @@
1
+ import assert from "node:assert/strict";
2
+ import { mkdir, mkdtemp, rm, writeFile } from "node:fs/promises";
3
+ import os from "node:os";
4
+ import path from "node:path";
5
+ import test from "node:test";
6
+
7
+ import { initLogger } from "./logger.js";
8
+ import { rebuildMemoryProjection } from "./maintenance/rebuild-memory-projection.js";
9
+ import {
10
+ __resetProjectionOpenFailureSuppressionForTest,
11
+ __setProjectionReadonlyOpenerForTest,
12
+ getMemoryProjectionPath,
13
+ probeProjectionHealth,
14
+ readProjectedMemoryBrowse,
15
+ } from "./memory-projection-store.js";
16
+ import { summarizeProjectionHealth } from "./operator-toolkit.js";
17
+ import { openBetterSqlite3 } from "./runtime/better-sqlite.js";
18
+ import { StorageManager } from "./storage.js";
19
+
20
+ // Serialized: every test mutates the process-global logger backend and the
21
+ // module-level projection opener / rate-limit map. concurrency:false keeps
22
+ // them from racing each other.
23
+ const SERIAL = { concurrency: false } as const;
24
+
25
+ // The opener seam expects the real opener signature; derive it without
26
+ // importing the (unexported) alias.
27
+ type ProjectionOpener = NonNullable<Parameters<typeof __setProjectionReadonlyOpenerForTest>[0]>;
28
+
29
+ function abiStyleError(): Error {
30
+ // Mirrors the real native-binding mismatch better-sqlite3 throws when its
31
+ // addon was compiled for a different Node.js ABI (issue #1829 root cause).
32
+ // Classified as a native mismatch by isLikelyBetterSqlite3NativeBindingError.
33
+ return new Error(
34
+ "The module was compiled against a different Node.js version using NODE_MODULE_VERSION 127. " +
35
+ "This version of Node.js requires NODE_MODULE_VERSION 137."
36
+ );
37
+ }
38
+
39
+ function captureLogs(): { warns: string[]; errors: string[]; restore: () => void } {
40
+ const warns: string[] = [];
41
+ const errors: string[] = [];
42
+ initLogger({
43
+ info() {},
44
+ warn(msg) {
45
+ warns.push(msg);
46
+ },
47
+ error(msg) {
48
+ errors.push(msg);
49
+ },
50
+ debug() {},
51
+ });
52
+ return { warns, errors, restore: () => initLogger() };
53
+ }
54
+
55
+ test("absent projection index → quiet fallback, no error log, probe reports absent", SERIAL, async () => {
56
+ const dir = await mkdtemp(path.join(os.tmpdir(), "remnic-proj-absent-"));
57
+ const { warns, restore } = captureLogs();
58
+ try {
59
+ // No projection file exists — the normal cold-install / never-built case.
60
+ assert.equal(probeProjectionHealth(dir).state, "absent");
61
+ assert.equal(readProjectedMemoryBrowse(dir, { limit: 5, offset: 0 }), null);
62
+ // The distinguishing guarantee from issue #1829: a MISSING file must stay
63
+ // quiet (no error spam). Only a PRESENT-but-unopenable file logs.
64
+ assert.equal(warns.length, 0);
65
+ } finally {
66
+ restore();
67
+ await rm(dir, { recursive: true, force: true });
68
+ }
69
+ });
70
+
71
+ test("present index + ABI-style open failure → distinct rate-limited warn + null + doctor signal", SERIAL, async () => {
72
+ const dir = await mkdtemp(path.join(os.tmpdir(), "remnic-proj-abi-"));
73
+ const { warns, restore } = captureLogs();
74
+ __resetProjectionOpenFailureSuppressionForTest();
75
+ try {
76
+ // Build a real projection so the index file genuinely EXISTS on disk;
77
+ // then inject an opener that throws the wrong-ABI error on every open.
78
+ await rebuildMemoryProjection({ memoryDir: dir, dryRun: false });
79
+ assert.ok(getMemoryProjectionPath(dir));
80
+ const throwingOpener: ProjectionOpener = () => {
81
+ throw abiStyleError();
82
+ };
83
+ const restoreOpener = __setProjectionReadonlyOpenerForTest(throwingOpener);
84
+
85
+ try {
86
+ // The read path returns null (full-scan fallback trigger preserved),
87
+ // but now emits the DISTINCT real-error warn that was missing before.
88
+ const first = readProjectedMemoryBrowse(dir, { limit: 5, offset: 0 });
89
+ assert.equal(first, null);
90
+ assert.equal(warns.length, 1);
91
+ const msg = warns[0];
92
+ assert.ok(msg, "expected a distinct projection open-failure warn");
93
+ assert.match(msg, /present but could not be opened/);
94
+ assert.match(msg, /wrong Node\.js ABI/);
95
+
96
+ // Doctor lens classifies the same failure.
97
+ const probe = probeProjectionHealth(dir);
98
+ assert.equal(probe.state, "unopenable");
99
+ assert.equal(probe.nativeBindingMismatch, true);
100
+
101
+ // Rate limit: a second open within the window is suppressed.
102
+ const second = readProjectedMemoryBrowse(dir, { limit: 5, offset: 0 });
103
+ assert.equal(second, null);
104
+ assert.equal(warns.length, 1, "rate limit must suppress the repeat warn");
105
+
106
+ // After resetting suppression, the warn fires again.
107
+ __resetProjectionOpenFailureSuppressionForTest();
108
+ readProjectedMemoryBrowse(dir, { limit: 5, offset: 0 });
109
+ assert.equal(warns.length, 2, "warn fires again after suppression reset");
110
+ } finally {
111
+ restoreOpener();
112
+ }
113
+ } finally {
114
+ restore();
115
+ await rm(dir, { recursive: true, force: true });
116
+ }
117
+ });
118
+
119
+ test("present index + WRAPPED ABI open failure (cause = native-binding error) → still reports the native-binding hint", SERIAL, async () => {
120
+ const dir = await mkdtemp(path.join(os.tmpdir(), "remnic-proj-wrapped-abi-"));
121
+ const { warns, restore } = captureLogs();
122
+ __resetProjectionOpenFailureSuppressionForTest();
123
+ try {
124
+ await rebuildMemoryProjection({ memoryDir: dir, dryRun: false });
125
+ // Mirrors the production path: loadBetterSqlite3() catches the ABI error
126
+ // and re-throws a sanitized unavailableError WRAPPER whose message drops
127
+ // the ABI markers. The original error survives only on .cause. Before
128
+ // #1848 the classifier inspected only the wrapper's message and MISSED the
129
+ // mismatch, so the warn and doctor check wrongly suggested rebuilding the
130
+ // projection instead of better-sqlite3.
131
+ const throwingOpener: ProjectionOpener = () => {
132
+ throw new Error(
133
+ "better-sqlite3 is unavailable. Remnic attempted to load the native SQLite binding and could not.",
134
+ { cause: abiStyleError() },
135
+ );
136
+ };
137
+ const restoreOpener = __setProjectionReadonlyOpenerForTest(throwingOpener);
138
+
139
+ try {
140
+ // Browse warn carries the native-binding ABI hint (not the projection-rebuild hint).
141
+ const first = readProjectedMemoryBrowse(dir, { limit: 5, offset: 0 });
142
+ assert.equal(first, null);
143
+ assert.equal(warns.length, 1);
144
+ assert.match(warns[0], /wrong Node\.js ABI/);
145
+ assert.doesNotMatch(warns[0], /rebuild-memory-projection/);
146
+
147
+ // Doctor lens classifies the SAME wrapped failure as a native-binding mismatch.
148
+ const probe = probeProjectionHealth(dir);
149
+ assert.equal(probe.state, "unopenable");
150
+ assert.equal(probe.nativeBindingMismatch, true);
151
+ } finally {
152
+ restoreOpener();
153
+ }
154
+ } finally {
155
+ restore();
156
+ await rm(dir, { recursive: true, force: true });
157
+ }
158
+ });
159
+
160
+ test("present index + non-ABI open failure → distinct warn without the native-binding hint", SERIAL, async () => {
161
+ const dir = await mkdtemp(path.join(os.tmpdir(), "remnic-proj-cantopen-"));
162
+ const { warns, restore } = captureLogs();
163
+ __resetProjectionOpenFailureSuppressionForTest();
164
+ try {
165
+ await rebuildMemoryProjection({ memoryDir: dir, dryRun: false });
166
+ // A corrupt-file / permission style failure: carries a stable SQLite
167
+ // code but is NOT a native-binding mismatch. Tests the failure CLASS,
168
+ // not just the reported ABI instance (hardening playbook).
169
+ const cantOpen = Object.assign(new Error("unable to open database file"), {
170
+ code: "SQLITE_CANTOPEN",
171
+ });
172
+ const restoreOpener = __setProjectionReadonlyOpenerForTest((() => {
173
+ throw cantOpen;
174
+ }) as ProjectionOpener);
175
+ try {
176
+ assert.equal(readProjectedMemoryBrowse(dir, { limit: 5, offset: 0 }), null);
177
+ assert.equal(warns.length, 1);
178
+ const msg = warns[0];
179
+ assert.ok(msg, "expected a distinct projection open-failure warn");
180
+ assert.match(msg, /present but could not be opened/);
181
+ assert.match(msg, /SQLITE_CANTOPEN/);
182
+ assert.doesNotMatch(msg, /native binding/);
183
+ const probe = probeProjectionHealth(dir);
184
+ assert.equal(probe.state, "unopenable");
185
+ assert.equal(probe.nativeBindingMismatch, false);
186
+ } finally {
187
+ restoreOpener();
188
+ }
189
+ } finally {
190
+ restore();
191
+ await rm(dir, { recursive: true, force: true });
192
+ }
193
+ });
194
+
195
+ test("full-scan fallback still returns results when the projection cannot serve", SERIAL, async () => {
196
+ const dir = await mkdtemp(path.join(os.tmpdir(), "remnic-proj-fullscan-"));
197
+ const { restore } = captureLogs();
198
+ try {
199
+ const storage = new StorageManager(dir);
200
+ const a = await storage.writeMemory("fact", "alpha synthetic fact", { source: "test" });
201
+ const b = await storage.writeMemory("fact", "beta synthetic fact", { source: "test" });
202
+
203
+ // Absent projection: the browse returns null (the trigger the access
204
+ // layer keys on to run its full-corpus scan) ...
205
+ assert.equal(readProjectedMemoryBrowse(dir, { limit: 10, offset: 0 }), null);
206
+
207
+ // ... and the full-corpus data source the access layer scans still
208
+ // holds every memory (issue #1829: the fallback must keep serving).
209
+ const full = [...(await storage.readAllMemories()), ...(await storage.readArchivedMemories())];
210
+ const ids = new Set(full.map((m) => m.frontmatter.id));
211
+ assert.ok(ids.has(a.id));
212
+ assert.ok(ids.has(b.id));
213
+ assert.equal(full.length, 2);
214
+
215
+ // Regression guard for the exact #1829 failure: build the projection,
216
+ // then make it unopenable, and confirm the null trigger STILL fires so
217
+ // the full scan is reached (not a throw, not a stale cached page).
218
+ await rebuildMemoryProjection({ memoryDir: dir, dryRun: false });
219
+ const restoreOpener = __setProjectionReadonlyOpenerForTest((() => {
220
+ throw abiStyleError();
221
+ }) as ProjectionOpener);
222
+ try {
223
+ assert.equal(
224
+ readProjectedMemoryBrowse(dir, { limit: 10, offset: 0 }),
225
+ null,
226
+ "unopenable projection must return null so the full scan runs"
227
+ );
228
+ } finally {
229
+ restoreOpener();
230
+ }
231
+ } finally {
232
+ restore();
233
+ await rm(dir, { recursive: true, force: true });
234
+ }
235
+ });
236
+
237
+ test("present + valid projection → probe openable + doctor ok (schema validation passes)", SERIAL, async () => {
238
+ const dir = await mkdtemp(path.join(os.tmpdir(), "remnic-proj-healthy-"));
239
+ try {
240
+ await rebuildMemoryProjection({ memoryDir: dir, dryRun: false });
241
+ const probe = probeProjectionHealth(dir);
242
+ assert.equal(probe.state, "openable");
243
+ assert.equal(probe.nativeBindingMismatch, false);
244
+ // Doctor lens reports OK for a schema-valid projection (issue #1848 round 2).
245
+ const check = summarizeProjectionHealth({ memoryDir: dir });
246
+ assert.equal(check.status, "ok");
247
+ assert.match(check.summary, /opens cleanly/i);
248
+ } finally {
249
+ await rm(dir, { recursive: true, force: true });
250
+ }
251
+ });
252
+
253
+ test("present projection with MISSING schema tables → probe present-but-invalid (doctor non-ok, rebuild hint)", SERIAL, async () => {
254
+ const dir = await mkdtemp(path.join(os.tmpdir(), "remnic-proj-no-tables-"));
255
+ try {
256
+ // Create a sqlite file that OPENS cleanly but has NO projection tables
257
+ // (mirrors a half-built / stale / externally-created .sqlite that opens
258
+ // but cannot serve any browse query — issue #1848 round 2 root cause).
259
+ const dbPath = getMemoryProjectionPath(dir);
260
+ await mkdir(path.dirname(dbPath), { recursive: true });
261
+ const empty = openBetterSqlite3(dbPath); // creates a valid empty db
262
+ empty.close();
263
+
264
+ // Before #1848 round 2 this reported "openable" → doctor said ok while
265
+ // browse silently fell back to full-corpus scans on every memory list.
266
+ const probe = probeProjectionHealth(dir);
267
+ assert.equal(probe.state, "present-but-invalid");
268
+ assert.equal(probe.nativeBindingMismatch, false);
269
+ assert.ok(probe.detail, "expected a path-free detail for the schema failure");
270
+
271
+ // Doctor lens surfaces this as an ERROR with a rebuild hint — NOT ok.
272
+ const check = summarizeProjectionHealth({ memoryDir: dir });
273
+ assert.equal(check.status, "error");
274
+ assert.match(check.summary, /missing or unreadable/i);
275
+ assert.ok(check.remediation);
276
+ assert.match(check.remediation, /rebuild-memory-projection/);
277
+
278
+ // Browse still returns null (full-scan fallback trigger) — the projection
279
+ // is unusable, so the access layer must reach the full corpus.
280
+ assert.equal(readProjectedMemoryBrowse(dir, { limit: 5, offset: 0 }), null);
281
+ } finally {
282
+ await rm(dir, { recursive: true, force: true });
283
+ }
284
+ });
285
+
286
+ test("present projection with memory_current MISSING required columns → probe present-but-invalid (doctor non-ok, rebuild hint)", SERIAL, async () => {
287
+ const dir = await mkdtemp(path.join(os.tmpdir(), "remnic-proj-no-cols-"));
288
+ try {
289
+ // A sqlite file that opens cleanly AND has a `memory_current` table, but
290
+ // the table is stale/half-built with none of the columns
291
+ // readProjectedMemoryBrowse SELECTs (issue #1848 round 3 root cause). The
292
+ // round-2 check only verified the table exists + answers `SELECT 1`, so a
293
+ // file like `CREATE TABLE memory_current (x INTEGER)` was reported
294
+ // "openable" → doctor said healthy while browse threw on the missing
295
+ // memory_id/path_rel/category/... columns and silently fell back.
296
+ const dbPath = getMemoryProjectionPath(dir);
297
+ await mkdir(path.dirname(dbPath), { recursive: true });
298
+ const stale = openBetterSqlite3(dbPath); // creates a valid db
299
+ stale.exec("CREATE TABLE memory_current (x INTEGER)");
300
+ stale.close();
301
+
302
+ // Before round 3 this reported "openable" → doctor said ok.
303
+ const probe = probeProjectionHealth(dir);
304
+ assert.equal(probe.state, "present-but-invalid");
305
+ assert.equal(probe.nativeBindingMismatch, false);
306
+ // displayErrorDetail surfaces only the path-free error class (ProjectionSchemaError).
307
+ assert.ok(probe.detail, "expected a path-free detail for the missing-column schema failure");
308
+
309
+ // Doctor lens surfaces this as an ERROR with a rebuild hint — NOT ok.
310
+ const check = summarizeProjectionHealth({ memoryDir: dir });
311
+ assert.equal(check.status, "error");
312
+ assert.match(check.summary, /missing or unreadable/i);
313
+ assert.ok(check.remediation);
314
+ assert.match(check.remediation, /rebuild-memory-projection/);
315
+
316
+ // Browse still returns null (full-scan fallback trigger) — the projection
317
+ // cannot serve queries missing the browse columns.
318
+ assert.equal(readProjectedMemoryBrowse(dir, { limit: 5, offset: 0 }), null);
319
+ } finally {
320
+ await rm(dir, { recursive: true, force: true });
321
+ }
322
+ });
@@ -12,7 +12,10 @@ import type {
12
12
  MemoryProjectionCurrentState,
13
13
  MemoryStatus,
14
14
  } from "./types.js";
15
+ import { log } from "./logger.js";
15
16
  import {
17
+ displayErrorDetail,
18
+ isLikelyBetterSqlite3NativeBindingError,
16
19
  openBetterSqlite3,
17
20
  type BetterSqlite3Database,
18
21
  } from "./runtime/better-sqlite.js";
@@ -314,14 +317,188 @@ export function initializeMemoryProjectionDb(db: BetterSqlite3Database): void {
314
317
  .run("schemaVersion", String(MEMORY_PROJECTION_SCHEMA_VERSION));
315
318
  }
316
319
 
320
+ // Test seam (issue #1829): the readonly projection opener is overridable so
321
+ // tests can inject an ABI-style throw against a PRESENT file without faking a
322
+ // corrupt native binding. Default delegates to the shared opener.
323
+ type ProjectionReadonlyOpener = typeof openBetterSqlite3;
324
+ let projectionReadonlyOpener: ProjectionReadonlyOpener = openBetterSqlite3;
325
+
326
+ /** @internal Test seam: override the readonly projection opener; returns a restore fn. */
327
+ export function __setProjectionReadonlyOpenerForTest(
328
+ opener: ProjectionReadonlyOpener | null,
329
+ ): () => void {
330
+ const previous = projectionReadonlyOpener;
331
+ projectionReadonlyOpener = opener ?? openBetterSqlite3;
332
+ return () => {
333
+ projectionReadonlyOpener = previous;
334
+ };
335
+ }
336
+
337
+ // Rate-limited suppression for open failures — mirrors warnProjectionFallback
338
+ // in storage-guards.ts (5-min dedup per memoryDir). A present-but-unopenable
339
+ // projection is a real failure that must surface, but it repeats on every
340
+ // browse, so the log is throttled to keep output actionable (issue #1829).
341
+ const PROJECTION_OPEN_FAILURE_LOG_INTERVAL_MS = 5 * 60_000;
342
+ const projectionOpenFailureLoggedAt = new Map<string, number>();
343
+
344
+ /** @internal Test seam: clear the rate-limit dedup map. */
345
+ export function __resetProjectionOpenFailureSuppressionForTest(): void {
346
+ projectionOpenFailureLoggedAt.clear();
347
+ }
348
+
349
+ function logProjectionOpenFailure(memoryDir: string, error: unknown): void {
350
+ const now = Date.now();
351
+ const warnedAt = projectionOpenFailureLoggedAt.get(memoryDir) ?? 0;
352
+ if (now - warnedAt < PROJECTION_OPEN_FAILURE_LOG_INTERVAL_MS) return;
353
+ projectionOpenFailureLoggedAt.set(memoryDir, now);
354
+ // Path-free detail only (rule 11 / CodeQL js/stack-trace-exposure): the raw
355
+ // message can embed absolute loader paths; displayErrorDetail returns class +
356
+ // code. This is the DISTINCT signal that was missing before #1829, when a
357
+ // wrong-ABI build collapsed into the same silent null as a missing file.
358
+ const detail = displayErrorDetail(error);
359
+ const nativeMismatch = isLikelyBetterSqlite3NativeBindingError(error);
360
+ const detailSuffix = detail ? ` (${detail})` : "";
361
+ const abiSuffix = nativeMismatch
362
+ ? " — native binding built for the wrong Node.js ABI; rebuild better-sqlite3 for this process"
363
+ : "";
364
+ log.warn(
365
+ `storage.memory-projection: index present but could not be opened${detailSuffix}${abiSuffix}; falling back to full-corpus scan`,
366
+ );
367
+ }
368
+
317
369
  function openProjectionReadonly(memoryDir: string): BetterSqlite3Database | null {
318
370
  const dbPath = getMemoryProjectionPath(memoryDir);
371
+ // A MISSING file is a normal fallback (cold install, never built). A file
372
+ // that EXISTS but cannot open (wrong ABI / corrupt / bad perms) is a real
373
+ // failure that must surface (issue #1829): previously both collapsed into
374
+ // the same silent null, hiding native-binding mismatches behind 17-27s
375
+ // full-corpus scans on every memory list.
376
+ if (!fs.existsSync(dbPath)) return null;
319
377
  try {
320
- return openBetterSqlite3(dbPath, { readonly: true, fileMustExist: true });
321
- } catch {
378
+ return projectionReadonlyOpener(dbPath, { readonly: true, fileMustExist: true });
379
+ } catch (error) {
380
+ logProjectionOpenFailure(memoryDir, error);
322
381
  return null;
323
382
  }
324
383
  }
384
+
385
+ export type ProjectionHealthState = "absent" | "openable" | "present-but-invalid" | "unopenable";
386
+
387
+ export interface ProjectionHealthProbe {
388
+ state: ProjectionHealthState;
389
+ /** Path-free detail (class + code) when state !== "absent"/"openable"; "" otherwise. */
390
+ detail: string;
391
+ /** true when the open failure is classified as a native-binding ABI mismatch. */
392
+ nativeBindingMismatch: boolean;
393
+ }
394
+
395
+ /**
396
+ * Confirm the projection schema is present and queryable, not just that the
397
+ * sqlite file opens (issue #1848 round 2). A file that opens but has the
398
+ * projection tables missing — or is corrupt and only fails on first statement
399
+ * — previously reported `openable`/ok, so `remnic doctor` said healthy while
400
+ * browse silently fell back to full-corpus scans. `memory_current` is the
401
+ * core browse table; if it is absent or unreadable the projection cannot
402
+ * serve any query. Throws so the probe can report a distinct state.
403
+ */
404
+ // Named so displayErrorDetail surfaces a clear, path-free identifier in the
405
+ // doctor report rather than a generic "Error".
406
+ class ProjectionSchemaError extends Error {
407
+ constructor(message: string) {
408
+ super(message);
409
+ this.name = "ProjectionSchemaError";
410
+ }
411
+ }
412
+
413
+ /**
414
+ * Columns `readProjectedMemoryBrowse` SELECTs from `memory_current` with no
415
+ * graceful fallback. `tags_json`/`preview_text` fall back via
416
+ * memoryCurrentSelectExpressions and `path_valid` falls back via the legacy
417
+ * full-scan branch — none of those make a browse query throw. A projection
418
+ * whose `memory_current` lacks any of these columns cannot serve browse
419
+ * queries: the SELECT throws and browse silently falls back to a full-corpus
420
+ * scan. Enumerated directly from readProjectedMemoryBrowse's SELECT list +
421
+ * ORDER BY references (issue #1848 round 3).
422
+ */
423
+ const REQUIRED_MEMORY_CURRENT_COLUMNS = [
424
+ "memory_id",
425
+ "path_rel",
426
+ "category",
427
+ "status",
428
+ "created_at",
429
+ "updated_at",
430
+ "entity_ref",
431
+ ] as const;
432
+
433
+ function assertProjectionSchemaReadable(db: BetterSqlite3Database): void {
434
+ const row = db
435
+ .prepare(
436
+ "SELECT name FROM sqlite_master WHERE type = 'table' AND name = 'memory_current'",
437
+ )
438
+ .get() as { name: string } | undefined;
439
+ if (!row?.name) {
440
+ throw new ProjectionSchemaError(
441
+ "projection schema is missing or was not initialized (memory_current table not found)",
442
+ );
443
+ }
444
+ // Round 3 (issue #1848): verify the columns readProjectedMemoryBrowse
445
+ // actually SELECT are present, not just that the table exists. A file like
446
+ // `CREATE TABLE memory_current (x INTEGER)` passed the round-2 table-existence
447
+ // + SELECT-1 check but cannot serve browse queries, so browse silently fell
448
+ // back to full-corpus scans while `remnic doctor` reported healthy. Classify
449
+ // such a stale/half-built table as present-but-invalid so the operator gets a
450
+ // rebuild hint instead of a false-clean bill of health.
451
+ const columns = listTableColumns(db, "memory_current");
452
+ const missing = REQUIRED_MEMORY_CURRENT_COLUMNS.filter((c) => !columns.has(c));
453
+ if (missing.length > 0) {
454
+ throw new ProjectionSchemaError(
455
+ `projection memory_current table is missing required column(s): ${missing.join(", ")}`,
456
+ );
457
+ }
458
+ // Also confirm the table is queryable — catches physical corruption that
459
+ // only surfaces on first read (the file opens but statements fail).
460
+ db.prepare("SELECT 1 FROM memory_current LIMIT 1").get();
461
+ }
462
+
463
+ /**
464
+ * Stat + attempt-open + schema-validate the memory projection WITHOUT keeping
465
+ * a handle, for the `remnic doctor` health check (issue #1829 / #1848).
466
+ * Distinguishes four states so operators can tell "not built yet" (normal) from
467
+ * "exists but broken" (needs a rebuild / native-binding rebuild). Never throws.
468
+ */
469
+ export function probeProjectionHealth(memoryDir: string): ProjectionHealthProbe {
470
+ const dbPath = getMemoryProjectionPath(memoryDir);
471
+ if (!fs.existsSync(dbPath)) {
472
+ return { state: "absent", detail: "", nativeBindingMismatch: false };
473
+ }
474
+ let db: BetterSqlite3Database | null = null;
475
+ try {
476
+ db = projectionReadonlyOpener(dbPath, { readonly: true, fileMustExist: true });
477
+ // Validate the projection is actually USABLE, not just that the file
478
+ // opens (issue #1848 round 2): a file that opens but has the schema
479
+ // missing/corrupt previously reported ok, so doctor said healthy while
480
+ // browse silently fell back to full-corpus scans.
481
+ assertProjectionSchemaReadable(db);
482
+ return { state: "openable", detail: "", nativeBindingMismatch: false };
483
+ } catch (error) {
484
+ // If the file OPENED (db assigned) but the schema query failed, the
485
+ // native binding is fine — the projection itself is stale/corrupt.
486
+ if (db) {
487
+ return {
488
+ state: "present-but-invalid",
489
+ detail: displayErrorDetail(error),
490
+ nativeBindingMismatch: false,
491
+ };
492
+ }
493
+ return {
494
+ state: "unopenable",
495
+ detail: displayErrorDetail(error),
496
+ nativeBindingMismatch: isLikelyBetterSqlite3NativeBindingError(error),
497
+ };
498
+ } finally {
499
+ db?.close();
500
+ }
501
+ }
325
502
  function updateProjectionBestEffort(
326
503
  memoryDir: string,
327
504
  sql: string,
@@ -11,6 +11,7 @@ import {
11
11
  resolveOpenClawWorkspaceStatePath,
12
12
  } from "./native-knowledge.js";
13
13
  import { StorageManager } from "./storage.js";
14
+ import { probeProjectionHealth } from "./memory-projection-store.js";
14
15
  import { listNamespaces } from "./namespaces/migrate.js";
15
16
  import { summarizeNamespaceMaintenanceHealth } from "./maintenance/namespace-maintenance-fanout.js";
16
17
  import {
@@ -1094,6 +1095,64 @@ export function summarizeProvenanceCoverage(
1094
1095
  return { counts, total: memories.length };
1095
1096
  }
1096
1097
 
1098
+ /**
1099
+ * Memory projection health (issue #1829). A present-but-unopenable projection
1100
+ * (wrong ABI / corrupt / bad perms) previously collapsed into the same silent
1101
+ * null as a missing file, hiding native-binding mismatches behind full-corpus
1102
+ * scans. This check stats + attempt-opens the projection so `remnic doctor`
1103
+ * distinguishes "not built yet" (normal) from "exists but broken" (needs a
1104
+ * rebuild / native-binding rebuild). Never throws.
1105
+ */
1106
+ export function summarizeProjectionHealth(
1107
+ config: Pick<PluginConfig, "memoryDir">,
1108
+ ): OperatorDoctorCheck {
1109
+ const probe = probeProjectionHealth(config.memoryDir);
1110
+ if (probe.state === "absent") {
1111
+ return {
1112
+ key: "memory_projection",
1113
+ status: "ok",
1114
+ summary: "Memory projection index not built yet (normal until first maintenance run).",
1115
+ details: { state: probe.state },
1116
+ };
1117
+ }
1118
+ if (probe.state === "openable") {
1119
+ return {
1120
+ key: "memory_projection",
1121
+ status: "ok",
1122
+ summary: "Memory projection index opens cleanly.",
1123
+ details: { state: probe.state },
1124
+ };
1125
+ }
1126
+ if (probe.state === "present-but-invalid") {
1127
+ const schemaDetailSuffix = probe.detail ? ` (${probe.detail})` : "";
1128
+ return {
1129
+ key: "memory_projection",
1130
+ status: "error",
1131
+ summary: `Memory projection index exists but its schema is missing or unreadable${schemaDetailSuffix}; memory browse is falling back to full-corpus scans.`,
1132
+ remediation:
1133
+ "Rebuild the projection with `remnic rebuild-memory-projection --write`.",
1134
+ details: { state: probe.state, detail: probe.detail },
1135
+ };
1136
+ }
1137
+ const nativeSuffix = probe.nativeBindingMismatch
1138
+ ? " Native binding appears built for the wrong Node.js ABI."
1139
+ : "";
1140
+ const detailSuffix = probe.detail ? ` (${probe.detail})` : "";
1141
+ return {
1142
+ key: "memory_projection",
1143
+ status: "error",
1144
+ summary: `Memory projection index exists but could not be opened${detailSuffix}.${nativeSuffix}`,
1145
+ remediation: probe.nativeBindingMismatch
1146
+ ? "Rebuild the better-sqlite3 native binding for this Node.js (`node scripts/ensure-better-sqlite3.mjs` or `pnpm rebuild better-sqlite3`), then restart."
1147
+ : "Rebuild the projection with `remnic rebuild-memory-projection --write`; check file permissions and integrity.",
1148
+ details: {
1149
+ state: probe.state,
1150
+ detail: probe.detail,
1151
+ nativeBindingMismatch: probe.nativeBindingMismatch,
1152
+ },
1153
+ };
1154
+ }
1155
+
1097
1156
  export async function runOperatorDoctor(options: OperatorDoctorOptions): Promise<OperatorDoctorReport> {
1098
1157
  const now = options.now ?? new Date();
1099
1158
  const configStatus = await loadCliPluginConfig(options.configPath);
@@ -1211,6 +1270,11 @@ export async function runOperatorDoctor(options: OperatorDoctorOptions): Promise
1211
1270
  details: meta,
1212
1271
  });
1213
1272
 
1273
+ // Memory projection health (issue #1829). Surfaces a present-but-unopenable
1274
+ // projection (wrong ABI / corrupt) that previously degraded every memory
1275
+ // list to a silent full-corpus scan. Absent and openable are both "ok".
1276
+ checks.push(summarizeProjectionHealth(config));
1277
+
1214
1278
  // Provenance coverage (issue #1575 PR 3). Operators need to see the
1215
1279
  // verified/unverified/none distribution grow as extraction backfills.
1216
1280
  // Best-effort: a read failure surfaces as a warn, not an error.