@remnic/core 9.69.101 → 9.69.103
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 +52 -51
- package/dist/access-authorization-probe.js +53 -52
- package/dist/access-boundary.js +52 -51
- package/dist/access-cli.js +151 -148
- package/dist/access-cli.js.map +1 -1
- package/dist/access-extraction-force-flush.js +52 -51
- package/dist/access-http-briefing.js +53 -52
- package/dist/access-http-query.js +54 -53
- package/dist/access-http.js +76 -75
- package/dist/access-identity-continuity-surface.js +52 -51
- package/dist/access-lcm-surface.js +52 -51
- package/dist/access-mcp.js +65 -64
- package/dist/access-namespace-preflight.js +52 -51
- package/dist/access-observe-write-category.js +8 -8
- package/dist/access-observe-write-surface.js +52 -51
- package/dist/access-offline-manifest.js +5 -5
- package/dist/access-operations-batch.js +56 -55
- package/dist/access-operations.js +63 -62
- package/dist/access-recall-concurrency.js +52 -51
- package/dist/access-recall-response.js +52 -51
- package/dist/access-recall-surface.js +52 -51
- package/dist/access-schema.js +7 -7
- package/dist/access-service.js +52 -51
- package/dist/access-surface-catalog.js +54 -53
- package/dist/access-surface-catalog.js.map +1 -1
- package/dist/active-recall.js +20 -19
- package/dist/active-recall.js.map +1 -1
- package/dist/adapters/index.js +4 -4
- package/dist/adapters/registry.js +2 -2
- package/dist/briefing.js +14 -13
- package/dist/buffer.js +5 -5
- package/dist/{capsule-crypto-7FJQINUR.js → capsule-crypto-YO5QJ6L3.js} +2 -2
- package/dist/{capsule-merge-ZCXUPLNJ.js → capsule-merge-42GJGDUC.js} +4 -3
- package/dist/{capsule-merge-ZCXUPLNJ.js.map → capsule-merge-42GJGDUC.js.map} +1 -1
- package/dist/causal-behavior.js +2 -2
- package/dist/causal-chain.js +2 -2
- package/dist/causal-consolidation.js +17 -16
- package/dist/causal-consolidation.js.map +1 -1
- package/dist/causal-retrieval.js +2 -2
- package/dist/causal-trajectory-graph.js +3 -3
- package/dist/causal-trajectory.js +1 -1
- package/dist/{chunk-UL7KDVLC.js → chunk-26DJ7FG6.js} +11 -11
- package/dist/{chunk-QCYUIZK2.js → chunk-2GSO4FEC.js} +7 -7
- package/dist/{chunk-533RHNHB.js → chunk-2JJD7GIW.js} +2 -2
- package/dist/{chunk-IOYY6ROU.js → chunk-2MMBJE53.js} +7 -7
- package/dist/{chunk-QSXEQPC4.js → chunk-2OFDC3AK.js} +2 -2
- package/dist/{chunk-5FAGCOZD.js → chunk-2U3R3DDH.js} +9 -9
- package/dist/{chunk-SCXURCVT.js → chunk-2UPTRDGM.js} +3 -3
- package/dist/{chunk-XVZA6VXC.js → chunk-3SL52JKD.js} +4 -4
- package/dist/{chunk-XEATJBNB.js → chunk-4QHWRWCF.js} +70 -70
- package/dist/{chunk-SBOCJ47I.js → chunk-5FHH4NJR.js} +6 -6
- package/dist/{chunk-V3I4NLZP.js → chunk-5GKRQBPQ.js} +2 -2
- package/dist/{chunk-PYYQCHJC.js → chunk-5PEBJUYD.js} +2 -2
- package/dist/{chunk-PEZAHDDU.js → chunk-6RWPKWCZ.js} +4 -4
- package/dist/{chunk-DSPWVQZ6.js → chunk-73R4HOOK.js} +3 -3
- package/dist/chunk-7CT3AI52.js +84 -0
- package/dist/chunk-7CT3AI52.js.map +1 -0
- package/dist/{chunk-S533KGX6.js → chunk-7X7A7KVN.js} +2 -2
- package/dist/{chunk-F5MINS3R.js → chunk-A6WNTVA7.js} +3 -3
- package/dist/{chunk-F5MINS3R.js.map → chunk-A6WNTVA7.js.map} +1 -1
- package/dist/{chunk-JZ66PN72.js → chunk-AWSP3WNM.js} +2 -2
- package/dist/{chunk-IY47KPML.js → chunk-B3OFQP5Y.js} +6 -6
- package/dist/{chunk-GNBZB43J.js → chunk-B44TVSEI.js} +2 -2
- package/dist/{chunk-4DXNOVNW.js → chunk-B454SHPU.js} +6 -6
- package/dist/{chunk-3LMOJXZR.js → chunk-BKVCTTEN.js} +2 -2
- package/dist/{chunk-2NLLXCJG.js → chunk-BXLOS5AJ.js} +2 -2
- package/dist/{chunk-HWLIMQE2.js → chunk-CHDGJJV6.js} +2 -2
- package/dist/{chunk-UYPQOASU.js → chunk-CNBGAFDY.js} +2 -2
- package/dist/{chunk-CYG3K2T2.js → chunk-D42DLA4D.js} +6 -6
- package/dist/{chunk-X5TAWDRO.js → chunk-D4MXH276.js} +2 -2
- package/dist/{chunk-X7Y7WX73.js → chunk-DQEMWVMT.js} +1 -1
- package/dist/{chunk-UFIZRMQZ.js → chunk-E4XLFYHV.js} +61 -29
- package/dist/chunk-E4XLFYHV.js.map +1 -0
- package/dist/{chunk-4OXXDL3X.js → chunk-EWWK65CI.js} +2 -2
- package/dist/{chunk-DFPG47ET.js → chunk-EZ3F4YIN.js} +11 -11
- package/dist/{chunk-PNVOKGAX.js → chunk-G5I5ZT6O.js} +2 -2
- package/dist/{chunk-7Z2UKRNJ.js → chunk-GQ6ZF4LD.js} +2 -2
- package/dist/{chunk-7OA4JWP5.js → chunk-GWWHV7F3.js} +2 -2
- package/dist/chunk-H6Z3RNW3.js +330 -0
- package/dist/chunk-H6Z3RNW3.js.map +1 -0
- package/dist/{chunk-645WOU5I.js → chunk-H7BZBSP6.js} +4 -4
- package/dist/{chunk-PHBH74Q6.js → chunk-HTXTVY64.js} +2 -2
- package/dist/{chunk-AXHU56C3.js → chunk-HWGFTTPS.js} +2 -2
- package/dist/{chunk-ARY6O3JN.js → chunk-HYFVZJLX.js} +2 -2
- package/dist/{chunk-2SGLMP2E.js → chunk-IDSZNHIP.js} +2 -2
- package/dist/{chunk-LO4UKPKX.js → chunk-IFCKHPY4.js} +2 -2
- package/dist/{chunk-ZHXSOMYH.js → chunk-IOMLGJHG.js} +4 -4
- package/dist/{chunk-YRULQW5J.js → chunk-ISX7GVZE.js} +4 -4
- package/dist/{chunk-5PHZSD75.js → chunk-J3ZJBX7I.js} +2 -2
- package/dist/{chunk-DINOVGA5.js → chunk-JHZOY4CJ.js} +48 -48
- package/dist/chunk-JHZOY4CJ.js.map +1 -0
- package/dist/{chunk-RFQT7IZO.js → chunk-JRDL3FGM.js} +3 -3
- package/dist/{chunk-OGZC2WUX.js → chunk-JWFTPRYD.js} +2 -2
- package/dist/{chunk-VPOAMPDD.js → chunk-K5ABKRXK.js} +59 -59
- package/dist/{chunk-P3WRFSYF.js → chunk-KMN2JE4D.js} +2 -2
- package/dist/{chunk-4MP2Q2EI.js → chunk-KN4GNQH7.js} +3 -3
- package/dist/{chunk-L6YCGS7I.js → chunk-KYUVFCT2.js} +2 -2
- package/dist/{chunk-5BVL2XUD.js → chunk-L6BUCPGI.js} +17 -17
- package/dist/{chunk-55NZVUNI.js → chunk-LP4UW5WI.js} +2 -2
- package/dist/{chunk-SF7CWSR6.js → chunk-LXJZUNLX.js} +2 -2
- package/dist/{chunk-XCB6QDL6.js → chunk-M5GVCJSJ.js} +8 -2
- package/dist/chunk-M5GVCJSJ.js.map +1 -0
- package/dist/{chunk-DGS34QSY.js → chunk-MFZACYZ6.js} +2 -2
- package/dist/{chunk-G3BW6SDE.js → chunk-NBYSCXOY.js} +3 -3
- package/dist/{chunk-KYLYXGKD.js → chunk-NIGZ3LE7.js} +2 -2
- package/dist/{chunk-JTC3PBNH.js → chunk-NN62MUZA.js} +2 -2
- package/dist/{chunk-R2TVWQLR.js → chunk-NT2Q6AHC.js} +2 -2
- package/dist/{chunk-ABGM2BN4.js → chunk-O43AAJFQ.js} +2 -2
- package/dist/{chunk-CZN53OT5.js → chunk-OC6ATPIP.js} +2 -2
- package/dist/{chunk-6Z263Z3E.js → chunk-OWV6LKNZ.js} +2 -2
- package/dist/{chunk-NM3BT2OH.js → chunk-PNZAFABF.js} +2 -2
- package/dist/{chunk-HIHN4XHL.js → chunk-PQ7EB5AD.js} +3 -3
- package/dist/{chunk-VVYLVE6R.js → chunk-PQKZLBCJ.js} +1 -1
- package/dist/{chunk-OFFW73OZ.js → chunk-PRNR47DI.js} +2 -2
- package/dist/{chunk-RZXFXKWY.js → chunk-Q3LWW7WS.js} +2 -2
- package/dist/{chunk-M7T2NKET.js → chunk-QKCGOECM.js} +2 -2
- package/dist/{chunk-Z3O53WZR.js → chunk-QWUJWBZ5.js} +2 -2
- package/dist/{chunk-HFOY2SBG.js → chunk-RYN4AIRM.js} +7 -5
- package/dist/chunk-RYN4AIRM.js.map +1 -0
- package/dist/{chunk-4LNCLZNK.js → chunk-S2GR3T7W.js} +2 -2
- package/dist/{chunk-2IWBHZ3H.js → chunk-SIGDETMB.js} +2 -2
- package/dist/{chunk-7AUIGOH4.js → chunk-SOVRTUD3.js} +2 -2
- package/dist/chunk-SP4DB6SX.js +19 -0
- package/dist/chunk-SP4DB6SX.js.map +1 -0
- package/dist/{chunk-S5R77WHV.js → chunk-SWXPR5SE.js} +2 -2
- package/dist/{chunk-3DNTFZAT.js → chunk-T32YYUT2.js} +2 -2
- package/dist/{chunk-W5JQSVPZ.js → chunk-T6RQNV7E.js} +2 -2
- package/dist/{chunk-O23QMYN4.js → chunk-TRN56SPQ.js} +2 -2
- package/dist/{chunk-KQAFEZQX.js → chunk-VDX2J7OX.js} +2 -2
- package/dist/{chunk-ZGYLPCWN.js → chunk-VO2BNAA2.js} +3 -3
- package/dist/{chunk-WVEECBCG.js → chunk-VV2MVHCJ.js} +2 -2
- package/dist/{chunk-HVDEKJJR.js → chunk-VYIBRCVW.js} +70 -61
- package/dist/chunk-VYIBRCVW.js.map +1 -0
- package/dist/{chunk-B3O3IRZN.js → chunk-VZBW6CDG.js} +1 -1
- package/dist/{chunk-JPKK466T.js → chunk-W5XWHTDO.js} +2 -2
- package/dist/{chunk-JNAOTN7B.js → chunk-XLWMNDQR.js} +4 -4
- package/dist/chunk-XLWMNDQR.js.map +1 -0
- package/dist/{chunk-DBWNTLLI.js → chunk-XM32D6YZ.js} +6 -6
- package/dist/{chunk-RP7RSENW.js → chunk-XQWKB6PY.js} +2 -2
- package/dist/{chunk-64S6X4IU.js → chunk-XTN54NML.js} +4 -4
- package/dist/{chunk-EZ25VE3G.js → chunk-YNDLCWXS.js} +4 -4
- package/dist/{chunk-Y7427SL7.js → chunk-Z6EZCN6Z.js} +10 -10
- package/dist/{chunk-5K2ALMSZ.js → chunk-ZRUC7FD3.js} +2 -2
- package/dist/cli.js +102 -101
- package/dist/coding/export-okf-codegraph.js +13 -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 +14 -13
- package/dist/config.js +20 -19
- package/dist/connectors/codex-materialize-runner.js +14 -13
- package/dist/connectors/index.js +14 -13
- package/dist/consolidation-provenance-check.js +4 -3
- package/dist/consolidation-undo.js +3 -2
- package/dist/consolidation-undo.js.map +1 -1
- package/dist/contradiction/index.js +3 -2
- package/dist/convergence-refresh.js +16 -15
- package/dist/corpus-watermark.js +16 -15
- package/dist/dashboard-runtime.js +2 -2
- package/dist/deep-recall-http-glue.js +8 -8
- package/dist/deep-recall.js +2 -2
- package/dist/entity-mention-carry.d.ts +16 -0
- package/dist/entity-mention-carry.d.ts.map +1 -0
- package/dist/entity-mention-carry.js +8 -0
- package/dist/entity-mention-epoch.d.ts +22 -0
- package/dist/entity-mention-epoch.d.ts.map +1 -0
- package/dist/entity-mention-epoch.js +8 -0
- package/dist/entity-mention-index-cache.d.ts +28 -0
- package/dist/entity-mention-index-cache.d.ts.map +1 -0
- package/dist/entity-mention-index-cache.js +29 -0
- package/dist/entity-mention-index-cache.js.map +1 -0
- package/dist/entity-retrieval.d.ts.map +1 -1
- package/dist/entity-retrieval.js +18 -15
- package/dist/event-order-recall.js +2 -2
- package/dist/external-wiki-access.js +53 -52
- package/dist/extraction-span-materialize.js +2 -2
- package/dist/extraction.js +21 -20
- package/dist/{graph-edge-decay-MKIRZ762.js → graph-edge-decay-PVKDKLQK.js} +6 -6
- package/dist/graph-edge-decay-PVKDKLQK.js.map +1 -0
- package/dist/graph-jsonl.js +4 -4
- package/dist/graph-snapshot.js +4 -4
- package/dist/graph.js +5 -5
- package/dist/harmonic-retrieval.js +1 -1
- package/dist/index.js +186 -183
- package/dist/lcm/engine.js +4 -4
- package/dist/lcm/index.js +13 -13
- package/dist/location/index.js +16 -15
- package/dist/maintenance/memory-governance.js +14 -13
- package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +14 -13
- package/dist/maintenance/rebuild-memory-projection.js +15 -14
- package/dist/memory-browse-operations.js +53 -52
- package/dist/memory-browse.js +5 -5
- package/dist/memory-corpus-version.d.ts +8 -3
- package/dist/memory-corpus-version.d.ts.map +1 -1
- package/dist/memory-corpus-version.js +2 -1
- package/dist/namespaces/migrate.js +15 -14
- package/dist/namespaces/search.js +7 -7
- package/dist/namespaces/storage.js +14 -13
- package/dist/offline-sync-changeset-generations.js +3 -3
- package/dist/offline-sync-embedding-generation.js +2 -2
- package/dist/offline-sync-snapshot-stream.js +5 -5
- package/dist/offline-sync.js +7 -7
- package/dist/operator-doctor-corpus.js +17 -16
- package/dist/operator-toolkit.js +33 -32
- package/dist/orchestration/maintenance.js +22 -21
- package/dist/orchestrator.js +151 -148
- package/dist/page-versioning.js +3 -2
- package/dist/preferences/preference-drift.d.ts.map +1 -1
- package/dist/procedural/library-maintenance.d.ts.map +1 -1
- package/dist/qmd.js +4 -4
- package/dist/recall-navigation-operations.js +53 -52
- package/dist/recall-navigation.js +52 -51
- package/dist/recall-state-view-admission.js +5 -5
- package/dist/recall-why-service.js +5 -5
- package/dist/{resolution-BROE3TRO.js → resolution-RSG4HZTL.js} +4 -3
- package/dist/resolution-RSG4HZTL.js.map +1 -0
- package/dist/resume-bundles.js +21 -20
- package/dist/search/factory.js +6 -6
- package/dist/search/index.js +12 -12
- package/dist/semantic-consolidation.js +15 -14
- package/dist/semantic-rule-promotion.js +14 -13
- package/dist/semantic-rule-verifier.js +14 -13
- package/dist/source-agent-qualifier.js +17 -16
- package/dist/standup/build.js +14 -13
- package/dist/storage/entity-mention-frontmatter-hold.d.ts +14 -0
- package/dist/storage/entity-mention-frontmatter-hold.d.ts.map +1 -0
- package/dist/storage.d.ts +13 -9
- package/dist/storage.d.ts.map +1 -1
- package/dist/storage.js +13 -12
- package/dist/support-passport/index.js +55 -54
- package/dist/transfer/backup.js +2 -2
- package/dist/transfer/capsule-export.js +2 -2
- package/dist/transfer/capsule-import.js +5 -4
- package/dist/transfer/export-okf.js +14 -13
- package/dist/verified-recall.js +14 -13
- package/dist/who-knows-cli.js +15 -14
- package/dist/who-knows.js +14 -13
- package/package.json +2 -2
- package/src/entity-mention-carry.ts +31 -0
- package/src/entity-mention-epoch.ts +120 -0
- package/src/entity-mention-index-cache.ts +443 -0
- package/src/entity-retrieval.ts +72 -71
- package/src/maintenance/rebuild-memory-projection.ts +1 -1
- package/src/memory-corpus-version.ts +13 -3
- package/src/preferences/preference-drift.ts +5 -3
- package/src/procedural/library-maintenance.ts +4 -3
- package/src/semantic-rule-promotion.ts +1 -1
- package/src/storage/entity-mention-frontmatter-hold.ts +44 -0
- package/src/storage.ts +40 -38
- package/dist/chunk-DINOVGA5.js.map +0 -1
- package/dist/chunk-HFOY2SBG.js.map +0 -1
- package/dist/chunk-HVDEKJJR.js.map +0 -1
- package/dist/chunk-JNAOTN7B.js.map +0 -1
- package/dist/chunk-UFIZRMQZ.js.map +0 -1
- package/dist/chunk-XCB6QDL6.js.map +0 -1
- /package/dist/{capsule-crypto-7FJQINUR.js.map → capsule-crypto-YO5QJ6L3.js.map} +0 -0
- /package/dist/{chunk-UL7KDVLC.js.map → chunk-26DJ7FG6.js.map} +0 -0
- /package/dist/{chunk-QCYUIZK2.js.map → chunk-2GSO4FEC.js.map} +0 -0
- /package/dist/{chunk-533RHNHB.js.map → chunk-2JJD7GIW.js.map} +0 -0
- /package/dist/{chunk-IOYY6ROU.js.map → chunk-2MMBJE53.js.map} +0 -0
- /package/dist/{chunk-QSXEQPC4.js.map → chunk-2OFDC3AK.js.map} +0 -0
- /package/dist/{chunk-5FAGCOZD.js.map → chunk-2U3R3DDH.js.map} +0 -0
- /package/dist/{chunk-SCXURCVT.js.map → chunk-2UPTRDGM.js.map} +0 -0
- /package/dist/{chunk-XVZA6VXC.js.map → chunk-3SL52JKD.js.map} +0 -0
- /package/dist/{chunk-XEATJBNB.js.map → chunk-4QHWRWCF.js.map} +0 -0
- /package/dist/{chunk-SBOCJ47I.js.map → chunk-5FHH4NJR.js.map} +0 -0
- /package/dist/{chunk-V3I4NLZP.js.map → chunk-5GKRQBPQ.js.map} +0 -0
- /package/dist/{chunk-PYYQCHJC.js.map → chunk-5PEBJUYD.js.map} +0 -0
- /package/dist/{chunk-PEZAHDDU.js.map → chunk-6RWPKWCZ.js.map} +0 -0
- /package/dist/{chunk-DSPWVQZ6.js.map → chunk-73R4HOOK.js.map} +0 -0
- /package/dist/{chunk-S533KGX6.js.map → chunk-7X7A7KVN.js.map} +0 -0
- /package/dist/{chunk-JZ66PN72.js.map → chunk-AWSP3WNM.js.map} +0 -0
- /package/dist/{chunk-IY47KPML.js.map → chunk-B3OFQP5Y.js.map} +0 -0
- /package/dist/{chunk-GNBZB43J.js.map → chunk-B44TVSEI.js.map} +0 -0
- /package/dist/{chunk-4DXNOVNW.js.map → chunk-B454SHPU.js.map} +0 -0
- /package/dist/{chunk-3LMOJXZR.js.map → chunk-BKVCTTEN.js.map} +0 -0
- /package/dist/{chunk-2NLLXCJG.js.map → chunk-BXLOS5AJ.js.map} +0 -0
- /package/dist/{chunk-HWLIMQE2.js.map → chunk-CHDGJJV6.js.map} +0 -0
- /package/dist/{chunk-UYPQOASU.js.map → chunk-CNBGAFDY.js.map} +0 -0
- /package/dist/{chunk-CYG3K2T2.js.map → chunk-D42DLA4D.js.map} +0 -0
- /package/dist/{chunk-X5TAWDRO.js.map → chunk-D4MXH276.js.map} +0 -0
- /package/dist/{chunk-X7Y7WX73.js.map → chunk-DQEMWVMT.js.map} +0 -0
- /package/dist/{chunk-4OXXDL3X.js.map → chunk-EWWK65CI.js.map} +0 -0
- /package/dist/{chunk-DFPG47ET.js.map → chunk-EZ3F4YIN.js.map} +0 -0
- /package/dist/{chunk-PNVOKGAX.js.map → chunk-G5I5ZT6O.js.map} +0 -0
- /package/dist/{chunk-7Z2UKRNJ.js.map → chunk-GQ6ZF4LD.js.map} +0 -0
- /package/dist/{chunk-7OA4JWP5.js.map → chunk-GWWHV7F3.js.map} +0 -0
- /package/dist/{chunk-645WOU5I.js.map → chunk-H7BZBSP6.js.map} +0 -0
- /package/dist/{chunk-PHBH74Q6.js.map → chunk-HTXTVY64.js.map} +0 -0
- /package/dist/{chunk-AXHU56C3.js.map → chunk-HWGFTTPS.js.map} +0 -0
- /package/dist/{chunk-ARY6O3JN.js.map → chunk-HYFVZJLX.js.map} +0 -0
- /package/dist/{chunk-2SGLMP2E.js.map → chunk-IDSZNHIP.js.map} +0 -0
- /package/dist/{chunk-LO4UKPKX.js.map → chunk-IFCKHPY4.js.map} +0 -0
- /package/dist/{chunk-ZHXSOMYH.js.map → chunk-IOMLGJHG.js.map} +0 -0
- /package/dist/{chunk-YRULQW5J.js.map → chunk-ISX7GVZE.js.map} +0 -0
- /package/dist/{chunk-5PHZSD75.js.map → chunk-J3ZJBX7I.js.map} +0 -0
- /package/dist/{chunk-RFQT7IZO.js.map → chunk-JRDL3FGM.js.map} +0 -0
- /package/dist/{chunk-OGZC2WUX.js.map → chunk-JWFTPRYD.js.map} +0 -0
- /package/dist/{chunk-VPOAMPDD.js.map → chunk-K5ABKRXK.js.map} +0 -0
- /package/dist/{chunk-P3WRFSYF.js.map → chunk-KMN2JE4D.js.map} +0 -0
- /package/dist/{chunk-4MP2Q2EI.js.map → chunk-KN4GNQH7.js.map} +0 -0
- /package/dist/{chunk-L6YCGS7I.js.map → chunk-KYUVFCT2.js.map} +0 -0
- /package/dist/{chunk-5BVL2XUD.js.map → chunk-L6BUCPGI.js.map} +0 -0
- /package/dist/{chunk-55NZVUNI.js.map → chunk-LP4UW5WI.js.map} +0 -0
- /package/dist/{chunk-SF7CWSR6.js.map → chunk-LXJZUNLX.js.map} +0 -0
- /package/dist/{chunk-DGS34QSY.js.map → chunk-MFZACYZ6.js.map} +0 -0
- /package/dist/{chunk-G3BW6SDE.js.map → chunk-NBYSCXOY.js.map} +0 -0
- /package/dist/{chunk-KYLYXGKD.js.map → chunk-NIGZ3LE7.js.map} +0 -0
- /package/dist/{chunk-JTC3PBNH.js.map → chunk-NN62MUZA.js.map} +0 -0
- /package/dist/{chunk-R2TVWQLR.js.map → chunk-NT2Q6AHC.js.map} +0 -0
- /package/dist/{chunk-ABGM2BN4.js.map → chunk-O43AAJFQ.js.map} +0 -0
- /package/dist/{chunk-CZN53OT5.js.map → chunk-OC6ATPIP.js.map} +0 -0
- /package/dist/{chunk-6Z263Z3E.js.map → chunk-OWV6LKNZ.js.map} +0 -0
- /package/dist/{chunk-NM3BT2OH.js.map → chunk-PNZAFABF.js.map} +0 -0
- /package/dist/{chunk-HIHN4XHL.js.map → chunk-PQ7EB5AD.js.map} +0 -0
- /package/dist/{chunk-VVYLVE6R.js.map → chunk-PQKZLBCJ.js.map} +0 -0
- /package/dist/{chunk-OFFW73OZ.js.map → chunk-PRNR47DI.js.map} +0 -0
- /package/dist/{chunk-RZXFXKWY.js.map → chunk-Q3LWW7WS.js.map} +0 -0
- /package/dist/{chunk-M7T2NKET.js.map → chunk-QKCGOECM.js.map} +0 -0
- /package/dist/{chunk-Z3O53WZR.js.map → chunk-QWUJWBZ5.js.map} +0 -0
- /package/dist/{chunk-4LNCLZNK.js.map → chunk-S2GR3T7W.js.map} +0 -0
- /package/dist/{chunk-2IWBHZ3H.js.map → chunk-SIGDETMB.js.map} +0 -0
- /package/dist/{chunk-7AUIGOH4.js.map → chunk-SOVRTUD3.js.map} +0 -0
- /package/dist/{chunk-S5R77WHV.js.map → chunk-SWXPR5SE.js.map} +0 -0
- /package/dist/{chunk-3DNTFZAT.js.map → chunk-T32YYUT2.js.map} +0 -0
- /package/dist/{chunk-W5JQSVPZ.js.map → chunk-T6RQNV7E.js.map} +0 -0
- /package/dist/{chunk-O23QMYN4.js.map → chunk-TRN56SPQ.js.map} +0 -0
- /package/dist/{chunk-KQAFEZQX.js.map → chunk-VDX2J7OX.js.map} +0 -0
- /package/dist/{chunk-ZGYLPCWN.js.map → chunk-VO2BNAA2.js.map} +0 -0
- /package/dist/{chunk-WVEECBCG.js.map → chunk-VV2MVHCJ.js.map} +0 -0
- /package/dist/{chunk-B3O3IRZN.js.map → chunk-VZBW6CDG.js.map} +0 -0
- /package/dist/{chunk-JPKK466T.js.map → chunk-W5XWHTDO.js.map} +0 -0
- /package/dist/{chunk-DBWNTLLI.js.map → chunk-XM32D6YZ.js.map} +0 -0
- /package/dist/{chunk-RP7RSENW.js.map → chunk-XQWKB6PY.js.map} +0 -0
- /package/dist/{chunk-64S6X4IU.js.map → chunk-XTN54NML.js.map} +0 -0
- /package/dist/{chunk-EZ25VE3G.js.map → chunk-YNDLCWXS.js.map} +0 -0
- /package/dist/{chunk-Y7427SL7.js.map → chunk-Z6EZCN6Z.js.map} +0 -0
- /package/dist/{chunk-5K2ALMSZ.js.map → chunk-ZRUC7FD3.js.map} +0 -0
- /package/dist/{graph-edge-decay-MKIRZ762.js.map → entity-mention-carry.js.map} +0 -0
- /package/dist/{resolution-BROE3TRO.js.map → entity-mention-epoch.js.map} +0 -0
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
2
|
+
import { appendFileSync, mkdirSync, statSync } from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Entity-mention freshness, separate from the hot-memories corpus sentinel.
|
|
7
|
+
*
|
|
8
|
+
* Every memory write bumps the corpus version so the hot cache stays coherent.
|
|
9
|
+
* The entity mention index does not depend on heat, decay, lifecycle, or
|
|
10
|
+
* lastValidatedAt, so those rewrites must not force a fact-file rescan.
|
|
11
|
+
* Content, entityRef, and origin changes bump this epoch. Metadata-only
|
|
12
|
+
* frontmatter rewrites run inside `hold()` and leave it alone.
|
|
13
|
+
*
|
|
14
|
+
* The on-disk sentinel is the byte size of `state/.entity-mention-epoch.log`
|
|
15
|
+
* (same pattern as the other version logs). A failed append adds one to an
|
|
16
|
+
* in-process extra, and `current` is the file size plus that extra. A later
|
|
17
|
+
* successful append, including one from another process, grows the file and
|
|
18
|
+
* moves `current` past those failed bumps. `reset` drops caches that
|
|
19
|
+
* registered at load (the mention index).
|
|
20
|
+
*/
|
|
21
|
+
const fallbackByDir = new Map<string, { extra: number; disk: number }>();
|
|
22
|
+
const suppression = new AsyncLocalStorage<true>();
|
|
23
|
+
let onReset: (() => void) | null = null;
|
|
24
|
+
|
|
25
|
+
type MentionFrontmatter = {
|
|
26
|
+
entityRef?: unknown;
|
|
27
|
+
origin?: unknown;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
function epochFile(dir: string): string {
|
|
31
|
+
return path.join(path.resolve(dir), "state", ".entity-mention-epoch.log");
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function mentionField(value: unknown): string | undefined {
|
|
35
|
+
return typeof value === "string" ? value : undefined;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function diskSize(filePath: string): number {
|
|
39
|
+
try {
|
|
40
|
+
return statSync(filePath).size;
|
|
41
|
+
} catch {
|
|
42
|
+
return 0;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function localEpoch(key: string, disk: number): { extra: number; disk: number } {
|
|
47
|
+
const existing = fallbackByDir.get(key);
|
|
48
|
+
if (existing) return existing;
|
|
49
|
+
const created = { extra: 0, disk };
|
|
50
|
+
fallbackByDir.set(key, created);
|
|
51
|
+
return created;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function bump(dir: string): void {
|
|
55
|
+
const key = path.resolve(dir);
|
|
56
|
+
const filePath = epochFile(dir);
|
|
57
|
+
const before = diskSize(filePath);
|
|
58
|
+
let appended = false;
|
|
59
|
+
try {
|
|
60
|
+
mkdirSync(path.dirname(filePath), { recursive: true });
|
|
61
|
+
appendFileSync(filePath, "x");
|
|
62
|
+
appended = true;
|
|
63
|
+
} catch {
|
|
64
|
+
// The file can still be stat-able (read-only, or the disk is full). The
|
|
65
|
+
// in-process counter has to move or this process keeps serving a stale index.
|
|
66
|
+
}
|
|
67
|
+
const after = diskSize(filePath);
|
|
68
|
+
const local = localEpoch(key, before);
|
|
69
|
+
if (!appended || after <= before) local.extra += 1;
|
|
70
|
+
noteDisk(local, after);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function noteDisk(local: { extra: number; disk: number }, disk: number): void {
|
|
74
|
+
if (disk < local.disk) local.extra += local.disk - disk;
|
|
75
|
+
local.disk = disk;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function current(dir: string): number {
|
|
79
|
+
const key = path.resolve(dir);
|
|
80
|
+
const disk = diskSize(epochFile(dir));
|
|
81
|
+
const local = fallbackByDir.get(key);
|
|
82
|
+
if (!local) return disk;
|
|
83
|
+
noteDisk(local, disk);
|
|
84
|
+
// Failed appends stay ahead of the file. A peer's later successful append
|
|
85
|
+
// grows `disk` and must not land on the same number as those local failures.
|
|
86
|
+
return disk + local.extra;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function suppressed(): boolean {
|
|
90
|
+
return suppression.getStore() === true;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function hold<T>(fn: () => T): T {
|
|
94
|
+
return suppression.run(true, fn);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function registerReset(fn: () => void): void {
|
|
98
|
+
onReset = fn;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function reset(): void {
|
|
102
|
+
onReset?.();
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function neutral(before: MentionFrontmatter, after: MentionFrontmatter): boolean {
|
|
106
|
+
return (
|
|
107
|
+
mentionField(before.entityRef) === mentionField(after.entityRef) &&
|
|
108
|
+
mentionField(before.origin) === mentionField(after.origin)
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export const entityMentionEpoch = {
|
|
113
|
+
bump,
|
|
114
|
+
current,
|
|
115
|
+
suppressed,
|
|
116
|
+
hold,
|
|
117
|
+
neutral,
|
|
118
|
+
registerReset,
|
|
119
|
+
reset,
|
|
120
|
+
};
|
|
@@ -0,0 +1,443 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { mkdir, readFile, rm, writeFile } from "node:fs/promises";
|
|
3
|
+
import { raceAbort } from "./abort-error.js";
|
|
4
|
+
import { entityMentionEpoch } from "./entity-mention-epoch.js";
|
|
5
|
+
import { log } from "./logger.js";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* In-memory entity mention index.
|
|
9
|
+
*
|
|
10
|
+
* Scope (namespaces, dirs, secure-store key, alias map, native revision) is
|
|
11
|
+
* the cache key. Mention epoch and entity-file mutation are identity inside
|
|
12
|
+
* the slot, not part of the key:
|
|
13
|
+
*
|
|
14
|
+
* - metadata-only corpus bumps do not move the epoch, so the next recall hits
|
|
15
|
+
* - entity-file changes rebuild from entity files before the recall returns,
|
|
16
|
+
* reusing memory snippets (no fact scan). A new entity is visible on that
|
|
17
|
+
* recall; its memory snippets arrive with the next full reconcile. A new
|
|
18
|
+
* canonical id schedules that reconcile. Callers share one scan. One
|
|
19
|
+
* caller's abort stops that caller waiting and leaves the scan running
|
|
20
|
+
* for the others
|
|
21
|
+
* - memory create/edit/delete moves the epoch. The recall serves the last
|
|
22
|
+
* index immediately and one background reconcile runs, plus at most one
|
|
23
|
+
* follow-up if that scan overlaps a later write. A recall during either
|
|
24
|
+
* scan does not grant another follow-up. Further rescans wait for the
|
|
25
|
+
* next recall. After writes quiesce and `settleEntityMentionIndex`
|
|
26
|
+
* finishes, the index matches a full rebuild
|
|
27
|
+
* - secure-store key, alias map, and native revision stay in the key, so a
|
|
28
|
+
* change is a miss and awaits a full rebuild. A locked store never reads
|
|
29
|
+
* another key's plaintext. Evicting that older scope also stops its scan
|
|
30
|
+
* from publishing the persisted index
|
|
31
|
+
* - a persisted index is readable per storage root. A process-wide cache
|
|
32
|
+
* clear blocks every root until that root's own post-clear build publishes.
|
|
33
|
+
* One root publishing does not reopen another root's file
|
|
34
|
+
*/
|
|
35
|
+
|
|
36
|
+
const MAX_SLOTS = 32;
|
|
37
|
+
const SETTLE_ROUNDS = 8;
|
|
38
|
+
|
|
39
|
+
export type EntityMentionIdentity = {
|
|
40
|
+
mentionEpoch: string;
|
|
41
|
+
entityMutation: string;
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
type ScopedStorage = {
|
|
45
|
+
dir: string;
|
|
46
|
+
hotCacheKeyId?(): string;
|
|
47
|
+
entityAliases?: Readonly<Record<string, string>> | null;
|
|
48
|
+
getEntityMutationVersion?(): number;
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
type Slot = {
|
|
52
|
+
scopeKey: string;
|
|
53
|
+
index: unknown;
|
|
54
|
+
identity: EntityMentionIdentity;
|
|
55
|
+
token: number;
|
|
56
|
+
rebuild: Promise<void> | null;
|
|
57
|
+
entityJob: Promise<void> | null;
|
|
58
|
+
again: boolean;
|
|
59
|
+
followUps: number;
|
|
60
|
+
currentIdentity: () => EntityMentionIdentity;
|
|
61
|
+
buildFull: (abortSignal?: AbortSignal) => Promise<unknown>;
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
const slots = new Map<string, Slot>();
|
|
65
|
+
const rebuildsByScope = new Map<string, number>();
|
|
66
|
+
const GENERATION_STRIDE = 1_000_000_000;
|
|
67
|
+
let clearEpoch = 0;
|
|
68
|
+
const localGenerationByDir = new Map<string, number>();
|
|
69
|
+
const publishedGenerationByDir = new Map<string, number>();
|
|
70
|
+
const persistTailByDir = new Map<string, Promise<void>>();
|
|
71
|
+
|
|
72
|
+
function resolveMentionDir(dir: string): string {
|
|
73
|
+
return path.resolve(dir);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export function entityMentionCacheGeneration(dir: string): number {
|
|
77
|
+
const resolved = resolveMentionDir(dir);
|
|
78
|
+
return clearEpoch * GENERATION_STRIDE + (localGenerationByDir.get(resolved) ?? 0);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export function entityMentionPersistedIndexReadable(dir: string): boolean {
|
|
82
|
+
const resolved = resolveMentionDir(dir);
|
|
83
|
+
const local = localGenerationByDir.get(resolved) ?? 0;
|
|
84
|
+
if (clearEpoch === 0 && local === 0) return true;
|
|
85
|
+
return publishedGenerationByDir.get(resolved) === entityMentionCacheGeneration(resolved);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function dirsInScope(scopeKey: string): string[] {
|
|
89
|
+
const nsEnd = scopeKey.indexOf("\u001e");
|
|
90
|
+
const storageEnd = nsEnd === -1 ? -1 : scopeKey.indexOf("\u001e", nsEnd + 1);
|
|
91
|
+
if (nsEnd === -1 || storageEnd === -1) return [];
|
|
92
|
+
return scopeKey
|
|
93
|
+
.slice(nsEnd + 1, storageEnd)
|
|
94
|
+
.split("\u001f")
|
|
95
|
+
.map((part) => part.split("\u001d")[0] ?? "")
|
|
96
|
+
.filter((dir) => dir.length > 0)
|
|
97
|
+
.map((dir) => resolveMentionDir(dir));
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function persistedMentionIndexFiles(scopeKey: string): string[] {
|
|
101
|
+
return dirsInScope(scopeKey).map((dir) => path.join(dir, "state", "entity-mention-index.json"));
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function bumpScopeDirs(scopeKey: string): void {
|
|
105
|
+
for (const dir of dirsInScope(scopeKey)) {
|
|
106
|
+
localGenerationByDir.set(dir, (localGenerationByDir.get(dir) ?? 0) + 1);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function captureDirGenerations(scopeKey: string): Map<string, number> {
|
|
111
|
+
const captured = new Map<string, number>();
|
|
112
|
+
for (const dir of dirsInScope(scopeKey)) captured.set(dir, entityMentionCacheGeneration(dir));
|
|
113
|
+
return captured;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function generationsCurrent(captured: Map<string, number>): boolean {
|
|
117
|
+
for (const [dir, generation] of captured) {
|
|
118
|
+
if (entityMentionCacheGeneration(dir) !== generation) return false;
|
|
119
|
+
}
|
|
120
|
+
return true;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function publishDirGenerations(captured: Map<string, number>): void {
|
|
124
|
+
for (const [dir, generation] of captured) {
|
|
125
|
+
if (entityMentionCacheGeneration(dir) === generation) publishedGenerationByDir.set(dir, generation);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export async function writePersistedEntityMentionIndex(
|
|
130
|
+
dir: string,
|
|
131
|
+
generation: number,
|
|
132
|
+
content: string,
|
|
133
|
+
): Promise<void> {
|
|
134
|
+
const resolved = resolveMentionDir(dir);
|
|
135
|
+
const previous = persistTailByDir.get(resolved) ?? Promise.resolve();
|
|
136
|
+
const run = previous.catch(() => undefined).then(async () => {
|
|
137
|
+
if (entityMentionCacheGeneration(resolved) !== generation) return;
|
|
138
|
+
const file = path.join(resolved, "state", "entity-mention-index.json");
|
|
139
|
+
await mkdir(path.dirname(file), { recursive: true });
|
|
140
|
+
const current = await readFile(file, "utf-8").catch(() => "");
|
|
141
|
+
if (current === content) return;
|
|
142
|
+
if (entityMentionCacheGeneration(resolved) !== generation) return;
|
|
143
|
+
await writeFile(file, content, "utf-8");
|
|
144
|
+
});
|
|
145
|
+
persistTailByDir.set(resolved, run.then(() => undefined, () => undefined));
|
|
146
|
+
await run;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function discardPersistedMentionIndex(scopeKey: string): void {
|
|
150
|
+
for (const file of persistedMentionIndexFiles(scopeKey)) {
|
|
151
|
+
void rm(file, { force: true }).catch(() => undefined);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function throwIfMentionAborted(signal?: AbortSignal): void {
|
|
156
|
+
if (!signal?.aborted) return;
|
|
157
|
+
const reason = signal.reason;
|
|
158
|
+
throw reason instanceof Error ? reason : new Error("entity mention rebuild aborted");
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function compareStrings(left: string, right: string): number {
|
|
162
|
+
if (left < right) return -1;
|
|
163
|
+
if (left > right) return 1;
|
|
164
|
+
return 0;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function deduped(values: readonly string[]): string[] {
|
|
168
|
+
const seen = new Set<string>();
|
|
169
|
+
const out: string[] = [];
|
|
170
|
+
for (const value of values) {
|
|
171
|
+
const trimmed = value.trim();
|
|
172
|
+
if (trimmed.length === 0 || seen.has(trimmed)) continue;
|
|
173
|
+
seen.add(trimmed);
|
|
174
|
+
out.push(trimmed);
|
|
175
|
+
}
|
|
176
|
+
return out;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function sameIdentity(left: EntityMentionIdentity, right: EntityMentionIdentity): boolean {
|
|
180
|
+
return left.mentionEpoch === right.mentionEpoch && left.entityMutation === right.entityMutation;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function noteRebuild(scopeKey: string): void {
|
|
184
|
+
rebuildsByScope.set(scopeKey, (rebuildsByScope.get(scopeKey) ?? 0) + 1);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function scopeFamily(scopeKey: string): string {
|
|
188
|
+
const nsEnd = scopeKey.indexOf("\u001e");
|
|
189
|
+
const storageEnd = nsEnd === -1 ? -1 : scopeKey.indexOf("\u001e", nsEnd + 1);
|
|
190
|
+
const namespaceKey = nsEnd === -1 ? scopeKey : scopeKey.slice(0, nsEnd);
|
|
191
|
+
const storageKey = nsEnd === -1 || storageEnd === -1 ? "" : scopeKey.slice(nsEnd + 1, storageEnd);
|
|
192
|
+
const dirs = storageKey
|
|
193
|
+
.split("\u001f")
|
|
194
|
+
.map((part) => part.split("\u001d")[0] ?? "")
|
|
195
|
+
.join("\u001f");
|
|
196
|
+
return `${namespaceKey}\u001e${dirs}`;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function forgetScope(scopeKey: string, bumpPersisted = false): void {
|
|
200
|
+
const slot = slots.get(scopeKey);
|
|
201
|
+
if (slot) {
|
|
202
|
+
slot.token += 1;
|
|
203
|
+
slot.again = false;
|
|
204
|
+
}
|
|
205
|
+
if (bumpPersisted) bumpScopeDirs(scopeKey);
|
|
206
|
+
slots.delete(scopeKey);
|
|
207
|
+
rebuildsByScope.delete(scopeKey);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
function evictOldest(scopeKey: string): void {
|
|
211
|
+
if (slots.size < MAX_SLOTS || slots.has(scopeKey)) return;
|
|
212
|
+
const oldest = slots.keys().next().value;
|
|
213
|
+
if (typeof oldest === "string") forgetScope(oldest);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
function evictSameFamily(scopeKey: string): void {
|
|
217
|
+
const family = scopeFamily(scopeKey);
|
|
218
|
+
for (const key of [...slots.keys()]) {
|
|
219
|
+
if (key !== scopeKey && scopeFamily(key) === family) forgetScope(key, true);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
function wantsSnippetReconcile(index: unknown): boolean {
|
|
224
|
+
return (
|
|
225
|
+
!!index &&
|
|
226
|
+
typeof index === "object" &&
|
|
227
|
+
(index as { pendingSnippetReconcile?: boolean }).pendingSnippetReconcile === true
|
|
228
|
+
);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
function armRebuild(slot: Slot): void {
|
|
232
|
+
// A recall that arrives while a scan is already running must not grant
|
|
233
|
+
// another follow-up. The chain stops after the one already armed.
|
|
234
|
+
if (!slot.rebuild) slot.followUps = 1;
|
|
235
|
+
startRebuild(slot);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
function startRebuild(slot: Slot): void {
|
|
239
|
+
// A recall that arrives during a scan is not a write. The follow-up is
|
|
240
|
+
// armed only when the scan's end identity differs from the identity it
|
|
241
|
+
// started with. Setting `again` here made every concurrent recall pay a
|
|
242
|
+
// second full corpus scan after a stable rebuild.
|
|
243
|
+
if (slot.rebuild) return;
|
|
244
|
+
const token = slot.token;
|
|
245
|
+
const captured = captureDirGenerations(slot.scopeKey);
|
|
246
|
+
noteRebuild(slot.scopeKey);
|
|
247
|
+
const started = slot.currentIdentity();
|
|
248
|
+
slot.again = false;
|
|
249
|
+
const handle: { pending: Promise<void> | null } = { pending: null };
|
|
250
|
+
handle.pending = slot
|
|
251
|
+
.buildFull(undefined)
|
|
252
|
+
.then((index) => {
|
|
253
|
+
if (slot.token !== token || !generationsCurrent(captured)) return;
|
|
254
|
+
publishDirGenerations(captured);
|
|
255
|
+
const ended = slot.currentIdentity();
|
|
256
|
+
slot.index = index;
|
|
257
|
+
if (sameIdentity(started, ended)) {
|
|
258
|
+
slot.identity = ended;
|
|
259
|
+
return;
|
|
260
|
+
}
|
|
261
|
+
// Scan overlapped later writes. Keep the start identity so the next
|
|
262
|
+
// recall stays stale and exactly one follow-up reconcile runs.
|
|
263
|
+
slot.identity = started;
|
|
264
|
+
slot.again = true;
|
|
265
|
+
})
|
|
266
|
+
.catch((err: unknown) => {
|
|
267
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
268
|
+
log.warn(`entity mention index rebuild failed: ${message}`);
|
|
269
|
+
})
|
|
270
|
+
.finally(() => {
|
|
271
|
+
if (slot.rebuild === handle.pending) slot.rebuild = null;
|
|
272
|
+
// One automatic follow-up per caller-started rebuild. A store that keeps
|
|
273
|
+
// changing during every scan waits for the next recall (or settle) instead
|
|
274
|
+
// of chaining full scans with nobody waiting.
|
|
275
|
+
if (!slot.again || slot.followUps <= 0) return;
|
|
276
|
+
slot.again = false;
|
|
277
|
+
slot.followUps -= 1;
|
|
278
|
+
startRebuild(slot);
|
|
279
|
+
});
|
|
280
|
+
slot.rebuild = handle.pending;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
export function entityMentionScopeKey(
|
|
284
|
+
namespaces: readonly string[] | undefined,
|
|
285
|
+
storages: readonly ScopedStorage[],
|
|
286
|
+
nativeRevision: string,
|
|
287
|
+
): string {
|
|
288
|
+
const namespaceKey = deduped(namespaces ?? []).join("\u001f");
|
|
289
|
+
const storageKey = storages
|
|
290
|
+
.map((storage) => {
|
|
291
|
+
const aliases = Object.entries(storage.entityAliases ?? {}).sort(([left], [right]) =>
|
|
292
|
+
compareStrings(left, right),
|
|
293
|
+
);
|
|
294
|
+
return `${path.resolve(storage.dir)}\u001d${storage.hotCacheKeyId?.() ?? ""}\u001d${JSON.stringify(aliases)}`;
|
|
295
|
+
})
|
|
296
|
+
.join("\u001f");
|
|
297
|
+
return `${namespaceKey}\u001e${storageKey}\u001e${nativeRevision}`;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
export function entityMentionIdentity(storages: readonly ScopedStorage[]): EntityMentionIdentity {
|
|
301
|
+
return {
|
|
302
|
+
mentionEpoch: storages.map((storage) => String(entityMentionEpoch.current(storage.dir))).join("\u001f"),
|
|
303
|
+
entityMutation: storages.map((storage) => String(storage.getEntityMutationVersion?.() ?? 0)).join("\u001f"),
|
|
304
|
+
};
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
export function entityMentionFullRebuildsStarted(scopeKey: string): number {
|
|
308
|
+
return rebuildsByScope.get(scopeKey) ?? 0;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
export function entityMentionIndexScopeKeys(): string[] {
|
|
312
|
+
return [...slots.keys()];
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
export function dropEntityMentionIndexCache(scopeKey?: string): void {
|
|
316
|
+
if (scopeKey === undefined) {
|
|
317
|
+
clearEpoch += 1;
|
|
318
|
+
for (const key of [...slots.keys()]) {
|
|
319
|
+
discardPersistedMentionIndex(key);
|
|
320
|
+
forgetScope(key);
|
|
321
|
+
}
|
|
322
|
+
return;
|
|
323
|
+
}
|
|
324
|
+
discardPersistedMentionIndex(scopeKey);
|
|
325
|
+
forgetScope(scopeKey, true);
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
export async function settleEntityMentionIndex(scopeKey?: string): Promise<void> {
|
|
329
|
+
const matches = (slot: Slot) => scopeKey === undefined || slot.scopeKey === scopeKey;
|
|
330
|
+
for (let round = 0; round < SETTLE_ROUNDS; round += 1) {
|
|
331
|
+
for (const slot of slots.values()) {
|
|
332
|
+
if (!matches(slot)) continue;
|
|
333
|
+
if (!slot.rebuild && slot.again) {
|
|
334
|
+
slot.again = false;
|
|
335
|
+
startRebuild(slot);
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
const pending = [...slots.values()].filter((slot) => matches(slot) && slot.rebuild).map((slot) => slot.rebuild!);
|
|
339
|
+
if (pending.length === 0) return;
|
|
340
|
+
await Promise.all(pending);
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
export async function resolveEntityMentionIndex<T>(options: {
|
|
345
|
+
scopeKey: string;
|
|
346
|
+
currentIdentity: () => EntityMentionIdentity;
|
|
347
|
+
buildFull: (abortSignal?: AbortSignal) => Promise<T>;
|
|
348
|
+
rebuildEntities: (previous: T, abortSignal?: AbortSignal) => Promise<T>;
|
|
349
|
+
abortSignal?: AbortSignal;
|
|
350
|
+
}): Promise<T> {
|
|
351
|
+
const live = options.currentIdentity();
|
|
352
|
+
const existing = slots.get(options.scopeKey);
|
|
353
|
+
if (!existing) {
|
|
354
|
+
evictSameFamily(options.scopeKey);
|
|
355
|
+
evictOldest(options.scopeKey);
|
|
356
|
+
noteRebuild(options.scopeKey);
|
|
357
|
+
const captured = captureDirGenerations(options.scopeKey);
|
|
358
|
+
const started = options.currentIdentity();
|
|
359
|
+
const index = await options.buildFull(options.abortSignal);
|
|
360
|
+
// A clear or family eviction during this first scan must not publish it.
|
|
361
|
+
if (!generationsCurrent(captured)) return index;
|
|
362
|
+
publishDirGenerations(captured);
|
|
363
|
+
const ended = options.currentIdentity();
|
|
364
|
+
evictSameFamily(options.scopeKey);
|
|
365
|
+
evictOldest(options.scopeKey);
|
|
366
|
+
const slot: Slot = {
|
|
367
|
+
scopeKey: options.scopeKey,
|
|
368
|
+
index,
|
|
369
|
+
identity: sameIdentity(started, ended) ? ended : started,
|
|
370
|
+
token: 0,
|
|
371
|
+
rebuild: null,
|
|
372
|
+
entityJob: null,
|
|
373
|
+
again: false,
|
|
374
|
+
followUps: 0,
|
|
375
|
+
currentIdentity: options.currentIdentity,
|
|
376
|
+
buildFull: options.buildFull,
|
|
377
|
+
};
|
|
378
|
+
slots.set(options.scopeKey, slot);
|
|
379
|
+
if (!sameIdentity(started, ended)) armRebuild(slot);
|
|
380
|
+
return index;
|
|
381
|
+
}
|
|
382
|
+
existing.currentIdentity = options.currentIdentity;
|
|
383
|
+
existing.buildFull = options.buildFull;
|
|
384
|
+
while (existing.identity.entityMutation !== options.currentIdentity().entityMutation) {
|
|
385
|
+
throwIfMentionAborted(options.abortSignal);
|
|
386
|
+
const pending = existing.entityJob;
|
|
387
|
+
if (pending) {
|
|
388
|
+
await raceAbort(pending, options.abortSignal, "entity mention rebuild aborted");
|
|
389
|
+
continue;
|
|
390
|
+
}
|
|
391
|
+
const liveNow = options.currentIdentity();
|
|
392
|
+
const epochBefore = existing.identity.mentionEpoch;
|
|
393
|
+
const mutationBefore = liveNow.entityMutation;
|
|
394
|
+
let release = (): void => {};
|
|
395
|
+
const gate = new Promise<void>((resolve) => {
|
|
396
|
+
release = resolve;
|
|
397
|
+
});
|
|
398
|
+
existing.entityJob = gate;
|
|
399
|
+
existing.token += 1;
|
|
400
|
+
existing.again = false;
|
|
401
|
+
const token = existing.token;
|
|
402
|
+
const captured = captureDirGenerations(existing.scopeKey);
|
|
403
|
+
// Shared scan. This caller's abort stops its wait and leaves the scan
|
|
404
|
+
// running. Publish when the scan settles, even if this caller has left.
|
|
405
|
+
// A family eviction bumps the token and the root generation; this scan
|
|
406
|
+
// must not install its result or start a follow-up that would republish.
|
|
407
|
+
const scan = options.rebuildEntities(existing.index as T, undefined);
|
|
408
|
+
void scan
|
|
409
|
+
.then(
|
|
410
|
+
(rebuilt) => {
|
|
411
|
+
if (existing.token !== token || !generationsCurrent(captured)) return;
|
|
412
|
+
const now = options.currentIdentity();
|
|
413
|
+
existing.index = rebuilt;
|
|
414
|
+
const epochStable = epochBefore === liveNow.mentionEpoch && epochBefore === now.mentionEpoch;
|
|
415
|
+
const mutationStable = mutationBefore === now.entityMutation;
|
|
416
|
+
existing.identity = {
|
|
417
|
+
entityMutation: mutationStable ? now.entityMutation : mutationBefore,
|
|
418
|
+
mentionEpoch: epochStable ? now.mentionEpoch : epochBefore,
|
|
419
|
+
};
|
|
420
|
+
if (existing.identity.mentionEpoch !== now.mentionEpoch || wantsSnippetReconcile(rebuilt)) {
|
|
421
|
+
armRebuild(existing);
|
|
422
|
+
}
|
|
423
|
+
},
|
|
424
|
+
() => {},
|
|
425
|
+
)
|
|
426
|
+
.finally(() => {
|
|
427
|
+
if (existing.entityJob === gate) existing.entityJob = null;
|
|
428
|
+
release();
|
|
429
|
+
});
|
|
430
|
+
const rebuilt = await raceAbort(scan, options.abortSignal, "entity mention rebuild aborted");
|
|
431
|
+
throwIfMentionAborted(options.abortSignal);
|
|
432
|
+
return rebuilt;
|
|
433
|
+
}
|
|
434
|
+
if (existing.identity.mentionEpoch !== live.mentionEpoch) {
|
|
435
|
+
armRebuild(existing);
|
|
436
|
+
return existing.index as T;
|
|
437
|
+
}
|
|
438
|
+
return existing.index as T;
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
entityMentionEpoch.registerReset(() => {
|
|
442
|
+
dropEntityMentionIndexCache();
|
|
443
|
+
});
|