@remnic/core 10.0.84 → 10.0.85
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 +67 -66
- package/dist/access-authorization-probe.js +68 -67
- package/dist/access-boundary.js +67 -66
- package/dist/access-cli.js +146 -144
- package/dist/access-cli.js.map +1 -1
- package/dist/access-extraction-force-flush.js +67 -66
- package/dist/access-http-briefing.js +68 -67
- package/dist/access-http-query.js +69 -68
- package/dist/access-http.js +91 -89
- package/dist/access-identity-continuity-surface.js +67 -66
- package/dist/access-lcm-surface.js +67 -66
- package/dist/access-mcp-frames.d.ts +29 -0
- package/dist/access-mcp-frames.d.ts.map +1 -0
- package/dist/access-mcp-frames.js +10 -0
- package/dist/access-mcp.d.ts +1 -1
- package/dist/access-mcp.d.ts.map +1 -1
- package/dist/access-mcp.js +82 -80
- package/dist/access-namespace-preflight.js +67 -66
- package/dist/access-observe-write-category.js +9 -9
- package/dist/access-observe-write-surface.d.ts.map +1 -1
- package/dist/access-observe-write-surface.js +67 -66
- package/dist/access-offline-manifest.js +3 -3
- package/dist/access-operations-batch.js +71 -70
- package/dist/access-operations.js +78 -77
- package/dist/access-recall-concurrency.js +67 -66
- package/dist/access-recall-response.js +67 -66
- package/dist/access-recall-surface.js +67 -66
- package/dist/access-schema.js +4 -4
- package/dist/access-service.js +67 -66
- package/dist/access-surface-catalog.js +69 -68
- package/dist/access-surface-catalog.js.map +1 -1
- package/dist/active-recall.js +31 -31
- package/dist/{auto-sync-C7E6G7Y2.js → auto-sync-3AZW7MXI.js} +6 -6
- package/dist/briefing.js +25 -25
- package/dist/buffer-persistence.d.ts +8 -4
- package/dist/buffer-persistence.d.ts.map +1 -1
- package/dist/buffer-persistence.js +1 -1
- package/dist/buffer-turn-helpers.d.ts +5 -0
- package/dist/buffer-turn-helpers.d.ts.map +1 -1
- package/dist/buffer-turn-helpers.js +1 -1
- package/dist/buffer.d.ts +4 -1
- package/dist/buffer.d.ts.map +1 -1
- package/dist/buffer.js +9 -9
- package/dist/calibration.js +2 -2
- package/dist/{capsule-crypto-YO5QJ6L3.js → capsule-crypto-GWVG7LGC.js} +2 -2
- package/dist/capsule-gunzip-KGBDGF7P.js +11 -0
- package/dist/{capsule-merge-XVTQJJ6R.js → capsule-merge-2IIXFXN5.js} +5 -11
- package/dist/capsule-merge-2IIXFXN5.js.map +1 -0
- package/dist/causal-behavior.js +2 -2
- package/dist/causal-chain.js +2 -2
- package/dist/causal-consolidation.js +32 -32
- 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-T6T2RMWW.js → chunk-23B4HLWG.js} +5 -5
- package/dist/{chunk-LECSS7EE.js → chunk-2ASEDFOA.js} +2 -2
- package/dist/{chunk-TPX7XXY4.js → chunk-2JAT4S7N.js} +2 -2
- package/dist/{chunk-XRBHIR7Q.js → chunk-2URKYLMK.js} +2 -2
- package/dist/{chunk-IN4OFAHX.js → chunk-2XYDL2FG.js} +2 -2
- package/dist/{chunk-HQGATKG2.js → chunk-3KGCHTTK.js} +2 -2
- package/dist/{chunk-YAUHSNPB.js → chunk-3LHPTBWG.js} +2 -2
- package/dist/{chunk-7PMOCQOL.js → chunk-3ODZLCHC.js} +192 -63
- package/dist/chunk-3ODZLCHC.js.map +1 -0
- package/dist/{chunk-XT45WCKX.js → chunk-3VU2FDTU.js} +8 -8
- package/dist/{chunk-CIIHIAJ2.js → chunk-433EXTC4.js} +3 -3
- package/dist/{chunk-QZSIDSB2.js → chunk-44FQCC5D.js} +2 -2
- package/dist/{chunk-YUTT4ZTI.js → chunk-45VUHDV2.js} +2 -2
- package/dist/{chunk-54FDG7K7.js → chunk-4DPLCYEK.js} +2 -2
- package/dist/{chunk-KO3YBHOV.js → chunk-4IWEPQEV.js} +2 -2
- package/dist/{chunk-H7BZBSP6.js → chunk-52II7PB6.js} +4 -4
- package/dist/{chunk-K6RTQZA4.js → chunk-6DANGCMO.js} +2 -2
- package/dist/{chunk-2IW5PT7U.js → chunk-6KFFRMPQ.js} +2 -2
- package/dist/{chunk-MSIRVXQ3.js → chunk-73YVMZ7D.js} +4 -4
- package/dist/{chunk-A7BM2JTH.js → chunk-AJHNXIQU.js} +2 -2
- package/dist/{chunk-2D7BTSDW.js → chunk-ANZZLRUX.js} +2 -2
- package/dist/{chunk-YOEYL6CH.js → chunk-AP3TSV6S.js} +2 -2
- package/dist/{chunk-GQZSGXR6.js → chunk-BATPQNRG.js} +2 -2
- package/dist/{chunk-D5SSBCYY.js → chunk-BLGQUZFB.js} +2 -2
- package/dist/{chunk-BDN3TDGV.js → chunk-BLMTOIBK.js} +2 -2
- package/dist/{chunk-B74NPUHT.js → chunk-BMIM6ESC.js} +43 -43
- package/dist/chunk-BMIM6ESC.js.map +1 -0
- package/dist/{chunk-R45L4TV6.js → chunk-BUKK5SWA.js} +6 -6
- package/dist/{chunk-TFLPDNQV.js → chunk-CCU7URRP.js} +2 -2
- package/dist/{chunk-2XJZLSXE.js → chunk-CMRHXPLQ.js} +20 -20
- package/dist/{chunk-BK4FBHZ3.js → chunk-D6THW7PA.js} +3 -3
- package/dist/{chunk-BFBS7OFQ.js → chunk-DC55RIEZ.js} +4 -4
- package/dist/{chunk-VKDAJJQP.js → chunk-DI5ET2IE.js} +5 -5
- package/dist/{chunk-QZBTIYKD.js → chunk-DM4RRTN7.js} +3 -3
- package/dist/{chunk-EBOHFZI4.js → chunk-E5QEF3JO.js} +2 -2
- package/dist/{chunk-K3B72DKJ.js → chunk-E7V5AK7I.js} +2 -2
- package/dist/{chunk-WAD32CZG.js → chunk-ELWI2R6H.js} +26 -13
- package/dist/chunk-ELWI2R6H.js.map +1 -0
- package/dist/{chunk-BV2BRHD4.js → chunk-FHGWKODN.js} +2 -2
- package/dist/{chunk-HPFVZPRH.js → chunk-FK4WC6VK.js} +3 -3
- package/dist/{chunk-VLMBSGQF.js → chunk-FRM4UTDR.js} +6 -6
- package/dist/{chunk-LTDLJCMQ.js → chunk-FZPDRCOB.js} +2 -2
- package/dist/{chunk-ZJJFUEPO.js → chunk-GBOYOL5L.js} +2 -2
- package/dist/{chunk-YUTG35M4.js → chunk-GJVGFKOU.js} +2 -2
- package/dist/{chunk-77R7BQZ2.js → chunk-GKYIRTNX.js} +33 -11
- package/dist/{chunk-77R7BQZ2.js.map → chunk-GKYIRTNX.js.map} +1 -1
- package/dist/{chunk-K36RMINQ.js → chunk-GOZXQNIM.js} +3 -3
- package/dist/{chunk-HWHNQVRV.js → chunk-GYNRNKMW.js} +2 -2
- package/dist/{chunk-LU66I56Q.js → chunk-H34INI3P.js} +29 -3
- package/dist/chunk-H34INI3P.js.map +1 -0
- package/dist/{chunk-HLKUZW42.js → chunk-H4D5YURJ.js} +80 -94
- package/dist/chunk-H4D5YURJ.js.map +1 -0
- package/dist/{chunk-6LCWKS62.js → chunk-HVFX2N2G.js} +60 -59
- package/dist/chunk-HVFX2N2G.js.map +1 -0
- package/dist/chunk-IGLJNHOX.js +62 -0
- package/dist/chunk-IGLJNHOX.js.map +1 -0
- package/dist/{chunk-KFLD7ZZG.js → chunk-IUKYFTGW.js} +3 -3
- package/dist/{chunk-YK7GOPIE.js → chunk-JFH6JAGY.js} +6 -4
- package/dist/chunk-JFH6JAGY.js.map +1 -0
- package/dist/{chunk-BNQJKWWZ.js → chunk-K3FH3LAQ.js} +4 -4
- package/dist/{chunk-FJ2VJTGA.js → chunk-KHZVAISH.js} +2 -2
- package/dist/{chunk-WHQXWFH7.js → chunk-LT3CMO65.js} +2 -2
- package/dist/{chunk-NUSAAN75.js → chunk-MHSBOEH7.js} +2 -2
- package/dist/{chunk-KJCL2FZO.js → chunk-MNYQVYJ4.js} +2 -2
- package/dist/{chunk-RZH5RVJC.js → chunk-MTTYIXSU.js} +2 -2
- package/dist/{chunk-FVRTCYZ3.js → chunk-MVOJDLXS.js} +2 -2
- package/dist/{chunk-KESJ5XX3.js → chunk-MYCCO6AA.js} +2 -2
- package/dist/{chunk-66JOLMWS.js → chunk-MZCGHPIJ.js} +2 -2
- package/dist/{chunk-B4ZU34JQ.js → chunk-N6II66E5.js} +3 -3
- package/dist/{chunk-BK7AJUWB.js → chunk-NIV7RPWQ.js} +6 -6
- package/dist/{chunk-TLHF75OR.js → chunk-O55AGDTI.js} +10 -10
- package/dist/{chunk-GGJFRVRD.js → chunk-OMYRPMQC.js} +2 -2
- package/dist/{chunk-3JPTUCD7.js → chunk-ONN2E5QD.js} +32 -23
- package/dist/chunk-ONN2E5QD.js.map +1 -0
- package/dist/{chunk-XHGYZRVW.js → chunk-OSTFDWZJ.js} +4 -4
- package/dist/{chunk-BXLOS5AJ.js → chunk-OWHERGF2.js} +2 -2
- package/dist/{chunk-WC475JOG.js → chunk-P225W4RQ.js} +6 -6
- package/dist/{chunk-TFA6HPOA.js → chunk-QCP75JGO.js} +5 -5
- package/dist/{chunk-JRVMCZE3.js → chunk-QEYTRFR3.js} +2 -2
- package/dist/chunk-QV2VZKD5.js +31 -0
- package/dist/chunk-QV2VZKD5.js.map +1 -0
- package/dist/{chunk-IZJQNDGI.js → chunk-R6HMAIKM.js} +2 -2
- package/dist/{chunk-VUROWXFL.js → chunk-RNGDKFY2.js} +2 -2
- package/dist/{chunk-FF6N7BXC.js → chunk-S45INSV3.js} +4 -4
- package/dist/{chunk-2RUTN7PR.js → chunk-S7GJNA3K.js} +2 -2
- package/dist/{chunk-XYP5WBDO.js → chunk-SGHV6LZ7.js} +13 -13
- package/dist/chunk-SGHV6LZ7.js.map +1 -0
- package/dist/{chunk-RJWL3RHB.js → chunk-TFEHSPGQ.js} +2 -2
- package/dist/{chunk-T77AX6DG.js → chunk-TQVECGUQ.js} +4 -4
- package/dist/{chunk-OQSSQOKQ.js → chunk-TV65NR7L.js} +2 -2
- package/dist/{chunk-KQUSP6LB.js → chunk-UDKWFDV2.js} +1 -1
- package/dist/chunk-UDKWFDV2.js.map +1 -0
- package/dist/{chunk-HQPFACXO.js → chunk-UH3FPX3L.js} +2 -2
- package/dist/{chunk-4EACCIPO.js → chunk-UHPHH74F.js} +2 -2
- package/dist/{chunk-DQEMWVMT.js → chunk-UVYI6VIX.js} +1 -1
- package/dist/{chunk-6QYPL6NI.js → chunk-V45TEVDA.js} +3 -3
- package/dist/{chunk-GCAQ3VUJ.js → chunk-VCVEMQJK.js} +2 -2
- package/dist/{chunk-6L63IN3M.js → chunk-VEFGHRKV.js} +1 -1
- package/dist/{chunk-E33UJHY3.js → chunk-WFDWLWDK.js} +2 -2
- package/dist/{chunk-4KLQI3L2.js → chunk-WLNWFHX3.js} +1 -1
- package/dist/chunk-WLNWFHX3.js.map +1 -0
- package/dist/{chunk-KQLBXUUQ.js → chunk-WYG5UNDY.js} +2 -2
- package/dist/{chunk-MPGLM2VU.js → chunk-X7REO4CH.js} +2 -2
- package/dist/{chunk-MPDLLLOG.js → chunk-XO23OKMK.js} +9 -5
- package/dist/chunk-XO23OKMK.js.map +1 -0
- package/dist/{chunk-Z3DDQXM4.js → chunk-XSFQN7TP.js} +35 -36
- package/dist/chunk-XSFQN7TP.js.map +1 -0
- package/dist/{chunk-FZVROZKC.js → chunk-XVZA6VXC.js} +4 -4
- package/dist/{chunk-WV254W4V.js → chunk-Y3IYKNBY.js} +2 -2
- package/dist/{chunk-FUOAIJKQ.js → chunk-Y43X3E24.js} +2 -2
- package/dist/{chunk-3T3VHXUH.js → chunk-YAAKVTZA.js} +2 -2
- package/dist/{chunk-VPEIWMUT.js → chunk-YLTU2DC4.js} +2 -2
- package/dist/{chunk-65SKM4BF.js → chunk-YNBMBL4C.js} +2 -2
- package/dist/{chunk-PJYK7HH2.js → chunk-ZWTHVUT5.js} +2 -2
- package/dist/cli.js +110 -108
- package/dist/coding/export-okf-codegraph.js +25 -25
- package/dist/coding/pre-action-gate.js +2 -2
- package/dist/compounding/engine.js +27 -27
- package/dist/config.js +28 -28
- package/dist/connectors/codex-materialize-runner.js +25 -25
- package/dist/connectors/index.js +25 -25
- package/dist/contradiction/index.js +4 -4
- package/dist/convergence-refresh.js +27 -27
- package/dist/conversation-index/backend.js +2 -2
- package/dist/corpus-watermark.js +27 -27
- package/dist/deep-recall-http-glue.js +5 -5
- package/dist/entity-retrieval.js +25 -25
- package/dist/event-order-recall.js +2 -2
- package/dist/explicit-capture.js +5 -5
- package/dist/external-wiki-access.js +68 -67
- package/dist/extraction-evidence-class.js +2 -2
- package/dist/extraction-quote-grounding.d.ts.map +1 -1
- package/dist/extraction-quote-grounding.js +1 -1
- package/dist/extraction-source-grounding-rules.js +2 -2
- package/dist/extraction-source-grounding.js +5 -5
- package/dist/extraction-span-materialize.js +2 -2
- package/dist/extraction.js +40 -40
- package/dist/{first-start-migration-QVD22WWS.js → first-start-migration-A6PHIBUQ.js} +4 -4
- package/dist/{graph-edge-decay-CXTNJ7IQ.js → graph-edge-decay-EKWXRBWF.js} +5 -5
- package/dist/graph-edge-decay-EKWXRBWF.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 +25 -25
- package/dist/index.js +195 -193
- package/dist/lcm/engine.js +2 -2
- package/dist/lcm/index.js +2 -2
- package/dist/local-llm.js +2 -2
- package/dist/location/index.js +28 -28
- package/dist/maintenance/memory-governance.js +25 -25
- package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +25 -25
- package/dist/maintenance/rebuild-memory-projection-runner.js +27 -27
- package/dist/maintenance/rebuild-memory-projection.js +26 -26
- package/dist/memory-browse-operations.js +68 -67
- package/dist/memory-subject.js +2 -2
- package/dist/namespaces/migrate.js +26 -26
- package/dist/namespaces/search.d.ts +8 -0
- package/dist/namespaces/search.d.ts.map +1 -1
- package/dist/namespaces/search.js +10 -6
- package/dist/namespaces/storage.js +25 -25
- package/dist/operator-doctor-corpus.js +28 -28
- package/dist/operator-toolkit.js +39 -39
- package/dist/orchestration/backlog-scheduler.d.ts +3 -2
- package/dist/orchestration/backlog-scheduler.d.ts.map +1 -1
- package/dist/orchestration/backlog-scheduler.js +157 -155
- package/dist/orchestration/backlog-scheduler.js.map +1 -1
- package/dist/orchestration/buffer-drain-runner.d.ts +6 -1
- package/dist/orchestration/buffer-drain-runner.d.ts.map +1 -1
- package/dist/orchestration/buffer-drain-runner.js +147 -145
- package/dist/orchestration/extraction-persist.d.ts.map +1 -1
- package/dist/orchestration/fact-dedup-window.d.ts +1 -4
- package/dist/orchestration/fact-dedup-window.d.ts.map +1 -1
- package/dist/orchestration/maintenance.js +39 -39
- package/dist/orchestrator.js +146 -144
- package/dist/provenance.js +2 -2
- package/dist/recall-navigation-operations.js +68 -67
- package/dist/recall-navigation.js +67 -66
- package/dist/reconcile/manifest.js +2 -2
- package/dist/{resolution-BNWE5Y25.js → resolution-23ZBDM6A.js} +5 -5
- package/dist/resolution-23ZBDM6A.js.map +1 -0
- package/dist/resume-bundles.js +29 -29
- package/dist/salvage-envelope.js +4 -4
- package/dist/search/factory.js +5 -5
- package/dist/search/index.js +9 -9
- package/dist/semantic-consolidation.js +26 -26
- package/dist/semantic-rule-promotion.js +25 -25
- package/dist/semantic-rule-verifier.js +25 -25
- package/dist/source-agent-qualifier.js +31 -31
- package/dist/standup/build.js +25 -25
- package/dist/storage/buffer-drain-claims.d.ts +18 -6
- package/dist/storage/buffer-drain-claims.d.ts.map +1 -1
- package/dist/storage/buffer-drain-claims.js +2 -2
- package/dist/storage/buffer-session-files.js +2 -2
- package/dist/storage/content-hash-index.d.ts +8 -0
- package/dist/storage/content-hash-index.d.ts.map +1 -1
- package/dist/storage.d.ts +10 -1
- package/dist/storage.d.ts.map +1 -1
- package/dist/storage.js +24 -24
- package/dist/subject-config.js +3 -3
- package/dist/summarizer.js +3 -3
- package/dist/support-passport/index.js +70 -69
- package/dist/transfer/autodetect.js +1 -1
- package/dist/transfer/backup.js +2 -2
- package/dist/transfer/capsule-export.js +3 -3
- package/dist/transfer/capsule-gunzip.d.ts +14 -0
- package/dist/transfer/capsule-gunzip.d.ts.map +1 -0
- package/dist/transfer/capsule-import.d.ts.map +1 -1
- package/dist/transfer/capsule-import.js +3 -2
- package/dist/transfer/capsule-merge.d.ts.map +1 -1
- package/dist/transfer/export-okf.js +25 -25
- package/dist/verified-recall.js +25 -25
- package/dist/who-knows-cli.js +26 -26
- package/dist/who-knows.js +25 -25
- package/dist/write-envelope.js +3 -3
- package/package.json +2 -2
- package/src/access-mcp-frames.test.ts +47 -0
- package/src/access-mcp-frames.ts +74 -0
- package/src/access-mcp.test.ts +146 -0
- package/src/access-mcp.ts +30 -30
- package/src/access-observe-write-surface.ts +4 -4
- package/src/backlog-scheduler.test.ts +443 -3
- package/src/buffer-persistence.ts +15 -6
- package/src/buffer-turn-helpers.ts +6 -0
- package/src/buffer.ts +6 -6
- package/src/cli.ts +4 -4
- package/src/extraction-quote-grounding.test.ts +49 -0
- package/src/extraction-quote-grounding.ts +50 -12
- package/src/fact-dedup-window.test.ts +103 -20
- package/src/namespaces/search.test.ts +339 -0
- package/src/namespaces/search.ts +54 -1
- package/src/orchestration/backlog-scheduler.ts +13 -12
- package/src/orchestration/buffer-drain-runner.ts +40 -16
- package/src/orchestration/extraction-persist.ts +2 -10
- package/src/orchestration/fact-dedup-window.ts +5 -12
- package/src/storage/buffer-drain-claims.test.ts +330 -6
- package/src/storage/buffer-drain-claims.ts +226 -80
- package/src/storage/buffer-session-files.ts +2 -2
- package/src/storage/content-hash-index.ts +9 -0
- package/src/storage.ts +15 -0
- package/src/transfer/capsule-gunzip.ts +39 -0
- package/src/transfer/capsule-import.test.ts +73 -0
- package/src/transfer/capsule-import.ts +2 -2
- package/src/transfer/capsule-merge.test.ts +34 -0
- package/src/transfer/capsule-merge.ts +2 -10
- package/dist/capsule-merge-XVTQJJ6R.js.map +0 -1
- package/dist/chunk-3JPTUCD7.js.map +0 -1
- package/dist/chunk-4KLQI3L2.js.map +0 -1
- package/dist/chunk-6LCWKS62.js.map +0 -1
- package/dist/chunk-7PMOCQOL.js.map +0 -1
- package/dist/chunk-B74NPUHT.js.map +0 -1
- package/dist/chunk-HLKUZW42.js.map +0 -1
- package/dist/chunk-KQUSP6LB.js.map +0 -1
- package/dist/chunk-LU66I56Q.js.map +0 -1
- package/dist/chunk-MPDLLLOG.js.map +0 -1
- package/dist/chunk-WAD32CZG.js.map +0 -1
- package/dist/chunk-XYP5WBDO.js.map +0 -1
- package/dist/chunk-YK7GOPIE.js.map +0 -1
- package/dist/chunk-Z3DDQXM4.js.map +0 -1
- /package/dist/{capsule-crypto-YO5QJ6L3.js.map → access-mcp-frames.js.map} +0 -0
- /package/dist/{auto-sync-C7E6G7Y2.js.map → auto-sync-3AZW7MXI.js.map} +0 -0
- /package/dist/{graph-edge-decay-CXTNJ7IQ.js.map → capsule-crypto-GWVG7LGC.js.map} +0 -0
- /package/dist/{resolution-BNWE5Y25.js.map → capsule-gunzip-KGBDGF7P.js.map} +0 -0
- /package/dist/{chunk-T6T2RMWW.js.map → chunk-23B4HLWG.js.map} +0 -0
- /package/dist/{chunk-LECSS7EE.js.map → chunk-2ASEDFOA.js.map} +0 -0
- /package/dist/{chunk-TPX7XXY4.js.map → chunk-2JAT4S7N.js.map} +0 -0
- /package/dist/{chunk-XRBHIR7Q.js.map → chunk-2URKYLMK.js.map} +0 -0
- /package/dist/{chunk-IN4OFAHX.js.map → chunk-2XYDL2FG.js.map} +0 -0
- /package/dist/{chunk-HQGATKG2.js.map → chunk-3KGCHTTK.js.map} +0 -0
- /package/dist/{chunk-YAUHSNPB.js.map → chunk-3LHPTBWG.js.map} +0 -0
- /package/dist/{chunk-XT45WCKX.js.map → chunk-3VU2FDTU.js.map} +0 -0
- /package/dist/{chunk-CIIHIAJ2.js.map → chunk-433EXTC4.js.map} +0 -0
- /package/dist/{chunk-QZSIDSB2.js.map → chunk-44FQCC5D.js.map} +0 -0
- /package/dist/{chunk-YUTT4ZTI.js.map → chunk-45VUHDV2.js.map} +0 -0
- /package/dist/{chunk-54FDG7K7.js.map → chunk-4DPLCYEK.js.map} +0 -0
- /package/dist/{chunk-KO3YBHOV.js.map → chunk-4IWEPQEV.js.map} +0 -0
- /package/dist/{chunk-H7BZBSP6.js.map → chunk-52II7PB6.js.map} +0 -0
- /package/dist/{chunk-K6RTQZA4.js.map → chunk-6DANGCMO.js.map} +0 -0
- /package/dist/{chunk-2IW5PT7U.js.map → chunk-6KFFRMPQ.js.map} +0 -0
- /package/dist/{chunk-MSIRVXQ3.js.map → chunk-73YVMZ7D.js.map} +0 -0
- /package/dist/{chunk-A7BM2JTH.js.map → chunk-AJHNXIQU.js.map} +0 -0
- /package/dist/{chunk-2D7BTSDW.js.map → chunk-ANZZLRUX.js.map} +0 -0
- /package/dist/{chunk-YOEYL6CH.js.map → chunk-AP3TSV6S.js.map} +0 -0
- /package/dist/{chunk-GQZSGXR6.js.map → chunk-BATPQNRG.js.map} +0 -0
- /package/dist/{chunk-D5SSBCYY.js.map → chunk-BLGQUZFB.js.map} +0 -0
- /package/dist/{chunk-BDN3TDGV.js.map → chunk-BLMTOIBK.js.map} +0 -0
- /package/dist/{chunk-R45L4TV6.js.map → chunk-BUKK5SWA.js.map} +0 -0
- /package/dist/{chunk-TFLPDNQV.js.map → chunk-CCU7URRP.js.map} +0 -0
- /package/dist/{chunk-2XJZLSXE.js.map → chunk-CMRHXPLQ.js.map} +0 -0
- /package/dist/{chunk-BK4FBHZ3.js.map → chunk-D6THW7PA.js.map} +0 -0
- /package/dist/{chunk-BFBS7OFQ.js.map → chunk-DC55RIEZ.js.map} +0 -0
- /package/dist/{chunk-VKDAJJQP.js.map → chunk-DI5ET2IE.js.map} +0 -0
- /package/dist/{chunk-QZBTIYKD.js.map → chunk-DM4RRTN7.js.map} +0 -0
- /package/dist/{chunk-EBOHFZI4.js.map → chunk-E5QEF3JO.js.map} +0 -0
- /package/dist/{chunk-K3B72DKJ.js.map → chunk-E7V5AK7I.js.map} +0 -0
- /package/dist/{chunk-BV2BRHD4.js.map → chunk-FHGWKODN.js.map} +0 -0
- /package/dist/{chunk-HPFVZPRH.js.map → chunk-FK4WC6VK.js.map} +0 -0
- /package/dist/{chunk-VLMBSGQF.js.map → chunk-FRM4UTDR.js.map} +0 -0
- /package/dist/{chunk-LTDLJCMQ.js.map → chunk-FZPDRCOB.js.map} +0 -0
- /package/dist/{chunk-ZJJFUEPO.js.map → chunk-GBOYOL5L.js.map} +0 -0
- /package/dist/{chunk-YUTG35M4.js.map → chunk-GJVGFKOU.js.map} +0 -0
- /package/dist/{chunk-K36RMINQ.js.map → chunk-GOZXQNIM.js.map} +0 -0
- /package/dist/{chunk-HWHNQVRV.js.map → chunk-GYNRNKMW.js.map} +0 -0
- /package/dist/{chunk-KFLD7ZZG.js.map → chunk-IUKYFTGW.js.map} +0 -0
- /package/dist/{chunk-BNQJKWWZ.js.map → chunk-K3FH3LAQ.js.map} +0 -0
- /package/dist/{chunk-FJ2VJTGA.js.map → chunk-KHZVAISH.js.map} +0 -0
- /package/dist/{chunk-WHQXWFH7.js.map → chunk-LT3CMO65.js.map} +0 -0
- /package/dist/{chunk-NUSAAN75.js.map → chunk-MHSBOEH7.js.map} +0 -0
- /package/dist/{chunk-KJCL2FZO.js.map → chunk-MNYQVYJ4.js.map} +0 -0
- /package/dist/{chunk-RZH5RVJC.js.map → chunk-MTTYIXSU.js.map} +0 -0
- /package/dist/{chunk-FVRTCYZ3.js.map → chunk-MVOJDLXS.js.map} +0 -0
- /package/dist/{chunk-KESJ5XX3.js.map → chunk-MYCCO6AA.js.map} +0 -0
- /package/dist/{chunk-66JOLMWS.js.map → chunk-MZCGHPIJ.js.map} +0 -0
- /package/dist/{chunk-B4ZU34JQ.js.map → chunk-N6II66E5.js.map} +0 -0
- /package/dist/{chunk-BK7AJUWB.js.map → chunk-NIV7RPWQ.js.map} +0 -0
- /package/dist/{chunk-TLHF75OR.js.map → chunk-O55AGDTI.js.map} +0 -0
- /package/dist/{chunk-GGJFRVRD.js.map → chunk-OMYRPMQC.js.map} +0 -0
- /package/dist/{chunk-XHGYZRVW.js.map → chunk-OSTFDWZJ.js.map} +0 -0
- /package/dist/{chunk-BXLOS5AJ.js.map → chunk-OWHERGF2.js.map} +0 -0
- /package/dist/{chunk-WC475JOG.js.map → chunk-P225W4RQ.js.map} +0 -0
- /package/dist/{chunk-TFA6HPOA.js.map → chunk-QCP75JGO.js.map} +0 -0
- /package/dist/{chunk-JRVMCZE3.js.map → chunk-QEYTRFR3.js.map} +0 -0
- /package/dist/{chunk-IZJQNDGI.js.map → chunk-R6HMAIKM.js.map} +0 -0
- /package/dist/{chunk-VUROWXFL.js.map → chunk-RNGDKFY2.js.map} +0 -0
- /package/dist/{chunk-FF6N7BXC.js.map → chunk-S45INSV3.js.map} +0 -0
- /package/dist/{chunk-2RUTN7PR.js.map → chunk-S7GJNA3K.js.map} +0 -0
- /package/dist/{chunk-RJWL3RHB.js.map → chunk-TFEHSPGQ.js.map} +0 -0
- /package/dist/{chunk-T77AX6DG.js.map → chunk-TQVECGUQ.js.map} +0 -0
- /package/dist/{chunk-OQSSQOKQ.js.map → chunk-TV65NR7L.js.map} +0 -0
- /package/dist/{chunk-HQPFACXO.js.map → chunk-UH3FPX3L.js.map} +0 -0
- /package/dist/{chunk-4EACCIPO.js.map → chunk-UHPHH74F.js.map} +0 -0
- /package/dist/{chunk-DQEMWVMT.js.map → chunk-UVYI6VIX.js.map} +0 -0
- /package/dist/{chunk-6QYPL6NI.js.map → chunk-V45TEVDA.js.map} +0 -0
- /package/dist/{chunk-GCAQ3VUJ.js.map → chunk-VCVEMQJK.js.map} +0 -0
- /package/dist/{chunk-6L63IN3M.js.map → chunk-VEFGHRKV.js.map} +0 -0
- /package/dist/{chunk-E33UJHY3.js.map → chunk-WFDWLWDK.js.map} +0 -0
- /package/dist/{chunk-KQLBXUUQ.js.map → chunk-WYG5UNDY.js.map} +0 -0
- /package/dist/{chunk-MPGLM2VU.js.map → chunk-X7REO4CH.js.map} +0 -0
- /package/dist/{chunk-FZVROZKC.js.map → chunk-XVZA6VXC.js.map} +0 -0
- /package/dist/{chunk-WV254W4V.js.map → chunk-Y3IYKNBY.js.map} +0 -0
- /package/dist/{chunk-FUOAIJKQ.js.map → chunk-Y43X3E24.js.map} +0 -0
- /package/dist/{chunk-3T3VHXUH.js.map → chunk-YAAKVTZA.js.map} +0 -0
- /package/dist/{chunk-VPEIWMUT.js.map → chunk-YLTU2DC4.js.map} +0 -0
- /package/dist/{chunk-65SKM4BF.js.map → chunk-YNBMBL4C.js.map} +0 -0
- /package/dist/{chunk-PJYK7HH2.js.map → chunk-ZWTHVUT5.js.map} +0 -0
- /package/dist/{first-start-migration-QVD22WWS.js.map → first-start-migration-A6PHIBUQ.js.map} +0 -0
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/buffer.ts"],"sourcesContent":["import { log } from \"./logger.js\";\nimport { throwIfAborted } from \"./abort-error.js\";\nimport { scanSignals } from \"./signal.js\";\nimport { BufferSessionPersistence, type BufferStorage } from \"./buffer-persistence.js\";\nimport { selectEvictedBufferKeys } from \"./buffer-eviction.js\";\nimport type {\n BufferEntryState,\n BufferState,\n BufferSurpriseEvent,\n BufferTurn,\n PluginConfig,\n SignalLevel,\n} from \"./types.js\";\nimport { resolvePresentationCapabilities, resolveNamespaceCapabilities } from \"./capabilities.js\";\nimport { resolvePrincipal } from \"./namespaces/principal.js\";\nimport { scopeProfilePlanIdentity, subtractBufferTurns } from \"./buffer-turn-helpers.js\";\nimport { ExtractionDeadlineError } from \"./orchestration/extraction-run.js\";\nimport type { ExtractionBufferSnapshot } from \"./extraction-liveness.js\";\nimport {\n bufferEntryCounts,\n bufferTurnArrayIsSuffixOfSnapshot,\n bufferTurnArraysEqual,\n bufferTurnsEqual,\n copyBufferTurn,\n describeError,\n hasPinnedCaptureScope,\n liveTurnsFromExtractionSnapshot,\n matchingQueuedExtractionPrefixLength,\n probeWithTimeout,\n selectRetainedDeferredTurns,\n type BufferEntryCounts,\n} from \"./buffer-turn-helpers.js\";\n\nexport type TriggerDecision = \"extract_now\" | \"extract_batch\" | \"keep_buffering\";\n\nexport type { BufferSurpriseProbe } from \"./buffer-types.js\";\nimport type { BufferSurpriseProbe, SurpriseTelemetryQueueEntry } from \"./buffer-types.js\";\n\nexport interface AddTurnOutcome {\n decision: TriggerDecision;\n extractionTurns?: BufferTurn[];\n}\n\nexport interface RetainedTurnCleanupOptions {\n abortSignal?: AbortSignal;\n deadlineMs?: number;\n /** Keep cleanup within the accepted namespace. */\n writeNamespaceOverride?: string;\n /** Match the original profile and project binding, not just its write namespace. */\n scopeProfileWritePlan?: import(\"./namespaces/scope-profiles.js\").ResolvedScopeProfilePlan | null;\n codingContext?: import(\"./types.js\").CodingContext;\n}\n\n// Raised from 200 in #3303: live captures were being dropped at busy hours\n// while the single extraction worker drained a backlog.\nconst FALLBACK_MAX_BUFFER_SESSIONS = 1000;\nconst isAcceptedTurn = (t: { acceptedCapture?: boolean; writeNamespaceOverride?: string }): boolean =>\n t.acceptedCapture === true || t.writeNamespaceOverride !== undefined;\n\n/**\n * Upper bound on how long a debounced buffer save may be deferred under\n * sustained activity (issue #1909). Each new turn re-arms the trailing-edge\n * timer, so a steady stream of turns could otherwise push the save out\n * indefinitely and widen the crash-loss window without bound. Once a pending\n * save has been deferred this many debounce windows, the next scheduled save\n * forces an inline flush instead of re-arming.\n */\nconst BUFFER_SAVE_MAX_DEFER_MULTIPLIER = 5;\n\n/**\n * Node's maximum 32-bit `setTimeout` delay (2^31-1 ms, ~24.8 days). A larger\n * value is overflow-clamped by Node to 1ms with a TimeoutOverflowWarning, so we\n * clamp defensively at every timer arm (issue #1909 review round 5) even though\n * parseConfig already caps `bufferSaveDebounceMs` — a directly-constructed config\n * could still exceed it.\n */\nconst MAX_SET_TIMEOUT_MS = 2_147_483_647;\n\ninterface AddTurnMutationResult {\n decision: TriggerDecision;\n signalLevel: SignalLevel;\n priorTurns: BufferTurn[];\n activeTurnsSnapshot: BufferTurn[];\n retainedTurnsSnapshot: BufferTurn[];\n turnCountInWindow: number;\n}\n\nexport class SmartBuffer {\n private state: BufferState;\n private loaded = false;\n private loadPromise: Promise<void> | null = null;\n private readonly surpriseProbe: BufferSurpriseProbe | null;\n private mutationChain: Promise<unknown> = Promise.resolve();\n /**\n * Serialized write chain for `BUFFER_SURPRISE` telemetry events.\n *\n * The telemetry path is fire-and-forget (`addTurn` does not await the\n * ledger append), but multiple concurrent appends would still settle\n * out of order under variable filesystem latency. The report path\n * assumes chronological ordering — it slices the tail of the ledger\n * and treats the most recent entry as the current threshold in force.\n * Chaining ensures each append only runs after the previous settles,\n * preserving wall-clock order.\n *\n * We include a `.catch` on every link so a rejected append does not\n * poison the chain (CLAUDE.md rule #40).\n */\n private surpriseTelemetryWriteChain: Promise<unknown> = Promise.resolve();\n\n /**\n * Debounced buffer-save state (issue #1909). Steady-state buffering used to\n * serialize + rewrite the whole multi-session buffer on EVERY turn. We now\n * coalesce those writes onto a trailing-edge timer; correctness boundaries\n * (extraction trigger, extraction clear, shutdown) still force an immediate\n * flush. `bufferSaveDebounceMs: 0` restores save-every-turn exactly.\n */\n private saveTimer: NodeJS.Timeout | null = null;\n private pendingSave = false;\n /** Wall-clock ms when the currently-pending save was first scheduled (issue\n * #1909). Null when nothing is pending. Used to bound deferral under sustained\n * activity (see BUFFER_SAVE_MAX_DEFER_MULTIPLIER). */\n private firstPendingAtMs: number | null = null;\n\n /** Which sessions' persisted files are stale (per-session incremental saves). */\n private readonly persistence = new BufferSessionPersistence();\n\n constructor(\n private readonly config: PluginConfig,\n private readonly storage: BufferStorage,\n surpriseProbe: BufferSurpriseProbe | null = null,\n ) {\n this.state = { turns: [], lastExtractionAt: null, extractionCount: 0 };\n this.surpriseProbe = surpriseProbe;\n }\n\n /** Distinct sessions admitted before backpressure; configurable (#3303, raised from 200). */\n private get maxSessionEntries(): number {\n // parseConfig defaults, validates, and floors bufferMaxSessions at the I/O\n // boundary; FALLBACK_MAX_BUFFER_SESSIONS covers direct constructions with\n // configs from before the knob existed.\n return this.config.bufferMaxSessions ?? FALLBACK_MAX_BUFFER_SESSIONS;\n }\n\n private enqueueMutation<T>(op: () => Promise<T>): Promise<T> {\n const run = this.mutationChain.catch(() => {}).then(op);\n this.mutationChain = run.catch(() => {});\n return run;\n }\n\n private entryFor(key: string): BufferEntryState {\n if (!this.state.entries) {\n this.state.entries = Object.create(null) as NonNullable<BufferState[\"entries\"]>;\n }\n const entries = this.state.entries as NonNullable<BufferState[\"entries\"]>;\n if (Object.hasOwn(entries, key)) {\n const stored = entries[key];\n // Guard against corrupted state/buffer.json — if the stored entry\n // is not a valid object shape, discard it and recreate.\n if (stored && typeof stored === \"object\" && Array.isArray(stored.turns)) {\n return stored;\n }\n // Corrupted — fall through to recreate.\n }\n const created: BufferEntryState = {\n turns: [],\n lastExtractionAt: null,\n extractionCount: 0,\n };\n entries[key] = created;\n return created;\n }\n\n private peekEntry(key: string): BufferEntryState | null {\n const existing = this.state.entries?.[key];\n if (existing) return existing;\n if (key !== \"default\") return null;\n return {\n turns: Array.isArray(this.state.turns) ? this.state.turns : [],\n lastExtractionAt: this.state.lastExtractionAt ?? null,\n extractionCount:\n typeof this.state.extractionCount === \"number\" ? this.state.extractionCount : 0,\n };\n }\n\n private normalizeState(state: BufferState): BufferState {\n const entries = Object.assign(\n Object.create(null),\n state.entries ?? {},\n ) as NonNullable<BufferState[\"entries\"]>;\n if (!entries.default) {\n entries.default = {\n turns: Array.isArray(state.turns) ? [...state.turns] : [],\n lastExtractionAt: state.lastExtractionAt ?? null,\n extractionCount:\n typeof state.extractionCount === \"number\" ? state.extractionCount : 0,\n };\n }\n return {\n turns: entries.default.turns,\n lastExtractionAt: entries.default.lastExtractionAt,\n extractionCount: entries.default.extractionCount,\n entries,\n };\n }\n\n private entryActivityAt(entry: BufferEntryState): number {\n const lastTurnAt = entry.turns.reduce((latest, turn) => {\n const parsed = Date.parse(turn.timestamp);\n return Number.isFinite(parsed) ? Math.max(latest, parsed) : latest;\n }, -1);\n const lastExtractionAt =\n typeof entry.lastExtractionAt === \"string\"\n ? Date.parse(entry.lastExtractionAt)\n : Number.NaN;\n return Math.max(\n lastTurnAt,\n Number.isFinite(lastExtractionAt) ? lastExtractionAt : -1,\n );\n }\n\n /** Evict oldest entries past this.maxSessionEntries and queue their session files for deletion. */\n private pruneEntries(retainKeys: string[], maxCount = this.maxSessionEntries): void {\n const entries = this.state.entries;\n if (!entries) return;\n const evicted = selectEvictedBufferKeys(entries, retainKeys, maxCount, {\n activityAt: (entry) => this.entryActivityAt(entry),\n holdsAcceptedCapture: (entry) => this.entryHoldsAcceptedCapture(entry),\n });\n for (const key of evicted) delete entries[key];\n this.persistence.markEvicted(evicted);\n if (evicted.length > 0) {\n // Loud degradation (extraction hot-loop hardening, Step 7): buffer\n // pressure during a provider outage is bounded and expected, but must\n // be observable, not silent.\n log.warn(\n `buffer: pruned ${evicted.length} oldest session entr${evicted.length === 1 ? \"y\" : \"ies\"} ` +\n `past the session-entry cap (${this.maxSessionEntries}); buffered turn data for those sessions was dropped`,\n );\n }\n }\n\n private async loadUnlocked(): Promise<void> {\n if (this.loaded) return;\n if (!this.loadPromise) {\n this.loadPromise = this.storage.loadBuffer()\n .then((state) => {\n this.state = this.normalizeState(state);\n this.loaded = true;\n })\n .finally(() => {\n this.loadPromise = null;\n });\n }\n await this.loadPromise;\n }\n\n async load(): Promise<void> {\n await this.enqueueMutation(async () => this.loadUnlocked());\n }\n\n /** Snapshot of buffered, not-yet-extracted work across sessions (#2151): session count,\n * pending-turn total, oldest buffered turn. Counts retained (#562) + active turns per entry. */\n async getBufferSnapshot(): Promise<ExtractionBufferSnapshot> {\n await this.load();\n let bufferedSessionCount = 0;\n let pendingTurnCount = 0;\n let oldestMs = Number.POSITIVE_INFINITY;\n let oldestTurnTimestamp: string | null = null;\n const consider = (turns: BufferTurn[] | undefined): void => {\n if (!Array.isArray(turns) || turns.length === 0) return;\n bufferedSessionCount += 1;\n pendingTurnCount += turns.length;\n const first = turns[0]?.timestamp;\n const parsed = typeof first === \"string\" ? Date.parse(first) : Number.NaN;\n if (Number.isFinite(parsed) && parsed < oldestMs) {\n oldestMs = parsed;\n oldestTurnTimestamp = first ?? null;\n }\n };\n const entries = this.state.entries;\n if (entries) {\n // Retained turns (deferred #562) are pending too - count with active (oldest-first), like getTurns().\n for (const entry of Object.values(entries)) consider([...(entry?.retainedTurns ?? []), ...(entry?.turns ?? [])]);\n } else {\n consider(this.state.turns);\n }\n return { bufferedSessionCount, pendingTurnCount, oldestTurnTimestamp };\n }\n\n /**\n * Reset the buffer to an empty, usable state.\n * Called when the persisted buffer file is corrupt and load() fails,\n * so the buffer can still accept new turns for the rest of the session.\n */\n resetToEmpty(): void {\n for (const key of Object.keys(this.state.entries ?? {})) {\n this.persistence.markDeleted(key);\n }\n this.state = { turns: [], lastExtractionAt: null, extractionCount: 0 };\n this.loaded = true;\n }\n\n private async saveUnlocked(): Promise<void> {\n const { handedOver, drainedTurns } = await this.persistence.persist(this.storage, this.state, (key) => this.peekEntry(key) ?? undefined);\n // A drain tombstone: the turns the drain extracted leave this copy too,\n // so a stale owner never extracts (or persists) them a second time.\n for (const [key, drained] of drainedTurns) {\n const entry = this.peekEntry(key);\n if (!entry) continue;\n entry.turns = subtractBufferTurns(entry.turns, drained);\n if (entry.retainedTurns && entry.retainedTurns.length > 0) {\n entry.retainedTurns = subtractBufferTurns(entry.retainedTurns, drained);\n if (entry.retainedTurns.length === 0) delete entry.retainedTurns;\n }\n if (key === \"default\") this.state.turns = entry.turns;\n }\n if (handedOver.length === 0) return;\n // A drain claim now owns these sessions' turns (durable in the claim\n // file): drop the in-memory entries so this process cannot resurrect\n // them. A later observe for the same key starts a fresh entry whose\n // turns flow into the claim at the next handover.\n const entries = this.state.entries;\n if (!entries) return;\n for (const key of handedOver) {\n delete entries[key];\n if (key === \"default\") {\n this.state.turns = [];\n }\n }\n }\n\n /** Sync gate for the extraction queue: is this session claimed by a buffer drain? */\n isSessionClaimed(key: string): boolean {\n return this.storage.isBufferSessionClaimed?.(key) === true;\n }\n\n /** Per-key pending counts over the IN-MEMORY state (counts only — no turn\n * payloads), for the daemon backlog scheduler's bounded candidate scan. */\n entryCounts(): Map<string, BufferEntryCounts> {\n return bufferEntryCounts(this.state);\n }\n\n async save(): Promise<void> {\n await this.enqueueMutation(async () => this.saveUnlocked());\n }\n\n /**\n * Schedule a coalesced, TRUE trailing-edge buffer save (issue #1909). Only\n * used for the debounced (`bufferSaveDebounceMs > 0`) steady-state buffering\n * path; the debounce-off and correctness-boundary paths save inline within the\n * mutation (see `recordTurnUnlocked`). Each call re-arms the timer from now so\n * the write lands one full window after the LAST turn, not the first. To keep\n * sustained activity from deferring the save without bound (which would widen\n * the crash-loss window), once a pending save has been deferred\n * BUFFER_SAVE_MAX_DEFER_MULTIPLIER windows it is flushed inline instead of\n * re-armed. Runs inside the record mutation, so the inline save is awaited.\n */\n private async scheduleSave(): Promise<void> {\n const ms = Math.min(this.config.bufferSaveDebounceMs, MAX_SET_TIMEOUT_MS);\n const now = Date.now();\n this.pendingSave = true;\n if (this.firstPendingAtMs === null) this.firstPendingAtMs = now;\n if (now - this.firstPendingAtMs >= ms * BUFFER_SAVE_MAX_DEFER_MULTIPLIER) {\n // Staleness cap hit: persist now rather than deferring further. Drop the\n // armed timer, attempt the write, and only mark clean on success — a\n // failure keeps the save pending + re-arms so shutdown/the next timer\n // retries (issue #1909 review round 2), never diverging memory from disk.\n clearTimeout(this.saveTimer ?? undefined);\n this.saveTimer = null;\n try {\n await this.saveUnlocked();\n this.pendingSave = false;\n this.firstPendingAtMs = null;\n } catch (err) {\n log.warn(`buffer.scheduleSave: staleness-cap save failed, keeping it pending: ${describeError(err)}`);\n this.saveTimer = setTimeout(() => {\n this.saveTimer = null;\n void this.flushPendingSave();\n }, ms);\n this.saveTimer.unref?.();\n }\n return;\n }\n // True trailing edge: drop any armed timer and re-arm from now.\n clearTimeout(this.saveTimer ?? undefined);\n this.saveTimer = setTimeout(() => {\n this.saveTimer = null;\n void this.flushPendingSave();\n }, ms);\n this.saveTimer.unref?.();\n }\n\n /** Cancel any armed debounced save without persisting. */\n private cancelScheduledSave(): void {\n if (this.saveTimer) {\n clearTimeout(this.saveTimer);\n this.saveTimer = null;\n }\n this.pendingSave = false;\n this.firstPendingAtMs = null;\n }\n\n /**\n * Persist the current in-memory buffer state inline, keeping the global\n * pending-save state armed until the write SUCCEEDS (issue #1909). On failure\n * the save stays pending and — when debounced — a background retry timer is\n * (re-)armed before the error propagates, so a graceful shutdown flush and the\n * next timer tick both retry. A failed post-mutation save therefore can never\n * silently drop buffered turns, including turns from OTHER sessions that were\n * only in the debounced pending state. The pending flag is cleared (and any\n * armed timer dropped) only after a durable write.\n */\n private async saveNowRetainingPendingOnFailure(context: string): Promise<void> {\n this.pendingSave = true;\n if (this.firstPendingAtMs === null) this.firstPendingAtMs = Date.now();\n try {\n await this.saveUnlocked();\n } catch (err) {\n log.warn(\n `buffer.${context}: inline save failed, keeping it pending for retry: ${describeError(err)}`,\n );\n const ms = Math.min(this.config.bufferSaveDebounceMs, MAX_SET_TIMEOUT_MS);\n if (ms > 0 && !this.saveTimer) {\n this.saveTimer = setTimeout(() => {\n this.saveTimer = null;\n void this.flushPendingSave();\n }, ms);\n this.saveTimer.unref?.();\n }\n throw err;\n }\n this.cancelScheduledSave();\n }\n\n /**\n * Force any pending debounced save to land now (issue #1909). Idempotent and\n * safe to call from outside a mutation (timer tick, shutdown/dispose, or the\n * surprise-promotion path in `addTurnWithOutcome`). Do NOT `await` this from\n * inside a mutation — it enqueues its own mutation and would deadlock the\n * serializer.\n *\n * `throwOnFailure` (review round 7 finding 5): when set, a failed write is\n * rethrown AFTER the keep-pending + re-arm bookkeeping, so the surprise\n * extraction boundary gets the same durability guarantee as the built-in\n * extract triggers (which propagate saveUnlocked failures) instead of\n * proceeding to extract_now on a non-durable triggering turn. Default false\n * keeps the fail-open behavior for the timer/shutdown callers.\n */\n async flushPendingSave(opts?: { throwOnFailure?: boolean }): Promise<void> {\n if (this.saveTimer) {\n clearTimeout(this.saveTimer);\n this.saveTimer = null;\n }\n // resetToEmpty-only deletions are pending work too, even with no\n // pending-save flag set.\n if (!this.pendingSave && !this.persistence.hasPending()) return;\n try {\n await this.enqueueMutation(async () => {\n // Re-check under the serializer: a concurrent flush may have already\n // persisted this pending save. Skip the redundant write (no phantom\n // writes). resetToEmpty-only deletions also land here without a\n // pending-save flag.\n if (!this.pendingSave && !this.persistence.hasPending()) return;\n await this.saveUnlocked();\n // Clear the pending flag INSIDE the same serialized mutation as the\n // write (issue #1909 review). Clearing it after the enqueue await\n // resolved raced an addTurn interleaving between the write and the\n // clear: that turn's scheduleSave re-armed pendingSave + a timer, then\n // this clear wiped it and the debounced flush was lost. Atomic\n // write+clear means any addTurn runs wholly before (its turn is in the\n // write) or wholly after (its pendingSave survives and its timer\n // persists it).\n this.pendingSave = false;\n this.firstPendingAtMs = null;\n });\n } catch (err) {\n // The write failed: memory and disk diverge (issue #1909 review round 2).\n // Keep the save PENDING (do NOT clear pendingSave/firstPendingAtMs) so a\n // graceful shutdown flush and the re-armed timer both retry it — clearing\n // the flag here would drop the buffered turns permanently. Re-arm a\n // background retry when debounced.\n log.warn(`buffer.flushPendingSave: save failed, keeping it pending for retry: ${describeError(err)}`);\n const ms = Math.min(this.config.bufferSaveDebounceMs, MAX_SET_TIMEOUT_MS);\n if (ms > 0 && !this.saveTimer) {\n this.saveTimer = setTimeout(() => {\n this.saveTimer = null;\n void this.flushPendingSave();\n }, ms);\n this.saveTimer.unref?.();\n }\n // Surprise-extraction boundary needs the durability guarantee — surface\n // the failure so the caller does not proceed with a non-durable flush.\n if (opts?.throwOnFailure) throw err;\n return; // fail-open for timer/shutdown callers\n }\n }\n\n async addTurn(bufferKey: string, turn: BufferTurn): Promise<TriggerDecision> {\n return (await this.addTurnWithOutcome(bufferKey, turn)).decision;\n }\n\n /** Admit atomically; refuse at capacity rather than evict accepted work. */\n async addTurnBounded(bufferKey: string, turn: BufferTurn): Promise<AddTurnOutcome | null> {\n const mutation = await this.enqueueMutation(async () => {\n await this.loadUnlocked();\n if (this.peekEntry(bufferKey) === null) {\n this.makeRoomForEntryUnlocked();\n if (Object.keys(this.state.entries ?? {}).length >= this.maxSessionEntries) {\n log.warn(\n `buffer[${bufferKey}]: durable capture refused at the session-entry cap (${this.maxSessionEntries})`,\n );\n return null;\n }\n }\n return this.recordTurnUnlocked(bufferKey, turn);\n });\n if (mutation === null) return null;\n return this.completeAddTurnOutcome(bufferKey, turn, mutation);\n }\n\n /** Free one slot for an accepted capture via the native eviction policy (COS-244c). */\n private makeRoomForEntryUnlocked(): void {\n const entries = this.state.entries ?? {};\n const target = Math.max(0, this.maxSessionEntries - 1);\n const pinned = Object.keys(entries).filter((k) => k === \"default\" || this.entryHoldsAcceptedCapture(entries[k]));\n if (pinned.length <= target) this.pruneEntries([], target); // evict only if it frees a slot\n }\n\n private entryHoldsAcceptedCapture(entry: BufferEntryState | undefined): boolean {\n return (entry?.turns ?? []).some(isAcceptedTurn) || (entry?.retainedTurns ?? []).some(isAcceptedTurn);\n }\n\n async addTurnWithOutcome(\n bufferKey: string,\n turn: BufferTurn,\n ): Promise<AddTurnOutcome> {\n const mutation = await this.enqueueMutation(() => this.recordTurnUnlocked(bufferKey, turn));\n return this.completeAddTurnOutcome(bufferKey, turn, mutation);\n }\n\n /** Run probes outside the mutation lock to avoid reentrancy deadlocks. */\n private async completeAddTurnOutcome(\n bufferKey: string,\n turn: BufferTurn,\n mutation: AddTurnMutationResult,\n ): Promise<AddTurnOutcome> {\n let decision = mutation.decision;\n let extractionTurns: BufferTurn[] | undefined;\n\n // Surprise-gated flush (issue #563). Additive only: if the probe is\n // disabled, unavailable, or the score is below threshold, the decision\n // from the existing trigger logic stands. The probe only ever *promotes*\n // `keep_buffering` → `extract_now`; it never suppresses an existing\n // flush. This preserves the invariant that enabling surprise cannot\n // *reduce* extraction frequency.\n if (\n decision === \"keep_buffering\" &&\n resolvePresentationCapabilities(this.config).bufferSurpriseTrigger &&\n this.surpriseProbe !== null &&\n // Matching the existing \"smart\" branch: surprise is a lower-tier\n // novelty signal that should not second-guess a high-signal hit\n // (which already flushes) or fight `every_n` / `time_based` modes.\n this.config.triggerMode === \"smart\" &&\n mutation.signalLevel !== \"high\"\n ) {\n const surprise = await this.computeSurpriseSafe(bufferKey, turn, mutation.priorTurns);\n if (surprise !== null) {\n const shouldPromote = surprise > this.config.bufferSurpriseThreshold;\n let triggered = false;\n if (shouldPromote) {\n const currentTurns = await this.getExtractionTurnsIfBufferSnapshotStillCurrent(\n bufferKey,\n mutation.activeTurnsSnapshot,\n mutation.retainedTurnsSnapshot,\n );\n if (currentTurns) {\n log.debug(\n `buffer[${bufferKey}]: surprise=${surprise.toFixed(3)} > threshold=${this.config.bufferSurpriseThreshold} → extract_now`,\n );\n decision = \"extract_now\";\n triggered = true;\n extractionTurns = currentTurns;\n // Issue #1909 (review): the record mutation for a `keep_buffering`\n // turn only SCHEDULED a debounced save. Surprise now promotes it to\n // extract_now AFTER that mutation, so force the buffer durable before\n // the caller runs extraction — otherwise state/buffer.json lags the\n // extracted turns by up to the debounce window on a crash. Safe to\n // await here: we are outside the record mutation (deadlock-free).\n // throwOnFailure (round 7 finding 5): match the built-in extract\n // triggers — a failed durability write must NOT silently proceed to\n // extract_now on a non-durable turn.\n await this.flushPendingSave({ throwOnFailure: true });\n } else {\n log.debug(\n `buffer[${bufferKey}]: surprise=${surprise.toFixed(3)} ignored because buffer changed before probe resolved`,\n );\n }\n }\n // Emit telemetry on every scored turn — both triggering and\n // non-triggering — so operators can fit the threshold to real\n // traffic distributions. Fire-and-forget: `addTurn` does NOT\n // await the ledger append, so slow/contended filesystems cannot\n // add JSONL-append latency to every `processTurn`. But we DO\n // serialize writes through a promise chain so concurrent\n // appends settle in wall-clock order — the report path assumes\n // chronological tail rows and reads the most recent as the\n // \"current\" threshold.\n //\n // Project only the fields we need into the queue entry rather\n // than capturing the full `BufferTurn` — under slow filesystem\n // latency the chain can back up, and we must not retain the\n // (potentially large) `turn.content` string for every pending\n // append.\n this.queueSurpriseTelemetryWrite({\n bufferKey,\n turnRole: turn.role,\n sessionKey:\n typeof turn.sessionKey === \"string\" ? turn.sessionKey : null,\n surpriseScore: surprise,\n triggered,\n turnCountInWindow: mutation.turnCountInWindow,\n // Stamp at decision time so backpressure on the write chain\n // does not shift the event's apparent moment away from when\n // the turn was actually scored.\n timestamp: new Date().toISOString(),\n // Snapshot the threshold used to compute `triggered` so a\n // concurrent config mutation cannot retroactively change\n // what the ledger row claims the decision was against.\n threshold: this.config.bufferSurpriseThreshold,\n });\n }\n }\n\n log.debug(\n `buffer[${bufferKey}]: ${mutation.turnCountInWindow} turns, signal=${mutation.signalLevel}, decision=${decision}`,\n );\n if (decision !== \"keep_buffering\" && extractionTurns === undefined) {\n extractionTurns = [\n ...mutation.retainedTurnsSnapshot,\n ...mutation.activeTurnsSnapshot,\n ];\n }\n return extractionTurns ? { decision, extractionTurns } : { decision };\n }\n\n private async recordTurnUnlocked(bufferKey: string, turn: BufferTurn): Promise<AddTurnMutationResult> {\n await this.loadUnlocked();\n const entry = this.entryFor(bufferKey);\n const signal = scanSignals(turn.content, this.config.highSignalPatterns);\n entry.turns.push(turn);\n const decision = this.evaluate(entry, signal.level);\n // Snapshots serve only extraction decisions and surprise-eligible keep_buffering turns (probe, smart mode, sub-high signal); steady-state buffering copies nothing.\n const surpriseEligible = this.surpriseProbe !== null && this.config.triggerMode === \"smart\" && signal.level !== \"high\";\n const snapshotsNeeded = decision !== \"keep_buffering\" || surpriseEligible;\n const priorTurns = snapshotsNeeded ? entry.turns.slice(0, -1) : [];\n const activeTurnsSnapshot = snapshotsNeeded ? entry.turns.map(copyBufferTurn) : [];\n const retainedTurnsSnapshot = snapshotsNeeded ? (entry.retainedTurns ?? []).map(copyBufferTurn) : [];\n if (bufferKey === \"default\") this.state.turns = entry.turns;\n const turnCountInWindow = entry.turns.length;\n\n this.persistence.markDirty(bufferKey);\n this.pruneEntries([bufferKey]);\n if (decision === \"keep_buffering\" && this.config.bufferSaveDebounceMs > 0) {\n // Steady-state buffering: coalesce the whole-state serialize onto a\n // trailing-edge timer (issue #1909) instead of rewriting per turn.\n await this.scheduleSave();\n } else {\n // Persist immediately within this mutation (awaited) when either:\n // - the turn triggered extraction (the buffer must be durable), or\n // - debounce is disabled (bufferSaveDebounceMs <= 0), which reproduces\n // the legacy save-every-turn behavior byte-for-byte.\n // Round 8 thread 4: the write is attempted BEFORE the pending state is\n // cleared, and a failed write leaves it pending (+ a re-armed timer) for\n // shutdown/timer retry instead of dropping the in-memory turns.\n await this.saveNowRetainingPendingOnFailure(\"recordTurn\");\n }\n return {\n decision,\n signalLevel: signal.level,\n priorTurns,\n activeTurnsSnapshot,\n retainedTurnsSnapshot,\n turnCountInWindow,\n };\n }\n\n private async getExtractionTurnsIfBufferSnapshotStillCurrent(\n bufferKey: string,\n activeTurnsSnapshot: readonly BufferTurn[],\n retainedTurnsSnapshot: readonly BufferTurn[],\n ): Promise<BufferTurn[] | null> {\n return this.enqueueMutation(async () => {\n await this.loadUnlocked();\n const entry = this.peekEntry(bufferKey);\n if (!entry) return null;\n const retained = entry.retainedTurns ?? [];\n const stillCurrent =\n bufferTurnArrayIsSuffixOfSnapshot(entry.turns, activeTurnsSnapshot) &&\n bufferTurnArraysEqual(retained, retainedTurnsSnapshot);\n if (!stillCurrent) return null;\n return [...retained, ...entry.turns];\n });\n }\n\n /**\n * Enqueue a telemetry append on the serialized write chain.\n *\n * The chain is a classic `writeChain = writeChain.then(fn).catch(...)`\n * — each link waits for the previous to settle before its append\n * starts, so out-of-order chronology cannot happen even under\n * variable filesystem latency. We always attach `.catch` so one\n * rejection does not poison the chain for the rest of the session\n * (CLAUDE.md rule #40). The error is logged through\n * `emitSurpriseEventSafe` itself, which swallows its own rejections.\n *\n * Public surface is deliberately narrow — only `addTurn` should call\n * this, so the surprise telemetry path stays centralized.\n */\n private queueSurpriseTelemetryWrite(params: SurpriseTelemetryQueueEntry): void {\n this.surpriseTelemetryWriteChain = this.surpriseTelemetryWriteChain\n .then(() => this.emitSurpriseEventSafe(params))\n .catch(() => {\n // `emitSurpriseEventSafe` already handles the logging. We\n // swallow here only so one failure does not break the chain\n // for future writes.\n });\n }\n\n /**\n * Append a single `BUFFER_SURPRISE` telemetry row (issue #563 PR 3).\n *\n * Deliberately swallows write errors: the buffer must never fail to\n * record a turn because the observation ledger is read-only, out of\n * disk, or otherwise unhappy. The log line at debug lets operators\n * confirm the path fired without polluting the error channel.\n */\n private async emitSurpriseEventSafe(\n params: SurpriseTelemetryQueueEntry,\n ): Promise<void> {\n const storage = this.storage;\n if (typeof storage.appendBufferSurpriseEvents !== \"function\") {\n // Older StorageManager / test double without the telemetry sink.\n // Silently skip — core path is still covered by the log line above.\n return;\n }\n const event: BufferSurpriseEvent = {\n event: \"BUFFER_SURPRISE\",\n // Use the decision-time stamp captured when the event was\n // queued, NOT `Date.now()` here — backpressure on the write\n // chain could otherwise shift timestamps into the future relative\n // to when the turn was scored.\n timestamp: params.timestamp,\n bufferKey: params.bufferKey,\n sessionKey: params.sessionKey,\n turnRole: params.turnRole,\n surpriseScore: params.surpriseScore,\n // Use the snapshotted threshold from the queue entry, not the\n // live config — see `SurpriseTelemetryQueueEntry.threshold`\n // doc for the rationale.\n threshold: params.threshold,\n triggeredFlush: params.triggered,\n turnCountInWindow: params.turnCountInWindow,\n };\n try {\n await storage.appendBufferSurpriseEvents([event]);\n } catch (err) {\n // Same guard as `computeSurpriseSafe`: non-Error rejections must\n // not crash the telemetry helper, which would defeat the whole\n // point of isolating the ledger write from the hot path.\n log.debug(\n `buffer[${params.bufferKey}]: surprise telemetry write failed, continuing: ${describeError(err)}`,\n );\n }\n }\n\n /**\n * Invoke the injected surprise probe defensively. Any error (probe throws,\n * embedder unavailable, timeout) is swallowed and logged at debug: the\n * surprise path must never crash the happy-path trigger evaluation. A\n * `null` return indicates \"no score available, fall through to existing\n * triggers\".\n */\n private async computeSurpriseSafe(\n bufferKey: string,\n turn: BufferTurn,\n priorTurns: readonly BufferTurn[],\n ): Promise<number | null> {\n if (!this.surpriseProbe) return null;\n try {\n // Hard timeout around the probe so a hung embedder cannot stall\n // `addTurn()` before `save()`. A slow probe would otherwise\n // prevent the just-appended turn from ever being persisted. The\n // timeout is a soft bound — we race it against the probe, take\n // whichever settles first, and treat the timeout as\n // \"probe unavailable, fall through\" rather than an error that\n // surfaces to the caller.\n const score = await probeWithTimeout(\n this.surpriseProbe.scoreTurn(bufferKey, turn, priorTurns),\n this.config.bufferSurpriseProbeTimeoutMs,\n );\n if (score === null) return null;\n if (typeof score !== \"number\" || !Number.isFinite(score)) {\n log.debug(\n `buffer[${bufferKey}]: surprise probe returned non-finite score (${String(score)}), ignoring`,\n );\n return null;\n }\n // Defensive clamp: formula lives in buffer-surprise.ts, but we never\n // want a misbehaving probe to inject an out-of-range value into the\n // threshold comparison.\n if (score < 0) return 0;\n if (score > 1) return 1;\n return score;\n } catch (err) {\n // `err` may be any thrown value — `throw null` and\n // `Promise.reject(\"x\")` are both legal. Accessing `.message` on a\n // non-Error would itself throw and defeat the failure-isolation\n // contract, so describe the value safely.\n log.debug(\n `buffer[${bufferKey}]: surprise probe failed, falling back to existing triggers: ${describeError(err)}`,\n );\n return null;\n }\n }\n\n private evaluate(entry: BufferEntryState, signalLevel: SignalLevel): TriggerDecision {\n if (this.config.triggerMode === \"smart\") {\n if (signalLevel === \"high\") return \"extract_now\";\n\n if (entry.turns.length >= this.config.bufferMaxTurns) {\n return \"extract_batch\";\n }\n\n if (entry.lastExtractionAt) {\n const elapsed =\n Date.now() - new Date(entry.lastExtractionAt).getTime();\n if (elapsed >= this.config.bufferMaxMinutes * 60_000) {\n return \"extract_batch\";\n }\n }\n\n return \"keep_buffering\";\n }\n\n if (this.config.triggerMode === \"every_n\") {\n return entry.turns.length >= this.config.bufferMaxTurns\n ? \"extract_batch\"\n : \"keep_buffering\";\n }\n\n if (this.config.triggerMode === \"time_based\") {\n if (!entry.lastExtractionAt) {\n return entry.turns.length >= this.config.bufferMaxTurns\n ? \"extract_batch\"\n : \"keep_buffering\";\n }\n const elapsed =\n Date.now() - new Date(entry.lastExtractionAt).getTime();\n return elapsed >= this.config.bufferMaxMinutes * 60_000\n ? \"extract_batch\"\n : \"keep_buffering\";\n }\n\n return \"keep_buffering\";\n }\n\n getTurns(bufferKey = \"default\"): BufferTurn[] {\n const entry = this.peekEntry(bufferKey);\n if (!entry) return [];\n const retained = entry.retainedTurns ?? [];\n // Retained turns (from a previous defer verdict, issue #562 PR 2) are\n // prepended so the chronological order — oldest context first — is\n // preserved for the next extraction pass.\n return [...retained, ...entry.turns];\n }\n\n /**\n * Retain a subset of the current turns across `clearAfterExtraction` so a\n * future extraction pass sees the context behind a deferred candidate\n * (issue #562, PR 2). Callers pass the turns that were seen during the\n * current extraction; the buffer keeps the tail (latest `max` turns) as\n * the retention window. Passing an empty array or `max <= 0` clears the\n * retention slot instead.\n */\n async retainDeferredTurns(\n bufferKey: string,\n turns: BufferTurn[],\n max = 10,\n originalTurns?: readonly BufferTurn[],\n ): Promise<void> {\n await this.enqueueMutation(async () => {\n await this.loadUnlocked();\n const entry = this.entryFor(bufferKey);\n const retained = selectRetainedDeferredTurns(\n entry.retainedTurns ?? [], turns, max, originalTurns,\n );\n if (retained.length > 0) entry.retainedTurns = retained;\n else delete entry.retainedTurns;\n this.persistence.markDirty(bufferKey);\n await this.saveUnlocked();\n });\n }\n\n /**\n * Return the current retention window (issue #562, PR 2). Primarily for\n * tests and diagnostics.\n */\n getRetainedDeferredTurns(bufferKey = \"default\"): BufferTurn[] {\n const entry = this.peekEntry(bufferKey);\n return entry?.retainedTurns ? [...entry.retainedTurns] : [];\n }\n\n /**\n * Clear deferred retention copies for a force-flushed session. Normal\n * extraction preserves these copies so a deferred candidate can be retried;\n * an explicit force flush is the caller's request to drain that context.\n */\n async clearRetainedTurnsForSession(\n sessionKey: string,\n ownerPrincipal?: string,\n options: RetainedTurnCleanupOptions = {},\n ): Promise<void> {\n if (typeof sessionKey !== \"string\" || sessionKey.length === 0) return;\n const normalizedOwnerPrincipal =\n typeof ownerPrincipal === \"string\" && ownerPrincipal.trim().length > 0\n ? ownerPrincipal.trim()\n : undefined;\n const namespacesEnabled = resolveNamespaceCapabilities(this.config).namespaces === true;\n const normalizedCleanupNamespace =\n namespacesEnabled && options.writeNamespaceOverride !== undefined && options.writeNamespaceOverride.length > 0\n ? options.writeNamespaceOverride\n : undefined;\n const assertLifecycle = (): void => {\n throwIfAborted(options.abortSignal, \"extraction force-flush aborted\");\n if (typeof options.deadlineMs === \"number\" && Date.now() >= options.deadlineMs) {\n throw new ExtractionDeadlineError(\"retained_turn_cleanup\");\n }\n };\n assertLifecycle();\n await this.enqueueMutation(async () => {\n assertLifecycle();\n const hadPendingSave = this.pendingSave;\n if (this.saveTimer) {\n clearTimeout(this.saveTimer);\n this.saveTimer = null;\n }\n await this.loadUnlocked();\n assertLifecycle();\n const bufferKeys = this.matchingSessionBufferKeysUnlocked(sessionKey);\n if (bufferKeys.length === 0) {\n if (hadPendingSave) {\n assertLifecycle();\n await this.saveNowRetainingPendingOnFailure(\"clearRetainedTurnsForSession\");\n }\n return;\n }\n let changed = false;\n const matchesCleanupScope = (turn: BufferTurn): boolean => {\n // Legacy unpinned turns keep their existing cleanup semantics.\n if (\n turn.writeNamespaceOverride !== undefined &&\n normalizedCleanupNamespace !== undefined &&\n turn.writeNamespaceOverride !== normalizedCleanupNamespace\n ) {\n return false;\n }\n // Equal write namespaces can still belong to different project bindings.\n if (options.scopeProfileWritePlan && hasPinnedCaptureScope(turn)) {\n if (\n scopeProfilePlanIdentity(turn.scopeProfileWritePlan) !== scopeProfilePlanIdentity(options.scopeProfileWritePlan) &&\n !(turn.acceptedCapture === true && turn.scopeProfileWritePlan === undefined)\n ) {\n return false;\n }\n const ctx = options.codingContext;\n if (\n ctx && turn.codingContext &&\n (turn.codingContext.projectId !== ctx.projectId ||\n turn.codingContext.rootPath !== ctx.rootPath ||\n turn.codingContext.branch !== ctx.branch ||\n turn.codingContext.defaultBranch !== ctx.defaultBranch)\n ) {\n return false;\n }\n }\n return true;\n };\n const belongsToOwner = (turn: BufferTurn): boolean => {\n if (turn.sessionKey !== sessionKey) return false;\n if (!matchesCleanupScope(turn)) return false;\n if (normalizedOwnerPrincipal === undefined) return true;\n if (turn.sessionOwnerPrincipal === normalizedOwnerPrincipal) return true;\n return (\n turn.sessionOwnerPrincipal === undefined &&\n resolvePrincipal(turn.sessionKey, this.config) === normalizedOwnerPrincipal\n );\n };\n for (const bufferKey of bufferKeys) {\n const entry = this.entryFor(bufferKey);\n const retainedTurns = entry.retainedTurns ?? [];\n const remainingTurns = retainedTurns.filter((turn) => !belongsToOwner(turn));\n if (remainingTurns.length === retainedTurns.length) continue;\n changed = true;\n if (remainingTurns.length > 0) entry.retainedTurns = remainingTurns;\n else delete entry.retainedTurns;\n if (bufferKey === \"default\") this.state.turns = entry.turns;\n this.persistence.markDirty(bufferKey);\n }\n if (changed || hadPendingSave) {\n assertLifecycle();\n await this.saveNowRetainingPendingOnFailure(\"clearRetainedTurnsForSession\");\n }\n assertLifecycle();\n });\n }\n\n async findBufferKeyForSession(sessionKey: string): Promise<string | null> {\n const bufferKeys = await this.findBufferKeysForSession(sessionKey);\n return bufferKeys[0] ?? null;\n }\n\n private matchingSessionBufferKeysUnlocked(sessionKey: string): string[] {\n const matches: string[] = [];\n const hasSessionTurns = (entry: BufferEntryState | null | undefined): boolean =>\n [...(entry?.turns ?? []), ...(entry?.retainedTurns ?? [])].some(\n (turn) => typeof turn.sessionKey === \"string\" && turn.sessionKey === sessionKey,\n );\n const directEntry = this.peekEntry(sessionKey);\n if (hasSessionTurns(directEntry)) {\n matches.push(sessionKey);\n }\n\n const entries = this.state.entries ?? {};\n for (const [bufferKey, entry] of Object.entries(entries)) {\n if (!matches.includes(bufferKey) && hasSessionTurns(entry)) {\n matches.push(bufferKey);\n }\n }\n\n return matches;\n }\n\n async findBufferKeysForSession(sessionKey: string): Promise<string[]> {\n if (typeof sessionKey !== \"string\" || sessionKey.length === 0) return [];\n await this.mutationChain.catch(() => {});\n await this.load();\n return this.matchingSessionBufferKeysUnlocked(sessionKey);\n }\n\n async clearAfterExtraction(\n bufferKey = \"default\",\n extractedTurns?: readonly BufferTurn[],\n options?: { allowNonPrefix?: boolean },\n ): Promise<void> {\n await this.enqueueMutation(async () => {\n // Drop any armed debounce TIMER so it cannot fire mid-mutation and race\n // our post-clear write, but KEEP the pending-save state (issue #1909\n // review round 13): the post-clear save below can fail, and clearing the\n // pending flag here would strand buffered turns — including turns from\n // OTHER sessions that only ever entered the debounced pending state — with\n // nothing left to retry them. saveNowRetainingPendingOnFailure clears the\n // pending state only after a durable write, or re-arms it on failure.\n if (this.saveTimer) {\n clearTimeout(this.saveTimer);\n this.saveTimer = null;\n }\n await this.loadUnlocked();\n const entry = this.entryFor(bufferKey);\n if (Array.isArray(extractedTurns)) {\n const liveExtractedTurns = liveTurnsFromExtractionSnapshot(\n entry,\n extractedTurns,\n );\n let clearedLiveTurns = false;\n if (liveExtractedTurns.length > 0) {\n if (options?.allowNonPrefix === true) {\n const remainingTurns = [...entry.turns];\n for (const extractedTurn of liveExtractedTurns) {\n const matchingIndex = remainingTurns.findIndex((liveTurn) =>\n bufferTurnsEqual(liveTurn, extractedTurn),\n );\n if (matchingIndex >= 0) {\n remainingTurns.splice(matchingIndex, 1);\n clearedLiveTurns = true;\n }\n }\n if (clearedLiveTurns) entry.turns = remainingTurns;\n } else {\n const matchedCount = matchingQueuedExtractionPrefixLength(\n entry.turns,\n liveExtractedTurns,\n );\n if (matchedCount > 0) {\n entry.turns = entry.turns.slice(matchedCount);\n clearedLiveTurns = true;\n }\n }\n if (!clearedLiveTurns) {\n log.debug(\n `buffer[${bufferKey}]: extraction clear skipped because live turns changed before clear`,\n );\n }\n }\n if (!clearedLiveTurns) {\n await this.saveNowRetainingPendingOnFailure(\"clearAfterExtraction\");\n return;\n }\n } else {\n entry.turns = [];\n }\n entry.lastExtractionAt = new Date().toISOString();\n entry.extractionCount += 1;\n if (bufferKey === \"default\") {\n this.state.turns = entry.turns;\n this.state.lastExtractionAt = entry.lastExtractionAt;\n this.state.extractionCount = entry.extractionCount;\n }\n this.persistence.markDirty(bufferKey);\n this.pruneEntries([bufferKey]);\n await this.saveNowRetainingPendingOnFailure(\"clearAfterExtraction\");\n });\n }\n\n /**\n * Remove an extraction-window chunk from the buffer WITHOUT recording an\n * extraction (trigger-drain drop path). Used when a chunk fails extraction\n * repeatedly and must be dropped so the session can drain; the caller is\n * responsible for the side-file copy and logging. Returns the number of\n * live turns dropped; 0 when the turns no longer match. `allowNonPrefix`\n * matches by identity, like `clearAfterExtraction`, for callers (native\n * groups) whose chunk can sit behind a pinned capture.\n */\n async dropExtractionChunk(\n bufferKey = \"default\",\n turns: readonly BufferTurn[],\n options?: { allowNonPrefix?: boolean },\n ): Promise<number> {\n return await this.enqueueMutation(async () => {\n await this.loadUnlocked();\n const entry = this.entryFor(bufferKey);\n // A window's head can be deferred-retained turns (retainDeferredTurns): take those out of the chunk first.\n const retained = [...(entry.retainedTurns ?? [])];\n const liveTurns: BufferTurn[] = [];\n for (const turn of turns) {\n const at = retained.findIndex((kept) => bufferTurnsEqual(kept, turn));\n if (at >= 0) retained.splice(at, 1);\n else liveTurns.push(turn);\n }\n const retainedDropped = (entry.retainedTurns?.length ?? 0) - retained.length;\n let remaining = entry.turns;\n if (options?.allowNonPrefix === true) {\n remaining = [...entry.turns];\n for (const turn of liveTurns) {\n const at = remaining.findIndex((live) => bufferTurnsEqual(live, turn));\n if (at >= 0) remaining.splice(at, 1);\n }\n } else {\n remaining = entry.turns.slice(matchingQueuedExtractionPrefixLength(entry.turns, liveTurns));\n }\n const matchedCount = entry.turns.length - remaining.length + retainedDropped;\n if (matchedCount === 0) return 0;\n if (this.saveTimer) {\n clearTimeout(this.saveTimer);\n this.saveTimer = null;\n }\n entry.turns = remaining;\n if (retainedDropped > 0) {\n if (retained.length > 0) entry.retainedTurns = retained;\n else delete entry.retainedTurns;\n }\n if (bufferKey === \"default\") {\n this.state.turns = entry.turns;\n }\n this.persistence.markDirty(bufferKey);\n this.pruneEntries([bufferKey]);\n await this.saveNowRetainingPendingOnFailure(\"dropExtractionChunk\");\n return matchedCount;\n });\n }\n\n getExtractionCount(bufferKey = \"default\"): number {\n return this.peekEntry(bufferKey)?.extractionCount ?? 0;\n }\n\n /**\n * Await any pending `BUFFER_SURPRISE` telemetry writes.\n *\n * The telemetry path is fire-and-forget from the hot path's point of\n * view, but tests and before-exit hooks sometimes need to make sure\n * the ledger has been flushed before they assert on its contents or\n * close the process. This method resolves once the current chain\n * head has settled; new writes scheduled after this call return a\n * separate, later settlement.\n *\n * Never throws — the chain already catches its own rejections.\n */\n async flushSurpriseTelemetry(): Promise<void> {\n await this.surpriseTelemetryWriteChain;\n }\n}\n"],"mappings":"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","names":[]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/transfer/capsule-import.ts"],"sourcesContent":["import { lstat, mkdir, readFile, realpath, stat, unlink, writeFile } from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { createHash, randomUUID } from \"node:crypto\";\nimport { gunzipSync } from \"node:zlib\";\nimport { bumpMemoryCorpusVersionForDir } from \"../memory-corpus-version.js\";\nimport {\n HistoricalEntityCanonicalIdCache,\n readEntityRefFromFrontmatter,\n canonicalizeEntityRefFrontmatter,\n classifyEntityRefWritePath,\n repairContentAfterJournalMove,\n requestEntityCanonicalIdReconcile,\n} from \"../storage/entity-canonical-id-references.js\";\nimport { withRawEntityPageMutation } from \"../storage/entity-canonical-id-lock.js\";\nimport {\n createVersion,\n type VersioningConfig,\n type VersioningLogger,\n} from \"../page-versioning.js\";\nimport {\n assertIsDirectoryNotSymlink,\n assertRealpathInsideRoot,\n fromPosixRelPath,\n isPathInsideRoot,\n sha256String,\n} from \"./fs-utils.js\";\nimport {\n parseExportBundle,\n type CapsuleBlock,\n type ExportManifestV2,\n type ExportMemoryRecordV1,\n} from \"./types.js\";\nimport { isEncryptedCapsuleFile, decryptCapsuleFileInMemory } from \"./capsule-crypto.js\";\n\n/**\n * Conflict-resolution mode for {@link importCapsule}. Inverse of the\n * `mode` selector in PR 4/6's CLI surface.\n *\n * - `\"skip\"` (default) — when a target file already exists at the same\n * relative path, leave it untouched. The corresponding record is reported\n * via {@link ImportCapsuleResult.skipped}.\n * - `\"overwrite\"` — when a target file exists, snapshot the prior content\n * via {@link createVersion} (gotcha #54: write-before-delete; gotcha #25:\n * don't destroy old state until new state is confirmed), then write the\n * incoming content. The snapshot's `note` includes the source capsule id.\n * - `\"fork\"` — never overwrite. Every record is rebased under\n * `forks/<capsule-id>/<original-path>` and any YAML-frontmatter `id:`\n * field is rewritten to a new fork-scoped value. The original tree is\n * not touched.\n *\n * The mode is selected once per call and applied uniformly to every record\n * in the bundle. Mixed-mode imports are not supported by design — that\n * would let a single corrupted manifest silently land partial state.\n */\nexport type ImportCapsuleMode = \"skip\" | \"overwrite\" | \"fork\";\n\n/**\n * Options accepted by {@link importCapsule}.\n *\n * `archivePath` — absolute or cwd-relative path to a `.capsule.json.gz`\n * archive produced by `exportCapsule`. The archive must contain a V2\n * bundle (`schemaVersion: 2`); V1 archives are rejected.\n *\n * `root` — absolute or cwd-relative path to the memory directory that\n * will receive the records. Must be an existing directory.\n *\n * `mode` — see {@link ImportCapsuleMode}. Defaults to `\"skip\"`.\n *\n * `versioning` — optional page-versioning config used by `mode: \"overwrite\"`\n * to snapshot prior content before replacing it. Snapshots are skipped when\n * not provided or when `enabled === false`. Tests pass an enabled config\n * to assert snapshot creation; production callers thread the\n * orchestrator's resolved versioning config.\n *\n * `log` — optional logger forwarded to {@link createVersion}.\n *\n * `now` — optional clock override (ms epoch) used to derive deterministic\n * fork ids in tests. Production callers omit this.\n */\nexport interface ImportCapsuleOptions {\n archivePath: string;\n root: string;\n mode?: ImportCapsuleMode;\n versioning?: VersioningConfig;\n log?: VersioningLogger;\n now?: number;\n /**\n * Memory directory whose secure-store keyring is used when the archive is\n * encrypted. Required when `archivePath` ends with `.enc` or when the file\n * starts with the REMNIC-ENC magic header. If `memoryDir` is omitted and the\n * archive turns out to be encrypted, `importCapsule` throws a clear error\n * rather than silently failing.\n *\n * When provided and the archive is NOT encrypted, the value is ignored.\n */\n memoryDir?: string;\n /**\n * Original secure-store passphrase for encrypted format-v2 archives. When\n * supplied, the importer derives the archive key from the embedded KDF\n * header, enabling restore without the source machine's unlocked keyring.\n */\n passphrase?: string;\n}\n\nexport interface ImportCapsuleSkippedRecord {\n /** Original record path (capsule-relative, posix). */\n path: string;\n /**\n * Why this record was not written.\n *\n * `\"exists\"` — the target path already existed and the mode was `\"skip\"`,\n * OR the computed fork path already existed in fork mode.\n *\n * `\"checksum_mismatch\"` is intentionally absent: a checksum failure aborts\n * the import entirely (fail-closed) rather than skipping the offending\n * record, so no `ImportCapsuleSkippedRecord` is ever produced for it.\n */\n reason: \"exists\";\n}\n\nexport interface ImportCapsuleImportedRecord {\n /** Capsule-relative posix path the record carried. */\n sourcePath: string;\n /** Memory-dir-relative posix path the file was written to. */\n targetPath: string;\n /** Whether a prior version snapshot was taken (overwrite-mode only). */\n snapshotted: boolean;\n /** Whether the frontmatter `id:` field was rewritten (fork-mode only). */\n rewroteId: boolean;\n}\n\nexport interface ImportCapsuleResult {\n /** Records that landed on disk. */\n imported: ImportCapsuleImportedRecord[];\n /** Records that were not written. */\n skipped: ImportCapsuleSkippedRecord[];\n /** The manifest decoded from the archive. */\n manifest: ExportManifestV2;\n}\n\n/**\n * Pure async function that imports a capsule archive into a memory\n * directory. Inverse of {@link import(\"./capsule-export.js\").exportCapsule}.\n *\n * Sequence:\n * 1. Read + gunzip + JSON.parse the archive.\n * 2. Validate the bundle through `parseExportBundle` (V1 rejected).\n * 3. Verify each record's content sha256 against the manifest entry.\n * Any mismatch aborts the import BEFORE any file is written —\n * partial-write recovery would require a full rollback we cannot\n * offer cheaply, so we fail closed (gotcha #25).\n * 4. Apply the selected {@link ImportCapsuleMode} to every record.\n *\n * Determinism guarantees:\n * - Imported records are returned sorted by `sourcePath`.\n * - Skipped records are returned sorted by `path`.\n * - Fork-mode rebases under a stable `forks/<capsule-id>/` prefix so\n * repeated fork imports of the same capsule shape land in predictable\n * locations (subsequent fork imports still skip-on-exist because the\n * target tree is computed from the source path).\n */\nexport async function importCapsule(\n opts: ImportCapsuleOptions,\n): Promise<ImportCapsuleResult> {\n const archiveAbs = path.resolve(opts.archivePath);\n const rootAbs = path.resolve(opts.root);\n await assertIsDirectoryNotSymlink(rootAbs, \"importCapsule\", \"root\");\n\n const mode: ImportCapsuleMode = opts.mode ?? \"skip\";\n // Reject unknown mode values up-front (rule 51). TypeScript callers get a\n // compile-time check via the union type; JS/CLI callers get a runtime error\n // here before any write begins — not a silent destructive fallback.\n if (mode !== \"skip\" && mode !== \"overwrite\" && mode !== \"fork\") {\n throw new Error(\n `importCapsule: unknown mode ${JSON.stringify(mode)}; expected \"skip\", \"overwrite\", or \"fork\"`,\n );\n }\n\n // Auto-detect encrypted archives. If the file ends with `.enc` or starts\n // with the REMNIC-ENC magic header, decrypt it first before gunzip+parse.\n // Rule 48: default is \"not encrypted\"; we only attempt decryption when the\n // header is definitively present, never on ambiguous content.\n const encrypted = await isEncryptedCapsuleFile(archiveAbs);\n let raw: Buffer;\n if (encrypted) {\n if (!opts.memoryDir && !opts.passphrase) {\n throw new Error(\n `importCapsule: archive is encrypted but 'memoryDir' was not provided. ` +\n `Pass the memory directory so the secure-store key can be retrieved, ` +\n `provide the original passphrase for a format-v2 archive restore, ` +\n `or run \\`remnic secure-store unlock\\` before importing.`,\n );\n }\n raw = await decryptCapsuleFileInMemory(archiveAbs, opts.memoryDir, {\n passphrase: opts.passphrase,\n });\n } else {\n raw = await readFile(archiveAbs);\n }\n const json = gunzipSync(raw).toString(\"utf-8\");\n let parsedJson: unknown;\n try {\n parsedJson = JSON.parse(json);\n } catch (cause) {\n throw new Error(\n `importCapsule: archive is not valid JSON after gunzip: ${archiveAbs}`,\n { cause: cause as Error },\n );\n }\n\n const parsed = parseExportBundle(parsedJson);\n if (parsed.capsuleVersion !== 2) {\n // PR 1/6 ships a V1 reader; capsule import is V2-only by design. A V1\n // bundle does not carry a capsule block, so fork-mode (which depends on\n // `capsule.id` for the rebase prefix) cannot work. Reject up-front per\n // gotcha #51 instead of silently routing to a different code path.\n throw new Error(\n \"importCapsule: archive is V1; only V2 capsule archives are supported\",\n );\n }\n const bundle = parsed.bundle as { manifest: ExportManifestV2; records: ExportMemoryRecordV1[] };\n const manifest = bundle.manifest;\n const capsule = manifest.capsule;\n\n // Build a path → manifest-entry index for O(1) checksum lookup. The\n // manifest is the source of truth; records-without-manifest-entries and\n // manifest-entries-without-records are both treated as corruption.\n const manifestIndex = new Map<string, ExportManifestV2[\"files\"][number]>();\n for (const f of manifest.files) {\n manifestIndex.set(f.path, f);\n }\n if (manifestIndex.size !== manifest.files.length) {\n throw new Error(\n \"importCapsule: manifest contains duplicate file paths\",\n );\n }\n const recordPaths = new Set<string>();\n for (const rec of bundle.records) {\n if (recordPaths.has(rec.path)) {\n throw new Error(\n `importCapsule: bundle contains duplicate record path: ${rec.path}`,\n );\n }\n recordPaths.add(rec.path);\n }\n\n // Phase 1: verify every record matches its manifest entry AND validate all\n // target paths before any filesystem mutation. We do both in a single pass\n // so a corrupted or malicious archive cannot leave the memory dir partially\n // written (fail-closed per gotcha #25).\n //\n // The real root is resolved once via realpath so the subsequent per-record\n // inside-root checks are symlink-aware: a record path like `facts/a.md`\n // cannot write outside the intended sandbox via a symlinked subdirectory\n // (Codex P1 feedback). If realpath fails (root does not exist) the earlier\n // assertIsDirectoryNotSymlink call already threw, so this should always succeed.\n const rootReal = await realpath(rootAbs).catch(() => rootAbs);\n\n // Tracks normalized, case-folded target paths seen so far in phase 1. Maps\n // targetAbs.toLowerCase() → first source path so the collision error can name\n // both offending entries. Two manifest entries whose computed target paths\n // normalize to the same absolute path (e.g. `subdir/file.md` and\n // `subdir/./file.md`, or differing case on case-insensitive filesystems such\n // as macOS and Windows) would both try to write the same inode — the second\n // would silently overwrite the first. We reject the import up-front before\n // any write (Codex P2 thread on PR #741, line 283).\n const seenTargetPaths = new Map<string, string>();\n\n for (const rec of bundle.records) {\n // Checksum validation.\n const entry = manifestIndex.get(rec.path);\n if (!entry) {\n throw new Error(\n `importCapsule: archive checksum mismatch (record without manifest entry: ${rec.path})`,\n );\n }\n const { sha256, bytes } = sha256String(rec.content);\n if (sha256 !== entry.sha256 || bytes !== entry.bytes) {\n throw new Error(\n `importCapsule: archive checksum mismatch for ${rec.path}: ` +\n `expected sha256=${entry.sha256} bytes=${entry.bytes}, ` +\n `got sha256=${sha256} bytes=${bytes}`,\n );\n }\n\n // Source path validation: reject any record whose posix source path\n // contains a `..` segment, an absolute prefix, or any component that would\n // let fork-mode bypass its isolation invariant. This check runs before\n // `computeTargetPath` so that fork mode's `forks/<id>/` rebase cannot be\n // bypassed by a path like `../../profile.md` (which `path.join` would\n // silently collapse, landing the file inside root but outside the fork\n // prefix — Cursor medium thread #741). Rejecting `..` up-front is simpler\n // and more robust than normalising after the fact.\n //\n // Extended guard (Codex P1 #741 round 5): also reject paths containing\n // backslash separators (`\\`), which are Windows path separators. On Windows\n // a path like `..\\\\escape.md` or `subdir\\\\..\\\\..\\\\escape.md` would resolve\n // to a parent traversal but would bypass a purely posix-split check.\n // Additionally, after posix-normalizing the path we re-check for a leading\n // `..` or `/` — this catches edge-cases such as `subdir/../..` where the\n // individual split segments are not `..` but the normalized result is.\n if (rec.path.includes(\"\\\\\")) {\n throw new Error(\n `importCapsule: record path contains backslash separators (Windows-style paths are not allowed): ${rec.path}`,\n );\n }\n const posixNormalized = path.posix.normalize(rec.path);\n if (\n rec.path.startsWith(\"/\") ||\n rec.path.split(\"/\").some((seg) => seg === \"..\") ||\n posixNormalized.startsWith(\"..\") ||\n posixNormalized.startsWith(\"/\")\n ) {\n throw new Error(\n `importCapsule: record path escapes target root: ${rec.path}`,\n );\n }\n\n // Path-traversal validation (moved here from phase 2 per Cursor feedback:\n // the traversal check must run before ANY write so a malicious archive\n // with an escaping path that sorts last cannot land partial writes).\n //\n // We build targetAbs relative to the real-resolved root (rootReal) so\n // that the inside-root check is symlink-aware: if `rootReal !== rootAbs`\n // (the import root is itself behind a symlink), we still compare against\n // the canonical path (Codex P1).\n const targetRel = computeTargetPath(rec.path, mode, capsule.id);\n const targetAbs = path.join(rootReal, fromPosixRelPath(targetRel));\n if (!isPathInsideRoot(rootReal, targetAbs)) {\n throw new Error(\n `importCapsule: record path escapes target root: ${rec.path}`,\n );\n }\n\n // Symlink-aware containment check (Cursor thread #741 line 247/264).\n // The lexical check above handles `..`-traversal but cannot detect\n // symlinked subdirectories that point outside the root. We resolve the\n // nearest existing ancestor of the target via realpath to catch symlinks\n // anywhere in the path components. If any resolved prefix escapes rootReal,\n // the import is rejected before any write. This applies to ALL modes,\n // including fork mode whose rebase prefix may itself traverse a symlink.\n await assertRealpathInsideRoot(rootReal, targetAbs, rec.path, \"importCapsule\");\n\n // Target-file symlink check (Codex P1 #741 round 4, line 260).\n // `assertRealpathInsideRoot` resolves the nearest existing *ancestor* to\n // catch symlinked parent directories, but if the TARGET FILE itself already\n // exists and is a symlink, that check passes (the parent is real) while the\n // write would silently follow the symlink to a path outside root. We\n // therefore lstat the target directly: if it exists and is a symlink we\n // reject the record up-front. For new files the parent-canonicalization\n // performed above is sufficient; no realpath of a non-existent file is\n // needed.\n const targetLstat = await lstat(targetAbs).catch(() => null);\n if (targetLstat !== null && targetLstat.isSymbolicLink()) {\n throw new Error(\n `importCapsule: record target is a symlink and cannot be written to safely: ${rec.path}`,\n );\n }\n\n // Duplicate normalized target path detection (Codex P2 #741, line 283).\n // `path.join` already normalises `.` segments (e.g. `subdir/./file.md` →\n // `subdir/file.md`). On case-insensitive filesystems (macOS default,\n // Windows), two paths that differ only in case would resolve to the same\n // inode. We fold the dedup key to lowercase so that `subdir/File.md` and\n // `subdir/file.md` are detected as duplicates before any write occurs.\n // This is intentionally unconditional: the cost of an extra `.toLowerCase()`\n // on case-sensitive filesystems is negligible, and a defensive lowercase\n // is far simpler than probing filesystem case-sensitivity at runtime.\n const dedupKey = targetAbs.toLowerCase();\n const firstSourcePath = seenTargetPaths.get(dedupKey);\n if (firstSourcePath !== undefined) {\n throw new Error(\n `importCapsule: manifest contains two entries that resolve to the same target path: ` +\n `\"${firstSourcePath}\" and \"${rec.path}\" both map to \"${targetRel}\"`,\n );\n }\n seenTargetPaths.set(dedupKey, rec.path);\n }\n // Detect manifest-only entries (missing record). Treat as corruption.\n for (const f of manifest.files) {\n if (!recordPaths.has(f.path)) {\n throw new Error(\n `importCapsule: archive checksum mismatch (manifest entry without record: ${f.path})`,\n );\n }\n }\n\n // Phase 2: apply the mode. All records were validated in phase 1; we now\n // write to disk. Per-record errors propagate; each individual write is\n // atomic (mkdir + writeFile pair).\n const imported: ImportCapsuleImportedRecord[] = [];\n const skipped: ImportCapsuleSkippedRecord[] = [];\n // The target's completed migration journal is a write boundary here too\n // (issue #2213): a capsule can carry pre-migration memories whose entityRef\n // the target already renamed, and no recurring reconciliation pass exists\n // to absorb a stale ref after the fact. Read through the identity-keyed\n // cache PER RECORD so a peer migration completing mid-import is picked up\n // for every record written after it (one lstat per record).\n const journalStateDir = path.join(rootReal, \"state\");\n const historicalIdCache = new HistoricalEntityCanonicalIdCache();\n // Entity pages in THIS capsule can re-establish collisions the target's\n // journal already resolved (Codex P1): once the next migration parks the\n // mapping, a memory canonicalized onto the canonical claimant is\n // permanently misattributed. Memories whose ref targets a capsule-supplied\n // entity id import byte-faithful behind the reconcile marker instead; the\n // migration settles the collision and rewrites references per its verdict.\n const capsuleEntityIds = new Set(\n bundle.records\n .map((record) => computeTargetPath(record.path, mode, capsule.id))\n .filter((rel) => classifyEntityRefWritePath(rootReal, path.join(rootReal, fromPosixRelPath(rel))) === \"entity\")\n .map((rel) => path.basename(rel, \".md\")),\n );\n\n // Sort by source path so the imported/skipped lists are deterministic\n // regardless of bundle order. (`exportCapsule` already sorts; we re-sort\n // defensively in case a hand-edited archive ships records in another order.)\n const sortedRecords = [...bundle.records].sort((a, b) =>\n a.path.localeCompare(b.path),\n );\n\n // The reconcile marker is published ONCE, after every record landed\n // (Codex P1, round 21): a per-record marker could be consumed by a peer\n // between a legacy-preserving memory write and its colliding entity page,\n // canonicalizing the memory onto the wrong claimant irreversibly.\n let reconcileNeeded = false;\n const applyRecords = async (): Promise<void> => {\n for (const rec of sortedRecords) {\n const targetRel = computeTargetPath(rec.path, mode, capsule.id);\n // Use rootReal (realpath-resolved) to stay consistent with phase 1's\n // traversal validation and avoid writing through stale symlinks.\n const targetAbs = path.join(rootReal, fromPosixRelPath(targetRel));\n\n const exists = await fileExistsAt(targetAbs);\n\n // Skip logic applies to both `skip` mode (explicit) and `fork` mode\n // (Cursor medium: fork mode must also be skip-on-exist for the computed\n // fork path, because production imports generate non-deterministic IDs —\n // so re-importing the same capsule should not overwrite user edits in the\n // fork tree or silently change file identities by generating new IDs).\n if ((mode === \"skip\" || mode === \"fork\") && exists) {\n skipped.push({ path: rec.path, reason: \"exists\" });\n continue;\n }\n\n let snapshotted = false;\n if (mode === \"overwrite\" && exists) {\n // Gotcha #54: snapshot BEFORE overwriting. If snapshot creation fails\n // we abort this record's import rather than silently destroying\n // history. The page-versioning module is responsible for atomic\n // sidecar writes; we just call it.\n if (opts.versioning && opts.versioning.enabled) {\n const prior = await readFile(targetAbs, \"utf-8\").catch(() => \"\");\n await createVersion(\n targetAbs,\n prior,\n \"manual\",\n opts.versioning,\n opts.log,\n `capsule-import: ${capsule.id}`,\n rootReal,\n );\n snapshotted = true;\n }\n }\n\n let contentToWrite = rec.content;\n let rewroteId = false;\n if (mode === \"fork\") {\n const forked = rewriteFrontmatterIdForFork(contentToWrite, capsule.id, opts.now);\n contentToWrite = forked.content;\n rewroteId = forked.rewrote;\n }\n\n await mkdir(path.dirname(targetAbs), { recursive: true });\n // Resolve at the write itself (mirrors capsule-merge): a peer migration\n // can complete during the snapshot/mkdir awaits, and capsule writes bump\n // only the corpus version, which the migration fingerprint ignores. The\n // post-write REPAIR re-canonicalizes and rewrites when the journal moved\n // across the write itself (a parked mapping cannot be fixed after the\n // fact by the reconcile pass). Only memory-tier records canonicalize;\n // entities/ pages carry relationship TARGETS only\n // rewriteRelationshipTargets understands, so they import byte-faithful\n // with a reconcile marker; everything else imports losslessly.\n const targetKind = classifyEntityRefWritePath(rootReal, targetAbs);\n const rawRef = targetKind === \"memory\" ? readEntityRefFromFrontmatter(contentToWrite) : null;\n if (targetKind === \"memory\" && rawRef !== null && capsuleEntityIds.has(rawRef)) {\n // Ref targets a capsule-supplied entity page (potential collision\n // claimant): keep the legacy bytes; the DEFERRED marker below lets the\n // migration settle the collision and rewrite per its verdict (Codex P1).\n await writeFile(targetAbs, contentToWrite, \"utf-8\");\n reconcileNeeded = true;\n } else if (targetKind === \"memory\") {\n const idsAtWrite = historicalIdCache.get(journalStateDir);\n const rawRecord = contentToWrite;\n contentToWrite = canonicalizeEntityRefFrontmatter(rawRecord, idsAtWrite);\n // Snapshot the pre-write bytes: a repair failure after the write must\n // restore them (or remove a newly created target), or the import\n // reports failure while an overwrite already destroyed the prior state\n // and the skipped corpus bump leaves warm caches stale (AGENTS.md §14).\n const originalBytes = await readFile(targetAbs, \"utf-8\").catch(() => null);\n await writeFile(targetAbs, contentToWrite, \"utf-8\");\n try {\n await repairContentAfterJournalMove({\n stateDir: journalStateDir,\n cache: historicalIdCache,\n idsAtWrite,\n rawContent: rawRecord,\n lastWritten: contentToWrite,\n rewrite: (content) => writeFile(targetAbs, content, \"utf-8\"),\n });\n } catch (err) {\n if (originalBytes === null) {\n await unlink(targetAbs).catch(() => undefined);\n } else {\n await writeFile(targetAbs, originalBytes, \"utf-8\").catch(() => undefined);\n }\n throw err;\n }\n } else {\n await writeFile(targetAbs, contentToWrite, \"utf-8\");\n if (targetKind === \"entity\") {\n reconcileNeeded = true;\n }\n }\n // Bump the corpus sentinel per write (Cursor Medium, #1902): a concurrent\n // readAllMemories during the import must rescan and see records already on\n // disk, never the pre-import corpus, mid-batch.\n bumpMemoryCorpusVersionForDir(rootReal);\n imported.push({\n sourcePath: rec.path,\n targetPath: targetRel,\n snapshotted,\n rewroteId,\n });\n }\n\n // Every record — including every collision claimant entity page — is on\n // disk; NOW the reconcile pass can settle collisions safely.\n if (reconcileNeeded) {\n await requestEntityCanonicalIdReconcile(journalStateDir);\n }\n };\n const firstEntityId = capsuleEntityIds.values().next().value;\n if (firstEntityId === undefined) {\n await applyRecords();\n } else {\n await withRawEntityPageMutation(\n rootReal,\n path.join(rootReal, \"entities\", `${firstEntityId}.md`),\n applyRecords,\n );\n }\n\n // Sort skipped for stable output (see comment above).\n skipped.sort((a, b) => a.path.localeCompare(b.path));\n\n // Per-record corpus bumps happen inside the loop above (Cursor Medium,\n // #1902); no after-batch bump needed here.\n\n return { imported, skipped, manifest };\n}\n\n// ---------------------------------------------------------------------------\n// Path helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Compute the destination relative path for a record under the chosen\n * mode.\n *\n * - `skip` / `overwrite` — identity: the record's posix path is used as-is.\n * - `fork` — rebased under `forks/<capsule-id>/<original-path>` so the\n * original tree is never modified.\n */\nfunction computeTargetPath(\n sourcePosix: string,\n mode: ImportCapsuleMode,\n capsuleId: string,\n): string {\n if (mode === \"fork\") return `forks/${capsuleId}/${sourcePosix}`;\n return sourcePosix;\n}\n\nasync function fileExistsAt(absPath: string): Promise<boolean> {\n const st = await stat(absPath).catch(() => null);\n return st !== null && st.isFile();\n}\n\n// ---------------------------------------------------------------------------\n// Fork-mode id rewriting\n// ---------------------------------------------------------------------------\n\n/**\n * Rewrite a YAML-frontmatter `id:` field to a fork-scoped value.\n *\n * Strategy: we deliberately do NOT parse the entire YAML — memory files\n * use a deterministic top-of-file frontmatter delimited by `---` lines,\n * and the orchestrator's `parseFrontmatter` reads `id` as a top-level\n * key with a single-line scalar value. We mirror that contract:\n *\n * - Detect a leading `---\\n` … `\\n---` block at the top of the file.\n * - Within that block, locate the first `id:` line that is not nested\n * under another key (cheap heuristic: line starts at column 0).\n * - Replace its value with a new fork-id derived from `capsuleId` and\n * a short random suffix (or {@link opts.now} when provided for tests).\n *\n * Files without a frontmatter block, or without an `id:` key inside one,\n * are returned unchanged with `rewrote: false`. This keeps non-memory\n * artifacts (READMEs, transcripts) byte-identical to the source.\n */\nfunction rewriteFrontmatterIdForFork(\n content: string,\n capsuleId: string,\n now: number | undefined,\n): { content: string; rewrote: boolean } {\n // Detect the dominant line ending in the file so we can preserve it when\n // rebuilding the frontmatter delimiters. Splitting/joining on `\\n` silently\n // drops `\\r` from CRLF files, changing the byte content (Cursor thread #741\n // line 467). We detect the ending of the very first line (the opening `---`)\n // and use that as the canonical separator for the reconstructed delimiters.\n // All three cases are covered: CRLF (\\r\\n), LF (\\n), and bare CR (\\r).\n const crlfMatch = /^---(\\r\\n|\\r|\\n)/.exec(content);\n // Fall back to LF if the file starts with `---` but has no newline (EOF).\n const eol = crlfMatch ? crlfMatch[1] : \"\\n\";\n\n // Frontmatter block detection: `---` on its own line at the very start,\n // followed by content, followed by `---` on its own line. The closing\n // delimiter must be at column 0. We capture the trailing terminator\n // (`\\r?\\n` or end-of-string) as a separate group so we can re-emit it\n // verbatim, preserving CRLF/LF/EOF shape from the original file.\n const fmMatch = /^---\\r?\\n([\\s\\S]*?)\\r?\\n---(\\r?\\n|$)/.exec(content);\n if (!fmMatch) return { content, rewrote: false };\n const fmBody = fmMatch[1];\n const fmTrailer = fmMatch[2];\n const fmStart = fmMatch.index;\n const fmEnd = fmStart + fmMatch[0].length;\n\n // Find a top-level `id:` line. Top-level means the line begins at\n // column 0 inside the frontmatter body (no leading whitespace) and the\n // key is exactly `id`. We deliberately accept `id:` and `id : ` shapes\n // but reject `nested_id:` to avoid touching unrelated keys.\n const idLineRe = /^id:[ \\t]*([^\\r\\n]*)$/m;\n const idMatch = idLineRe.exec(fmBody);\n if (!idMatch) return { content, rewrote: false };\n\n const newId = mintForkId(capsuleId, idMatch[1]?.trim() ?? \"\", now);\n const replacedBody = fmBody.replace(idLineRe, `id: ${newId}`);\n // Reconstruct the file: original prefix (always empty here, fmStart === 0\n // by the `^` anchor) + frontmatter delimiters around the rewritten body.\n // Use the detected `eol` for the opening/closing `---` lines so that CRLF\n // files remain CRLF and LF files remain LF, preserving byte-content fidelity\n // (Cursor thread #741 line 467). The captured `fmTrailer` (the newline\n // immediately after the closing `---`, or end-of-string) is re-emitted\n // verbatim so the byte immediately after the closing fence is unchanged.\n const prefix = content.slice(0, fmStart);\n const tail = content.slice(fmEnd);\n const rebuilt = `${prefix}---${eol}${replacedBody}${eol}---${fmTrailer}${tail}`;\n return { content: rebuilt, rewrote: true };\n}\n\n/**\n * Derive a deterministic-when-`now`-is-set fork id from the capsule id and\n * the original record id. Production callers omit `now` and get a UUID\n * suffix; tests pass `now` for byte-stable assertions.\n */\nfunction mintForkId(capsuleId: string, originalId: string, now: number | undefined): string {\n const base = originalId.length > 0 ? originalId : \"fork\";\n if (now === undefined) {\n const suffix = randomUUID().slice(0, 8);\n return `${base}-fork-${capsuleId}-${suffix}`;\n }\n // Deterministic suffix: short hash of (capsuleId, originalId, now). Using\n // sha256 over a sorted-key serialization (gotcha #38) so the suffix does\n // not depend on Object.entries order.\n const payload = JSON.stringify({ c: capsuleId, i: originalId, n: now });\n const suffix = createHash(\"sha256\").update(payload, \"utf-8\").digest(\"hex\").slice(0, 8);\n return `${base}-fork-${capsuleId}-${suffix}`;\n}\n\n// Note: `CapsuleBlock` is re-exported here purely so callers that want to\n// inspect the manifest block don't have to deep-import from `./types.js`.\nexport type { CapsuleBlock };\n"],"mappings":"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","names":["suffix"]}
|