@remnic/core 9.3.725 → 9.3.727

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 (352) hide show
  1. package/dist/access-boundary.d.ts +5 -5
  2. package/dist/access-boundary.js +28 -27
  3. package/dist/access-cli.js +65 -63
  4. package/dist/access-cli.js.map +1 -1
  5. package/dist/access-http.d.ts +20 -5
  6. package/dist/access-http.js +34 -33
  7. package/dist/access-mcp.d.ts +5 -5
  8. package/dist/access-mcp.js +31 -30
  9. package/dist/access-operations-batch.js +29 -28
  10. package/dist/access-operations.d.ts +5 -5
  11. package/dist/access-operations.js +30 -29
  12. package/dist/{access-service-B2PlPtxh.d.ts → access-service-Bgpjxo4f.d.ts} +36 -4
  13. package/dist/access-service.d.ts +5 -5
  14. package/dist/access-service.js +27 -26
  15. package/dist/access-surface-catalog.d.ts +5 -5
  16. package/dist/action-confidence.d.ts +1 -1
  17. package/dist/active-memory-bridge.d.ts +1 -1
  18. package/dist/active-recall.d.ts +1 -1
  19. package/dist/active-recall.js +3 -3
  20. package/dist/adapters/index.js +4 -4
  21. package/dist/adapters/registry.js +2 -2
  22. package/dist/behavior-learner.d.ts +1 -1
  23. package/dist/behavior-signals.d.ts +1 -1
  24. package/dist/bootstrap.d.ts +4 -4
  25. package/dist/briefing.d.ts +1 -1
  26. package/dist/briefing.js +9 -8
  27. package/dist/buffer-surprise-report.d.ts +1 -1
  28. package/dist/buffer.d.ts +1 -1
  29. package/dist/calibration.d.ts +1 -1
  30. package/dist/capabilities.d.ts +1 -1
  31. package/dist/{catalog-CzUb-GyD.d.ts → catalog-bMczlO2G.d.ts} +1 -1
  32. package/dist/causal-behavior.d.ts +1 -1
  33. package/dist/causal-consolidation.d.ts +1 -1
  34. package/dist/causal-consolidation.js +10 -9
  35. package/dist/causal-consolidation.js.map +1 -1
  36. package/dist/causal-trajectory-graph.d.ts +1 -1
  37. package/dist/{chunk-KGZF2VO3.js → chunk-27QQC7ZX.js} +80 -19
  38. package/dist/{chunk-KGZF2VO3.js.map → chunk-27QQC7ZX.js.map} +1 -1
  39. package/dist/{chunk-HCVWF657.js → chunk-2KAYTPPT.js} +12 -12
  40. package/dist/{chunk-4SF232E6.js → chunk-36SEDFAU.js} +19 -7
  41. package/dist/chunk-36SEDFAU.js.map +1 -0
  42. package/dist/{chunk-ZK2L72VN.js → chunk-37436GML.js} +2 -2
  43. package/dist/{chunk-SC2YAWRF.js → chunk-3TSTGI6F.js} +2 -2
  44. package/dist/{chunk-FACTVMXQ.js → chunk-4H4F3BFL.js} +2 -2
  45. package/dist/{chunk-WAFDHKES.js → chunk-4NOI577P.js} +2 -2
  46. package/dist/{chunk-YQMPUYHE.js → chunk-4NR2PDZ5.js} +2 -2
  47. package/dist/{chunk-TAPSNIMA.js → chunk-5CEJH5ZN.js} +4 -4
  48. package/dist/{chunk-FN6QP7JQ.js → chunk-6PY5YJAV.js} +100 -14
  49. package/dist/{chunk-FN6QP7JQ.js.map → chunk-6PY5YJAV.js.map} +1 -1
  50. package/dist/{chunk-PKMQK2WY.js → chunk-773DPI6T.js} +4 -4
  51. package/dist/{chunk-4R3CGCI5.js → chunk-7RLCCNHU.js} +15 -12
  52. package/dist/chunk-7RLCCNHU.js.map +1 -0
  53. package/dist/{chunk-XGTBNVIU.js → chunk-AAD2ZS6P.js} +11 -1
  54. package/dist/chunk-AAD2ZS6P.js.map +1 -0
  55. package/dist/{chunk-Z3FGC3QE.js → chunk-AQLOP5F4.js} +139 -30
  56. package/dist/chunk-AQLOP5F4.js.map +1 -0
  57. package/dist/{chunk-FXCIOLRB.js → chunk-ASKXIAHD.js} +2 -2
  58. package/dist/{chunk-5OPYYPJN.js → chunk-CBWI22JN.js} +2 -2
  59. package/dist/{chunk-ZOHMPFOB.js → chunk-CKFLLAIS.js} +3 -3
  60. package/dist/{chunk-GZQXOQQ2.js → chunk-CSRXQ6NB.js} +5 -5
  61. package/dist/{chunk-QR67FU5G.js → chunk-CTOQEZSN.js} +2 -2
  62. package/dist/{chunk-ZUDM75KG.js → chunk-DCWIQFNA.js} +4 -4
  63. package/dist/{chunk-6CWGS2LY.js → chunk-DKJCRGWM.js} +3 -3
  64. package/dist/{chunk-JJJCVCCQ.js → chunk-DMQWV6SQ.js} +2 -2
  65. package/dist/{chunk-F3XZN24Z.js → chunk-DRUDC2UX.js} +2 -2
  66. package/dist/{chunk-4WZ3J7M6.js → chunk-EY6MDMHK.js} +2 -2
  67. package/dist/{chunk-YNDLCWXS.js → chunk-EZ25VE3G.js} +4 -4
  68. package/dist/{chunk-C4X4QGIO.js → chunk-G6NKALRN.js} +5 -2
  69. package/dist/chunk-G6NKALRN.js.map +1 -0
  70. package/dist/{chunk-6SQQOJ4U.js → chunk-GKRLPHPU.js} +2 -2
  71. package/dist/{chunk-D7EBTQ24.js → chunk-HOHVIVDR.js} +2 -2
  72. package/dist/{chunk-RKNJBZ55.js → chunk-JBPKEARU.js} +4 -4
  73. package/dist/{chunk-UBHVAGO7.js → chunk-JYLFZWUX.js} +2 -2
  74. package/dist/{chunk-LD53WPMU.js → chunk-LQ6JI4VH.js} +4 -4
  75. package/dist/chunk-LTHVM4XN.js +109 -0
  76. package/dist/chunk-LTHVM4XN.js.map +1 -0
  77. package/dist/{chunk-WM7DNKBD.js → chunk-M4V2JVSZ.js} +2 -2
  78. package/dist/{chunk-I2HL5M6T.js → chunk-MJIHJ7OG.js} +52 -52
  79. package/dist/{chunk-J334N6QV.js → chunk-MLKWJOAJ.js} +2 -2
  80. package/dist/{chunk-C6BWLYW5.js → chunk-N3VNUM4Z.js} +171 -79
  81. package/dist/chunk-N3VNUM4Z.js.map +1 -0
  82. package/dist/{chunk-DF3I6JGP.js → chunk-NSA5ZRUL.js} +15 -15
  83. package/dist/{chunk-VI5SGGXB.js → chunk-OEEW4KJA.js} +4 -4
  84. package/dist/{chunk-7AG54AF3.js → chunk-PUR2CKX2.js} +1 -1
  85. package/dist/chunk-PUR2CKX2.js.map +1 -0
  86. package/dist/{chunk-NT36GU6X.js → chunk-R7IYU5DZ.js} +2 -2
  87. package/dist/{chunk-TT5U3UDO.js → chunk-SNKPUJHR.js} +4 -4
  88. package/dist/{chunk-T55EGZSJ.js → chunk-UVPL565C.js} +2 -2
  89. package/dist/chunk-W5H6ZRPT.js +14 -0
  90. package/dist/chunk-W5H6ZRPT.js.map +1 -0
  91. package/dist/{chunk-YQGHW5ML.js → chunk-WRFKZEO6.js} +4 -4
  92. package/dist/{chunk-KLFYKC3K.js → chunk-XPHVAOBT.js} +195 -74
  93. package/dist/chunk-XPHVAOBT.js.map +1 -0
  94. package/dist/{chunk-H7HUQIY7.js → chunk-Y4YD5TYW.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-YUYPWJD3.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 +60 -59
  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 +9 -8
  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 +9 -8
  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 +9 -8
  120. package/dist/consolidation-provenance-check.d.ts +1 -1
  121. package/dist/consolidation-undo.d.ts +1 -1
  122. package/dist/content-hash.d.ts +14 -0
  123. package/dist/content-hash.js +10 -0
  124. package/dist/content-hash.js.map +1 -0
  125. package/dist/contradiction/index.d.ts +1 -1
  126. package/dist/conversation-index/backend.d.ts +1 -1
  127. package/dist/conversation-index/backend.js +2 -2
  128. package/dist/conversation-index/chunker.d.ts +1 -1
  129. package/dist/conversation-index/faiss-adapter.d.ts +1 -1
  130. package/dist/conversation-index/indexer.d.ts +1 -1
  131. package/dist/conversation-index/search.d.ts +1 -1
  132. package/dist/dashboard-runtime.js +2 -2
  133. package/dist/day-summary.d.ts +1 -1
  134. package/dist/delinearize.d.ts +1 -1
  135. package/dist/direct-answer-wiring.d.ts +1 -1
  136. package/dist/direct-answer.d.ts +1 -1
  137. package/dist/embedding-fallback.d.ts +1 -1
  138. package/dist/enrichment/index.d.ts +1 -1
  139. package/dist/entity-retrieval.d.ts +1 -1
  140. package/dist/entity-retrieval.js +9 -8
  141. package/dist/entity-schema.d.ts +1 -1
  142. package/dist/explicit-capture.d.ts +4 -4
  143. package/dist/extraction-faithfulness.d.ts +1 -1
  144. package/dist/extraction-faithfulness.js +2 -2
  145. package/dist/extraction-judge-telemetry.d.ts +1 -1
  146. package/dist/extraction-judge-training.d.ts +1 -1
  147. package/dist/extraction-judge.d.ts +1 -1
  148. package/dist/extraction-redaction-rules.d.ts +33 -0
  149. package/dist/extraction-redaction-rules.js +14 -0
  150. package/dist/extraction-redaction-rules.js.map +1 -0
  151. package/dist/extraction.d.ts +1 -1
  152. package/dist/extraction.js +3 -3
  153. package/dist/faithfulness-config.d.ts +1 -0
  154. package/dist/faithfulness-config.js +1 -1
  155. package/dist/fallback-llm.d.ts +1 -1
  156. package/dist/{forget-OEE5OELK.js → forget-WYLR4ZFV.js} +2 -2
  157. package/dist/graph-dashboard-diff.d.ts +1 -1
  158. package/dist/graph-dashboard-key.d.ts +1 -1
  159. package/dist/graph-dashboard-parser.d.ts +1 -1
  160. package/dist/graph-edge-reinforcement.d.ts +1 -1
  161. package/dist/graph-snapshot.d.ts +1 -1
  162. package/dist/graph.d.ts +1 -1
  163. package/dist/identity-continuity.d.ts +1 -1
  164. package/dist/importance.d.ts +1 -1
  165. package/dist/index.d.ts +9 -9
  166. package/dist/index.js +107 -105
  167. package/dist/index.js.map +1 -1
  168. package/dist/intent.d.ts +1 -1
  169. package/dist/lcm/engine.d.ts +1 -1
  170. package/dist/lcm/engine.js +2 -2
  171. package/dist/lcm/index.d.ts +1 -1
  172. package/dist/lcm/index.js +4 -4
  173. package/dist/lcm/tools.d.ts +1 -1
  174. package/dist/lifecycle.d.ts +1 -1
  175. package/dist/live-connectors-runner.d.ts +1 -1
  176. package/dist/local-llm.d.ts +1 -1
  177. package/dist/local-model-endpoint.d.ts +9 -4
  178. package/dist/local-model-endpoint.js +1 -1
  179. package/dist/maintenance/memory-governance.d.ts +1 -1
  180. package/dist/maintenance/memory-governance.js +9 -8
  181. package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +9 -8
  182. package/dist/maintenance/rebuild-memory-projection.js +10 -9
  183. package/dist/mcp-memory-inspector-app.d.ts +5 -5
  184. package/dist/memory-action-policy.d.ts +1 -1
  185. package/dist/memory-cache.d.ts +1 -1
  186. package/dist/memory-lifecycle-ledger-utils.d.ts +1 -1
  187. package/dist/memory-projection-store.d.ts +1 -1
  188. package/dist/memory-provenance.d.ts +1 -1
  189. package/dist/memory-worth-outcomes.d.ts +1 -1
  190. package/dist/models-json.d.ts +1 -1
  191. package/dist/namespaces/migrate.d.ts +2 -2
  192. package/dist/namespaces/migrate.js +19 -18
  193. package/dist/namespaces/principal.d.ts +1 -1
  194. package/dist/namespaces/search.d.ts +1 -1
  195. package/dist/namespaces/search.js +9 -9
  196. package/dist/namespaces/storage.d.ts +2 -2
  197. package/dist/namespaces/storage.js +9 -8
  198. package/dist/native-knowledge.d.ts +1 -1
  199. package/dist/operator-toolkit.d.ts +1 -1
  200. package/dist/operator-toolkit.js +28 -27
  201. package/dist/orchestration/maintenance.d.ts +2 -2
  202. package/dist/orchestration/maintenance.js +11 -10
  203. package/dist/{orchestrator-C_OMSIMZ.d.ts → orchestrator-DqPAz8ot.d.ts} +3 -3
  204. package/dist/orchestrator.d.ts +4 -4
  205. package/dist/orchestrator.js +51 -49
  206. package/dist/patterns-cli.d.ts +1 -1
  207. package/dist/policy-runtime.d.ts +1 -1
  208. package/dist/provenance.d.ts +1 -1
  209. package/dist/provenance.js +1 -1
  210. package/dist/qmd-recall-cache.d.ts +1 -1
  211. package/dist/qmd.d.ts +1 -1
  212. package/dist/recall-disclosure-escalation.d.ts +1 -1
  213. package/dist/recall-explain-renderer.d.ts +1 -1
  214. package/dist/recall-explain-renderer.js +3 -3
  215. package/dist/recall-planner-llm.d.ts +1 -1
  216. package/dist/recall-state.d.ts +1 -1
  217. package/dist/recall-tag-filter.d.ts +1 -1
  218. package/dist/recall-xray-cli.d.ts +1 -1
  219. package/dist/recall-xray-cli.js +4 -4
  220. package/dist/recall-xray-renderer.d.ts +1 -1
  221. package/dist/recall-xray-renderer.js +3 -3
  222. package/dist/recall-xray.d.ts +1 -1
  223. package/dist/recall-xray.js +2 -2
  224. package/dist/resolve-auth-token.d.ts +1 -1
  225. package/dist/resume-bundles.js +4 -4
  226. package/dist/retrieval-agents.d.ts +1 -1
  227. package/dist/retrieval-tiers.d.ts +1 -1
  228. package/dist/routing/engine.d.ts +1 -1
  229. package/dist/routing/store.d.ts +1 -1
  230. package/dist/schemas.d.ts +22 -22
  231. package/dist/search/embed-helper.d.ts +1 -1
  232. package/dist/search/factory.d.ts +1 -1
  233. package/dist/search/factory.js +8 -8
  234. package/dist/search/index.d.ts +1 -1
  235. package/dist/search/index.js +14 -14
  236. package/dist/search/lancedb-backend.d.ts +1 -1
  237. package/dist/search/lancedb-backend.js +2 -2
  238. package/dist/search/meilisearch-backend.d.ts +1 -1
  239. package/dist/search/meilisearch-backend.js +2 -2
  240. package/dist/search/noop-backend.d.ts +1 -1
  241. package/dist/search/orama-backend.d.ts +1 -1
  242. package/dist/search/orama-backend.js +2 -2
  243. package/dist/search/port.d.ts +1 -1
  244. package/dist/search/remote-backend.d.ts +1 -1
  245. package/dist/{semantic-consolidation-B8LiYtvx.d.ts → semantic-consolidation-Cl15ksZy.d.ts} +1 -1
  246. package/dist/semantic-consolidation.d.ts +2 -2
  247. package/dist/semantic-consolidation.js +10 -9
  248. package/dist/semantic-rule-promotion.js +9 -8
  249. package/dist/semantic-rule-verifier.d.ts +1 -1
  250. package/dist/semantic-rule-verifier.js +9 -8
  251. package/dist/session-observer-bands.d.ts +1 -1
  252. package/dist/session-observer-state.d.ts +1 -1
  253. package/dist/shared-context/manager.d.ts +1 -1
  254. package/dist/signal.d.ts +1 -1
  255. package/dist/storage.d.ts +3 -3
  256. package/dist/storage.js +9 -8
  257. package/dist/summarizer.d.ts +1 -1
  258. package/dist/summary-snapshot.d.ts +1 -1
  259. package/dist/temporal-supersession.d.ts +1 -1
  260. package/dist/temporal-validity.d.ts +1 -1
  261. package/dist/threading.d.ts +1 -1
  262. package/dist/tier-migration.d.ts +1 -1
  263. package/dist/tier-routing.d.ts +1 -1
  264. package/dist/topics.d.ts +1 -1
  265. package/dist/transcript.d.ts +1 -1
  266. package/dist/transfer/types.d.ts +12 -12
  267. package/dist/trust-score-stage.d.ts +1 -1
  268. package/dist/trust-score.d.ts +1 -1
  269. package/dist/{types-DmTaqob2.d.ts → types-ChQb9D6s.d.ts} +8 -0
  270. package/dist/types.d.ts +1 -1
  271. package/dist/types.js +1 -1
  272. package/dist/utility-runtime.d.ts +1 -1
  273. package/dist/verified-recall.js +9 -8
  274. package/package.json +26 -2
  275. package/src/access-http.ts +68 -1
  276. package/src/admin/admin-surfaces.test.ts +289 -0
  277. package/src/admin/admin-surfaces.ts +138 -7
  278. package/src/bitemporal-dedup-backfill.test.ts +294 -0
  279. package/src/chat/chat-engine.test.ts +66 -1
  280. package/src/chat/chat-http.test.ts +119 -1
  281. package/src/chat/chat-http.ts +252 -97
  282. package/src/chat/chat-session.ts +143 -7
  283. package/src/compat/codex-cli-fallback.ts +23 -0
  284. package/src/compat/codex-subpath-aliases.test.ts +93 -0
  285. package/src/compat/codex-thread-key.ts +23 -0
  286. package/src/config.test.ts +50 -0
  287. package/src/config.ts +21 -4
  288. package/src/content-hash.ts +24 -0
  289. package/src/correction/correction-access-wiring.ts +104 -19
  290. package/src/correction/correction-contract.ts +68 -10
  291. package/src/correction/correction-executor.test.ts +112 -2
  292. package/src/correction/correction-executor.ts +65 -5
  293. package/src/correction/correction-planner.test.ts +47 -0
  294. package/src/correction/correction-planner.ts +40 -1
  295. package/src/correction/correction-storage-integration.test.ts +183 -0
  296. package/src/extraction-faithfulness.test.ts +240 -0
  297. package/src/extraction-faithfulness.ts +58 -6
  298. package/src/extraction-redaction-rules.test.ts +213 -0
  299. package/src/extraction-redaction-rules.ts +208 -0
  300. package/src/faithfulness-config.ts +16 -1
  301. package/src/local-model-endpoint.ts +24 -3
  302. package/src/orchestrator.ts +252 -9
  303. package/src/provenance-extraction.test.ts +266 -0
  304. package/src/provenance.ts +66 -16
  305. package/src/storage.ts +6 -10
  306. package/src/types.ts +8 -0
  307. package/dist/chunk-4R3CGCI5.js.map +0 -1
  308. package/dist/chunk-4SF232E6.js.map +0 -1
  309. package/dist/chunk-6OIDDKAA.js.map +0 -1
  310. package/dist/chunk-7AG54AF3.js.map +0 -1
  311. package/dist/chunk-C4X4QGIO.js.map +0 -1
  312. package/dist/chunk-C6BWLYW5.js.map +0 -1
  313. package/dist/chunk-KLFYKC3K.js.map +0 -1
  314. package/dist/chunk-MDBZTH7N.js.map +0 -1
  315. package/dist/chunk-XGTBNVIU.js.map +0 -1
  316. package/dist/chunk-Z3FGC3QE.js.map +0 -1
  317. /package/dist/{chunk-HCVWF657.js.map → chunk-2KAYTPPT.js.map} +0 -0
  318. /package/dist/{chunk-ZK2L72VN.js.map → chunk-37436GML.js.map} +0 -0
  319. /package/dist/{chunk-SC2YAWRF.js.map → chunk-3TSTGI6F.js.map} +0 -0
  320. /package/dist/{chunk-FACTVMXQ.js.map → chunk-4H4F3BFL.js.map} +0 -0
  321. /package/dist/{chunk-WAFDHKES.js.map → chunk-4NOI577P.js.map} +0 -0
  322. /package/dist/{chunk-YQMPUYHE.js.map → chunk-4NR2PDZ5.js.map} +0 -0
  323. /package/dist/{chunk-TAPSNIMA.js.map → chunk-5CEJH5ZN.js.map} +0 -0
  324. /package/dist/{chunk-PKMQK2WY.js.map → chunk-773DPI6T.js.map} +0 -0
  325. /package/dist/{chunk-FXCIOLRB.js.map → chunk-ASKXIAHD.js.map} +0 -0
  326. /package/dist/{chunk-5OPYYPJN.js.map → chunk-CBWI22JN.js.map} +0 -0
  327. /package/dist/{chunk-ZOHMPFOB.js.map → chunk-CKFLLAIS.js.map} +0 -0
  328. /package/dist/{chunk-GZQXOQQ2.js.map → chunk-CSRXQ6NB.js.map} +0 -0
  329. /package/dist/{chunk-QR67FU5G.js.map → chunk-CTOQEZSN.js.map} +0 -0
  330. /package/dist/{chunk-ZUDM75KG.js.map → chunk-DCWIQFNA.js.map} +0 -0
  331. /package/dist/{chunk-6CWGS2LY.js.map → chunk-DKJCRGWM.js.map} +0 -0
  332. /package/dist/{chunk-JJJCVCCQ.js.map → chunk-DMQWV6SQ.js.map} +0 -0
  333. /package/dist/{chunk-F3XZN24Z.js.map → chunk-DRUDC2UX.js.map} +0 -0
  334. /package/dist/{chunk-4WZ3J7M6.js.map → chunk-EY6MDMHK.js.map} +0 -0
  335. /package/dist/{chunk-YNDLCWXS.js.map → chunk-EZ25VE3G.js.map} +0 -0
  336. /package/dist/{chunk-6SQQOJ4U.js.map → chunk-GKRLPHPU.js.map} +0 -0
  337. /package/dist/{chunk-D7EBTQ24.js.map → chunk-HOHVIVDR.js.map} +0 -0
  338. /package/dist/{chunk-RKNJBZ55.js.map → chunk-JBPKEARU.js.map} +0 -0
  339. /package/dist/{chunk-UBHVAGO7.js.map → chunk-JYLFZWUX.js.map} +0 -0
  340. /package/dist/{chunk-LD53WPMU.js.map → chunk-LQ6JI4VH.js.map} +0 -0
  341. /package/dist/{chunk-WM7DNKBD.js.map → chunk-M4V2JVSZ.js.map} +0 -0
  342. /package/dist/{chunk-I2HL5M6T.js.map → chunk-MJIHJ7OG.js.map} +0 -0
  343. /package/dist/{chunk-J334N6QV.js.map → chunk-MLKWJOAJ.js.map} +0 -0
  344. /package/dist/{chunk-DF3I6JGP.js.map → chunk-NSA5ZRUL.js.map} +0 -0
  345. /package/dist/{chunk-VI5SGGXB.js.map → chunk-OEEW4KJA.js.map} +0 -0
  346. /package/dist/{chunk-NT36GU6X.js.map → chunk-R7IYU5DZ.js.map} +0 -0
  347. /package/dist/{chunk-TT5U3UDO.js.map → chunk-SNKPUJHR.js.map} +0 -0
  348. /package/dist/{chunk-T55EGZSJ.js.map → chunk-UVPL565C.js.map} +0 -0
  349. /package/dist/{chunk-YQGHW5ML.js.map → chunk-WRFKZEO6.js.map} +0 -0
  350. /package/dist/{chunk-H7HUQIY7.js.map → chunk-Y4YD5TYW.js.map} +0 -0
  351. /package/dist/{chunk-YUYPWJD3.js.map → chunk-YOLZS7NY.js.map} +0 -0
  352. /package/dist/{forget-OEE5OELK.js.map → forget-WYLR4ZFV.js.map} +0 -0
@@ -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
+ });
@@ -220,6 +220,27 @@ export function parseFaithfulnessResponse(
220
220
  return null;
221
221
  }
222
222
 
223
+ /**
224
+ * True when `map` contains an entry for every integer index in
225
+ * [0, expectedCount). The local parse-fallback gate uses this to accept a
226
+ * local model-lab response only when it is COMPLETE: size alone is
227
+ * insufficient because a malformed response with duplicate/fractional indexes
228
+ * can yield expectedCount distinct map keys without covering index 0..N-1
229
+ * (codex P2 PRRT_kwDORJXyws6O7PfY). parseEntries now rejects non-integer
230
+ * indexes so the two checks agree, but the explicit coverage loop documents
231
+ * the invariant and stays robust to any future parser relaxation.
232
+ */
233
+ function coversAllIndexes(
234
+ map: Map<number, ParsedFaithfulnessEntry>,
235
+ expectedCount: number,
236
+ ): boolean {
237
+ if (map.size < expectedCount) return false;
238
+ for (let i = 0; i < expectedCount; i++) {
239
+ if (!map.has(i)) return false;
240
+ }
241
+ return true;
242
+ }
243
+
223
244
  function parseEntries(
224
245
  data: unknown,
225
246
  expectedCount: number,
@@ -243,7 +264,7 @@ function parseEntries(
243
264
  if (!entry || typeof entry !== "object") continue;
244
265
  const obj = entry as Record<string, unknown>;
245
266
  const idx = typeof obj.index === "number" ? obj.index : undefined;
246
- if (idx === undefined || idx < 0 || idx >= expectedCount) continue;
267
+ if (idx === undefined || !Number.isInteger(idx) || idx < 0 || idx >= expectedCount) continue;
247
268
  const verdictRaw = typeof obj.verdict === "string" ? obj.verdict : undefined;
248
269
  if (!verdictRaw || !VALID_VERDICTS.has(verdictRaw)) continue;
249
270
  const rationale =
@@ -279,6 +300,7 @@ interface LlmCallResult {
279
300
  async function callFaithfulnessLlm(
280
301
  systemPrompt: string,
281
302
  userPrompt: string,
303
+ expectedCount: number,
282
304
  config: PluginConfig,
283
305
  localLlm: LocalLlmClient | null,
284
306
  fallbackLlm: FallbackLlmClient | null,
@@ -311,15 +333,44 @@ async function callFaithfulnessLlm(
311
333
  const result = await callOpenAiCompatibleChat(
312
334
  localEndpoint,
313
335
  messages,
314
- { temperature: 0.1, maxTokens: 2048, responseFormatJson: true, timeoutMs: probeBudgetMs },
336
+ {
337
+ temperature: 0.1,
338
+ maxTokens: 2048,
339
+ responseFormatJson: true,
340
+ timeoutMs: probeBudgetMs,
341
+ // Forward the batch signal so the probe aborts the instant the batch
342
+ // budget elapses, not after probeBudgetMs (issue #1700 nit #5).
343
+ ...(signal ? { signal } : {}),
344
+ },
315
345
  fetchImpl,
316
346
  );
317
347
  if (result?.content) {
318
- return { content: result.content, modelUsed: result.modelUsed };
348
+ // Issue #1700 nit #6: pre-validate the local endpoint response shape.
349
+ // Default (extractionFaithfulnessLocalParseFallback=false) returns the
350
+ // content as-is -- a 200-with-garbage local response surfaces
351
+ // malformed_output, alerting the operator to a misconfigured endpoint.
352
+ // When the operator opts into resilient fallback, an unparseable OR
353
+ // PARTIAL local response falls through to the configured chain instead
354
+ // of surfacing. parseFaithfulnessResponse is truthy as soon as ONE entry
355
+ // is valid, so accept the local response only when the parsed verdict
356
+ // map covers every expected index -- otherwise the missing indexes would
357
+ // surface as malformed_output downstream, defeating the resilient
358
+ // fallback (codex P2 PRRT_kwDORJXyws6O6zwZ).
359
+ if (!config.extractionFaithfulnessLocalParseFallback) {
360
+ return { content: result.content, modelUsed: result.modelUsed };
361
+ }
362
+ const parsedLocal = parseFaithfulnessResponse(result.content, expectedCount);
363
+ if (parsedLocal && coversAllIndexes(parsedLocal, expectedCount)) {
364
+ return { content: result.content, modelUsed: result.modelUsed };
365
+ }
366
+ log.debug(
367
+ "extraction-faithfulness: local endpoint returned incomplete/unparseable output; falling back to configured chain",
368
+ );
369
+ } else {
370
+ log.debug(
371
+ "extraction-faithfulness: local model-lab endpoint unavailable, trying configured chain",
372
+ );
319
373
  }
320
- log.debug(
321
- "extraction-faithfulness: local model-lab endpoint unavailable, trying configured chain",
322
- );
323
374
  }
324
375
 
325
376
  // extractionFaithfulnessModel is the LOCAL served model's name (e.g.
@@ -532,6 +583,7 @@ export async function checkFaithfulnessBatch(
532
583
  const callPromise = callFaithfulnessLlm(
533
584
  FAITHFULNESS_SYSTEM_PROMPT,
534
585
  userPrompt,
586
+ checkableInputs.length,
535
587
  config,
536
588
  localLlm,
537
589
  fallbackLlm,
@@ -0,0 +1,213 @@
1
+ /**
2
+ * extraction-redaction-rules.test.ts — issue #1669 (#1580 follow-up).
3
+ *
4
+ * Proves the extraction-layer redaction-rule helper reads persisted
5
+ * `redaction_rule` patterns and withholds matching content. The orchestrator
6
+ * consults these rules before the storage write chokepoint so a never_store
7
+ * correction actually blocks future extraction of matching content.
8
+ */
9
+ import { strict as assert } from "node:assert";
10
+ import { mkdir, mkdtemp, rm, writeFile } from "node:fs/promises";
11
+ import path from "node:path";
12
+ import { tmpdir } from "node:os";
13
+ import { test } from "node:test";
14
+ import {
15
+ REDACTION_RULES_SUBDIR,
16
+ compileRedactionPattern,
17
+ contentMatchesRedactionRules,
18
+ loadRedactionRules,
19
+ } from "./extraction-redaction-rules.js";
20
+ import { validateRedactionPattern } from "./correction/correction-contract.js";
21
+
22
+ async function withTempDir<T>(fn: (dir: string) => Promise<T>): Promise<T> {
23
+ const dir = await mkdtemp(path.join(tmpdir(), "remnic-redact-"));
24
+ try {
25
+ return await fn(dir);
26
+ } finally {
27
+ await rm(dir, { recursive: true, force: true });
28
+ }
29
+ }
30
+
31
+ async function writeRule(stateDir: string, pattern: string): Promise<void> {
32
+ const dir = path.join(stateDir, REDACTION_RULES_SUBDIR);
33
+ await mkdir(dir, { recursive: true });
34
+ const slug = pattern.replace(/[^a-zA-Z0-9]+/g, "-").slice(0, 64) || "rule";
35
+ await writeFile(
36
+ path.join(dir, `${slug}.json`),
37
+ `${JSON.stringify({ pattern, namespace: "default", createdAt: "2026-07-06T00:00:00Z" })}\n`,
38
+ "utf-8",
39
+ );
40
+ }
41
+
42
+ test("compileRedactionPattern: literal pattern matches by substring", () => {
43
+ const rule = compileRedactionPattern("secret-token-123");
44
+ assert.ok(rule.matcher("the secret-token-123 leaked here"));
45
+ assert.ok(!rule.matcher("nothing here"));
46
+ });
47
+
48
+ test("compileRedactionPattern: regex-like pattern compiles to a RegExp", () => {
49
+ // Only /.../-wrapped patterns compile as RegExp (review thread P1).
50
+ const rule = compileRedactionPattern("/secret-token-\\d+/");
51
+ assert.ok(rule.matcher("found secret-token-999 in logs"));
52
+ assert.ok(!rule.matcher("found secret-token-abc in logs"));
53
+ });
54
+
55
+ test("compileRedactionPattern (P1): unwrapped pattern with metacharacters is literal", () => {
56
+ // abc+def must match the LITERAL string, not the regex abccccdef.
57
+ const rule = compileRedactionPattern("abc+def");
58
+ assert.ok(rule.matcher("the value is abc+def here"),
59
+ "unwrapped metachar pattern must match the literal substring");
60
+ assert.ok(!rule.matcher("abccccdef"),
61
+ "unwrapped metachar pattern must NOT be compiled as a regex");
62
+ // An API-token-shaped literal like foo(bar) also works.
63
+ const rule2 = compileRedactionPattern("foo(bar)");
64
+ assert.ok(rule2.matcher("contains foo(bar) text"));
65
+ assert.ok(!rule2.matcher("contains foobar text"));
66
+ });
67
+
68
+ test("compileRedactionPattern: /wrapped/ regex strips delimiters", () => {
69
+ const rule = compileRedactionPattern("/internal-only-[a-z]+/");
70
+ assert.ok(rule.matcher("this is internal-only-foo data"));
71
+ assert.ok(!rule.matcher("this is public data"));
72
+ });
73
+
74
+ test("compileRedactionPattern: malformed /wrapped/ regex falls back to literal", () => {
75
+ // /.../-wrapped with an unbalanced bracket — RegExp() throws, falls back
76
+ // to literal substring on the BODY (without delimiters).
77
+ const rule = compileRedactionPattern("/[unclosed/");
78
+ assert.ok(rule.matcher("contains [unclosed literally"));
79
+ assert.ok(!rule.matcher("no match"));
80
+ });
81
+
82
+ test("loadRedactionRules: empty when the dir does not exist (cold install)", async () => {
83
+ await withTempDir(async (dir) => {
84
+ const rules = await loadRedactionRules(dir);
85
+ assert.equal(rules.length, 0);
86
+ });
87
+ });
88
+
89
+ test("loadRedactionRules: reads persisted literal + regex patterns", async () => {
90
+ await withTempDir(async (dir) => {
91
+ await writeRule(dir, "secret-token-123");
92
+ await writeRule(dir, "/api-key-[a-f0-9]{8}/");
93
+ const rules = await loadRedactionRules(dir);
94
+ assert.equal(rules.length, 2);
95
+ assert.ok(contentMatchesRedactionRules("leaked secret-token-123 here", rules));
96
+ assert.ok(contentMatchesRedactionRules("api-key-deadbeef was used", rules));
97
+ assert.ok(!contentMatchesRedactionRules("clean content", rules));
98
+ });
99
+ });
100
+
101
+ test("loadRedactionRules: skips a corrupt rule file without failing the pass", async () => {
102
+ await withTempDir(async (dir) => {
103
+ await writeRule(dir, "good-pattern");
104
+ const dir2 = path.join(dir, REDACTION_RULES_SUBDIR);
105
+ // A non-JSON file masquerading as a rule.
106
+ await writeFile(path.join(dir2, "broken.json"), "{not valid json", "utf-8");
107
+ // A rule file missing the `pattern` field.
108
+ await writeFile(path.join(dir2, "nopattern.json"), JSON.stringify({ foo: "bar" }), "utf-8");
109
+ const rules = await loadRedactionRules(dir);
110
+ assert.equal(rules.length, 1, "only the well-formed rule compiles");
111
+ assert.ok(contentMatchesRedactionRules("good-pattern here", rules));
112
+ });
113
+ });
114
+
115
+ test("contentMatchesRedactionRules: empty rule set never matches", () => {
116
+ assert.equal(contentMatchesRedactionRules("anything", []), false);
117
+ });
118
+
119
+ test("regression (#1669): a persisted redaction rule blocks matching content", async () => {
120
+ await withTempDir(async (dir) => {
121
+ // Simulate a `redaction_rule` correction that persisted a never-store pattern.
122
+ await writeRule(dir, "do-not-store-this-exact-phrase");
123
+ const rules = await loadRedactionRules(dir);
124
+ // Content matching the persisted pattern is withheld.
125
+ assert.ok(
126
+ contentMatchesRedactionRules("a fact containing do-not-store-this-exact-phrase inside", rules),
127
+ "matching content must be withheld",
128
+ );
129
+ // Non-matching content passes through.
130
+ assert.ok(
131
+ !contentMatchesRedactionRules("a perfectly fine durable fact", rules),
132
+ "non-matching content must pass",
133
+ );
134
+ });
135
+ });
136
+
137
+ test("#1669 thread #3: catastrophic-backtracking regex falls back to literal (ReDoS guard)", () => {
138
+ // A pattern like (a+)+ is a classic ReDoS shape. It must NOT compile as a
139
+ // RegExp — it would hang persistExtraction on a near-miss fact. Instead it
140
+ // falls back to literal substring match.
141
+ const rule = compileRedactionPattern("(a+)+");
142
+ // The literal fallback matches when the exact string "(a+)+" appears:
143
+ assert.equal(rule.matcher("a fact with (a+)+ inside"), true,
144
+ "literal fallback must still match the pattern as a substring");
145
+ // A long near-miss string that would cause exponential backtracking on the
146
+ // regex must return quickly (literal match, no regex evaluation):
147
+ const nearMiss = "a".repeat(100) + "!";
148
+ assert.equal(rule.matcher(nearMiss), false,
149
+ "near-miss must not hang — literal fallback, not regex");
150
+ });
151
+
152
+ test("#1669 thread #3: safe regex pattern still compiles as RegExp", () => {
153
+ // A benign anchored pattern compiles normally.
154
+ const rule = compileRedactionPattern("/^secret-key-[a-f0-9]+$/");
155
+ assert.equal(rule.matcher("secret-key-deadbeef"), true);
156
+ assert.equal(rule.matcher("not-a-secret"), false);
157
+ });
158
+
159
+ test("#1669 cursor thread: overly broad /wrapped/ regex falls back to literal (not compiled)", () => {
160
+ // A hand-edited rule file with /.*/ must NOT compile — it would match every
161
+ // fact and withhold all extraction. isSafeRegex now rejects overly-broad
162
+ // bodies, so the rule falls back to literal substring on the body.
163
+ const rule = compileRedactionPattern("/.*/");
164
+ assert.ok(!rule.matcher("any fact content"),
165
+ "overly broad regex must not match arbitrary content");
166
+ assert.ok(rule.matcher("contains .* literally"),
167
+ "literal fallback on the body must still match the substring .*");
168
+ });
169
+
170
+ test("#1669 thread #3 (first-line guard): validateRedactionPattern rejects catastrophic-backtracking shapes", () => {
171
+ // Classic ReDoS shapes must be rejected at apply time — not just fall back
172
+ // to literal at extraction time. Each is a nested quantifier or overlapping
173
+ // alternation that would hang on a near-miss fact.
174
+ const pathological = ["/(a+)+/", "/(a*)*/", "/(a?)+/", "/(a|a)+/"];
175
+ for (const p of pathological) {
176
+ assert.throws(
177
+ () => validateRedactionPattern(p),
178
+ { message: /unsafe/ },
179
+ `validateRedactionPattern must reject catastrophic pattern: ${p}`,
180
+ );
181
+ }
182
+ // The overly-broad check still fires for /.../-wrapped .* / .+ patterns.
183
+ assert.throws(() => validateRedactionPattern("/.*/"));
184
+ assert.throws(() => validateRedactionPattern("/.+/"));
185
+ // A near-miss string that would cause exponential backtracking on the
186
+ // regex never reaches extraction — the plan is rejected at apply time.
187
+ const nearMiss = "a".repeat(100) + "!";
188
+ assert.ok(nearMiss.length === 101);
189
+ });
190
+
191
+ test("#1669 thread #3 (first-line guard): valid patterns still pass", () => {
192
+ // Bounded literal + benign anchored regex must NOT be rejected.
193
+ assert.equal(validateRedactionPattern("secret-token-\\d+"), "secret-token-\\d+");
194
+ assert.equal(validateRedactionPattern("(a|b)+"), "(a|b)+");
195
+ assert.equal(validateRedactionPattern("/^secret-key-[a-f0-9]+$/"), "/^secret-key-[a-f0-9]+$/");
196
+ });
197
+
198
+ test("#1669 P2: validateRedactionPattern rejects zero-width regex", () => {
199
+ // Empty body or regex that matches the empty string would withhold every fact.
200
+ assert.throws(() => validateRedactionPattern("//"));
201
+ assert.throws(() => validateRedactionPattern("/a?/"));
202
+ assert.throws(() => validateRedactionPattern("/a*/"));
203
+ assert.throws(() => validateRedactionPattern("/(a|)/"));
204
+ // A bounded non-zero-width regex still passes.
205
+ assert.equal(validateRedactionPattern("/^secret-[a-f]+$/"), "/^secret-[a-f]+$/");
206
+ });
207
+
208
+ test("#1669 P2: compileRedactionPattern empty body never matches", () => {
209
+ // A hand-edited // rule file must not withhold all extraction.
210
+ const rule = compileRedactionPattern("//");
211
+ assert.equal(rule.matcher("anything"), false);
212
+ assert.equal(rule.matcher(""), false);
213
+ });