@remnic/core 10.0.59 → 10.0.61
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 +53 -50
- package/dist/access-authorization-probe.js +54 -51
- package/dist/access-boundary.js +53 -50
- package/dist/access-cli.js +143 -140
- package/dist/access-cli.js.map +1 -1
- package/dist/access-extraction-force-flush.js +53 -50
- package/dist/access-http-briefing.js +54 -51
- package/dist/access-http-query.js +55 -52
- package/dist/access-http.js +76 -73
- package/dist/access-identity-continuity-surface.js +53 -50
- package/dist/access-lcm-surface.js +53 -50
- package/dist/access-mcp.js +64 -61
- package/dist/access-namespace-preflight.js +53 -50
- package/dist/access-observe-write-category.js +10 -10
- package/dist/access-observe-write-surface.js +53 -50
- package/dist/access-offline-manifest.js +7 -7
- package/dist/access-operations-batch.js +56 -53
- package/dist/access-operations.js +63 -60
- package/dist/access-recall-concurrency.js +53 -50
- package/dist/access-recall-response.js +53 -50
- package/dist/access-recall-surface.js +53 -50
- package/dist/access-schema.js +9 -9
- package/dist/access-service.js +53 -50
- package/dist/access-surface-catalog.js +55 -52
- package/dist/access-surface-catalog.js.map +1 -1
- package/dist/active-recall.js +24 -20
- 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 +16 -12
- package/dist/buffer-persistence.d.ts +14 -3
- package/dist/buffer-persistence.d.ts.map +1 -1
- package/dist/buffer-persistence.js +1 -1
- package/dist/buffer-turn-helpers.d.ts +7 -0
- package/dist/buffer-turn-helpers.d.ts.map +1 -1
- package/dist/buffer-turn-helpers.js +5 -3
- package/dist/buffer.d.ts +2 -0
- package/dist/buffer.d.ts.map +1 -1
- package/dist/buffer.js +7 -7
- package/dist/{capsule-crypto-7FJQINUR.js → capsule-crypto-GWVG7LGC.js} +2 -2
- package/dist/causal-behavior.js +2 -2
- package/dist/causal-chain.js +2 -2
- package/dist/causal-consolidation.js +19 -15
- 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-ZAZZOQAQ.js → chunk-23OEPU3K.js} +2 -2
- package/dist/{chunk-XDKEHZUC.js → chunk-264B57GV.js} +2 -2
- package/dist/{chunk-5U2BBF7E.js → chunk-46U6L5DM.js} +20 -10
- package/dist/chunk-46U6L5DM.js.map +1 -0
- package/dist/chunk-4LZ7SVKM.js +211 -0
- package/dist/chunk-4LZ7SVKM.js.map +1 -0
- package/dist/{chunk-NRGQ5HJO.js → chunk-4P4VLPGB.js} +1 -1
- package/dist/{chunk-GIRWNTMV.js → chunk-4PG5R6BJ.js} +2 -2
- 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-WASVDS6G.js → chunk-6HTEPYOI.js} +4 -4
- package/dist/{chunk-6AEKSOWV.js → chunk-6I6DFULH.js} +8 -8
- package/dist/{chunk-DJR37Q37.js → chunk-6T4CMKNP.js} +2 -2
- package/dist/{chunk-RHJ5VGB3.js → chunk-73A2DDDR.js} +2 -2
- package/dist/{chunk-4B73TQQV.js → chunk-7AKPLEJV.js} +2 -2
- package/dist/{chunk-UHH67NLU.js → chunk-7OKYO2UC.js} +2 -2
- package/dist/{chunk-KAL5SXPP.js → chunk-7ULSP7AU.js} +6 -6
- package/dist/{chunk-USNU3BEQ.js → chunk-A4FZLM6Q.js} +2 -2
- package/dist/{chunk-VV73XT3A.js → chunk-BJLCV2P3.js} +2 -2
- package/dist/{chunk-KQAFEZQX.js → chunk-BUKK5SWA.js} +2 -2
- package/dist/{chunk-DJFULNXK.js → chunk-C2AYUXEF.js} +2 -2
- package/dist/{chunk-QY24FSXT.js → chunk-D3BYJSHX.js} +51 -51
- package/dist/{chunk-JZRQY7ZQ.js → chunk-D4WR56FR.js} +2 -2
- package/dist/{chunk-3E3Z6UA3.js → chunk-DMYNM3WG.js} +124 -97
- package/dist/chunk-DMYNM3WG.js.map +1 -0
- package/dist/{chunk-FB4JQEFS.js → chunk-DP22K7IC.js} +2 -2
- package/dist/{chunk-EJFOGYF3.js → chunk-DTQJ7BRN.js} +3 -3
- package/dist/{chunk-MLUPSZFK.js → chunk-DZL5PBEJ.js} +2 -2
- package/dist/{chunk-JD5F5MYN.js → chunk-EGCC5OJE.js} +3 -3
- package/dist/{chunk-IQSRVR6Z.js → chunk-EJ4RS7I7.js} +2 -2
- package/dist/{chunk-WBN66HPI.js → chunk-ELHJK7LM.js} +6 -6
- package/dist/{chunk-EH46DHBU.js → chunk-ENAPZQA6.js} +2 -2
- package/dist/{chunk-LLG5VNK7.js → chunk-F2UAAI6B.js} +2 -2
- package/dist/{chunk-YZNTAOPZ.js → chunk-FKNNWH7P.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-OL7A7NKH.js → chunk-GNP4BV4Z.js} +2 -2
- package/dist/{chunk-GDYLZ6FR.js → chunk-HJ3UTGIW.js} +3 -3
- package/dist/{chunk-CCL2OXFO.js → chunk-HSHLDSPX.js} +3 -3
- package/dist/{chunk-R4BR62UO.js → chunk-HWMGQDZI.js} +2 -2
- package/dist/{chunk-BPIAXMGC.js → chunk-IIUEHIQ2.js} +2 -2
- package/dist/{chunk-RTJJPW3C.js → chunk-ILMB3X4X.js} +5 -5
- package/dist/{chunk-RFT4OPYI.js → chunk-IPN67KVR.js} +2 -2
- package/dist/chunk-JCL7RO2G.js +674 -0
- package/dist/chunk-JCL7RO2G.js.map +1 -0
- package/dist/{chunk-IEW6NDHX.js → chunk-JGSWWD33.js} +9 -9
- package/dist/{chunk-A3UFV4K3.js → chunk-JIYJIH5N.js} +2 -2
- package/dist/{chunk-4G2A7MH3.js → chunk-JKUVPVKR.js} +3 -3
- package/dist/{chunk-YJHOVA42.js → chunk-JL5FHZ7O.js} +30 -6
- package/dist/chunk-JL5FHZ7O.js.map +1 -0
- package/dist/{chunk-VCLNU6MY.js → chunk-L2HSSVVD.js} +2 -2
- package/dist/{chunk-U2A56LLF.js → chunk-L334PSLW.js} +8 -8
- package/dist/{chunk-HQUYENKK.js → chunk-LIUI46BX.js} +3 -3
- package/dist/{chunk-ERMT5WQM.js → chunk-MKE5DP3S.js} +2 -2
- package/dist/{chunk-4OFXLQJD.js → chunk-MPDLLLOG.js} +8 -3
- package/dist/chunk-MPDLLLOG.js.map +1 -0
- package/dist/{chunk-6BO27AWQ.js → chunk-MPJ7VHHA.js} +2 -2
- package/dist/{chunk-WXBVGKS6.js → chunk-N7RAABFM.js} +182 -421
- package/dist/chunk-N7RAABFM.js.map +1 -0
- package/dist/{chunk-KMJDH4KX.js → chunk-NSUGAIIB.js} +16 -16
- package/dist/{chunk-7NT3FKAF.js → chunk-NXHBQC2O.js} +2 -2
- package/dist/{chunk-JEX2JL3U.js → chunk-OH55F76C.js} +2 -2
- package/dist/{chunk-37VTMUAL.js → chunk-OUJ4DVR5.js} +2 -2
- package/dist/{chunk-2NLLXCJG.js → chunk-OWHERGF2.js} +2 -2
- package/dist/{chunk-2IVK5IDY.js → chunk-P5F7ISLV.js} +2 -2
- package/dist/{chunk-PM4PKC3Y.js → chunk-PNVQNAJM.js} +3 -3
- package/dist/{chunk-FXWX7VD3.js → chunk-PRYDNBCO.js} +12 -12
- package/dist/{chunk-YDDLSGYE.js → chunk-PUTH5REB.js} +2 -2
- package/dist/{chunk-GFAEKSFR.js → chunk-QBK3IM6W.js} +5 -5
- package/dist/{chunk-KRAMCBIW.js → chunk-QKXPOLQD.js} +4 -4
- package/dist/{chunk-YDT3OEYF.js → chunk-R4ZHF4K2.js} +2 -2
- package/dist/{chunk-ISNPYOXR.js → chunk-RBUR6USI.js} +5 -5
- package/dist/{chunk-WZYAXOGO.js → chunk-RZSHIJYY.js} +2 -2
- package/dist/{chunk-TYSD7UQO.js → chunk-SVTIWBSA.js} +2 -2
- package/dist/{chunk-AMKIIRLL.js → chunk-TBFAQHQZ.js} +2 -2
- package/dist/{chunk-ZE5N4JSY.js → chunk-TKCJI7WA.js} +2 -2
- package/dist/{chunk-RYB553SS.js → chunk-TLOAAOT3.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-KZMRMKC7.js → chunk-U7MIMS2Y.js} +11 -1
- package/dist/chunk-U7MIMS2Y.js.map +1 -0
- package/dist/{chunk-OMWQO7H6.js → chunk-UEDC2VGP.js} +2 -2
- package/dist/{chunk-X7Y7WX73.js → chunk-UVYI6VIX.js} +1 -1
- package/dist/{chunk-JY2BGCYN.js → chunk-VCYH2XUY.js} +2 -2
- package/dist/{chunk-NGH74LDV.js → chunk-WD2QH3JM.js} +9 -9
- package/dist/{chunk-G6A3SGCE.js → chunk-WVEEVYMH.js} +8 -25
- package/dist/chunk-WVEEVYMH.js.map +1 -0
- package/dist/{chunk-UGTYADB4.js → chunk-WWSJQ7MP.js} +3 -3
- package/dist/{chunk-UQBOPCDK.js → chunk-X4PAATLA.js} +2 -2
- package/dist/{chunk-KBOQQEVC.js → chunk-XAFSZ4DP.js} +2 -2
- package/dist/{chunk-2S6JADBB.js → chunk-XICJL56Z.js} +2 -2
- package/dist/{chunk-Y5KESB7T.js → chunk-XJDSZFBZ.js} +45 -45
- 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/cli.js +100 -97
- package/dist/coding/export-okf-codegraph.js +15 -11
- package/dist/coding/export-okf-codegraph.js.map +1 -1
- package/dist/coding/pre-action-gate.js +2 -2
- package/dist/compounding/engine.js +17 -13
- package/dist/config.js +24 -20
- package/dist/connectors/codex-materialize-runner.js +16 -12
- package/dist/connectors/index.js +16 -12
- package/dist/convergence-refresh.js +18 -14
- 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 +18 -14
- package/dist/dashboard-runtime.js +2 -2
- package/dist/deep-recall-http-glue.js +10 -10
- 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 +18 -14
- package/dist/event-order-recall.js +3 -3
- package/dist/external-wiki-access.js +54 -51
- package/dist/extraction.js +21 -17
- 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 +16 -12
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +174 -169
- package/dist/lcm/engine.js +2 -2
- package/dist/lcm/index.js +5 -5
- package/dist/location/index.js +18 -14
- package/dist/maintenance/memory-governance.js +16 -12
- package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +16 -12
- package/dist/maintenance/rebuild-memory-projection-runner.js +18 -14
- package/dist/maintenance/rebuild-memory-projection.js +17 -13
- package/dist/memory-browse-operations.js +54 -51
- package/dist/memory-browse.js +5 -5
- package/dist/namespaces/migrate.js +17 -13
- package/dist/namespaces/search.js +7 -7
- package/dist/namespaces/storage.js +16 -12
- 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 +19 -15
- package/dist/operator-toolkit.js +35 -31
- package/dist/orchestration/buffer-drain-gate.d.ts +21 -0
- package/dist/orchestration/buffer-drain-gate.d.ts.map +1 -0
- package/dist/orchestration/buffer-drain-runner.d.ts +79 -0
- package/dist/orchestration/buffer-drain-runner.d.ts.map +1 -0
- package/dist/orchestration/buffer-drain-runner.js +780 -0
- package/dist/orchestration/buffer-drain-runner.js.map +1 -0
- package/dist/orchestration/buffer-swap.d.ts +12 -0
- package/dist/orchestration/buffer-swap.d.ts.map +1 -0
- package/dist/orchestration/maintenance.js +26 -22
- package/dist/orchestration/turn-ingestion.d.ts.map +1 -1
- package/dist/orchestrator.d.ts +4 -1
- package/dist/orchestrator.d.ts.map +1 -1
- package/dist/orchestrator.js +143 -140
- package/dist/qmd.js +4 -4
- package/dist/recall-navigation-operations.js +54 -51
- package/dist/recall-navigation.js +53 -50
- package/dist/recall-state-view-admission.js +5 -5
- package/dist/recall-why-service.js +5 -5
- package/dist/resume-bundles.js +25 -21
- package/dist/search/factory.js +6 -6
- package/dist/search/index.js +12 -12
- package/dist/semantic-consolidation.js +17 -13
- package/dist/semantic-rule-promotion.js +16 -12
- package/dist/semantic-rule-verifier.js +16 -12
- package/dist/source-agent-qualifier.js +19 -15
- package/dist/standup/build.js +16 -12
- package/dist/storage/buffer-drain-claims.d.ts +159 -0
- package/dist/storage/buffer-drain-claims.d.ts.map +1 -0
- package/dist/storage/buffer-drain-claims.js +18 -0
- package/dist/storage/buffer-drain-claims.js.map +1 -0
- package/dist/storage/buffer-session-files.d.ts +14 -2
- package/dist/storage/buffer-session-files.d.ts.map +1 -1
- package/dist/storage/buffer-session-files.js +22 -0
- package/dist/storage/buffer-session-files.js.map +1 -0
- package/dist/storage/entity-canonical-id-migration.d.ts.map +1 -1
- package/dist/storage/memory-read-store.d.ts +5 -9
- package/dist/storage/memory-read-store.d.ts.map +1 -1
- package/dist/storage.d.ts +6 -1
- package/dist/storage.d.ts.map +1 -1
- package/dist/storage.js +17 -13
- package/dist/support-passport/index.js +56 -53
- package/dist/transfer/backup.js +2 -2
- package/dist/transfer/capsule-export.js +2 -2
- package/dist/transfer/capsule-import.js +2 -2
- package/dist/transfer/export-okf.js +16 -12
- package/dist/verified-recall.js +16 -12
- package/dist/who-knows-cli.js +17 -13
- package/dist/who-knows.js +16 -12
- package/package.json +32 -2
- package/scripts/build-with-heap.mjs +8 -0
- package/src/buffer-drain-handover.test.ts +307 -0
- package/src/buffer-persistence.ts +27 -5
- package/src/buffer-turn-helpers.ts +16 -0
- package/src/buffer.ts +32 -2
- 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/index.ts +1 -1
- package/src/orchestration/buffer-drain-gate.test.ts +40 -0
- package/src/orchestration/buffer-drain-gate.ts +38 -0
- package/src/orchestration/buffer-drain-runner.ts +312 -0
- package/src/orchestration/buffer-swap.ts +27 -0
- package/src/orchestration/turn-ingestion.ts +2 -0
- package/src/orchestrator.ts +9 -6
- package/src/storage/buffer-drain-claims.test.ts +448 -0
- package/src/storage/buffer-drain-claims.ts +593 -0
- package/src/storage/buffer-session-files.ts +50 -3
- package/src/storage/entity-canonical-id-migration.ts +4 -26
- package/src/storage/memory-read-store.ts +17 -99
- package/src/storage.ts +8 -11
- package/src/testing/subjects/extraction-lifecycle.test.ts +34 -0
- package/dist/chunk-3E3Z6UA3.js.map +0 -1
- package/dist/chunk-4OFXLQJD.js.map +0 -1
- package/dist/chunk-5U2BBF7E.js.map +0 -1
- package/dist/chunk-G6A3SGCE.js.map +0 -1
- package/dist/chunk-KZMRMKC7.js.map +0 -1
- package/dist/chunk-WXBVGKS6.js.map +0 -1
- package/dist/chunk-YJHOVA42.js.map +0 -1
- /package/dist/{capsule-crypto-7FJQINUR.js.map → capsule-crypto-GWVG7LGC.js.map} +0 -0
- /package/dist/{chunk-ZAZZOQAQ.js.map → chunk-23OEPU3K.js.map} +0 -0
- /package/dist/{chunk-XDKEHZUC.js.map → chunk-264B57GV.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-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-WASVDS6G.js.map → chunk-6HTEPYOI.js.map} +0 -0
- /package/dist/{chunk-6AEKSOWV.js.map → chunk-6I6DFULH.js.map} +0 -0
- /package/dist/{chunk-DJR37Q37.js.map → chunk-6T4CMKNP.js.map} +0 -0
- /package/dist/{chunk-RHJ5VGB3.js.map → chunk-73A2DDDR.js.map} +0 -0
- /package/dist/{chunk-4B73TQQV.js.map → chunk-7AKPLEJV.js.map} +0 -0
- /package/dist/{chunk-UHH67NLU.js.map → chunk-7OKYO2UC.js.map} +0 -0
- /package/dist/{chunk-KAL5SXPP.js.map → chunk-7ULSP7AU.js.map} +0 -0
- /package/dist/{chunk-USNU3BEQ.js.map → chunk-A4FZLM6Q.js.map} +0 -0
- /package/dist/{chunk-VV73XT3A.js.map → chunk-BJLCV2P3.js.map} +0 -0
- /package/dist/{chunk-KQAFEZQX.js.map → chunk-BUKK5SWA.js.map} +0 -0
- /package/dist/{chunk-DJFULNXK.js.map → chunk-C2AYUXEF.js.map} +0 -0
- /package/dist/{chunk-QY24FSXT.js.map → chunk-D3BYJSHX.js.map} +0 -0
- /package/dist/{chunk-JZRQY7ZQ.js.map → chunk-D4WR56FR.js.map} +0 -0
- /package/dist/{chunk-FB4JQEFS.js.map → chunk-DP22K7IC.js.map} +0 -0
- /package/dist/{chunk-EJFOGYF3.js.map → chunk-DTQJ7BRN.js.map} +0 -0
- /package/dist/{chunk-MLUPSZFK.js.map → chunk-DZL5PBEJ.js.map} +0 -0
- /package/dist/{chunk-JD5F5MYN.js.map → chunk-EGCC5OJE.js.map} +0 -0
- /package/dist/{chunk-IQSRVR6Z.js.map → chunk-EJ4RS7I7.js.map} +0 -0
- /package/dist/{chunk-WBN66HPI.js.map → chunk-ELHJK7LM.js.map} +0 -0
- /package/dist/{chunk-EH46DHBU.js.map → chunk-ENAPZQA6.js.map} +0 -0
- /package/dist/{chunk-LLG5VNK7.js.map → chunk-F2UAAI6B.js.map} +0 -0
- /package/dist/{chunk-YZNTAOPZ.js.map → chunk-FKNNWH7P.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-OL7A7NKH.js.map → chunk-GNP4BV4Z.js.map} +0 -0
- /package/dist/{chunk-GDYLZ6FR.js.map → chunk-HJ3UTGIW.js.map} +0 -0
- /package/dist/{chunk-CCL2OXFO.js.map → chunk-HSHLDSPX.js.map} +0 -0
- /package/dist/{chunk-R4BR62UO.js.map → chunk-HWMGQDZI.js.map} +0 -0
- /package/dist/{chunk-BPIAXMGC.js.map → chunk-IIUEHIQ2.js.map} +0 -0
- /package/dist/{chunk-RTJJPW3C.js.map → chunk-ILMB3X4X.js.map} +0 -0
- /package/dist/{chunk-RFT4OPYI.js.map → chunk-IPN67KVR.js.map} +0 -0
- /package/dist/{chunk-IEW6NDHX.js.map → chunk-JGSWWD33.js.map} +0 -0
- /package/dist/{chunk-A3UFV4K3.js.map → chunk-JIYJIH5N.js.map} +0 -0
- /package/dist/{chunk-4G2A7MH3.js.map → chunk-JKUVPVKR.js.map} +0 -0
- /package/dist/{chunk-VCLNU6MY.js.map → chunk-L2HSSVVD.js.map} +0 -0
- /package/dist/{chunk-U2A56LLF.js.map → chunk-L334PSLW.js.map} +0 -0
- /package/dist/{chunk-HQUYENKK.js.map → chunk-LIUI46BX.js.map} +0 -0
- /package/dist/{chunk-ERMT5WQM.js.map → chunk-MKE5DP3S.js.map} +0 -0
- /package/dist/{chunk-6BO27AWQ.js.map → chunk-MPJ7VHHA.js.map} +0 -0
- /package/dist/{chunk-KMJDH4KX.js.map → chunk-NSUGAIIB.js.map} +0 -0
- /package/dist/{chunk-7NT3FKAF.js.map → chunk-NXHBQC2O.js.map} +0 -0
- /package/dist/{chunk-JEX2JL3U.js.map → chunk-OH55F76C.js.map} +0 -0
- /package/dist/{chunk-37VTMUAL.js.map → chunk-OUJ4DVR5.js.map} +0 -0
- /package/dist/{chunk-2NLLXCJG.js.map → chunk-OWHERGF2.js.map} +0 -0
- /package/dist/{chunk-2IVK5IDY.js.map → chunk-P5F7ISLV.js.map} +0 -0
- /package/dist/{chunk-PM4PKC3Y.js.map → chunk-PNVQNAJM.js.map} +0 -0
- /package/dist/{chunk-FXWX7VD3.js.map → chunk-PRYDNBCO.js.map} +0 -0
- /package/dist/{chunk-YDDLSGYE.js.map → chunk-PUTH5REB.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-YDT3OEYF.js.map → chunk-R4ZHF4K2.js.map} +0 -0
- /package/dist/{chunk-ISNPYOXR.js.map → chunk-RBUR6USI.js.map} +0 -0
- /package/dist/{chunk-WZYAXOGO.js.map → chunk-RZSHIJYY.js.map} +0 -0
- /package/dist/{chunk-TYSD7UQO.js.map → chunk-SVTIWBSA.js.map} +0 -0
- /package/dist/{chunk-AMKIIRLL.js.map → chunk-TBFAQHQZ.js.map} +0 -0
- /package/dist/{chunk-ZE5N4JSY.js.map → chunk-TKCJI7WA.js.map} +0 -0
- /package/dist/{chunk-RYB553SS.js.map → chunk-TLOAAOT3.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-X7Y7WX73.js.map → chunk-UVYI6VIX.js.map} +0 -0
- /package/dist/{chunk-JY2BGCYN.js.map → chunk-VCYH2XUY.js.map} +0 -0
- /package/dist/{chunk-NGH74LDV.js.map → chunk-WD2QH3JM.js.map} +0 -0
- /package/dist/{chunk-UGTYADB4.js.map → chunk-WWSJQ7MP.js.map} +0 -0
- /package/dist/{chunk-UQBOPCDK.js.map → chunk-X4PAATLA.js.map} +0 -0
- /package/dist/{chunk-KBOQQEVC.js.map → chunk-XAFSZ4DP.js.map} +0 -0
- /package/dist/{chunk-2S6JADBB.js.map → chunk-XICJL56Z.js.map} +0 -0
- /package/dist/{chunk-Y5KESB7T.js.map → chunk-XJDSZFBZ.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/{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
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/buffer-persistence.ts"],"sourcesContent":["import type { BufferEntryState, BufferState, BufferSurpriseEvent } from \"./types.js\";\n\n/**\n * The buffer persistence surface SmartBuffer depends on.\n *\n * `saveBufferSession` / `deleteBufferSession` are the per-session incremental\n * writes (one file per session); when a storage backend does not provide\n * them, the buffer falls back to the whole-state `saveBuffer`. Both are\n * optional in the type so a storage double only has to implement what it\n * actually supports.\n */\nexport interface BufferStorage {\n loadBuffer(): Promise<BufferState>;\n saveBuffer(state: BufferState): Promise<void>;\n saveBufferSession?(key: string, entry: BufferEntryState): Promise<void>;\n deleteBufferSession?(key: string): Promise<void>;\n /** Optional surprise-telemetry sink; older storage and test doubles omit it. */\n appendBufferSurpriseEvents?(events: BufferSurpriseEvent[]): Promise<number>;\n}\n\n/**\n * Tracks which sessions' persisted files are stale and writes only those.\n *\n * Marks are cleared only after a durable write of that session. All calls\n * happen under SmartBuffer's serialized mutation chain: one writer.\n */\nexport class BufferSessionPersistence {\n private readonly dirty = new Set<string>();\n private readonly deleted = new Set<string>();\n\n markDirty(key: string): void {\n this.dirty.add(key);\n this.deleted.delete(key);\n }\n\n markDeleted(key: string): void {\n this.deleted.add(key);\n this.dirty.delete(key);\n }\n\n /** Eviction removed `key` from memory: its session file must go, any pending write is moot. */\n markEvicted(keys: readonly string[]): void {\n for (const key of keys) this.markDeleted(key);\n }\n\n hasPending(): boolean {\n return this.dirty.size > 0 || this.deleted.size > 0;\n }\n\n /**\n * Persist buffer state. On session-capable storage this writes ONLY the\n * sessions whose state changed since the last successful persist and\n * deletes files for evicted/reset sessions, so the observe path never\n * re-serializes other sessions' turns. On legacy storage it falls back to\n * the whole-state saveBuffer. Files are deleted before dirty writes: a\n * crash mid-persist can then only resurrect already-evicted data (the same\n * exposure as a whole-file rewrite), never drop sessions the state says\n * exist.\n */\n async persist(\n storage: BufferStorage,\n state: BufferState,\n peekEntry: (key: string) => BufferEntryState | undefined,\n ): Promise<void> {\n const saveSession = storage.saveBufferSession?.bind(storage);\n const deleteSession = storage.deleteBufferSession?.bind(storage);\n if (!saveSession || !deleteSession) {\n await storage.saveBuffer(state);\n this.dirty.clear();\n this.deleted.clear();\n return;\n }\n for (const key of [...this.deleted]) {\n await deleteSession(key);\n this.deleted.delete(key);\n }\n for (const key of [...this.dirty]) {\n const entry = peekEntry(key);\n if (!entry) {\n // A dirty key without state must not be written from a fabricated\n // empty entry: treat it as a deletion instead.\n this.markDeleted(key);\n continue;\n }\n await saveSession(key, entry);\n this.dirty.delete(key);\n }\n }\n}\n"],"mappings":";AA0BO,IAAM,2BAAN,MAA+B;AAAA,EACnB,QAAQ,oBAAI,IAAY;AAAA,EACxB,UAAU,oBAAI,IAAY;AAAA,EAE3C,UAAU,KAAmB;AAC3B,SAAK,MAAM,IAAI,GAAG;AAClB,SAAK,QAAQ,OAAO,GAAG;AAAA,EACzB;AAAA,EAEA,YAAY,KAAmB;AAC7B,SAAK,QAAQ,IAAI,GAAG;AACpB,SAAK,MAAM,OAAO,GAAG;AAAA,EACvB;AAAA;AAAA,EAGA,YAAY,MAA+B;AACzC,eAAW,OAAO,KAAM,MAAK,YAAY,GAAG;AAAA,EAC9C;AAAA,EAEA,aAAsB;AACpB,WAAO,KAAK,MAAM,OAAO,KAAK,KAAK,QAAQ,OAAO;AAAA,EACpD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAYA,MAAM,QACJ,SACA,OACA,WACe;AACf,UAAM,cAAc,QAAQ,mBAAmB,KAAK,OAAO;AAC3D,UAAM,gBAAgB,QAAQ,qBAAqB,KAAK,OAAO;AAC/D,QAAI,CAAC,eAAe,CAAC,eAAe;AAClC,YAAM,QAAQ,WAAW,KAAK;AAC9B,WAAK,MAAM,MAAM;AACjB,WAAK,QAAQ,MAAM;AACnB;AAAA,IACF;AACA,eAAW,OAAO,CAAC,GAAG,KAAK,OAAO,GAAG;AACnC,YAAM,cAAc,GAAG;AACvB,WAAK,QAAQ,OAAO,GAAG;AAAA,IACzB;AACA,eAAW,OAAO,CAAC,GAAG,KAAK,KAAK,GAAG;AACjC,YAAM,QAAQ,UAAU,GAAG;AAC3B,UAAI,CAAC,OAAO;AAGV,aAAK,YAAY,GAAG;AACpB;AAAA,MACF;AACA,YAAM,YAAY,KAAK,KAAK;AAC5B,WAAK,MAAM,OAAO,GAAG;AAAA,IACvB;AAAA,EACF;AACF;","names":[]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/corpus-watermark.ts"],"sourcesContent":["/**\n * Corpus watermark (issue #2149) — a cheap, comparable fingerprint of a\n * daemon's active-memory corpus.\n *\n * Deployment shape this addresses: two daemons behind an active/backup VIP,\n * each owning its own memory directory. Nothing compared the two corpora, so a\n * pair could diverge for months (e.g. one holding ~190k files, its backup\n * ~340k with different histories) with no signal until a manual audit. This\n * module ships the WATERMARK primitive and the read surfaces that expose it;\n * peer polling and divergence alerting are a follow-up.\n *\n * The digest is a CENSUS fingerprint (per day-partition file counts), NOT a\n * content hash — two daemons that agree on how many memory FILES live in\n * each `<tier>:<category>/<day>` partition produce the same digest, so a\n * differing digest is a cheap divergence signal without reading or hashing file\n * bodies. Buckets are tier-aware (`hot:` / `cold:`): demoted memories stay\n * reachable via cold recall, so the census counts BOTH tiers and a hot/cold\n * split shows in the digest rather than being silently folded together (issue\n * #2156 finding D). `newestWriteAt` stays scoped to the newest HOT partition\n * only, so the freshness probe stays O(one day's active files) even on a 100k+\n * corpus.\n *\n * Robustness (issue #2156 review rounds):\n * - `/health` and `remnic doctor` resolve their namespace set through the ONE\n * {@link resolveCorpusNamespaceRoots} helper — config-driven enumeration\n * unioned with the PERSISTED namespace catalog read from config — so both\n * surfaces enumerate the same tenants and cannot drift.\n * - The `/health` builder serves through a stale-while-revalidate\n * {@link CorpusWatermarkCache}, so a probe NEVER awaits the recursive corpus\n * scan (it returns the cached/stale value and refreshes in the background).\n * - Each namespace's census brackets its hot+cold walk with a corpus-mutation\n * sentinel and retries when a tier migration races it, so a transient\n * double-count/miss is never cached as a false divergence.\n * - An unreadable corpus root (EACCES) is OMITTED, not published as a false\n * empty; a missing root (ENOENT — a not-yet-created namespace) is a genuine\n * empty corpus.\n */\n\nimport { createHash } from \"node:crypto\";\nimport { stat } from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { capabilityAllowsNamespace, type TokenCapabilities } from \"./access-token-capabilities.js\";\nimport { listNamespaces } from \"./namespaces/migrate.js\";\nimport type { PluginConfig } from \"./types.js\";\n\nexport interface CorpusWatermark {\n namespace: string;\n /**\n * Count of memory files under the census scan roots (hot + cold). This is a\n * FILE census, not a status-filtered active count: reading each file's\n * frontmatter status would defeat the cheap-probe design, so an in-place\n * status change (e.g. archived-in-place) is not reflected here.\n */\n memoryFileCount: number;\n /** Newest `YYYY-MM-DD` day-partition seen in the HOT tier, or null when none is dated. */\n newestPartition: string | null;\n /** ISO max mtime within the newest HOT partition, or null when it has no files. */\n newestWriteAt: string | null;\n /** sha256 hex over the deterministic per-partition census. */\n digest: string;\n computedAt: string;\n}\n\n/** Explicit bucket for paths that do not match the `<category>/<day>` shape. */\nexport const UNPARTITIONED_BUCKET = \"unpartitioned\";\n\n/** Storage tier a memory file lives in. */\nexport type CorpusTier = \"hot\" | \"cold\";\n\nconst DAY_PARTITION_RE = /^\\d{4}-\\d{2}-\\d{2}$/;\n/** Cold-tier root segment (relative to a namespace base dir): `<baseDir>/cold/...`. */\nconst COLD_TIER_DIR = \"cold\";\n\n/**\n * True when `token` is not just `YYYY-MM-DD`-shaped but a REAL calendar day.\n * The regex alone accepts impossible dates (e.g. `2026-99-99`) that would then\n * sort lexically above every valid partition and corrupt `newestPartition`\n * (issue #2156 round-6); round-tripping through a UTC Date rejects month/day\n * overflow. A malformed dir falls back to the unpartitioned bucket.\n */\nfunction isRealCalendarDay(token: string): boolean {\n if (!DAY_PARTITION_RE.test(token)) return false;\n const [year, month, day] = token.split(\"-\").map(Number);\n const date = new Date(Date.UTC(year, month - 1, day));\n return date.getUTCFullYear() === year && date.getUTCMonth() === month - 1 && date.getUTCDate() === day;\n}\n\ninterface ParsedPath {\n readonly bucket: string;\n readonly date: string | null;\n readonly tier: CorpusTier;\n}\n\nfunction parsePartition(baseDir: string, filePath: string): ParsedPath {\n const rel = path.relative(baseDir, filePath).split(path.sep).join(\"/\");\n const parts = rel.split(\"/\");\n // Cold-tier files live under `<baseDir>/cold/<category>/<day>/...`; hot files\n // sit directly under `<baseDir>/<category>/<day>/...`. Strip the leading\n // `cold/` segment so the category/day parse is tier-agnostic, then prefix the\n // bucket with the tier so a hot and cold partition of the same day never\n // collide (they must diverge the digest, not fold together).\n const tier: CorpusTier = parts[0] === COLD_TIER_DIR ? \"cold\" : \"hot\";\n const categoryParts = tier === \"cold\" ? parts.slice(1) : parts;\n if (categoryParts.length >= 2 && isRealCalendarDay(categoryParts[1])) {\n return { bucket: `${tier}:${categoryParts[0]}/${categoryParts[1]}`, date: categoryParts[1], tier };\n }\n return { bucket: `${tier}:${UNPARTITIONED_BUCKET}`, date: null, tier };\n}\n\n/**\n * Build the per-partition census: bucket key -> file count. Bucket keys are\n * tier-aware (`hot:`/`cold:`). Paths not matching a day-partition land in the\n * explicit tier {@link UNPARTITIONED_BUCKET}; they are never silently dropped.\n * Pure — no filesystem access.\n */\nexport function buildPartitionCensus(paths: readonly string[], baseDir: string): Map<string, number> {\n const census = new Map<string, number>();\n for (const filePath of paths) {\n const { bucket } = parsePartition(baseDir, filePath);\n census.set(bucket, (census.get(bucket) ?? 0) + 1);\n }\n return census;\n}\n\n/**\n * Deterministic census -> digest. Bucket keys are SORTED before serialization\n * (AGENTS.md pattern 26: insertion order must not affect a hash), then emitted\n * as `\"<bucket>:<count>\"` lines joined by `\\n` and hashed with sha256. Pure —\n * separately exported so it can be unit-tested without a filesystem.\n */\nexport function digestPartitionCensus(census: ReadonlyMap<string, number> | Record<string, number>): string {\n const entries = census instanceof Map ? [...census.entries()] : Object.entries(census);\n const serialized = entries\n .sort((a, b) => (a[0] < b[0] ? -1 : a[0] > b[0] ? 1 : 0))\n .map(([bucket, count]) => `${bucket}:${count}`)\n .join(\"\\n\");\n return createHash(\"sha256\").update(serialized).digest(\"hex\");\n}\n\n/**\n * Compute a watermark for a single namespace from an already-collected path\n * list (both tiers, under `baseDir`) plus its memory base directory. Never\n * throws on an empty list — an empty corpus yields the digest of the empty\n * census and null timestamps. Only the newest HOT partition's files are\n * `stat`-ed, so freshness stays cheap even on a 100k+ corpus.\n */\nexport async function computeCorpusWatermark(input: {\n namespace: string;\n paths: readonly string[];\n baseDir: string;\n now?: Date;\n}): Promise<CorpusWatermark> {\n const { namespace, paths, baseDir } = input;\n\n const census = new Map<string, number>();\n let newestPartition: string | null = null;\n const parsedByPath: ParsedPath[] = [];\n for (const filePath of paths) {\n const parsed = parsePartition(baseDir, filePath);\n parsedByPath.push(parsed);\n census.set(parsed.bucket, (census.get(parsed.bucket) ?? 0) + 1);\n // Freshness is the newest ACTIVE (hot) write. Cold memories are demoted and\n // keep their original (older) partition date, so they never advance the\n // freshness probe and their files are never stat-ed below.\n if (parsed.tier === \"hot\" && parsed.date !== null && (newestPartition === null || parsed.date > newestPartition)) {\n newestPartition = parsed.date;\n }\n }\n\n const digest = digestPartitionCensus(census);\n\n let newestWriteAt: string | null = null;\n if (newestPartition !== null) {\n let maxMtimeMs = Number.NEGATIVE_INFINITY;\n for (let i = 0; i < paths.length; i += 1) {\n if (parsedByPath[i].tier !== \"hot\" || parsedByPath[i].date !== newestPartition) continue;\n try {\n const st = await stat(paths[i]);\n if (st.mtimeMs > maxMtimeMs) maxMtimeMs = st.mtimeMs;\n } catch (err) {\n // A file vanishing between scan and stat (ENOENT) is an expected race —\n // skip it. A backend read failure (EACCES/EIO/…) means the corpus is\n // unreadable: propagate so the namespace is omitted, not published with\n // a stale/null freshness (issue #2156 round-6).\n if ((err as NodeJS.ErrnoException).code !== \"ENOENT\") throw err;\n }\n }\n if (maxMtimeMs > Number.NEGATIVE_INFINITY) {\n newestWriteAt = new Date(maxMtimeMs).toISOString();\n }\n }\n\n // Stamp AFTER the (possibly slow) stat loop so an age-based HA consumer never\n // sees a timestamp earlier than when the scan actually finished (finding\n // round-4). An explicit `now` (tests / determinism) still wins.\n const computedAt = (input.now ?? new Date()).toISOString();\n\n return {\n namespace,\n memoryFileCount: paths.length,\n newestPartition,\n newestWriteAt,\n digest,\n computedAt,\n };\n}\n\n/** Minimal storage surface a watermark needs — satisfied by StorageManager. */\nexport interface CorpusStorage {\n readonly dir: string;\n /** Hot-tier active memory paths (the recall corpus). */\n collectActiveMemoryPaths(options?: { propagateReadErrors?: boolean }): Promise<string[]>;\n /** Cold-tier (demoted-but-reachable) memory paths (issue #2156 finding D). */\n collectColdMemoryPaths(options?: { propagateReadErrors?: boolean }): Promise<string[]>;\n /**\n * Optional combined hot+cold corpus-mutation sentinel. When present, the\n * census brackets its scan with it and retries on change so a tier migration\n * racing the walkers is not cached as a false divergence. Absent (test fakes)\n * ⇒ single-pass.\n */\n getCorpusScanVersion?(): string | Promise<string>;\n}\n\n/** Max census re-scans when a corpus mutation keeps racing the walkers. */\nconst CENSUS_RACE_MAX_ATTEMPTS = 3;\n\n/**\n * Compute one namespace's watermark from its storage, spanning BOTH tiers so a\n * cold-tier divergence is caught. Reads are STRICT: the collectors propagate a\n * backend read failure (EACCES on the root OR any nested category dir, ENOTDIR,\n * …) so an unreadable corpus is OMITTED by the caller rather than published as a\n * false empty census; a missing root (ENOENT — a not-yet-created namespace)\n * stays a genuine empty corpus. The hot+cold walk is bracketed by the\n * corpus-mutation sentinel (when the storage exposes one) and retried on change;\n * if it never stabilizes (sustained tier churn) this THROWS rather than caching\n * a transient double-count/miss as a false divergence.\n */\nasync function computeNamespaceWatermark(\n namespace: string,\n storage: CorpusStorage,\n now?: Date,\n): Promise<CorpusWatermark> {\n const readVersion = storage.getCorpusScanVersion?.bind(storage);\n const maxAttempts = readVersion ? CENSUS_RACE_MAX_ATTEMPTS : 1;\n for (let attempt = 0; attempt < maxAttempts; attempt += 1) {\n const before = readVersion ? await readVersion() : null;\n const [hotPaths, coldPaths] = await Promise.all([\n storage.collectActiveMemoryPaths({ propagateReadErrors: true }),\n storage.collectColdMemoryPaths({ propagateReadErrors: true }),\n ]);\n const result = await computeCorpusWatermark({ namespace, paths: [...hotPaths, ...coldPaths], baseDir: storage.dir, now });\n const after = readVersion ? await readVersion() : null;\n if (before === after) return result;\n // A tier write (e.g. hot→cold migration) raced the two walkers, so the\n // count/digest may double-count or miss the in-flight memory. Retry for a\n // consistent snapshot.\n }\n throw new Error(`corpus census for namespace \"${namespace}\" did not stabilize after ${maxAttempts} attempts`);\n}\n\n/**\n * Compute watermarks for a set of namespaces. A namespace whose storage is\n * unavailable (or unreadable) is skipped rather than failing the whole read, so\n * a single bad namespace never blanks the health/doctor payload.\n */\nexport async function computeCorpusWatermarks(\n namespaces: readonly string[],\n storageFor: (namespace: string) => CorpusStorage | Promise<CorpusStorage>,\n now?: Date,\n): Promise<CorpusWatermark[]> {\n const watermarks: CorpusWatermark[] = [];\n for (const namespace of namespaces) {\n try {\n watermarks.push(await computeNamespaceWatermark(namespace, await storageFor(namespace), now));\n } catch {\n // Storage unavailable/unreadable for this namespace — degrade gracefully.\n }\n }\n return watermarks;\n}\n\n/** A namespace and the memory root its corpus lives under. */\nexport interface CorpusNamespaceRoot {\n /** Representative namespace label for this root (default wins on collapse). */\n readonly namespace: string;\n readonly rootDir: string;\n /**\n * Every configured namespace name that resolves to `rootDir` (aliases that\n * collapsed onto one physical root, e.g. `global` + `shared` when namespaces\n * are disabled). `/health` filters visibility against ALL of these so a token\n * scoped to a non-representative alias still sees the shared corpus (issue\n * #2156 round-10). Always includes `namespace`.\n */\n readonly namespaces: readonly string[];\n}\n\n/**\n * Resolve the namespace set the corpus census covers, shared by the `/health`\n * builder AND the `remnic doctor` corpus check (issue #2156 finding C) so the\n * two surfaces enumerate the SAME tenants — and, critically, resolve each\n * namespace's ROOT the SAME way — and cannot drift. Enumeration is purely\n * config-driven (`listNamespaces({ config })`): it discovers the default,\n * shared, policy, and on-disk `namespaces/<ns>` tenants and resolves every root\n * through the namespace storage router, exactly as the live daemon's\n * `getStorage(namespace)` does. It still works when the namespace catalog is\n * opted out. The catalog is deliberately NOT unioned in: a catalog-registered\n * tenant with no directory has an empty corpus (nothing to fingerprint), and\n * trusting `catalog.storageDir` here could make the doctor scan a different root\n * than `/health` for the same namespace (issue #2156 round-8). Deduped by\n * resolved root so a namespaces-disabled / flat-root deployment reports its\n * single shared corpus once, under the default-namespace label.\n */\nexport async function resolveCorpusNamespaceRoots(options: {\n config: PluginConfig;\n propagateDiscoveryErrors?: boolean;\n}): Promise<CorpusNamespaceRoot[]> {\n const { config } = options;\n const configDriven = await listNamespaces({\n config,\n propagateDiscoveryErrors: options.propagateDiscoveryErrors,\n });\n // Default namespace first so it wins the representative label when several\n // configured names collapse onto one root (namespaces disabled → memoryDir).\n const ordered = [\n ...configDriven.filter((entry) => entry.namespace === config.defaultNamespace),\n ...configDriven.filter((entry) => entry.namespace !== config.defaultNamespace),\n ];\n const seenNamespaces = new Set<string>();\n const byRoot = new Map<string, { namespace: string; rootDir: string; namespaces: string[] }>();\n for (const entry of ordered) {\n if (seenNamespaces.has(entry.namespace)) continue;\n seenNamespaces.add(entry.namespace);\n const existing = byRoot.get(entry.rootDir);\n if (existing) {\n existing.namespaces.push(entry.namespace); // an alias collapsing onto a seen root\n } else {\n byRoot.set(entry.rootDir, { namespace: entry.namespace, rootDir: entry.rootDir, namespaces: [entry.namespace] });\n }\n }\n const roots: CorpusNamespaceRoot[] = [...byRoot.values()];\n if (roots.length === 0) {\n roots.push({ namespace: config.defaultNamespace, rootDir: config.memoryDir, namespaces: [config.defaultNamespace] });\n }\n return roots;\n}\n\n/**\n * Divergence detection tolerates a watermark that is stale by up to a minute\n * (peers are compared on a slow cadence); a health/readiness probe does NOT\n * tolerate a full corpus scan (a recursive realpath-per-Markdown-file walk) on\n * every request. This TTL is that trade-off: recompute a namespace's watermark\n * at most once per window; probes in between serve the cached census (whose\n * `computedAt` shows its staleness).\n */\nexport const WATERMARK_CACHE_TTL_MS = 60_000;\n\n/**\n * Cap on concurrent background corpus scans. A probe that finds many namespaces\n * cold/expired would otherwise fan out one recursive hot/cold walk PER namespace\n * at once, a periodic unbounded filesystem-work burst that can starve recall and\n * storage (issue #2156 round-9). Excess refreshes queue and start as slots free;\n * single-flight still dedupes duplicate requests for the same namespace.\n */\nconst MAX_CONCURRENT_WATERMARK_REFRESHES = 4;\n\n/**\n * Instance-scoped (AGENTS.md pattern 5 — never a bare module global)\n * stale-while-revalidate cache in front of the per-namespace corpus scan.\n * {@link get} NEVER awaits the scan: it returns the freshest already-computed\n * watermark (stale allowed, or undefined for a cold namespace) and triggers a\n * single-flight background refresh when the entry is missing or expired. So a\n * `/health` probe is always O(1) — it never blocks on a recursive corpus walk,\n * even on the first request after startup or after a TTL expiry. Owned by the\n * long-lived access service instance.\n */\nexport class CorpusWatermarkCache {\n private readonly entries = new Map<string, { value: CorpusWatermark; expiresAt: number }>();\n private readonly inFlight = new Map<string, Promise<void>>();\n private readonly ttlMs: number;\n private readonly clock: () => number;\n private rootsEntry: { value: CorpusNamespaceRoot[]; expiresAt: number } | undefined;\n private rootsInFlight: Promise<void> | undefined;\n private rootsRefreshError: unknown;\n private readonly queued = new Map<string, () => void>();\n private activeRefreshes = 0;\n private readonly maxConcurrentRefreshes: number;\n\n constructor(options: { ttlMs?: number; clock?: () => number; maxConcurrentRefreshes?: number } = {}) {\n this.ttlMs = options.ttlMs ?? WATERMARK_CACHE_TTL_MS;\n this.clock = options.clock ?? Date.now;\n this.maxConcurrentRefreshes = Math.max(1, options.maxConcurrentRefreshes ?? MAX_CONCURRENT_WATERMARK_REFRESHES);\n }\n\n get(namespace: string, compute: () => Promise<CorpusWatermark>): CorpusWatermark | undefined {\n const cached = this.entries.get(namespace);\n const fresh = cached !== undefined && this.clock() < cached.expiresAt;\n if (!fresh) this.refresh(namespace, compute);\n return cached?.value;\n }\n\n private refresh(namespace: string, compute: () => Promise<CorpusWatermark>): void {\n // Single-flight + not-already-queued: one background scan per namespace.\n if (this.inFlight.has(namespace) || this.queued.has(namespace)) return;\n const run = (): void => {\n this.activeRefreshes += 1;\n const pending = compute()\n .then((value) => {\n this.entries.set(namespace, { value, expiresAt: this.clock() + this.ttlMs });\n })\n .catch(() => {\n // Failed/unreadable scan: never cache it; keep serving any stale value.\n })\n .finally(() => {\n this.inFlight.delete(namespace);\n this.activeRefreshes -= 1;\n this.drainQueue();\n });\n this.inFlight.set(namespace, pending);\n };\n // Bound the concurrent fan-out: run now if a slot is free, else queue.\n if (this.activeRefreshes < this.maxConcurrentRefreshes) {\n run();\n } else {\n this.queued.set(namespace, run);\n }\n }\n\n private drainQueue(): void {\n while (this.activeRefreshes < this.maxConcurrentRefreshes && this.queued.size > 0) {\n const next = this.queued.entries().next().value;\n if (!next) break;\n const [namespace, run] = next;\n this.queued.delete(namespace);\n run();\n }\n }\n\n\n /**\n * Stale-while-revalidate the resolved namespace roots (issue #2156 round-7).\n * Namespace enumeration (config scan + persisted-catalog parse) is O(tenants)\n * filesystem work; without this it ran on every probe ahead of the watermark\n * cache. NEVER awaits: returns the cached/stale roots (or undefined when cold)\n * and single-flights a background refresh.\n */\n getResolvedRoots(compute: () => Promise<CorpusNamespaceRoot[]>): CorpusNamespaceRoot[] | undefined {\n const fresh = this.rootsEntry !== undefined && this.clock() < this.rootsEntry.expiresAt;\n if (!fresh && this.rootsInFlight === undefined) {\n this.rootsInFlight = compute()\n .then((value) => {\n this.rootsEntry = { value, expiresAt: this.clock() + this.ttlMs };\n this.rootsRefreshError = undefined;\n })\n .catch((error: unknown) => {\n this.rootsRefreshError = error;\n // Keep serving any stale roots; callers that require completeness\n // inspect the refresh status before trusting them.\n })\n .finally(() => {\n this.rootsInFlight = undefined;\n });\n }\n return this.rootsEntry?.value;\n }\n\n getResolvedRootsStatus(compute: () => Promise<CorpusNamespaceRoot[]>): {\n roots: CorpusNamespaceRoot[] | undefined;\n refreshError: unknown;\n } {\n const roots = this.getResolvedRoots(compute);\n return { roots, refreshError: this.rootsRefreshError };\n }\n /** Await every background refresh — in-flight AND queued (issue #2156 round-9) —\n * plus any roots refresh (shutdown / deterministic tests). */\n async whenIdle(): Promise<void> {\n while (this.inFlight.size > 0 || this.rootsInFlight !== undefined || this.queued.size > 0) {\n const pending: Promise<unknown>[] = [...this.inFlight.values()];\n if (this.rootsInFlight) pending.push(this.rootsInFlight);\n if (pending.length === 0) {\n this.drainQueue(); // queued work with no running peer — start it\n await Promise.resolve();\n continue;\n }\n await Promise.all(pending);\n }\n }\n}\n\n/** Orchestrator surface the service watermark builder reads — satisfied by Orchestrator. */\nexport interface CorpusWatermarkHost {\n config: PluginConfig;\n getStorage(namespace: string): CorpusStorage | Promise<CorpusStorage>;\n}\n\nexport interface ServiceCorpusWatermarkOptions {\n /** Stale-while-revalidate cache so probes never await the corpus scan. */\n cache?: CorpusWatermarkCache;\n /**\n * Presenting token capabilities. When the token is namespace-restricted the\n * fleet view is filtered to the namespaces it may access (issue #2156 finding\n * B); an unrestricted/legacy token keeps the full fleet view.\n */\n caps?: TokenCapabilities | null;\n now?: Date;\n}\n\n/**\n * Build the per-namespace watermark array served on `/health`. Namespaces are\n * resolved through {@link resolveCorpusNamespaceRoots} (shared with the doctor),\n * filtered to the caller's capabilities, then served through the (optional)\n * stale-while-revalidate cache so a probe never blocks on a scan. Degrades to an\n * empty array — never throws — if enumeration is unavailable; an individual\n * namespace whose scan fails is omitted, not allowed to blank the payload.\n */\nexport async function computeServiceCorpusWatermarks(\n host: CorpusWatermarkHost,\n options: ServiceCorpusWatermarkOptions = {},\n): Promise<CorpusWatermark[]> {\n return (await computeServiceCorpusCensus(host, options)).watermarks;\n}\n\n/**\n * Watermarks PLUS whether every configured namespace made it into the set.\n *\n * Completeness is returned ATOMICALLY with the data rather than stashed in a\n * side channel: every early exit here (enumeration threw, roots still warming,\n * a per-namespace scan failed, a cache entry not yet populated) yields an\n * incomplete set, and a caller that certifies convergence against a partial\n * census would hide the divergence of whatever was dropped (issue #2149).\n */\nexport async function computeServiceCorpusCensus(\n host: CorpusWatermarkHost,\n options: ServiceCorpusWatermarkOptions = {},\n): Promise<{ watermarks: CorpusWatermark[]; complete: boolean }> {\n let roots: CorpusNamespaceRoot[] | undefined;\n if (options.cache) {\n const { roots: cachedRoots, refreshError } = options.cache.getResolvedRootsStatus(() =>\n resolveCorpusNamespaceRoots({ config: host.config, propagateDiscoveryErrors: true }),\n );\n if (refreshError !== undefined) return { watermarks: [], complete: false };\n roots = cachedRoots;\n } else {\n try {\n roots = await resolveCorpusNamespaceRoots({\n config: host.config,\n propagateDiscoveryErrors: true,\n });\n } catch {\n return { watermarks: [], complete: false }; // never enumerated — not an empty corpus\n }\n }\n // Enumeration still warming (cold): served on a later probe, and NOT complete.\n if (!roots) return { watermarks: [], complete: false };\n const visible = roots.filter((root) => root.namespaces.some((ns) => capabilityAllowsNamespace(options.caps, ns)));\n const watermarks: CorpusWatermark[] = [];\n let complete = true;\n for (const { namespace } of visible) {\n const compute = async (): Promise<CorpusWatermark> =>\n computeNamespaceWatermark(namespace, await host.getStorage(namespace), options.now);\n if (options.cache) {\n const cached = options.cache.get(namespace, compute);\n if (cached) watermarks.push(cached);\n else complete = false; // still warming — the set is not the full census yet\n } else {\n try {\n watermarks.push(await compute());\n } catch {\n // One tenant's storage/scan failed — omit just that namespace.\n complete = false;\n }\n }\n }\n return { watermarks, complete };\n}\n\n\n"],"mappings":"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","names":[]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/storage/entity-canonical-id-migration.ts"],"sourcesContent":["import { createHash, randomUUID } from \"node:crypto\";\nimport {\n access,\n lstat,\n readFile,\n readdir,\n realpath,\n rename,\n stat as statFile,\n unlink,\n writeFile,\n} from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { lstatSync, readFileSync } from \"node:fs\";\nimport { z } from \"zod\";\nimport { computeSupersessionKey, normalizeSupersessionKey } from \"../temporal-supersession.js\";\nimport type { EntityFile, MemoryFile } from \"../types.js\";\nimport { log } from \"../logger.js\";\nimport { isErrnoCode } from \"../utils/errno.js\";\nimport { withHeldFileLock } from \"../utils/serialize-mutations.js\";\nimport { RECALL_FALLBACK_DIRS } from \"../utils/category-dir.js\";\nimport { assertPathInsideRoot } from \"../utils/path-containment.js\";\nimport { withEntityCanonicalMutationLock } from \"./entity-canonical-id-lock.js\";\nimport { assertSafeEntityId } from \"./entity-id-safety.js\";\nimport { isMalformedEntityRecord } from \"../utils/malformed-entity-record.js\";\nimport {\n isMacOsReplicationJunkBasename,\n warnIgnoredMacOsReplicationJunk,\n} from \"../utils/macos-replication-junk.js\";\n\nimport {\n ENTITY_CANONICAL_ID_MIGRATION_FILE,\n ENTITY_CANONICAL_ID_RECONCILE_CONSUMING_MARKER,\n ENTITY_CANONICAL_ID_RECONCILE_MARKER,\n resolveHistoricalEntityCanonicalId,\n} from \"./entity-canonical-id-references.js\";\n\nconst ENTITY_CANONICAL_ID_MIGRATION_VERSION = 1;\nconst ENTITY_CANONICAL_ID_MIGRATION_LOCK_STALE_MS = 60_000;\nconst ENTITY_CANONICAL_ID_MIGRATION_LOCK_MAX_WAIT_MS = 300_000;\nconst TOMBSTONE_LOCK_STALE_MS = 30_000;\nconst TOMBSTONE_LOCK_MAX_WAIT_MS = 5_000;\nconst MEMORY_REWRITE_MAX_PASSES = 3;\nconst ENTITY_MAPPING_RESCAN_MAX_PASSES = 3;\n\nconst EntityCanonicalIdMigrationStateSchema = z.object({\n version: z.literal(ENTITY_CANONICAL_ID_MIGRATION_VERSION),\n complete: z.boolean(),\n mappings: z.record(z.string(), z.string().min(1)),\n completionFingerprint: z.string().optional(),\n /**\n * Pairs the migration refused to resolve, PARKED rather than dropped.\n * Deleting the record would strand the collision: once an operator removes\n * the legacy file, no later scan can rediscover the mapping, so references\n * still naming the legacy id would never be rewritten. Optional so a journal\n * written by an older build still parses.\n */\n blocked: z.record(z.string(), z.string().min(1)).optional(),\n});\n\ntype EntityCanonicalIdMigrationState = z.infer<typeof EntityCanonicalIdMigrationStateSchema>;\n\nexport interface EntityCanonicalIdMigrationDependencies {\n stateDir: string;\n entitiesDir: string;\n normalizeEntityName(raw: string, type: string): string;\n resolveEntityFilePath(id: string): string | null;\n readStorageSecureFile(filePath: string): Promise<string>;\n writeStorageSecureFile(filePath: string, content: string, forceEncrypt?: boolean): Promise<void>;\n isEncryptedStorageFile(filePath: string): Promise<boolean>;\n snapshotBeforeWrite(filePath: string, trigger: \"write\"): Promise<void>;\n parseEntityFile(content: string): EntityFile;\n serializeEntityFile(entity: EntityFile): string;\n serializeMemoryWithEntityRef(memory: MemoryFile, entityRef: string): string;\n readMemoryByPath(filePath: string): Promise<MemoryFile | null>;\n readAllMemories(): Promise<MemoryFile[]>;\n readAllColdMemories(): Promise<MemoryFile[]>;\n readArchivedMemories(): Promise<MemoryFile[]>;\n validateMemoryScanRoots?(): Promise<void>;\n rewriteProjectedEntityReferences(mappings: Readonly<Record<string, string>>): Promise<void>;\n rewriteProjectedMemoryEntityReference(\n memoryId: string,\n previousEntityRef: string,\n nextEntityRef: string,\n ): Promise<void>;\n invalidateKnowledgeIndexCache(): void;\n invalidateAllMemoriesCache(): void;\n invalidateColdMemoriesCache(): void;\n bumpMemoryStatusVersion(): void;\n readMigrationFingerprint?(): Promise<string>;\n}\n\nasync function fingerprintPath(filePath: string): Promise<string> {\n let fileStat;\n try {\n fileStat = await lstat(filePath);\n } catch (error) {\n if (!isErrnoCode(error, \"ENOENT\")) throw error;\n }\n return fileStat\n ? `${fileStat.dev}:${fileStat.ino}:${fileStat.mtimeMs}:${fileStat.ctimeMs}:${fileStat.size}`\n : \"missing\";\n}\n\nasync function fingerprintEntityEntries(entitiesDir: string): Promise<string> {\n let names: string[];\n try {\n names = (await readdir(entitiesDir))\n .filter((name) => path.extname(name) === \".md\" && !isMacOsReplicationJunkBasename(name))\n .sort();\n } catch (error) {\n if (isErrnoCode(error, \"ENOENT\")) return \"missing\";\n throw error;\n }\n const hash = createHash(\"sha256\");\n for (const name of names) {\n hash.update(`${name.length}:${name}:`);\n try {\n const entry = await lstat(path.join(entitiesDir, name), { bigint: true });\n hash.update(`${entry.dev}:${entry.ino}:${entry.size}:${entry.mtimeNs}:${entry.ctimeNs}`);\n } catch (error) {\n if (!isErrnoCode(error, \"ENOENT\")) throw error;\n hash.update(\"missing\");\n }\n hash.update(\"\\n\");\n }\n return hash.digest(\"hex\");\n}\n\n/**\n * Completion fingerprint for migration inputs only. Entity entry identities\n * catch same-path edits that do not change the directory inode; the cooperative\n * sentinel covers managed writes. Memory-corpus writes stay excluded so plain\n * fact writes do not reopen migration.\n */\nexport async function getFingerprint(\n baseDir: string,\n entitiesDir: string,\n getEntityMutationVersion: () => string,\n): Promise<string> {\n const [\n entityDirectoryFingerprint,\n entityEntriesFingerprint,\n aliasFingerprint,\n reconcileFingerprint,\n consumingFingerprint,\n ] = await Promise.all([\n fingerprintPath(entitiesDir),\n fingerprintEntityEntries(entitiesDir),\n fingerprintPath(path.join(baseDir, \"config\", \"aliases.json\")),\n fingerprintPath(path.join(baseDir, \"state\", ENTITY_CANONICAL_ID_RECONCILE_MARKER)),\n fingerprintPath(path.join(baseDir, \"state\", ENTITY_CANONICAL_ID_RECONCILE_CONSUMING_MARKER)),\n ]);\n return [\n entityDirectoryFingerprint,\n entityEntriesFingerprint,\n aliasFingerprint,\n reconcileFingerprint,\n consumingFingerprint,\n getEntityMutationVersion(),\n ].join(\":\");\n}\n\nexport async function validateRoots(\n baseDir: string,\n entitiesDir: string,\n stateDir: string,\n archiveDir: string,\n): Promise<void> {\n let baseStat;\n try {\n baseStat = await lstat(baseDir);\n } catch (error) {\n if (isErrnoCode(error, \"ENOENT\")) return;\n throw error;\n }\n if (baseStat.isSymbolicLink() || !baseStat.isDirectory()) {\n throw new Error(`Refusing entity canonical-id migration through unsafe memory root: ${baseDir}.`);\n }\n const memoryRoot = await realpath(baseDir);\n const roots = [\n entitiesDir,\n stateDir,\n ...RECALL_FALLBACK_DIRS.map((directory) => path.join(baseDir, directory)),\n path.join(baseDir, \"cold\"),\n archiveDir,\n ];\n for (const root of roots) {\n let rootStat;\n try {\n rootStat = await lstat(root);\n } catch (error) {\n if (isErrnoCode(error, \"ENOENT\")) continue;\n throw error;\n }\n if (rootStat.isSymbolicLink() || !rootStat.isDirectory()) {\n throw new Error(`Refusing entity canonical-id migration through unsafe memory root: ${root}.`);\n }\n assertPathInsideRoot(memoryRoot, await realpath(root), root);\n }\n}\n\nfunction statePath(deps: EntityCanonicalIdMigrationDependencies): string {\n return path.join(deps.stateDir, ENTITY_CANONICAL_ID_MIGRATION_FILE);\n}\n\nasync function assertSafeMigrationStateFile(\n deps: EntityCanonicalIdMigrationDependencies,\n): Promise<void> {\n const migrationStatePath = statePath(deps);\n let fileStat;\n try {\n fileStat = await lstat(migrationStatePath);\n } catch (error) {\n if (isErrnoCode(error, \"ENOENT\")) return;\n throw error;\n }\n if (fileStat.isSymbolicLink() || !fileStat.isFile()) {\n throw new Error(\n `Refusing entity canonical-id migration through unsafe migration state file: ${migrationStatePath}.`,\n );\n }\n const [stateRoot, stateFile] = await Promise.all([\n realpath(deps.stateDir),\n realpath(migrationStatePath),\n ]);\n assertPathInsideRoot(stateRoot, stateFile, migrationStatePath);\n}\nasync function assertSafeTombstoneFile(\n deps: EntityCanonicalIdMigrationDependencies,\n tombstonePath: string,\n): Promise<boolean> {\n let fileStat;\n try {\n fileStat = await lstat(tombstonePath);\n } catch (error) {\n if (isErrnoCode(error, \"ENOENT\")) return false;\n throw error;\n }\n if (fileStat.isSymbolicLink() || !fileStat.isFile()) {\n throw new Error(\n `Refusing entity canonical-id migration through unsafe tombstone ledger: ${tombstonePath}.`,\n );\n }\n const [stateRoot, tombstoneFile] = await Promise.all([\n realpath(deps.stateDir),\n realpath(tombstonePath),\n ]);\n assertPathInsideRoot(stateRoot, tombstoneFile, tombstonePath);\n return true;\n}\n\nfunction lockPath(deps: EntityCanonicalIdMigrationDependencies): string {\n return path.join(deps.stateDir, \"entity-canonical-id-migration.lock\");\n}\n\n\nasync function readState(\n deps: EntityCanonicalIdMigrationDependencies\n): Promise<EntityCanonicalIdMigrationState | null> {\n await assertSafeMigrationStateFile(deps);\n let raw: string;\n try {\n raw = await readFile(statePath(deps), \"utf-8\");\n } catch (error) {\n if (isErrnoCode(error, \"ENOENT\")) return null;\n throw error;\n }\n\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch {\n throw new Error(\"Invalid entity canonical-id migration state.\");\n }\n const result = EntityCanonicalIdMigrationStateSchema.safeParse(parsed);\n if (!result.success) throw new Error(\"Invalid entity canonical-id migration state.\");\n for (const mappings of [result.data.mappings, result.data.blocked ?? {}]) {\n for (const [legacyId, canonicalId] of Object.entries(mappings)) {\n assertSafeEntityId(legacyId);\n assertSafeEntityId(canonicalId);\n }\n }\n return result.data;\n}\n\nasync function writeState(\n deps: EntityCanonicalIdMigrationDependencies,\n state: EntityCanonicalIdMigrationState\n): Promise<void> {\n const destination = statePath(deps);\n const temporary = `${destination}.${process.pid}.${randomUUID()}.tmp`;\n try {\n await writeFile(temporary, `${JSON.stringify(state, null, 2)}\\n`, \"utf-8\");\n await rename(temporary, destination);\n } finally {\n await unlink(temporary).catch(() => undefined);\n }\n}\n\n/**\n * Every journal publish — additive merges, completion markers, prunes —\n * serializes under the entity MUTATION lock (issue #2213 review): entity\n * writers, post-persist repair settles, and tombstone appends revalidate\n * the journal identity under that lock, so an unlocked publish could land\n * inside their resolve→write window and strand the write on a superseded\n * id space.\n */\nasync function publishState(\n deps: EntityCanonicalIdMigrationDependencies,\n state: EntityCanonicalIdMigrationState\n): Promise<void> {\n await withEntityCanonicalMutationLock(deps.stateDir, async () => {\n await writeState(deps, state);\n });\n}\n\nasync function fileExists(filePath: string): Promise<boolean> {\n try {\n await access(filePath);\n return true;\n } catch (error) {\n if (isErrnoCode(error, \"ENOENT\")) return false;\n throw error;\n }\n}\n\nasync function sameFileIdentity(leftPath: string, rightPath: string): Promise<boolean> {\n try {\n const [left, right] = await Promise.all([statFile(leftPath), statFile(rightPath)]);\n return left.dev === right.dev && left.ino === right.ino;\n } catch (error) {\n if (isErrnoCode(error, \"ENOENT\")) return false;\n throw error;\n }\n}\n\nasync function assertNotSymlink(filePath: string, label: string): Promise<void> {\n let stat;\n try {\n stat = await lstat(filePath);\n } catch (error) {\n if (isErrnoCode(error, \"ENOENT\")) return;\n throw error;\n }\n if (stat.isSymbolicLink()) {\n throw new Error(`Refusing entity canonical-id migration through symlink: ${label}.`);\n }\n}\nasync function assertNotMemorySymlink(filePath: string): Promise<void> {\n let stat;\n try {\n stat = await lstat(filePath);\n } catch (error) {\n if (isErrnoCode(error, \"ENOENT\")) return;\n throw error;\n }\n if (stat.isSymbolicLink()) {\n throw new Error(`Refusing entity canonical-id migration through symlinked memory: ${filePath}.`);\n }\n}\nasync function assertEntityContentUnchanged(\n deps: EntityCanonicalIdMigrationDependencies,\n filePath: string,\n expectedContent: string,\n legacyId: string,\n): Promise<void> {\n const latestContent = await deps.readStorageSecureFile(filePath);\n if (latestContent !== expectedContent) {\n throw new Error(`Legacy entity id ${legacyId} changed while migration was running; retry migration.`);\n }\n}\n\n/**\n * Fail-closed entity-entry validation for the startup path: refuses a\n * symlinked or non-directory entities root and any symlinked entry, without\n * taking the migration lock. Exported so `ensureDirectories()` can run it\n * before deferring the migration (#3139) — the same check\n * `migrateLegacyEntityCanonicalIds` performs under the lock.\n */\nexport async function readSafeEntityEntries(entitiesDir: string): Promise<string[]> {\n const rootStat = await lstat(entitiesDir);\n if (rootStat.isSymbolicLink() || !rootStat.isDirectory()) {\n throw new Error(\"Refusing entity canonical-id migration through a symlinked or non-directory entities root.\");\n }\n const entries = await readdir(entitiesDir);\n for (const entry of entries) {\n await assertNotSymlink(path.join(entitiesDir, entry), entry);\n }\n return entries;\n}\n\n/**\n * Legacy ids the migration cannot resolve on its own, mapped to the canonical\n * id they collide with. Two entity files describing one entity is an ORDINARY\n * operator state, and picking a winner would silently drop whichever side lost.\n * So the pair is skipped and reported: reads of the legacy id keep working\n * exactly as they did before the migration ran.\n */\nexport type BlockedEntityPairs = Map<string, string>;\n\ninterface EntityScanNotes {\n junk: Set<string>;\n malformed: Set<string>;\n}\n\nasync function discoverMappings(\n deps: EntityCanonicalIdMigrationDependencies,\n knownMappings: Readonly<Record<string, string>> = {},\n blocked: BlockedEntityPairs = new Map(),\n scanNotes?: EntityScanNotes,\n): Promise<Record<string, string>> {\n const mappings: Record<string, string> = {};\n const canonicalOwners = new Map<string, string[]>();\n // Blocks describe THIS scan of the directory, so they are rebuilt per pass.\n // Accumulating them across a run would let a collision another writer has\n // since resolved keep pruning its now-valid mapping: the entity file gets\n // renamed while the pruned mapping skips the reference rewrite, stranding\n // memories on a filename that no longer exists.\n blocked.clear();\n const entityEntries = await readSafeEntityEntries(deps.entitiesDir);\n for (const entry of entityEntries) {\n // AppleDouble (`._*`) and `.DS_Store` are Finder metadata, not entity\n // files. A real file that cannot be parsed is left in place: this scan\n // runs during directory init, and aborting it took the daemon down.\n // Skipping is the same outcome as the old throw (the file is not\n // rewritten) without refusing to boot. Symlink checks above still run.\n if (isMacOsReplicationJunkBasename(entry)) {\n scanNotes?.junk.add(entry);\n continue;\n }\n if (!entry.endsWith(\".md\")) continue;\n const legacyId = entry.slice(0, -\".md\".length);\n assertSafeEntityId(legacyId);\n const legacyPath = deps.resolveEntityFilePath(legacyId);\n if (legacyPath === null) continue;\n const entityContent = await deps.readStorageSecureFile(legacyPath);\n if (isMalformedEntityRecord(entityContent)) {\n scanNotes?.malformed.add(entry);\n continue;\n }\n const entity = deps.parseEntityFile(entityContent);\n if (!entity.name.trim() || !entity.type.trim()) {\n scanNotes?.malformed.add(entry);\n continue;\n }\n const canonicalId = deps.normalizeEntityName(entity.name, entity.type);\n assertSafeEntityId(canonicalId);\n if (legacyId === canonicalId) continue;\n const canonicalPath = deps.resolveEntityFilePath(canonicalId);\n if (canonicalPath === null) continue;\n const canonicalExists = await fileExists(canonicalPath);\n if (\n canonicalExists\n && await sameFileIdentity(legacyPath, canonicalPath)\n && knownMappings[legacyId] === canonicalId\n ) continue;\n if (canonicalExists) {\n const canonicalContent = await deps.readStorageSecureFile(canonicalPath);\n if (entityContent !== canonicalContent) {\n blocked.set(legacyId, canonicalId);\n continue;\n }\n }\n canonicalOwners.set(canonicalId, [...(canonicalOwners.get(canonicalId) ?? []), legacyId]);\n mappings[legacyId] = canonicalId;\n }\n // A canonical id claimed by more than one legacy file has no non-arbitrary\n // winner, and readdir order is not stable (§6) - so the \"winner\" would differ\n // between hosts reading identical data. Block every claimant instead.\n for (const [canonicalId, claimants] of canonicalOwners) {\n if (claimants.length < 2) continue;\n for (const legacyId of claimants) {\n blocked.set(legacyId, canonicalId);\n delete mappings[legacyId];\n }\n }\n return mappings;\n}\n\nasync function rewriteReferences(\n deps: EntityCanonicalIdMigrationDependencies,\n mappings: Readonly<Record<string, string>>,\n refreshLock: () => Promise<boolean>\n): Promise<{ rewroteColdMemory: boolean }> {\n let rewroteColdMemory = false;\n for (let pass = 0; pass < MEMORY_REWRITE_MAX_PASSES; pass += 1) {\n deps.invalidateAllMemoriesCache();\n deps.invalidateColdMemoriesCache();\n const [hotMemories, coldMemories, archivedMemories] = await Promise.all([\n deps.readAllMemories(),\n deps.readAllColdMemories(),\n deps.readArchivedMemories(),\n ]);\n const seenPaths = new Set<string>();\n let rewroteAny = false;\n for (const memory of [...hotMemories, ...coldMemories, ...archivedMemories]) {\n const memoryPath = path.resolve(memory.path);\n if (seenPaths.has(memoryPath)) continue;\n seenPaths.add(memoryPath);\n const previousEntityRef = memory.frontmatter.entityRef;\n const canonicalId = previousEntityRef ? mappings[previousEntityRef] : undefined;\n if (!previousEntityRef || !canonicalId) continue;\n await assertNotMemorySymlink(memory.path);\n const observedMemoryFingerprint = await fingerprintPath(memory.path);\n if (observedMemoryFingerprint === \"missing\") continue;\n if (!(await refreshLock())) throw new Error(\"Lost entity canonical-id migration lock.\");\n const latestMemory = await deps.readMemoryByPath(memory.path);\n if (!latestMemory) continue;\n const latestMemoryFingerprint = await fingerprintPath(latestMemory.path);\n if (latestMemoryFingerprint !== observedMemoryFingerprint) continue;\n const latestPreviousEntityRef = latestMemory.frontmatter.entityRef;\n const latestCanonicalId = latestPreviousEntityRef ? mappings[latestPreviousEntityRef] : undefined;\n if (!latestPreviousEntityRef || !latestCanonicalId) continue;\n const memoryEncrypted = await deps.isEncryptedStorageFile(latestMemory.path);\n if ((await fingerprintPath(latestMemory.path)) !== latestMemoryFingerprint) continue;\n await deps.snapshotBeforeWrite(latestMemory.path, \"write\");\n if ((await fingerprintPath(latestMemory.path)) !== latestMemoryFingerprint) continue;\n await deps.writeStorageSecureFile(\n latestMemory.path,\n deps.serializeMemoryWithEntityRef(latestMemory, latestCanonicalId),\n memoryEncrypted,\n );\n await deps.rewriteProjectedMemoryEntityReference(\n latestMemory.frontmatter.id,\n latestPreviousEntityRef,\n latestCanonicalId,\n );\n rewroteAny = true;\n rewroteColdMemory ||= latestMemory.path.includes(`${path.sep}cold${path.sep}`);\n }\n if (!rewroteAny) return { rewroteColdMemory };\n }\n throw new Error(\"Memory references changed while entity canonical-id migration was running; retry migration.\");\n}\n\nfunction rewriteTombstoneLine(\n line: string,\n mappings: Readonly<Record<string, string>>,\n normalizedMappings: ReadonlyMap<string, string>,\n): string {\n let parsed: unknown;\n try {\n parsed = JSON.parse(line);\n } catch {\n return line;\n }\n if (!parsed || typeof parsed !== \"object\" || Array.isArray(parsed)) return line;\n\n const tombstone = parsed as Record<string, unknown>;\n let changed = false;\n if (typeof tombstone.entityRef === \"string\") {\n const canonicalId = mappings[tombstone.entityRef];\n if (canonicalId) {\n tombstone.entityRef = canonicalId;\n changed = true;\n }\n }\n if (typeof tombstone.supersessionKey === \"string\") {\n const separator = tombstone.supersessionKey.indexOf(\"::\");\n if (separator < 0) return changed ? JSON.stringify(tombstone) : line;\n const normalizedLegacyId = normalizeSupersessionKey(tombstone.supersessionKey.slice(0, separator));\n const canonicalId = normalizedMappings.get(normalizedLegacyId);\n if (canonicalId) {\n const canonicalKey = computeSupersessionKey(canonicalId, tombstone.supersessionKey.slice(separator + 2));\n if (canonicalKey) {\n tombstone.supersessionKey = canonicalKey;\n changed = true;\n }\n }\n }\n return changed ? JSON.stringify(tombstone) : line;\n}\n\nasync function rewriteTombstoneReferences(\n deps: EntityCanonicalIdMigrationDependencies,\n mappings: Readonly<Record<string, string>>,\n refreshLock: () => Promise<boolean>\n): Promise<void> {\n const tombstonePath = path.join(deps.stateDir, \"tombstones.jsonl\");\n const normalizedMappings = new Map<string, string>();\n for (const [legacyId, canonicalId] of Object.entries(mappings)) {\n const normalizedLegacyId = normalizeSupersessionKey(legacyId);\n if (normalizedLegacyId && !normalizedMappings.has(normalizedLegacyId)) {\n normalizedMappings.set(normalizedLegacyId, canonicalId);\n }\n }\n\n await withHeldFileLock(\n path.join(deps.stateDir, \"tombstones.lock\"),\n {\n staleMs: TOMBSTONE_LOCK_STALE_MS,\n maxWaitMs: TOMBSTONE_LOCK_MAX_WAIT_MS,\n },\n async (acquired, tombstoneLock) => {\n if (!acquired) throw new Error(\"Timed out waiting for active tombstone migration.\");\n if (!(await assertSafeTombstoneFile(deps, tombstonePath))) return;\n const tombstoneEncrypted = await deps.isEncryptedStorageFile(tombstonePath);\n const original = await deps.readStorageSecureFile(tombstonePath);\n let changed = false;\n const rewritten = original\n .split(\"\\n\")\n .map((line) => {\n const next = rewriteTombstoneLine(line, mappings, normalizedMappings);\n changed ||= next !== line;\n return next;\n })\n .join(\"\\n\");\n if (!changed) return;\n if (!(await refreshLock())) throw new Error(\"Lost entity canonical-id migration lock.\");\n if (!(await tombstoneLock.refresh())) throw new Error(\"Lost tombstone migration lock.\");\n await deps.snapshotBeforeWrite(tombstonePath, \"write\");\n await deps.writeStorageSecureFile(tombstonePath, rewritten, tombstoneEncrypted);\n },\n );\n}\n\nasync function rewriteRelationshipTargets(\n deps: EntityCanonicalIdMigrationDependencies,\n mappings: Readonly<Record<string, string>>,\n refreshLock: () => Promise<boolean>\n): Promise<boolean> {\n let rewritten = false;\n const entries = await readSafeEntityEntries(deps.entitiesDir);\n for (const entry of entries) {\n if (!entry.endsWith(\".md\") || isMacOsReplicationJunkBasename(entry)) continue;\n const entityPath = deps.resolveEntityFilePath(entry.slice(0, -\".md\".length));\n if (entityPath === null) continue;\n const observedEntityFingerprint = await fingerprintPath(entityPath);\n if (observedEntityFingerprint === \"missing\") continue;\n const entityRaw = await deps.readStorageSecureFile(entityPath);\n if (isMalformedEntityRecord(entityRaw)) continue;\n const entity = deps.parseEntityFile(entityRaw);\n const relationships = entity.relationships.map((relationship) => {\n const target = mappings[relationship.target];\n return target ? { ...relationship, target } : relationship;\n });\n if (relationships.every((relationship, index) => relationship === entity.relationships[index])) continue;\n const rewroteEntity = await withEntityCanonicalMutationLock(\n deps.stateDir,\n async (refreshEntityLock) => {\n if (!(await refreshLock())) throw new Error(\"Lost entity canonical-id migration lock.\");\n const latestRaw = await deps.readStorageSecureFile(entityPath);\n if (isMalformedEntityRecord(latestRaw)) return false;\n const latestEntity = deps.parseEntityFile(latestRaw);\n if ((await fingerprintPath(entityPath)) !== observedEntityFingerprint) return false;\n const latestRelationships = latestEntity.relationships.map((relationship) => {\n const target = mappings[relationship.target];\n return target ? { ...relationship, target } : relationship;\n });\n if (latestRelationships.every((relationship, index) => relationship === latestEntity.relationships[index])) {\n return false;\n }\n const entityEncrypted = await deps.isEncryptedStorageFile(entityPath);\n if (!(await refreshEntityLock())) throw new Error(\"Lost entity mutation lock.\");\n if ((await fingerprintPath(entityPath)) !== observedEntityFingerprint) return false;\n await deps.snapshotBeforeWrite(entityPath, \"write\");\n if ((await fingerprintPath(entityPath)) !== observedEntityFingerprint) return false;\n await deps.writeStorageSecureFile(\n entityPath,\n deps.serializeEntityFile({ ...latestEntity, relationships: latestRelationships }),\n entityEncrypted,\n );\n return true;\n },\n );\n rewritten ||= rewroteEntity;\n }\n return rewritten;\n}\n\nasync function rewriteKnownReferences(\n deps: EntityCanonicalIdMigrationDependencies,\n mappings: Readonly<Record<string, string>>,\n refreshLock: () => Promise<boolean>,\n): Promise<boolean> {\n await rewriteRelationshipTargets(deps, mappings, refreshLock);\n let rewroteColdMemory = (await rewriteReferences(deps, mappings, refreshLock)).rewroteColdMemory;\n await rewriteTombstoneReferences(deps, mappings, refreshLock);\n await deps.rewriteProjectedEntityReferences(mappings);\n const finalMemoryPass = await rewriteReferences(deps, mappings, refreshLock);\n rewroteColdMemory ||= finalMemoryPass.rewroteColdMemory;\n return rewroteColdMemory;\n}\n\nfunction wouldCreateMappingCycle(\n mappings: Readonly<Record<string, string>>,\n legacyId: string,\n canonicalId: string,\n): boolean {\n let current = canonicalId;\n const seen = new Set<string>();\n while (true) {\n if (current === legacyId) return true;\n if (seen.has(current)) return false;\n seen.add(current);\n const next = mappings[current];\n if (!next || next === current) return false;\n current = next;\n }\n}\n\nfunction rejectMappingCycles(\n existing: Readonly<Record<string, string>>,\n discovered: Readonly<Record<string, string>>,\n): Record<string, string> {\n const accepted: Record<string, string> = {};\n const merged = { ...existing };\n for (const [legacyId, canonicalId] of Object.entries(discovered)) {\n if (\n merged[legacyId] === undefined &&\n wouldCreateMappingCycle(merged, legacyId, canonicalId)\n ) continue;\n accepted[legacyId] = canonicalId;\n merged[legacyId] = canonicalId;\n }\n return accepted;\n}\n\nfunction mergeDiscoveredMappings(\n existing: Readonly<Record<string, string>>,\n discovered: Readonly<Record<string, string>>,\n): Record<string, string> {\n const merged = { ...existing };\n const canonicalOwners = new Map<string, string>();\n for (const [legacyId, canonicalId] of Object.entries(discovered)) {\n const previousMapping = merged[legacyId];\n if (previousMapping !== undefined && previousMapping !== canonicalId) {\n continue;\n }\n if (previousMapping === undefined && wouldCreateMappingCycle(merged, legacyId, canonicalId)) continue;\n const previousOwner = canonicalOwners.get(canonicalId);\n if (previousOwner !== undefined && previousOwner !== legacyId) {\n throw new Error(\n `Cannot migrate legacy entity ids ${previousOwner} and ${legacyId}: both normalize to ${canonicalId}.`,\n );\n }\n merged[legacyId] = canonicalId;\n canonicalOwners.set(canonicalId, legacyId);\n }\n return merged;\n}\nfunction collapseMappings(mappings: Readonly<Record<string, string>>): Record<string, string> {\n const collapsed: Record<string, string> = {};\n for (const legacyId of Object.keys(mappings)) {\n let current = legacyId;\n const seen = new Set<string>();\n while (true) {\n const next = mappings[current];\n if (!next || next === current) break;\n if (seen.has(current)) {\n throw new Error(`Entity canonical-id migration mapping cycle includes ${legacyId}.`);\n }\n seen.add(current);\n current = next;\n }\n if (current !== legacyId) collapsed[legacyId] = current;\n }\n return collapsed;\n}\n\ntype EntityFileMigrationResult = {\n deferred: boolean;\n progressed: boolean;\n /** The pair collided on content; skipped and reported, never migrated. */\n blocked?: boolean;\n};\n\nasync function migrateEntityFilePair(\n deps: EntityCanonicalIdMigrationDependencies,\n legacyId: string,\n canonicalId: string,\n refreshMigrationLock: () => Promise<boolean>,\n): Promise<EntityFileMigrationResult> {\n return withEntityCanonicalMutationLock(deps.stateDir, async (refreshEntityLock) => {\n assertSafeEntityId(legacyId);\n assertSafeEntityId(canonicalId);\n const legacyPath = deps.resolveEntityFilePath(legacyId);\n const canonicalPath = deps.resolveEntityFilePath(canonicalId);\n if (legacyPath === null || canonicalPath === null) {\n throw new Error(\"Invalid entity canonical-id migration path.\");\n }\n await assertNotSymlink(legacyPath, legacyId);\n await assertNotSymlink(canonicalPath, canonicalId);\n const legacyExists = await fileExists(legacyPath);\n const canonicalExists = await fileExists(canonicalPath);\n if (!legacyExists && !canonicalExists) return { deferred: true, progressed: false };\n if (legacyExists && canonicalExists) {\n if (await sameFileIdentity(legacyPath, canonicalPath)) return { deferred: false, progressed: false };\n const [legacyContent, canonicalContent, legacyEncrypted, canonicalEncrypted] = await Promise.all([\n deps.readStorageSecureFile(legacyPath),\n deps.readStorageSecureFile(canonicalPath),\n deps.isEncryptedStorageFile(legacyPath),\n deps.isEncryptedStorageFile(canonicalPath),\n ]);\n if (legacyContent !== canonicalContent) {\n // A mapping persisted by an older build, or a file changed since\n // discovery. Same verdict as discovery: skip the pair, keep both files.\n return { deferred: false, progressed: false, blocked: true };\n }\n if (legacyEncrypted && !canonicalEncrypted) {\n if (!(await refreshMigrationLock()) || !(await refreshEntityLock())) {\n throw new Error(\"Lost entity canonical-id migration lock.\");\n }\n await deps.snapshotBeforeWrite(canonicalPath, \"write\");\n await deps.writeStorageSecureFile(canonicalPath, legacyContent, true);\n if ((await deps.readStorageSecureFile(canonicalPath)) !== legacyContent) {\n throw new Error(`Cannot verify migrated entity id ${canonicalId}.`);\n }\n }\n if (!(await refreshMigrationLock()) || !(await refreshEntityLock())) {\n throw new Error(\"Lost entity canonical-id migration lock.\");\n }\n await deps.snapshotBeforeWrite(legacyPath, \"write\");\n await assertNotSymlink(legacyPath, legacyId);\n await assertEntityContentUnchanged(deps, legacyPath, legacyContent, legacyId);\n await unlink(legacyPath);\n return { deferred: false, progressed: true };\n }\n if (!legacyExists) return { deferred: false, progressed: false };\n if (!(await refreshMigrationLock()) || !(await refreshEntityLock())) {\n throw new Error(\"Lost entity canonical-id migration lock.\");\n }\n const entityEncrypted = await deps.isEncryptedStorageFile(legacyPath);\n const entityContent = await deps.readStorageSecureFile(legacyPath);\n await assertNotSymlink(legacyPath, legacyId);\n await assertEntityContentUnchanged(deps, legacyPath, entityContent, legacyId);\n // A journal entry must not relocate Finder metadata or a record this scan\n // would leave in place. readStorageSecureFile has already decrypted, so\n // the check is on that plaintext. The mapping stays for a later repair.\n if (\n isMacOsReplicationJunkBasename(path.basename(legacyPath))\n || isMalformedEntityRecord(entityContent)\n ) {\n return { deferred: false, progressed: false };\n }\n let canonicalFingerprintAfterWrite: string | null = null;\n try {\n await deps.writeStorageSecureFile(canonicalPath, entityContent, entityEncrypted);\n canonicalFingerprintAfterWrite = await fingerprintPath(canonicalPath);\n if ((await deps.readStorageSecureFile(canonicalPath)) !== entityContent) {\n throw new Error(`Cannot verify migrated entity id ${canonicalId}.`);\n }\n if (!(await refreshMigrationLock()) || !(await refreshEntityLock())) {\n throw new Error(\"Lost entity canonical-id migration lock.\");\n }\n await deps.snapshotBeforeWrite(legacyPath, \"write\");\n await assertNotSymlink(legacyPath, legacyId);\n await assertEntityContentUnchanged(deps, legacyPath, entityContent, legacyId);\n await unlink(legacyPath);\n } catch (error) {\n if (!canonicalExists && canonicalFingerprintAfterWrite !== null) {\n try {\n if ((await fingerprintPath(canonicalPath)) === canonicalFingerprintAfterWrite) {\n await assertNotSymlink(canonicalPath, canonicalId);\n await unlink(canonicalPath);\n }\n } catch {\n // Preserve the migration error when cleanup races another writer.\n }\n }\n throw error;\n }\n return { deferred: false, progressed: true };\n });\n}\n\nexport async function migrateLegacyEntityCanonicalIds(\n deps: EntityCanonicalIdMigrationDependencies,\n): Promise<string | undefined> {\n return withHeldFileLock(\n lockPath(deps),\n {\n staleMs: ENTITY_CANONICAL_ID_MIGRATION_LOCK_STALE_MS,\n maxWaitMs: ENTITY_CANONICAL_ID_MIGRATION_LOCK_MAX_WAIT_MS,\n },\n async (acquired, lock) => {\n if (!acquired) throw new Error(\"Timed out waiting for active entity canonical-id migration.\");\n await deps.validateMemoryScanRoots?.();\n await readSafeEntityEntries(deps.entitiesDir);\n // A raw-byte writer (offline sync, consolidation-undo, governance\n // restore) may have landed memory bytes this process never vetted; the\n // marker requests ONE reference-reconciliation pass (issue #2213). The\n // observed generation is RENAMED aside before the pass so a request made\n // WHILE this run scans creates a fresh marker that survives finish() —\n // and a `.consuming` file left by a crashed run still owes its pass.\n const reconcileMarkerPath = path.join(deps.stateDir, ENTITY_CANONICAL_ID_RECONCILE_MARKER);\n const consumingMarkerPath = path.join(deps.stateDir, ENTITY_CANONICAL_ID_RECONCILE_CONSUMING_MARKER);\n let reconcileRequested = await fileExists(consumingMarkerPath);\n if (await fileExists(reconcileMarkerPath)) {\n try {\n await rename(reconcileMarkerPath, consumingMarkerPath);\n reconcileRequested = true;\n } catch (error) {\n if (!isErrnoCode(error, \"ENOENT\")) throw error;\n }\n }\n let state: EntityCanonicalIdMigrationState = await readState(deps) ?? {\n version: ENTITY_CANONICAL_ID_MIGRATION_VERSION,\n complete: false,\n mappings: {},\n };\n const blocked: BlockedEntityPairs = new Map();\n const scanNotes: EntityScanNotes = { junk: new Set(), malformed: new Set() };\n // Parks for pairs whose files are GONE, tracked separately from\n // collision parks: pruneBlocked drops these (the canonical file is gone\n // too), and the rescan's blocked.clear() would otherwise silence them\n // before finish() reports anything.\n const staleParked: BlockedEntityPairs = new Map();\n // ONE aggregated line per run, not one per pair per restart: this used to\n // abort the daemon, so the operator needs the whole list and the remedy.\n const finish = async (): Promise<string | undefined> => {\n if (blocked.size > 0) {\n const pairs = [...blocked].map(([legacyId, canonicalId]) => `${legacyId} -> ${canonicalId}`).join(\", \");\n log.warn(\n `entity canonical-id migration skipped ${blocked.size} unresolvable pair(s): ${pairs}. `\n + \"Both files were kept and the legacy id still resolves; merge or delete one side to migrate it.\",\n );\n }\n if (staleParked.size > 0) {\n const pairs = [...staleParked].map(([legacyId, canonicalId]) => `${legacyId} -> ${canonicalId}`).join(\", \");\n log.warn(\n `entity canonical-id migration dropped ${staleParked.size} mapping(s) whose entity files are gone: ${pairs}. `\n + \"The stale journal entries were removed; references to those legacy ids do not resolve either way.\",\n );\n }\n warnIgnoredMacOsReplicationJunk(deps.entitiesDir, scanNotes.junk.size, (message) => {\n log.warn(`entity canonical-id migration ${message}`);\n });\n if (scanNotes.malformed.size > 0) {\n const names = [...scanNotes.malformed].sort().join(\", \");\n log.warn(\n `entity canonical-id migration skipped ${scanNotes.malformed.size} malformed entity file(s): ${names}. `\n + \"Each file was left in place; startup continues. Fix or remove the file to migrate it.\",\n );\n }\n // Every finish() path either just rewrote references (main/reconcile\n // paths), has none to rewrite (empty mappings), or observed no marker\n // (stable no-op) — so the CONSUMED generation is removed here. A fresh\n // marker written mid-run is untouched, and a crash before this point\n // leaves `.consuming` set for the next run (the rewrite is idempotent).\n if (reconcileRequested) {\n await unlink(consumingMarkerPath).catch((error: unknown) => {\n if (!isErrnoCode(error, \"ENOENT\")) throw error;\n });\n }\n // A FRESH marker written while this run scanned means bytes landed the\n // pass may not have covered. Withholding the completion fingerprint\n // makes the runner's stability check fail, so it immediately retries —\n // and the retry consumes the fresh generation. ORDER MATTERS: read the\n // fingerprint BEFORE the marker check. A marker landing after the\n // fingerprint read but before the check is caught by the check; one\n // landing after the check is absent from the returned fingerprint, so\n // the live marker mismatches the runner's cached completion and the\n // next ensure() re-invokes. Checking first left a window where the\n // fingerprint could SEAL a marker the check never saw.\n const completionFingerprint = await deps.readMigrationFingerprint?.();\n if (await fileExists(reconcileMarkerPath)) return undefined;\n if (completionFingerprint !== undefined && state.completionFingerprint !== completionFingerprint) {\n state = { ...state, complete: true, completionFingerprint };\n await publishState(deps, state);\n }\n return completionFingerprint;\n };\n /**\n * Drop blocked pairs from the working set. Discovery only refuses to\n * ADD them; a mapping persisted by an earlier run would otherwise still\n * be applied - migrating one claimant and rewriting every reference onto\n * the canonical file, i.e. picking the winner this fix exists to avoid,\n * off stale state rather than what is on disk.\n */\n /** @returns true when a park was promoted back into the mapping set. */\n const pruneBlocked = async (): Promise<boolean> => {\n const current = state;\n if (!current) return false;\n // THIS scan's verdict wins over the journal: normalization can change\n // between runs, so a stale parked target would otherwise be promoted\n // later and rewrite references onto an id that does not exist.\n let revived = false;\n const parked: Record<string, string> = { ...(current.blocked ?? {}) };\n const active: Record<string, string> = {};\n for (const [legacyId, canonicalId] of Object.entries(current.mappings)) {\n if (!blocked.has(legacyId)) active[legacyId] = canonicalId;\n }\n for (const [legacyId, canonicalId] of blocked) parked[legacyId] = canonicalId;\n // A parked pair whose legacy file is gone was resolved by keeping the\n // canonical side. The rename is moot; the reference rewrite is not, and\n // this record is the only thing that can still perform it.\n // A park survives ONLY while this scan re-establishes the collision.\n // Anything else - the scan found a real migration, or found the source\n // needs none (already canonical under a newer normalization) - makes\n // the record obsolete, and keeping it lets a later deletion promote a\n // target the entity never occupied.\n for (const [legacyId, canonicalId] of Object.entries(parked)) {\n if (blocked.has(legacyId)) continue;\n delete parked[legacyId];\n if (active[legacyId] !== undefined) continue;\n const legacyPath = deps.resolveEntityFilePath(legacyId);\n if (legacyPath !== null && (await fileExists(legacyPath))) continue;\n // Resolve the parked target through ACTIVE moves before deciding\n // its file is gone: a parked A -> B beside an active B -> C must\n // promote to A -> C and rewrite, not drop because B was moved\n // out-of-band. Only a target with no surviving file may drop.\n let target = canonicalId;\n const seenTargets = new Set<string>();\n while (active[target] !== undefined && !seenTargets.has(target)) {\n seenTargets.add(target);\n target = active[target]!;\n }\n const targetPath = deps.resolveEntityFilePath(target);\n if (targetPath === null || !(await fileExists(targetPath))) {\n staleParked.set(legacyId, canonicalId);\n continue;\n }\n active[legacyId] = target;\n revived = true;\n }\n // Reviving a whole chain at once yields A -> B -> C, and each rewrite\n // surface makes a different number of passes over it - so collapse\n // transitively before anything consumes the set.\n const collapsed = collapseMappings(active);\n // Parked targets follow ACTIVE moves only: hopping over a still-blocked\n // link would jump to an id the migration has not been allowed to reach.\n for (const [legacyId, canonicalId] of Object.entries(parked)) {\n const moved = collapsed[canonicalId];\n if (moved !== undefined && moved !== legacyId) parked[legacyId] = moved;\n }\n const same = (a: Record<string, string>, b: Record<string, string>): boolean =>\n Object.keys(a).length === Object.keys(b).length\n && Object.entries(a).every(([key, value]) => b[key] === value);\n if (same(collapsed, current.mappings) && same(parked, current.blocked ?? {})) return revived;\n // Parking/removing an active mapping mid-entity-write would land an\n // alias/activity/relationship mutation on a now-unrelated canonical\n // claimant — the reconcile pass rewrites references, it cannot move\n // a misplaced mutation. publishState serializes under the lock.\n state = { ...current, mappings: collapsed, blocked: parked };\n await publishState(deps, state);\n return revived;\n };\n const previousMappings = state.mappings;\n const collapsedMappings = collapseMappings(previousMappings);\n const mappingsChanged =\n Object.keys(collapsedMappings).length !== Object.keys(previousMappings).length\n || Object.entries(collapsedMappings).some(([legacyId, canonicalId]) =>\n previousMappings[legacyId] !== canonicalId\n );\n state = { ...state, mappings: collapsedMappings };\n for (let migrationPass = 0; migrationPass < ENTITY_MAPPING_RESCAN_MAX_PASSES; migrationPass += 1) {\n if (!(await lock.refresh())) throw new Error(\"Lost entity canonical-id migration lock.\");\n const discoveredBefore = rejectMappingCycles(\n state.mappings,\n await discoverMappings(deps, state.mappings, blocked, scanNotes),\n );\n const revivedBefore = await pruneBlocked();\n const mergedBefore = collapseMappings(mergeDiscoveredMappings(state.mappings, discoveredBefore));\n // Stable completed journal → no-op (issue #2213). Retained mappings are\n // read-compat state whose references were already rewritten (to\n // convergence) before `complete: true` was persisted, so re-running\n // `rewriteKnownReferences` here re-read the ENTIRE hot+cold+archived\n // corpus — twice — and invalidated every memory cache on EVERY\n // `ensureDirectories()`. On a write-active daemon that was a\n // self-sustaining hot loop (multi-GB/s of re-reads, caches never warm,\n // recall degraded to the fallback path). A park revived by THIS run's\n // pruneBlocked still owes its reference rewrite: it falls through to\n // the main flow below, which demotes `complete` BEFORE rewriting — so\n // a crash mid-rewrite resumes, which the old in-place rewrite did not.\n if (\n state.complete\n && Object.keys(discoveredBefore).length === 0\n && !mappingsChanged\n && !revivedBefore\n ) {\n // Stable journal, but a raw-byte writer requested reconciliation:\n // run ONE bounded rewrite pass over the retained mappings, then let\n // finish() consume the marker.\n if (reconcileRequested && Object.keys(state.mappings).length > 0) {\n const rewroteColdMemory = await rewriteKnownReferences(\n deps,\n state.mappings,\n () => lock.refresh(),\n );\n deps.invalidateKnowledgeIndexCache();\n deps.invalidateAllMemoriesCache();\n if (rewroteColdMemory) deps.invalidateColdMemoriesCache();\n deps.bumpMemoryStatusVersion();\n }\n return finish();\n }\n if (\n state.complete\n || Object.keys(discoveredBefore).length > 0\n || Object.keys(mergedBefore).length !== Object.keys(state.mappings).length\n ) {\n state = { ...state, complete: false, mappings: mergedBefore };\n await publishState(deps, state);\n }\n if (Object.keys(state.mappings).length === 0) {\n if (!(await lock.refresh())) throw new Error(\"Lost entity canonical-id migration lock.\");\n const discoveredAfter = rejectMappingCycles(\n state.mappings,\n await discoverMappings(deps, state.mappings, blocked, scanNotes),\n );\n // Reconcile before declaring completion: this rescan can resolve a\n // park (another writer removed the legacy file), and completing\n // without promoting it strands its references behind a fingerprint\n // that already reflects the deletion - so no later run revisits it.\n const revivedInRescan = await pruneBlocked();\n if (Object.keys(discoveredAfter).length === 0 && !revivedInRescan) {\n await publishState(deps, { ...state, complete: true });\n return finish();\n }\n state = {\n ...state,\n complete: false,\n mappings: collapseMappings(mergeDiscoveredMappings(state.mappings, discoveredAfter)),\n };\n await publishState(deps, state);\n continue;\n }\n let pendingMappings = Object.entries(state.mappings);\n while (pendingMappings.length > 0) {\n const deferredMappings: Array<[string, string]> = [];\n let progressed = false;\n for (const [legacyId, canonicalId] of pendingMappings) {\n const result = await migrateEntityFilePair(\n deps,\n legacyId,\n canonicalId,\n () => lock.refresh(),\n );\n if (result.blocked) blocked.set(legacyId, canonicalId);\n else if (result.deferred) deferredMappings.push([legacyId, canonicalId]);\n progressed ||= result.progressed;\n }\n if (deferredMappings.length === 0) break;\n if (!progressed) {\n // Deferred means both files are missing: no later pass can help\n // these pairs, so park them all instead of Fatal-exiting.\n for (const [legacyId, canonicalId] of deferredMappings) {\n blocked.set(legacyId, canonicalId);\n staleParked.set(legacyId, canonicalId);\n }\n break;\n }\n pendingMappings = deferredMappings;\n }\n await pruneBlocked();\n\n const rewroteColdMemory = await rewriteKnownReferences(\n deps,\n state.mappings,\n () => lock.refresh(),\n );\n deps.invalidateKnowledgeIndexCache();\n deps.invalidateAllMemoriesCache();\n if (rewroteColdMemory) deps.invalidateColdMemoriesCache();\n deps.bumpMemoryStatusVersion();\n\n if (!(await lock.refresh())) throw new Error(\"Lost entity canonical-id migration lock.\");\n const discoveredAfter = rejectMappingCycles(\n state.mappings,\n await discoverMappings(deps, state.mappings, blocked, scanNotes),\n );\n // A park resolved after the rewrite pass is promoted here, and a\n // promotion still has to be APPLIED. Completing on `discoveredAfter`\n // alone fingerprints the post-deletion corpus, so the runner never\n // retries and those references stay stranded.\n const revivedAfterRewrite = await pruneBlocked();\n if (Object.keys(discoveredAfter).length === 0 && !revivedAfterRewrite) {\n await publishState(deps, { ...state, complete: true });\n return finish();\n }\n state = {\n ...state,\n complete: false,\n mappings: collapseMappings(mergeDiscoveredMappings(state.mappings, discoveredAfter)),\n };\n await publishState(deps, state);\n }\n throw new Error(\"Entity canonical-id migration mappings changed while migration was running; retry migration.\");\n }\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,SAAS,YAAY,kBAAkB;AACvC;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,QAAQ;AAAA,EACR;AAAA,EACA;AAAA,OACK;AACP,OAAO,UAAU;AAEjB,SAAS,SAAS;AAuBlB,IAAM,wCAAwC;AAC9C,IAAM,8CAA8C;AACpD,IAAM,iDAAiD;AACvD,IAAM,0BAA0B;AAChC,IAAM,6BAA6B;AACnC,IAAM,4BAA4B;AAClC,IAAM,mCAAmC;AAEzC,IAAM,wCAAwC,EAAE,OAAO;AAAA,EACrD,SAAS,EAAE,QAAQ,qCAAqC;AAAA,EACxD,UAAU,EAAE,QAAQ;AAAA,EACpB,UAAU,EAAE,OAAO,EAAE,OAAO,GAAG,EAAE,OAAO,EAAE,IAAI,CAAC,CAAC;AAAA,EAChD,uBAAuB,EAAE,OAAO,EAAE,SAAS;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQ3C,SAAS,EAAE,OAAO,EAAE,OAAO,GAAG,EAAE,OAAO,EAAE,IAAI,CAAC,CAAC,EAAE,SAAS;AAC5D,CAAC;AAkCD,eAAe,gBAAgB,UAAmC;AAChE,MAAI;AACJ,MAAI;AACF,eAAW,MAAM,MAAM,QAAQ;AAAA,EACjC,SAAS,OAAO;AACd,QAAI,CAAC,YAAY,OAAO,QAAQ,EAAG,OAAM;AAAA,EAC3C;AACA,SAAO,WACH,GAAG,SAAS,GAAG,IAAI,SAAS,GAAG,IAAI,SAAS,OAAO,IAAI,SAAS,OAAO,IAAI,SAAS,IAAI,KACxF;AACN;AAEA,eAAe,yBAAyB,aAAsC;AAC5E,MAAI;AACJ,MAAI;AACF,aAAS,MAAM,QAAQ,WAAW,GAC/B,OAAO,CAAC,SAAS,KAAK,QAAQ,IAAI,MAAM,SAAS,CAAC,+BAA+B,IAAI,CAAC,EACtF,KAAK;AAAA,EACV,SAAS,OAAO;AACd,QAAI,YAAY,OAAO,QAAQ,EAAG,QAAO;AACzC,UAAM;AAAA,EACR;AACA,QAAM,OAAO,WAAW,QAAQ;AAChC,aAAW,QAAQ,OAAO;AACxB,SAAK,OAAO,GAAG,KAAK,MAAM,IAAI,IAAI,GAAG;AACrC,QAAI;AACF,YAAM,QAAQ,MAAM,MAAM,KAAK,KAAK,aAAa,IAAI,GAAG,EAAE,QAAQ,KAAK,CAAC;AACxE,WAAK,OAAO,GAAG,MAAM,GAAG,IAAI,MAAM,GAAG,IAAI,MAAM,IAAI,IAAI,MAAM,OAAO,IAAI,MAAM,OAAO,EAAE;AAAA,IACzF,SAAS,OAAO;AACd,UAAI,CAAC,YAAY,OAAO,QAAQ,EAAG,OAAM;AACzC,WAAK,OAAO,SAAS;AAAA,IACvB;AACA,SAAK,OAAO,IAAI;AAAA,EAClB;AACA,SAAO,KAAK,OAAO,KAAK;AAC1B;AAQA,eAAsB,eACpB,SACA,aACA,0BACiB;AACjB,QAAM;AAAA,IACJ;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,IAAI,MAAM,QAAQ,IAAI;AAAA,IACpB,gBAAgB,WAAW;AAAA,IAC3B,yBAAyB,WAAW;AAAA,IACpC,gBAAgB,KAAK,KAAK,SAAS,UAAU,cAAc,CAAC;AAAA,IAC5D,gBAAgB,KAAK,KAAK,SAAS,SAAS,oCAAoC,CAAC;AAAA,IACjF,gBAAgB,KAAK,KAAK,SAAS,SAAS,8CAA8C,CAAC;AAAA,EAC7F,CAAC;AACD,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,yBAAyB;AAAA,EAC3B,EAAE,KAAK,GAAG;AACZ;AAEA,eAAsB,cACpB,SACA,aACA,UACA,YACe;AACf,MAAI;AACJ,MAAI;AACF,eAAW,MAAM,MAAM,OAAO;AAAA,EAChC,SAAS,OAAO;AACd,QAAI,YAAY,OAAO,QAAQ,EAAG;AAClC,UAAM;AAAA,EACR;AACA,MAAI,SAAS,eAAe,KAAK,CAAC,SAAS,YAAY,GAAG;AACxD,UAAM,IAAI,MAAM,sEAAsE,OAAO,GAAG;AAAA,EAClG;AACA,QAAM,aAAa,MAAM,SAAS,OAAO;AACzC,QAAM,QAAQ;AAAA,IACZ;AAAA,IACA;AAAA,IACA,GAAG,qBAAqB,IAAI,CAAC,cAAc,KAAK,KAAK,SAAS,SAAS,CAAC;AAAA,IACxE,KAAK,KAAK,SAAS,MAAM;AAAA,IACzB;AAAA,EACF;AACA,aAAW,QAAQ,OAAO;AACxB,QAAI;AACJ,QAAI;AACF,iBAAW,MAAM,MAAM,IAAI;AAAA,IAC7B,SAAS,OAAO;AACd,UAAI,YAAY,OAAO,QAAQ,EAAG;AAClC,YAAM;AAAA,IACR;AACA,QAAI,SAAS,eAAe,KAAK,CAAC,SAAS,YAAY,GAAG;AACxD,YAAM,IAAI,MAAM,sEAAsE,IAAI,GAAG;AAAA,IAC/F;AACA,yBAAqB,YAAY,MAAM,SAAS,IAAI,GAAG,IAAI;AAAA,EAC7D;AACF;AAEA,SAAS,UAAU,MAAsD;AACvE,SAAO,KAAK,KAAK,KAAK,UAAU,kCAAkC;AACpE;AAEA,eAAe,6BACb,MACe;AACf,QAAM,qBAAqB,UAAU,IAAI;AACzC,MAAI;AACJ,MAAI;AACF,eAAW,MAAM,MAAM,kBAAkB;AAAA,EAC3C,SAAS,OAAO;AACd,QAAI,YAAY,OAAO,QAAQ,EAAG;AAClC,UAAM;AAAA,EACR;AACA,MAAI,SAAS,eAAe,KAAK,CAAC,SAAS,OAAO,GAAG;AACnD,UAAM,IAAI;AAAA,MACR,+EAA+E,kBAAkB;AAAA,IACnG;AAAA,EACF;AACA,QAAM,CAAC,WAAW,SAAS,IAAI,MAAM,QAAQ,IAAI;AAAA,IAC/C,SAAS,KAAK,QAAQ;AAAA,IACtB,SAAS,kBAAkB;AAAA,EAC7B,CAAC;AACD,uBAAqB,WAAW,WAAW,kBAAkB;AAC/D;AACA,eAAe,wBACb,MACA,eACkB;AAClB,MAAI;AACJ,MAAI;AACF,eAAW,MAAM,MAAM,aAAa;AAAA,EACtC,SAAS,OAAO;AACd,QAAI,YAAY,OAAO,QAAQ,EAAG,QAAO;AACzC,UAAM;AAAA,EACR;AACA,MAAI,SAAS,eAAe,KAAK,CAAC,SAAS,OAAO,GAAG;AACnD,UAAM,IAAI;AAAA,MACR,2EAA2E,aAAa;AAAA,IAC1F;AAAA,EACF;AACA,QAAM,CAAC,WAAW,aAAa,IAAI,MAAM,QAAQ,IAAI;AAAA,IACnD,SAAS,KAAK,QAAQ;AAAA,IACtB,SAAS,aAAa;AAAA,EACxB,CAAC;AACD,uBAAqB,WAAW,eAAe,aAAa;AAC5D,SAAO;AACT;AAEA,SAAS,SAAS,MAAsD;AACtE,SAAO,KAAK,KAAK,KAAK,UAAU,oCAAoC;AACtE;AAGA,eAAe,UACb,MACiD;AACjD,QAAM,6BAA6B,IAAI;AACvC,MAAI;AACJ,MAAI;AACF,UAAM,MAAM,SAAS,UAAU,IAAI,GAAG,OAAO;AAAA,EAC/C,SAAS,OAAO;AACd,QAAI,YAAY,OAAO,QAAQ,EAAG,QAAO;AACzC,UAAM;AAAA,EACR;AAEA,MAAI;AACJ,MAAI;AACF,aAAS,KAAK,MAAM,GAAG;AAAA,EACzB,QAAQ;AACN,UAAM,IAAI,MAAM,8CAA8C;AAAA,EAChE;AACA,QAAM,SAAS,sCAAsC,UAAU,MAAM;AACrE,MAAI,CAAC,OAAO,QAAS,OAAM,IAAI,MAAM,8CAA8C;AACnF,aAAW,YAAY,CAAC,OAAO,KAAK,UAAU,OAAO,KAAK,WAAW,CAAC,CAAC,GAAG;AACxE,eAAW,CAAC,UAAU,WAAW,KAAK,OAAO,QAAQ,QAAQ,GAAG;AAC9D,yBAAmB,QAAQ;AAC3B,yBAAmB,WAAW;AAAA,IAChC;AAAA,EACF;AACA,SAAO,OAAO;AAChB;AAEA,eAAe,WACb,MACA,OACe;AACf,QAAM,cAAc,UAAU,IAAI;AAClC,QAAM,YAAY,GAAG,WAAW,IAAI,QAAQ,GAAG,IAAI,WAAW,CAAC;AAC/D,MAAI;AACF,UAAM,UAAU,WAAW,GAAG,KAAK,UAAU,OAAO,MAAM,CAAC,CAAC;AAAA,GAAM,OAAO;AACzE,UAAM,OAAO,WAAW,WAAW;AAAA,EACrC,UAAE;AACA,UAAM,OAAO,SAAS,EAAE,MAAM,MAAM,MAAS;AAAA,EAC/C;AACF;AAUA,eAAe,aACb,MACA,OACe;AACf,QAAM,gCAAgC,KAAK,UAAU,YAAY;AAC/D,UAAM,WAAW,MAAM,KAAK;AAAA,EAC9B,CAAC;AACH;AAEA,eAAe,WAAW,UAAoC;AAC5D,MAAI;AACF,UAAM,OAAO,QAAQ;AACrB,WAAO;AAAA,EACT,SAAS,OAAO;AACd,QAAI,YAAY,OAAO,QAAQ,EAAG,QAAO;AACzC,UAAM;AAAA,EACR;AACF;AAEA,eAAe,iBAAiB,UAAkB,WAAqC;AACrF,MAAI;AACF,UAAM,CAAC,MAAM,KAAK,IAAI,MAAM,QAAQ,IAAI,CAAC,SAAS,QAAQ,GAAG,SAAS,SAAS,CAAC,CAAC;AACjF,WAAO,KAAK,QAAQ,MAAM,OAAO,KAAK,QAAQ,MAAM;AAAA,EACtD,SAAS,OAAO;AACd,QAAI,YAAY,OAAO,QAAQ,EAAG,QAAO;AACzC,UAAM;AAAA,EACR;AACF;AAEA,eAAe,iBAAiB,UAAkB,OAA8B;AAC9E,MAAI;AACJ,MAAI;AACF,WAAO,MAAM,MAAM,QAAQ;AAAA,EAC7B,SAAS,OAAO;AACd,QAAI,YAAY,OAAO,QAAQ,EAAG;AAClC,UAAM;AAAA,EACR;AACA,MAAI,KAAK,eAAe,GAAG;AACzB,UAAM,IAAI,MAAM,2DAA2D,KAAK,GAAG;AAAA,EACrF;AACF;AACA,eAAe,uBAAuB,UAAiC;AACrE,MAAI;AACJ,MAAI;AACF,WAAO,MAAM,MAAM,QAAQ;AAAA,EAC7B,SAAS,OAAO;AACd,QAAI,YAAY,OAAO,QAAQ,EAAG;AAClC,UAAM;AAAA,EACR;AACA,MAAI,KAAK,eAAe,GAAG;AACzB,UAAM,IAAI,MAAM,oEAAoE,QAAQ,GAAG;AAAA,EACjG;AACF;AACA,eAAe,6BACb,MACA,UACA,iBACA,UACe;AACf,QAAM,gBAAgB,MAAM,KAAK,sBAAsB,QAAQ;AAC/D,MAAI,kBAAkB,iBAAiB;AACrC,UAAM,IAAI,MAAM,oBAAoB,QAAQ,wDAAwD;AAAA,EACtG;AACF;AASA,eAAsB,sBAAsB,aAAwC;AAClF,QAAM,WAAW,MAAM,MAAM,WAAW;AACxC,MAAI,SAAS,eAAe,KAAK,CAAC,SAAS,YAAY,GAAG;AACxD,UAAM,IAAI,MAAM,4FAA4F;AAAA,EAC9G;AACA,QAAM,UAAU,MAAM,QAAQ,WAAW;AACzC,aAAW,SAAS,SAAS;AAC3B,UAAM,iBAAiB,KAAK,KAAK,aAAa,KAAK,GAAG,KAAK;AAAA,EAC7D;AACA,SAAO;AACT;AAgBA,eAAe,iBACb,MACA,gBAAkD,CAAC,GACnD,UAA8B,oBAAI,IAAI,GACtC,WACiC;AACjC,QAAM,WAAmC,CAAC;AAC1C,QAAM,kBAAkB,oBAAI,IAAsB;AAMlD,UAAQ,MAAM;AACd,QAAM,gBAAgB,MAAM,sBAAsB,KAAK,WAAW;AAClE,aAAW,SAAS,eAAe;AAMjC,QAAI,+BAA+B,KAAK,GAAG;AACzC,iBAAW,KAAK,IAAI,KAAK;AACzB;AAAA,IACF;AACA,QAAI,CAAC,MAAM,SAAS,KAAK,EAAG;AAC5B,UAAM,WAAW,MAAM,MAAM,GAAG,CAAC,MAAM,MAAM;AAC7C,uBAAmB,QAAQ;AAC3B,UAAM,aAAa,KAAK,sBAAsB,QAAQ;AACtD,QAAI,eAAe,KAAM;AACzB,UAAM,gBAAgB,MAAM,KAAK,sBAAsB,UAAU;AACjE,QAAI,wBAAwB,aAAa,GAAG;AAC1C,iBAAW,UAAU,IAAI,KAAK;AAC9B;AAAA,IACF;AACA,UAAM,SAAS,KAAK,gBAAgB,aAAa;AACjD,QAAI,CAAC,OAAO,KAAK,KAAK,KAAK,CAAC,OAAO,KAAK,KAAK,GAAG;AAC9C,iBAAW,UAAU,IAAI,KAAK;AAC9B;AAAA,IACF;AACA,UAAM,cAAc,KAAK,oBAAoB,OAAO,MAAM,OAAO,IAAI;AACrE,uBAAmB,WAAW;AAC9B,QAAI,aAAa,YAAa;AAC9B,UAAM,gBAAgB,KAAK,sBAAsB,WAAW;AAC5D,QAAI,kBAAkB,KAAM;AAC5B,UAAM,kBAAkB,MAAM,WAAW,aAAa;AACtD,QACE,mBACG,MAAM,iBAAiB,YAAY,aAAa,KAChD,cAAc,QAAQ,MAAM,YAC/B;AACF,QAAI,iBAAiB;AACnB,YAAM,mBAAmB,MAAM,KAAK,sBAAsB,aAAa;AACvE,UAAI,kBAAkB,kBAAkB;AACtC,gBAAQ,IAAI,UAAU,WAAW;AACjC;AAAA,MACF;AAAA,IACF;AACA,oBAAgB,IAAI,aAAa,CAAC,GAAI,gBAAgB,IAAI,WAAW,KAAK,CAAC,GAAI,QAAQ,CAAC;AACxF,aAAS,QAAQ,IAAI;AAAA,EACvB;AAIA,aAAW,CAAC,aAAa,SAAS,KAAK,iBAAiB;AACtD,QAAI,UAAU,SAAS,EAAG;AAC1B,eAAW,YAAY,WAAW;AAChC,cAAQ,IAAI,UAAU,WAAW;AACjC,aAAO,SAAS,QAAQ;AAAA,IAC1B;AAAA,EACF;AACA,SAAO;AACT;AAEA,eAAe,kBACb,MACA,UACA,aACyC;AACzC,MAAI,oBAAoB;AACxB,WAAS,OAAO,GAAG,OAAO,2BAA2B,QAAQ,GAAG;AAC9D,SAAK,2BAA2B;AAChC,SAAK,4BAA4B;AACjC,UAAM,CAAC,aAAa,cAAc,gBAAgB,IAAI,MAAM,QAAQ,IAAI;AAAA,MACtE,KAAK,gBAAgB;AAAA,MACrB,KAAK,oBAAoB;AAAA,MACzB,KAAK,qBAAqB;AAAA,IAC5B,CAAC;AACD,UAAM,YAAY,oBAAI,IAAY;AAClC,QAAI,aAAa;AACjB,eAAW,UAAU,CAAC,GAAG,aAAa,GAAG,cAAc,GAAG,gBAAgB,GAAG;AAC3E,YAAM,aAAa,KAAK,QAAQ,OAAO,IAAI;AAC3C,UAAI,UAAU,IAAI,UAAU,EAAG;AAC/B,gBAAU,IAAI,UAAU;AACxB,YAAM,oBAAoB,OAAO,YAAY;AAC7C,YAAM,cAAc,oBAAoB,SAAS,iBAAiB,IAAI;AACtE,UAAI,CAAC,qBAAqB,CAAC,YAAa;AACxC,YAAM,uBAAuB,OAAO,IAAI;AACxC,YAAM,4BAA4B,MAAM,gBAAgB,OAAO,IAAI;AACnE,UAAI,8BAA8B,UAAW;AAC7C,UAAI,CAAE,MAAM,YAAY,EAAI,OAAM,IAAI,MAAM,0CAA0C;AACtF,YAAM,eAAe,MAAM,KAAK,iBAAiB,OAAO,IAAI;AAC5D,UAAI,CAAC,aAAc;AACnB,YAAM,0BAA0B,MAAM,gBAAgB,aAAa,IAAI;AACvE,UAAI,4BAA4B,0BAA2B;AAC3D,YAAM,0BAA0B,aAAa,YAAY;AACzD,YAAM,oBAAoB,0BAA0B,SAAS,uBAAuB,IAAI;AACxF,UAAI,CAAC,2BAA2B,CAAC,kBAAmB;AACpD,YAAM,kBAAkB,MAAM,KAAK,uBAAuB,aAAa,IAAI;AAC3E,UAAK,MAAM,gBAAgB,aAAa,IAAI,MAAO,wBAAyB;AAC5E,YAAM,KAAK,oBAAoB,aAAa,MAAM,OAAO;AACzD,UAAK,MAAM,gBAAgB,aAAa,IAAI,MAAO,wBAAyB;AAC5E,YAAM,KAAK;AAAA,QACT,aAAa;AAAA,QACb,KAAK,6BAA6B,cAAc,iBAAiB;AAAA,QACjE;AAAA,MACF;AACA,YAAM,KAAK;AAAA,QACT,aAAa,YAAY;AAAA,QACzB;AAAA,QACA;AAAA,MACF;AACA,mBAAa;AACb,4BAAsB,aAAa,KAAK,SAAS,GAAG,KAAK,GAAG,OAAO,KAAK,GAAG,EAAE;AAAA,IAC/E;AACA,QAAI,CAAC,WAAY,QAAO,EAAE,kBAAkB;AAAA,EAC9C;AACA,QAAM,IAAI,MAAM,6FAA6F;AAC/G;AAEA,SAAS,qBACP,MACA,UACA,oBACQ;AACR,MAAI;AACJ,MAAI;AACF,aAAS,KAAK,MAAM,IAAI;AAAA,EAC1B,QAAQ;AACN,WAAO;AAAA,EACT;AACA,MAAI,CAAC,UAAU,OAAO,WAAW,YAAY,MAAM,QAAQ,MAAM,EAAG,QAAO;AAE3E,QAAM,YAAY;AAClB,MAAI,UAAU;AACd,MAAI,OAAO,UAAU,cAAc,UAAU;AAC3C,UAAM,cAAc,SAAS,UAAU,SAAS;AAChD,QAAI,aAAa;AACf,gBAAU,YAAY;AACtB,gBAAU;AAAA,IACZ;AAAA,EACF;AACA,MAAI,OAAO,UAAU,oBAAoB,UAAU;AACjD,UAAM,YAAY,UAAU,gBAAgB,QAAQ,IAAI;AACxD,QAAI,YAAY,EAAG,QAAO,UAAU,KAAK,UAAU,SAAS,IAAI;AAChE,UAAM,qBAAqB,yBAAyB,UAAU,gBAAgB,MAAM,GAAG,SAAS,CAAC;AACjG,UAAM,cAAc,mBAAmB,IAAI,kBAAkB;AAC7D,QAAI,aAAa;AACf,YAAM,eAAe,uBAAuB,aAAa,UAAU,gBAAgB,MAAM,YAAY,CAAC,CAAC;AACvG,UAAI,cAAc;AAChB,kBAAU,kBAAkB;AAC5B,kBAAU;AAAA,MACZ;AAAA,IACF;AAAA,EACF;AACA,SAAO,UAAU,KAAK,UAAU,SAAS,IAAI;AAC/C;AAEA,eAAe,2BACb,MACA,UACA,aACe;AACf,QAAM,gBAAgB,KAAK,KAAK,KAAK,UAAU,kBAAkB;AACjE,QAAM,qBAAqB,oBAAI,IAAoB;AACnD,aAAW,CAAC,UAAU,WAAW,KAAK,OAAO,QAAQ,QAAQ,GAAG;AAC9D,UAAM,qBAAqB,yBAAyB,QAAQ;AAC5D,QAAI,sBAAsB,CAAC,mBAAmB,IAAI,kBAAkB,GAAG;AACrE,yBAAmB,IAAI,oBAAoB,WAAW;AAAA,IACxD;AAAA,EACF;AAEA,QAAM;AAAA,IACJ,KAAK,KAAK,KAAK,UAAU,iBAAiB;AAAA,IAC1C;AAAA,MACE,SAAS;AAAA,MACT,WAAW;AAAA,IACb;AAAA,IACA,OAAO,UAAU,kBAAkB;AACjC,UAAI,CAAC,SAAU,OAAM,IAAI,MAAM,mDAAmD;AAClF,UAAI,CAAE,MAAM,wBAAwB,MAAM,aAAa,EAAI;AAC3D,YAAM,qBAAqB,MAAM,KAAK,uBAAuB,aAAa;AAC1E,YAAM,WAAW,MAAM,KAAK,sBAAsB,aAAa;AAC/D,UAAI,UAAU;AACd,YAAM,YAAY,SACf,MAAM,IAAI,EACV,IAAI,CAAC,SAAS;AACb,cAAM,OAAO,qBAAqB,MAAM,UAAU,kBAAkB;AACpE,oBAAY,SAAS;AACrB,eAAO;AAAA,MACT,CAAC,EACA,KAAK,IAAI;AACZ,UAAI,CAAC,QAAS;AACd,UAAI,CAAE,MAAM,YAAY,EAAI,OAAM,IAAI,MAAM,0CAA0C;AACtF,UAAI,CAAE,MAAM,cAAc,QAAQ,EAAI,OAAM,IAAI,MAAM,gCAAgC;AACtF,YAAM,KAAK,oBAAoB,eAAe,OAAO;AACrD,YAAM,KAAK,uBAAuB,eAAe,WAAW,kBAAkB;AAAA,IAChF;AAAA,EACF;AACF;AAEA,eAAe,2BACb,MACA,UACA,aACkB;AAClB,MAAI,YAAY;AAChB,QAAM,UAAU,MAAM,sBAAsB,KAAK,WAAW;AAC5D,aAAW,SAAS,SAAS;AAC3B,QAAI,CAAC,MAAM,SAAS,KAAK,KAAK,+BAA+B,KAAK,EAAG;AACrE,UAAM,aAAa,KAAK,sBAAsB,MAAM,MAAM,GAAG,CAAC,MAAM,MAAM,CAAC;AAC3E,QAAI,eAAe,KAAM;AACzB,UAAM,4BAA4B,MAAM,gBAAgB,UAAU;AAClE,QAAI,8BAA8B,UAAW;AAC7C,UAAM,YAAY,MAAM,KAAK,sBAAsB,UAAU;AAC7D,QAAI,wBAAwB,SAAS,EAAG;AACxC,UAAM,SAAS,KAAK,gBAAgB,SAAS;AAC7C,UAAM,gBAAgB,OAAO,cAAc,IAAI,CAAC,iBAAiB;AAC/D,YAAM,SAAS,SAAS,aAAa,MAAM;AAC3C,aAAO,SAAS,EAAE,GAAG,cAAc,OAAO,IAAI;AAAA,IAChD,CAAC;AACD,QAAI,cAAc,MAAM,CAAC,cAAc,UAAU,iBAAiB,OAAO,cAAc,KAAK,CAAC,EAAG;AAChG,UAAM,gBAAgB,MAAM;AAAA,MAC1B,KAAK;AAAA,MACL,OAAO,sBAAsB;AAC3B,YAAI,CAAE,MAAM,YAAY,EAAI,OAAM,IAAI,MAAM,0CAA0C;AACtF,cAAM,YAAY,MAAM,KAAK,sBAAsB,UAAU;AAC7D,YAAI,wBAAwB,SAAS,EAAG,QAAO;AAC/C,cAAM,eAAe,KAAK,gBAAgB,SAAS;AACnD,YAAK,MAAM,gBAAgB,UAAU,MAAO,0BAA2B,QAAO;AAC9E,cAAM,sBAAsB,aAAa,cAAc,IAAI,CAAC,iBAAiB;AAC3E,gBAAM,SAAS,SAAS,aAAa,MAAM;AAC3C,iBAAO,SAAS,EAAE,GAAG,cAAc,OAAO,IAAI;AAAA,QAChD,CAAC;AACD,YAAI,oBAAoB,MAAM,CAAC,cAAc,UAAU,iBAAiB,aAAa,cAAc,KAAK,CAAC,GAAG;AAC1G,iBAAO;AAAA,QACT;AACA,cAAM,kBAAkB,MAAM,KAAK,uBAAuB,UAAU;AACpE,YAAI,CAAE,MAAM,kBAAkB,EAAI,OAAM,IAAI,MAAM,4BAA4B;AAC9E,YAAK,MAAM,gBAAgB,UAAU,MAAO,0BAA2B,QAAO;AAC9E,cAAM,KAAK,oBAAoB,YAAY,OAAO;AAClD,YAAK,MAAM,gBAAgB,UAAU,MAAO,0BAA2B,QAAO;AAC9E,cAAM,KAAK;AAAA,UACT;AAAA,UACA,KAAK,oBAAoB,EAAE,GAAG,cAAc,eAAe,oBAAoB,CAAC;AAAA,UAChF;AAAA,QACF;AACA,eAAO;AAAA,MACT;AAAA,IACF;AACA,kBAAc;AAAA,EAChB;AACA,SAAO;AACT;AAEA,eAAe,uBACb,MACA,UACA,aACkB;AAClB,QAAM,2BAA2B,MAAM,UAAU,WAAW;AAC5D,MAAI,qBAAqB,MAAM,kBAAkB,MAAM,UAAU,WAAW,GAAG;AAC/E,QAAM,2BAA2B,MAAM,UAAU,WAAW;AAC5D,QAAM,KAAK,iCAAiC,QAAQ;AACpD,QAAM,kBAAkB,MAAM,kBAAkB,MAAM,UAAU,WAAW;AAC3E,wBAAsB,gBAAgB;AACtC,SAAO;AACT;AAEA,SAAS,wBACP,UACA,UACA,aACS;AACT,MAAI,UAAU;AACd,QAAM,OAAO,oBAAI,IAAY;AAC7B,SAAO,MAAM;AACX,QAAI,YAAY,SAAU,QAAO;AACjC,QAAI,KAAK,IAAI,OAAO,EAAG,QAAO;AAC9B,SAAK,IAAI,OAAO;AAChB,UAAM,OAAO,SAAS,OAAO;AAC7B,QAAI,CAAC,QAAQ,SAAS,QAAS,QAAO;AACtC,cAAU;AAAA,EACZ;AACF;AAEA,SAAS,oBACP,UACA,YACwB;AACxB,QAAM,WAAmC,CAAC;AAC1C,QAAM,SAAS,EAAE,GAAG,SAAS;AAC7B,aAAW,CAAC,UAAU,WAAW,KAAK,OAAO,QAAQ,UAAU,GAAG;AAChE,QACE,OAAO,QAAQ,MAAM,UACrB,wBAAwB,QAAQ,UAAU,WAAW,EACrD;AACF,aAAS,QAAQ,IAAI;AACrB,WAAO,QAAQ,IAAI;AAAA,EACrB;AACA,SAAO;AACT;AAEA,SAAS,wBACP,UACA,YACwB;AACxB,QAAM,SAAS,EAAE,GAAG,SAAS;AAC7B,QAAM,kBAAkB,oBAAI,IAAoB;AAChD,aAAW,CAAC,UAAU,WAAW,KAAK,OAAO,QAAQ,UAAU,GAAG;AAChE,UAAM,kBAAkB,OAAO,QAAQ;AACvC,QAAI,oBAAoB,UAAa,oBAAoB,aAAa;AACpE;AAAA,IACF;AACA,QAAI,oBAAoB,UAAa,wBAAwB,QAAQ,UAAU,WAAW,EAAG;AAC7F,UAAM,gBAAgB,gBAAgB,IAAI,WAAW;AACrD,QAAI,kBAAkB,UAAa,kBAAkB,UAAU;AAC7D,YAAM,IAAI;AAAA,QACR,oCAAoC,aAAa,QAAQ,QAAQ,uBAAuB,WAAW;AAAA,MACrG;AAAA,IACF;AACA,WAAO,QAAQ,IAAI;AACnB,oBAAgB,IAAI,aAAa,QAAQ;AAAA,EAC3C;AACA,SAAO;AACT;AACA,SAAS,iBAAiB,UAAoE;AAC5F,QAAM,YAAoC,CAAC;AAC3C,aAAW,YAAY,OAAO,KAAK,QAAQ,GAAG;AAC5C,QAAI,UAAU;AACd,UAAM,OAAO,oBAAI,IAAY;AAC7B,WAAO,MAAM;AACX,YAAM,OAAO,SAAS,OAAO;AAC7B,UAAI,CAAC,QAAQ,SAAS,QAAS;AAC/B,UAAI,KAAK,IAAI,OAAO,GAAG;AACrB,cAAM,IAAI,MAAM,wDAAwD,QAAQ,GAAG;AAAA,MACrF;AACA,WAAK,IAAI,OAAO;AAChB,gBAAU;AAAA,IACZ;AACA,QAAI,YAAY,SAAU,WAAU,QAAQ,IAAI;AAAA,EAClD;AACA,SAAO;AACT;AASA,eAAe,sBACb,MACA,UACA,aACA,sBACoC;AACpC,SAAO,gCAAgC,KAAK,UAAU,OAAO,sBAAsB;AACjF,uBAAmB,QAAQ;AAC3B,uBAAmB,WAAW;AAC9B,UAAM,aAAa,KAAK,sBAAsB,QAAQ;AACtD,UAAM,gBAAgB,KAAK,sBAAsB,WAAW;AAC5D,QAAI,eAAe,QAAQ,kBAAkB,MAAM;AACjD,YAAM,IAAI,MAAM,6CAA6C;AAAA,IAC/D;AACA,UAAM,iBAAiB,YAAY,QAAQ;AAC3C,UAAM,iBAAiB,eAAe,WAAW;AACjD,UAAM,eAAe,MAAM,WAAW,UAAU;AAChD,UAAM,kBAAkB,MAAM,WAAW,aAAa;AACtD,QAAI,CAAC,gBAAgB,CAAC,gBAAiB,QAAO,EAAE,UAAU,MAAM,YAAY,MAAM;AAClF,QAAI,gBAAgB,iBAAiB;AACnC,UAAI,MAAM,iBAAiB,YAAY,aAAa,EAAG,QAAO,EAAE,UAAU,OAAO,YAAY,MAAM;AACnG,YAAM,CAAC,eAAe,kBAAkB,iBAAiB,kBAAkB,IAAI,MAAM,QAAQ,IAAI;AAAA,QAC/F,KAAK,sBAAsB,UAAU;AAAA,QACrC,KAAK,sBAAsB,aAAa;AAAA,QACxC,KAAK,uBAAuB,UAAU;AAAA,QACtC,KAAK,uBAAuB,aAAa;AAAA,MAC3C,CAAC;AACD,UAAI,kBAAkB,kBAAkB;AAGtC,eAAO,EAAE,UAAU,OAAO,YAAY,OAAO,SAAS,KAAK;AAAA,MAC7D;AACA,UAAI,mBAAmB,CAAC,oBAAoB;AAC1C,YAAI,CAAE,MAAM,qBAAqB,KAAM,CAAE,MAAM,kBAAkB,GAAI;AACnE,gBAAM,IAAI,MAAM,0CAA0C;AAAA,QAC5D;AACA,cAAM,KAAK,oBAAoB,eAAe,OAAO;AACrD,cAAM,KAAK,uBAAuB,eAAe,eAAe,IAAI;AACpE,YAAK,MAAM,KAAK,sBAAsB,aAAa,MAAO,eAAe;AACvE,gBAAM,IAAI,MAAM,oCAAoC,WAAW,GAAG;AAAA,QACpE;AAAA,MACF;AACA,UAAI,CAAE,MAAM,qBAAqB,KAAM,CAAE,MAAM,kBAAkB,GAAI;AACnE,cAAM,IAAI,MAAM,0CAA0C;AAAA,MAC5D;AACA,YAAM,KAAK,oBAAoB,YAAY,OAAO;AAClD,YAAM,iBAAiB,YAAY,QAAQ;AAC3C,YAAM,6BAA6B,MAAM,YAAY,eAAe,QAAQ;AAC5E,YAAM,OAAO,UAAU;AACvB,aAAO,EAAE,UAAU,OAAO,YAAY,KAAK;AAAA,IAC7C;AACA,QAAI,CAAC,aAAc,QAAO,EAAE,UAAU,OAAO,YAAY,MAAM;AAC/D,QAAI,CAAE,MAAM,qBAAqB,KAAM,CAAE,MAAM,kBAAkB,GAAI;AACnE,YAAM,IAAI,MAAM,0CAA0C;AAAA,IAC5D;AACA,UAAM,kBAAkB,MAAM,KAAK,uBAAuB,UAAU;AACpE,UAAM,gBAAgB,MAAM,KAAK,sBAAsB,UAAU;AACjE,UAAM,iBAAiB,YAAY,QAAQ;AAC3C,UAAM,6BAA6B,MAAM,YAAY,eAAe,QAAQ;AAI5E,QACE,+BAA+B,KAAK,SAAS,UAAU,CAAC,KACrD,wBAAwB,aAAa,GACxC;AACA,aAAO,EAAE,UAAU,OAAO,YAAY,MAAM;AAAA,IAC9C;AACA,QAAI,iCAAgD;AACpD,QAAI;AACF,YAAM,KAAK,uBAAuB,eAAe,eAAe,eAAe;AAC/E,uCAAiC,MAAM,gBAAgB,aAAa;AACpE,UAAK,MAAM,KAAK,sBAAsB,aAAa,MAAO,eAAe;AACvE,cAAM,IAAI,MAAM,oCAAoC,WAAW,GAAG;AAAA,MACpE;AACA,UAAI,CAAE,MAAM,qBAAqB,KAAM,CAAE,MAAM,kBAAkB,GAAI;AACnE,cAAM,IAAI,MAAM,0CAA0C;AAAA,MAC5D;AACA,YAAM,KAAK,oBAAoB,YAAY,OAAO;AAClD,YAAM,iBAAiB,YAAY,QAAQ;AAC3C,YAAM,6BAA6B,MAAM,YAAY,eAAe,QAAQ;AAC5E,YAAM,OAAO,UAAU;AAAA,IACzB,SAAS,OAAO;AACd,UAAI,CAAC,mBAAmB,mCAAmC,MAAM;AAC/D,YAAI;AACF,cAAK,MAAM,gBAAgB,aAAa,MAAO,gCAAgC;AAC7E,kBAAM,iBAAiB,eAAe,WAAW;AACjD,kBAAM,OAAO,aAAa;AAAA,UAC5B;AAAA,QACF,QAAQ;AAAA,QAER;AAAA,MACF;AACA,YAAM;AAAA,IACR;AACA,WAAO,EAAE,UAAU,OAAO,YAAY,KAAK;AAAA,EAC7C,CAAC;AACH;AAEA,eAAsB,gCACpB,MAC6B;AAC7B,SAAO;AAAA,IACL,SAAS,IAAI;AAAA,IACb;AAAA,MACE,SAAS;AAAA,MACT,WAAW;AAAA,IACb;AAAA,IACA,OAAO,UAAU,SAAS;AACxB,UAAI,CAAC,SAAU,OAAM,IAAI,MAAM,6DAA6D;AAC5F,YAAM,KAAK,0BAA0B;AACrC,YAAM,sBAAsB,KAAK,WAAW;AAO5C,YAAM,sBAAsB,KAAK,KAAK,KAAK,UAAU,oCAAoC;AACzF,YAAM,sBAAsB,KAAK,KAAK,KAAK,UAAU,8CAA8C;AACnG,UAAI,qBAAqB,MAAM,WAAW,mBAAmB;AAC7D,UAAI,MAAM,WAAW,mBAAmB,GAAG;AACzC,YAAI;AACF,gBAAM,OAAO,qBAAqB,mBAAmB;AACrD,+BAAqB;AAAA,QACvB,SAAS,OAAO;AACd,cAAI,CAAC,YAAY,OAAO,QAAQ,EAAG,OAAM;AAAA,QAC3C;AAAA,MACF;AACA,UAAI,QAAyC,MAAM,UAAU,IAAI,KAAK;AAAA,QACpE,SAAS;AAAA,QACT,UAAU;AAAA,QACV,UAAU,CAAC;AAAA,MACb;AACA,YAAM,UAA8B,oBAAI,IAAI;AAC5C,YAAM,YAA6B,EAAE,MAAM,oBAAI,IAAI,GAAG,WAAW,oBAAI,IAAI,EAAE;AAK3E,YAAM,cAAkC,oBAAI,IAAI;AAGhD,YAAM,SAAS,YAAyC;AACtD,YAAI,QAAQ,OAAO,GAAG;AACpB,gBAAM,QAAQ,CAAC,GAAG,OAAO,EAAE,IAAI,CAAC,CAAC,UAAU,WAAW,MAAM,GAAG,QAAQ,OAAO,WAAW,EAAE,EAAE,KAAK,IAAI;AACtG,cAAI;AAAA,YACF,yCAAyC,QAAQ,IAAI,0BAA0B,KAAK;AAAA,UAEtF;AAAA,QACF;AACA,YAAI,YAAY,OAAO,GAAG;AACxB,gBAAM,QAAQ,CAAC,GAAG,WAAW,EAAE,IAAI,CAAC,CAAC,UAAU,WAAW,MAAM,GAAG,QAAQ,OAAO,WAAW,EAAE,EAAE,KAAK,IAAI;AAC1G,cAAI;AAAA,YACF,yCAAyC,YAAY,IAAI,4CAA4C,KAAK;AAAA,UAE5G;AAAA,QACF;AACA,wCAAgC,KAAK,aAAa,UAAU,KAAK,MAAM,CAAC,YAAY;AAClF,cAAI,KAAK,iCAAiC,OAAO,EAAE;AAAA,QACrD,CAAC;AACD,YAAI,UAAU,UAAU,OAAO,GAAG;AAChC,gBAAM,QAAQ,CAAC,GAAG,UAAU,SAAS,EAAE,KAAK,EAAE,KAAK,IAAI;AACvD,cAAI;AAAA,YACF,yCAAyC,UAAU,UAAU,IAAI,8BAA8B,KAAK;AAAA,UAEtG;AAAA,QACF;AAMA,YAAI,oBAAoB;AACtB,gBAAM,OAAO,mBAAmB,EAAE,MAAM,CAAC,UAAmB;AAC1D,gBAAI,CAAC,YAAY,OAAO,QAAQ,EAAG,OAAM;AAAA,UAC3C,CAAC;AAAA,QACH;AAWA,cAAM,wBAAwB,MAAM,KAAK,2BAA2B;AACpE,YAAI,MAAM,WAAW,mBAAmB,EAAG,QAAO;AAClD,YAAI,0BAA0B,UAAa,MAAM,0BAA0B,uBAAuB;AAChG,kBAAQ,EAAE,GAAG,OAAO,UAAU,MAAM,sBAAsB;AAC1D,gBAAM,aAAa,MAAM,KAAK;AAAA,QAChC;AACA,eAAO;AAAA,MACT;AASA,YAAM,eAAe,YAA8B;AACjD,cAAM,UAAU;AAChB,YAAI,CAAC,QAAS,QAAO;AAIrB,YAAI,UAAU;AACd,cAAM,SAAiC,EAAE,GAAI,QAAQ,WAAW,CAAC,EAAG;AACpE,cAAM,SAAiC,CAAC;AACxC,mBAAW,CAAC,UAAU,WAAW,KAAK,OAAO,QAAQ,QAAQ,QAAQ,GAAG;AACtE,cAAI,CAAC,QAAQ,IAAI,QAAQ,EAAG,QAAO,QAAQ,IAAI;AAAA,QACjD;AACA,mBAAW,CAAC,UAAU,WAAW,KAAK,QAAS,QAAO,QAAQ,IAAI;AASlE,mBAAW,CAAC,UAAU,WAAW,KAAK,OAAO,QAAQ,MAAM,GAAG;AAC5D,cAAI,QAAQ,IAAI,QAAQ,EAAG;AAC3B,iBAAO,OAAO,QAAQ;AACtB,cAAI,OAAO,QAAQ,MAAM,OAAW;AACpC,gBAAM,aAAa,KAAK,sBAAsB,QAAQ;AACtD,cAAI,eAAe,QAAS,MAAM,WAAW,UAAU,EAAI;AAK3D,cAAI,SAAS;AACb,gBAAM,cAAc,oBAAI,IAAY;AACpC,iBAAO,OAAO,MAAM,MAAM,UAAa,CAAC,YAAY,IAAI,MAAM,GAAG;AAC/D,wBAAY,IAAI,MAAM;AACtB,qBAAS,OAAO,MAAM;AAAA,UACxB;AACA,gBAAM,aAAa,KAAK,sBAAsB,MAAM;AACpD,cAAI,eAAe,QAAQ,CAAE,MAAM,WAAW,UAAU,GAAI;AAC1D,wBAAY,IAAI,UAAU,WAAW;AACrC;AAAA,UACF;AACA,iBAAO,QAAQ,IAAI;AACnB,oBAAU;AAAA,QACZ;AAIA,cAAM,YAAY,iBAAiB,MAAM;AAGzC,mBAAW,CAAC,UAAU,WAAW,KAAK,OAAO,QAAQ,MAAM,GAAG;AAC5D,gBAAM,QAAQ,UAAU,WAAW;AACnC,cAAI,UAAU,UAAa,UAAU,SAAU,QAAO,QAAQ,IAAI;AAAA,QACpE;AACA,cAAM,OAAO,CAAC,GAA2B,MACvC,OAAO,KAAK,CAAC,EAAE,WAAW,OAAO,KAAK,CAAC,EAAE,UACtC,OAAO,QAAQ,CAAC,EAAE,MAAM,CAAC,CAAC,KAAK,KAAK,MAAM,EAAE,GAAG,MAAM,KAAK;AAC/D,YAAI,KAAK,WAAW,QAAQ,QAAQ,KAAK,KAAK,QAAQ,QAAQ,WAAW,CAAC,CAAC,EAAG,QAAO;AAKrF,gBAAQ,EAAE,GAAG,SAAS,UAAU,WAAW,SAAS,OAAO;AAC3D,cAAM,aAAa,MAAM,KAAK;AAC9B,eAAO;AAAA,MACT;AACA,YAAM,mBAAmB,MAAM;AAC/B,YAAM,oBAAoB,iBAAiB,gBAAgB;AAC3D,YAAM,kBACJ,OAAO,KAAK,iBAAiB,EAAE,WAAW,OAAO,KAAK,gBAAgB,EAAE,UACrE,OAAO,QAAQ,iBAAiB,EAAE;AAAA,QAAK,CAAC,CAAC,UAAU,WAAW,MAC/D,iBAAiB,QAAQ,MAAM;AAAA,MACjC;AACF,cAAQ,EAAE,GAAG,OAAO,UAAU,kBAAkB;AAChD,eAAS,gBAAgB,GAAG,gBAAgB,kCAAkC,iBAAiB,GAAG;AAChG,YAAI,CAAE,MAAM,KAAK,QAAQ,EAAI,OAAM,IAAI,MAAM,0CAA0C;AACvF,cAAM,mBAAmB;AAAA,UACvB,MAAM;AAAA,UACN,MAAM,iBAAiB,MAAM,MAAM,UAAU,SAAS,SAAS;AAAA,QACjE;AACA,cAAM,gBAAgB,MAAM,aAAa;AACzC,cAAM,eAAe,iBAAiB,wBAAwB,MAAM,UAAU,gBAAgB,CAAC;AAY/F,YACE,MAAM,YACH,OAAO,KAAK,gBAAgB,EAAE,WAAW,KACzC,CAAC,mBACD,CAAC,eACJ;AAIA,cAAI,sBAAsB,OAAO,KAAK,MAAM,QAAQ,EAAE,SAAS,GAAG;AAChE,kBAAMA,qBAAoB,MAAM;AAAA,cAC9B;AAAA,cACA,MAAM;AAAA,cACN,MAAM,KAAK,QAAQ;AAAA,YACrB;AACA,iBAAK,8BAA8B;AACnC,iBAAK,2BAA2B;AAChC,gBAAIA,mBAAmB,MAAK,4BAA4B;AACxD,iBAAK,wBAAwB;AAAA,UAC/B;AACA,iBAAO,OAAO;AAAA,QAChB;AACA,YACE,MAAM,YACH,OAAO,KAAK,gBAAgB,EAAE,SAAS,KACvC,OAAO,KAAK,YAAY,EAAE,WAAW,OAAO,KAAK,MAAM,QAAQ,EAAE,QACpE;AACA,kBAAQ,EAAE,GAAG,OAAO,UAAU,OAAO,UAAU,aAAa;AAC5D,gBAAM,aAAa,MAAM,KAAK;AAAA,QAChC;AACA,YAAI,OAAO,KAAK,MAAM,QAAQ,EAAE,WAAW,GAAG;AAC5C,cAAI,CAAE,MAAM,KAAK,QAAQ,EAAI,OAAM,IAAI,MAAM,0CAA0C;AACvF,gBAAMC,mBAAkB;AAAA,YACtB,MAAM;AAAA,YACN,MAAM,iBAAiB,MAAM,MAAM,UAAU,SAAS,SAAS;AAAA,UACjE;AAKA,gBAAM,kBAAkB,MAAM,aAAa;AAC3C,cAAI,OAAO,KAAKA,gBAAe,EAAE,WAAW,KAAK,CAAC,iBAAiB;AACjE,kBAAM,aAAa,MAAM,EAAE,GAAG,OAAO,UAAU,KAAK,CAAC;AACrD,mBAAO,OAAO;AAAA,UAChB;AACA,kBAAQ;AAAA,YACN,GAAG;AAAA,YACH,UAAU;AAAA,YACV,UAAU,iBAAiB,wBAAwB,MAAM,UAAUA,gBAAe,CAAC;AAAA,UACrF;AACA,gBAAM,aAAa,MAAM,KAAK;AAC9B;AAAA,QACF;AACA,YAAI,kBAAkB,OAAO,QAAQ,MAAM,QAAQ;AACnD,eAAO,gBAAgB,SAAS,GAAG;AACjC,gBAAM,mBAA4C,CAAC;AACnD,cAAI,aAAa;AACjB,qBAAW,CAAC,UAAU,WAAW,KAAK,iBAAiB;AACrD,kBAAM,SAAS,MAAM;AAAA,cACnB;AAAA,cACA;AAAA,cACA;AAAA,cACA,MAAM,KAAK,QAAQ;AAAA,YACrB;AACA,gBAAI,OAAO,QAAS,SAAQ,IAAI,UAAU,WAAW;AAAA,qBAC5C,OAAO,SAAU,kBAAiB,KAAK,CAAC,UAAU,WAAW,CAAC;AACvE,2BAAe,OAAO;AAAA,UACxB;AACA,cAAI,iBAAiB,WAAW,EAAG;AACnC,cAAI,CAAC,YAAY;AAGf,uBAAW,CAAC,UAAU,WAAW,KAAK,kBAAkB;AACtD,sBAAQ,IAAI,UAAU,WAAW;AACjC,0BAAY,IAAI,UAAU,WAAW;AAAA,YACvC;AACA;AAAA,UACF;AACA,4BAAkB;AAAA,QACpB;AACA,cAAM,aAAa;AAEnB,cAAM,oBAAoB,MAAM;AAAA,UAC9B;AAAA,UACA,MAAM;AAAA,UACN,MAAM,KAAK,QAAQ;AAAA,QACrB;AACA,aAAK,8BAA8B;AACnC,aAAK,2BAA2B;AAChC,YAAI,kBAAmB,MAAK,4BAA4B;AACxD,aAAK,wBAAwB;AAE7B,YAAI,CAAE,MAAM,KAAK,QAAQ,EAAI,OAAM,IAAI,MAAM,0CAA0C;AACvF,cAAM,kBAAkB;AAAA,UACtB,MAAM;AAAA,UACN,MAAM,iBAAiB,MAAM,MAAM,UAAU,SAAS,SAAS;AAAA,QACjE;AAKA,cAAM,sBAAsB,MAAM,aAAa;AAC/C,YAAI,OAAO,KAAK,eAAe,EAAE,WAAW,KAAK,CAAC,qBAAqB;AACrE,gBAAM,aAAa,MAAM,EAAE,GAAG,OAAO,UAAU,KAAK,CAAC;AACrD,iBAAO,OAAO;AAAA,QAChB;AACA,gBAAQ;AAAA,UACN,GAAG;AAAA,UACH,UAAU;AAAA,UACV,UAAU,iBAAiB,wBAAwB,MAAM,UAAU,eAAe,CAAC;AAAA,QACrF;AACA,cAAM,aAAa,MAAM,KAAK;AAAA,MAChC;AACA,YAAM,IAAI,MAAM,8FAA8F;AAAA,IAChH;AAAA,EACF;AACF;","names":["rewroteColdMemory","discoveredAfter"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/buffer-turn-helpers.ts"],"sourcesContent":["import type { BufferEntryState, BufferTurn } from \"./types.js\";\n\nexport interface BufferTurnOwner {\n /**\n * Server-resolved principal that owns this session at the authenticated\n * observe/write boundary. Persisted with the buffered turn so lifecycle\n * flushes do not have to infer ownership from a client-chosen session key.\n */\n sessionOwnerPrincipal?: string;\n}\n\nexport interface AmbientCaptureProvenance {\n /**\n * True when this turn's content came from an always-on ambient capture\n * device (a wearable recorder, a room microphone) and may therefore carry\n * speech the user never authored: TV, podcasts, music, overheard\n * conversation (issue #2294). Set by the ingesting subsystem, never from\n * tool arguments — extraction reads it to warn the model about media audio\n * and to clamp high-impact personal facts to the speculative tier.\n */\n ambientCapture?: boolean;\n}\n\nexport function describeError(err: unknown): string {\n if (err instanceof Error) return err.message;\n if (err === null) return \"null\";\n if (err === undefined) return \"undefined\";\n if (typeof err === \"string\") return err;\n try {\n return JSON.stringify(err);\n } catch {\n return String(err);\n }\n}\n\nexport function copyBufferTurn(turn: BufferTurn): BufferTurn {\n const copy: BufferTurn = {\n role: turn.role,\n content: turn.content,\n timestamp: turn.timestamp,\n };\n if (typeof turn.sessionKey === \"string\") copy.sessionKey = turn.sessionKey;\n if (typeof turn.logicalSessionKey === \"string\") {\n copy.logicalSessionKey = turn.logicalSessionKey;\n }\n if (turn.providerThreadId === null || typeof turn.providerThreadId === \"string\") {\n copy.providerThreadId = turn.providerThreadId;\n }\n if (typeof turn.sessionOwnerPrincipal === \"string\") {\n copy.sessionOwnerPrincipal = turn.sessionOwnerPrincipal;\n }\n if (typeof turn.turnFingerprint === \"string\") {\n copy.turnFingerprint = turn.turnFingerprint;\n }\n if (typeof turn.persistProcessedFingerprint === \"boolean\") {\n copy.persistProcessedFingerprint = turn.persistProcessedFingerprint;\n }\n if (typeof turn.sourceConnector === \"string\") {\n copy.sourceConnector = turn.sourceConnector;\n }\n if (turn.originRole === \"user\" || turn.originRole === \"assistant\" || turn.originRole === \"tool\") {\n copy.originRole = turn.originRole;\n }\n if (typeof turn.ambientCapture === \"boolean\") {\n copy.ambientCapture = turn.ambientCapture;\n }\n if (turn.sourceValidAt !== undefined) {\n copy.sourceValidAt = turn.sourceValidAt;\n }\n if (turn.parts !== undefined) {\n copy.parts = turn.parts;\n }\n if (turn.rawContent !== undefined) {\n copy.rawContent = turn.rawContent;\n }\n if (turn.sourceFormat !== undefined) {\n copy.sourceFormat = turn.sourceFormat;\n }\n // #3251 scope pins: strings copy by value; the plan object is an immutable\n // config-derived identity passed by reference.\n if (turn.writeNamespaceOverride !== undefined) {\n copy.writeNamespaceOverride = turn.writeNamespaceOverride;\n }\n if (turn.principalOverride !== undefined) {\n copy.principalOverride = turn.principalOverride;\n }\n if (turn.scopeProfileWritePlan !== undefined) {\n copy.scopeProfileWritePlan = turn.scopeProfileWritePlan;\n }\n if (turn.acceptedCapture === true) {\n copy.acceptedCapture = true;\n }\n const ctx = turn.codingContext;\n if (ctx) {\n // All four fields are identity inputs (branchScope uses branch) — copy\n // explicitly as-is; never a spread of external input.\n copy.codingContext = {\n projectId: ctx.projectId,\n branch: ctx.branch,\n rootPath: ctx.rootPath,\n defaultBranch: ctx.defaultBranch,\n };\n }\n return copy;\n}\n\nexport function bufferTurnsEqual(left: BufferTurn | undefined, right: BufferTurn): boolean {\n if (!left) return false;\n return (\n left.role === right.role &&\n left.content === right.content &&\n left.timestamp === right.timestamp &&\n left.sessionKey === right.sessionKey &&\n left.sessionOwnerPrincipal === right.sessionOwnerPrincipal &&\n left.logicalSessionKey === right.logicalSessionKey &&\n left.providerThreadId === right.providerThreadId &&\n left.turnFingerprint === right.turnFingerprint &&\n left.persistProcessedFingerprint === right.persistProcessedFingerprint &&\n left.sourceConnector === right.sourceConnector &&\n left.originRole === right.originRole &&\n left.ambientCapture === right.ambientCapture &&\n left.sourceValidAt === right.sourceValidAt &&\n left.acceptedCapture === right.acceptedCapture &&\n left.codingContext?.projectId === right.codingContext?.projectId &&\n left.codingContext?.rootPath === right.codingContext?.rootPath &&\n left.codingContext?.branch === right.codingContext?.branch &&\n left.codingContext?.defaultBranch === right.codingContext?.defaultBranch &&\n // #3251 scope pins compare by identity triple, not deep plan equality: a\n // restart-rehydrated plan must still match the pin it was accepted under.\n left.writeNamespaceOverride === right.writeNamespaceOverride &&\n left.principalOverride === right.principalOverride &&\n scopeProfilePlanIdentity(left.scopeProfileWritePlan) ===\n scopeProfilePlanIdentity(right.scopeProfileWritePlan)\n );\n}\n\n/** Replace only the processed snapshot; callers without one retain legacy whole-slot semantics. */\nexport function selectRetainedDeferredTurns(\n existing: readonly BufferTurn[],\n turns: BufferTurn[],\n max: number,\n originalTurns?: readonly BufferTurn[],\n): BufferTurn[] {\n const others = originalTurns === undefined ? [] : existing.filter(\n (turn) => !originalTurns.some((original) => bufferTurnsEqual(turn, original)),\n );\n if (!Array.isArray(turns) || max <= 0) return others;\n return [...others, ...turns.slice(-max).map(copyBufferTurn)];\n}\n\nexport function hasPinnedCaptureScope(turn: BufferTurn): boolean {\n return turn.acceptedCapture === true ||\n turn.writeNamespaceOverride !== undefined ||\n turn.scopeProfileWritePlan !== undefined;\n}\n\n/** Stable identity string for a pinned scope-profile plan (#3251). */\nexport function scopeProfilePlanIdentity(\n plan: BufferTurn[\"scopeProfileWritePlan\"],\n): string {\n if (!plan) return \"\";\n return `${plan.profileId}\\u0000${plan.writeLayer}\\u0000${plan.writeNamespace}`;\n}\n\nexport interface ExtractionScopeGroup {\n writeNamespaceOverride?: string;\n principalOverride?: string;\n scopeProfileWritePlan?: BufferTurn[\"scopeProfileWritePlan\"];\n codingContext?: BufferTurn[\"codingContext\"];\n turns: BufferTurn[];\n}\n\n/**\n * Partition extraction turns by their durable scope pins (#3251). Turns\n * captured under different namespaces / principals / scope profiles /\n * project bindings must never be extracted together — each group runs its\n * own extraction under its own scope. Native (unpinned) turns form their own\n * group and keep today's single-store routing.\n */\nexport function groupTurnsByExtractionScope(turns: BufferTurn[]): ExtractionScopeGroup[] {\n const groups = new Map<string, ExtractionScopeGroup>();\n for (const turn of turns) {\n const key = [\n turn.writeNamespaceOverride ?? \"\",\n turn.principalOverride ?? \"\",\n scopeProfilePlanIdentity(turn.scopeProfileWritePlan),\n turn.codingContext?.projectId ?? \"\",\n turn.codingContext?.rootPath ?? \"\",\n turn.codingContext?.branch ?? \"\",\n turn.codingContext?.defaultBranch ?? \"\",\n ].join(\"\\u0000\");\n const group = groups.get(key);\n if (group) {\n group.turns.push(turn);\n } else {\n const created: ExtractionScopeGroup = { turns: [turn] };\n if (turn.writeNamespaceOverride !== undefined) created.writeNamespaceOverride = turn.writeNamespaceOverride;\n if (turn.principalOverride !== undefined) created.principalOverride = turn.principalOverride;\n if (turn.scopeProfileWritePlan !== undefined) created.scopeProfileWritePlan = turn.scopeProfileWritePlan;\n if (turn.codingContext !== undefined) created.codingContext = turn.codingContext;\n groups.set(key, created);\n }\n }\n return [...groups.values()];\n}\n\nexport function bufferTurnArraysEqual(\n left: readonly BufferTurn[],\n right: readonly BufferTurn[],\n): boolean {\n return left.length === right.length && left.every((turn, index) => bufferTurnsEqual(turn, right[index]));\n}\n\nexport function bufferTurnArrayIsSuffixOfSnapshot(\n liveTurns: readonly BufferTurn[],\n snapshot: readonly BufferTurn[],\n): boolean {\n if (liveTurns.length === 0 || liveTurns.length > snapshot.length) {\n return false;\n }\n const offset = snapshot.length - liveTurns.length;\n return liveTurns.every((turn, index) => bufferTurnsEqual(turn, snapshot[offset + index]));\n}\n\nexport function liveTurnsFromExtractionSnapshot(\n entry: BufferEntryState,\n extractedTurns: readonly BufferTurn[],\n): readonly BufferTurn[] {\n const retainedTurns = entry.retainedTurns ?? [];\n if (\n retainedTurns.length > 0 &&\n extractedTurns.length >= retainedTurns.length &&\n retainedTurns.every((turn, index) => bufferTurnsEqual(extractedTurns[index], turn))\n ) {\n const withoutRetainedPrefix = extractedTurns.slice(retainedTurns.length);\n if (withoutRetainedPrefix.length > 0 && matchingPrefixLength(entry.turns, withoutRetainedPrefix) > 0) {\n return withoutRetainedPrefix;\n }\n }\n return extractedTurns;\n}\n\nfunction matchingPrefixLength(\n liveTurns: readonly BufferTurn[],\n extractedTurns: readonly BufferTurn[],\n): number {\n let index = 0;\n while (\n index < liveTurns.length &&\n index < extractedTurns.length &&\n bufferTurnsEqual(liveTurns[index], extractedTurns[index])\n ) {\n index += 1;\n }\n return index;\n}\n\nexport function matchingQueuedExtractionPrefixLength(\n liveTurns: readonly BufferTurn[],\n extractedTurns: readonly BufferTurn[],\n): number {\n let bestMatchedCount = 0;\n for (let start = 0; start < extractedTurns.length; start += 1) {\n const matchedCount = matchingPrefixLength(liveTurns, extractedTurns.slice(start));\n if (matchedCount > bestMatchedCount) {\n bestMatchedCount = matchedCount;\n if (bestMatchedCount === liveTurns.length) break;\n }\n }\n return bestMatchedCount;\n}\n\nexport class ProbeTimeoutError extends Error {\n constructor(timeoutMs: number) {\n super(`probe exceeded ${timeoutMs}ms`);\n this.name = \"ProbeTimeoutError\";\n }\n}\n\nexport function probeWithTimeout<T>(inflight: Promise<T>, timeoutMs: number): Promise<T> {\n let timer: NodeJS.Timeout | null = null;\n const timeout = new Promise<T>((_, reject) => {\n timer = setTimeout(() => reject(new ProbeTimeoutError(timeoutMs)), timeoutMs);\n if (typeof (timer as NodeJS.Timeout).unref === \"function\") {\n (timer as NodeJS.Timeout).unref();\n }\n });\n return Promise.race([inflight, timeout]).finally(() => {\n if (timer) clearTimeout(timer);\n });\n}\n"],"mappings":"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","names":[]}
|