@remnic/core 10.0.63 → 10.0.64
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/access-admin-ops-surface.js +46 -46
- package/dist/access-authorization-probe.js +47 -47
- package/dist/access-boundary.js +46 -46
- package/dist/access-cli.js +96 -96
- package/dist/access-extraction-force-flush.js +46 -46
- package/dist/access-http-briefing.js +47 -47
- package/dist/access-http-query.js +48 -48
- package/dist/access-http.js +66 -66
- package/dist/access-identity-continuity-surface.js +46 -46
- package/dist/access-lcm-surface.js +46 -46
- package/dist/access-mcp.js +58 -58
- package/dist/access-namespace-preflight.js +46 -46
- package/dist/access-observe-write-category.js +11 -11
- package/dist/access-observe-write-surface.js +46 -46
- package/dist/access-offline-manifest.js +10 -10
- package/dist/access-operations-batch.js +50 -50
- package/dist/access-operations.js +57 -57
- package/dist/access-recall-concurrency.js +46 -46
- package/dist/access-recall-response.js +46 -46
- package/dist/access-recall-surface.js +46 -46
- package/dist/access-schema.js +6 -6
- package/dist/access-service.js +46 -46
- package/dist/access-surface-catalog.js +48 -48
- package/dist/active-recall.js +21 -21
- package/dist/{auto-sync-UQQ6YJSB.js → auto-sync-ANOJDQD4.js} +7 -7
- package/dist/briefing.js +14 -14
- package/dist/buffer.js +7 -7
- package/dist/{capsule-merge-D4BTIJGK.js → capsule-merge-XVTQJJ6R.js} +4 -4
- package/dist/capture-write-lock.js +2 -2
- package/dist/causal-behavior.js +2 -2
- package/dist/causal-chain.js +2 -2
- package/dist/causal-consolidation.js +17 -17
- package/dist/causal-retrieval.js +2 -2
- package/dist/causal-trajectory-graph.js +3 -3
- package/dist/causal-trajectory.js +1 -1
- package/dist/{chunk-S4OZKPBS.js → chunk-2D7BTSDW.js} +2 -2
- package/dist/{chunk-WB3OFPM2.js → chunk-2DNFRLLL.js} +3 -3
- package/dist/{chunk-EKPRLTAE.js → chunk-2RUTN7PR.js} +2 -2
- package/dist/{chunk-EAALT5NB.js → chunk-2ZSRACNK.js} +42 -42
- package/dist/{chunk-X4PAATLA.js → chunk-3LBDEGQA.js} +4 -4
- package/dist/{chunk-2U7ZDC2E.js → chunk-3LSTL6EY.js} +2 -2
- package/dist/{chunk-QCAJMIAE.js → chunk-434E4BTO.js} +3 -3
- package/dist/{chunk-TPKB7T6Z.js → chunk-4XINO77L.js} +2 -2
- package/dist/{chunk-EADUOZWH.js → chunk-5JABRT5G.js} +5 -5
- package/dist/{chunk-6I6DFULH.js → chunk-5KDDAQ6K.js} +2 -2
- package/dist/{chunk-YDIOMDBW.js → chunk-5QV6CE6K.js} +3 -3
- package/dist/{chunk-QI7LLSWX.js → chunk-5UEGL5BF.js} +3 -3
- package/dist/{chunk-TMWSCE2L.js → chunk-5ZHFPUBC.js} +11 -11
- package/dist/{chunk-TUCEP2LS.js → chunk-63A2BU2B.js} +2 -2
- package/dist/{chunk-264B57GV.js → chunk-6D3IWYNY.js} +71 -88
- package/dist/chunk-6D3IWYNY.js.map +1 -0
- package/dist/{chunk-HJ3UTGIW.js → chunk-6P7U3HWG.js} +4 -4
- package/dist/{chunk-UEDC2VGP.js → chunk-7JYK7NNY.js} +2 -2
- package/dist/{chunk-IWAB7JVH.js → chunk-7ZTGMEXF.js} +2 -2
- package/dist/{chunk-QYAXMXOB.js → chunk-A7BM2JTH.js} +2 -2
- package/dist/{chunk-HSHLDSPX.js → chunk-AUFPPBC3.js} +3 -3
- package/dist/{chunk-QJ6IR35B.js → chunk-B7VD36TI.js} +4 -4
- package/dist/{chunk-ECT5ME6X.js → chunk-BBHKN2CN.js} +2 -2
- package/dist/{chunk-WA6XRNWE.js → chunk-BH7NVSEC.js} +4 -4
- package/dist/{chunk-QIMGULGN.js → chunk-BNZAR6PO.js} +2 -2
- package/dist/{chunk-C5OW2FFB.js → chunk-BOZQAUAJ.js} +2 -2
- package/dist/{chunk-WJCXUE2J.js → chunk-BV2BRHD4.js} +5 -5
- package/dist/{chunk-Z2T5BPOZ.js → chunk-CIEEYVUB.js} +2 -2
- package/dist/{chunk-PA6ZNFB7.js → chunk-CLSKGTAT.js} +2 -2
- package/dist/{chunk-YRL4PGPP.js → chunk-DRTSPRWL.js} +2 -2
- package/dist/{chunk-ZRSXXWIX.js → chunk-E4QIX2OB.js} +3 -3
- package/dist/{chunk-EBRG7ZIK.js → chunk-EBV6VXKF.js} +24 -2
- package/dist/{chunk-EBRG7ZIK.js.map → chunk-EBV6VXKF.js.map} +1 -1
- package/dist/{chunk-X2ZLHUEO.js → chunk-ECF627KC.js} +2 -2
- package/dist/{chunk-XICJL56Z.js → chunk-EI6M5NVK.js} +2 -2
- package/dist/{chunk-564YP3DW.js → chunk-ER7XANP3.js} +3 -3
- package/dist/{chunk-ILMB3X4X.js → chunk-FKGLJJVS.js} +3 -3
- package/dist/{chunk-L74VVEJA.js → chunk-FR7PK7ED.js} +3 -3
- package/dist/{chunk-UXRCORZX.js → chunk-FUDKGJDN.js} +2 -2
- package/dist/{chunk-GNEB3WXR.js → chunk-G6L3REL3.js} +2 -2
- package/dist/{chunk-4P4VLPGB.js → chunk-GKL6QYZO.js} +1 -1
- package/dist/{chunk-IMNJJKHW.js → chunk-GRAUQXQB.js} +28 -28
- package/dist/{chunk-JVFYP2LD.js → chunk-GROHOWR5.js} +2 -2
- package/dist/{chunk-LIUI46BX.js → chunk-H6VJSITJ.js} +2 -2
- package/dist/{chunk-YGM47S7M.js → chunk-HFEGRMFG.js} +8 -8
- package/dist/{chunk-A6JV3WHI.js → chunk-HG3CG557.js} +2 -2
- package/dist/{chunk-7JFXXA57.js → chunk-HHL4MX3W.js} +2 -2
- package/dist/{chunk-53OWLEAD.js → chunk-HNOSWPKG.js} +2 -2
- package/dist/{chunk-IJKBIOWL.js → chunk-HOR4RMUM.js} +25 -25
- package/dist/{chunk-WZEDFDJ6.js → chunk-HRRAOTOQ.js} +15 -15
- package/dist/{chunk-A5L5KGDI.js → chunk-IAYUTGF2.js} +2 -2
- package/dist/{chunk-WVEEVYMH.js → chunk-IGOW4TJE.js} +3 -3
- package/dist/{chunk-MKE5DP3S.js → chunk-IHCXMYSE.js} +2 -2
- package/dist/{chunk-SDXBR5QO.js → chunk-IZJQNDGI.js} +2 -2
- package/dist/{chunk-BQOOUKEN.js → chunk-JL5HXL25.js} +6 -6
- package/dist/{chunk-ZFU2B2GM.js → chunk-JPY3RMG2.js} +5 -5
- package/dist/{chunk-TWRPWJJT.js → chunk-JRVMCZE3.js} +2 -2
- package/dist/{chunk-FYPKSR6Z.js → chunk-KISOMGY2.js} +2 -2
- package/dist/{chunk-J62O2UW5.js → chunk-KNMSD2MQ.js} +2 -2
- package/dist/{chunk-MTVC5S5N.js → chunk-KO224R36.js} +2 -2
- package/dist/{chunk-UFQB5VQ5.js → chunk-KQJUEQ2E.js} +7 -7
- package/dist/{chunk-QNIYL5SK.js → chunk-KQLBXUUQ.js} +2 -2
- package/dist/{chunk-W5EO4USJ.js → chunk-KQUSP6LB.js} +2 -2
- package/dist/{chunk-BDNPW3KM.js → chunk-KYSSUY66.js} +4 -4
- package/dist/{chunk-R25JMO5O.js → chunk-LDHR465K.js} +2 -2
- package/dist/{chunk-CYEEVWQ6.js → chunk-LIGZO7XN.js} +2 -2
- package/dist/{chunk-QKXPOLQD.js → chunk-M4IEZGCK.js} +2 -2
- package/dist/{chunk-Q3DAWUAV.js → chunk-MRF5V5CD.js} +2 -2
- package/dist/{chunk-HTTZ3GJ6.js → chunk-MUHGYV2V.js} +2 -2
- package/dist/{chunk-HNZH57JK.js → chunk-N4T5FW6U.js} +2 -2
- package/dist/{chunk-YLB7TW2B.js → chunk-NM2II57L.js} +4 -4
- package/dist/{chunk-3VNRDRBM.js → chunk-OVL5AD3N.js} +2 -2
- package/dist/{chunk-3HLWLYRD.js → chunk-P2EM5LKH.js} +2 -2
- package/dist/{chunk-XL2EE5W3.js → chunk-PLCYKAWH.js} +2 -2
- package/dist/{chunk-JIYJIH5N.js → chunk-QDZWQBLU.js} +3 -3
- package/dist/{chunk-YXW4KXCV.js → chunk-QGJSBU4R.js} +2 -2
- package/dist/{chunk-DM5QZ3YU.js → chunk-QGVCFVX6.js} +2 -2
- package/dist/{chunk-VSHWPOUB.js → chunk-RJWL3RHB.js} +2 -2
- package/dist/{chunk-WU5WTCH5.js → chunk-RZH5RVJC.js} +3 -3
- package/dist/{chunk-76LKZ42K.js → chunk-SHHZWSSE.js} +2 -2
- package/dist/{chunk-N7CATBYH.js → chunk-SNS3DWG6.js} +2 -2
- package/dist/{chunk-IVJI5FDA.js → chunk-T22FBDG3.js} +2 -2
- package/dist/{chunk-YH7HJNWV.js → chunk-TGQW3AMK.js} +4 -4
- package/dist/{chunk-KQXRVELF.js → chunk-TIGYOZBU.js} +2 -2
- package/dist/{chunk-Z45353ML.js → chunk-UH6ROXFJ.js} +2 -2
- package/dist/{chunk-C2AYUXEF.js → chunk-UYUFDTIX.js} +2 -2
- package/dist/{chunk-2AKIVSHP.js → chunk-W26D2USW.js} +2 -2
- package/dist/{chunk-FZVZYU5C.js → chunk-W7K5BP3M.js} +3 -3
- package/dist/{chunk-ZARHK7PT.js → chunk-WWH7GJ42.js} +2 -2
- package/dist/{chunk-4NLHEE2Z.js → chunk-XLCEFUAU.js} +2 -2
- package/dist/{chunk-PWEIZCPS.js → chunk-XOFIJGB2.js} +2 -2
- package/dist/{chunk-6VBKU3FY.js → chunk-XVQVF2TK.js} +18 -12
- package/dist/chunk-XVQVF2TK.js.map +1 -0
- package/dist/{chunk-TRSKATZQ.js → chunk-XZ66U5M3.js} +2 -2
- package/dist/{chunk-UUZ7KZAO.js → chunk-Y33KJ556.js} +2 -2
- package/dist/{chunk-BU4W2ISH.js → chunk-YJQJRXUI.js} +2 -2
- package/dist/{chunk-QBK3IM6W.js → chunk-YSATDEBY.js} +2 -2
- package/dist/{chunk-WR5POHQ3.js → chunk-YUTT4ZTI.js} +2 -2
- package/dist/{chunk-JCL7RO2G.js → chunk-ZOBYTRBU.js} +2 -2
- package/dist/{chunk-FMOUUWQN.js → chunk-ZPLQ27MY.js} +3 -3
- package/dist/cli.js +81 -81
- package/dist/coding/export-okf-codegraph.js +14 -14
- package/dist/coding/pre-action-gate.js +2 -2
- package/dist/compounding/engine.js +16 -16
- package/dist/config.js +21 -21
- package/dist/connectors/codex-materialize-runner.js +14 -14
- package/dist/connectors/index.js +14 -14
- package/dist/consolidation-provenance-check.js +4 -4
- package/dist/consolidation-undo.js +3 -3
- package/dist/contradiction/index.js +5 -5
- package/dist/convergence-refresh.js +16 -16
- package/dist/corpus-watermark.js +16 -16
- package/dist/deep-recall-http-glue.js +7 -7
- package/dist/embedding-fallback.js +3 -3
- package/dist/embedding-generation-lock.js +2 -2
- package/dist/{entity-canonical-id-migration-CUDCKWPF.js → entity-canonical-id-migration-JJSTZF73.js} +4 -4
- package/dist/entity-retrieval.js +14 -14
- package/dist/explicit-capture.js +8 -8
- package/dist/external-wiki-access.js +47 -47
- package/dist/extraction.js +18 -18
- package/dist/{forget-E6ZTUKEC.js → forget-DE56ICCL.js} +3 -3
- package/dist/{graph-edge-decay-2BZTY2IM.js → graph-edge-decay-OEX5OGMI.js} +6 -6
- package/dist/graph-jsonl.js +4 -4
- package/dist/graph-snapshot.js +4 -4
- package/dist/graph-write-lock.js +2 -2
- package/dist/graph.js +3 -3
- package/dist/harmonic-retrieval.js +14 -14
- package/dist/index.js +96 -96
- package/dist/location/index.js +17 -17
- package/dist/maintenance/memory-governance.js +14 -14
- package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +14 -14
- package/dist/maintenance/rebuild-memory-projection-runner.js +16 -16
- package/dist/maintenance/rebuild-memory-projection.js +15 -15
- package/dist/memory-browse-operations.js +47 -47
- package/dist/memory-subject.js +3 -3
- package/dist/namespaces/migrate.js +15 -15
- package/dist/namespaces/storage.js +14 -14
- package/dist/offline-sync-changeset-generations.js +4 -4
- package/dist/offline-sync-embedding-generation.js +3 -3
- package/dist/offline-sync-impression-drain.js +3 -3
- package/dist/offline-sync-snapshot-stream.js +6 -6
- package/dist/offline-sync.js +5 -5
- package/dist/operator-doctor-corpus.js +17 -17
- package/dist/operator-toolkit.js +33 -33
- package/dist/orchestration/buffer-drain-runner.js +96 -96
- package/dist/orchestration/extraction-run-helpers.d.ts +0 -3
- package/dist/orchestration/extraction-run-helpers.d.ts.map +1 -1
- package/dist/orchestration/extraction-run.d.ts +4 -2
- package/dist/orchestration/extraction-run.d.ts.map +1 -1
- package/dist/orchestration/maintenance.js +26 -26
- package/dist/orchestrator.js +96 -96
- package/dist/page-versioning.js +3 -3
- package/dist/recall-navigation-operations.js +47 -47
- package/dist/recall-navigation.js +46 -46
- package/dist/recall-state.js +2 -2
- package/dist/reconcile/manifest.js +3 -3
- package/dist/{resolution-NNYG37YB.js → resolution-BNWE5Y25.js} +6 -6
- package/dist/resume-bundles.js +22 -22
- package/dist/salvage-envelope.js +5 -5
- package/dist/semantic-consolidation.js +15 -15
- package/dist/semantic-rule-promotion.js +14 -14
- package/dist/semantic-rule-verifier.js +14 -14
- package/dist/source-agent-qualifier.js +17 -17
- package/dist/standup/build.js +14 -14
- package/dist/storage/buffer-drain-claims.js +2 -2
- package/dist/storage/buffer-session-files.js +2 -2
- package/dist/storage.d.ts +1 -0
- package/dist/storage.d.ts.map +1 -1
- package/dist/storage.js +13 -13
- package/dist/subject-config.js +4 -4
- package/dist/summarizer.js +3 -3
- package/dist/summary-snapshot.js +2 -2
- package/dist/support-passport/index.js +49 -49
- package/dist/transfer/capsule-import.js +4 -4
- package/dist/transfer/export-okf.js +14 -14
- package/dist/utils/serialize-mutations.d.ts +13 -0
- package/dist/utils/serialize-mutations.d.ts.map +1 -1
- package/dist/utils/serialize-mutations.js +3 -1
- package/dist/verified-recall.js +14 -14
- package/dist/who-knows-cli.js +15 -15
- package/dist/who-knows.js +14 -14
- package/dist/write-envelope.js +4 -4
- package/package.json +2 -2
- package/src/orchestration/extraction-run-helpers.ts +33 -52
- package/src/orchestration/extraction-run.ts +54 -54
- package/src/orchestrator-flush.test.ts +103 -79
- package/src/storage-update-meta.test.ts +114 -0
- package/src/storage.ts +7 -6
- package/src/utils/serialize-mutations.ts +53 -0
- package/dist/chunk-264B57GV.js.map +0 -1
- package/dist/chunk-6VBKU3FY.js.map +0 -1
- /package/dist/{auto-sync-UQQ6YJSB.js.map → auto-sync-ANOJDQD4.js.map} +0 -0
- /package/dist/{capsule-merge-D4BTIJGK.js.map → capsule-merge-XVTQJJ6R.js.map} +0 -0
- /package/dist/{chunk-S4OZKPBS.js.map → chunk-2D7BTSDW.js.map} +0 -0
- /package/dist/{chunk-WB3OFPM2.js.map → chunk-2DNFRLLL.js.map} +0 -0
- /package/dist/{chunk-EKPRLTAE.js.map → chunk-2RUTN7PR.js.map} +0 -0
- /package/dist/{chunk-EAALT5NB.js.map → chunk-2ZSRACNK.js.map} +0 -0
- /package/dist/{chunk-X4PAATLA.js.map → chunk-3LBDEGQA.js.map} +0 -0
- /package/dist/{chunk-2U7ZDC2E.js.map → chunk-3LSTL6EY.js.map} +0 -0
- /package/dist/{chunk-QCAJMIAE.js.map → chunk-434E4BTO.js.map} +0 -0
- /package/dist/{chunk-TPKB7T6Z.js.map → chunk-4XINO77L.js.map} +0 -0
- /package/dist/{chunk-EADUOZWH.js.map → chunk-5JABRT5G.js.map} +0 -0
- /package/dist/{chunk-6I6DFULH.js.map → chunk-5KDDAQ6K.js.map} +0 -0
- /package/dist/{chunk-YDIOMDBW.js.map → chunk-5QV6CE6K.js.map} +0 -0
- /package/dist/{chunk-QI7LLSWX.js.map → chunk-5UEGL5BF.js.map} +0 -0
- /package/dist/{chunk-TMWSCE2L.js.map → chunk-5ZHFPUBC.js.map} +0 -0
- /package/dist/{chunk-TUCEP2LS.js.map → chunk-63A2BU2B.js.map} +0 -0
- /package/dist/{chunk-HJ3UTGIW.js.map → chunk-6P7U3HWG.js.map} +0 -0
- /package/dist/{chunk-UEDC2VGP.js.map → chunk-7JYK7NNY.js.map} +0 -0
- /package/dist/{chunk-IWAB7JVH.js.map → chunk-7ZTGMEXF.js.map} +0 -0
- /package/dist/{chunk-QYAXMXOB.js.map → chunk-A7BM2JTH.js.map} +0 -0
- /package/dist/{chunk-HSHLDSPX.js.map → chunk-AUFPPBC3.js.map} +0 -0
- /package/dist/{chunk-QJ6IR35B.js.map → chunk-B7VD36TI.js.map} +0 -0
- /package/dist/{chunk-ECT5ME6X.js.map → chunk-BBHKN2CN.js.map} +0 -0
- /package/dist/{chunk-WA6XRNWE.js.map → chunk-BH7NVSEC.js.map} +0 -0
- /package/dist/{chunk-QIMGULGN.js.map → chunk-BNZAR6PO.js.map} +0 -0
- /package/dist/{chunk-C5OW2FFB.js.map → chunk-BOZQAUAJ.js.map} +0 -0
- /package/dist/{chunk-WJCXUE2J.js.map → chunk-BV2BRHD4.js.map} +0 -0
- /package/dist/{chunk-Z2T5BPOZ.js.map → chunk-CIEEYVUB.js.map} +0 -0
- /package/dist/{chunk-PA6ZNFB7.js.map → chunk-CLSKGTAT.js.map} +0 -0
- /package/dist/{chunk-YRL4PGPP.js.map → chunk-DRTSPRWL.js.map} +0 -0
- /package/dist/{chunk-ZRSXXWIX.js.map → chunk-E4QIX2OB.js.map} +0 -0
- /package/dist/{chunk-X2ZLHUEO.js.map → chunk-ECF627KC.js.map} +0 -0
- /package/dist/{chunk-XICJL56Z.js.map → chunk-EI6M5NVK.js.map} +0 -0
- /package/dist/{chunk-564YP3DW.js.map → chunk-ER7XANP3.js.map} +0 -0
- /package/dist/{chunk-ILMB3X4X.js.map → chunk-FKGLJJVS.js.map} +0 -0
- /package/dist/{chunk-L74VVEJA.js.map → chunk-FR7PK7ED.js.map} +0 -0
- /package/dist/{chunk-UXRCORZX.js.map → chunk-FUDKGJDN.js.map} +0 -0
- /package/dist/{chunk-GNEB3WXR.js.map → chunk-G6L3REL3.js.map} +0 -0
- /package/dist/{chunk-4P4VLPGB.js.map → chunk-GKL6QYZO.js.map} +0 -0
- /package/dist/{chunk-IMNJJKHW.js.map → chunk-GRAUQXQB.js.map} +0 -0
- /package/dist/{chunk-JVFYP2LD.js.map → chunk-GROHOWR5.js.map} +0 -0
- /package/dist/{chunk-LIUI46BX.js.map → chunk-H6VJSITJ.js.map} +0 -0
- /package/dist/{chunk-YGM47S7M.js.map → chunk-HFEGRMFG.js.map} +0 -0
- /package/dist/{chunk-A6JV3WHI.js.map → chunk-HG3CG557.js.map} +0 -0
- /package/dist/{chunk-7JFXXA57.js.map → chunk-HHL4MX3W.js.map} +0 -0
- /package/dist/{chunk-53OWLEAD.js.map → chunk-HNOSWPKG.js.map} +0 -0
- /package/dist/{chunk-IJKBIOWL.js.map → chunk-HOR4RMUM.js.map} +0 -0
- /package/dist/{chunk-WZEDFDJ6.js.map → chunk-HRRAOTOQ.js.map} +0 -0
- /package/dist/{chunk-A5L5KGDI.js.map → chunk-IAYUTGF2.js.map} +0 -0
- /package/dist/{chunk-WVEEVYMH.js.map → chunk-IGOW4TJE.js.map} +0 -0
- /package/dist/{chunk-MKE5DP3S.js.map → chunk-IHCXMYSE.js.map} +0 -0
- /package/dist/{chunk-SDXBR5QO.js.map → chunk-IZJQNDGI.js.map} +0 -0
- /package/dist/{chunk-BQOOUKEN.js.map → chunk-JL5HXL25.js.map} +0 -0
- /package/dist/{chunk-ZFU2B2GM.js.map → chunk-JPY3RMG2.js.map} +0 -0
- /package/dist/{chunk-TWRPWJJT.js.map → chunk-JRVMCZE3.js.map} +0 -0
- /package/dist/{chunk-FYPKSR6Z.js.map → chunk-KISOMGY2.js.map} +0 -0
- /package/dist/{chunk-J62O2UW5.js.map → chunk-KNMSD2MQ.js.map} +0 -0
- /package/dist/{chunk-MTVC5S5N.js.map → chunk-KO224R36.js.map} +0 -0
- /package/dist/{chunk-UFQB5VQ5.js.map → chunk-KQJUEQ2E.js.map} +0 -0
- /package/dist/{chunk-QNIYL5SK.js.map → chunk-KQLBXUUQ.js.map} +0 -0
- /package/dist/{chunk-W5EO4USJ.js.map → chunk-KQUSP6LB.js.map} +0 -0
- /package/dist/{chunk-BDNPW3KM.js.map → chunk-KYSSUY66.js.map} +0 -0
- /package/dist/{chunk-R25JMO5O.js.map → chunk-LDHR465K.js.map} +0 -0
- /package/dist/{chunk-CYEEVWQ6.js.map → chunk-LIGZO7XN.js.map} +0 -0
- /package/dist/{chunk-QKXPOLQD.js.map → chunk-M4IEZGCK.js.map} +0 -0
- /package/dist/{chunk-Q3DAWUAV.js.map → chunk-MRF5V5CD.js.map} +0 -0
- /package/dist/{chunk-HTTZ3GJ6.js.map → chunk-MUHGYV2V.js.map} +0 -0
- /package/dist/{chunk-HNZH57JK.js.map → chunk-N4T5FW6U.js.map} +0 -0
- /package/dist/{chunk-YLB7TW2B.js.map → chunk-NM2II57L.js.map} +0 -0
- /package/dist/{chunk-3VNRDRBM.js.map → chunk-OVL5AD3N.js.map} +0 -0
- /package/dist/{chunk-3HLWLYRD.js.map → chunk-P2EM5LKH.js.map} +0 -0
- /package/dist/{chunk-XL2EE5W3.js.map → chunk-PLCYKAWH.js.map} +0 -0
- /package/dist/{chunk-JIYJIH5N.js.map → chunk-QDZWQBLU.js.map} +0 -0
- /package/dist/{chunk-YXW4KXCV.js.map → chunk-QGJSBU4R.js.map} +0 -0
- /package/dist/{chunk-DM5QZ3YU.js.map → chunk-QGVCFVX6.js.map} +0 -0
- /package/dist/{chunk-VSHWPOUB.js.map → chunk-RJWL3RHB.js.map} +0 -0
- /package/dist/{chunk-WU5WTCH5.js.map → chunk-RZH5RVJC.js.map} +0 -0
- /package/dist/{chunk-76LKZ42K.js.map → chunk-SHHZWSSE.js.map} +0 -0
- /package/dist/{chunk-N7CATBYH.js.map → chunk-SNS3DWG6.js.map} +0 -0
- /package/dist/{chunk-IVJI5FDA.js.map → chunk-T22FBDG3.js.map} +0 -0
- /package/dist/{chunk-YH7HJNWV.js.map → chunk-TGQW3AMK.js.map} +0 -0
- /package/dist/{chunk-KQXRVELF.js.map → chunk-TIGYOZBU.js.map} +0 -0
- /package/dist/{chunk-Z45353ML.js.map → chunk-UH6ROXFJ.js.map} +0 -0
- /package/dist/{chunk-C2AYUXEF.js.map → chunk-UYUFDTIX.js.map} +0 -0
- /package/dist/{chunk-2AKIVSHP.js.map → chunk-W26D2USW.js.map} +0 -0
- /package/dist/{chunk-FZVZYU5C.js.map → chunk-W7K5BP3M.js.map} +0 -0
- /package/dist/{chunk-ZARHK7PT.js.map → chunk-WWH7GJ42.js.map} +0 -0
- /package/dist/{chunk-4NLHEE2Z.js.map → chunk-XLCEFUAU.js.map} +0 -0
- /package/dist/{chunk-PWEIZCPS.js.map → chunk-XOFIJGB2.js.map} +0 -0
- /package/dist/{chunk-TRSKATZQ.js.map → chunk-XZ66U5M3.js.map} +0 -0
- /package/dist/{chunk-UUZ7KZAO.js.map → chunk-Y33KJ556.js.map} +0 -0
- /package/dist/{chunk-BU4W2ISH.js.map → chunk-YJQJRXUI.js.map} +0 -0
- /package/dist/{chunk-QBK3IM6W.js.map → chunk-YSATDEBY.js.map} +0 -0
- /package/dist/{chunk-WR5POHQ3.js.map → chunk-YUTT4ZTI.js.map} +0 -0
- /package/dist/{chunk-JCL7RO2G.js.map → chunk-ZOBYTRBU.js.map} +0 -0
- /package/dist/{chunk-FMOUUWQN.js.map → chunk-ZPLQ27MY.js.map} +0 -0
- /package/dist/{entity-canonical-id-migration-CUDCKWPF.js.map → entity-canonical-id-migration-JJSTZF73.js.map} +0 -0
- /package/dist/{forget-E6ZTUKEC.js.map → forget-DE56ICCL.js.map} +0 -0
- /package/dist/{graph-edge-decay-2BZTY2IM.js.map → graph-edge-decay-OEX5OGMI.js.map} +0 -0
- /package/dist/{resolution-NNYG37YB.js.map → resolution-BNWE5Y25.js.map} +0 -0
|
@@ -13,6 +13,7 @@ import { SmartBuffer } from "./buffer.js";
|
|
|
13
13
|
import { parseConfig } from "./config.js";
|
|
14
14
|
import { stableHash } from "./coding/git-context.js";
|
|
15
15
|
import type { BufferState, BufferTurn, PluginConfig } from "./types.js";
|
|
16
|
+
import type { MetaState } from "./types.js";
|
|
16
17
|
import type { ImportTurn } from "./bulk-import/types.js";
|
|
17
18
|
import type { LcmEngine } from "./lcm/index.js";
|
|
18
19
|
import type { ExtractionRunResult } from "./orchestration/extraction-run.js";
|
|
@@ -20,6 +21,24 @@ import { namespaceIdentityToken } from "./namespaces/identity.js";
|
|
|
20
21
|
import { readNamespaceMaintenanceStatuses } from "./maintenance/namespace-planner.js";
|
|
21
22
|
import { stubPersistExtraction } from "./testing/orchestrator-lite.js";
|
|
22
23
|
|
|
24
|
+
/** Adds the `updateMeta` seam to a StorageManager stub that only has loadMeta/saveMeta. */
|
|
25
|
+
function withMetaSeam<S extends { loadMeta: () => Promise<MetaState>; saveMeta: (meta: MetaState) => Promise<void> }>(
|
|
26
|
+
storage: S,
|
|
27
|
+
): S & { updateMeta: (mutate: (meta: MetaState) => boolean | void | Promise<boolean | void>) => Promise<void> } {
|
|
28
|
+
// SAFETY: the constraint above declares exactly the loadMeta/saveMeta shape every
|
|
29
|
+
// stub here provides; re-typing to `S & { updateMeta }` adds one member and keeps
|
|
30
|
+
// all other members of S unchanged.
|
|
31
|
+
const updatable = storage as S & {
|
|
32
|
+
updateMeta: (mutate: (meta: MetaState) => boolean | void | Promise<boolean | void>) => Promise<void>;
|
|
33
|
+
};
|
|
34
|
+
updatable.updateMeta = async (mutate) => {
|
|
35
|
+
const meta = await storage.loadMeta();
|
|
36
|
+
if ((await mutate(meta)) === false) return;
|
|
37
|
+
await storage.saveMeta(meta);
|
|
38
|
+
};
|
|
39
|
+
return updatable;
|
|
40
|
+
}
|
|
41
|
+
|
|
23
42
|
function makeTurn(sessionKey: string, content: string, sessionOwnerPrincipal?: string): BufferTurn {
|
|
24
43
|
return {
|
|
25
44
|
role: "user",
|
|
@@ -1525,7 +1544,7 @@ test("runExtraction writes buffered turns to active scope profile write layer",
|
|
|
1525
1544
|
orchestrator.storageRouter = {
|
|
1526
1545
|
storageFor: async (namespace: string) => {
|
|
1527
1546
|
requestedNamespace = namespace;
|
|
1528
|
-
return {
|
|
1547
|
+
return withMetaSeam({
|
|
1529
1548
|
listEntityNames: async () => [],
|
|
1530
1549
|
loadMeta: async () => ({
|
|
1531
1550
|
extractionCount: 0,
|
|
@@ -1541,7 +1560,7 @@ test("runExtraction writes buffered turns to active scope profile write layer",
|
|
|
1541
1560
|
],
|
|
1542
1561
|
}),
|
|
1543
1562
|
saveMeta: async () => undefined,
|
|
1544
|
-
};
|
|
1563
|
+
});
|
|
1545
1564
|
},
|
|
1546
1565
|
};
|
|
1547
1566
|
orchestrator.extraction = {
|
|
@@ -1570,7 +1589,7 @@ test("runExtraction skips batches whose persisted fingerprint already exists in
|
|
|
1570
1589
|
},
|
|
1571
1590
|
};
|
|
1572
1591
|
orchestrator.storageRouter = {
|
|
1573
|
-
storageFor: async () => ({
|
|
1592
|
+
storageFor: async () => withMetaSeam({
|
|
1574
1593
|
listEntityNames: async () => [],
|
|
1575
1594
|
loadMeta: async () => ({
|
|
1576
1595
|
extractionCount: 0,
|
|
@@ -1651,7 +1670,7 @@ test("runExtraction fails closed on invalid extraction results when required", a
|
|
|
1651
1670
|
},
|
|
1652
1671
|
};
|
|
1653
1672
|
orchestrator.storageRouter = {
|
|
1654
|
-
storageFor: async () => ({
|
|
1673
|
+
storageFor: async () => withMetaSeam({
|
|
1655
1674
|
listEntityNames: async () => [],
|
|
1656
1675
|
loadMeta: async () => ({
|
|
1657
1676
|
extractionCount: 0,
|
|
@@ -1689,18 +1708,7 @@ test("runExtraction persists processed fingerprints for empty extraction results
|
|
|
1689
1708
|
|
|
1690
1709
|
let clearCalls = 0;
|
|
1691
1710
|
let saveMetaCalls = 0;
|
|
1692
|
-
let savedMeta:
|
|
1693
|
-
| {
|
|
1694
|
-
extractionCount: number;
|
|
1695
|
-
lastExtractionAt: string | null;
|
|
1696
|
-
totalMemories: number;
|
|
1697
|
-
totalEntities: number;
|
|
1698
|
-
processedExtractionFingerprints: Array<{
|
|
1699
|
-
fingerprint: string;
|
|
1700
|
-
observedAt: string;
|
|
1701
|
-
}>;
|
|
1702
|
-
}
|
|
1703
|
-
| undefined;
|
|
1711
|
+
let savedMeta: MetaState | undefined;
|
|
1704
1712
|
|
|
1705
1713
|
const meta = {
|
|
1706
1714
|
extractionCount: 0,
|
|
@@ -1722,10 +1730,10 @@ test("runExtraction persists processed fingerprints for empty extraction results
|
|
|
1722
1730
|
},
|
|
1723
1731
|
};
|
|
1724
1732
|
orchestrator.storageRouter = {
|
|
1725
|
-
storageFor: async () => ({
|
|
1733
|
+
storageFor: async () => withMetaSeam({
|
|
1726
1734
|
listEntityNames: async () => [],
|
|
1727
1735
|
loadMeta: async () => meta,
|
|
1728
|
-
saveMeta: async (nextMeta:
|
|
1736
|
+
saveMeta: async (nextMeta: MetaState) => {
|
|
1729
1737
|
saveMetaCalls += 1;
|
|
1730
1738
|
savedMeta = structuredClone(nextMeta);
|
|
1731
1739
|
},
|
|
@@ -1762,9 +1770,9 @@ test("runExtraction persists processed fingerprints for empty extraction results
|
|
|
1762
1770
|
assert.equal(savedMeta?.extractionCount, 1);
|
|
1763
1771
|
assert.equal(savedMeta?.totalMemories, 0);
|
|
1764
1772
|
assert.equal(savedMeta?.totalEntities, 0);
|
|
1765
|
-
assert.equal(savedMeta?.processedExtractionFingerprints
|
|
1773
|
+
assert.equal(savedMeta?.processedExtractionFingerprints?.length, 1);
|
|
1766
1774
|
assert.equal(
|
|
1767
|
-
savedMeta?.processedExtractionFingerprints[0]?.fingerprint,
|
|
1775
|
+
savedMeta?.processedExtractionFingerprints?.[0]?.fingerprint,
|
|
1768
1776
|
orchestrator.buildExtractionFingerprint(turns, "logical-thread:empty"),
|
|
1769
1777
|
);
|
|
1770
1778
|
});
|
|
@@ -1797,7 +1805,7 @@ test("runExtraction does not persist processed fingerprints for failed empty ext
|
|
|
1797
1805
|
},
|
|
1798
1806
|
};
|
|
1799
1807
|
orchestrator.storageRouter = {
|
|
1800
|
-
storageFor: async () => ({
|
|
1808
|
+
storageFor: async () => withMetaSeam({
|
|
1801
1809
|
listEntityNames: async () => [],
|
|
1802
1810
|
loadMeta: async () => meta,
|
|
1803
1811
|
saveMeta: async () => {
|
|
@@ -1853,7 +1861,7 @@ test("runExtraction clears the buffer on a failed extraction when extractionRetr
|
|
|
1853
1861
|
orchestrator.config = config;
|
|
1854
1862
|
orchestrator.buffer = { clearAfterExtraction: async () => { clearCalls += 1; } };
|
|
1855
1863
|
orchestrator.storageRouter = {
|
|
1856
|
-
storageFor: async () => ({
|
|
1864
|
+
storageFor: async () => withMetaSeam({
|
|
1857
1865
|
listEntityNames: async () => [],
|
|
1858
1866
|
loadMeta: async () => ({ extractionCount: 0, lastExtractionAt: null, lastConsolidationAt: null, totalMemories: 0, totalEntities: 0, processedExtractionFingerprints: [] }),
|
|
1859
1867
|
saveMeta: async () => {},
|
|
@@ -1870,6 +1878,45 @@ test("runExtraction clears the buffer on a failed extraction when extractionRetr
|
|
|
1870
1878
|
assert.equal(clearCalls, 1, "retry disabled → buffer cleared (pre-#1908 behavior)");
|
|
1871
1879
|
});
|
|
1872
1880
|
|
|
1881
|
+
test("runExtraction retains buffers and keeps in-memory backoff when the meta lock is unavailable", async () => {
|
|
1882
|
+
// Lock contention makes updateMeta fail closed without running the mutator.
|
|
1883
|
+
// The retry gate reads the in-memory mirror, so the failure must still park
|
|
1884
|
+
// backoff there and retain the buffer — a cleared buffer with no retry state
|
|
1885
|
+
// silently drops the turns (base behavior retained them).
|
|
1886
|
+
const config = parseConfig({});
|
|
1887
|
+
config.extractionMinChars = 0;
|
|
1888
|
+
config.extractionMinUserTurns = 1;
|
|
1889
|
+
|
|
1890
|
+
let clearCalls = 0;
|
|
1891
|
+
let updateMetaCalls = 0;
|
|
1892
|
+
// SAFETY: runExtraction is driven only through the stub members assigned below;
|
|
1893
|
+
// the prototype harness leaves the rest of Orchestrator unreachable in this test.
|
|
1894
|
+
const orchestrator = Object.create(Orchestrator.prototype) as any;
|
|
1895
|
+
orchestrator.config = config;
|
|
1896
|
+
orchestrator.buffer = { clearAfterExtraction: async () => { clearCalls += 1; } };
|
|
1897
|
+
orchestrator.storageRouter = {
|
|
1898
|
+
storageFor: async () => ({
|
|
1899
|
+
listEntityNames: async () => [],
|
|
1900
|
+
loadMeta: async () => ({ extractionCount: 0, lastExtractionAt: null, lastConsolidationAt: null, totalMemories: 0, totalEntities: 0, processedExtractionFingerprints: [] }),
|
|
1901
|
+
saveMeta: async () => {},
|
|
1902
|
+
updateMeta: async () => {
|
|
1903
|
+
updateMetaCalls += 1;
|
|
1904
|
+
throw new Error("meta update skipped: meta.json.lock not acquired (test)");
|
|
1905
|
+
},
|
|
1906
|
+
}),
|
|
1907
|
+
};
|
|
1908
|
+
orchestrator.extraction = {
|
|
1909
|
+
extract: async () => ({ facts: [], entities: [], questions: [], profileUpdates: [], extractionFailure: "gateway_unavailable" }),
|
|
1910
|
+
};
|
|
1911
|
+
stubPersistExtraction(orchestrator);
|
|
1912
|
+
const turns = [{ ...makeTurn("session-lock-fail", "failed gateway under lock contention"), logicalSessionKey: "logical-thread:lock-fail", turnFingerprint: "fp-lock-fail", persistProcessedFingerprint: true }];
|
|
1913
|
+
const result = await orchestrator.runExtraction(turns, { bufferKey: "logical-thread:lock-fail" });
|
|
1914
|
+
assert.equal(result.status, "skipped");
|
|
1915
|
+
assert.equal(result.reason, "empty_extraction_result");
|
|
1916
|
+
assert.equal(updateMetaCalls, 1);
|
|
1917
|
+
assert.equal(clearCalls, 0, "buffer must be retained when retry state could not persist");
|
|
1918
|
+
});
|
|
1919
|
+
|
|
1873
1920
|
test("runExtraction preserves empty-result buffers when fingerprint persistence fails", async () => {
|
|
1874
1921
|
const config = parseConfig({});
|
|
1875
1922
|
config.extractionMinChars = 0;
|
|
@@ -1897,7 +1944,7 @@ test("runExtraction preserves empty-result buffers when fingerprint persistence
|
|
|
1897
1944
|
},
|
|
1898
1945
|
};
|
|
1899
1946
|
orchestrator.storageRouter = {
|
|
1900
|
-
storageFor: async () => ({
|
|
1947
|
+
storageFor: async () => withMetaSeam({
|
|
1901
1948
|
listEntityNames: async () => [],
|
|
1902
1949
|
loadMeta: async () => meta,
|
|
1903
1950
|
saveMeta: async () => {
|
|
@@ -1961,7 +2008,7 @@ test("runExtraction keeps the empty live path fail-open when liveness metadata w
|
|
|
1961
2008
|
},
|
|
1962
2009
|
};
|
|
1963
2010
|
orchestrator.storageRouter = {
|
|
1964
|
-
storageFor: async () => ({
|
|
2011
|
+
storageFor: async () => withMetaSeam({
|
|
1965
2012
|
listEntityNames: async () => [],
|
|
1966
2013
|
loadMeta: async () => meta,
|
|
1967
2014
|
saveMeta: async () => {
|
|
@@ -2003,7 +2050,7 @@ test("runExtraction preserves deduped buffers when the caller aborts during meta
|
|
|
2003
2050
|
},
|
|
2004
2051
|
};
|
|
2005
2052
|
orchestrator.storageRouter = {
|
|
2006
|
-
storageFor: async () => ({
|
|
2053
|
+
storageFor: async () => withMetaSeam({
|
|
2007
2054
|
listEntityNames: async () => [],
|
|
2008
2055
|
loadMeta: async () => {
|
|
2009
2056
|
abortController.abort();
|
|
@@ -2075,7 +2122,7 @@ test("runExtraction still clears the buffer when fingerprint persistence fails a
|
|
|
2075
2122
|
},
|
|
2076
2123
|
};
|
|
2077
2124
|
orchestrator.storageRouter = {
|
|
2078
|
-
storageFor: async () => ({
|
|
2125
|
+
storageFor: async () => withMetaSeam({
|
|
2079
2126
|
listEntityNames: async () => [],
|
|
2080
2127
|
loadMeta: async () => ({
|
|
2081
2128
|
extractionCount: 0,
|
|
@@ -2138,18 +2185,7 @@ test("runExtraction persists fingerprint and extraction counters through one coh
|
|
|
2138
2185
|
let clearCalls = 0;
|
|
2139
2186
|
let loadMetaCalls = 0;
|
|
2140
2187
|
let saveMetaCalls = 0;
|
|
2141
|
-
let savedMeta:
|
|
2142
|
-
| {
|
|
2143
|
-
extractionCount: number;
|
|
2144
|
-
lastExtractionAt: string | null;
|
|
2145
|
-
totalMemories: number;
|
|
2146
|
-
totalEntities: number;
|
|
2147
|
-
processedExtractionFingerprints: Array<{
|
|
2148
|
-
fingerprint: string;
|
|
2149
|
-
observedAt: string;
|
|
2150
|
-
}>;
|
|
2151
|
-
}
|
|
2152
|
-
| undefined;
|
|
2188
|
+
let savedMeta: MetaState | undefined;
|
|
2153
2189
|
|
|
2154
2190
|
const meta = {
|
|
2155
2191
|
extractionCount: 0,
|
|
@@ -2171,13 +2207,13 @@ test("runExtraction persists fingerprint and extraction counters through one coh
|
|
|
2171
2207
|
},
|
|
2172
2208
|
};
|
|
2173
2209
|
orchestrator.storageRouter = {
|
|
2174
|
-
storageFor: async () => ({
|
|
2210
|
+
storageFor: async () => withMetaSeam({
|
|
2175
2211
|
listEntityNames: async () => [],
|
|
2176
2212
|
loadMeta: async () => {
|
|
2177
2213
|
loadMetaCalls += 1;
|
|
2178
2214
|
return meta;
|
|
2179
2215
|
},
|
|
2180
|
-
saveMeta: async (nextMeta:
|
|
2216
|
+
saveMeta: async (nextMeta: MetaState) => {
|
|
2181
2217
|
saveMetaCalls += 1;
|
|
2182
2218
|
savedMeta = structuredClone(nextMeta);
|
|
2183
2219
|
},
|
|
@@ -2216,14 +2252,17 @@ test("runExtraction persists fingerprint and extraction counters through one coh
|
|
|
2216
2252
|
},
|
|
2217
2253
|
);
|
|
2218
2254
|
|
|
2219
|
-
|
|
2255
|
+
// The counters and fingerprint land on a FRESHLY loaded meta (the serialized
|
|
2256
|
+
// updateMeta re-read), so more than one load is expected; the single save is
|
|
2257
|
+
// the coherent-write contract under test.
|
|
2258
|
+
assert.ok(loadMetaCalls >= 2, 'expected a fresh meta read before the save');
|
|
2220
2259
|
assert.equal(saveMetaCalls, 1);
|
|
2221
2260
|
assert.equal(clearCalls, 1);
|
|
2222
2261
|
assert.equal(savedMeta?.extractionCount, 1);
|
|
2223
2262
|
assert.equal(savedMeta?.totalMemories, 1);
|
|
2224
|
-
assert.equal(savedMeta?.processedExtractionFingerprints
|
|
2263
|
+
assert.equal(savedMeta?.processedExtractionFingerprints?.length, 1);
|
|
2225
2264
|
assert.equal(
|
|
2226
|
-
savedMeta?.processedExtractionFingerprints[0]?.fingerprint,
|
|
2265
|
+
savedMeta?.processedExtractionFingerprints?.[0]?.fingerprint,
|
|
2227
2266
|
orchestrator.buildExtractionFingerprint(
|
|
2228
2267
|
[
|
|
2229
2268
|
{
|
|
@@ -2246,18 +2285,7 @@ test("runExtraction loads meta before updating extraction counters when fingerpr
|
|
|
2246
2285
|
let clearCalls = 0;
|
|
2247
2286
|
let loadMetaCalls = 0;
|
|
2248
2287
|
let saveMetaCalls = 0;
|
|
2249
|
-
let savedMeta:
|
|
2250
|
-
| {
|
|
2251
|
-
extractionCount: number;
|
|
2252
|
-
lastExtractionAt: string | null;
|
|
2253
|
-
totalMemories: number;
|
|
2254
|
-
totalEntities: number;
|
|
2255
|
-
processedExtractionFingerprints: Array<{
|
|
2256
|
-
fingerprint: string;
|
|
2257
|
-
observedAt: string;
|
|
2258
|
-
}>;
|
|
2259
|
-
}
|
|
2260
|
-
| undefined;
|
|
2288
|
+
let savedMeta: MetaState | undefined;
|
|
2261
2289
|
|
|
2262
2290
|
const meta = {
|
|
2263
2291
|
extractionCount: 0,
|
|
@@ -2279,13 +2307,13 @@ test("runExtraction loads meta before updating extraction counters when fingerpr
|
|
|
2279
2307
|
},
|
|
2280
2308
|
};
|
|
2281
2309
|
orchestrator.storageRouter = {
|
|
2282
|
-
storageFor: async () => ({
|
|
2310
|
+
storageFor: async () => withMetaSeam({
|
|
2283
2311
|
listEntityNames: async () => [],
|
|
2284
2312
|
loadMeta: async () => {
|
|
2285
2313
|
loadMetaCalls += 1;
|
|
2286
2314
|
return meta;
|
|
2287
2315
|
},
|
|
2288
|
-
saveMeta: async (nextMeta:
|
|
2316
|
+
saveMeta: async (nextMeta: MetaState) => {
|
|
2289
2317
|
saveMetaCalls += 1;
|
|
2290
2318
|
savedMeta = structuredClone(nextMeta);
|
|
2291
2319
|
},
|
|
@@ -2314,12 +2342,15 @@ test("runExtraction loads meta before updating extraction counters when fingerpr
|
|
|
2314
2342
|
bufferKey: "logical-thread:thread-15",
|
|
2315
2343
|
});
|
|
2316
2344
|
|
|
2317
|
-
|
|
2345
|
+
// The counters and fingerprint land on a FRESHLY loaded meta (the serialized
|
|
2346
|
+
// updateMeta re-read), so more than one load is expected; the single save is
|
|
2347
|
+
// the coherent-write contract under test.
|
|
2348
|
+
assert.ok(loadMetaCalls >= 2, 'expected a fresh meta read before the save');
|
|
2318
2349
|
assert.equal(saveMetaCalls, 1);
|
|
2319
2350
|
assert.equal(clearCalls, 1);
|
|
2320
2351
|
assert.equal(savedMeta?.extractionCount, 1);
|
|
2321
2352
|
assert.equal(savedMeta?.totalMemories, 1);
|
|
2322
|
-
assert.equal(savedMeta?.processedExtractionFingerprints
|
|
2353
|
+
assert.equal(savedMeta?.processedExtractionFingerprints?.length, 0);
|
|
2323
2354
|
});
|
|
2324
2355
|
|
|
2325
2356
|
test("runExtraction completes after late threading failures and saves the processed fingerprint", async () => {
|
|
@@ -2330,18 +2361,7 @@ test("runExtraction completes after late threading failures and saves the proces
|
|
|
2330
2361
|
|
|
2331
2362
|
let clearCalls = 0;
|
|
2332
2363
|
let saveMetaCalls = 0;
|
|
2333
|
-
let savedMeta:
|
|
2334
|
-
| {
|
|
2335
|
-
extractionCount: number;
|
|
2336
|
-
lastExtractionAt: string | null;
|
|
2337
|
-
totalMemories: number;
|
|
2338
|
-
totalEntities: number;
|
|
2339
|
-
processedExtractionFingerprints: Array<{
|
|
2340
|
-
fingerprint: string;
|
|
2341
|
-
observedAt: string;
|
|
2342
|
-
}>;
|
|
2343
|
-
}
|
|
2344
|
-
| undefined;
|
|
2364
|
+
let savedMeta: MetaState | undefined;
|
|
2345
2365
|
|
|
2346
2366
|
const meta = {
|
|
2347
2367
|
extractionCount: 0,
|
|
@@ -2363,10 +2383,10 @@ test("runExtraction completes after late threading failures and saves the proces
|
|
|
2363
2383
|
},
|
|
2364
2384
|
};
|
|
2365
2385
|
orchestrator.storageRouter = {
|
|
2366
|
-
storageFor: async () => ({
|
|
2386
|
+
storageFor: async () => withMetaSeam({
|
|
2367
2387
|
listEntityNames: async () => [],
|
|
2368
2388
|
loadMeta: async () => meta,
|
|
2369
|
-
saveMeta: async (nextMeta:
|
|
2389
|
+
saveMeta: async (nextMeta: MetaState) => {
|
|
2370
2390
|
saveMetaCalls += 1;
|
|
2371
2391
|
savedMeta = structuredClone(nextMeta);
|
|
2372
2392
|
},
|
|
@@ -2420,9 +2440,9 @@ test("runExtraction completes after late threading failures and saves the proces
|
|
|
2420
2440
|
assert.equal(result.postPersistMetadataFailed, false);
|
|
2421
2441
|
assert.equal(saveMetaCalls, 1);
|
|
2422
2442
|
assert.equal(clearCalls, 1);
|
|
2423
|
-
assert.equal(savedMeta?.processedExtractionFingerprints
|
|
2443
|
+
assert.equal(savedMeta?.processedExtractionFingerprints?.length, 1);
|
|
2424
2444
|
assert.equal(
|
|
2425
|
-
savedMeta?.processedExtractionFingerprints[0]?.fingerprint,
|
|
2445
|
+
savedMeta?.processedExtractionFingerprints?.[0]?.fingerprint,
|
|
2426
2446
|
orchestrator.buildExtractionFingerprint(
|
|
2427
2447
|
[
|
|
2428
2448
|
{
|
|
@@ -2465,7 +2485,7 @@ test("runExtraction completes and clears the buffer when the post-persist meta s
|
|
|
2465
2485
|
},
|
|
2466
2486
|
};
|
|
2467
2487
|
orchestrator.storageRouter = {
|
|
2468
|
-
storageFor: async () => ({
|
|
2488
|
+
storageFor: async () => withMetaSeam({
|
|
2469
2489
|
listEntityNames: async () => [],
|
|
2470
2490
|
loadMeta: async () => meta,
|
|
2471
2491
|
saveMeta: async () => {
|
|
@@ -2549,7 +2569,7 @@ test("runExtraction still runs follow-on extraction helpers when the post-persis
|
|
|
2549
2569
|
},
|
|
2550
2570
|
};
|
|
2551
2571
|
orchestrator.storageRouter = {
|
|
2552
|
-
storageFor: async () => ({
|
|
2572
|
+
storageFor: async () => withMetaSeam({
|
|
2553
2573
|
listEntityNames: async () => [],
|
|
2554
2574
|
loadMeta: async () => meta,
|
|
2555
2575
|
saveMeta: async () => {
|
|
@@ -2654,13 +2674,15 @@ test("runExtraction aborts before late buffer clearing when the caller cancels",
|
|
|
2654
2674
|
},
|
|
2655
2675
|
};
|
|
2656
2676
|
orchestrator.storageRouter = {
|
|
2657
|
-
storageFor: async () => ({
|
|
2677
|
+
storageFor: async () => withMetaSeam({
|
|
2658
2678
|
listEntityNames: async () => [],
|
|
2659
2679
|
loadMeta: async () => ({
|
|
2660
2680
|
extractionCount: 0,
|
|
2661
2681
|
lastExtractionAt: null,
|
|
2682
|
+
lastConsolidationAt: null,
|
|
2662
2683
|
totalMemories: 0,
|
|
2663
2684
|
totalEntities: 0,
|
|
2685
|
+
processedExtractionFingerprints: [],
|
|
2664
2686
|
}),
|
|
2665
2687
|
saveMeta: async () => undefined,
|
|
2666
2688
|
}),
|
|
@@ -2716,13 +2738,15 @@ test("runExtraction still clears the session buffer after persistence even if re
|
|
|
2716
2738
|
},
|
|
2717
2739
|
};
|
|
2718
2740
|
orchestrator.storageRouter = {
|
|
2719
|
-
storageFor: async () => ({
|
|
2741
|
+
storageFor: async () => withMetaSeam({
|
|
2720
2742
|
listEntityNames: async () => [],
|
|
2721
2743
|
loadMeta: async () => ({
|
|
2722
2744
|
extractionCount: 0,
|
|
2723
2745
|
lastExtractionAt: null,
|
|
2746
|
+
lastConsolidationAt: null,
|
|
2724
2747
|
totalMemories: 0,
|
|
2725
2748
|
totalEntities: 0,
|
|
2749
|
+
processedExtractionFingerprints: [],
|
|
2726
2750
|
}),
|
|
2727
2751
|
saveMeta: async () => undefined,
|
|
2728
2752
|
}),
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import test from "node:test";
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
import { mkdir, mkdtemp, rm, stat } from "node:fs/promises";
|
|
4
|
+
import os from "node:os";
|
|
5
|
+
import path from "node:path";
|
|
6
|
+
import { StorageManager } from "./storage.js";
|
|
7
|
+
import { withHeldFileLock } from "./utils/serialize-mutations.js";
|
|
8
|
+
|
|
9
|
+
// Real wall-clock is deliberate here: the lock helper's acquire budget and
|
|
10
|
+
// heartbeat polling run on platform timers, and the contention windows below
|
|
11
|
+
// exist to bracket updateMeta's real 2s acquire budget. Fake timers cannot
|
|
12
|
+
// drive the helper's internal polling loop.
|
|
13
|
+
function delay(ms: number): Promise<void> {
|
|
14
|
+
const { promise, resolve } = Promise.withResolvers<void>();
|
|
15
|
+
setTimeout(resolve, ms);
|
|
16
|
+
return promise;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/** Await the real signal "holder owns the lock file", not a guessed duration. */
|
|
20
|
+
async function waitForLockFile(lockPath: string): Promise<void> {
|
|
21
|
+
const deadline = Date.now() + 2_000;
|
|
22
|
+
for (;;) {
|
|
23
|
+
try {
|
|
24
|
+
await stat(lockPath);
|
|
25
|
+
return;
|
|
26
|
+
} catch {
|
|
27
|
+
if (Date.now() >= deadline) throw new Error(`lock file never appeared: ${lockPath}`);
|
|
28
|
+
await delay(20);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
async function makeStorage(): Promise<{
|
|
34
|
+
storage: StorageManager;
|
|
35
|
+
lockPath: string;
|
|
36
|
+
cleanup: () => Promise<void>;
|
|
37
|
+
}> {
|
|
38
|
+
const root = await mkdtemp(path.join(os.tmpdir(), "remnic-meta-lock-"));
|
|
39
|
+
await mkdir(path.join(root, "state"), { recursive: true });
|
|
40
|
+
return {
|
|
41
|
+
storage: new StorageManager(root),
|
|
42
|
+
lockPath: path.join(root, "state", "meta.json.lock"),
|
|
43
|
+
cleanup: () => rm(root, { recursive: true, force: true }),
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
test("updateMeta fails closed when meta.json.lock stays held past the acquire budget", async () => {
|
|
48
|
+
const { storage, lockPath, cleanup } = await makeStorage();
|
|
49
|
+
try {
|
|
50
|
+
await storage.updateMeta((meta) => {
|
|
51
|
+
meta.extractionCount = 1;
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
// A live external holder (a different owner id through the same helper the
|
|
55
|
+
// out-of-process drain workers use) keeps the lock past updateMeta's 2s
|
|
56
|
+
// wait budget. The update must fail instead of writing unlocked.
|
|
57
|
+
const hold = withHeldFileLock(
|
|
58
|
+
lockPath,
|
|
59
|
+
{ staleMs: 30_000, maxWaitMs: 30_000, pollMs: 50 },
|
|
60
|
+
async () => {
|
|
61
|
+
await delay(2_600);
|
|
62
|
+
},
|
|
63
|
+
);
|
|
64
|
+
await waitForLockFile(lockPath);
|
|
65
|
+
|
|
66
|
+
await assert.rejects(
|
|
67
|
+
storage.updateMeta((meta) => {
|
|
68
|
+
meta.extractionCount = 2;
|
|
69
|
+
}),
|
|
70
|
+
/not acquired/,
|
|
71
|
+
);
|
|
72
|
+
assert.equal(
|
|
73
|
+
(await storage.loadMeta()).extractionCount,
|
|
74
|
+
1,
|
|
75
|
+
"a timed-out update must not perform an unlocked read-modify-write",
|
|
76
|
+
);
|
|
77
|
+
|
|
78
|
+
await hold;
|
|
79
|
+
await storage.updateMeta((meta) => {
|
|
80
|
+
meta.extractionCount = 3;
|
|
81
|
+
});
|
|
82
|
+
assert.equal((await storage.loadMeta()).extractionCount, 3);
|
|
83
|
+
} finally {
|
|
84
|
+
await cleanup();
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
test("concurrent updateMeta additive updates both survive short external contention", async () => {
|
|
89
|
+
const { storage, lockPath, cleanup } = await makeStorage();
|
|
90
|
+
try {
|
|
91
|
+
const hold = withHeldFileLock(
|
|
92
|
+
lockPath,
|
|
93
|
+
{ staleMs: 30_000, maxWaitMs: 30_000, pollMs: 50 },
|
|
94
|
+
async () => {
|
|
95
|
+
await delay(300);
|
|
96
|
+
},
|
|
97
|
+
);
|
|
98
|
+
await waitForLockFile(lockPath);
|
|
99
|
+
await Promise.all([
|
|
100
|
+
storage.updateMeta((meta) => {
|
|
101
|
+
meta.totalMemories += 5;
|
|
102
|
+
}),
|
|
103
|
+
storage.updateMeta((meta) => {
|
|
104
|
+
meta.totalEntities += 7;
|
|
105
|
+
}),
|
|
106
|
+
]);
|
|
107
|
+
await hold;
|
|
108
|
+
const meta = await storage.loadMeta();
|
|
109
|
+
assert.equal(meta.totalMemories, 5);
|
|
110
|
+
assert.equal(meta.totalEntities, 7);
|
|
111
|
+
} finally {
|
|
112
|
+
await cleanup();
|
|
113
|
+
}
|
|
114
|
+
});
|
package/src/storage.ts
CHANGED
|
@@ -34,6 +34,7 @@ import { appendContextTransformRecords, readContextTransformRecords } from "./st
|
|
|
34
34
|
import type { ContextTransformTelemetryRecord } from "./active-context-transform.js";
|
|
35
35
|
import { readMaybeEncryptedLines, readMemoryActionEventRowsFromLines } from "./storage/secure-line-reader.js";
|
|
36
36
|
import { parseOfflineSyncDigestCache, type OfflineSyncDigestCacheEntry } from "./offline-sync-digest-cache.js";
|
|
37
|
+
import { updateMetaSerialized, withHeldFileLock } from "./utils/serialize-mutations.js";
|
|
37
38
|
import {
|
|
38
39
|
appendLifecycleEventsSerialized,
|
|
39
40
|
type DrainPendingLifecycleForSyncResult,
|
|
@@ -5558,12 +5559,8 @@ export class StorageManager extends TombstoneBlockedCaptureIndexHost {
|
|
|
5558
5559
|
if (err instanceof SecureStoreLockedError) throw err;
|
|
5559
5560
|
if (!isErrnoCode(err, "ENOENT")) throw err;
|
|
5560
5561
|
return {
|
|
5561
|
-
extractionCount: 0,
|
|
5562
|
-
|
|
5563
|
-
lastConsolidationAt: null,
|
|
5564
|
-
totalMemories: 0,
|
|
5565
|
-
totalEntities: 0,
|
|
5566
|
-
processedExtractionFingerprints: [],
|
|
5562
|
+
extractionCount: 0, lastExtractionAt: null, lastConsolidationAt: null,
|
|
5563
|
+
totalMemories: 0, totalEntities: 0, processedExtractionFingerprints: [],
|
|
5567
5564
|
};
|
|
5568
5565
|
}
|
|
5569
5566
|
}
|
|
@@ -5573,6 +5570,10 @@ export class StorageManager extends TombstoneBlockedCaptureIndexHost {
|
|
|
5573
5570
|
await this.writeStorageSecureFile(path.join(this.stateDir, "meta.json"), JSON.stringify(state, null, 2));
|
|
5574
5571
|
}
|
|
5575
5572
|
|
|
5573
|
+
async updateMeta(mutate: (meta: MetaState) => boolean | void | Promise<boolean | void>): Promise<void> {
|
|
5574
|
+
await updateMetaSerialized(this.stateDir, () => this.loadMeta(), (meta) => this.saveMeta(meta), mutate);
|
|
5575
|
+
}
|
|
5576
|
+
|
|
5576
5577
|
async appendMemoryActionEvents(events: MemoryActionEvent[]): Promise<number> {
|
|
5577
5578
|
if (events.length === 0) return 0;
|
|
5578
5579
|
await this.ensureDirectories();
|
|
@@ -733,3 +733,56 @@ async function refreshHeldLock(
|
|
|
733
733
|
}
|
|
734
734
|
return lockHeldBySelf(held);
|
|
735
735
|
}
|
|
736
|
+
|
|
737
|
+
/**
|
|
738
|
+
* Process-wide chains serializing meta updates per state dir inside one
|
|
739
|
+
* process. The cross-process advisory lock below only orders DIFFERENT
|
|
740
|
+
* processes; without the chain, two same-process updates would contend on the
|
|
741
|
+
* lock file and one could time out to failure.
|
|
742
|
+
*/
|
|
743
|
+
const metaUpdateChains = new Map<string, Promise<unknown>>();
|
|
744
|
+
|
|
745
|
+
/**
|
|
746
|
+
* Serialized meta read-modify-write for one state dir (`<stateDir>/meta.json`).
|
|
747
|
+
* Parallel extraction workers (the daemon's bounded queue and out-of-process
|
|
748
|
+
* buffer-drain workers) each load meta, mutate counters, fingerprints or retry
|
|
749
|
+
* state, and save; unlocked, a slow writer's save silently reverts a fast
|
|
750
|
+
* writer's update. The chain orders same-process callers and the advisory lock
|
|
751
|
+
* file (`meta.json.lock`) orders different processes; a worker that cannot
|
|
752
|
+
* take the lock inside the budget FAILS the update instead of writing
|
|
753
|
+
* unlocked — callers are fail-open, so the outcome is a logged miss retried by
|
|
754
|
+
* the next extraction, never a silent lost update. The mutator may return
|
|
755
|
+
* `false` to skip the save (nothing changed).
|
|
756
|
+
*/
|
|
757
|
+
export async function updateMetaSerialized<M>(
|
|
758
|
+
dir: string,
|
|
759
|
+
loadMeta: () => Promise<M>,
|
|
760
|
+
saveMeta: (meta: M) => Promise<void>,
|
|
761
|
+
mutate: (meta: M) => boolean | void | Promise<boolean | void>,
|
|
762
|
+
): Promise<void> {
|
|
763
|
+
const previous = metaUpdateChains.get(dir) ?? Promise.resolve();
|
|
764
|
+
const run = previous.then(() =>
|
|
765
|
+
withHeldFileLock(
|
|
766
|
+
path.join(dir, "meta.json.lock"),
|
|
767
|
+
// Bounded: a meta update is a short read-merge-write, never a scan.
|
|
768
|
+
{ staleMs: 5_000, maxWaitMs: 2_000, pollMs: 50 },
|
|
769
|
+
async (acquired) => {
|
|
770
|
+
// A lock we could not take (busy peer past the wait budget, or a
|
|
771
|
+
// filesystem error) must NOT fall back to an unlocked
|
|
772
|
+
// read-modify-write: that silently reintroduces the lost-update race
|
|
773
|
+
// this function exists to close. Fail instead — every caller treats
|
|
774
|
+
// meta updates as fail-open, so the outcome is a logged miss that
|
|
775
|
+
// the next extraction retries, never a silent clobber.
|
|
776
|
+
if (!acquired) {
|
|
777
|
+
throw new Error(`meta update skipped: meta.json.lock not acquired (${dir})`);
|
|
778
|
+
}
|
|
779
|
+
const meta = await loadMeta();
|
|
780
|
+
if ((await mutate(meta)) === false) return;
|
|
781
|
+
await saveMeta(meta);
|
|
782
|
+
},
|
|
783
|
+
),
|
|
784
|
+
);
|
|
785
|
+
// Every waiter keeps its own result/error; the chain only carries completion.
|
|
786
|
+
metaUpdateChains.set(dir, run.then(() => undefined, () => undefined));
|
|
787
|
+
await run;
|
|
788
|
+
}
|