@remnic/core 10.0.58 → 10.0.60
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 +49 -47
- package/dist/access-authorization-probe.js +50 -48
- package/dist/access-boundary.js +49 -47
- package/dist/access-cli.js +136 -134
- package/dist/access-cli.js.map +1 -1
- package/dist/access-extraction-force-flush.js +49 -47
- package/dist/access-http-briefing.js +50 -48
- package/dist/access-http-query.js +51 -49
- package/dist/access-http.js +72 -70
- package/dist/access-identity-continuity-surface.js +49 -47
- package/dist/access-lcm-surface.js +49 -47
- package/dist/access-mcp.js +60 -58
- package/dist/access-namespace-preflight.js +49 -47
- package/dist/access-observe-write-category.js +8 -8
- package/dist/access-observe-write-surface.js +49 -47
- package/dist/access-offline-manifest.js +7 -7
- package/dist/access-operations-batch.js +52 -50
- package/dist/access-operations.js +59 -57
- package/dist/access-recall-concurrency.js +49 -47
- package/dist/access-recall-response.js +49 -47
- package/dist/access-recall-surface.js +49 -47
- package/dist/access-schema.js +7 -7
- package/dist/access-service.js +49 -47
- package/dist/access-surface-catalog.js +51 -49
- package/dist/access-surface-catalog.js.map +1 -1
- package/dist/active-recall.js +23 -21
- package/dist/active-recall.js.map +1 -1
- package/dist/adapters/index.js +7 -7
- package/dist/adapters/registry.js +3 -3
- package/dist/briefing.js +15 -13
- package/dist/buffer.js +5 -5
- package/dist/causal-behavior.js +2 -2
- package/dist/causal-chain.js +2 -2
- package/dist/causal-consolidation.js +18 -16
- package/dist/causal-consolidation.js.map +1 -1
- package/dist/causal-retrieval.js +2 -2
- package/dist/causal-trajectory-graph.js +2 -2
- package/dist/causal-trajectory.js +1 -1
- package/dist/{chunk-XDKEHZUC.js → chunk-264B57GV.js} +2 -2
- package/dist/{chunk-UQYBPPVC.js → chunk-2EJAPJ5G.js} +20 -10
- package/dist/chunk-2EJAPJ5G.js.map +1 -0
- package/dist/{chunk-LTDK6HBV.js → chunk-2HVCTFKG.js} +2 -2
- package/dist/{chunk-E2YAGEFR.js → chunk-3DG2YAAX.js} +86 -86
- package/dist/chunk-4LZ7SVKM.js +211 -0
- package/dist/chunk-4LZ7SVKM.js.map +1 -0
- package/dist/{chunk-LIVNX7WK.js → chunk-4O4VAEWH.js} +2 -2
- package/dist/{chunk-NRGQ5HJO.js → chunk-4P4VLPGB.js} +1 -1
- package/dist/{chunk-GIRWNTMV.js → chunk-4PG5R6BJ.js} +2 -2
- package/dist/{chunk-AOSCGNIC.js → chunk-5XY46Z2W.js} +3 -3
- package/dist/{chunk-52II7PB6.js → chunk-645WOU5I.js} +4 -4
- package/dist/{chunk-RG3I2663.js → chunk-64JLBFCQ.js} +2 -2
- package/dist/{chunk-G3T6YMBO.js → chunk-64S6X4IU.js} +7 -7
- package/dist/{chunk-XHEKS4UN.js → chunk-66CC6L6R.js} +2 -2
- package/dist/{chunk-3632XJMR.js → chunk-6BKSUA6P.js} +2 -2
- package/dist/{chunk-6AEKSOWV.js → chunk-6I6DFULH.js} +8 -8
- package/dist/{chunk-2EYCW2ET.js → chunk-74Z2BYEK.js} +2 -2
- package/dist/{chunk-QAHP5SBG.js → chunk-7DHNBABU.js} +3 -3
- package/dist/{chunk-HJ4X4BGS.js → chunk-ADK5TMQL.js} +2 -2
- package/dist/{chunk-KCTL5EY7.js → chunk-AWKADEMO.js} +3 -3
- package/dist/{chunk-VV73XT3A.js → chunk-BJLCV2P3.js} +2 -2
- package/dist/{chunk-QF5EJ5VP.js → chunk-C5GTIRIO.js} +38 -38
- package/dist/{chunk-LRVFS4KA.js → chunk-DF4SYU4Z.js} +2 -2
- package/dist/{chunk-WBN66HPI.js → chunk-ELHJK7LM.js} +6 -6
- package/dist/{chunk-6CDNOREO.js → chunk-F3GX76LV.js} +2 -2
- package/dist/{chunk-AAANTXCQ.js → chunk-FZVZYU5C.js} +2 -2
- package/dist/{chunk-ON77FQXH.js → chunk-G6GMZJQ4.js} +2 -2
- package/dist/{chunk-HJQ4NKKO.js → chunk-GGHX4YHM.js} +2 -2
- package/dist/{chunk-HM3ZTDAX.js → chunk-GJF337YM.js} +2 -2
- package/dist/{chunk-GDYLZ6FR.js → chunk-HJ3UTGIW.js} +3 -3
- package/dist/{chunk-W3R77WHT.js → chunk-HL643H2F.js} +12 -12
- package/dist/{chunk-CCL2OXFO.js → chunk-HSHLDSPX.js} +3 -3
- package/dist/{chunk-OCXDZJL4.js → chunk-I6UTOUBK.js} +37 -80
- package/dist/chunk-I6UTOUBK.js.map +1 -0
- package/dist/{chunk-TTTPTQ6S.js → chunk-IA3XZG4V.js} +2 -2
- package/dist/{chunk-CIMD2MUR.js → chunk-IJFX4JRI.js} +7 -7
- package/dist/{chunk-RTJJPW3C.js → chunk-ILMB3X4X.js} +5 -5
- package/dist/{chunk-5LBBXI2A.js → chunk-J53IBVMF.js} +2 -2
- package/dist/{chunk-JEX2JL3U.js → chunk-JEDS2QYV.js} +2 -2
- package/dist/{chunk-W7NKMRAF.js → chunk-JFRO5HJY.js} +2 -2
- package/dist/{chunk-4W5B5Z6P.js → chunk-JM2WJBP7.js} +2 -2
- package/dist/{chunk-DJFULNXK.js → chunk-JRY55I6Z.js} +2 -2
- package/dist/{chunk-M4U25HLR.js → chunk-KXDUSGPM.js} +2 -2
- package/dist/{chunk-YJHOVA42.js → chunk-L5RMURVI.js} +2 -2
- package/dist/{chunk-QS5DUSBM.js → chunk-LGLLCWA2.js} +2 -2
- package/dist/{chunk-34UVJNSG.js → chunk-M3GOWG2P.js} +2 -2
- package/dist/{chunk-53OFDW3P.js → chunk-M7KAJBO2.js} +3 -3
- package/dist/{chunk-V4VCBFAL.js → chunk-MHSUEA32.js} +3 -3
- package/dist/{chunk-ERMT5WQM.js → chunk-MKE5DP3S.js} +2 -2
- package/dist/{chunk-IFOCK3OJ.js → chunk-MOB4JHG6.js} +2 -2
- package/dist/{chunk-W4QTA2H5.js → chunk-N5H22V75.js} +2 -2
- package/dist/{chunk-3LNAQLW4.js → chunk-NF5EHQTJ.js} +4 -4
- package/dist/{chunk-FFQNNFHX.js → chunk-NJZY5LOC.js} +2 -2
- package/dist/{chunk-YLN4ZQV4.js → chunk-PIVSUPF2.js} +2 -2
- package/dist/{chunk-2S6JADBB.js → chunk-PJLJN23N.js} +2 -2
- package/dist/{chunk-3H3PNQEF.js → chunk-PRSF7DWT.js} +2 -2
- package/dist/{chunk-W2KH27MQ.js → chunk-PTJWUN4H.js} +45 -45
- package/dist/{chunk-GFAEKSFR.js → chunk-QBK3IM6W.js} +5 -5
- package/dist/{chunk-KRAMCBIW.js → chunk-QKXPOLQD.js} +4 -4
- package/dist/{chunk-OWNAWY6D.js → chunk-QWURVT5Z.js} +2 -2
- package/dist/{chunk-YDT3OEYF.js → chunk-R4ZHF4K2.js} +2 -2
- package/dist/{chunk-CTNDFKEX.js → chunk-RSYFSCKL.js} +16 -16
- package/dist/{chunk-7XGNGLAN.js → chunk-T4SMMTGY.js} +2 -2
- package/dist/{chunk-FZLZUDJJ.js → chunk-TIS6T6UP.js} +2 -2
- package/dist/{chunk-RKRX4C4H.js → chunk-TPKB7T6Z.js} +2 -2
- package/dist/{chunk-JZCBMKR5.js → chunk-TUCEP2LS.js} +5 -5
- package/dist/{chunk-OMWQO7H6.js → chunk-UEDC2VGP.js} +2 -2
- package/dist/{chunk-B43P3DTC.js → chunk-UIX4JLVV.js} +2 -2
- package/dist/{chunk-VGE4PUY6.js → chunk-UP2Q7U4G.js} +9 -9
- package/dist/{chunk-HQUYENKK.js → chunk-UY67YP6Q.js} +2 -2
- package/dist/{chunk-U2A56LLF.js → chunk-VQ6H4DL6.js} +11 -11
- package/dist/{chunk-AO5ZOD32.js → chunk-W37MS6SX.js} +2 -2
- package/dist/{chunk-UBOWDOL5.js → chunk-WJC5KGYC.js} +6 -6
- package/dist/{chunk-G6A3SGCE.js → chunk-WVEEVYMH.js} +8 -25
- package/dist/chunk-WVEEVYMH.js.map +1 -0
- package/dist/{chunk-TRAOZ2WP.js → chunk-X2TZWVCC.js} +2 -2
- package/dist/{chunk-UQBOPCDK.js → chunk-X4PAATLA.js} +2 -2
- package/dist/{chunk-24SIB3EW.js → chunk-XWE3EOWX.js} +2 -2
- package/dist/{chunk-Y77FXULH.js → chunk-XWXBXBOX.js} +2 -2
- package/dist/{chunk-VKQI47DJ.js → chunk-Y7427SL7.js} +4 -4
- package/dist/{chunk-TEH5MDX5.js → chunk-YH7HJNWV.js} +22 -22
- package/dist/chunk-YHTG64XV.js +117 -0
- package/dist/chunk-YHTG64XV.js.map +1 -0
- package/dist/{chunk-EZ25VE3G.js → chunk-YNDLCWXS.js} +4 -4
- package/dist/{chunk-2M36PR7R.js → chunk-Z2T5BPOZ.js} +2 -2
- package/dist/{chunk-65B5RFKQ.js → chunk-Z4EH5TSO.js} +2 -2
- package/dist/{chunk-P5FYZH5G.js → chunk-ZRDEOC4X.js} +2 -2
- package/dist/{chunk-L5UB5BIY.js → chunk-ZXEPLZUQ.js} +9 -9
- package/dist/cli.js +94 -92
- package/dist/coding/export-okf-codegraph.js +14 -12
- package/dist/coding/export-okf-codegraph.js.map +1 -1
- package/dist/coding/pre-action-gate.js +2 -2
- package/dist/compounding/engine.js +16 -14
- package/dist/config.js +23 -21
- package/dist/connectors/codex-materialize-runner.js +15 -13
- package/dist/connectors/index.js +15 -13
- package/dist/convergence-refresh.js +17 -15
- package/dist/corpus-scan-worker-host.d.ts +109 -0
- package/dist/corpus-scan-worker-host.d.ts.map +1 -0
- package/dist/corpus-scan-worker-host.js +15 -0
- package/dist/corpus-scan-worker.entry.d.ts +21 -0
- package/dist/corpus-scan-worker.entry.d.ts.map +1 -0
- package/dist/corpus-scan-worker.entry.js +60 -0
- package/dist/corpus-scan-worker.entry.js.map +1 -0
- package/dist/corpus-scan-worker.entry.mjs +176 -0
- package/dist/corpus-scan.d.ts +71 -0
- package/dist/corpus-scan.d.ts.map +1 -0
- package/dist/corpus-scan.js +16 -0
- package/dist/corpus-watermark.d.ts.map +1 -1
- package/dist/corpus-watermark.js +17 -15
- package/dist/dashboard-runtime.js +2 -2
- package/dist/deep-recall-http-glue.js +8 -8
- package/dist/embedding-fallback.js +2 -2
- package/dist/{entity-canonical-id-migration-F3JVG6K6.js → entity-canonical-id-migration-CUDCKWPF.js} +4 -2
- package/dist/entity-canonical-id-migration-CUDCKWPF.js.map +1 -0
- package/dist/entity-retrieval.js +17 -15
- package/dist/event-order-recall.js +3 -3
- package/dist/external-wiki-access.js +50 -48
- package/dist/extraction.js +20 -18
- package/dist/{graph-edge-decay-IU75NV42.js → graph-edge-decay-2BZTY2IM.js} +5 -5
- package/dist/graph-edge-decay-2BZTY2IM.js.map +1 -0
- package/dist/graph-jsonl.js +3 -3
- package/dist/graph-snapshot.js +3 -3
- package/dist/graph.js +2 -2
- package/dist/harmonic-retrieval.js +15 -13
- package/dist/index.js +164 -162
- package/dist/lcm/engine.js +2 -2
- package/dist/lcm/index.js +5 -5
- package/dist/location/index.js +17 -15
- package/dist/maintenance/memory-governance.js +15 -13
- package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +15 -13
- package/dist/maintenance/rebuild-memory-projection-runner.js +17 -15
- package/dist/maintenance/rebuild-memory-projection.js +16 -14
- package/dist/memory-browse-operations.js +50 -48
- package/dist/memory-browse.js +5 -5
- package/dist/namespaces/migrate.js +16 -14
- package/dist/namespaces/search.js +8 -8
- package/dist/namespaces/storage.js +15 -13
- package/dist/offline-sync-changeset-generations.js +4 -4
- package/dist/offline-sync-embedding-generation.js +3 -3
- package/dist/offline-sync-snapshot-stream.js +7 -7
- package/dist/offline-sync.js +6 -6
- package/dist/operator-doctor-corpus.js +18 -16
- package/dist/operator-toolkit.js +34 -32
- package/dist/orchestration/maintenance.js +25 -23
- package/dist/orchestrator.js +136 -134
- package/dist/qmd.js +4 -4
- package/dist/recall-navigation-operations.js +50 -48
- package/dist/recall-navigation.js +49 -47
- package/dist/recall-state-view-admission.js +5 -5
- package/dist/recall-why-service.js +5 -5
- package/dist/resume-bundles.js +24 -22
- package/dist/search/factory.js +7 -7
- package/dist/search/index.js +15 -15
- package/dist/semantic-consolidation.js +16 -14
- package/dist/semantic-rule-promotion.js +15 -13
- package/dist/semantic-rule-verifier.js +15 -13
- package/dist/source-agent-qualifier.js +18 -16
- package/dist/standup/build.js +15 -13
- package/dist/storage/entity-canonical-id-migration.d.ts.map +1 -1
- package/dist/storage/memory-read-store.d.ts +7 -3
- package/dist/storage/memory-read-store.d.ts.map +1 -1
- package/dist/storage.js +16 -14
- package/dist/support-passport/index.js +52 -50
- package/dist/transfer/export-okf.js +15 -13
- package/dist/verified-recall.js +15 -13
- package/dist/who-knows-cli.js +16 -14
- package/dist/who-knows.js +15 -13
- package/package.json +2 -2
- package/scripts/build-with-heap.mjs +8 -0
- package/src/corpus-scan-worker-host.ts +314 -0
- package/src/corpus-scan-worker.entry.mjs +176 -0
- package/src/corpus-scan-worker.entry.ts +95 -0
- package/src/corpus-scan.ts +215 -0
- package/src/corpus-watermark.ts +28 -12
- package/src/storage/entity-canonical-id-migration.ts +4 -26
- package/src/storage/memory-read-store.ts +19 -111
- package/dist/chunk-G6A3SGCE.js.map +0 -1
- package/dist/chunk-OCXDZJL4.js.map +0 -1
- package/dist/chunk-UQYBPPVC.js.map +0 -1
- /package/dist/{chunk-XDKEHZUC.js.map → chunk-264B57GV.js.map} +0 -0
- /package/dist/{chunk-LTDK6HBV.js.map → chunk-2HVCTFKG.js.map} +0 -0
- /package/dist/{chunk-E2YAGEFR.js.map → chunk-3DG2YAAX.js.map} +0 -0
- /package/dist/{chunk-LIVNX7WK.js.map → chunk-4O4VAEWH.js.map} +0 -0
- /package/dist/{chunk-NRGQ5HJO.js.map → chunk-4P4VLPGB.js.map} +0 -0
- /package/dist/{chunk-GIRWNTMV.js.map → chunk-4PG5R6BJ.js.map} +0 -0
- /package/dist/{chunk-AOSCGNIC.js.map → chunk-5XY46Z2W.js.map} +0 -0
- /package/dist/{chunk-52II7PB6.js.map → chunk-645WOU5I.js.map} +0 -0
- /package/dist/{chunk-RG3I2663.js.map → chunk-64JLBFCQ.js.map} +0 -0
- /package/dist/{chunk-G3T6YMBO.js.map → chunk-64S6X4IU.js.map} +0 -0
- /package/dist/{chunk-XHEKS4UN.js.map → chunk-66CC6L6R.js.map} +0 -0
- /package/dist/{chunk-3632XJMR.js.map → chunk-6BKSUA6P.js.map} +0 -0
- /package/dist/{chunk-6AEKSOWV.js.map → chunk-6I6DFULH.js.map} +0 -0
- /package/dist/{chunk-2EYCW2ET.js.map → chunk-74Z2BYEK.js.map} +0 -0
- /package/dist/{chunk-QAHP5SBG.js.map → chunk-7DHNBABU.js.map} +0 -0
- /package/dist/{chunk-HJ4X4BGS.js.map → chunk-ADK5TMQL.js.map} +0 -0
- /package/dist/{chunk-KCTL5EY7.js.map → chunk-AWKADEMO.js.map} +0 -0
- /package/dist/{chunk-VV73XT3A.js.map → chunk-BJLCV2P3.js.map} +0 -0
- /package/dist/{chunk-QF5EJ5VP.js.map → chunk-C5GTIRIO.js.map} +0 -0
- /package/dist/{chunk-LRVFS4KA.js.map → chunk-DF4SYU4Z.js.map} +0 -0
- /package/dist/{chunk-WBN66HPI.js.map → chunk-ELHJK7LM.js.map} +0 -0
- /package/dist/{chunk-6CDNOREO.js.map → chunk-F3GX76LV.js.map} +0 -0
- /package/dist/{chunk-AAANTXCQ.js.map → chunk-FZVZYU5C.js.map} +0 -0
- /package/dist/{chunk-ON77FQXH.js.map → chunk-G6GMZJQ4.js.map} +0 -0
- /package/dist/{chunk-HJQ4NKKO.js.map → chunk-GGHX4YHM.js.map} +0 -0
- /package/dist/{chunk-HM3ZTDAX.js.map → chunk-GJF337YM.js.map} +0 -0
- /package/dist/{chunk-GDYLZ6FR.js.map → chunk-HJ3UTGIW.js.map} +0 -0
- /package/dist/{chunk-W3R77WHT.js.map → chunk-HL643H2F.js.map} +0 -0
- /package/dist/{chunk-CCL2OXFO.js.map → chunk-HSHLDSPX.js.map} +0 -0
- /package/dist/{chunk-TTTPTQ6S.js.map → chunk-IA3XZG4V.js.map} +0 -0
- /package/dist/{chunk-CIMD2MUR.js.map → chunk-IJFX4JRI.js.map} +0 -0
- /package/dist/{chunk-RTJJPW3C.js.map → chunk-ILMB3X4X.js.map} +0 -0
- /package/dist/{chunk-5LBBXI2A.js.map → chunk-J53IBVMF.js.map} +0 -0
- /package/dist/{chunk-JEX2JL3U.js.map → chunk-JEDS2QYV.js.map} +0 -0
- /package/dist/{chunk-W7NKMRAF.js.map → chunk-JFRO5HJY.js.map} +0 -0
- /package/dist/{chunk-4W5B5Z6P.js.map → chunk-JM2WJBP7.js.map} +0 -0
- /package/dist/{chunk-DJFULNXK.js.map → chunk-JRY55I6Z.js.map} +0 -0
- /package/dist/{chunk-M4U25HLR.js.map → chunk-KXDUSGPM.js.map} +0 -0
- /package/dist/{chunk-YJHOVA42.js.map → chunk-L5RMURVI.js.map} +0 -0
- /package/dist/{chunk-QS5DUSBM.js.map → chunk-LGLLCWA2.js.map} +0 -0
- /package/dist/{chunk-34UVJNSG.js.map → chunk-M3GOWG2P.js.map} +0 -0
- /package/dist/{chunk-53OFDW3P.js.map → chunk-M7KAJBO2.js.map} +0 -0
- /package/dist/{chunk-V4VCBFAL.js.map → chunk-MHSUEA32.js.map} +0 -0
- /package/dist/{chunk-ERMT5WQM.js.map → chunk-MKE5DP3S.js.map} +0 -0
- /package/dist/{chunk-IFOCK3OJ.js.map → chunk-MOB4JHG6.js.map} +0 -0
- /package/dist/{chunk-W4QTA2H5.js.map → chunk-N5H22V75.js.map} +0 -0
- /package/dist/{chunk-3LNAQLW4.js.map → chunk-NF5EHQTJ.js.map} +0 -0
- /package/dist/{chunk-FFQNNFHX.js.map → chunk-NJZY5LOC.js.map} +0 -0
- /package/dist/{chunk-YLN4ZQV4.js.map → chunk-PIVSUPF2.js.map} +0 -0
- /package/dist/{chunk-2S6JADBB.js.map → chunk-PJLJN23N.js.map} +0 -0
- /package/dist/{chunk-3H3PNQEF.js.map → chunk-PRSF7DWT.js.map} +0 -0
- /package/dist/{chunk-W2KH27MQ.js.map → chunk-PTJWUN4H.js.map} +0 -0
- /package/dist/{chunk-GFAEKSFR.js.map → chunk-QBK3IM6W.js.map} +0 -0
- /package/dist/{chunk-KRAMCBIW.js.map → chunk-QKXPOLQD.js.map} +0 -0
- /package/dist/{chunk-OWNAWY6D.js.map → chunk-QWURVT5Z.js.map} +0 -0
- /package/dist/{chunk-YDT3OEYF.js.map → chunk-R4ZHF4K2.js.map} +0 -0
- /package/dist/{chunk-CTNDFKEX.js.map → chunk-RSYFSCKL.js.map} +0 -0
- /package/dist/{chunk-7XGNGLAN.js.map → chunk-T4SMMTGY.js.map} +0 -0
- /package/dist/{chunk-FZLZUDJJ.js.map → chunk-TIS6T6UP.js.map} +0 -0
- /package/dist/{chunk-RKRX4C4H.js.map → chunk-TPKB7T6Z.js.map} +0 -0
- /package/dist/{chunk-JZCBMKR5.js.map → chunk-TUCEP2LS.js.map} +0 -0
- /package/dist/{chunk-OMWQO7H6.js.map → chunk-UEDC2VGP.js.map} +0 -0
- /package/dist/{chunk-B43P3DTC.js.map → chunk-UIX4JLVV.js.map} +0 -0
- /package/dist/{chunk-VGE4PUY6.js.map → chunk-UP2Q7U4G.js.map} +0 -0
- /package/dist/{chunk-HQUYENKK.js.map → chunk-UY67YP6Q.js.map} +0 -0
- /package/dist/{chunk-U2A56LLF.js.map → chunk-VQ6H4DL6.js.map} +0 -0
- /package/dist/{chunk-AO5ZOD32.js.map → chunk-W37MS6SX.js.map} +0 -0
- /package/dist/{chunk-UBOWDOL5.js.map → chunk-WJC5KGYC.js.map} +0 -0
- /package/dist/{chunk-TRAOZ2WP.js.map → chunk-X2TZWVCC.js.map} +0 -0
- /package/dist/{chunk-UQBOPCDK.js.map → chunk-X4PAATLA.js.map} +0 -0
- /package/dist/{chunk-24SIB3EW.js.map → chunk-XWE3EOWX.js.map} +0 -0
- /package/dist/{chunk-Y77FXULH.js.map → chunk-XWXBXBOX.js.map} +0 -0
- /package/dist/{chunk-VKQI47DJ.js.map → chunk-Y7427SL7.js.map} +0 -0
- /package/dist/{chunk-TEH5MDX5.js.map → chunk-YH7HJNWV.js.map} +0 -0
- /package/dist/{chunk-EZ25VE3G.js.map → chunk-YNDLCWXS.js.map} +0 -0
- /package/dist/{chunk-2M36PR7R.js.map → chunk-Z2T5BPOZ.js.map} +0 -0
- /package/dist/{chunk-65B5RFKQ.js.map → chunk-Z4EH5TSO.js.map} +0 -0
- /package/dist/{chunk-P5FYZH5G.js.map → chunk-ZRDEOC4X.js.map} +0 -0
- /package/dist/{chunk-L5UB5BIY.js.map → chunk-ZXEPLZUQ.js.map} +0 -0
- /package/dist/{entity-canonical-id-migration-F3JVG6K6.js.map → corpus-scan-worker-host.js.map} +0 -0
- /package/dist/{graph-edge-decay-IU75NV42.js.map → corpus-scan.js.map} +0 -0
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Corpus maintenance scans (the pure, read-only filesystem work the daemon
|
|
3
|
+
* runs periodically).
|
|
4
|
+
*
|
|
5
|
+
* Three scans live here, all of them corpus-sized:
|
|
6
|
+
* - the recall-corpus path walk (hot + cold tiers);
|
|
7
|
+
* - the entity-entry fingerprint the canonical-id migration reads on every
|
|
8
|
+
* entity unlock;
|
|
9
|
+
* - the watermark's freshness stat over the newest hot partition.
|
|
10
|
+
*
|
|
11
|
+
* They are plain functions over plain inputs so the corpus-scan worker can run
|
|
12
|
+
* them off the main event loop: `src/corpus-scan-worker.entry.ts` is bundled
|
|
13
|
+
* into a plain-JavaScript worker entry (`src/corpus-scan-worker.entry.mjs`,
|
|
14
|
+
* generated — see scripts/sync-corpus-scan-worker.mjs) and the host falls back
|
|
15
|
+
* to these functions inline when a worker cannot start. One implementation,
|
|
16
|
+
* two execution sites, identical results.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import type { Dirent, Stats } from "node:fs";
|
|
20
|
+
import { createHash } from "node:crypto";
|
|
21
|
+
import { lstat, readdir, realpath, stat } from "node:fs/promises";
|
|
22
|
+
import path from "node:path";
|
|
23
|
+
import { isErrnoCode } from "./utils/errno.js";
|
|
24
|
+
import { isMacOsReplicationJunkBasename } from "./utils/macos-replication-junk.js";
|
|
25
|
+
|
|
26
|
+
/** An errno-carrying filesystem failure. */
|
|
27
|
+
export type ErrnoFailure = NodeJS.ErrnoException;
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* A backend READ failure worth propagating from a strict (census) scan: an
|
|
31
|
+
* errno-coded fs error that is NOT `ENOENT`. `ENOENT` (an absent dir) and
|
|
32
|
+
* code-less rejections are expected and skipped; `EACCES`/`EIO`/… mean the
|
|
33
|
+
* corpus is unreadable and a divergence census must fail loud rather than
|
|
34
|
+
* silently publish a partial count (issue #2156 round-5). Non-strict (recall)
|
|
35
|
+
* callers ignore this and degrade to a best-effort partial scan.
|
|
36
|
+
*/
|
|
37
|
+
export function isPropagatableReadError(error: ErrnoFailure): boolean {
|
|
38
|
+
return error.code !== undefined && error.code !== "ENOENT";
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export interface CorpusScanWalkOptions {
|
|
42
|
+
/** Memory root the walk is contained in. */
|
|
43
|
+
baseDir: string;
|
|
44
|
+
/** Directories to walk (category roots, cold root, archive root). */
|
|
45
|
+
startDirs: readonly string[];
|
|
46
|
+
/** Strict mode: backend read failures propagate instead of being skipped. */
|
|
47
|
+
propagateReadErrors?: boolean;
|
|
48
|
+
/**
|
|
49
|
+
* Cancellation check at every directory/entry boundary. The inline reader
|
|
50
|
+
* passes the corpus-read abort check; the worker passes its cancel flag.
|
|
51
|
+
*/
|
|
52
|
+
checkCancelled?(): void;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Recursively collect `*.md` paths under `startDirs`. Paths only — no
|
|
57
|
+
* frontmatter parse. Never throws for a missing root (an empty result), and
|
|
58
|
+
* throws in strict mode when a backend read failure makes the corpus
|
|
59
|
+
* unreadable.
|
|
60
|
+
*
|
|
61
|
+
* Containment is established once, not per file: the memory root is resolved
|
|
62
|
+
* with a single `realpath`, every directory the walk enters is `lstat`ed (a
|
|
63
|
+
* symlinked or non-directory entry is never followed), and the entry TYPE
|
|
64
|
+
* comes from `readdir(withFileTypes)` — so a symlinked entry, file or
|
|
65
|
+
* directory, is skipped without a per-entry `realpath`. Reads refuse
|
|
66
|
+
* symlinked files at open time with `O_NOFOLLOW`, so a path swapped to a
|
|
67
|
+
* symlink mid-scan is refused rather than followed.
|
|
68
|
+
*/
|
|
69
|
+
export async function collectContainedMarkdownPaths(options: CorpusScanWalkOptions): Promise<string[]> {
|
|
70
|
+
const { baseDir, startDirs, propagateReadErrors = false, checkCancelled } = options;
|
|
71
|
+
const filePaths: string[] = [];
|
|
72
|
+
|
|
73
|
+
// Resolve the memory root once: a missing root is an empty corpus and an
|
|
74
|
+
// unreadable root is a backend failure the strict census must surface.
|
|
75
|
+
try {
|
|
76
|
+
await realpath(baseDir);
|
|
77
|
+
} catch (error) {
|
|
78
|
+
// SAFETY: node:fs/promises rejects with an Error; the errno `code` is
|
|
79
|
+
// optional on that type, so this adds no unchecked claim.
|
|
80
|
+
const failure = error as ErrnoFailure;
|
|
81
|
+
// ENOENT: a not-yet-created root ⇒ empty. Any other errno is a backend
|
|
82
|
+
// read failure — propagate for strict census callers (never publish an
|
|
83
|
+
// unreadable corpus as empty); non-strict (recall) callers degrade.
|
|
84
|
+
if (propagateReadErrors && isPropagatableReadError(failure)) throw error;
|
|
85
|
+
return filePaths;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const collectPaths = async (dir: string): Promise<void> => {
|
|
89
|
+
checkCancelled?.();
|
|
90
|
+
// Directory-level guard, isolated from per-entry handling: skip symlinked
|
|
91
|
+
// or non-directory category dirs. A failure here means the whole subtree
|
|
92
|
+
// does not exist or escaped the store — fail closed by skipping it.
|
|
93
|
+
let dirStat: Stats;
|
|
94
|
+
try {
|
|
95
|
+
dirStat = await lstat(dir);
|
|
96
|
+
} catch (error) {
|
|
97
|
+
// SAFETY: node:fs/promises rejects with an Error; `code` is optional.
|
|
98
|
+
const failure = error as ErrnoFailure;
|
|
99
|
+
// ENOENT (absent category dir) is expected; a backend read error
|
|
100
|
+
// (EACCES/EIO/…) propagates in strict census mode.
|
|
101
|
+
if (propagateReadErrors && isPropagatableReadError(failure)) throw error;
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
if (dirStat.isSymbolicLink()) return; // never follow symlinks out of the store (both modes)
|
|
105
|
+
if (!dirStat.isDirectory()) {
|
|
106
|
+
// A category root that EXISTS but is not a directory (e.g. `facts/`
|
|
107
|
+
// replaced by a regular file) is a layout/backend failure: a strict
|
|
108
|
+
// census must not silently publish the remaining categories as healthy.
|
|
109
|
+
// Non-strict (recall) stays best-effort and skips it (issue #2156 round-10).
|
|
110
|
+
if (propagateReadErrors) {
|
|
111
|
+
throw new Error(`corpus scan: expected a directory but found a non-directory at ${dir}`);
|
|
112
|
+
}
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
let entries: Dirent[];
|
|
116
|
+
try {
|
|
117
|
+
entries = await readdir(dir, { withFileTypes: true });
|
|
118
|
+
} catch (error) {
|
|
119
|
+
// SAFETY: node:fs/promises rejects with an Error; `code` is optional.
|
|
120
|
+
const failure = error as ErrnoFailure;
|
|
121
|
+
// A backend read error (EACCES …) propagates in strict census mode so a
|
|
122
|
+
// partial subtree is not silently counted. ENOENT (vanished mid-scan)
|
|
123
|
+
// and other non-propagatable failures skip the directory.
|
|
124
|
+
if (propagateReadErrors && isPropagatableReadError(failure)) throw error;
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const subdirs: string[] = [];
|
|
129
|
+
for (const entry of entries) {
|
|
130
|
+
// Outside the per-entry handling below, which deliberately swallows
|
|
131
|
+
// non-propagatable errors — a cancellation must never be swallowed with
|
|
132
|
+
// them (issue #2307).
|
|
133
|
+
checkCancelled?.();
|
|
134
|
+
// Never follow symlinked entries out of the store.
|
|
135
|
+
if (entry.isSymbolicLink()) continue;
|
|
136
|
+
if (isMacOsReplicationJunkBasename(entry.name)) continue;
|
|
137
|
+
const fullPath = path.join(dir, entry.name);
|
|
138
|
+
if (entry.isDirectory()) {
|
|
139
|
+
subdirs.push(fullPath);
|
|
140
|
+
} else if (entry.name.endsWith(".md")) {
|
|
141
|
+
filePaths.push(fullPath);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
// Recurse into real subdirectories regardless of any single poisoned entry
|
|
145
|
+
// above, so valid nested in-store memories are never dropped.
|
|
146
|
+
for (const subdir of subdirs) {
|
|
147
|
+
await collectPaths(subdir);
|
|
148
|
+
}
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
for (const dir of startDirs) {
|
|
152
|
+
await collectPaths(dir);
|
|
153
|
+
}
|
|
154
|
+
// A signal that fired during the last directory read would otherwise return a
|
|
155
|
+
// truncated path list as if it were the whole corpus (issue #2307 review).
|
|
156
|
+
checkCancelled?.();
|
|
157
|
+
return filePaths;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Fingerprint of the entity entries a migration's completion verdict depends
|
|
162
|
+
* on: the sorted entry names plus each entry's identity and timestamps. Two
|
|
163
|
+
* runs over the same entries produce the same string, and an in-place edit of
|
|
164
|
+
* an entity file changes it.
|
|
165
|
+
*/
|
|
166
|
+
export async function fingerprintEntityEntries(entitiesDir: string): Promise<string> {
|
|
167
|
+
let names: string[];
|
|
168
|
+
try {
|
|
169
|
+
names = (await readdir(entitiesDir))
|
|
170
|
+
.filter((name) => path.extname(name) === ".md" && !isMacOsReplicationJunkBasename(name))
|
|
171
|
+
.sort();
|
|
172
|
+
} catch (error) {
|
|
173
|
+
if (isErrnoCode(error, "ENOENT")) return "missing";
|
|
174
|
+
throw error;
|
|
175
|
+
}
|
|
176
|
+
const hash = createHash("sha256");
|
|
177
|
+
for (const name of names) {
|
|
178
|
+
hash.update(`${name.length}:${name}:`);
|
|
179
|
+
try {
|
|
180
|
+
const entry = await lstat(path.join(entitiesDir, name), { bigint: true });
|
|
181
|
+
hash.update(`${entry.dev}:${entry.ino}:${entry.size}:${entry.mtimeNs}:${entry.ctimeNs}`);
|
|
182
|
+
} catch (error) {
|
|
183
|
+
if (!isErrnoCode(error, "ENOENT")) throw error;
|
|
184
|
+
hash.update("missing");
|
|
185
|
+
}
|
|
186
|
+
hash.update("\n");
|
|
187
|
+
}
|
|
188
|
+
return hash.digest("hex");
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Stat a batch of files and return their mtimes, `null` for files that
|
|
193
|
+
* vanished (ENOENT). A backend read failure (EACCES/EIO/…) throws with its
|
|
194
|
+
* errno code intact so the caller can omit an unreadable corpus instead of
|
|
195
|
+
* publishing a stale freshness value.
|
|
196
|
+
*/
|
|
197
|
+
export async function statMtimes(paths: readonly string[]): Promise<Array<number | null>> {
|
|
198
|
+
const results: Array<number | null> = new Array(paths.length).fill(null);
|
|
199
|
+
// Fixed-size batches: an unbounded Promise.all submits one libuv operation
|
|
200
|
+
// per file at once, and the worker and main thread share that threadpool, so
|
|
201
|
+
// a large bulk import would delay every other filesystem operation.
|
|
202
|
+
const STAT_BATCH_SIZE = 256;
|
|
203
|
+
for (let start = 0; start < paths.length; start += STAT_BATCH_SIZE) {
|
|
204
|
+
await Promise.all(paths.slice(start, start + STAT_BATCH_SIZE).map(async (filePath, offset) => {
|
|
205
|
+
try {
|
|
206
|
+
const entry = await stat(filePath);
|
|
207
|
+
results[start + offset] = entry.mtimeMs;
|
|
208
|
+
} catch (error) {
|
|
209
|
+
if (isErrnoCode(error, "ENOENT")) return;
|
|
210
|
+
throw error;
|
|
211
|
+
}
|
|
212
|
+
}));
|
|
213
|
+
}
|
|
214
|
+
return results;
|
|
215
|
+
}
|
package/src/corpus-watermark.ts
CHANGED
|
@@ -37,10 +37,11 @@
|
|
|
37
37
|
*/
|
|
38
38
|
|
|
39
39
|
import { createHash } from "node:crypto";
|
|
40
|
-
import { stat } from "node:fs/promises";
|
|
41
40
|
import path from "node:path";
|
|
41
|
+
import { setImmediate as yieldToEventLoop } from "node:timers/promises";
|
|
42
42
|
import { capabilityAllowsNamespace, type TokenCapabilities } from "./access-token-capabilities.js";
|
|
43
43
|
import { listNamespaces } from "./namespaces/migrate.js";
|
|
44
|
+
import { corpusScanWorker } from "./corpus-scan-worker-host.js";
|
|
44
45
|
import type { PluginConfig } from "./types.js";
|
|
45
46
|
|
|
46
47
|
export interface CorpusWatermark {
|
|
@@ -67,6 +68,9 @@ export const UNPARTITIONED_BUCKET = "unpartitioned";
|
|
|
67
68
|
/** Storage tier a memory file lives in. */
|
|
68
69
|
export type CorpusTier = "hot" | "cold";
|
|
69
70
|
|
|
71
|
+
/** Paths parsed between event-loop yields while building a census. */
|
|
72
|
+
const CENSUS_YIELD_EVERY = 4_096;
|
|
73
|
+
|
|
70
74
|
const DAY_PARTITION_RE = /^\d{4}-\d{2}-\d{2}$/;
|
|
71
75
|
/** Cold-tier root segment (relative to a namespace base dir): `<baseDir>/cold/...`. */
|
|
72
76
|
const COLD_TIER_DIR = "cold";
|
|
@@ -155,7 +159,17 @@ export async function computeCorpusWatermark(input: {
|
|
|
155
159
|
const census = new Map<string, number>();
|
|
156
160
|
let newestPartition: string | null = null;
|
|
157
161
|
const parsedByPath: ParsedPath[] = [];
|
|
162
|
+
let sinceYield = 0;
|
|
158
163
|
for (const filePath of paths) {
|
|
164
|
+
// Parsing one path is cheap, but a census covers the whole corpus (100k+
|
|
165
|
+
// paths): one uninterrupted pass holds the main thread for hundreds of
|
|
166
|
+
// milliseconds. Yield to the event loop every CENSUS_YIELD_EVERY paths so
|
|
167
|
+
// observe/recall interleave; the result is order-independent and identical.
|
|
168
|
+
if (sinceYield >= CENSUS_YIELD_EVERY) {
|
|
169
|
+
sinceYield = 0;
|
|
170
|
+
await yieldToEventLoop();
|
|
171
|
+
}
|
|
172
|
+
sinceYield += 1;
|
|
159
173
|
const parsed = parsePartition(baseDir, filePath);
|
|
160
174
|
parsedByPath.push(parsed);
|
|
161
175
|
census.set(parsed.bucket, (census.get(parsed.bucket) ?? 0) + 1);
|
|
@@ -171,19 +185,21 @@ export async function computeCorpusWatermark(input: {
|
|
|
171
185
|
|
|
172
186
|
let newestWriteAt: string | null = null;
|
|
173
187
|
if (newestPartition !== null) {
|
|
174
|
-
|
|
188
|
+
const newestPaths: string[] = [];
|
|
175
189
|
for (let i = 0; i < paths.length; i += 1) {
|
|
176
190
|
if (parsedByPath[i].tier !== "hot" || parsedByPath[i].date !== newestPartition) continue;
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
191
|
+
newestPaths.push(paths[i]);
|
|
192
|
+
}
|
|
193
|
+
// The freshness sweep is one stat per file in the newest hot partition; it
|
|
194
|
+
// runs in the corpus-scan worker so the periodic census does not spend
|
|
195
|
+
// main-thread time on a per-file syscall sweep. A file that vanished
|
|
196
|
+
// (ENOENT) reports null; a backend read failure (EACCES/EIO/…) propagates
|
|
197
|
+
// with its errno code so the namespace is omitted, not published with a
|
|
198
|
+
// stale/null freshness (issue #2156 round-6).
|
|
199
|
+
const mtimes = await corpusScanWorker.request({ kind: "stat-mtimes", paths: newestPaths });
|
|
200
|
+
let maxMtimeMs = Number.NEGATIVE_INFINITY;
|
|
201
|
+
for (const mtimeMs of mtimes) {
|
|
202
|
+
if (mtimeMs !== null && mtimeMs > maxMtimeMs) maxMtimeMs = mtimeMs;
|
|
187
203
|
}
|
|
188
204
|
if (maxMtimeMs > Number.NEGATIVE_INFINITY) {
|
|
189
205
|
newestWriteAt = new Date(maxMtimeMs).toISOString();
|
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
import path from "node:path";
|
|
14
14
|
import { lstatSync, readFileSync } from "node:fs";
|
|
15
15
|
import { z } from "zod";
|
|
16
|
+
import { corpusScanWorker } from "../corpus-scan-worker-host.js";
|
|
16
17
|
import { computeSupersessionKey, normalizeSupersessionKey } from "../temporal-supersession.js";
|
|
17
18
|
import type { EntityFile, MemoryFile } from "../types.js";
|
|
18
19
|
import { log } from "../logger.js";
|
|
@@ -102,31 +103,6 @@ async function fingerprintPath(filePath: string): Promise<string> {
|
|
|
102
103
|
: "missing";
|
|
103
104
|
}
|
|
104
105
|
|
|
105
|
-
async function fingerprintEntityEntries(entitiesDir: string): Promise<string> {
|
|
106
|
-
let names: string[];
|
|
107
|
-
try {
|
|
108
|
-
names = (await readdir(entitiesDir))
|
|
109
|
-
.filter((name) => path.extname(name) === ".md" && !isMacOsReplicationJunkBasename(name))
|
|
110
|
-
.sort();
|
|
111
|
-
} catch (error) {
|
|
112
|
-
if (isErrnoCode(error, "ENOENT")) return "missing";
|
|
113
|
-
throw error;
|
|
114
|
-
}
|
|
115
|
-
const hash = createHash("sha256");
|
|
116
|
-
for (const name of names) {
|
|
117
|
-
hash.update(`${name.length}:${name}:`);
|
|
118
|
-
try {
|
|
119
|
-
const entry = await lstat(path.join(entitiesDir, name), { bigint: true });
|
|
120
|
-
hash.update(`${entry.dev}:${entry.ino}:${entry.size}:${entry.mtimeNs}:${entry.ctimeNs}`);
|
|
121
|
-
} catch (error) {
|
|
122
|
-
if (!isErrnoCode(error, "ENOENT")) throw error;
|
|
123
|
-
hash.update("missing");
|
|
124
|
-
}
|
|
125
|
-
hash.update("\n");
|
|
126
|
-
}
|
|
127
|
-
return hash.digest("hex");
|
|
128
|
-
}
|
|
129
|
-
|
|
130
106
|
/**
|
|
131
107
|
* Completion fingerprint for migration inputs only. Entity entry identities
|
|
132
108
|
* catch same-path edits that do not change the directory inode; the cooperative
|
|
@@ -146,7 +122,9 @@ export async function getFingerprint(
|
|
|
146
122
|
consumingFingerprint,
|
|
147
123
|
] = await Promise.all([
|
|
148
124
|
fingerprintPath(entitiesDir),
|
|
149
|
-
|
|
125
|
+
// The entry enumeration is one lstat per entity file — a corpus-sized
|
|
126
|
+
// syscall storm on the main thread. It runs in the corpus-scan worker.
|
|
127
|
+
corpusScanWorker.request({ kind: "entity-entries-fingerprint", entitiesDir }),
|
|
150
128
|
fingerprintPath(path.join(baseDir, "config", "aliases.json")),
|
|
151
129
|
fingerprintPath(path.join(baseDir, "state", ENTITY_CANONICAL_ID_RECONCILE_MARKER)),
|
|
152
130
|
fingerprintPath(path.join(baseDir, "state", ENTITY_CANONICAL_ID_RECONCILE_CONSUMING_MARKER)),
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
12
|
import type { Dirent, Stats } from "node:fs";
|
|
13
|
-
import { lstat, readdir,
|
|
13
|
+
import { lstat, readdir, stat } from "node:fs/promises";
|
|
14
14
|
import path from "node:path";
|
|
15
15
|
import { toMemoryPathRel } from "../memory-lifecycle-ledger-utils.js";
|
|
16
16
|
import { SecureStoreLockedError, readMaybeEncryptedFile } from "../secure-store/secure-fs.js";
|
|
@@ -19,7 +19,6 @@ import { RECALL_FALLBACK_DIRS } from "../utils/category-dir.js";
|
|
|
19
19
|
import { isErrnoCode } from "../utils/errno.js";
|
|
20
20
|
import { isPopulatedUnnamedEntityRecord } from "../utils/malformed-entity-record.js";
|
|
21
21
|
import { isMacOsReplicationJunkBasename } from "../utils/macos-replication-junk.js";
|
|
22
|
-
import { assertPathInsideRoot } from "../utils/path-containment.js";
|
|
23
22
|
import { isAbortError } from "../abort-error.js";
|
|
24
23
|
import { checkCorpusReadAbort, type CorpusReadOptions } from "../corpus-read-cancellation.js";
|
|
25
24
|
import { isValidTranscriptDate } from "../wearables/day-store.js";
|
|
@@ -31,6 +30,7 @@ import {
|
|
|
31
30
|
StorageManager,
|
|
32
31
|
} from "../storage.js";
|
|
33
32
|
import { rememberRawFrontmatter } from "./memory-frontmatter-metadata.js";
|
|
33
|
+
import { corpusScanWorker } from "../corpus-scan-worker-host.js";
|
|
34
34
|
|
|
35
35
|
export interface MemoryReadStoreDeps {
|
|
36
36
|
/** Live class object of the host instance — shared static caches (see storage.ts storageManagerClass). */
|
|
@@ -86,20 +86,6 @@ export interface MemoryReadStoreDeps {
|
|
|
86
86
|
readonly wearablesDir: string;
|
|
87
87
|
}
|
|
88
88
|
|
|
89
|
-
/**
|
|
90
|
-
* A backend READ failure worth propagating from a strict (census) scan: an
|
|
91
|
-
* errno-coded fs error that is NOT `ENOENT`. `ENOENT` (an absent dir) and
|
|
92
|
-
* code-less rejections (symlink-escape containment failures from
|
|
93
|
-
* assertPathInsideRoot) are expected and skipped; `EACCES`/`EIO`/… mean the
|
|
94
|
-
* corpus is unreadable and a divergence census must fail loud rather than
|
|
95
|
-
* silently publish a partial count (issue #2156 round-5). Non-strict (recall)
|
|
96
|
-
* callers ignore this and degrade to a best-effort partial scan.
|
|
97
|
-
*/
|
|
98
|
-
function isPropagatableReadError(err: unknown): boolean {
|
|
99
|
-
const code = (err as NodeJS.ErrnoException).code;
|
|
100
|
-
return typeof code === "string" && code !== "ENOENT";
|
|
101
|
-
}
|
|
102
|
-
|
|
103
89
|
/**
|
|
104
90
|
* Markdown under this store's `entities/` directory. The first relative
|
|
105
91
|
* segment must be `entities`; a memory root whose absolute path contains
|
|
@@ -233,9 +219,13 @@ export class MemoryReadStore {
|
|
|
233
219
|
/**
|
|
234
220
|
* Recursively collect `*.md` paths under `startDirs`, hardened against symlink
|
|
235
221
|
* escape: a category dir symlinked outside memoryDir (e.g. decisions/ -> an
|
|
236
|
-
* external dir) must NOT pull out-of-store files into a scan (info leak).
|
|
237
|
-
*
|
|
238
|
-
*
|
|
222
|
+
* external dir) must NOT pull out-of-store files into a scan (info leak).
|
|
223
|
+
*
|
|
224
|
+
* The walk runs in the corpus-scan worker: it is one `readdir` + `lstat` per
|
|
225
|
+
* directory over the whole corpus, and on the main event loop that starves
|
|
226
|
+
* observe, recall and the extraction queue behind it. The implementation is
|
|
227
|
+
* shared (src/corpus-scan.ts) and the host runs it inline when no worker
|
|
228
|
+
* can start, so the collected path set is the same either way.
|
|
239
229
|
* Paths only — no frontmatter parse.
|
|
240
230
|
*/
|
|
241
231
|
private async collectContainedMarkdownPaths(
|
|
@@ -243,98 +233,16 @@ export class MemoryReadStore {
|
|
|
243
233
|
propagateReadErrors = false,
|
|
244
234
|
options?: CorpusReadOptions,
|
|
245
235
|
): Promise<string[]> {
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
if (propagateReadErrors && isPropagatableReadError(err)) throw err;
|
|
257
|
-
return filePaths;
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
const collectPaths = async (dir: string): Promise<void> => {
|
|
261
|
-
checkCorpusReadAbort(options);
|
|
262
|
-
// Directory-level guard, isolated from per-entry handling: skip symlinked
|
|
263
|
-
// or non-directory category dirs and assert the resolved dir stays inside
|
|
264
|
-
// the memory root before reading. A failure here means the whole subtree
|
|
265
|
-
// does not exist or escaped the store — fail closed by skipping it.
|
|
266
|
-
let dirStat: Stats;
|
|
267
|
-
try {
|
|
268
|
-
dirStat = await lstat(dir);
|
|
269
|
-
} catch (err) {
|
|
270
|
-
// ENOENT (absent category dir) is expected; a backend read error
|
|
271
|
-
// (EACCES/EIO/…) propagates in strict census mode.
|
|
272
|
-
if (propagateReadErrors && isPropagatableReadError(err)) throw err;
|
|
273
|
-
return;
|
|
274
|
-
}
|
|
275
|
-
if (dirStat.isSymbolicLink()) return; // never follow symlinks out of the store (both modes)
|
|
276
|
-
if (!dirStat.isDirectory()) {
|
|
277
|
-
// A category root that EXISTS but is not a directory (e.g. `facts/`
|
|
278
|
-
// replaced by a regular file) is a layout/backend failure: a strict
|
|
279
|
-
// census must not silently publish the remaining categories as healthy.
|
|
280
|
-
// Non-strict (recall) stays best-effort and skips it (issue #2156 round-10).
|
|
281
|
-
if (propagateReadErrors) {
|
|
282
|
-
throw new Error(`corpus scan: expected a directory but found a non-directory at ${dir}`);
|
|
283
|
-
}
|
|
284
|
-
return;
|
|
285
|
-
}
|
|
286
|
-
let entries: Dirent[];
|
|
287
|
-
try {
|
|
288
|
-
assertPathInsideRoot(memoryRootReal, await realpath(dir), dir);
|
|
289
|
-
entries = await readdir(dir, { withFileTypes: true });
|
|
290
|
-
} catch (err) {
|
|
291
|
-
// A containment failure carries no errno (skip); a backend read error
|
|
292
|
-
// (EACCES …) propagates in strict census mode so a partial subtree is
|
|
293
|
-
// not silently counted.
|
|
294
|
-
if (propagateReadErrors && isPropagatableReadError(err)) throw err;
|
|
295
|
-
return;
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
const subdirs: string[] = [];
|
|
299
|
-
for (const entry of entries) {
|
|
300
|
-
// Outside the per-entry try/catch below, which deliberately swallows
|
|
301
|
-
// non-propagatable errors — a cancellation must never be swallowed with
|
|
302
|
-
// them (issue #2307).
|
|
303
|
-
checkCorpusReadAbort(options);
|
|
304
|
-
// Never follow symlinked entries out of the store.
|
|
305
|
-
if (entry.isSymbolicLink()) continue;
|
|
306
|
-
if (isMacOsReplicationJunkBasename(entry.name)) continue;
|
|
307
|
-
const fullPath = path.join(dir, entry.name);
|
|
308
|
-
if (entry.isDirectory()) {
|
|
309
|
-
subdirs.push(fullPath);
|
|
310
|
-
} else if (entry.name.endsWith(".md")) {
|
|
311
|
-
// Isolate per-entry failures in their own try/catch: a containment or
|
|
312
|
-
// realpath failure on ONE .md entry must not drop sibling files or,
|
|
313
|
-
// crucially, the deferred subdir recursion below (Cursor Bugbot:
|
|
314
|
-
// "Poisoned md skips sibling subdirs"). Mirrors the per-file try/catch
|
|
315
|
-
// in search/document-scanner.ts scanDir and
|
|
316
|
-
// consolidation-provenance-check.ts walkMarkdownFiles.
|
|
317
|
-
try {
|
|
318
|
-
assertPathInsideRoot(memoryRootReal, await realpath(fullPath), fullPath);
|
|
319
|
-
filePaths.push(fullPath);
|
|
320
|
-
} catch (err) {
|
|
321
|
-
// ENOENT (vanished mid-scan) or a code-less containment/symlink
|
|
322
|
-
// rejection ⇒ skip just this file. A backend read error propagates
|
|
323
|
-
// in strict census mode.
|
|
324
|
-
if (propagateReadErrors && isPropagatableReadError(err)) throw err;
|
|
325
|
-
}
|
|
326
|
-
}
|
|
327
|
-
}
|
|
328
|
-
// Recurse into real subdirectories regardless of any single poisoned entry
|
|
329
|
-
// above, so valid nested in-store memories are never dropped.
|
|
330
|
-
for (const subdir of subdirs) {
|
|
331
|
-
await collectPaths(subdir);
|
|
332
|
-
}
|
|
333
|
-
};
|
|
334
|
-
|
|
335
|
-
for (const dir of startDirs) {
|
|
336
|
-
await collectPaths(dir);
|
|
337
|
-
}
|
|
236
|
+
checkCorpusReadAbort(options);
|
|
237
|
+
const filePaths = await corpusScanWorker.request(
|
|
238
|
+
{
|
|
239
|
+
kind: "walk",
|
|
240
|
+
baseDir: this.deps.baseDir,
|
|
241
|
+
startDirs: [...startDirs],
|
|
242
|
+
propagateReadErrors,
|
|
243
|
+
},
|
|
244
|
+
options?.abortSignal,
|
|
245
|
+
);
|
|
338
246
|
// A signal that fired during the last directory read would otherwise return a
|
|
339
247
|
// truncated path list as if it were the whole corpus (issue #2307 review).
|
|
340
248
|
checkCorpusReadAbort(options);
|