@remnic/core 10.0.28 → 10.0.30
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 +47 -46
- package/dist/access-authorization-probe.js +48 -47
- package/dist/access-boundary.js +47 -46
- package/dist/access-citation-namespace.js +3 -2
- package/dist/access-cli.js +96 -95
- package/dist/access-cli.js.map +1 -1
- package/dist/access-extraction-force-flush.js +47 -46
- package/dist/access-http-briefing.js +48 -47
- package/dist/access-http-query.js +49 -48
- package/dist/access-http.js +65 -64
- package/dist/access-identity-continuity-surface.js +47 -46
- package/dist/access-lcm-surface.js +47 -46
- package/dist/access-mcp.js +57 -56
- package/dist/access-namespace-preflight.js +47 -46
- package/dist/access-observe-write-category.js +11 -11
- package/dist/access-observe-write-surface.js +47 -46
- package/dist/access-offline-manifest.js +10 -10
- package/dist/access-operations-batch.js +49 -48
- package/dist/access-operations.js +56 -55
- package/dist/access-recall-concurrency.js +47 -46
- package/dist/access-recall-response.js +47 -46
- package/dist/access-recall-surface.js +47 -46
- package/dist/access-schema.js +6 -5
- package/dist/access-service.js +47 -46
- package/dist/access-surface-catalog.js +49 -48
- package/dist/access-surface-catalog.js.map +1 -1
- package/dist/active-recall.js +23 -22
- package/dist/active-recall.js.map +1 -1
- package/dist/{auto-sync-UQESPLCB.js → auto-sync-MX2AHVPQ.js} +8 -8
- package/dist/briefing.js +19 -18
- package/dist/buffer.js +8 -8
- package/dist/{capsule-merge-LGYURS3C.js → capsule-merge-LGYZAMO4.js} +3 -3
- package/dist/causal-consolidation.js +21 -20
- package/dist/causal-consolidation.js.map +1 -1
- package/dist/{chunk-2TGDT37F.js → chunk-27OJ5G3D.js} +13 -13
- package/dist/{chunk-5LRV557D.js → chunk-2FIJQNL7.js} +2 -2
- package/dist/{chunk-ETPIYK4J.js → chunk-2VBDSZ7U.js} +2 -2
- package/dist/{chunk-QLI74KWK.js → chunk-2XVLY424.js} +3 -3
- package/dist/{chunk-P55FACBG.js → chunk-3BOQPLCF.js} +6 -6
- package/dist/{chunk-3LRBJQZE.js → chunk-3CHHQXAR.js} +2 -2
- package/dist/{chunk-3GYSZHMT.js → chunk-3CZRBTJK.js} +2 -2
- package/dist/{chunk-HFSPY26A.js → chunk-3IXNL6SK.js} +2 -2
- package/dist/{chunk-4DQ4VEIQ.js → chunk-3SN2D43X.js} +2 -2
- package/dist/{chunk-QMYB3N7X.js → chunk-4HNINU7Q.js} +28 -28
- package/dist/{chunk-LW6UHNWA.js → chunk-4HYP4OKH.js} +2 -2
- package/dist/{chunk-GKJJNO7C.js → chunk-4OE4UE2Y.js} +6 -6
- package/dist/{chunk-5SMR542T.js → chunk-5GT4ESLR.js} +2 -2
- package/dist/{chunk-DWL6YIZH.js → chunk-5JI3XWI2.js} +2 -2
- package/dist/{chunk-PDN6MRWF.js → chunk-5NPZFXQH.js} +2 -2
- package/dist/{chunk-Y6IF6H2U.js → chunk-5SMHQW45.js} +2 -2
- package/dist/{chunk-PRUXE3RC.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-XOSCGZEV.js → chunk-7FZ4YVOH.js} +2 -2
- package/dist/{chunk-MWWAQXZ6.js → chunk-7HFTCBCZ.js} +2 -2
- package/dist/{chunk-NDZ7APNY.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-KNWC2EPP.js → chunk-AUYFUBBE.js} +5 -5
- package/dist/{chunk-XN6ABQGM.js → chunk-BIE3ZH3N.js} +27 -27
- package/dist/{chunk-A3IURDU2.js → chunk-C2AOL6MK.js} +3 -3
- package/dist/{chunk-J4PILQJG.js → chunk-C2QVDLQV.js} +2 -2
- package/dist/{chunk-RKB4JZEM.js → chunk-CKR7LXEK.js} +67 -45
- package/dist/chunk-CKR7LXEK.js.map +1 -0
- package/dist/{chunk-AWLQNLTM.js → chunk-CSGFGQBI.js} +2 -2
- package/dist/{chunk-HZKDYR3Z.js → chunk-D4RV54HH.js} +2 -2
- package/dist/{chunk-OKNB2II7.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-XAJH5H3Z.js → chunk-FN6OFDBZ.js} +2 -2
- package/dist/{chunk-SUZG7UUK.js → chunk-G6DAE2LT.js} +2 -2
- package/dist/{chunk-AUPL775F.js → chunk-GSKW64NB.js} +2 -2
- package/dist/{chunk-3Z5LSDKR.js → chunk-GWB7ZD2A.js} +2 -2
- package/dist/{chunk-XOWR3I2W.js → chunk-HG2VVIKI.js} +9 -9
- package/dist/{chunk-23ZQPP2G.js → chunk-HOSJESKO.js} +3 -3
- package/dist/{chunk-EU6IRUCY.js → chunk-IDPZCHET.js} +2 -2
- package/dist/{chunk-3EE7EHFX.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-MIIZAEJD.js → chunk-IY7DGT2Z.js} +2 -2
- package/dist/{chunk-STBX5X6B.js → chunk-JAYNUYSJ.js} +2 -2
- package/dist/{chunk-GV2V4CX6.js → chunk-K2REHR42.js} +2 -2
- package/dist/{chunk-NRG67KMO.js → chunk-KFKFK6G6.js} +10 -10
- package/dist/{chunk-FDPE6OIK.js → chunk-LDEKVBVO.js} +4 -4
- package/dist/{chunk-C2J4LBNB.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-BQOGQULP.js → chunk-MZZEMPCI.js} +2 -2
- package/dist/{chunk-CQBNFDSG.js → chunk-NAPHQ2MG.js} +2 -2
- package/dist/{chunk-B6VPA3PF.js → chunk-NB2YX6X2.js} +6 -6
- package/dist/{chunk-PCSPXMPD.js → chunk-NCPR2FOJ.js} +3 -3
- package/dist/{chunk-67F6SBCJ.js → chunk-NOMHXZD7.js} +2 -2
- package/dist/{chunk-LWFWZTWX.js → chunk-NQV6G6MU.js} +2 -2
- package/dist/{chunk-JSFU7AFZ.js → chunk-OANSZGAG.js} +2 -2
- package/dist/{chunk-364M2T3D.js → chunk-OIIAIY7J.js} +3 -3
- package/dist/{chunk-IP5QGSNH.js → chunk-OXX4ORRL.js} +15 -15
- 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-P3NXZAV7.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-V4JZCTMK.js → chunk-TKBW4YLS.js} +2 -2
- package/dist/{chunk-4D2YH47Z.js → chunk-TRW7N4FS.js} +2 -2
- package/dist/{chunk-XALR4DOQ.js → chunk-TTIEXXL3.js} +365 -1108
- package/dist/chunk-TTIEXXL3.js.map +1 -0
- package/dist/{chunk-OQJKY3Z5.js → chunk-UAAAXSKQ.js} +2 -2
- package/dist/{chunk-4LJSCW24.js → chunk-UM73Q46J.js} +2 -2
- package/dist/{chunk-7EJCAFMS.js → chunk-UTXYOWDR.js} +2 -2
- package/dist/{chunk-OGOADX26.js → chunk-UUYAW3JH.js} +2 -2
- package/dist/{chunk-UTGHW2Y5.js → chunk-UVUBLLM3.js} +3 -3
- package/dist/{chunk-4GGTC2AX.js → chunk-V4FI2TG6.js} +3 -3
- package/dist/{chunk-MO6N4QYY.js → chunk-VARNSLUW.js} +4 -4
- package/dist/{chunk-RQUF3FXK.js → chunk-VEB7NEON.js} +4 -4
- package/dist/{chunk-SSFO7B7H.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-I4MYLDRY.js → chunk-WQIYGYY6.js} +2 -2
- package/dist/{chunk-GQJKVQJR.js → chunk-WYLBEJQI.js} +2 -2
- package/dist/{chunk-JCPR47NY.js → chunk-XNROYP5E.js} +2 -2
- package/dist/{chunk-XNAOCY67.js → chunk-XVZIBMSY.js} +3 -3
- package/dist/{chunk-TZZFGR4E.js → chunk-YGVZ4QOZ.js} +4 -4
- package/dist/{chunk-C5JXYJHN.js → chunk-YZTFQBYY.js} +4 -4
- package/dist/{chunk-JTLXVXYH.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-CRVL6SLC.js → chunk-ZXTAHSQU.js} +2 -2
- package/dist/cli.js +80 -79
- package/dist/coding/export-okf-codegraph.js +20 -19
- package/dist/coding/export-okf-codegraph.js.map +1 -1
- package/dist/compounding/engine.js +22 -21
- package/dist/config.js +23 -22
- package/dist/connectors/codex-materialize-runner.js +20 -19
- package/dist/connectors/codex-materialize.js +2 -2
- package/dist/connectors/index.js +20 -19
- 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 +21 -20
- package/dist/corpus-watermark.js +21 -20
- package/dist/deep-recall-http-glue.js +7 -6
- package/dist/entity-canonical-id-migration-GD5MPFUF.js +28 -0
- package/dist/entity-retrieval.js +19 -18
- package/dist/explicit-capture.js +8 -7
- package/dist/external-wiki-access.js +48 -47
- package/dist/extraction.js +24 -23
- package/dist/harmonic-retrieval.js +20 -19
- package/dist/harmonic-source-lookup.js +3 -2
- package/dist/index.js +96 -95
- package/dist/location/index.js +22 -21
- package/dist/maintenance/memory-governance.js +19 -18
- package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +19 -18
- package/dist/maintenance/rebuild-memory-projection-runner.js +21 -20
- package/dist/maintenance/rebuild-memory-projection.js +20 -19
- package/dist/memory-browse-operations.js +48 -47
- package/dist/memory-subject.js +4 -3
- package/dist/namespaces/migrate.js +20 -19
- package/dist/namespaces/search.js +7 -7
- package/dist/namespaces/storage.js +19 -18
- 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 +22 -21
- package/dist/operator-toolkit.js +33 -32
- package/dist/orchestration/maintenance.js +32 -31
- 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 +96 -95
- package/dist/page-versioning.js +2 -2
- package/dist/recall-navigation-operations.js +48 -47
- package/dist/recall-navigation.js +47 -46
- package/dist/recall-state.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 +24 -23
- 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 +21 -20
- package/dist/semantic-rule-promotion.js +19 -18
- package/dist/semantic-rule-verifier.js +19 -18
- package/dist/shared-context/manager.js +2 -2
- package/dist/source-agent-qualifier.js +23 -22
- package/dist/standup/build.js +19 -18
- 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 +18 -17
- package/dist/subject-config.js +5 -4
- package/dist/support-passport/index.js +50 -49
- package/dist/transfer/capsule-import.js +3 -3
- package/dist/transfer/export-okf.js +20 -19
- package/dist/verified-recall.js +19 -18
- package/dist/who-knows-cli.js +20 -19
- package/dist/who-knows.js +19 -18
- package/dist/write-envelope.js +5 -4
- package/package.json +2 -2
- 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/dist/chunk-MAAL6OZV.js.map +0 -1
- package/dist/chunk-NZFFGO5O.js.map +0 -1
- package/dist/chunk-RKB4JZEM.js.map +0 -1
- package/dist/chunk-XALR4DOQ.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-2TGDT37F.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-QLI74KWK.js.map → chunk-2XVLY424.js.map} +0 -0
- /package/dist/{chunk-P55FACBG.js.map → chunk-3BOQPLCF.js.map} +0 -0
- /package/dist/{chunk-3LRBJQZE.js.map → chunk-3CHHQXAR.js.map} +0 -0
- /package/dist/{chunk-3GYSZHMT.js.map → chunk-3CZRBTJK.js.map} +0 -0
- /package/dist/{chunk-HFSPY26A.js.map → chunk-3IXNL6SK.js.map} +0 -0
- /package/dist/{chunk-4DQ4VEIQ.js.map → chunk-3SN2D43X.js.map} +0 -0
- /package/dist/{chunk-QMYB3N7X.js.map → chunk-4HNINU7Q.js.map} +0 -0
- /package/dist/{chunk-LW6UHNWA.js.map → chunk-4HYP4OKH.js.map} +0 -0
- /package/dist/{chunk-GKJJNO7C.js.map → chunk-4OE4UE2Y.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-PDN6MRWF.js.map → chunk-5NPZFXQH.js.map} +0 -0
- /package/dist/{chunk-Y6IF6H2U.js.map → chunk-5SMHQW45.js.map} +0 -0
- /package/dist/{chunk-PRUXE3RC.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-XOSCGZEV.js.map → chunk-7FZ4YVOH.js.map} +0 -0
- /package/dist/{chunk-MWWAQXZ6.js.map → chunk-7HFTCBCZ.js.map} +0 -0
- /package/dist/{chunk-NDZ7APNY.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-KNWC2EPP.js.map → chunk-AUYFUBBE.js.map} +0 -0
- /package/dist/{chunk-XN6ABQGM.js.map → chunk-BIE3ZH3N.js.map} +0 -0
- /package/dist/{chunk-A3IURDU2.js.map → chunk-C2AOL6MK.js.map} +0 -0
- /package/dist/{chunk-J4PILQJG.js.map → chunk-C2QVDLQV.js.map} +0 -0
- /package/dist/{chunk-AWLQNLTM.js.map → chunk-CSGFGQBI.js.map} +0 -0
- /package/dist/{chunk-HZKDYR3Z.js.map → chunk-D4RV54HH.js.map} +0 -0
- /package/dist/{chunk-OKNB2II7.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-XAJH5H3Z.js.map → chunk-FN6OFDBZ.js.map} +0 -0
- /package/dist/{chunk-SUZG7UUK.js.map → chunk-G6DAE2LT.js.map} +0 -0
- /package/dist/{chunk-AUPL775F.js.map → chunk-GSKW64NB.js.map} +0 -0
- /package/dist/{chunk-3Z5LSDKR.js.map → chunk-GWB7ZD2A.js.map} +0 -0
- /package/dist/{chunk-XOWR3I2W.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-3EE7EHFX.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-MIIZAEJD.js.map → chunk-IY7DGT2Z.js.map} +0 -0
- /package/dist/{chunk-STBX5X6B.js.map → chunk-JAYNUYSJ.js.map} +0 -0
- /package/dist/{chunk-GV2V4CX6.js.map → chunk-K2REHR42.js.map} +0 -0
- /package/dist/{chunk-NRG67KMO.js.map → chunk-KFKFK6G6.js.map} +0 -0
- /package/dist/{chunk-FDPE6OIK.js.map → chunk-LDEKVBVO.js.map} +0 -0
- /package/dist/{chunk-C2J4LBNB.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-BQOGQULP.js.map → chunk-MZZEMPCI.js.map} +0 -0
- /package/dist/{chunk-CQBNFDSG.js.map → chunk-NAPHQ2MG.js.map} +0 -0
- /package/dist/{chunk-B6VPA3PF.js.map → chunk-NB2YX6X2.js.map} +0 -0
- /package/dist/{chunk-PCSPXMPD.js.map → chunk-NCPR2FOJ.js.map} +0 -0
- /package/dist/{chunk-67F6SBCJ.js.map → chunk-NOMHXZD7.js.map} +0 -0
- /package/dist/{chunk-LWFWZTWX.js.map → chunk-NQV6G6MU.js.map} +0 -0
- /package/dist/{chunk-JSFU7AFZ.js.map → chunk-OANSZGAG.js.map} +0 -0
- /package/dist/{chunk-364M2T3D.js.map → chunk-OIIAIY7J.js.map} +0 -0
- /package/dist/{chunk-IP5QGSNH.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-P3NXZAV7.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-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-4LJSCW24.js.map → chunk-UM73Q46J.js.map} +0 -0
- /package/dist/{chunk-7EJCAFMS.js.map → chunk-UTXYOWDR.js.map} +0 -0
- /package/dist/{chunk-OGOADX26.js.map → chunk-UUYAW3JH.js.map} +0 -0
- /package/dist/{chunk-UTGHW2Y5.js.map → chunk-UVUBLLM3.js.map} +0 -0
- /package/dist/{chunk-4GGTC2AX.js.map → chunk-V4FI2TG6.js.map} +0 -0
- /package/dist/{chunk-MO6N4QYY.js.map → chunk-VARNSLUW.js.map} +0 -0
- /package/dist/{chunk-RQUF3FXK.js.map → chunk-VEB7NEON.js.map} +0 -0
- /package/dist/{chunk-SSFO7B7H.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-I4MYLDRY.js.map → chunk-WQIYGYY6.js.map} +0 -0
- /package/dist/{chunk-GQJKVQJR.js.map → chunk-WYLBEJQI.js.map} +0 -0
- /package/dist/{chunk-JCPR47NY.js.map → chunk-XNROYP5E.js.map} +0 -0
- /package/dist/{chunk-XNAOCY67.js.map → chunk-XVZIBMSY.js.map} +0 -0
- /package/dist/{chunk-TZZFGR4E.js.map → chunk-YGVZ4QOZ.js.map} +0 -0
- /package/dist/{chunk-C5JXYJHN.js.map → chunk-YZTFQBYY.js.map} +0 -0
- /package/dist/{chunk-JTLXVXYH.js.map → chunk-ZCRDYQL6.js.map} +0 -0
- /package/dist/{chunk-CRVL6SLC.js.map → chunk-ZXTAHSQU.js.map} +0 -0
- /package/dist/{resolution-ZU5BIAJA.js.map → entity-canonical-id-migration-GD5MPFUF.js.map} +0 -0
|
@@ -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
|
|
|
@@ -23,7 +23,7 @@ import {
|
|
|
23
23
|
} from "../utils/macos-replication-junk.js";
|
|
24
24
|
import { getCachedEntities, setCachedEntities } from "../memory-cache.js";
|
|
25
25
|
import type { VersionTrigger } from "../page-versioning.js";
|
|
26
|
-
import { SecureStoreLockedError } from "../secure-store/secure-fs.js";
|
|
26
|
+
import { SecureStoreDecryptError, SecureStoreLockedError } from "../secure-store/secure-fs.js";
|
|
27
27
|
import type {
|
|
28
28
|
EntityActivityEntry,
|
|
29
29
|
EntityFile,
|
|
@@ -67,6 +67,8 @@ export interface EntityStoreDeps {
|
|
|
67
67
|
readonly entitiesDir: string;
|
|
68
68
|
readonly entitySchemas: PluginConfig["entitySchemas"] | undefined;
|
|
69
69
|
currentHistoricalIds(): Readonly<Record<string, string>>;
|
|
70
|
+
/** True when the entity canonical-id journal says migration completed. */
|
|
71
|
+
entityCanonicalMigrationComplete(): boolean;
|
|
70
72
|
findMatchingEntity(proposedName: string, type: string): Promise<string | null>;
|
|
71
73
|
getEntityCacheSecureStoreKey(): string;
|
|
72
74
|
getEntityMutationVersion(): number;
|
|
@@ -373,18 +375,56 @@ export class EntityStore {
|
|
|
373
375
|
return normalized;
|
|
374
376
|
}
|
|
375
377
|
|
|
376
|
-
async
|
|
377
|
-
const filePath = this.deps.resolveEntityFilePath(name);
|
|
378
|
-
if (filePath === null) {
|
|
379
|
-
return "";
|
|
380
|
-
}
|
|
378
|
+
private async readEntityFileResolvingRename(firstPath: string, entityId: string) {
|
|
381
379
|
try {
|
|
382
|
-
return await this.deps.readStorageSecureFile(
|
|
380
|
+
return { id: entityId, content: await this.deps.readStorageSecureFile(firstPath) };
|
|
383
381
|
} catch (err) {
|
|
384
382
|
if (err instanceof SecureStoreLockedError) throw err;
|
|
383
|
+
// ENOENT: renamed away (retry below). ELOOP: planted symlink — skip.
|
|
384
|
+
if (isErrnoCode(err, "ELOOP")) return null;
|
|
385
385
|
if (!isErrnoCode(err, "ENOENT")) throw err;
|
|
386
|
+
}
|
|
387
|
+
const historicalIds = this.deps.currentHistoricalIds();
|
|
388
|
+
const canonical = resolveHistoricalEntityCanonicalId(entityId, historicalIds);
|
|
389
|
+
const candidates = new Set([canonical]);
|
|
390
|
+
for (const legacy of Object.keys(historicalIds)) {
|
|
391
|
+
if (resolveHistoricalEntityCanonicalId(legacy, historicalIds) === canonical) candidates.add(legacy);
|
|
392
|
+
}
|
|
393
|
+
candidates.delete(entityId);
|
|
394
|
+
for (const candidate of candidates) {
|
|
395
|
+
try {
|
|
396
|
+
return { id: candidate, content: await this.deps.readStorageSecureFile(
|
|
397
|
+
path.join(this.deps.entitiesDir, `${candidate}.md`),
|
|
398
|
+
) };
|
|
399
|
+
} catch (err) {
|
|
400
|
+
if (err instanceof SecureStoreLockedError) throw err;
|
|
401
|
+
// ENOENT/ELOOP: gone or a planted symlink — try the next candidate.
|
|
402
|
+
if (isErrnoCode(err, "ENOENT") || isErrnoCode(err, "ELOOP")) continue;
|
|
403
|
+
throw err;
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
return null;
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
async readEntity(name: string): Promise<string> {
|
|
410
|
+
const filePath = this.deps.resolveEntityFilePath(name);
|
|
411
|
+
if (filePath === null) {
|
|
386
412
|
return "";
|
|
387
413
|
}
|
|
414
|
+
// Batch reads retry renames unconditionally (a listed file that ENOENTs
|
|
415
|
+
// is definitionally a race), but a DIRECT lookup of a legacy id must stop
|
|
416
|
+
// resolving once the journal says migration completed and its file is gone.
|
|
417
|
+
if (this.deps.entityCanonicalMigrationComplete()) {
|
|
418
|
+
try {
|
|
419
|
+
return await this.deps.readStorageSecureFile(filePath);
|
|
420
|
+
} catch (err) {
|
|
421
|
+
if (err instanceof SecureStoreLockedError) throw err;
|
|
422
|
+
// ENOENT: gone. ELOOP: a planted symlink the open refused to follow.
|
|
423
|
+
if (isErrnoCode(err, "ENOENT") || isErrnoCode(err, "ELOOP")) return "";
|
|
424
|
+
throw err;
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
return (await this.readEntityFileResolvingRename(filePath, name))?.content ?? "";
|
|
388
428
|
}
|
|
389
429
|
|
|
390
430
|
/**
|
|
@@ -434,6 +474,16 @@ export class EntityStore {
|
|
|
434
474
|
* Symlinks, FIFOs, and devices are skipped before any read. The entities
|
|
435
475
|
* root is refused separately, before the directory is listed.
|
|
436
476
|
*/
|
|
477
|
+
private async isMissingEntry(file: string): Promise<boolean> {
|
|
478
|
+
try {
|
|
479
|
+
await lstat(path.join(this.deps.entitiesDir, file));
|
|
480
|
+
return false;
|
|
481
|
+
} catch (err) {
|
|
482
|
+
if (isErrnoCode(err, "ENOENT")) return true;
|
|
483
|
+
throw err;
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
|
|
437
487
|
private async isContainedRegularEntityFile(file: string): Promise<boolean> {
|
|
438
488
|
const filePath = path.join(this.deps.entitiesDir, file);
|
|
439
489
|
let entryStat;
|
|
@@ -491,23 +541,54 @@ export class EntityStore {
|
|
|
491
541
|
const names: string[] = [];
|
|
492
542
|
const batchSize = 100;
|
|
493
543
|
for (let i = 0; i < mdFiles.length; i += batchSize) {
|
|
544
|
+
// Journal table snapshot once per call (per-entry reads are quadratic).
|
|
545
|
+
const historicalIds = this.deps.currentHistoricalIds();
|
|
546
|
+
const snapshotIds = new Set(mdFiles.map((file) => file.slice(0, -3)));
|
|
547
|
+
const journalComplete = this.deps.entityCanonicalMigrationComplete();
|
|
548
|
+
const resolveCanonical = (entityId: string): string => {
|
|
549
|
+
const canonical = resolveHistoricalEntityCanonicalId(entityId, historicalIds);
|
|
550
|
+
if (canonical === entityId) return entityId;
|
|
551
|
+
// Completed journal: the on-disk filename is authoritative; a
|
|
552
|
+
// late legacy file stays under its own readable name.
|
|
553
|
+
if (journalComplete) return entityId;
|
|
554
|
+
// Both claimants coexisting (copy-before-unlink) keep both names.
|
|
555
|
+
if (snapshotIds.has(canonical)) return entityId;
|
|
556
|
+
return canonical;
|
|
557
|
+
};
|
|
494
558
|
const resolved = await Promise.all(mdFiles.slice(i, i + batchSize).map(async (file) => {
|
|
559
|
+
const entityId = file.slice(0, -3);
|
|
495
560
|
try {
|
|
496
|
-
|
|
561
|
+
// Report the canonical id for every indexable file so legacy
|
|
562
|
+
// names surface as their canonical identity from the start.
|
|
563
|
+
if (await this.isIndexableEntityMarkdown(file)) {
|
|
564
|
+
return resolveCanonical(entityId);
|
|
565
|
+
}
|
|
497
566
|
} catch (err) {
|
|
498
|
-
|
|
499
|
-
|
|
567
|
+
// Lock and decrypt errors abort the listing; anything else
|
|
568
|
+
// (EACCES) fails only this entry — one unhealthy file must not
|
|
569
|
+
// empty it.
|
|
570
|
+
if (err instanceof SecureStoreLockedError || err instanceof SecureStoreDecryptError) throw err;
|
|
571
|
+
}
|
|
572
|
+
// Vanished listed entry: resolve through the journal (rename
|
|
573
|
+
// race); lstat failures stay within this entry.
|
|
574
|
+
try {
|
|
575
|
+
if (await this.isMissingEntry(file)) {
|
|
576
|
+
const canonical = resolveHistoricalEntityCanonicalId(entityId, this.deps.currentHistoricalIds());
|
|
577
|
+
if (canonical !== entityId) return canonical;
|
|
578
|
+
}
|
|
579
|
+
} catch (err) {
|
|
580
|
+
if (err instanceof SecureStoreLockedError || err instanceof SecureStoreDecryptError) throw err;
|
|
500
581
|
}
|
|
501
|
-
return
|
|
582
|
+
return null;
|
|
502
583
|
}));
|
|
503
|
-
|
|
504
|
-
if (name) names.push(name);
|
|
505
|
-
}
|
|
584
|
+
names.push(...resolved.filter((name): name is string => name !== null));
|
|
506
585
|
}
|
|
507
|
-
|
|
508
|
-
|
|
586
|
+
// The copy-before-unlink migration window can snapshot both the legacy
|
|
587
|
+
// and canonical names; the legacy fallback then resolves to the same
|
|
588
|
+
// canonical id. Deduplicate so the total stays correct.
|
|
589
|
+
return [...new Set(names)].sort();
|
|
509
590
|
} catch (err) {
|
|
510
|
-
if (err instanceof SecureStoreLockedError) throw err;
|
|
591
|
+
if (err instanceof SecureStoreLockedError || err instanceof SecureStoreDecryptError) throw err;
|
|
511
592
|
return [];
|
|
512
593
|
}
|
|
513
594
|
}
|
|
@@ -636,10 +717,22 @@ export class EntityStore {
|
|
|
636
717
|
if (cached) return cached;
|
|
637
718
|
|
|
638
719
|
try {
|
|
639
|
-
|
|
720
|
+
// A symlinked/replaced entities root has no readable entries (merged
|
|
721
|
+
// #3207 semantics): resolve empty rather than following it. Within a
|
|
722
|
+
// trusted root, readdir directly so planted entries reach the secure
|
|
723
|
+
// read instead of being swallowed by the listing guards.
|
|
724
|
+
if (!(await this.isTrustedEntitiesRoot())) return [];
|
|
725
|
+
const listed = await readdir(this.deps.entitiesDir);
|
|
726
|
+
const { kept: entries, ignored } = withoutMacOsReplicationJunk(listed, (name) => name);
|
|
727
|
+
warnIgnoredMacOsReplicationJunk(this.deps.entitiesDir, ignored, (message) => {
|
|
728
|
+
log.warn(`entity load ${message}`);
|
|
729
|
+
});
|
|
640
730
|
const mdFiles = entries.filter((e) => e.endsWith(".md"));
|
|
641
731
|
if (mdFiles.length === 0) return [];
|
|
642
732
|
|
|
733
|
+
const historicalIds = this.deps.currentHistoricalIds();
|
|
734
|
+
const seen = new Map<string, Set<string>>();
|
|
735
|
+
|
|
643
736
|
// Read all entity files in parallel batches to avoid O(N) sequential I/O.
|
|
644
737
|
// With 3000+ entity files, sequential reads can take 15-20s under load.
|
|
645
738
|
// Batching at 100 keeps file-descriptor pressure manageable while staying fast.
|
|
@@ -652,19 +745,39 @@ export class EntityStore {
|
|
|
652
745
|
const batch = mdFiles.slice(i, i + BATCH_SIZE);
|
|
653
746
|
const results = await Promise.all(
|
|
654
747
|
batch.map(async (entry) => {
|
|
748
|
+
const entityId = entry.slice(0, -".md".length);
|
|
749
|
+
const entryPath = path.join(this.deps.entitiesDir, entry);
|
|
655
750
|
try {
|
|
656
|
-
if (!(await this.isContainedRegularEntityFile(entry)))
|
|
657
|
-
|
|
751
|
+
if (!(await this.isContainedRegularEntityFile(entry))) {
|
|
752
|
+
// A listed entry that vanished races a background migration
|
|
753
|
+
// rename: resolve through historical ids like a direct read.
|
|
754
|
+
// Still-present symlinks/dirs stay skipped.
|
|
755
|
+
if (await this.isMissingEntry(entry)) {
|
|
756
|
+
return await this.readEntityFileResolvingRename(entryPath, entityId);
|
|
757
|
+
}
|
|
758
|
+
return null;
|
|
759
|
+
}
|
|
760
|
+
return await this.readEntityFileResolvingRename(entryPath, entityId);
|
|
658
761
|
} catch (err) {
|
|
659
|
-
|
|
660
|
-
|
|
762
|
+
// Lock and decrypt failures abort the corpus read (the store
|
|
763
|
+
// is unusable or tampered); anything else isolates to this entry.
|
|
764
|
+
if (err instanceof SecureStoreLockedError || err instanceof SecureStoreDecryptError) throw err;
|
|
765
|
+
log.warn(`entity read isolated failure for ${entry}: ${err instanceof Error ? err.message : String(err)}`);
|
|
661
766
|
return null;
|
|
662
767
|
}
|
|
663
768
|
}),
|
|
664
769
|
);
|
|
665
|
-
for (const
|
|
666
|
-
if (
|
|
667
|
-
const
|
|
770
|
+
for (const result of results) {
|
|
771
|
+
if (result === null) continue;
|
|
772
|
+
const canonical = resolveHistoricalEntityCanonicalId(result.id, historicalIds);
|
|
773
|
+
let contents = seen.get(canonical);
|
|
774
|
+
if (contents?.has(result.content)) continue;
|
|
775
|
+
if (!contents) {
|
|
776
|
+
contents = new Set();
|
|
777
|
+
seen.set(canonical, contents);
|
|
778
|
+
}
|
|
779
|
+
contents.add(result.content);
|
|
780
|
+
const entity = parseEntityFile(result.content, this.deps.entitySchemas);
|
|
668
781
|
if (!entity.name.trim() || !entity.type.trim()) continue;
|
|
669
782
|
entities.push(entity);
|
|
670
783
|
}
|
package/src/storage.ts
CHANGED
|
@@ -2323,10 +2323,7 @@ export class StorageManager extends TombstoneBlockedCaptureIndexHost {
|
|
|
2323
2323
|
private _entityStore: EntityStore | undefined;
|
|
2324
2324
|
|
|
2325
2325
|
private get entityStore(): EntityStore {
|
|
2326
|
-
|
|
2327
|
-
this._entityStore = new EntityStore(selfDeps<ConstructorParameters<typeof EntityStore>[0]>(this));
|
|
2328
|
-
}
|
|
2329
|
-
return this._entityStore;
|
|
2326
|
+
return (this._entityStore ??= new EntityStore(selfDeps<ConstructorParameters<typeof EntityStore>[0]>(this)));
|
|
2330
2327
|
}
|
|
2331
2328
|
|
|
2332
2329
|
/** IdentityContinuityStore (storage.ts decomposition). Lazy; selfDeps live wiring. */
|
|
@@ -3537,6 +3534,16 @@ export class StorageManager extends TombstoneBlockedCaptureIndexHost {
|
|
|
3537
3534
|
private currentHistoricalIds(): Readonly<Record<string, string>> {
|
|
3538
3535
|
return this.historicalEntityCanonicalIds.get(this.stateDir);
|
|
3539
3536
|
}
|
|
3537
|
+
|
|
3538
|
+
/** Journal completion for EntityStore's legacy-id retry gate (live view). */
|
|
3539
|
+
entityCanonicalMigrationComplete = entityRefs.memoizedJournalCompletion(this.historicalEntityCanonicalIds, () => this.stateDir);
|
|
3540
|
+
|
|
3541
|
+
normalizeEntityName(raw: string, type: string): string {
|
|
3542
|
+
return entityRefs.resolveHistoricalEntityCanonicalId(
|
|
3543
|
+
this.entityStore.normalizeEntityName(raw, type),
|
|
3544
|
+
this.currentHistoricalIds()
|
|
3545
|
+
);
|
|
3546
|
+
}
|
|
3540
3547
|
/** Post-persist repair subsystem (issue #2213) — storage/entity-ref-repair.ts. */
|
|
3541
3548
|
private _entityRefRepair: EntityRefRepair | undefined;
|
|
3542
3549
|
private get entityRefRepair(): EntityRefRepair {
|
|
@@ -3561,12 +3568,6 @@ export class StorageManager extends TombstoneBlockedCaptureIndexHost {
|
|
|
3561
3568
|
() => this.runLegacyEntityCanonicalIdMigration(),
|
|
3562
3569
|
() => entityMigration.getFingerprint(this.baseDir, this.entitiesDir, () => String(this.getEntityMutationVersion()))
|
|
3563
3570
|
);
|
|
3564
|
-
normalizeEntityName(raw: string, type: string): string {
|
|
3565
|
-
return entityRefs.resolveHistoricalEntityCanonicalId(
|
|
3566
|
-
this.entityStore.normalizeEntityName(raw, type),
|
|
3567
|
-
this.currentHistoricalIds()
|
|
3568
|
-
);
|
|
3569
|
-
}
|
|
3570
3571
|
|
|
3571
3572
|
/**
|
|
3572
3573
|
* Read-only view of this store's user alias table, for call sites that
|
|
@@ -3623,7 +3624,7 @@ export class StorageManager extends TombstoneBlockedCaptureIndexHost {
|
|
|
3623
3624
|
return completionFingerprint;
|
|
3624
3625
|
}
|
|
3625
3626
|
|
|
3626
|
-
async ensureDirectories(): Promise<void> {
|
|
3627
|
+
async ensureDirectories(options: { waitForEntityMigration?: boolean } = {}): Promise<void> {
|
|
3627
3628
|
await mkdir(this.baseDir, { recursive: true });
|
|
3628
3629
|
await entityMigration.validateRoots(this.baseDir, this.entitiesDir, this.stateDir, this.archiveDir);
|
|
3629
3630
|
const today = new Date().toISOString().slice(0, 10);
|
|
@@ -3640,7 +3641,7 @@ export class StorageManager extends TombstoneBlockedCaptureIndexHost {
|
|
|
3640
3641
|
await mkdir(this.identityAuditsWeeklyDir, { recursive: true });
|
|
3641
3642
|
await mkdir(this.identityAuditsMonthlyDir, { recursive: true });
|
|
3642
3643
|
await mkdir(path.join(this.baseDir, "config"), { recursive: true });
|
|
3643
|
-
await this.entityCanonicalIdMigration.
|
|
3644
|
+
await this.entityCanonicalIdMigration.begin(options.waitForEntityMigration !== false, this.entitiesDir);
|
|
3644
3645
|
// Activate the corpus sentinel at setup (issue #1902, Cursor Medium): an
|
|
3645
3646
|
// existing corpus predating this feature has a size-0/absent sentinel, which
|
|
3646
3647
|
// getCachedMemories treats as a miss — so a read-heavy workload would never
|
|
@@ -3654,9 +3655,9 @@ export class StorageManager extends TombstoneBlockedCaptureIndexHost {
|
|
|
3654
3655
|
if (this.hotMemoriesCacheEnabled && this.getMemoryCorpusVersion() === 0) {
|
|
3655
3656
|
this.bumpMemoryCorpusVersion();
|
|
3656
3657
|
}
|
|
3657
|
-
|
|
3658
|
+
return this.entityCanonicalIdMigration.finishBegin(options.waitForEntityMigration !== false);
|
|
3658
3659
|
}
|
|
3659
|
-
|
|
3660
|
+
drainEntityCanonicalIdMigration = (): Promise<void> => this.entityCanonicalIdMigration.drain();
|
|
3660
3661
|
/**
|
|
3661
3662
|
* Resolve the on-disk write path for a memory of the given category, creating
|
|
3662
3663
|
* the target directory. Category routing goes through the shared
|
|
@@ -4079,7 +4080,6 @@ export class StorageManager extends TombstoneBlockedCaptureIndexHost {
|
|
|
4079
4080
|
await this.addActiveFactContentHash(memory);
|
|
4080
4081
|
}
|
|
4081
4082
|
|
|
4082
|
-
|
|
4083
4083
|
private async syncFactHashIndexAfterRewrite(before: MemoryFile, after: MemoryFile): Promise<void> {
|
|
4084
4084
|
if (before.frontmatter.category !== "fact" && after.frontmatter.category !== "fact") return;
|
|
4085
4085
|
|