@remnic/core 10.0.4 → 10.0.6

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 (308) hide show
  1. package/dist/access-admin-ops-surface.js +49 -49
  2. package/dist/access-authorization-probe.js +50 -50
  3. package/dist/access-boundary.js +49 -49
  4. package/dist/access-cli.d.ts.map +1 -1
  5. package/dist/access-cli.js +120 -115
  6. package/dist/access-cli.js.map +1 -1
  7. package/dist/access-extraction-force-flush.js +49 -49
  8. package/dist/access-http-briefing.js +50 -50
  9. package/dist/access-http-query.js +51 -51
  10. package/dist/access-http.d.ts.map +1 -1
  11. package/dist/access-http.js +72 -71
  12. package/dist/access-identity-continuity-surface.js +49 -49
  13. package/dist/access-lcm-surface.js +49 -49
  14. package/dist/access-mcp-correction-tools.d.ts +10 -0
  15. package/dist/access-mcp-correction-tools.d.ts.map +1 -0
  16. package/dist/access-mcp-correction-tools.js +8 -0
  17. package/dist/access-mcp.d.ts.map +1 -1
  18. package/dist/access-mcp.js +63 -62
  19. package/dist/access-namespace-preflight.js +49 -49
  20. package/dist/access-observe-write-category.js +3 -3
  21. package/dist/access-observe-write-surface.js +49 -49
  22. package/dist/access-offline-manifest.js +5 -5
  23. package/dist/access-operations-batch.js +52 -52
  24. package/dist/access-operations.d.ts +1 -0
  25. package/dist/access-operations.d.ts.map +1 -1
  26. package/dist/access-operations.js +59 -59
  27. package/dist/access-recall-concurrency.js +49 -49
  28. package/dist/access-recall-diagnostics.js +2 -2
  29. package/dist/access-recall-response.js +49 -49
  30. package/dist/access-recall-surface.js +49 -49
  31. package/dist/access-service.d.ts.map +1 -1
  32. package/dist/access-service.js +49 -49
  33. package/dist/access-surface-catalog.js +51 -51
  34. package/dist/active-recall.js +22 -22
  35. package/dist/adapters/index.js +4 -4
  36. package/dist/adapters/registry.js +2 -2
  37. package/dist/auto-sync-FN7E3OTO.js +2 -2
  38. package/dist/briefing.js +18 -18
  39. package/dist/buffer.js +1 -1
  40. package/dist/calibration.d.ts +31 -3
  41. package/dist/calibration.d.ts.map +1 -1
  42. package/dist/calibration.js +67 -16
  43. package/dist/calibration.js.map +1 -1
  44. package/dist/causal-consolidation.js +18 -18
  45. package/dist/{chunk-5YDWSWBU.js → chunk-2CP577K6.js} +2 -2
  46. package/dist/{chunk-QVBB2CLG.js → chunk-2UF42WXV.js} +27 -26
  47. package/dist/chunk-2UF42WXV.js.map +1 -0
  48. package/dist/{chunk-ABET5LA5.js → chunk-37HCPM56.js} +23 -23
  49. package/dist/{chunk-HSZBALHV.js → chunk-4JNTGPBR.js} +2 -2
  50. package/dist/{chunk-H6FA36LC.js → chunk-4K2H4KTR.js} +5 -5
  51. package/dist/{chunk-H7BZBSP6.js → chunk-52II7PB6.js} +4 -4
  52. package/dist/{chunk-RH6MQ5NG.js → chunk-5BPXMAUP.js} +17 -68
  53. package/dist/chunk-5BPXMAUP.js.map +1 -0
  54. package/dist/{chunk-U3LVARGV.js → chunk-5X4ZJESN.js} +2 -2
  55. package/dist/{chunk-SNGNGH53.js → chunk-63UNVGZW.js} +4 -4
  56. package/dist/{chunk-2KB7TTAP.js → chunk-64S6X4IU.js} +4 -4
  57. package/dist/{chunk-MEMH5NKJ.js → chunk-6C76FZRM.js} +2 -2
  58. package/dist/{chunk-3QAIX7TV.js → chunk-6KHF6PAB.js} +2 -2
  59. package/dist/{chunk-MYBCB4AT.js → chunk-6T42L5FI.js} +2 -2
  60. package/dist/{chunk-XTWF5XR6.js → chunk-7CZIKCJR.js} +69 -69
  61. package/dist/{chunk-R6EOFKZG.js → chunk-7LYRFI5A.js} +2 -2
  62. package/dist/{chunk-4NFRYOHT.js → chunk-ANZXWOYD.js} +2 -2
  63. package/dist/{chunk-5XXTZ4Y7.js → chunk-B657ZGDH.js} +2 -2
  64. package/dist/{chunk-5QHTQMRL.js → chunk-CCMXCZLT.js} +8 -6
  65. package/dist/chunk-CCMXCZLT.js.map +1 -0
  66. package/dist/{chunk-NSQ3GUVO.js → chunk-CYEK6Z3P.js} +4 -4
  67. package/dist/{chunk-4DXVWBDB.js → chunk-D6M3VKMY.js} +4 -10
  68. package/dist/chunk-D6M3VKMY.js.map +1 -0
  69. package/dist/{chunk-IRYKRBEX.js → chunk-DJMVSUPK.js} +3 -3
  70. package/dist/{chunk-7OOW72QA.js → chunk-DTXN4NVD.js} +307 -138
  71. package/dist/chunk-DTXN4NVD.js.map +1 -0
  72. package/dist/{chunk-UCCB7RKF.js → chunk-EMVJU7WC.js} +2 -2
  73. package/dist/{chunk-IY7ZISXV.js → chunk-ENNC3RZ6.js} +2 -2
  74. package/dist/{chunk-QLK3SL7Q.js → chunk-ETSJD5IV.js} +131 -12
  75. package/dist/chunk-ETSJD5IV.js.map +1 -0
  76. package/dist/{chunk-PTFL3INS.js → chunk-FC6RCOIU.js} +2 -2
  77. package/dist/{chunk-CQQTEDHS.js → chunk-FLV7M7CT.js} +2 -2
  78. package/dist/{chunk-3SL52JKD.js → chunk-FZVROZKC.js} +5 -5
  79. package/dist/{chunk-Z2EVKNZR.js → chunk-GEGUBU3D.js} +2 -2
  80. package/dist/{chunk-LIGP5M2C.js → chunk-GF4H3B3R.js} +8 -8
  81. package/dist/{chunk-FMIVV5SV.js → chunk-I56MTWE7.js} +2 -2
  82. package/dist/{chunk-EUL3S3LE.js → chunk-J5WI4QRP.js} +4 -4
  83. package/dist/{chunk-436IRJUD.js → chunk-JA54KIYD.js} +2 -2
  84. package/dist/{chunk-QWNBQ2T5.js → chunk-JMF5Q4JY.js} +10 -10
  85. package/dist/{chunk-IDSSDL4W.js → chunk-JXSYWTFK.js} +21 -4
  86. package/dist/{chunk-IDSSDL4W.js.map → chunk-JXSYWTFK.js.map} +1 -1
  87. package/dist/{chunk-ABDTBGI2.js → chunk-KCCZFDJD.js} +2 -2
  88. package/dist/chunk-KIUVEMBI.js +49 -0
  89. package/dist/chunk-KIUVEMBI.js.map +1 -0
  90. package/dist/{chunk-IINXHPCV.js → chunk-KQI2PKYP.js} +2 -2
  91. package/dist/{chunk-ZOGZMAGI.js → chunk-KQT4DRKF.js} +2 -2
  92. package/dist/{chunk-RGPPNNDJ.js → chunk-KSUOM3YU.js} +9 -9
  93. package/dist/{chunk-CXG2T7BJ.js → chunk-KZEMFD6E.js} +3 -3
  94. package/dist/{chunk-O57VUN4U.js → chunk-L2VS27PZ.js} +2 -2
  95. package/dist/{chunk-IVGJXS5F.js → chunk-L34W4W4I.js} +2 -2
  96. package/dist/{chunk-MEP3POVW.js → chunk-L3PWXD73.js} +2 -2
  97. package/dist/{chunk-NQHSXHJP.js → chunk-L6GNS5GB.js} +2 -2
  98. package/dist/{chunk-QDKABMUE.js → chunk-MH2DXDN6.js} +2 -2
  99. package/dist/{chunk-DQJCQHII.js → chunk-MQNOIH2C.js} +6 -6
  100. package/dist/{chunk-IDSZNHIP.js → chunk-MULNBSPP.js} +4 -4
  101. package/dist/{chunk-BDHWKFGM.js → chunk-OFEHITAZ.js} +2 -2
  102. package/dist/{chunk-RYN4AIRM.js → chunk-OZABSIOV.js} +4 -4
  103. package/dist/{chunk-IXF7TRMQ.js → chunk-Q562GF7J.js} +2 -2
  104. package/dist/{chunk-H2XCCUOT.js → chunk-QEPL7KIZ.js} +15 -15
  105. package/dist/{chunk-WLC6XA6C.js → chunk-QGLEM46C.js} +16 -53
  106. package/dist/chunk-QGLEM46C.js.map +1 -0
  107. package/dist/{chunk-Y7CXM7UM.js → chunk-QMFKW4OU.js} +3 -3
  108. package/dist/{chunk-Z63GU5NN.js → chunk-QWWMS4AE.js} +1 -1
  109. package/dist/chunk-QWWMS4AE.js.map +1 -0
  110. package/dist/{chunk-R4O2G6ML.js → chunk-RSMIRLKM.js} +2 -2
  111. package/dist/{chunk-6AH2DDHI.js → chunk-S5TGRZWY.js} +2 -2
  112. package/dist/{chunk-TYOBAFCY.js → chunk-SNXBXYUE.js} +3 -3
  113. package/dist/{chunk-IHIP7TJ3.js → chunk-SWRN6U7Q.js} +2 -2
  114. package/dist/{chunk-DBA2W6VV.js → chunk-SZXPKUMN.js} +6 -6
  115. package/dist/{chunk-WQUGCOKF.js → chunk-TFA6HPOA.js} +4 -4
  116. package/dist/{chunk-DYRXRQHV.js → chunk-TL4PM7PC.js} +2 -2
  117. package/dist/{chunk-TSEUVMJL.js → chunk-TRBLJU6Y.js} +39 -39
  118. package/dist/{chunk-VWCFR4NR.js → chunk-U6OXJQEH.js} +5 -5
  119. package/dist/{chunk-YMXCJ7SE.js → chunk-VUEG3TZ4.js} +2 -2
  120. package/dist/{chunk-ORAL4HJC.js → chunk-WG4TMZJ2.js} +2 -2
  121. package/dist/{chunk-CV5AMCPX.js → chunk-XPRAOTJY.js} +2 -2
  122. package/dist/{chunk-RZCRXCLI.js → chunk-XQSJXNWC.js} +2 -2
  123. package/dist/{chunk-GLS3A4SQ.js → chunk-YGPXV546.js} +2 -2
  124. package/dist/{chunk-P22M3DX3.js → chunk-YJAOYEWI.js} +2 -2
  125. package/dist/{chunk-OAFRMLL4.js → chunk-YJEUHGZZ.js} +2 -2
  126. package/dist/cli.js +92 -91
  127. package/dist/coding/export-okf-codegraph.js +16 -16
  128. package/dist/compounding/engine.d.ts.map +1 -1
  129. package/dist/compounding/engine.js +17 -17
  130. package/dist/config.d.ts.map +1 -1
  131. package/dist/config.js +22 -22
  132. package/dist/connectors/codex-materialize-runner.js +17 -17
  133. package/dist/connectors/index.js +17 -17
  134. package/dist/consolidation-provenance-check.js +2 -2
  135. package/dist/contradiction/index.js +7 -7
  136. package/dist/convergence-refresh.js +19 -19
  137. package/dist/conversation-index/backend.js +2 -2
  138. package/dist/corpus-watermark.js +19 -19
  139. package/dist/correction/correction-access-wiring.d.ts +6 -6
  140. package/dist/correction/correction-access-wiring.d.ts.map +1 -1
  141. package/dist/correction/correction-classify.d.ts +25 -0
  142. package/dist/correction/correction-classify.d.ts.map +1 -0
  143. package/dist/correction/correction-config.d.ts +11 -0
  144. package/dist/correction/correction-config.d.ts.map +1 -0
  145. package/dist/correction/correction-contract.d.ts +13 -1
  146. package/dist/correction/correction-contract.d.ts.map +1 -1
  147. package/dist/correction/correction-executor.d.ts +2 -0
  148. package/dist/correction/correction-executor.d.ts.map +1 -1
  149. package/dist/correction/correction-planner.d.ts +24 -3
  150. package/dist/correction/correction-planner.d.ts.map +1 -1
  151. package/dist/entity-retrieval.js +17 -17
  152. package/dist/event-order-recall.js +2 -2
  153. package/dist/explicit-capture.js +2 -2
  154. package/dist/external-wiki-access.js +50 -50
  155. package/dist/extraction-span-materialize.js +2 -2
  156. package/dist/extraction.js +25 -25
  157. package/dist/harmonic-retrieval.js +17 -17
  158. package/dist/index.js +192 -191
  159. package/dist/lcm/index.js +3 -3
  160. package/dist/local-llm.d.ts.map +1 -1
  161. package/dist/local-llm.js +1 -1
  162. package/dist/location/index.js +19 -19
  163. package/dist/maintenance/memory-governance.js +17 -17
  164. package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +17 -17
  165. package/dist/maintenance/rebuild-memory-projection-runner.js +19 -19
  166. package/dist/maintenance/rebuild-memory-projection.js +18 -18
  167. package/dist/memory-browse-operations.js +50 -50
  168. package/dist/memory-browse.js +1 -1
  169. package/dist/memory-subject.js +1 -1
  170. package/dist/namespaces/migrate.js +18 -18
  171. package/dist/namespaces/search.js +6 -6
  172. package/dist/namespaces/storage.js +17 -17
  173. package/dist/operator-doctor-corpus.js +20 -20
  174. package/dist/operator-toolkit.js +32 -32
  175. package/dist/orchestration/maintenance.js +28 -28
  176. package/dist/orchestration/session-context.d.ts.map +1 -1
  177. package/dist/orchestrator.js +116 -115
  178. package/dist/recall-explain-renderer.js +3 -3
  179. package/dist/recall-navigate-access.js +2 -2
  180. package/dist/recall-navigate-disclosure.js +2 -2
  181. package/dist/recall-navigate.js +2 -2
  182. package/dist/recall-navigation-operations.js +50 -50
  183. package/dist/recall-navigation.js +49 -49
  184. package/dist/recall-why-service.js +2 -2
  185. package/dist/recall-xray-cli.js +4 -4
  186. package/dist/recall-xray-renderer.js +3 -3
  187. package/dist/recall-xray.js +2 -2
  188. package/dist/reconcile/manifest.js +3 -3
  189. package/dist/{resolution-RSG4HZTL.js → resolution-TV3F7ZTH.js} +7 -7
  190. package/dist/resolution-TV3F7ZTH.js.map +1 -0
  191. package/dist/resume-bundles.js +23 -23
  192. package/dist/salvage-envelope.js +1 -1
  193. package/dist/search/factory.js +5 -5
  194. package/dist/search/index.js +9 -9
  195. package/dist/semantic-consolidation.js +23 -23
  196. package/dist/semantic-rule-promotion.js +17 -17
  197. package/dist/semantic-rule-verifier.js +17 -17
  198. package/dist/source-agent-qualifier.js +17 -17
  199. package/dist/standup/build.js +17 -17
  200. package/dist/storage.js +16 -16
  201. package/dist/subject-config.js +1 -1
  202. package/dist/summarizer.js +3 -3
  203. package/dist/support-passport/index.js +66 -66
  204. package/dist/transfer/export-okf.js +17 -17
  205. package/dist/types.d.ts +6 -23
  206. package/dist/types.d.ts.map +1 -1
  207. package/dist/types.js +1 -1
  208. package/dist/verified-recall.js +17 -17
  209. package/dist/who-knows-cli.js +18 -18
  210. package/dist/who-knows.js +17 -17
  211. package/dist/write-envelope.js +1 -1
  212. package/package.json +2 -2
  213. package/src/access-cli.ts +4 -0
  214. package/src/access-http.ts +1 -0
  215. package/src/access-mcp-correction-tools.ts +56 -0
  216. package/src/access-mcp.ts +4 -49
  217. package/src/access-operations.ts +3 -0
  218. package/src/access-service.ts +12 -21
  219. package/src/calibration-local-llm.test.ts +361 -0
  220. package/src/calibration.ts +109 -26
  221. package/src/compounding/engine.ts +2 -8
  222. package/src/config.test.ts +26 -0
  223. package/src/config.ts +4 -60
  224. package/src/correction/correction-access-wiring.ts +56 -16
  225. package/src/correction/correction-classify.test.ts +185 -0
  226. package/src/correction/correction-classify.ts +157 -0
  227. package/src/correction/correction-config.ts +93 -0
  228. package/src/correction/correction-contract.ts +61 -14
  229. package/src/correction/correction-executor.test.ts +102 -2
  230. package/src/correction/correction-executor.ts +19 -1
  231. package/src/correction/correction-planner.test.ts +141 -0
  232. package/src/correction/correction-planner.ts +117 -11
  233. package/src/correction/correction-surfaces.test.ts +22 -1
  234. package/src/local-llm-qos.test.ts +36 -0
  235. package/src/local-llm.ts +20 -3
  236. package/src/orchestration/session-context.ts +0 -15
  237. package/src/recall-handles-wiring.test.ts +24 -0
  238. package/src/types.ts +6 -23
  239. package/dist/chunk-4DXVWBDB.js.map +0 -1
  240. package/dist/chunk-5QHTQMRL.js.map +0 -1
  241. package/dist/chunk-7OOW72QA.js.map +0 -1
  242. package/dist/chunk-QLK3SL7Q.js.map +0 -1
  243. package/dist/chunk-QVBB2CLG.js.map +0 -1
  244. package/dist/chunk-RH6MQ5NG.js.map +0 -1
  245. package/dist/chunk-WLC6XA6C.js.map +0 -1
  246. package/dist/chunk-Z63GU5NN.js.map +0 -1
  247. /package/dist/{resolution-RSG4HZTL.js.map → access-mcp-correction-tools.js.map} +0 -0
  248. /package/dist/{chunk-5YDWSWBU.js.map → chunk-2CP577K6.js.map} +0 -0
  249. /package/dist/{chunk-ABET5LA5.js.map → chunk-37HCPM56.js.map} +0 -0
  250. /package/dist/{chunk-HSZBALHV.js.map → chunk-4JNTGPBR.js.map} +0 -0
  251. /package/dist/{chunk-H6FA36LC.js.map → chunk-4K2H4KTR.js.map} +0 -0
  252. /package/dist/{chunk-H7BZBSP6.js.map → chunk-52II7PB6.js.map} +0 -0
  253. /package/dist/{chunk-U3LVARGV.js.map → chunk-5X4ZJESN.js.map} +0 -0
  254. /package/dist/{chunk-SNGNGH53.js.map → chunk-63UNVGZW.js.map} +0 -0
  255. /package/dist/{chunk-2KB7TTAP.js.map → chunk-64S6X4IU.js.map} +0 -0
  256. /package/dist/{chunk-MEMH5NKJ.js.map → chunk-6C76FZRM.js.map} +0 -0
  257. /package/dist/{chunk-3QAIX7TV.js.map → chunk-6KHF6PAB.js.map} +0 -0
  258. /package/dist/{chunk-MYBCB4AT.js.map → chunk-6T42L5FI.js.map} +0 -0
  259. /package/dist/{chunk-XTWF5XR6.js.map → chunk-7CZIKCJR.js.map} +0 -0
  260. /package/dist/{chunk-R6EOFKZG.js.map → chunk-7LYRFI5A.js.map} +0 -0
  261. /package/dist/{chunk-4NFRYOHT.js.map → chunk-ANZXWOYD.js.map} +0 -0
  262. /package/dist/{chunk-5XXTZ4Y7.js.map → chunk-B657ZGDH.js.map} +0 -0
  263. /package/dist/{chunk-NSQ3GUVO.js.map → chunk-CYEK6Z3P.js.map} +0 -0
  264. /package/dist/{chunk-IRYKRBEX.js.map → chunk-DJMVSUPK.js.map} +0 -0
  265. /package/dist/{chunk-UCCB7RKF.js.map → chunk-EMVJU7WC.js.map} +0 -0
  266. /package/dist/{chunk-IY7ZISXV.js.map → chunk-ENNC3RZ6.js.map} +0 -0
  267. /package/dist/{chunk-PTFL3INS.js.map → chunk-FC6RCOIU.js.map} +0 -0
  268. /package/dist/{chunk-CQQTEDHS.js.map → chunk-FLV7M7CT.js.map} +0 -0
  269. /package/dist/{chunk-3SL52JKD.js.map → chunk-FZVROZKC.js.map} +0 -0
  270. /package/dist/{chunk-Z2EVKNZR.js.map → chunk-GEGUBU3D.js.map} +0 -0
  271. /package/dist/{chunk-LIGP5M2C.js.map → chunk-GF4H3B3R.js.map} +0 -0
  272. /package/dist/{chunk-FMIVV5SV.js.map → chunk-I56MTWE7.js.map} +0 -0
  273. /package/dist/{chunk-EUL3S3LE.js.map → chunk-J5WI4QRP.js.map} +0 -0
  274. /package/dist/{chunk-436IRJUD.js.map → chunk-JA54KIYD.js.map} +0 -0
  275. /package/dist/{chunk-QWNBQ2T5.js.map → chunk-JMF5Q4JY.js.map} +0 -0
  276. /package/dist/{chunk-ABDTBGI2.js.map → chunk-KCCZFDJD.js.map} +0 -0
  277. /package/dist/{chunk-IINXHPCV.js.map → chunk-KQI2PKYP.js.map} +0 -0
  278. /package/dist/{chunk-ZOGZMAGI.js.map → chunk-KQT4DRKF.js.map} +0 -0
  279. /package/dist/{chunk-RGPPNNDJ.js.map → chunk-KSUOM3YU.js.map} +0 -0
  280. /package/dist/{chunk-CXG2T7BJ.js.map → chunk-KZEMFD6E.js.map} +0 -0
  281. /package/dist/{chunk-O57VUN4U.js.map → chunk-L2VS27PZ.js.map} +0 -0
  282. /package/dist/{chunk-IVGJXS5F.js.map → chunk-L34W4W4I.js.map} +0 -0
  283. /package/dist/{chunk-MEP3POVW.js.map → chunk-L3PWXD73.js.map} +0 -0
  284. /package/dist/{chunk-NQHSXHJP.js.map → chunk-L6GNS5GB.js.map} +0 -0
  285. /package/dist/{chunk-QDKABMUE.js.map → chunk-MH2DXDN6.js.map} +0 -0
  286. /package/dist/{chunk-DQJCQHII.js.map → chunk-MQNOIH2C.js.map} +0 -0
  287. /package/dist/{chunk-IDSZNHIP.js.map → chunk-MULNBSPP.js.map} +0 -0
  288. /package/dist/{chunk-BDHWKFGM.js.map → chunk-OFEHITAZ.js.map} +0 -0
  289. /package/dist/{chunk-RYN4AIRM.js.map → chunk-OZABSIOV.js.map} +0 -0
  290. /package/dist/{chunk-IXF7TRMQ.js.map → chunk-Q562GF7J.js.map} +0 -0
  291. /package/dist/{chunk-H2XCCUOT.js.map → chunk-QEPL7KIZ.js.map} +0 -0
  292. /package/dist/{chunk-Y7CXM7UM.js.map → chunk-QMFKW4OU.js.map} +0 -0
  293. /package/dist/{chunk-R4O2G6ML.js.map → chunk-RSMIRLKM.js.map} +0 -0
  294. /package/dist/{chunk-6AH2DDHI.js.map → chunk-S5TGRZWY.js.map} +0 -0
  295. /package/dist/{chunk-TYOBAFCY.js.map → chunk-SNXBXYUE.js.map} +0 -0
  296. /package/dist/{chunk-IHIP7TJ3.js.map → chunk-SWRN6U7Q.js.map} +0 -0
  297. /package/dist/{chunk-DBA2W6VV.js.map → chunk-SZXPKUMN.js.map} +0 -0
  298. /package/dist/{chunk-WQUGCOKF.js.map → chunk-TFA6HPOA.js.map} +0 -0
  299. /package/dist/{chunk-DYRXRQHV.js.map → chunk-TL4PM7PC.js.map} +0 -0
  300. /package/dist/{chunk-TSEUVMJL.js.map → chunk-TRBLJU6Y.js.map} +0 -0
  301. /package/dist/{chunk-VWCFR4NR.js.map → chunk-U6OXJQEH.js.map} +0 -0
  302. /package/dist/{chunk-YMXCJ7SE.js.map → chunk-VUEG3TZ4.js.map} +0 -0
  303. /package/dist/{chunk-ORAL4HJC.js.map → chunk-WG4TMZJ2.js.map} +0 -0
  304. /package/dist/{chunk-CV5AMCPX.js.map → chunk-XPRAOTJY.js.map} +0 -0
  305. /package/dist/{chunk-RZCRXCLI.js.map → chunk-XQSJXNWC.js.map} +0 -0
  306. /package/dist/{chunk-GLS3A4SQ.js.map → chunk-YGPXV546.js.map} +0 -0
  307. /package/dist/{chunk-P22M3DX3.js.map → chunk-YJAOYEWI.js.map} +0 -0
  308. /package/dist/{chunk-OAFRMLL4.js.map → chunk-YJEUHGZZ.js.map} +0 -0
@@ -0,0 +1,185 @@
1
+ /**
2
+ * Correction classify completion: short timeout, caller abort, gateway fallback.
3
+ */
4
+ import { strict as assert } from "node:assert";
5
+ import { test } from "node:test";
6
+
7
+ import { isAbortError } from "../abort-error.js";
8
+ import type { PluginConfig } from "../types.js";
9
+ import { type CorrectionClassifyDeps, completeCorrectionClassify } from "./correction-classify.js";
10
+
11
+ const MESSAGES = [{ role: "user" as const, content: "the old fact is a duplicate" }];
12
+
13
+ function config(over: Partial<PluginConfig> = {}): PluginConfig {
14
+ return {
15
+ localLlmEnabled: true,
16
+ localLlmFallback: true,
17
+ modelSource: "plugin",
18
+ correctionClassifyTimeoutMs: 40,
19
+ ...over,
20
+ } as PluginConfig;
21
+ }
22
+
23
+ function hangUntilAbort(): CorrectionClassifyDeps["localLlm"] {
24
+ return {
25
+ chatCompletion: (_messages, options) =>
26
+ new Promise((resolve) => {
27
+ const finish = () => resolve(null);
28
+ if (options?.signal?.aborted) {
29
+ finish();
30
+ return;
31
+ }
32
+ options?.signal?.addEventListener("abort", finish, { once: true });
33
+ }),
34
+ };
35
+ }
36
+
37
+ test("classify falls back after the local attempt times out", async () => {
38
+ let fallbackCalls = 0;
39
+ let localSignal: AbortSignal | undefined;
40
+ const deps: CorrectionClassifyDeps = {
41
+ config: config(),
42
+ localLlm: {
43
+ chatCompletion: (_messages, options) =>
44
+ new Promise((resolve) => {
45
+ localSignal = options?.signal;
46
+ const finish = () => resolve(null);
47
+ if (options?.signal?.aborted) {
48
+ finish();
49
+ return;
50
+ }
51
+ options?.signal?.addEventListener("abort", finish, { once: true });
52
+ }),
53
+ },
54
+ fallbackChat: async () => {
55
+ fallbackCalls += 1;
56
+ return { content: '{"classification":"outdated","confidence":0.8,"actions":[]}', modelUsed: "gateway" };
57
+ },
58
+ };
59
+ const content = await completeCorrectionClassify(MESSAGES, deps);
60
+ assert.equal(fallbackCalls, 1);
61
+ assert.equal(localSignal?.aborted, true);
62
+ assert.match(content ?? "", /outdated/);
63
+ });
64
+
65
+ test("classify cancels on caller abort and does not call the fallback", async () => {
66
+ let fallbackCalls = 0;
67
+ const caller = new AbortController();
68
+ const deps: CorrectionClassifyDeps = {
69
+ config: config({ correctionClassifyTimeoutMs: 30_000 }),
70
+ localLlm: hangUntilAbort(),
71
+ fallbackChat: async () => {
72
+ fallbackCalls += 1;
73
+ return { content: "should-not-run", modelUsed: "gateway" };
74
+ },
75
+ };
76
+ const pending = completeCorrectionClassify(MESSAGES, deps, caller.signal);
77
+ caller.abort();
78
+ await assert.rejects(pending, (err: unknown) => isAbortError(err));
79
+ assert.equal(fallbackCalls, 0);
80
+ });
81
+
82
+ test("classify uses the fallback when the local LLM returns null", async () => {
83
+ let localCalls = 0;
84
+ let fallbackCalls = 0;
85
+ const deps: CorrectionClassifyDeps = {
86
+ config: config(),
87
+ localLlm: {
88
+ chatCompletion: async () => {
89
+ localCalls += 1;
90
+ return null;
91
+ },
92
+ },
93
+ fallbackChat: async () => {
94
+ fallbackCalls += 1;
95
+ return { content: '{"ok":true}', modelUsed: "gateway" };
96
+ },
97
+ };
98
+ const content = await completeCorrectionClassify(MESSAGES, deps);
99
+ assert.equal(localCalls, 1);
100
+ assert.equal(fallbackCalls, 1);
101
+ assert.equal(content, '{"ok":true}');
102
+ });
103
+
104
+ test("classify does not call the fallback when the local LLM returns content", async () => {
105
+ let fallbackCalls = 0;
106
+ const deps: CorrectionClassifyDeps = {
107
+ config: config(),
108
+ localLlm: { chatCompletion: async () => ({ content: "local-json" }) },
109
+ fallbackChat: async () => {
110
+ fallbackCalls += 1;
111
+ return { content: "gateway", modelUsed: "gateway" };
112
+ },
113
+ };
114
+ assert.equal(await completeCorrectionClassify(MESSAGES, deps), "local-json");
115
+ assert.equal(fallbackCalls, 0);
116
+ });
117
+
118
+ test("gateway modelSource skips the local LLM", async () => {
119
+ let localCalls = 0;
120
+ const deps: CorrectionClassifyDeps = {
121
+ config: config({ modelSource: "gateway", localLlmEnabled: true }),
122
+ localLlm: {
123
+ chatCompletion: async () => {
124
+ localCalls += 1;
125
+ return { content: "local" };
126
+ },
127
+ },
128
+ fallbackChat: async () => ({ content: "gateway-primary", modelUsed: "gateway" }),
129
+ };
130
+ assert.equal(await completeCorrectionClassify(MESSAGES, deps), "gateway-primary");
131
+ assert.equal(localCalls, 0);
132
+ });
133
+
134
+ test("no configured gateway models return null without waiting out the classify timeout", async () => {
135
+ const started = Date.now();
136
+ const content = await completeCorrectionClassify(MESSAGES, {
137
+ config: config({ localLlmEnabled: false }),
138
+ localLlm: {
139
+ chatCompletion: async () => {
140
+ throw new Error("local must not run");
141
+ },
142
+ },
143
+ });
144
+ assert.equal(content, null);
145
+ assert.ok(Date.now() - started < 1000, `no-models path took ${Date.now() - started}ms`);
146
+ });
147
+
148
+ test("a fallback attempt timeout returns null instead of an abort", async () => {
149
+ const deps: CorrectionClassifyDeps = {
150
+ config: config({ localLlmEnabled: false, correctionClassifyTimeoutMs: 40 }),
151
+ localLlm: {
152
+ chatCompletion: async () => {
153
+ throw new Error("local must not run");
154
+ },
155
+ },
156
+ fallbackChat: (_messages, options) =>
157
+ new Promise((_resolve, reject) => {
158
+ const fail = () => {
159
+ const err = new Error("fallback attempt aborted");
160
+ err.name = "AbortError";
161
+ reject(err);
162
+ };
163
+ if (options.signal?.aborted) {
164
+ fail();
165
+ return;
166
+ }
167
+ options.signal?.addEventListener("abort", fail, { once: true });
168
+ }),
169
+ };
170
+ assert.equal(await completeCorrectionClassify(MESSAGES, deps), null);
171
+ });
172
+
173
+ test("localLlmFallback false does not call the gateway after a local miss", async () => {
174
+ let fallbackCalls = 0;
175
+ const deps: CorrectionClassifyDeps = {
176
+ config: config({ localLlmFallback: false }),
177
+ localLlm: { chatCompletion: async () => null },
178
+ fallbackChat: async () => {
179
+ fallbackCalls += 1;
180
+ return { content: "nope", modelUsed: "gateway" };
181
+ },
182
+ };
183
+ await assert.rejects(() => completeCorrectionClassify(MESSAGES, deps), /fallback disabled/);
184
+ assert.equal(fallbackCalls, 0);
185
+ });
@@ -0,0 +1,157 @@
1
+ /**
2
+ * Correction classify completion: a short per-attempt budget, caller abort,
3
+ * and the same gateway/fallback client extraction uses when the local LLM
4
+ * is down or `modelSource` is `gateway`.
5
+ *
6
+ * Each attempt (local, then gateway) gets `correctionClassifyTimeoutMs`.
7
+ * A caller abort cancels the in-flight attempt and does not fall through.
8
+ * A local timeout, null, or error falls through when `localLlmFallback` is
9
+ * not false. Gateway mode skips the local client and uses the task chain
10
+ * as the primary path.
11
+ */
12
+ import { abortError, isAbortError, throwIfAborted } from "../abort-error.js";
13
+ import { resolveLocalLlmCapabilities } from "../capabilities.js";
14
+ import { fallbackLlmRuntimeContextFromConfig } from "../fallback-llm-codex-subscription.js";
15
+ import {
16
+ FallbackLlmClient,
17
+ type FallbackLlmOptions,
18
+ type FallbackLlmResponse,
19
+ gatewayTaskChainOptions,
20
+ } from "../fallback-llm.js";
21
+ import type { LocalLlmChatCompletionOptions } from "../local-llm-helpers.js";
22
+ import type { PluginConfig } from "../types.js";
23
+ import { DEFAULT_CORRECTION_CLASSIFY_TIMEOUT_MS, MAX_CORRECTION_CLASSIFY_TIMEOUT_MS } from "./correction-config.js";
24
+
25
+ export interface CorrectionClassifyMessage {
26
+ role: "system" | "user" | "assistant";
27
+ content: string;
28
+ }
29
+
30
+ export interface CorrectionClassifyDeps {
31
+ config: PluginConfig;
32
+ localLlm: {
33
+ chatCompletion(
34
+ messages: Array<{ role: string; content: string }>,
35
+ options?: LocalLlmChatCompletionOptions
36
+ ): Promise<{ content: string } | null>;
37
+ };
38
+ /**
39
+ * Test seam. Production builds a {@link FallbackLlmClient} from config.
40
+ * Returning null is the no-models fast path.
41
+ */
42
+ fallbackChat?(
43
+ messages: CorrectionClassifyMessage[],
44
+ options: FallbackLlmOptions
45
+ ): Promise<FallbackLlmResponse | null>;
46
+ }
47
+
48
+ export async function completeCorrectionClassify(
49
+ messages: CorrectionClassifyMessage[],
50
+ deps: CorrectionClassifyDeps,
51
+ abortSignal?: AbortSignal
52
+ ): Promise<string | null> {
53
+ throwIfAborted(abortSignal, "correction classify aborted");
54
+ const timeoutMs = classifyTimeoutMs(deps.config);
55
+ const gatewayPrimary = deps.config.modelSource === "gateway";
56
+ const localEnabled = resolveLocalLlmCapabilities(deps.config).localLlm === true && !gatewayPrimary;
57
+
58
+ if (localEnabled) {
59
+ const localAttempt = startAttempt(abortSignal, timeoutMs);
60
+ try {
61
+ const local = await deps.localLlm.chatCompletion(messages, {
62
+ operation: "correction-classify",
63
+ priority: "background",
64
+ timeoutMs,
65
+ temperature: 0.2,
66
+ signal: localAttempt.signal,
67
+ });
68
+ if (abortSignal?.aborted) throw abortError("correction classify aborted");
69
+ if (local?.content) return local.content;
70
+ if (deps.config.localLlmFallback === false) {
71
+ throw new Error("correction classify: local LLM unavailable and fallback disabled");
72
+ }
73
+ } catch (err) {
74
+ if (abortSignal?.aborted) {
75
+ throw isAbortError(err) ? err : abortError("correction classify aborted");
76
+ }
77
+ // A timeout abort belongs to this attempt. Fall through unless the
78
+ // operator turned the gateway path off.
79
+ if (deps.config.localLlmFallback === false) {
80
+ if (isAbortError(err)) {
81
+ throw new Error("correction classify: local LLM timed out and fallback disabled");
82
+ }
83
+ throw err;
84
+ }
85
+ } finally {
86
+ localAttempt.dispose();
87
+ }
88
+ } else if (!gatewayPrimary && deps.config.localLlmFallback === false) {
89
+ throw new Error("correction classify: local LLM disabled and fallback disabled");
90
+ }
91
+
92
+ throwIfAborted(abortSignal, "correction classify aborted");
93
+ const fallbackAttempt = startAttempt(abortSignal, timeoutMs);
94
+ try {
95
+ const fallbackOptions: FallbackLlmOptions = {
96
+ temperature: 0.2,
97
+ timeoutMs,
98
+ signal: fallbackAttempt.signal,
99
+ ...gatewayTaskChainOptions(deps.config),
100
+ };
101
+ const fallback = deps.fallbackChat
102
+ ? await deps.fallbackChat(messages, fallbackOptions)
103
+ : await new FallbackLlmClient(
104
+ deps.config.gatewayConfig,
105
+ fallbackLlmRuntimeContextFromConfig(deps.config)
106
+ ).chatCompletion(messages, fallbackOptions);
107
+ if (abortSignal?.aborted) throw abortError("correction classify aborted");
108
+ return fallback?.content ?? null;
109
+ } catch (err) {
110
+ // The attempt signal aborts on its own budget. That is a finished miss
111
+ // (empty plan), not a caller disconnect — only the caller signal skips
112
+ // the empty-plan path.
113
+ if (abortSignal?.aborted) {
114
+ throw isAbortError(err) ? err : abortError("correction classify aborted");
115
+ }
116
+ if (isAbortError(err)) return null;
117
+ throw err;
118
+ } finally {
119
+ fallbackAttempt.dispose();
120
+ }
121
+ }
122
+
123
+ function classifyTimeoutMs(config: PluginConfig): number {
124
+ const configured = config.correctionClassifyTimeoutMs;
125
+ if (
126
+ typeof configured === "number" &&
127
+ Number.isInteger(configured) &&
128
+ configured >= 1 &&
129
+ configured <= MAX_CORRECTION_CLASSIFY_TIMEOUT_MS
130
+ ) {
131
+ return configured;
132
+ }
133
+ return DEFAULT_CORRECTION_CLASSIFY_TIMEOUT_MS;
134
+ }
135
+
136
+ function startAttempt(
137
+ caller: AbortSignal | undefined,
138
+ timeoutMs: number
139
+ ): { signal: AbortSignal; dispose: () => void } {
140
+ // A ref'd timer, not AbortSignal.timeout: that helper's timer is unref'd,
141
+ // so a classify wait would not keep a short-lived caller alive until the
142
+ // budget elapses.
143
+ const controller = new AbortController();
144
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
145
+ const onCaller = (): void => controller.abort();
146
+ if (caller?.aborted) controller.abort();
147
+ else caller?.addEventListener("abort", onCaller, { once: true });
148
+ let disposed = false;
149
+ const dispose = (): void => {
150
+ if (disposed) return;
151
+ disposed = true;
152
+ clearTimeout(timer);
153
+ caller?.removeEventListener("abort", onCaller);
154
+ };
155
+ controller.signal.addEventListener("abort", dispose, { once: true });
156
+ return { signal: controller.signal, dispose };
157
+ }
@@ -0,0 +1,93 @@
1
+ /**
2
+ * Correction-contract config (issue #1580 plus classify timeout).
3
+ *
4
+ * Extracted from config.ts so the grandfathered parser file does not grow.
5
+ * Nested `correction.<key>` wins over the flat `correction<Key>` legacy key.
6
+ * Invalid values throw — they are never clamped or silently defaulted.
7
+ *
8
+ * Reads stay inline on `cfg` so the parsed-key extractor records the same
9
+ * paths the plugin schema declares.
10
+ */
11
+ import { coerceBool, coerceNumber } from "../connectors/coerce.js";
12
+
13
+ export const DEFAULT_CORRECTION_CLASSIFY_TIMEOUT_MS = 45_000;
14
+ export const MAX_CORRECTION_CLASSIFY_TIMEOUT_MS = 300_000;
15
+
16
+ export interface ParsedCorrectionConfig {
17
+ correctionEnabled: boolean;
18
+ correctionApplyRequiresConfirm: boolean;
19
+ correctionMaxAffected: number;
20
+ correctionPlanTtlHours: number;
21
+ correctionClassifyTimeoutMs: number;
22
+ }
23
+
24
+ export function parseCorrectionConfig(cfg: Record<string, unknown>): ParsedCorrectionConfig {
25
+ return {
26
+ correctionEnabled: (() => {
27
+ const nested = (cfg.correction as Record<string, unknown> | undefined)?.enabled;
28
+ const raw = nested !== undefined ? nested : cfg.correctionEnabled;
29
+ if (raw === undefined || raw === null) return true;
30
+ const value = coerceBool(raw);
31
+ if (value === undefined) {
32
+ throw new Error(`Invalid correction.enabled: expected a boolean, got ${JSON.stringify(raw)}`);
33
+ }
34
+ return value;
35
+ })(),
36
+ correctionApplyRequiresConfirm: (() => {
37
+ const nested = (cfg.correction as Record<string, unknown> | undefined)?.applyRequiresConfirm;
38
+ const raw = nested !== undefined ? nested : cfg.correctionApplyRequiresConfirm;
39
+ if (raw === undefined || raw === null) return true;
40
+ const value = coerceBool(raw);
41
+ if (value === undefined) {
42
+ throw new Error(`Invalid correction.applyRequiresConfirm: expected a boolean, got ${JSON.stringify(raw)}`);
43
+ }
44
+ return value;
45
+ })(),
46
+ correctionMaxAffected: (() => {
47
+ const rawNested = (cfg.correction as Record<string, unknown> | undefined)?.maxAffected;
48
+ const raw = rawNested !== undefined ? rawNested : cfg.correctionMaxAffected;
49
+ if (raw === undefined || raw === null) return 10;
50
+ const n = coerceNumber(raw);
51
+ if (n === undefined || !Number.isFinite(n) || n < 1 || !Number.isInteger(n)) {
52
+ throw new Error(`Invalid correction.maxAffected: expected an integer >= 1, got ${JSON.stringify(raw)}`);
53
+ }
54
+ return n;
55
+ })(),
56
+ correctionPlanTtlHours: (() => {
57
+ const nested = (cfg.correction as Record<string, unknown> | undefined)?.planTtlHours;
58
+ const fromNested = nested !== undefined ? coerceNumber(nested) : undefined;
59
+ if (fromNested !== undefined && fromNested >= 1) return fromNested;
60
+ if (nested !== undefined && fromNested === undefined) {
61
+ throw new Error(`Invalid correction.planTtlHours: expected a number >= 1, got ${JSON.stringify(nested)}`);
62
+ }
63
+ if (fromNested !== undefined && fromNested < 1) {
64
+ throw new Error(`Invalid correction.planTtlHours: expected a number >= 1, got ${JSON.stringify(nested)}`);
65
+ }
66
+ const fromFlat = coerceNumber(cfg.correctionPlanTtlHours);
67
+ if (fromFlat !== undefined && fromFlat >= 1) return fromFlat;
68
+ if (cfg.correctionPlanTtlHours !== undefined && fromFlat === undefined) {
69
+ throw new Error(
70
+ `Invalid correction.planTtlHours: expected a number >= 1, got ${JSON.stringify(cfg.correctionPlanTtlHours)}`
71
+ );
72
+ }
73
+ if (fromFlat !== undefined && fromFlat < 1) {
74
+ throw new Error(
75
+ `Invalid correction.planTtlHours: expected a number >= 1, got ${JSON.stringify(cfg.correctionPlanTtlHours)}`
76
+ );
77
+ }
78
+ return 24;
79
+ })(),
80
+ correctionClassifyTimeoutMs: (() => {
81
+ const nested = (cfg.correction as Record<string, unknown> | undefined)?.classifyTimeoutMs;
82
+ const raw = nested !== undefined ? nested : cfg.correctionClassifyTimeoutMs;
83
+ if (raw === undefined || raw === null) return DEFAULT_CORRECTION_CLASSIFY_TIMEOUT_MS;
84
+ const n = coerceNumber(raw);
85
+ if (n === undefined || !Number.isInteger(n) || n < 1 || n > MAX_CORRECTION_CLASSIFY_TIMEOUT_MS) {
86
+ throw new Error(
87
+ `Invalid correction.classifyTimeoutMs: expected an integer from 1 to ${MAX_CORRECTION_CLASSIFY_TIMEOUT_MS}, got ${JSON.stringify(raw)}`
88
+ );
89
+ }
90
+ return n;
91
+ })(),
92
+ };
93
+ }
@@ -51,6 +51,12 @@ export interface CorrectionRequest {
51
51
  * planner only sees the ALREADY-AUTHORIZED namespace.
52
52
  */
53
53
  namespace?: string;
54
+ /**
55
+ * Winner memory id. When set, `targetIds` must be exactly the duplicate
56
+ * (the loser). The planner skips classification and drafts one recoverable
57
+ * supersede — no hard delete, no replacement write.
58
+ */
59
+ duplicateOf?: string;
54
60
  }
55
61
 
56
62
  // ---------------------------------------------------------------------------
@@ -103,8 +109,11 @@ export const MEMORY_CATEGORIES: readonly MemoryCategory[] = [
103
109
  ];
104
110
 
105
111
  export type CorrectionAction =
106
- /** Outdated: a new fact replaces the loser. */
107
- | { kind: "supersede"; loserId: string; replacement?: MemoryDraft }
112
+ /**
113
+ * Outdated: a new fact replaces the loser, or `winnerId` records an
114
+ * existing memory as the replacement without writing a new one.
115
+ */
116
+ | { kind: "supersede"; loserId: string; replacement?: MemoryDraft; winnerId?: string }
108
117
  /** Incomplete / minor-wrong: a versioned edit to an existing memory. */
109
118
  | { kind: "edit"; memoryId: string; patch: string }
110
119
  /** Wrong: retire + tombstone. */
@@ -213,6 +222,9 @@ export const CORRECTION_TEXT_MAX = 10_000;
213
222
  /** Maximum supported redaction pattern length (§34 — bounded patterns). */
214
223
  export const REDACTION_PATTERN_MAX = 256;
215
224
 
225
+ /** Maximum memory-id length for `duplicateOf` / explicit targets. */
226
+ export const CORRECTION_MEMORY_ID_MAX = 512;
227
+
216
228
  /**
217
229
  * Validate a {@link CorrectionRequest}'s invariants. Returns the cleaned
218
230
  * request or throws with a field-specific message (rule 51 — list valid
@@ -234,25 +246,47 @@ export function validateCorrectionRequest(request: CorrectionRequest): Correctio
234
246
  const targetIds = Array.isArray(request.targetIds)
235
247
  ? request.targetIds.filter((id): id is string => typeof id === "string" && id.length > 0)
236
248
  : undefined;
237
- if (targetIds !== undefined && targetIds.length === 0) {
238
- // An empty array is treated as "not provided" so callers can forward
239
- // optional fields without a separate presence flag.
240
- return {
241
- text,
242
- ...(request.sessionKey ? { sessionKey: request.sessionKey } : {}),
243
- ...(request.principal ? { principal: request.principal } : {}),
244
- ...(request.namespace ? { namespace: request.namespace } : {}),
245
- };
249
+ const normalizedTargets = targetIds !== undefined && targetIds.length === 0 ? undefined : targetIds;
250
+ const duplicateOf = readDuplicateOf(request.duplicateOf);
251
+ if (duplicateOf) {
252
+ if (!normalizedTargets || normalizedTargets.length !== 1) {
253
+ throw new CorrectionContractError(
254
+ "CorrectionRequest.duplicateOf requires exactly one targetIds entry (the duplicate memory).",
255
+ );
256
+ }
257
+ if (normalizedTargets[0] === duplicateOf) {
258
+ throw new CorrectionContractError(
259
+ "CorrectionRequest.duplicateOf must be a different memory from targetIds.",
260
+ );
261
+ }
246
262
  }
247
263
  return {
248
264
  text,
249
- ...(targetIds ? { targetIds } : {}),
265
+ ...(normalizedTargets ? { targetIds: normalizedTargets } : {}),
266
+ ...(duplicateOf ? { duplicateOf } : {}),
250
267
  ...(request.sessionKey ? { sessionKey: request.sessionKey } : {}),
251
268
  ...(request.principal ? { principal: request.principal } : {}),
252
269
  ...(request.namespace ? { namespace: request.namespace } : {}),
253
270
  };
254
271
  }
255
272
 
273
+ function readDuplicateOf(value: unknown): string | undefined {
274
+ if (value === undefined || value === null) return undefined;
275
+ if (typeof value !== "string") {
276
+ throw new CorrectionContractError("CorrectionRequest.duplicateOf must be a memory id string.");
277
+ }
278
+ const id = value.trim();
279
+ if (id.length === 0 || id !== value) {
280
+ throw new CorrectionContractError("CorrectionRequest.duplicateOf must be a non-empty memory id.");
281
+ }
282
+ if (id.length > CORRECTION_MEMORY_ID_MAX) {
283
+ throw new CorrectionContractError(
284
+ `CorrectionRequest.duplicateOf exceeds the ${CORRECTION_MEMORY_ID_MAX}-character limit (${id.length}).`,
285
+ );
286
+ }
287
+ return id;
288
+ }
289
+
256
290
  /**
257
291
  * Validate a redaction pattern (§34 — bounded, literal-or-safe-regex; reject
258
292
  * catastrophic patterns). Returns the cleaned pattern or throws.
@@ -377,8 +411,21 @@ export function validateCorrectionAction(action: unknown): asserts action is Cor
377
411
  if (typeof a.loserId !== "string" || a.loserId.length === 0) {
378
412
  throw new CorrectionContractError("supersede.loserId is required.");
379
413
  }
380
- if (a.replacement !== undefined && a.replacement !== null) {
381
- validateMemoryDraft(a.replacement);
414
+ {
415
+ const hasReplacement = a.replacement !== undefined && a.replacement !== null;
416
+ const hasWinner = a.winnerId !== undefined && a.winnerId !== null;
417
+ if (hasWinner) {
418
+ if (typeof a.winnerId !== "string" || a.winnerId.trim().length === 0 || a.winnerId !== a.winnerId.trim()) {
419
+ throw new CorrectionContractError("supersede.winnerId must be a non-empty memory id.");
420
+ }
421
+ if (a.winnerId === a.loserId) {
422
+ throw new CorrectionContractError("supersede.winnerId must differ from loserId.");
423
+ }
424
+ if (hasReplacement) {
425
+ throw new CorrectionContractError("supersede.winnerId cannot be combined with a replacement draft.");
426
+ }
427
+ }
428
+ if (hasReplacement) validateMemoryDraft(a.replacement);
382
429
  }
383
430
  break;
384
431
  case "edit":
@@ -35,7 +35,7 @@ import { CorrectionExecutor, CORRECTION_ERROR_MAX, sanitizeErrorMessage, type Ex
35
35
 
36
36
  interface FakeState {
37
37
  /** memoryId → current memory state. */
38
- memories: Map<string, ExecutorMemory & { status: string; supersededBy?: string; validUntil?: string }>;
38
+ memories: Map<string, ExecutorMemory & { status: string; supersededBy?: string; validUntil?: string; lineage?: string[] }>;
39
39
  tombstones: Array<{ reason: string; sourceMemoryId: string; rawContent: string; supersessionKey?: string; supersessionKeys?: string[]; contentHash?: string }>;
40
40
  redactionRules: string[];
41
41
  auditRecords: Array<{ planId: string; outcome: CorrectionOutcome }>;
@@ -98,7 +98,7 @@ function makeExecutorDeps(state: FakeState, opts: { biTemporalEnabled?: boolean;
98
98
  getMemory: async (_ns, memoryId) => {
99
99
  const m = state.memories.get(memoryId);
100
100
  if (!m) return null;
101
- const { status: _s, supersededBy: _sb, validUntil: _v, ...rest } = m;
101
+ const { supersededBy: _sb, validUntil: _v, ...rest } = m;
102
102
  return rest;
103
103
  },
104
104
  writeReplacement: async (namespace, draft) => {
@@ -1589,3 +1589,103 @@ test("#1678: sanitizeErrorMessage strips absolute paths and caps length", () =>
1589
1589
  assert.ok(!atPath.includes("secret.txt"), "path suffix must be consumed: " + atPath);
1590
1590
  assert.ok(atPath.includes("<path>"), "must leave <path> placeholder: " + atPath);
1591
1591
  });
1592
+
1593
+ test("winnerId supersede leaves the loser recoverable and the winner unchanged", async () => {
1594
+ await withTempDir(async (dir) => {
1595
+ const candidates = new Map<string, PlannerCandidate>([
1596
+ ["mem-loser", { memoryId: "mem-loser", path: "facts/mem-loser.md", content: "duplicate", excerpt: "duplicate", score: 1 }],
1597
+ ["mem-winner", { memoryId: "mem-winner", path: "facts/mem-winner.md", content: "canonical", excerpt: "canonical", score: 1 }],
1598
+ ]);
1599
+ const state: FakeState = {
1600
+ memories: new Map([
1601
+ ["mem-loser", fakeMemory({ memoryId: "mem-loser", content: "duplicate", rawContent: "duplicate raw" })],
1602
+ ["mem-winner", fakeMemory({ memoryId: "mem-winner", content: "canonical", rawContent: "canonical raw" })],
1603
+ ]),
1604
+ tombstones: [],
1605
+ redactionRules: [],
1606
+ auditRecords: [],
1607
+ propagateCalls: 0,
1608
+ writtenReplacements: [],
1609
+ };
1610
+ const planner = new CorrectionPlanner({
1611
+ ...makePlannerDeps(dir, candidates),
1612
+ inspectMemory: async ({ memoryId }) => {
1613
+ const candidate = candidates.get(memoryId);
1614
+ if (!candidate) return null;
1615
+ return { candidate, status: "active" };
1616
+ },
1617
+ });
1618
+ const plan = await planner.plan(
1619
+ { text: "duplicate", targetIds: ["mem-loser"], duplicateOf: "mem-winner" },
1620
+ ["default"],
1621
+ );
1622
+ assert.equal(plan.actions.length, 1);
1623
+ const executor = new CorrectionExecutor(makeExecutorDeps(state), planner);
1624
+ const outcome = await executor.apply("default", plan.planId, { confirm: true });
1625
+ assert.equal(outcome.status, "applied");
1626
+ const loser = state.memories.get("mem-loser");
1627
+ const winner = state.memories.get("mem-winner");
1628
+ assert.ok(loser, "loser file remains");
1629
+ assert.equal(loser?.status, "superseded");
1630
+ assert.equal(loser?.supersededBy, "mem-winner");
1631
+ assert.equal(loser?.lineage, undefined);
1632
+ assert.equal(winner?.status, "active");
1633
+ assert.equal(winner?.content, "canonical");
1634
+ assert.equal(state.writtenReplacements.length, 0);
1635
+ assert.equal(state.tombstones.length, 1);
1636
+ assert.equal(state.tombstones[0]?.reason, "supersession");
1637
+ assert.equal(state.tombstones[0]?.sourceMemoryId, "mem-loser");
1638
+ });
1639
+ });
1640
+
1641
+ test("winnerId supersede fails closed when the winner is no longer active", async () => {
1642
+ await withTempDir(async (dir) => {
1643
+ const candidates = new Map<string, PlannerCandidate>([
1644
+ ["mem-loser", { memoryId: "mem-loser", path: "facts/mem-loser.md", content: "duplicate", excerpt: "duplicate", score: 1 }],
1645
+ ]);
1646
+ const state: FakeState = {
1647
+ memories: new Map([
1648
+ ["mem-loser", fakeMemory({ memoryId: "mem-loser", content: "duplicate" })],
1649
+ ["mem-winner", fakeMemory({ memoryId: "mem-winner", content: "canonical", status: "superseded" })],
1650
+ ]),
1651
+ tombstones: [],
1652
+ redactionRules: [],
1653
+ auditRecords: [],
1654
+ propagateCalls: 0,
1655
+ writtenReplacements: [],
1656
+ };
1657
+ const planner = new CorrectionPlanner({
1658
+ ...makePlannerDeps(dir, candidates),
1659
+ inspectMemory: async ({ memoryId }) => {
1660
+ if (memoryId === "mem-winner") {
1661
+ return {
1662
+ candidate: {
1663
+ memoryId,
1664
+ path: "facts/mem-winner.md",
1665
+ content: "canonical",
1666
+ excerpt: "canonical",
1667
+ score: 1,
1668
+ },
1669
+ status: "active",
1670
+ };
1671
+ }
1672
+ const candidate = candidates.get(memoryId);
1673
+ if (!candidate) return null;
1674
+ return { candidate, status: "active" };
1675
+ },
1676
+ });
1677
+ const plan = await planner.plan(
1678
+ { text: "duplicate", targetIds: ["mem-loser"], duplicateOf: "mem-winner" },
1679
+ ["default"],
1680
+ );
1681
+ const outcome = await new CorrectionExecutor(makeExecutorDeps(state), planner).apply(
1682
+ "default",
1683
+ plan.planId,
1684
+ { confirm: true },
1685
+ );
1686
+ assert.equal(outcome.results[0]?.status, "failed");
1687
+ assert.match(outcome.results[0]?.error ?? "", /winner is not active/);
1688
+ assert.equal(state.memories.get("mem-loser")?.status, "active");
1689
+ assert.equal(state.tombstones.length, 0);
1690
+ });
1691
+ });