@remnic/core 10.0.27 → 10.0.29
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 +54 -53
- package/dist/access-authorization-probe.js +55 -54
- package/dist/access-boundary.js +54 -53
- package/dist/access-citation-namespace.js +3 -2
- package/dist/access-cli.js +106 -105
- package/dist/access-cli.js.map +1 -1
- package/dist/access-extraction-force-flush.js +54 -53
- package/dist/access-http-briefing.js +55 -54
- package/dist/access-http-query.js +56 -55
- package/dist/access-http.js +73 -72
- package/dist/access-identity-continuity-surface.js +54 -53
- package/dist/access-lcm-surface.js +54 -53
- package/dist/access-mcp.js +65 -64
- package/dist/access-namespace-preflight.js +54 -53
- package/dist/access-observe-write-category.js +11 -11
- package/dist/access-observe-write-surface.js +54 -53
- package/dist/access-offline-manifest.js +10 -10
- package/dist/access-operations-batch.js +56 -55
- package/dist/access-operations.js +63 -62
- package/dist/access-recall-concurrency.js +54 -53
- package/dist/access-recall-diagnostics.js +2 -2
- package/dist/access-recall-response.js +54 -53
- package/dist/access-recall-surface.js +54 -53
- package/dist/access-schema.js +6 -5
- package/dist/access-service.js +54 -53
- package/dist/access-surface-catalog.js +56 -55
- package/dist/access-surface-catalog.js.map +1 -1
- package/dist/active-recall.js +24 -23
- package/dist/active-recall.js.map +1 -1
- package/dist/{auto-sync-UQESPLCB.js → auto-sync-MX2AHVPQ.js} +8 -8
- package/dist/briefing.js +20 -19
- package/dist/buffer.js +8 -8
- package/dist/calibration.js +2 -2
- package/dist/{capsule-merge-LGYURS3C.js → capsule-merge-LGYZAMO4.js} +3 -3
- package/dist/causal-consolidation.js +22 -21
- package/dist/causal-consolidation.js.map +1 -1
- package/dist/{chunk-G6XEYFCO.js → chunk-27OJ5G3D.js} +14 -14
- package/dist/{chunk-5LRV557D.js → chunk-2FIJQNL7.js} +2 -2
- package/dist/{chunk-ETPIYK4J.js → chunk-2VBDSZ7U.js} +2 -2
- package/dist/{chunk-QNKEMFZY.js → chunk-2WDMUQOH.js} +2 -2
- package/dist/{chunk-W6MOLW5S.js → chunk-2XVLY424.js} +4 -4
- package/dist/{chunk-P55FACBG.js → chunk-3BOQPLCF.js} +6 -6
- package/dist/{chunk-27SAUQ5R.js → chunk-3CHHQXAR.js} +2 -2
- package/dist/{chunk-3GYSZHMT.js → chunk-3CZRBTJK.js} +2 -2
- package/dist/{chunk-FJJWA7NL.js → chunk-3EJQXF3B.js} +2 -2
- package/dist/{chunk-HFSPY26A.js → chunk-3IXNL6SK.js} +2 -2
- package/dist/{chunk-PLD6SOU7.js → chunk-3SN2D43X.js} +2 -2
- package/dist/{chunk-COGLFRXT.js → chunk-4HNINU7Q.js} +31 -31
- package/dist/{chunk-CCDHRDQ3.js → chunk-4HYP4OKH.js} +2 -2
- package/dist/{chunk-GKJJNO7C.js → chunk-4OE4UE2Y.js} +6 -6
- package/dist/{chunk-7RTNJ6M2.js → chunk-4QPW6JVI.js} +2 -2
- package/dist/{chunk-5SMR542T.js → chunk-5GT4ESLR.js} +2 -2
- package/dist/{chunk-DWL6YIZH.js → chunk-5JI3XWI2.js} +2 -2
- package/dist/{chunk-27LPB4NS.js → chunk-5NPZFXQH.js} +2 -2
- package/dist/{chunk-Y6IF6H2U.js → chunk-5SMHQW45.js} +2 -2
- package/dist/{chunk-VN3GHPBU.js → chunk-654GT5UP.js} +2 -2
- package/dist/{chunk-3E6DGSGZ.js → chunk-6MC7UQ6D.js} +2 -2
- package/dist/{chunk-655FVTCD.js → chunk-74IN6PV5.js} +2 -2
- package/dist/{chunk-WFUO7NWX.js → chunk-7FZ4YVOH.js} +2 -2
- package/dist/{chunk-MWWAQXZ6.js → chunk-7HFTCBCZ.js} +2 -2
- package/dist/{chunk-7IOB5F7Y.js → chunk-7VB3Y5OL.js} +3 -3
- package/dist/{chunk-YEF5AAWN.js → chunk-A54E2ZTD.js} +2 -2
- package/dist/{chunk-DEY7B77Y.js → chunk-AHCELJMH.js} +2 -2
- package/dist/{chunk-PIWFNPQT.js → chunk-AL3TCNSJ.js} +4 -4
- package/dist/{chunk-EET24NSB.js → chunk-AUYFUBBE.js} +6 -6
- package/dist/{chunk-L3IP56F4.js → chunk-BIE3ZH3N.js} +33 -33
- package/dist/{chunk-A3IURDU2.js → chunk-C2AOL6MK.js} +3 -3
- package/dist/{chunk-BDQMMX3M.js → chunk-C2QVDLQV.js} +2 -2
- package/dist/{chunk-H75L4ZOH.js → chunk-CKR7LXEK.js} +71 -49
- package/dist/chunk-CKR7LXEK.js.map +1 -0
- package/dist/{chunk-AWLQNLTM.js → chunk-CSGFGQBI.js} +2 -2
- package/dist/{chunk-JHQAXATW.js → chunk-D4RV54HH.js} +2 -2
- package/dist/{chunk-33UAWCBN.js → chunk-DDLRKPR2.js} +6 -6
- package/dist/{chunk-3X3PNU3T.js → chunk-DLH3ECZP.js} +5 -5
- package/dist/chunk-DNIG3JME.js +892 -0
- package/dist/chunk-DNIG3JME.js.map +1 -0
- package/dist/{chunk-SA6GMZJY.js → chunk-DP3TSAYT.js} +2 -2
- package/dist/{chunk-H2X22KZW.js → chunk-FN6OFDBZ.js} +2 -2
- package/dist/{chunk-SU5V2LWR.js → chunk-G6DAE2LT.js} +2 -2
- package/dist/{chunk-V7KGDLBC.js → chunk-GQ7VWY6N.js} +2 -2
- package/dist/{chunk-SYUNCM26.js → chunk-GSKW64NB.js} +2 -2
- package/dist/{chunk-4WT6I4VG.js → chunk-GWB7ZD2A.js} +2 -2
- package/dist/{chunk-JYZLDZ2R.js → chunk-HG2VVIKI.js} +9 -9
- package/dist/{chunk-3VRMUNWL.js → chunk-HJ444KNS.js} +15 -7
- package/dist/chunk-HJ444KNS.js.map +1 -0
- package/dist/{chunk-23ZQPP2G.js → chunk-HOSJESKO.js} +3 -3
- package/dist/{chunk-EU6IRUCY.js → chunk-IDPZCHET.js} +2 -2
- package/dist/{chunk-VMHCC7LO.js → chunk-IO2UQVR2.js} +2 -2
- package/dist/{chunk-BWXKJDDH.js → chunk-ISMZ5UNE.js} +3 -3
- package/dist/{chunk-SX4QDWXK.js → chunk-IWUFKS4J.js} +2 -2
- package/dist/{chunk-I27WOMJ7.js → chunk-IY7DGT2Z.js} +2 -2
- package/dist/{chunk-STBX5X6B.js → chunk-JAYNUYSJ.js} +2 -2
- package/dist/{chunk-N34ZW6E4.js → chunk-K2REHR42.js} +2 -2
- package/dist/{chunk-UJPGQCXD.js → chunk-KFKFK6G6.js} +10 -10
- package/dist/{chunk-2HLMFRZ7.js → chunk-LDEKVBVO.js} +4 -4
- package/dist/{chunk-42KSEX6C.js → chunk-LDXNKEYB.js} +1 -1
- package/dist/chunk-LDXNKEYB.js.map +1 -0
- package/dist/{chunk-MPZYCRC6.js → chunk-M236YHUH.js} +5 -5
- package/dist/{chunk-L5VCT7LU.js → chunk-MCLZM5G7.js} +2 -2
- package/dist/{chunk-7Z3DLNSD.js → chunk-MGKH5KO3.js} +2 -2
- package/dist/{chunk-PUDYANAX.js → chunk-MMBLOJJB.js} +2 -2
- package/dist/{chunk-EFHNFTWB.js → chunk-MND4YJSX.js} +2 -2
- package/dist/{chunk-HJQXHLQQ.js → chunk-MROA6OFU.js} +2 -2
- package/dist/{chunk-BQOGQULP.js → chunk-MZZEMPCI.js} +2 -2
- package/dist/{chunk-ERAVI25Z.js → chunk-NAPHQ2MG.js} +2 -2
- package/dist/{chunk-L5TJWQEF.js → chunk-NB2YX6X2.js} +6 -6
- package/dist/{chunk-IHGLQJW6.js → chunk-NCPR2FOJ.js} +3 -3
- package/dist/{chunk-UKWRCQWT.js → chunk-NOMHXZD7.js} +2 -2
- package/dist/{chunk-DF2G4IXN.js → chunk-NPO4ZQFY.js} +3 -3
- package/dist/{chunk-LWFWZTWX.js → chunk-NQV6G6MU.js} +2 -2
- package/dist/{chunk-5HT35H4L.js → chunk-NRLWHZVS.js} +2 -2
- package/dist/{chunk-GCZHKBY2.js → chunk-OANSZGAG.js} +2 -2
- package/dist/{chunk-BHQPOCQE.js → chunk-OIIAIY7J.js} +3 -3
- package/dist/{chunk-FFGHFU7A.js → chunk-OXX4ORRL.js} +17 -17
- package/dist/{chunk-RTTPBBAN.js → chunk-OXYXMGRH.js} +4 -4
- package/dist/{chunk-K7BN34IA.js → chunk-PL26BCRN.js} +5 -5
- package/dist/{chunk-PM72NSEW.js → chunk-PYCTZ2HX.js} +2 -2
- package/dist/{chunk-AQ52ZFBC.js → chunk-PZEADMX4.js} +2 -2
- package/dist/{chunk-QPH52AZB.js → chunk-QNDOTDJK.js} +2 -2
- package/dist/{chunk-LZACDIM2.js → chunk-RBGRQEW3.js} +4 -4
- package/dist/{chunk-H3AW6QZF.js → chunk-RRJ462HP.js} +2 -2
- package/dist/{chunk-V4JZCTMK.js → chunk-TKBW4YLS.js} +2 -2
- package/dist/{chunk-4D2YH47Z.js → chunk-TRW7N4FS.js} +2 -2
- package/dist/{chunk-IHS2UGYM.js → chunk-TTIEXXL3.js} +366 -1109
- package/dist/chunk-TTIEXXL3.js.map +1 -0
- package/dist/{chunk-OQJKY3Z5.js → chunk-UAAAXSKQ.js} +2 -2
- package/dist/{chunk-S36KJNQH.js → chunk-UM73Q46J.js} +2 -2
- package/dist/{chunk-7EJCAFMS.js → chunk-UTXYOWDR.js} +2 -2
- package/dist/{chunk-AYHPSYZI.js → chunk-UUYAW3JH.js} +2 -2
- package/dist/{chunk-MSMGQDVW.js → chunk-UVUBLLM3.js} +3 -3
- package/dist/{chunk-JZIZBJCW.js → chunk-V4FI2TG6.js} +3 -3
- package/dist/{chunk-MO6N4QYY.js → chunk-VARNSLUW.js} +4 -4
- package/dist/{chunk-WJAGO5LL.js → chunk-VEB7NEON.js} +4 -4
- package/dist/{chunk-GMZNJZRP.js → chunk-VLBFSD7B.js} +2 -2
- package/dist/{chunk-NZFFGO5O.js → chunk-VXWOJPSY.js} +36 -3
- package/dist/chunk-VXWOJPSY.js.map +1 -0
- package/dist/{chunk-JNIVZNIB.js → chunk-WKVL2G7X.js} +4 -4
- package/dist/{chunk-KJXP6RUG.js → chunk-WNEVEMJ3.js} +4 -4
- package/dist/{chunk-HNEOLXRF.js → chunk-WQIYGYY6.js} +2 -2
- package/dist/{chunk-GQJKVQJR.js → chunk-WYLBEJQI.js} +2 -2
- package/dist/{chunk-36DZ242S.js → chunk-XNROYP5E.js} +2 -2
- package/dist/{chunk-XNAOCY67.js → chunk-XVZIBMSY.js} +3 -3
- package/dist/{chunk-PXTLPC3P.js → chunk-YGVZ4QOZ.js} +4 -4
- package/dist/{chunk-JX32ROLZ.js → chunk-YZTFQBYY.js} +4 -4
- package/dist/{chunk-XSXM5LYZ.js → chunk-ZCRDYQL6.js} +2 -2
- package/dist/{chunk-MAAL6OZV.js → chunk-ZG3HZZ6K.js} +90 -36
- package/dist/chunk-ZG3HZZ6K.js.map +1 -0
- package/dist/{chunk-Z3XVYXQ3.js → chunk-ZXTAHSQU.js} +2 -2
- package/dist/cli.js +89 -88
- package/dist/coding/export-okf-codegraph.js +21 -20
- package/dist/coding/export-okf-codegraph.js.map +1 -1
- package/dist/compounding/engine.js +23 -22
- package/dist/config.js +24 -23
- package/dist/connectors/codex-materialize-runner.js +21 -20
- package/dist/connectors/codex-materialize.js +2 -2
- package/dist/connectors/index.js +21 -20
- package/dist/consolidation-provenance-check.js +3 -3
- package/dist/consolidation-undo.js +2 -2
- package/dist/contradiction/index.js +6 -5
- package/dist/convergence-refresh.js +22 -21
- package/dist/corpus-watermark.js +22 -21
- package/dist/deep-recall-http-glue.js +7 -6
- package/dist/entity-canonical-id-migration-GD5MPFUF.js +28 -0
- package/dist/entity-retrieval.js +20 -19
- package/dist/explicit-capture.js +8 -7
- package/dist/external-wiki-access.js +55 -54
- package/dist/extraction.js +27 -26
- package/dist/harmonic-retrieval.js +21 -20
- package/dist/harmonic-source-lookup.js +3 -2
- package/dist/index.js +106 -105
- package/dist/local-llm-rate-limit.d.ts +3 -1
- package/dist/local-llm-rate-limit.d.ts.map +1 -1
- package/dist/local-llm-rate-limit.js +1 -1
- package/dist/local-llm.js +2 -2
- package/dist/location/index.js +23 -22
- package/dist/maintenance/memory-governance.js +20 -19
- package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +20 -19
- package/dist/maintenance/rebuild-memory-projection-runner.js +22 -21
- package/dist/maintenance/rebuild-memory-projection.js +21 -20
- package/dist/memory-browse-operations.js +55 -54
- package/dist/memory-subject.js +4 -3
- package/dist/namespaces/migrate.js +21 -20
- package/dist/namespaces/search.js +7 -7
- package/dist/namespaces/storage.js +20 -19
- package/dist/offline-sync-file-io.js +3 -2
- package/dist/offline-sync-impression-drain.js +4 -4
- package/dist/offline-sync-snapshot-stream.js +6 -5
- package/dist/offline-sync-upload-staging.js +4 -3
- package/dist/offline-sync.js +5 -4
- package/dist/operator-doctor-corpus.js +23 -22
- package/dist/operator-toolkit.js +34 -33
- package/dist/orchestration/maintenance.js +35 -34
- package/dist/orchestration/orchestrator-init.d.ts +3 -0
- package/dist/orchestration/orchestrator-init.d.ts.map +1 -1
- package/dist/orchestrator.d.ts.map +1 -1
- package/dist/orchestrator.js +106 -105
- package/dist/page-versioning.js +2 -2
- package/dist/recall-explain-renderer.js +3 -3
- package/dist/recall-navigate-access.js +2 -2
- package/dist/recall-navigate-disclosure.js +2 -2
- package/dist/recall-navigate.js +2 -2
- package/dist/recall-navigation-operations.js +55 -54
- package/dist/recall-navigation.js +54 -53
- package/dist/recall-state.js +2 -2
- package/dist/recall-xray-cli.js +4 -4
- package/dist/recall-xray-renderer.js +3 -3
- package/dist/recall-xray.js +2 -2
- package/dist/reconcile/manifest.js +4 -3
- package/dist/{resolution-ZU5BIAJA.js → resolution-AMYTPT5X.js} +7 -6
- package/dist/resolution-AMYTPT5X.js.map +1 -0
- package/dist/resume-bundles.js +25 -24
- package/dist/retrieval-agents.js +3 -2
- package/dist/salvage-envelope.js +6 -5
- package/dist/search/document-scanner.js +2 -2
- package/dist/search/factory.js +6 -6
- package/dist/search/index.js +6 -6
- package/dist/search/lancedb-backend.js +3 -3
- package/dist/search/meilisearch-backend.js +3 -3
- package/dist/search/orama-backend.js +3 -3
- package/dist/secure-store/entity-file-read.d.ts +9 -0
- package/dist/secure-store/entity-file-read.d.ts.map +1 -0
- package/dist/secure-store/index.js +3 -2
- package/dist/secure-store/secure-fs.d.ts.map +1 -1
- package/dist/semantic-consolidation.js +22 -21
- package/dist/semantic-rule-promotion.js +20 -19
- package/dist/semantic-rule-verifier.js +20 -19
- package/dist/shared-context/manager.js +2 -2
- package/dist/source-agent-qualifier.js +24 -23
- package/dist/standup/build.js +20 -19
- package/dist/storage/entity-canonical-id-migration-runner.d.ts +12 -0
- package/dist/storage/entity-canonical-id-migration-runner.d.ts.map +1 -1
- package/dist/storage/entity-canonical-id-migration.d.ts +1 -0
- package/dist/storage/entity-canonical-id-migration.d.ts.map +1 -1
- package/dist/storage/entity-canonical-id-references.d.ts +14 -0
- package/dist/storage/entity-canonical-id-references.d.ts.map +1 -1
- package/dist/storage/entity-store.d.ts +4 -0
- package/dist/storage/entity-store.d.ts.map +1 -1
- package/dist/storage.d.ts +7 -2
- package/dist/storage.d.ts.map +1 -1
- package/dist/storage.js +19 -18
- package/dist/subject-config.js +5 -4
- package/dist/summarizer.js +3 -3
- package/dist/support-passport/index.js +57 -56
- package/dist/transfer/capsule-import.js +3 -3
- package/dist/transfer/export-okf.js +21 -20
- package/dist/types.d.ts +1 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +1 -1
- package/dist/verified-recall.js +20 -19
- package/dist/who-knows-cli.js +21 -20
- package/dist/who-knows.js +20 -19
- package/dist/write-envelope.js +5 -4
- package/package.json +2 -2
- package/src/local-llm-rate-limit.test.ts +59 -1
- package/src/local-llm-rate-limit.ts +22 -7
- package/src/orchestration/orchestrator-init.ts +26 -3
- package/src/orchestrator.ts +1 -0
- package/src/secure-store/entity-file-read.ts +55 -0
- package/src/secure-store/secure-fs.ts +2 -1
- package/src/storage/entity-canonical-id-migration-runner.ts +36 -0
- package/src/storage/entity-canonical-id-migration.ts +1 -1
- package/src/storage/entity-canonical-id-references.ts +57 -2
- package/src/storage/entity-store.ts +138 -25
- package/src/storage.ts +15 -15
- package/src/types.ts +1 -1
- package/dist/chunk-3VRMUNWL.js.map +0 -1
- package/dist/chunk-42KSEX6C.js.map +0 -1
- package/dist/chunk-H75L4ZOH.js.map +0 -1
- package/dist/chunk-IHS2UGYM.js.map +0 -1
- package/dist/chunk-MAAL6OZV.js.map +0 -1
- package/dist/chunk-NZFFGO5O.js.map +0 -1
- /package/dist/{auto-sync-UQESPLCB.js.map → auto-sync-MX2AHVPQ.js.map} +0 -0
- /package/dist/{capsule-merge-LGYURS3C.js.map → capsule-merge-LGYZAMO4.js.map} +0 -0
- /package/dist/{chunk-G6XEYFCO.js.map → chunk-27OJ5G3D.js.map} +0 -0
- /package/dist/{chunk-5LRV557D.js.map → chunk-2FIJQNL7.js.map} +0 -0
- /package/dist/{chunk-ETPIYK4J.js.map → chunk-2VBDSZ7U.js.map} +0 -0
- /package/dist/{chunk-QNKEMFZY.js.map → chunk-2WDMUQOH.js.map} +0 -0
- /package/dist/{chunk-W6MOLW5S.js.map → chunk-2XVLY424.js.map} +0 -0
- /package/dist/{chunk-P55FACBG.js.map → chunk-3BOQPLCF.js.map} +0 -0
- /package/dist/{chunk-27SAUQ5R.js.map → chunk-3CHHQXAR.js.map} +0 -0
- /package/dist/{chunk-3GYSZHMT.js.map → chunk-3CZRBTJK.js.map} +0 -0
- /package/dist/{chunk-FJJWA7NL.js.map → chunk-3EJQXF3B.js.map} +0 -0
- /package/dist/{chunk-HFSPY26A.js.map → chunk-3IXNL6SK.js.map} +0 -0
- /package/dist/{chunk-PLD6SOU7.js.map → chunk-3SN2D43X.js.map} +0 -0
- /package/dist/{chunk-COGLFRXT.js.map → chunk-4HNINU7Q.js.map} +0 -0
- /package/dist/{chunk-CCDHRDQ3.js.map → chunk-4HYP4OKH.js.map} +0 -0
- /package/dist/{chunk-GKJJNO7C.js.map → chunk-4OE4UE2Y.js.map} +0 -0
- /package/dist/{chunk-7RTNJ6M2.js.map → chunk-4QPW6JVI.js.map} +0 -0
- /package/dist/{chunk-5SMR542T.js.map → chunk-5GT4ESLR.js.map} +0 -0
- /package/dist/{chunk-DWL6YIZH.js.map → chunk-5JI3XWI2.js.map} +0 -0
- /package/dist/{chunk-27LPB4NS.js.map → chunk-5NPZFXQH.js.map} +0 -0
- /package/dist/{chunk-Y6IF6H2U.js.map → chunk-5SMHQW45.js.map} +0 -0
- /package/dist/{chunk-VN3GHPBU.js.map → chunk-654GT5UP.js.map} +0 -0
- /package/dist/{chunk-3E6DGSGZ.js.map → chunk-6MC7UQ6D.js.map} +0 -0
- /package/dist/{chunk-655FVTCD.js.map → chunk-74IN6PV5.js.map} +0 -0
- /package/dist/{chunk-WFUO7NWX.js.map → chunk-7FZ4YVOH.js.map} +0 -0
- /package/dist/{chunk-MWWAQXZ6.js.map → chunk-7HFTCBCZ.js.map} +0 -0
- /package/dist/{chunk-7IOB5F7Y.js.map → chunk-7VB3Y5OL.js.map} +0 -0
- /package/dist/{chunk-YEF5AAWN.js.map → chunk-A54E2ZTD.js.map} +0 -0
- /package/dist/{chunk-DEY7B77Y.js.map → chunk-AHCELJMH.js.map} +0 -0
- /package/dist/{chunk-PIWFNPQT.js.map → chunk-AL3TCNSJ.js.map} +0 -0
- /package/dist/{chunk-EET24NSB.js.map → chunk-AUYFUBBE.js.map} +0 -0
- /package/dist/{chunk-L3IP56F4.js.map → chunk-BIE3ZH3N.js.map} +0 -0
- /package/dist/{chunk-A3IURDU2.js.map → chunk-C2AOL6MK.js.map} +0 -0
- /package/dist/{chunk-BDQMMX3M.js.map → chunk-C2QVDLQV.js.map} +0 -0
- /package/dist/{chunk-AWLQNLTM.js.map → chunk-CSGFGQBI.js.map} +0 -0
- /package/dist/{chunk-JHQAXATW.js.map → chunk-D4RV54HH.js.map} +0 -0
- /package/dist/{chunk-33UAWCBN.js.map → chunk-DDLRKPR2.js.map} +0 -0
- /package/dist/{chunk-3X3PNU3T.js.map → chunk-DLH3ECZP.js.map} +0 -0
- /package/dist/{chunk-SA6GMZJY.js.map → chunk-DP3TSAYT.js.map} +0 -0
- /package/dist/{chunk-H2X22KZW.js.map → chunk-FN6OFDBZ.js.map} +0 -0
- /package/dist/{chunk-SU5V2LWR.js.map → chunk-G6DAE2LT.js.map} +0 -0
- /package/dist/{chunk-V7KGDLBC.js.map → chunk-GQ7VWY6N.js.map} +0 -0
- /package/dist/{chunk-SYUNCM26.js.map → chunk-GSKW64NB.js.map} +0 -0
- /package/dist/{chunk-4WT6I4VG.js.map → chunk-GWB7ZD2A.js.map} +0 -0
- /package/dist/{chunk-JYZLDZ2R.js.map → chunk-HG2VVIKI.js.map} +0 -0
- /package/dist/{chunk-23ZQPP2G.js.map → chunk-HOSJESKO.js.map} +0 -0
- /package/dist/{chunk-EU6IRUCY.js.map → chunk-IDPZCHET.js.map} +0 -0
- /package/dist/{chunk-VMHCC7LO.js.map → chunk-IO2UQVR2.js.map} +0 -0
- /package/dist/{chunk-BWXKJDDH.js.map → chunk-ISMZ5UNE.js.map} +0 -0
- /package/dist/{chunk-SX4QDWXK.js.map → chunk-IWUFKS4J.js.map} +0 -0
- /package/dist/{chunk-I27WOMJ7.js.map → chunk-IY7DGT2Z.js.map} +0 -0
- /package/dist/{chunk-STBX5X6B.js.map → chunk-JAYNUYSJ.js.map} +0 -0
- /package/dist/{chunk-N34ZW6E4.js.map → chunk-K2REHR42.js.map} +0 -0
- /package/dist/{chunk-UJPGQCXD.js.map → chunk-KFKFK6G6.js.map} +0 -0
- /package/dist/{chunk-2HLMFRZ7.js.map → chunk-LDEKVBVO.js.map} +0 -0
- /package/dist/{chunk-MPZYCRC6.js.map → chunk-M236YHUH.js.map} +0 -0
- /package/dist/{chunk-L5VCT7LU.js.map → chunk-MCLZM5G7.js.map} +0 -0
- /package/dist/{chunk-7Z3DLNSD.js.map → chunk-MGKH5KO3.js.map} +0 -0
- /package/dist/{chunk-PUDYANAX.js.map → chunk-MMBLOJJB.js.map} +0 -0
- /package/dist/{chunk-EFHNFTWB.js.map → chunk-MND4YJSX.js.map} +0 -0
- /package/dist/{chunk-HJQXHLQQ.js.map → chunk-MROA6OFU.js.map} +0 -0
- /package/dist/{chunk-BQOGQULP.js.map → chunk-MZZEMPCI.js.map} +0 -0
- /package/dist/{chunk-ERAVI25Z.js.map → chunk-NAPHQ2MG.js.map} +0 -0
- /package/dist/{chunk-L5TJWQEF.js.map → chunk-NB2YX6X2.js.map} +0 -0
- /package/dist/{chunk-IHGLQJW6.js.map → chunk-NCPR2FOJ.js.map} +0 -0
- /package/dist/{chunk-UKWRCQWT.js.map → chunk-NOMHXZD7.js.map} +0 -0
- /package/dist/{chunk-DF2G4IXN.js.map → chunk-NPO4ZQFY.js.map} +0 -0
- /package/dist/{chunk-LWFWZTWX.js.map → chunk-NQV6G6MU.js.map} +0 -0
- /package/dist/{chunk-5HT35H4L.js.map → chunk-NRLWHZVS.js.map} +0 -0
- /package/dist/{chunk-GCZHKBY2.js.map → chunk-OANSZGAG.js.map} +0 -0
- /package/dist/{chunk-BHQPOCQE.js.map → chunk-OIIAIY7J.js.map} +0 -0
- /package/dist/{chunk-FFGHFU7A.js.map → chunk-OXX4ORRL.js.map} +0 -0
- /package/dist/{chunk-RTTPBBAN.js.map → chunk-OXYXMGRH.js.map} +0 -0
- /package/dist/{chunk-K7BN34IA.js.map → chunk-PL26BCRN.js.map} +0 -0
- /package/dist/{chunk-PM72NSEW.js.map → chunk-PYCTZ2HX.js.map} +0 -0
- /package/dist/{chunk-AQ52ZFBC.js.map → chunk-PZEADMX4.js.map} +0 -0
- /package/dist/{chunk-QPH52AZB.js.map → chunk-QNDOTDJK.js.map} +0 -0
- /package/dist/{chunk-LZACDIM2.js.map → chunk-RBGRQEW3.js.map} +0 -0
- /package/dist/{chunk-H3AW6QZF.js.map → chunk-RRJ462HP.js.map} +0 -0
- /package/dist/{chunk-V4JZCTMK.js.map → chunk-TKBW4YLS.js.map} +0 -0
- /package/dist/{chunk-4D2YH47Z.js.map → chunk-TRW7N4FS.js.map} +0 -0
- /package/dist/{chunk-OQJKY3Z5.js.map → chunk-UAAAXSKQ.js.map} +0 -0
- /package/dist/{chunk-S36KJNQH.js.map → chunk-UM73Q46J.js.map} +0 -0
- /package/dist/{chunk-7EJCAFMS.js.map → chunk-UTXYOWDR.js.map} +0 -0
- /package/dist/{chunk-AYHPSYZI.js.map → chunk-UUYAW3JH.js.map} +0 -0
- /package/dist/{chunk-MSMGQDVW.js.map → chunk-UVUBLLM3.js.map} +0 -0
- /package/dist/{chunk-JZIZBJCW.js.map → chunk-V4FI2TG6.js.map} +0 -0
- /package/dist/{chunk-MO6N4QYY.js.map → chunk-VARNSLUW.js.map} +0 -0
- /package/dist/{chunk-WJAGO5LL.js.map → chunk-VEB7NEON.js.map} +0 -0
- /package/dist/{chunk-GMZNJZRP.js.map → chunk-VLBFSD7B.js.map} +0 -0
- /package/dist/{chunk-JNIVZNIB.js.map → chunk-WKVL2G7X.js.map} +0 -0
- /package/dist/{chunk-KJXP6RUG.js.map → chunk-WNEVEMJ3.js.map} +0 -0
- /package/dist/{chunk-HNEOLXRF.js.map → chunk-WQIYGYY6.js.map} +0 -0
- /package/dist/{chunk-GQJKVQJR.js.map → chunk-WYLBEJQI.js.map} +0 -0
- /package/dist/{chunk-36DZ242S.js.map → chunk-XNROYP5E.js.map} +0 -0
- /package/dist/{chunk-XNAOCY67.js.map → chunk-XVZIBMSY.js.map} +0 -0
- /package/dist/{chunk-PXTLPC3P.js.map → chunk-YGVZ4QOZ.js.map} +0 -0
- /package/dist/{chunk-JX32ROLZ.js.map → chunk-YZTFQBYY.js.map} +0 -0
- /package/dist/{chunk-XSXM5LYZ.js.map → chunk-ZCRDYQL6.js.map} +0 -0
- /package/dist/{chunk-Z3XVYXQ3.js.map → chunk-ZXTAHSQU.js.map} +0 -0
- /package/dist/{resolution-ZU5BIAJA.js.map → entity-canonical-id-migration-GD5MPFUF.js.map} +0 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@remnic/core",
|
|
3
|
-
"version": "10.0.
|
|
3
|
+
"version": "10.0.29",
|
|
4
4
|
"description": "Framework-agnostic Remnic memory engine — orchestrator, storage, extraction, search, trust zones",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -3347,7 +3347,7 @@
|
|
|
3347
3347
|
"core"
|
|
3348
3348
|
],
|
|
3349
3349
|
"peerDependencies": {
|
|
3350
|
-
"@remnic/coding-graph": "^10.0.
|
|
3350
|
+
"@remnic/coding-graph": "^10.0.29"
|
|
3351
3351
|
},
|
|
3352
3352
|
"peerDependenciesMeta": {
|
|
3353
3353
|
"@remnic/coding-graph": {
|
|
@@ -339,7 +339,10 @@ test("process-wide concurrency cap bounds in-flight requests across clients", as
|
|
|
339
339
|
const config = createConfig({ localLlmUrl: "http://127.0.0.1:1007", localLlmMaxConcurrency: 2 });
|
|
340
340
|
const clients = [new LocalLlmClient(config), new LocalLlmClient(config), new LocalLlmClient(config)];
|
|
341
341
|
const results = await Promise.all(
|
|
342
|
-
Array.from({ length: 7 }, (_, i) => clients[i % 3].chatCompletion(
|
|
342
|
+
Array.from({ length: 7 }, (_, i) => clients[i % 3].chatCompletion(
|
|
343
|
+
[{ role: "user", content: `q${i}` }],
|
|
344
|
+
{ priority: i % 2 === 0 ? "recall-critical" : "background" }
|
|
345
|
+
))
|
|
343
346
|
);
|
|
344
347
|
assert.equal(results.filter((r) => r?.content === "ok").length, 7);
|
|
345
348
|
assert.equal(peak, 2);
|
|
@@ -404,3 +407,58 @@ test("waiting for a concurrency slot is bounded by the wait budget", async () =>
|
|
|
404
407
|
}
|
|
405
408
|
);
|
|
406
409
|
});
|
|
410
|
+
|
|
411
|
+
test("limiter reserves one finite permit for recall-critical work", async () => {
|
|
412
|
+
const limiter = new LocalLlmConcurrencyLimiter(2);
|
|
413
|
+
const background = await limiter.acquire();
|
|
414
|
+
const secondBackground = await limiter.acquire({ timeoutMs: 0 });
|
|
415
|
+
try {
|
|
416
|
+
assert.ok(background);
|
|
417
|
+
assert.equal(secondBackground, null, "background must not consume the recall permit");
|
|
418
|
+
const foreground = await limiter.acquire({ highPriority: true, timeoutMs: 0 });
|
|
419
|
+
assert.ok(foreground, "recall must start without waiting for background completion");
|
|
420
|
+
foreground();
|
|
421
|
+
foreground();
|
|
422
|
+
assert.equal(limiter.inFlight, 1, "release remains idempotent");
|
|
423
|
+
} finally {
|
|
424
|
+
secondBackground?.();
|
|
425
|
+
background?.();
|
|
426
|
+
}
|
|
427
|
+
});
|
|
428
|
+
|
|
429
|
+
test("reserved background queue survives aborts and responds to resizing", async () => {
|
|
430
|
+
const limiter = new LocalLlmConcurrencyLimiter(2);
|
|
431
|
+
const background = await limiter.acquire();
|
|
432
|
+
assert.ok(background);
|
|
433
|
+
const controller = new AbortController();
|
|
434
|
+
const aborted = limiter.acquire({ signal: controller.signal });
|
|
435
|
+
controller.abort();
|
|
436
|
+
const abortedRelease = await aborted;
|
|
437
|
+
abortedRelease?.();
|
|
438
|
+
assert.equal(abortedRelease, null);
|
|
439
|
+
const queued = limiter.acquire();
|
|
440
|
+
const foreground = await limiter.acquire({ highPriority: true, timeoutMs: 0 });
|
|
441
|
+
assert.ok(foreground);
|
|
442
|
+
assert.equal(limiter.queued, 1);
|
|
443
|
+
limiter.setMax(3);
|
|
444
|
+
const nextBackground = await queued;
|
|
445
|
+
assert.ok(nextBackground);
|
|
446
|
+
assert.equal(limiter.inFlight, 3);
|
|
447
|
+
nextBackground();
|
|
448
|
+
background();
|
|
449
|
+
foreground();
|
|
450
|
+
assert.equal(limiter.inFlight, 0);
|
|
451
|
+
});
|
|
452
|
+
|
|
453
|
+
test("one-permit and unlimited limits still admit background work", async () => {
|
|
454
|
+
for (const max of [1, 0]) {
|
|
455
|
+
const limiter = new LocalLlmConcurrencyLimiter(max);
|
|
456
|
+
const first = await limiter.acquire({ timeoutMs: 0 });
|
|
457
|
+
assert.ok(first);
|
|
458
|
+
const second = await limiter.acquire({ timeoutMs: 0 });
|
|
459
|
+
assert.equal(second === null, max === 1);
|
|
460
|
+
second?.();
|
|
461
|
+
first();
|
|
462
|
+
assert.equal(limiter.inFlight, 0);
|
|
463
|
+
}
|
|
464
|
+
});
|
|
@@ -151,9 +151,10 @@ export function computeRateLimitDelayMs(
|
|
|
151
151
|
|
|
152
152
|
type Waiter = { highPriority: boolean; grant: () => void };
|
|
153
153
|
|
|
154
|
-
/** FIFO
|
|
154
|
+
/** FIFO semaphore; finite caps above one reserve a permit for recall-critical work. */
|
|
155
155
|
export class LocalLlmConcurrencyLimiter {
|
|
156
156
|
private active = 0;
|
|
157
|
+
private activeBackground = 0;
|
|
157
158
|
private max: number;
|
|
158
159
|
private readonly waiters: Waiter[] = [];
|
|
159
160
|
|
|
@@ -182,20 +183,25 @@ export class LocalLlmConcurrencyLimiter {
|
|
|
182
183
|
options: { highPriority?: boolean; signal?: AbortSignal; timeoutMs?: number } = {}
|
|
183
184
|
): Promise<(() => void) | null> {
|
|
184
185
|
const { signal, timeoutMs } = options;
|
|
186
|
+
const highPriority = options.highPriority === true;
|
|
185
187
|
if (signal?.aborted) return Promise.resolve(null);
|
|
186
|
-
if (
|
|
188
|
+
if (
|
|
189
|
+
this.canAcquire(highPriority) &&
|
|
190
|
+
(this.waiters.length === 0 || (highPriority && !this.waiters[0].highPriority))
|
|
191
|
+
) {
|
|
187
192
|
this.active += 1;
|
|
188
|
-
|
|
193
|
+
if (!highPriority) this.activeBackground += 1;
|
|
194
|
+
return Promise.resolve(this.releaser(highPriority));
|
|
189
195
|
}
|
|
190
196
|
if (timeoutMs !== undefined && timeoutMs <= 0) return Promise.resolve(null);
|
|
191
197
|
return new Promise((resolve) => {
|
|
192
198
|
let timer: NodeJS.Timeout | undefined;
|
|
193
199
|
const waiter: Waiter = {
|
|
194
|
-
highPriority
|
|
200
|
+
highPriority,
|
|
195
201
|
grant: () => {
|
|
196
202
|
signal?.removeEventListener("abort", onAbort);
|
|
197
203
|
if (timer) clearTimeout(timer);
|
|
198
|
-
resolve(this.releaser());
|
|
204
|
+
resolve(this.releaser(highPriority));
|
|
199
205
|
},
|
|
200
206
|
};
|
|
201
207
|
const onAbort = (): void => {
|
|
@@ -217,21 +223,30 @@ export class LocalLlmConcurrencyLimiter {
|
|
|
217
223
|
});
|
|
218
224
|
}
|
|
219
225
|
|
|
220
|
-
private
|
|
226
|
+
private canAcquire(highPriority: boolean): boolean {
|
|
227
|
+
return this.max <= 0 || (
|
|
228
|
+
this.active < this.max &&
|
|
229
|
+
(highPriority || this.max === 1 || this.activeBackground < this.max - 1)
|
|
230
|
+
);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
private releaser(highPriority: boolean): () => void {
|
|
221
234
|
let released = false;
|
|
222
235
|
return () => {
|
|
223
236
|
if (released) return;
|
|
224
237
|
released = true;
|
|
225
238
|
this.active -= 1;
|
|
239
|
+
if (!highPriority) this.activeBackground -= 1;
|
|
226
240
|
this.pump();
|
|
227
241
|
};
|
|
228
242
|
}
|
|
229
243
|
|
|
230
244
|
private pump(): void {
|
|
231
|
-
while (this.waiters.length > 0 && (this.
|
|
245
|
+
while (this.waiters.length > 0 && this.canAcquire(this.waiters[0].highPriority)) {
|
|
232
246
|
const next = this.waiters.shift();
|
|
233
247
|
if (!next) break;
|
|
234
248
|
this.active += 1;
|
|
249
|
+
if (!next.highPriority) this.activeBackground += 1;
|
|
235
250
|
next.grant();
|
|
236
251
|
}
|
|
237
252
|
}
|
|
@@ -114,6 +114,7 @@ export class OrchestratorInitCoordinator {
|
|
|
114
114
|
private startupEpochAbort:
|
|
115
115
|
| { epoch: number; controller: AbortController }
|
|
116
116
|
| undefined;
|
|
117
|
+
private readonly startupStorageWrites = new Set<Promise<void>>();
|
|
117
118
|
|
|
118
119
|
constructor(
|
|
119
120
|
private readonly deps: OrchestratorInitDeps,
|
|
@@ -127,6 +128,25 @@ export class OrchestratorInitCoordinator {
|
|
|
127
128
|
this.activeStartupEpoch = null;
|
|
128
129
|
}
|
|
129
130
|
|
|
131
|
+
private async initializeStorage(storage: StorageManager): Promise<void> {
|
|
132
|
+
const write = storage.ensureDirectories({ waitForEntityMigration: false });
|
|
133
|
+
this.startupStorageWrites.add(write);
|
|
134
|
+
try {
|
|
135
|
+
await write;
|
|
136
|
+
} finally {
|
|
137
|
+
this.startupStorageWrites.delete(write);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
async drainEntityMigrations(): Promise<void> {
|
|
142
|
+
await Promise.allSettled([...this.startupStorageWrites]);
|
|
143
|
+
const drains = [this.deps.storage.drainEntityCanonicalIdMigration()];
|
|
144
|
+
this.deps.storageRouter.forEachCachedStorage((storage) => {
|
|
145
|
+
drains.push(storage.drainEntityCanonicalIdMigration());
|
|
146
|
+
});
|
|
147
|
+
await Promise.all(drains);
|
|
148
|
+
}
|
|
149
|
+
|
|
130
150
|
private beginStartupEpoch(): number {
|
|
131
151
|
this.startupEpochAbort?.controller.abort();
|
|
132
152
|
this.startupEpochAbort = undefined;
|
|
@@ -305,7 +325,8 @@ export class OrchestratorInitCoordinator {
|
|
|
305
325
|
logger: (message) => log.info(message),
|
|
306
326
|
});
|
|
307
327
|
await this.deps.storage.loadAliases();
|
|
308
|
-
|
|
328
|
+
if (exitIfStale()) return;
|
|
329
|
+
await this.initializeStorage(this.deps.storage);
|
|
309
330
|
await Promise.all(
|
|
310
331
|
this.deps.config.externalWikis.map((wiki) =>
|
|
311
332
|
assertExternalWikiRootOutsideMemoryDirCanonical(
|
|
@@ -323,7 +344,8 @@ export class OrchestratorInitCoordinator {
|
|
|
323
344
|
for (const ns of namespaces) {
|
|
324
345
|
const sm = await this.deps.storageRouter.storageFor(ns);
|
|
325
346
|
await sm.loadAliases().catch(() => undefined);
|
|
326
|
-
|
|
347
|
+
if (exitIfStale()) return;
|
|
348
|
+
await this.initializeStorage(sm);
|
|
327
349
|
}
|
|
328
350
|
// Explicitly seed the catalog with all configured namespaces at startup
|
|
329
351
|
// (round 6, cursor Medium — NBLlR). The storageFor loop above fires the
|
|
@@ -564,7 +586,8 @@ export class OrchestratorInitCoordinator {
|
|
|
564
586
|
.catch((err) => {
|
|
565
587
|
log.error(`deferred initialization failed (non-fatal): ${err}`);
|
|
566
588
|
})
|
|
567
|
-
.finally(() => {
|
|
589
|
+
.finally(async () => {
|
|
590
|
+
await this.drainEntityMigrations();
|
|
568
591
|
settleOwnDeferredReady();
|
|
569
592
|
});
|
|
570
593
|
} catch (err) {
|
package/src/orchestrator.ts
CHANGED
|
@@ -969,6 +969,7 @@ export class Orchestrator {
|
|
|
969
969
|
this._orchestratorInitCoordinator?.retireStartupEpoch();
|
|
970
970
|
this.abortDeferredInit();
|
|
971
971
|
this.extractionQueueCoordinator.stopAccepting();
|
|
972
|
+
await this._orchestratorInitCoordinator?.drainEntityMigrations();
|
|
972
973
|
if (this.wearablesAutoSyncHandle) {
|
|
973
974
|
await this.wearablesAutoSyncHandle.stop();
|
|
974
975
|
this.wearablesAutoSyncHandle = null;
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { constants } from "node:fs";
|
|
2
|
+
import { lstat, open, readFile, realpath } from "node:fs/promises";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { assertPathInsideRoot } from "../utils/path-containment.js";
|
|
5
|
+
|
|
6
|
+
export async function readStoreFileBuffer(filePath: string, memoryDir: string): Promise<Buffer> {
|
|
7
|
+
const root = path.resolve(memoryDir);
|
|
8
|
+
const entities = path.join(root, "entities");
|
|
9
|
+
const target = path.resolve(filePath);
|
|
10
|
+
const relative = path.relative(entities, target);
|
|
11
|
+
if (relative === ".." || relative.startsWith(`..${path.sep}`) || path.isAbsolute(relative)) {
|
|
12
|
+
return readFile(filePath);
|
|
13
|
+
}
|
|
14
|
+
const handle = await open(target, constants.O_RDONLY | (constants.O_NOFOLLOW ?? 0) | (constants.O_NONBLOCK ?? 0));
|
|
15
|
+
try {
|
|
16
|
+
for (let parent = path.dirname(target); ; parent = path.dirname(parent)) {
|
|
17
|
+
const info = await lstat(parent);
|
|
18
|
+
if (info.isSymbolicLink() || !info.isDirectory()) {
|
|
19
|
+
throw new Error(`Refusing unsafe entity directory: ${parent}.`);
|
|
20
|
+
}
|
|
21
|
+
if (parent === root) break;
|
|
22
|
+
}
|
|
23
|
+
const [opened, entry, realRoot, realEntities, realTarget] = await Promise.all([
|
|
24
|
+
handle.stat(), lstat(target), realpath(root), realpath(entities), realpath(target),
|
|
25
|
+
]);
|
|
26
|
+
if (!opened.isFile() || entry.isSymbolicLink() || !entry.isFile()
|
|
27
|
+
|| opened.dev !== entry.dev || opened.ino !== entry.ino
|
|
28
|
+
|| realEntities !== path.join(realRoot, "entities")) {
|
|
29
|
+
throw new Error(`Refusing unsafe entity file: ${filePath}.`);
|
|
30
|
+
}
|
|
31
|
+
assertPathInsideRoot(realEntities, realTarget, filePath);
|
|
32
|
+
return await handle.readFile();
|
|
33
|
+
} finally {
|
|
34
|
+
await handle.close();
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Atomic-replacement-tolerant variant: entity writes ship as temp-file +
|
|
41
|
+
* rename, so an open/lstat identity mismatch during a concurrent write is an
|
|
42
|
+
* ordinary race, not tampering. Retry a bounded number of times with fresh
|
|
43
|
+
* containment checks before surfacing the mismatch as unsafe.
|
|
44
|
+
*/
|
|
45
|
+
export async function readStoreFileBufferTolerant(filePath: string, memoryDir: string, attempts = 3): Promise<Buffer> {
|
|
46
|
+
for (let attempt = 0; ; attempt += 1) {
|
|
47
|
+
try {
|
|
48
|
+
return await readStoreFileBuffer(filePath, memoryDir);
|
|
49
|
+
} catch (err) {
|
|
50
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
51
|
+
const identityRace = /unsafe entity (?:file|directory)/.test(message);
|
|
52
|
+
if (!identityRace || attempt >= attempts - 1) throw err;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
@@ -61,6 +61,7 @@ import {
|
|
|
61
61
|
} from "./cipher.js";
|
|
62
62
|
import { RECALL_FALLBACK_DIRS } from "../utils/category-dir.js";
|
|
63
63
|
import { isErrnoCode } from "../utils/errno.js";
|
|
64
|
+
import { readStoreFileBufferTolerant as readStoreFileBuffer } from "./entity-file-read.js";
|
|
64
65
|
|
|
65
66
|
// ---------------------------------------------------------------------------
|
|
66
67
|
// Error classes
|
|
@@ -291,7 +292,7 @@ export async function readMaybeEncryptedFileBuffer(
|
|
|
291
292
|
memoryDir?: string,
|
|
292
293
|
opts?: { aadFilePath?: string }
|
|
293
294
|
): Promise<Buffer> {
|
|
294
|
-
const buf = await readFile(filePath);
|
|
295
|
+
const buf = memoryDir ? await readStoreFileBuffer(filePath, memoryDir) : await readFile(filePath);
|
|
295
296
|
if (!isEncryptedFile(buf)) {
|
|
296
297
|
// Plain file — legacy or unencrypted store.
|
|
297
298
|
return buf;
|
|
@@ -10,6 +10,38 @@ export class EntityCanonicalIdMigrationRunner {
|
|
|
10
10
|
private readonly readFingerprint: () => Promise<string> = async () => "static",
|
|
11
11
|
) {}
|
|
12
12
|
|
|
13
|
+
/**
|
|
14
|
+
* First half of deferred startup: when waiting, ensure the migration runs;
|
|
15
|
+
* when deferring, pre-read the entity entries the store serves instead and
|
|
16
|
+
* let the migration start later (finishBegin detaches it).
|
|
17
|
+
*/
|
|
18
|
+
public async begin(wait: boolean, entitiesDir: string): Promise<void> {
|
|
19
|
+
if (wait) {
|
|
20
|
+
await this.ensure();
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
const { readSafeEntityEntries } = await import("./entity-canonical-id-migration.js");
|
|
24
|
+
await readSafeEntityEntries(entitiesDir);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Second half: return the directory-initialized barrier when waiting, or
|
|
29
|
+
* detach it with a warning when the caller deferred the migration.
|
|
30
|
+
*/
|
|
31
|
+
public finishBegin(wait: boolean): Promise<void> {
|
|
32
|
+
const barrier = this.markDirectoriesInitialized();
|
|
33
|
+
if (wait) return barrier;
|
|
34
|
+
// Thrown rejections are Error-or-primitive; the message extractor
|
|
35
|
+
// handles both without asserting a shape.
|
|
36
|
+
const describeDeferredFailure = (failure: Error | string): string =>
|
|
37
|
+
failure instanceof Error ? failure.message : failure;
|
|
38
|
+
void barrier.catch(describeDeferredFailureAndWarn);
|
|
39
|
+
function describeDeferredFailureAndWarn(failure: Error | string): void {
|
|
40
|
+
console.warn(`entity canonical-id migration deferred at startup failed: ${describeDeferredFailure(failure)}`);
|
|
41
|
+
}
|
|
42
|
+
return Promise.resolve();
|
|
43
|
+
}
|
|
44
|
+
|
|
13
45
|
public async markDirectoriesInitialized(): Promise<void> {
|
|
14
46
|
this.directoriesInitialized = true;
|
|
15
47
|
await this.ensure();
|
|
@@ -32,6 +64,10 @@ export class EntityCanonicalIdMigrationRunner {
|
|
|
32
64
|
return this.ensure();
|
|
33
65
|
}
|
|
34
66
|
|
|
67
|
+
public async drain(): Promise<void> {
|
|
68
|
+
while (this.inFlight) await this.inFlight.catch(() => undefined);
|
|
69
|
+
}
|
|
70
|
+
|
|
35
71
|
private async runOrReuse(): Promise<void> {
|
|
36
72
|
let fingerprintAtStart = await this.readFingerprint();
|
|
37
73
|
if (this.migrationComplete && fingerprintAtStart === this.completedFingerprint) return;
|
|
@@ -370,7 +370,7 @@ async function assertEntityContentUnchanged(
|
|
|
370
370
|
}
|
|
371
371
|
}
|
|
372
372
|
|
|
373
|
-
async function readSafeEntityEntries(entitiesDir: string): Promise<string[]> {
|
|
373
|
+
export async function readSafeEntityEntries(entitiesDir: string): Promise<string[]> {
|
|
374
374
|
const rootStat = await lstat(entitiesDir);
|
|
375
375
|
if (rootStat.isSymbolicLink() || !rootStat.isDirectory()) {
|
|
376
376
|
throw new Error("Refusing entity canonical-id migration through a symlinked or non-directory entities root.");
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { statSync } from "node:fs";
|
|
1
2
|
/**
|
|
2
3
|
* Write-boundary surface of the entity canonical-id migration (issue #2213).
|
|
3
4
|
*
|
|
@@ -60,6 +61,13 @@ function parseJournalMappings(raw: string): Readonly<Record<string, string>> {
|
|
|
60
61
|
return cleaned;
|
|
61
62
|
}
|
|
62
63
|
|
|
64
|
+
/** Whether the journal says its migration completed. Throws like the mapping parser. */
|
|
65
|
+
function parseJournalComplete(raw: string): boolean {
|
|
66
|
+
// SAFETY: JSON.parse output is unknown; the shape is validated on access.
|
|
67
|
+
const parsed = JSON.parse(raw) as { complete?: unknown };
|
|
68
|
+
return parsed !== null && parsed.complete === true;
|
|
69
|
+
}
|
|
70
|
+
|
|
63
71
|
type JournalRead =
|
|
64
72
|
| { kind: "ok"; raw: string; key: string }
|
|
65
73
|
| { kind: "missing" }
|
|
@@ -183,13 +191,57 @@ export async function appendCanonicalizedTombstone(
|
|
|
183
191
|
* identity, so the next lookup retries instead of caching an empty table
|
|
184
192
|
* under a valid key.
|
|
185
193
|
*/
|
|
194
|
+
|
|
195
|
+
/** Journal lookup result: mapping table plus its completion flag. */
|
|
196
|
+
export type HistoricalEntityIdsEntry = {
|
|
197
|
+
mappings: Readonly<Record<string, string>>;
|
|
198
|
+
complete: boolean;
|
|
199
|
+
};
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Memoized journal-completion view keyed on the journal file's stat identity.
|
|
205
|
+
* entityList consults completion once per listed entity; without this each
|
|
206
|
+
* consult re-opens and re-reads the journal (quadratic I/O on large corpora).
|
|
207
|
+
*/
|
|
208
|
+
export function memoizedJournalCompletion(
|
|
209
|
+
cache: HistoricalEntityCanonicalIdCache,
|
|
210
|
+
stateDir: () => string,
|
|
211
|
+
): () => boolean {
|
|
212
|
+
let memo: { key: string; complete: boolean } | undefined;
|
|
213
|
+
return (): boolean => {
|
|
214
|
+
let key = "missing";
|
|
215
|
+
try {
|
|
216
|
+
const journalPath = path.join(stateDir(), ENTITY_CANONICAL_ID_MIGRATION_FILE);
|
|
217
|
+
const j = statSync(journalPath);
|
|
218
|
+
key = `${j.dev}:${j.ino}:${j.mtimeMs}:${j.ctimeMs}:${j.size}`;
|
|
219
|
+
} catch {
|
|
220
|
+
key = "missing";
|
|
221
|
+
}
|
|
222
|
+
if (memo && memo.key === key) return memo.complete;
|
|
223
|
+
const entry = cache.getEntry(stateDir());
|
|
224
|
+
memo = { key, complete: entry.complete };
|
|
225
|
+
return memo.complete;
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
|
|
186
229
|
export class HistoricalEntityCanonicalIdCache {
|
|
187
230
|
private mappings: Readonly<Record<string, string>> = {};
|
|
231
|
+
private complete = false;
|
|
188
232
|
private key: string | null = null;
|
|
189
233
|
private stateDir: string | null = null;
|
|
190
234
|
|
|
191
235
|
get(stateDir: string): Readonly<Record<string, string>> {
|
|
192
|
-
|
|
236
|
+
return this.getEntry(stateDir).mappings;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
/** Table plus the journal's completion flag, cached under one identity. */
|
|
241
|
+
getEntry(stateDir: string): HistoricalEntityIdsEntry {
|
|
242
|
+
const lastKnownMappings = this.stateDir === stateDir ? this.mappings : {};
|
|
243
|
+
const lastKnownComplete = this.stateDir === stateDir ? this.complete : false;
|
|
244
|
+
const lastKnown: HistoricalEntityIdsEntry = { mappings: lastKnownMappings, complete: lastKnownComplete };
|
|
193
245
|
const result = readJournalFileNoFollow(path.join(stateDir, ENTITY_CANONICAL_ID_MIGRATION_FILE));
|
|
194
246
|
if (result.kind === "refused") {
|
|
195
247
|
// Symlinked/non-regular journal: never follow it, never adopt its
|
|
@@ -208,9 +260,11 @@ export class HistoricalEntityCanonicalIdCache {
|
|
|
208
260
|
const key = result.kind === "missing" ? "missing" : result.key;
|
|
209
261
|
if (key !== this.key || stateDir !== this.stateDir) {
|
|
210
262
|
let table: Readonly<Record<string, string>> = {};
|
|
263
|
+
let complete = false;
|
|
211
264
|
if (result.kind === "ok") {
|
|
212
265
|
try {
|
|
213
266
|
table = parseJournalMappings(result.raw);
|
|
267
|
+
complete = parseJournalComplete(result.raw);
|
|
214
268
|
} catch {
|
|
215
269
|
// Parse failed under a VALID identity: do not commit the key —
|
|
216
270
|
// the next lookup retries instead of pinning an empty table.
|
|
@@ -218,10 +272,11 @@ export class HistoricalEntityCanonicalIdCache {
|
|
|
218
272
|
}
|
|
219
273
|
}
|
|
220
274
|
this.mappings = table;
|
|
275
|
+
this.complete = complete;
|
|
221
276
|
this.key = key;
|
|
222
277
|
this.stateDir = stateDir;
|
|
223
278
|
}
|
|
224
|
-
return this.mappings;
|
|
279
|
+
return { mappings: this.mappings, complete: this.complete };
|
|
225
280
|
}
|
|
226
281
|
}
|
|
227
282
|
|