@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,462 @@
1
+ /**
2
+ * Extraction-pipeline liveness (issue #2151).
3
+ *
4
+ * A daemon's implicit extraction can fail continuously for months while
5
+ * `/health` stays green and recall keeps serving: every individual failure is
6
+ * logged (timeouts, fallback-no-parsed-output), but nothing aggregates them
7
+ * into a checkable status. This module surfaces a liveness *watermark* — the
8
+ * last successful extraction plus the current buffer backlog — and decides when
9
+ * that watermark is stale enough to call the pipeline degraded.
10
+ *
11
+ * The §22 error-vs-empty principle applies at the pipeline level: "no
12
+ * extraction succeeded for 90 days" (a non-empty buffer with a stale/absent
13
+ * watermark) must be distinguishable from "nothing to extract" (an empty
14
+ * buffer). Degradation therefore requires a NON-EMPTY buffer — an idle daemon
15
+ * with nothing buffered is healthy, not degraded.
16
+ *
17
+ * The core decision (`evaluateExtractionLiveness`) is a pure function shared by
18
+ * every surface (authenticated `/health`, `remnic doctor`, `remnic stats`) so
19
+ * the verdict never diverges across code paths.
20
+ */
21
+ import { coerceBool, coerceNumber } from "./connectors/coerce.js";
22
+ import { log } from "./logger.js";
23
+ import type { OperatorDoctorCheck } from "./operator-doctor-types.js";
24
+ import type { MetaState, PluginConfig } from "./types.js";
25
+
26
+ /** Default staleness window: 24h. A watermark older than this with a non-empty buffer is degraded. */
27
+ const DEFAULT_STALE_WINDOW_MS = 86_400_000;
28
+
29
+ export interface ExtractionLivenessConfig {
30
+ /** Master gate. When false, liveness is never reported degraded. Default true. */
31
+ enabled: boolean;
32
+ /**
33
+ * How stale the last-successful-extraction watermark may get (ms) before a
34
+ * non-empty buffer flags the pipeline degraded. Default 24h.
35
+ */
36
+ staleWindowMs: number;
37
+ }
38
+
39
+ /**
40
+ * A read-only snapshot of buffered, not-yet-extracted work. Structurally
41
+ * satisfied by `SmartBuffer.getBufferSnapshot()` so callers can pass the live
42
+ * buffer without this module depending on the buffer implementation.
43
+ */
44
+ export interface ExtractionBufferSnapshot {
45
+ bufferedSessionCount: number;
46
+ pendingTurnCount: number;
47
+ oldestTurnTimestamp: string | null;
48
+ /**
49
+ * Set by the liveness reader (never by `SmartBuffer`) when the buffer exists
50
+ * but its snapshot read threw. An unreadable buffer is a pipeline fault, not
51
+ * an empty queue (§22): it degrades the pipeline with a distinct reason
52
+ * instead of masquerading as a zero-backlog (healthy) snapshot.
53
+ */
54
+ readFailed?: boolean;
55
+ /** Read-failure detail, surfaced in the degraded reason when `readFailed`. */
56
+ readError?: string;
57
+ }
58
+
59
+ export interface ExtractionBufferSource {
60
+ getBufferSnapshot(): Promise<ExtractionBufferSnapshot>;
61
+ }
62
+
63
+ /**
64
+ * The liveness payload surfaced on `/health.extraction` and in the doctor/stats
65
+ * details.
66
+ *
67
+ * DAEMON-SCOPED SIGNAL: a daemon exposes a single extraction verdict; the
68
+ * last-extraction watermark is read from the daemon's default/root store, so
69
+ * `/health` returns the same `extraction` block for every namespace argument.
70
+ *
71
+ * KNOWN LIMITATION (issue #2159): extraction stamps `lastExtractionAt` on the
72
+ * PER-NAMESPACE store (`storageFor(selfNamespace)`), so this watermark reflects
73
+ * only extraction that stamped the root store (default-namespace work). On a
74
+ * single-namespace deployment (the common case) the watermark and buffer share
75
+ * one scope and the verdict is exact. On a multi-namespace deployment the verdict
76
+ * is NOT guaranteed correct for work isolated to a non-default namespace: it can
77
+ * over-report `degraded` (the default namespace idle while another is extracting)
78
+ * or miss a stall confined to a non-default namespace. Aggregating the watermark
79
+ * across namespace stores is tracked in #2159.
80
+ */
81
+ export interface ExtractionLivenessStatus {
82
+ lastExtractionAt: string | null;
83
+ bufferedSessionCount: number;
84
+ pendingTurnCount: number;
85
+ oldestBufferedTurnAgeMs: number | null;
86
+ degraded: boolean;
87
+ degradedReason: string | null;
88
+ /**
89
+ * Scope of the last-extraction watermark: `root-store` today (read from the
90
+ * daemon's default/root store only), `aggregate` once it is resolved across
91
+ * every namespace store (#2159). A monitor can alert on
92
+ * `watermarkScope !== "aggregate"` while namespaces are enabled instead of
93
+ * trusting a number that may be wrong in either direction.
94
+ */
95
+ watermarkScope: "root-store" | "aggregate";
96
+ }
97
+
98
+ interface ExtractionLivenessStorageLike {
99
+ loadMeta(): Promise<Pick<MetaState, "lastExtractionAt">>;
100
+ }
101
+
102
+ interface ExtractionLivenessOrchestratorLike {
103
+ config: Pick<PluginConfig, "extractionLiveness">;
104
+ buffer?: ExtractionBufferSource;
105
+ }
106
+
107
+ /**
108
+ * Parse the `extractionLiveness` config block. Follows the nested-block shape
109
+ * validation used elsewhere in `config.ts` and coerces string booleans (§24:
110
+ * CLI/JSON deliver `"false"`/`"0"` as strings, which must read as falsy).
111
+ */
112
+ export function parseExtractionLivenessConfig(
113
+ cfg: Record<string, unknown>,
114
+ ): ExtractionLivenessConfig {
115
+ const raw = cfg.extractionLiveness;
116
+ if (raw !== undefined && (raw === null || typeof raw !== "object" || Array.isArray(raw))) {
117
+ throw new Error(`extractionLiveness must be a plain object (got ${JSON.stringify(raw)})`);
118
+ }
119
+ const block = (raw ?? {}) as Record<string, unknown>;
120
+ return {
121
+ // `?? true` keeps the default on when the key is absent while honoring
122
+ // explicit "false"/"0"/false opt-outs (coerceBool handles the string forms).
123
+ enabled: coerceBool(block.enabled) ?? true,
124
+ staleWindowMs: parseStaleWindowMs(block.staleWindowMs),
125
+ };
126
+ }
127
+
128
+ /**
129
+ * Parse `extractionLiveness.staleWindowMs`. Absent (`undefined`) → the 24h
130
+ * default; any PRESENT value (including an explicit `null`) must coerce to a
131
+ * positive INTEGER of milliseconds, else THROW. A fractional or non-positive
132
+ * value ("0.5" would floor to 0, making every backlog instantly stale; 1.9 would
133
+ * floor to 1) is rejected, never floored/reinterpreted (§1/§17/§39). Mirrors
134
+ * config.ts's `parseIntegerInClosedRange` (round 9 finding 1): only an absent key
135
+ * falls back; an explicit `null` is invalid input, not "use the default".
136
+ * Inlined rather than imported because config.ts already imports this module
137
+ * (importing back would be circular), the same reason `recall-concurrency-config.ts`
138
+ * inlines its numeric validation.
139
+ */
140
+ function parseStaleWindowMs(value: unknown): number {
141
+ // Only an absent key (undefined) falls back to the default; an explicit `null`
142
+ // is invalid input and proceeds to validation → throw (mirrors
143
+ // parseIntegerInClosedRange, round 9 finding 1).
144
+ if (value === undefined) return DEFAULT_STALE_WINDOW_MS;
145
+ const coerced = coerceNumber(value);
146
+ if (coerced === undefined || !Number.isFinite(coerced) || !Number.isInteger(coerced) || coerced < 1) {
147
+ throw new Error(
148
+ `extractionLiveness.staleWindowMs must be an integer greater than or equal to 1; got ${JSON.stringify(value)}`,
149
+ );
150
+ }
151
+ return coerced;
152
+ }
153
+
154
+ /** Milliseconds between `timestamp` and `nowMs`, clamped at 0; null when unparsable/absent. */
155
+ function ageMsFrom(timestamp: string | null, nowMs: number): number | null {
156
+ if (typeof timestamp !== "string") return null;
157
+ const parsed = Date.parse(timestamp);
158
+ if (!Number.isFinite(parsed)) return null;
159
+ return Math.max(0, nowMs - parsed);
160
+ }
161
+
162
+ /** Compact human-readable duration ("3d", "5h", "12m", "45s") for logs and CLI output. */
163
+ export function formatAgeMs(ms: number): string {
164
+ const s = Math.max(0, Math.floor(ms / 1000));
165
+ if (s >= 86_400) return `${Math.floor(s / 86_400)}d`;
166
+ if (s >= 3_600) return `${Math.floor(s / 3_600)}h`;
167
+ if (s >= 60) return `${Math.floor(s / 60)}m`;
168
+ return `${s}s`;
169
+ }
170
+
171
+ /**
172
+ * Pure liveness verdict. Degraded when the feature is enabled AND any of: the
173
+ * last-extraction watermark could not be READ (a storage fault, §22), the buffer
174
+ * read failed (a pipeline fault, §22), or the buffer is non-empty and the
175
+ * watermark is absent or stale. Each cause yields a DISTINCT reason so an
176
+ * operator can tell a meta-read failure from a buffer-read failure from a
177
+ * pipeline that has genuinely never extracted.
178
+ *
179
+ * Staleness uses a half-open freshness window (§23): an extraction is fresh
180
+ * while its age is in `[0, staleWindowMs)`, so an age of EXACTLY `staleWindowMs`
181
+ * is stale (the upper bound on "fresh" is exclusive). A null/unparsable watermark
182
+ * means "never succeeded" and is treated as stale — as is a FUTURE watermark (a
183
+ * clock-skewed or corrupt timestamp ahead of now must not read as a fresh
184
+ * extraction and hide a real backlog stall).
185
+ */
186
+ export function evaluateExtractionLiveness(input: {
187
+ config: ExtractionLivenessConfig;
188
+ lastExtractionAt: string | null;
189
+ snapshot: ExtractionBufferSnapshot;
190
+ nowMs: number;
191
+ metaReadFailed?: boolean;
192
+ metaReadError?: string;
193
+ watermarkScope?: "root-store" | "aggregate";
194
+ }): ExtractionLivenessStatus {
195
+ const { config, lastExtractionAt, snapshot, nowMs } = input;
196
+ const lastMs = lastExtractionAt !== null ? Date.parse(lastExtractionAt) : Number.NaN;
197
+ const hasWatermark = Number.isFinite(lastMs);
198
+ const ageMs = nowMs - lastMs;
199
+ // A future watermark (negative age) is clock-skewed or corrupt data, not a
200
+ // fresh extraction: treat it as stale so a real backlog stall is not hidden.
201
+ const stale = !hasWatermark || ageMs < 0 || ageMs >= config.staleWindowMs;
202
+ const hasBacklog = snapshot.bufferedSessionCount > 0;
203
+ // A watermark or buffer that cannot be READ is a fault, not an empty queue
204
+ // (§22): a storage/pipeline outage must not read as healthy, and must stay
205
+ // distinct from a pipeline that has genuinely never extracted.
206
+ const metaReadFailed = input.metaReadFailed === true;
207
+ const readFailed = snapshot.readFailed === true;
208
+ const degraded = config.enabled && (metaReadFailed || readFailed || (hasBacklog && stale));
209
+
210
+ let degradedReason: string | null = null;
211
+ if (degraded) {
212
+ if (metaReadFailed) {
213
+ degradedReason = `extraction watermark unreadable: ${input.metaReadError ?? "meta read failed"}`;
214
+ } else if (readFailed) {
215
+ degradedReason = `extraction buffer unreadable: ${snapshot.readError ?? "buffer snapshot read failed"}`;
216
+ } else {
217
+ const watermark = hasWatermark
218
+ ? `last succeeded ${formatAgeMs(nowMs - lastMs)} ago`
219
+ : "no successful extraction on record";
220
+ degradedReason =
221
+ `${watermark}; ${snapshot.bufferedSessionCount} buffered session(s), ` +
222
+ `${snapshot.pendingTurnCount} turn(s) pending extraction`;
223
+ }
224
+ }
225
+
226
+ return {
227
+ lastExtractionAt,
228
+ bufferedSessionCount: snapshot.bufferedSessionCount,
229
+ pendingTurnCount: snapshot.pendingTurnCount,
230
+ oldestBufferedTurnAgeMs: ageMsFrom(snapshot.oldestTurnTimestamp, nowMs),
231
+ degraded,
232
+ degradedReason,
233
+ watermarkScope: input.watermarkScope ?? "root-store",
234
+ };
235
+ }
236
+
237
+ const EMPTY_SNAPSHOT: ExtractionBufferSnapshot = {
238
+ bufferedSessionCount: 0,
239
+ pendingTurnCount: 0,
240
+ oldestTurnTimestamp: null,
241
+ };
242
+
243
+ /**
244
+ * Read a buffer snapshot without throwing. An ABSENT buffer degrades to an empty
245
+ * (healthy) snapshot — some service constructions have no orchestrator buffer. A
246
+ * buffer that EXISTS but whose read throws yields a `readFailed` snapshot so the
247
+ * verdict can distinguish a pipeline fault from an empty queue (§22).
248
+ */
249
+ async function readBufferSnapshot(
250
+ buffer: ExtractionBufferSource | undefined,
251
+ ): Promise<ExtractionBufferSnapshot> {
252
+ if (!buffer || typeof buffer.getBufferSnapshot !== "function") return EMPTY_SNAPSHOT;
253
+ try {
254
+ return await buffer.getBufferSnapshot();
255
+ } catch (err) {
256
+ return {
257
+ ...EMPTY_SNAPSHOT,
258
+ readFailed: true,
259
+ readError: err instanceof Error ? err.message : String(err),
260
+ };
261
+ }
262
+ }
263
+
264
+ /**
265
+ * Gather the liveness status from a meta store (last-extraction watermark) and
266
+ * an optional buffer (backlog). A read failure on EITHER side is captured and
267
+ * surfaced as a DEGRADED status with a distinct reason (§22) rather than being
268
+ * swallowed into a healthy-looking verdict; no failure path throws.
269
+ */
270
+ export async function gatherExtractionLivenessStatus(input: {
271
+ config: ExtractionLivenessConfig;
272
+ storage: ExtractionLivenessStorageLike;
273
+ buffer: ExtractionBufferSource | undefined;
274
+ nowMs: number;
275
+ }): Promise<ExtractionLivenessStatus> {
276
+ const { config, storage, buffer, nowMs } = input;
277
+ let lastExtractionAt: string | null = null;
278
+ let metaReadFailed = false;
279
+ let metaReadError: string | undefined;
280
+ try {
281
+ lastExtractionAt = (await storage.loadMeta()).lastExtractionAt ?? null;
282
+ } catch (err) {
283
+ // A swallowed loadMeta() failure that reads as "never extracted" is the
284
+ // exact §22 conflation this feature exists to kill — surface it explicitly.
285
+ metaReadFailed = true;
286
+ metaReadError = err instanceof Error ? err.message : String(err);
287
+ }
288
+ const snapshot = await readBufferSnapshot(buffer);
289
+ return evaluateExtractionLiveness({
290
+ config,
291
+ lastExtractionAt,
292
+ snapshot,
293
+ nowMs,
294
+ metaReadFailed,
295
+ metaReadError,
296
+ // Today the watermark comes from the root store only; #2159 flips this to
297
+ // "aggregate" when the resolver reads across every namespace store.
298
+ watermarkScope: "root-store",
299
+ });
300
+ }
301
+
302
+ /**
303
+ * Aggregated-WARN throttle (issue #2151): emit at most ONE warning per
304
+ * staleness window while degraded — not one line per failed extraction attempt.
305
+ * State is instance-scoped (held by the owning service), never a module global
306
+ * (§5), so multiple service instances throttle independently.
307
+ */
308
+ export class ExtractionLivenessWarnThrottle {
309
+ private lastWarnAtMs: number | null = null;
310
+
311
+ /** Warn if degraded and outside the throttle window; returns whether a WARN was emitted. */
312
+ maybeWarn(status: ExtractionLivenessStatus, staleWindowMs: number, nowMs: number): boolean {
313
+ if (!status.degraded) {
314
+ // Recovered (or never degraded): reset so a fresh episode warns at once.
315
+ this.lastWarnAtMs = null;
316
+ return false;
317
+ }
318
+ if (this.lastWarnAtMs !== null && nowMs - this.lastWarnAtMs < staleWindowMs) return false;
319
+ this.lastWarnAtMs = nowMs;
320
+ log.warn(
321
+ `extraction pipeline liveness degraded: ${status.degradedReason ?? "buffered turns are not being extracted"}`,
322
+ );
323
+ return true;
324
+ }
325
+ }
326
+
327
+ /**
328
+ * Normalize the `extractionLiveness` block at the read boundary. `parseConfig`
329
+ * always populates it, but a host adapter, an older persisted config, or a
330
+ * hand-built `PluginConfig` can hand us an absent, partial, or loosely-typed
331
+ * block - and every liveness surface (/health, doctor, stats) must degrade to
332
+ * the documented default (enabled, 24h window) rather than throw. Both fields
333
+ * are coerced with the same rules as `parseExtractionLivenessConfig` (§24:
334
+ * `"false"`/`"0"` read as falsy; a numeric string like `"5000"` parses), except a
335
+ * present but non-integer or non-positive `staleWindowMs` falls back to the
336
+ * default here rather than throwing.
337
+ */
338
+ export function resolveExtractionLivenessConfig(
339
+ block: { enabled?: unknown; staleWindowMs?: unknown } | undefined,
340
+ ): ExtractionLivenessConfig {
341
+ const staleWindowMs = coerceNumber(block?.staleWindowMs);
342
+ return {
343
+ enabled: coerceBool(block?.enabled) ?? true,
344
+ staleWindowMs:
345
+ staleWindowMs !== undefined && Number.isInteger(staleWindowMs) && staleWindowMs >= 1
346
+ ? staleWindowMs
347
+ : DEFAULT_STALE_WINDOW_MS,
348
+ };
349
+ }
350
+
351
+ /**
352
+ * Compute the liveness status for the authenticated `/health` payload and emit
353
+ * the throttled aggregated WARN.
354
+ *
355
+ * The watermark is read from the daemon's default/root store (`orchestrator.storage`),
356
+ * which is why `/health` returns the same `extraction` block for any namespace
357
+ * argument. KNOWN LIMITATION (issue #2159): extraction writes `lastExtractionAt`
358
+ * per-namespace, so this watermark reflects only default-namespace extraction and
359
+ * the verdict is exact only for a single-namespace daemon (see
360
+ * `ExtractionLivenessStatus`).
361
+ */
362
+ export async function computeExtractionLivenessStatus(
363
+ orchestrator: ExtractionLivenessOrchestratorLike & { storage: ExtractionLivenessStorageLike },
364
+ throttle?: ExtractionLivenessWarnThrottle,
365
+ nowMs: number = Date.now(),
366
+ ): Promise<ExtractionLivenessStatus> {
367
+ const config = resolveExtractionLivenessConfig(orchestrator.config.extractionLiveness);
368
+ const status = await gatherExtractionLivenessStatus({
369
+ config,
370
+ storage: orchestrator.storage,
371
+ buffer: orchestrator.buffer,
372
+ nowMs,
373
+ });
374
+ throttle?.maybeWarn(status, config.staleWindowMs, nowMs);
375
+ return status;
376
+ }
377
+
378
+ /**
379
+ * `remnic doctor` check for extraction-pipeline liveness. Status `warn` when
380
+ * degraded (with a remediation hint), `ok` otherwise.
381
+ */
382
+ export async function summarizeExtractionLiveness(
383
+ config: Pick<PluginConfig, "extractionLiveness">,
384
+ storage: ExtractionLivenessStorageLike,
385
+ buffer?: ExtractionBufferSource,
386
+ nowMs: number = Date.now(),
387
+ ): Promise<OperatorDoctorCheck> {
388
+ const livenessConfig = resolveExtractionLivenessConfig(config.extractionLiveness);
389
+ const status = await gatherExtractionLivenessStatus({
390
+ config: livenessConfig,
391
+ storage,
392
+ buffer,
393
+ nowMs,
394
+ });
395
+ const scopeNote =
396
+ ` Watermark scope: ${status.watermarkScope}` +
397
+ (status.watermarkScope === "root-store"
398
+ ? " (default-namespace only; multi-namespace aggregation tracked in #2159)."
399
+ : ".");
400
+ const summary =
401
+ (status.degraded
402
+ ? `Extraction pipeline degraded: ${status.degradedReason}.`
403
+ : `Extraction pipeline healthy: last extraction ${status.lastExtractionAt ?? "never"}, ` +
404
+ `${status.bufferedSessionCount} buffered session(s), ${status.pendingTurnCount} turn(s) pending` +
405
+ `${livenessConfig.enabled ? "" : " (liveness check disabled)"}.`) + scopeNote;
406
+ return {
407
+ key: "extraction_liveness",
408
+ status: status.degraded ? "warn" : "ok",
409
+ summary,
410
+ remediation: status.degraded
411
+ ? "Extraction has stalled while turns are buffered. Inspect daemon logs for extraction " +
412
+ "timeouts or fallback-no-parsed-output errors, verify the extraction model/provider is " +
413
+ "reachable, then force a flush (e.g. `remnic flush`)."
414
+ : undefined,
415
+ details: { ...status, staleWindowMs: livenessConfig.staleWindowMs, enabled: livenessConfig.enabled },
416
+ };
417
+ }
418
+
419
+ /** Meta fields the `remnic stats` view renders directly, beyond the liveness watermark. */
420
+ type ExtractionStatsMeta = Pick<MetaState, "extractionCount" | "lastExtractionAt" | "lastConsolidationAt">;
421
+
422
+ /**
423
+ * Lines for `remnic stats`: extraction watermark, buffer backlog, and the
424
+ * liveness verdict so dashboards can alert on a stalled pipeline. The meta
425
+ * watermark is loaded HERE (not by the caller) so a meta-read failure surfaces
426
+ * as an explicit DEGRADED verdict plus "unavailable" counts (§22) instead of
427
+ * crashing the whole `stats` command.
428
+ */
429
+ export async function renderExtractionLivenessStats(
430
+ orchestrator: ExtractionLivenessOrchestratorLike & { storage: { loadMeta(): Promise<ExtractionStatsMeta> } },
431
+ nowMs: number = Date.now(),
432
+ ): Promise<string[]> {
433
+ let meta: ExtractionStatsMeta | null = null;
434
+ let metaReadFailed = false;
435
+ let metaReadError: string | undefined;
436
+ try {
437
+ meta = await orchestrator.storage.loadMeta();
438
+ } catch (err) {
439
+ metaReadFailed = true;
440
+ metaReadError = err instanceof Error ? err.message : String(err);
441
+ }
442
+ const status = evaluateExtractionLiveness({
443
+ config: resolveExtractionLivenessConfig(orchestrator.config.extractionLiveness),
444
+ lastExtractionAt: meta?.lastExtractionAt ?? null,
445
+ snapshot: await readBufferSnapshot(orchestrator.buffer),
446
+ nowMs,
447
+ metaReadFailed,
448
+ metaReadError,
449
+ });
450
+ const oldestAge =
451
+ status.oldestBufferedTurnAgeMs !== null ? formatAgeMs(status.oldestBufferedTurnAgeMs) : "n/a";
452
+ const unavailable = metaReadFailed ? "unavailable" : "never";
453
+ return [
454
+ `Extractions: ${meta?.extractionCount ?? unavailable}`,
455
+ `Last extraction: ${meta?.lastExtractionAt ?? unavailable}`,
456
+ `Last consolidation: ${meta?.lastConsolidationAt ?? unavailable}`,
457
+ `Buffered sessions: ${status.bufferedSessionCount} (${status.pendingTurnCount} turns pending)`,
458
+ `Oldest buffered turn age: ${oldestAge}`,
459
+ `Extraction watermark scope: ${status.watermarkScope}`,
460
+ `Extraction liveness: ${status.degraded ? `DEGRADED — ${status.degradedReason}` : "ok"}`,
461
+ ];
462
+ }
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Shared operator-doctor result type.
3
+ *
4
+ * Extracted from `operator-toolkit.ts` (issue #2151 review, PR 2155) so that a
5
+ * lightweight doctor summarizer such as `extraction-liveness.ts` can type its
6
+ * return value WITHOUT importing the whole `operator-toolkit` module. That
7
+ * back-edge (`extraction-liveness` -> `operator-toolkit` -> `extraction-liveness`)
8
+ * was a circular type reference that made the standalone DTS entry for
9
+ * `extraction-liveness.ts` resolve the entire operator-toolkit + config + types
10
+ * graph, pushing the tsup DTS worker past its heap limit
11
+ * (ERR_WORKER_OUT_OF_MEMORY). This interface has no dependencies, so importing it
12
+ * costs nothing.
13
+ */
14
+ export interface OperatorDoctorCheck {
15
+ key: string;
16
+ status: "ok" | "warn" | "error";
17
+ summary: string;
18
+ remediation?: string;
19
+ details?: unknown;
20
+ }
@@ -3,6 +3,8 @@ import { constants as fsConstants } from "node:fs";
3
3
  import { access, mkdir, readFile, readdir, stat, unlink, writeFile } from "node:fs/promises";
4
4
  import { lintWorkspaceFiles } from "./hygiene.js";
5
5
  import { parseConfig } from "./config.js";
6
+ import { summarizeExtractionLiveness, type ExtractionBufferSource } from "./extraction-liveness.js";
7
+ import type { OperatorDoctorCheck } from "./operator-doctor-types.js";
6
8
  import { readEnvVar, resolveHomeDir } from "./runtime/env.js";
7
9
  import { resolvePluginEntry } from "./plugin-entry-resolver.js";
8
10
  import {
@@ -157,6 +159,7 @@ export interface OperatorToolkitOrchestrator extends ConversationIndexLike {
157
159
  config: PluginConfig;
158
160
  storage: StorageManager;
159
161
  qmd: QmdRuntimeLike;
162
+ buffer?: ExtractionBufferSource;
160
163
  }
161
164
 
162
165
  export interface OperatorConfigLoadResult {
@@ -225,13 +228,11 @@ export interface OperatorSetupReport {
225
228
  verificationCommands: string[];
226
229
  }
227
230
 
228
- export interface OperatorDoctorCheck {
229
- key: string;
230
- status: "ok" | "warn" | "error";
231
- summary: string;
232
- remediation?: string;
233
- details?: unknown;
234
- }
231
+ // `OperatorDoctorCheck` moved to `./operator-doctor-types.js` so summarizers avoid
232
+ // pulling the operator-toolkit DTS graph (PR 2155); re-exported to keep the surface stable.
233
+ export type { OperatorDoctorCheck };
234
+ export { summarizeExtractionLiveness };
235
+ export type { ExtractionBufferSource };
235
236
 
236
237
  export interface OperatorConfigReviewFinding {
237
238
  key: string;
@@ -1496,10 +1497,8 @@ export async function runOperatorDoctor(options: OperatorDoctorOptions): Promise
1496
1497
  // Informational only — never errors, never blocks doctor from returning ok.
1497
1498
  checks.push(await summarizeTierDistribution(options.orchestrator.storage));
1498
1499
 
1499
- // Dreams phases thresholds and last-run timestamps (issue #678 PR 2/4).
1500
- // Surfaces per-phase: enabled status, cadence, threshold values, and the
1501
- // best-available last-run timestamp for each of the three pipeline phases.
1502
1500
  checks.push(await summarizeDreamsPhases(config, storage));
1501
+ checks.push(await summarizeExtractionLiveness(config, storage, options.orchestrator.buffer));
1503
1502
  checks.push(await summarizeCorpusWatermark(config, (dir) => new StorageManager(dir))); // corpus watermark #2149
1504
1503
 
1505
1504
  // Security mitigation status (issue #565).
package/src/types.ts CHANGED
@@ -2,6 +2,7 @@ import type { BoundedJsonlStateConfig } from "./bounded-jsonl-state.js";
2
2
  import type { MeetingsConfig } from "./meetings/types.js";
3
3
  import type { ActivityConfig } from "./activity/types.js";
4
4
  import type { WearablesConfig } from "./wearables/types.js";
5
+ import type { ExtractionLivenessConfig } from "./extraction-liveness.js";
5
6
 
6
7
  export type ReasoningEffort = "none" | "low" | "medium" | "high";
7
8
  export type TriggerMode = "smart" | "every_n" | "time_based";
@@ -1206,10 +1207,9 @@ export interface PluginConfig extends BoundedJsonlStateConfig {
1206
1207
  */
1207
1208
  dreamsPhases: DreamsPhasesConfig;
1208
1209
  procedural: ProceduralConfig;
1209
- /**
1210
- * Claim-level provenance spans (issue #1575). Parsed from the
1211
- * `provenance` config block; see `ProvenanceConfig` for the documented
1212
- * defaults. PR 1 parses + persists only; extraction wiring lands in PR 2.
1210
+ extractionLiveness: ExtractionLivenessConfig;
1211
+ /** Claim-level provenance spans (#1575); see `ProvenanceConfig` for defaults.
1212
+ * PR 1 parses + persists only; extraction wiring lands in PR 2.
1213
1213
  */
1214
1214
  provenance: ProvenanceConfig;
1215
1215
  /**