@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
@@ -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
+ );
@@ -2092,3 +2092,53 @@ test("parseConfig rejects invalid correction.enabled instead of silently enablin
2092
2092
  assert.equal(parseConfig({ correctionEnabled: "0" }).correctionEnabled, false);
2093
2093
  assert.equal(parseConfig({}).correctionEnabled, true);
2094
2094
  });
2095
+
2096
+ test("parseConfig rejects invalid correction.planTtlHours instead of silently defaulting (#1678)", () => {
2097
+ // Nested form: correction.planTtlHours.
2098
+ for (const value of [0, -1, 0.5, "0", "-5", "abc", true, null]) {
2099
+ assert.throws(
2100
+ () => parseConfig({ correction: { planTtlHours: value } } as Record<string, unknown>),
2101
+ /Invalid correction\.planTtlHours/,
2102
+ `nested planTtlHours=${JSON.stringify(value)} should throw`,
2103
+ );
2104
+ }
2105
+ // Flat form: correctionPlanTtlHours.
2106
+ for (const value of [0, -3, "abc", true]) {
2107
+ assert.throws(
2108
+ () => parseConfig({ correctionPlanTtlHours: value } as Record<string, unknown>),
2109
+ /Invalid correction\.planTtlHours/,
2110
+ `flat correctionPlanTtlHours=${JSON.stringify(value)} should throw`,
2111
+ );
2112
+ }
2113
+ // Valid values resolve (no regression on the happy path).
2114
+ assert.equal(parseConfig({ correction: { planTtlHours: 48 } }).correctionPlanTtlHours, 48);
2115
+ assert.equal(parseConfig({ correctionPlanTtlHours: 12 }).correctionPlanTtlHours, 12);
2116
+ assert.equal(parseConfig({}).correctionPlanTtlHours, 24, "absent → documented default");
2117
+ });
2118
+
2119
+ test("parseConfig correction.maxAffected rejects non-integers (Number.isInteger, #1678)", () => {
2120
+ // The error message says "integer" — 3.7 must be rejected, not silently floored to 3.
2121
+ for (const value of [3.7, 2.5, "1.5", 0.99]) {
2122
+ assert.throws(
2123
+ () => parseConfig({ correction: { maxAffected: value } } as Record<string, unknown>),
2124
+ /Invalid correction\.maxAffected/,
2125
+ `maxAffected=${JSON.stringify(value)} should throw (non-integer)`,
2126
+ );
2127
+ }
2128
+ for (const value of [0, -1, "abc"]) {
2129
+ assert.throws(
2130
+ () => parseConfig({ correction: { maxAffected: value } } as Record<string, unknown>),
2131
+ /Invalid correction\.maxAffected/,
2132
+ `maxAffected=${JSON.stringify(value)} should throw`,
2133
+ );
2134
+ }
2135
+ // Flat form.
2136
+ assert.throws(
2137
+ () => parseConfig({ correctionMaxAffected: 4.2 } as Record<string, unknown>),
2138
+ /Invalid correction\.maxAffected/,
2139
+ );
2140
+ // Valid integers resolve.
2141
+ assert.equal(parseConfig({ correction: { maxAffected: 5 } }).correctionMaxAffected, 5);
2142
+ assert.equal(parseConfig({ correctionMaxAffected: 20 }).correctionMaxAffected, 20);
2143
+ assert.equal(parseConfig({}).correctionMaxAffected, 10, "absent → documented default");
2144
+ });
package/src/config.ts CHANGED
@@ -2130,17 +2130,34 @@ export function parseConfig(
2130
2130
  const raw = rawNested !== undefined ? rawNested : cfg.correctionMaxAffected;
2131
2131
  if (raw === undefined || raw === null) return 10;
2132
2132
  const n = coerceNumber(raw);
2133
- if (n === undefined || !Number.isFinite(n) || n < 1) {
2133
+ // #1678 (thread Oiup6): the error message says "integer" enforce it
2134
+ // with Number.isInteger so 3.7 is rejected rather than silently floored.
2135
+ if (n === undefined || !Number.isFinite(n) || n < 1 || !Number.isInteger(n)) {
2134
2136
  throw new Error(`Invalid correction.maxAffected: expected an integer >= 1, got ${JSON.stringify(raw)}`);
2135
2137
  }
2136
- return Math.floor(n);
2138
+ return n;
2137
2139
  })(),
2138
2140
  correctionPlanTtlHours: (() => {
2139
2141
  const nested = (cfg.correction as Record<string, unknown> | undefined)?.planTtlHours;
2140
2142
  const fromNested = nested !== undefined ? coerceNumber(nested) : undefined;
2141
- if (fromNested !== undefined && fromNested > 0) return fromNested;
2143
+ if (fromNested !== undefined && fromNested >= 1) return fromNested;
2144
+ // #1678 (threads Ohtvi/Ohyvf): reject invalid (0/negative/non-numeric)
2145
+ // instead of silently defaulting to 24h — completes the config-reject
2146
+ // class already enforced for enabled/applyRequiresConfirm/maxAffected.
2147
+ if (nested !== undefined && fromNested === undefined) {
2148
+ throw new Error(`Invalid correction.planTtlHours: expected a number >= 1, got ${JSON.stringify(nested)}`);
2149
+ }
2150
+ if (fromNested !== undefined && fromNested < 1) {
2151
+ throw new Error(`Invalid correction.planTtlHours: expected a number >= 1, got ${JSON.stringify(nested)}`);
2152
+ }
2142
2153
  const fromFlat = coerceNumber(cfg.correctionPlanTtlHours);
2143
- if (fromFlat !== undefined && fromFlat > 0) return fromFlat;
2154
+ if (fromFlat !== undefined && fromFlat >= 1) return fromFlat;
2155
+ if (cfg.correctionPlanTtlHours !== undefined && fromFlat === undefined) {
2156
+ throw new Error(`Invalid correction.planTtlHours: expected a number >= 1, got ${JSON.stringify(cfg.correctionPlanTtlHours)}`);
2157
+ }
2158
+ if (fromFlat !== undefined && fromFlat < 1) {
2159
+ throw new Error(`Invalid correction.planTtlHours: expected a number >= 1, got ${JSON.stringify(cfg.correctionPlanTtlHours)}`);
2160
+ }
2144
2161
  return 24;
2145
2162
  })(),
2146
2163
  // Passive correction capture (issue #1581). Parsed from
@@ -0,0 +1,24 @@
1
+ /**
2
+ * content-hash.ts — standalone content normalization + SHA-256 hashing.
3
+ *
4
+ * Extracted from `ContentHashIndex` (storage.ts) so correction modules and
5
+ * other consumers can hash content WITHOUT importing the storage god file
6
+ * (issue #1533 directStorageImports ratchet). The static methods on
7
+ * `ContentHashIndex` delegate here to guarantee identical results.
8
+ */
9
+
10
+ import { createHash } from "node:crypto";
11
+
12
+ /** Normalize content: lowercase, strip non-alphanumerics, collapse whitespace. */
13
+ export function normalizeContent(content: string): string {
14
+ return content
15
+ .toLowerCase()
16
+ .replace(/[^a-z0-9\s]/g, " ")
17
+ .replace(/\s+/g, " ")
18
+ .trim();
19
+ }
20
+
21
+ /** Normalize content and compute SHA-256 hash. */
22
+ export function computeContentHash(content: string): string {
23
+ return createHash("sha256").update(normalizeContent(content)).digest("hex");
24
+ }
@@ -21,10 +21,14 @@
21
21
  */
22
22
 
23
23
  import path from "node:path";
24
+ import { createHash } from "node:crypto";
24
25
  import { mkdir, writeFile } from "node:fs/promises";
25
26
  import type { Orchestrator } from "../orchestrator.js";
26
27
  import type { MemoryFile, MemoryStatus, PluginConfig } from "../types.js";
27
28
  import { stripAttributesSuffix } from "../structured-attributes.js";
29
+ import { supersessionKeysForFact } from "../temporal-supersession.js";
30
+ import { computeContentHash } from "../content-hash.js";
31
+ import { sanitizeMemoryContent } from "../sanitize.js";
28
32
  import {
29
33
  CorrectionContractError,
30
34
  validateCorrectionAction,
@@ -168,7 +172,7 @@ async function searchMemories(
168
172
  const storage = await wiring.orchestrator.getStorage(ns);
169
173
  const all = await storage.readAllMemories();
170
174
  for (const m of all) {
171
- if (m.frontmatter.status && m.frontmatter.status !== "active") continue;
175
+ if (!isEligibleCorrectionCandidate(m)) continue;
172
176
  const hay = `${m.content} ${m.frontmatter.tags?.join(" ") ?? ""}`.toLowerCase();
173
177
  let hits = 0;
174
178
  for (const tok of tokens) {
@@ -265,7 +269,7 @@ async function expandEntityNeighbors(
265
269
  for (const m of all) {
266
270
  if (out.length >= limit) break;
267
271
  if (seen.has(m.frontmatter.id)) continue;
268
- if (m.frontmatter.status && m.frontmatter.status !== "active") continue;
272
+ if (!isEligibleCorrectionCandidate(m)) continue;
269
273
  if (m.frontmatter.entityRef && seedRefs.has(m.frontmatter.entityRef)) {
270
274
  out.push(toCandidate(m, ns, 0.5));
271
275
  seen.add(m.frontmatter.id);
@@ -427,9 +431,22 @@ async function applyEditMemory(
427
431
  // The StorageManager's writeMemoryFrontmatter snapshots the prior version
428
432
  // internally when page-versioning is configured (issue #371), so every edit
429
433
  // is revertable without the correction layer forking versioning logic.
434
+ //
435
+ // #1672 item 3: recompute the contentHash from the patched body so the
436
+ // dedup index + tombstone exact tier match the NEW content. writeMemory
437
+ // computes this at write time, but writeMemoryFrontmatter preserves the
438
+ // prior frontmatter hash — without this the hash no longer matches the
439
+ // patched body and a later re-extraction of the same fact dedup-misses.
440
+ const contentHashForPatch =
441
+ existing.frontmatter.category === "fact"
442
+ ? computeContentHash(sanitizeMemoryContent(patch).text)
443
+ : undefined;
430
444
  await storage.writeMemoryFrontmatter(
431
445
  { ...existing, content: patch },
432
- { updated: new Date().toISOString() },
446
+ {
447
+ updated: new Date().toISOString(),
448
+ ...(contentHashForPatch ? { contentHash: contentHashForPatch } : {}),
449
+ },
433
450
  );
434
451
  return memoryId;
435
452
  }
@@ -535,6 +552,8 @@ async function appendTombstoneFn(
535
552
  rawContent: string;
536
553
  entityRef?: string;
537
554
  supersessionKey?: string;
555
+ supersessionKeys?: string[];
556
+ contentHash?: string;
538
557
  },
539
558
  ): Promise<string | null> {
540
559
  const storage = await wiring.orchestrator.getStorage(namespace);
@@ -550,20 +569,46 @@ async function appendTombstoneFn(
550
569
  typeof storage.isTombstonesEnabled === "function"
551
570
  ? storage.isTombstonesEnabled()
552
571
  : true;
553
- const result = await storage.appendTombstone({
554
- reason: input.reason,
555
- createdBy: "user_correction",
556
- sourceMemoryId: input.sourceMemoryId,
557
- rawContent: input.rawContent,
558
- ...(input.entityRef ? { entityRef: input.entityRef } : {}),
559
- ...(input.supersessionKey ? { supersessionKey: input.supersessionKey } : {}),
560
- });
561
- if (result === null && enabled) {
562
- throw new CorrectionContractError(
563
- `tombstone persistence failed for memory ${input.sourceMemoryId} (tombstones enabled but store returned null — I/O error swallowed)`,
564
- );
572
+ // #1672 item 4: emit one tombstone per derived supersession key (mirrors
573
+ // temporal-supersession / forget / pattern-reinforcement) so a paraphrased
574
+ // re-observation placing the attribute at a different key position is still
575
+ // blocked at the keyed tier. When no keys are derived, emit a single
576
+ // content-only tombstone. Each carries the canonical contentHash so the
577
+ // exact tier also matches re-extraction.
578
+ const keys =
579
+ input.supersessionKeys && input.supersessionKeys.length > 0
580
+ ? input.supersessionKeys
581
+ : input.supersessionKey
582
+ ? [input.supersessionKey]
583
+ : [undefined];
584
+ const writtenIds: string[] = [];
585
+ let firstId: string | null = null;
586
+ for (const key of keys) {
587
+ const result = await storage.appendTombstone({
588
+ reason: input.reason,
589
+ createdBy: "user_correction",
590
+ sourceMemoryId: input.sourceMemoryId,
591
+ rawContent: input.rawContent,
592
+ ...(input.entityRef ? { entityRef: input.entityRef } : {}),
593
+ ...(key ? { supersessionKey: key } : {}),
594
+ ...(input.contentHash ? { contentHash: input.contentHash } : {}),
595
+ });
596
+ if (result === null && enabled) {
597
+ // Rollback already-written tombstones so a partial multi-key failure
598
+ // does not leave incomplete resurrection blocking for the still-active
599
+ // memory (review thread #1). Best-effort — a dangling tombstone is the
600
+ // safer failure mode (false-positive block vs. false-negative leak).
601
+ for (const id of writtenIds) {
602
+ try { await storage.revokeTombstone(id, "user_correction"); } catch { /* best-effort */ }
603
+ }
604
+ throw new CorrectionContractError(
605
+ `tombstone persistence failed for memory ${input.sourceMemoryId} (tombstones enabled but store returned null — I/O error swallowed)`,
606
+ );
607
+ }
608
+ if (firstId === null) firstId = result;
609
+ if (result) writtenIds.push(result);
565
610
  }
566
- return result;
611
+ return firstId;
567
612
  }
568
613
 
569
614
  async function registerRedactionRuleFn(
@@ -576,9 +621,14 @@ async function registerRedactionRuleFn(
576
621
  const storage = await wiring.orchestrator.getStorage(namespace);
577
622
  const dir = path.join(storage.dir, "state", "corrections", "redaction-rules");
578
623
  await mkdir(dir, { recursive: true });
579
- // Idempotent: filename is a slug of the pattern so re-registering the same
580
- // pattern overwrites rather than duplicates.
581
- const slug = pattern.replace(/[^a-zA-Z0-9]+/g, "-").slice(0, 64) || "rule";
624
+ // Idempotent: filename is a slug + short hash of the full pattern so
625
+ // re-registering the same pattern overwrites rather than duplicates, while
626
+ // distinct patterns that slug identically (e.g. "abc+def" vs "abc.def"
627
+ // "abc-def") no longer collide and silently remove each other's enforcement
628
+ // (review thread P1).
629
+ const slugBase = pattern.replace(/[^a-zA-Z0-9]+/g, "-").slice(0, 48) || "rule";
630
+ const patternHash = createHash("sha256").update(pattern).digest("hex").slice(0, 16);
631
+ const slug = `${slugBase}-${patternHash}`;
582
632
  await writeFile(
583
633
  path.join(dir, `${slug}.json`),
584
634
  `${JSON.stringify({ pattern, namespace, createdAt: new Date().toISOString() })}\n`,
@@ -686,6 +736,22 @@ function toCandidate(m: MemoryFile, namespace: string, score: number): PlannerCa
686
736
  } satisfies PlannerCandidate & { namespace?: string };
687
737
  }
688
738
 
739
+ /**
740
+ * Lifecycle-aware eligibility for a correction candidate (#1672 item 2). A
741
+ * memory is eligible only when it is `status: "active"` AND not archived.
742
+ * Planning a correction against a memory that was archived via the
743
+ * `archivedAt`/archive path but still marked active would surface stale
744
+ * content the user already set aside — and apply would reject the rescope
745
+ * (non-active source) or supersede a fact the user intended gone. Both the
746
+ * status flip and the archive timestamp are checked so the candidate set
747
+ * stays consistent with the rescope/retire apply-time guards.
748
+ */
749
+ function isEligibleCorrectionCandidate(m: MemoryFile): boolean {
750
+ if (m.frontmatter.status && m.frontmatter.status !== "active") return false;
751
+ if (typeof m.frontmatter.archivedAt === "string" && m.frontmatter.archivedAt.length > 0) return false;
752
+ return true;
753
+ }
754
+
689
755
  function toExecutorMemory(m: MemoryFile): ExecutorMemory {
690
756
  const fm = m.frontmatter;
691
757
  // The tombstone hash must use the ORIGINAL unsuffixed body: writeMemory
@@ -693,12 +759,27 @@ function toExecutorMemory(m: MemoryFile): ExecutorMemory {
693
759
  // hashing m.content would never match the pre-suffix content hash (thread
694
760
  // OhX2N, rule 23). Strip the suffix when attributes are present.
695
761
  const rawBody = fm.structuredAttributes ? stripAttributesSuffix(m.content) : m.content;
762
+ // #1672 item 4: derive the full supersession-key set from the memory's
763
+ // structuredAttributes so the tombstone's keyed tier blocks paraphrased
764
+ // re-observations that place the attribute at a different key position
765
+ // (mirrors temporal-supersession's per-key emission). Also forward the
766
+ // canonical contentHash so the exact tier matches re-extraction without
767
+ // recomputing from a citation-annotated body.
768
+ const supersessionKeys = supersessionKeysForFact({
769
+ entityRef: fm.entityRef,
770
+ structuredAttributes: fm.structuredAttributes,
771
+ });
696
772
  return {
697
773
  memoryId: fm.id,
698
774
  content: m.content,
699
775
  category: fm.category,
700
776
  rawContent: rawBody,
701
777
  ...(fm.entityRef ? { entityRef: fm.entityRef } : {}),
778
+ ...(supersessionKeys.length > 0 ? { supersessionKeys } : {}),
779
+ ...(supersessionKeys.length > 0 ? { supersessionKey: supersessionKeys[0] } : {}),
780
+ ...(typeof fm.contentHash === "string" && fm.contentHash.length > 0
781
+ ? { contentHash: fm.contentHash }
782
+ : {}),
702
783
  } satisfies ExecutorMemory;
703
784
  }
704
785
 
@@ -884,4 +965,8 @@ export {
884
965
  buildClassifyPrompt,
885
966
  CLASSIFY_SYSTEM_PROMPT,
886
967
  parseClassifyResponse,
968
+ // #1672: exported for focused storage-integration regression tests.
969
+ isEligibleCorrectionCandidate,
970
+ applyEditMemory,
971
+ appendTombstoneFn,
887
972
  };
@@ -263,25 +263,50 @@ export function validateRedactionPattern(pattern: string): string {
263
263
  `redaction_rule.pattern exceeds the ${REDACTION_PATTERN_MAX}-character bound.`,
264
264
  );
265
265
  }
266
- // Reject patterns that could match an unbounded string (catastrophic /
267
- // overly-broad). A literal or a bounded regex is fine; a bare `.*` / `.`
268
- // regex is not. We do NOT execute the regex (ReDoS); we only inspect shape.
269
- if (isRegexLike(trimmed) && isOverlyBroadRegex(trimmed)) {
266
+ // Reject patterns that could match an unbounded string (overly-broad) OR
267
+ // exhibit catastrophic backtracking (nested quantifiers like `(a+)+`). We do
268
+ // NOT execute the regex (that would be ReDoS itself); we only inspect shape.
269
+ if (isRegexLike(trimmed) && isUnsafeRedactionRegex(trimmed)) {
270
270
  throw new CorrectionContractError(
271
- "redaction_rule.pattern is too broad — use a bounded literal or a more specific pattern.",
271
+ "redaction_rule.pattern is unsafe — use a bounded literal or a regex without nested quantifiers / overlapping alternation.",
272
272
  );
273
273
  }
274
+ // #1669 P2: reject zero-width regex (empty body or matches empty string)
275
+ // that would withhold every fact.
276
+ if (isRegexLike(trimmed)) {
277
+ const regexBody = trimmed.slice(1, -1);
278
+ if (regexBody.length === 0) {
279
+ throw new CorrectionContractError("redaction_rule.pattern body is empty — use a non-empty regex.");
280
+ }
281
+ try {
282
+ if (new RegExp(regexBody).test("")) {
283
+ throw new CorrectionContractError(
284
+ "redaction_rule.pattern matches the empty string — it would withhold every fact.",
285
+ );
286
+ }
287
+ } catch (e) {
288
+ if (e instanceof CorrectionContractError) throw e;
289
+ // Compilation failed — the extraction-time defense handles it.
290
+ }
291
+ }
274
292
  return trimmed;
275
293
  }
276
294
 
277
- /** Heuristic: treat `/.../` or presence of regex metacharacters as regex. */
295
+ /** Only `/.../`-wrapped patterns are treated as regex; everything else is a
296
+ * literal (review thread P1 — consistent with extraction-redaction-rules.ts). */
278
297
  function isRegexLike(pattern: string): boolean {
279
- if (pattern.startsWith("/") && pattern.endsWith("/") && pattern.length >= 2) return true;
280
- return /[\\^$.|?*+()[\]{}]/.test(pattern);
298
+ return pattern.startsWith("/") && pattern.endsWith("/") && pattern.length >= 2;
281
299
  }
282
300
 
283
- /** Reject a regex that would match an arbitrary-length run of any character. */
284
- function isOverlyBroadRegex(pattern: string): boolean {
301
+ /**
302
+ * Reject a regex that would either match an arbitrary-length run of any
303
+ * character (overly-broad) OR exhibit catastrophic backtracking (ReDoS).
304
+ * Mirrors the safe-regex heuristic in extraction-redaction-rules.ts so the
305
+ * apply-time chokepoint and the extraction-time defense agree on what is
306
+ * pathological — intentionally not imported to keep the two modules decoupled
307
+ * (contract owns validation; extraction owns consultation).
308
+ */
309
+ function isUnsafeRedactionRegex(pattern: string): boolean {
285
310
  const body = pattern.startsWith("/") && pattern.endsWith("/")
286
311
  ? pattern.slice(1, -1)
287
312
  : pattern;
@@ -289,6 +314,39 @@ function isOverlyBroadRegex(pattern: string): boolean {
289
314
  if (/(?:^|[^\\])\(\.\*\)|(?:^|[^\\])\.\*|(?:^|[^\\])\.\+|^\.([^*+]?)$/.test(body)) {
290
315
  return true;
291
316
  }
317
+ // Nested quantifier: a group (...) whose body ends with a quantifier and
318
+ // which is itself quantified → exponential blowup on near-miss inputs
319
+ // (classic ReDoS shapes: (a+)+, (a*)*, (a?)+). Also catches overlapping
320
+ // alternation under repetition: (a|a)+ where branches share a prefix.
321
+ if (body.length > 512) return true;
322
+ for (let i = 0; i < body.length; i++) {
323
+ if (body[i] !== "(") continue;
324
+ let depth = 1;
325
+ let j = i + 1;
326
+ while (j < body.length && depth > 0) {
327
+ if (body[j] === "\\") { j += 2; continue; }
328
+ if (body[j] === "(") depth++;
329
+ else if (body[j] === ")") depth--;
330
+ j++;
331
+ }
332
+ if (depth !== 0) continue;
333
+ const afterGroup = body[j];
334
+ if (afterGroup !== "+" && afterGroup !== "*" && afterGroup !== "{") continue;
335
+ const groupBody = body.slice(i + 1, j - 1);
336
+ if (/[+*?]$/.test(groupBody) || /\{\d+,?\d*\}[+*?]?$/.test(groupBody)) {
337
+ return true;
338
+ }
339
+ if (groupBody.includes("|")) {
340
+ const branches = groupBody.split("|");
341
+ if (branches.length >= 2) {
342
+ const firstChars = new Set(branches.map((b) => b[0]).filter(Boolean));
343
+ if (firstChars.size < branches.filter((b) => b.length > 0).length) {
344
+ return true;
345
+ }
346
+ }
347
+ }
348
+ i = j;
349
+ }
292
350
  return false;
293
351
  }
294
352