@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.
- package/dist/access-admin-ops-surface.js +62 -62
- package/dist/access-authorization-probe.js +63 -63
- package/dist/access-boundary.js +62 -62
- package/dist/access-cli.d.ts.map +1 -1
- package/dist/access-cli.js +146 -139
- package/dist/access-cli.js.map +1 -1
- package/dist/access-extraction-force-flush.js +62 -62
- package/dist/access-http-briefing.js +63 -63
- package/dist/access-http-query.js +64 -64
- package/dist/access-http.d.ts.map +1 -1
- package/dist/access-http.js +86 -85
- package/dist/access-identity-continuity-surface.js +62 -62
- package/dist/access-lcm-surface.js +62 -62
- package/dist/access-mcp-correction-tools.d.ts +10 -0
- package/dist/access-mcp-correction-tools.d.ts.map +1 -0
- package/dist/access-mcp-correction-tools.js +8 -0
- package/dist/access-mcp.d.ts.map +1 -1
- package/dist/access-mcp.js +76 -75
- package/dist/access-namespace-preflight.js +62 -62
- package/dist/access-observe-write-category.js +5 -5
- package/dist/access-observe-write-surface.js +62 -62
- package/dist/access-offline-manifest.js +8 -8
- package/dist/access-operations-batch.js +65 -65
- package/dist/access-operations.d.ts +1 -0
- package/dist/access-operations.d.ts.map +1 -1
- package/dist/access-operations.js +72 -72
- package/dist/access-recall-concurrency.js +62 -62
- package/dist/access-recall-diagnostics.js +2 -2
- package/dist/access-recall-response.js +62 -62
- package/dist/access-recall-surface.js +62 -62
- package/dist/access-service.d.ts.map +1 -1
- package/dist/access-service.js +62 -62
- package/dist/access-surface-catalog.js +64 -64
- package/dist/active-recall.js +31 -30
- package/dist/active-recall.js.map +1 -1
- package/dist/adapters/index.js +7 -7
- package/dist/adapters/registry.js +3 -3
- package/dist/{auto-sync-PAWBFQWL.js → auto-sync-FN7E3OTO.js} +6 -6
- package/dist/briefing.js +26 -26
- package/dist/buffer.js +8 -8
- package/dist/causal-consolidation.js +26 -26
- package/dist/{chunk-YQSWNFZE.js → chunk-2CP577K6.js} +2 -2
- package/dist/{chunk-6JIHC5AS.js → chunk-37HCPM56.js} +78 -67
- package/dist/chunk-37HCPM56.js.map +1 -0
- package/dist/{chunk-345MEMRU.js → chunk-3OUGSOJP.js} +2 -2
- package/dist/{chunk-CHI37EHP.js → chunk-4CWUZBPF.js} +8 -6
- package/dist/chunk-4CWUZBPF.js.map +1 -0
- package/dist/{chunk-G2STTRYB.js → chunk-4D2YH47Z.js} +2 -2
- package/dist/{chunk-IXYIW3MZ.js → chunk-4K2H4KTR.js} +5 -5
- package/dist/{chunk-UVVSOSHL.js → chunk-52CQKST6.js} +53 -53
- package/dist/{chunk-H7BZBSP6.js → chunk-52II7PB6.js} +4 -4
- package/dist/{chunk-BLHPADUX.js → chunk-5BPXMAUP.js} +22 -78
- package/dist/chunk-5BPXMAUP.js.map +1 -0
- package/dist/{chunk-ZS7RK6H6.js → chunk-5BY3IA2U.js} +16 -53
- package/dist/chunk-5BY3IA2U.js.map +1 -0
- package/dist/{chunk-2QZFZNXO.js → chunk-5CQO6LVM.js} +2 -2
- package/dist/{chunk-NLDMTXWV.js → chunk-5LNPW7NE.js} +2 -2
- package/dist/{chunk-WHTMVTU4.js → chunk-5X4ZJESN.js} +2 -2
- package/dist/{chunk-WI37MWRZ.js → chunk-63UNVGZW.js} +5 -5
- package/dist/{chunk-G3T6YMBO.js → chunk-64S6X4IU.js} +7 -7
- package/dist/{chunk-CFCEJNOE.js → chunk-6KHF6PAB.js} +2 -2
- package/dist/{chunk-5GINB5LC.js → chunk-6MKLMEBE.js} +1 -1
- package/dist/{chunk-5GINB5LC.js.map → chunk-6MKLMEBE.js.map} +1 -1
- package/dist/{chunk-35A7GCHG.js → chunk-6T42L5FI.js} +2 -2
- package/dist/{chunk-JDOBIFBK.js → chunk-7CXL25AA.js} +2 -2
- package/dist/{chunk-YLBE6PLR.js → chunk-7IGMKQWK.js} +2 -2
- package/dist/{chunk-CSLK7BN6.js → chunk-7LYRFI5A.js} +2 -2
- package/dist/{chunk-IB63OVUB.js → chunk-AJ6ORARY.js} +6 -6
- package/dist/{chunk-CGOKNQDK.js → chunk-ANZXWOYD.js} +2 -2
- package/dist/{chunk-5QXMESS7.js → chunk-AXC7UTLM.js} +2 -2
- package/dist/{chunk-RFIJRZ4K.js → chunk-B657ZGDH.js} +2 -2
- package/dist/{chunk-AW4QUFQF.js → chunk-BTNNL7ZW.js} +3 -2
- package/dist/{chunk-AW4QUFQF.js.map → chunk-BTNNL7ZW.js.map} +1 -1
- package/dist/{chunk-MY7B3HYC.js → chunk-CLCMDKTB.js} +2 -2
- package/dist/{chunk-QPWUG2S3.js → chunk-CYEK6Z3P.js} +4 -4
- package/dist/{chunk-IHCYCE6D.js → chunk-DJMVSUPK.js} +4 -4
- package/dist/{chunk-HP4CU2ZA.js → chunk-DST7HRSG.js} +2 -2
- package/dist/{chunk-IIFFC3RL.js → chunk-EDR6CVWZ.js} +2 -2
- package/dist/{chunk-AUWMKDKL.js → chunk-EMVJU7WC.js} +2 -2
- package/dist/{chunk-O6AYMAPJ.js → chunk-ENNC3RZ6.js} +2 -2
- package/dist/{chunk-2JRB3Z5N.js → chunk-EP5WXE2G.js} +3 -3
- package/dist/{chunk-QLK3SL7Q.js → chunk-ETSJD5IV.js} +131 -12
- package/dist/chunk-ETSJD5IV.js.map +1 -0
- package/dist/chunk-EWMRFW3F.js +38 -0
- package/dist/chunk-EWMRFW3F.js.map +1 -0
- package/dist/{chunk-I2KEH2LS.js → chunk-FC6RCOIU.js} +2 -2
- package/dist/{chunk-OZVHLBH6.js → chunk-FLV7M7CT.js} +2 -2
- package/dist/{chunk-Q6GI7HEC.js → chunk-FRH2UC7Q.js} +2 -2
- package/dist/{chunk-3SL52JKD.js → chunk-FZVROZKC.js} +5 -5
- package/dist/{chunk-2EZB2DL5.js → chunk-GE6HETIF.js} +3 -3
- package/dist/{chunk-2DHJTJIY.js → chunk-GEGUBU3D.js} +2 -2
- package/dist/{chunk-6AA7XFMO.js → chunk-GF4H3B3R.js} +8 -8
- package/dist/{chunk-NGOZH7R5.js → chunk-GVUW5XK5.js} +311 -142
- package/dist/chunk-GVUW5XK5.js.map +1 -0
- package/dist/{chunk-ME2LMFHF.js → chunk-HPAOMYKM.js} +92 -92
- package/dist/{chunk-4NRZ7BRS.js → chunk-I25RINZK.js} +2 -2
- package/dist/{chunk-XG4XXI66.js → chunk-I25RQA65.js} +2 -2
- package/dist/chunk-I56MTWE7.js +80 -0
- package/dist/chunk-I56MTWE7.js.map +1 -0
- package/dist/{chunk-EJJMDLVB.js → chunk-IJ4FEGZR.js} +2 -2
- package/dist/{chunk-EUL3S3LE.js → chunk-J5WI4QRP.js} +4 -4
- package/dist/{chunk-LMMC55WM.js → chunk-JA54KIYD.js} +4 -4
- package/dist/{chunk-5FLFPTDK.js → chunk-JA72X46J.js} +2 -2
- package/dist/{chunk-6GU3TNJJ.js → chunk-JMF5Q4JY.js} +246 -49
- package/dist/chunk-JMF5Q4JY.js.map +1 -0
- package/dist/{chunk-IDSSDL4W.js → chunk-JXSYWTFK.js} +21 -4
- package/dist/{chunk-IDSSDL4W.js.map → chunk-JXSYWTFK.js.map} +1 -1
- package/dist/{chunk-NLYOOGZ7.js → chunk-KE5PWLI6.js} +3 -3
- package/dist/chunk-KIUVEMBI.js +49 -0
- package/dist/chunk-KIUVEMBI.js.map +1 -0
- package/dist/{chunk-4DDIWRT7.js → chunk-KQI2PKYP.js} +2 -2
- package/dist/{chunk-Y2ADLCB3.js → chunk-KSUOM3YU.js} +9 -9
- package/dist/{chunk-BTOHGPHC.js → chunk-KZEMFD6E.js} +3 -3
- package/dist/{chunk-NXVD46HT.js → chunk-L2VS27PZ.js} +4 -4
- package/dist/{chunk-MYYHACOW.js → chunk-L34W4W4I.js} +2 -2
- package/dist/{chunk-KRTIHXWC.js → chunk-L3PWXD73.js} +2 -2
- package/dist/{chunk-NQHSXHJP.js → chunk-L6GNS5GB.js} +2 -2
- package/dist/{chunk-44PBRUXB.js → chunk-M47Q6566.js} +2 -2
- package/dist/{chunk-E7FN5N5E.js → chunk-MQNOIH2C.js} +7 -7
- package/dist/{chunk-IDSZNHIP.js → chunk-MULNBSPP.js} +4 -4
- package/dist/{chunk-RKSGUMVL.js → chunk-NBQIFCJE.js} +2 -2
- package/dist/{chunk-I6WFBNA4.js → chunk-NHSVW7UT.js} +15 -15
- package/dist/{chunk-32HO62I4.js → chunk-OFEHITAZ.js} +2 -2
- package/dist/{chunk-PCO3P7WJ.js → chunk-OGVJXIHX.js} +2 -2
- package/dist/{chunk-RYN4AIRM.js → chunk-OZABSIOV.js} +4 -4
- package/dist/{chunk-HO7IQP7G.js → chunk-QMFKW4OU.js} +3 -3
- package/dist/{chunk-34G4U6HS.js → chunk-QRZMJ2WN.js} +2 -2
- package/dist/{chunk-RKDSELA3.js → chunk-QWWMS4AE.js} +1 -1
- package/dist/chunk-QWWMS4AE.js.map +1 -0
- package/dist/{chunk-GUD6FYQG.js → chunk-RSMIRLKM.js} +2 -2
- package/dist/{chunk-TNVZCLJU.js → chunk-S5TGRZWY.js} +2 -2
- package/dist/{chunk-O24EWFPQ.js → chunk-SNXBXYUE.js} +3 -3
- package/dist/{chunk-KW77LGDX.js → chunk-SUAX5JWY.js} +2 -2
- package/dist/{chunk-PNFWIB4X.js → chunk-SWRN6U7Q.js} +2 -2
- package/dist/{chunk-X6X4UASX.js → chunk-SZXPKUMN.js} +232 -83
- package/dist/chunk-SZXPKUMN.js.map +1 -0
- package/dist/{chunk-R4NGLTUS.js → chunk-TDMNADPI.js} +2 -2
- package/dist/{chunk-QCP75JGO.js → chunk-TFA6HPOA.js} +5 -5
- package/dist/{chunk-LI76ESJL.js → chunk-TZQOCQXB.js} +2 -2
- package/dist/{chunk-OV7EYM6M.js → chunk-U6OXJQEH.js} +12 -12
- package/dist/{chunk-OWHPA4JS.js → chunk-V32NHP2R.js} +2 -2
- package/dist/{chunk-JSUA4YFE.js → chunk-WG4TMZJ2.js} +2 -2
- package/dist/{chunk-DQTCGVFK.js → chunk-WHCD4Z3D.js} +2 -2
- package/dist/{chunk-GBA4N2SG.js → chunk-WRICYGYG.js} +27 -26
- package/dist/chunk-WRICYGYG.js.map +1 -0
- package/dist/{chunk-KATEWTQJ.js → chunk-X7J56GPG.js} +2 -2
- package/dist/{chunk-7KICGR5W.js → chunk-XPRAOTJY.js} +2 -2
- package/dist/{chunk-2NV7MMMM.js → chunk-XQSJXNWC.js} +2 -2
- package/dist/{chunk-22DYJLFN.js → chunk-XVC7JU2N.js} +39 -18
- package/dist/chunk-XVC7JU2N.js.map +1 -0
- package/dist/{chunk-RXT6UB25.js → chunk-YJAOYEWI.js} +2 -2
- package/dist/{chunk-L6DJJEFW.js → chunk-YJEUHGZZ.js} +2 -2
- package/dist/{chunk-AXFWMRKI.js → chunk-YQJCWR4R.js} +2 -2
- package/dist/cli.js +110 -108
- package/dist/coding/export-okf-codegraph.js +25 -25
- package/dist/compounding/engine.js +25 -25
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +31 -30
- package/dist/connectors/codex-materialize-runner.js +25 -25
- package/dist/connectors/index.js +25 -25
- package/dist/consolidation-provenance-check.js +2 -2
- package/dist/contradiction/index.js +7 -7
- package/dist/convergence-refresh.js +27 -27
- package/dist/conversation-index/backend.js +2 -2
- package/dist/corpus-read-cancellation.d.ts +7 -0
- package/dist/corpus-read-cancellation.d.ts.map +1 -1
- package/dist/corpus-read-cancellation.js +1 -1
- package/dist/corpus-watermark.js +27 -27
- package/dist/correction/correction-access-wiring.d.ts +6 -6
- package/dist/correction/correction-access-wiring.d.ts.map +1 -1
- package/dist/correction/correction-classify.d.ts +25 -0
- package/dist/correction/correction-classify.d.ts.map +1 -0
- package/dist/correction/correction-config.d.ts +11 -0
- package/dist/correction/correction-config.d.ts.map +1 -0
- package/dist/correction/correction-contract.d.ts +13 -1
- package/dist/correction/correction-contract.d.ts.map +1 -1
- package/dist/correction/correction-executor.d.ts +2 -0
- package/dist/correction/correction-executor.d.ts.map +1 -1
- package/dist/correction/correction-planner.d.ts +24 -3
- package/dist/correction/correction-planner.d.ts.map +1 -1
- package/dist/directory-sidecars.js +2 -2
- package/dist/entity-retrieval.js +25 -25
- package/dist/event-order-recall.js +2 -2
- package/dist/explicit-capture.js +2 -2
- package/dist/external-wiki-access.js +63 -63
- package/dist/extraction-span-materialize.js +2 -2
- package/dist/extraction.js +34 -34
- package/dist/harmonic-retrieval.js +25 -25
- package/dist/index.js +221 -218
- package/dist/lcm/index.js +3 -3
- package/dist/local-llm.d.ts.map +1 -1
- package/dist/local-llm.js +1 -1
- package/dist/location/index.js +28 -28
- package/dist/maintenance/archive-observations.js +2 -2
- package/dist/maintenance/atomic-file.d.ts +1 -1
- package/dist/maintenance/atomic-file.d.ts.map +1 -1
- package/dist/maintenance/atomic-file.js +1 -1
- package/dist/maintenance/memory-governance.js +25 -25
- package/dist/maintenance/migrate-observations.js +3 -3
- package/dist/maintenance/observation-ledger-utils.js +2 -2
- package/dist/maintenance/projection-rebuild-abort.d.ts +17 -0
- package/dist/maintenance/projection-rebuild-abort.d.ts.map +1 -0
- package/dist/maintenance/projection-timeline.d.ts +19 -0
- package/dist/maintenance/projection-timeline.d.ts.map +1 -0
- package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +25 -25
- package/dist/maintenance/rebuild-memory-projection-runner.d.ts +8 -0
- package/dist/maintenance/rebuild-memory-projection-runner.d.ts.map +1 -0
- package/dist/maintenance/rebuild-memory-projection-runner.js +113 -0
- package/dist/maintenance/rebuild-memory-projection-runner.js.map +1 -0
- package/dist/maintenance/rebuild-memory-projection.d.ts +8 -0
- package/dist/maintenance/rebuild-memory-projection.d.ts.map +1 -1
- package/dist/maintenance/rebuild-memory-projection.js +26 -26
- package/dist/maintenance/rebuild-observations.js +3 -3
- package/dist/memory-browse-operations.js +63 -63
- package/dist/memory-browse.js +7 -7
- package/dist/memory-subject.js +1 -1
- package/dist/namespaces/migrate.js +26 -26
- package/dist/namespaces/search.js +10 -10
- package/dist/namespaces/storage.js +25 -25
- package/dist/offline-sync-impression-drain.js +3 -3
- package/dist/operator-doctor-corpus.js +28 -28
- package/dist/operator-toolkit.js +41 -40
- package/dist/orchestration/full-corpus-heap-guard.d.ts +25 -0
- package/dist/orchestration/full-corpus-heap-guard.d.ts.map +1 -0
- package/dist/orchestration/maintenance.d.ts.map +1 -1
- package/dist/orchestration/maintenance.js +38 -36
- package/dist/orchestration/projection-rebuild-schedule.d.ts +40 -0
- package/dist/orchestration/projection-rebuild-schedule.d.ts.map +1 -1
- package/dist/orchestration/session-context.d.ts.map +1 -1
- package/dist/orchestrator.js +142 -139
- package/dist/projection-rebuild-config.d.ts +52 -0
- package/dist/projection-rebuild-config.d.ts.map +1 -0
- package/dist/projection-rebuild-config.js +14 -0
- package/dist/projection-rebuild-config.js.map +1 -0
- package/dist/qmd.js +4 -4
- package/dist/recall-explain-renderer.js +3 -3
- package/dist/recall-navigate-access.js +2 -2
- package/dist/recall-navigate-disclosure.js +2 -2
- package/dist/recall-navigate.js +2 -2
- package/dist/recall-navigation-operations.js +63 -63
- package/dist/recall-navigation.js +62 -62
- package/dist/recall-state-view-admission.js +7 -7
- package/dist/recall-state.js +2 -2
- package/dist/recall-why-service.js +8 -8
- package/dist/recall-xray-cli.js +4 -4
- package/dist/recall-xray-renderer.js +3 -3
- package/dist/recall-xray.js +2 -2
- package/dist/reconcile/manifest.js +3 -3
- package/dist/relevance.js +2 -2
- package/dist/{resolution-RSG4HZTL.js → resolution-TV3F7ZTH.js} +7 -7
- package/dist/resolution-TV3F7ZTH.js.map +1 -0
- package/dist/resume-bundles.js +32 -31
- package/dist/salvage-envelope.js +1 -1
- package/dist/search/factory.js +9 -9
- package/dist/search/index.js +17 -17
- package/dist/semantic-consolidation.js +31 -31
- package/dist/semantic-rule-promotion.js +25 -25
- package/dist/semantic-rule-verifier.js +25 -25
- package/dist/source-agent-qualifier.js +29 -29
- package/dist/standup/build.js +25 -25
- package/dist/storage/memory-lifecycle-ledger-access.d.ts +5 -3
- package/dist/storage/memory-lifecycle-ledger-access.d.ts.map +1 -1
- package/dist/storage/memory-read-store.d.ts +2 -2
- package/dist/storage/memory-read-store.d.ts.map +1 -1
- package/dist/storage.d.ts +4 -2
- package/dist/storage.d.ts.map +1 -1
- package/dist/storage.js +24 -24
- package/dist/subject-config.js +1 -1
- package/dist/summarizer.js +3 -3
- package/dist/support-passport/index.js +78 -78
- package/dist/transfer/export-okf.js +26 -26
- package/dist/types.d.ts +8 -40
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +1 -1
- package/dist/verified-recall.js +25 -25
- package/dist/who-knows-cli.js +26 -26
- package/dist/who-knows.js +25 -25
- package/dist/write-envelope.js +1 -1
- package/package.json +2 -2
- package/src/access-cli.ts +4 -0
- package/src/access-http.ts +1 -0
- package/src/access-mcp-correction-tools.ts +56 -0
- package/src/access-mcp.ts +4 -49
- package/src/access-operations.ts +3 -0
- package/src/access-service.ts +12 -21
- package/src/config.test.ts +35 -0
- package/src/config.ts +6 -65
- package/src/corpus-read-cancellation.ts +7 -0
- package/src/correction/correction-access-wiring.ts +56 -16
- package/src/correction/correction-classify.test.ts +185 -0
- package/src/correction/correction-classify.ts +157 -0
- package/src/correction/correction-config.ts +93 -0
- package/src/correction/correction-contract.ts +61 -14
- package/src/correction/correction-executor.test.ts +102 -2
- package/src/correction/correction-executor.ts +19 -1
- package/src/correction/correction-planner.test.ts +141 -0
- package/src/correction/correction-planner.ts +117 -11
- package/src/correction/correction-surfaces.test.ts +22 -1
- package/src/local-llm-qos.test.ts +36 -0
- package/src/local-llm.ts +20 -3
- package/src/maintenance/atomic-file.ts +2 -0
- package/src/maintenance/projection-rebuild-abort.ts +40 -0
- package/src/maintenance/projection-timeline.ts +209 -0
- package/src/maintenance/rebuild-memory-projection-runner.ts +94 -0
- package/src/maintenance/rebuild-memory-projection.memory.test.ts +159 -0
- package/src/maintenance/rebuild-memory-projection.retain-raw.test.ts +202 -0
- package/src/maintenance/rebuild-memory-projection.ts +63 -91
- package/src/orchestration/full-corpus-heap-guard.ts +38 -0
- package/src/orchestration/maintenance.test.ts +2 -2
- package/src/orchestration/maintenance.ts +2 -0
- package/src/orchestration/projection-rebuild-schedule.test.ts +498 -0
- package/src/orchestration/projection-rebuild-schedule.ts +280 -9
- package/src/orchestration/session-context.ts +0 -15
- package/src/projection-rebuild-config.ts +82 -0
- package/src/recall-handles-wiring.test.ts +24 -0
- package/src/storage/memory-lifecycle-ledger-access.ts +61 -33
- package/src/storage/memory-read-store.ts +10 -4
- package/src/storage.ts +45 -33
- package/src/types.ts +8 -41
- package/dist/chunk-22DYJLFN.js.map +0 -1
- package/dist/chunk-6GU3TNJJ.js.map +0 -1
- package/dist/chunk-6JIHC5AS.js.map +0 -1
- package/dist/chunk-BLHPADUX.js.map +0 -1
- package/dist/chunk-CHI37EHP.js.map +0 -1
- package/dist/chunk-GBA4N2SG.js.map +0 -1
- package/dist/chunk-NGOZH7R5.js.map +0 -1
- package/dist/chunk-QLK3SL7Q.js.map +0 -1
- package/dist/chunk-RKDSELA3.js.map +0 -1
- package/dist/chunk-X6X4UASX.js.map +0 -1
- package/dist/chunk-ZS7RK6H6.js.map +0 -1
- /package/dist/{resolution-RSG4HZTL.js.map → access-mcp-correction-tools.js.map} +0 -0
- /package/dist/{auto-sync-PAWBFQWL.js.map → auto-sync-FN7E3OTO.js.map} +0 -0
- /package/dist/{chunk-YQSWNFZE.js.map → chunk-2CP577K6.js.map} +0 -0
- /package/dist/{chunk-345MEMRU.js.map → chunk-3OUGSOJP.js.map} +0 -0
- /package/dist/{chunk-G2STTRYB.js.map → chunk-4D2YH47Z.js.map} +0 -0
- /package/dist/{chunk-IXYIW3MZ.js.map → chunk-4K2H4KTR.js.map} +0 -0
- /package/dist/{chunk-UVVSOSHL.js.map → chunk-52CQKST6.js.map} +0 -0
- /package/dist/{chunk-H7BZBSP6.js.map → chunk-52II7PB6.js.map} +0 -0
- /package/dist/{chunk-2QZFZNXO.js.map → chunk-5CQO6LVM.js.map} +0 -0
- /package/dist/{chunk-NLDMTXWV.js.map → chunk-5LNPW7NE.js.map} +0 -0
- /package/dist/{chunk-WHTMVTU4.js.map → chunk-5X4ZJESN.js.map} +0 -0
- /package/dist/{chunk-WI37MWRZ.js.map → chunk-63UNVGZW.js.map} +0 -0
- /package/dist/{chunk-G3T6YMBO.js.map → chunk-64S6X4IU.js.map} +0 -0
- /package/dist/{chunk-CFCEJNOE.js.map → chunk-6KHF6PAB.js.map} +0 -0
- /package/dist/{chunk-35A7GCHG.js.map → chunk-6T42L5FI.js.map} +0 -0
- /package/dist/{chunk-JDOBIFBK.js.map → chunk-7CXL25AA.js.map} +0 -0
- /package/dist/{chunk-YLBE6PLR.js.map → chunk-7IGMKQWK.js.map} +0 -0
- /package/dist/{chunk-CSLK7BN6.js.map → chunk-7LYRFI5A.js.map} +0 -0
- /package/dist/{chunk-IB63OVUB.js.map → chunk-AJ6ORARY.js.map} +0 -0
- /package/dist/{chunk-CGOKNQDK.js.map → chunk-ANZXWOYD.js.map} +0 -0
- /package/dist/{chunk-5QXMESS7.js.map → chunk-AXC7UTLM.js.map} +0 -0
- /package/dist/{chunk-RFIJRZ4K.js.map → chunk-B657ZGDH.js.map} +0 -0
- /package/dist/{chunk-MY7B3HYC.js.map → chunk-CLCMDKTB.js.map} +0 -0
- /package/dist/{chunk-QPWUG2S3.js.map → chunk-CYEK6Z3P.js.map} +0 -0
- /package/dist/{chunk-IHCYCE6D.js.map → chunk-DJMVSUPK.js.map} +0 -0
- /package/dist/{chunk-HP4CU2ZA.js.map → chunk-DST7HRSG.js.map} +0 -0
- /package/dist/{chunk-IIFFC3RL.js.map → chunk-EDR6CVWZ.js.map} +0 -0
- /package/dist/{chunk-AUWMKDKL.js.map → chunk-EMVJU7WC.js.map} +0 -0
- /package/dist/{chunk-O6AYMAPJ.js.map → chunk-ENNC3RZ6.js.map} +0 -0
- /package/dist/{chunk-2JRB3Z5N.js.map → chunk-EP5WXE2G.js.map} +0 -0
- /package/dist/{chunk-I2KEH2LS.js.map → chunk-FC6RCOIU.js.map} +0 -0
- /package/dist/{chunk-OZVHLBH6.js.map → chunk-FLV7M7CT.js.map} +0 -0
- /package/dist/{chunk-Q6GI7HEC.js.map → chunk-FRH2UC7Q.js.map} +0 -0
- /package/dist/{chunk-3SL52JKD.js.map → chunk-FZVROZKC.js.map} +0 -0
- /package/dist/{chunk-2EZB2DL5.js.map → chunk-GE6HETIF.js.map} +0 -0
- /package/dist/{chunk-2DHJTJIY.js.map → chunk-GEGUBU3D.js.map} +0 -0
- /package/dist/{chunk-6AA7XFMO.js.map → chunk-GF4H3B3R.js.map} +0 -0
- /package/dist/{chunk-ME2LMFHF.js.map → chunk-HPAOMYKM.js.map} +0 -0
- /package/dist/{chunk-4NRZ7BRS.js.map → chunk-I25RINZK.js.map} +0 -0
- /package/dist/{chunk-XG4XXI66.js.map → chunk-I25RQA65.js.map} +0 -0
- /package/dist/{chunk-EJJMDLVB.js.map → chunk-IJ4FEGZR.js.map} +0 -0
- /package/dist/{chunk-EUL3S3LE.js.map → chunk-J5WI4QRP.js.map} +0 -0
- /package/dist/{chunk-LMMC55WM.js.map → chunk-JA54KIYD.js.map} +0 -0
- /package/dist/{chunk-5FLFPTDK.js.map → chunk-JA72X46J.js.map} +0 -0
- /package/dist/{chunk-NLYOOGZ7.js.map → chunk-KE5PWLI6.js.map} +0 -0
- /package/dist/{chunk-4DDIWRT7.js.map → chunk-KQI2PKYP.js.map} +0 -0
- /package/dist/{chunk-Y2ADLCB3.js.map → chunk-KSUOM3YU.js.map} +0 -0
- /package/dist/{chunk-BTOHGPHC.js.map → chunk-KZEMFD6E.js.map} +0 -0
- /package/dist/{chunk-NXVD46HT.js.map → chunk-L2VS27PZ.js.map} +0 -0
- /package/dist/{chunk-MYYHACOW.js.map → chunk-L34W4W4I.js.map} +0 -0
- /package/dist/{chunk-KRTIHXWC.js.map → chunk-L3PWXD73.js.map} +0 -0
- /package/dist/{chunk-NQHSXHJP.js.map → chunk-L6GNS5GB.js.map} +0 -0
- /package/dist/{chunk-44PBRUXB.js.map → chunk-M47Q6566.js.map} +0 -0
- /package/dist/{chunk-E7FN5N5E.js.map → chunk-MQNOIH2C.js.map} +0 -0
- /package/dist/{chunk-IDSZNHIP.js.map → chunk-MULNBSPP.js.map} +0 -0
- /package/dist/{chunk-RKSGUMVL.js.map → chunk-NBQIFCJE.js.map} +0 -0
- /package/dist/{chunk-I6WFBNA4.js.map → chunk-NHSVW7UT.js.map} +0 -0
- /package/dist/{chunk-32HO62I4.js.map → chunk-OFEHITAZ.js.map} +0 -0
- /package/dist/{chunk-PCO3P7WJ.js.map → chunk-OGVJXIHX.js.map} +0 -0
- /package/dist/{chunk-RYN4AIRM.js.map → chunk-OZABSIOV.js.map} +0 -0
- /package/dist/{chunk-HO7IQP7G.js.map → chunk-QMFKW4OU.js.map} +0 -0
- /package/dist/{chunk-34G4U6HS.js.map → chunk-QRZMJ2WN.js.map} +0 -0
- /package/dist/{chunk-GUD6FYQG.js.map → chunk-RSMIRLKM.js.map} +0 -0
- /package/dist/{chunk-TNVZCLJU.js.map → chunk-S5TGRZWY.js.map} +0 -0
- /package/dist/{chunk-O24EWFPQ.js.map → chunk-SNXBXYUE.js.map} +0 -0
- /package/dist/{chunk-KW77LGDX.js.map → chunk-SUAX5JWY.js.map} +0 -0
- /package/dist/{chunk-PNFWIB4X.js.map → chunk-SWRN6U7Q.js.map} +0 -0
- /package/dist/{chunk-R4NGLTUS.js.map → chunk-TDMNADPI.js.map} +0 -0
- /package/dist/{chunk-QCP75JGO.js.map → chunk-TFA6HPOA.js.map} +0 -0
- /package/dist/{chunk-LI76ESJL.js.map → chunk-TZQOCQXB.js.map} +0 -0
- /package/dist/{chunk-OV7EYM6M.js.map → chunk-U6OXJQEH.js.map} +0 -0
- /package/dist/{chunk-OWHPA4JS.js.map → chunk-V32NHP2R.js.map} +0 -0
- /package/dist/{chunk-JSUA4YFE.js.map → chunk-WG4TMZJ2.js.map} +0 -0
- /package/dist/{chunk-DQTCGVFK.js.map → chunk-WHCD4Z3D.js.map} +0 -0
- /package/dist/{chunk-KATEWTQJ.js.map → chunk-X7J56GPG.js.map} +0 -0
- /package/dist/{chunk-7KICGR5W.js.map → chunk-XPRAOTJY.js.map} +0 -0
- /package/dist/{chunk-2NV7MMMM.js.map → chunk-XQSJXNWC.js.map} +0 -0
- /package/dist/{chunk-RXT6UB25.js.map → chunk-YJAOYEWI.js.map} +0 -0
- /package/dist/{chunk-L6DJJEFW.js.map → chunk-YJEUHGZZ.js.map} +0 -0
- /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
|
-
/**
|
|
107
|
-
|
|
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
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
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
|
-
...(
|
|
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
|
-
|
|
381
|
-
|
|
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 {
|
|
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
|
-
{
|
|
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)
|
|
11
|
-
*
|
|
12
|
-
*
|
|
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
|
|
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 =
|
|
195
|
-
|
|
196
|
-
|
|
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
|
-
|
|
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
|
// ---------------------------------------------------------------------------
|