@remnic/core 9.69.22 → 9.69.24
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/abstraction-nodes.js +3 -2
- package/dist/access-admin-ops-surface.d.ts +14 -12
- package/dist/access-admin-ops-surface.js +94 -91
- package/dist/access-authorization-probe.d.ts +14 -12
- package/dist/access-authorization-probe.js +95 -92
- package/dist/access-boundary.d.ts +14 -12
- package/dist/access-boundary.js +94 -91
- package/dist/access-citation-namespace.js +3 -3
- package/dist/access-cli.js +200 -195
- package/dist/access-cli.js.map +1 -1
- package/dist/access-coding-context-resolution.d.ts +2 -1
- package/dist/access-extraction-force-flush.d.ts +14 -12
- package/dist/access-extraction-force-flush.js +94 -91
- package/dist/access-health-types.d.ts +2 -1
- package/dist/access-http-briefing.d.ts +156 -0
- package/dist/access-http-briefing.js +233 -0
- package/dist/access-http-lcm-compaction.d.ts +14 -12
- package/dist/access-http-lifecycle-flush.d.ts +14 -12
- package/dist/access-http-offline-stream.d.ts +14 -12
- package/dist/access-http-query.d.ts +3 -2
- package/dist/access-http-query.js +96 -93
- package/dist/access-http.d.ts +15 -13
- package/dist/access-http.js +119 -114
- package/dist/access-identity-continuity-surface.d.ts +12 -10
- package/dist/access-identity-continuity-surface.js +94 -91
- package/dist/access-lcm-surface.d.ts +14 -12
- package/dist/access-lcm-surface.js +94 -91
- package/dist/access-mcp.d.ts +14 -12
- package/dist/access-mcp.js +105 -101
- package/dist/access-memory-search-fanout.d.ts +3 -2
- package/dist/{access-namespace-preflight-BMHhxoL9.d.ts → access-namespace-preflight-BsvmyENS.d.ts} +1 -1
- package/dist/access-namespace-preflight.d.ts +4 -3
- package/dist/access-namespace-preflight.js +94 -91
- package/dist/access-observe-transcript.d.ts +12 -10
- package/dist/access-observe-write-surface.d.ts +14 -12
- package/dist/access-observe-write-surface.js +94 -91
- package/dist/access-offline-manifest.d.ts +12 -10
- package/dist/access-offline-manifest.js +15 -15
- package/dist/access-operations-batch.js +99 -95
- package/dist/access-operations.d.ts +14 -12
- package/dist/access-operations.js +104 -100
- package/dist/access-recall-concurrency.d.ts +14 -12
- package/dist/access-recall-concurrency.js +94 -91
- package/dist/access-recall-response.d.ts +14 -12
- package/dist/access-recall-response.js +94 -91
- package/dist/access-recall-surface.d.ts +14 -12
- package/dist/access-recall-surface.js +94 -91
- package/dist/access-schema.js +8 -7
- package/dist/{access-service-BlsNXFwx.d.ts → access-service-DWdY9Mlk.d.ts} +7 -7
- package/dist/access-service-helpers.d.ts +14 -12
- package/dist/access-service.d.ts +14 -12
- package/dist/access-service.js +94 -91
- package/dist/access-surface-catalog.d.ts +14 -12
- package/dist/access-surface-catalog.js +97 -93
- package/dist/access-surface-catalog.js.map +1 -1
- package/dist/access-wearables-meetings-surface.d.ts +4 -3
- package/dist/action-confidence.d.ts +2 -1
- package/dist/active-memory-bridge.d.ts +2 -1
- package/dist/active-recall.d.ts +2 -1
- package/dist/active-recall.js +16 -13
- package/dist/active-recall.js.map +1 -1
- package/dist/adapters/index.js +7 -7
- package/dist/adapters/registry.js +3 -3
- package/dist/ambient-provenance.d.ts +2 -1
- package/dist/artifact-search.d.ts +2 -1
- package/dist/{auto-sync-GRUJFD2F.js → auto-sync-WNGG2GC2.js} +9 -9
- package/dist/behavior-learner.d.ts +2 -1
- package/dist/behavior-signals.d.ts +2 -1
- package/dist/bootstrap.d.ts +12 -10
- package/dist/briefing-window.d.ts +3 -2
- package/dist/briefing.d.ts +3 -2
- package/dist/briefing.js +36 -36
- package/dist/buffer-surprise-report.d.ts +2 -1
- package/dist/buffer-turn-helpers.d.ts +2 -1
- package/dist/buffer.d.ts +3 -2
- package/dist/buffer.js +13 -13
- package/dist/bulk-import/index.d.ts +4 -3
- package/dist/calibration.d.ts +2 -1
- package/dist/calibration.js +2 -1
- package/dist/calibration.js.map +1 -1
- package/dist/capabilities.d.ts +2 -1
- package/dist/{capsule-crypto-7FJQINUR.js → capsule-crypto-YO5QJ6L3.js} +2 -2
- package/dist/{capsule-merge-SEHERWWP.js → capsule-merge-IPG4IOFP.js} +5 -5
- package/dist/{catalog-C1qHsGew.d.ts → catalog-CDZLyiXL.d.ts} +1 -1
- package/dist/causal-behavior.d.ts +2 -1
- package/dist/causal-behavior.js +4 -3
- package/dist/causal-behavior.js.map +1 -1
- package/dist/causal-chain.js +4 -3
- package/dist/causal-consolidation.d.ts +2 -1
- package/dist/causal-consolidation.js +42 -42
- package/dist/causal-retrieval.js +4 -3
- package/dist/causal-retrieval.js.map +1 -1
- package/dist/causal-trajectory-graph.d.ts +5 -2
- package/dist/causal-trajectory-graph.js +6 -2
- package/dist/causal-trajectory-graph.js.map +1 -1
- package/dist/causal-trajectory.js +3 -2
- package/dist/{chunk-KTVSPFCQ.js → chunk-22FVR2KE.js} +2 -2
- package/dist/{chunk-7Y5V3LAP.js → chunk-27QVNSQ3.js} +2 -2
- package/dist/{chunk-R7LY4YKF.js → chunk-2EKJDQZB.js} +2 -2
- package/dist/{chunk-FCATYKQA.js → chunk-2IOHTUP3.js} +263 -13
- package/dist/chunk-2IOHTUP3.js.map +1 -0
- package/dist/{chunk-HOUST5UX.js → chunk-2ITE4C2Z.js} +2 -2
- package/dist/{chunk-JOYC4RYC.js → chunk-2JVJ7UHC.js} +2 -2
- package/dist/{chunk-X2NFFBTO.js → chunk-2L5FSNPC.js} +7 -7
- package/dist/{chunk-DUAJKRR2.js → chunk-2SGLMP2E.js} +2 -2
- package/dist/{chunk-6FUMA6AA.js → chunk-2Z4GS5E6.js} +2 -2
- package/dist/{chunk-G6MXN2G5.js → chunk-37KUQPX4.js} +6 -6
- package/dist/{chunk-DDQQY2E3.js → chunk-3BTFBQLO.js} +7 -7
- package/dist/chunk-3BTFBQLO.js.map +1 -0
- package/dist/{chunk-UR6PBX2J.js → chunk-3K7V3HE6.js} +35 -60
- package/dist/chunk-3K7V3HE6.js.map +1 -0
- package/dist/{chunk-WKAXBXVA.js → chunk-3QQTXYZ6.js} +2 -2
- package/dist/{chunk-DJUKBGAD.js → chunk-46EST7JO.js} +5 -5
- package/dist/chunk-46EST7JO.js.map +1 -0
- package/dist/{chunk-4ZWCQJNM.js → chunk-46WM6JX2.js} +5 -5
- package/dist/{chunk-CASWJ2HM.js → chunk-46ZPIGWI.js} +3 -3
- package/dist/{chunk-HTIIXNJQ.js → chunk-4FIH6MPS.js} +6 -3
- package/dist/chunk-4FIH6MPS.js.map +1 -0
- package/dist/{chunk-MCA2X4WH.js → chunk-4IHBN6WU.js} +2 -2
- package/dist/{chunk-W7IGLX4G.js → chunk-4O6WRBGE.js} +7 -7
- package/dist/chunk-4O6WRBGE.js.map +1 -0
- package/dist/{chunk-FB7LAJQM.js → chunk-4TKJGOCJ.js} +2 -2
- package/dist/{chunk-H7BZBSP6.js → chunk-52II7PB6.js} +4 -4
- package/dist/{chunk-N4Y5T6VD.js → chunk-54WHLA65.js} +4 -4
- package/dist/chunk-56Y6EKZN.js +24 -0
- package/dist/chunk-56Y6EKZN.js.map +1 -0
- package/dist/{chunk-YVLENSNS.js → chunk-5ATKSPEQ.js} +2 -2
- package/dist/{chunk-OLT3YTDH.js → chunk-5FAGCOZD.js} +37 -24
- package/dist/chunk-5FAGCOZD.js.map +1 -0
- package/dist/{chunk-ZVZHG7AI.js → chunk-5UBH37SL.js} +3 -3
- package/dist/{chunk-N3WZU76A.js → chunk-66OJSCI6.js} +2 -2
- package/dist/{chunk-D35XLYQ7.js → chunk-6CVNSDCZ.js} +11 -11
- package/dist/{chunk-KA2HZCMG.js → chunk-6NXSYYGP.js} +2 -2
- package/dist/{chunk-2HIJHXB3.js → chunk-6WHJ2EAB.js} +2 -2
- package/dist/{chunk-ZZM476NV.js → chunk-6XL73HQ4.js} +4 -4
- package/dist/{chunk-EJ5RKUXW.js → chunk-AQ7SYDVO.js} +2 -2
- package/dist/{chunk-2Z3DG5CQ.js → chunk-B3O3IRZN.js} +2 -2
- package/dist/{chunk-P77QX3ID.js → chunk-B6O2YLUR.js} +21 -20
- package/dist/chunk-B6O2YLUR.js.map +1 -0
- package/dist/{chunk-RACZB5HT.js → chunk-BD7JTBYP.js} +4 -4
- package/dist/{chunk-I762GW7V.js → chunk-BLJWYCNO.js} +5 -5
- package/dist/{chunk-ACPCA4NO.js → chunk-BMWN7B3Y.js} +6 -6
- package/dist/chunk-BMWN7B3Y.js.map +1 -0
- package/dist/{chunk-2NLLXCJG.js → chunk-BXLOS5AJ.js} +2 -2
- package/dist/{chunk-KK66P5GQ.js → chunk-C553ZTLF.js} +46 -48
- package/dist/chunk-C553ZTLF.js.map +1 -0
- package/dist/{chunk-GCASGYIO.js → chunk-C7HJ3NEI.js} +2 -2
- package/dist/{chunk-C24UZSFW.js → chunk-CEH2AFXG.js} +6 -6
- package/dist/{chunk-WVGXCBOC.js → chunk-D5F7TKMH.js} +2 -2
- package/dist/chunk-DGS34QSY.js +141 -0
- package/dist/chunk-DGS34QSY.js.map +1 -0
- package/dist/{chunk-X7Y7WX73.js → chunk-DQEMWVMT.js} +1 -1
- package/dist/{chunk-2OXIPF3A.js → chunk-DZD3WGCD.js} +4 -4
- package/dist/{chunk-UMEOXLIZ.js → chunk-E2P7TZTF.js} +5 -5
- package/dist/{chunk-6FC3IZ26.js → chunk-EBA5DA77.js} +2 -2
- package/dist/{chunk-N7JK2RKG.js → chunk-EOQ6FSQP.js} +2 -2
- package/dist/chunk-EUT2WCWS.js +54 -0
- package/dist/chunk-EUT2WCWS.js.map +1 -0
- package/dist/{chunk-YNDLCWXS.js → chunk-EZ25VE3G.js} +4 -4
- package/dist/{chunk-XDMI5VKE.js → chunk-FVOP3NT7.js} +2 -2
- package/dist/{chunk-NRLELHAY.js → chunk-G7NYVKD6.js} +2 -2
- package/dist/{chunk-B3W7GWQB.js → chunk-GHTSNGBZ.js} +2 -2
- package/dist/chunk-GKCXTSFF.js +17 -0
- package/dist/chunk-GKCXTSFF.js.map +1 -0
- package/dist/{chunk-LUDBXTBJ.js → chunk-H272ZPQ3.js} +5 -5
- package/dist/{chunk-SN4JXNLL.js → chunk-H3TKENU4.js} +2 -2
- package/dist/{chunk-4HYNMLT6.js → chunk-H4SEGIZY.js} +2 -2
- package/dist/{chunk-ACZN5ZXD.js → chunk-H54VWRPS.js} +13 -13
- package/dist/{chunk-HTP6XRUI.js → chunk-HM7RHKS4.js} +2 -2
- package/dist/{chunk-Z3SQEEB6.js → chunk-HWLIMQE2.js} +2 -2
- package/dist/{chunk-NQFCLAYT.js → chunk-ICKJ52PE.js} +99 -322
- package/dist/chunk-ICKJ52PE.js.map +1 -0
- package/dist/{chunk-2QSZNTDO.js → chunk-JBPKEARU.js} +7 -7
- package/dist/{chunk-DK6GMO6D.js → chunk-JEC4DVM3.js} +16 -16
- package/dist/chunk-JEC4DVM3.js.map +1 -0
- package/dist/{chunk-ET2CNMLD.js → chunk-JN72EPOE.js} +2 -2
- package/dist/{chunk-U62OK7BB.js → chunk-JPKK466T.js} +188 -21
- package/dist/chunk-JPKK466T.js.map +1 -0
- package/dist/{chunk-JCCWJHOP.js → chunk-JYZSRNR7.js} +2 -2
- package/dist/{chunk-7D6HXODY.js → chunk-K7724CH2.js} +2 -2
- package/dist/{chunk-X33MJFD2.js → chunk-KAXCYM66.js} +3 -3
- package/dist/{chunk-7RRUHJPG.js → chunk-KB2G2I6Q.js} +3 -3
- package/dist/{chunk-ZXBTTMGZ.js → chunk-KEF46KL2.js} +5 -5
- package/dist/{chunk-5UZXUTVO.js → chunk-KNA3BXXL.js} +6 -2
- package/dist/chunk-KNA3BXXL.js.map +1 -0
- package/dist/{chunk-UWTDARPF.js → chunk-KTWG7CNY.js} +6 -6
- package/dist/{chunk-XQYTSZW3.js → chunk-L7Z57SID.js} +6 -6
- package/dist/{chunk-JMAG7WZ7.js → chunk-LO4UKPKX.js} +3 -3
- package/dist/{chunk-LDWIPYJ6.js → chunk-LS2FHUML.js} +3 -3
- package/dist/{chunk-GDB4J2H3.js → chunk-MPC7TYTZ.js} +2 -2
- package/dist/{chunk-2GF27PGH.js → chunk-MRPOZBJC.js} +3 -3
- package/dist/{chunk-BR3OOQZQ.js → chunk-MWN6QEI2.js} +2 -2
- package/dist/{chunk-NZGO2GKL.js → chunk-MY5EDR46.js} +4 -4
- package/dist/{chunk-LCMUZDBZ.js → chunk-N3ZOP7KC.js} +3 -3
- package/dist/{chunk-TVW4C7PV.js → chunk-NB3LRY4T.js} +6 -6
- package/dist/{chunk-QJCWK2A7.js → chunk-ND6IQGSZ.js} +2 -2
- package/dist/{chunk-NOB2T2FC.js → chunk-NHLLKEKK.js} +2 -2
- package/dist/{chunk-7GSSHAXC.js → chunk-NLYVH66G.js} +6 -6
- package/dist/chunk-NLYVH66G.js.map +1 -0
- package/dist/{chunk-EJIWBTVM.js → chunk-O6ZNNVSP.js} +2 -2
- package/dist/{chunk-AG46P24C.js → chunk-OB3XMWX5.js} +4 -4
- package/dist/{chunk-NYRB7BKY.js → chunk-OEYZXDQI.js} +6 -6
- package/dist/chunk-OEYZXDQI.js.map +1 -0
- package/dist/{chunk-46QMN7I7.js → chunk-PAZY2ZKP.js} +6 -6
- package/dist/{chunk-EF4JUM55.js → chunk-PX4UMA5Z.js} +2 -2
- package/dist/chunk-QLK3SL7Q.js +242 -0
- package/dist/chunk-QLK3SL7Q.js.map +1 -0
- package/dist/{chunk-GGYDJJBO.js → chunk-QOOBICJF.js} +5 -5
- package/dist/chunk-QOOBICJF.js.map +1 -0
- package/dist/{chunk-NNZFPYYX.js → chunk-R7QY5ZVB.js} +2 -2
- package/dist/{chunk-RO2X2EYM.js → chunk-R7RTXEYG.js} +2 -2
- package/dist/{chunk-3LJPSRAN.js → chunk-RBCQVOVJ.js} +3 -3
- package/dist/{chunk-N4F7ZSSG.js → chunk-RC5DXXWC.js} +2 -2
- package/dist/{chunk-VLAQV4HT.js → chunk-RJYJ7KR7.js} +1636 -611
- package/dist/chunk-RJYJ7KR7.js.map +1 -0
- package/dist/{chunk-5CF2R4NS.js → chunk-RKBLRTNT.js} +2 -2
- package/dist/{chunk-5QZR2RA3.js → chunk-ROBHL3GL.js} +2 -2
- package/dist/{chunk-D5ZNOKR7.js → chunk-S7T2TY65.js} +3 -3
- package/dist/{chunk-C2JCVZWX.js → chunk-SCXURCVT.js} +25 -13
- package/dist/chunk-SCXURCVT.js.map +1 -0
- package/dist/{chunk-3LADWWMA.js → chunk-SJDQ5RSN.js} +3 -3
- package/dist/{chunk-45XVII3J.js → chunk-SMWTZLHE.js} +99 -89
- package/dist/{chunk-45XVII3J.js.map → chunk-SMWTZLHE.js.map} +1 -1
- package/dist/{chunk-XOQGUGJL.js → chunk-SO7SV523.js} +1 -1
- package/dist/{chunk-XOQGUGJL.js.map → chunk-SO7SV523.js.map} +1 -1
- package/dist/{chunk-CHWUICVY.js → chunk-SV4Z2S67.js} +4 -4
- package/dist/{chunk-JHP45LO5.js → chunk-SVXJLDDC.js} +9 -7
- package/dist/{chunk-JHP45LO5.js.map → chunk-SVXJLDDC.js.map} +1 -1
- package/dist/{chunk-SEF4IOFE.js → chunk-SWIKABKI.js} +2 -2
- package/dist/{chunk-JRRQ747T.js → chunk-TBXAY2SS.js} +13 -13
- package/dist/{chunk-J6A3CX5N.js → chunk-TG3FAM2S.js} +33 -8
- package/dist/{chunk-J6A3CX5N.js.map → chunk-TG3FAM2S.js.map} +1 -1
- package/dist/{chunk-XZBXINXZ.js → chunk-TNSCIPBQ.js} +4 -4
- package/dist/{chunk-S37NN23M.js → chunk-TVEONGBU.js} +2 -2
- package/dist/{chunk-5RPMTFB6.js → chunk-TWF35IC6.js} +3 -3
- package/dist/{chunk-BZTJEILZ.js → chunk-U6N5IBVY.js} +2 -2
- package/dist/{chunk-7TUB6JWA.js → chunk-UCKYTA2T.js} +3 -3
- package/dist/{chunk-PPOT6SPR.js → chunk-UNNPNBR2.js} +11 -11
- package/dist/{chunk-KQAFEZQX.js → chunk-VDX2J7OX.js} +2 -2
- package/dist/{chunk-J2R275FN.js → chunk-VVX5PZFS.js} +4 -4
- package/dist/{chunk-ME35HVDJ.js → chunk-VYCOJKSX.js} +2 -2
- package/dist/{chunk-F5UWQ4LX.js → chunk-W24T5YMW.js} +2 -2
- package/dist/{chunk-HDQUQBPW.js → chunk-W3K3HITK.js} +2 -2
- package/dist/{chunk-GDASG7NC.js → chunk-W4F3C7N7.js} +2 -2
- package/dist/{chunk-LFK623SF.js → chunk-WJHGDE7X.js} +5 -5
- package/dist/{chunk-DP4EBV7C.js → chunk-WMLEPWMO.js} +16 -16
- package/dist/chunk-WMLEPWMO.js.map +1 -0
- package/dist/{chunk-3Q6RD76J.js → chunk-WQZZDN5M.js} +5 -5
- package/dist/{chunk-ZJWDWPX6.js → chunk-X5K5TAQZ.js} +4 -4
- package/dist/{chunk-ZL3XX5LW.js → chunk-XM24CDZX.js} +3 -3
- package/dist/{chunk-JTWZKNUH.js → chunk-XOVKXJEP.js} +3 -3
- package/dist/{chunk-OGTKQ5MJ.js → chunk-XSGUMQQQ.js} +62 -50
- package/dist/chunk-XSGUMQQQ.js.map +1 -0
- package/dist/{chunk-X5YYSAZX.js → chunk-XUE46UYW.js} +3 -3
- package/dist/{chunk-DJORULH3.js → chunk-YRLHEEQT.js} +2 -2
- package/dist/{chunk-TA7UZZMW.js → chunk-Z3O53WZR.js} +2 -2
- package/dist/{chunk-4LG4CPOC.js → chunk-Z6COGHHN.js} +2 -2
- package/dist/{chunk-RE3H6Y2X.js → chunk-ZIW5JVRM.js} +2 -2
- package/dist/{cli-BNtvR-nU.d.ts → cli-DD0P-lu8.d.ts} +6 -6
- package/dist/cli.d.ts +16 -14
- package/dist/cli.js +154 -149
- package/dist/coding/export-okf-codegraph.d.ts +2 -1
- package/dist/coding/export-okf-codegraph.js +37 -37
- package/dist/coding/pre-action-gate.d.ts +2 -1
- package/dist/coding/pre-action-gate.js +4 -3
- package/dist/commitment-ledger.js +3 -2
- package/dist/compounding/engine.d.ts +3 -2
- package/dist/compounding/engine.js +37 -37
- package/dist/compounding/preference-consolidator.d.ts +2 -1
- package/dist/compression-optimizer.d.ts +2 -1
- package/dist/config.d.ts +2 -1
- package/dist/config.js +16 -13
- package/dist/connectors/codex-materialize-runner.d.ts +2 -1
- package/dist/connectors/codex-materialize-runner.js +38 -38
- package/dist/connectors/codex-materialize.d.ts +2 -1
- package/dist/connectors/codex-materialize.js +4 -4
- package/dist/connectors/index.d.ts +2 -1
- package/dist/connectors/index.js +38 -38
- package/dist/consolidation-provenance-check.d.ts +3 -2
- package/dist/consolidation-provenance-check.js +4 -4
- package/dist/consolidation-undo.d.ts +3 -2
- package/dist/consolidation-undo.js +3 -3
- package/dist/contradiction/index.d.ts +4 -3
- package/dist/contradiction/index.js +9 -9
- package/dist/converge-config.d.ts +2 -1
- package/dist/convergence-refresh.d.ts +3 -2
- package/dist/convergence-refresh.js +38 -38
- package/dist/conversation-index/backend.d.ts +2 -1
- package/dist/conversation-index/chunker.d.ts +2 -1
- package/dist/conversation-index/faiss-adapter.d.ts +2 -1
- package/dist/conversation-index/indexer.d.ts +2 -1
- package/dist/conversation-index/search.d.ts +2 -1
- package/dist/corpus-watermark.d.ts +2 -1
- package/dist/corpus-watermark.js +38 -38
- package/dist/cross-lingual-recall.d.ts +2 -1
- package/dist/cue-anchors.js +4 -3
- package/dist/dashboard-runtime.js +2 -2
- package/dist/day-summary.d.ts +2 -1
- package/dist/deep-recall-http-glue.d.ts +14 -12
- package/dist/deep-recall-http-glue.js +9 -8
- package/dist/deep-recall-mcp-tools.d.ts +14 -12
- package/dist/deep-recall-policy-llm.d.ts +2 -1
- package/dist/deep-recall-seeds.d.ts +2 -1
- package/dist/deep-recall.js +2 -2
- package/dist/delinearize.d.ts +2 -1
- package/dist/dependency-propagation-config.d.ts +2 -1
- package/dist/{dependency-propagation-delivery-DjpUlhLi.d.ts → dependency-propagation-delivery-CQPCu0o5.d.ts} +1 -1
- package/dist/direct-answer-wiring.d.ts +2 -1
- package/dist/direct-answer.d.ts +2 -1
- package/dist/embedding-fallback.d.ts +2 -1
- package/dist/enrichment/index.d.ts +2 -1
- package/dist/entity-origin-fields.d.ts +2 -1
- package/dist/entity-retrieval.d.ts +3 -2
- package/dist/entity-retrieval.js +36 -36
- package/dist/entity-schema.d.ts +2 -1
- package/dist/episodic-context.d.ts +2 -1
- package/dist/eval-trajectory-contracts.d.ts +2 -1
- package/dist/eval-trajectory-store.d.ts +2 -1
- package/dist/eval-trajectory-store.js +4 -3
- package/dist/eval-trajectory.d.ts +2 -1
- package/dist/eval-trajectory.js +4 -3
- package/dist/evals.js +3 -2
- package/dist/event-order-recall.js +2 -2
- package/dist/explicit-capture.d.ts +12 -10
- package/dist/explicit-capture.js +10 -10
- package/dist/external-wiki-access.d.ts +14 -12
- package/dist/external-wiki-access.js +95 -92
- package/dist/external-wiki-collection-registration.d.ts +2 -1
- package/dist/external-wiki-collection.d.ts +2 -1
- package/dist/external-wiki-mcp-tools.d.ts +14 -12
- package/dist/extraction-error-classification.d.ts +2 -1
- package/dist/extraction-faithfulness.d.ts +2 -1
- package/dist/extraction-judge-telemetry.d.ts +2 -1
- package/dist/extraction-judge-training.d.ts +2 -1
- package/dist/extraction-judge.d.ts +2 -1
- package/dist/extraction-liveness.d.ts +2 -1
- package/dist/extraction-normalization.d.ts +2 -1
- package/dist/extraction-prompt.d.ts +2 -1
- package/dist/extraction-source-grounding-rules.d.ts +2 -1
- package/dist/extraction-source-grounding.d.ts +2 -1
- package/dist/extraction.d.ts +2 -1
- package/dist/extraction.js +43 -43
- package/dist/fallback-llm.d.ts +2 -1
- package/dist/{first-start-migration-KHIMCBIC.js → first-start-migration-RC345GPL.js} +6 -5
- package/dist/{first-start-migration-KHIMCBIC.js.map → first-start-migration-RC345GPL.js.map} +1 -1
- package/dist/{forget-2AE4MDK3.js → forget-SZ2BXDGF.js} +3 -3
- package/dist/{graph-444E-DrF.d.ts → graph-DRkl22KE.d.ts} +10 -12
- package/dist/graph-append-error.d.ts +40 -0
- package/dist/graph-append-error.js +8 -0
- package/dist/graph-dashboard-diff.d.ts +5 -2
- package/dist/graph-dashboard-key.d.ts +5 -2
- package/dist/graph-dashboard-parser.d.ts +5 -2
- package/dist/{graph-edge-decay-DGTBFALQ.js → graph-edge-decay-MKIRZ762.js} +10 -4
- package/dist/graph-edge-decay-MKIRZ762.js.map +1 -0
- package/dist/graph-edge-reinforcement.d.ts +5 -2
- package/dist/graph-jsonl.d.ts +82 -0
- package/dist/graph-jsonl.js +23 -0
- package/dist/graph-jsonl.js.map +1 -0
- package/dist/graph-path-reconstruction.d.ts +5 -2
- package/dist/graph-path-scoring.d.ts +5 -2
- package/dist/graph-snapshot.d.ts +5 -2
- package/dist/graph-snapshot.js +7 -3
- package/dist/graph-write-lock.d.ts +84 -0
- package/dist/graph-write-lock.js +15 -0
- package/dist/graph-write-lock.js.map +1 -0
- package/dist/graph.d.ts +5 -2
- package/dist/graph.js +10 -3
- package/dist/harmonic-construction.js +5 -4
- package/dist/harmonic-retrieval.js +5 -4
- package/dist/importance.d.ts +2 -1
- package/dist/importers/index.d.ts +2 -1
- package/dist/in-flight-reads.d.ts +2 -1
- package/dist/{index-CsUrdMyL.d.ts → index-C9VXenR8.d.ts} +2 -2
- package/dist/index.d.ts +39 -25
- package/dist/index.js +291 -284
- package/dist/intent.d.ts +2 -1
- package/dist/json-store.js +2 -1
- package/dist/lcm/engine.d.ts +2 -1
- package/dist/lcm/engine.js +3 -3
- package/dist/lcm/index.d.ts +2 -1
- package/dist/lcm/index.js +6 -6
- package/dist/lcm/tools.d.ts +2 -1
- package/dist/lifecycle.d.ts +2 -1
- package/dist/live-connectors-runner.d.ts +2 -1
- package/dist/live-connectors-runner.js +3 -2
- package/dist/local-llm.d.ts +2 -1
- package/dist/local-model-endpoint.d.ts +2 -1
- package/dist/location/index.d.ts +4 -3
- package/dist/location/index.js +39 -39
- package/dist/maintenance/memory-governance.d.ts +2 -1
- package/dist/maintenance/memory-governance.js +36 -36
- package/dist/maintenance/rebuild-memory-lifecycle-ledger.d.ts +3 -2
- package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +36 -36
- package/dist/maintenance/rebuild-memory-projection.d.ts +3 -2
- package/dist/maintenance/rebuild-memory-projection.js +37 -37
- package/dist/{maintenance-D1GRGLTE.d.ts → maintenance-C14sorKa.d.ts} +3 -3
- package/dist/mcp-memory-inspector-app.d.ts +14 -12
- package/dist/memory-action-policy.d.ts +2 -1
- package/dist/memory-cache.d.ts +2 -1
- package/dist/memory-controller.d.ts +2 -1
- package/dist/memory-lifecycle-ledger-utils.d.ts +2 -1
- package/dist/memory-projection-mutations.js +3 -3
- package/dist/memory-projection-store.d.ts +2 -1
- package/dist/memory-provenance.d.ts +2 -1
- package/dist/memory-snapshot.d.ts +2 -1
- package/dist/memory-subject-cli.d.ts +14 -12
- package/dist/memory-subject.d.ts +14 -12
- package/dist/memory-subject.js +5 -5
- package/dist/memory-worth-outcomes.d.ts +3 -2
- package/dist/models-json.d.ts +2 -1
- package/dist/namespaces/migrate.d.ts +4 -3
- package/dist/namespaces/migrate.js +37 -37
- package/dist/namespaces/principal.d.ts +2 -1
- package/dist/namespaces/search.d.ts +2 -1
- package/dist/namespaces/search.js +11 -11
- package/dist/namespaces/storage.d.ts +4 -3
- package/dist/namespaces/storage.js +36 -36
- package/dist/native-knowledge.d.ts +2 -1
- package/dist/objective-state-writers.js +4 -3
- package/dist/objective-state.js +3 -2
- package/dist/offline-sync-file-io.js +4 -3
- package/dist/offline-sync-impression-drain.d.ts +2 -1
- package/dist/offline-sync-impression-drain.js +7 -7
- package/dist/offline-sync.js +5 -4
- package/dist/operator-doctor-corpus.d.ts +2 -1
- package/dist/operator-doctor-corpus.js +39 -39
- package/dist/operator-doctor-replica.d.ts +2 -1
- package/dist/operator-toolkit.d.ts +6 -4
- package/dist/operator-toolkit.js +61 -58
- package/dist/orchestration/compression-guideline-coordinator.d.ts +3 -2
- package/dist/orchestration/maintenance.d.ts +5 -4
- package/dist/orchestration/maintenance.js +46 -46
- package/dist/{orchestrator-DeqaPGT5.d.ts → orchestrator-DgyvFfPR.d.ts} +9 -9
- package/dist/orchestrator.d.ts +12 -10
- package/dist/orchestrator.js +201 -196
- package/dist/page-versioning.d.ts +73 -4
- package/dist/page-versioning.js +11 -3
- package/dist/patterns-cli.d.ts +2 -1
- package/dist/{pipeline-umUzU0D1.d.ts → pipeline-DRUwHb1X.d.ts} +1 -1
- package/dist/policy-runtime.d.ts +2 -1
- package/dist/proactive-contention.d.ts +2 -1
- package/dist/provenance.d.ts +2 -1
- package/dist/{public-http-Bevrjll9.d.ts → public-http-C4i8Opxc.d.ts} +1 -1
- package/dist/{purge-OP6BEDFY.js → purge-TZKRE6ZQ.js} +5 -5
- package/dist/purge-TZKRE6ZQ.js.map +1 -0
- package/dist/{qmd-BasT78PP.d.ts → qmd-BVWlg1YS.d.ts} +1 -1
- package/dist/qmd-preflight.d.ts +3 -2
- package/dist/qmd-recall-cache.d.ts +2 -1
- package/dist/qmd.d.ts +3 -2
- package/dist/qmd.js +4 -4
- package/dist/recall-concurrency-config.d.ts +2 -1
- package/dist/recall-disclosure-escalation.d.ts +2 -1
- package/dist/recall-explain-renderer.d.ts +2 -1
- package/dist/recall-explain-renderer.js +3 -3
- package/dist/recall-memory-map.d.ts +2 -1
- package/dist/recall-navigate-access.d.ts +2 -1
- package/dist/recall-navigate-access.js +2 -2
- package/dist/recall-navigate-disclosure.d.ts +2 -1
- package/dist/recall-navigate-disclosure.js +1 -1
- package/dist/recall-navigate.d.ts +2 -1
- package/dist/recall-navigate.js +2 -2
- package/dist/recall-planner-llm.d.ts +2 -1
- package/dist/recall-state.d.ts +2 -1
- package/dist/recall-state.js +3 -3
- package/dist/recall-tag-filter.d.ts +2 -1
- package/dist/recall-timings.d.ts +2 -1
- package/dist/recall-xray-cli.d.ts +2 -1
- package/dist/recall-xray-cli.js +4 -4
- package/dist/recall-xray-renderer.d.ts +2 -1
- package/dist/recall-xray-renderer.js +3 -3
- package/dist/recall-xray.d.ts +2 -1
- package/dist/recall-xray.js +2 -2
- package/dist/reconcile/cursor.d.ts +2 -1
- package/dist/reconcile/manifest.d.ts +2 -1
- package/dist/reconcile/manifest.js +7 -7
- package/dist/reconcile/plan.d.ts +2 -1
- package/dist/replay/normalizers/chatgpt.d.ts +2 -1
- package/dist/replay/normalizers/claude.d.ts +2 -1
- package/dist/replay/normalizers/openclaw.d.ts +2 -1
- package/dist/replay/normalizers/shared.d.ts +2 -1
- package/dist/replay/runner.d.ts +2 -1
- package/dist/replay/types.d.ts +2 -1
- package/dist/replica-divergence.d.ts +2 -1
- package/dist/replica-peers-config.d.ts +2 -1
- package/dist/{resolution-4DLI3IOR.js → resolution-VKATP2HT.js} +10 -10
- package/dist/resolution-VKATP2HT.js.map +1 -0
- package/dist/resolve-auth-token.d.ts +2 -1
- package/dist/resume-bundles.js +21 -18
- package/dist/retrieval-agents.d.ts +3 -2
- package/dist/retrieval-tiers.d.ts +2 -1
- package/dist/routing/engine.d.ts +2 -1
- package/dist/routing/store.d.ts +2 -1
- package/dist/salvage-envelope.d.ts +2 -1
- package/dist/salvage-envelope.js +7 -7
- package/dist/{scope-profiles-DXYzyOXW.d.ts → scope-profiles-Vl00B1KU.d.ts} +1 -1
- package/dist/search/document-scanner.js +3 -3
- package/dist/search/embed-helper.d.ts +2 -1
- package/dist/search/factory.d.ts +2 -1
- package/dist/search/factory.js +10 -10
- package/dist/search/index.d.ts +2 -1
- package/dist/search/index.js +16 -16
- package/dist/search/lancedb-backend.d.ts +2 -1
- package/dist/search/lancedb-backend.js +4 -4
- package/dist/search/meilisearch-backend.d.ts +2 -1
- package/dist/search/meilisearch-backend.js +4 -4
- package/dist/search/noop-backend.d.ts +2 -1
- package/dist/search/orama-backend.d.ts +2 -1
- package/dist/search/orama-backend.js +4 -4
- package/dist/search/port.d.ts +2 -1
- package/dist/search/remote-backend.d.ts +2 -1
- package/dist/secure-store/index.js +3 -3
- package/dist/{semantic-consolidation-CqDeaY0U.d.ts → semantic-consolidation-BC9-GFuT.d.ts} +1 -1
- package/dist/semantic-consolidation.d.ts +3 -2
- package/dist/semantic-consolidation.js +39 -39
- package/dist/semantic-merge-config.d.ts +17 -0
- package/dist/semantic-merge-config.js +1 -0
- package/dist/semantic-merge-config.js.map +1 -0
- package/dist/semantic-rule-promotion.js +36 -36
- package/dist/semantic-rule-verifier.d.ts +2 -1
- package/dist/semantic-rule-verifier.js +36 -36
- package/dist/{service-BKD-DVYZ.d.ts → service-BOy6dHfc.d.ts} +2 -2
- package/dist/session-observer-bands.d.ts +2 -1
- package/dist/session-observer-state.d.ts +2 -1
- package/dist/shared-context/manager.d.ts +2 -1
- package/dist/signal.d.ts +2 -1
- package/dist/source-agent-qualifier.d.ts +2 -1
- package/dist/source-agent-qualifier.js +42 -42
- package/dist/source-attribution.d.ts +18 -6
- package/dist/source-attribution.js +3 -1
- package/dist/standup/build.js +36 -36
- package/dist/{storage-BcFd1lN_.d.ts → storage-BLJRnFi6.d.ts} +15 -8
- package/dist/storage.d.ts +3 -2
- package/dist/storage.js +35 -35
- package/dist/subject-config.d.ts +2 -1
- package/dist/subject-config.js +6 -6
- package/dist/summarizer.d.ts +2 -1
- package/dist/summary-snapshot.d.ts +2 -1
- package/dist/support-passport/index.d.ts +17 -15
- package/dist/support-passport/index.js +97 -94
- package/dist/temporal-supersession.d.ts +3 -2
- package/dist/temporal-timeline-recall.d.ts +2 -1
- package/dist/temporal-validity.d.ts +2 -1
- package/dist/threading.d.ts +2 -1
- package/dist/tier-migration.d.ts +3 -2
- package/dist/tier-routing.d.ts +2 -1
- package/dist/{tier-stats-2TOW3QNL.js → tier-stats-KA6OZX2C.js} +6 -5
- package/dist/{tier-stats-2TOW3QNL.js.map → tier-stats-KA6OZX2C.js.map} +1 -1
- package/dist/topics.d.ts +2 -1
- package/dist/transcript.d.ts +2 -1
- package/dist/transfer/backup.js +2 -2
- package/dist/transfer/capsule-export.js +2 -2
- package/dist/transfer/capsule-import.js +6 -6
- package/dist/transfer/export-okf.d.ts +2 -1
- package/dist/transfer/export-okf.js +37 -37
- package/dist/trust-score-stage.d.ts +2 -1
- package/dist/trust-score.d.ts +2 -1
- package/dist/trust-zones.js +3 -2
- package/dist/{types-BRg3tMyp.d.ts → types-YY-eKKU7.d.ts} +3 -0
- package/dist/types.d.ts +2 -1
- package/dist/types.js +1 -1
- package/dist/utility-learner.js +4 -3
- package/dist/utility-runtime.d.ts +2 -1
- package/dist/utility-runtime.js +5 -4
- package/dist/utility-telemetry.js +3 -2
- package/dist/verified-recall.js +36 -36
- package/dist/who-knows-cli.d.ts +14 -12
- package/dist/who-knows-cli.js +37 -37
- package/dist/who-knows.d.ts +14 -12
- package/dist/who-knows.js +36 -36
- package/dist/work/board.js +3 -3
- package/dist/work/storage.js +2 -2
- package/dist/work-product-ledger.js +3 -2
- package/dist/write-envelope.d.ts +2 -1
- package/dist/write-envelope.js +6 -6
- package/package.json +2 -2
- package/src/abstraction-nodes.ts +1 -4
- package/src/access-http-briefing.ts +35 -0
- package/src/access-http.test.ts +76 -0
- package/src/access-http.ts +20 -23
- package/src/access-surface-catalog.test.ts +9 -0
- package/src/access-surface-catalog.ts +1 -0
- package/src/binary-lifecycle/backend.ts +2 -1
- package/src/config.ts +2 -0
- package/src/connectors/live/github.ts +10 -17
- package/src/connectors/live/gmail.ts +8 -19
- package/src/connectors/live/google-drive.ts +6 -13
- package/src/connectors/live/notion.ts +11 -21
- package/src/cue-anchors.ts +1 -6
- package/src/dedup/connector-scope.ts +39 -0
- package/src/dedup/merge-content.test.ts +21 -0
- package/src/dedup/merge-content.ts +21 -7
- package/src/dedup/merge-judge.ts +146 -0
- package/src/dedup/merge.test.ts +3542 -0
- package/src/dedup/merge.ts +384 -0
- package/src/dedup/novelty-gate.ts +12 -10
- package/src/dedup/semantic.ts +7 -9
- package/src/graph-append-error.ts +28 -0
- package/src/graph-jsonl.ts +267 -0
- package/src/graph-write-lock.test.ts +228 -0
- package/src/graph-write-lock.ts +132 -0
- package/src/graph.ts +49 -45
- package/src/harmonic-construction.test.ts +329 -0
- package/src/json-store.ts +17 -20
- package/src/live-connectors-runner.ts +2 -1
- package/src/location/store.ts +1 -4
- package/src/maintenance/graph-edge-decay.ts +24 -13
- package/src/maintenance/purge.ts +2 -5
- package/src/maintenance/rebuild-memory-lifecycle-ledger.ts +3 -6
- package/src/maintenance/rebuild-memory-projection.ts +2 -5
- package/src/namespaces/catalog.ts +1 -9
- package/src/offline-sync-file-io.test.ts +39 -0
- package/src/offline-sync-file-io.ts +3 -7
- package/src/orchestration/contradiction-localization.integration.test.ts +35 -0
- package/src/orchestration/extraction-anchor-snapshot.ts +6 -1
- package/src/orchestration/extraction-persist-deps.ts +2 -1
- package/src/orchestration/extraction-persist-profile-promotion.test.ts +456 -0
- package/src/orchestration/extraction-persist-promotion.test.ts +534 -0
- package/src/orchestration/extraction-persist-promotion.ts +387 -1
- package/src/orchestration/extraction-persist.ts +95 -94
- package/src/orchestration/harmonic-construction-persist.test.ts +69 -0
- package/src/orchestration/harmonic-construction-persist.ts +55 -0
- package/src/orchestration/persistence-index.ts +35 -7
- package/src/orchestration/semantic-merge-commit-effects.ts +354 -0
- package/src/orchestration/semantic-merge-persist.ts +1095 -0
- package/src/orchestration/semantic-merge-promotion-payload.ts +161 -0
- package/src/orchestrator.ts +2 -2
- package/src/page-versioning.ts +396 -22
- package/src/procedural/procedure-miner.ts +1 -9
- package/src/search/document-scanner.ts +3 -6
- package/src/semantic-merge-config.ts +16 -0
- package/src/semantic-rule-promotion.ts +1 -4
- package/src/source-attribution-roundtrip.test.ts +21 -0
- package/src/source-attribution.ts +57 -17
- package/src/storage-contract/versioning.test.ts +500 -1
- package/src/storage.ts +30 -30
- package/src/testing/subjects/graph-edge-decay.test.ts +163 -0
- package/src/testing/subjects/semantic-merge-lifecycle.test.ts +1175 -0
- package/src/types.ts +2 -0
- package/src/utils/errno.test.ts +31 -0
- package/src/utils/errno.ts +5 -0
- package/src/wearables/index.ts +1 -0
- package/src/wearables/registry.ts +23 -0
- package/dist/chunk-5UZXUTVO.js.map +0 -1
- package/dist/chunk-7GSSHAXC.js.map +0 -1
- package/dist/chunk-ACPCA4NO.js.map +0 -1
- package/dist/chunk-C2JCVZWX.js.map +0 -1
- package/dist/chunk-DDQQY2E3.js.map +0 -1
- package/dist/chunk-DJUKBGAD.js.map +0 -1
- package/dist/chunk-DK6GMO6D.js.map +0 -1
- package/dist/chunk-DP4EBV7C.js.map +0 -1
- package/dist/chunk-FCATYKQA.js.map +0 -1
- package/dist/chunk-GGYDJJBO.js.map +0 -1
- package/dist/chunk-HTIIXNJQ.js.map +0 -1
- package/dist/chunk-KK66P5GQ.js.map +0 -1
- package/dist/chunk-NQFCLAYT.js.map +0 -1
- package/dist/chunk-NYRB7BKY.js.map +0 -1
- package/dist/chunk-OGTKQ5MJ.js.map +0 -1
- package/dist/chunk-OLT3YTDH.js.map +0 -1
- package/dist/chunk-P77QX3ID.js.map +0 -1
- package/dist/chunk-U62OK7BB.js.map +0 -1
- package/dist/chunk-UR6PBX2J.js.map +0 -1
- package/dist/chunk-VLAQV4HT.js.map +0 -1
- package/dist/chunk-W7IGLX4G.js.map +0 -1
- package/dist/purge-OP6BEDFY.js.map +0 -1
- /package/dist/{capsule-crypto-7FJQINUR.js.map → access-http-briefing.js.map} +0 -0
- /package/dist/{auto-sync-GRUJFD2F.js.map → auto-sync-WNGG2GC2.js.map} +0 -0
- /package/dist/{graph-edge-decay-DGTBFALQ.js.map → capsule-crypto-YO5QJ6L3.js.map} +0 -0
- /package/dist/{capsule-merge-SEHERWWP.js.map → capsule-merge-IPG4IOFP.js.map} +0 -0
- /package/dist/{chunk-KTVSPFCQ.js.map → chunk-22FVR2KE.js.map} +0 -0
- /package/dist/{chunk-7Y5V3LAP.js.map → chunk-27QVNSQ3.js.map} +0 -0
- /package/dist/{chunk-R7LY4YKF.js.map → chunk-2EKJDQZB.js.map} +0 -0
- /package/dist/{chunk-HOUST5UX.js.map → chunk-2ITE4C2Z.js.map} +0 -0
- /package/dist/{chunk-JOYC4RYC.js.map → chunk-2JVJ7UHC.js.map} +0 -0
- /package/dist/{chunk-X2NFFBTO.js.map → chunk-2L5FSNPC.js.map} +0 -0
- /package/dist/{chunk-DUAJKRR2.js.map → chunk-2SGLMP2E.js.map} +0 -0
- /package/dist/{chunk-6FUMA6AA.js.map → chunk-2Z4GS5E6.js.map} +0 -0
- /package/dist/{chunk-G6MXN2G5.js.map → chunk-37KUQPX4.js.map} +0 -0
- /package/dist/{chunk-WKAXBXVA.js.map → chunk-3QQTXYZ6.js.map} +0 -0
- /package/dist/{chunk-4ZWCQJNM.js.map → chunk-46WM6JX2.js.map} +0 -0
- /package/dist/{chunk-CASWJ2HM.js.map → chunk-46ZPIGWI.js.map} +0 -0
- /package/dist/{chunk-MCA2X4WH.js.map → chunk-4IHBN6WU.js.map} +0 -0
- /package/dist/{chunk-FB7LAJQM.js.map → chunk-4TKJGOCJ.js.map} +0 -0
- /package/dist/{chunk-H7BZBSP6.js.map → chunk-52II7PB6.js.map} +0 -0
- /package/dist/{chunk-N4Y5T6VD.js.map → chunk-54WHLA65.js.map} +0 -0
- /package/dist/{chunk-YVLENSNS.js.map → chunk-5ATKSPEQ.js.map} +0 -0
- /package/dist/{chunk-ZVZHG7AI.js.map → chunk-5UBH37SL.js.map} +0 -0
- /package/dist/{chunk-N3WZU76A.js.map → chunk-66OJSCI6.js.map} +0 -0
- /package/dist/{chunk-D35XLYQ7.js.map → chunk-6CVNSDCZ.js.map} +0 -0
- /package/dist/{chunk-KA2HZCMG.js.map → chunk-6NXSYYGP.js.map} +0 -0
- /package/dist/{chunk-2HIJHXB3.js.map → chunk-6WHJ2EAB.js.map} +0 -0
- /package/dist/{chunk-ZZM476NV.js.map → chunk-6XL73HQ4.js.map} +0 -0
- /package/dist/{chunk-EJ5RKUXW.js.map → chunk-AQ7SYDVO.js.map} +0 -0
- /package/dist/{chunk-2Z3DG5CQ.js.map → chunk-B3O3IRZN.js.map} +0 -0
- /package/dist/{chunk-RACZB5HT.js.map → chunk-BD7JTBYP.js.map} +0 -0
- /package/dist/{chunk-I762GW7V.js.map → chunk-BLJWYCNO.js.map} +0 -0
- /package/dist/{chunk-2NLLXCJG.js.map → chunk-BXLOS5AJ.js.map} +0 -0
- /package/dist/{chunk-GCASGYIO.js.map → chunk-C7HJ3NEI.js.map} +0 -0
- /package/dist/{chunk-C24UZSFW.js.map → chunk-CEH2AFXG.js.map} +0 -0
- /package/dist/{chunk-WVGXCBOC.js.map → chunk-D5F7TKMH.js.map} +0 -0
- /package/dist/{chunk-X7Y7WX73.js.map → chunk-DQEMWVMT.js.map} +0 -0
- /package/dist/{chunk-2OXIPF3A.js.map → chunk-DZD3WGCD.js.map} +0 -0
- /package/dist/{chunk-UMEOXLIZ.js.map → chunk-E2P7TZTF.js.map} +0 -0
- /package/dist/{chunk-6FC3IZ26.js.map → chunk-EBA5DA77.js.map} +0 -0
- /package/dist/{chunk-N7JK2RKG.js.map → chunk-EOQ6FSQP.js.map} +0 -0
- /package/dist/{chunk-YNDLCWXS.js.map → chunk-EZ25VE3G.js.map} +0 -0
- /package/dist/{chunk-XDMI5VKE.js.map → chunk-FVOP3NT7.js.map} +0 -0
- /package/dist/{chunk-NRLELHAY.js.map → chunk-G7NYVKD6.js.map} +0 -0
- /package/dist/{chunk-B3W7GWQB.js.map → chunk-GHTSNGBZ.js.map} +0 -0
- /package/dist/{chunk-LUDBXTBJ.js.map → chunk-H272ZPQ3.js.map} +0 -0
- /package/dist/{chunk-SN4JXNLL.js.map → chunk-H3TKENU4.js.map} +0 -0
- /package/dist/{chunk-4HYNMLT6.js.map → chunk-H4SEGIZY.js.map} +0 -0
- /package/dist/{chunk-ACZN5ZXD.js.map → chunk-H54VWRPS.js.map} +0 -0
- /package/dist/{chunk-HTP6XRUI.js.map → chunk-HM7RHKS4.js.map} +0 -0
- /package/dist/{chunk-Z3SQEEB6.js.map → chunk-HWLIMQE2.js.map} +0 -0
- /package/dist/{chunk-2QSZNTDO.js.map → chunk-JBPKEARU.js.map} +0 -0
- /package/dist/{chunk-ET2CNMLD.js.map → chunk-JN72EPOE.js.map} +0 -0
- /package/dist/{chunk-JCCWJHOP.js.map → chunk-JYZSRNR7.js.map} +0 -0
- /package/dist/{chunk-7D6HXODY.js.map → chunk-K7724CH2.js.map} +0 -0
- /package/dist/{chunk-X33MJFD2.js.map → chunk-KAXCYM66.js.map} +0 -0
- /package/dist/{chunk-7RRUHJPG.js.map → chunk-KB2G2I6Q.js.map} +0 -0
- /package/dist/{chunk-ZXBTTMGZ.js.map → chunk-KEF46KL2.js.map} +0 -0
- /package/dist/{chunk-UWTDARPF.js.map → chunk-KTWG7CNY.js.map} +0 -0
- /package/dist/{chunk-XQYTSZW3.js.map → chunk-L7Z57SID.js.map} +0 -0
- /package/dist/{chunk-JMAG7WZ7.js.map → chunk-LO4UKPKX.js.map} +0 -0
- /package/dist/{chunk-LDWIPYJ6.js.map → chunk-LS2FHUML.js.map} +0 -0
- /package/dist/{chunk-GDB4J2H3.js.map → chunk-MPC7TYTZ.js.map} +0 -0
- /package/dist/{chunk-2GF27PGH.js.map → chunk-MRPOZBJC.js.map} +0 -0
- /package/dist/{chunk-BR3OOQZQ.js.map → chunk-MWN6QEI2.js.map} +0 -0
- /package/dist/{chunk-NZGO2GKL.js.map → chunk-MY5EDR46.js.map} +0 -0
- /package/dist/{chunk-LCMUZDBZ.js.map → chunk-N3ZOP7KC.js.map} +0 -0
- /package/dist/{chunk-TVW4C7PV.js.map → chunk-NB3LRY4T.js.map} +0 -0
- /package/dist/{chunk-QJCWK2A7.js.map → chunk-ND6IQGSZ.js.map} +0 -0
- /package/dist/{chunk-NOB2T2FC.js.map → chunk-NHLLKEKK.js.map} +0 -0
- /package/dist/{chunk-EJIWBTVM.js.map → chunk-O6ZNNVSP.js.map} +0 -0
- /package/dist/{chunk-AG46P24C.js.map → chunk-OB3XMWX5.js.map} +0 -0
- /package/dist/{chunk-46QMN7I7.js.map → chunk-PAZY2ZKP.js.map} +0 -0
- /package/dist/{chunk-EF4JUM55.js.map → chunk-PX4UMA5Z.js.map} +0 -0
- /package/dist/{chunk-NNZFPYYX.js.map → chunk-R7QY5ZVB.js.map} +0 -0
- /package/dist/{chunk-RO2X2EYM.js.map → chunk-R7RTXEYG.js.map} +0 -0
- /package/dist/{chunk-3LJPSRAN.js.map → chunk-RBCQVOVJ.js.map} +0 -0
- /package/dist/{chunk-N4F7ZSSG.js.map → chunk-RC5DXXWC.js.map} +0 -0
- /package/dist/{chunk-5CF2R4NS.js.map → chunk-RKBLRTNT.js.map} +0 -0
- /package/dist/{chunk-5QZR2RA3.js.map → chunk-ROBHL3GL.js.map} +0 -0
- /package/dist/{chunk-D5ZNOKR7.js.map → chunk-S7T2TY65.js.map} +0 -0
- /package/dist/{chunk-3LADWWMA.js.map → chunk-SJDQ5RSN.js.map} +0 -0
- /package/dist/{chunk-CHWUICVY.js.map → chunk-SV4Z2S67.js.map} +0 -0
- /package/dist/{chunk-SEF4IOFE.js.map → chunk-SWIKABKI.js.map} +0 -0
- /package/dist/{chunk-JRRQ747T.js.map → chunk-TBXAY2SS.js.map} +0 -0
- /package/dist/{chunk-XZBXINXZ.js.map → chunk-TNSCIPBQ.js.map} +0 -0
- /package/dist/{chunk-S37NN23M.js.map → chunk-TVEONGBU.js.map} +0 -0
- /package/dist/{chunk-5RPMTFB6.js.map → chunk-TWF35IC6.js.map} +0 -0
- /package/dist/{chunk-BZTJEILZ.js.map → chunk-U6N5IBVY.js.map} +0 -0
- /package/dist/{chunk-7TUB6JWA.js.map → chunk-UCKYTA2T.js.map} +0 -0
- /package/dist/{chunk-PPOT6SPR.js.map → chunk-UNNPNBR2.js.map} +0 -0
- /package/dist/{chunk-KQAFEZQX.js.map → chunk-VDX2J7OX.js.map} +0 -0
- /package/dist/{chunk-J2R275FN.js.map → chunk-VVX5PZFS.js.map} +0 -0
- /package/dist/{chunk-ME35HVDJ.js.map → chunk-VYCOJKSX.js.map} +0 -0
- /package/dist/{chunk-F5UWQ4LX.js.map → chunk-W24T5YMW.js.map} +0 -0
- /package/dist/{chunk-HDQUQBPW.js.map → chunk-W3K3HITK.js.map} +0 -0
- /package/dist/{chunk-GDASG7NC.js.map → chunk-W4F3C7N7.js.map} +0 -0
- /package/dist/{chunk-LFK623SF.js.map → chunk-WJHGDE7X.js.map} +0 -0
- /package/dist/{chunk-3Q6RD76J.js.map → chunk-WQZZDN5M.js.map} +0 -0
- /package/dist/{chunk-ZJWDWPX6.js.map → chunk-X5K5TAQZ.js.map} +0 -0
- /package/dist/{chunk-ZL3XX5LW.js.map → chunk-XM24CDZX.js.map} +0 -0
- /package/dist/{chunk-JTWZKNUH.js.map → chunk-XOVKXJEP.js.map} +0 -0
- /package/dist/{chunk-X5YYSAZX.js.map → chunk-XUE46UYW.js.map} +0 -0
- /package/dist/{chunk-DJORULH3.js.map → chunk-YRLHEEQT.js.map} +0 -0
- /package/dist/{chunk-TA7UZZMW.js.map → chunk-Z3O53WZR.js.map} +0 -0
- /package/dist/{chunk-4LG4CPOC.js.map → chunk-Z6COGHHN.js.map} +0 -0
- /package/dist/{chunk-RE3H6Y2X.js.map → chunk-ZIW5JVRM.js.map} +0 -0
- /package/dist/{forget-2AE4MDK3.js.map → forget-SZ2BXDGF.js.map} +0 -0
- /package/dist/{resolution-4DLI3IOR.js.map → graph-append-error.js.map} +0 -0
|
@@ -0,0 +1,1175 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Semantic merge-on-write lifecycle subject for the scenario-matrix harness
|
|
3
|
+
* (PR #2771 review finding B, issue #2330).
|
|
4
|
+
*
|
|
5
|
+
* The lifecycle-matrix gate previously accepted changes to
|
|
6
|
+
* `orchestration/semantic-merge-persist.ts` through the `extraction-lifecycle`
|
|
7
|
+
* subject, which builds every configuration with `semanticMerge` disabled —
|
|
8
|
+
* the required gate passed without ever exercising the merge path it claimed
|
|
9
|
+
* to cover. This subject runs the REAL orchestrator + storage persist path
|
|
10
|
+
* (turn ingestion → buffer → flush → `ExtractionPersistCoordinator` →
|
|
11
|
+
* `applySemanticMergeAtPersist` → `StorageManager` compare-and-swap) with
|
|
12
|
+
* `semanticMerge` ENABLED for all nine canonical rows, and asserts the merge
|
|
13
|
+
* outcome survives each lifecycle transition.
|
|
14
|
+
*
|
|
15
|
+
* Deterministic sensors use the established field-level seams (never
|
|
16
|
+
* production hooks): the extraction client (`stubExtraction`), the embedding
|
|
17
|
+
* neighbor index (`embeddingFallback`), and the local-llm judge client
|
|
18
|
+
* (`localLlm`, answering only the merge-judge system prompt). Storage,
|
|
19
|
+
* versioning, the merge decision, the CAS writes, the rollback data, and the
|
|
20
|
+
* index repair are all production code.
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
import assert from "node:assert/strict";
|
|
24
|
+
import { test } from "node:test";
|
|
25
|
+
import crypto from "node:crypto";
|
|
26
|
+
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
27
|
+
import path from "node:path";
|
|
28
|
+
import { Orchestrator } from "../../orchestrator.js";
|
|
29
|
+
import { resolveNamespaceStorageRoot } from "../../namespaces/storage.js";
|
|
30
|
+
import type { BufferTurn, PluginConfig } from "../../types.js";
|
|
31
|
+
import {
|
|
32
|
+
type LifecycleSubject,
|
|
33
|
+
type MatrixRow,
|
|
34
|
+
runLifecycleMatrix,
|
|
35
|
+
} from "../lifecycle-matrix.js";
|
|
36
|
+
import {
|
|
37
|
+
cleanupDir,
|
|
38
|
+
makeLifecycleConfig,
|
|
39
|
+
markdownFilesUnder,
|
|
40
|
+
mkTempMemoryDir,
|
|
41
|
+
pastIso,
|
|
42
|
+
singleFactResult,
|
|
43
|
+
sleep,
|
|
44
|
+
stubExtraction,
|
|
45
|
+
} from "../orchestrator-lite.js";
|
|
46
|
+
import { queryTemporalTimelineAsync } from "../../temporal-index.js";
|
|
47
|
+
|
|
48
|
+
// ── Fixtures (synthetic; no real paths, hosts, or memory content) ─────────────
|
|
49
|
+
|
|
50
|
+
const SEED_A = "The billing service deploys on a Tuesday cadence.";
|
|
51
|
+
const INCOMING_A = "The billing service deploys at 09:00 UTC sharp.";
|
|
52
|
+
const INCOMING_B = "The billing service deploy includes the payments module.";
|
|
53
|
+
const SEED_B = "The audit service tracks quarterly access reviews.";
|
|
54
|
+
const INCOMING_BOB = "The audit service also covers vendor onboarding.";
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Capture-cued turn contents. The smart trigger auto-fires on the explicit
|
|
58
|
+
* "Please remember" cue (the same pattern every auto-extraction row in the
|
|
59
|
+
* reference subject uses); flush-driven rows use the same turns for uniformity,
|
|
60
|
+
* and the fact body carries the cue verbatim.
|
|
61
|
+
*/
|
|
62
|
+
const TURN_A = `Please remember: ${INCOMING_A}`;
|
|
63
|
+
const TURN_B = `Please remember: ${INCOMING_B}`;
|
|
64
|
+
const TURN_BOB = `Please remember: ${INCOMING_BOB}`;
|
|
65
|
+
const GRAPH_SEED = "The billing service deploys on a Tuesday cadence.";
|
|
66
|
+
const GRAPH_INCOMING = "The billing service deploys at 09:00 UTC because payments reconciliation gates the window.";
|
|
67
|
+
const GRAPH_SIBLING = "The billing service pages the on-call engineer after failed deploys.";
|
|
68
|
+
const PREF_SEED = "Commit messages favor terse commit subject lines.";
|
|
69
|
+
const PREF_INCOMING = "Commit messages favor imperative mood subjects.";
|
|
70
|
+
|
|
71
|
+
const NEEDLE_SEED_A = "Tuesday cadence";
|
|
72
|
+
const NEEDLE_A = "09:00 UTC sharp";
|
|
73
|
+
const NEEDLE_B = "payments module";
|
|
74
|
+
const NEEDLE_SEED_B = "quarterly access reviews";
|
|
75
|
+
const NEEDLE_BOB = "vendor onboarding";
|
|
76
|
+
|
|
77
|
+
/** A sparse, opaque session id remembered (bound) to alice from a PRIOR session. */
|
|
78
|
+
const REMEMBERED_SESSION = "restored-session-9f2a";
|
|
79
|
+
/** A sparse, opaque session id with NO remembered binding. */
|
|
80
|
+
const UNBOUND_SESSION = "unbound-session-0000";
|
|
81
|
+
|
|
82
|
+
/** Merge band neighbor score: inside [minSimilarity=0.8, dedupThreshold=0.92). */
|
|
83
|
+
const BAND_SCORE = 0.85;
|
|
84
|
+
|
|
85
|
+
interface SemanticMergeLifecycleState {
|
|
86
|
+
memoryDir: string;
|
|
87
|
+
cfg: PluginConfig;
|
|
88
|
+
orchestrators: Orchestrator[];
|
|
89
|
+
calls: BufferTurn[][];
|
|
90
|
+
restartCalls?: BufferTurn[][];
|
|
91
|
+
/** Judge verdicts answered across ALL orchestrator instances. */
|
|
92
|
+
judge: { calls: number };
|
|
93
|
+
aliceRoot?: string;
|
|
94
|
+
bobRoot?: string;
|
|
95
|
+
aliceFile?: string;
|
|
96
|
+
bobFile?: string;
|
|
97
|
+
targetFile?: string;
|
|
98
|
+
callsBeforeForceFlush?: number;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// ── Configs ───────────────────────────────────────────────────────────────────
|
|
102
|
+
|
|
103
|
+
/** Merge-on-write lifecycle config: semanticMerge ON, embedding + versioning ON. */
|
|
104
|
+
function mergeLifecycleConfig(memoryDir: string, overrides: Record<string, unknown> = {}): PluginConfig {
|
|
105
|
+
return makeLifecycleConfig(memoryDir, {
|
|
106
|
+
semanticMerge: { enabled: true },
|
|
107
|
+
embeddingFallbackEnabled: true,
|
|
108
|
+
versioningEnabled: true,
|
|
109
|
+
// Round N+12 (CI-0): the commit-effects sibling (snapshot staging, prune
|
|
110
|
+
// finalization, durable thread-episode persist) runs on this subject's
|
|
111
|
+
// merge path — threading ON gives the thread-episode helper a real
|
|
112
|
+
// thread id so every exported function of that module executes here.
|
|
113
|
+
threadingEnabled: true,
|
|
114
|
+
...overrides,
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/** Namespaces-on config with alice/bob principal prefix routing (identity rows). */
|
|
119
|
+
function namespacedConfig(memoryDir: string): PluginConfig {
|
|
120
|
+
return mergeLifecycleConfig(memoryDir, {
|
|
121
|
+
namespacesEnabled: true,
|
|
122
|
+
defaultNamespace: "default",
|
|
123
|
+
sharedNamespace: "shared",
|
|
124
|
+
defaultRecallNamespaces: ["self"],
|
|
125
|
+
principalFromSessionKeyMode: "prefix",
|
|
126
|
+
principalFromSessionKeyRules: [
|
|
127
|
+
{ match: "alice:", principal: "alice" },
|
|
128
|
+
{ match: "bob:", principal: "bob" },
|
|
129
|
+
],
|
|
130
|
+
namespacePolicies: [
|
|
131
|
+
{ name: "alice", readPrincipals: ["alice"], writePrincipals: ["alice"] },
|
|
132
|
+
{ name: "bob", readPrincipals: ["bob"], writePrincipals: ["bob"] },
|
|
133
|
+
],
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/** Map-mode config where {@link REMEMBERED_SESSION} is bound to alice. */
|
|
138
|
+
function rememberedBindingConfig(memoryDir: string): PluginConfig {
|
|
139
|
+
return mergeLifecycleConfig(memoryDir, {
|
|
140
|
+
namespacesEnabled: true,
|
|
141
|
+
defaultNamespace: "default",
|
|
142
|
+
sharedNamespace: "shared",
|
|
143
|
+
defaultRecallNamespaces: ["self"],
|
|
144
|
+
principalFromSessionKeyMode: "map",
|
|
145
|
+
principalFromSessionKeyRules: [{ match: REMEMBERED_SESSION, principal: "alice" }],
|
|
146
|
+
namespacePolicies: [{ name: "alice", readPrincipals: ["alice"], writePrincipals: ["alice"] }],
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// ── Deterministic sensors (field-level seams) ─────────────────────────────────
|
|
151
|
+
|
|
152
|
+
/** The embedding-fallback field seam replaced by {@link installNeighbors}. */
|
|
153
|
+
interface EmbeddingFallbackSeam {
|
|
154
|
+
embeddingFallback: {
|
|
155
|
+
isAvailable: () => Promise<boolean>;
|
|
156
|
+
search: (
|
|
157
|
+
query: string,
|
|
158
|
+
limit: number,
|
|
159
|
+
options?: unknown,
|
|
160
|
+
) => Promise<Array<{ id: string; score: number; path: string }>>;
|
|
161
|
+
indexFile: (id: string, content: string, path: string) => Promise<void>;
|
|
162
|
+
removeFromIndex: (id: string) => Promise<void>;
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Serve in-band nearest neighbors for EXACT incoming fact contents only.
|
|
168
|
+
* Recall queries and any other content get an empty index, so the stub can
|
|
169
|
+
* never fabricate a recall hit.
|
|
170
|
+
*/
|
|
171
|
+
function installNeighbors(
|
|
172
|
+
orchestrator: Orchestrator,
|
|
173
|
+
neighborsFor: (query: string) => Array<{ id: string; score: number }>,
|
|
174
|
+
): void {
|
|
175
|
+
(orchestrator as unknown as EmbeddingFallbackSeam).embeddingFallback = {
|
|
176
|
+
isAvailable: async () => true,
|
|
177
|
+
search: async (query: string) =>
|
|
178
|
+
neighborsFor(query).map((n) => ({ id: n.id, score: n.score, path: "" })),
|
|
179
|
+
indexFile: async () => {},
|
|
180
|
+
removeFromIndex: async () => {},
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/** The local-llm field seam replaced by {@link installJudge}. */
|
|
185
|
+
interface LocalLlmSeam {
|
|
186
|
+
localLlm: {
|
|
187
|
+
chatCompletion: (
|
|
188
|
+
messages: Array<{ role: string; content: string }>,
|
|
189
|
+
options?: unknown,
|
|
190
|
+
) => Promise<{ content: string } | null>;
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Answer ONLY the merge-judge prompt (the production callMergeJudge routing
|
|
196
|
+
* reaches this field through the late-binding `getLocalLlm` dep). Every other
|
|
197
|
+
* caller gets `null` — the same "no backend answered" they already handle.
|
|
198
|
+
* The verdict merges the top candidate's CURRENT body with the incoming text,
|
|
199
|
+
* so replays merge into the already-merged target.
|
|
200
|
+
*/
|
|
201
|
+
function installJudge(orchestrator: Orchestrator, judge: { calls: number }): void {
|
|
202
|
+
(orchestrator as unknown as LocalLlmSeam).localLlm = {
|
|
203
|
+
chatCompletion: async (messages) => {
|
|
204
|
+
if (messages[0]?.role !== "system" || !messages[0].content.startsWith("You maintain a long-term memory store")) {
|
|
205
|
+
return null;
|
|
206
|
+
}
|
|
207
|
+
judge.calls++;
|
|
208
|
+
const input = JSON.parse(messages[1]?.content ?? "{}") as {
|
|
209
|
+
new?: { content?: string };
|
|
210
|
+
existing?: Array<{ id?: string; content?: string }>;
|
|
211
|
+
};
|
|
212
|
+
const target = input.existing?.[0];
|
|
213
|
+
if (!target?.id || typeof target.content !== "string" || typeof input.new?.content !== "string") {
|
|
214
|
+
return { content: JSON.stringify({ decision: "create", targetId: null, mergedContent: null, reason: "no candidate" }) };
|
|
215
|
+
}
|
|
216
|
+
return {
|
|
217
|
+
content: JSON.stringify({
|
|
218
|
+
decision: "merge",
|
|
219
|
+
targetId: target.id,
|
|
220
|
+
mergedContent: `${target.content} ${input.new.content}`.trim(),
|
|
221
|
+
reason: "deterministic lifecycle subject judge",
|
|
222
|
+
}),
|
|
223
|
+
};
|
|
224
|
+
},
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// ── Seeding ───────────────────────────────────────────────────────────────────
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Seed a merge TARGET directly on disk with PAST timestamps and a high
|
|
232
|
+
* importance score, so an incoming fact's own importance never bypasses the
|
|
233
|
+
* merge as unpreservable metadata. Returns the file path.
|
|
234
|
+
*/
|
|
235
|
+
async function seedMergeTarget(
|
|
236
|
+
root: string,
|
|
237
|
+
id: string,
|
|
238
|
+
content: string,
|
|
239
|
+
options: {
|
|
240
|
+
/** Category (and its directory) for the target; default `fact`/`facts`. */
|
|
241
|
+
category?: "fact" | "preference";
|
|
242
|
+
/** Storage tier: `cold` seeds under `<root>/cold/...` (demoted target). */
|
|
243
|
+
tier?: "hot" | "cold";
|
|
244
|
+
/** Extra frontmatter lines stamped between `status` and `importanceScore`. */
|
|
245
|
+
extraFrontmatter?: string[];
|
|
246
|
+
} = {},
|
|
247
|
+
): Promise<string> {
|
|
248
|
+
const category = options.category ?? "fact";
|
|
249
|
+
const created = pastIso();
|
|
250
|
+
const dir = path.join(
|
|
251
|
+
root,
|
|
252
|
+
options.tier === "cold" ? "cold" : ".",
|
|
253
|
+
category === "preference" ? "preferences" : "facts",
|
|
254
|
+
created.slice(0, 10),
|
|
255
|
+
);
|
|
256
|
+
await mkdir(dir, { recursive: true });
|
|
257
|
+
const file = path.join(dir, `${id}.md`);
|
|
258
|
+
await writeFile(
|
|
259
|
+
file,
|
|
260
|
+
[
|
|
261
|
+
"---",
|
|
262
|
+
`id: ${id}`,
|
|
263
|
+
`category: ${category}`,
|
|
264
|
+
`created: ${created}`,
|
|
265
|
+
`updated: ${created}`,
|
|
266
|
+
"source: extraction",
|
|
267
|
+
"confidence: 0.9",
|
|
268
|
+
"confidenceTier: explicit",
|
|
269
|
+
"status: active",
|
|
270
|
+
...(options.extraFrontmatter ?? []),
|
|
271
|
+
"importanceScore: 0.9",
|
|
272
|
+
"importanceLevel: high",
|
|
273
|
+
"---",
|
|
274
|
+
"",
|
|
275
|
+
content,
|
|
276
|
+
"",
|
|
277
|
+
].join("\n"),
|
|
278
|
+
"utf8",
|
|
279
|
+
);
|
|
280
|
+
return file;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
async function assertMerged(file: string, needles: string[], reinforcementCount: number): Promise<void> {
|
|
284
|
+
const raw = await readFile(file, "utf8");
|
|
285
|
+
for (const needle of needles) {
|
|
286
|
+
assert.ok(raw.includes(needle), `merged target must contain "${needle}"`);
|
|
287
|
+
}
|
|
288
|
+
assert.ok(raw.includes("derived_via: merge"), "merged target must carry merge provenance");
|
|
289
|
+
assert.ok(
|
|
290
|
+
raw.includes(`reinforcement_count: ${reinforcementCount}`),
|
|
291
|
+
`merged target must record reinforcement_count: ${reinforcementCount}`,
|
|
292
|
+
);
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
/** Read one graph JSONL edge file (entity/time/causal) as parsed rows. */
|
|
296
|
+
async function readGraphEdges(
|
|
297
|
+
memoryDir: string,
|
|
298
|
+
type: "entity" | "time" | "causal",
|
|
299
|
+
): Promise<Array<{ from: string; to: string; label: string }>> {
|
|
300
|
+
const file = path.join(memoryDir, "state", "graphs", `${type}.jsonl`);
|
|
301
|
+
try {
|
|
302
|
+
const raw = await readFile(file, "utf8");
|
|
303
|
+
return raw
|
|
304
|
+
.split("\n")
|
|
305
|
+
.map((line) => line.trim())
|
|
306
|
+
.filter((line) => line.length > 0)
|
|
307
|
+
.map((line) => JSON.parse(line) as { from: string; to: string; label: string });
|
|
308
|
+
} catch (err) {
|
|
309
|
+
if ((err as NodeJS.ErrnoException).code === "ENOENT") return [];
|
|
310
|
+
throw err;
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
test("a judge-accepted merge builds the create path's graph edges for the target (round N+5 A)", async () => {
|
|
315
|
+
const memoryDir = await mkTempMemoryDir("semantic-merge-graph");
|
|
316
|
+
let orch: Orchestrator | undefined;
|
|
317
|
+
try {
|
|
318
|
+
const cfg = mergeLifecycleConfig(memoryDir, {
|
|
319
|
+
multiGraphMemoryEnabled: true,
|
|
320
|
+
threadingEnabled: true,
|
|
321
|
+
});
|
|
322
|
+
orch = new Orchestrator(cfg);
|
|
323
|
+
const judge = { calls: 0 };
|
|
324
|
+
installJudge(orch, judge);
|
|
325
|
+
// Fact 1 has no merge candidate (created; becomes the batch's causal and
|
|
326
|
+
// time predecessor). Fact 2 merges into the seeded target.
|
|
327
|
+
const FIRST = "The audit service tracks quarterly access reviews.";
|
|
328
|
+
stubExtraction(orch, () => ({
|
|
329
|
+
facts: [
|
|
330
|
+
{ category: "fact", content: FIRST, confidence: 0.9, tags: [] },
|
|
331
|
+
{ category: "fact", content: GRAPH_INCOMING, confidence: 0.9, tags: [] },
|
|
332
|
+
],
|
|
333
|
+
entities: [],
|
|
334
|
+
relationships: [],
|
|
335
|
+
questions: [],
|
|
336
|
+
profileUpdates: [],
|
|
337
|
+
}));
|
|
338
|
+
installNeighbors(orch, (query) =>
|
|
339
|
+
query === GRAPH_INCOMING ? [{ id: "merge-target-graph", score: BAND_SCORE }] : [],
|
|
340
|
+
);
|
|
341
|
+
const targetFile = await seedMergeTarget(memoryDir, "merge-target-graph", GRAPH_SEED, {
|
|
342
|
+
extraFrontmatter: ["entityRef: entity-billing-service"],
|
|
343
|
+
});
|
|
344
|
+
await seedMergeTarget(memoryDir, "merge-sibling-graph", GRAPH_SIBLING, {
|
|
345
|
+
extraFrontmatter: ["entityRef: entity-billing-service"],
|
|
346
|
+
});
|
|
347
|
+
await orch.processTurn("user", `Please remember: ${GRAPH_INCOMING}`, "session-graph");
|
|
348
|
+
assert.equal(await orch.waitForExtractionIdle(15_000), true);
|
|
349
|
+
|
|
350
|
+
assert.equal(judge.calls, 1, "the mergeable fact must reach the judge exactly once");
|
|
351
|
+
await assertMerged(targetFile, ["Tuesday cadence", "09:00 UTC"], 1);
|
|
352
|
+
|
|
353
|
+
const entityEdges = await readGraphEdges(memoryDir, "entity");
|
|
354
|
+
assert.equal(
|
|
355
|
+
entityEdges.filter((e) => e.from.endsWith("merge-target-graph.md")).length,
|
|
356
|
+
1,
|
|
357
|
+
"the merged target must carry an entity edge to its seeded sibling",
|
|
358
|
+
);
|
|
359
|
+
assert.ok(
|
|
360
|
+
entityEdges.some(
|
|
361
|
+
(e) =>
|
|
362
|
+
e.from.endsWith("merge-target-graph.md") &&
|
|
363
|
+
e.to.endsWith("merge-sibling-graph.md") &&
|
|
364
|
+
e.label === "entity-billing-service",
|
|
365
|
+
),
|
|
366
|
+
);
|
|
367
|
+
const timeEdges = await readGraphEdges(memoryDir, "time");
|
|
368
|
+
assert.equal(
|
|
369
|
+
timeEdges.filter((e) => e.to.endsWith("merge-target-graph.md")).length,
|
|
370
|
+
1,
|
|
371
|
+
"the merged target must carry a time edge from the same extraction's created fact",
|
|
372
|
+
);
|
|
373
|
+
assert.ok(
|
|
374
|
+
timeEdges.every((e) => typeof e.label === "string" && e.label.length > 0),
|
|
375
|
+
"time edges carry the thread id as their label",
|
|
376
|
+
);
|
|
377
|
+
const causalEdges = await readGraphEdges(memoryDir, "causal");
|
|
378
|
+
assert.equal(
|
|
379
|
+
causalEdges.filter((e) => e.to.endsWith("merge-target-graph.md")).length,
|
|
380
|
+
1,
|
|
381
|
+
"the merged body's causal phrase must link the created fact to the merged target",
|
|
382
|
+
);
|
|
383
|
+
} finally {
|
|
384
|
+
await orch?.destroy().catch(() => undefined);
|
|
385
|
+
await cleanupDir(memoryDir);
|
|
386
|
+
}
|
|
387
|
+
});
|
|
388
|
+
|
|
389
|
+
test("a preference accepted through semantic merge reaches the behavior-signal ledger (round N+5 B)", async () => {
|
|
390
|
+
const memoryDir = await mkTempMemoryDir("semantic-merge-pref");
|
|
391
|
+
let orch: Orchestrator | undefined;
|
|
392
|
+
try {
|
|
393
|
+
const cfg = mergeLifecycleConfig(memoryDir);
|
|
394
|
+
orch = new Orchestrator(cfg);
|
|
395
|
+
const judge = { calls: 0 };
|
|
396
|
+
installJudge(orch, judge);
|
|
397
|
+
stubExtraction(orch, () => ({
|
|
398
|
+
facts: [
|
|
399
|
+
{ category: "preference", content: PREF_INCOMING, confidence: 0.9, tags: [] },
|
|
400
|
+
],
|
|
401
|
+
entities: [],
|
|
402
|
+
relationships: [],
|
|
403
|
+
questions: [],
|
|
404
|
+
profileUpdates: [],
|
|
405
|
+
}));
|
|
406
|
+
installNeighbors(orch, (query) =>
|
|
407
|
+
query === PREF_INCOMING ? [{ id: "merge-target-pref", score: BAND_SCORE }] : [],
|
|
408
|
+
);
|
|
409
|
+
const targetFile = await seedMergeTarget(memoryDir, "merge-target-pref", PREF_SEED, {
|
|
410
|
+
category: "preference",
|
|
411
|
+
});
|
|
412
|
+
await orch.processTurn("user", `Please remember: ${PREF_INCOMING}`, "session-pref");
|
|
413
|
+
assert.equal(await orch.waitForExtractionIdle(15_000), true);
|
|
414
|
+
|
|
415
|
+
assert.equal(judge.calls, 1, "the preference must reach the judge exactly once");
|
|
416
|
+
await assertMerged(targetFile, ["terse commit subject lines", "imperative mood"], 1);
|
|
417
|
+
|
|
418
|
+
const ledgerPath = path.join(memoryDir, "state", "behavior-signals.jsonl");
|
|
419
|
+
const rows = (await readFile(ledgerPath, "utf8"))
|
|
420
|
+
.split("\n")
|
|
421
|
+
.map((line) => line.trim())
|
|
422
|
+
.filter((line) => line.length > 0)
|
|
423
|
+
.map((line) => JSON.parse(line) as Record<string, string>);
|
|
424
|
+
const affinity = rows.filter(
|
|
425
|
+
(row) => row.memoryId === "merge-target-pref" && row.signalType === "preference_affinity",
|
|
426
|
+
);
|
|
427
|
+
assert.equal(
|
|
428
|
+
affinity.length,
|
|
429
|
+
1,
|
|
430
|
+
"the merged preference target must record exactly one preference_affinity event",
|
|
431
|
+
);
|
|
432
|
+
assert.equal(affinity[0]?.direction, "positive");
|
|
433
|
+
assert.equal(affinity[0]?.category, "preference");
|
|
434
|
+
assert.equal(affinity[0]?.source, "extraction");
|
|
435
|
+
} finally {
|
|
436
|
+
await orch?.destroy().catch(() => undefined);
|
|
437
|
+
await cleanupDir(memoryDir);
|
|
438
|
+
}
|
|
439
|
+
});
|
|
440
|
+
|
|
441
|
+
test("a merge into a cold-tier target records graph edges at the committed cold path (round N+6 A)", async () => {
|
|
442
|
+
const memoryDir = await mkTempMemoryDir("semantic-merge-cold-graph");
|
|
443
|
+
let orch: Orchestrator | undefined;
|
|
444
|
+
try {
|
|
445
|
+
const cfg = mergeLifecycleConfig(memoryDir, {
|
|
446
|
+
multiGraphMemoryEnabled: true,
|
|
447
|
+
threadingEnabled: true,
|
|
448
|
+
});
|
|
449
|
+
orch = new Orchestrator(cfg);
|
|
450
|
+
const judge = { calls: 0 };
|
|
451
|
+
installJudge(orch, judge);
|
|
452
|
+
stubExtraction(orch, () => ({
|
|
453
|
+
facts: [
|
|
454
|
+
{ category: "fact", content: INCOMING_A, confidence: 0.9, tags: [] },
|
|
455
|
+
],
|
|
456
|
+
entities: [],
|
|
457
|
+
relationships: [],
|
|
458
|
+
questions: [],
|
|
459
|
+
profileUpdates: [],
|
|
460
|
+
}));
|
|
461
|
+
installNeighbors(orch, (query) =>
|
|
462
|
+
query === INCOMING_A ? [{ id: "merge-target-cold", score: BAND_SCORE }] : [],
|
|
463
|
+
);
|
|
464
|
+
// The sibling stays hot so the entity-edge neighbor scan can see it; the
|
|
465
|
+
// TARGET is demoted to cold/, invisible to the hot-only graph context.
|
|
466
|
+
await seedMergeTarget(memoryDir, "merge-sibling-cold", GRAPH_SIBLING, {
|
|
467
|
+
extraFrontmatter: ["entityRef: entity-billing-service"],
|
|
468
|
+
});
|
|
469
|
+
const coldFile = await seedMergeTarget(memoryDir, "merge-target-cold", GRAPH_SEED, {
|
|
470
|
+
tier: "cold",
|
|
471
|
+
extraFrontmatter: ["entityRef: entity-billing-service"],
|
|
472
|
+
});
|
|
473
|
+
await orch.processTurn("user", `Please remember: ${INCOMING_A}`, "session-cold");
|
|
474
|
+
assert.equal(await orch.waitForExtractionIdle(15_000), true);
|
|
475
|
+
|
|
476
|
+
assert.equal(judge.calls, 1, "the cold-tier target must still reach the judge");
|
|
477
|
+
await assertMerged(coldFile, ["Tuesday cadence", "09:00 UTC sharp"], 1);
|
|
478
|
+
|
|
479
|
+
const entityEdges = await readGraphEdges(memoryDir, "entity");
|
|
480
|
+
const targetEdges = entityEdges.filter((e) => e.from.endsWith("merge-target-cold.md"));
|
|
481
|
+
assert.equal(targetEdges.length, 1, "the cold target must carry its entity edge");
|
|
482
|
+
assert.ok(
|
|
483
|
+
targetEdges[0]?.from.startsWith(`cold${path.sep}`),
|
|
484
|
+
`entity edge must reference the committed cold path, got ${targetEdges[0]?.from}`,
|
|
485
|
+
);
|
|
486
|
+
// The pre-fix hot-only fallback fabricated facts/<day>/merge-target-cold.md.
|
|
487
|
+
const day = path.basename(path.dirname(coldFile));
|
|
488
|
+
const fabricated = path.join("facts", day, "merge-target-cold.md");
|
|
489
|
+
for (const type of ["entity", "time", "causal"] as const) {
|
|
490
|
+
const edges = await readGraphEdges(memoryDir, type);
|
|
491
|
+
assert.ok(
|
|
492
|
+
edges.every((e) => e.from !== fabricated && e.to !== fabricated),
|
|
493
|
+
`no ${type} edge may reference the fabricated hot path ${fabricated}`,
|
|
494
|
+
);
|
|
495
|
+
}
|
|
496
|
+
} finally {
|
|
497
|
+
await orch?.destroy().catch(() => undefined);
|
|
498
|
+
await cleanupDir(memoryDir);
|
|
499
|
+
}
|
|
500
|
+
});
|
|
501
|
+
|
|
502
|
+
test("repeated merges of one target replace, not re-append, its entity edges (round N+6 B)", async () => {
|
|
503
|
+
const memoryDir = await mkTempMemoryDir("semantic-merge-dup-edges");
|
|
504
|
+
let orch: Orchestrator | undefined;
|
|
505
|
+
try {
|
|
506
|
+
const cfg = mergeLifecycleConfig(memoryDir, { multiGraphMemoryEnabled: true });
|
|
507
|
+
orch = new Orchestrator(cfg);
|
|
508
|
+
const judge = { calls: 0 };
|
|
509
|
+
installJudge(orch, judge);
|
|
510
|
+
// Turn 1 merges INCOMING_A into the target; turn 2 merges INCOMING_B into
|
|
511
|
+
// the SAME (already-merged) target — the replay pattern the judge stub
|
|
512
|
+
// exists for.
|
|
513
|
+
let extractionCall = 0;
|
|
514
|
+
stubExtraction(orch, () => {
|
|
515
|
+
extractionCall++;
|
|
516
|
+
return {
|
|
517
|
+
facts: [
|
|
518
|
+
{
|
|
519
|
+
category: "fact",
|
|
520
|
+
content: extractionCall === 1 ? INCOMING_A : INCOMING_B,
|
|
521
|
+
confidence: 0.9,
|
|
522
|
+
tags: [],
|
|
523
|
+
},
|
|
524
|
+
],
|
|
525
|
+
entities: [],
|
|
526
|
+
relationships: [],
|
|
527
|
+
questions: [],
|
|
528
|
+
profileUpdates: [],
|
|
529
|
+
};
|
|
530
|
+
});
|
|
531
|
+
installNeighbors(orch, (query) =>
|
|
532
|
+
query === INCOMING_A || query === INCOMING_B
|
|
533
|
+
? [{ id: "merge-target-dup", score: BAND_SCORE }]
|
|
534
|
+
: [],
|
|
535
|
+
);
|
|
536
|
+
const targetFile = await seedMergeTarget(memoryDir, "merge-target-dup", GRAPH_SEED, {
|
|
537
|
+
extraFrontmatter: ["entityRef: entity-billing-service"],
|
|
538
|
+
});
|
|
539
|
+
await seedMergeTarget(memoryDir, "merge-sibling-dup", GRAPH_SIBLING, {
|
|
540
|
+
extraFrontmatter: ["entityRef: entity-billing-service"],
|
|
541
|
+
});
|
|
542
|
+
await orch.processTurn("user", `Please remember: ${INCOMING_A}`, "session-dup-1");
|
|
543
|
+
assert.equal(await orch.waitForExtractionIdle(15_000), true);
|
|
544
|
+
await orch.processTurn("user", `Please remember: ${INCOMING_B}`, "session-dup-2");
|
|
545
|
+
assert.equal(await orch.waitForExtractionIdle(15_000), true);
|
|
546
|
+
|
|
547
|
+
assert.equal(judge.calls, 2, "both turns must reach the judge");
|
|
548
|
+
await assertMerged(targetFile, ["Tuesday cadence", "09:00 UTC sharp", "payments module"], 2);
|
|
549
|
+
|
|
550
|
+
const entityEdges = await readGraphEdges(memoryDir, "entity");
|
|
551
|
+
const fromTarget = entityEdges.filter((e) => e.from.endsWith("merge-target-dup.md"));
|
|
552
|
+
assert.equal(
|
|
553
|
+
fromTarget.length,
|
|
554
|
+
1,
|
|
555
|
+
`re-merging must replace the target's entity edges, got ${JSON.stringify(fromTarget)}`,
|
|
556
|
+
);
|
|
557
|
+
assert.equal(
|
|
558
|
+
fromTarget.filter((e) => e.to.endsWith("merge-sibling-dup.md")).length,
|
|
559
|
+
1,
|
|
560
|
+
"exactly one edge per (target, neighbor) pair after both merges",
|
|
561
|
+
);
|
|
562
|
+
} finally {
|
|
563
|
+
await orch?.destroy().catch(() => undefined);
|
|
564
|
+
await cleanupDir(memoryDir);
|
|
565
|
+
}
|
|
566
|
+
});
|
|
567
|
+
|
|
568
|
+
test("the fact after a merge chains time/causal adjacency through the merged target (round N+6 C)", async () => {
|
|
569
|
+
const memoryDir = await mkTempMemoryDir("semantic-merge-thread-chain");
|
|
570
|
+
let orch: Orchestrator | undefined;
|
|
571
|
+
try {
|
|
572
|
+
const cfg = mergeLifecycleConfig(memoryDir, {
|
|
573
|
+
multiGraphMemoryEnabled: true,
|
|
574
|
+
threadingEnabled: true,
|
|
575
|
+
});
|
|
576
|
+
orch = new Orchestrator(cfg);
|
|
577
|
+
const judge = { calls: 0 };
|
|
578
|
+
installJudge(orch, judge);
|
|
579
|
+
// One batch: FIRST is created (becomes the thread's first episode),
|
|
580
|
+
// INCOMING_A merges into the target, then SUCCESSOR is created and must
|
|
581
|
+
// chain through the merged target — its "because" phrase drives the
|
|
582
|
+
// causal edge. No other content carries a causal phrase.
|
|
583
|
+
const FIRST = "The audit service tracks quarterly access reviews.";
|
|
584
|
+
const SUCCESSOR =
|
|
585
|
+
"The billing service deploy queue drains slowly because reconciliation locks the ledger.";
|
|
586
|
+
stubExtraction(orch, () => ({
|
|
587
|
+
facts: [
|
|
588
|
+
{ category: "fact", content: FIRST, confidence: 0.9, tags: [] },
|
|
589
|
+
{ category: "fact", content: INCOMING_A, confidence: 0.9, tags: [] },
|
|
590
|
+
{ category: "fact", content: SUCCESSOR, confidence: 0.9, tags: [] },
|
|
591
|
+
],
|
|
592
|
+
entities: [],
|
|
593
|
+
relationships: [],
|
|
594
|
+
questions: [],
|
|
595
|
+
profileUpdates: [],
|
|
596
|
+
}));
|
|
597
|
+
installNeighbors(orch, (query) =>
|
|
598
|
+
query === INCOMING_A ? [{ id: "merge-target-thread", score: BAND_SCORE }] : [],
|
|
599
|
+
);
|
|
600
|
+
const targetFile = await seedMergeTarget(memoryDir, "merge-target-thread", GRAPH_SEED, {});
|
|
601
|
+
await orch.processTurn("user", `Please remember: ${INCOMING_A}`, "session-chain");
|
|
602
|
+
assert.equal(await orch.waitForExtractionIdle(15_000), true);
|
|
603
|
+
|
|
604
|
+
assert.equal(judge.calls, 1, "only the mergeable fact reaches the judge");
|
|
605
|
+
await assertMerged(targetFile, ["Tuesday cadence", "09:00 UTC sharp"], 1);
|
|
606
|
+
|
|
607
|
+
const timeEdges = await readGraphEdges(memoryDir, "time");
|
|
608
|
+
const intoTarget = timeEdges.find((e) => e.to.endsWith("merge-target-thread.md"));
|
|
609
|
+
const outOfTarget = timeEdges.find((e) => e.from.endsWith("merge-target-thread.md"));
|
|
610
|
+
assert.ok(intoTarget, "the merged target keeps its own incoming time edge");
|
|
611
|
+
assert.ok(
|
|
612
|
+
outOfTarget,
|
|
613
|
+
"the fact following the merge must chain its time edge through the merged target",
|
|
614
|
+
);
|
|
615
|
+
assert.equal(
|
|
616
|
+
outOfTarget?.from,
|
|
617
|
+
intoTarget?.to,
|
|
618
|
+
"the successor edge starts at the merged target's committed path",
|
|
619
|
+
);
|
|
620
|
+
const causalEdges = await readGraphEdges(memoryDir, "causal");
|
|
621
|
+
assert.equal(
|
|
622
|
+
causalEdges.filter((e) => e.from.endsWith("merge-target-thread.md")).length,
|
|
623
|
+
1,
|
|
624
|
+
"the successor fact's causal phrase must link through the merged target",
|
|
625
|
+
);
|
|
626
|
+
} finally {
|
|
627
|
+
await orch?.destroy().catch(() => undefined);
|
|
628
|
+
await cleanupDir(memoryDir);
|
|
629
|
+
}
|
|
630
|
+
});
|
|
631
|
+
|
|
632
|
+
// ── Subject ───────────────────────────────────────────────────────────────────
|
|
633
|
+
|
|
634
|
+
const subject: LifecycleSubject<SemanticMergeLifecycleState> = {
|
|
635
|
+
async setup(row: MatrixRow): Promise<SemanticMergeLifecycleState> {
|
|
636
|
+
const memoryDir = await mkTempMemoryDir(`semantic-merge-${row.id}`);
|
|
637
|
+
let primary: Orchestrator | undefined;
|
|
638
|
+
try {
|
|
639
|
+
const cfg =
|
|
640
|
+
row.id === "sparse-metadata-with-binding" || row.id === "sparse-metadata-without-binding"
|
|
641
|
+
? rememberedBindingConfig(memoryDir)
|
|
642
|
+
: row.id === "explicit-provider-identity" || row.id === "provider-rebinding"
|
|
643
|
+
? namespacedConfig(memoryDir)
|
|
644
|
+
: row.id === "dedupe-replay"
|
|
645
|
+
? mergeLifecycleConfig(memoryDir, {
|
|
646
|
+
extractionDedupeEnabled: true,
|
|
647
|
+
extractionDedupeWindowMs: 60_000,
|
|
648
|
+
})
|
|
649
|
+
: mergeLifecycleConfig(memoryDir);
|
|
650
|
+
primary = new Orchestrator(cfg);
|
|
651
|
+
const judge = { calls: 0 };
|
|
652
|
+
const state: SemanticMergeLifecycleState = {
|
|
653
|
+
memoryDir,
|
|
654
|
+
cfg,
|
|
655
|
+
orchestrators: [primary],
|
|
656
|
+
calls: [],
|
|
657
|
+
judge,
|
|
658
|
+
};
|
|
659
|
+
state.calls = stubExtraction(primary, (turns) =>
|
|
660
|
+
singleFactResult(
|
|
661
|
+
// Identical buffered turns (the replay row keeps its in-window
|
|
662
|
+
// duplicate buffered) collapse to one fact body so the neighbor
|
|
663
|
+
// index sees the same content on the force-flushed re-extraction.
|
|
664
|
+
[...new Set(turns.filter((turn) => turn.role === "user").map((turn) => turn.content))].join(" "),
|
|
665
|
+
),
|
|
666
|
+
);
|
|
667
|
+
installJudge(primary, judge);
|
|
668
|
+
|
|
669
|
+
// In-band neighbors for the row's OWN target ids: candidate resolution
|
|
670
|
+
// runs against the write's storage, so a foreign row's id resolves to
|
|
671
|
+
// nothing and the merge correctly falls back to create.
|
|
672
|
+
const namespacedRow =
|
|
673
|
+
row.id === "explicit-provider-identity" ||
|
|
674
|
+
row.id === "provider-rebinding" ||
|
|
675
|
+
row.id === "sparse-metadata-with-binding" ||
|
|
676
|
+
row.id === "sparse-metadata-without-binding";
|
|
677
|
+
const neighbors = (query: string): Array<{ id: string; score: number }> => {
|
|
678
|
+
if (query === TURN_A || query === INCOMING_A) {
|
|
679
|
+
return [{ id: namespacedRow ? "merge-target-alice" : "merge-target-main", score: BAND_SCORE }];
|
|
680
|
+
}
|
|
681
|
+
if (query === TURN_BOB) return [{ id: "merge-target-bob", score: BAND_SCORE }];
|
|
682
|
+
if (query === TURN_B) return [{ id: "merge-target-main", score: BAND_SCORE }];
|
|
683
|
+
return [];
|
|
684
|
+
};
|
|
685
|
+
installNeighbors(primary, neighbors);
|
|
686
|
+
|
|
687
|
+
// Seed the merge targets AFTER construction (cold caches) so the
|
|
688
|
+
// orchestrator's own storage instances discover them on first read.
|
|
689
|
+
if (row.id === "explicit-provider-identity" || row.id === "provider-rebinding") {
|
|
690
|
+
state.aliceRoot = await resolveNamespaceStorageRoot(cfg, "alice");
|
|
691
|
+
state.aliceFile = await seedMergeTarget(state.aliceRoot, "merge-target-alice", SEED_A);
|
|
692
|
+
}
|
|
693
|
+
if (row.id === "sparse-metadata-with-binding" || row.id === "sparse-metadata-without-binding") {
|
|
694
|
+
state.aliceRoot = await resolveNamespaceStorageRoot(cfg, "alice");
|
|
695
|
+
state.aliceFile = await seedMergeTarget(state.aliceRoot, "merge-target-alice", SEED_A);
|
|
696
|
+
}
|
|
697
|
+
if (row.id === "provider-rebinding") {
|
|
698
|
+
state.bobRoot = await resolveNamespaceStorageRoot(cfg, "bob");
|
|
699
|
+
state.bobFile = await seedMergeTarget(state.bobRoot, "merge-target-bob", SEED_B);
|
|
700
|
+
}
|
|
701
|
+
if (
|
|
702
|
+
row.id === "restart-reload-recovery" ||
|
|
703
|
+
row.id === "compaction-flush" ||
|
|
704
|
+
row.id === "before-reset" ||
|
|
705
|
+
row.id === "session-end" ||
|
|
706
|
+
row.id === "dedupe-replay"
|
|
707
|
+
) {
|
|
708
|
+
state.targetFile = await seedMergeTarget(memoryDir, "merge-target-main", SEED_A);
|
|
709
|
+
}
|
|
710
|
+
return state;
|
|
711
|
+
} catch (err) {
|
|
712
|
+
// Transactional setup: a partial build must not leak the orchestrator or temp dir.
|
|
713
|
+
await primary?.destroy().catch(() => undefined);
|
|
714
|
+
await cleanupDir(memoryDir);
|
|
715
|
+
throw err;
|
|
716
|
+
}
|
|
717
|
+
},
|
|
718
|
+
|
|
719
|
+
async exercise(state: SemanticMergeLifecycleState, row: MatrixRow): Promise<void> {
|
|
720
|
+
const primary = state.orchestrators[0];
|
|
721
|
+
switch (row.id) {
|
|
722
|
+
case "explicit-provider-identity": {
|
|
723
|
+
await primary.processTurn("user", TURN_A, "alice:chat");
|
|
724
|
+
assert.equal(await primary.waitForExtractionIdle(15_000), true);
|
|
725
|
+
return;
|
|
726
|
+
}
|
|
727
|
+
case "sparse-metadata-with-binding": {
|
|
728
|
+
await primary.processTurn("user", INCOMING_A, REMEMBERED_SESSION);
|
|
729
|
+
await primary.flushSession(REMEMBERED_SESSION, { reason: "before_reset" });
|
|
730
|
+
assert.equal(await primary.waitForExtractionIdle(15_000), true);
|
|
731
|
+
return;
|
|
732
|
+
}
|
|
733
|
+
case "sparse-metadata-without-binding": {
|
|
734
|
+
await primary.processTurn("user", INCOMING_A, UNBOUND_SESSION);
|
|
735
|
+
await primary.flushSession(UNBOUND_SESSION, { reason: "before_reset" });
|
|
736
|
+
assert.equal(await primary.waitForExtractionIdle(15_000), true);
|
|
737
|
+
return;
|
|
738
|
+
}
|
|
739
|
+
case "provider-rebinding": {
|
|
740
|
+
await primary.processTurn("user", TURN_A, "alice:chat");
|
|
741
|
+
assert.equal(await primary.waitForExtractionIdle(15_000), true);
|
|
742
|
+
await primary.processTurn("user", TURN_BOB, "bob:chat");
|
|
743
|
+
assert.equal(await primary.waitForExtractionIdle(15_000), true);
|
|
744
|
+
return;
|
|
745
|
+
}
|
|
746
|
+
case "restart-reload-recovery": {
|
|
747
|
+
await primary.processTurn("user", TURN_A, "session-merge");
|
|
748
|
+
assert.equal(await primary.waitForExtractionIdle(15_000), true);
|
|
749
|
+
await primary.destroy();
|
|
750
|
+
state.orchestrators.length = 0;
|
|
751
|
+
const second = new Orchestrator(mergeLifecycleConfig(state.memoryDir));
|
|
752
|
+
state.orchestrators.push(second);
|
|
753
|
+
state.restartCalls = stubExtraction(second, (turns) =>
|
|
754
|
+
singleFactResult(
|
|
755
|
+
turns
|
|
756
|
+
.filter((turn) => turn.role === "user")
|
|
757
|
+
.map((turn) => turn.content)
|
|
758
|
+
.join(" "),
|
|
759
|
+
),
|
|
760
|
+
);
|
|
761
|
+
installJudge(second, state.judge);
|
|
762
|
+
installNeighbors(second, (query) =>
|
|
763
|
+
query === TURN_B ? [{ id: "merge-target-main", score: BAND_SCORE }] : [],
|
|
764
|
+
);
|
|
765
|
+
await second.processTurn("user", TURN_B, "session-merge");
|
|
766
|
+
assert.equal(await second.waitForExtractionIdle(15_000), true);
|
|
767
|
+
return;
|
|
768
|
+
}
|
|
769
|
+
case "compaction-flush": {
|
|
770
|
+
await primary.processTurn("user", INCOMING_A, "session-compact");
|
|
771
|
+
await primary.processTurn("assistant", "Noted the Tuesday deploy detail.", "session-compact");
|
|
772
|
+
await primary.flushSession("session-compact", { reason: "compaction" });
|
|
773
|
+
return;
|
|
774
|
+
}
|
|
775
|
+
case "before-reset": {
|
|
776
|
+
await primary.processTurn("user", INCOMING_A, "session-reset");
|
|
777
|
+
await primary.flushSession("session-reset", { reason: "before_reset" });
|
|
778
|
+
return;
|
|
779
|
+
}
|
|
780
|
+
case "session-end": {
|
|
781
|
+
await primary.processTurn("user", INCOMING_A, "session-end");
|
|
782
|
+
await primary.flushSession("session-end", { reason: "session_end" });
|
|
783
|
+
return;
|
|
784
|
+
}
|
|
785
|
+
case "dedupe-replay": {
|
|
786
|
+
await primary.processTurn("user", TURN_A, "session-dedupe");
|
|
787
|
+
assert.equal(await primary.waitForExtractionIdle(15_000), true);
|
|
788
|
+
await primary.processTurn("user", TURN_A, "session-dedupe");
|
|
789
|
+
assert.equal(await primary.waitForExtractionIdle(15_000), true);
|
|
790
|
+
state.callsBeforeForceFlush = state.calls.length;
|
|
791
|
+
await primary.flushSession("session-dedupe", { reason: "before_reset" });
|
|
792
|
+
return;
|
|
793
|
+
}
|
|
794
|
+
default: {
|
|
795
|
+
const exhaustive: never = row.id;
|
|
796
|
+
throw new Error(`unhandled row ${String(exhaustive)}`);
|
|
797
|
+
}
|
|
798
|
+
}
|
|
799
|
+
},
|
|
800
|
+
|
|
801
|
+
async invariants(state: SemanticMergeLifecycleState, row: MatrixRow): Promise<void> {
|
|
802
|
+
const primary = state.orchestrators[0];
|
|
803
|
+
assert.equal(state.cfg.semanticMerge.enabled, true, "every lifecycle row enables semantic merge-on-write");
|
|
804
|
+
switch (row.id) {
|
|
805
|
+
case "explicit-provider-identity": {
|
|
806
|
+
assert.equal(state.judge.calls, 1, "the turn extracts and merges exactly once");
|
|
807
|
+
assert.ok(state.aliceFile);
|
|
808
|
+
await assertMerged(state.aliceFile, [NEEDLE_SEED_A, NEEDLE_A], 1);
|
|
809
|
+
assert.equal(
|
|
810
|
+
(await markdownFilesUnder(path.join(state.aliceRoot ?? "", "facts"))).length,
|
|
811
|
+
1,
|
|
812
|
+
"a merge updates in place — no second fragment",
|
|
813
|
+
);
|
|
814
|
+
assert.equal(
|
|
815
|
+
(await markdownFilesUnder(path.join(state.memoryDir, "facts"))).length,
|
|
816
|
+
0,
|
|
817
|
+
"an identity-routed merge must not land in the default root",
|
|
818
|
+
);
|
|
819
|
+
return;
|
|
820
|
+
}
|
|
821
|
+
case "sparse-metadata-with-binding": {
|
|
822
|
+
assert.equal(state.judge.calls, 1);
|
|
823
|
+
assert.ok(state.aliceFile);
|
|
824
|
+
await assertMerged(state.aliceFile, [NEEDLE_SEED_A, NEEDLE_A], 1);
|
|
825
|
+
assert.equal((await markdownFilesUnder(path.join(state.aliceRoot ?? "", "facts"))).length, 1);
|
|
826
|
+
return;
|
|
827
|
+
}
|
|
828
|
+
case "sparse-metadata-without-binding": {
|
|
829
|
+
// The unbound session must NOT merge into alice's target: candidate
|
|
830
|
+
// resolution runs against the WRITE's storage, where her id is unknown.
|
|
831
|
+
assert.equal(state.judge.calls, 0, "no candidate resolves for an unbound session — the judge is never asked");
|
|
832
|
+
assert.ok(state.aliceFile);
|
|
833
|
+
const raw = await readFile(state.aliceFile, "utf8");
|
|
834
|
+
assert.ok(raw.includes(NEEDLE_SEED_A), "alice's target keeps its original body");
|
|
835
|
+
assert.ok(!raw.includes(NEEDLE_A), "an unbound session must not merge into alice's target");
|
|
836
|
+
assert.ok(!raw.includes("derived_via"), "alice's target carries no merge provenance");
|
|
837
|
+
assert.equal(
|
|
838
|
+
(await markdownFilesUnder(path.join(state.memoryDir, "facts"))).length,
|
|
839
|
+
1,
|
|
840
|
+
"the fact is CREATED in the default root instead of merging cross-tenant",
|
|
841
|
+
);
|
|
842
|
+
return;
|
|
843
|
+
}
|
|
844
|
+
case "provider-rebinding": {
|
|
845
|
+
assert.equal(state.judge.calls, 2, "each identity's turn merges exactly once");
|
|
846
|
+
assert.ok(state.aliceFile && state.bobFile);
|
|
847
|
+
const aliceRaw = await readFile(state.aliceFile, "utf8");
|
|
848
|
+
const bobRaw = await readFile(state.bobFile, "utf8");
|
|
849
|
+
assert.ok(aliceRaw.includes(NEEDLE_A) && aliceRaw.includes(NEEDLE_SEED_A));
|
|
850
|
+
assert.ok(bobRaw.includes(NEEDLE_BOB) && bobRaw.includes(NEEDLE_SEED_B));
|
|
851
|
+
assert.ok(!aliceRaw.includes(NEEDLE_BOB), "bob's fact must not merge into alice's target");
|
|
852
|
+
assert.ok(!bobRaw.includes(NEEDLE_A), "alice's fact must not merge into bob's target");
|
|
853
|
+
assert.ok(aliceRaw.includes("derived_via: merge") && bobRaw.includes("derived_via: merge"));
|
|
854
|
+
return;
|
|
855
|
+
}
|
|
856
|
+
case "restart-reload-recovery": {
|
|
857
|
+
const second = state.orchestrators[0];
|
|
858
|
+
assert.ok(state.targetFile);
|
|
859
|
+
assert.equal(state.judge.calls, 2, "one merge before the restart, one through the restarted instance");
|
|
860
|
+
await assertMerged(state.targetFile, [NEEDLE_SEED_A, NEEDLE_A, NEEDLE_B], 2);
|
|
861
|
+
assert.equal(
|
|
862
|
+
(await markdownFilesUnder(path.join(state.memoryDir, "facts"))).length,
|
|
863
|
+
1,
|
|
864
|
+
"both merges update the SAME target — restart must not fork a fragment",
|
|
865
|
+
);
|
|
866
|
+
const context = await second.recall("What does the billing service deploy include?", "reader");
|
|
867
|
+
assert.match(context, /payments module/i, "the post-restart merge is recallable");
|
|
868
|
+
return;
|
|
869
|
+
}
|
|
870
|
+
case "compaction-flush": {
|
|
871
|
+
assert.equal(state.calls.length, 1, "the flush compacts the buffered turns into one extraction");
|
|
872
|
+
assert.deepEqual(
|
|
873
|
+
state.calls[0]?.map((turn) => turn.content),
|
|
874
|
+
[INCOMING_A, "Noted the Tuesday deploy detail."],
|
|
875
|
+
);
|
|
876
|
+
assert.equal(state.judge.calls, 1);
|
|
877
|
+
assert.ok(state.targetFile);
|
|
878
|
+
await assertMerged(state.targetFile, [NEEDLE_SEED_A, NEEDLE_A], 1);
|
|
879
|
+
assert.equal((await markdownFilesUnder(path.join(state.memoryDir, "facts"))).length, 1);
|
|
880
|
+
return;
|
|
881
|
+
}
|
|
882
|
+
case "before-reset": {
|
|
883
|
+
assert.equal(state.judge.calls, 1);
|
|
884
|
+
assert.ok(state.targetFile);
|
|
885
|
+
await assertMerged(state.targetFile, [NEEDLE_SEED_A, NEEDLE_A], 1);
|
|
886
|
+
const merged = await readFile(state.targetFile, "utf8");
|
|
887
|
+
await primary.flushSession("session-reset", { reason: "before_reset" });
|
|
888
|
+
assert.equal(state.judge.calls, 1, "an empty-buffer re-flush must not merge again");
|
|
889
|
+
assert.equal(
|
|
890
|
+
await readFile(state.targetFile, "utf8").then((raw) => raw === merged),
|
|
891
|
+
true,
|
|
892
|
+
"the drained re-flush leaves the merged target byte-identical",
|
|
893
|
+
);
|
|
894
|
+
assert.equal((await markdownFilesUnder(path.join(state.memoryDir, "facts"))).length, 1);
|
|
895
|
+
return;
|
|
896
|
+
}
|
|
897
|
+
case "session-end": {
|
|
898
|
+
assert.equal(state.calls.length, 1, "session_end drains the buffer like before_reset");
|
|
899
|
+
assert.equal(state.judge.calls, 1);
|
|
900
|
+
assert.ok(state.targetFile);
|
|
901
|
+
await assertMerged(state.targetFile, [NEEDLE_SEED_A, NEEDLE_A], 1);
|
|
902
|
+
assert.equal((await markdownFilesUnder(path.join(state.memoryDir, "facts"))).length, 1);
|
|
903
|
+
return;
|
|
904
|
+
}
|
|
905
|
+
case "dedupe-replay": {
|
|
906
|
+
assert.equal(
|
|
907
|
+
state.callsBeforeForceFlush,
|
|
908
|
+
1,
|
|
909
|
+
"the in-window duplicate must be suppressed — one extraction before the force flush",
|
|
910
|
+
);
|
|
911
|
+
assert.equal(state.calls.length, 2, "the force flush bypasses the dedupe fingerprint and re-extracts");
|
|
912
|
+
assert.equal(state.judge.calls, 2, "the replayed duplicate MERGES AGAIN into the same target");
|
|
913
|
+
assert.ok(state.targetFile);
|
|
914
|
+
await assertMerged(state.targetFile, [NEEDLE_SEED_A, NEEDLE_A], 2);
|
|
915
|
+
assert.equal(
|
|
916
|
+
(await markdownFilesUnder(path.join(state.memoryDir, "facts"))).length,
|
|
917
|
+
1,
|
|
918
|
+
"replaying the duplicate must not fork a second fragment",
|
|
919
|
+
);
|
|
920
|
+
return;
|
|
921
|
+
}
|
|
922
|
+
default: {
|
|
923
|
+
const exhaustive: never = row.id;
|
|
924
|
+
throw new Error(`unhandled row ${String(exhaustive)}`);
|
|
925
|
+
}
|
|
926
|
+
}
|
|
927
|
+
},
|
|
928
|
+
|
|
929
|
+
async teardown(state: SemanticMergeLifecycleState): Promise<void> {
|
|
930
|
+
for (const orchestrator of state.orchestrators) {
|
|
931
|
+
await orchestrator.destroy().catch(() => undefined);
|
|
932
|
+
}
|
|
933
|
+
await cleanupDir(state.memoryDir);
|
|
934
|
+
},
|
|
935
|
+
};
|
|
936
|
+
|
|
937
|
+
test("a merged target's new claims reach the temporal timeline index (round N+7 D)", async () => {
|
|
938
|
+
const memoryDir = await mkTempMemoryDir("semantic-merge-temporal");
|
|
939
|
+
let orch: Orchestrator | undefined;
|
|
940
|
+
try {
|
|
941
|
+
const cfg = mergeLifecycleConfig(memoryDir, { queryAwareIndexingEnabled: true });
|
|
942
|
+
orch = new Orchestrator(cfg);
|
|
943
|
+
const judge = { calls: 0 };
|
|
944
|
+
installJudge(orch, judge);
|
|
945
|
+
const targetFile = await seedMergeTarget(memoryDir, "merge-target-temporal", SEED_A);
|
|
946
|
+
// Turn 1: an unrelated created fact bootstraps the temporal index, so the
|
|
947
|
+
// seeded target is indexed with its PRE-merge text (which has no
|
|
948
|
+
// "sharp") and turn 2 runs the INCREMENTAL, id-filtered update.
|
|
949
|
+
const FIRST = "The audit service tracks quarterly access reviews.";
|
|
950
|
+
stubExtraction(orch, () => ({
|
|
951
|
+
facts: [{ category: "fact", content: FIRST, confidence: 0.9, tags: [] }],
|
|
952
|
+
entities: [],
|
|
953
|
+
relationships: [],
|
|
954
|
+
questions: [],
|
|
955
|
+
profileUpdates: [],
|
|
956
|
+
}));
|
|
957
|
+
installNeighbors(orch, () => []);
|
|
958
|
+
await orch.processTurn("user", `Please remember: ${FIRST}`, "session-temporal-1");
|
|
959
|
+
assert.equal(await orch.waitForExtractionIdle(15_000), true);
|
|
960
|
+
// Turn 1's temporal-index bootstrap is fire-and-forget: wait until it
|
|
961
|
+
// has COMPLETED (the created fact's row is served) so turn 2 runs the
|
|
962
|
+
// INCREMENTAL id-filtered update instead of re-bootstrapping the whole
|
|
963
|
+
// corpus — a re-bootstrap would index the already-merged target even
|
|
964
|
+
// without the fix, masking the defect.
|
|
965
|
+
let bootstrapped = false;
|
|
966
|
+
for (let attempt = 0; attempt < 40 && !bootstrapped; attempt++) {
|
|
967
|
+
const events =
|
|
968
|
+
(await queryTemporalTimelineAsync(memoryDir, { query: "quarterly", limit: 10 })) ?? [];
|
|
969
|
+
bootstrapped = events.length > 0;
|
|
970
|
+
if (!bootstrapped) await sleep(250);
|
|
971
|
+
}
|
|
972
|
+
assert.ok(bootstrapped, "turn 1's temporal index bootstrap must complete");
|
|
973
|
+
// Turn 2: the merge commits "09:00 UTC sharp" into the target.
|
|
974
|
+
stubExtraction(orch, () => ({
|
|
975
|
+
facts: [{ category: "fact", content: INCOMING_A, confidence: 0.9, tags: [] }],
|
|
976
|
+
entities: [],
|
|
977
|
+
relationships: [],
|
|
978
|
+
questions: [],
|
|
979
|
+
profileUpdates: [],
|
|
980
|
+
}));
|
|
981
|
+
installNeighbors(orch, (query) =>
|
|
982
|
+
query === INCOMING_A ? [{ id: "merge-target-temporal", score: BAND_SCORE }] : [],
|
|
983
|
+
);
|
|
984
|
+
await orch.processTurn("user", `Please remember: ${INCOMING_A}`, "session-temporal-2");
|
|
985
|
+
assert.equal(await orch.waitForExtractionIdle(15_000), true);
|
|
986
|
+
assert.equal(judge.calls, 1);
|
|
987
|
+
await assertMerged(targetFile, ["Tuesday cadence", "09:00 UTC sharp"], 1);
|
|
988
|
+
// Membership in the returned rows is not evidence on its own: with few
|
|
989
|
+
// rows the query returns the chronology unfiltered (and the no-match
|
|
990
|
+
// fallback returns both edges). Assert the merged token's HASH is on the
|
|
991
|
+
// target's own row — the exact contract queryTemporalTimelineAsync
|
|
992
|
+
// scores against.
|
|
993
|
+
const sharpToken = crypto.createHash("sha256").update("sharp").digest("hex");
|
|
994
|
+
let served = false;
|
|
995
|
+
for (let attempt = 0; attempt < 40 && !served; attempt++) {
|
|
996
|
+
const events =
|
|
997
|
+
(await queryTemporalTimelineAsync(memoryDir, { query: "sharp", limit: 10 })) ?? [];
|
|
998
|
+
served = events.some(
|
|
999
|
+
(event) =>
|
|
1000
|
+
event.path.endsWith("merge-target-temporal.md") &&
|
|
1001
|
+
(event.searchTokenHashes ?? []).includes(sharpToken),
|
|
1002
|
+
);
|
|
1003
|
+
if (!served) await sleep(250);
|
|
1004
|
+
}
|
|
1005
|
+
assert.ok(
|
|
1006
|
+
served,
|
|
1007
|
+
"event-order queries must find the merged target's new tokens without a full rebuild",
|
|
1008
|
+
);
|
|
1009
|
+
} finally {
|
|
1010
|
+
await orch?.destroy().catch(() => undefined);
|
|
1011
|
+
await cleanupDir(memoryDir);
|
|
1012
|
+
}
|
|
1013
|
+
});
|
|
1014
|
+
|
|
1015
|
+
test("a cold-tier merged target's new claims reach the temporal timeline index (round N+9 B)", async () => {
|
|
1016
|
+
const memoryDir = await mkTempMemoryDir("semantic-merge-temporal-cold");
|
|
1017
|
+
let orch: Orchestrator | undefined;
|
|
1018
|
+
try {
|
|
1019
|
+
const cfg = mergeLifecycleConfig(memoryDir, { queryAwareIndexingEnabled: true });
|
|
1020
|
+
orch = new Orchestrator(cfg);
|
|
1021
|
+
const judge = { calls: 0 };
|
|
1022
|
+
installJudge(orch, judge);
|
|
1023
|
+
// The target lives under cold/ — invisible to the hot-only
|
|
1024
|
+
// readAllMemories() scan that builds the incremental temporal-index
|
|
1025
|
+
// pool, so only a cold-aware id lookup can refresh its row.
|
|
1026
|
+
const coldFile = await seedMergeTarget(memoryDir, "merge-target-cold-temporal", SEED_A, {
|
|
1027
|
+
tier: "cold",
|
|
1028
|
+
});
|
|
1029
|
+
// Turn 1 bootstraps the index from a hot fact so turn 2 runs the
|
|
1030
|
+
// INCREMENTAL id-filtered update (a re-bootstrap would index the cold
|
|
1031
|
+
// target even without the fix, masking the defect).
|
|
1032
|
+
const FIRST = "The audit service tracks quarterly access reviews.";
|
|
1033
|
+
stubExtraction(orch, () => ({
|
|
1034
|
+
facts: [{ category: "fact", content: FIRST, confidence: 0.9, tags: [] }],
|
|
1035
|
+
entities: [],
|
|
1036
|
+
relationships: [],
|
|
1037
|
+
questions: [],
|
|
1038
|
+
profileUpdates: [],
|
|
1039
|
+
}));
|
|
1040
|
+
installNeighbors(orch, () => []);
|
|
1041
|
+
await orch.processTurn("user", `Please remember: ${FIRST}`, "session-temporal-cold-1");
|
|
1042
|
+
assert.equal(await orch.waitForExtractionIdle(15_000), true);
|
|
1043
|
+
let bootstrapped = false;
|
|
1044
|
+
for (let attempt = 0; attempt < 40 && !bootstrapped; attempt++) {
|
|
1045
|
+
const events =
|
|
1046
|
+
(await queryTemporalTimelineAsync(memoryDir, { query: "quarterly", limit: 10 })) ?? [];
|
|
1047
|
+
bootstrapped = events.length > 0;
|
|
1048
|
+
if (!bootstrapped) await sleep(250);
|
|
1049
|
+
}
|
|
1050
|
+
assert.ok(bootstrapped, "turn 1's temporal index bootstrap must complete");
|
|
1051
|
+
// Turn 2: the merge commits "09:00 UTC sharp" into the COLD target.
|
|
1052
|
+
stubExtraction(orch, () => ({
|
|
1053
|
+
facts: [{ category: "fact", content: INCOMING_A, confidence: 0.9, tags: [] }],
|
|
1054
|
+
entities: [],
|
|
1055
|
+
relationships: [],
|
|
1056
|
+
questions: [],
|
|
1057
|
+
profileUpdates: [],
|
|
1058
|
+
}));
|
|
1059
|
+
installNeighbors(orch, (query) =>
|
|
1060
|
+
query === INCOMING_A ? [{ id: "merge-target-cold-temporal", score: BAND_SCORE }] : [],
|
|
1061
|
+
);
|
|
1062
|
+
await orch.processTurn("user", `Please remember: ${INCOMING_A}`, "session-temporal-cold-2");
|
|
1063
|
+
assert.equal(await orch.waitForExtractionIdle(15_000), true);
|
|
1064
|
+
assert.equal(judge.calls, 1);
|
|
1065
|
+
await assertMerged(coldFile, ["Tuesday cadence", "09:00 UTC sharp"], 1);
|
|
1066
|
+
// Same token-hash contract as the hot-tier test above: the merged
|
|
1067
|
+
// token's HASH must sit on the cold target's own row.
|
|
1068
|
+
const sharpToken = crypto.createHash("sha256").update("sharp").digest("hex");
|
|
1069
|
+
let served = false;
|
|
1070
|
+
for (let attempt = 0; attempt < 40 && !served; attempt++) {
|
|
1071
|
+
const events =
|
|
1072
|
+
(await queryTemporalTimelineAsync(memoryDir, { query: "sharp", limit: 10 })) ?? [];
|
|
1073
|
+
served = events.some(
|
|
1074
|
+
(event) =>
|
|
1075
|
+
event.path.endsWith("merge-target-cold-temporal.md") &&
|
|
1076
|
+
(event.searchTokenHashes ?? []).includes(sharpToken),
|
|
1077
|
+
);
|
|
1078
|
+
if (!served) await sleep(250);
|
|
1079
|
+
}
|
|
1080
|
+
assert.ok(
|
|
1081
|
+
served,
|
|
1082
|
+
"event-order queries must find the cold-tier merged target's new tokens",
|
|
1083
|
+
);
|
|
1084
|
+
} finally {
|
|
1085
|
+
await orch?.destroy().catch(() => undefined);
|
|
1086
|
+
await cleanupDir(memoryDir);
|
|
1087
|
+
}
|
|
1088
|
+
});
|
|
1089
|
+
|
|
1090
|
+
test("repeated merges leave exactly one generated edge per graph type for the target (round N+7 G)", async () => {
|
|
1091
|
+
const memoryDir = await mkTempMemoryDir("semantic-merge-graph-parity");
|
|
1092
|
+
let orch: Orchestrator | undefined;
|
|
1093
|
+
try {
|
|
1094
|
+
const cfg = mergeLifecycleConfig(memoryDir, {
|
|
1095
|
+
multiGraphMemoryEnabled: true,
|
|
1096
|
+
threadingEnabled: true,
|
|
1097
|
+
});
|
|
1098
|
+
orch = new Orchestrator(cfg);
|
|
1099
|
+
const judge = { calls: 0 };
|
|
1100
|
+
installJudge(orch, judge);
|
|
1101
|
+
// Two turns; each creates its own thread-local fact (becomes that
|
|
1102
|
+
// thread's first episode) and merges a because-sentence into the SAME
|
|
1103
|
+
// target. Each merge appends time+causal edges INTO the target from that
|
|
1104
|
+
// turn's created fact — without per-type replacement the second merge
|
|
1105
|
+
// duplicates them.
|
|
1106
|
+
const GRAPH_INCOMING_2 =
|
|
1107
|
+
"The billing service deploy window opens early because the EU handoff needs overlap.";
|
|
1108
|
+
let extractionCall = 0;
|
|
1109
|
+
stubExtraction(orch, () => {
|
|
1110
|
+
extractionCall++;
|
|
1111
|
+
return {
|
|
1112
|
+
facts: [
|
|
1113
|
+
{
|
|
1114
|
+
category: "fact",
|
|
1115
|
+
content: extractionCall === 1 ? "The audit service tracks quarterly access reviews." : "The audit service also archives vendor onboarding evidence.",
|
|
1116
|
+
confidence: 0.9,
|
|
1117
|
+
tags: [],
|
|
1118
|
+
},
|
|
1119
|
+
{
|
|
1120
|
+
category: "fact",
|
|
1121
|
+
content: extractionCall === 1 ? GRAPH_INCOMING : GRAPH_INCOMING_2,
|
|
1122
|
+
confidence: 0.9,
|
|
1123
|
+
tags: [],
|
|
1124
|
+
},
|
|
1125
|
+
],
|
|
1126
|
+
entities: [],
|
|
1127
|
+
relationships: [],
|
|
1128
|
+
questions: [],
|
|
1129
|
+
profileUpdates: [],
|
|
1130
|
+
};
|
|
1131
|
+
});
|
|
1132
|
+
installNeighbors(orch, (query) =>
|
|
1133
|
+
query === GRAPH_INCOMING || query === GRAPH_INCOMING_2
|
|
1134
|
+
? [{ id: "merge-target-parity", score: BAND_SCORE }]
|
|
1135
|
+
: [],
|
|
1136
|
+
);
|
|
1137
|
+
const targetFile = await seedMergeTarget(memoryDir, "merge-target-parity", GRAPH_SEED, {
|
|
1138
|
+
extraFrontmatter: ["entityRef: entity-billing-service"],
|
|
1139
|
+
});
|
|
1140
|
+
await seedMergeTarget(memoryDir, "merge-sibling-parity", GRAPH_SIBLING, {
|
|
1141
|
+
extraFrontmatter: ["entityRef: entity-billing-service"],
|
|
1142
|
+
});
|
|
1143
|
+
await orch.processTurn("user", `Please remember: ${GRAPH_INCOMING}`, "session-parity-1");
|
|
1144
|
+
assert.equal(await orch.waitForExtractionIdle(15_000), true);
|
|
1145
|
+
await orch.processTurn("user", `Please remember: ${GRAPH_INCOMING_2}`, "session-parity-2");
|
|
1146
|
+
assert.equal(await orch.waitForExtractionIdle(15_000), true);
|
|
1147
|
+
|
|
1148
|
+
assert.equal(judge.calls, 2, "both turns must reach the judge");
|
|
1149
|
+
await assertMerged(targetFile, ["Tuesday cadence", "09:00 UTC"], 2);
|
|
1150
|
+
|
|
1151
|
+
const entityEdges = await readGraphEdges(memoryDir, "entity");
|
|
1152
|
+
assert.equal(
|
|
1153
|
+
entityEdges.filter((e) => e.from.endsWith("merge-target-parity.md")).length,
|
|
1154
|
+
1,
|
|
1155
|
+
"entity: the target's from-side edges are replaced, not accumulated",
|
|
1156
|
+
);
|
|
1157
|
+
const timeEdges = await readGraphEdges(memoryDir, "time");
|
|
1158
|
+
assert.equal(
|
|
1159
|
+
timeEdges.filter((e) => e.to.endsWith("merge-target-parity.md")).length,
|
|
1160
|
+
1,
|
|
1161
|
+
`time: one inbound edge for the target, got ${JSON.stringify(timeEdges)}`,
|
|
1162
|
+
);
|
|
1163
|
+
const causalEdges = await readGraphEdges(memoryDir, "causal");
|
|
1164
|
+
assert.equal(
|
|
1165
|
+
causalEdges.filter((e) => e.to.endsWith("merge-target-parity.md")).length,
|
|
1166
|
+
1,
|
|
1167
|
+
`causal: one inbound edge for the target, got ${JSON.stringify(causalEdges)}`,
|
|
1168
|
+
);
|
|
1169
|
+
} finally {
|
|
1170
|
+
await orch?.destroy().catch(() => undefined);
|
|
1171
|
+
await cleanupDir(memoryDir);
|
|
1172
|
+
}
|
|
1173
|
+
});
|
|
1174
|
+
|
|
1175
|
+
runLifecycleMatrix("semantic-merge-lifecycle", subject);
|