@remnic/core 10.0.3 → 10.0.5

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 (410) hide show
  1. package/dist/access-admin-ops-surface.js +62 -62
  2. package/dist/access-authorization-probe.js +63 -63
  3. package/dist/access-boundary.js +62 -62
  4. package/dist/access-cli.d.ts.map +1 -1
  5. package/dist/access-cli.js +146 -139
  6. package/dist/access-cli.js.map +1 -1
  7. package/dist/access-extraction-force-flush.js +62 -62
  8. package/dist/access-http-briefing.js +63 -63
  9. package/dist/access-http-query.js +64 -64
  10. package/dist/access-http.d.ts.map +1 -1
  11. package/dist/access-http.js +86 -85
  12. package/dist/access-identity-continuity-surface.js +62 -62
  13. package/dist/access-lcm-surface.js +62 -62
  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 +76 -75
  19. package/dist/access-namespace-preflight.js +62 -62
  20. package/dist/access-observe-write-category.js +5 -5
  21. package/dist/access-observe-write-surface.js +62 -62
  22. package/dist/access-offline-manifest.js +8 -8
  23. package/dist/access-operations-batch.js +65 -65
  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 +72 -72
  27. package/dist/access-recall-concurrency.js +62 -62
  28. package/dist/access-recall-diagnostics.js +2 -2
  29. package/dist/access-recall-response.js +62 -62
  30. package/dist/access-recall-surface.js +62 -62
  31. package/dist/access-service.d.ts.map +1 -1
  32. package/dist/access-service.js +62 -62
  33. package/dist/access-surface-catalog.js +64 -64
  34. package/dist/active-recall.js +31 -30
  35. package/dist/active-recall.js.map +1 -1
  36. package/dist/adapters/index.js +7 -7
  37. package/dist/adapters/registry.js +3 -3
  38. package/dist/{auto-sync-PAWBFQWL.js → auto-sync-FN7E3OTO.js} +6 -6
  39. package/dist/briefing.js +26 -26
  40. package/dist/buffer.js +8 -8
  41. package/dist/causal-consolidation.js +26 -26
  42. package/dist/{chunk-YQSWNFZE.js → chunk-2CP577K6.js} +2 -2
  43. package/dist/{chunk-6JIHC5AS.js → chunk-37HCPM56.js} +78 -67
  44. package/dist/chunk-37HCPM56.js.map +1 -0
  45. package/dist/{chunk-345MEMRU.js → chunk-3OUGSOJP.js} +2 -2
  46. package/dist/{chunk-CHI37EHP.js → chunk-4CWUZBPF.js} +8 -6
  47. package/dist/chunk-4CWUZBPF.js.map +1 -0
  48. package/dist/{chunk-G2STTRYB.js → chunk-4D2YH47Z.js} +2 -2
  49. package/dist/{chunk-IXYIW3MZ.js → chunk-4K2H4KTR.js} +5 -5
  50. package/dist/{chunk-UVVSOSHL.js → chunk-52CQKST6.js} +53 -53
  51. package/dist/{chunk-H7BZBSP6.js → chunk-52II7PB6.js} +4 -4
  52. package/dist/{chunk-BLHPADUX.js → chunk-5BPXMAUP.js} +22 -78
  53. package/dist/chunk-5BPXMAUP.js.map +1 -0
  54. package/dist/{chunk-ZS7RK6H6.js → chunk-5BY3IA2U.js} +16 -53
  55. package/dist/chunk-5BY3IA2U.js.map +1 -0
  56. package/dist/{chunk-2QZFZNXO.js → chunk-5CQO6LVM.js} +2 -2
  57. package/dist/{chunk-NLDMTXWV.js → chunk-5LNPW7NE.js} +2 -2
  58. package/dist/{chunk-WHTMVTU4.js → chunk-5X4ZJESN.js} +2 -2
  59. package/dist/{chunk-WI37MWRZ.js → chunk-63UNVGZW.js} +5 -5
  60. package/dist/{chunk-G3T6YMBO.js → chunk-64S6X4IU.js} +7 -7
  61. package/dist/{chunk-CFCEJNOE.js → chunk-6KHF6PAB.js} +2 -2
  62. package/dist/{chunk-5GINB5LC.js → chunk-6MKLMEBE.js} +1 -1
  63. package/dist/{chunk-5GINB5LC.js.map → chunk-6MKLMEBE.js.map} +1 -1
  64. package/dist/{chunk-35A7GCHG.js → chunk-6T42L5FI.js} +2 -2
  65. package/dist/{chunk-JDOBIFBK.js → chunk-7CXL25AA.js} +2 -2
  66. package/dist/{chunk-YLBE6PLR.js → chunk-7IGMKQWK.js} +2 -2
  67. package/dist/{chunk-CSLK7BN6.js → chunk-7LYRFI5A.js} +2 -2
  68. package/dist/{chunk-IB63OVUB.js → chunk-AJ6ORARY.js} +6 -6
  69. package/dist/{chunk-CGOKNQDK.js → chunk-ANZXWOYD.js} +2 -2
  70. package/dist/{chunk-5QXMESS7.js → chunk-AXC7UTLM.js} +2 -2
  71. package/dist/{chunk-RFIJRZ4K.js → chunk-B657ZGDH.js} +2 -2
  72. package/dist/{chunk-AW4QUFQF.js → chunk-BTNNL7ZW.js} +3 -2
  73. package/dist/{chunk-AW4QUFQF.js.map → chunk-BTNNL7ZW.js.map} +1 -1
  74. package/dist/{chunk-MY7B3HYC.js → chunk-CLCMDKTB.js} +2 -2
  75. package/dist/{chunk-QPWUG2S3.js → chunk-CYEK6Z3P.js} +4 -4
  76. package/dist/{chunk-IHCYCE6D.js → chunk-DJMVSUPK.js} +4 -4
  77. package/dist/{chunk-HP4CU2ZA.js → chunk-DST7HRSG.js} +2 -2
  78. package/dist/{chunk-IIFFC3RL.js → chunk-EDR6CVWZ.js} +2 -2
  79. package/dist/{chunk-AUWMKDKL.js → chunk-EMVJU7WC.js} +2 -2
  80. package/dist/{chunk-O6AYMAPJ.js → chunk-ENNC3RZ6.js} +2 -2
  81. package/dist/{chunk-2JRB3Z5N.js → chunk-EP5WXE2G.js} +3 -3
  82. package/dist/{chunk-QLK3SL7Q.js → chunk-ETSJD5IV.js} +131 -12
  83. package/dist/chunk-ETSJD5IV.js.map +1 -0
  84. package/dist/chunk-EWMRFW3F.js +38 -0
  85. package/dist/chunk-EWMRFW3F.js.map +1 -0
  86. package/dist/{chunk-I2KEH2LS.js → chunk-FC6RCOIU.js} +2 -2
  87. package/dist/{chunk-OZVHLBH6.js → chunk-FLV7M7CT.js} +2 -2
  88. package/dist/{chunk-Q6GI7HEC.js → chunk-FRH2UC7Q.js} +2 -2
  89. package/dist/{chunk-3SL52JKD.js → chunk-FZVROZKC.js} +5 -5
  90. package/dist/{chunk-2EZB2DL5.js → chunk-GE6HETIF.js} +3 -3
  91. package/dist/{chunk-2DHJTJIY.js → chunk-GEGUBU3D.js} +2 -2
  92. package/dist/{chunk-6AA7XFMO.js → chunk-GF4H3B3R.js} +8 -8
  93. package/dist/{chunk-NGOZH7R5.js → chunk-GVUW5XK5.js} +311 -142
  94. package/dist/chunk-GVUW5XK5.js.map +1 -0
  95. package/dist/{chunk-ME2LMFHF.js → chunk-HPAOMYKM.js} +92 -92
  96. package/dist/{chunk-4NRZ7BRS.js → chunk-I25RINZK.js} +2 -2
  97. package/dist/{chunk-XG4XXI66.js → chunk-I25RQA65.js} +2 -2
  98. package/dist/chunk-I56MTWE7.js +80 -0
  99. package/dist/chunk-I56MTWE7.js.map +1 -0
  100. package/dist/{chunk-EJJMDLVB.js → chunk-IJ4FEGZR.js} +2 -2
  101. package/dist/{chunk-EUL3S3LE.js → chunk-J5WI4QRP.js} +4 -4
  102. package/dist/{chunk-LMMC55WM.js → chunk-JA54KIYD.js} +4 -4
  103. package/dist/{chunk-5FLFPTDK.js → chunk-JA72X46J.js} +2 -2
  104. package/dist/{chunk-6GU3TNJJ.js → chunk-JMF5Q4JY.js} +246 -49
  105. package/dist/chunk-JMF5Q4JY.js.map +1 -0
  106. package/dist/{chunk-IDSSDL4W.js → chunk-JXSYWTFK.js} +21 -4
  107. package/dist/{chunk-IDSSDL4W.js.map → chunk-JXSYWTFK.js.map} +1 -1
  108. package/dist/{chunk-NLYOOGZ7.js → chunk-KE5PWLI6.js} +3 -3
  109. package/dist/chunk-KIUVEMBI.js +49 -0
  110. package/dist/chunk-KIUVEMBI.js.map +1 -0
  111. package/dist/{chunk-4DDIWRT7.js → chunk-KQI2PKYP.js} +2 -2
  112. package/dist/{chunk-Y2ADLCB3.js → chunk-KSUOM3YU.js} +9 -9
  113. package/dist/{chunk-BTOHGPHC.js → chunk-KZEMFD6E.js} +3 -3
  114. package/dist/{chunk-NXVD46HT.js → chunk-L2VS27PZ.js} +4 -4
  115. package/dist/{chunk-MYYHACOW.js → chunk-L34W4W4I.js} +2 -2
  116. package/dist/{chunk-KRTIHXWC.js → chunk-L3PWXD73.js} +2 -2
  117. package/dist/{chunk-NQHSXHJP.js → chunk-L6GNS5GB.js} +2 -2
  118. package/dist/{chunk-44PBRUXB.js → chunk-M47Q6566.js} +2 -2
  119. package/dist/{chunk-E7FN5N5E.js → chunk-MQNOIH2C.js} +7 -7
  120. package/dist/{chunk-IDSZNHIP.js → chunk-MULNBSPP.js} +4 -4
  121. package/dist/{chunk-RKSGUMVL.js → chunk-NBQIFCJE.js} +2 -2
  122. package/dist/{chunk-I6WFBNA4.js → chunk-NHSVW7UT.js} +15 -15
  123. package/dist/{chunk-32HO62I4.js → chunk-OFEHITAZ.js} +2 -2
  124. package/dist/{chunk-PCO3P7WJ.js → chunk-OGVJXIHX.js} +2 -2
  125. package/dist/{chunk-RYN4AIRM.js → chunk-OZABSIOV.js} +4 -4
  126. package/dist/{chunk-HO7IQP7G.js → chunk-QMFKW4OU.js} +3 -3
  127. package/dist/{chunk-34G4U6HS.js → chunk-QRZMJ2WN.js} +2 -2
  128. package/dist/{chunk-RKDSELA3.js → chunk-QWWMS4AE.js} +1 -1
  129. package/dist/chunk-QWWMS4AE.js.map +1 -0
  130. package/dist/{chunk-GUD6FYQG.js → chunk-RSMIRLKM.js} +2 -2
  131. package/dist/{chunk-TNVZCLJU.js → chunk-S5TGRZWY.js} +2 -2
  132. package/dist/{chunk-O24EWFPQ.js → chunk-SNXBXYUE.js} +3 -3
  133. package/dist/{chunk-KW77LGDX.js → chunk-SUAX5JWY.js} +2 -2
  134. package/dist/{chunk-PNFWIB4X.js → chunk-SWRN6U7Q.js} +2 -2
  135. package/dist/{chunk-X6X4UASX.js → chunk-SZXPKUMN.js} +232 -83
  136. package/dist/chunk-SZXPKUMN.js.map +1 -0
  137. package/dist/{chunk-R4NGLTUS.js → chunk-TDMNADPI.js} +2 -2
  138. package/dist/{chunk-QCP75JGO.js → chunk-TFA6HPOA.js} +5 -5
  139. package/dist/{chunk-LI76ESJL.js → chunk-TZQOCQXB.js} +2 -2
  140. package/dist/{chunk-OV7EYM6M.js → chunk-U6OXJQEH.js} +12 -12
  141. package/dist/{chunk-OWHPA4JS.js → chunk-V32NHP2R.js} +2 -2
  142. package/dist/{chunk-JSUA4YFE.js → chunk-WG4TMZJ2.js} +2 -2
  143. package/dist/{chunk-DQTCGVFK.js → chunk-WHCD4Z3D.js} +2 -2
  144. package/dist/{chunk-GBA4N2SG.js → chunk-WRICYGYG.js} +27 -26
  145. package/dist/chunk-WRICYGYG.js.map +1 -0
  146. package/dist/{chunk-KATEWTQJ.js → chunk-X7J56GPG.js} +2 -2
  147. package/dist/{chunk-7KICGR5W.js → chunk-XPRAOTJY.js} +2 -2
  148. package/dist/{chunk-2NV7MMMM.js → chunk-XQSJXNWC.js} +2 -2
  149. package/dist/{chunk-22DYJLFN.js → chunk-XVC7JU2N.js} +39 -18
  150. package/dist/chunk-XVC7JU2N.js.map +1 -0
  151. package/dist/{chunk-RXT6UB25.js → chunk-YJAOYEWI.js} +2 -2
  152. package/dist/{chunk-L6DJJEFW.js → chunk-YJEUHGZZ.js} +2 -2
  153. package/dist/{chunk-AXFWMRKI.js → chunk-YQJCWR4R.js} +2 -2
  154. package/dist/cli.js +110 -108
  155. package/dist/coding/export-okf-codegraph.js +25 -25
  156. package/dist/compounding/engine.js +25 -25
  157. package/dist/config.d.ts.map +1 -1
  158. package/dist/config.js +31 -30
  159. package/dist/connectors/codex-materialize-runner.js +25 -25
  160. package/dist/connectors/index.js +25 -25
  161. package/dist/consolidation-provenance-check.js +2 -2
  162. package/dist/contradiction/index.js +7 -7
  163. package/dist/convergence-refresh.js +27 -27
  164. package/dist/conversation-index/backend.js +2 -2
  165. package/dist/corpus-read-cancellation.d.ts +7 -0
  166. package/dist/corpus-read-cancellation.d.ts.map +1 -1
  167. package/dist/corpus-read-cancellation.js +1 -1
  168. package/dist/corpus-watermark.js +27 -27
  169. package/dist/correction/correction-access-wiring.d.ts +6 -6
  170. package/dist/correction/correction-access-wiring.d.ts.map +1 -1
  171. package/dist/correction/correction-classify.d.ts +25 -0
  172. package/dist/correction/correction-classify.d.ts.map +1 -0
  173. package/dist/correction/correction-config.d.ts +11 -0
  174. package/dist/correction/correction-config.d.ts.map +1 -0
  175. package/dist/correction/correction-contract.d.ts +13 -1
  176. package/dist/correction/correction-contract.d.ts.map +1 -1
  177. package/dist/correction/correction-executor.d.ts +2 -0
  178. package/dist/correction/correction-executor.d.ts.map +1 -1
  179. package/dist/correction/correction-planner.d.ts +24 -3
  180. package/dist/correction/correction-planner.d.ts.map +1 -1
  181. package/dist/directory-sidecars.js +2 -2
  182. package/dist/entity-retrieval.js +25 -25
  183. package/dist/event-order-recall.js +2 -2
  184. package/dist/explicit-capture.js +2 -2
  185. package/dist/external-wiki-access.js +63 -63
  186. package/dist/extraction-span-materialize.js +2 -2
  187. package/dist/extraction.js +34 -34
  188. package/dist/harmonic-retrieval.js +25 -25
  189. package/dist/index.js +221 -218
  190. package/dist/lcm/index.js +3 -3
  191. package/dist/local-llm.d.ts.map +1 -1
  192. package/dist/local-llm.js +1 -1
  193. package/dist/location/index.js +28 -28
  194. package/dist/maintenance/archive-observations.js +2 -2
  195. package/dist/maintenance/atomic-file.d.ts +1 -1
  196. package/dist/maintenance/atomic-file.d.ts.map +1 -1
  197. package/dist/maintenance/atomic-file.js +1 -1
  198. package/dist/maintenance/memory-governance.js +25 -25
  199. package/dist/maintenance/migrate-observations.js +3 -3
  200. package/dist/maintenance/observation-ledger-utils.js +2 -2
  201. package/dist/maintenance/projection-rebuild-abort.d.ts +17 -0
  202. package/dist/maintenance/projection-rebuild-abort.d.ts.map +1 -0
  203. package/dist/maintenance/projection-timeline.d.ts +19 -0
  204. package/dist/maintenance/projection-timeline.d.ts.map +1 -0
  205. package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +25 -25
  206. package/dist/maintenance/rebuild-memory-projection-runner.d.ts +8 -0
  207. package/dist/maintenance/rebuild-memory-projection-runner.d.ts.map +1 -0
  208. package/dist/maintenance/rebuild-memory-projection-runner.js +113 -0
  209. package/dist/maintenance/rebuild-memory-projection-runner.js.map +1 -0
  210. package/dist/maintenance/rebuild-memory-projection.d.ts +8 -0
  211. package/dist/maintenance/rebuild-memory-projection.d.ts.map +1 -1
  212. package/dist/maintenance/rebuild-memory-projection.js +26 -26
  213. package/dist/maintenance/rebuild-observations.js +3 -3
  214. package/dist/memory-browse-operations.js +63 -63
  215. package/dist/memory-browse.js +7 -7
  216. package/dist/memory-subject.js +1 -1
  217. package/dist/namespaces/migrate.js +26 -26
  218. package/dist/namespaces/search.js +10 -10
  219. package/dist/namespaces/storage.js +25 -25
  220. package/dist/offline-sync-impression-drain.js +3 -3
  221. package/dist/operator-doctor-corpus.js +28 -28
  222. package/dist/operator-toolkit.js +41 -40
  223. package/dist/orchestration/full-corpus-heap-guard.d.ts +25 -0
  224. package/dist/orchestration/full-corpus-heap-guard.d.ts.map +1 -0
  225. package/dist/orchestration/maintenance.d.ts.map +1 -1
  226. package/dist/orchestration/maintenance.js +38 -36
  227. package/dist/orchestration/projection-rebuild-schedule.d.ts +40 -0
  228. package/dist/orchestration/projection-rebuild-schedule.d.ts.map +1 -1
  229. package/dist/orchestration/session-context.d.ts.map +1 -1
  230. package/dist/orchestrator.js +142 -139
  231. package/dist/projection-rebuild-config.d.ts +52 -0
  232. package/dist/projection-rebuild-config.d.ts.map +1 -0
  233. package/dist/projection-rebuild-config.js +14 -0
  234. package/dist/projection-rebuild-config.js.map +1 -0
  235. package/dist/qmd.js +4 -4
  236. package/dist/recall-explain-renderer.js +3 -3
  237. package/dist/recall-navigate-access.js +2 -2
  238. package/dist/recall-navigate-disclosure.js +2 -2
  239. package/dist/recall-navigate.js +2 -2
  240. package/dist/recall-navigation-operations.js +63 -63
  241. package/dist/recall-navigation.js +62 -62
  242. package/dist/recall-state-view-admission.js +7 -7
  243. package/dist/recall-state.js +2 -2
  244. package/dist/recall-why-service.js +8 -8
  245. package/dist/recall-xray-cli.js +4 -4
  246. package/dist/recall-xray-renderer.js +3 -3
  247. package/dist/recall-xray.js +2 -2
  248. package/dist/reconcile/manifest.js +3 -3
  249. package/dist/relevance.js +2 -2
  250. package/dist/{resolution-RSG4HZTL.js → resolution-TV3F7ZTH.js} +7 -7
  251. package/dist/resolution-TV3F7ZTH.js.map +1 -0
  252. package/dist/resume-bundles.js +32 -31
  253. package/dist/salvage-envelope.js +1 -1
  254. package/dist/search/factory.js +9 -9
  255. package/dist/search/index.js +17 -17
  256. package/dist/semantic-consolidation.js +31 -31
  257. package/dist/semantic-rule-promotion.js +25 -25
  258. package/dist/semantic-rule-verifier.js +25 -25
  259. package/dist/source-agent-qualifier.js +29 -29
  260. package/dist/standup/build.js +25 -25
  261. package/dist/storage/memory-lifecycle-ledger-access.d.ts +5 -3
  262. package/dist/storage/memory-lifecycle-ledger-access.d.ts.map +1 -1
  263. package/dist/storage/memory-read-store.d.ts +2 -2
  264. package/dist/storage/memory-read-store.d.ts.map +1 -1
  265. package/dist/storage.d.ts +4 -2
  266. package/dist/storage.d.ts.map +1 -1
  267. package/dist/storage.js +24 -24
  268. package/dist/subject-config.js +1 -1
  269. package/dist/summarizer.js +3 -3
  270. package/dist/support-passport/index.js +78 -78
  271. package/dist/transfer/export-okf.js +26 -26
  272. package/dist/types.d.ts +8 -40
  273. package/dist/types.d.ts.map +1 -1
  274. package/dist/types.js +1 -1
  275. package/dist/verified-recall.js +25 -25
  276. package/dist/who-knows-cli.js +26 -26
  277. package/dist/who-knows.js +25 -25
  278. package/dist/write-envelope.js +1 -1
  279. package/package.json +2 -2
  280. package/src/access-cli.ts +4 -0
  281. package/src/access-http.ts +1 -0
  282. package/src/access-mcp-correction-tools.ts +56 -0
  283. package/src/access-mcp.ts +4 -49
  284. package/src/access-operations.ts +3 -0
  285. package/src/access-service.ts +12 -21
  286. package/src/config.test.ts +35 -0
  287. package/src/config.ts +6 -65
  288. package/src/corpus-read-cancellation.ts +7 -0
  289. package/src/correction/correction-access-wiring.ts +56 -16
  290. package/src/correction/correction-classify.test.ts +185 -0
  291. package/src/correction/correction-classify.ts +157 -0
  292. package/src/correction/correction-config.ts +93 -0
  293. package/src/correction/correction-contract.ts +61 -14
  294. package/src/correction/correction-executor.test.ts +102 -2
  295. package/src/correction/correction-executor.ts +19 -1
  296. package/src/correction/correction-planner.test.ts +141 -0
  297. package/src/correction/correction-planner.ts +117 -11
  298. package/src/correction/correction-surfaces.test.ts +22 -1
  299. package/src/local-llm-qos.test.ts +36 -0
  300. package/src/local-llm.ts +20 -3
  301. package/src/maintenance/atomic-file.ts +2 -0
  302. package/src/maintenance/projection-rebuild-abort.ts +40 -0
  303. package/src/maintenance/projection-timeline.ts +209 -0
  304. package/src/maintenance/rebuild-memory-projection-runner.ts +94 -0
  305. package/src/maintenance/rebuild-memory-projection.memory.test.ts +159 -0
  306. package/src/maintenance/rebuild-memory-projection.retain-raw.test.ts +202 -0
  307. package/src/maintenance/rebuild-memory-projection.ts +63 -91
  308. package/src/orchestration/full-corpus-heap-guard.ts +38 -0
  309. package/src/orchestration/maintenance.test.ts +2 -2
  310. package/src/orchestration/maintenance.ts +2 -0
  311. package/src/orchestration/projection-rebuild-schedule.test.ts +498 -0
  312. package/src/orchestration/projection-rebuild-schedule.ts +280 -9
  313. package/src/orchestration/session-context.ts +0 -15
  314. package/src/projection-rebuild-config.ts +82 -0
  315. package/src/recall-handles-wiring.test.ts +24 -0
  316. package/src/storage/memory-lifecycle-ledger-access.ts +61 -33
  317. package/src/storage/memory-read-store.ts +10 -4
  318. package/src/storage.ts +45 -33
  319. package/src/types.ts +8 -41
  320. package/dist/chunk-22DYJLFN.js.map +0 -1
  321. package/dist/chunk-6GU3TNJJ.js.map +0 -1
  322. package/dist/chunk-6JIHC5AS.js.map +0 -1
  323. package/dist/chunk-BLHPADUX.js.map +0 -1
  324. package/dist/chunk-CHI37EHP.js.map +0 -1
  325. package/dist/chunk-GBA4N2SG.js.map +0 -1
  326. package/dist/chunk-NGOZH7R5.js.map +0 -1
  327. package/dist/chunk-QLK3SL7Q.js.map +0 -1
  328. package/dist/chunk-RKDSELA3.js.map +0 -1
  329. package/dist/chunk-X6X4UASX.js.map +0 -1
  330. package/dist/chunk-ZS7RK6H6.js.map +0 -1
  331. /package/dist/{resolution-RSG4HZTL.js.map → access-mcp-correction-tools.js.map} +0 -0
  332. /package/dist/{auto-sync-PAWBFQWL.js.map → auto-sync-FN7E3OTO.js.map} +0 -0
  333. /package/dist/{chunk-YQSWNFZE.js.map → chunk-2CP577K6.js.map} +0 -0
  334. /package/dist/{chunk-345MEMRU.js.map → chunk-3OUGSOJP.js.map} +0 -0
  335. /package/dist/{chunk-G2STTRYB.js.map → chunk-4D2YH47Z.js.map} +0 -0
  336. /package/dist/{chunk-IXYIW3MZ.js.map → chunk-4K2H4KTR.js.map} +0 -0
  337. /package/dist/{chunk-UVVSOSHL.js.map → chunk-52CQKST6.js.map} +0 -0
  338. /package/dist/{chunk-H7BZBSP6.js.map → chunk-52II7PB6.js.map} +0 -0
  339. /package/dist/{chunk-2QZFZNXO.js.map → chunk-5CQO6LVM.js.map} +0 -0
  340. /package/dist/{chunk-NLDMTXWV.js.map → chunk-5LNPW7NE.js.map} +0 -0
  341. /package/dist/{chunk-WHTMVTU4.js.map → chunk-5X4ZJESN.js.map} +0 -0
  342. /package/dist/{chunk-WI37MWRZ.js.map → chunk-63UNVGZW.js.map} +0 -0
  343. /package/dist/{chunk-G3T6YMBO.js.map → chunk-64S6X4IU.js.map} +0 -0
  344. /package/dist/{chunk-CFCEJNOE.js.map → chunk-6KHF6PAB.js.map} +0 -0
  345. /package/dist/{chunk-35A7GCHG.js.map → chunk-6T42L5FI.js.map} +0 -0
  346. /package/dist/{chunk-JDOBIFBK.js.map → chunk-7CXL25AA.js.map} +0 -0
  347. /package/dist/{chunk-YLBE6PLR.js.map → chunk-7IGMKQWK.js.map} +0 -0
  348. /package/dist/{chunk-CSLK7BN6.js.map → chunk-7LYRFI5A.js.map} +0 -0
  349. /package/dist/{chunk-IB63OVUB.js.map → chunk-AJ6ORARY.js.map} +0 -0
  350. /package/dist/{chunk-CGOKNQDK.js.map → chunk-ANZXWOYD.js.map} +0 -0
  351. /package/dist/{chunk-5QXMESS7.js.map → chunk-AXC7UTLM.js.map} +0 -0
  352. /package/dist/{chunk-RFIJRZ4K.js.map → chunk-B657ZGDH.js.map} +0 -0
  353. /package/dist/{chunk-MY7B3HYC.js.map → chunk-CLCMDKTB.js.map} +0 -0
  354. /package/dist/{chunk-QPWUG2S3.js.map → chunk-CYEK6Z3P.js.map} +0 -0
  355. /package/dist/{chunk-IHCYCE6D.js.map → chunk-DJMVSUPK.js.map} +0 -0
  356. /package/dist/{chunk-HP4CU2ZA.js.map → chunk-DST7HRSG.js.map} +0 -0
  357. /package/dist/{chunk-IIFFC3RL.js.map → chunk-EDR6CVWZ.js.map} +0 -0
  358. /package/dist/{chunk-AUWMKDKL.js.map → chunk-EMVJU7WC.js.map} +0 -0
  359. /package/dist/{chunk-O6AYMAPJ.js.map → chunk-ENNC3RZ6.js.map} +0 -0
  360. /package/dist/{chunk-2JRB3Z5N.js.map → chunk-EP5WXE2G.js.map} +0 -0
  361. /package/dist/{chunk-I2KEH2LS.js.map → chunk-FC6RCOIU.js.map} +0 -0
  362. /package/dist/{chunk-OZVHLBH6.js.map → chunk-FLV7M7CT.js.map} +0 -0
  363. /package/dist/{chunk-Q6GI7HEC.js.map → chunk-FRH2UC7Q.js.map} +0 -0
  364. /package/dist/{chunk-3SL52JKD.js.map → chunk-FZVROZKC.js.map} +0 -0
  365. /package/dist/{chunk-2EZB2DL5.js.map → chunk-GE6HETIF.js.map} +0 -0
  366. /package/dist/{chunk-2DHJTJIY.js.map → chunk-GEGUBU3D.js.map} +0 -0
  367. /package/dist/{chunk-6AA7XFMO.js.map → chunk-GF4H3B3R.js.map} +0 -0
  368. /package/dist/{chunk-ME2LMFHF.js.map → chunk-HPAOMYKM.js.map} +0 -0
  369. /package/dist/{chunk-4NRZ7BRS.js.map → chunk-I25RINZK.js.map} +0 -0
  370. /package/dist/{chunk-XG4XXI66.js.map → chunk-I25RQA65.js.map} +0 -0
  371. /package/dist/{chunk-EJJMDLVB.js.map → chunk-IJ4FEGZR.js.map} +0 -0
  372. /package/dist/{chunk-EUL3S3LE.js.map → chunk-J5WI4QRP.js.map} +0 -0
  373. /package/dist/{chunk-LMMC55WM.js.map → chunk-JA54KIYD.js.map} +0 -0
  374. /package/dist/{chunk-5FLFPTDK.js.map → chunk-JA72X46J.js.map} +0 -0
  375. /package/dist/{chunk-NLYOOGZ7.js.map → chunk-KE5PWLI6.js.map} +0 -0
  376. /package/dist/{chunk-4DDIWRT7.js.map → chunk-KQI2PKYP.js.map} +0 -0
  377. /package/dist/{chunk-Y2ADLCB3.js.map → chunk-KSUOM3YU.js.map} +0 -0
  378. /package/dist/{chunk-BTOHGPHC.js.map → chunk-KZEMFD6E.js.map} +0 -0
  379. /package/dist/{chunk-NXVD46HT.js.map → chunk-L2VS27PZ.js.map} +0 -0
  380. /package/dist/{chunk-MYYHACOW.js.map → chunk-L34W4W4I.js.map} +0 -0
  381. /package/dist/{chunk-KRTIHXWC.js.map → chunk-L3PWXD73.js.map} +0 -0
  382. /package/dist/{chunk-NQHSXHJP.js.map → chunk-L6GNS5GB.js.map} +0 -0
  383. /package/dist/{chunk-44PBRUXB.js.map → chunk-M47Q6566.js.map} +0 -0
  384. /package/dist/{chunk-E7FN5N5E.js.map → chunk-MQNOIH2C.js.map} +0 -0
  385. /package/dist/{chunk-IDSZNHIP.js.map → chunk-MULNBSPP.js.map} +0 -0
  386. /package/dist/{chunk-RKSGUMVL.js.map → chunk-NBQIFCJE.js.map} +0 -0
  387. /package/dist/{chunk-I6WFBNA4.js.map → chunk-NHSVW7UT.js.map} +0 -0
  388. /package/dist/{chunk-32HO62I4.js.map → chunk-OFEHITAZ.js.map} +0 -0
  389. /package/dist/{chunk-PCO3P7WJ.js.map → chunk-OGVJXIHX.js.map} +0 -0
  390. /package/dist/{chunk-RYN4AIRM.js.map → chunk-OZABSIOV.js.map} +0 -0
  391. /package/dist/{chunk-HO7IQP7G.js.map → chunk-QMFKW4OU.js.map} +0 -0
  392. /package/dist/{chunk-34G4U6HS.js.map → chunk-QRZMJ2WN.js.map} +0 -0
  393. /package/dist/{chunk-GUD6FYQG.js.map → chunk-RSMIRLKM.js.map} +0 -0
  394. /package/dist/{chunk-TNVZCLJU.js.map → chunk-S5TGRZWY.js.map} +0 -0
  395. /package/dist/{chunk-O24EWFPQ.js.map → chunk-SNXBXYUE.js.map} +0 -0
  396. /package/dist/{chunk-KW77LGDX.js.map → chunk-SUAX5JWY.js.map} +0 -0
  397. /package/dist/{chunk-PNFWIB4X.js.map → chunk-SWRN6U7Q.js.map} +0 -0
  398. /package/dist/{chunk-R4NGLTUS.js.map → chunk-TDMNADPI.js.map} +0 -0
  399. /package/dist/{chunk-QCP75JGO.js.map → chunk-TFA6HPOA.js.map} +0 -0
  400. /package/dist/{chunk-LI76ESJL.js.map → chunk-TZQOCQXB.js.map} +0 -0
  401. /package/dist/{chunk-OV7EYM6M.js.map → chunk-U6OXJQEH.js.map} +0 -0
  402. /package/dist/{chunk-OWHPA4JS.js.map → chunk-V32NHP2R.js.map} +0 -0
  403. /package/dist/{chunk-JSUA4YFE.js.map → chunk-WG4TMZJ2.js.map} +0 -0
  404. /package/dist/{chunk-DQTCGVFK.js.map → chunk-WHCD4Z3D.js.map} +0 -0
  405. /package/dist/{chunk-KATEWTQJ.js.map → chunk-X7J56GPG.js.map} +0 -0
  406. /package/dist/{chunk-7KICGR5W.js.map → chunk-XPRAOTJY.js.map} +0 -0
  407. /package/dist/{chunk-2NV7MMMM.js.map → chunk-XQSJXNWC.js.map} +0 -0
  408. /package/dist/{chunk-RXT6UB25.js.map → chunk-YJAOYEWI.js.map} +0 -0
  409. /package/dist/{chunk-L6DJJEFW.js.map → chunk-YJEUHGZZ.js.map} +0 -0
  410. /package/dist/{chunk-AXFWMRKI.js.map → chunk-YQJCWR4R.js.map} +0 -0
@@ -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
+ });
@@ -68,6 +68,8 @@ export interface ExecutorMemory {
68
68
  entityRef?: string;
69
69
  /** Raw content used for the tombstone hash (rule 23). */
70
70
  rawContent: string;
71
+ /** Frontmatter status. Absent is treated as active. */
72
+ status?: string;
71
73
  }
72
74
 
73
75
  export interface ExecutorDeps {
@@ -486,13 +488,29 @@ export class CorrectionExecutor {
486
488
  if (action.replacement && !replacementResult) {
487
489
  continue;
488
490
  }
491
+ if (action.winnerId && !action.replacement) {
492
+ const winner = await this.deps.getMemory(namespace, action.winnerId, abortSignal);
493
+ const winnerStatus = winner ? (winner.status ?? "active") : undefined;
494
+ if (!winner || winnerStatus !== "active") {
495
+ results.push({
496
+ action,
497
+ status: "failed",
498
+ error: winner
499
+ ? `supersede winner is not active: ${action.winnerId} (${winnerStatus})`
500
+ : `supersede winner not found: ${action.winnerId}`,
501
+ });
502
+ continue;
503
+ }
504
+ }
489
505
  await this.retireAndTombstone(
490
506
  namespace,
491
507
  action,
492
508
  "supersession",
493
509
  results,
494
510
  appliedTouched,
495
- { supersededBy: replacementResult?.memoryId },
511
+ {
512
+ supersededBy: replacementResult?.memoryId ?? action.winnerId,
513
+ },
496
514
  abortSignal
497
515
  );
498
516
  if (results.at(-1)?.status === "applied") {
@@ -38,6 +38,8 @@ interface StubState {
38
38
  writeReplacementCalls: number;
39
39
  propagateCalls: number;
40
40
  retireCalls: number;
41
+ statusById?: Map<string, string>;
42
+ classifyCalls?: number;
41
43
  }
42
44
 
43
45
  function makeCandidate(over: Partial<PlannerCandidate> & { memoryId: string }): PlannerCandidate {
@@ -72,12 +74,18 @@ function makeDeps(stateDir: string, state: StubState, opts: { maxAffected?: numb
72
74
  }
73
75
  return out;
74
76
  },
77
+ inspectMemory: async ({ memoryId }) => {
78
+ const candidate = state.candidatesById.get(memoryId);
79
+ if (!candidate) return null;
80
+ return { candidate, status: state.statusById?.get(memoryId) ?? "active" };
81
+ },
75
82
  expandNeighbors: async ({ seedIds }) => {
76
83
  // No neighbors in the stub; return the seeds themselves so neighbor
77
84
  // expansion is exercised but produces no surprises.
78
85
  return seedIds.map((id) => state.candidatesById.get(id)).filter((c): c is PlannerCandidate => c !== undefined);
79
86
  },
80
87
  classifyAndDraft: async ({ text, candidates }) => {
88
+ state.classifyCalls = (state.classifyCalls ?? 0) + 1;
81
89
  return typeof state.llmResult === "function"
82
90
  ? state.llmResult(text, candidates)
83
91
  : state.llmResult;
@@ -787,3 +795,136 @@ test("#1713: recoverStaleApplyingPlans recovers pre-fix plans immediately at exp
787
795
  assert.equal(recovered.length, 1, "pre-fix plan at expiry must be recovered immediately");
788
796
  });
789
797
  });
798
+
799
+ test("duplicateOf skips the LLM and drafts one recoverable supersede linked to the winner", async () => {
800
+ await withTempDir(async (dir) => {
801
+ const state: StubState = {
802
+ candidatesById: new Map([
803
+ ["mem-loser", makeCandidate({ memoryId: "mem-loser", content: "duplicate fact" })],
804
+ ["mem-winner", makeCandidate({ memoryId: "mem-winner", content: "canonical fact" })],
805
+ ]),
806
+ llmResult: () => {
807
+ throw new Error("classifyAndDraft must not run in duplicate mode");
808
+ },
809
+ writeReplacementCalls: 0,
810
+ propagateCalls: 0,
811
+ retireCalls: 0,
812
+ };
813
+ const plan = await new CorrectionPlanner(makeDeps(dir, state)).plan(
814
+ { text: "mem-loser duplicates mem-winner", targetIds: ["mem-loser"], duplicateOf: "mem-winner" },
815
+ ["default"],
816
+ );
817
+ assert.equal(state.classifyCalls ?? 0, 0);
818
+ assert.equal(plan.actions.length, 1);
819
+ assert.deepEqual(plan.actions[0], { kind: "supersede", loserId: "mem-loser", winnerId: "mem-winner" });
820
+ assert.equal(plan.classification, "outdated");
821
+ assert.equal(plan.confidence, 1);
822
+ assert.equal(plan.affected.length, 1);
823
+ assert.equal(plan.affected[0]?.memoryId, "mem-loser");
824
+ assert.equal(plan.request.duplicateOf, "mem-winner");
825
+ });
826
+ });
827
+
828
+ test("a model-selected winnerId outside the located set is dropped", async () => {
829
+ await withTempDir(async (dir) => {
830
+ const state: StubState = {
831
+ candidatesById: new Map([
832
+ ["mem-loser", makeCandidate({ memoryId: "mem-loser", content: "duplicate fact" })],
833
+ ["mem-winner", makeCandidate({ memoryId: "mem-winner", content: "canonical fact" })],
834
+ ]),
835
+ llmResult: {
836
+ classification: "outdated",
837
+ confidence: 0.9,
838
+ actions: [{ kind: "supersede", loserId: "mem-loser", winnerId: "mem-not-located" }],
839
+ relevance: [{ memoryId: "mem-loser", why: "duplicate" }],
840
+ warnings: [],
841
+ },
842
+ writeReplacementCalls: 0,
843
+ propagateCalls: 0,
844
+ retireCalls: 0,
845
+ };
846
+ const plan = await new CorrectionPlanner(makeDeps(dir, state)).plan(
847
+ { text: "retire the duplicate", targetIds: ["mem-loser"] },
848
+ ["default"],
849
+ );
850
+ assert.equal(plan.actions.length, 0);
851
+ assert.match(plan.warnings.join(" "), /outside the located candidate set/);
852
+ });
853
+ });
854
+
855
+ test("a model-selected winnerId inside the located set is kept", async () => {
856
+ await withTempDir(async (dir) => {
857
+ const state: StubState = {
858
+ candidatesById: new Map([
859
+ ["mem-loser", makeCandidate({ memoryId: "mem-loser", content: "duplicate fact" })],
860
+ ["mem-winner", makeCandidate({ memoryId: "mem-winner", content: "canonical fact" })],
861
+ ]),
862
+ llmResult: {
863
+ classification: "outdated",
864
+ confidence: 0.9,
865
+ actions: [{ kind: "supersede", loserId: "mem-loser", winnerId: "mem-winner" }],
866
+ relevance: [{ memoryId: "mem-loser", why: "duplicate" }],
867
+ warnings: [],
868
+ },
869
+ writeReplacementCalls: 0,
870
+ propagateCalls: 0,
871
+ retireCalls: 0,
872
+ };
873
+ const plan = await new CorrectionPlanner(makeDeps(dir, state)).plan(
874
+ { text: "retire the duplicate", targetIds: ["mem-loser", "mem-winner"] },
875
+ ["default"],
876
+ );
877
+ assert.deepEqual(plan.actions, [{ kind: "supersede", loserId: "mem-loser", winnerId: "mem-winner" }]);
878
+ });
879
+ });
880
+
881
+ test("duplicateOf rejects same id, missing memories, inactive memories, and a target list that is not exactly one", async () => {
882
+ await withTempDir(async (dir) => {
883
+ const state: StubState = {
884
+ candidatesById: new Map([
885
+ ["mem-loser", makeCandidate({ memoryId: "mem-loser" })],
886
+ ["mem-winner", makeCandidate({ memoryId: "mem-winner" })],
887
+ ["mem-old", makeCandidate({ memoryId: "mem-old" })],
888
+ ]),
889
+ statusById: new Map([["mem-old", "superseded"]]),
890
+ llmResult: { classification: "outdated", confidence: 1, actions: [], relevance: [], warnings: [] },
891
+ writeReplacementCalls: 0,
892
+ propagateCalls: 0,
893
+ retireCalls: 0,
894
+ };
895
+ const planner = new CorrectionPlanner(makeDeps(dir, state));
896
+ await assert.rejects(
897
+ () => planner.plan({ text: "same", targetIds: ["mem-loser"], duplicateOf: "mem-loser" }, ["default"]),
898
+ /different memory from targetIds/,
899
+ );
900
+ await assert.rejects(
901
+ () => planner.plan({ text: "missing loser", targetIds: ["missing"], duplicateOf: "mem-winner" }, ["default"]),
902
+ /target memory not found: missing/,
903
+ );
904
+ await assert.rejects(
905
+ () => planner.plan({ text: "missing winner", targetIds: ["mem-loser"], duplicateOf: "missing-winner" }, ["default"]),
906
+ /duplicateOf memory not found: missing-winner/,
907
+ );
908
+ await assert.rejects(
909
+ () => planner.plan({ text: "inactive loser", targetIds: ["mem-old"], duplicateOf: "mem-winner" }, ["default"]),
910
+ /target memory is not active: mem-old \(superseded\)/,
911
+ );
912
+ state.statusById = new Map([["mem-winner", "forgotten"]]);
913
+ await assert.rejects(
914
+ () => planner.plan({ text: "inactive winner", targetIds: ["mem-loser"], duplicateOf: "mem-winner" }, ["default"]),
915
+ /duplicateOf memory is not active: mem-winner \(forgotten\)/,
916
+ );
917
+ await assert.rejects(
918
+ () => planner.plan({ text: "no target", duplicateOf: "mem-winner" }, ["default"]),
919
+ /exactly one targetIds entry/,
920
+ );
921
+ await assert.rejects(
922
+ () => planner.plan(
923
+ { text: "two targets", targetIds: ["mem-loser", "mem-winner"], duplicateOf: "mem-winner" },
924
+ ["default"],
925
+ ),
926
+ /exactly one targetIds entry/,
927
+ );
928
+ assert.equal(state.classifyCalls ?? 0, 0);
929
+ });
930
+ });
@@ -7,9 +7,11 @@
7
7
  * error, rule 34). Else: search via the injected search path scoped to
8
8
  * the caller's readable namespaces, plus 1-hop entity-graph neighbors of
9
9
  * top hits. Cap candidates at `maxAffected`.
10
- * 2. CLASSIFY + DRAFT via one LLM call (Responses API only, gotcha 1). On
11
- * LLM failure → deterministic fallback plan (rule 13): classification
12
- * `outdated`, `confidence: 0`, empty actions, warning set.
10
+ * 2. CLASSIFY + DRAFT via one LLM call (Responses API only, gotcha 1),
11
+ * unless `duplicateOf` is set — that path skips the LLM and drafts one
12
+ * recoverable supersede. On LLM failure → deterministic fallback plan
13
+ * (rule 13): classification `outdated`, `confidence: 0`, empty actions.
14
+ * A caller abort throws instead of falling back.
13
15
  * 3. RENDER DIFF by materializing what each action would do, using the
14
16
  * injected diff renderer (page-versioning — reuse, don't fork).
15
17
  * 4. PERSIST the plan atomically under
@@ -81,6 +83,16 @@ export interface PlannerDeps {
81
83
  targetIds: readonly string[];
82
84
  namespaces: readonly string[];
83
85
  }): Promise<PlannerCandidate[]>;
86
+ /**
87
+ * Inspect one memory for duplicate-mode validation. Absent means "not
88
+ * found" (including outside the readable namespaces). A missing method
89
+ * falls back to {@link resolveTargets}, which reports inactive memories
90
+ * as not found.
91
+ */
92
+ inspectMemory?(request: {
93
+ memoryId: string;
94
+ namespaces: readonly string[];
95
+ }): Promise<PlannerMemoryInspection | null>;
84
96
  /**
85
97
  * One-hop entity-graph neighbors of the supplied candidates. Returns the
86
98
  * union (deduped by memoryId); neighbors not already in `seedIds` only.
@@ -99,6 +111,7 @@ export interface PlannerDeps {
99
111
  classifyAndDraft(request: {
100
112
  text: string;
101
113
  candidates: PlannerCandidate[];
114
+ abortSignal?: AbortSignal;
102
115
  }): Promise<LlmClassificationResult>;
103
116
  /**
104
117
  * Render a human-readable diff preview for the planned actions, using
@@ -120,6 +133,13 @@ export interface PlannerDeps {
120
133
  now(): Date;
121
134
  }
122
135
 
136
+ /** One memory inspected for an explicit duplicate supersede. */
137
+ export interface PlannerMemoryInspection {
138
+ candidate: PlannerCandidate;
139
+ /** Omitted status is treated as active (older files leave it unset). */
140
+ status?: string;
141
+ }
142
+
123
143
  /** Result of the single LLM classify+draft call. */
124
144
  export interface LlmClassificationResult {
125
145
  classification: CorrectionClassification;
@@ -177,10 +197,15 @@ export class CorrectionPlanner {
177
197
  );
178
198
  }
179
199
 
180
- // 1. LOCATE
181
- const located = await this.locateCandidates(cleaned, namespaces, abortSignal);
200
+ // 1. LOCATE — duplicateOf skips search and the classifier.
201
+ const explicitDuplicate = cleaned.duplicateOf
202
+ ? await this.planExplicitDuplicate(cleaned, namespaces, abortSignal)
203
+ : null;
204
+ const located = explicitDuplicate
205
+ ? { candidates: [explicitDuplicate.loser] }
206
+ : await this.locateCandidates(cleaned, namespaces, abortSignal);
182
207
  throwIfAborted(abortSignal, "correction planning aborted");
183
- if (located.candidates.length === 0) {
208
+ if (!explicitDuplicate && located.candidates.length === 0) {
184
209
  // No candidates AND no explicit targets → empty plan is valid (the user
185
210
  // can still discard). But explicit-target-not-found was already raised
186
211
  // inside locateCandidates (rule 34).
@@ -191,10 +216,13 @@ export class CorrectionPlanner {
191
216
  }
192
217
 
193
218
  // 2. CLASSIFY + DRAFT
194
- const llm = await this.deps.classifyAndDraft({
195
- text: cleaned.text,
196
- candidates: located.candidates,
197
- });
219
+ const llm = explicitDuplicate
220
+ ? explicitDuplicate.llm
221
+ : await this.deps.classifyAndDraft({
222
+ text: cleaned.text,
223
+ candidates: located.candidates,
224
+ abortSignal,
225
+ });
198
226
  throwIfAborted(abortSignal, "correction planning aborted");
199
227
 
200
228
  // Validate every action shape before persisting — the adapter is trusted
@@ -235,6 +263,7 @@ export class CorrectionPlanner {
235
263
  // absent from the candidate set and warn.
236
264
  const candidateIds = new Set(located.candidates.map((c) => c.memoryId));
237
265
  if (llm.actions.length > 0 && candidateIds.size > 0) {
266
+ const explicitWinner = cleaned.duplicateOf;
238
267
  const filtered = llm.actions.filter((action) => {
239
268
  const id =
240
269
  action.kind === "supersede"
@@ -242,7 +271,19 @@ export class CorrectionPlanner {
242
271
  : action.kind === "edit" || action.kind === "retract" || action.kind === "rescope"
243
272
  ? action.memoryId
244
273
  : null;
245
- return id === null || candidateIds.has(id);
274
+ if (id !== null && !candidateIds.has(id)) return false;
275
+ // A model-picked winner must be a located candidate. duplicateOf is
276
+ // the exception: that winner was validated as an active memory and is
277
+ // intentionally not part of the mutated set.
278
+ if (
279
+ action.kind === "supersede" &&
280
+ action.winnerId &&
281
+ action.winnerId !== explicitWinner &&
282
+ !candidateIds.has(action.winnerId)
283
+ ) {
284
+ return false;
285
+ }
286
+ return true;
246
287
  });
247
288
  if (filtered.length < llm.actions.length) {
248
289
  const dropped = llm.actions.length - filtered.length;
@@ -290,6 +331,71 @@ export class CorrectionPlanner {
290
331
  return this.persist(plan, abortSignal);
291
332
  }
292
333
 
334
+ private async planExplicitDuplicate(
335
+ request: CorrectionRequest,
336
+ namespaces: readonly string[],
337
+ abortSignal?: AbortSignal,
338
+ ): Promise<{ loser: PlannerCandidate; llm: LlmClassificationResult }> {
339
+ const winnerId = request.duplicateOf;
340
+ if (!winnerId || !request.targetIds || request.targetIds.length !== 1) {
341
+ throw new CorrectionContractError(
342
+ "CorrectionRequest.duplicateOf requires exactly one targetIds entry (the duplicate memory).",
343
+ );
344
+ }
345
+ if (request.targetIds[0] === winnerId) {
346
+ throw new CorrectionContractError(
347
+ "CorrectionRequest.duplicateOf must be a different memory from targetIds.",
348
+ );
349
+ }
350
+ const loserId = request.targetIds[0];
351
+ const loser = await this.inspectDuplicateParty(loserId, namespaces, "target");
352
+ throwIfAborted(abortSignal, "correction planning aborted");
353
+ await this.inspectDuplicateParty(winnerId, namespaces, "duplicateOf");
354
+ throwIfAborted(abortSignal, "correction planning aborted");
355
+ return {
356
+ loser,
357
+ llm: {
358
+ classification: "outdated",
359
+ confidence: 1,
360
+ actions: [{ kind: "supersede", loserId, winnerId }],
361
+ relevance: [{
362
+ memoryId: loserId,
363
+ why: `duplicate of ${winnerId}; recoverable supersede`,
364
+ }],
365
+ warnings: [],
366
+ },
367
+ };
368
+ }
369
+
370
+ private async inspectDuplicateParty(
371
+ memoryId: string,
372
+ namespaces: readonly string[],
373
+ role: "target" | "duplicateOf",
374
+ ): Promise<PlannerCandidate> {
375
+ const notFound = role === "target"
376
+ ? `target memory not found: ${memoryId}`
377
+ : `duplicateOf memory not found: ${memoryId}`;
378
+ const inactive = (status: string) => role === "target"
379
+ ? `target memory is not active: ${memoryId} (${status})`
380
+ : `duplicateOf memory is not active: ${memoryId} (${status})`;
381
+ if (this.deps.inspectMemory) {
382
+ const found = await this.deps.inspectMemory({ memoryId, namespaces });
383
+ if (!found) throw new CorrectionContractError(notFound);
384
+ const status = found.status && found.status.length > 0 ? found.status : "active";
385
+ if (status !== "active") throw new CorrectionContractError(inactive(status));
386
+ return found.candidate;
387
+ }
388
+ try {
389
+ const resolved = await this.deps.resolveTargets({ targetIds: [memoryId], namespaces });
390
+ const candidate = resolved.find((item) => item.memoryId === memoryId) ?? resolved[0];
391
+ if (!candidate) throw new CorrectionContractError(notFound);
392
+ return candidate;
393
+ } catch (err) {
394
+ if (err instanceof CorrectionContractError) throw new CorrectionContractError(notFound);
395
+ throw err;
396
+ }
397
+ }
398
+
293
399
  private async locateCandidates(
294
400
  request: CorrectionRequest,
295
401
  namespaces: readonly string[],
@@ -39,7 +39,7 @@ function extractToolNames(response: unknown): Set<string> {
39
39
  }
40
40
 
41
41
  interface MockCalls {
42
- planRequests: Array<{ text: string; targetIds?: string[]; principal?: string; abortSignal?: AbortSignal }>;
42
+ planRequests: Array<{ text: string; targetIds?: string[]; duplicateOf?: string; principal?: string; abortSignal?: AbortSignal }>;
43
43
  applyRequests: Array<{ planId: string; confirm: boolean; principal?: string; abortSignal?: AbortSignal }>;
44
44
  }
45
45
 
@@ -54,6 +54,7 @@ function makeMockService(
54
54
  req: {
55
55
  text: string;
56
56
  targetIds?: string[];
57
+ duplicateOf?: string;
57
58
  principal?: string;
58
59
  },
59
60
  opts?: { abortSignal?: AbortSignal },
@@ -128,6 +129,26 @@ test("tools/list gate: correction tools present when correctionVisible on (defau
128
129
  assert.equal(names.has("engram.memory_correct_apply"), true, "apply tool must be present by default");
129
130
  assert.equal(names.has("remnic_memory_correct_plan"), true, "plan alias must be present");
130
131
  assert.equal(names.has("remnic_memory_correct_apply"), true, "apply alias must be present");
132
+ const tools = (response as { result?: { tools?: Array<{ name?: string; inputSchema?: { properties?: Record<string, unknown> } }> } }).result?.tools ?? [];
133
+ const planTool = tools.find((tool) => tool.name === "engram.memory_correct_plan");
134
+ assert.ok(planTool?.inputSchema?.properties?.duplicateOf, "plan schema must advertise duplicateOf");
135
+ });
136
+
137
+ test("MCP surface: memory_correct_plan forwards duplicateOf", async () => {
138
+ const { service, calls } = makeMockService(STUB_PLAN, STUB_OUTCOME);
139
+ const server = new EngramMcpServer(service, { emitLegacyTools: true, correctionVisible: true });
140
+ await server.handleRequest({
141
+ jsonrpc: "2.0",
142
+ id: 2,
143
+ method: "tools/call",
144
+ params: {
145
+ name: "engram.memory_correct_plan",
146
+ arguments: { text: "duplicate", targetIds: ["mem-loser"], duplicateOf: "mem-winner" },
147
+ },
148
+ });
149
+ assert.equal(calls.planRequests.length, 1);
150
+ assert.deepEqual(calls.planRequests[0]?.targetIds, ["mem-loser"]);
151
+ assert.equal(calls.planRequests[0]?.duplicateOf, "mem-winner");
131
152
  });
132
153
 
133
154
  // ---------------------------------------------------------------------------