@remnic/core 9.23.3 → 9.24.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (377) hide show
  1. package/dist/access-admin-ops-surface.d.ts +13 -10
  2. package/dist/access-admin-ops-surface.js +50 -46
  3. package/dist/access-boundary.d.ts +13 -10
  4. package/dist/access-boundary.js +50 -46
  5. package/dist/access-cli.js +125 -122
  6. package/dist/access-cli.js.map +1 -1
  7. package/dist/access-health-types.d.ts +54 -0
  8. package/dist/access-health-types.js +1 -0
  9. package/dist/access-health-types.js.map +1 -0
  10. package/dist/access-http.d.ts +14 -11
  11. package/dist/access-http.js +59 -55
  12. package/dist/access-identity-continuity-surface.d.ts +9 -8
  13. package/dist/access-identity-continuity-surface.js +50 -46
  14. package/dist/access-lcm-surface.d.ts +13 -10
  15. package/dist/access-lcm-surface.js +50 -46
  16. package/dist/access-mcp.d.ts +13 -10
  17. package/dist/access-mcp.js +55 -51
  18. package/dist/access-memory-search-fanout.d.ts +3 -2
  19. package/dist/{access-namespace-preflight-CmErR03h.d.ts → access-namespace-preflight-wRSZEO5V.d.ts} +1 -1
  20. package/dist/access-namespace-preflight.d.ts +4 -3
  21. package/dist/access-namespace-preflight.js +50 -46
  22. package/dist/access-observe-write-surface.d.ts +13 -10
  23. package/dist/access-observe-write-surface.js +50 -46
  24. package/dist/access-operations-batch.js +52 -48
  25. package/dist/access-operations.d.ts +17 -14
  26. package/dist/access-operations.js +54 -50
  27. package/dist/access-recall-concurrency.d.ts +13 -10
  28. package/dist/access-recall-concurrency.js +50 -46
  29. package/dist/access-recall-response.d.ts +13 -10
  30. package/dist/access-recall-response.js +50 -46
  31. package/dist/access-recall-surface.d.ts +13 -10
  32. package/dist/access-recall-surface.js +50 -46
  33. package/dist/access-schema.d.ts +86 -86
  34. package/dist/access-schema.js +8 -8
  35. package/dist/{access-service-CKEROeFP.d.ts → access-service-BlNKWBC9.d.ts} +11 -38
  36. package/dist/access-service.d.ts +15 -12
  37. package/dist/access-service.js +50 -46
  38. package/dist/access-surface-catalog.d.ts +13 -10
  39. package/dist/access-wearables-meetings-surface.d.ts +4 -3
  40. package/dist/action-confidence.d.ts +2 -1
  41. package/dist/active-memory-bridge.d.ts +2 -1
  42. package/dist/active-recall.d.ts +2 -1
  43. package/dist/active-recall.js +3 -2
  44. package/dist/active-recall.js.map +1 -1
  45. package/dist/adapters/index.js +4 -4
  46. package/dist/adapters/registry.js +2 -2
  47. package/dist/behavior-learner.d.ts +2 -1
  48. package/dist/behavior-signals.d.ts +2 -1
  49. package/dist/bootstrap.d.ts +9 -8
  50. package/dist/briefing.d.ts +3 -2
  51. package/dist/briefing.js +14 -14
  52. package/dist/buffer-surprise-report.d.ts +2 -1
  53. package/dist/buffer.d.ts +6 -2
  54. package/dist/buffer.js +1 -1
  55. package/dist/bulk-import/index.d.ts +4 -3
  56. package/dist/calibration.d.ts +2 -1
  57. package/dist/capabilities.d.ts +2 -1
  58. package/dist/{catalog-BFTVo2Ez.d.ts → catalog-BLLxZ5L5.d.ts} +1 -1
  59. package/dist/causal-behavior.d.ts +2 -1
  60. package/dist/causal-consolidation.d.ts +2 -1
  61. package/dist/causal-consolidation.js +15 -15
  62. package/dist/causal-trajectory-graph.d.ts +2 -1
  63. package/dist/{chunk-P2BLHUL2.js → chunk-2NAFR36N.js} +5 -5
  64. package/dist/{chunk-WMLHJ5UW.js → chunk-2WSX7U2N.js} +2 -2
  65. package/dist/{chunk-E3ZMFFQZ.js → chunk-3FLPLDIL.js} +2 -2
  66. package/dist/chunk-443XCR34.js +213 -0
  67. package/dist/chunk-443XCR34.js.map +1 -0
  68. package/dist/{chunk-JNIJBXVK.js → chunk-4KAIGQVU.js} +2 -2
  69. package/dist/{chunk-354T54RR.js → chunk-4TFIRMTE.js} +28 -1
  70. package/dist/chunk-4TFIRMTE.js.map +1 -0
  71. package/dist/{chunk-XWPFZX6S.js → chunk-7CP7SO32.js} +108 -37
  72. package/dist/chunk-7CP7SO32.js.map +1 -0
  73. package/dist/{chunk-XGEFGNL5.js → chunk-7IECFM4E.js} +18 -10
  74. package/dist/chunk-7IECFM4E.js.map +1 -0
  75. package/dist/{chunk-ZN4UZWMT.js → chunk-A5N6ISRK.js} +2 -2
  76. package/dist/{chunk-XKGPIWGI.js → chunk-AVPXXV6J.js} +4 -4
  77. package/dist/{chunk-4PQ4KLLK.js → chunk-BHKDCWGH.js} +9 -9
  78. package/dist/{chunk-5CPI2QWG.js → chunk-CSXHRAJJ.js} +3 -3
  79. package/dist/{chunk-QCKQEBIY.js → chunk-DFTCJVXI.js} +6 -6
  80. package/dist/{chunk-QE63OHLB.js → chunk-ECG64R7B.js} +5 -5
  81. package/dist/{chunk-ATEAL5EI.js → chunk-EFYACT2X.js} +2 -2
  82. package/dist/{chunk-YNDLCWXS.js → chunk-EZ25VE3G.js} +4 -4
  83. package/dist/{chunk-UGH6VPPO.js → chunk-FHH6R3QI.js} +2 -2
  84. package/dist/{chunk-WZVIB5BK.js → chunk-GCKIDUHE.js} +69 -53
  85. package/dist/chunk-GCKIDUHE.js.map +1 -0
  86. package/dist/{chunk-MAXVH44E.js → chunk-GF22HWS2.js} +10 -10
  87. package/dist/{chunk-ZYU7ZFLT.js → chunk-GJJBPI6I.js} +5 -1
  88. package/dist/chunk-GJJBPI6I.js.map +1 -0
  89. package/dist/{chunk-AU7Q3LSC.js → chunk-JBPKEARU.js} +4 -4
  90. package/dist/{chunk-IPLYGWQF.js → chunk-KQAFEZQX.js} +5 -5
  91. package/dist/{chunk-CUUY5CC4.js → chunk-L7DTBRTI.js} +2 -2
  92. package/dist/{chunk-AYAROCWD.js → chunk-LLISELSY.js} +2 -2
  93. package/dist/{chunk-MMTG3SCJ.js → chunk-M5EPMNS4.js} +2 -2
  94. package/dist/{chunk-HSSIWAZP.js → chunk-M6ZJR6HU.js} +140 -124
  95. package/dist/{chunk-HSSIWAZP.js.map → chunk-M6ZJR6HU.js.map} +1 -1
  96. package/dist/{chunk-WWZ35GWW.js → chunk-MX4TUDMB.js} +2 -2
  97. package/dist/{chunk-SVZGA2MK.js → chunk-NHPROHJD.js} +2 -2
  98. package/dist/{chunk-6TWXQ4E2.js → chunk-NM7SKDZM.js} +2 -2
  99. package/dist/chunk-NQ7E27HN.js +261 -0
  100. package/dist/chunk-NQ7E27HN.js.map +1 -0
  101. package/dist/{chunk-QJAQNXWI.js → chunk-ONHRRJZM.js} +6 -6
  102. package/dist/{chunk-V2XVMCP6.js → chunk-P25IPRXP.js} +7 -7
  103. package/dist/{chunk-KXGE7L62.js → chunk-RENY3AIW.js} +67 -67
  104. package/dist/chunk-RENY3AIW.js.map +1 -0
  105. package/dist/{chunk-3L5O7SMT.js → chunk-SGMORUEU.js} +3 -3
  106. package/dist/{chunk-3ZW6ZQO6.js → chunk-TBJV4BZE.js} +13 -13
  107. package/dist/{chunk-7I7AYIUQ.js → chunk-TGHUJC64.js} +5 -5
  108. package/dist/{chunk-SCOT5DTA.js → chunk-V25OWXAC.js} +1 -1
  109. package/dist/chunk-V25OWXAC.js.map +1 -0
  110. package/dist/{chunk-DLKRXYRL.js → chunk-V4MAO2Y4.js} +15 -15
  111. package/dist/{chunk-5FZQ74K5.js → chunk-WMR5WPZU.js} +2 -2
  112. package/dist/{chunk-M6LJXJ3J.js → chunk-WNSQOXWS.js} +6 -6
  113. package/dist/{chunk-KNB7IBBU.js → chunk-WO7KBATL.js} +4 -4
  114. package/dist/{chunk-3NAHZQU3.js → chunk-X3SQYAPP.js} +2 -2
  115. package/dist/{chunk-3UOSVTFR.js → chunk-XBRPEGXF.js} +5 -5
  116. package/dist/{chunk-GD3QOBMB.js → chunk-XHYCPA5C.js} +2 -2
  117. package/dist/{chunk-ES5IHQ6M.js → chunk-Z3B4V4YI.js} +5 -5
  118. package/dist/chunk-ZLNOX7B2.js +60 -0
  119. package/dist/chunk-ZLNOX7B2.js.map +1 -0
  120. package/dist/{cli-BrwT9YMD.d.ts → cli-Zc6UclD1.d.ts} +5 -5
  121. package/dist/cli.d.ts +15 -12
  122. package/dist/cli.js +85 -82
  123. package/dist/compounding/engine.d.ts +3 -2
  124. package/dist/compounding/engine.js +14 -14
  125. package/dist/compounding/preference-consolidator.d.ts +2 -1
  126. package/dist/compression-optimizer.d.ts +2 -1
  127. package/dist/config.d.ts +2 -1
  128. package/dist/config.js +3 -2
  129. package/dist/connectors/codex-materialize-runner.d.ts +2 -1
  130. package/dist/connectors/codex-materialize-runner.js +14 -14
  131. package/dist/connectors/codex-materialize.d.ts +2 -1
  132. package/dist/connectors/index.d.ts +2 -1
  133. package/dist/connectors/index.js +17 -17
  134. package/dist/consolidation-provenance-check.d.ts +3 -2
  135. package/dist/consolidation-undo.d.ts +3 -2
  136. package/dist/contradiction/index.d.ts +3 -2
  137. package/dist/conversation-index/backend.d.ts +2 -1
  138. package/dist/conversation-index/chunker.d.ts +2 -1
  139. package/dist/conversation-index/faiss-adapter.d.ts +2 -1
  140. package/dist/conversation-index/indexer.d.ts +2 -1
  141. package/dist/conversation-index/search.d.ts +2 -1
  142. package/dist/corpus-watermark.d.ts +228 -0
  143. package/dist/corpus-watermark.js +106 -0
  144. package/dist/corpus-watermark.js.map +1 -0
  145. package/dist/dashboard-runtime.js +2 -2
  146. package/dist/day-summary.d.ts +2 -1
  147. package/dist/delinearize.d.ts +2 -1
  148. package/dist/direct-answer-wiring.d.ts +2 -1
  149. package/dist/direct-answer.d.ts +2 -1
  150. package/dist/embedding-fallback.d.ts +2 -1
  151. package/dist/enrichment/index.d.ts +2 -1
  152. package/dist/entity-retrieval.d.ts +3 -2
  153. package/dist/entity-retrieval.js +14 -14
  154. package/dist/entity-schema.d.ts +2 -1
  155. package/dist/explicit-capture.d.ts +9 -8
  156. package/dist/extraction-error-classification.d.ts +2 -1
  157. package/dist/extraction-faithfulness.d.ts +2 -1
  158. package/dist/extraction-judge-telemetry.d.ts +2 -1
  159. package/dist/extraction-judge-training.d.ts +2 -1
  160. package/dist/extraction-judge.d.ts +2 -1
  161. package/dist/extraction-liveness.d.ts +5 -0
  162. package/dist/extraction-liveness.js +26 -0
  163. package/dist/extraction-liveness.js.map +1 -0
  164. package/dist/extraction.d.ts +2 -1
  165. package/dist/extraction.js +1 -1
  166. package/dist/fallback-llm.d.ts +2 -1
  167. package/dist/graph-dashboard-diff.d.ts +2 -1
  168. package/dist/graph-dashboard-key.d.ts +2 -1
  169. package/dist/graph-dashboard-parser.d.ts +2 -1
  170. package/dist/graph-edge-reinforcement.d.ts +2 -1
  171. package/dist/graph-snapshot.d.ts +2 -1
  172. package/dist/graph.d.ts +2 -1
  173. package/dist/importance.d.ts +2 -1
  174. package/dist/importers/index.d.ts +2 -1
  175. package/dist/in-flight-reads.d.ts +2 -1
  176. package/dist/index.d.ts +427 -424
  177. package/dist/index.js +212 -209
  178. package/dist/intent.d.ts +2 -1
  179. package/dist/lcm/engine.d.ts +2 -1
  180. package/dist/lcm/engine.js +4 -4
  181. package/dist/lcm/index.d.ts +2 -1
  182. package/dist/lcm/index.js +12 -12
  183. package/dist/lcm/tools.d.ts +2 -1
  184. package/dist/lifecycle.d.ts +2 -1
  185. package/dist/live-connectors-runner.d.ts +2 -1
  186. package/dist/local-llm.d.ts +2 -1
  187. package/dist/local-model-endpoint.d.ts +2 -1
  188. package/dist/maintenance/memory-governance.d.ts +2 -1
  189. package/dist/maintenance/memory-governance.js +14 -14
  190. package/dist/maintenance/rebuild-memory-lifecycle-ledger.d.ts +3 -2
  191. package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +14 -14
  192. package/dist/maintenance/rebuild-memory-projection.d.ts +3 -2
  193. package/dist/maintenance/rebuild-memory-projection.js +15 -15
  194. package/dist/{maintenance-C98njYb8.d.ts → maintenance-DD22oIx9.d.ts} +3 -3
  195. package/dist/mcp-memory-inspector-app.d.ts +13 -10
  196. package/dist/memory-action-policy.d.ts +2 -1
  197. package/dist/memory-cache.d.ts +2 -1
  198. package/dist/memory-lifecycle-ledger-utils.d.ts +2 -1
  199. package/dist/memory-projection-store.d.ts +2 -1
  200. package/dist/memory-provenance.d.ts +2 -1
  201. package/dist/memory-worth-outcomes.d.ts +3 -2
  202. package/dist/models-json.d.ts +2 -1
  203. package/dist/namespaces/migrate.d.ts +4 -3
  204. package/dist/namespaces/migrate.js +21 -21
  205. package/dist/namespaces/principal.d.ts +2 -1
  206. package/dist/namespaces/search.d.ts +2 -1
  207. package/dist/namespaces/search.js +6 -6
  208. package/dist/namespaces/storage.d.ts +4 -3
  209. package/dist/namespaces/storage.js +14 -14
  210. package/dist/native-knowledge.d.ts +2 -1
  211. package/dist/offline-sync-impression-drain.d.ts +2 -1
  212. package/dist/offline-sync-impression-drain.js +4 -4
  213. package/dist/operator-doctor-corpus.d.ts +31 -0
  214. package/dist/operator-doctor-corpus.js +91 -0
  215. package/dist/operator-doctor-corpus.js.map +1 -0
  216. package/dist/operator-doctor-types.d.ts +22 -0
  217. package/dist/operator-doctor-types.js +1 -0
  218. package/dist/operator-doctor-types.js.map +1 -0
  219. package/dist/operator-toolkit.d.ts +10 -13
  220. package/dist/operator-toolkit.js +35 -29
  221. package/dist/orchestration/compression-guideline-coordinator.d.ts +3 -2
  222. package/dist/orchestration/maintenance.d.ts +5 -4
  223. package/dist/orchestration/maintenance.js +20 -20
  224. package/dist/{orchestrator-C9ZVppNh.d.ts → orchestrator-BTGgoKli.d.ts} +7 -7
  225. package/dist/orchestrator.d.ts +9 -8
  226. package/dist/orchestrator.js +123 -120
  227. package/dist/patterns-cli.d.ts +2 -1
  228. package/dist/{pipeline-id_-sqYm.d.ts → pipeline-Bq5zU6Bp.d.ts} +1 -1
  229. package/dist/policy-runtime.d.ts +2 -1
  230. package/dist/proactive-contention.d.ts +2 -1
  231. package/dist/provenance.d.ts +2 -1
  232. package/dist/{qmd-R6XaQOZK.d.ts → qmd-3ILJkC70.d.ts} +1 -1
  233. package/dist/qmd-preflight.d.ts +3 -2
  234. package/dist/qmd-recall-cache.d.ts +2 -1
  235. package/dist/qmd.d.ts +3 -2
  236. package/dist/qmd.js +4 -4
  237. package/dist/recall-concurrency-config.d.ts +2 -1
  238. package/dist/recall-disclosure-escalation.d.ts +2 -1
  239. package/dist/recall-explain-renderer.d.ts +2 -1
  240. package/dist/recall-explain-renderer.js +3 -3
  241. package/dist/recall-memory-map.d.ts +2 -1
  242. package/dist/recall-planner-llm.d.ts +2 -1
  243. package/dist/recall-state.d.ts +2 -1
  244. package/dist/recall-tag-filter.d.ts +2 -1
  245. package/dist/recall-timings.d.ts +2 -1
  246. package/dist/recall-xray-cli.d.ts +2 -1
  247. package/dist/recall-xray-cli.js +4 -4
  248. package/dist/recall-xray-renderer.d.ts +2 -1
  249. package/dist/recall-xray-renderer.js +3 -3
  250. package/dist/recall-xray.d.ts +2 -1
  251. package/dist/recall-xray.js +2 -2
  252. package/dist/replay/normalizers/chatgpt.d.ts +2 -1
  253. package/dist/replay/normalizers/claude.d.ts +2 -1
  254. package/dist/replay/normalizers/openclaw.d.ts +2 -1
  255. package/dist/replay/normalizers/shared.d.ts +2 -1
  256. package/dist/replay/runner.d.ts +2 -1
  257. package/dist/replay/types.d.ts +2 -1
  258. package/dist/resolve-auth-token.d.ts +2 -1
  259. package/dist/resume-bundles.js +5 -4
  260. package/dist/retrieval-agents.d.ts +3 -2
  261. package/dist/retrieval-tiers.d.ts +2 -1
  262. package/dist/routing/engine.d.ts +2 -1
  263. package/dist/routing/store.d.ts +2 -1
  264. package/dist/salvage-envelope.d.ts +2 -1
  265. package/dist/schemas.d.ts +76 -76
  266. package/dist/{scope-profiles-EhyuAKkd.d.ts → scope-profiles-D0-ff4xT.d.ts} +1 -1
  267. package/dist/search/embed-helper.d.ts +2 -1
  268. package/dist/search/factory.d.ts +2 -1
  269. package/dist/search/factory.js +5 -5
  270. package/dist/search/index.d.ts +2 -1
  271. package/dist/search/index.js +9 -9
  272. package/dist/search/lancedb-backend.d.ts +2 -1
  273. package/dist/search/meilisearch-backend.d.ts +2 -1
  274. package/dist/search/noop-backend.d.ts +2 -1
  275. package/dist/search/orama-backend.d.ts +2 -1
  276. package/dist/search/port.d.ts +2 -1
  277. package/dist/search/remote-backend.d.ts +2 -1
  278. package/dist/{semantic-consolidation-B2xo26UQ.d.ts → semantic-consolidation-4Tfh7c1g.d.ts} +1 -1
  279. package/dist/semantic-consolidation.d.ts +3 -2
  280. package/dist/semantic-consolidation.js +15 -15
  281. package/dist/semantic-rule-promotion.js +14 -14
  282. package/dist/semantic-rule-verifier.d.ts +2 -1
  283. package/dist/semantic-rule-verifier.js +14 -14
  284. package/dist/{service-DgFWlZ-r.d.ts → service-Z8uzoAST.d.ts} +2 -2
  285. package/dist/session-observer-bands.d.ts +2 -1
  286. package/dist/session-observer-state.d.ts +2 -1
  287. package/dist/shared-context/manager.d.ts +10 -9
  288. package/dist/signal.d.ts +2 -1
  289. package/dist/{storage-XGawwqXE.d.ts → storage-BZVMn8yJ.d.ts} +25 -2
  290. package/dist/storage.d.ts +3 -2
  291. package/dist/storage.js +13 -13
  292. package/dist/summarizer.d.ts +2 -1
  293. package/dist/summary-snapshot.d.ts +2 -1
  294. package/dist/temporal-supersession.d.ts +3 -2
  295. package/dist/temporal-timeline-recall.d.ts +2 -1
  296. package/dist/temporal-validity.d.ts +2 -1
  297. package/dist/threading.d.ts +2 -1
  298. package/dist/tier-migration.d.ts +3 -2
  299. package/dist/tier-routing.d.ts +2 -1
  300. package/dist/tokens.js +2 -2
  301. package/dist/topics.d.ts +2 -1
  302. package/dist/transcript.d.ts +2 -1
  303. package/dist/transfer/autodetect.js +1 -1
  304. package/dist/transfer/backup.js +3 -3
  305. package/dist/transfer/capsule-export.js +4 -4
  306. package/dist/transfer/capsule-import.js +1 -1
  307. package/dist/transfer/types.d.ts +66 -66
  308. package/dist/trust-score-stage.d.ts +2 -1
  309. package/dist/trust-score.d.ts +2 -1
  310. package/dist/{types-Bbs0qz6b.d.ts → types-DHBxIF0s.d.ts} +181 -5
  311. package/dist/types.d.ts +2 -1
  312. package/dist/types.js +1 -1
  313. package/dist/utility-runtime.d.ts +2 -1
  314. package/dist/verified-recall.js +14 -14
  315. package/dist/write-envelope.d.ts +2 -1
  316. package/package.json +12 -2
  317. package/src/access-health-types.ts +51 -0
  318. package/src/access-http.test.ts +22 -2
  319. package/src/access-service-health.test.ts +212 -0
  320. package/src/access-service.ts +14 -37
  321. package/src/buffer.ts +30 -0
  322. package/src/cli.ts +2 -4
  323. package/src/config.ts +4 -4
  324. package/src/corpus-watermark.test.ts +840 -0
  325. package/src/corpus-watermark.ts +531 -0
  326. package/src/extraction-liveness.test.ts +461 -0
  327. package/src/extraction-liveness.ts +462 -0
  328. package/src/operator-doctor-corpus.test.ts +171 -0
  329. package/src/operator-doctor-corpus.ts +95 -0
  330. package/src/operator-doctor-types.ts +20 -0
  331. package/src/operator-toolkit.ts +12 -13
  332. package/src/recall/archive-scoring.ts +18 -0
  333. package/src/storage/memory-read-store.ts +91 -22
  334. package/src/storage.ts +27 -2
  335. package/src/types.ts +4 -4
  336. package/dist/chunk-354T54RR.js.map +0 -1
  337. package/dist/chunk-KXGE7L62.js.map +0 -1
  338. package/dist/chunk-SCOT5DTA.js.map +0 -1
  339. package/dist/chunk-WZVIB5BK.js.map +0 -1
  340. package/dist/chunk-XGEFGNL5.js.map +0 -1
  341. package/dist/chunk-XWPFZX6S.js.map +0 -1
  342. package/dist/chunk-ZYU7ZFLT.js.map +0 -1
  343. /package/dist/{chunk-P2BLHUL2.js.map → chunk-2NAFR36N.js.map} +0 -0
  344. /package/dist/{chunk-WMLHJ5UW.js.map → chunk-2WSX7U2N.js.map} +0 -0
  345. /package/dist/{chunk-E3ZMFFQZ.js.map → chunk-3FLPLDIL.js.map} +0 -0
  346. /package/dist/{chunk-JNIJBXVK.js.map → chunk-4KAIGQVU.js.map} +0 -0
  347. /package/dist/{chunk-ZN4UZWMT.js.map → chunk-A5N6ISRK.js.map} +0 -0
  348. /package/dist/{chunk-XKGPIWGI.js.map → chunk-AVPXXV6J.js.map} +0 -0
  349. /package/dist/{chunk-4PQ4KLLK.js.map → chunk-BHKDCWGH.js.map} +0 -0
  350. /package/dist/{chunk-5CPI2QWG.js.map → chunk-CSXHRAJJ.js.map} +0 -0
  351. /package/dist/{chunk-QCKQEBIY.js.map → chunk-DFTCJVXI.js.map} +0 -0
  352. /package/dist/{chunk-QE63OHLB.js.map → chunk-ECG64R7B.js.map} +0 -0
  353. /package/dist/{chunk-ATEAL5EI.js.map → chunk-EFYACT2X.js.map} +0 -0
  354. /package/dist/{chunk-YNDLCWXS.js.map → chunk-EZ25VE3G.js.map} +0 -0
  355. /package/dist/{chunk-UGH6VPPO.js.map → chunk-FHH6R3QI.js.map} +0 -0
  356. /package/dist/{chunk-MAXVH44E.js.map → chunk-GF22HWS2.js.map} +0 -0
  357. /package/dist/{chunk-AU7Q3LSC.js.map → chunk-JBPKEARU.js.map} +0 -0
  358. /package/dist/{chunk-IPLYGWQF.js.map → chunk-KQAFEZQX.js.map} +0 -0
  359. /package/dist/{chunk-CUUY5CC4.js.map → chunk-L7DTBRTI.js.map} +0 -0
  360. /package/dist/{chunk-AYAROCWD.js.map → chunk-LLISELSY.js.map} +0 -0
  361. /package/dist/{chunk-MMTG3SCJ.js.map → chunk-M5EPMNS4.js.map} +0 -0
  362. /package/dist/{chunk-WWZ35GWW.js.map → chunk-MX4TUDMB.js.map} +0 -0
  363. /package/dist/{chunk-SVZGA2MK.js.map → chunk-NHPROHJD.js.map} +0 -0
  364. /package/dist/{chunk-6TWXQ4E2.js.map → chunk-NM7SKDZM.js.map} +0 -0
  365. /package/dist/{chunk-QJAQNXWI.js.map → chunk-ONHRRJZM.js.map} +0 -0
  366. /package/dist/{chunk-V2XVMCP6.js.map → chunk-P25IPRXP.js.map} +0 -0
  367. /package/dist/{chunk-3L5O7SMT.js.map → chunk-SGMORUEU.js.map} +0 -0
  368. /package/dist/{chunk-3ZW6ZQO6.js.map → chunk-TBJV4BZE.js.map} +0 -0
  369. /package/dist/{chunk-7I7AYIUQ.js.map → chunk-TGHUJC64.js.map} +0 -0
  370. /package/dist/{chunk-DLKRXYRL.js.map → chunk-V4MAO2Y4.js.map} +0 -0
  371. /package/dist/{chunk-5FZQ74K5.js.map → chunk-WMR5WPZU.js.map} +0 -0
  372. /package/dist/{chunk-M6LJXJ3J.js.map → chunk-WNSQOXWS.js.map} +0 -0
  373. /package/dist/{chunk-KNB7IBBU.js.map → chunk-WO7KBATL.js.map} +0 -0
  374. /package/dist/{chunk-3NAHZQU3.js.map → chunk-X3SQYAPP.js.map} +0 -0
  375. /package/dist/{chunk-3UOSVTFR.js.map → chunk-XBRPEGXF.js.map} +0 -0
  376. /package/dist/{chunk-GD3QOBMB.js.map → chunk-XHYCPA5C.js.map} +0 -0
  377. /package/dist/{chunk-ES5IHQ6M.js.map → chunk-Z3B4V4YI.js.map} +0 -0
@@ -0,0 +1,840 @@
1
+ import assert from "node:assert/strict";
2
+ import { chmod, mkdir, mkdtemp, readdir, rm, stat, utimes, writeFile } from "node:fs/promises";
3
+ import os from "node:os";
4
+ import path from "node:path";
5
+ /**
6
+ * Corpus watermark primitive (issue #2149) + pre-merge hardening (issue #2156).
7
+ *
8
+ * The digest is a per-partition, TIER-AWARE census fingerprint, so two daemons
9
+ * that agree on per-day HOT and COLD file counts share a digest and a differing
10
+ * digest is a cheap divergence signal. These tests pin that property, the census
11
+ * determinism (AGENTS.md pattern 26), the "newest write is scoped to the newest
12
+ * HOT partition" contract, and the #2156 findings across review rounds: the
13
+ * cold-tier census (D), the shared config-driven namespace resolver (C), the
14
+ * caller-capability filter (B), the stale-while-revalidate cache (A), the
15
+ * per-namespace degrade, the migration-race retry, and fail-distinct scans.
16
+ */
17
+ import test from "node:test";
18
+ import { parseConfig } from "./config.js";
19
+ import {
20
+ type CorpusNamespaceRoot,
21
+ type CorpusStorage,
22
+ type CorpusWatermark,
23
+ CorpusWatermarkCache,
24
+ UNPARTITIONED_BUCKET,
25
+ buildPartitionCensus,
26
+ computeCorpusWatermark,
27
+ computeCorpusWatermarks,
28
+ computeServiceCorpusWatermarks,
29
+ digestPartitionCensus,
30
+ resolveCorpusNamespaceRoots,
31
+ } from "./corpus-watermark.js";
32
+ import { StorageManager } from "./storage.js";
33
+ import type { PluginConfig } from "./types.js";
34
+
35
+ async function makeMemoryDir(prefix = "remnic-corpus-wm-"): Promise<string> {
36
+ return mkdtemp(path.join(os.tmpdir(), prefix));
37
+ }
38
+
39
+ async function writeMemory(memoryDir: string, rel: string): Promise<string> {
40
+ const full = path.join(memoryDir, rel);
41
+ await mkdir(path.dirname(full), { recursive: true });
42
+ await writeFile(full, `---\nid: ${path.basename(rel, ".md")}\n---\n\nbody\n`, "utf-8");
43
+ return full;
44
+ }
45
+
46
+ /** Hot-tier scan only (the recall corpus). */
47
+ async function scanHot(memoryDir: string): Promise<{ paths: string[]; baseDir: string }> {
48
+ const storage = new StorageManager(memoryDir);
49
+ return { paths: await storage.collectActiveMemoryPaths(), baseDir: storage.dir };
50
+ }
51
+
52
+ /** Both tiers combined, exactly as the watermark builder feeds computeCorpusWatermark. */
53
+ async function scanBothTiers(memoryDir: string): Promise<{ paths: string[]; baseDir: string }> {
54
+ const storage = new StorageManager(memoryDir);
55
+ const [hot, cold] = await Promise.all([
56
+ storage.collectActiveMemoryPaths(),
57
+ storage.collectColdMemoryPaths(),
58
+ ]);
59
+ return { paths: [...hot, ...cold], baseDir: storage.dir };
60
+ }
61
+
62
+ function fakeStorage(dir: string, hotPaths: string[], coldPaths: string[] = []): CorpusStorage {
63
+ return {
64
+ dir,
65
+ collectActiveMemoryPaths: async () => hotPaths,
66
+ collectColdMemoryPaths: async () => coldPaths,
67
+ };
68
+ }
69
+
70
+ function sampleWatermark(namespace = "global"): CorpusWatermark {
71
+ return {
72
+ namespace,
73
+ memoryFileCount: 0,
74
+ newestPartition: null,
75
+ newestWriteAt: null,
76
+ digest: "d",
77
+ computedAt: "t",
78
+ };
79
+ }
80
+
81
+ /** A valid (parseConfig-normalized) service config rooted at `memoryDir`. */
82
+ function serviceConfig(memoryDir: string, overrides: Record<string, unknown> = {}): PluginConfig {
83
+ return parseConfig({
84
+ openaiApiKey: "sk-test",
85
+ memoryDir,
86
+ workspaceDir: path.join(memoryDir, "workspace"),
87
+ qmdEnabled: false,
88
+ transcriptEnabled: false,
89
+ hourlySummariesEnabled: false,
90
+ identityEnabled: false,
91
+ identityContinuityEnabled: false,
92
+ sharedContextEnabled: false,
93
+ captureMode: "implicit",
94
+ namespacesEnabled: false,
95
+ defaultNamespace: "global",
96
+ ...overrides,
97
+ });
98
+ }
99
+
100
+ // ── digest determinism (pattern 26) ──────────────────────────────────────────
101
+
102
+ test("digestPartitionCensus is insertion-order independent (Map and Record agree)", () => {
103
+ const forward = new Map<string, number>([
104
+ ["facts/2026-01-01", 2],
105
+ ["procedures/2026-01-02", 5],
106
+ [UNPARTITIONED_BUCKET, 1],
107
+ ]);
108
+ const reversed = new Map<string, number>([
109
+ [UNPARTITIONED_BUCKET, 1],
110
+ ["procedures/2026-01-02", 5],
111
+ ["facts/2026-01-01", 2],
112
+ ]);
113
+ assert.equal(digestPartitionCensus(forward), digestPartitionCensus(reversed));
114
+
115
+ const recordA = { "facts/2026-01-01": 2, "procedures/2026-01-02": 5, [UNPARTITIONED_BUCKET]: 1 };
116
+ const recordB = { [UNPARTITIONED_BUCKET]: 1, "procedures/2026-01-02": 5, "facts/2026-01-01": 2 };
117
+ assert.equal(digestPartitionCensus(recordA), digestPartitionCensus(recordB));
118
+ assert.equal(digestPartitionCensus(forward), digestPartitionCensus(recordA));
119
+ });
120
+
121
+ test("digestPartitionCensus changes when a count changes", () => {
122
+ const base = new Map<string, number>([["facts/2026-01-01", 2]]);
123
+ const grown = new Map<string, number>([["facts/2026-01-01", 3]]);
124
+ assert.notEqual(digestPartitionCensus(base), digestPartitionCensus(grown));
125
+ });
126
+
127
+ test("digestPartitionCensus changes when a bucket is added or removed", () => {
128
+ const base = new Map<string, number>([["facts/2026-01-01", 2]]);
129
+ const added = new Map<string, number>([
130
+ ["facts/2026-01-01", 2],
131
+ ["facts/2026-01-02", 1],
132
+ ]);
133
+ assert.notEqual(digestPartitionCensus(base), digestPartitionCensus(added));
134
+ assert.notEqual(digestPartitionCensus(base), digestPartitionCensus(new Map<string, number>()));
135
+ });
136
+
137
+ // ── census bucketing (tier-aware) ────────────────────────────────────────────
138
+
139
+ test("buildPartitionCensus buckets by <tier>:<category>/<day>; non-day paths land in the explicit unpartitioned bucket", () => {
140
+ const baseDir = path.join(os.tmpdir(), "mem-fixture");
141
+ const paths = [
142
+ path.join(baseDir, "facts/2026-01-01/a.md"),
143
+ path.join(baseDir, "facts/2026-01-01/b.md"),
144
+ path.join(baseDir, "procedures/2026-01-02/c.md"),
145
+ path.join(baseDir, "facts/loose.md"),
146
+ path.join(baseDir, "cold/facts/2026-01-01/d.md"),
147
+ ];
148
+ const census = buildPartitionCensus(paths, baseDir);
149
+ assert.equal(census.get("hot:facts/2026-01-01"), 2);
150
+ assert.equal(census.get("hot:procedures/2026-01-02"), 1);
151
+ assert.equal(census.get(`hot:${UNPARTITIONED_BUCKET}`), 1);
152
+ // The cold-tier file is bucketed separately, never folded into the hot count.
153
+ assert.equal(census.get("cold:facts/2026-01-01"), 1);
154
+ });
155
+
156
+ // ── watermark computation ────────────────────────────────────────────────────
157
+
158
+ test("computeCorpusWatermark: an empty corpus yields a stable digest, null timestamps, and does not throw", async () => {
159
+ const empty = await computeCorpusWatermark({ namespace: "global", paths: [], baseDir: "/mem" });
160
+ const emptyElsewhere = await computeCorpusWatermark({ namespace: "global", paths: [], baseDir: "/other" });
161
+ assert.equal(empty.memoryFileCount, 0);
162
+ assert.equal(empty.newestPartition, null);
163
+ assert.equal(empty.newestWriteAt, null);
164
+ assert.equal(empty.digest, digestPartitionCensus(new Map<string, number>()));
165
+ assert.equal(empty.digest, emptyElsewhere.digest);
166
+ });
167
+
168
+ test("computeCorpusWatermark: memoryFileCount matches the files written by the fixture", async () => {
169
+ const memoryDir = await makeMemoryDir();
170
+ try {
171
+ await writeMemory(memoryDir, "facts/2026-03-08/a.md");
172
+ await writeMemory(memoryDir, "facts/2026-03-08/b.md");
173
+ await writeMemory(memoryDir, "procedures/2026-03-09/c.md");
174
+ const { paths, baseDir } = await scanHot(memoryDir);
175
+ const watermark = await computeCorpusWatermark({ namespace: "global", paths, baseDir });
176
+ assert.equal(watermark.memoryFileCount, 3);
177
+ } finally {
178
+ await rm(memoryDir, { recursive: true, force: true });
179
+ }
180
+ });
181
+
182
+ test("computeCorpusWatermark: a memory without a day dir is counted in the unpartitioned bucket", async () => {
183
+ const memoryDir = await makeMemoryDir();
184
+ try {
185
+ await writeMemory(memoryDir, "facts/2026-03-08/a.md");
186
+ await writeMemory(memoryDir, "facts/loose.md");
187
+ const { paths, baseDir } = await scanHot(memoryDir);
188
+ const census = buildPartitionCensus(paths, baseDir);
189
+ assert.equal(census.get(`hot:${UNPARTITIONED_BUCKET}`), 1);
190
+ const watermark = await computeCorpusWatermark({ namespace: "global", paths, baseDir });
191
+ assert.equal(watermark.memoryFileCount, 2);
192
+ } finally {
193
+ await rm(memoryDir, { recursive: true, force: true });
194
+ }
195
+ });
196
+
197
+ test("computeCorpusWatermark: newestPartition is the lexicographically greatest date across day dirs", async () => {
198
+ const memoryDir = await makeMemoryDir();
199
+ try {
200
+ await writeMemory(memoryDir, "facts/2026-03-01/a.md");
201
+ await writeMemory(memoryDir, "facts/2026-03-15/b.md");
202
+ await writeMemory(memoryDir, "procedures/2026-03-10/c.md");
203
+ const { paths, baseDir } = await scanHot(memoryDir);
204
+ const watermark = await computeCorpusWatermark({ namespace: "global", paths, baseDir });
205
+ assert.equal(watermark.newestPartition, "2026-03-15");
206
+ } finally {
207
+ await rm(memoryDir, { recursive: true, force: true });
208
+ }
209
+ });
210
+
211
+ test("computeCorpusWatermark: newestWriteAt reflects the newest mtime IN the newest partition, not an older partition's newer file", async () => {
212
+ const memoryDir = await makeMemoryDir();
213
+ try {
214
+ const olderPartitionFile = await writeMemory(memoryDir, "facts/2026-03-08/old.md");
215
+ const newestPartitionFile = await writeMemory(memoryDir, "facts/2026-03-10/new.md");
216
+ // Deliberately make the OLDER partition's file the newest on disk.
217
+ const newestPartitionMtime = new Date("2026-03-10T00:00:00.000Z");
218
+ const olderPartitionButNewerOnDisk = new Date("2030-01-01T00:00:00.000Z");
219
+ await utimes(newestPartitionFile, newestPartitionMtime, newestPartitionMtime);
220
+ await utimes(olderPartitionFile, olderPartitionButNewerOnDisk, olderPartitionButNewerOnDisk);
221
+
222
+ const { paths, baseDir } = await scanHot(memoryDir);
223
+ const watermark = await computeCorpusWatermark({ namespace: "global", paths, baseDir });
224
+ assert.equal(watermark.newestPartition, "2026-03-10");
225
+ assert.equal(watermark.newestWriteAt, newestPartitionMtime.toISOString());
226
+ assert.notEqual(watermark.newestWriteAt, olderPartitionButNewerOnDisk.toISOString());
227
+ } finally {
228
+ await rm(memoryDir, { recursive: true, force: true });
229
+ }
230
+ });
231
+
232
+ test("computeCorpusWatermark: computedAt is stamped and reflects the invocation, not passed as a stale literal", async () => {
233
+ // Finding round-4: computedAt is captured AFTER the (possibly slow) stat loop.
234
+ const before = Date.now();
235
+ const memoryDir = await makeMemoryDir();
236
+ try {
237
+ await writeMemory(memoryDir, "facts/2026-03-08/a.md");
238
+ const { paths, baseDir } = await scanHot(memoryDir);
239
+ const watermark = await computeCorpusWatermark({ namespace: "global", paths, baseDir });
240
+ const stampedMs = Date.parse(watermark.computedAt);
241
+ assert.ok(Number.isFinite(stampedMs), "computedAt is a valid ISO timestamp");
242
+ assert.ok(stampedMs >= before, "computedAt is not stamped before the scan began");
243
+ // An explicit `now` still wins (deterministic callers).
244
+ const fixed = await computeCorpusWatermark({ namespace: "global", paths, baseDir, now: new Date("2026-01-01T00:00:00.000Z") });
245
+ assert.equal(fixed.computedAt, "2026-01-01T00:00:00.000Z");
246
+ } finally {
247
+ await rm(memoryDir, { recursive: true, force: true });
248
+ }
249
+ });
250
+
251
+ test("two corpora with identical layouts share a digest; adding one file diverges it (the detection property)", async () => {
252
+ const dirA = await makeMemoryDir("remnic-corpus-wm-a-");
253
+ const dirB = await makeMemoryDir("remnic-corpus-wm-b-");
254
+ try {
255
+ for (const dir of [dirA, dirB]) {
256
+ await writeMemory(dir, "facts/2026-03-08/a.md");
257
+ await writeMemory(dir, "facts/2026-03-08/b.md");
258
+ await writeMemory(dir, "procedures/2026-03-09/c.md");
259
+ }
260
+ const scanA = await scanHot(dirA);
261
+ const scanB = await scanHot(dirB);
262
+ const watermarkA = await computeCorpusWatermark({ namespace: "global", paths: scanA.paths, baseDir: scanA.baseDir });
263
+ const watermarkB = await computeCorpusWatermark({ namespace: "global", paths: scanB.paths, baseDir: scanB.baseDir });
264
+ assert.equal(watermarkA.digest, watermarkB.digest);
265
+
266
+ await writeMemory(dirB, "facts/2026-03-08/d.md");
267
+ const scanB2 = await scanHot(dirB);
268
+ const watermarkB2 = await computeCorpusWatermark({ namespace: "global", paths: scanB2.paths, baseDir: scanB2.baseDir });
269
+ assert.notEqual(watermarkA.digest, watermarkB2.digest);
270
+ } finally {
271
+ await rm(dirA, { recursive: true, force: true });
272
+ await rm(dirB, { recursive: true, force: true });
273
+ }
274
+ });
275
+
276
+ // ── finding D: cold-tier census ──────────────────────────────────────────────
277
+
278
+ test("finding D: moving a memory from hot to cold changes the digest while the count is unchanged", async () => {
279
+ const memoryDir = await makeMemoryDir();
280
+ try {
281
+ const hotFile = await writeMemory(memoryDir, "facts/2026-03-08/a.md");
282
+ const hotScan = await scanBothTiers(memoryDir);
283
+ const hotWatermark = await computeCorpusWatermark({ namespace: "global", ...hotScan });
284
+
285
+ // Demote: same memory, same day-partition, now under cold/.
286
+ await rm(hotFile);
287
+ await writeMemory(memoryDir, "cold/facts/2026-03-08/a.md");
288
+ const coldScan = await scanBothTiers(memoryDir);
289
+ const coldWatermark = await computeCorpusWatermark({ namespace: "global", ...coldScan });
290
+
291
+ assert.equal(hotWatermark.memoryFileCount, 1);
292
+ assert.equal(coldWatermark.memoryFileCount, 1, "cold memories still count");
293
+ assert.notEqual(
294
+ hotWatermark.digest,
295
+ coldWatermark.digest,
296
+ "a hot->cold demotion must change the digest even though the count is identical",
297
+ );
298
+ } finally {
299
+ await rm(memoryDir, { recursive: true, force: true });
300
+ }
301
+ });
302
+
303
+ test("finding D: a replica missing a cold file diverges from one that has it", async () => {
304
+ const dirA = await makeMemoryDir("remnic-corpus-wm-a-");
305
+ const dirB = await makeMemoryDir("remnic-corpus-wm-b-");
306
+ try {
307
+ for (const dir of [dirA, dirB]) {
308
+ await writeMemory(dir, "facts/2026-03-08/hot.md");
309
+ }
310
+ // Only replica A holds the cold memory; the hot tiers are identical.
311
+ await writeMemory(dirA, "cold/facts/2026-03-08/cold.md");
312
+
313
+ const scanA = await scanBothTiers(dirA);
314
+ const scanB = await scanBothTiers(dirB);
315
+ const wmA = await computeCorpusWatermark({ namespace: "global", ...scanA });
316
+ const wmB = await computeCorpusWatermark({ namespace: "global", ...scanB });
317
+
318
+ assert.equal(wmA.memoryFileCount, 2);
319
+ assert.equal(wmB.memoryFileCount, 1);
320
+ assert.notEqual(wmA.digest, wmB.digest, "differing cold tiers must diverge the digest, not read as converged");
321
+ } finally {
322
+ await rm(dirA, { recursive: true, force: true });
323
+ await rm(dirB, { recursive: true, force: true });
324
+ }
325
+ });
326
+
327
+ test("finding D: cold files count in the census but never advance the newest-write freshness probe", async () => {
328
+ const memoryDir = await makeMemoryDir();
329
+ try {
330
+ const hotFile = await writeMemory(memoryDir, "facts/2026-03-08/hot.md");
331
+ const coldFile = await writeMemory(memoryDir, "cold/facts/2030-01-01/cold.md");
332
+ // Make the COLD file the newest on disk AND in a later-dated partition.
333
+ const hotMtime = new Date("2026-03-08T00:00:00.000Z");
334
+ const coldMtime = new Date("2030-01-01T00:00:00.000Z");
335
+ await utimes(hotFile, hotMtime, hotMtime);
336
+ await utimes(coldFile, coldMtime, coldMtime);
337
+
338
+ const scan = await scanBothTiers(memoryDir);
339
+ const watermark = await computeCorpusWatermark({ namespace: "global", ...scan });
340
+ assert.equal(watermark.newestPartition, "2026-03-08", "newest partition is scoped to the hot tier");
341
+ assert.equal(watermark.newestWriteAt, hotMtime.toISOString(), "freshness ignores the newer cold file");
342
+ assert.equal(watermark.memoryFileCount, 2, "but the cold file still counts in the census");
343
+ } finally {
344
+ await rm(memoryDir, { recursive: true, force: true });
345
+ }
346
+ });
347
+
348
+ test("StorageManager.collectColdMemoryPaths finds cold files and leaves the hot recall scan untouched", async () => {
349
+ const memoryDir = await makeMemoryDir();
350
+ try {
351
+ await writeMemory(memoryDir, "facts/2026-03-08/hot.md");
352
+ await writeMemory(memoryDir, "cold/facts/2026-03-08/cold.md");
353
+ const storage = new StorageManager(memoryDir);
354
+ const hot = await storage.collectActiveMemoryPaths();
355
+ const cold = await storage.collectColdMemoryPaths();
356
+ assert.equal(hot.length, 1, "the hot recall scan must not pick up cold files");
357
+ assert.ok(hot[0]?.endsWith(path.join("facts", "2026-03-08", "hot.md")));
358
+ assert.equal(cold.length, 1, "the cold scan finds demoted memories under cold/");
359
+ assert.ok(cold[0]?.includes(`${path.sep}cold${path.sep}`));
360
+ } finally {
361
+ await rm(memoryDir, { recursive: true, force: true });
362
+ }
363
+ });
364
+
365
+ // ── finding C / round-4: one shared, config-driven namespace resolver ─────────
366
+
367
+ test("resolveCorpusNamespaceRoots: config-driven enumeration is deterministic and covers on-disk tenants", async () => {
368
+ const memoryDir = await makeMemoryDir();
369
+ try {
370
+ await writeMemory(memoryDir, "namespaces/team-a/facts/2026-03-08/x.md");
371
+ const config = serviceConfig(memoryDir, { namespacesEnabled: true, defaultNamespace: "global" });
372
+ // Both /health and the doctor call this ONE helper with just `{ config }`, so
373
+ // identical output here is what guarantees the two surfaces cannot drift.
374
+ const a = (await resolveCorpusNamespaceRoots({ config })).map((r) => r.namespace).sort();
375
+ const b = (await resolveCorpusNamespaceRoots({ config })).map((r) => r.namespace).sort();
376
+ assert.deepEqual(a, b, "the shared resolver is deterministic for a given config");
377
+ assert.ok(a.includes("global") && a.includes("team-a"), "config-driven enumeration finds on-disk tenants");
378
+ } finally {
379
+ await rm(memoryDir, { recursive: true, force: true });
380
+ }
381
+ });
382
+
383
+ test("computeServiceCorpusWatermarks: namespaces disabled -> single default-namespace watermark", async () => {
384
+ const memoryDir = await makeMemoryDir();
385
+ try {
386
+ const config = serviceConfig(memoryDir, { namespacesEnabled: false, defaultNamespace: "global" });
387
+ const host = {
388
+ config,
389
+ getStorage: (_namespace: string) =>
390
+ fakeStorage("/mem", ["/mem/facts/2026-03-08/a.md", "/mem/facts/2026-03-08/b.md"]),
391
+ };
392
+ const result = await computeServiceCorpusWatermarks(host);
393
+ assert.equal(result.length, 1, "flat-root deployment reports its shared corpus once");
394
+ assert.equal(result[0]?.namespace, "global");
395
+ assert.equal(result[0]?.memoryFileCount, 2);
396
+ } finally {
397
+ await rm(memoryDir, { recursive: true, force: true });
398
+ }
399
+ });
400
+
401
+ test("finding C: with the catalog opted out, config-driven enumeration still covers every tenant", async () => {
402
+ const memoryDir = await makeMemoryDir();
403
+ try {
404
+ // A tenant present on disk; there is NO persisted catalog (opted out). The
405
+ // pre-fix health path returned ONLY the default namespace here.
406
+ await writeMemory(memoryDir, "namespaces/team-a/facts/2026-03-08/x.md");
407
+ const config = serviceConfig(memoryDir, { namespacesEnabled: true, defaultNamespace: "global" });
408
+ const host = {
409
+ config,
410
+ getStorage: (namespace: string) => fakeStorage(`/mem/${namespace}`, [`/mem/${namespace}/facts/2026-03-08/x.md`]),
411
+ };
412
+ const names = (await computeServiceCorpusWatermarks(host)).map((w) => w.namespace);
413
+ assert.ok(names.includes("global"), "default namespace present");
414
+ assert.ok(names.includes("team-a"), "config-driven enumeration finds the on-disk tenant despite an empty catalog");
415
+ } finally {
416
+ await rm(memoryDir, { recursive: true, force: true });
417
+ }
418
+ });
419
+
420
+ // ── finding B: caller-capability filter ──────────────────────────────────────
421
+
422
+ test("finding B: a namespace-restricted token sees only the namespaces it may access", async () => {
423
+ const memoryDir = await makeMemoryDir();
424
+ try {
425
+ await writeMemory(memoryDir, "namespaces/team-a/facts/2026-03-08/x.md");
426
+ const config = serviceConfig(memoryDir, { namespacesEnabled: true, defaultNamespace: "global" });
427
+ const host = {
428
+ config,
429
+ getStorage: (namespace: string) => fakeStorage(`/mem/${namespace}`, [`/mem/${namespace}/facts/2026-03-08/x.md`]),
430
+ };
431
+ const restricted = await computeServiceCorpusWatermarks(host, {
432
+ caps: { version: 1, namespaces: ["team-a"] },
433
+ });
434
+ assert.deepEqual(
435
+ restricted.map((w) => w.namespace),
436
+ ["team-a"],
437
+ "a token scoped to team-a must not learn any other tenant's name, count, or digest",
438
+ );
439
+ } finally {
440
+ await rm(memoryDir, { recursive: true, force: true });
441
+ }
442
+ });
443
+
444
+ test("finding B: an unrestricted/operator token keeps the full fleet view", async () => {
445
+ const memoryDir = await makeMemoryDir();
446
+ try {
447
+ await writeMemory(memoryDir, "namespaces/team-a/facts/2026-03-08/x.md");
448
+ const config = serviceConfig(memoryDir, { namespacesEnabled: true, defaultNamespace: "global" });
449
+ const host = {
450
+ config,
451
+ getStorage: (namespace: string) => fakeStorage(`/mem/${namespace}`, [`/mem/${namespace}/facts/2026-03-08/x.md`]),
452
+ };
453
+ const names = (await computeServiceCorpusWatermarks(host, { caps: { version: 1 } })).map((w) => w.namespace);
454
+ assert.ok(names.includes("global") && names.includes("team-a"), "an unrestricted token sees all tenants");
455
+ assert.ok(names.length >= 2);
456
+ } finally {
457
+ await rm(memoryDir, { recursive: true, force: true });
458
+ }
459
+ });
460
+
461
+ // ── per-namespace degrade + fail-distinct scans ──────────────────────────────
462
+
463
+ test("computeServiceCorpusWatermarks: one failing namespace is omitted, not the whole payload (per-namespace degrade)", async () => {
464
+ const memoryDir = await makeMemoryDir();
465
+ try {
466
+ await writeMemory(memoryDir, "namespaces/team-a/facts/2026-03-08/x.md");
467
+ const config = serviceConfig(memoryDir, { namespacesEnabled: true, defaultNamespace: "global" });
468
+ const host = {
469
+ config,
470
+ getStorage: (namespace: string) => {
471
+ if (namespace === "team-a") throw new Error("storage unavailable for this tenant");
472
+ return fakeStorage(`/mem/${namespace}`, [`/mem/${namespace}/facts/2026-03-08/x.md`]);
473
+ },
474
+ };
475
+ const names = (await computeServiceCorpusWatermarks(host)).map((w) => w.namespace);
476
+ assert.ok(names.includes("global"), "a healthy namespace is still reported");
477
+ assert.ok(!names.includes("team-a"), "the failing namespace is omitted");
478
+ assert.ok(names.length >= 1, "a single bad namespace must not blank the whole corpus payload");
479
+ } finally {
480
+ await rm(memoryDir, { recursive: true, force: true });
481
+ }
482
+ });
483
+
484
+ test("finding round-4/5: an unreadable corpus root is omitted, not published as false-empty; ENOENT stays empty", async () => {
485
+ const root = await makeMemoryDir();
486
+ try {
487
+ // A regular FILE used as a namespace base dir: the strict scan hits ENOTDIR
488
+ // walking its category children — a backend read failure it must propagate,
489
+ // portably (no permission bits, so it holds even for the superuser).
490
+ const brokenBase = path.join(root, "broken-base");
491
+ await writeFile(brokenBase, "not a directory", "utf-8");
492
+ // A never-created base dir is ENOENT => a genuine empty corpus.
493
+ const missingBase = path.join(root, "never-created");
494
+
495
+ const watermarks = await computeCorpusWatermarks(
496
+ ["broken", "empty"],
497
+ (namespace) => new StorageManager(namespace === "broken" ? brokenBase : missingBase),
498
+ );
499
+ const names = watermarks.map((w) => w.namespace);
500
+ assert.ok(!names.includes("broken"), "an unreadable corpus root is omitted, not counted as zero");
501
+ assert.ok(names.includes("empty"), "a not-yet-created (ENOENT) namespace is a legitimate empty corpus");
502
+ assert.equal(watermarks.find((w) => w.namespace === "empty")?.memoryFileCount, 0);
503
+ } finally {
504
+ await rm(root, { recursive: true, force: true });
505
+ }
506
+ });
507
+
508
+ test("finding round-5: an unreadable NESTED category dir propagates (partial census is omitted, not published)", async () => {
509
+ const memoryDir = await makeMemoryDir();
510
+ try {
511
+ await writeMemory(memoryDir, "facts/2026-03-08/a.md");
512
+ const lockedDir = path.join(memoryDir, "procedures");
513
+ await mkdir(lockedDir, { recursive: true });
514
+ await chmod(lockedDir, 0o000);
515
+ // The superuser bypasses directory permissions; only assert where the OS
516
+ // actually enforces them (probe the exact op the walker performs).
517
+ let enforced = false;
518
+ try {
519
+ await readdir(lockedDir);
520
+ } catch {
521
+ enforced = true;
522
+ }
523
+ try {
524
+ const watermarks = await computeCorpusWatermarks(["global"], () => new StorageManager(memoryDir));
525
+ if (enforced) {
526
+ assert.deepEqual(watermarks, [], "an unreadable category dir omits the namespace, not a partial count");
527
+ } else {
528
+ // perms bypassed (superuser): the scan succeeds and counts the readable hot file.
529
+ assert.equal(watermarks.length, 1);
530
+ assert.equal(watermarks[0]?.memoryFileCount, 1);
531
+ }
532
+ } finally {
533
+ await chmod(lockedDir, 0o755); // restore so rm() can clean up
534
+ }
535
+ } finally {
536
+ await rm(memoryDir, { recursive: true, force: true });
537
+ }
538
+ });
539
+
540
+ // ── migration-race retry ─────────────────────────────────────────────────────
541
+
542
+ test("computeNamespaceWatermark: retries when a corpus-mutation sentinel changes mid-scan (tier-migration race)", async () => {
543
+ const memoryDir = await makeMemoryDir();
544
+ try {
545
+ let scanVersion = 0;
546
+ let racesLeft = 1; // a single racing write during the first attempt, then stable
547
+ let collectCalls = 0;
548
+ const storage: CorpusStorage = {
549
+ dir: memoryDir, // a real dir for the watermark baseDir
550
+ getCorpusScanVersion: async () => String(scanVersion),
551
+ collectActiveMemoryPaths: async () => {
552
+ collectCalls += 1;
553
+ if (racesLeft > 0) {
554
+ racesLeft -= 1;
555
+ scanVersion += 1; // a tier write lands between the before/after sentinel reads
556
+ }
557
+ return [path.join(memoryDir, "facts/2026-03-08/a.md")];
558
+ },
559
+ collectColdMemoryPaths: async () => [],
560
+ };
561
+ const [watermark] = await computeCorpusWatermarks(["global"], () => storage);
562
+ assert.equal(collectCalls, 2, "a sentinel change mid-scan triggers exactly one retry");
563
+ assert.equal(watermark?.memoryFileCount, 1, "the retried snapshot is returned");
564
+ } finally {
565
+ await rm(memoryDir, { recursive: true, force: true });
566
+ }
567
+ });
568
+
569
+ test("computeNamespaceWatermark: a stable sentinel scans once (no needless retry)", async () => {
570
+ const memoryDir = await makeMemoryDir();
571
+ try {
572
+ let collectCalls = 0;
573
+ const storage: CorpusStorage = {
574
+ dir: memoryDir,
575
+ getCorpusScanVersion: async () => "stable",
576
+ collectActiveMemoryPaths: async () => {
577
+ collectCalls += 1;
578
+ return [];
579
+ },
580
+ collectColdMemoryPaths: async () => [],
581
+ };
582
+ await computeCorpusWatermarks(["global"], () => storage);
583
+ assert.equal(collectCalls, 1, "a consistent snapshot is not re-scanned");
584
+ } finally {
585
+ await rm(memoryDir, { recursive: true, force: true });
586
+ }
587
+ });
588
+
589
+ test("computeNamespaceWatermark: throws (namespace omitted) when the sentinel never stabilizes", async () => {
590
+ const memoryDir = await makeMemoryDir();
591
+ try {
592
+ let scanVersion = 0;
593
+ let collectCalls = 0;
594
+ const storage: CorpusStorage = {
595
+ dir: memoryDir,
596
+ getCorpusScanVersion: async () => String(scanVersion),
597
+ collectActiveMemoryPaths: async () => {
598
+ collectCalls += 1;
599
+ scanVersion += 1; // a write races EVERY attempt (sustained churn)
600
+ return [path.join(memoryDir, "facts/2026-03-08/a.md")];
601
+ },
602
+ collectColdMemoryPaths: async () => [],
603
+ };
604
+ const watermarks = await computeCorpusWatermarks(["global"], () => storage);
605
+ assert.deepEqual(watermarks, [], "a never-stable census is omitted, not cached as a transient snapshot");
606
+ assert.ok(collectCalls >= 2, "it retried before giving up");
607
+ } finally {
608
+ await rm(memoryDir, { recursive: true, force: true });
609
+ }
610
+ });
611
+
612
+ // ── finding A: stale-while-revalidate TTL + single-flight cache ───────────────
613
+
614
+ test("finding A: the cache computes once for back-to-back calls and recomputes after the TTL expires", async () => {
615
+ let now = 1_000;
616
+ const cache = new CorpusWatermarkCache({ ttlMs: 60_000, clock: () => now });
617
+ let computeCalls = 0;
618
+ const compute = async (): Promise<CorpusWatermark> => {
619
+ computeCalls += 1;
620
+ return sampleWatermark();
621
+ };
622
+ assert.equal(cache.get("global", compute), undefined, "cold start returns nothing and refreshes in the background");
623
+ await cache.whenIdle();
624
+ const warm = cache.get("global", compute);
625
+ assert.ok(warm, "the value is served once the background refresh settles");
626
+ assert.equal(computeCalls, 1, "a fresh entry is not recomputed");
627
+ now += 60_001;
628
+ cache.get("global", compute); // stale -> triggers a background refresh
629
+ await cache.whenIdle();
630
+ assert.equal(computeCalls, 2, "the cache recomputes once the TTL has elapsed");
631
+ });
632
+
633
+ test("finding A: a probe serves the stale value immediately while revalidating (never blocks on the scan)", async () => {
634
+ let now = 1_000;
635
+ const cache = new CorpusWatermarkCache({ ttlMs: 60_000, clock: () => now });
636
+ let release!: () => void;
637
+ const gate = new Promise<void>((resolve) => {
638
+ release = resolve;
639
+ });
640
+ let computeCalls = 0;
641
+ const first = sampleWatermark();
642
+ const compute = async (): Promise<CorpusWatermark> => {
643
+ computeCalls += 1;
644
+ if (computeCalls === 1) return first; // warm the cache promptly
645
+ await gate; // the second (revalidation) scan is slow
646
+ return { ...sampleWatermark(), digest: "d2" };
647
+ };
648
+ cache.get("global", compute);
649
+ await cache.whenIdle(); // entry is now warm with `first`
650
+ now += 60_001; // expire
651
+
652
+ const served = cache.get("global", compute); // stale served immediately; revalidation in flight
653
+ assert.equal(served?.digest, "d", "the probe gets the stale value without awaiting the slow rescan");
654
+ assert.equal(computeCalls, 2, "a background revalidation was triggered");
655
+ release();
656
+ await cache.whenIdle();
657
+ assert.equal(cache.get("global", compute)?.digest, "d2", "the refreshed value is served after revalidation");
658
+ });
659
+
660
+ test("finding A: concurrent probes for one namespace collapse to a single computation", async () => {
661
+ const cache = new CorpusWatermarkCache();
662
+ let computeCalls = 0;
663
+ let release!: () => void;
664
+ const gate = new Promise<void>((resolve) => {
665
+ release = resolve;
666
+ });
667
+ const compute = async (): Promise<CorpusWatermark> => {
668
+ computeCalls += 1;
669
+ await gate;
670
+ return sampleWatermark();
671
+ };
672
+ cache.get("global", compute);
673
+ cache.get("global", compute);
674
+ release();
675
+ await cache.whenIdle();
676
+ assert.equal(computeCalls, 1, "N in-flight probes trigger ONE background scan, not N");
677
+ });
678
+
679
+ test("finding A: with a cache, /health probes never re-run the corpus scan within the TTL", async () => {
680
+ const memoryDir = await makeMemoryDir();
681
+ try {
682
+ const config = serviceConfig(memoryDir, { namespacesEnabled: false, defaultNamespace: "global" });
683
+ let scans = 0;
684
+ const storage: CorpusStorage = {
685
+ dir: memoryDir,
686
+ collectActiveMemoryPaths: async () => {
687
+ scans += 1;
688
+ return [path.join(memoryDir, "facts/2026-03-08/a.md")];
689
+ },
690
+ collectColdMemoryPaths: async () => [],
691
+ };
692
+ const host = { config, getStorage: (_namespace: string) => storage };
693
+ const cache = new CorpusWatermarkCache();
694
+
695
+ // Two stale-while-revalidate layers warm in turn (never blocking a probe):
696
+ // 1st probe resolves nothing (enumeration in flight); 2nd sees the roots and
697
+ // kicks off the corpus scan; 3rd serves the fully-warm watermark.
698
+ assert.deepEqual(await computeServiceCorpusWatermarks(host, { cache }), [], "probe 1 never blocks (enumeration async)");
699
+ await cache.whenIdle();
700
+ assert.deepEqual(await computeServiceCorpusWatermarks(host, { cache }), [], "probe 2 never blocks (corpus scan async)");
701
+ await cache.whenIdle();
702
+ const warm = await computeServiceCorpusWatermarks(host, { cache });
703
+ assert.equal(warm.length, 1, "the fully-warmed probe serves the cached watermark");
704
+ assert.equal(scans, 1, "the path-collector runs exactly once across the probes");
705
+ } finally {
706
+ await rm(memoryDir, { recursive: true, force: true });
707
+ }
708
+ });
709
+
710
+ // ── finding round-6: calendar validation + freshness-stat propagation ─────────
711
+
712
+ test("finding round-6: an impossible date-shaped dir is bucketed unpartitioned, not as a dated partition", () => {
713
+ const baseDir = path.join(os.tmpdir(), "mem-fixture");
714
+ const census = buildPartitionCensus(
715
+ [
716
+ path.join(baseDir, "facts/2026-03-08/a.md"),
717
+ path.join(baseDir, "facts/2026-99-99/b.md"), // date-shaped but not a real calendar day
718
+ ],
719
+ baseDir,
720
+ );
721
+ assert.equal(census.get("hot:facts/2026-03-08"), 1);
722
+ assert.equal(census.get(`hot:${UNPARTITIONED_BUCKET}`), 1, "the impossible date falls back to unpartitioned");
723
+ assert.equal(census.get("hot:facts/2026-99-99"), undefined, "an impossible date is never a dated bucket");
724
+ });
725
+
726
+ test("finding round-6: an impossible date-shaped dir never becomes newestPartition", async () => {
727
+ const paths = ["/mem/facts/2026-03-08/a.md", "/mem/facts/2026-99-99/b.md"];
728
+ const watermark = await computeCorpusWatermark({ namespace: "global", paths, baseDir: "/mem" });
729
+ assert.equal(watermark.newestPartition, "2026-03-08", "the impossible date is not promoted above the valid partition");
730
+ assert.equal(watermark.memoryFileCount, 2, "but both files still count in the census");
731
+ });
732
+
733
+ test("finding round-6: a non-ENOENT stat failure in the freshness loop propagates (not published as stale)", async () => {
734
+ const memoryDir = await makeMemoryDir();
735
+ try {
736
+ await writeMemory(memoryDir, "facts/2026-03-08/a.md");
737
+ const { paths, baseDir } = await scanHot(memoryDir);
738
+ const partitionDir = path.join(memoryDir, "facts", "2026-03-08");
739
+ await chmod(partitionDir, 0o000); // make stat() on the file (traverse its parent) fail
740
+ let enforced = false;
741
+ try {
742
+ await stat(paths[0]); // probe the exact op the freshness loop performs
743
+ } catch {
744
+ enforced = true;
745
+ }
746
+ try {
747
+ if (enforced) {
748
+ await assert.rejects(
749
+ () => computeCorpusWatermark({ namespace: "global", paths, baseDir }),
750
+ "a backend stat failure propagates instead of publishing a stale/null freshness",
751
+ );
752
+ } else {
753
+ const watermark = await computeCorpusWatermark({ namespace: "global", paths, baseDir });
754
+ assert.equal(watermark.memoryFileCount, 1, "perms bypassed (superuser): the scan simply succeeds");
755
+ }
756
+ } finally {
757
+ await chmod(partitionDir, 0o755); // restore so rm() can clean up
758
+ }
759
+ } finally {
760
+ await rm(memoryDir, { recursive: true, force: true });
761
+ }
762
+ });
763
+
764
+ test("finding round-7: namespace enumeration is cached (resolved once per TTL, served stale-while-revalidate)", async () => {
765
+ let now = 1_000;
766
+ const cache = new CorpusWatermarkCache({ ttlMs: 60_000, clock: () => now });
767
+ let resolveCalls = 0;
768
+ const resolve = async (): Promise<CorpusNamespaceRoot[]> => {
769
+ resolveCalls += 1;
770
+ return [{ namespace: "global", rootDir: "/mem", namespaces: ["global"] }];
771
+ };
772
+ assert.equal(cache.getResolvedRoots(resolve), undefined, "cold: nothing yet, enumerating in background");
773
+ await cache.whenIdle();
774
+ assert.equal(cache.getResolvedRoots(resolve)?.length, 1, "warm: served from cache");
775
+ assert.equal(resolveCalls, 1, "back-to-back probes do not re-enumerate");
776
+ now += 60_001;
777
+ cache.getResolvedRoots(resolve); // stale -> background refresh
778
+ await cache.whenIdle();
779
+ assert.equal(resolveCalls, 2, "re-enumerated once the TTL elapsed");
780
+ });
781
+
782
+ test("finding round-9: background refresh scans are bounded to maxConcurrentRefreshes", async () => {
783
+ const cache = new CorpusWatermarkCache({ maxConcurrentRefreshes: 2 });
784
+ let active = 0;
785
+ let peak = 0;
786
+ let release!: () => void;
787
+ const gate = new Promise<void>((resolve) => {
788
+ release = resolve;
789
+ });
790
+ const compute = async (): Promise<CorpusWatermark> => {
791
+ active += 1;
792
+ peak = Math.max(peak, active);
793
+ await gate;
794
+ active -= 1;
795
+ return sampleWatermark();
796
+ };
797
+ for (const namespace of ["a", "b", "c", "d", "e"]) cache.get(namespace, compute);
798
+ release();
799
+ await cache.whenIdle();
800
+ assert.ok(peak >= 1, "at least one scan ran");
801
+ assert.ok(peak <= 2, `peak concurrent scans ${peak} must not exceed the cap (2), even with 5 cold namespaces`);
802
+ });
803
+
804
+ test("finding round-10: a token scoped to a non-representative alias still sees the shared-root corpus", async () => {
805
+ const memoryDir = await makeMemoryDir();
806
+ try {
807
+ // namespaces disabled: default (global) and shared collapse onto memoryDir,
808
+ // so `global` wins dedupe. A token scoped to `shared` must still see it.
809
+ const config = serviceConfig(memoryDir, {
810
+ namespacesEnabled: false,
811
+ defaultNamespace: "global",
812
+ sharedNamespace: "shared",
813
+ });
814
+ const host = {
815
+ config,
816
+ getStorage: (_namespace: string) => fakeStorage(memoryDir, [path.join(memoryDir, "facts/2026-03-08/a.md")]),
817
+ };
818
+ const roots = await resolveCorpusNamespaceRoots({ config });
819
+ assert.equal(roots.length, 1, "the shared root is reported once");
820
+ assert.ok(roots[0]?.namespaces.includes("global") && roots[0]?.namespaces.includes("shared"), "both aliases are tracked");
821
+ const scopedToShared = await computeServiceCorpusWatermarks(host, { caps: { version: 1, namespaces: ["shared"] } });
822
+ assert.equal(scopedToShared.length, 1, "a shared-scoped token sees the shared-root corpus, not an empty array");
823
+ } finally {
824
+ await rm(memoryDir, { recursive: true, force: true });
825
+ }
826
+ });
827
+
828
+ test("finding round-10: a non-directory category root fails a strict census (namespace omitted, not partial)", async () => {
829
+ const memoryDir = await makeMemoryDir();
830
+ try {
831
+ await writeMemory(memoryDir, "facts/2026-03-08/a.md");
832
+ // A category dir replaced by a regular file is a layout corruption; the
833
+ // strict census must omit the namespace rather than publish facts/ alone.
834
+ await writeFile(path.join(memoryDir, "procedures"), "not a directory", "utf-8");
835
+ const watermarks = await computeCorpusWatermarks(["global"], () => new StorageManager(memoryDir));
836
+ assert.deepEqual(watermarks, [], "a non-directory category root omits the namespace, not a partial count");
837
+ } finally {
838
+ await rm(memoryDir, { recursive: true, force: true });
839
+ }
840
+ });