@remnic/core 9.7.14 → 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 +88 -74
- package/dist/access-audit.js +2 -2
- package/dist/access-boundary.d.ts +35 -17
- package/dist/access-boundary.js +89 -77
- package/dist/access-citation.d.ts +28 -0
- package/dist/access-citation.js +9 -0
- package/dist/access-cli.js +197 -181
- 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 +103 -90
- package/dist/access-identity-continuity-surface.d.ts +13 -8
- package/dist/access-identity-continuity-surface.js +88 -74
- package/dist/access-lcm-surface.d.ts +16 -9
- package/dist/access-lcm-surface.js +88 -74
- package/dist/access-mcp.d.ts +23 -16
- package/dist/access-mcp.js +99 -87
- 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 +88 -74
- package/dist/access-operations-batch.js +95 -83
- package/dist/access-operations.d.ts +26 -19
- package/dist/access-operations.js +97 -85
- 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 +88 -74
- package/dist/access-schema.d.ts +34 -34
- package/dist/access-schema.js +8 -6
- package/dist/{access-service-CPhSnLOH.d.ts → access-service-DEL4qCrc.d.ts} +25 -32
- package/dist/access-service.d.ts +13 -6
- package/dist/access-service.js +92 -75
- 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 +37 -27
- 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/{capsule-merge-Z2HUO6IK.js → capsule-merge-5KKJQBWQ.js} +4 -4
- package/dist/{catalog-uX9sT9ws.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 +46 -37
- 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-ASKEM4AR.js → chunk-2EJOEICD.js} +1823 -1483
- package/dist/chunk-2EJOEICD.js.map +1 -0
- package/dist/{chunk-L2B3EE7B.js → chunk-2QMKMVHH.js} +2 -2
- package/dist/{chunk-6CALL5VN.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-5S7Z5SHA.js → chunk-34RC5RT7.js} +2 -2
- package/dist/{chunk-KGWXHWAH.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-ZIDSD6VC.js → chunk-3Y224NYC.js} +3 -3
- package/dist/{chunk-RBQLJSJ7.js → chunk-44XZ75OQ.js} +46 -10
- package/dist/chunk-44XZ75OQ.js.map +1 -0
- package/dist/{chunk-VFSP73LP.js → chunk-45GF7Y3V.js} +2 -2
- package/dist/{chunk-7ICYQUOL.js → chunk-4BJQODZF.js} +2514 -1526
- package/dist/chunk-4BJQODZF.js.map +1 -0
- package/dist/{chunk-JMBVCAR4.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-2E6G7FCE.js → chunk-5Q7EVV4X.js} +7 -7
- package/dist/{chunk-LC7OODWZ.js → chunk-5SB7SC2G.js} +80 -49
- package/dist/chunk-5SB7SC2G.js.map +1 -0
- package/dist/{chunk-VZ5OJE4X.js → chunk-5VQJBH2G.js} +2 -2
- 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-4LEF6XWJ.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-F7AU34FC.js → chunk-76GLCM57.js} +160 -127
- 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-TXVD7PLT.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-OKUEZOGW.js → chunk-AITCS2RC.js} +2 -2
- package/dist/{chunk-OC35KQIN.js → chunk-AJ7QCSIS.js} +4 -4
- 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-N3JDIRBW.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-FBNWOMCE.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-DDGV7NAX.js → chunk-CQMEM5CC.js} +2 -2
- package/dist/{chunk-A7B4RMQG.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-IFJ23BHU.js → chunk-EEC7UJPX.js} +112 -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-7MW3CVLD.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-7LFXDGLH.js → chunk-F7GAIWFA.js} +20 -20
- package/dist/{chunk-IKSELOIV.js → chunk-FFPJINJE.js} +7 -7
- 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-V53GJUSL.js → chunk-FUTF72MJ.js} +15 -15
- package/dist/chunk-GCASGYIO.js +71 -0
- package/dist/chunk-GCASGYIO.js.map +1 -0
- package/dist/{chunk-5UNWK7KU.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-HLTAPHZZ.js → chunk-HNZKVLAN.js} +2 -2
- package/dist/{chunk-5EZXHU36.js → chunk-HYQJQGD3.js} +2 -2
- package/dist/{chunk-ANADNYWK.js → chunk-IDZFBSHN.js} +8 -6
- package/dist/{chunk-ANADNYWK.js.map → chunk-IDZFBSHN.js.map} +1 -1
- package/dist/{chunk-XVLEGMJI.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-ND5M64JJ.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-HG32URJJ.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-MKDRQAA3.js → chunk-KAVBJSBQ.js} +4 -4
- package/dist/{chunk-EEARRVZ2.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-OBMR4VIX.js → chunk-L2DPF4DF.js} +186 -15
- package/dist/chunk-L2DPF4DF.js.map +1 -0
- 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-NUWYOKS6.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-67ZCJLJY.js → chunk-MYFQ3ED4.js} +8 -8
- 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-AD6AMF2N.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-AHLPZ2SF.js → chunk-QJAQNXWI.js} +44 -6
- package/dist/chunk-QJAQNXWI.js.map +1 -0
- package/dist/{chunk-3D6AFGQR.js → chunk-QVT3E4CE.js} +7 -7
- package/dist/{chunk-VYOFVVNK.js → chunk-R26ZMXR2.js} +24 -24
- package/dist/{chunk-VYOFVVNK.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-UTRJFDP2.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-GMSDQUCR.js → chunk-TDTXKOAJ.js} +6475 -5748
- 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-PK5XBDA6.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-7XGSIUP5.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-W5NKDU24.js → chunk-WON4APQY.js} +4 -4
- package/dist/{chunk-NKCPUHCI.js → chunk-WZFAPHIK.js} +6 -6
- package/dist/{chunk-KQPJLK3X.js → chunk-XL24QQ63.js} +2 -2
- package/dist/{chunk-X47LWMKA.js → chunk-XMMESCPD.js} +26 -1
- package/dist/chunk-XMMESCPD.js.map +1 -0
- package/dist/{chunk-4B4MUS5D.js → chunk-YROFNEG5.js} +7 -7
- package/dist/{chunk-WU46YPY6.js → chunk-YV32N67O.js} +1 -1
- package/dist/chunk-YV32N67O.js.map +1 -0
- package/dist/{chunk-B3F4CWQR.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-_Y2Bmt6A.d.ts → cli-C_pqqTCw.d.ts} +28 -12
- package/dist/cli.d.ts +16 -9
- package/dist/cli.js +145 -130
- package/dist/compounding/engine.d.ts +5 -2
- package/dist/compounding/engine.js +39 -29
- 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 +39 -29
- 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 +44 -35
- package/dist/consolidation-provenance-check.d.ts +5 -2
- package/dist/consolidation-provenance-check.js +4 -4
- package/dist/consolidation-undo.d.ts +5 -2
- package/dist/consolidation-undo.js +3 -3
- 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 +38 -29
- 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-FO7AM4UR.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 +57 -10
- 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 +101 -92
- package/dist/index.js +290 -242
- 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 +38 -28
- 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 +40 -28
- package/dist/maintenance/rebuild-memory-projection.js +41 -31
- 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 +46 -2
- package/dist/memory-cache.js +7 -1
- package/dist/memory-corpus-version.js +2 -2
- 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 +54 -45
- 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 +25 -21
- package/dist/namespaces/storage.d.ts +24 -3
- package/dist/namespaces/storage.js +38 -30
- 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 +73 -58
- 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 +47 -34
- package/dist/{orchestrator-BbZ_dQSU.d.ts → orchestrator-CQJ8U7En.d.ts} +85 -79
- package/dist/orchestrator.d.ts +13 -8
- package/dist/orchestrator.js +197 -180
- package/dist/page-versioning.js +3 -3
- package/dist/patterns-cli.d.ts +3 -1
- package/dist/{pipeline-FRMDb1fn.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-xJRrHFbA.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 +8 -4
- 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 +24 -24
- 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 +19 -17
- package/dist/search/index.d.ts +3 -1
- package/dist/search/index.js +23 -21
- 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 +27 -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-fvdZRa5Z.d.ts → semantic-consolidation-DILZEQq3.d.ts} +1 -1
- package/dist/semantic-consolidation.d.ts +4 -2
- package/dist/semantic-consolidation.js +41 -32
- package/dist/semantic-rule-promotion.js +37 -27
- package/dist/semantic-rule-verifier.d.ts +3 -1
- package/dist/semantic-rule-verifier.js +38 -28
- 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 +3 -1
- package/dist/shared-context/manager.js +3 -3
- package/dist/signal.d.ts +3 -1
- package/dist/{storage-CrvzaM9C.d.ts → storage-NIyKYD8g.d.ts} +505 -110
- package/dist/storage-guards.js +2 -2
- package/dist/storage.d.ts +6 -3
- package/dist/storage.js +41 -29
- 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 +6 -6
- package/dist/transfer/types.d.ts +24 -24
- 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-Bl4BWDM8.d.ts → types-D_fG2ix8.d.ts} +49 -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 +39 -29
- 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 +122 -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/graph.test.ts +137 -1
- package/src/graph.ts +274 -17
- 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-cache.ts +70 -0
- 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 +45 -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 +581 -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 +172 -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 +166 -150
- 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.test.ts +22 -0
- package/src/qmd.ts +60 -48
- 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 +24 -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 +942 -903
- 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 +66 -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-7ICYQUOL.js.map +0 -1
- package/dist/chunk-AD6AMF2N.js.map +0 -1
- package/dist/chunk-AHLPZ2SF.js.map +0 -1
- package/dist/chunk-ASKEM4AR.js.map +0 -1
- package/dist/chunk-B3F4CWQR.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-E5BK6CGV.js +0 -335
- package/dist/chunk-E5BK6CGV.js.map +0 -1
- package/dist/chunk-EEARRVZ2.js.map +0 -1
- package/dist/chunk-F7AU34FC.js.map +0 -1
- package/dist/chunk-FBNWOMCE.js.map +0 -1
- package/dist/chunk-FTYDWKO6.js.map +0 -1
- package/dist/chunk-GMSDQUCR.js.map +0 -1
- package/dist/chunk-GWTFLF2P.js.map +0 -1
- package/dist/chunk-IFJ23BHU.js.map +0 -1
- package/dist/chunk-IKQTFQMI.js.map +0 -1
- package/dist/chunk-J2HSAU72.js.map +0 -1
- package/dist/chunk-JMBVCAR4.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-ND5M64JJ.js.map +0 -1
- package/dist/chunk-NUWYOKS6.js.map +0 -1
- package/dist/chunk-NWTGD4AN.js.map +0 -1
- package/dist/chunk-OBMR4VIX.js.map +0 -1
- package/dist/chunk-ODPLEWB6.js.map +0 -1
- package/dist/chunk-PHK3HARR.js +0 -32
- package/dist/chunk-PHK3HARR.js.map +0 -1
- package/dist/chunk-RANHFI3P.js +0 -8
- package/dist/chunk-RANHFI3P.js.map +0 -1
- package/dist/chunk-RBQLJSJ7.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-TXLGHDHS.js.map +0 -1
- package/dist/chunk-UN3CH3CO.js +0 -118
- package/dist/chunk-UN3CH3CO.js.map +0 -1
- package/dist/chunk-VSMRDBFT.js +0 -17
- package/dist/chunk-VSMRDBFT.js.map +0 -1
- package/dist/chunk-WU46YPY6.js.map +0 -1
- package/dist/chunk-X47LWMKA.js.map +0 -1
- package/dist/chunk-X64YLC24.js.map +0 -1
- package/dist/chunk-XVLEGMJI.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-FO7AM4UR.js.map → access-recall-response.js.map} +0 -0
- /package/dist/{resolution-VH4NGENZ.js.map → bounded-jsonl-state.js.map} +0 -0
- /package/dist/{capsule-merge-Z2HUO6IK.js.map → capsule-merge-5KKJQBWQ.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-6CALL5VN.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-5S7Z5SHA.js.map → chunk-34RC5RT7.js.map} +0 -0
- /package/dist/{chunk-KGWXHWAH.js.map → chunk-3QAH2S33.js.map} +0 -0
- /package/dist/{chunk-ZIDSD6VC.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-2E6G7FCE.js.map → chunk-5Q7EVV4X.js.map} +0 -0
- /package/dist/{chunk-VZ5OJE4X.js.map → chunk-5VQJBH2G.js.map} +0 -0
- /package/dist/{chunk-PUBTHW5O.js.map → chunk-6DRPPQEJ.js.map} +0 -0
- /package/dist/{chunk-4LEF6XWJ.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-TXVD7PLT.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-OKUEZOGW.js.map → chunk-AITCS2RC.js.map} +0 -0
- /package/dist/{chunk-OC35KQIN.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-N3JDIRBW.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-DDGV7NAX.js.map → chunk-CQMEM5CC.js.map} +0 -0
- /package/dist/{chunk-A7B4RMQG.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-7MW3CVLD.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-7LFXDGLH.js.map → chunk-F7GAIWFA.js.map} +0 -0
- /package/dist/{chunk-IKSELOIV.js.map → chunk-FFPJINJE.js.map} +0 -0
- /package/dist/{chunk-V53GJUSL.js.map → chunk-FUTF72MJ.js.map} +0 -0
- /package/dist/{chunk-5UNWK7KU.js.map → chunk-GFPZS3F5.js.map} +0 -0
- /package/dist/{chunk-G6NKALRN.js.map → chunk-H4WG3UQZ.js.map} +0 -0
- /package/dist/{chunk-HLTAPHZZ.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-HG32URJJ.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-MKDRQAA3.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-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-67ZCJLJY.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-UTRJFDP2.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-PK5XBDA6.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-7XGSIUP5.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-W5NKDU24.js.map → chunk-WON4APQY.js.map} +0 -0
- /package/dist/{chunk-NKCPUHCI.js.map → chunk-WZFAPHIK.js.map} +0 -0
- /package/dist/{chunk-KQPJLK3X.js.map → chunk-XL24QQ63.js.map} +0 -0
- /package/dist/{chunk-4B4MUS5D.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
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import { mkdtemp, readdir, rm, symlink, utimes, writeFile } from "node:fs/promises";
|
|
3
|
+
import { tmpdir } from "node:os";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import test from "node:test";
|
|
6
|
+
|
|
7
|
+
import { WriteQuarantineStore } from "./write-quarantine.js";
|
|
8
|
+
|
|
9
|
+
async function withTempDir(fn: (dir: string) => Promise<void>): Promise<void> {
|
|
10
|
+
const dir = await mkdtemp(path.join(tmpdir(), "remnic-quarantine-"));
|
|
11
|
+
try {
|
|
12
|
+
await fn(dir);
|
|
13
|
+
} finally {
|
|
14
|
+
await rm(dir, { recursive: true, force: true });
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function isInside(root: string, child: string): boolean {
|
|
19
|
+
const rel = path.relative(path.resolve(root), path.resolve(child));
|
|
20
|
+
return rel !== "" && !rel.startsWith("..") && !path.isAbsolute(rel);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
test("quarantine writes a record under state/quarantine and list() returns it", async () => {
|
|
24
|
+
await withTempDir(async (dir) => {
|
|
25
|
+
const store = new WriteQuarantineStore(dir);
|
|
26
|
+
const written = await store.quarantine({
|
|
27
|
+
operation: "memory_store",
|
|
28
|
+
principal: "alice",
|
|
29
|
+
attemptedNamespace: "victim-secret",
|
|
30
|
+
payload: { content: "hello" },
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
assert.ok(isInside(path.join(dir, "state", "quarantine"), written));
|
|
34
|
+
|
|
35
|
+
const records = await store.list();
|
|
36
|
+
assert.equal(records.length, 1);
|
|
37
|
+
assert.equal(records[0]?.operation, "memory_store");
|
|
38
|
+
assert.equal(records[0]?.principal, "alice");
|
|
39
|
+
assert.equal(records[0]?.attemptedNamespace, "victim-secret");
|
|
40
|
+
assert.deepEqual(records[0]?.payload, { content: "hello" });
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
test("principal with path-traversal characters stays contained in the root", async () => {
|
|
45
|
+
await withTempDir(async (dir) => {
|
|
46
|
+
const store = new WriteQuarantineStore(dir);
|
|
47
|
+
const written = await store.quarantine({
|
|
48
|
+
operation: "observe",
|
|
49
|
+
principal: "../../etc",
|
|
50
|
+
attemptedNamespace: "default",
|
|
51
|
+
payload: { messages: [] },
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
assert.ok(isInside(store.root, written), `escaped root: ${written}`);
|
|
55
|
+
|
|
56
|
+
// The principal is preserved verbatim in the record even though the
|
|
57
|
+
// on-disk segment is encoded.
|
|
58
|
+
const records = await store.list();
|
|
59
|
+
assert.equal(records.length, 1);
|
|
60
|
+
assert.equal(records[0]?.principal, "../../etc");
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
test("count reflects entries across principals", async () => {
|
|
65
|
+
await withTempDir(async (dir) => {
|
|
66
|
+
const store = new WriteQuarantineStore(dir);
|
|
67
|
+
await store.quarantine({ operation: "memory_store", principal: "alice", attemptedNamespace: "ns", payload: 1 });
|
|
68
|
+
await store.quarantine({ operation: "observe", principal: "bob", attemptedNamespace: "ns", payload: 2 });
|
|
69
|
+
assert.equal(await store.count(), 2);
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
test("maxEntriesPerPrincipal caps a principal bucket", async () => {
|
|
74
|
+
await withTempDir(async (dir) => {
|
|
75
|
+
const store = new WriteQuarantineStore(dir, { maxEntriesPerPrincipal: 2, maxAgeMs: 60 * 60 * 1000 });
|
|
76
|
+
for (let i = 0; i < 5; i++) {
|
|
77
|
+
await store.quarantine({ operation: "memory_store", principal: "alice", attemptedNamespace: "ns", payload: i });
|
|
78
|
+
await store.quarantine({ operation: "memory_store", principal: "bob", attemptedNamespace: "ns", payload: i });
|
|
79
|
+
}
|
|
80
|
+
const records = await store.list();
|
|
81
|
+
// The cap is per principal, not global: two survive for each.
|
|
82
|
+
assert.equal(records.length, 4);
|
|
83
|
+
assert.equal(records.filter((r) => r.principal === "alice").length, 2);
|
|
84
|
+
assert.equal(records.filter((r) => r.principal === "bob").length, 2);
|
|
85
|
+
});
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
test("age cap drops records older than maxAgeMs", async () => {
|
|
89
|
+
await withTempDir(async (dir) => {
|
|
90
|
+
const store = new WriteQuarantineStore(dir, { maxEntriesPerPrincipal: 1000, maxAgeMs: 1_000 });
|
|
91
|
+
const oldPath = await store.quarantine({
|
|
92
|
+
operation: "memory_store",
|
|
93
|
+
principal: "alice",
|
|
94
|
+
attemptedNamespace: "ns",
|
|
95
|
+
payload: "old",
|
|
96
|
+
});
|
|
97
|
+
// Backdate the first record an hour into the past.
|
|
98
|
+
const hourAgo = new Date(Date.now() - 60 * 60 * 1000);
|
|
99
|
+
await utimes(oldPath, hourAgo, hourAgo);
|
|
100
|
+
|
|
101
|
+
// A fresh write into the same bucket triggers the prune.
|
|
102
|
+
await store.quarantine({
|
|
103
|
+
operation: "memory_store",
|
|
104
|
+
principal: "alice",
|
|
105
|
+
attemptedNamespace: "ns",
|
|
106
|
+
payload: "new",
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
const records = await store.list();
|
|
110
|
+
assert.equal(records.length, 1);
|
|
111
|
+
assert.equal(records[0]?.payload, "new");
|
|
112
|
+
});
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
test("list() skips malformed and wrong-shape JSON files", async () => {
|
|
116
|
+
await withTempDir(async (dir) => {
|
|
117
|
+
const store = new WriteQuarantineStore(dir);
|
|
118
|
+
await store.quarantine({ operation: "memory_store", principal: "alice", attemptedNamespace: "ns", payload: "ok" });
|
|
119
|
+
|
|
120
|
+
// Drop a corrupt file and a valid-JSON-but-wrong-shape file beside the good
|
|
121
|
+
// record in the principal bucket.
|
|
122
|
+
const quarantineRoot = path.join(dir, "state", "quarantine");
|
|
123
|
+
const [principalName] = await readdir(quarantineRoot);
|
|
124
|
+
const principalDir = path.join(quarantineRoot, principalName ?? "");
|
|
125
|
+
await writeFile(path.join(principalDir, "corrupt.json"), "{ not json", "utf8");
|
|
126
|
+
await writeFile(path.join(principalDir, "wrong-shape.json"), "{}", "utf8");
|
|
127
|
+
|
|
128
|
+
const records = await store.list();
|
|
129
|
+
assert.equal(records.length, 1);
|
|
130
|
+
assert.equal(records[0]?.payload, "ok");
|
|
131
|
+
assert.equal(await store.count(), 1);
|
|
132
|
+
});
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
test("constructor rejects invalid retention caps", () => {
|
|
136
|
+
assert.throws(() => new WriteQuarantineStore("/tmp/x", { maxEntriesPerPrincipal: 0, maxAgeMs: 1000 }));
|
|
137
|
+
assert.throws(() => new WriteQuarantineStore("/tmp/x", { maxEntriesPerPrincipal: 1.5, maxAgeMs: 1000 }));
|
|
138
|
+
assert.throws(() => new WriteQuarantineStore("/tmp/x", { maxEntriesPerPrincipal: 10, maxAgeMs: Number.NaN }));
|
|
139
|
+
assert.throws(() => new WriteQuarantineStore("/tmp/x", { maxEntriesPerPrincipal: 10, maxAgeMs: 0 }));
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
test("expired records are dropped on read even without a follow-up write", async () => {
|
|
143
|
+
await withTempDir(async (dir) => {
|
|
144
|
+
const store = new WriteQuarantineStore(dir, { maxEntriesPerPrincipal: 1000, maxAgeMs: 1_000 });
|
|
145
|
+
const only = await store.quarantine({
|
|
146
|
+
operation: "memory_store",
|
|
147
|
+
principal: "alice",
|
|
148
|
+
attemptedNamespace: "ns",
|
|
149
|
+
payload: "old",
|
|
150
|
+
});
|
|
151
|
+
const hourAgo = new Date(Date.now() - 60 * 60 * 1000);
|
|
152
|
+
await utimes(only, hourAgo, hourAgo);
|
|
153
|
+
|
|
154
|
+
// No further write; list()/count() must expire it and delete the file.
|
|
155
|
+
assert.equal(await store.count(), 0);
|
|
156
|
+
assert.deepEqual(await store.list(), []);
|
|
157
|
+
});
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
test("a symlinked state directory is not followed outside the memory dir", async () => {
|
|
161
|
+
await withTempDir(async (dir) => {
|
|
162
|
+
const external = await mkdtemp(path.join(tmpdir(), "remnic-quarantine-ext-"));
|
|
163
|
+
try {
|
|
164
|
+
// Replace <memoryDir>/state with a symlink to an external directory.
|
|
165
|
+
await symlink(external, path.join(dir, "state"), "dir");
|
|
166
|
+
const store = new WriteQuarantineStore(dir);
|
|
167
|
+
|
|
168
|
+
await assert.rejects(
|
|
169
|
+
store.quarantine({ operation: "memory_store", principal: "alice", attemptedNamespace: "ns", payload: "x" })
|
|
170
|
+
);
|
|
171
|
+
// Nothing was written into the external directory.
|
|
172
|
+
assert.deepEqual(await readdir(external), []);
|
|
173
|
+
} finally {
|
|
174
|
+
await rm(external, { recursive: true, force: true });
|
|
175
|
+
}
|
|
176
|
+
});
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
test("list() surfaces a symlink-escaping root instead of reporting empty", async () => {
|
|
180
|
+
await withTempDir(async (dir) => {
|
|
181
|
+
const external = await mkdtemp(path.join(tmpdir(), "remnic-quarantine-ext-"));
|
|
182
|
+
try {
|
|
183
|
+
// A symlinked `state` that escapes the memory dir must not read back as
|
|
184
|
+
// an empty (all-clear) store — the inspection failure has to surface.
|
|
185
|
+
await symlink(external, path.join(dir, "state"), "dir");
|
|
186
|
+
const store = new WriteQuarantineStore(dir);
|
|
187
|
+
await assert.rejects(store.list());
|
|
188
|
+
await assert.rejects(store.count());
|
|
189
|
+
} finally {
|
|
190
|
+
await rm(external, { recursive: true, force: true });
|
|
191
|
+
}
|
|
192
|
+
});
|
|
193
|
+
});
|
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dead-letter quarantine for writes rejected by the namespace write-ACL
|
|
3
|
+
* (issue #1888).
|
|
4
|
+
*
|
|
5
|
+
* When `observe` / `memory_store` / `suggestion_submit` fail namespace
|
|
6
|
+
* authorization, the payload used to be dropped with no recoverable copy: a
|
|
7
|
+
* client configured with a namespace that matches no policy (and is not the
|
|
8
|
+
* default/shared namespace) had every write in the window silently destroyed.
|
|
9
|
+
* The ACL rejection is CORRECT and stays fail-closed; this store only
|
|
10
|
+
* preserves the payload so it can be replayed after the config is fixed,
|
|
11
|
+
* following the AGENTS.md "reject loudly but never destroy state" posture
|
|
12
|
+
* (patterns 14/42).
|
|
13
|
+
*
|
|
14
|
+
* Records live OUTSIDE any memory namespace at
|
|
15
|
+
* `<memoryDir>/state/quarantine/<principalSafe>/<timestamp>-<id>.json`, keyed
|
|
16
|
+
* by the encoded principal, and are bounded per principal by count and age.
|
|
17
|
+
* Every path is resolved from `memoryDir` through `resolveSafeStoragePath`, so
|
|
18
|
+
* a symlinked `state`/`quarantine` cannot redirect writes outside the memory
|
|
19
|
+
* directory. The store never re-routes a payload into a writable namespace.
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
import { randomUUID } from "node:crypto";
|
|
23
|
+
import type { Dirent } from "node:fs";
|
|
24
|
+
import { lstat, mkdir, readFile, readdir, rename, rm, stat, writeFile } from "node:fs/promises";
|
|
25
|
+
import path from "node:path";
|
|
26
|
+
|
|
27
|
+
import { encodeStoragePathSegment, resolveSafeStoragePath } from "./storage-paths.js";
|
|
28
|
+
|
|
29
|
+
export type QuarantineOperation = "observe" | "memory_store" | "suggestion_submit";
|
|
30
|
+
|
|
31
|
+
export interface QuarantineEntryInput {
|
|
32
|
+
operation: QuarantineOperation;
|
|
33
|
+
/** Authenticated principal the write was attributed to, if any. */
|
|
34
|
+
principal: string | undefined;
|
|
35
|
+
/** Namespace the caller attempted to write and was denied. */
|
|
36
|
+
attemptedNamespace: string;
|
|
37
|
+
/** The original request payload, preserved verbatim for replay. */
|
|
38
|
+
payload: unknown;
|
|
39
|
+
/** ISO timestamp; defaults to now. Injectable for deterministic tests. */
|
|
40
|
+
timestamp?: string;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export interface QuarantinedRecord {
|
|
44
|
+
operation: QuarantineOperation;
|
|
45
|
+
principal: string | null;
|
|
46
|
+
attemptedNamespace: string;
|
|
47
|
+
timestamp: string;
|
|
48
|
+
payload: unknown;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export interface QuarantineCaps {
|
|
52
|
+
/** Keep at most this many records per principal (newest win). */
|
|
53
|
+
maxEntriesPerPrincipal: number;
|
|
54
|
+
/** Drop records older than this many milliseconds. */
|
|
55
|
+
maxAgeMs: number;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export const DEFAULT_QUARANTINE_CAPS: QuarantineCaps = {
|
|
59
|
+
maxEntriesPerPrincipal: 500,
|
|
60
|
+
maxAgeMs: 14 * 24 * 60 * 60 * 1000,
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
const QUARANTINE_DIRNAME = "quarantine";
|
|
64
|
+
|
|
65
|
+
function validateCaps(caps: QuarantineCaps): void {
|
|
66
|
+
if (!Number.isInteger(caps.maxEntriesPerPrincipal) || caps.maxEntriesPerPrincipal < 1) {
|
|
67
|
+
throw new Error(
|
|
68
|
+
`WriteQuarantineStore: maxEntriesPerPrincipal must be a positive integer (got ${caps.maxEntriesPerPrincipal})`
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
if (!Number.isFinite(caps.maxAgeMs) || caps.maxAgeMs <= 0) {
|
|
72
|
+
throw new Error(
|
|
73
|
+
`WriteQuarantineStore: maxAgeMs must be a positive finite number of milliseconds (got ${caps.maxAgeMs})`
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function isQuarantinedRecord(value: unknown): value is QuarantinedRecord {
|
|
79
|
+
if (!value || typeof value !== "object") return false;
|
|
80
|
+
const record = value as Record<string, unknown>;
|
|
81
|
+
return (
|
|
82
|
+
(record.operation === "observe" ||
|
|
83
|
+
record.operation === "memory_store" ||
|
|
84
|
+
record.operation === "suggestion_submit") &&
|
|
85
|
+
(record.principal === null || typeof record.principal === "string") &&
|
|
86
|
+
typeof record.attemptedNamespace === "string" &&
|
|
87
|
+
typeof record.timestamp === "string" &&
|
|
88
|
+
"payload" in record
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export class WriteQuarantineStore {
|
|
93
|
+
private readonly memoryDir: string;
|
|
94
|
+
|
|
95
|
+
constructor(
|
|
96
|
+
memoryDir: string,
|
|
97
|
+
private readonly caps: QuarantineCaps = DEFAULT_QUARANTINE_CAPS
|
|
98
|
+
) {
|
|
99
|
+
validateCaps(caps);
|
|
100
|
+
this.memoryDir = path.resolve(memoryDir);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/** Absolute quarantine root (`<memoryDir>/state/quarantine`). */
|
|
104
|
+
get root(): string {
|
|
105
|
+
return path.join(this.memoryDir, "state", QUARANTINE_DIRNAME);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Resolve a path under the quarantine root, checked for containment and
|
|
110
|
+
* symlink traversal from `memoryDir` down — so a symlinked `state` or
|
|
111
|
+
* `quarantine` component that escapes the memory directory is rejected.
|
|
112
|
+
*/
|
|
113
|
+
private safeDir(...segments: string[]): Promise<string> {
|
|
114
|
+
return resolveSafeStoragePath(this.memoryDir, "state", QUARANTINE_DIRNAME, ...segments);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Park a rejected write. Returns the absolute path of the record written.
|
|
119
|
+
* Prunes the principal's bucket to the configured caps afterward.
|
|
120
|
+
*/
|
|
121
|
+
async quarantine(input: QuarantineEntryInput): Promise<string> {
|
|
122
|
+
const timestamp = input.timestamp ?? new Date().toISOString();
|
|
123
|
+
const record: QuarantinedRecord = {
|
|
124
|
+
operation: input.operation,
|
|
125
|
+
principal: input.principal ?? null,
|
|
126
|
+
attemptedNamespace: input.attemptedNamespace,
|
|
127
|
+
timestamp,
|
|
128
|
+
payload: input.payload,
|
|
129
|
+
};
|
|
130
|
+
const principalSegment = encodeStoragePathSegment(input.principal ?? "unknown");
|
|
131
|
+
const fileName = `${timestamp.replace(/[:.]/g, "-")}-${randomUUID().slice(0, 8)}.json`;
|
|
132
|
+
|
|
133
|
+
const dir = await this.safeDir(principalSegment);
|
|
134
|
+
await mkdir(dir, { recursive: true });
|
|
135
|
+
// The temp write uses the exclusive `wx` flag so a pre-planted symlink at
|
|
136
|
+
// the temp name can never be followed (O_EXCL); rename is atomic (rule 42).
|
|
137
|
+
const finalPath = await this.safeDir(principalSegment, fileName);
|
|
138
|
+
const tmpPath = await this.safeDir(principalSegment, `${fileName}.tmp`);
|
|
139
|
+
await writeFile(tmpPath, JSON.stringify(record, null, 2), { encoding: "utf8", flag: "wx" });
|
|
140
|
+
await rename(tmpPath, finalPath);
|
|
141
|
+
|
|
142
|
+
await this.pruneDir(dir);
|
|
143
|
+
return finalPath;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* All valid, non-expired quarantined records across every principal, oldest
|
|
148
|
+
* first. Expired records are deleted on read (lazy GC), so an inactive
|
|
149
|
+
* principal bucket with no follow-up write is still bounded by age.
|
|
150
|
+
*/
|
|
151
|
+
async list(): Promise<QuarantinedRecord[]> {
|
|
152
|
+
const now = Date.now();
|
|
153
|
+
const records: QuarantinedRecord[] = [];
|
|
154
|
+
for (const dir of await this.principalDirs()) {
|
|
155
|
+
for (const file of await this.entryFiles(dir)) {
|
|
156
|
+
const info = await stat(file).catch(() => null);
|
|
157
|
+
if (!info) continue;
|
|
158
|
+
if (now - info.mtimeMs > this.caps.maxAgeMs) {
|
|
159
|
+
await rm(file, { force: true }).catch(() => {});
|
|
160
|
+
continue;
|
|
161
|
+
}
|
|
162
|
+
let parsed: unknown;
|
|
163
|
+
try {
|
|
164
|
+
parsed = JSON.parse(await readFile(file, "utf8"));
|
|
165
|
+
} catch {
|
|
166
|
+
continue;
|
|
167
|
+
}
|
|
168
|
+
if (isQuarantinedRecord(parsed)) records.push(parsed);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
records.sort((a, b) => (a.timestamp < b.timestamp ? -1 : a.timestamp > b.timestamp ? 1 : 0));
|
|
172
|
+
return records;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/** Total valid, non-expired quarantined record count across every principal. */
|
|
176
|
+
async count(): Promise<number> {
|
|
177
|
+
return (await this.list()).length;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/** lstat (no symlink follow): true only for a real directory. */
|
|
181
|
+
private async isRealDir(dir: string): Promise<boolean> {
|
|
182
|
+
const info = await lstat(dir).catch(() => null);
|
|
183
|
+
return info?.isDirectory() === true;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
private async principalDirs(): Promise<string[]> {
|
|
187
|
+
// safeDir() rejects a symlinked/containment-escaping root — let that
|
|
188
|
+
// propagate instead of masquerading as an empty store.
|
|
189
|
+
const root = await this.safeDir();
|
|
190
|
+
if (!(await this.isRealDir(root))) return [];
|
|
191
|
+
let entries: Dirent[];
|
|
192
|
+
try {
|
|
193
|
+
entries = await readdir(root, { withFileTypes: true });
|
|
194
|
+
} catch (err) {
|
|
195
|
+
// Root vanished mid-scan → legitimately empty. Any other failure
|
|
196
|
+
// (permissions, IO) is a genuine inspection failure and must surface.
|
|
197
|
+
if ((err as NodeJS.ErrnoException)?.code === "ENOENT") return [];
|
|
198
|
+
throw err;
|
|
199
|
+
}
|
|
200
|
+
return entries
|
|
201
|
+
.filter((entry) => entry.isDirectory())
|
|
202
|
+
.map((entry) => path.join(root, entry.name))
|
|
203
|
+
.sort();
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
private async entryFiles(dir: string): Promise<string[]> {
|
|
207
|
+
if (!(await this.isRealDir(dir))) return [];
|
|
208
|
+
let entries: Dirent[];
|
|
209
|
+
try {
|
|
210
|
+
entries = await readdir(dir, { withFileTypes: true });
|
|
211
|
+
} catch (err) {
|
|
212
|
+
if ((err as NodeJS.ErrnoException)?.code === "ENOENT") return [];
|
|
213
|
+
throw err;
|
|
214
|
+
}
|
|
215
|
+
return entries
|
|
216
|
+
.filter((entry) => entry.isFile() && entry.name.endsWith(".json"))
|
|
217
|
+
.map((entry) => path.join(dir, entry.name))
|
|
218
|
+
.sort();
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
private async pruneDir(dir: string): Promise<void> {
|
|
222
|
+
const files = await this.entryFiles(dir);
|
|
223
|
+
const now = Date.now();
|
|
224
|
+
const survivors: Array<{ path: string; mtimeMs: number }> = [];
|
|
225
|
+
for (const file of files) {
|
|
226
|
+
const info = await stat(file).catch(() => null);
|
|
227
|
+
if (!info) continue;
|
|
228
|
+
if (now - info.mtimeMs > this.caps.maxAgeMs) {
|
|
229
|
+
await rm(file, { force: true }).catch(() => {});
|
|
230
|
+
continue;
|
|
231
|
+
}
|
|
232
|
+
survivors.push({ path: file, mtimeMs: info.mtimeMs });
|
|
233
|
+
}
|
|
234
|
+
const surplus = survivors.length - this.caps.maxEntriesPerPrincipal;
|
|
235
|
+
if (surplus <= 0) return;
|
|
236
|
+
survivors.sort((a, b) => a.mtimeMs - b.mtimeMs || (a.path < b.path ? -1 : a.path > b.path ? 1 : 0));
|
|
237
|
+
for (const victim of survivors.slice(0, surplus)) {
|
|
238
|
+
await rm(victim.path, { force: true }).catch(() => {});
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import test from "node:test";
|
|
3
|
+
|
|
4
|
+
import { log } from "./logger.js";
|
|
5
|
+
import { WriteRateLimiter } from "./write-rate-limiter.js";
|
|
6
|
+
|
|
7
|
+
test("WriteRateLimiter: defaults applied when unset or invalid (issue #1937)", () => {
|
|
8
|
+
assert.equal(new WriteRateLimiter().maxRequests, 30);
|
|
9
|
+
assert.equal(new WriteRateLimiter().windowMs, 60_000);
|
|
10
|
+
assert.equal(new WriteRateLimiter(0).maxRequests, 30, "0 is invalid -> default");
|
|
11
|
+
assert.equal(new WriteRateLimiter(1.5).maxRequests, 30, "non-integer -> default");
|
|
12
|
+
const custom = new WriteRateLimiter(120, 30_000);
|
|
13
|
+
assert.equal(custom.maxRequests, 120);
|
|
14
|
+
assert.equal(custom.windowMs, 30_000);
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
test("WriteRateLimiter: capacity check, record, and sampled rejection logging (issue #2029)", () => {
|
|
18
|
+
const limiter = new WriteRateLimiter(1, 60_000);
|
|
19
|
+
const warnings: string[] = [];
|
|
20
|
+
const originalWarn = log.warn;
|
|
21
|
+
log.warn = (msg: string) => {
|
|
22
|
+
warnings.push(msg);
|
|
23
|
+
};
|
|
24
|
+
try {
|
|
25
|
+
assert.equal(limiter.hasCapacity(), true);
|
|
26
|
+
limiter.record();
|
|
27
|
+
assert.equal(limiter.hasCapacity(), false, "over the limit after one recorded write");
|
|
28
|
+
assert.equal(limiter.hasCapacity(), false);
|
|
29
|
+
assert.ok(
|
|
30
|
+
warnings.some((w) => w.includes("write_rate_limited")),
|
|
31
|
+
"a refused write is logged server-side",
|
|
32
|
+
);
|
|
33
|
+
assert.equal(warnings.length, 1, "logging is sampled within the interval, not per-refusal");
|
|
34
|
+
} finally {
|
|
35
|
+
log.warn = originalWarn;
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
test("WriteRateLimiter: reserve returns a release, refuses at the limit, frees on release", () => {
|
|
40
|
+
const limiter = new WriteRateLimiter(1, 60_000);
|
|
41
|
+
const release = limiter.reserve();
|
|
42
|
+
assert.ok(release, "first reservation succeeds");
|
|
43
|
+
assert.equal(limiter.slotsFor().length, 1);
|
|
44
|
+
assert.equal(limiter.reserve(), null, "second reservation refused at the limit");
|
|
45
|
+
release?.();
|
|
46
|
+
assert.equal(limiter.slotsFor().length, 0, "release frees the reserved slot");
|
|
47
|
+
assert.ok(limiter.reserve(), "capacity restored after release");
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
test("WriteRateLimiter: old slots pruned after the rolling window", () => {
|
|
51
|
+
const realNow = Date.now;
|
|
52
|
+
try {
|
|
53
|
+
let now = 1_000_000;
|
|
54
|
+
Date.now = () => now;
|
|
55
|
+
const limiter = new WriteRateLimiter(1, 1000);
|
|
56
|
+
limiter.record();
|
|
57
|
+
assert.equal(limiter.hasCapacity(), false, "at the limit within the window");
|
|
58
|
+
now += 1001;
|
|
59
|
+
assert.equal(limiter.hasCapacity(), true, "slot pruned once older than the window");
|
|
60
|
+
} finally {
|
|
61
|
+
Date.now = realNow;
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
test("WriteRateLimiter: buckets are isolated per principal (issue #2029)", () => {
|
|
66
|
+
const limiter = new WriteRateLimiter(1, 60_000);
|
|
67
|
+
limiter.record("alice");
|
|
68
|
+
assert.equal(limiter.hasCapacity("alice"), false, "alice is at her own limit");
|
|
69
|
+
assert.equal(limiter.hasCapacity("bob"), true, "bob is unaffected by alice");
|
|
70
|
+
assert.equal(limiter.hasCapacity(), true, "the no-principal global bucket is separate");
|
|
71
|
+
limiter.record("bob");
|
|
72
|
+
assert.equal(limiter.slotsFor("alice").length, 1);
|
|
73
|
+
assert.equal(limiter.slotsFor("bob").length, 1);
|
|
74
|
+
assert.equal(limiter.totalSlots(), 2, "each principal keeps its own window");
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
test("WriteRateLimiter: rolling-window end is exclusive (issue #2029 review)", () => {
|
|
78
|
+
const realNow = Date.now;
|
|
79
|
+
try {
|
|
80
|
+
let now = 1_000_000;
|
|
81
|
+
Date.now = () => now;
|
|
82
|
+
const limiter = new WriteRateLimiter(1, 1000);
|
|
83
|
+
limiter.record();
|
|
84
|
+
now += 1000; // exactly at recordedAt + windowMs
|
|
85
|
+
assert.equal(limiter.hasCapacity(), true, "a slot at the exact window end is expired");
|
|
86
|
+
} finally {
|
|
87
|
+
Date.now = realNow;
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
test("WriteRateLimiter: expired buckets from transient principals are swept globally (issue #2029 review)", () => {
|
|
92
|
+
const realNow = Date.now;
|
|
93
|
+
try {
|
|
94
|
+
let now = 1_000_000;
|
|
95
|
+
Date.now = () => now;
|
|
96
|
+
const limiter = new WriteRateLimiter(5, 1000);
|
|
97
|
+
limiter.record("ephemeral-1");
|
|
98
|
+
limiter.record("ephemeral-2");
|
|
99
|
+
assert.equal(limiter.totalSlots(), 2);
|
|
100
|
+
now += 1001; // both windows expire
|
|
101
|
+
assert.equal(limiter.totalSlots(), 0, "expired buckets are swept, not reported as live");
|
|
102
|
+
} finally {
|
|
103
|
+
Date.now = realNow;
|
|
104
|
+
}
|
|
105
|
+
});
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-principal write rate limiter for the access HTTP surface
|
|
3
|
+
* (issues #1937 / #2029).
|
|
4
|
+
*
|
|
5
|
+
* Extracted from access-http.ts so the limiter is a cohesive unit. Each
|
|
6
|
+
* principal (authenticated caller identity) gets its own rolling-window
|
|
7
|
+
* bucket, so one chatty connector cannot starve writes for the others;
|
|
8
|
+
* requests with no resolved principal share a single global bucket.
|
|
9
|
+
*
|
|
10
|
+
* Deliberately transport-agnostic: it never throws `HttpError` — callers
|
|
11
|
+
* translate a refusal into their own 429. It logs a sampled warning when it
|
|
12
|
+
* refuses a write, so a sustained limit condition is diagnosable server-side
|
|
13
|
+
* (previously the refusal was a bare throw with no trace).
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import { log } from "./logger.js";
|
|
17
|
+
|
|
18
|
+
// Defaults; overridable per instance via the constructor (config/env feed these
|
|
19
|
+
// through the server's `writeRateLimit*` settings).
|
|
20
|
+
const DEFAULT_WINDOW_MS = 60_000;
|
|
21
|
+
const DEFAULT_MAX_REQUESTS = 30;
|
|
22
|
+
// At most one rejection warning per this interval, carrying a rejected-count,
|
|
23
|
+
// so a storm is visible without flooding the log.
|
|
24
|
+
const LOG_INTERVAL_MS = 10_000;
|
|
25
|
+
// Bucket key for requests with no resolved principal.
|
|
26
|
+
const GLOBAL_BUCKET = "";
|
|
27
|
+
|
|
28
|
+
type Slot = { readonly recordedAt: number };
|
|
29
|
+
|
|
30
|
+
function sanitizePositiveInteger(value: number | undefined, fallback: number): number {
|
|
31
|
+
return typeof value === "number" && Number.isInteger(value) && value >= 1 ? value : fallback;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export class WriteRateLimiter {
|
|
35
|
+
readonly maxRequests: number;
|
|
36
|
+
readonly windowMs: number;
|
|
37
|
+
// One rolling window per principal. Empty buckets are pruned so the map does
|
|
38
|
+
// not grow without bound.
|
|
39
|
+
private readonly buckets = new Map<string, Slot[]>();
|
|
40
|
+
private lastLogAt = 0;
|
|
41
|
+
private suppressed = 0;
|
|
42
|
+
|
|
43
|
+
constructor(maxRequests?: number, windowMs?: number) {
|
|
44
|
+
this.maxRequests = sanitizePositiveInteger(maxRequests, DEFAULT_MAX_REQUESTS);
|
|
45
|
+
this.windowMs = sanitizePositiveInteger(windowMs, DEFAULT_WINDOW_MS);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
private key(principal?: string): string {
|
|
49
|
+
return principal?.trim() ? principal.trim() : GLOBAL_BUCKET;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
private prune(slots: Slot[], now: number): void {
|
|
53
|
+
while (slots.length > 0 && now - (slots[0]?.recordedAt ?? 0) >= this.windowMs) {
|
|
54
|
+
slots.shift();
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** Prune every bucket to the current window; drop buckets that go empty. */
|
|
59
|
+
private sweep(now: number): void {
|
|
60
|
+
for (const [key, slots] of this.buckets) {
|
|
61
|
+
this.prune(slots, now);
|
|
62
|
+
if (slots.length === 0) this.buckets.delete(key);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** Live reservation/record count for a principal (test/introspection). */
|
|
67
|
+
slotsFor(principal?: string): ReadonlyArray<Slot> {
|
|
68
|
+
return this.buckets.get(this.key(principal)) ?? [];
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** Total live reservations across all principals (test/introspection). */
|
|
72
|
+
totalSlots(): number {
|
|
73
|
+
this.sweep(Date.now());
|
|
74
|
+
let total = 0;
|
|
75
|
+
for (const slots of this.buckets.values()) total += slots.length;
|
|
76
|
+
return total;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/** True if a write may proceed for the principal. Logs (sampled) when not. */
|
|
80
|
+
hasCapacity(principal?: string): boolean {
|
|
81
|
+
this.sweep(Date.now());
|
|
82
|
+
const key = this.key(principal);
|
|
83
|
+
const slots = this.buckets.get(key);
|
|
84
|
+
if (slots && slots.length >= this.maxRequests) {
|
|
85
|
+
this.logRejected(key);
|
|
86
|
+
return false;
|
|
87
|
+
}
|
|
88
|
+
return true;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/** Record a committed write against the principal's rolling window. */
|
|
92
|
+
record(principal?: string): void {
|
|
93
|
+
this.sweep(Date.now());
|
|
94
|
+
const key = this.key(principal);
|
|
95
|
+
const slots = this.buckets.get(key) ?? this.buckets.set(key, []).get(key)!;
|
|
96
|
+
slots.push({ recordedAt: Date.now() });
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Reserve a slot for an in-flight write. Returns a release function, or
|
|
101
|
+
* `null` when the principal is already at the limit (caller raises its 429).
|
|
102
|
+
*/
|
|
103
|
+
reserve(principal?: string): (() => void) | null {
|
|
104
|
+
if (!this.hasCapacity(principal)) return null;
|
|
105
|
+
const key = this.key(principal);
|
|
106
|
+
const slots = this.buckets.get(key) ?? this.buckets.set(key, []).get(key)!;
|
|
107
|
+
const slot: Slot = { recordedAt: Date.now() };
|
|
108
|
+
slots.push(slot);
|
|
109
|
+
let active = true;
|
|
110
|
+
return () => {
|
|
111
|
+
if (!active) return;
|
|
112
|
+
active = false;
|
|
113
|
+
const current = this.buckets.get(key);
|
|
114
|
+
if (!current) return;
|
|
115
|
+
const index = current.indexOf(slot);
|
|
116
|
+
if (index >= 0) current.splice(index, 1);
|
|
117
|
+
if (current.length === 0) this.buckets.delete(key);
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
private logRejected(principal: string): void {
|
|
122
|
+
const now = Date.now();
|
|
123
|
+
this.suppressed += 1;
|
|
124
|
+
if (now - this.lastLogAt < LOG_INTERVAL_MS) return;
|
|
125
|
+
const rejected = this.suppressed;
|
|
126
|
+
this.suppressed = 0;
|
|
127
|
+
this.lastLogAt = now;
|
|
128
|
+
const who = principal === GLOBAL_BUCKET ? "(no principal)" : principal;
|
|
129
|
+
log.warn(
|
|
130
|
+
`write_rate_limited: rejected ${rejected} write(s) for principal ${who}; ` +
|
|
131
|
+
`per-principal limit is ${this.maxRequests} per ${this.windowMs}ms. Raise ` +
|
|
132
|
+
`server.writeRateLimitMaxRequests (or REMNIC_WRITE_RATE_LIMIT_MAX_REQUESTS) if sustained.`,
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
}
|