@remnic/core 9.23.4 → 9.25.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 (412) hide show
  1. package/dist/access-admin-ops-surface.d.ts +12 -10
  2. package/dist/access-admin-ops-surface.js +40 -39
  3. package/dist/access-authorization-probe.d.ts +122 -0
  4. package/dist/access-authorization-probe.js +154 -0
  5. package/dist/access-boundary.d.ts +12 -10
  6. package/dist/access-boundary.js +40 -39
  7. package/dist/access-cli.js +119 -117
  8. package/dist/access-cli.js.map +1 -1
  9. package/dist/access-health-types.d.ts +54 -0
  10. package/dist/access-health-types.js +1 -0
  11. package/dist/access-http.d.ts +12 -10
  12. package/dist/access-http.js +51 -49
  13. package/dist/access-identity-continuity-surface.d.ts +9 -8
  14. package/dist/access-identity-continuity-surface.js +40 -39
  15. package/dist/access-lcm-surface.d.ts +12 -10
  16. package/dist/access-lcm-surface.js +40 -39
  17. package/dist/access-mcp.d.ts +12 -10
  18. package/dist/access-mcp.js +46 -45
  19. package/dist/access-memory-search-fanout.d.ts +3 -2
  20. package/dist/{access-namespace-preflight-CmErR03h.d.ts → access-namespace-preflight-wRSZEO5V.d.ts} +1 -1
  21. package/dist/access-namespace-preflight.d.ts +4 -3
  22. package/dist/access-namespace-preflight.js +40 -39
  23. package/dist/access-observe-write-surface.d.ts +13 -11
  24. package/dist/access-observe-write-surface.js +40 -39
  25. package/dist/access-operations-batch.js +42 -41
  26. package/dist/access-operations.d.ts +13 -11
  27. package/dist/access-operations.js +44 -43
  28. package/dist/access-recall-concurrency.d.ts +12 -10
  29. package/dist/access-recall-concurrency.js +40 -39
  30. package/dist/access-recall-response.d.ts +12 -10
  31. package/dist/access-recall-response.js +40 -39
  32. package/dist/access-recall-surface.d.ts +12 -10
  33. package/dist/access-recall-surface.js +40 -39
  34. package/dist/access-schema.d.ts +10 -10
  35. package/dist/access-schema.js +8 -8
  36. package/dist/{access-service-Pl-h7x4p.d.ts → access-service-BlNKWBC9.d.ts} +10 -40
  37. package/dist/access-service.d.ts +13 -11
  38. package/dist/access-service.js +40 -39
  39. package/dist/access-surface-catalog.d.ts +12 -10
  40. package/dist/access-token-capabilities.d.ts +15 -1
  41. package/dist/access-token-capabilities.js +3 -1
  42. package/dist/access-wearables-meetings-surface.d.ts +4 -3
  43. package/dist/action-confidence.d.ts +2 -1
  44. package/dist/active-memory-bridge.d.ts +2 -1
  45. package/dist/active-recall.d.ts +2 -1
  46. package/dist/active-recall.js +7 -6
  47. package/dist/active-recall.js.map +1 -1
  48. package/dist/adapters/index.js +4 -4
  49. package/dist/adapters/registry.js +2 -2
  50. package/dist/auto-sync-5OE5QOZB.js +1 -1
  51. package/dist/behavior-learner.d.ts +2 -1
  52. package/dist/behavior-signals.d.ts +2 -1
  53. package/dist/bootstrap.d.ts +9 -8
  54. package/dist/briefing.d.ts +3 -2
  55. package/dist/briefing.js +9 -9
  56. package/dist/buffer-surprise-report.d.ts +2 -1
  57. package/dist/buffer.d.ts +6 -2
  58. package/dist/buffer.js +1 -1
  59. package/dist/bulk-import/index.d.ts +4 -3
  60. package/dist/calibration.d.ts +2 -1
  61. package/dist/capabilities.d.ts +2 -1
  62. package/dist/{capsule-crypto-YO5QJ6L3.js → capsule-crypto-7FJQINUR.js} +2 -2
  63. package/dist/capsule-crypto-7FJQINUR.js.map +1 -0
  64. package/dist/{catalog-BFTVo2Ez.d.ts → catalog-BLLxZ5L5.d.ts} +1 -1
  65. package/dist/causal-behavior.d.ts +2 -1
  66. package/dist/causal-behavior.js +2 -2
  67. package/dist/causal-chain.js +2 -2
  68. package/dist/causal-consolidation.d.ts +2 -1
  69. package/dist/causal-consolidation.js +12 -12
  70. package/dist/causal-retrieval.js +2 -2
  71. package/dist/causal-trajectory-graph.d.ts +2 -1
  72. package/dist/causal-trajectory-graph.js +2 -2
  73. package/dist/causal-trajectory.js +1 -1
  74. package/dist/{chunk-LEPOP46W.js → chunk-227UFSEQ.js} +4 -4
  75. package/dist/{chunk-BXLOS5AJ.js → chunk-2NLLXCJG.js} +2 -2
  76. package/dist/{chunk-34RC5RT7.js → chunk-2SA56WMI.js} +4 -4
  77. package/dist/{chunk-POHSSW2T.js → chunk-3CDMIR3C.js} +2 -2
  78. package/dist/{chunk-EXH5AK46.js → chunk-3I35RLJW.js} +27 -15
  79. package/dist/chunk-3I35RLJW.js.map +1 -0
  80. package/dist/{chunk-JRQBPN63.js → chunk-3UOSVTFR.js} +8 -8
  81. package/dist/chunk-443XCR34.js +213 -0
  82. package/dist/chunk-443XCR34.js.map +1 -0
  83. package/dist/{chunk-354T54RR.js → chunk-4TFIRMTE.js} +28 -1
  84. package/dist/chunk-4TFIRMTE.js.map +1 -0
  85. package/dist/{chunk-CNUZ2PN2.js → chunk-5ZGWA4AG.js} +14 -10
  86. package/dist/chunk-5ZGWA4AG.js.map +1 -0
  87. package/dist/{chunk-AT7XL2JJ.js → chunk-6LRTGCRV.js} +10 -10
  88. package/dist/{chunk-AJ7QCSIS.js → chunk-7CPOZH6Y.js} +2 -2
  89. package/dist/{chunk-EHOTE26K.js → chunk-AGR7MGUX.js} +2 -2
  90. package/dist/{chunk-D5ILARSD.js → chunk-AL4TZWI3.js} +2 -2
  91. package/dist/{chunk-RPOSRICW.js → chunk-AT6DNUCM.js} +2 -2
  92. package/dist/{chunk-KNB7IBBU.js → chunk-CKMO4RDY.js} +5 -5
  93. package/dist/{chunk-5CPI2QWG.js → chunk-CSXHRAJJ.js} +3 -3
  94. package/dist/{chunk-5XMJ2P62.js → chunk-DPYYSG6F.js} +7 -7
  95. package/dist/{chunk-YNDLCWXS.js → chunk-EZ25VE3G.js} +4 -4
  96. package/dist/{chunk-K3C6CJOF.js → chunk-FIGDB2HR.js} +2 -2
  97. package/dist/{chunk-Z54EM5YZ.js → chunk-GF22HWS2.js} +4 -4
  98. package/dist/{chunk-ZYU7ZFLT.js → chunk-GJJBPI6I.js} +5 -1
  99. package/dist/chunk-GJJBPI6I.js.map +1 -0
  100. package/dist/{chunk-UZ7HGRHK.js → chunk-GNFZFJBJ.js} +2 -2
  101. package/dist/{chunk-W6ROK2KU.js → chunk-HNZKVLAN.js} +2 -2
  102. package/dist/{chunk-HVYOZAQ7.js → chunk-IE4HB6O5.js} +3 -3
  103. package/dist/{chunk-RKNJBZ55.js → chunk-JBPKEARU.js} +4 -4
  104. package/dist/chunk-JMDZDT24.js +38 -0
  105. package/dist/chunk-JMDZDT24.js.map +1 -0
  106. package/dist/{chunk-UZGJFIIG.js → chunk-JYIFGGEM.js} +2 -2
  107. package/dist/{chunk-VDX2J7OX.js → chunk-KQAFEZQX.js} +2 -2
  108. package/dist/{chunk-3SHB4YAW.js → chunk-L2DPF4DF.js} +4 -4
  109. package/dist/{chunk-SPWOLGXT.js → chunk-LARPWF3S.js} +3 -3
  110. package/dist/{chunk-NSRTLSBX.js → chunk-LFHMUWA2.js} +131 -131
  111. package/dist/{chunk-AYAROCWD.js → chunk-LLISELSY.js} +2 -2
  112. package/dist/{chunk-W3BKLKG6.js → chunk-LXKJNUND.js} +2 -2
  113. package/dist/{chunk-MMTG3SCJ.js → chunk-M5EPMNS4.js} +2 -2
  114. package/dist/{chunk-KYP5FSPG.js → chunk-MNLZGCM2.js} +10 -10
  115. package/dist/{chunk-TMX4MBSK.js → chunk-MPDX5QMH.js} +2 -2
  116. package/dist/{chunk-PO5LSKQO.js → chunk-MU7ZHSZE.js} +2 -2
  117. package/dist/{chunk-7AMFUATK.js → chunk-MZWSOLMT.js} +14 -1
  118. package/dist/chunk-MZWSOLMT.js.map +1 -0
  119. package/dist/{chunk-JVOA5VDZ.js → chunk-NMGO2HAZ.js} +3 -3
  120. package/dist/{chunk-CAGPP5NB.js → chunk-NRV4Y3PM.js} +1 -1
  121. package/dist/{chunk-H2M4YA6K.js → chunk-NVYCTASB.js} +2 -2
  122. package/dist/{chunk-M2PG7OTI.js → chunk-P25IPRXP.js} +7 -7
  123. package/dist/{chunk-6MFMTNY4.js → chunk-RROMWDDC.js} +50 -54
  124. package/dist/chunk-RROMWDDC.js.map +1 -0
  125. package/dist/{chunk-FPJWQZ35.js → chunk-SZN6XVFC.js} +2 -2
  126. package/dist/{chunk-ZLDMAGCB.js → chunk-TG2DJFH7.js} +80 -80
  127. package/dist/chunk-TG2DJFH7.js.map +1 -0
  128. package/dist/{chunk-4E3DWNTJ.js → chunk-THHPDPMS.js} +2 -2
  129. package/dist/{chunk-XPLTZEJU.js → chunk-U37RGPJT.js} +2 -2
  130. package/dist/{chunk-SCOT5DTA.js → chunk-V25OWXAC.js} +1 -1
  131. package/dist/chunk-V25OWXAC.js.map +1 -0
  132. package/dist/{chunk-3UD5ZAUA.js → chunk-VY6SDKWZ.js} +2 -2
  133. package/dist/{chunk-XJTYLU6J.js → chunk-W4ZDTHWZ.js} +3 -3
  134. package/dist/{chunk-3NAHZQU3.js → chunk-X3SQYAPP.js} +2 -2
  135. package/dist/{chunk-WMLHJ5UW.js → chunk-X6ROIHFE.js} +2 -2
  136. package/dist/{chunk-DQEMWVMT.js → chunk-X7Y7WX73.js} +1 -1
  137. package/dist/{chunk-GCQIMJFH.js → chunk-XTAAG4SQ.js} +8 -8
  138. package/dist/{chunk-RSLXMGFP.js → chunk-ZN4UZWMT.js} +2 -2
  139. package/dist/{cli-DuMB83Mw.d.ts → cli-Zc6UclD1.d.ts} +5 -5
  140. package/dist/cli.d.ts +14 -12
  141. package/dist/cli.js +82 -80
  142. package/dist/compounding/engine.d.ts +3 -2
  143. package/dist/compounding/engine.js +9 -9
  144. package/dist/compounding/preference-consolidator.d.ts +2 -1
  145. package/dist/compression-optimizer.d.ts +2 -1
  146. package/dist/config.d.ts +2 -1
  147. package/dist/config.js +4 -3
  148. package/dist/connectors/codex-materialize-runner.d.ts +2 -1
  149. package/dist/connectors/codex-materialize-runner.js +9 -9
  150. package/dist/connectors/codex-materialize.d.ts +2 -1
  151. package/dist/connectors/index.d.ts +2 -1
  152. package/dist/connectors/index.js +12 -12
  153. package/dist/consolidation-provenance-check.d.ts +3 -2
  154. package/dist/consolidation-provenance-check.js +2 -2
  155. package/dist/consolidation-undo.d.ts +3 -2
  156. package/dist/contradiction/index.d.ts +3 -2
  157. package/dist/contradiction/index.js +1 -1
  158. package/dist/conversation-index/backend.d.ts +2 -1
  159. package/dist/conversation-index/backend.js +2 -2
  160. package/dist/conversation-index/chunker.d.ts +2 -1
  161. package/dist/conversation-index/faiss-adapter.d.ts +2 -1
  162. package/dist/conversation-index/indexer.d.ts +2 -1
  163. package/dist/conversation-index/search.d.ts +2 -1
  164. package/dist/corpus-watermark.d.ts +2 -1
  165. package/dist/corpus-watermark.js +18 -18
  166. package/dist/dashboard-runtime.js +2 -2
  167. package/dist/day-summary.d.ts +2 -1
  168. package/dist/delinearize.d.ts +2 -1
  169. package/dist/direct-answer-wiring.d.ts +2 -1
  170. package/dist/direct-answer.d.ts +2 -1
  171. package/dist/embedding-fallback.d.ts +2 -1
  172. package/dist/enrichment/index.d.ts +2 -1
  173. package/dist/entity-retrieval.d.ts +3 -2
  174. package/dist/entity-retrieval.js +9 -9
  175. package/dist/entity-schema.d.ts +2 -1
  176. package/dist/explicit-capture.d.ts +9 -8
  177. package/dist/explicit-capture.js +1 -1
  178. package/dist/extraction-error-classification.d.ts +2 -1
  179. package/dist/extraction-faithfulness.d.ts +2 -1
  180. package/dist/extraction-judge-telemetry.d.ts +2 -1
  181. package/dist/extraction-judge-training.d.ts +2 -1
  182. package/dist/extraction-judge.d.ts +2 -1
  183. package/dist/extraction-liveness.d.ts +5 -0
  184. package/dist/extraction-liveness.js +26 -0
  185. package/dist/extraction-liveness.js.map +1 -0
  186. package/dist/extraction.d.ts +2 -1
  187. package/dist/extraction.js +1 -1
  188. package/dist/fallback-llm.d.ts +2 -1
  189. package/dist/graph-dashboard-diff.d.ts +2 -1
  190. package/dist/graph-dashboard-key.d.ts +2 -1
  191. package/dist/graph-dashboard-parser.d.ts +2 -1
  192. package/dist/{graph-edge-decay-GF3ZSGSP.js → graph-edge-decay-Z6ENALU2.js} +4 -4
  193. package/dist/graph-edge-decay-Z6ENALU2.js.map +1 -0
  194. package/dist/graph-edge-reinforcement.d.ts +2 -1
  195. package/dist/graph-snapshot.d.ts +2 -1
  196. package/dist/graph-snapshot.js +3 -3
  197. package/dist/graph.d.ts +2 -1
  198. package/dist/graph.js +2 -2
  199. package/dist/importance.d.ts +2 -1
  200. package/dist/importers/index.d.ts +2 -1
  201. package/dist/in-flight-reads.d.ts +2 -1
  202. package/dist/index.d.ts +53 -51
  203. package/dist/index.js +229 -227
  204. package/dist/intent.d.ts +2 -1
  205. package/dist/lcm/engine.d.ts +2 -1
  206. package/dist/lcm/engine.js +2 -2
  207. package/dist/lcm/index.d.ts +2 -1
  208. package/dist/lcm/index.js +4 -4
  209. package/dist/lcm/tools.d.ts +2 -1
  210. package/dist/lifecycle.d.ts +2 -1
  211. package/dist/live-connectors-runner.d.ts +2 -1
  212. package/dist/local-llm.d.ts +2 -1
  213. package/dist/local-model-endpoint.d.ts +2 -1
  214. package/dist/maintenance/memory-governance.d.ts +2 -1
  215. package/dist/maintenance/memory-governance.js +9 -9
  216. package/dist/maintenance/rebuild-memory-lifecycle-ledger.d.ts +3 -2
  217. package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +9 -9
  218. package/dist/maintenance/rebuild-memory-projection.d.ts +3 -2
  219. package/dist/maintenance/rebuild-memory-projection.js +10 -10
  220. package/dist/{maintenance-DGvDB1xF.d.ts → maintenance-DD22oIx9.d.ts} +3 -3
  221. package/dist/mcp-memory-inspector-app.d.ts +12 -10
  222. package/dist/memory-action-policy.d.ts +2 -1
  223. package/dist/memory-cache.d.ts +2 -1
  224. package/dist/memory-lifecycle-ledger-utils.d.ts +2 -1
  225. package/dist/memory-projection-store.d.ts +2 -1
  226. package/dist/memory-provenance.d.ts +2 -1
  227. package/dist/memory-worth-outcomes.d.ts +3 -2
  228. package/dist/models-json.d.ts +2 -1
  229. package/dist/namespaces/migrate.d.ts +4 -3
  230. package/dist/namespaces/migrate.js +16 -16
  231. package/dist/namespaces/principal.d.ts +2 -1
  232. package/dist/namespaces/search.d.ts +2 -1
  233. package/dist/namespaces/search.js +6 -6
  234. package/dist/namespaces/storage.d.ts +4 -3
  235. package/dist/namespaces/storage.js +9 -9
  236. package/dist/native-knowledge.d.ts +2 -1
  237. package/dist/offline-sync-impression-drain.d.ts +2 -1
  238. package/dist/offline-sync-impression-drain.js +1 -1
  239. package/dist/operator-doctor-corpus.d.ts +2 -15
  240. package/dist/operator-doctor-corpus.js +19 -19
  241. package/dist/operator-doctor-types.d.ts +22 -0
  242. package/dist/operator-doctor-types.js +1 -0
  243. package/dist/operator-doctor-types.js.map +1 -0
  244. package/dist/operator-toolkit.d.ts +10 -13
  245. package/dist/operator-toolkit.js +32 -28
  246. package/dist/orchestration/compression-guideline-coordinator.d.ts +3 -2
  247. package/dist/orchestration/maintenance.d.ts +5 -4
  248. package/dist/orchestration/maintenance.js +15 -15
  249. package/dist/{orchestrator-RXHefhAy.d.ts → orchestrator-BTGgoKli.d.ts} +7 -7
  250. package/dist/orchestrator.d.ts +9 -8
  251. package/dist/orchestrator.js +120 -118
  252. package/dist/patterns-cli.d.ts +2 -1
  253. package/dist/{pipeline-id_-sqYm.d.ts → pipeline-Bq5zU6Bp.d.ts} +1 -1
  254. package/dist/policy-runtime.d.ts +2 -1
  255. package/dist/proactive-contention.d.ts +2 -1
  256. package/dist/provenance.d.ts +2 -1
  257. package/dist/{qmd-R6XaQOZK.d.ts → qmd-3ILJkC70.d.ts} +1 -1
  258. package/dist/qmd-preflight.d.ts +3 -2
  259. package/dist/qmd-recall-cache.d.ts +2 -1
  260. package/dist/qmd.d.ts +3 -2
  261. package/dist/recall-concurrency-config.d.ts +2 -1
  262. package/dist/recall-disclosure-escalation.d.ts +2 -1
  263. package/dist/recall-explain-renderer.d.ts +2 -1
  264. package/dist/recall-explain-renderer.js +3 -3
  265. package/dist/recall-memory-map.d.ts +2 -1
  266. package/dist/recall-planner-llm.d.ts +2 -1
  267. package/dist/recall-state.d.ts +2 -1
  268. package/dist/recall-tag-filter.d.ts +2 -1
  269. package/dist/recall-timings.d.ts +2 -1
  270. package/dist/recall-xray-cli.d.ts +2 -1
  271. package/dist/recall-xray-cli.js +4 -4
  272. package/dist/recall-xray-renderer.d.ts +2 -1
  273. package/dist/recall-xray-renderer.js +3 -3
  274. package/dist/recall-xray.d.ts +2 -1
  275. package/dist/recall-xray.js +2 -2
  276. package/dist/replay/normalizers/chatgpt.d.ts +2 -1
  277. package/dist/replay/normalizers/claude.d.ts +2 -1
  278. package/dist/replay/normalizers/openclaw.d.ts +2 -1
  279. package/dist/replay/normalizers/shared.d.ts +2 -1
  280. package/dist/replay/runner.d.ts +2 -1
  281. package/dist/replay/types.d.ts +2 -1
  282. package/dist/resolution-3K27T4VO.js +1 -1
  283. package/dist/resolve-auth-token.d.ts +2 -1
  284. package/dist/resume-bundles.js +6 -5
  285. package/dist/retrieval-agents.d.ts +3 -2
  286. package/dist/retrieval-tiers.d.ts +2 -1
  287. package/dist/routing/engine.d.ts +2 -1
  288. package/dist/routing/store.d.ts +2 -1
  289. package/dist/salvage-envelope.d.ts +2 -1
  290. package/dist/salvage-envelope.js +1 -1
  291. package/dist/{scope-profiles-EhyuAKkd.d.ts → scope-profiles-D0-ff4xT.d.ts} +1 -1
  292. package/dist/search/embed-helper.d.ts +2 -1
  293. package/dist/search/factory.d.ts +2 -1
  294. package/dist/search/factory.js +5 -5
  295. package/dist/search/index.d.ts +2 -1
  296. package/dist/search/index.js +9 -9
  297. package/dist/search/lancedb-backend.d.ts +2 -1
  298. package/dist/search/meilisearch-backend.d.ts +2 -1
  299. package/dist/search/noop-backend.d.ts +2 -1
  300. package/dist/search/orama-backend.d.ts +2 -1
  301. package/dist/search/port.d.ts +2 -1
  302. package/dist/search/remote-backend.d.ts +2 -1
  303. package/dist/{semantic-consolidation-B2xo26UQ.d.ts → semantic-consolidation-4Tfh7c1g.d.ts} +1 -1
  304. package/dist/semantic-consolidation.d.ts +3 -2
  305. package/dist/semantic-consolidation.js +14 -14
  306. package/dist/semantic-rule-promotion.js +9 -9
  307. package/dist/semantic-rule-verifier.d.ts +2 -1
  308. package/dist/semantic-rule-verifier.js +9 -9
  309. package/dist/{service-BrWqlsZ4.d.ts → service-Z8uzoAST.d.ts} +2 -2
  310. package/dist/session-observer-bands.d.ts +2 -1
  311. package/dist/session-observer-state.d.ts +2 -1
  312. package/dist/shared-context/manager.d.ts +2 -1
  313. package/dist/signal.d.ts +2 -1
  314. package/dist/{storage-DgNVexXm.d.ts → storage-BZVMn8yJ.d.ts} +1 -1
  315. package/dist/storage.d.ts +3 -2
  316. package/dist/storage.js +8 -8
  317. package/dist/summarizer.d.ts +2 -1
  318. package/dist/summary-snapshot.d.ts +2 -1
  319. package/dist/temporal-supersession.d.ts +3 -2
  320. package/dist/temporal-timeline-recall.d.ts +2 -1
  321. package/dist/temporal-validity.d.ts +2 -1
  322. package/dist/threading.d.ts +2 -1
  323. package/dist/tier-migration.d.ts +3 -2
  324. package/dist/tier-routing.d.ts +2 -1
  325. package/dist/tokens.js +2 -2
  326. package/dist/topics.d.ts +2 -1
  327. package/dist/transcript.d.ts +2 -1
  328. package/dist/transfer/backup.js +4 -4
  329. package/dist/transfer/capsule-export.js +4 -4
  330. package/dist/transfer/capsule-import.js +3 -3
  331. package/dist/trust-score-stage.d.ts +2 -1
  332. package/dist/trust-score.d.ts +2 -1
  333. package/dist/{types-Bbs0qz6b.d.ts → types-DHBxIF0s.d.ts} +181 -5
  334. package/dist/types.d.ts +2 -1
  335. package/dist/types.js +1 -1
  336. package/dist/utility-runtime.d.ts +2 -1
  337. package/dist/verified-recall.js +9 -9
  338. package/dist/write-envelope.d.ts +2 -1
  339. package/dist/write-envelope.js +1 -1
  340. package/package.json +2 -2
  341. package/src/access-authorization-probe.ts +43 -0
  342. package/src/access-boundary.ts +2 -31
  343. package/src/access-health-types.ts +51 -0
  344. package/src/access-http.test.ts +94 -0
  345. package/src/access-http.ts +11 -0
  346. package/src/access-service-health.test.ts +212 -0
  347. package/src/access-service.ts +11 -39
  348. package/src/access-surface-catalog.test.ts +4 -3
  349. package/src/access-token-capabilities.ts +31 -0
  350. package/src/buffer.ts +30 -0
  351. package/src/cli.ts +2 -4
  352. package/src/config.ts +4 -4
  353. package/src/extraction-liveness.test.ts +461 -0
  354. package/src/extraction-liveness.ts +462 -0
  355. package/src/operator-doctor-types.ts +20 -0
  356. package/src/operator-toolkit.ts +9 -10
  357. package/src/types.ts +4 -4
  358. package/dist/chunk-354T54RR.js.map +0 -1
  359. package/dist/chunk-6MFMTNY4.js.map +0 -1
  360. package/dist/chunk-7AMFUATK.js.map +0 -1
  361. package/dist/chunk-CNUZ2PN2.js.map +0 -1
  362. package/dist/chunk-EXH5AK46.js.map +0 -1
  363. package/dist/chunk-SCOT5DTA.js.map +0 -1
  364. package/dist/chunk-ZLDMAGCB.js.map +0 -1
  365. package/dist/chunk-ZYU7ZFLT.js.map +0 -1
  366. /package/dist/{capsule-crypto-YO5QJ6L3.js.map → access-authorization-probe.js.map} +0 -0
  367. /package/dist/{graph-edge-decay-GF3ZSGSP.js.map → access-health-types.js.map} +0 -0
  368. /package/dist/{chunk-LEPOP46W.js.map → chunk-227UFSEQ.js.map} +0 -0
  369. /package/dist/{chunk-BXLOS5AJ.js.map → chunk-2NLLXCJG.js.map} +0 -0
  370. /package/dist/{chunk-34RC5RT7.js.map → chunk-2SA56WMI.js.map} +0 -0
  371. /package/dist/{chunk-POHSSW2T.js.map → chunk-3CDMIR3C.js.map} +0 -0
  372. /package/dist/{chunk-JRQBPN63.js.map → chunk-3UOSVTFR.js.map} +0 -0
  373. /package/dist/{chunk-AT7XL2JJ.js.map → chunk-6LRTGCRV.js.map} +0 -0
  374. /package/dist/{chunk-AJ7QCSIS.js.map → chunk-7CPOZH6Y.js.map} +0 -0
  375. /package/dist/{chunk-EHOTE26K.js.map → chunk-AGR7MGUX.js.map} +0 -0
  376. /package/dist/{chunk-D5ILARSD.js.map → chunk-AL4TZWI3.js.map} +0 -0
  377. /package/dist/{chunk-RPOSRICW.js.map → chunk-AT6DNUCM.js.map} +0 -0
  378. /package/dist/{chunk-KNB7IBBU.js.map → chunk-CKMO4RDY.js.map} +0 -0
  379. /package/dist/{chunk-5CPI2QWG.js.map → chunk-CSXHRAJJ.js.map} +0 -0
  380. /package/dist/{chunk-5XMJ2P62.js.map → chunk-DPYYSG6F.js.map} +0 -0
  381. /package/dist/{chunk-YNDLCWXS.js.map → chunk-EZ25VE3G.js.map} +0 -0
  382. /package/dist/{chunk-K3C6CJOF.js.map → chunk-FIGDB2HR.js.map} +0 -0
  383. /package/dist/{chunk-Z54EM5YZ.js.map → chunk-GF22HWS2.js.map} +0 -0
  384. /package/dist/{chunk-UZ7HGRHK.js.map → chunk-GNFZFJBJ.js.map} +0 -0
  385. /package/dist/{chunk-W6ROK2KU.js.map → chunk-HNZKVLAN.js.map} +0 -0
  386. /package/dist/{chunk-HVYOZAQ7.js.map → chunk-IE4HB6O5.js.map} +0 -0
  387. /package/dist/{chunk-RKNJBZ55.js.map → chunk-JBPKEARU.js.map} +0 -0
  388. /package/dist/{chunk-UZGJFIIG.js.map → chunk-JYIFGGEM.js.map} +0 -0
  389. /package/dist/{chunk-VDX2J7OX.js.map → chunk-KQAFEZQX.js.map} +0 -0
  390. /package/dist/{chunk-3SHB4YAW.js.map → chunk-L2DPF4DF.js.map} +0 -0
  391. /package/dist/{chunk-SPWOLGXT.js.map → chunk-LARPWF3S.js.map} +0 -0
  392. /package/dist/{chunk-NSRTLSBX.js.map → chunk-LFHMUWA2.js.map} +0 -0
  393. /package/dist/{chunk-AYAROCWD.js.map → chunk-LLISELSY.js.map} +0 -0
  394. /package/dist/{chunk-W3BKLKG6.js.map → chunk-LXKJNUND.js.map} +0 -0
  395. /package/dist/{chunk-MMTG3SCJ.js.map → chunk-M5EPMNS4.js.map} +0 -0
  396. /package/dist/{chunk-KYP5FSPG.js.map → chunk-MNLZGCM2.js.map} +0 -0
  397. /package/dist/{chunk-TMX4MBSK.js.map → chunk-MPDX5QMH.js.map} +0 -0
  398. /package/dist/{chunk-PO5LSKQO.js.map → chunk-MU7ZHSZE.js.map} +0 -0
  399. /package/dist/{chunk-JVOA5VDZ.js.map → chunk-NMGO2HAZ.js.map} +0 -0
  400. /package/dist/{chunk-CAGPP5NB.js.map → chunk-NRV4Y3PM.js.map} +0 -0
  401. /package/dist/{chunk-H2M4YA6K.js.map → chunk-NVYCTASB.js.map} +0 -0
  402. /package/dist/{chunk-M2PG7OTI.js.map → chunk-P25IPRXP.js.map} +0 -0
  403. /package/dist/{chunk-FPJWQZ35.js.map → chunk-SZN6XVFC.js.map} +0 -0
  404. /package/dist/{chunk-4E3DWNTJ.js.map → chunk-THHPDPMS.js.map} +0 -0
  405. /package/dist/{chunk-XPLTZEJU.js.map → chunk-U37RGPJT.js.map} +0 -0
  406. /package/dist/{chunk-3UD5ZAUA.js.map → chunk-VY6SDKWZ.js.map} +0 -0
  407. /package/dist/{chunk-XJTYLU6J.js.map → chunk-W4ZDTHWZ.js.map} +0 -0
  408. /package/dist/{chunk-3NAHZQU3.js.map → chunk-X3SQYAPP.js.map} +0 -0
  409. /package/dist/{chunk-WMLHJ5UW.js.map → chunk-X6ROIHFE.js.map} +0 -0
  410. /package/dist/{chunk-DQEMWVMT.js.map → chunk-X7Y7WX73.js.map} +0 -0
  411. /package/dist/{chunk-GCQIMJFH.js.map → chunk-XTAAG4SQ.js.map} +0 -0
  412. /package/dist/{chunk-RSLXMGFP.js.map → chunk-ZN4UZWMT.js.map} +0 -0
@@ -0,0 +1,461 @@
1
+ import assert from "node:assert/strict";
2
+ import test from "node:test";
3
+
4
+ import { SmartBuffer } from "./buffer.js";
5
+ import { parseConfig } from "./config.js";
6
+ import {
7
+ ExtractionLivenessWarnThrottle,
8
+ evaluateExtractionLiveness,
9
+ parseExtractionLivenessConfig,
10
+ renderExtractionLivenessStats,
11
+ resolveExtractionLivenessConfig,
12
+ summarizeExtractionLiveness,
13
+ type ExtractionBufferSnapshot,
14
+ type ExtractionLivenessStatus,
15
+ } from "./extraction-liveness.js";
16
+ import type { BufferState, BufferTurn } from "./types.js";
17
+
18
+ const NOW = 1_700_000_000_000;
19
+ const WINDOW = 1000;
20
+
21
+ function turn(timestamp: string, content = "hi"): BufferTurn {
22
+ return { role: "user", content, timestamp };
23
+ }
24
+
25
+ function snapshot(over: Partial<ExtractionBufferSnapshot> = {}): ExtractionBufferSnapshot {
26
+ return { bufferedSessionCount: 0, pendingTurnCount: 0, oldestTurnTimestamp: null, ...over };
27
+ }
28
+
29
+ class FakeBufferStorage {
30
+ constructor(private readonly initial: BufferState) {}
31
+ async loadBuffer(): Promise<BufferState> {
32
+ return structuredClone(this.initial);
33
+ }
34
+ async saveBuffer(): Promise<void> {}
35
+ }
36
+
37
+ function bufferFor(state: BufferState): SmartBuffer {
38
+ return new SmartBuffer(
39
+ parseConfig({}),
40
+ new FakeBufferStorage(state) as unknown as ConstructorParameters<typeof SmartBuffer>[1],
41
+ );
42
+ }
43
+
44
+ // ── parseExtractionLivenessConfig ────────────────────────────────────────────
45
+
46
+ test("parseExtractionLivenessConfig: defaults enabled=true, staleWindowMs=24h", () => {
47
+ assert.deepEqual(parseExtractionLivenessConfig({}), {
48
+ enabled: true,
49
+ staleWindowMs: 86_400_000,
50
+ });
51
+ });
52
+
53
+ test("parseExtractionLivenessConfig: coerces string/boolean falsy opt-outs (§24)", () => {
54
+ assert.equal(parseExtractionLivenessConfig({ extractionLiveness: { enabled: "false" } }).enabled, false);
55
+ assert.equal(parseExtractionLivenessConfig({ extractionLiveness: { enabled: "0" } }).enabled, false);
56
+ assert.equal(parseExtractionLivenessConfig({ extractionLiveness: { enabled: false } }).enabled, false);
57
+ assert.equal(parseExtractionLivenessConfig({ extractionLiveness: { enabled: "true" } }).enabled, true);
58
+ });
59
+
60
+ test("parseExtractionLivenessConfig: honors a valid integer staleWindowMs (number or string)", () => {
61
+ assert.equal(parseExtractionLivenessConfig({ extractionLiveness: { staleWindowMs: 5000 } }).staleWindowMs, 5000);
62
+ assert.equal(parseExtractionLivenessConfig({ extractionLiveness: { staleWindowMs: "7000" } }).staleWindowMs, 7000);
63
+ });
64
+
65
+ test("parseExtractionLivenessConfig: rejects fractional/non-positive/non-numeric staleWindowMs (§1/§17/§39)", () => {
66
+ // Fractional values must be REJECTED, not floored: "0.5"→0 would make every
67
+ // backlog instantly stale and 1.9→1 is a silent reinterpretation.
68
+ for (const bad of ["0.5", 1.9, 0, -5, "abc"]) {
69
+ assert.throws(
70
+ () => parseExtractionLivenessConfig({ extractionLiveness: { staleWindowMs: bad } }),
71
+ /staleWindowMs must be an integer greater than or equal to 1/,
72
+ `expected ${JSON.stringify(bad)} to be rejected`,
73
+ );
74
+ }
75
+ });
76
+
77
+ test("parseExtractionLivenessConfig: rejects an explicit null staleWindowMs; only an absent key defaults (round 9 finding 1 parity)", () => {
78
+ // An explicit `null` is invalid input, not "use the default" — mirrors
79
+ // config.ts parseIntegerInClosedRange. Only an absent key (undefined) defaults.
80
+ assert.throws(
81
+ () => parseExtractionLivenessConfig({ extractionLiveness: { staleWindowMs: null } }),
82
+ /staleWindowMs must be an integer greater than or equal to 1/,
83
+ );
84
+ assert.equal(
85
+ parseExtractionLivenessConfig({ extractionLiveness: { staleWindowMs: undefined } }).staleWindowMs,
86
+ 86_400_000,
87
+ );
88
+ });
89
+
90
+ test("parseExtractionLivenessConfig: rejects a non-object block", () => {
91
+ assert.throws(() => parseExtractionLivenessConfig({ extractionLiveness: 5 }), /must be a plain object/);
92
+ assert.throws(() => parseExtractionLivenessConfig({ extractionLiveness: [] }), /must be a plain object/);
93
+ });
94
+
95
+ // ── evaluateExtractionLiveness: degraded matrix ──────────────────────────────
96
+
97
+ const ENABLED = { enabled: true, staleWindowMs: WINDOW };
98
+ const ancient = new Date(NOW - 10 * WINDOW).toISOString();
99
+ const nonEmpty = snapshot({ bufferedSessionCount: 2, pendingTurnCount: 5, oldestTurnTimestamp: ancient });
100
+
101
+ test("evaluate: disabled is never degraded even with a stale watermark and backlog", () => {
102
+ const status = evaluateExtractionLiveness({
103
+ config: { enabled: false, staleWindowMs: WINDOW },
104
+ lastExtractionAt: ancient,
105
+ snapshot: nonEmpty,
106
+ nowMs: NOW,
107
+ });
108
+ assert.equal(status.degraded, false);
109
+ assert.equal(status.degradedReason, null);
110
+ assert.equal(status.bufferedSessionCount, 2);
111
+ });
112
+
113
+ test("evaluate: empty buffer with an ancient watermark is NOT degraded (nothing to extract, §22)", () => {
114
+ const status = evaluateExtractionLiveness({
115
+ config: ENABLED,
116
+ lastExtractionAt: ancient,
117
+ snapshot: snapshot(),
118
+ nowMs: NOW,
119
+ });
120
+ assert.equal(status.degraded, false);
121
+ });
122
+
123
+ test("evaluate: non-empty buffer with an ancient watermark is degraded", () => {
124
+ const status = evaluateExtractionLiveness({
125
+ config: ENABLED,
126
+ lastExtractionAt: ancient,
127
+ snapshot: nonEmpty,
128
+ nowMs: NOW,
129
+ });
130
+ assert.equal(status.degraded, true);
131
+ assert.match(status.degradedReason ?? "", /buffered session/);
132
+ assert.equal(status.oldestBufferedTurnAgeMs, 10 * WINDOW);
133
+ });
134
+
135
+ test("evaluate: non-empty buffer with a null watermark (never succeeded) is degraded", () => {
136
+ const status = evaluateExtractionLiveness({
137
+ config: ENABLED,
138
+ lastExtractionAt: null,
139
+ snapshot: nonEmpty,
140
+ nowMs: NOW,
141
+ });
142
+ assert.equal(status.degraded, true);
143
+ assert.match(status.degradedReason ?? "", /no successful extraction on record/);
144
+ });
145
+
146
+ test("evaluate: staleness boundary is half-open — exactly staleWindowMs is stale, just under is fresh (§23)", () => {
147
+ const atBoundary = evaluateExtractionLiveness({
148
+ config: ENABLED,
149
+ lastExtractionAt: new Date(NOW - WINDOW).toISOString(),
150
+ snapshot: nonEmpty,
151
+ nowMs: NOW,
152
+ });
153
+ assert.equal(atBoundary.degraded, true, "age === staleWindowMs is stale");
154
+
155
+ const justUnder = evaluateExtractionLiveness({
156
+ config: ENABLED,
157
+ lastExtractionAt: new Date(NOW - (WINDOW - 1)).toISOString(),
158
+ snapshot: nonEmpty,
159
+ nowMs: NOW,
160
+ });
161
+ assert.equal(justUnder.degraded, false, "age === staleWindowMs - 1 is fresh");
162
+ });
163
+
164
+ test("evaluate: a future (clock-skewed/corrupt) watermark is stale, not fresh — a real backlog stall is not hidden", () => {
165
+ const status = evaluateExtractionLiveness({
166
+ config: ENABLED,
167
+ lastExtractionAt: new Date(NOW + 10 * WINDOW).toISOString(),
168
+ snapshot: nonEmpty,
169
+ nowMs: NOW,
170
+ });
171
+ assert.equal(status.degraded, true, "a future watermark must not read as a fresh extraction");
172
+ assert.match(status.degradedReason ?? "", /buffered session/);
173
+ });
174
+
175
+ test("evaluate: an unreadable buffer degrades with a distinct reason, even with a fresh watermark (§22)", () => {
176
+ const fresh = new Date(NOW).toISOString();
177
+ const readFailedSnap = snapshot({ readFailed: true, readError: "ENOENT: buffer.json missing" });
178
+ const status = evaluateExtractionLiveness({ config: ENABLED, lastExtractionAt: fresh, snapshot: readFailedSnap, nowMs: NOW });
179
+ assert.equal(status.degraded, true, "an unreadable buffer is a pipeline fault, not empty");
180
+ assert.match(status.degradedReason ?? "", /unreadable/);
181
+ assert.match(status.degradedReason ?? "", /ENOENT: buffer\.json missing/);
182
+
183
+ // Distinct from a genuinely empty buffer: same fresh watermark, no read failure → healthy.
184
+ const empty = evaluateExtractionLiveness({ config: ENABLED, lastExtractionAt: fresh, snapshot: snapshot(), nowMs: NOW });
185
+ assert.equal(empty.degraded, false, "an empty buffer with a fresh watermark stays healthy");
186
+
187
+ // The master gate still suppresses degradation when the feature is disabled.
188
+ const disabled = evaluateExtractionLiveness({
189
+ config: { enabled: false, staleWindowMs: WINDOW },
190
+ lastExtractionAt: fresh,
191
+ snapshot: readFailedSnap,
192
+ nowMs: NOW,
193
+ });
194
+ assert.equal(disabled.degraded, false, "disabled gate suppresses even a read-failure degradation");
195
+ });
196
+
197
+ test("evaluate: an unreadable watermark degrades with a distinct reason, even with an empty buffer (§22)", () => {
198
+ const status = evaluateExtractionLiveness({
199
+ config: ENABLED,
200
+ lastExtractionAt: null,
201
+ snapshot: snapshot(),
202
+ nowMs: NOW,
203
+ metaReadFailed: true,
204
+ metaReadError: "EIO: meta.json unreadable",
205
+ });
206
+ assert.equal(status.degraded, true, "an unreadable watermark is a storage fault, not never-extracted");
207
+ assert.match(status.degradedReason ?? "", /watermark unreadable/);
208
+ assert.match(status.degradedReason ?? "", /EIO: meta\.json unreadable/);
209
+
210
+ // The three fault classes carry distinct reasons (meta-read vs buffer-read vs never-extracted).
211
+ const bufferFail = evaluateExtractionLiveness({
212
+ config: ENABLED,
213
+ lastExtractionAt: new Date(NOW).toISOString(),
214
+ snapshot: snapshot({ readFailed: true, readError: "boom" }),
215
+ nowMs: NOW,
216
+ });
217
+ assert.match(bufferFail.degradedReason ?? "", /buffer unreadable/);
218
+
219
+ const neverExtracted = evaluateExtractionLiveness({
220
+ config: ENABLED,
221
+ lastExtractionAt: null,
222
+ snapshot: snapshot({ bufferedSessionCount: 1, pendingTurnCount: 1 }),
223
+ nowMs: NOW,
224
+ });
225
+ assert.match(neverExtracted.degradedReason ?? "", /no successful extraction on record/);
226
+
227
+ // A meta-read failure takes precedence over staleness when both would degrade.
228
+ const metaOverStale = evaluateExtractionLiveness({
229
+ config: ENABLED,
230
+ lastExtractionAt: null,
231
+ snapshot: snapshot({ bufferedSessionCount: 3, pendingTurnCount: 9 }),
232
+ nowMs: NOW,
233
+ metaReadFailed: true,
234
+ metaReadError: "boom",
235
+ });
236
+ assert.match(metaOverStale.degradedReason ?? "", /watermark unreadable/);
237
+ });
238
+
239
+ // ── SmartBuffer.getBufferSnapshot ────────────────────────────────────────────
240
+
241
+ test("getBufferSnapshot: empty buffer reports zeros and a null oldest", async () => {
242
+ const snap = await bufferFor({ turns: [], lastExtractionAt: null, extractionCount: 0 }).getBufferSnapshot();
243
+ assert.deepEqual(snap, { bufferedSessionCount: 0, pendingTurnCount: 0, oldestTurnTimestamp: null });
244
+ });
245
+
246
+ test("getBufferSnapshot: counts sessions/turns across the entries map and picks the oldest turn", async () => {
247
+ const older = "2026-02-01T00:00:00.000Z";
248
+ const newer = "2026-03-01T00:00:00.000Z";
249
+ const snap = await bufferFor({
250
+ turns: [],
251
+ lastExtractionAt: null,
252
+ extractionCount: 0,
253
+ entries: {
254
+ recent: { turns: [turn(newer)], lastExtractionAt: null, extractionCount: 0 },
255
+ stale: { turns: [turn(older), turn("2026-03-15T00:00:00.000Z")], lastExtractionAt: null, extractionCount: 0 },
256
+ },
257
+ }).getBufferSnapshot();
258
+ assert.equal(snap.bufferedSessionCount, 2);
259
+ assert.equal(snap.pendingTurnCount, 3);
260
+ assert.equal(snap.oldestTurnTimestamp, older);
261
+ });
262
+
263
+ test("getBufferSnapshot: covers the legacy top-level turns array (single session)", async () => {
264
+ const first = "2026-01-10T00:00:00.000Z";
265
+ const snap = await bufferFor({
266
+ turns: [turn(first), turn("2026-01-11T00:00:00.000Z")],
267
+ lastExtractionAt: null,
268
+ extractionCount: 0,
269
+ }).getBufferSnapshot();
270
+ assert.equal(snap.bufferedSessionCount, 1);
271
+ assert.equal(snap.pendingTurnCount, 2);
272
+ assert.equal(snap.oldestTurnTimestamp, first);
273
+ });
274
+
275
+ test("getBufferSnapshot: empty entries are not counted as sessions", async () => {
276
+ const snap = await bufferFor({
277
+ turns: [],
278
+ lastExtractionAt: null,
279
+ extractionCount: 0,
280
+ entries: {
281
+ empty: { turns: [], lastExtractionAt: null, extractionCount: 0 },
282
+ live: { turns: [turn("2026-04-01T00:00:00.000Z")], lastExtractionAt: null, extractionCount: 0 },
283
+ },
284
+ }).getBufferSnapshot();
285
+ assert.equal(snap.bufferedSessionCount, 1);
286
+ assert.equal(snap.pendingTurnCount, 1);
287
+ });
288
+
289
+ test("getBufferSnapshot: counts retained (deferred) turns as pending, not just active (#562)", async () => {
290
+ const snap = await bufferFor({
291
+ turns: [],
292
+ lastExtractionAt: null,
293
+ extractionCount: 0,
294
+ entries: {
295
+ mixed: {
296
+ turns: [turn("2026-04-01T00:00:02.000Z")],
297
+ retainedTurns: [turn("2026-04-01T00:00:00.000Z"), turn("2026-04-01T00:00:01.000Z")],
298
+ lastExtractionAt: null,
299
+ extractionCount: 0,
300
+ },
301
+ retainedOnly: {
302
+ turns: [],
303
+ retainedTurns: [turn("2026-04-01T00:00:03.000Z")],
304
+ lastExtractionAt: null,
305
+ extractionCount: 0,
306
+ },
307
+ },
308
+ }).getBufferSnapshot();
309
+ assert.equal(snap.bufferedSessionCount, 2, "an entry with only retained turns still counts");
310
+ assert.equal(snap.pendingTurnCount, 4, "retained (2 + 1) + active (1) = 4");
311
+ assert.equal(snap.oldestTurnTimestamp, "2026-04-01T00:00:00.000Z", "oldest is the first retained turn");
312
+ });
313
+
314
+ test("evaluate: emits watermarkScope, defaulting to root-store and honoring an explicit scope", () => {
315
+ const rootScoped = evaluateExtractionLiveness({
316
+ config: ENABLED,
317
+ lastExtractionAt: null,
318
+ snapshot: snapshot(),
319
+ nowMs: NOW,
320
+ });
321
+ assert.equal(rootScoped.watermarkScope, "root-store", "defaults to root-store today (#2159)");
322
+ const aggregate = evaluateExtractionLiveness({
323
+ config: ENABLED,
324
+ lastExtractionAt: null,
325
+ snapshot: snapshot(),
326
+ nowMs: NOW,
327
+ watermarkScope: "aggregate",
328
+ });
329
+ assert.equal(aggregate.watermarkScope, "aggregate", "threads an explicit scope for #2159");
330
+ });
331
+
332
+ // ── ExtractionLivenessWarnThrottle ───────────────────────────────────────────
333
+
334
+ test("throttle: warns once per staleness window and again after it elapses; resets on recovery", () => {
335
+ const degraded: ExtractionLivenessStatus = {
336
+ lastExtractionAt: null,
337
+ bufferedSessionCount: 1,
338
+ pendingTurnCount: 1,
339
+ oldestBufferedTurnAgeMs: null,
340
+ degraded: true,
341
+ degradedReason: "stalled",
342
+ watermarkScope: "root-store",
343
+ };
344
+ const healthy: ExtractionLivenessStatus = { ...degraded, degraded: false, degradedReason: null };
345
+ const t = new ExtractionLivenessWarnThrottle();
346
+
347
+ assert.equal(t.maybeWarn(degraded, WINDOW, NOW), true, "first degraded evaluation warns");
348
+ assert.equal(t.maybeWarn(degraded, WINDOW, NOW + WINDOW - 1), false, "same window is throttled");
349
+ assert.equal(t.maybeWarn(degraded, WINDOW, NOW + WINDOW), true, "next window warns again");
350
+ assert.equal(t.maybeWarn(healthy, WINDOW, NOW + WINDOW + 1), false, "healthy never warns");
351
+ assert.equal(t.maybeWarn(degraded, WINDOW, NOW + WINDOW + 2), true, "recovery reset lets a fresh episode warn at once");
352
+ });
353
+
354
+ // ── renderExtractionLivenessStats ────────────────────────────────────────────
355
+
356
+ test("renderExtractionLivenessStats: emits watermark, backlog, and a degraded verdict", async () => {
357
+ const orchestrator = {
358
+ config: { extractionLiveness: { enabled: true, staleWindowMs: WINDOW } },
359
+ storage: {
360
+ loadMeta: async () => ({ extractionCount: 7, lastExtractionAt: ancient, lastConsolidationAt: null }),
361
+ },
362
+ buffer: {
363
+ getBufferSnapshot: async (): Promise<ExtractionBufferSnapshot> => ({
364
+ bufferedSessionCount: 2,
365
+ pendingTurnCount: 5,
366
+ oldestTurnTimestamp: ancient,
367
+ }),
368
+ },
369
+ };
370
+ const lines = await renderExtractionLivenessStats(orchestrator, NOW);
371
+ assert.ok(lines.includes("Extractions: 7"), "reports extraction count");
372
+ assert.ok(lines.includes("Buffered sessions: 2 (5 turns pending)"), "reports backlog");
373
+ assert.ok(lines.includes("Extraction watermark scope: root-store"), "reports the watermark scope");
374
+ assert.ok(
375
+ lines.some((l) => l.startsWith("Extraction liveness: DEGRADED")),
376
+ "reports a degraded verdict when the watermark is stale",
377
+ );
378
+ });
379
+
380
+ test("renderExtractionLivenessStats: reports DEGRADED when the buffer read fails (§22)", async () => {
381
+ const orchestrator = {
382
+ config: { extractionLiveness: { enabled: true, staleWindowMs: WINDOW } },
383
+ storage: {
384
+ loadMeta: async () => ({
385
+ extractionCount: 7,
386
+ lastExtractionAt: new Date(NOW).toISOString(),
387
+ lastConsolidationAt: null,
388
+ }),
389
+ },
390
+ buffer: {
391
+ getBufferSnapshot: async (): Promise<ExtractionBufferSnapshot> => {
392
+ throw new Error("buffer file corrupt");
393
+ },
394
+ },
395
+ };
396
+ const lines = await renderExtractionLivenessStats(orchestrator, NOW);
397
+ const verdict = lines.find((l) => l.startsWith("Extraction liveness:"));
398
+ assert.ok(verdict, "emits a liveness verdict line");
399
+ assert.match(verdict, /DEGRADED/);
400
+ assert.match(verdict, /unreadable/);
401
+ assert.match(verdict, /buffer file corrupt/);
402
+ });
403
+
404
+ test("renderExtractionLivenessStats: reports DEGRADED + unavailable counts when the meta read fails (§22)", async () => {
405
+ const orchestrator = {
406
+ config: { extractionLiveness: { enabled: true, staleWindowMs: WINDOW } },
407
+ storage: {
408
+ loadMeta: async () => {
409
+ throw new Error("meta.json corrupt");
410
+ },
411
+ },
412
+ buffer: {
413
+ // Empty buffer: proves a meta-read failure degrades even with nothing buffered.
414
+ getBufferSnapshot: async (): Promise<ExtractionBufferSnapshot> => ({
415
+ bufferedSessionCount: 0,
416
+ pendingTurnCount: 0,
417
+ oldestTurnTimestamp: null,
418
+ }),
419
+ },
420
+ };
421
+ const lines = await renderExtractionLivenessStats(orchestrator, NOW);
422
+ assert.ok(lines.includes("Extractions: unavailable"), "meta counts read as unavailable, not 0/never");
423
+ const verdict = lines.find((l) => l.startsWith("Extraction liveness:"));
424
+ assert.match(verdict ?? "", /DEGRADED/);
425
+ assert.match(verdict ?? "", /watermark unreadable/);
426
+ assert.match(verdict ?? "", /meta\.json corrupt/);
427
+ });
428
+
429
+ test("resolveExtractionLivenessConfig: absent or partial block defaults to enabled/24h (read-boundary robustness)", () => {
430
+ const DEFAULT = { enabled: true, staleWindowMs: 86_400_000 };
431
+ assert.deepEqual(resolveExtractionLivenessConfig(undefined), DEFAULT, "absent block -> documented default");
432
+ assert.deepEqual(resolveExtractionLivenessConfig({}), DEFAULT, "partial {} -> documented default");
433
+ assert.deepEqual(
434
+ resolveExtractionLivenessConfig({ enabled: false }),
435
+ { enabled: false, staleWindowMs: 86_400_000 },
436
+ "explicit enabled honored; missing window defaults",
437
+ );
438
+ assert.deepEqual(resolveExtractionLivenessConfig({ staleWindowMs: 5000 }), { enabled: true, staleWindowMs: 5000 });
439
+ // A present-but-invalid window (0/fractional) falls back rather than making every backlog instantly stale.
440
+ assert.equal(resolveExtractionLivenessConfig({ staleWindowMs: 0 }).staleWindowMs, 86_400_000);
441
+ assert.equal(resolveExtractionLivenessConfig({ staleWindowMs: 1.9 }).staleWindowMs, 86_400_000);
442
+ assert.equal(resolveExtractionLivenessConfig({ staleWindowMs: "5000" }).staleWindowMs, 5000, 'numeric string "5000" parses');
443
+ assert.equal(resolveExtractionLivenessConfig({ staleWindowMs: "0.5" }).staleWindowMs, 86_400_000, "fractional string falls back");
444
+ // enabled is coerced like parseExtractionLivenessConfig: string opt-outs read as falsy (#2155 review).
445
+ assert.equal(resolveExtractionLivenessConfig({ enabled: "false" }).enabled, false, 'string "false" disables');
446
+ assert.equal(resolveExtractionLivenessConfig({ enabled: "0" }).enabled, false, 'string "0" disables');
447
+ assert.equal(resolveExtractionLivenessConfig({ enabled: "true" }).enabled, true, 'string "true" enables');
448
+ });
449
+
450
+ test("summarizeExtractionLiveness: no throw when the extractionLiveness block is absent or partial (host/legacy config)", async () => {
451
+ const storage = { loadMeta: async () => ({ lastExtractionAt: null }) };
452
+ // A PluginConfig built directly (host adapter / older persisted config) can omit
453
+ // the block or leave it partial; every liveness surface must return a sane status
454
+ // rather than crash (#2155 review).
455
+ for (const config of [{}, { extractionLiveness: {} }, { extractionLiveness: { enabled: true } }]) {
456
+ const check = await summarizeExtractionLiveness(config as never, storage);
457
+ assert.equal(check.key, "extraction_liveness");
458
+ assert.ok(check.status === "ok" || check.status === "warn", `sane status for ${JSON.stringify(config)}`);
459
+ assert.ok(!check.summary.includes("liveness check disabled"), "missing/partial block reads as enabled default");
460
+ }
461
+ });