@remnic/core 9.7.15 → 9.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/access-admin-ops-surface.d.ts +13 -6
- package/dist/access-admin-ops-surface.js +85 -71
- package/dist/access-audit.js +2 -2
- package/dist/access-boundary.d.ts +35 -17
- package/dist/access-boundary.js +86 -74
- package/dist/access-citation.d.ts +28 -0
- package/dist/access-citation.js +9 -0
- package/dist/access-cli.js +194 -178
- package/dist/access-cli.js.map +1 -1
- package/dist/access-errors.d.ts +16 -1
- package/dist/access-errors.js +7 -3
- package/dist/access-http.d.ts +25 -19
- package/dist/access-http.js +100 -87
- package/dist/access-identity-continuity-surface.d.ts +13 -8
- package/dist/access-identity-continuity-surface.js +85 -71
- package/dist/access-lcm-surface.d.ts +16 -9
- package/dist/access-lcm-surface.js +85 -71
- package/dist/access-mcp.d.ts +23 -16
- package/dist/access-mcp.js +96 -84
- package/dist/access-memory-search-fanout.d.ts +93 -0
- package/dist/access-memory-search-fanout.js +18 -0
- package/dist/access-observe-write-surface.d.ts +26 -8
- package/dist/access-observe-write-surface.js +85 -71
- package/dist/access-operations-batch.js +92 -80
- package/dist/access-operations.d.ts +29 -22
- package/dist/access-operations.js +94 -82
- package/dist/access-recall-concurrency.d.ts +250 -0
- package/dist/access-recall-concurrency.js +157 -0
- package/dist/access-recall-response.d.ts +130 -0
- package/dist/access-recall-response.js +147 -0
- package/dist/access-recall-surface.d.ts +15 -7
- package/dist/access-recall-surface.js +85 -71
- package/dist/access-schema.d.ts +98 -98
- package/dist/access-schema.js +8 -6
- package/dist/{access-service-DpcYZ9Nl.d.ts → access-service-DEL4qCrc.d.ts} +25 -32
- package/dist/access-service.d.ts +13 -6
- package/dist/access-service.js +89 -72
- package/dist/access-surface-catalog.d.ts +23 -16
- package/dist/access-surface-catalog.js +1 -0
- package/dist/access-surface-catalog.js.map +1 -1
- package/dist/access-token-capabilities.d.ts +2 -1
- package/dist/access-token-capabilities.js +4 -2
- package/dist/action-confidence.d.ts +3 -1
- package/dist/active-memory-bridge.d.ts +3 -1
- package/dist/active-memory-bridge.js +4 -4
- package/dist/active-recall.d.ts +4 -1
- package/dist/active-recall.js +23 -13
- package/dist/active-recall.js.map +1 -1
- package/dist/adapters/index.js +4 -4
- package/dist/adapters/registry.js +2 -2
- package/dist/{auto-sync-AAMTVZMK.js → auto-sync-UZG3XQZ5.js} +14 -8
- package/dist/{auto-sync-AAMTVZMK.js.map → auto-sync-UZG3XQZ5.js.map} +1 -1
- package/dist/behavior-learner.d.ts +3 -1
- package/dist/behavior-signals.d.ts +3 -1
- package/dist/bootstrap.d.ts +12 -7
- package/dist/bootstrap.js +2 -2
- package/dist/bounded-jsonl-state.d.ts +54 -0
- package/dist/bounded-jsonl-state.js +10 -0
- package/dist/boxes.js +2 -2
- package/dist/briefing.d.ts +5 -2
- package/dist/briefing.js +34 -24
- package/dist/buffer-surprise-report.d.ts +3 -1
- package/dist/buffer.d.ts +60 -2
- package/dist/buffer.js +3 -3
- package/dist/bulk-import/index.d.ts +5 -3
- package/dist/calibration.d.ts +3 -1
- package/dist/calibration.js +6 -6
- package/dist/capabilities.d.ts +7 -7
- package/dist/capabilities.js +1 -1
- package/dist/{capsule-crypto-CZJSLEFG.js → capsule-crypto-YO5QJ6L3.js} +3 -3
- package/dist/capsule-crypto-YO5QJ6L3.js.map +1 -0
- package/dist/{catalog-C9444P10.d.ts → catalog-BWqHb5HT.d.ts} +1 -1
- package/dist/causal-behavior.d.ts +3 -1
- package/dist/causal-behavior.js +6 -6
- package/dist/causal-chain.js +4 -4
- package/dist/causal-consolidation.d.ts +3 -1
- package/dist/causal-consolidation.js +43 -34
- package/dist/causal-consolidation.js.map +1 -1
- package/dist/causal-retrieval.js +6 -6
- package/dist/causal-trajectory-graph.d.ts +3 -1
- package/dist/causal-trajectory-graph.js +1 -1
- package/dist/causal-trajectory.js +1 -1
- package/dist/{chunk-A3GOMRJG.js → chunk-23L3IXFD.js} +2 -2
- package/dist/chunk-2CGLBUW3.js +15 -0
- package/dist/chunk-2CGLBUW3.js.map +1 -0
- package/dist/{chunk-MRHFJV44.js → chunk-2DQ2XREU.js} +2 -2
- package/dist/{chunk-QKQYXZVS.js → chunk-2EJOEICD.js} +1819 -1481
- package/dist/chunk-2EJOEICD.js.map +1 -0
- package/dist/{chunk-L2B3EE7B.js → chunk-2QMKMVHH.js} +2 -2
- package/dist/{chunk-VJVNLNDQ.js → chunk-2UFO7X4Q.js} +7 -7
- package/dist/{chunk-2MR3MFQB.js → chunk-2VEOKUPP.js} +3 -3
- package/dist/chunk-2Z24E5W2.js +22 -0
- package/dist/chunk-2Z24E5W2.js.map +1 -0
- package/dist/{chunk-RHOZ5LLO.js → chunk-347WD2LE.js} +2 -2
- package/dist/{chunk-HDTQVNWD.js → chunk-3QAH2S33.js} +3 -3
- package/dist/{chunk-37CTGERM.js → chunk-3WCASNQZ.js} +12 -1
- package/dist/chunk-3WCASNQZ.js.map +1 -0
- package/dist/{chunk-LRNMRXJO.js → chunk-3Y224NYC.js} +3 -3
- package/dist/{chunk-3IY64MV6.js → chunk-44XZ75OQ.js} +44 -10
- package/dist/chunk-44XZ75OQ.js.map +1 -0
- package/dist/{chunk-VFSP73LP.js → chunk-45GF7Y3V.js} +2 -2
- package/dist/{chunk-K7LOWSLU.js → chunk-4BJQODZF.js} +2479 -1520
- package/dist/chunk-4BJQODZF.js.map +1 -0
- package/dist/{chunk-JREB6UFH.js → chunk-57OWKFUQ.js} +17 -6
- package/dist/chunk-57OWKFUQ.js.map +1 -0
- package/dist/{chunk-OEHCMALU.js → chunk-5I4M26Q7.js} +5 -5
- package/dist/{chunk-2AZXIZEN.js → chunk-5Q7EVV4X.js} +6 -6
- package/dist/{chunk-LC7OODWZ.js → chunk-5SB7SC2G.js} +80 -49
- package/dist/chunk-5SB7SC2G.js.map +1 -0
- package/dist/{chunk-PUBTHW5O.js → chunk-6DRPPQEJ.js} +2 -2
- package/dist/{chunk-NWTGD4AN.js → chunk-6KIQT7RG.js} +10 -9
- package/dist/chunk-6KIQT7RG.js.map +1 -0
- package/dist/{chunk-FDCTQWLW.js → chunk-6O22TZSB.js} +3 -3
- package/dist/{chunk-ZQ7UBUZ6.js → chunk-6QYST5XN.js} +6 -6
- package/dist/{chunk-W4PGTAS2.js → chunk-6WAFYNVT.js} +3 -3
- package/dist/chunk-6ZMAMSHS.js +20 -0
- package/dist/chunk-6ZMAMSHS.js.map +1 -0
- package/dist/{chunk-R5VAVVHP.js → chunk-72A4L5JJ.js} +2 -2
- package/dist/{chunk-X64YLC24.js → chunk-72VAXGEI.js} +5 -13
- package/dist/chunk-72VAXGEI.js.map +1 -0
- package/dist/{chunk-IKQTFQMI.js → chunk-755Q3DGF.js} +22 -3
- package/dist/chunk-755Q3DGF.js.map +1 -0
- package/dist/{chunk-OVJ2HIPE.js → chunk-76GLCM57.js} +158 -125
- package/dist/chunk-76GLCM57.js.map +1 -0
- package/dist/{chunk-D7FOKIY5.js → chunk-7AMFUATK.js} +12 -9
- package/dist/{chunk-D7FOKIY5.js.map → chunk-7AMFUATK.js.map} +1 -1
- package/dist/{chunk-BFVEEB7W.js → chunk-7ON2JW73.js} +57 -22
- package/dist/chunk-7ON2JW73.js.map +1 -0
- package/dist/chunk-7RNBVZZO.js +270 -0
- package/dist/chunk-7RNBVZZO.js.map +1 -0
- package/dist/{chunk-3FAMU5TX.js → chunk-A6USBZTO.js} +2 -2
- package/dist/{chunk-ZHIVCODT.js → chunk-A72SWTWW.js} +7 -7
- package/dist/{chunk-FC77VUX5.js → chunk-A7Q43EUZ.js} +2 -2
- package/dist/{chunk-UJBESW6X.js → chunk-AE7WMV4V.js} +7 -7
- package/dist/{chunk-J2HSAU72.js → chunk-AIK5W3IW.js} +29 -1
- package/dist/chunk-AIK5W3IW.js.map +1 -0
- package/dist/{chunk-A5UXZRRM.js → chunk-AJ7QCSIS.js} +2 -2
- package/dist/{chunk-FNMFJWBY.js → chunk-ANEUPF6O.js} +5 -5
- package/dist/chunk-AREGMX2J.js +50 -0
- package/dist/chunk-AREGMX2J.js.map +1 -0
- package/dist/chunk-AVPXXV6J.js +448 -0
- package/dist/chunk-AVPXXV6J.js.map +1 -0
- package/dist/{chunk-2A3OXAP2.js → chunk-B7RWBZXW.js} +2 -2
- package/dist/{chunk-GWTFLF2P.js → chunk-BJMBJZ2Y.js} +59 -59
- package/dist/chunk-BJMBJZ2Y.js.map +1 -0
- package/dist/{chunk-74F5STS4.js → chunk-BLAKS3CP.js} +4 -4
- package/dist/{chunk-M7VGG3ID.js → chunk-BV5WMSIJ.js} +9 -9
- package/dist/{chunk-2BFLDH5F.js → chunk-BXLOS5AJ.js} +2 -2
- package/dist/{chunk-UXKMMNJD.js → chunk-C23BYTJV.js} +6 -6
- package/dist/{chunk-JZHHREBL.js → chunk-C3AKM55W.js} +2 -2
- package/dist/{chunk-RP7YXA67.js → chunk-C5JF6EZ7.js} +2 -2
- package/dist/{chunk-T7Y5KPLN.js → chunk-C6XBRYVD.js} +16 -15
- package/dist/chunk-C6XBRYVD.js.map +1 -0
- package/dist/{chunk-FTYDWKO6.js → chunk-CEN6DW6X.js} +12 -2
- package/dist/chunk-CEN6DW6X.js.map +1 -0
- package/dist/chunk-CGASUJVP.js +421 -0
- package/dist/chunk-CGASUJVP.js.map +1 -0
- package/dist/{chunk-EHCONI6E.js → chunk-CQMEM5CC.js} +2 -2
- package/dist/{chunk-PBNZVPFS.js → chunk-CXZQOVIE.js} +12 -12
- package/dist/chunk-D3V3UMET.js +44 -0
- package/dist/chunk-D3V3UMET.js.map +1 -0
- package/dist/{chunk-7N2RUBAP.js → chunk-DDA47TES.js} +2 -2
- package/dist/{chunk-64SE4X5G.js → chunk-DEBHXHJR.js} +3 -3
- package/dist/{chunk-EHK6KZHK.js → chunk-DNHVPZOD.js} +2 -2
- package/dist/{chunk-437S3G37.js → chunk-DQEMWVMT.js} +2 -2
- package/dist/{chunk-LZZNTPLR.js → chunk-DX3XRGLH.js} +2 -2
- package/dist/chunk-EA2H5LML.js +72 -0
- package/dist/chunk-EA2H5LML.js.map +1 -0
- package/dist/{chunk-INELTPRG.js → chunk-EEC7UJPX.js} +103 -106
- package/dist/chunk-EEC7UJPX.js.map +1 -0
- package/dist/{chunk-KSNVDPDF.js → chunk-EKBNDKDY.js} +21 -20
- package/dist/chunk-EKBNDKDY.js.map +1 -0
- package/dist/{chunk-YF6FMLBT.js → chunk-EQWEIIKQ.js} +64 -34
- package/dist/chunk-EQWEIIKQ.js.map +1 -0
- package/dist/{chunk-DZKHSDOM.js → chunk-ES36HTSC.js} +4 -4
- package/dist/{chunk-TN7THW5U.js → chunk-ET356KSB.js} +2 -2
- package/dist/{chunk-YNDLCWXS.js → chunk-EZ25VE3G.js} +4 -4
- package/dist/{chunk-DMSDUGDM.js → chunk-F7GAIWFA.js} +20 -20
- package/dist/{chunk-G6XXLKC5.js → chunk-FFPJINJE.js} +6 -6
- package/dist/chunk-FMLKOL2Z.js +636 -0
- package/dist/chunk-FMLKOL2Z.js.map +1 -0
- package/dist/chunk-FQC3DHH2.js +20 -0
- package/dist/chunk-FQC3DHH2.js.map +1 -0
- package/dist/{chunk-G4CN3ZXL.js → chunk-FUTF72MJ.js} +15 -15
- package/dist/chunk-GCASGYIO.js +71 -0
- package/dist/chunk-GCASGYIO.js.map +1 -0
- package/dist/{chunk-HF77M34J.js → chunk-GFPZS3F5.js} +8 -8
- package/dist/{chunk-BCDKBDSY.js → chunk-GNVZQ5JN.js} +99 -41
- package/dist/chunk-GNVZQ5JN.js.map +1 -0
- package/dist/{chunk-G6NKALRN.js → chunk-H4WG3UQZ.js} +2 -2
- package/dist/{chunk-UDQUSU27.js → chunk-HNZKVLAN.js} +2 -2
- package/dist/{chunk-5EZXHU36.js → chunk-HYQJQGD3.js} +2 -2
- package/dist/{chunk-OAZVR2BV.js → chunk-IDZFBSHN.js} +8 -6
- package/dist/{chunk-OAZVR2BV.js.map → chunk-IDZFBSHN.js.map} +1 -1
- package/dist/{chunk-PLQE6MJA.js → chunk-IFH6LWTO.js} +50 -8
- package/dist/chunk-IFH6LWTO.js.map +1 -0
- package/dist/{chunk-RULE4VG5.js → chunk-ILGC6JH6.js} +21 -6
- package/dist/chunk-ILGC6JH6.js.map +1 -0
- package/dist/chunk-IUM3VSHI.js +113 -0
- package/dist/chunk-IUM3VSHI.js.map +1 -0
- package/dist/{chunk-DCFV7A7H.js → chunk-IYNGIMVG.js} +101 -91
- package/dist/chunk-IYNGIMVG.js.map +1 -0
- package/dist/{chunk-TRWPCR6P.js → chunk-JECBQBEX.js} +3 -3
- package/dist/{chunk-E6CBU7XP.js → chunk-JYIFGGEM.js} +2 -2
- package/dist/{chunk-VA4WSW54.js → chunk-JYIJNNN3.js} +3 -3
- package/dist/{chunk-L5HJQC36.js → chunk-K2TDK7GG.js} +3 -3
- package/dist/{chunk-KDXVOFYY.js → chunk-KAVBJSBQ.js} +4 -4
- package/dist/{chunk-BS4SGLWV.js → chunk-KF5EUVKQ.js} +16 -6
- package/dist/chunk-KF5EUVKQ.js.map +1 -0
- package/dist/chunk-KFZY2OFH.js +42 -0
- package/dist/chunk-KFZY2OFH.js.map +1 -0
- package/dist/{chunk-4OKLXWDZ.js → chunk-KNB7IBBU.js} +2 -2
- package/dist/{chunk-QLULYKWZ.js → chunk-KSXJOC4F.js} +2 -2
- package/dist/{chunk-2S6QHZGJ.js → chunk-L2DPF4DF.js} +1 -1
- package/dist/{chunk-JI5AVXYO.js → chunk-LEPOP46W.js} +6 -6
- package/dist/{chunk-O7BBFJLP.js → chunk-LTAVSOZW.js} +2 -2
- package/dist/{chunk-E3VODCC3.js → chunk-LUDXM7MD.js} +14 -2
- package/dist/{chunk-E3VODCC3.js.map → chunk-LUDXM7MD.js.map} +1 -1
- package/dist/{chunk-2IZAGAJR.js → chunk-LULR4IUW.js} +3 -3
- package/dist/{chunk-QQGPUKMT.js → chunk-MC5Q3J7S.js} +398 -7
- package/dist/chunk-MC5Q3J7S.js.map +1 -0
- package/dist/{chunk-WMWVO45V.js → chunk-MC6XO7T4.js} +2 -2
- package/dist/chunk-MKQBXQKU.js +107 -0
- package/dist/chunk-MKQBXQKU.js.map +1 -0
- package/dist/{chunk-7PND4PUC.js → chunk-MMGGVZ4Q.js} +29 -9
- package/dist/{chunk-7PND4PUC.js.map → chunk-MMGGVZ4Q.js.map} +1 -1
- package/dist/{chunk-D6RIFG4O.js → chunk-MNB5RMAY.js} +2 -2
- package/dist/{chunk-Q5WG43OP.js → chunk-MYFQ3ED4.js} +7 -7
- package/dist/{chunk-V3UE5I5F.js → chunk-MYVPLJOO.js} +2 -2
- package/dist/chunk-NINSR6LX.js +83 -0
- package/dist/chunk-NINSR6LX.js.map +1 -0
- package/dist/{chunk-MTWKDOVO.js → chunk-NLE6TSLG.js} +2 -2
- package/dist/{chunk-DBRYEIGY.js → chunk-OJP3N7UK.js} +2 -2
- package/dist/{chunk-VDW4VU7B.js → chunk-OQJIB32H.js} +28 -23
- package/dist/chunk-OQJIB32H.js.map +1 -0
- package/dist/{chunk-SMX5267P.js → chunk-OZKOPQCM.js} +2 -2
- package/dist/{chunk-JCK7V6SN.js → chunk-P65YJEEL.js} +2 -2
- package/dist/{chunk-MTR7BZQN.js → chunk-PBGWYM3H.js} +3 -3
- package/dist/chunk-PIG4WJFQ.js +87 -0
- package/dist/chunk-PIG4WJFQ.js.map +1 -0
- package/dist/chunk-PQVCH2AE.js +76 -0
- package/dist/chunk-PQVCH2AE.js.map +1 -0
- package/dist/{chunk-AXTXN4T6.js → chunk-PWFCHOB3.js} +2 -2
- package/dist/{chunk-FDPZVY6D.js → chunk-QFN4U7AG.js} +2 -2
- package/dist/{chunk-K2ZLHESQ.js → chunk-QIEUU4UK.js} +2 -2
- package/dist/{chunk-DYP3YYJJ.js → chunk-QJAQNXWI.js} +39 -5
- package/dist/chunk-QJAQNXWI.js.map +1 -0
- package/dist/{chunk-3D6AFGQR.js → chunk-QVT3E4CE.js} +7 -7
- package/dist/{chunk-FIJUY7GG.js → chunk-R26ZMXR2.js} +23 -23
- package/dist/{chunk-FIJUY7GG.js.map → chunk-R26ZMXR2.js.map} +1 -1
- package/dist/chunk-R3DJVOSM.js +34 -0
- package/dist/chunk-R3DJVOSM.js.map +1 -0
- package/dist/{chunk-SFQ6QNL7.js → chunk-R6YJI3HE.js} +2 -1
- package/dist/{chunk-5CJVLG3H.js → chunk-RFDD3B7W.js} +5 -5
- package/dist/{chunk-JBPKEARU.js → chunk-RKNJBZ55.js} +4 -4
- package/dist/chunk-ROI7BK7H.js +88 -0
- package/dist/chunk-ROI7BK7H.js.map +1 -0
- package/dist/{chunk-FNZQGN76.js → chunk-S2BBISHU.js} +4 -4
- package/dist/{chunk-IALLX7EE.js → chunk-S2FSBU4H.js} +3 -3
- package/dist/{chunk-BFPETA3S.js → chunk-SCCO46KZ.js} +3 -2
- package/dist/chunk-SCCO46KZ.js.map +1 -0
- package/dist/chunk-SICXL4MK.js +165 -0
- package/dist/chunk-SICXL4MK.js.map +1 -0
- package/dist/chunk-SK7AMF2J.js +168 -0
- package/dist/chunk-SK7AMF2J.js.map +1 -0
- package/dist/{chunk-LZOV7MX7.js → chunk-TDTXKOAJ.js} +6474 -5747
- package/dist/chunk-TDTXKOAJ.js.map +1 -0
- package/dist/{chunk-KSBEEMQF.js → chunk-TG6IHVE2.js} +151 -6
- package/dist/chunk-TG6IHVE2.js.map +1 -0
- package/dist/{chunk-7K5Q6COX.js → chunk-TVVEYCNW.js} +4 -4
- package/dist/{chunk-DPAENM7O.js → chunk-U3ZAVRP3.js} +2 -2
- package/dist/{chunk-ODPLEWB6.js → chunk-UFGFMWAD.js} +47 -17
- package/dist/chunk-UFGFMWAD.js.map +1 -0
- package/dist/{chunk-4KJLYTLS.js → chunk-V57NLO5P.js} +2 -2
- package/dist/{chunk-FLVL4L3L.js → chunk-VDX2J7OX.js} +2 -2
- package/dist/{chunk-AJLLIIML.js → chunk-W4FBGYGK.js} +1 -1
- package/dist/{chunk-TXLGHDHS.js → chunk-W4XJFLAL.js} +53 -32
- package/dist/chunk-W4XJFLAL.js.map +1 -0
- package/dist/{chunk-6SXVCD7W.js → chunk-W7IGLX4G.js} +2 -2
- package/dist/{chunk-KQDNAXOM.js → chunk-WG5HSQ4P.js} +19 -6
- package/dist/chunk-WG5HSQ4P.js.map +1 -0
- package/dist/{chunk-LTJAMRGI.js → chunk-WL5EBRDH.js} +17 -3
- package/dist/chunk-WL5EBRDH.js.map +1 -0
- package/dist/{chunk-KAWTOYAW.js → chunk-WLZYGLJ4.js} +2 -2
- package/dist/{chunk-RO4LQGT3.js → chunk-WON4APQY.js} +4 -4
- package/dist/{chunk-PQVK2S3J.js → chunk-WZFAPHIK.js} +6 -6
- package/dist/{chunk-KQPJLK3X.js → chunk-XL24QQ63.js} +2 -2
- package/dist/{chunk-RVZRYUQH.js → chunk-YROFNEG5.js} +7 -7
- package/dist/{chunk-RAR2BSEM.js → chunk-YV32N67O.js} +1 -1
- package/dist/chunk-YV32N67O.js.map +1 -0
- package/dist/{chunk-PBSAPHHA.js → chunk-YVKF3HEW.js} +1 -1
- package/dist/chunk-YVKF3HEW.js.map +1 -0
- package/dist/{chunk-VRAD2FGO.js → chunk-Z54EM5YZ.js} +12 -12
- package/dist/{chunk-2VN4JAX7.js → chunk-ZOKZIR5K.js} +95 -33
- package/dist/chunk-ZOKZIR5K.js.map +1 -0
- package/dist/{cli-Cd5fMmhq.d.ts → cli-C_pqqTCw.d.ts} +28 -12
- package/dist/cli.d.ts +16 -9
- package/dist/cli.js +142 -127
- package/dist/compounding/engine.d.ts +5 -2
- package/dist/compounding/engine.js +36 -26
- package/dist/compounding/preference-consolidator.d.ts +3 -1
- package/dist/compounding/preference-consolidator.js +1 -1
- package/dist/compression-optimizer.d.ts +3 -1
- package/dist/config-qmd.d.ts +8 -0
- package/dist/config-qmd.js +20 -0
- package/dist/config-qmd.js.map +1 -0
- package/dist/config.d.ts +3 -1
- package/dist/config.js +15 -10
- package/dist/connectors/codex-materialize-runner.d.ts +3 -1
- package/dist/connectors/codex-materialize-runner.js +36 -26
- package/dist/connectors/codex-materialize.d.ts +3 -1
- package/dist/connectors/codex-materialize.js +2 -2
- package/dist/connectors/index.d.ts +3 -1
- package/dist/connectors/index.js +41 -32
- package/dist/consolidation-provenance-check.d.ts +5 -2
- package/dist/consolidation-undo.d.ts +5 -2
- package/dist/contradiction/index.d.ts +5 -2
- package/dist/contradiction/index.js +10 -4
- package/dist/{contradiction-scan-J57T5QV7.js → contradiction-scan-RJBABNRH.js} +4 -4
- package/dist/contradiction-scan-RJBABNRH.js.map +1 -0
- package/dist/conversation-index/backend.d.ts +3 -1
- package/dist/conversation-index/backend.js +5 -5
- package/dist/conversation-index/chunker.d.ts +3 -1
- package/dist/conversation-index/cleanup.js +2 -2
- package/dist/conversation-index/faiss-adapter.d.ts +3 -1
- package/dist/conversation-index/faiss-adapter.js +2 -2
- package/dist/conversation-index/indexer.d.ts +3 -1
- package/dist/conversation-index/indexer.js +2 -2
- package/dist/conversation-index/search.d.ts +3 -1
- package/dist/conversation-index/search.js +2 -2
- package/dist/cross-namespace-budget.d.ts +45 -1
- package/dist/cross-namespace-budget.js +5 -3
- package/dist/dashboard-runtime.js +2 -2
- package/dist/day-summary.d.ts +3 -1
- package/dist/day-summary.js +3 -3
- package/dist/delinearize.d.ts +3 -1
- package/dist/direct-answer-wiring.d.ts +3 -1
- package/dist/direct-answer.d.ts +3 -1
- package/dist/{dreams-ledger-6UODQOX6.js → dreams-ledger-567VKVOU.js} +4 -2
- package/dist/dreams-ledger-567VKVOU.js.map +1 -0
- package/dist/embedding-fallback.d.ts +3 -1
- package/dist/embedding-fallback.js +3 -3
- package/dist/enrichment/index.d.ts +3 -1
- package/dist/entity-retrieval.d.ts +5 -2
- package/dist/entity-retrieval.js +35 -26
- package/dist/entity-schema.d.ts +3 -1
- package/dist/explicit-capture.d.ts +13 -8
- package/dist/explicit-capture.js +7 -3
- package/dist/extraction-faithfulness.d.ts +3 -1
- package/dist/extraction-faithfulness.js +5 -5
- package/dist/extraction-judge-telemetry.d.ts +3 -1
- package/dist/extraction-judge-telemetry.js +2 -2
- package/dist/extraction-judge-training.d.ts +3 -1
- package/dist/extraction-judge-training.js +2 -2
- package/dist/extraction-judge.d.ts +3 -1
- package/dist/extraction-judge.js +5 -5
- package/dist/extraction.d.ts +3 -1
- package/dist/extraction.js +13 -12
- package/dist/faithfulness-config.js +3 -2
- package/dist/fallback-llm.d.ts +3 -1
- package/dist/fallback-llm.js +4 -4
- package/dist/{first-start-migration-TI67YMLG.js → first-start-migration-CSRHCJIQ.js} +2 -2
- package/dist/{forget-CQCLB5LB.js → forget-HRPLVB3W.js} +5 -5
- package/dist/graph-dashboard-diff.d.ts +3 -1
- package/dist/graph-dashboard-key.d.ts +3 -1
- package/dist/graph-dashboard-parser.d.ts +3 -1
- package/dist/{graph-edge-decay-IOGBBAGC.js → graph-edge-decay-Z6ENALU2.js} +3 -3
- package/dist/graph-edge-decay-Z6ENALU2.js.map +1 -0
- package/dist/graph-edge-reinforcement.d.ts +3 -1
- package/dist/graph-snapshot.d.ts +3 -1
- package/dist/graph-snapshot.js +2 -2
- package/dist/graph.d.ts +3 -1
- package/dist/graph.js +1 -1
- package/dist/identity-continuity.d.ts +1 -2
- package/dist/importance.d.ts +3 -1
- package/dist/importers/index.d.ts +3 -1
- package/dist/in-flight-reads.d.ts +3 -1
- package/dist/index.d.ts +473 -464
- package/dist/index.js +287 -239
- package/dist/intent.d.ts +3 -1
- package/dist/lcm/archive.js +2 -2
- package/dist/lcm/engine.d.ts +3 -1
- package/dist/lcm/engine.js +6 -6
- package/dist/lcm/index.d.ts +3 -1
- package/dist/lcm/index.js +13 -13
- package/dist/lcm/schema.js +2 -2
- package/dist/lcm/summarizer.js +3 -3
- package/dist/lcm/tools.d.ts +3 -1
- package/dist/lifecycle.d.ts +3 -1
- package/dist/live-connectors-runner.d.ts +3 -1
- package/dist/local-llm.d.ts +5 -2
- package/dist/local-llm.js +3 -3
- package/dist/local-model-endpoint.d.ts +3 -1
- package/dist/logger.d.ts +3 -1
- package/dist/logger.js +7 -3
- package/dist/maintenance/archive-observations.js +2 -2
- package/dist/maintenance/memory-governance.d.ts +3 -1
- package/dist/maintenance/memory-governance.js +35 -25
- package/dist/maintenance/migrate-observations.js +3 -3
- package/dist/maintenance/observation-ledger-utils.js +2 -2
- package/dist/maintenance/rebuild-memory-lifecycle-ledger.d.ts +102 -1
- package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +37 -25
- package/dist/maintenance/rebuild-memory-projection.js +38 -28
- package/dist/maintenance/rebuild-observations.js +3 -3
- package/dist/mcp-memory-inspector-app.d.ts +22 -15
- package/dist/memory-action-policy.d.ts +3 -1
- package/dist/memory-cache.d.ts +3 -1
- package/dist/memory-lifecycle-ledger-utils.d.ts +25 -2
- package/dist/memory-lifecycle-ledger-utils.js +11 -1
- package/dist/memory-projection-store.d.ts +3 -1
- package/dist/memory-projection-store.js +2 -2
- package/dist/memory-provenance.d.ts +3 -1
- package/dist/memory-worth-outcomes.d.ts +5 -2
- package/dist/model-registry.js +2 -2
- package/dist/models-json.d.ts +3 -1
- package/dist/models-json.js +2 -2
- package/dist/namespaces/migrate.d.ts +6 -3
- package/dist/namespaces/migrate.js +51 -42
- package/dist/namespaces/principal.d.ts +27 -2
- package/dist/namespaces/principal.js +6 -2
- package/dist/namespaces/search.d.ts +16 -2
- package/dist/namespaces/search.js +24 -20
- package/dist/namespaces/storage.d.ts +24 -3
- package/dist/namespaces/storage.js +35 -27
- package/dist/native-knowledge.d.ts +3 -1
- package/dist/native-knowledge.js +2 -2
- package/dist/negative.js +2 -2
- package/dist/offline-sync-exclude-globs.d.ts +13 -0
- package/dist/offline-sync-exclude-globs.js +10 -0
- package/dist/offline-sync-exclude-globs.js.map +1 -0
- package/dist/offline-sync-impression-drain.d.ts +78 -0
- package/dist/offline-sync-impression-drain.js +37 -0
- package/dist/offline-sync-impression-drain.js.map +1 -0
- package/dist/offline-sync.js +4 -2
- package/dist/operator-toolkit.d.ts +5 -2
- package/dist/operator-toolkit.js +69 -54
- package/dist/orchestration/compression-guideline-coordinator.d.ts +5 -2
- package/dist/orchestration/compression-guideline-coordinator.js +3 -3
- package/dist/orchestration/maintenance.d.ts +121 -2
- package/dist/orchestration/maintenance.js +44 -31
- package/dist/{orchestrator-Bp_jeBHK.d.ts → orchestrator-CQJ8U7En.d.ts} +85 -79
- package/dist/orchestrator.d.ts +13 -8
- package/dist/orchestrator.js +194 -177
- package/dist/patterns-cli.d.ts +3 -1
- package/dist/{pipeline-BAj5bx7g.d.ts → pipeline-Cn-kvYeV.d.ts} +1 -1
- package/dist/policy-runtime.d.ts +3 -1
- package/dist/prioritized-embed.d.ts +19 -0
- package/dist/prioritized-embed.js +8 -0
- package/dist/prioritized-embed.js.map +1 -0
- package/dist/proactive-contention.d.ts +28 -0
- package/dist/proactive-contention.js +8 -0
- package/dist/proactive-contention.js.map +1 -0
- package/dist/profiling.js +2 -2
- package/dist/provenance.d.ts +3 -1
- package/dist/provenance.js +3 -2
- package/dist/{purge-NDWV6WYD.js → purge-IRF47ZTX.js} +3 -3
- package/dist/{qmd-_MDSlj1m.d.ts → qmd-oh9VXz1d.d.ts} +11 -1
- package/dist/qmd-preflight.d.ts +5 -2
- package/dist/qmd-recall-cache.d.ts +3 -1
- package/dist/qmd-status.d.ts +57 -0
- package/dist/qmd-status.js +18 -0
- package/dist/qmd-status.js.map +1 -0
- package/dist/qmd.d.ts +5 -2
- package/dist/qmd.js +7 -3
- package/dist/recall-concurrency-config.d.ts +16 -0
- package/dist/recall-concurrency-config.js +10 -0
- package/dist/recall-concurrency-config.js.map +1 -0
- package/dist/recall-disclosure-escalation.d.ts +3 -1
- package/dist/recall-explain-renderer.d.ts +4 -1
- package/dist/recall-explain-renderer.js +3 -3
- package/dist/recall-memory-map.d.ts +26 -0
- package/dist/recall-memory-map.js +18 -0
- package/dist/recall-memory-map.js.map +1 -0
- package/dist/recall-planner-llm.d.ts +3 -1
- package/dist/recall-planner-llm.js +4 -4
- package/dist/recall-qos.js +2 -2
- package/dist/recall-state.d.ts +163 -2
- package/dist/recall-state.js +6 -3
- package/dist/recall-tag-filter.d.ts +3 -1
- package/dist/recall-timings.d.ts +11 -2
- package/dist/recall-timings.js +3 -1
- package/dist/recall-xray-cli.d.ts +3 -1
- package/dist/recall-xray-cli.js +4 -4
- package/dist/recall-xray-renderer.d.ts +3 -1
- package/dist/recall-xray-renderer.js +3 -3
- package/dist/recall-xray.d.ts +3 -1
- package/dist/recall-xray.js +2 -2
- package/dist/relevance.js +3 -3
- package/dist/replay/normalizers/chatgpt.d.ts +3 -1
- package/dist/replay/normalizers/claude.d.ts +3 -1
- package/dist/replay/normalizers/openclaw.d.ts +3 -1
- package/dist/replay/normalizers/shared.d.ts +3 -1
- package/dist/replay/runner.d.ts +3 -1
- package/dist/replay/types.d.ts +3 -1
- package/dist/rerank.d.ts +8 -1
- package/dist/rerank.js +3 -1
- package/dist/resolution-3K27T4VO.js +18 -0
- package/dist/resolution-3K27T4VO.js.map +1 -0
- package/dist/resolve-auth-token.d.ts +3 -1
- package/dist/resolve-provider-secret.js +2 -2
- package/dist/resume-bundles.js +19 -14
- package/dist/retrieval-agents.d.ts +19 -16
- package/dist/retrieval-agents.js +3 -3
- package/dist/retrieval-tiers.d.ts +3 -1
- package/dist/routing/engine.d.ts +3 -1
- package/dist/routing/store.d.ts +3 -1
- package/dist/routing/store.js +2 -2
- package/dist/salvage-envelope.d.ts +18 -0
- package/dist/salvage-envelope.js +15 -0
- package/dist/salvage-envelope.js.map +1 -0
- package/dist/schemas.d.ts +98 -98
- package/dist/scope-profiles-EpJG7yKP.d.ts +30 -0
- package/dist/search/document-scanner.js +3 -2
- package/dist/search/embed-helper.d.ts +3 -1
- package/dist/search/embed-helper.js +3 -3
- package/dist/search/factory.d.ts +3 -1
- package/dist/search/factory.js +18 -16
- package/dist/search/index.d.ts +3 -1
- package/dist/search/index.js +22 -20
- package/dist/search/lancedb-backend.d.ts +3 -1
- package/dist/search/lancedb-backend.js +6 -5
- package/dist/search/meilisearch-backend.d.ts +3 -1
- package/dist/search/meilisearch-backend.js +6 -5
- package/dist/search/noop-backend.d.ts +3 -1
- package/dist/search/orama-backend.d.ts +3 -1
- package/dist/search/orama-backend.js +6 -5
- package/dist/search/port.d.ts +16 -2
- package/dist/search/port.js +1 -1
- package/dist/search/remote-backend.d.ts +3 -1
- package/dist/search/remote-backend.js +3 -3
- package/dist/secure-store/index.d.ts +12 -1
- package/dist/secure-store/index.js +6 -3
- package/dist/{semantic-consolidation-C2m5eHHE.d.ts → semantic-consolidation-DILZEQq3.d.ts} +1 -1
- package/dist/semantic-consolidation.d.ts +4 -2
- package/dist/semantic-consolidation.js +38 -29
- package/dist/semantic-rule-promotion.js +34 -24
- package/dist/semantic-rule-verifier.d.ts +3 -1
- package/dist/semantic-rule-verifier.js +35 -25
- package/dist/session-observer-bands.d.ts +3 -1
- package/dist/session-observer-state.d.ts +3 -1
- package/dist/session-observer-state.js +2 -2
- package/dist/session-transcript-migration.js +2 -2
- package/dist/shared-context/manager.d.ts +11 -9
- package/dist/shared-context/manager.js +3 -3
- package/dist/signal.d.ts +3 -1
- package/dist/{storage-hr_Y2z5R.d.ts → storage-NIyKYD8g.d.ts} +489 -110
- package/dist/storage-guards.js +2 -2
- package/dist/storage.d.ts +6 -3
- package/dist/storage.js +38 -26
- package/dist/structured-attributes.d.ts +23 -1
- package/dist/structured-attributes.js +7 -1
- package/dist/summarizer.d.ts +3 -1
- package/dist/summarizer.js +11 -11
- package/dist/summary-snapshot.d.ts +3 -1
- package/dist/summary-snapshot.js +2 -2
- package/dist/temporal-index-batch.d.ts +1 -0
- package/dist/temporal-index-batch.js +10 -0
- package/dist/temporal-index-batch.js.map +1 -0
- package/dist/temporal-index-compat.d.ts +279 -1
- package/dist/temporal-index-compat.js +1 -1
- package/dist/temporal-index.d.ts +1 -225
- package/dist/temporal-index.js +35 -3
- package/dist/temporal-supersession.d.ts +5 -2
- package/dist/temporal-supersession.js +2 -2
- package/dist/temporal-timeline-recall.d.ts +3 -1
- package/dist/temporal-validity.d.ts +3 -1
- package/dist/threading.d.ts +3 -1
- package/dist/threading.js +2 -2
- package/dist/tier-migration.d.ts +5 -2
- package/dist/tier-routing.d.ts +3 -1
- package/dist/{tier-stats-KV7IWCJR.js → tier-stats-QEJSPFV2.js} +2 -2
- package/dist/tokens.js +3 -3
- package/dist/topics.d.ts +3 -1
- package/dist/transcript.d.ts +3 -1
- package/dist/transcript.js +2 -2
- package/dist/transfer/backup.js +3 -3
- package/dist/transfer/capsule-export.js +3 -3
- package/dist/transfer/capsule-import.js +3 -3
- package/dist/transfer/types.d.ts +90 -90
- package/dist/trust-score-stage.d.ts +75 -38
- package/dist/trust-score-stage.js +11 -3
- package/dist/trust-score.d.ts +3 -1
- package/dist/{types-Cg8q7W7_.d.ts → types-D_fG2ix8.d.ts} +39 -63
- package/dist/types-continuity.d.ts +52 -0
- package/dist/types-continuity.js +1 -0
- package/dist/types-continuity.js.map +1 -0
- package/dist/types.d.ts +3 -1
- package/dist/types.js +1 -1
- package/dist/utility-runtime.d.ts +3 -1
- package/dist/utils/serialize-mutations.d.ts +31 -2
- package/dist/utils/serialize-mutations.js +1 -1
- package/dist/verified-recall.js +36 -26
- package/dist/write-envelope.d.ts +282 -0
- package/dist/write-envelope.js +35 -0
- package/dist/write-envelope.js.map +1 -0
- package/dist/write-quarantine.d.ts +79 -0
- package/dist/write-quarantine.js +11 -0
- package/dist/write-quarantine.js.map +1 -0
- package/dist/write-rate-limiter.d.ts +45 -0
- package/dist/write-rate-limiter.js +9 -0
- package/dist/write-rate-limiter.js.map +1 -0
- package/package.json +37 -2
- package/src/access-admin-ops-surface.ts +17 -7
- package/src/access-boundary.test.ts +28 -0
- package/src/access-boundary.ts +31 -2
- package/src/access-citation.test.ts +72 -0
- package/src/access-citation.ts +139 -0
- package/src/access-errors.ts +21 -0
- package/src/access-fingerprint-parity.test.ts +211 -0
- package/src/access-http-cancellation.test.ts +59 -21
- package/src/access-http.test.ts +90 -0
- package/src/access-http.ts +79 -79
- package/src/access-mcp-cancellation.test.ts +16 -7
- package/src/access-memory-search-fanout.test.ts +220 -0
- package/src/access-memory-search-fanout.ts +168 -0
- package/src/access-observe-quarantine.test.ts +120 -0
- package/src/access-observe-write-surface.ts +130 -11
- package/src/access-operations-batch.ts +2 -0
- package/src/access-recall-concurrency.ts +773 -0
- package/src/access-recall-response.ts +307 -0
- package/src/access-recall-surface.ts +100 -252
- package/src/access-service-coding-write.test.ts +22 -4
- package/src/access-service-health.test.ts +49 -0
- package/src/access-service-namespace.test.ts +486 -8
- package/src/access-service-observe-scope.test.ts +4 -0
- package/src/access-service-recall-concurrency.test.ts +1990 -0
- package/src/access-service.ts +182 -175
- package/src/access-surface-catalog.ts +1 -0
- package/src/access-token-capabilities.ts +16 -9
- package/src/active-recall.test.ts +35 -0
- package/src/active-recall.ts +7 -0
- package/src/bounded-jsonl-state.ts +93 -0
- package/src/buffer-session.test.ts +422 -5
- package/src/buffer-surprise-trigger.test.ts +41 -0
- package/src/buffer.ts +218 -3
- package/src/capabilities-cluster-b.test.ts +1 -0
- package/src/capabilities.ts +5 -6
- package/src/cli.ts +21 -22
- package/src/coding/architecture-surfaces.test.ts +6 -3
- package/src/coding/architecture-surfaces.ts +36 -25
- package/src/coding/decision-surfaces.ts +42 -34
- package/src/compounding/engine.test.ts +5 -1
- package/src/compounding/engine.ts +7 -7
- package/src/config-qmd.ts +89 -0
- package/src/config.test.ts +136 -0
- package/src/config.ts +113 -118
- package/src/connector-provenance-lifecycle.test.ts +4 -4
- package/src/connectors/coerce.test.ts +131 -0
- package/src/connectors/coerce.ts +80 -7
- package/src/contradiction/contradiction.test.ts +10 -6
- package/src/contradiction/resolution.ts +17 -4
- package/src/correction/correction-access-wiring.ts +62 -18
- package/src/cross-namespace-budget.test.ts +35 -0
- package/src/cross-namespace-budget.ts +86 -19
- package/src/explicit-capture.ts +54 -17
- package/src/extraction.ts +2 -3
- package/src/faithfulness-graph-guard.test.ts +16 -69
- package/src/graph-recall.test.ts +31 -0
- package/src/index.ts +22 -2
- package/src/local-llm.ts +4 -6
- package/src/logger.ts +16 -0
- package/src/maintenance/dreams-ledger.ts +100 -49
- package/src/maintenance/rebuild-memory-lifecycle-ledger-cli.ts +77 -0
- package/src/maintenance/rebuild-memory-lifecycle-ledger.ts +373 -9
- package/src/memory-lifecycle-ledger-utils.ts +51 -5
- package/src/namespaces/principal.test.ts +37 -1
- package/src/namespaces/principal.ts +48 -0
- package/src/namespaces/search.test.ts +79 -5
- package/src/namespaces/search.ts +59 -6
- package/src/namespaces/storage.ts +43 -1
- package/src/offline-sync-exclude-globs.ts +58 -0
- package/src/offline-sync-impression-drain.test.ts +316 -0
- package/src/offline-sync-impression-drain.ts +260 -0
- package/src/offline-sync.test.ts +32 -5
- package/src/offline-sync.ts +1 -14
- package/src/operator-toolkit.ts +1 -1
- package/src/orchestration/batch-deindex-flush.test.ts +146 -0
- package/src/orchestration/consolidation-run.ts +96 -85
- package/src/orchestration/extraction-envelope.ts +94 -0
- package/src/orchestration/extraction-persist-promotion.ts +134 -0
- package/src/orchestration/extraction-persist.ts +325 -260
- package/src/orchestration/extraction-run.test.ts +1 -1
- package/src/orchestration/extraction-run.ts +2 -2
- package/src/orchestration/graph-recall-coordinator.ts +18 -7
- package/src/orchestration/lifecycle-policy-coordinator.ts +63 -48
- package/src/orchestration/maintenance.test.ts +2103 -3
- package/src/orchestration/maintenance.ts +574 -2
- package/src/orchestration/namespace-read-fanout.ts +14 -8
- package/src/orchestration/orchestrator-helpers.ts +8 -1
- package/src/orchestration/orchestrator-init.ts +22 -6
- package/src/orchestration/orchestrator-namespace-scope.ts +71 -0
- package/src/orchestration/persistence-index.ts +64 -9
- package/src/orchestration/qmd-result-resolver.ts +27 -0
- package/src/orchestration/recall-entry.ts +34 -19
- package/src/orchestration/recall-internal.ts +207 -79
- package/src/orchestration/recall-rerank-coordinator.ts +229 -82
- package/src/orchestration/recall-result-formatter.ts +76 -20
- package/src/orchestration/recall-search-pipeline.test.ts +70 -0
- package/src/orchestration/recall-search-pipeline.ts +102 -129
- package/src/orchestration/recall-section-coordinator.ts +315 -85
- package/src/orchestration/semantic-consolidation-coordinator.ts +69 -37
- package/src/orchestration/session-context.ts +21 -0
- package/src/orchestration/workspace-ops.test.ts +348 -0
- package/src/orchestration/workspace-ops.ts +168 -34
- package/src/orchestrator-chunked-hash-durability.test.ts +126 -0
- package/src/orchestrator-connector-dedup.test.ts +1061 -40
- package/src/orchestrator-flush.test.ts +155 -39
- package/src/orchestrator-nonchunked-hash-durability.test.ts +121 -0
- package/src/orchestrator-source-attribution.test.ts +9 -9
- package/src/orchestrator.ts +160 -149
- package/src/prioritized-embed.ts +123 -0
- package/src/proactive-contention.test.ts +80 -0
- package/src/proactive-contention.ts +39 -0
- package/src/procedural/procedure-miner.test.ts +19 -14
- package/src/procedural/procedure-miner.ts +44 -13
- package/src/qmd-client.test.ts +111 -1
- package/src/qmd-status.ts +186 -0
- package/src/qmd.ts +47 -47
- package/src/recall-budget-config.test.ts +30 -0
- package/src/recall-cold-deadline.test.ts +149 -0
- package/src/recall-concurrency-config.ts +28 -0
- package/src/recall-memory-map.ts +74 -0
- package/src/recall-rerank-coordinator.test.ts +508 -0
- package/src/recall-state.test.ts +825 -1
- package/src/recall-state.ts +406 -7
- package/src/recall-timings-access.test.ts +41 -0
- package/src/recall-timings.ts +16 -0
- package/src/relay/mission-access.test.ts +5 -5
- package/src/rerank.ts +22 -0
- package/src/retrieval-agents.test.ts +79 -0
- package/src/retrieval-agents.ts +71 -19
- package/src/salvage-envelope.ts +28 -0
- package/src/scopes/scope-plan.test.ts +42 -0
- package/src/scopes/scope-plan.ts +45 -0
- package/src/search/port.ts +13 -0
- package/src/secure-store/index.ts +1 -0
- package/src/secure-store/secure-fs.ts +48 -0
- package/src/secure-store/secure-store.test.ts +40 -0
- package/src/semantic-rule-promotion.ts +13 -4
- package/src/storage/content-hash-index.ts +655 -0
- package/src/storage/entity-timeline.ts +473 -0
- package/src/storage/memory-lifecycle-ledger-access.ts +603 -0
- package/src/storage/secure-line-reader.test.ts +197 -0
- package/src/storage/secure-line-reader.ts +181 -3
- package/src/storage-append-header-sniff.test.ts +195 -0
- package/src/storage-behavior-signals-cache.test.ts +237 -0
- package/src/storage-contract/cache-coherence.test.ts +96 -1
- package/src/storage-contract/content-hash.test.ts +47 -2
- package/src/storage-fact-hash-index-defer.test.ts +1200 -0
- package/src/storage-memory-lifecycle-ledger.test.ts +1146 -3
- package/src/storage.ts +882 -890
- package/src/structured-attributes.ts +52 -0
- package/src/temporal-index-batch.ts +112 -0
- package/src/temporal-index.test.ts +193 -0
- package/src/temporal-index.ts +49 -53
- package/src/testing/lifecycle-matrix.test.ts +212 -0
- package/src/testing/lifecycle-matrix.ts +271 -0
- package/src/testing/orchestrator-lite.ts +236 -0
- package/src/testing/subjects/dreams-ledger.test.ts +67 -0
- package/src/testing/subjects/extraction-lifecycle.test.ts +442 -0
- package/src/testing/subjects/lifecycle-ledger.test.ts +93 -0
- package/src/testing/subjects/recall-budget.test.ts +461 -0
- package/src/testing/subjects/serialized-write-chain.test.ts +224 -0
- package/src/trust-score-stage.test.ts +154 -0
- package/src/trust-score-stage.ts +216 -53
- package/src/types-continuity.ts +57 -0
- package/src/types.ts +51 -77
- package/src/utils/path-containment.test.ts +75 -0
- package/src/utils/path-containment.ts +76 -0
- package/src/utils/serialize-mutations.test.ts +122 -0
- package/src/utils/serialize-mutations.ts +68 -3
- package/src/wearables/memory-gen.test.ts +31 -9
- package/src/wearables/memory-gen.ts +95 -55
- package/src/wearables/pipeline.test.ts +10 -7
- package/src/wearables/service.test.ts +6 -8
- package/src/wearables/service.ts +2 -2
- package/src/write-envelope.extension.test.ts +111 -0
- package/src/write-envelope.test.ts +683 -0
- package/src/write-envelope.ts +902 -0
- package/src/write-quarantine.test.ts +193 -0
- package/src/write-quarantine.ts +241 -0
- package/src/write-rate-limiter.test.ts +105 -0
- package/src/write-rate-limiter.ts +135 -0
- package/src/write-sealed-parity.test.ts +204 -0
- package/dist/chunk-2VN4JAX7.js.map +0 -1
- package/dist/chunk-37CTGERM.js.map +0 -1
- package/dist/chunk-3IY64MV6.js.map +0 -1
- package/dist/chunk-BCDKBDSY.js.map +0 -1
- package/dist/chunk-BFPETA3S.js.map +0 -1
- package/dist/chunk-BFVEEB7W.js.map +0 -1
- package/dist/chunk-BS4SGLWV.js.map +0 -1
- package/dist/chunk-DCFV7A7H.js.map +0 -1
- package/dist/chunk-DYP3YYJJ.js.map +0 -1
- package/dist/chunk-E5BK6CGV.js +0 -335
- package/dist/chunk-E5BK6CGV.js.map +0 -1
- package/dist/chunk-FTYDWKO6.js.map +0 -1
- package/dist/chunk-GWTFLF2P.js.map +0 -1
- package/dist/chunk-IKQTFQMI.js.map +0 -1
- package/dist/chunk-INELTPRG.js.map +0 -1
- package/dist/chunk-J2HSAU72.js.map +0 -1
- package/dist/chunk-JREB6UFH.js.map +0 -1
- package/dist/chunk-K7LOWSLU.js.map +0 -1
- package/dist/chunk-KQDNAXOM.js.map +0 -1
- package/dist/chunk-KSBEEMQF.js.map +0 -1
- package/dist/chunk-KSNVDPDF.js.map +0 -1
- package/dist/chunk-LC7OODWZ.js.map +0 -1
- package/dist/chunk-LTJAMRGI.js.map +0 -1
- package/dist/chunk-LZOV7MX7.js.map +0 -1
- package/dist/chunk-NWTGD4AN.js.map +0 -1
- package/dist/chunk-ODPLEWB6.js.map +0 -1
- package/dist/chunk-OVJ2HIPE.js.map +0 -1
- package/dist/chunk-PBSAPHHA.js.map +0 -1
- package/dist/chunk-PHK3HARR.js +0 -32
- package/dist/chunk-PHK3HARR.js.map +0 -1
- package/dist/chunk-PLQE6MJA.js.map +0 -1
- package/dist/chunk-QKQYXZVS.js.map +0 -1
- package/dist/chunk-QQGPUKMT.js.map +0 -1
- package/dist/chunk-RANHFI3P.js +0 -8
- package/dist/chunk-RANHFI3P.js.map +0 -1
- package/dist/chunk-RAR2BSEM.js.map +0 -1
- package/dist/chunk-RULE4VG5.js.map +0 -1
- package/dist/chunk-SVOZFLIQ.js +0 -16
- package/dist/chunk-SVOZFLIQ.js.map +0 -1
- package/dist/chunk-T7Y5KPLN.js.map +0 -1
- package/dist/chunk-TXLGHDHS.js.map +0 -1
- package/dist/chunk-VDW4VU7B.js.map +0 -1
- package/dist/chunk-VSMRDBFT.js +0 -17
- package/dist/chunk-VSMRDBFT.js.map +0 -1
- package/dist/chunk-X64YLC24.js.map +0 -1
- package/dist/chunk-XDSXEKYK.js +0 -118
- package/dist/chunk-XDSXEKYK.js.map +0 -1
- package/dist/chunk-YF6FMLBT.js.map +0 -1
- package/dist/resolution-VH4NGENZ.js +0 -12
- /package/dist/{capsule-crypto-CZJSLEFG.js.map → access-citation.js.map} +0 -0
- /package/dist/{contradiction-scan-J57T5QV7.js.map → access-memory-search-fanout.js.map} +0 -0
- /package/dist/{dreams-ledger-6UODQOX6.js.map → access-recall-concurrency.js.map} +0 -0
- /package/dist/{graph-edge-decay-IOGBBAGC.js.map → access-recall-response.js.map} +0 -0
- /package/dist/{resolution-VH4NGENZ.js.map → bounded-jsonl-state.js.map} +0 -0
- /package/dist/{chunk-A3GOMRJG.js.map → chunk-23L3IXFD.js.map} +0 -0
- /package/dist/{chunk-MRHFJV44.js.map → chunk-2DQ2XREU.js.map} +0 -0
- /package/dist/{chunk-L2B3EE7B.js.map → chunk-2QMKMVHH.js.map} +0 -0
- /package/dist/{chunk-VJVNLNDQ.js.map → chunk-2UFO7X4Q.js.map} +0 -0
- /package/dist/{chunk-2MR3MFQB.js.map → chunk-2VEOKUPP.js.map} +0 -0
- /package/dist/{chunk-RHOZ5LLO.js.map → chunk-347WD2LE.js.map} +0 -0
- /package/dist/{chunk-HDTQVNWD.js.map → chunk-3QAH2S33.js.map} +0 -0
- /package/dist/{chunk-LRNMRXJO.js.map → chunk-3Y224NYC.js.map} +0 -0
- /package/dist/{chunk-VFSP73LP.js.map → chunk-45GF7Y3V.js.map} +0 -0
- /package/dist/{chunk-OEHCMALU.js.map → chunk-5I4M26Q7.js.map} +0 -0
- /package/dist/{chunk-2AZXIZEN.js.map → chunk-5Q7EVV4X.js.map} +0 -0
- /package/dist/{chunk-PUBTHW5O.js.map → chunk-6DRPPQEJ.js.map} +0 -0
- /package/dist/{chunk-FDCTQWLW.js.map → chunk-6O22TZSB.js.map} +0 -0
- /package/dist/{chunk-ZQ7UBUZ6.js.map → chunk-6QYST5XN.js.map} +0 -0
- /package/dist/{chunk-W4PGTAS2.js.map → chunk-6WAFYNVT.js.map} +0 -0
- /package/dist/{chunk-R5VAVVHP.js.map → chunk-72A4L5JJ.js.map} +0 -0
- /package/dist/{chunk-3FAMU5TX.js.map → chunk-A6USBZTO.js.map} +0 -0
- /package/dist/{chunk-ZHIVCODT.js.map → chunk-A72SWTWW.js.map} +0 -0
- /package/dist/{chunk-FC77VUX5.js.map → chunk-A7Q43EUZ.js.map} +0 -0
- /package/dist/{chunk-UJBESW6X.js.map → chunk-AE7WMV4V.js.map} +0 -0
- /package/dist/{chunk-A5UXZRRM.js.map → chunk-AJ7QCSIS.js.map} +0 -0
- /package/dist/{chunk-FNMFJWBY.js.map → chunk-ANEUPF6O.js.map} +0 -0
- /package/dist/{chunk-2A3OXAP2.js.map → chunk-B7RWBZXW.js.map} +0 -0
- /package/dist/{chunk-74F5STS4.js.map → chunk-BLAKS3CP.js.map} +0 -0
- /package/dist/{chunk-M7VGG3ID.js.map → chunk-BV5WMSIJ.js.map} +0 -0
- /package/dist/{chunk-2BFLDH5F.js.map → chunk-BXLOS5AJ.js.map} +0 -0
- /package/dist/{chunk-UXKMMNJD.js.map → chunk-C23BYTJV.js.map} +0 -0
- /package/dist/{chunk-JZHHREBL.js.map → chunk-C3AKM55W.js.map} +0 -0
- /package/dist/{chunk-RP7YXA67.js.map → chunk-C5JF6EZ7.js.map} +0 -0
- /package/dist/{chunk-EHCONI6E.js.map → chunk-CQMEM5CC.js.map} +0 -0
- /package/dist/{chunk-PBNZVPFS.js.map → chunk-CXZQOVIE.js.map} +0 -0
- /package/dist/{chunk-7N2RUBAP.js.map → chunk-DDA47TES.js.map} +0 -0
- /package/dist/{chunk-64SE4X5G.js.map → chunk-DEBHXHJR.js.map} +0 -0
- /package/dist/{chunk-EHK6KZHK.js.map → chunk-DNHVPZOD.js.map} +0 -0
- /package/dist/{chunk-437S3G37.js.map → chunk-DQEMWVMT.js.map} +0 -0
- /package/dist/{chunk-LZZNTPLR.js.map → chunk-DX3XRGLH.js.map} +0 -0
- /package/dist/{chunk-DZKHSDOM.js.map → chunk-ES36HTSC.js.map} +0 -0
- /package/dist/{chunk-TN7THW5U.js.map → chunk-ET356KSB.js.map} +0 -0
- /package/dist/{chunk-YNDLCWXS.js.map → chunk-EZ25VE3G.js.map} +0 -0
- /package/dist/{chunk-DMSDUGDM.js.map → chunk-F7GAIWFA.js.map} +0 -0
- /package/dist/{chunk-G6XXLKC5.js.map → chunk-FFPJINJE.js.map} +0 -0
- /package/dist/{chunk-G4CN3ZXL.js.map → chunk-FUTF72MJ.js.map} +0 -0
- /package/dist/{chunk-HF77M34J.js.map → chunk-GFPZS3F5.js.map} +0 -0
- /package/dist/{chunk-G6NKALRN.js.map → chunk-H4WG3UQZ.js.map} +0 -0
- /package/dist/{chunk-UDQUSU27.js.map → chunk-HNZKVLAN.js.map} +0 -0
- /package/dist/{chunk-5EZXHU36.js.map → chunk-HYQJQGD3.js.map} +0 -0
- /package/dist/{chunk-TRWPCR6P.js.map → chunk-JECBQBEX.js.map} +0 -0
- /package/dist/{chunk-E6CBU7XP.js.map → chunk-JYIFGGEM.js.map} +0 -0
- /package/dist/{chunk-VA4WSW54.js.map → chunk-JYIJNNN3.js.map} +0 -0
- /package/dist/{chunk-L5HJQC36.js.map → chunk-K2TDK7GG.js.map} +0 -0
- /package/dist/{chunk-KDXVOFYY.js.map → chunk-KAVBJSBQ.js.map} +0 -0
- /package/dist/{chunk-4OKLXWDZ.js.map → chunk-KNB7IBBU.js.map} +0 -0
- /package/dist/{chunk-QLULYKWZ.js.map → chunk-KSXJOC4F.js.map} +0 -0
- /package/dist/{chunk-2S6QHZGJ.js.map → chunk-L2DPF4DF.js.map} +0 -0
- /package/dist/{chunk-JI5AVXYO.js.map → chunk-LEPOP46W.js.map} +0 -0
- /package/dist/{chunk-O7BBFJLP.js.map → chunk-LTAVSOZW.js.map} +0 -0
- /package/dist/{chunk-2IZAGAJR.js.map → chunk-LULR4IUW.js.map} +0 -0
- /package/dist/{chunk-WMWVO45V.js.map → chunk-MC6XO7T4.js.map} +0 -0
- /package/dist/{chunk-D6RIFG4O.js.map → chunk-MNB5RMAY.js.map} +0 -0
- /package/dist/{chunk-Q5WG43OP.js.map → chunk-MYFQ3ED4.js.map} +0 -0
- /package/dist/{chunk-V3UE5I5F.js.map → chunk-MYVPLJOO.js.map} +0 -0
- /package/dist/{chunk-MTWKDOVO.js.map → chunk-NLE6TSLG.js.map} +0 -0
- /package/dist/{chunk-DBRYEIGY.js.map → chunk-OJP3N7UK.js.map} +0 -0
- /package/dist/{chunk-SMX5267P.js.map → chunk-OZKOPQCM.js.map} +0 -0
- /package/dist/{chunk-JCK7V6SN.js.map → chunk-P65YJEEL.js.map} +0 -0
- /package/dist/{chunk-MTR7BZQN.js.map → chunk-PBGWYM3H.js.map} +0 -0
- /package/dist/{chunk-AXTXN4T6.js.map → chunk-PWFCHOB3.js.map} +0 -0
- /package/dist/{chunk-FDPZVY6D.js.map → chunk-QFN4U7AG.js.map} +0 -0
- /package/dist/{chunk-K2ZLHESQ.js.map → chunk-QIEUU4UK.js.map} +0 -0
- /package/dist/{chunk-3D6AFGQR.js.map → chunk-QVT3E4CE.js.map} +0 -0
- /package/dist/{chunk-SFQ6QNL7.js.map → chunk-R6YJI3HE.js.map} +0 -0
- /package/dist/{chunk-5CJVLG3H.js.map → chunk-RFDD3B7W.js.map} +0 -0
- /package/dist/{chunk-JBPKEARU.js.map → chunk-RKNJBZ55.js.map} +0 -0
- /package/dist/{chunk-FNZQGN76.js.map → chunk-S2BBISHU.js.map} +0 -0
- /package/dist/{chunk-IALLX7EE.js.map → chunk-S2FSBU4H.js.map} +0 -0
- /package/dist/{chunk-7K5Q6COX.js.map → chunk-TVVEYCNW.js.map} +0 -0
- /package/dist/{chunk-DPAENM7O.js.map → chunk-U3ZAVRP3.js.map} +0 -0
- /package/dist/{chunk-4KJLYTLS.js.map → chunk-V57NLO5P.js.map} +0 -0
- /package/dist/{chunk-FLVL4L3L.js.map → chunk-VDX2J7OX.js.map} +0 -0
- /package/dist/{chunk-AJLLIIML.js.map → chunk-W4FBGYGK.js.map} +0 -0
- /package/dist/{chunk-6SXVCD7W.js.map → chunk-W7IGLX4G.js.map} +0 -0
- /package/dist/{chunk-KAWTOYAW.js.map → chunk-WLZYGLJ4.js.map} +0 -0
- /package/dist/{chunk-RO4LQGT3.js.map → chunk-WON4APQY.js.map} +0 -0
- /package/dist/{chunk-PQVK2S3J.js.map → chunk-WZFAPHIK.js.map} +0 -0
- /package/dist/{chunk-KQPJLK3X.js.map → chunk-XL24QQ63.js.map} +0 -0
- /package/dist/{chunk-RVZRYUQH.js.map → chunk-YROFNEG5.js.map} +0 -0
- /package/dist/{chunk-VRAD2FGO.js.map → chunk-Z54EM5YZ.js.map} +0 -0
- /package/dist/{first-start-migration-TI67YMLG.js.map → first-start-migration-CSRHCJIQ.js.map} +0 -0
- /package/dist/{forget-CQCLB5LB.js.map → forget-HRPLVB3W.js.map} +0 -0
- /package/dist/{purge-NDWV6WYD.js.map → purge-IRF47ZTX.js.map} +0 -0
- /package/dist/{tier-stats-KV7IWCJR.js.map → tier-stats-QEJSPFV2.js.map} +0 -0
|
@@ -17,37 +17,37 @@ import test from "node:test";
|
|
|
17
17
|
import assert from "node:assert/strict";
|
|
18
18
|
import os from "node:os";
|
|
19
19
|
import path from "node:path";
|
|
20
|
-
import { mkdtemp, mkdir } from "node:fs/promises";
|
|
20
|
+
import { mkdtemp, mkdir, rm } from "node:fs/promises";
|
|
21
|
+
import { existsSync } from "node:fs";
|
|
21
22
|
|
|
22
23
|
import { parseConfig } from "./config.js";
|
|
23
24
|
import { Orchestrator } from "./orchestrator.js";
|
|
24
|
-
import {
|
|
25
|
-
import
|
|
25
|
+
import { clearMemoryCache } from "./memory-cache.js";
|
|
26
|
+
import { ContentHashIndex, StorageManager } from "./storage.js";
|
|
27
|
+
import type { ExtractionResult, ExtractedFact, MemoryFile, MemoryCategory } from "./types.js";
|
|
26
28
|
import type { ResolvedScopeProfilePlan } from "./namespaces/scope-profiles.js";
|
|
29
|
+
import { buildProcedurePersistBody } from "./procedural/procedure-types.js";
|
|
30
|
+
import type { PersistExtractionFn } from "./testing/orchestrator-lite.js";
|
|
27
31
|
|
|
28
32
|
// ---------------------------------------------------------------------------
|
|
29
33
|
// Types — minimal surface of Orchestrator needed by these tests.
|
|
30
34
|
// persistExtraction is private; cast through unknown to reach it without `any`.
|
|
31
35
|
// ---------------------------------------------------------------------------
|
|
32
36
|
|
|
33
|
-
/** Source-context shape accepted by persistExtraction (connector subset). */
|
|
34
|
-
interface TestSourceContext {
|
|
35
|
-
sourceConnector?: string;
|
|
36
|
-
validAt?: string;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
37
|
/** Orchestrator fields the tests touch (all exist on the real instance). */
|
|
40
38
|
interface OrchestratorTestSurface {
|
|
41
|
-
persistExtraction:
|
|
42
|
-
result: ExtractionResult,
|
|
43
|
-
storage: StorageManager,
|
|
44
|
-
threadId: string | null,
|
|
45
|
-
sourceContext?: TestSourceContext,
|
|
46
|
-
baseNamespace?: string,
|
|
47
|
-
scopeProfileWritePlan?: ResolvedScopeProfilePlan | null,
|
|
48
|
-
) => Promise<string[]>;
|
|
39
|
+
persistExtraction: PersistExtractionFn;
|
|
49
40
|
getStorage: (namespace: string) => Promise<StorageManager>;
|
|
50
41
|
contentHashIndex: ContentHashIndex | null;
|
|
42
|
+
// Private on the real instance; reached through the unknown-cast surface for
|
|
43
|
+
// the #1909 defer-durability tests (registration failure + concurrent runs).
|
|
44
|
+
addContentHashDedup: (targetStorage: StorageManager, content: string) => Promise<void>;
|
|
45
|
+
hasContentHashDedup: (targetStorage: StorageManager, content: string) => Promise<boolean>;
|
|
46
|
+
removeContentHashForMemory: (
|
|
47
|
+
targetStorage: StorageManager,
|
|
48
|
+
memory: MemoryFile,
|
|
49
|
+
context: string,
|
|
50
|
+
) => Promise<void>;
|
|
51
51
|
}
|
|
52
52
|
|
|
53
53
|
// ---------------------------------------------------------------------------
|
|
@@ -121,7 +121,7 @@ test("connector dedup (QOjlB): same content + same connector dedupes", async ()
|
|
|
121
121
|
"The deployment pipeline runs canary checks every fifteen minutes before promoting to production.";
|
|
122
122
|
|
|
123
123
|
// First write with connector "chatgpt" — must succeed.
|
|
124
|
-
const ids1 = await orchestrator.persistExtraction(
|
|
124
|
+
const { persistedIds: ids1 } = await orchestrator.persistExtraction(
|
|
125
125
|
factResult(body),
|
|
126
126
|
storage,
|
|
127
127
|
null,
|
|
@@ -130,7 +130,7 @@ test("connector dedup (QOjlB): same content + same connector dedupes", async ()
|
|
|
130
130
|
assert.equal(ids1.length, 1, "first write must succeed");
|
|
131
131
|
|
|
132
132
|
// Second write: same content, same connector → must dedupe.
|
|
133
|
-
const ids2 = await orchestrator.persistExtraction(
|
|
133
|
+
const { persistedIds: ids2 } = await orchestrator.persistExtraction(
|
|
134
134
|
factResult(body),
|
|
135
135
|
storage,
|
|
136
136
|
null,
|
|
@@ -150,7 +150,7 @@ test("connector dedup (QOjlB): same content + different connector does NOT dedup
|
|
|
150
150
|
"The staging environment uses isolated feature flags per tenant for safer rollouts.";
|
|
151
151
|
|
|
152
152
|
// First write with connector "chatgpt".
|
|
153
|
-
const ids1 = await orchestrator.persistExtraction(
|
|
153
|
+
const { persistedIds: ids1 } = await orchestrator.persistExtraction(
|
|
154
154
|
factResult(body),
|
|
155
155
|
storage,
|
|
156
156
|
null,
|
|
@@ -159,7 +159,7 @@ test("connector dedup (QOjlB): same content + different connector does NOT dedup
|
|
|
159
159
|
assert.equal(ids1.length, 1, "first write must succeed");
|
|
160
160
|
|
|
161
161
|
// Second write: same content, DIFFERENT connector → must NOT dedupe.
|
|
162
|
-
const ids2 = await orchestrator.persistExtraction(
|
|
162
|
+
const { persistedIds: ids2 } = await orchestrator.persistExtraction(
|
|
163
163
|
factResult(body),
|
|
164
164
|
storage,
|
|
165
165
|
null,
|
|
@@ -190,7 +190,7 @@ test("connector dedup (QOjlB): stale hash with no matching memory writes through
|
|
|
190
190
|
|
|
191
191
|
// persistExtraction must write through — the connector-aware scan finds
|
|
192
192
|
// no matching memory, so exactDuplicate falls to false.
|
|
193
|
-
const ids = await orchestrator.persistExtraction(
|
|
193
|
+
const { persistedIds: ids } = await orchestrator.persistExtraction(
|
|
194
194
|
factResult(body),
|
|
195
195
|
storage,
|
|
196
196
|
null,
|
|
@@ -224,7 +224,7 @@ test("connector dedup (QOjlB): readAllMemories throws → writes through (fail o
|
|
|
224
224
|
|
|
225
225
|
// persistExtraction must write through — the scan failure is caught and
|
|
226
226
|
// exactDuplicate falls to false (fail open).
|
|
227
|
-
const ids = await orchestrator.persistExtraction(
|
|
227
|
+
const { persistedIds: ids } = await orchestrator.persistExtraction(
|
|
228
228
|
factResult(body),
|
|
229
229
|
storage,
|
|
230
230
|
null,
|
|
@@ -339,7 +339,7 @@ test("promotion dedup (QOjlD): same content + same connector skips promotion", a
|
|
|
339
339
|
});
|
|
340
340
|
|
|
341
341
|
// persistExtraction writes to source (default) and promotes to target.
|
|
342
|
-
const ids = await orchestrator.persistExtraction(
|
|
342
|
+
const { persistedIds: ids } = await orchestrator.persistExtraction(
|
|
343
343
|
factResult(body),
|
|
344
344
|
storage,
|
|
345
345
|
null,
|
|
@@ -370,7 +370,7 @@ test("promotion dedup (QOjlD): same content + different connector promotes", asy
|
|
|
370
370
|
});
|
|
371
371
|
|
|
372
372
|
// Extract with a DIFFERENT connector — promotion must proceed.
|
|
373
|
-
const ids = await orchestrator.persistExtraction(
|
|
373
|
+
const { persistedIds: ids } = await orchestrator.persistExtraction(
|
|
374
374
|
factResult(body),
|
|
375
375
|
storage,
|
|
376
376
|
null,
|
|
@@ -412,7 +412,7 @@ test("promotion dedup (QOjlD): connector vs operator promotes", async () => {
|
|
|
412
412
|
});
|
|
413
413
|
|
|
414
414
|
// Extract WITHOUT a sourceConnector (operator write).
|
|
415
|
-
const ids = await orchestrator.persistExtraction(
|
|
415
|
+
const { persistedIds: ids } = await orchestrator.persistExtraction(
|
|
416
416
|
factResult(body),
|
|
417
417
|
storage,
|
|
418
418
|
null,
|
|
@@ -442,7 +442,7 @@ test("promotion dedup (QOjlD): operator vs operator skips promotion", async () =
|
|
|
442
442
|
});
|
|
443
443
|
|
|
444
444
|
// Extract WITHOUT a sourceConnector (operator write).
|
|
445
|
-
const ids = await orchestrator.persistExtraction(
|
|
445
|
+
const { persistedIds: ids } = await orchestrator.persistExtraction(
|
|
446
446
|
factResult(body),
|
|
447
447
|
storage,
|
|
448
448
|
null,
|
|
@@ -491,7 +491,7 @@ test("promotion dedup (QOjlD): readAllMemories throws on target → promotion pr
|
|
|
491
491
|
throw new Error("simulated target readAllMemories failure");
|
|
492
492
|
};
|
|
493
493
|
|
|
494
|
-
const ids = await orchestrator.persistExtraction(
|
|
494
|
+
const { persistedIds: ids } = await orchestrator.persistExtraction(
|
|
495
495
|
factResult(body),
|
|
496
496
|
storage,
|
|
497
497
|
null,
|
|
@@ -526,7 +526,7 @@ test("promotion dedup (QOjlD): cited fact canonicalized before connector-aware d
|
|
|
526
526
|
"The deployment manifest pins container digests for reproducible builds.";
|
|
527
527
|
|
|
528
528
|
// First persist: orchestrator appends an inline citation and writes the fact.
|
|
529
|
-
const firstIds = await orchestrator.persistExtraction(
|
|
529
|
+
const { persistedIds: firstIds } = await orchestrator.persistExtraction(
|
|
530
530
|
factResult(rawBody),
|
|
531
531
|
storage,
|
|
532
532
|
null,
|
|
@@ -550,7 +550,7 @@ test("promotion dedup (QOjlD): cited fact canonicalized before connector-aware d
|
|
|
550
550
|
// normalizeStoredHashSource strips the citation before comparing, so the
|
|
551
551
|
// connector-aware scan finds the matching fact and dedupes at the source
|
|
552
552
|
// gate — promotion never fires.
|
|
553
|
-
const secondIds = await orchestrator.persistExtraction(
|
|
553
|
+
const { persistedIds: secondIds } = await orchestrator.persistExtraction(
|
|
554
554
|
factResult(firstMemory.content),
|
|
555
555
|
storage,
|
|
556
556
|
null,
|
|
@@ -614,7 +614,7 @@ test("connector dedup (QPDE5): same-connector procedure with steps dedupes", asy
|
|
|
614
614
|
];
|
|
615
615
|
|
|
616
616
|
// First write with connector "chatgpt" — must succeed.
|
|
617
|
-
const ids1 = await orchestrator.persistExtraction(
|
|
617
|
+
const { persistedIds: ids1 } = await orchestrator.persistExtraction(
|
|
618
618
|
procedureResult(title, steps),
|
|
619
619
|
storage,
|
|
620
620
|
null,
|
|
@@ -623,7 +623,7 @@ test("connector dedup (QPDE5): same-connector procedure with steps dedupes", asy
|
|
|
623
623
|
assert.equal(ids1.length, 1, "first procedure write must succeed");
|
|
624
624
|
|
|
625
625
|
// Second write: same procedure, same connector → must dedupe.
|
|
626
|
-
const ids2 = await orchestrator.persistExtraction(
|
|
626
|
+
const { persistedIds: ids2 } = await orchestrator.persistExtraction(
|
|
627
627
|
procedureResult(title, steps),
|
|
628
628
|
storage,
|
|
629
629
|
null,
|
|
@@ -647,7 +647,7 @@ test("connector dedup (QPDE5): same procedure + different connector does NOT ded
|
|
|
647
647
|
];
|
|
648
648
|
|
|
649
649
|
// First write with connector "chatgpt".
|
|
650
|
-
const ids1 = await orchestrator.persistExtraction(
|
|
650
|
+
const { persistedIds: ids1 } = await orchestrator.persistExtraction(
|
|
651
651
|
procedureResult(title, steps),
|
|
652
652
|
storage,
|
|
653
653
|
null,
|
|
@@ -656,7 +656,7 @@ test("connector dedup (QPDE5): same procedure + different connector does NOT ded
|
|
|
656
656
|
assert.equal(ids1.length, 1, "first procedure write must succeed");
|
|
657
657
|
|
|
658
658
|
// Second write: same procedure, DIFFERENT connector → must NOT dedupe.
|
|
659
|
-
const ids2 = await orchestrator.persistExtraction(
|
|
659
|
+
const { persistedIds: ids2 } = await orchestrator.persistExtraction(
|
|
660
660
|
procedureResult(title, steps),
|
|
661
661
|
storage,
|
|
662
662
|
null,
|
|
@@ -711,7 +711,7 @@ test("connector dedup (QO42V): same-connector fact with structuredAttributes ded
|
|
|
711
711
|
const attrs = { dbEngine: "postgresql", version: "16" };
|
|
712
712
|
|
|
713
713
|
// First write with connector "chatgpt" — must succeed.
|
|
714
|
-
const ids1 = await orchestrator.persistExtraction(
|
|
714
|
+
const { persistedIds: ids1 } = await orchestrator.persistExtraction(
|
|
715
715
|
enrichedResult(body, attrs),
|
|
716
716
|
storage,
|
|
717
717
|
null,
|
|
@@ -720,7 +720,7 @@ test("connector dedup (QO42V): same-connector fact with structuredAttributes ded
|
|
|
720
720
|
assert.equal(ids1.length, 1, "first enriched fact write must succeed");
|
|
721
721
|
|
|
722
722
|
// Second write: same content + attrs, same connector → must dedupe.
|
|
723
|
-
const ids2 = await orchestrator.persistExtraction(
|
|
723
|
+
const { persistedIds: ids2 } = await orchestrator.persistExtraction(
|
|
724
724
|
enrichedResult(body, attrs),
|
|
725
725
|
storage,
|
|
726
726
|
null,
|
|
@@ -809,7 +809,7 @@ test("shared promotion (QPAn-): different-connector fact promotes when supersess
|
|
|
809
809
|
|
|
810
810
|
// Extract with a DIFFERENT connector — shared promotion else branch
|
|
811
811
|
// must NOT short-circuit; the different-connector fact gets promoted.
|
|
812
|
-
const ids = await orchestrator.persistExtraction(
|
|
812
|
+
const { persistedIds: ids } = await orchestrator.persistExtraction(
|
|
813
813
|
factResult(body),
|
|
814
814
|
storage,
|
|
815
815
|
null,
|
|
@@ -848,7 +848,7 @@ test("shared promotion (QPAn-): same-connector fact skips promotion when superse
|
|
|
848
848
|
});
|
|
849
849
|
|
|
850
850
|
// Extract with the SAME connector — shared promotion must be skipped.
|
|
851
|
-
const ids = await orchestrator.persistExtraction(
|
|
851
|
+
const { persistedIds: ids } = await orchestrator.persistExtraction(
|
|
852
852
|
factResult(body),
|
|
853
853
|
storage,
|
|
854
854
|
null,
|
|
@@ -902,7 +902,7 @@ test("shared backfill: different-connector shared fact is NOT temporal-patched",
|
|
|
902
902
|
|
|
903
903
|
// Extract the SAME content with a DIFFERENT connector and an eventTime
|
|
904
904
|
// expression that resolves a validUntil bound.
|
|
905
|
-
const ids = await orchestrator.persistExtraction(
|
|
905
|
+
const { persistedIds: ids } = await orchestrator.persistExtraction(
|
|
906
906
|
{
|
|
907
907
|
facts: [{
|
|
908
908
|
content: body,
|
|
@@ -1044,7 +1044,7 @@ test("shared backfill: operator (no-connector) extraction must NOT patch a conne
|
|
|
1044
1044
|
|
|
1045
1045
|
// Extract the SAME content with NO sourceConnector (operator
|
|
1046
1046
|
// re-extraction) and an eventTime that resolves a validUntil bound.
|
|
1047
|
-
const ids = await orchestrator.persistExtraction(
|
|
1047
|
+
const { persistedIds: ids } = await orchestrator.persistExtraction(
|
|
1048
1048
|
{
|
|
1049
1049
|
facts: [{
|
|
1050
1050
|
content: body,
|
|
@@ -1080,3 +1080,1024 @@ test("shared backfill: operator (no-connector) extraction must NOT patch a conne
|
|
|
1080
1080
|
"tagged fact must NOT get invalid_at from connectorless/operator backfill",
|
|
1081
1081
|
);
|
|
1082
1082
|
});
|
|
1083
|
+
// ---------------------------------------------------------------------------
|
|
1084
|
+
// Issue #1909 review round 2 finding 2 — defer only covered by the batch save
|
|
1085
|
+
// ---------------------------------------------------------------------------
|
|
1086
|
+
|
|
1087
|
+
test("#1909: with factDeduplicationEnabled=false, extraction writes flush the fact-hash index immediately", async () => {
|
|
1088
|
+
// With dedup off, contentHashIndexForStorage() returns null so the
|
|
1089
|
+
// orchestrator's end-of-persist batch save is a no-op. The main-path fact
|
|
1090
|
+
// write must therefore NOT defer its per-fact index flush — otherwise the
|
|
1091
|
+
// storage-owned fact-hash index is never written, and a restart with
|
|
1092
|
+
// fact-hashes.ready present trusts a stale index missing the fact.
|
|
1093
|
+
const memoryDir = await mkdtemp(path.join(os.tmpdir(), "remnic-defer-dedup-off-"));
|
|
1094
|
+
const config = parseConfig({
|
|
1095
|
+
openaiApiKey: "sk-test",
|
|
1096
|
+
memoryDir,
|
|
1097
|
+
workspaceDir: path.join(memoryDir, "workspace"),
|
|
1098
|
+
qmdEnabled: false,
|
|
1099
|
+
embeddingFallbackEnabled: false,
|
|
1100
|
+
chunkingEnabled: false,
|
|
1101
|
+
multiGraphMemoryEnabled: false,
|
|
1102
|
+
factDeduplicationEnabled: false,
|
|
1103
|
+
});
|
|
1104
|
+
const orchestrator = new Orchestrator(config) as unknown as OrchestratorTestSurface;
|
|
1105
|
+
const storage = await orchestrator.getStorage("default");
|
|
1106
|
+
await storage.ensureDirectories();
|
|
1107
|
+
// Warm the index authoritative and create the .ready marker over the current
|
|
1108
|
+
// (empty) corpus, so a later fresh session trusts the on-disk index.
|
|
1109
|
+
assert.equal(await storage.hasFactContentHash("warm"), false);
|
|
1110
|
+
|
|
1111
|
+
const body = "The billing service retries failed charges with exponential backoff.";
|
|
1112
|
+
const { persistedIds: ids } = await orchestrator.persistExtraction(factResult(body), storage, null);
|
|
1113
|
+
assert.equal(ids.length, 1, "the fact is written");
|
|
1114
|
+
|
|
1115
|
+
// Fresh StorageManager with .ready present trusts the on-disk fact-hash index
|
|
1116
|
+
// (no rebuild). The hash must be there via the immediate per-fact save.
|
|
1117
|
+
const restarted = new StorageManager(memoryDir);
|
|
1118
|
+
assert.equal(
|
|
1119
|
+
await restarted.hasFactContentHash(body),
|
|
1120
|
+
true,
|
|
1121
|
+
"the fact hash was flushed immediately despite the batch saver being a no-op",
|
|
1122
|
+
);
|
|
1123
|
+
});
|
|
1124
|
+
test("#2016 SD-nH: fact dedup disabled never suppresses a write via the authority/corpus-confirm path", async () => {
|
|
1125
|
+
// With dedup DISABLED the entire content-hash dedup path must be
|
|
1126
|
+
// short-circuited. The pre-fix code still ran hasContentHashDedup (returns
|
|
1127
|
+
// false with a null index), then consulted isFactContentHashAuthoritative();
|
|
1128
|
+
// when that reported NON-authoritative (e.g. a peer holds the rebuild lock)
|
|
1129
|
+
// it set needsCorpusConfirm and the connector-aware corpus scan could find a
|
|
1130
|
+
// same-content same-connector active fact and SUPPRESS the write — dedup
|
|
1131
|
+
// behavior while dedup is turned off.
|
|
1132
|
+
const memoryDir = await mkdtemp(path.join(os.tmpdir(), "remnic-dedup-off-nosuppress-"));
|
|
1133
|
+
try {
|
|
1134
|
+
const config = parseConfig({
|
|
1135
|
+
openaiApiKey: "sk-test",
|
|
1136
|
+
memoryDir,
|
|
1137
|
+
workspaceDir: path.join(memoryDir, "workspace"),
|
|
1138
|
+
qmdEnabled: false,
|
|
1139
|
+
embeddingFallbackEnabled: false,
|
|
1140
|
+
chunkingEnabled: false,
|
|
1141
|
+
multiGraphMemoryEnabled: false,
|
|
1142
|
+
factDeduplicationEnabled: false,
|
|
1143
|
+
});
|
|
1144
|
+
const orchestrator = new Orchestrator(config) as unknown as OrchestratorTestSurface;
|
|
1145
|
+
const storage = await orchestrator.getStorage("default");
|
|
1146
|
+
await storage.ensureDirectories();
|
|
1147
|
+
|
|
1148
|
+
const body = "The billing service retries failed charges with exponential backoff.";
|
|
1149
|
+
const source = { sourceConnector: "slack" };
|
|
1150
|
+
|
|
1151
|
+
// Seed one active same-connector copy in the corpus.
|
|
1152
|
+
const first = await orchestrator.persistExtraction(factResult(body), storage, null, source);
|
|
1153
|
+
assert.equal(first.persistedIds.length, 1, "first write lands");
|
|
1154
|
+
|
|
1155
|
+
// Force the storage fact-hash index NON-authoritative so the pre-fix code
|
|
1156
|
+
// falls into needsCorpusConfirm and runs the corpus scan — the exact path
|
|
1157
|
+
// that (wrongly) suppressed a write while dedup is DISABLED.
|
|
1158
|
+
// isFactContentHashAuthoritative is a real method on StorageManager; the
|
|
1159
|
+
// cast only reaches it as an overridable slot for this test.
|
|
1160
|
+
const authorityStub = storage as unknown as {
|
|
1161
|
+
isFactContentHashAuthoritative: () => Promise<boolean>;
|
|
1162
|
+
};
|
|
1163
|
+
authorityStub.isFactContentHashAuthoritative = async () => false;
|
|
1164
|
+
// Read the corpus from disk so the scan sees the seeded copy.
|
|
1165
|
+
clearMemoryCache();
|
|
1166
|
+
|
|
1167
|
+
const second = await orchestrator.persistExtraction(factResult(body), storage, null, source);
|
|
1168
|
+
assert.equal(
|
|
1169
|
+
second.persistedIds.length,
|
|
1170
|
+
1,
|
|
1171
|
+
"dedup disabled must not suppress the write via the authority/corpus-confirm path",
|
|
1172
|
+
);
|
|
1173
|
+
} finally {
|
|
1174
|
+
await rm(memoryDir, { recursive: true, force: true });
|
|
1175
|
+
}
|
|
1176
|
+
});
|
|
1177
|
+
test("#1909 round 11: deferred persist writes no ready marker and a restart rebuild still dedups", async () => {
|
|
1178
|
+
// With dedup ON the main-path fact write defers; there is NO fact-hashes.ready
|
|
1179
|
+
// marker anymore. A restart rebuilds the fact-hash index authoritatively from
|
|
1180
|
+
// the corpus and still dedups the fact.
|
|
1181
|
+
const memoryDir = await mkdtemp(path.join(os.tmpdir(), "remnic-defer-window-"));
|
|
1182
|
+
const config = parseConfig({
|
|
1183
|
+
openaiApiKey: "sk-test",
|
|
1184
|
+
memoryDir,
|
|
1185
|
+
workspaceDir: path.join(memoryDir, "workspace"),
|
|
1186
|
+
qmdEnabled: false,
|
|
1187
|
+
embeddingFallbackEnabled: false,
|
|
1188
|
+
chunkingEnabled: false,
|
|
1189
|
+
multiGraphMemoryEnabled: false,
|
|
1190
|
+
factDeduplicationEnabled: true,
|
|
1191
|
+
});
|
|
1192
|
+
const orchestrator = new Orchestrator(config) as unknown as OrchestratorTestSurface;
|
|
1193
|
+
const storage = await orchestrator.getStorage("default");
|
|
1194
|
+
await storage.ensureDirectories();
|
|
1195
|
+
const readyPath = path.join(memoryDir, "state", "fact-hashes.ready");
|
|
1196
|
+
|
|
1197
|
+
const body = "The scheduler batches webhook deliveries into 250ms windows.";
|
|
1198
|
+
const { persistedIds: ids } = await orchestrator.persistExtraction(factResult(body), storage, null);
|
|
1199
|
+
assert.equal(ids.length, 1, "the fact is written");
|
|
1200
|
+
assert.equal(existsSync(readyPath), false, "no ready marker is ever written (round 11)");
|
|
1201
|
+
|
|
1202
|
+
// A fresh instance rebuilds from the corpus and still dedups.
|
|
1203
|
+
const restarted = new StorageManager(memoryDir);
|
|
1204
|
+
assert.equal(await restarted.hasFactContentHash(body), true);
|
|
1205
|
+
assert.equal(existsSync(readyPath), false, "still no marker after the restart rebuild");
|
|
1206
|
+
});
|
|
1207
|
+
test("#1909: a deferred fact stays durable when addContentHashDedup throws", async () => {
|
|
1208
|
+
// Review round 6 finding 1: the deferred write's durability must not depend on
|
|
1209
|
+
// the orchestrator dedup registration succeeding. writeMemory already added the
|
|
1210
|
+
// hash to the storage-owned index; the end-of-run storage-index flush must put
|
|
1211
|
+
// it on disk even though addContentHashDedup threw (caught+logged).
|
|
1212
|
+
const memoryDir = await mkdtemp(path.join(os.tmpdir(), "remnic-defer-regfail-"));
|
|
1213
|
+
const config = parseConfig({
|
|
1214
|
+
openaiApiKey: "sk-test",
|
|
1215
|
+
memoryDir,
|
|
1216
|
+
workspaceDir: path.join(memoryDir, "workspace"),
|
|
1217
|
+
qmdEnabled: false,
|
|
1218
|
+
embeddingFallbackEnabled: false,
|
|
1219
|
+
chunkingEnabled: false,
|
|
1220
|
+
multiGraphMemoryEnabled: false,
|
|
1221
|
+
factDeduplicationEnabled: true,
|
|
1222
|
+
});
|
|
1223
|
+
const orchestrator = new Orchestrator(config) as unknown as OrchestratorTestSurface;
|
|
1224
|
+
const storage = await orchestrator.getStorage("default");
|
|
1225
|
+
await storage.ensureDirectories();
|
|
1226
|
+
assert.equal(await storage.hasFactContentHash("warm"), false); // warm + create marker
|
|
1227
|
+
|
|
1228
|
+
// Force the orchestrator dedup registration to throw for every fact this run.
|
|
1229
|
+
orchestrator.addContentHashDedup = async () => {
|
|
1230
|
+
throw new Error("simulated registration failure");
|
|
1231
|
+
};
|
|
1232
|
+
|
|
1233
|
+
const body = "The queue drains oldest-first under sustained backpressure.";
|
|
1234
|
+
const { persistedIds: ids } = await orchestrator.persistExtraction(factResult(body), storage, null);
|
|
1235
|
+
assert.equal(ids.length, 1, "the fact is written despite the registration failure");
|
|
1236
|
+
|
|
1237
|
+
// Restart trusts the marker (restored) and finds the hash on disk — no
|
|
1238
|
+
// duplicate re-creation — because the storage-owned index flush persisted it.
|
|
1239
|
+
const restarted = new StorageManager(memoryDir);
|
|
1240
|
+
assert.equal(
|
|
1241
|
+
await restarted.hasFactContentHash(body),
|
|
1242
|
+
true,
|
|
1243
|
+
"the deferred hash reached disk independent of addContentHashDedup",
|
|
1244
|
+
);
|
|
1245
|
+
});
|
|
1246
|
+
|
|
1247
|
+
test("#1909: two interleaved persist runs both land on disk (merge, no clobber)", async () => {
|
|
1248
|
+
// Review round 6 finding 2: two orchestrators that snapshot an empty index
|
|
1249
|
+
// before either saves must not clobber each other — the merge-save unions.
|
|
1250
|
+
const memoryDir = await mkdtemp(path.join(os.tmpdir(), "remnic-defer-interleave-"));
|
|
1251
|
+
const makeConfig = () =>
|
|
1252
|
+
parseConfig({
|
|
1253
|
+
openaiApiKey: "sk-test",
|
|
1254
|
+
memoryDir,
|
|
1255
|
+
workspaceDir: path.join(memoryDir, "workspace"),
|
|
1256
|
+
qmdEnabled: false,
|
|
1257
|
+
embeddingFallbackEnabled: false,
|
|
1258
|
+
chunkingEnabled: false,
|
|
1259
|
+
multiGraphMemoryEnabled: false,
|
|
1260
|
+
factDeduplicationEnabled: true,
|
|
1261
|
+
});
|
|
1262
|
+
const orchA = new Orchestrator(makeConfig()) as unknown as OrchestratorTestSurface;
|
|
1263
|
+
const orchB = new Orchestrator(makeConfig()) as unknown as OrchestratorTestSurface;
|
|
1264
|
+
const storageA = await orchA.getStorage("default");
|
|
1265
|
+
const storageB = await orchB.getStorage("default");
|
|
1266
|
+
await storageA.ensureDirectories();
|
|
1267
|
+
|
|
1268
|
+
// Force BOTH orchestrator indexes to load an empty snapshot up front (the race:
|
|
1269
|
+
// each holds a pre-write view of the shared on-disk index).
|
|
1270
|
+
await orchA.hasContentHashDedup(storageA, "noop-a");
|
|
1271
|
+
await orchB.hasContentHashDedup(storageB, "noop-b");
|
|
1272
|
+
|
|
1273
|
+
await orchA.persistExtraction(factResult("alpha interleaved fact"), storageA, null);
|
|
1274
|
+
await orchB.persistExtraction(factResult("beta interleaved fact"), storageB, null);
|
|
1275
|
+
|
|
1276
|
+
// A blind overwrite by B (stale empty snapshot) would drop alpha; the merge
|
|
1277
|
+
// union preserves both.
|
|
1278
|
+
const fresh = new StorageManager(memoryDir);
|
|
1279
|
+
assert.equal(await fresh.hasFactContentHash("alpha interleaved fact"), true, "A's fact survived");
|
|
1280
|
+
assert.equal(await fresh.hasFactContentHash("beta interleaved fact"), true, "B's fact survived");
|
|
1281
|
+
});
|
|
1282
|
+
test("#1909 round 11: the restart rebuild reflects the current corpus (archived fact is not re-deduped)", async () => {
|
|
1283
|
+
// No ready marker exists — the fact-hash index is ALWAYS rebuilt from the
|
|
1284
|
+
// durable corpus on restart. So an archival/consolidation removal LANDS after
|
|
1285
|
+
// restart (the removed .md is excluded from the rebuild) even if that run's
|
|
1286
|
+
// reconciling save could not publish (lock timeout) — there is no stale
|
|
1287
|
+
// on-disk index to trust. This is the definitive replacement for the
|
|
1288
|
+
// marker-invalidate-on-removal machinery.
|
|
1289
|
+
const memoryDir = await mkdtemp(path.join(os.tmpdir(), "remnic-corpus-reflect-"));
|
|
1290
|
+
const config = parseConfig({
|
|
1291
|
+
openaiApiKey: "sk-test",
|
|
1292
|
+
memoryDir,
|
|
1293
|
+
workspaceDir: path.join(memoryDir, "workspace"),
|
|
1294
|
+
qmdEnabled: false,
|
|
1295
|
+
embeddingFallbackEnabled: false,
|
|
1296
|
+
chunkingEnabled: false,
|
|
1297
|
+
multiGraphMemoryEnabled: false,
|
|
1298
|
+
factDeduplicationEnabled: true,
|
|
1299
|
+
});
|
|
1300
|
+
const orchestrator = new Orchestrator(config) as unknown as OrchestratorTestSurface;
|
|
1301
|
+
const storage = await orchestrator.getStorage("default");
|
|
1302
|
+
await storage.ensureDirectories();
|
|
1303
|
+
|
|
1304
|
+
const body = "The nightly job compacts cold storage at 02:00 UTC.";
|
|
1305
|
+
await orchestrator.persistExtraction(factResult(body), storage, null);
|
|
1306
|
+
const readyPath = path.join(memoryDir, "state", "fact-hashes.ready");
|
|
1307
|
+
assert.equal(existsSync(readyPath), false, "no ready marker is ever written (round 11)");
|
|
1308
|
+
|
|
1309
|
+
// A fresh instance rebuilds from the corpus → dedups the persisted fact.
|
|
1310
|
+
const before = new StorageManager(memoryDir);
|
|
1311
|
+
assert.equal(await before.hasFactContentHash(body), true);
|
|
1312
|
+
|
|
1313
|
+
// Archive the fact: remove its .md from the active corpus.
|
|
1314
|
+
const mem = (await storage.readAllMemories()).find((m: MemoryFile) => m.content.includes(body));
|
|
1315
|
+
assert.ok(mem, "persisted fact is readable");
|
|
1316
|
+
await rm(mem.path);
|
|
1317
|
+
// Simulate a fresh process (real restart): drop the process-wide memory cache
|
|
1318
|
+
// so the rebuild re-reads the now-smaller corpus from disk.
|
|
1319
|
+
clearMemoryCache(memoryDir);
|
|
1320
|
+
|
|
1321
|
+
// A fresh instance rebuilds from the now-smaller corpus → the archived fact is
|
|
1322
|
+
// no longer deduped (re-extraction is allowed). The removal landed with no
|
|
1323
|
+
// marker and no reliance on the reconciling save having published.
|
|
1324
|
+
const after = new StorageManager(memoryDir);
|
|
1325
|
+
assert.equal(
|
|
1326
|
+
await after.hasFactContentHash(body),
|
|
1327
|
+
false,
|
|
1328
|
+
"archived fact is not deduped after the restart rebuild",
|
|
1329
|
+
);
|
|
1330
|
+
});
|
|
1331
|
+
test("#1909 round 11 finding 2: destroy() flushes the debounced buffer BEFORE catalog touches", async () => {
|
|
1332
|
+
// The buffer save fires a coalesced namespace-catalog touch; if catalog touches
|
|
1333
|
+
// were flushed first, that shutdown-time touch would queue after and be lost.
|
|
1334
|
+
// destroy() must flush the buffer first so its touch folds into the catalog
|
|
1335
|
+
// flush and both settle before destroy() returns.
|
|
1336
|
+
const memoryDir = await mkdtemp(path.join(os.tmpdir(), "remnic-destroy-order-"));
|
|
1337
|
+
const config = parseConfig({
|
|
1338
|
+
openaiApiKey: "sk-test",
|
|
1339
|
+
memoryDir,
|
|
1340
|
+
workspaceDir: path.join(memoryDir, "workspace"),
|
|
1341
|
+
qmdEnabled: false,
|
|
1342
|
+
embeddingFallbackEnabled: false,
|
|
1343
|
+
chunkingEnabled: false,
|
|
1344
|
+
multiGraphMemoryEnabled: false,
|
|
1345
|
+
});
|
|
1346
|
+
const orchestrator = new Orchestrator(config);
|
|
1347
|
+
const priv = orchestrator as unknown as {
|
|
1348
|
+
buffer: { flushPendingSave: () => Promise<void> };
|
|
1349
|
+
namespaceCatalog: { flushPendingTouches: () => Promise<void> };
|
|
1350
|
+
destroy: () => Promise<void>;
|
|
1351
|
+
};
|
|
1352
|
+
const order: string[] = [];
|
|
1353
|
+
const realFlushSave = priv.buffer.flushPendingSave.bind(priv.buffer);
|
|
1354
|
+
priv.buffer.flushPendingSave = async () => {
|
|
1355
|
+
order.push("buffer");
|
|
1356
|
+
return realFlushSave();
|
|
1357
|
+
};
|
|
1358
|
+
const realFlushTouch = priv.namespaceCatalog.flushPendingTouches.bind(priv.namespaceCatalog);
|
|
1359
|
+
priv.namespaceCatalog.flushPendingTouches = async () => {
|
|
1360
|
+
order.push("catalog");
|
|
1361
|
+
return realFlushTouch();
|
|
1362
|
+
};
|
|
1363
|
+
|
|
1364
|
+
await priv.destroy();
|
|
1365
|
+
assert.deepEqual(
|
|
1366
|
+
order,
|
|
1367
|
+
["buffer", "catalog"],
|
|
1368
|
+
"buffer flush must run before the catalog-touch flush on shutdown",
|
|
1369
|
+
);
|
|
1370
|
+
});
|
|
1371
|
+
test("#1909 round 14: destroy() surfaces a buffer flush failure and still completes teardown", async () => {
|
|
1372
|
+
// Graceful-shutdown durability contract: a failed shutdown buffer flush must
|
|
1373
|
+
// NOT be silently swallowed — the pending turns are retained in memory but
|
|
1374
|
+
// lost on process exit, so the host has to learn about it. destroy() runs the
|
|
1375
|
+
// remaining teardown in a finally block, then rethrows the flush failure.
|
|
1376
|
+
const memoryDir = await mkdtemp(path.join(os.tmpdir(), "remnic-destroy-fail-"));
|
|
1377
|
+
const config = parseConfig({
|
|
1378
|
+
openaiApiKey: "sk-test",
|
|
1379
|
+
memoryDir,
|
|
1380
|
+
workspaceDir: path.join(memoryDir, "workspace"),
|
|
1381
|
+
qmdEnabled: false,
|
|
1382
|
+
embeddingFallbackEnabled: false,
|
|
1383
|
+
chunkingEnabled: false,
|
|
1384
|
+
multiGraphMemoryEnabled: false,
|
|
1385
|
+
});
|
|
1386
|
+
const orchestrator = new Orchestrator(config);
|
|
1387
|
+
const priv = orchestrator as unknown as {
|
|
1388
|
+
buffer: { flushPendingSave: (opts?: { throwOnFailure?: boolean }) => Promise<void> };
|
|
1389
|
+
namespaceCatalog: { flushPendingTouches: () => Promise<void> };
|
|
1390
|
+
destroy: () => Promise<void>;
|
|
1391
|
+
};
|
|
1392
|
+
const flushErr = new Error("simulated shutdown buffer write failure");
|
|
1393
|
+
priv.buffer.flushPendingSave = async () => {
|
|
1394
|
+
throw flushErr;
|
|
1395
|
+
};
|
|
1396
|
+
let catalogFlushed = false;
|
|
1397
|
+
const realFlushTouch = priv.namespaceCatalog.flushPendingTouches.bind(priv.namespaceCatalog);
|
|
1398
|
+
priv.namespaceCatalog.flushPendingTouches = async () => {
|
|
1399
|
+
catalogFlushed = true;
|
|
1400
|
+
return realFlushTouch();
|
|
1401
|
+
};
|
|
1402
|
+
|
|
1403
|
+
await assert.rejects(
|
|
1404
|
+
priv.destroy(),
|
|
1405
|
+
(err: unknown) => err === flushErr,
|
|
1406
|
+
"destroy() must surface the buffer flush failure instead of swallowing it",
|
|
1407
|
+
);
|
|
1408
|
+
assert.equal(
|
|
1409
|
+
catalogFlushed,
|
|
1410
|
+
true,
|
|
1411
|
+
"teardown (catalog flush) must still run in the finally block despite the flush failure",
|
|
1412
|
+
);
|
|
1413
|
+
});
|
|
1414
|
+
test("#1909 round 12: after a crash before the batch save, the orchestrator dedup sees the fact (corpus rebuild)", async () => {
|
|
1415
|
+
// A deferred fact write persists the .md but not fact-hashes.txt; a crash
|
|
1416
|
+
// before saveContentHashIndexes leaves only the .md durable. On restart the
|
|
1417
|
+
// orchestrator's dedup index must be corpus-AUTHORITATIVE (round 12) — sharing
|
|
1418
|
+
// StorageManager's rebuild — so hasContentHashDedup sees the fact and
|
|
1419
|
+
// persistExtraction does NOT re-create it. Pre-round-12 it loaded a stale
|
|
1420
|
+
// fact-hashes.txt (missing the fact) and would duplicate.
|
|
1421
|
+
const memoryDir = await mkdtemp(path.join(os.tmpdir(), "remnic-orch-rebuild-"));
|
|
1422
|
+
const body = "The deploy pipeline gates on a green smoke suite.";
|
|
1423
|
+
|
|
1424
|
+
// Phase 1 — crash window: deferred write, NO batch save / index flush.
|
|
1425
|
+
{
|
|
1426
|
+
const seed = new StorageManager(memoryDir);
|
|
1427
|
+
await seed.ensureDirectories();
|
|
1428
|
+
await seed.writeMemory("fact", body, { source: "extraction", deferHashIndexSave: true });
|
|
1429
|
+
// CRASH: no saveContentHashIndexes → fact-hashes.txt never got the hash.
|
|
1430
|
+
}
|
|
1431
|
+
clearMemoryCache(memoryDir); // model a fresh process
|
|
1432
|
+
|
|
1433
|
+
// Phase 2 — restart: the orchestrator dedup index rebuilds from the corpus.
|
|
1434
|
+
const config = parseConfig({
|
|
1435
|
+
openaiApiKey: "sk-test",
|
|
1436
|
+
memoryDir,
|
|
1437
|
+
workspaceDir: path.join(memoryDir, "workspace"),
|
|
1438
|
+
qmdEnabled: false,
|
|
1439
|
+
embeddingFallbackEnabled: false,
|
|
1440
|
+
chunkingEnabled: false,
|
|
1441
|
+
multiGraphMemoryEnabled: false,
|
|
1442
|
+
factDeduplicationEnabled: true,
|
|
1443
|
+
});
|
|
1444
|
+
const orchestrator = new Orchestrator(config) as unknown as OrchestratorTestSurface;
|
|
1445
|
+
const storage = await orchestrator.getStorage("default");
|
|
1446
|
+
|
|
1447
|
+
assert.equal(
|
|
1448
|
+
await orchestrator.hasContentHashDedup(storage, body),
|
|
1449
|
+
true,
|
|
1450
|
+
"orchestrator dedup sees the crashed-but-durable fact via the corpus rebuild",
|
|
1451
|
+
);
|
|
1452
|
+
// And re-persisting the same fact is deduped (no duplicate .md created).
|
|
1453
|
+
const { persistedIds: ids } = await orchestrator.persistExtraction(factResult(body), storage, null);
|
|
1454
|
+
assert.equal(ids.length, 0, "the fact is deduped on re-extraction — not re-created");
|
|
1455
|
+
});
|
|
1456
|
+
|
|
1457
|
+
test("#1909 round 13: startup rebuild preserves PROCEDURE hashes as well as fact hashes across restart", async () => {
|
|
1458
|
+
// The content-hash dedup index is SHARED by fact AND procedure dedup —
|
|
1459
|
+
// procedures register their persist-body hash into the same index. The
|
|
1460
|
+
// marker-less startup rebuild (ensureFactHashIndexAuthoritative) clears the
|
|
1461
|
+
// on-disk index and reconstructs it from the .md corpus on first use per
|
|
1462
|
+
// process. A fact-only rebuild dropped every persisted procedure hash, so a
|
|
1463
|
+
// restart would re-create identical procedures. Both categories must survive.
|
|
1464
|
+
const memoryDir = await mkdtemp(path.join(os.tmpdir(), "remnic-proc-rebuild-"));
|
|
1465
|
+
const factBody = "The staging deploy gates on a green smoke suite.";
|
|
1466
|
+
const procTitle = "When you cut a hotfix release, follow the checklist";
|
|
1467
|
+
const procSteps = [
|
|
1468
|
+
{ intent: "Branch from main and cherry-pick the fix" },
|
|
1469
|
+
{ intent: "Run CI and tag the release" },
|
|
1470
|
+
];
|
|
1471
|
+
// The dedup key for a procedure is its full persist body (title + steps).
|
|
1472
|
+
const procBody = buildProcedurePersistBody(
|
|
1473
|
+
procTitle,
|
|
1474
|
+
procSteps.map((s, i) => ({ order: i + 1, intent: s.intent })),
|
|
1475
|
+
);
|
|
1476
|
+
|
|
1477
|
+
const makeConfig = () =>
|
|
1478
|
+
parseConfig({
|
|
1479
|
+
openaiApiKey: "sk-test",
|
|
1480
|
+
memoryDir,
|
|
1481
|
+
workspaceDir: path.join(memoryDir, "workspace"),
|
|
1482
|
+
qmdEnabled: false,
|
|
1483
|
+
embeddingFallbackEnabled: false,
|
|
1484
|
+
chunkingEnabled: false,
|
|
1485
|
+
multiGraphMemoryEnabled: false,
|
|
1486
|
+
factDeduplicationEnabled: true,
|
|
1487
|
+
// procedural.enabled defaults to true.
|
|
1488
|
+
});
|
|
1489
|
+
|
|
1490
|
+
// Phase 1 — persist a fact and a procedure through the real persist path.
|
|
1491
|
+
{
|
|
1492
|
+
const orch = new Orchestrator(makeConfig()) as unknown as OrchestratorTestSurface;
|
|
1493
|
+
const storage = await orch.getStorage("default");
|
|
1494
|
+
const { persistedIds: factIds } = await orch.persistExtraction(factResult(factBody), storage, null);
|
|
1495
|
+
assert.equal(factIds.length, 1, "fact persisted in phase 1");
|
|
1496
|
+
const { persistedIds: procIds } = await orch.persistExtraction(
|
|
1497
|
+
procedureResult(procTitle, procSteps),
|
|
1498
|
+
storage,
|
|
1499
|
+
null,
|
|
1500
|
+
);
|
|
1501
|
+
assert.equal(procIds.length, 1, "procedure persisted in phase 1");
|
|
1502
|
+
}
|
|
1503
|
+
clearMemoryCache(memoryDir); // model a fresh process
|
|
1504
|
+
|
|
1505
|
+
// Phase 2 — restart: the dedup index rebuilds authoritatively from the corpus.
|
|
1506
|
+
const orch2 = new Orchestrator(makeConfig()) as unknown as OrchestratorTestSurface;
|
|
1507
|
+
const storage2 = await orch2.getStorage("default");
|
|
1508
|
+
|
|
1509
|
+
assert.equal(
|
|
1510
|
+
await orch2.hasContentHashDedup(storage2, factBody),
|
|
1511
|
+
true,
|
|
1512
|
+
"fact hash survives the corpus rebuild",
|
|
1513
|
+
);
|
|
1514
|
+
assert.equal(
|
|
1515
|
+
await orch2.hasContentHashDedup(storage2, procBody),
|
|
1516
|
+
true,
|
|
1517
|
+
"PROCEDURE hash survives the corpus rebuild (round 13 fix)",
|
|
1518
|
+
);
|
|
1519
|
+
|
|
1520
|
+
// Re-extraction of both is deduped — neither is re-created.
|
|
1521
|
+
const { persistedIds: factReIds } = await orch2.persistExtraction(factResult(factBody), storage2, null);
|
|
1522
|
+
assert.equal(factReIds.length, 0, "fact is deduped on restart");
|
|
1523
|
+
const { persistedIds: procReIds } = await orch2.persistExtraction(
|
|
1524
|
+
procedureResult(procTitle, procSteps),
|
|
1525
|
+
storage2,
|
|
1526
|
+
null,
|
|
1527
|
+
);
|
|
1528
|
+
assert.equal(
|
|
1529
|
+
procReIds.length,
|
|
1530
|
+
0,
|
|
1531
|
+
"procedure is deduped on restart (would be re-created without the round 13 fix)",
|
|
1532
|
+
);
|
|
1533
|
+
|
|
1534
|
+
// Exactly one of each remains on disk.
|
|
1535
|
+
const all = await storage2.readAllMemories();
|
|
1536
|
+
assert.equal(
|
|
1537
|
+
all.filter((m: MemoryFile) => m.frontmatter.category === "fact").length,
|
|
1538
|
+
1,
|
|
1539
|
+
"one fact on disk",
|
|
1540
|
+
);
|
|
1541
|
+
assert.equal(
|
|
1542
|
+
all.filter((m: MemoryFile) => m.frontmatter.category === "procedure").length,
|
|
1543
|
+
1,
|
|
1544
|
+
"one procedure on disk",
|
|
1545
|
+
);
|
|
1546
|
+
|
|
1547
|
+
await rm(memoryDir, { recursive: true, force: true });
|
|
1548
|
+
});
|
|
1549
|
+
|
|
1550
|
+
test("#1909 round 15 (PR #2016): startup rebuild preserves EVERY registered category hash across restart", async () => {
|
|
1551
|
+
// The content-hash dedup index is shared by every registered write category:
|
|
1552
|
+
// persistExtraction calls addContentHashDedup for every writeCategory it
|
|
1553
|
+
// persists (preference, decision, commitment, …), not only fact/procedure.
|
|
1554
|
+
// A rebuild restricted to fact+procedure dropped those hashes on restart, so
|
|
1555
|
+
// the next extraction re-created identical active non-fact memories (the
|
|
1556
|
+
// retired fact-hashes.txt load used to preserve them). Every category's hash
|
|
1557
|
+
// must survive the corpus rebuild.
|
|
1558
|
+
const memoryDir = await mkdtemp(path.join(os.tmpdir(), "remnic-cat-rebuild-"));
|
|
1559
|
+
const prefContent = "The user prefers dark mode across every surface.";
|
|
1560
|
+
const decisionContent = "We standardized on pnpm for all workspace installs.";
|
|
1561
|
+
// A preference carrying structuredAttributes: writeMemory appends an
|
|
1562
|
+
// "[Attributes: …]" suffix to the stored body, but the registered dedup key
|
|
1563
|
+
// is the RAW content WITHOUT that suffix. The rebuild must strip the suffix
|
|
1564
|
+
// or the reconstructed hash never matches and the memory is re-created.
|
|
1565
|
+
const attrContent = "The user's working timezone is America/Chicago.";
|
|
1566
|
+
const attrs: Record<string, string> = { timezone: "America/Chicago", trust: "high" };
|
|
1567
|
+
|
|
1568
|
+
const categoryResult = (
|
|
1569
|
+
content: string,
|
|
1570
|
+
category: MemoryCategory,
|
|
1571
|
+
structuredAttributes?: Record<string, string>,
|
|
1572
|
+
): ExtractionResult => ({
|
|
1573
|
+
facts: [
|
|
1574
|
+
{
|
|
1575
|
+
content,
|
|
1576
|
+
category,
|
|
1577
|
+
tags: [],
|
|
1578
|
+
confidence: 0.9,
|
|
1579
|
+
...(structuredAttributes ? { structuredAttributes } : {}),
|
|
1580
|
+
} as ExtractedFact,
|
|
1581
|
+
],
|
|
1582
|
+
entities: [],
|
|
1583
|
+
relationships: [],
|
|
1584
|
+
questions: [],
|
|
1585
|
+
profileUpdates: [],
|
|
1586
|
+
});
|
|
1587
|
+
|
|
1588
|
+
const makeConfig = () =>
|
|
1589
|
+
parseConfig({
|
|
1590
|
+
openaiApiKey: "sk-test",
|
|
1591
|
+
memoryDir,
|
|
1592
|
+
workspaceDir: path.join(memoryDir, "workspace"),
|
|
1593
|
+
qmdEnabled: false,
|
|
1594
|
+
embeddingFallbackEnabled: false,
|
|
1595
|
+
chunkingEnabled: false,
|
|
1596
|
+
multiGraphMemoryEnabled: false,
|
|
1597
|
+
factDeduplicationEnabled: true,
|
|
1598
|
+
});
|
|
1599
|
+
|
|
1600
|
+
// Phase 1 — persist non-fact/procedure categories through the real path.
|
|
1601
|
+
{
|
|
1602
|
+
const orch = new Orchestrator(makeConfig()) as unknown as OrchestratorTestSurface;
|
|
1603
|
+
const storage = await orch.getStorage("default");
|
|
1604
|
+
assert.equal(
|
|
1605
|
+
(await orch.persistExtraction(categoryResult(prefContent, "preference"), storage, null)).persistedIds.length,
|
|
1606
|
+
1,
|
|
1607
|
+
"preference persisted in phase 1",
|
|
1608
|
+
);
|
|
1609
|
+
assert.equal(
|
|
1610
|
+
(await orch.persistExtraction(categoryResult(decisionContent, "decision"), storage, null)).persistedIds.length,
|
|
1611
|
+
1,
|
|
1612
|
+
"decision persisted in phase 1",
|
|
1613
|
+
);
|
|
1614
|
+
assert.equal(
|
|
1615
|
+
(await orch.persistExtraction(categoryResult(attrContent, "preference", attrs), storage, null)).persistedIds.length,
|
|
1616
|
+
1,
|
|
1617
|
+
"attributed preference persisted in phase 1",
|
|
1618
|
+
);
|
|
1619
|
+
}
|
|
1620
|
+
clearMemoryCache(memoryDir); // model a fresh process
|
|
1621
|
+
|
|
1622
|
+
// Phase 2 — restart: the dedup index rebuilds authoritatively from the corpus.
|
|
1623
|
+
const orch2 = new Orchestrator(makeConfig()) as unknown as OrchestratorTestSurface;
|
|
1624
|
+
const storage2 = await orch2.getStorage("default");
|
|
1625
|
+
|
|
1626
|
+
assert.equal(
|
|
1627
|
+
await orch2.hasContentHashDedup(storage2, prefContent),
|
|
1628
|
+
true,
|
|
1629
|
+
"preference hash survives the corpus rebuild (PR #2016 fix)",
|
|
1630
|
+
);
|
|
1631
|
+
assert.equal(
|
|
1632
|
+
await orch2.hasContentHashDedup(storage2, decisionContent),
|
|
1633
|
+
true,
|
|
1634
|
+
"decision hash survives the corpus rebuild (PR #2016 fix)",
|
|
1635
|
+
);
|
|
1636
|
+
assert.equal(
|
|
1637
|
+
await orch2.hasContentHashDedup(storage2, attrContent),
|
|
1638
|
+
true,
|
|
1639
|
+
"attributed preference hash survives the rebuild (attributes suffix stripped)",
|
|
1640
|
+
);
|
|
1641
|
+
|
|
1642
|
+
// Re-extraction of each is deduped — none is re-created.
|
|
1643
|
+
assert.equal(
|
|
1644
|
+
(await orch2.persistExtraction(categoryResult(prefContent, "preference"), storage2, null)).persistedIds.length,
|
|
1645
|
+
0,
|
|
1646
|
+
"preference is deduped on restart (would be re-created without the fix)",
|
|
1647
|
+
);
|
|
1648
|
+
assert.equal(
|
|
1649
|
+
(await orch2.persistExtraction(categoryResult(decisionContent, "decision"), storage2, null)).persistedIds.length,
|
|
1650
|
+
0,
|
|
1651
|
+
"decision is deduped on restart (would be re-created without the fix)",
|
|
1652
|
+
);
|
|
1653
|
+
assert.equal(
|
|
1654
|
+
(await orch2.persistExtraction(categoryResult(attrContent, "preference", attrs), storage2, null)).persistedIds.length,
|
|
1655
|
+
0,
|
|
1656
|
+
"attributed preference is deduped on restart (would be re-created without the fix)",
|
|
1657
|
+
);
|
|
1658
|
+
|
|
1659
|
+
// The originals remain the only copies on disk.
|
|
1660
|
+
const all = await storage2.readAllMemories();
|
|
1661
|
+
assert.equal(
|
|
1662
|
+
all.filter((m: MemoryFile) => m.frontmatter.category === "preference").length,
|
|
1663
|
+
2,
|
|
1664
|
+
"two preferences on disk (plain + attributed), none duplicated",
|
|
1665
|
+
);
|
|
1666
|
+
assert.equal(
|
|
1667
|
+
all.filter((m: MemoryFile) => m.frontmatter.category === "decision").length,
|
|
1668
|
+
1,
|
|
1669
|
+
"one decision on disk, not duplicated",
|
|
1670
|
+
);
|
|
1671
|
+
|
|
1672
|
+
await rm(memoryDir, { recursive: true, force: true });
|
|
1673
|
+
});
|
|
1674
|
+
|
|
1675
|
+
test("#1909 (PR #2016): a fact demoted to cold is not re-created as a duplicate hot copy after restart", async () => {
|
|
1676
|
+
// The authoritative content-hash rebuild unions the HOT and COLD tiers, so a
|
|
1677
|
+
// restart's hash index reports a hit for a fact whose only active copy was
|
|
1678
|
+
// demoted to cold/. The connector-aware confirmation scan in
|
|
1679
|
+
// ExtractionPersistCoordinator previously scanned readAllMemories() (hot)
|
|
1680
|
+
// only, found no matching row, flipped exactDuplicate back to false, and
|
|
1681
|
+
// wrote a SECOND active hot copy. It must scan the cold tier too so the
|
|
1682
|
+
// cold-only active copy still suppresses the redundant hot write.
|
|
1683
|
+
const memoryDir = await mkdtemp(path.join(os.tmpdir(), "remnic-cold-dedup-"));
|
|
1684
|
+
const body =
|
|
1685
|
+
"The disaster-recovery drill runs on the first Sunday of every quarter.";
|
|
1686
|
+
|
|
1687
|
+
const makeConfig = () =>
|
|
1688
|
+
parseConfig({
|
|
1689
|
+
openaiApiKey: "sk-test",
|
|
1690
|
+
memoryDir,
|
|
1691
|
+
workspaceDir: path.join(memoryDir, "workspace"),
|
|
1692
|
+
qmdEnabled: false,
|
|
1693
|
+
embeddingFallbackEnabled: false,
|
|
1694
|
+
chunkingEnabled: false,
|
|
1695
|
+
multiGraphMemoryEnabled: false,
|
|
1696
|
+
factDeduplicationEnabled: true,
|
|
1697
|
+
});
|
|
1698
|
+
|
|
1699
|
+
// Phase 1 — persist a fact, then demote its only active copy to cold/.
|
|
1700
|
+
{
|
|
1701
|
+
const orch = new Orchestrator(makeConfig()) as unknown as OrchestratorTestSurface;
|
|
1702
|
+
const storage = await orch.getStorage("default");
|
|
1703
|
+
const { persistedIds: ids } = await orch.persistExtraction(factResult(body), storage, null, {
|
|
1704
|
+
sourceConnector: "chatgpt",
|
|
1705
|
+
});
|
|
1706
|
+
assert.equal(ids.length, 1, "fact persisted to hot in phase 1");
|
|
1707
|
+
|
|
1708
|
+
const [hotMemory] = await storage.readAllMemories();
|
|
1709
|
+
assert.ok(hotMemory, "the persisted fact is readable from the hot tier");
|
|
1710
|
+
const { changed } = await storage.migrateMemoryToTier(hotMemory, "cold");
|
|
1711
|
+
assert.equal(changed, true, "the fact was demoted to cold");
|
|
1712
|
+
|
|
1713
|
+
assert.equal(
|
|
1714
|
+
(await storage.readAllMemories()).length,
|
|
1715
|
+
0,
|
|
1716
|
+
"no active copy remains in the hot tier after demotion",
|
|
1717
|
+
);
|
|
1718
|
+
const cold = await storage.readAllColdMemories();
|
|
1719
|
+
assert.equal(cold.length, 1, "the demoted fact is now in the cold tier");
|
|
1720
|
+
assert.equal(
|
|
1721
|
+
cold[0]?.frontmatter.status ?? "active",
|
|
1722
|
+
"active",
|
|
1723
|
+
"the cold copy is still active",
|
|
1724
|
+
);
|
|
1725
|
+
}
|
|
1726
|
+
clearMemoryCache(memoryDir); // model a fresh process
|
|
1727
|
+
|
|
1728
|
+
// Phase 2 — restart: the authoritative rebuild unions hot+cold, so the hash
|
|
1729
|
+
// index reports a hit for the cold-only copy.
|
|
1730
|
+
const orch2 = new Orchestrator(makeConfig()) as unknown as OrchestratorTestSurface;
|
|
1731
|
+
const storage2 = await orch2.getStorage("default");
|
|
1732
|
+
assert.equal(
|
|
1733
|
+
await orch2.hasContentHashDedup(storage2, body),
|
|
1734
|
+
true,
|
|
1735
|
+
"the demoted fact's hash survives the hot+cold rebuild",
|
|
1736
|
+
);
|
|
1737
|
+
|
|
1738
|
+
// Re-extraction of the same content with the same connector must be deduped
|
|
1739
|
+
// against the cold copy — no second hot copy is created.
|
|
1740
|
+
const { persistedIds: reIds } = await orch2.persistExtraction(factResult(body), storage2, null, {
|
|
1741
|
+
sourceConnector: "chatgpt",
|
|
1742
|
+
});
|
|
1743
|
+
assert.equal(
|
|
1744
|
+
reIds.length,
|
|
1745
|
+
0,
|
|
1746
|
+
"cold-only active copy suppresses the duplicate hot write (would be 1 without the fix)",
|
|
1747
|
+
);
|
|
1748
|
+
|
|
1749
|
+
assert.equal(
|
|
1750
|
+
(await storage2.readAllMemories()).filter((m: MemoryFile) => m.content.includes(body))
|
|
1751
|
+
.length,
|
|
1752
|
+
0,
|
|
1753
|
+
"no duplicate hot copy was written",
|
|
1754
|
+
);
|
|
1755
|
+
assert.equal(
|
|
1756
|
+
(await storage2.readAllColdMemories()).filter((m: MemoryFile) => m.content.includes(body))
|
|
1757
|
+
.length,
|
|
1758
|
+
1,
|
|
1759
|
+
"the single cold copy remains the only copy of the fact",
|
|
1760
|
+
);
|
|
1761
|
+
|
|
1762
|
+
await rm(memoryDir, { recursive: true, force: true });
|
|
1763
|
+
});
|
|
1764
|
+
|
|
1765
|
+
test("#1909 (PR #2016) review: an authoritative instance still confirms a fact a peer flushed after its rebuild (no duplicate)", async () => {
|
|
1766
|
+
// Fresh finding beyond the crash/restart tests above: those model a NEW
|
|
1767
|
+
// process rebuilding from the corpus. Here BOTH instances stay live. Instance
|
|
1768
|
+
// A rebuilds its fact-hash index and marks it authoritative; instance B then
|
|
1769
|
+
// persists AND flushes a fact through the real persist path. A is still
|
|
1770
|
+
// flagged authoritative from its earlier rebuild, so without a freshness gate
|
|
1771
|
+
// its stale in-memory index answers a MISS and skips corpus confirmation —
|
|
1772
|
+
// persisting a duplicate active memory. The cheap per-operation freshness
|
|
1773
|
+
// check (durable index fingerprint advanced) must drop authority so A's
|
|
1774
|
+
// duplicate check finds B's fact.
|
|
1775
|
+
const memoryDir = await mkdtemp(path.join(os.tmpdir(), "remnic-peer-fresh-"));
|
|
1776
|
+
const makeConfig = () =>
|
|
1777
|
+
parseConfig({
|
|
1778
|
+
openaiApiKey: "sk-test",
|
|
1779
|
+
memoryDir,
|
|
1780
|
+
workspaceDir: path.join(memoryDir, "workspace"),
|
|
1781
|
+
qmdEnabled: false,
|
|
1782
|
+
embeddingFallbackEnabled: false,
|
|
1783
|
+
chunkingEnabled: false,
|
|
1784
|
+
multiGraphMemoryEnabled: false,
|
|
1785
|
+
factDeduplicationEnabled: true,
|
|
1786
|
+
});
|
|
1787
|
+
const orchA = new Orchestrator(makeConfig()) as unknown as OrchestratorTestSurface;
|
|
1788
|
+
const orchB = new Orchestrator(makeConfig()) as unknown as OrchestratorTestSurface;
|
|
1789
|
+
const storageA = await orchA.getStorage("default");
|
|
1790
|
+
const storageB = await orchB.getStorage("default");
|
|
1791
|
+
await storageA.ensureDirectories();
|
|
1792
|
+
|
|
1793
|
+
const body = "The incident bridge auto-pages the on-call SRE after five minutes.";
|
|
1794
|
+
|
|
1795
|
+
// A rebuilds the index and becomes authoritative over a corpus without the
|
|
1796
|
+
// fact — its in-memory MISS for `body` is (correctly) trusted at this point.
|
|
1797
|
+
assert.equal(
|
|
1798
|
+
await storageA.isFactContentHashAuthoritative(),
|
|
1799
|
+
true,
|
|
1800
|
+
"A's fact-hash index rebuilt authoritative",
|
|
1801
|
+
);
|
|
1802
|
+
assert.equal(
|
|
1803
|
+
await orchA.hasContentHashDedup(storageA, body),
|
|
1804
|
+
false,
|
|
1805
|
+
"A has no such fact before B writes it",
|
|
1806
|
+
);
|
|
1807
|
+
|
|
1808
|
+
// B persists and flushes the fact through the real persist path (its
|
|
1809
|
+
// reconcile-save advances the durable fact-hashes.txt A rebuilt from).
|
|
1810
|
+
const { persistedIds: bIds } = await orchB.persistExtraction(factResult(body), storageB, null);
|
|
1811
|
+
assert.equal(bIds.length, 1, "B persisted the fact");
|
|
1812
|
+
|
|
1813
|
+
// A is still flagged authoritative, but the durable index advanced. The
|
|
1814
|
+
// freshness gate must catch it so A's duplicate check now finds B's fact
|
|
1815
|
+
// instead of trusting a stale miss.
|
|
1816
|
+
assert.equal(
|
|
1817
|
+
await orchA.hasContentHashDedup(storageA, body),
|
|
1818
|
+
true,
|
|
1819
|
+
"A sees B's flushed fact via the freshness-gated rebuild (stale-miss without the fix)",
|
|
1820
|
+
);
|
|
1821
|
+
assert.equal(
|
|
1822
|
+
await storageA.hasFactContentHash(body),
|
|
1823
|
+
true,
|
|
1824
|
+
"A's fact-only membership also reflects B's flushed fact",
|
|
1825
|
+
);
|
|
1826
|
+
|
|
1827
|
+
// Re-extracting the same fact on A is deduped — no duplicate .md is created.
|
|
1828
|
+
const { persistedIds: aIds } = await orchA.persistExtraction(factResult(body), storageA, null);
|
|
1829
|
+
assert.equal(aIds.length, 0, "A does not re-create B's fact (would be 1 without the fix)");
|
|
1830
|
+
const all = await new StorageManager(memoryDir).readAllMemories();
|
|
1831
|
+
assert.equal(
|
|
1832
|
+
all.filter((m: MemoryFile) => m.content.includes(body)).length,
|
|
1833
|
+
1,
|
|
1834
|
+
"exactly one copy of the fact exists on disk",
|
|
1835
|
+
);
|
|
1836
|
+
|
|
1837
|
+
await rm(memoryDir, { recursive: true, force: true });
|
|
1838
|
+
});
|
|
1839
|
+
|
|
1840
|
+
test("#1909 (PR #2016): a re-extracted duplicate backfills temporal bounds onto a COLD-only active copy", async () => {
|
|
1841
|
+
// Finding: when a content-hash hit is confirmed only by the cold tier, the
|
|
1842
|
+
// dedup short-circuit fires but backfillTemporalBoundsOnDedupHit() scanned
|
|
1843
|
+
// readAllMemories() (hot) alone, so a cold active fact re-extracted with a
|
|
1844
|
+
// newly resolved invalid_at never had its cold copy patched and the corrected
|
|
1845
|
+
// temporal write was suppressed — leaving recall with stale bounds. The
|
|
1846
|
+
// helper must scan the cold tier too.
|
|
1847
|
+
const memoryDir = await mkdtemp(path.join(os.tmpdir(), "remnic-cold-backfill-"));
|
|
1848
|
+
const body =
|
|
1849
|
+
"The legacy billing pipeline drains all remaining invoices before shutdown.";
|
|
1850
|
+
const anchor = "2025-06-20T00:00:00.000Z";
|
|
1851
|
+
|
|
1852
|
+
const makeConfig = () =>
|
|
1853
|
+
parseConfig({
|
|
1854
|
+
openaiApiKey: "sk-test",
|
|
1855
|
+
memoryDir,
|
|
1856
|
+
workspaceDir: path.join(memoryDir, "workspace"),
|
|
1857
|
+
qmdEnabled: false,
|
|
1858
|
+
embeddingFallbackEnabled: false,
|
|
1859
|
+
chunkingEnabled: false,
|
|
1860
|
+
multiGraphMemoryEnabled: false,
|
|
1861
|
+
factDeduplicationEnabled: true,
|
|
1862
|
+
temporalBiTemporal: true,
|
|
1863
|
+
});
|
|
1864
|
+
|
|
1865
|
+
// Phase 1 — persist a fact with NO end bound, then demote it to cold.
|
|
1866
|
+
{
|
|
1867
|
+
const orch = new Orchestrator(makeConfig()) as unknown as OrchestratorTestSurface;
|
|
1868
|
+
const storage = await orch.getStorage("default");
|
|
1869
|
+
const { persistedIds: ids } = await orch.persistExtraction(factResult(body), storage, null, {
|
|
1870
|
+
sourceConnector: "chatgpt",
|
|
1871
|
+
validAt: anchor,
|
|
1872
|
+
});
|
|
1873
|
+
assert.equal(ids.length, 1, "fact persisted to hot in phase 1");
|
|
1874
|
+
|
|
1875
|
+
const [hotMemory] = await storage.readAllMemories();
|
|
1876
|
+
assert.ok(hotMemory, "the persisted fact is readable from the hot tier");
|
|
1877
|
+
assert.ok(
|
|
1878
|
+
!hotMemory.frontmatter.invalid_at,
|
|
1879
|
+
"the fact has no end bound before re-extraction",
|
|
1880
|
+
);
|
|
1881
|
+
const { changed } = await storage.migrateMemoryToTier(hotMemory, "cold");
|
|
1882
|
+
assert.equal(changed, true, "the fact was demoted to cold");
|
|
1883
|
+
assert.equal(
|
|
1884
|
+
(await storage.readAllMemories()).length,
|
|
1885
|
+
0,
|
|
1886
|
+
"no active copy remains in the hot tier after demotion",
|
|
1887
|
+
);
|
|
1888
|
+
}
|
|
1889
|
+
clearMemoryCache(memoryDir); // model a fresh process
|
|
1890
|
+
|
|
1891
|
+
// Phase 2 — re-extract the SAME content, now carrying a resolved end bound
|
|
1892
|
+
// ("through 2026" → invalid_at 2027-01-01). The dedup hit is confirmed only
|
|
1893
|
+
// by the cold copy, so the backfill must patch the cold copy's invalid_at.
|
|
1894
|
+
const orch2 = new Orchestrator(makeConfig()) as unknown as OrchestratorTestSurface;
|
|
1895
|
+
const storage2 = await orch2.getStorage("default");
|
|
1896
|
+
const resultWithEndBound: ExtractionResult = {
|
|
1897
|
+
facts: [{ ...makeFact(body), eventTime: "through 2026" }],
|
|
1898
|
+
entities: [],
|
|
1899
|
+
relationships: [],
|
|
1900
|
+
questions: [],
|
|
1901
|
+
profileUpdates: [],
|
|
1902
|
+
};
|
|
1903
|
+
const { persistedIds: reIds } = await orch2.persistExtraction(resultWithEndBound, storage2, null, {
|
|
1904
|
+
sourceConnector: "chatgpt",
|
|
1905
|
+
validAt: anchor,
|
|
1906
|
+
});
|
|
1907
|
+
assert.equal(
|
|
1908
|
+
reIds.length,
|
|
1909
|
+
0,
|
|
1910
|
+
"cold-only active copy suppresses the duplicate hot write",
|
|
1911
|
+
);
|
|
1912
|
+
assert.equal(
|
|
1913
|
+
(await storage2.readAllMemories()).length,
|
|
1914
|
+
0,
|
|
1915
|
+
"no duplicate hot copy was written",
|
|
1916
|
+
);
|
|
1917
|
+
|
|
1918
|
+
const cold = await storage2.readAllColdMemories();
|
|
1919
|
+
const coldCopy = cold.find((m: MemoryFile) => m.content.includes(body));
|
|
1920
|
+
assert.ok(coldCopy, "the single cold copy remains");
|
|
1921
|
+
assert.equal(
|
|
1922
|
+
coldCopy!.frontmatter.invalid_at,
|
|
1923
|
+
"2027-01-01T00:00:00.000Z",
|
|
1924
|
+
"the corrected invalid_at was backfilled onto the COLD copy (empty without the fix)",
|
|
1925
|
+
);
|
|
1926
|
+
|
|
1927
|
+
await rm(memoryDir, { recursive: true, force: true });
|
|
1928
|
+
});
|
|
1929
|
+
|
|
1930
|
+
test("#1909 (PR #2016): a promoted fact demoted to cold is not re-promoted as a duplicate hot copy after restart", async () => {
|
|
1931
|
+
// Finding: adding cold memories to the authoritative hash rebuild means the
|
|
1932
|
+
// TARGET namespace's hasFactContentHash() can hit a promoted copy that was
|
|
1933
|
+
// demoted to cold/, but the promotion confirmation scan read readAllMemories()
|
|
1934
|
+
// (hot) alone, missed the cold copy, and fell through to writeSealedMemory —
|
|
1935
|
+
// creating a duplicate hot promotion. The confirmation must scan cold too.
|
|
1936
|
+
const memoryDir = await mkdtemp(path.join(os.tmpdir(), "remnic-cold-promo-"));
|
|
1937
|
+
const body = "The compliance archive is retained for exactly seven years.";
|
|
1938
|
+
const scopePlan = makePromotionScopePlan();
|
|
1939
|
+
|
|
1940
|
+
const makeConfig = () =>
|
|
1941
|
+
parseConfig({
|
|
1942
|
+
openaiApiKey: "sk-test",
|
|
1943
|
+
memoryDir,
|
|
1944
|
+
workspaceDir: path.join(memoryDir, "workspace"),
|
|
1945
|
+
qmdEnabled: false,
|
|
1946
|
+
embeddingFallbackEnabled: false,
|
|
1947
|
+
chunkingEnabled: false,
|
|
1948
|
+
multiGraphMemoryEnabled: false,
|
|
1949
|
+
factDeduplicationEnabled: true,
|
|
1950
|
+
namespacesEnabled: true,
|
|
1951
|
+
defaultNamespace: "default",
|
|
1952
|
+
sharedNamespace: "shared",
|
|
1953
|
+
});
|
|
1954
|
+
|
|
1955
|
+
// Phase 1 — pre-write the fact into the TARGET namespace, then demote its
|
|
1956
|
+
// only active copy to cold/.
|
|
1957
|
+
{
|
|
1958
|
+
const orch = new Orchestrator(makeConfig()) as unknown as OrchestratorTestSurface;
|
|
1959
|
+
const targetStorage = await orch.getStorage(PROMOTION_TARGET_NS);
|
|
1960
|
+
await targetStorage.ensureDirectories();
|
|
1961
|
+
await targetStorage.writeMemory("fact", body, {
|
|
1962
|
+
confidence: 0.9,
|
|
1963
|
+
sourceConnector: "chatgpt",
|
|
1964
|
+
});
|
|
1965
|
+
const [hotMemory] = await targetStorage.readAllMemories();
|
|
1966
|
+
assert.ok(hotMemory, "the pre-written promotion copy is in the hot tier");
|
|
1967
|
+
const { changed } = await targetStorage.migrateMemoryToTier(hotMemory, "cold");
|
|
1968
|
+
assert.equal(changed, true, "the promoted copy was demoted to cold");
|
|
1969
|
+
assert.equal(
|
|
1970
|
+
(await targetStorage.readAllMemories()).length,
|
|
1971
|
+
0,
|
|
1972
|
+
"no active promotion copy remains in the hot tier",
|
|
1973
|
+
);
|
|
1974
|
+
assert.equal(
|
|
1975
|
+
(await targetStorage.readAllColdMemories()).length,
|
|
1976
|
+
1,
|
|
1977
|
+
"the demoted promotion copy is now in the cold tier",
|
|
1978
|
+
);
|
|
1979
|
+
}
|
|
1980
|
+
clearMemoryCache(memoryDir); // model a fresh process
|
|
1981
|
+
|
|
1982
|
+
// Phase 2 — restart: the target rebuild unions hot+cold, so hasFactContentHash
|
|
1983
|
+
// reports a hit for the cold-only promoted copy.
|
|
1984
|
+
const orch2 = new Orchestrator(makeConfig()) as unknown as OrchestratorTestSurface;
|
|
1985
|
+
const storage2 = await orch2.getStorage("default");
|
|
1986
|
+
await storage2.ensureDirectories();
|
|
1987
|
+
const targetStorage2 = await orch2.getStorage(PROMOTION_TARGET_NS);
|
|
1988
|
+
assert.equal(
|
|
1989
|
+
await targetStorage2.hasFactContentHash(body),
|
|
1990
|
+
true,
|
|
1991
|
+
"the demoted promotion copy's hash survives the hot+cold rebuild",
|
|
1992
|
+
);
|
|
1993
|
+
|
|
1994
|
+
const { persistedIds: ids } = await orch2.persistExtraction(
|
|
1995
|
+
factResult(body),
|
|
1996
|
+
storage2,
|
|
1997
|
+
null,
|
|
1998
|
+
{ sourceConnector: "chatgpt" },
|
|
1999
|
+
"default",
|
|
2000
|
+
scopePlan,
|
|
2001
|
+
);
|
|
2002
|
+
assert.equal(ids.length, 1, "source write must succeed");
|
|
2003
|
+
|
|
2004
|
+
// The promotion must be deduped against the cold copy — no duplicate hot
|
|
2005
|
+
// promotion is created (would be 1 without the fix).
|
|
2006
|
+
assert.equal(
|
|
2007
|
+
(await targetStorage2.readAllMemories()).filter((m: MemoryFile) =>
|
|
2008
|
+
m.content.includes(body),
|
|
2009
|
+
).length,
|
|
2010
|
+
0,
|
|
2011
|
+
"no duplicate hot promotion copy was written",
|
|
2012
|
+
);
|
|
2013
|
+
assert.equal(
|
|
2014
|
+
(await targetStorage2.readAllColdMemories()).filter((m: MemoryFile) =>
|
|
2015
|
+
m.content.includes(body),
|
|
2016
|
+
).length,
|
|
2017
|
+
1,
|
|
2018
|
+
"the single cold promotion copy remains the only copy",
|
|
2019
|
+
);
|
|
2020
|
+
|
|
2021
|
+
await rm(memoryDir, { recursive: true, force: true });
|
|
2022
|
+
});
|
|
2023
|
+
|
|
2024
|
+
test("#2016 thread SDyCj: a peer-advanced hash visible only after the post-miss authority rebuild is not written as a duplicate", async () => {
|
|
2025
|
+
// Distinct from the freshness-gate test in the defer suite: that pre-warms the
|
|
2026
|
+
// freshness so the FIRST hasContentHashDedup already returns a hit. Here we
|
|
2027
|
+
// isolate the exact race INSIDE a single persistExtraction — a peer advances
|
|
2028
|
+
// the durable index AFTER our MISS but BEFORE the authority check. The MISS was
|
|
2029
|
+
// read from the pre-rebuild snapshot; the authority check then rebuilds and
|
|
2030
|
+
// reports authoritative, but `exactDuplicate` stayed the stale `false`, so the
|
|
2031
|
+
// corpus-confirm block was skipped and a duplicate written. The fix re-runs the
|
|
2032
|
+
// lookup against the freshly authoritative set.
|
|
2033
|
+
const { orchestrator, storage, memoryDir } = await makeDedupOrchestrator();
|
|
2034
|
+
const body = "The release train departs every second Thursday at 1500 UTC.";
|
|
2035
|
+
|
|
2036
|
+
// Peer persisted the fact durably through the real path.
|
|
2037
|
+
const { persistedIds: first } = await orchestrator.persistExtraction(factResult(body), storage, null);
|
|
2038
|
+
assert.equal(first.length, 1, "peer's initial write persists");
|
|
2039
|
+
|
|
2040
|
+
// Model the race: the first dedup lookup in the NEXT persist reports a stale
|
|
2041
|
+
// MISS (captured before the peer flush was visible to this snapshot); the
|
|
2042
|
+
// authority check that follows rebuilds from the corpus (which contains the
|
|
2043
|
+
// fact) and reports authoritative, so the re-run lookup must catch it.
|
|
2044
|
+
const realHas = orchestrator.hasContentHashDedup.bind(orchestrator);
|
|
2045
|
+
let calls = 0;
|
|
2046
|
+
orchestrator.hasContentHashDedup = async (ts: StorageManager, content: string) => {
|
|
2047
|
+
calls += 1;
|
|
2048
|
+
if (calls === 1) return false; // stale-snapshot miss
|
|
2049
|
+
return realHas(ts, content); // now-authoritative result
|
|
2050
|
+
};
|
|
2051
|
+
|
|
2052
|
+
const { persistedIds: ids } = await orchestrator.persistExtraction(factResult(body), storage, null);
|
|
2053
|
+
assert.equal(
|
|
2054
|
+
ids.length,
|
|
2055
|
+
0,
|
|
2056
|
+
"the re-run lookup after the authority rebuild catches the peer fact (a duplicate is written without the fix)",
|
|
2057
|
+
);
|
|
2058
|
+
|
|
2059
|
+
const all = await new StorageManager(memoryDir).readAllMemories();
|
|
2060
|
+
assert.equal(
|
|
2061
|
+
all.filter((m: MemoryFile) => m.content.includes(body)).length,
|
|
2062
|
+
1,
|
|
2063
|
+
"exactly one copy of the fact exists on disk",
|
|
2064
|
+
);
|
|
2065
|
+
|
|
2066
|
+
await rm(memoryDir, { recursive: true, force: true });
|
|
2067
|
+
});
|
|
2068
|
+
|
|
2069
|
+
test("#2016 threads SDzOP/SDzOR: orchestrator archival removal clears fact-only membership (no stale hasFactContentHash)", async () => {
|
|
2070
|
+
// The orchestrator's category-agnostic removeContentHashForMemory (archival /
|
|
2071
|
+
// semantic consolidation) used to mutate only the shared index, leaving the
|
|
2072
|
+
// fact-ONLY membership set holding the removed fact's hash. hasFactContentHash
|
|
2073
|
+
// reads that set when authoritative, so it returned a stale `true` and
|
|
2074
|
+
// wearable / explicit-capture / promotion callers skipped a valid write until
|
|
2075
|
+
// the next corpus rebuild.
|
|
2076
|
+
const { orchestrator, storage } = await makeDedupOrchestrator();
|
|
2077
|
+
const body = "The nightly backup job rotates encryption keys every thirty days.";
|
|
2078
|
+
|
|
2079
|
+
// Register an active fact (shared index + fact-only set).
|
|
2080
|
+
await storage.writeMemory("fact", body, { source: "manual" });
|
|
2081
|
+
assert.equal(
|
|
2082
|
+
await storage.isFactContentHashAuthoritative(),
|
|
2083
|
+
true,
|
|
2084
|
+
"index is authoritative so hasFactContentHash reads the in-memory fact-only set",
|
|
2085
|
+
);
|
|
2086
|
+
assert.equal(await storage.hasFactContentHash(body), true, "fact is registered");
|
|
2087
|
+
|
|
2088
|
+
const [memory] = (await storage.readAllMemories()).filter((m: MemoryFile) =>
|
|
2089
|
+
m.content.includes(body),
|
|
2090
|
+
);
|
|
2091
|
+
assert.ok(memory, "the fact is on disk");
|
|
2092
|
+
|
|
2093
|
+
// Remove via the ORCHESTRATOR coordinator path (what archival uses). It does
|
|
2094
|
+
// NOT save, so the durable index fingerprint is unchanged and
|
|
2095
|
+
// hasFactContentHash still reads the in-memory fact-only set below.
|
|
2096
|
+
await orchestrator.removeContentHashForMemory(storage, memory, "fact-archival");
|
|
2097
|
+
|
|
2098
|
+
assert.equal(
|
|
2099
|
+
await storage.hasFactContentHash(body),
|
|
2100
|
+
false,
|
|
2101
|
+
"the removed fact must not linger in the fact-only membership (stale true without the fix)",
|
|
2102
|
+
);
|
|
2103
|
+
});
|