@remnic/core 9.3.726 → 9.3.728

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 (342) hide show
  1. package/dist/access-boundary.d.ts +5 -5
  2. package/dist/access-boundary.js +22 -22
  3. package/dist/access-cli.js +45 -45
  4. package/dist/access-http.d.ts +20 -5
  5. package/dist/access-http.js +27 -27
  6. package/dist/access-mcp.d.ts +5 -5
  7. package/dist/access-mcp.js +26 -26
  8. package/dist/access-operations-batch.js +23 -23
  9. package/dist/access-operations.d.ts +6 -6
  10. package/dist/access-operations.js +25 -25
  11. package/dist/access-schema.d.ts +4 -4
  12. package/dist/access-schema.js +3 -3
  13. package/dist/{access-service-B2PlPtxh.d.ts → access-service-Bgpjxo4f.d.ts} +36 -4
  14. package/dist/access-service.d.ts +5 -5
  15. package/dist/access-service.js +21 -21
  16. package/dist/access-surface-catalog.d.ts +5 -5
  17. package/dist/action-confidence.d.ts +1 -1
  18. package/dist/active-memory-bridge.d.ts +1 -1
  19. package/dist/active-recall.d.ts +1 -1
  20. package/dist/active-recall.js +3 -3
  21. package/dist/behavior-learner.d.ts +1 -1
  22. package/dist/behavior-signals.d.ts +1 -1
  23. package/dist/bootstrap.d.ts +4 -4
  24. package/dist/briefing.d.ts +1 -1
  25. package/dist/briefing.js +7 -7
  26. package/dist/buffer-surprise-report.d.ts +1 -1
  27. package/dist/buffer.d.ts +1 -1
  28. package/dist/calibration.d.ts +1 -1
  29. package/dist/capabilities.d.ts +44 -2
  30. package/dist/capabilities.js +5 -3
  31. package/dist/{capsule-crypto-GWVG7LGC.js → capsule-crypto-7FJQINUR.js} +2 -2
  32. package/dist/{catalog-CzUb-GyD.d.ts → catalog-bMczlO2G.d.ts} +1 -1
  33. package/dist/causal-behavior.d.ts +1 -1
  34. package/dist/causal-consolidation.d.ts +1 -1
  35. package/dist/causal-consolidation.js +8 -8
  36. package/dist/causal-trajectory-graph.d.ts +1 -1
  37. package/dist/{chunk-OWHERGF2.js → chunk-2NLLXCJG.js} +2 -2
  38. package/dist/{chunk-BIA7ELIB.js → chunk-36SEDFAU.js} +3 -3
  39. package/dist/{chunk-UQSVUEN2.js → chunk-37436GML.js} +2 -2
  40. package/dist/{chunk-DVRHLN2O.js → chunk-3RCBIOOJ.js} +18 -2
  41. package/dist/{chunk-DVRHLN2O.js.map → chunk-3RCBIOOJ.js.map} +1 -1
  42. package/dist/{chunk-RRHHQYJS.js → chunk-3TSTGI6F.js} +2 -2
  43. package/dist/{chunk-5OOA7SGX.js → chunk-4ESAR77N.js} +2 -2
  44. package/dist/{chunk-FO3HO52S.js → chunk-4H4F3BFL.js} +2 -2
  45. package/dist/{chunk-BEUCACTV.js → chunk-4NOI577P.js} +2 -2
  46. package/dist/{chunk-BBIE7W52.js → chunk-4NR2PDZ5.js} +2 -2
  47. package/dist/{chunk-WGTWGDR5.js → chunk-76NHR6ZL.js} +5 -5
  48. package/dist/{chunk-PKMQK2WY.js → chunk-773DPI6T.js} +4 -4
  49. package/dist/{chunk-MQVKHFIU.js → chunk-7RLCCNHU.js} +8 -8
  50. package/dist/{chunk-XGTBNVIU.js → chunk-AAD2ZS6P.js} +11 -1
  51. package/dist/chunk-AAD2ZS6P.js.map +1 -0
  52. package/dist/{chunk-EZNDNMRH.js → chunk-AQLOP5F4.js} +2 -2
  53. package/dist/{chunk-UNNDFYUX.js → chunk-ARV3AUOM.js} +5 -5
  54. package/dist/{chunk-NPL7VDM6.js → chunk-ASKXIAHD.js} +2 -2
  55. package/dist/{chunk-TQQH2FTX.js → chunk-BQCHEV7O.js} +106 -15
  56. package/dist/{chunk-TQQH2FTX.js.map → chunk-BQCHEV7O.js.map} +1 -1
  57. package/dist/{chunk-ZA2S2VLL.js → chunk-C2PG7IEH.js} +5 -2
  58. package/dist/chunk-C2PG7IEH.js.map +1 -0
  59. package/dist/{chunk-K4ZNARDO.js → chunk-CBWI22JN.js} +2 -2
  60. package/dist/{chunk-ZOHMPFOB.js → chunk-CKFLLAIS.js} +3 -3
  61. package/dist/{chunk-JPUT5NGH.js → chunk-DMQWV6SQ.js} +2 -2
  62. package/dist/{chunk-F3XZN24Z.js → chunk-DRUDC2UX.js} +2 -2
  63. package/dist/{chunk-KJR66XOM.js → chunk-EY6MDMHK.js} +2 -2
  64. package/dist/{chunk-3TUT4PWB.js → chunk-FVZ2ISIX.js} +2 -2
  65. package/dist/{chunk-C4X4QGIO.js → chunk-G6NKALRN.js} +5 -2
  66. package/dist/chunk-G6NKALRN.js.map +1 -0
  67. package/dist/{chunk-6SQQOJ4U.js → chunk-GKRLPHPU.js} +2 -2
  68. package/dist/{chunk-PLG6JHQS.js → chunk-H5BWGCRZ.js} +43 -42
  69. package/dist/chunk-H5BWGCRZ.js.map +1 -0
  70. package/dist/{chunk-2NBP25BG.js → chunk-HGNOCPZX.js} +171 -79
  71. package/dist/chunk-HGNOCPZX.js.map +1 -0
  72. package/dist/{chunk-5NWYQUQC.js → chunk-HOHVIVDR.js} +2 -2
  73. package/dist/{chunk-DCKNMLEQ.js → chunk-I5REUWLH.js} +11 -5
  74. package/dist/chunk-I5REUWLH.js.map +1 -0
  75. package/dist/{chunk-6WAJQKBS.js → chunk-JYLFZWUX.js} +2 -2
  76. package/dist/{chunk-5W7CTZ2E.js → chunk-KQAFEZQX.js} +4 -4
  77. package/dist/{chunk-UKTDXTWX.js → chunk-M4AYVEWS.js} +7 -7
  78. package/dist/{chunk-YTZ3VGC7.js → chunk-M4V2JVSZ.js} +2 -2
  79. package/dist/{chunk-TA3Y6GEX.js → chunk-MLKWJOAJ.js} +2 -2
  80. package/dist/{chunk-DQXPJ6HD.js → chunk-O6YH5F47.js} +4 -4
  81. package/dist/{chunk-7AG54AF3.js → chunk-PUR2CKX2.js} +1 -1
  82. package/dist/chunk-PUR2CKX2.js.map +1 -0
  83. package/dist/{chunk-OAHOL3ZC.js → chunk-QNEUZIAH.js} +152 -62
  84. package/dist/chunk-QNEUZIAH.js.map +1 -0
  85. package/dist/{chunk-Q6D75LVH.js → chunk-UM5AWJNQ.js} +79 -17
  86. package/dist/chunk-UM5AWJNQ.js.map +1 -0
  87. package/dist/{chunk-53EEQYE5.js → chunk-UTHQPSRC.js} +8 -8
  88. package/dist/{chunk-BB57OZWC.js → chunk-UVPL565C.js} +2 -2
  89. package/dist/{chunk-6AFVIOVZ.js → chunk-V4GNSDFH.js} +2 -2
  90. package/dist/{chunk-EHISUJFN.js → chunk-WAS3NV46.js} +6 -3
  91. package/dist/chunk-WAS3NV46.js.map +1 -0
  92. package/dist/{chunk-UVYI6VIX.js → chunk-X7Y7WX73.js} +1 -1
  93. package/dist/{chunk-V7CMKMHM.js → chunk-Y4YD5TYW.js} +3 -3
  94. package/dist/{chunk-7NS7AWMZ.js → chunk-YGIQORJ3.js} +3 -3
  95. package/dist/{chunk-MDBZTH7N.js → chunk-YKBHOBJK.js} +44 -10
  96. package/dist/chunk-YKBHOBJK.js.map +1 -0
  97. package/dist/{chunk-3MS3QVX6.js → chunk-YOLZS7NY.js} +2 -2
  98. package/dist/{chunk-6OIDDKAA.js → chunk-YTU7WKYN.js} +35 -9
  99. package/dist/chunk-YTU7WKYN.js.map +1 -0
  100. package/dist/{cli-IOlDHSBT.d.ts → cli-BLME420h.d.ts} +3 -3
  101. package/dist/cli.d.ts +6 -6
  102. package/dist/cli.js +49 -49
  103. package/dist/compat/codex-cli-fallback.d.ts +1 -0
  104. package/dist/compat/codex-cli-fallback.js +22 -0
  105. package/dist/compat/codex-cli-fallback.js.map +1 -0
  106. package/dist/compat/codex-thread-key.d.ts +1 -0
  107. package/dist/compat/codex-thread-key.js +18 -0
  108. package/dist/compat/codex-thread-key.js.map +1 -0
  109. package/dist/compounding/engine.d.ts +1 -1
  110. package/dist/compounding/engine.js +7 -7
  111. package/dist/compounding/preference-consolidator.d.ts +1 -1
  112. package/dist/compression-optimizer.d.ts +1 -1
  113. package/dist/config.d.ts +1 -1
  114. package/dist/config.js +3 -3
  115. package/dist/connectors/codex-materialize-runner.d.ts +1 -1
  116. package/dist/connectors/codex-materialize-runner.js +7 -7
  117. package/dist/connectors/codex-materialize.d.ts +1 -1
  118. package/dist/connectors/index.d.ts +1 -1
  119. package/dist/connectors/index.js +7 -7
  120. package/dist/consolidation-provenance-check.d.ts +1 -1
  121. package/dist/consolidation-undo.d.ts +1 -1
  122. package/dist/contradiction/index.d.ts +1 -1
  123. package/dist/conversation-index/backend.d.ts +1 -1
  124. package/dist/conversation-index/chunker.d.ts +1 -1
  125. package/dist/conversation-index/faiss-adapter.d.ts +1 -1
  126. package/dist/conversation-index/indexer.d.ts +1 -1
  127. package/dist/conversation-index/search.d.ts +1 -1
  128. package/dist/day-summary.d.ts +1 -1
  129. package/dist/delinearize.d.ts +1 -1
  130. package/dist/direct-answer-wiring.d.ts +1 -1
  131. package/dist/direct-answer.d.ts +1 -1
  132. package/dist/embedding-fallback.d.ts +1 -1
  133. package/dist/embedding-fallback.js +2 -1
  134. package/dist/enrichment/index.d.ts +1 -1
  135. package/dist/entity-retrieval.d.ts +1 -1
  136. package/dist/entity-retrieval.js +7 -7
  137. package/dist/entity-schema.d.ts +1 -1
  138. package/dist/explicit-capture.d.ts +4 -4
  139. package/dist/extraction-faithfulness.d.ts +1 -1
  140. package/dist/extraction-faithfulness.js +2 -2
  141. package/dist/extraction-judge-telemetry.d.ts +1 -1
  142. package/dist/extraction-judge-training.d.ts +1 -1
  143. package/dist/extraction-judge.d.ts +1 -1
  144. package/dist/extraction.d.ts +1 -1
  145. package/dist/extraction.js +3 -2
  146. package/dist/faithfulness-config.d.ts +1 -0
  147. package/dist/faithfulness-config.js +1 -1
  148. package/dist/fallback-llm.d.ts +1 -1
  149. package/dist/{first-start-migration-PG5HBC3K.js → first-start-migration-22LPFHD5.js} +6 -2
  150. package/dist/first-start-migration-22LPFHD5.js.map +1 -0
  151. package/dist/graph-dashboard-diff.d.ts +1 -1
  152. package/dist/graph-dashboard-key.d.ts +1 -1
  153. package/dist/graph-dashboard-parser.d.ts +1 -1
  154. package/dist/graph-edge-reinforcement.d.ts +1 -1
  155. package/dist/graph-snapshot.d.ts +1 -1
  156. package/dist/graph.d.ts +1 -1
  157. package/dist/identity-continuity.d.ts +1 -1
  158. package/dist/importance.d.ts +1 -1
  159. package/dist/index.d.ts +9 -9
  160. package/dist/index.js +64 -64
  161. package/dist/intent.d.ts +1 -1
  162. package/dist/lcm/engine.d.ts +1 -1
  163. package/dist/lcm/engine.js +2 -2
  164. package/dist/lcm/index.d.ts +1 -1
  165. package/dist/lcm/index.js +7 -7
  166. package/dist/lcm/tools.d.ts +1 -1
  167. package/dist/lifecycle.d.ts +1 -1
  168. package/dist/live-connectors-runner.d.ts +1 -1
  169. package/dist/local-llm.d.ts +1 -1
  170. package/dist/local-model-endpoint.d.ts +9 -4
  171. package/dist/local-model-endpoint.js +1 -1
  172. package/dist/maintenance/memory-governance.d.ts +1 -1
  173. package/dist/maintenance/memory-governance.js +7 -7
  174. package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +7 -7
  175. package/dist/maintenance/rebuild-memory-projection.js +8 -8
  176. package/dist/mcp-memory-inspector-app.d.ts +5 -5
  177. package/dist/memory-action-policy.d.ts +1 -1
  178. package/dist/memory-cache.d.ts +1 -1
  179. package/dist/memory-lifecycle-ledger-utils.d.ts +1 -1
  180. package/dist/memory-projection-store.d.ts +1 -1
  181. package/dist/memory-provenance.d.ts +1 -1
  182. package/dist/memory-worth-outcomes.d.ts +1 -1
  183. package/dist/models-json.d.ts +1 -1
  184. package/dist/namespaces/migrate.d.ts +2 -2
  185. package/dist/namespaces/migrate.js +14 -13
  186. package/dist/namespaces/principal.d.ts +1 -1
  187. package/dist/namespaces/search.d.ts +1 -1
  188. package/dist/namespaces/search.js +6 -5
  189. package/dist/namespaces/storage.d.ts +2 -2
  190. package/dist/namespaces/storage.js +7 -7
  191. package/dist/native-knowledge.d.ts +1 -1
  192. package/dist/operator-toolkit.d.ts +1 -1
  193. package/dist/operator-toolkit.js +20 -20
  194. package/dist/orchestration/maintenance.d.ts +2 -2
  195. package/dist/orchestration/maintenance.js +9 -9
  196. package/dist/{orchestrator-C_OMSIMZ.d.ts → orchestrator-DqPAz8ot.d.ts} +3 -3
  197. package/dist/orchestrator.d.ts +4 -4
  198. package/dist/orchestrator.js +33 -33
  199. package/dist/patterns-cli.d.ts +1 -1
  200. package/dist/policy-runtime.d.ts +1 -1
  201. package/dist/provenance.d.ts +1 -1
  202. package/dist/provenance.js +1 -1
  203. package/dist/qmd-recall-cache.d.ts +1 -1
  204. package/dist/qmd.d.ts +1 -1
  205. package/dist/recall-disclosure-escalation.d.ts +1 -1
  206. package/dist/recall-explain-renderer.d.ts +1 -1
  207. package/dist/recall-explain-renderer.js +3 -3
  208. package/dist/recall-planner-llm.d.ts +1 -1
  209. package/dist/recall-state.d.ts +1 -1
  210. package/dist/recall-tag-filter.d.ts +1 -1
  211. package/dist/recall-xray-cli.d.ts +1 -1
  212. package/dist/recall-xray-cli.js +4 -4
  213. package/dist/recall-xray-renderer.d.ts +1 -1
  214. package/dist/recall-xray-renderer.js +3 -3
  215. package/dist/recall-xray.d.ts +1 -1
  216. package/dist/recall-xray.js +2 -2
  217. package/dist/resolve-auth-token.d.ts +1 -1
  218. package/dist/resume-bundles.js +4 -4
  219. package/dist/retrieval-agents.d.ts +1 -1
  220. package/dist/retrieval-tiers.d.ts +1 -1
  221. package/dist/routing/engine.d.ts +1 -1
  222. package/dist/routing/store.d.ts +1 -1
  223. package/dist/search/embed-helper.d.ts +1 -1
  224. package/dist/search/embed-helper.js +2 -1
  225. package/dist/search/factory.d.ts +1 -1
  226. package/dist/search/factory.js +5 -4
  227. package/dist/search/index.d.ts +1 -1
  228. package/dist/search/index.js +9 -8
  229. package/dist/search/lancedb-backend.d.ts +1 -1
  230. package/dist/search/meilisearch-backend.d.ts +1 -1
  231. package/dist/search/noop-backend.d.ts +1 -1
  232. package/dist/search/orama-backend.d.ts +1 -1
  233. package/dist/search/port.d.ts +1 -1
  234. package/dist/search/remote-backend.d.ts +1 -1
  235. package/dist/{semantic-consolidation-B8LiYtvx.d.ts → semantic-consolidation-Cl15ksZy.d.ts} +1 -1
  236. package/dist/semantic-consolidation.d.ts +2 -2
  237. package/dist/semantic-consolidation.js +8 -8
  238. package/dist/semantic-rule-promotion.js +7 -7
  239. package/dist/semantic-rule-verifier.d.ts +1 -1
  240. package/dist/semantic-rule-verifier.js +7 -7
  241. package/dist/session-observer-bands.d.ts +1 -1
  242. package/dist/session-observer-state.d.ts +1 -1
  243. package/dist/shared-context/manager.d.ts +1 -1
  244. package/dist/signal.d.ts +1 -1
  245. package/dist/storage.d.ts +1 -1
  246. package/dist/storage.js +6 -6
  247. package/dist/summarizer.d.ts +1 -1
  248. package/dist/summary-snapshot.d.ts +1 -1
  249. package/dist/temporal-supersession.d.ts +1 -1
  250. package/dist/temporal-validity.d.ts +1 -1
  251. package/dist/threading.d.ts +1 -1
  252. package/dist/tier-migration.d.ts +1 -1
  253. package/dist/tier-routing.d.ts +1 -1
  254. package/dist/topics.d.ts +1 -1
  255. package/dist/transcript.d.ts +1 -1
  256. package/dist/transfer/backup.js +2 -2
  257. package/dist/transfer/capsule-export.js +2 -2
  258. package/dist/transfer/capsule-import.js +2 -2
  259. package/dist/trust-score-stage.d.ts +1 -1
  260. package/dist/trust-score.d.ts +1 -1
  261. package/dist/{types-DmTaqob2.d.ts → types-ChQb9D6s.d.ts} +8 -0
  262. package/dist/types.d.ts +1 -1
  263. package/dist/types.js +1 -1
  264. package/dist/utility-runtime.d.ts +1 -1
  265. package/dist/verified-recall.js +7 -7
  266. package/package.json +26 -2
  267. package/src/access-http.ts +68 -1
  268. package/src/access-service.ts +2 -1
  269. package/src/admin/admin-surfaces.test.ts +289 -0
  270. package/src/admin/admin-surfaces.ts +138 -7
  271. package/src/bitemporal-dedup-backfill.test.ts +294 -0
  272. package/src/capabilities.test.ts +155 -0
  273. package/src/capabilities.ts +91 -0
  274. package/src/chat/chat-engine.test.ts +104 -1
  275. package/src/chat/chat-http.test.ts +119 -1
  276. package/src/chat/chat-http.ts +252 -97
  277. package/src/chat/chat-session.ts +166 -7
  278. package/src/cli.ts +3 -2
  279. package/src/compat/codex-cli-fallback.ts +23 -0
  280. package/src/compat/codex-subpath-aliases.test.ts +93 -0
  281. package/src/compat/codex-thread-key.ts +23 -0
  282. package/src/embedding-fallback.ts +2 -1
  283. package/src/extraction-faithfulness.test.ts +240 -0
  284. package/src/extraction-faithfulness.ts +58 -6
  285. package/src/extraction.ts +7 -3
  286. package/src/faithfulness-config.ts +16 -1
  287. package/src/local-model-endpoint.ts +24 -3
  288. package/src/maintenance/first-start-migration.ts +3 -1
  289. package/src/orchestrator.ts +238 -34
  290. package/src/provenance-extraction.test.ts +266 -0
  291. package/src/provenance.ts +66 -16
  292. package/src/search/embed-helper.ts +3 -2
  293. package/src/types.ts +8 -0
  294. package/dist/chunk-2NBP25BG.js.map +0 -1
  295. package/dist/chunk-6OIDDKAA.js.map +0 -1
  296. package/dist/chunk-7AG54AF3.js.map +0 -1
  297. package/dist/chunk-C4X4QGIO.js.map +0 -1
  298. package/dist/chunk-DCKNMLEQ.js.map +0 -1
  299. package/dist/chunk-EHISUJFN.js.map +0 -1
  300. package/dist/chunk-MDBZTH7N.js.map +0 -1
  301. package/dist/chunk-OAHOL3ZC.js.map +0 -1
  302. package/dist/chunk-PLG6JHQS.js.map +0 -1
  303. package/dist/chunk-Q6D75LVH.js.map +0 -1
  304. package/dist/chunk-XGTBNVIU.js.map +0 -1
  305. package/dist/chunk-ZA2S2VLL.js.map +0 -1
  306. package/dist/first-start-migration-PG5HBC3K.js.map +0 -1
  307. /package/dist/{capsule-crypto-GWVG7LGC.js.map → capsule-crypto-7FJQINUR.js.map} +0 -0
  308. /package/dist/{chunk-OWHERGF2.js.map → chunk-2NLLXCJG.js.map} +0 -0
  309. /package/dist/{chunk-BIA7ELIB.js.map → chunk-36SEDFAU.js.map} +0 -0
  310. /package/dist/{chunk-UQSVUEN2.js.map → chunk-37436GML.js.map} +0 -0
  311. /package/dist/{chunk-RRHHQYJS.js.map → chunk-3TSTGI6F.js.map} +0 -0
  312. /package/dist/{chunk-5OOA7SGX.js.map → chunk-4ESAR77N.js.map} +0 -0
  313. /package/dist/{chunk-FO3HO52S.js.map → chunk-4H4F3BFL.js.map} +0 -0
  314. /package/dist/{chunk-BEUCACTV.js.map → chunk-4NOI577P.js.map} +0 -0
  315. /package/dist/{chunk-BBIE7W52.js.map → chunk-4NR2PDZ5.js.map} +0 -0
  316. /package/dist/{chunk-WGTWGDR5.js.map → chunk-76NHR6ZL.js.map} +0 -0
  317. /package/dist/{chunk-PKMQK2WY.js.map → chunk-773DPI6T.js.map} +0 -0
  318. /package/dist/{chunk-MQVKHFIU.js.map → chunk-7RLCCNHU.js.map} +0 -0
  319. /package/dist/{chunk-EZNDNMRH.js.map → chunk-AQLOP5F4.js.map} +0 -0
  320. /package/dist/{chunk-UNNDFYUX.js.map → chunk-ARV3AUOM.js.map} +0 -0
  321. /package/dist/{chunk-NPL7VDM6.js.map → chunk-ASKXIAHD.js.map} +0 -0
  322. /package/dist/{chunk-K4ZNARDO.js.map → chunk-CBWI22JN.js.map} +0 -0
  323. /package/dist/{chunk-ZOHMPFOB.js.map → chunk-CKFLLAIS.js.map} +0 -0
  324. /package/dist/{chunk-JPUT5NGH.js.map → chunk-DMQWV6SQ.js.map} +0 -0
  325. /package/dist/{chunk-F3XZN24Z.js.map → chunk-DRUDC2UX.js.map} +0 -0
  326. /package/dist/{chunk-KJR66XOM.js.map → chunk-EY6MDMHK.js.map} +0 -0
  327. /package/dist/{chunk-3TUT4PWB.js.map → chunk-FVZ2ISIX.js.map} +0 -0
  328. /package/dist/{chunk-6SQQOJ4U.js.map → chunk-GKRLPHPU.js.map} +0 -0
  329. /package/dist/{chunk-5NWYQUQC.js.map → chunk-HOHVIVDR.js.map} +0 -0
  330. /package/dist/{chunk-6WAJQKBS.js.map → chunk-JYLFZWUX.js.map} +0 -0
  331. /package/dist/{chunk-5W7CTZ2E.js.map → chunk-KQAFEZQX.js.map} +0 -0
  332. /package/dist/{chunk-UKTDXTWX.js.map → chunk-M4AYVEWS.js.map} +0 -0
  333. /package/dist/{chunk-YTZ3VGC7.js.map → chunk-M4V2JVSZ.js.map} +0 -0
  334. /package/dist/{chunk-TA3Y6GEX.js.map → chunk-MLKWJOAJ.js.map} +0 -0
  335. /package/dist/{chunk-DQXPJ6HD.js.map → chunk-O6YH5F47.js.map} +0 -0
  336. /package/dist/{chunk-53EEQYE5.js.map → chunk-UTHQPSRC.js.map} +0 -0
  337. /package/dist/{chunk-BB57OZWC.js.map → chunk-UVPL565C.js.map} +0 -0
  338. /package/dist/{chunk-6AFVIOVZ.js.map → chunk-V4GNSDFH.js.map} +0 -0
  339. /package/dist/{chunk-UVYI6VIX.js.map → chunk-X7Y7WX73.js.map} +0 -0
  340. /package/dist/{chunk-V7CMKMHM.js.map → chunk-Y4YD5TYW.js.map} +0 -0
  341. /package/dist/{chunk-7NS7AWMZ.js.map → chunk-YGIQORJ3.js.map} +0 -0
  342. /package/dist/{chunk-3MS3QVX6.js.map → chunk-YOLZS7NY.js.map} +0 -0
@@ -7,7 +7,8 @@
7
7
  * *about* memories does not create memories of the conversation itself.
8
8
  */
9
9
 
10
- import { mkdir, readFile, appendFile, readdir, stat, unlink } from "node:fs/promises";
10
+ import { mkdir, readFile, appendFile, readdir, stat, unlink, open } from "node:fs/promises";
11
+ import { constants as fsConstants } from "node:fs";
11
12
  import { join } from "node:path";
12
13
  import { randomUUID } from "node:crypto";
13
14
 
@@ -40,6 +41,36 @@ export function chatSessionFile(memoryDir: string, chatSessionId: string): strin
40
41
  return join(chatSessionDir(memoryDir), `${chatSessionId}.jsonl`);
41
42
  }
42
43
 
44
+ // ---------------------------------------------------------------------------
45
+ // Monotonic transcript seq (issue #1687 review — strictly increasing)
46
+ // ---------------------------------------------------------------------------
47
+
48
+ /**
49
+ * Last emitted transcript `seq`. Seeded from `Date.now()` on first use and
50
+ * incremented when two appends land in the same millisecond, so `seq` is
51
+ * strictly increasing within a process (review thread: `Date.now()` alone is
52
+ * not monotonic — same-ms entries would share a seq and break the SSE
53
+ * reconnect dedup-by-seq contract). Across process restarts the counter is
54
+ * raised above the on-disk max by `loadChatSession` (issue #1718), so a
55
+ * backward clock step (NTP, VM migration, manual change) cannot re-seed it
56
+ * below lines already persisted for a resumed session.
57
+ */
58
+ let lastTranscriptSeq = 0;
59
+
60
+ function nextTranscriptSeq(): number {
61
+ const now = Date.now();
62
+ lastTranscriptSeq = now > lastTranscriptSeq ? now : lastTranscriptSeq + 1;
63
+ return lastTranscriptSeq;
64
+ }
65
+
66
+ /**
67
+ * Test-only: reset the module-level transcript seq counter so a regression
68
+ * test can deterministically simulate a fresh process restart (issue #1718).
69
+ */
70
+ export function __resetTranscriptSeqForTest(): void {
71
+ lastTranscriptSeq = 0;
72
+ }
73
+
43
74
  /**
44
75
  * Create a new chat session. The session binds the caller's principal and
45
76
  * sessionKey at creation (rule 42); every tool call flows through
@@ -154,6 +185,19 @@ export async function loadChatSession(
154
185
  // tolerated, never crash the engine).
155
186
  }
156
187
  }
188
+ // Issue #1718 — backward-clock-skew robustness: raise the module-level seq
189
+ // counter so the next append is strictly greater than any line already on
190
+ // disk for this session, regardless of clock direction across restarts
191
+ // (NTP step, VM migration, manual change). Without this, a backward clock
192
+ // skew would re-seed the counter below the on-disk max and the SSE
193
+ // reconnect dedup-by-seq contract could drop freshly-appended entries as
194
+ // duplicates. The transcript is already fully parsed above, so this is a
195
+ // single pass over the in-memory array.
196
+ for (const e of transcript) {
197
+ if (typeof e.seq === "number" && e.seq + 1 > lastTranscriptSeq) {
198
+ lastTranscriptSeq = e.seq + 1;
199
+ }
200
+ }
157
201
  return {
158
202
  id: chatSessionId,
159
203
  principal,
@@ -169,25 +213,130 @@ export async function loadChatSession(
169
213
 
170
214
  /**
171
215
  * Append a transcript entry to the session file (atomic append, rule 54).
216
+ *
217
+ * After the append resolves, in-process subscribers (open SSE connections —
218
+ * issue #1687) are notified synchronously so live clients receive the new
219
+ * entry without polling. Listener errors are swallowed so a misbehaving
220
+ * subscriber can never break a transcript append (rule 54 — atomic + safe).
172
221
  */
173
222
  export async function appendTranscriptEntry(
174
223
  memoryDir: string,
175
224
  chatSessionId: string,
176
225
  entry: Omit<ChatTranscriptEntry, "seq" | "ts">,
177
226
  ): Promise<ChatTranscriptEntry> {
227
+ const filePath = chatSessionFile(memoryDir, chatSessionId);
178
228
  const full: ChatTranscriptEntry = {
179
229
  ...entry,
180
- seq: Date.now(),
230
+ seq: nextTranscriptSeq(),
181
231
  ts: new Date().toISOString(),
182
232
  };
183
- await appendFile(
184
- chatSessionFile(memoryDir, chatSessionId),
185
- JSON.stringify(full) + "\n",
186
- "utf8",
187
- );
233
+ // Atomic resurrection guard (codex P2): open with O_APPEND but WITHOUT
234
+ // O_CREAT so a session already unlinked by a concurrent TTL sweep fails at
235
+ // open-time (ENOENT) there is no stat->appendFile TOCTOU. A headerless
236
+ // recreation would load with no principal binding and be treated as public
237
+ // by sessionBelongsToPrincipal. A concurrent unlink during the append is
238
+ // caught by the post-append re-stat below so the turn fails visibly rather
239
+ // than writing to an orphaned inode and returning 200.
240
+ let fh;
241
+ try {
242
+ fh = await open(filePath, fsConstants.O_APPEND | fsConstants.O_WRONLY);
243
+ } catch (err) {
244
+ // Only ENOENT (session unlinked by a concurrent TTL sweep) maps to
245
+ // chat_session_expired; real fs errors (EACCES, EMFILE, EISDIR, ...)
246
+ // are rethrown so the caller surfaces the actual failure (codex P2).
247
+ if (err instanceof Error && (err as NodeJS.ErrnoException).code === "ENOENT") {
248
+ throw new Error("chat_session_expired");
249
+ }
250
+ throw err;
251
+ }
252
+ try {
253
+ await fh.appendFile(JSON.stringify(full) + "\n", "utf8");
254
+ } finally {
255
+ await fh.close();
256
+ }
257
+ // Post-append re-check (codex P2): if a concurrent TTL sweep unlinked the
258
+ // session between open() and here, the append landed on the orphaned inode
259
+ // and the directory no longer references it. Re-stat the PATH (not the fd)
260
+ // so the turn fails with chat_session_expired instead of returning 200 with
261
+ // a reply the client cannot resume. A later sweep unlinking an over-TTL
262
+ // session is a legitimate TTL action, not a silent loss.
263
+ try {
264
+ await stat(filePath);
265
+ } catch (err) {
266
+ // Only ENOENT (concurrent TTL unlink) is session expiry; other fs
267
+ // errors (EACCES, EMFILE, ENOTDIR, ...) are rethrown (codex/kilo P2).
268
+ if (err instanceof Error && (err as NodeJS.ErrnoException).code === "ENOENT") {
269
+ throw new Error("chat_session_expired");
270
+ }
271
+ throw err;
272
+ }
273
+ notifyTranscriptListeners(memoryDir, chatSessionId, full);
188
274
  return full;
189
275
  }
190
276
 
277
+ // ---------------------------------------------------------------------------
278
+ // In-process transcript pub/sub (issue #1687 SSE push)
279
+ // ---------------------------------------------------------------------------
280
+
281
+ /**
282
+ * Per-session listener sets, keyed by `memoryDir + chatSessionId` so two
283
+ * memory stores sharing a legacy/imported session id cannot leak transcript
284
+ * entries to each other (AGENTS.md State Scoping — module-level singletons
285
+ * are scoped per store). Kept module-level so appendTranscriptEntry can fan
286
+ * out new entries to open SSE connections regardless of which caller
287
+ * appended (HTTP, MCP, or CLI). Empty sets are pruned so the map never
288
+ * grows unbounded (rule 47 — no leak).
289
+ */
290
+ const transcriptListeners = new Map<string, Set<(entry: ChatTranscriptEntry) => void>>();
291
+
292
+ function transcriptListenerKey(memoryDir: string, chatSessionId: string): string {
293
+ return `${memoryDir}\x00${chatSessionId}`;
294
+ }
295
+
296
+ function notifyTranscriptListeners(memoryDir: string, chatSessionId: string, entry: ChatTranscriptEntry): void {
297
+ const set = transcriptListeners.get(transcriptListenerKey(memoryDir, chatSessionId));
298
+ if (!set) return;
299
+ for (const fn of set) {
300
+ try {
301
+ fn(entry);
302
+ } catch {
303
+ // A listener must never break the append path (rule 54).
304
+ }
305
+ }
306
+ }
307
+
308
+ /**
309
+ * Subscribe to live transcript entries for a chat session in a given memory
310
+ * store. Returns an unsubscribe function that removes the listener and
311
+ * prunes the session's set when it becomes empty. Used by the SSE handler
312
+ * to push new user/assistant entries to connected clients (issue #1687).
313
+ *
314
+ * The `memoryDir` scopes the subscription to its store so a process hosting
315
+ * two Remnic services cannot cross-deliver transcript entries (AGENTS.md
316
+ * State Scoping).
317
+ */
318
+ export function subscribeChatTranscript(
319
+ memoryDir: string,
320
+ chatSessionId: string,
321
+ listener: (entry: ChatTranscriptEntry) => void,
322
+ ): () => void {
323
+ const key = transcriptListenerKey(memoryDir, chatSessionId);
324
+ let set = transcriptListeners.get(key);
325
+ if (!set) {
326
+ set = new Set();
327
+ transcriptListeners.set(key, set);
328
+ }
329
+ set.add(listener);
330
+ return () => {
331
+ const current = transcriptListeners.get(key);
332
+ if (!current) return;
333
+ current.delete(listener);
334
+ if (current.size === 0) {
335
+ transcriptListeners.delete(key);
336
+ }
337
+ };
338
+ }
339
+
191
340
  /**
192
341
  * Append a pending-plan marker so a later turn can confirm the plan
193
342
  * (append-only state — loadChatSession scans for the latest unresolved one).
@@ -271,6 +420,16 @@ export async function cleanupExpiredChatSessions(
271
420
  // boundary is inclusive; sub-millisecond APFS mtime skew is negligible
272
421
  // against an hour-scale threshold (rule 54 — deterministic cleanup).
273
422
  if (ttlHours <= 0 || ageHours >= ttlHours) {
423
+ // Re-check mtime immediately before unlinking so a turn appended
424
+ // mid-sweep (which refreshes mtime) is not deleted by the stale age
425
+ // decision captured above (codex P2). ttlHours <= 0 (expire-all)
426
+ // skips the re-check. The residual microsecond window is a
427
+ // legitimate sweep of an over-TTL file.
428
+ if (ttlHours > 0) {
429
+ const fresh = await stat(filePath);
430
+ const freshAge = (Date.now() - fresh.mtimeMs) / (1000 * 60 * 60);
431
+ if (freshAge < ttlHours) continue;
432
+ }
274
433
  await unlink(filePath);
275
434
  removed++;
276
435
  }
package/src/cli.ts CHANGED
@@ -4,6 +4,7 @@ import { access, lstat, readFile, readdir, realpath, unlink } from "node:fs/prom
4
4
  import { createHash } from "node:crypto";
5
5
  import type { Readable, Writable } from "node:stream";
6
6
  import type { Orchestrator } from "./orchestrator.js";
7
+ import { resolveMemoryLifecycleCapabilities } from "./capabilities.js";
7
8
  import { ThreadingManager } from "./threading.js";
8
9
  import { utcDayRange } from "./transcript.js";
9
10
  import { runWearablesCliCommand } from "./wearables/cli.js";
@@ -3728,7 +3729,7 @@ export function registerCli(
3728
3729
  console.log("=== Extraction Judge Verdict Stats ===\n");
3729
3730
  console.log(`Total verdicts: ${stats.total}`);
3730
3731
  if (stats.total === 0) {
3731
- if (!orchestrator.config.extractionJudgeTelemetryEnabled) {
3732
+ if (!resolveMemoryLifecycleCapabilities(orchestrator.config).extractionJudgeTelemetry) {
3732
3733
  console.log(
3733
3734
  "\nNote: extractionJudgeTelemetryEnabled is OFF. Enable it in plugin config to collect verdict telemetry.",
3734
3735
  );
@@ -8541,7 +8542,7 @@ export function registerCli(
8541
8542
  config: orchestrator.config,
8542
8543
  memoryDir: orchestrator.config.memoryDir,
8543
8544
  embeddingLookupFactory: (storage) => {
8544
- if (!orchestrator.config.embeddingFallbackEnabled) return undefined;
8545
+ if (!resolveMemoryLifecycleCapabilities(orchestrator.config).embeddingFallback) return undefined;
8545
8546
  return async (content: string, limit: number) => {
8546
8547
  try {
8547
8548
  return await orchestrator.semanticDedupLookup(content, limit, storage);
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Deprecated alias subpath `@remnic/core/codex-cli-fallback`.
3
+ *
4
+ * PR #1638 renamed `src/codex-cli-fallback.ts` -> `src/cli-fallback.ts` to
5
+ * graduate the host-prefixed name per CLAUDE.md rule 31 (generic @remnic/core
6
+ * modules must not carry a host prefix). This thin re-export keeps the old
7
+ * subpath resolving during the compat window so external consumers importing
8
+ * `@remnic/core/codex-cli-fallback` do not hit ERR_PACKAGE_PATH_NOT_EXPORTED.
9
+ * It forwards every export to the canonical module -- no logic is duplicated.
10
+ *
11
+ * Removal timeline: v10.0.0 (see issue #1643).
12
+ * Import from `@remnic/core/cli-fallback` instead.
13
+ */
14
+ export * from "../cli-fallback.js";
15
+
16
+ process.emitWarning(
17
+ "@remnic/core/codex-cli-fallback is a deprecated alias; import from @remnic/core/cli-fallback instead. Removed in v10.0.0 (issue #1643).",
18
+ {
19
+ type: "DeprecationWarning",
20
+ code: "REMNIC_DEP_CORE_SUBPATH_CODEX_CLI_FALLBACK",
21
+ detail: "PR #1638 renamed codex-cli-fallback.ts to cli-fallback.ts.",
22
+ },
23
+ );
@@ -0,0 +1,93 @@
1
+ import assert from "node:assert/strict";
2
+ import test from "node:test";
3
+ import { readFileSync } from "node:fs";
4
+ import { fileURLToPath } from "node:url";
5
+
6
+ import * as canonicalCliFallback from "../cli-fallback.js";
7
+ import * as canonicalThreadKey from "../thread-key.js";
8
+ import * as aliasCliFallback from "./codex-cli-fallback.js";
9
+ import * as aliasThreadKey from "./codex-thread-key.js";
10
+
11
+ const pkgJsonPath = fileURLToPath(new URL("../../package.json", import.meta.url));
12
+
13
+ test("codex-cli-fallback alias re-exports the same value symbols as cli-fallback", () => {
14
+ // export * forwards live bindings by reference, so the function objects and
15
+ // the test-hooks namespace are identical (===) across both subpaths.
16
+ assert.equal(
17
+ aliasCliFallback.callCodexCliFallback,
18
+ canonicalCliFallback.callCodexCliFallback,
19
+ );
20
+ assert.equal(
21
+ aliasCliFallback.setCodexCliFallbackRunnerForProcess,
22
+ canonicalCliFallback.setCodexCliFallbackRunnerForProcess,
23
+ );
24
+ assert.equal(
25
+ aliasCliFallback.__codexCliFallbackTestHooks,
26
+ canonicalCliFallback.__codexCliFallbackTestHooks,
27
+ );
28
+ });
29
+
30
+ test("codex-thread-key alias re-exports the same symbol as thread-key", () => {
31
+ assert.equal(
32
+ aliasThreadKey.CODEX_THREAD_KEY_PREFIX,
33
+ canonicalThreadKey.CODEX_THREAD_KEY_PREFIX,
34
+ );
35
+ assert.equal(aliasThreadKey.CODEX_THREAD_KEY_PREFIX, "codex-thread:");
36
+ });
37
+
38
+ test("every named runtime export of cli-fallback is forwarded by the alias", () => {
39
+ for (const key of Object.keys(canonicalCliFallback)) {
40
+ assert.equal(
41
+ key in aliasCliFallback,
42
+ true,
43
+ `alias missing forwarded export: ${key}`,
44
+ );
45
+ }
46
+ });
47
+
48
+ test("importing the old subpath resolves to the same symbol as the new subpath", async () => {
49
+ // @remnic/core self-references its own exports map. Under --conditions=
50
+ // remnic-source (set by preflight/test NODE_OPTIONS) the subpaths resolve to
51
+ // src/. Both specifiers land on the same module record, so the exported
52
+ // function is the same object reference.
53
+ const [byNew, byOld] = await Promise.all([
54
+ import("@remnic/core/cli-fallback"),
55
+ import("@remnic/core/codex-cli-fallback"),
56
+ ]);
57
+ assert.equal(byOld.callCodexCliFallback, byNew.callCodexCliFallback);
58
+
59
+ const [newKey, oldKey] = await Promise.all([
60
+ import("@remnic/core/thread-key"),
61
+ import("@remnic/core/codex-thread-key"),
62
+ ]);
63
+ assert.equal(oldKey.CODEX_THREAD_KEY_PREFIX, newKey.CODEX_THREAD_KEY_PREFIX);
64
+ });
65
+
66
+ test("the deprecated subpaths are registered with a removal timeline in the exports map", () => {
67
+ const pkg = JSON.parse(readFileSync(pkgJsonPath, "utf8")) as {
68
+ exports: Record<string, { deprecated?: string; import?: string }>;
69
+ };
70
+ const aliasEntries = [
71
+ "./codex-cli-fallback",
72
+ "./codex-cli-fallback.js",
73
+ "./codex-thread-key",
74
+ "./codex-thread-key.js",
75
+ ];
76
+ for (const subpath of aliasEntries) {
77
+ const entry = pkg.exports[subpath];
78
+ assert.ok(entry, `missing exports entry for ${subpath}`);
79
+ assert.ok(
80
+ entry.deprecated,
81
+ `exports entry for ${subpath} missing a deprecated field`,
82
+ );
83
+ assert.match(
84
+ entry.deprecated,
85
+ /v?10\.0\.0|remov/i,
86
+ `deprecated field for ${subpath} should state a removal timeline`,
87
+ );
88
+ assert.ok(
89
+ entry.import?.includes("/compat/"),
90
+ `exports entry for ${subpath} should target the compat re-export module`,
91
+ );
92
+ }
93
+ });
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Deprecated alias subpath `@remnic/core/codex-thread-key`.
3
+ *
4
+ * PR #1638 renamed `src/codex-thread-key.ts` -> `src/thread-key.ts` to
5
+ * graduate the host-prefixed name per CLAUDE.md rule 31. This thin re-export
6
+ * keeps the old subpath resolving during the compat window so external
7
+ * consumers importing `@remnic/core/codex-thread-key` do not hit
8
+ * ERR_PACKAGE_PATH_NOT_EXPORTED. It forwards every export to the canonical
9
+ * module -- no logic is duplicated.
10
+ *
11
+ * Removal timeline: v10.0.0 (see issue #1643).
12
+ * Import from `@remnic/core/thread-key` instead.
13
+ */
14
+ export * from "../thread-key.js";
15
+
16
+ process.emitWarning(
17
+ "@remnic/core/codex-thread-key is a deprecated alias; import from @remnic/core/thread-key instead. Removed in v10.0.0 (issue #1643).",
18
+ {
19
+ type: "DeprecationWarning",
20
+ code: "REMNIC_DEP_CORE_SUBPATH_CODEX_THREAD_KEY",
21
+ detail: "PR #1638 renamed codex-thread-key.ts to thread-key.ts.",
22
+ },
23
+ );
@@ -1,6 +1,7 @@
1
1
  import path from "node:path";
2
2
  import { mkdir, readFile, rename, rm, writeFile } from "node:fs/promises";
3
3
  import { log } from "./logger.js";
4
+ import { resolveMemoryLifecycleCapabilities } from "./capabilities.js";
4
5
  import { readEnvVar } from "./runtime/env.js";
5
6
  import type { PluginConfig } from "./types.js";
6
7
  import {
@@ -397,7 +398,7 @@ export class EmbeddingFallback {
397
398
  private async resolveProvider(
398
399
  options: { includeHost?: boolean } = {},
399
400
  ): Promise<ProviderConfig | null> {
400
- if (!this.config.embeddingFallbackEnabled) return null;
401
+ if (!resolveMemoryLifecycleCapabilities(this.config).embeddingFallback) return null;
401
402
 
402
403
  if (
403
404
  options.includeHost !== false &&
@@ -26,6 +26,7 @@ import type { FaithfulnessResult } from "./extraction-faithfulness.js";
26
26
  import type { FallbackLlmClient } from "./fallback-llm.js";
27
27
  import type { LocalLlmClient } from "./local-llm.js";
28
28
  import type { MemoryFrontmatter } from "./types.js";
29
+ import { callOpenAiCompatibleChat } from "./local-model-endpoint.js";
29
30
 
30
31
  // ---------------------------------------------------------------------------
31
32
  // Helpers — stub LLM clients
@@ -1429,3 +1430,242 @@ test("checkFaithfulnessBatch: local-endpoint failure does NOT leak the local mod
1429
1430
  );
1430
1431
  assert.equal(result.results[0]?.ok, true);
1431
1432
  });
1433
+
1434
+ // ---------------------------------------------------------------------------
1435
+ // Issue #1700 nits #5 + #6: probe signal forwarding + local parse-fallback
1436
+ // ---------------------------------------------------------------------------
1437
+
1438
+ test("callOpenAiCompatibleChat: a pre-aborted batch signal aborts the probe immediately, not after timeoutMs (#1700 nit #5)", async () => {
1439
+ // The batch signal is ALREADY aborted when the probe starts. With nit #5 the
1440
+ // probe forwards it and aborts instantly; without it the probe would wait for
1441
+ // its own timeoutMs (5000ms here). Assert the probe returns null fast.
1442
+ const endpoint = { baseUrl: "http://localhost:11434/v1", model: "remnic-faithfulness-gate-v1" };
1443
+ const batchController = new AbortController();
1444
+ batchController.abort();
1445
+ const spy = ((_url: string, init: RequestInit) =>
1446
+ new Promise<Response>((_resolve, reject) => {
1447
+ const sig = init.signal;
1448
+ if (!sig) return reject(new Error("no signal"));
1449
+ if (sig.aborted) return reject(new Error("aborted"));
1450
+ sig.addEventListener("abort", () => reject(new Error("aborted")), { once: true });
1451
+ })) as unknown as typeof fetch;
1452
+ const start = Date.now();
1453
+ const result = await callOpenAiCompatibleChat(
1454
+ endpoint,
1455
+ [{ role: "user", content: "hi" }],
1456
+ { timeoutMs: 5000, signal: batchController.signal },
1457
+ spy,
1458
+ );
1459
+ const elapsed = Date.now() - start;
1460
+ assert.equal(result, null, "a pre-aborted signal must fail the probe closed (null)");
1461
+ assert.ok(
1462
+ elapsed < 1000,
1463
+ `probe must abort immediately on a pre-aborted signal (${elapsed}ms), not wait timeoutMs`,
1464
+ );
1465
+ });
1466
+
1467
+ test("checkFaithfulnessBatch: default surfaces malformed_output for a 200-with-garbage local response (#1700 nit #6)", async () => {
1468
+ // extractionFaithfulnessLocalParseFallback unset (default false): a local
1469
+ // endpoint returning 200 with non-verdict JSON surfaces malformed_output so a
1470
+ // misconfigured endpoint is visible. The configured chain must NOT run.
1471
+ const inputs = [{ factText: "Fact", quote: "Quote" }];
1472
+ const config = parseConfig({
1473
+ extractionFaithfulnessModel: "remnic-faithfulness-gate-v1",
1474
+ extractionFaithfulnessBaseUrl: "http://localhost:11434/v1",
1475
+ extractionFaithfulnessTimeoutMs: 5000,
1476
+ });
1477
+ const fallbackCalls: Array<{ messages: unknown; options: unknown }> = [];
1478
+ const { fetch: garbageFetch } = fakeFetchFor("http://localhost:11434/v1", () => ({
1479
+ model: "remnic-faithfulness-gate-v1",
1480
+ choices: [{ message: { content: "this is not a verdict array" } }],
1481
+ }));
1482
+ const result = await checkFaithfulnessBatch(
1483
+ inputs,
1484
+ config,
1485
+ null,
1486
+ stubFallbackLlm(JSON.stringify([{ index: 0, verdict: "entailed" }]), fallbackCalls),
1487
+ garbageFetch,
1488
+ );
1489
+ assert.equal(fallbackCalls.length, 0, "chain must NOT run when flag is off — garbage surfaces loudly");
1490
+ assert.equal(result.results[0]?.ok, false);
1491
+ if (!result.results[0]?.ok) {
1492
+ assert.equal(result.results[0].error.code, "malformed_output");
1493
+ }
1494
+ });
1495
+
1496
+ test("checkFaithfulnessBatch: extractionFaithfulnessLocalParseFallback falls back to the chain on local parse failure (#1700 nit #6)", async () => {
1497
+ // Flag ON: the same 200-with-garbage local response now falls through to the
1498
+ // configured chain instead of surfacing malformed_output (resilient fallback).
1499
+ const inputs = [{ factText: "Fact", quote: "Quote" }];
1500
+ const config = parseConfig({
1501
+ extractionFaithfulnessModel: "remnic-faithfulness-gate-v1",
1502
+ extractionFaithfulnessBaseUrl: "http://localhost:11434/v1",
1503
+ extractionFaithfulnessTimeoutMs: 5000,
1504
+ extractionFaithfulnessLocalParseFallback: true,
1505
+ });
1506
+ const fallbackCalls: Array<{ messages: unknown; options: unknown }> = [];
1507
+ const { fetch: garbageFetch } = fakeFetchFor("http://localhost:11434/v1", () => ({
1508
+ model: "remnic-faithfulness-gate-v1",
1509
+ choices: [{ message: { content: "this is not a verdict array" } }],
1510
+ }));
1511
+ const result = await checkFaithfulnessBatch(
1512
+ inputs,
1513
+ config,
1514
+ null,
1515
+ stubFallbackLlm(JSON.stringify([{ index: 0, verdict: "entailed" }]), fallbackCalls),
1516
+ garbageFetch,
1517
+ );
1518
+ assert.equal(
1519
+ fallbackCalls.length,
1520
+ 1,
1521
+ "chain MUST run when flag is on and the local response is unparseable",
1522
+ );
1523
+ assert.equal(result.results[0]?.ok, true);
1524
+ if (result.results[0]?.ok) {
1525
+ assert.equal(result.results[0].verdict, "entailed", "fallback chain verdict is used");
1526
+ }
1527
+ });
1528
+
1529
+ test("checkFaithfulnessBatch: extractionFaithfulnessLocalParseFallback does NOT fall back when the local response is valid (#1700 nit #6 regression)", async () => {
1530
+ // Flag ON but the local response IS a valid verdict array → use it, do not
1531
+ // call the chain. Guards against the flag over-triggering on good responses.
1532
+ const inputs = [{ factText: "Fact", quote: "Quote" }];
1533
+ const config = parseConfig({
1534
+ extractionFaithfulnessModel: "remnic-faithfulness-gate-v1",
1535
+ extractionFaithfulnessBaseUrl: "http://localhost:11434/v1",
1536
+ extractionFaithfulnessTimeoutMs: 5000,
1537
+ extractionFaithfulnessLocalParseFallback: true,
1538
+ });
1539
+ const fallbackCalls: Array<{ messages: unknown; options: unknown }> = [];
1540
+ const { fetch: goodFetch } = fakeFetchFor("http://localhost:11434/v1", () => ({
1541
+ model: "remnic-faithfulness-gate-v1",
1542
+ choices: [{ message: { content: JSON.stringify([{ index: 0, verdict: "contradicted" }]) } }],
1543
+ }));
1544
+ const result = await checkFaithfulnessBatch(
1545
+ inputs,
1546
+ config,
1547
+ null,
1548
+ stubFallbackLlm(JSON.stringify([{ index: 0, verdict: "entailed" }]), fallbackCalls),
1549
+ goodFetch,
1550
+ );
1551
+ assert.equal(fallbackCalls.length, 0, "chain must NOT run when the local response is valid, even with the flag on");
1552
+ assert.equal(result.results[0]?.ok, true);
1553
+ if (result.results[0]?.ok) {
1554
+ assert.equal(result.results[0].verdict, "contradicted", "valid local verdict is used");
1555
+ }
1556
+ });
1557
+
1558
+ test("checkFaithfulnessBatch: extractionFaithfulnessLocalParseFallback falls back when the local response is a PARTIAL verdict array (codex P2 PRRT_kwDORJXyws6O6zwZ)", async () => {
1559
+ // Flag ON: a local endpoint that returns a valid-but-INCOMPLETE verdict
1560
+ // array (1 of N entries) must fall through to the configured chain, NOT be
1561
+ // accepted as-is. parseFaithfulnessResponse is truthy as soon as ONE entry
1562
+ // is valid, so the previous guard accepted the partial response and the
1563
+ // missing indexes surfaced as malformed_output downstream -- defeating the
1564
+ // resilient fallback. Accept the local response only when it covers every
1565
+ // expected index.
1566
+ const inputs = [
1567
+ { factText: "Fact A", quote: "Quote A" },
1568
+ { factText: "Fact B", quote: "Quote B" },
1569
+ ];
1570
+ const config = parseConfig({
1571
+ extractionFaithfulnessModel: "remnic-faithfulness-gate-v1",
1572
+ extractionFaithfulnessBaseUrl: "http://localhost:11434/v1",
1573
+ extractionFaithfulnessTimeoutMs: 5000,
1574
+ extractionFaithfulnessLocalParseFallback: true,
1575
+ });
1576
+ const fallbackCalls: Array<{ messages: unknown; options: unknown }> = [];
1577
+ // Local endpoint returns only index 0 -- index 1 is missing (partial).
1578
+ const { fetch: partialFetch } = fakeFetchFor("http://localhost:11434/v1", () => ({
1579
+ model: "remnic-faithfulness-gate-v1",
1580
+ choices: [{ message: { content: JSON.stringify([{ index: 0, verdict: "contradicted" }]) } }],
1581
+ }));
1582
+ const result = await checkFaithfulnessBatch(
1583
+ inputs,
1584
+ config,
1585
+ null,
1586
+ stubFallbackLlm(
1587
+ JSON.stringify([
1588
+ { index: 0, verdict: "entailed" },
1589
+ { index: 1, verdict: "unsupported" },
1590
+ ]),
1591
+ fallbackCalls,
1592
+ ),
1593
+ partialFetch,
1594
+ );
1595
+ assert.equal(
1596
+ fallbackCalls.length,
1597
+ 1,
1598
+ "a partial local verdict array must fall through to the configured chain",
1599
+ );
1600
+ // The chain's complete verdict array is used for both indexes.
1601
+ assert.equal(result.results[0]?.ok && result.results[0].verdict, "entailed");
1602
+ assert.equal(result.results[1]?.ok && result.results[1].verdict, "unsupported");
1603
+ });
1604
+
1605
+ test("checkFaithfulnessBatch: extractionFaithfulnessLocalParseFallback falls back when local indexes are fractional/non-contiguous (codex P2 PRRT_kwDORJXyws6O7PfY)", async () => {
1606
+ // Flag ON: a malformed local response with expectedCount distinct but
1607
+ // NON-INTEGER indexes (e.g. 0 and 0.5 for a two-item batch) must NOT be
1608
+ // accepted. parseFaithfulnessResponse used to admit any numeric index, so a
1609
+ // size-only check passed while index 1 stayed missing -> malformed_output.
1610
+ // parseEntries now rejects non-integer indexes, and the gate requires every
1611
+ // integer key 0..N-1 before accepting the local response.
1612
+ const inputs = [
1613
+ { factText: "Fact A", quote: "Quote A" },
1614
+ { factText: "Fact B", quote: "Quote B" },
1615
+ ];
1616
+ const config = parseConfig({
1617
+ extractionFaithfulnessModel: "remnic-faithfulness-gate-v1",
1618
+ extractionFaithfulnessBaseUrl: "http://localhost:11434/v1",
1619
+ extractionFaithfulnessTimeoutMs: 5000,
1620
+ extractionFaithfulnessLocalParseFallback: true,
1621
+ });
1622
+ const fallbackCalls: Array<{ messages: unknown; options: unknown }> = [];
1623
+ // Local endpoint returns indexes 0 and 0.5 -- two distinct keys, but index 1
1624
+ // is missing (fractional index is invalid).
1625
+ const { fetch: fracFetch } = fakeFetchFor("http://localhost:11434/v1", () => ({
1626
+ model: "remnic-faithfulness-gate-v1",
1627
+ choices: [{ message: { content: JSON.stringify([
1628
+ { index: 0, verdict: "contradicted" },
1629
+ { index: 0.5, verdict: "entailed" },
1630
+ ]) } }],
1631
+ }));
1632
+ const result = await checkFaithfulnessBatch(
1633
+ inputs,
1634
+ config,
1635
+ null,
1636
+ stubFallbackLlm(
1637
+ JSON.stringify([
1638
+ { index: 0, verdict: "entailed" },
1639
+ { index: 1, verdict: "unsupported" },
1640
+ ]),
1641
+ fallbackCalls,
1642
+ ),
1643
+ fracFetch,
1644
+ );
1645
+ assert.equal(
1646
+ fallbackCalls.length,
1647
+ 1,
1648
+ "a fractional/non-contiguous local response must fall through to the chain",
1649
+ );
1650
+ assert.equal(result.results[0]?.ok && result.results[0].verdict, "entailed");
1651
+ assert.equal(result.results[1]?.ok && result.results[1].verdict, "unsupported");
1652
+ });
1653
+
1654
+ test('parseConfig: extractionFaithfulnessLocalParseFallback coerces CLI string "true" (#1700 review — coerceBool parity)', () => {
1655
+ // A CLI override reaches the parser as the string "true"; === true would
1656
+ // silently leave the flag disabled. coerceBool must turn it on (gotcha 36).
1657
+ const onByString = parseConfig({
1658
+ extractionFaithfulnessLocalParseFallback: "true",
1659
+ });
1660
+ assert.equal(onByString.extractionFaithfulnessLocalParseFallback, true);
1661
+ const onByBool = parseConfig({
1662
+ extractionFaithfulnessLocalParseFallback: true,
1663
+ });
1664
+ assert.equal(onByBool.extractionFaithfulnessLocalParseFallback, true);
1665
+ const offDefault = parseConfig({});
1666
+ assert.equal(offDefault.extractionFaithfulnessLocalParseFallback, false);
1667
+ const offByString = parseConfig({
1668
+ extractionFaithfulnessLocalParseFallback: "false",
1669
+ });
1670
+ assert.equal(offByString.extractionFaithfulnessLocalParseFallback, false);
1671
+ });