@remnic/core 10.0.83 → 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 +148 -146
- 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.d.ts +1 -1
- 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-S57QBPTA.js → chunk-DPPLNZ3A.js} +4 -6
- package/dist/chunk-DPPLNZ3A.js.map +1 -0
- package/dist/{chunk-EBOHFZI4.js → chunk-E5QEF3JO.js} +2 -2
- package/dist/{chunk-DD6OIFDG.js → chunk-E7V5AK7I.js} +3 -3
- 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-AYTDCMEL.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-SVCHO337.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-mention-index-cache.d.ts.map +1 -1
- package/dist/entity-mention-index-cache.js +2 -1
- package/dist/entity-retrieval.js +27 -26
- 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 +205 -203
- 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 +159 -157
- 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 +149 -147
- 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 +148 -146
- 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-correction-tools.test.ts +45 -0
- 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/compounding/weekly-evidence.test.ts +237 -0
- package/src/entity-mention-index-cache.ts +1 -6
- 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/maintenance/rebuild-memory-projection-runner.test.ts +90 -0
- 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/orchestration/full-corpus-heap-guard.test.ts +52 -0
- package/src/projection-rebuild-config.test.ts +79 -0
- 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/entity-store-symlink.test.ts +44 -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/src/utils/malformed-entity-record.test.ts +69 -0
- package/dist/capsule-merge-XVTQJJ6R.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-AYTDCMEL.js.map +0 -1
- package/dist/chunk-B74NPUHT.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-S57QBPTA.js.map +0 -1
- package/dist/chunk-SVCHO337.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-DD6OIFDG.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
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
import assert from "node:assert/strict";
|
|
12
12
|
import test from "node:test";
|
|
13
13
|
|
|
14
|
+
import { hostname } from "node:os";
|
|
14
15
|
import {
|
|
15
16
|
BacklogScheduler,
|
|
16
17
|
type BacklogSchedulerDeps,
|
|
@@ -23,10 +24,12 @@ import { Orchestrator } from "./orchestrator.js";
|
|
|
23
24
|
import {
|
|
24
25
|
cleanupDir,
|
|
25
26
|
makeHarmonicLifecycleConfig,
|
|
27
|
+
memoryFilesContaining,
|
|
26
28
|
mkTempMemoryDir,
|
|
27
29
|
singleFactResult,
|
|
28
30
|
stubExtraction,
|
|
29
31
|
} from "./testing/orchestrator-lite.js";
|
|
32
|
+
import type { DrainLease } from "./storage/buffer-drain-claims.js";
|
|
30
33
|
import type { BufferTurn } from "./types.js";
|
|
31
34
|
|
|
32
35
|
interface StubState {
|
|
@@ -79,10 +82,10 @@ function makeDeps(state: StubState, queue: ExtractionQueueCoordinator): BacklogS
|
|
|
79
82
|
},
|
|
80
83
|
},
|
|
81
84
|
liveBufferCounts: () => state.counts,
|
|
82
|
-
lease: () => ({
|
|
83
|
-
ownerId
|
|
85
|
+
lease: (ownerId) => ({
|
|
86
|
+
ownerId,
|
|
84
87
|
pid: process.pid,
|
|
85
|
-
host:
|
|
88
|
+
host: hostname(),
|
|
86
89
|
acquiredAt: new Date().toISOString(),
|
|
87
90
|
heartbeatAt: new Date().toISOString(),
|
|
88
91
|
leaseMs: 60_000,
|
|
@@ -113,6 +116,50 @@ function turn(content: string, sessionKey: string, timestamp = "2026-11-01T00:00
|
|
|
113
116
|
return { role: "user", content, timestamp, sessionKey };
|
|
114
117
|
}
|
|
115
118
|
|
|
119
|
+
test("scheduler instances use distinct lease tokens through claim and drain", async () => {
|
|
120
|
+
const issuedOwners: string[] = [];
|
|
121
|
+
const drainedOwners: string[] = [];
|
|
122
|
+
for (let attempt = 0; attempt < 2; attempt += 1) {
|
|
123
|
+
const key = `token-session-${attempt}`;
|
|
124
|
+
const queue = new ExtractionQueueCoordinator(1);
|
|
125
|
+
const state: StubState = {
|
|
126
|
+
counts: new Map([[key, { turns: 1, retainedTurns: 0 }]]),
|
|
127
|
+
claimed: new Set(),
|
|
128
|
+
claimTurns: new Map(),
|
|
129
|
+
claimedThisRun: new Set(),
|
|
130
|
+
outcomes: [emptyOutcome(key)],
|
|
131
|
+
drained: [],
|
|
132
|
+
};
|
|
133
|
+
const deps = makeDeps(state, queue);
|
|
134
|
+
deps.lease = (ownerId) => {
|
|
135
|
+
issuedOwners.push(ownerId);
|
|
136
|
+
return {
|
|
137
|
+
ownerId,
|
|
138
|
+
pid: process.pid,
|
|
139
|
+
host: hostname(),
|
|
140
|
+
acquiredAt: new Date().toISOString(),
|
|
141
|
+
heartbeatAt: new Date().toISOString(),
|
|
142
|
+
leaseMs: 60_000,
|
|
143
|
+
};
|
|
144
|
+
};
|
|
145
|
+
deps.drainKey = async (_drainKey, drainOptions) => {
|
|
146
|
+
drainedOwners.push(drainOptions.ownerId);
|
|
147
|
+
return emptyOutcome(key);
|
|
148
|
+
};
|
|
149
|
+
const scheduler = new BacklogScheduler(deps, { ownerId: "shared-label", pollMs: 3_600_000 });
|
|
150
|
+
scheduler.start();
|
|
151
|
+
try {
|
|
152
|
+
await scheduler.tickForTest();
|
|
153
|
+
await scheduler.settleForTest();
|
|
154
|
+
} finally {
|
|
155
|
+
await scheduler.stop();
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
assert.notEqual(issuedOwners[0], issuedOwners[1]);
|
|
160
|
+
assert.deepEqual([...new Set(drainedOwners)], [...new Set(issuedOwners)]);
|
|
161
|
+
});
|
|
162
|
+
|
|
116
163
|
test("startup drains existing claims and newly buffered sessions within the queue's concurrency", async () => {
|
|
117
164
|
const queue = new ExtractionQueueCoordinator(2);
|
|
118
165
|
const state: StubState = {
|
|
@@ -464,6 +511,399 @@ test("a wired scheduler drains real backlog and resumes after tombstone-consumin
|
|
|
464
511
|
}
|
|
465
512
|
});
|
|
466
513
|
|
|
514
|
+
// ── Real wiring: full cap cycle (fill → refuse → drain → admit) ─────────────
|
|
515
|
+
|
|
516
|
+
test("a full session cap admits a new durable session after the backlog drains", async () => {
|
|
517
|
+
// Real production SmartBuffer admission, real backlog scheduler/drain and
|
|
518
|
+
// the real persistence path (session files → drain claims → memory files).
|
|
519
|
+
// The extraction MODEL is a test double: it records the exact turn slice it
|
|
520
|
+
// is asked to extract and derives one fact from the first turn's content —
|
|
521
|
+
// every durability, admission and clearing decision below is production code.
|
|
522
|
+
const memoryDir = await mkTempMemoryDir("backlog-cap-cycle");
|
|
523
|
+
let orchestrator: Orchestrator | undefined;
|
|
524
|
+
try {
|
|
525
|
+
orchestrator = new Orchestrator(
|
|
526
|
+
makeHarmonicLifecycleConfig(memoryDir, {
|
|
527
|
+
extractionMinChars: 0,
|
|
528
|
+
extractionMinUserTurns: 1,
|
|
529
|
+
bufferMaxSessions: 4, // + the legacy `default` sentinel entry: 3 durable slots
|
|
530
|
+
})
|
|
531
|
+
);
|
|
532
|
+
const calls = stubExtraction(orchestrator, (turns) => singleFactResult(`cap-cycle fact for ${turns[0]?.content ?? "empty"}`));
|
|
533
|
+
const buffer = orchestrator.buffer;
|
|
534
|
+
const defaultNamespace = orchestrator.config.defaultNamespace;
|
|
535
|
+
|
|
536
|
+
// Mirrors production ingestion (turn-ingestion.ts): accepted captures are
|
|
537
|
+
// pinned BEFORE bounded admission, so the cap applies to real durable work.
|
|
538
|
+
const pinned = (key: string, content: string): BufferTurn => ({
|
|
539
|
+
...turn(content, key),
|
|
540
|
+
acceptedCapture: true,
|
|
541
|
+
writeNamespaceOverride: defaultNamespace,
|
|
542
|
+
});
|
|
543
|
+
const capture = async (key: string, content: string): Promise<void> => {
|
|
544
|
+
const outcome = await buffer.addTurnBounded(key, pinned(key, content));
|
|
545
|
+
assert.notEqual(outcome, null, `${key} admitted below the cap`);
|
|
546
|
+
await buffer.flushPendingSave({ throwOnFailure: true });
|
|
547
|
+
};
|
|
548
|
+
await capture("cap-1", "alpha one");
|
|
549
|
+
await capture("cap-2", "beta two");
|
|
550
|
+
await capture("cap-3", "gamma three");
|
|
551
|
+
|
|
552
|
+
// The cap genuinely bites: a fourth durable session is refused, never
|
|
553
|
+
// evicted (#3251).
|
|
554
|
+
assert.equal(
|
|
555
|
+
await orchestrator.buffer.addTurnBounded("cap-4", turn("delta four", "cap-4")),
|
|
556
|
+
null,
|
|
557
|
+
"a fourth session is refused at the cap"
|
|
558
|
+
);
|
|
559
|
+
|
|
560
|
+
const scheduler = wireBacklogScheduler(orchestrator, { pollMs: 3_600_000 });
|
|
561
|
+
try {
|
|
562
|
+
scheduler.start();
|
|
563
|
+
for (let attempt = 0; attempt < 6 && scheduler.snapshot().counters.processed < 3; attempt += 1) {
|
|
564
|
+
await scheduler.tickForTest();
|
|
565
|
+
await scheduler.settleForTest();
|
|
566
|
+
}
|
|
567
|
+
assert.equal(scheduler.snapshot().counters.processed, 3, "all three sessions drained");
|
|
568
|
+
assert.equal(scheduler.snapshot().counters.durableOutputs, 3, "one durable output per session");
|
|
569
|
+
assert.equal(calls.length, 3, "the model double saw one turn slice per session");
|
|
570
|
+
|
|
571
|
+
// Durable extraction kept the content: each session's fact is on disk.
|
|
572
|
+
for (const source of ["alpha one", "beta two", "gamma three"]) {
|
|
573
|
+
const hits = await memoryFilesContaining(memoryDir, `cap-cycle fact for ${source}`);
|
|
574
|
+
assert.ok(hits.length > 0, `content of the drained session was lost: ${source}`);
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
|
|
578
|
+
// The source turns are cleared from both the durable claim and the live
|
|
579
|
+
// SmartBuffer; the claim tombstones remain until a later owner save.
|
|
580
|
+
const claims = orchestrator.storage.bufferDrainClaims();
|
|
581
|
+
assert.deepEqual(
|
|
582
|
+
(await claims.list()).map((l) => l.turnCount).sort(),
|
|
583
|
+
[0, 0, 0],
|
|
584
|
+
"all drained claims are empty tombstones"
|
|
585
|
+
);
|
|
586
|
+
for (const key of ["cap-1", "cap-2", "cap-3"]) {
|
|
587
|
+
assert.deepEqual(orchestrator.buffer.getTurns(key), [], `${key} stale source turns cleared in memory`);
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
const admitted = await orchestrator.buffer.addTurnBounded("cap-4", pinned("cap-4", "delta four"));
|
|
591
|
+
assert.notEqual(admitted, null, "a fully drained backlog frees cap slots for a new session");
|
|
592
|
+
} finally {
|
|
593
|
+
await scheduler.stop();
|
|
594
|
+
}
|
|
595
|
+
} finally {
|
|
596
|
+
await orchestrator?.destroy().catch(() => undefined);
|
|
597
|
+
await cleanupDir(memoryDir);
|
|
598
|
+
}
|
|
599
|
+
});
|
|
600
|
+
|
|
601
|
+
|
|
602
|
+
test("same-key accepted ingress during extraction survives owner-fenced reconciliation", async () => {
|
|
603
|
+
const memoryDir = await mkTempMemoryDir("backlog-drain-ingress-race");
|
|
604
|
+
let orchestrator: Orchestrator | undefined;
|
|
605
|
+
try {
|
|
606
|
+
orchestrator = new Orchestrator(
|
|
607
|
+
makeHarmonicLifecycleConfig(memoryDir, { extractionMinChars: 0, extractionMinUserTurns: 1 })
|
|
608
|
+
);
|
|
609
|
+
const buffer = orchestrator.buffer;
|
|
610
|
+
const namespace = orchestrator.config.defaultNamespace;
|
|
611
|
+
const key = "race-session";
|
|
612
|
+
const oldTurn: BufferTurn = {
|
|
613
|
+
...turn("original captured turn", key),
|
|
614
|
+
acceptedCapture: true,
|
|
615
|
+
writeNamespaceOverride: namespace,
|
|
616
|
+
};
|
|
617
|
+
const newerTurn: BufferTurn = {
|
|
618
|
+
...turn("new accepted turn during drain", key, "2026-11-02T00:00:00.000Z"),
|
|
619
|
+
acceptedCapture: true,
|
|
620
|
+
writeNamespaceOverride: namespace,
|
|
621
|
+
principalOverride: "principal-race",
|
|
622
|
+
};
|
|
623
|
+
const entered = Promise.withResolvers<void>();
|
|
624
|
+
const release = Promise.withResolvers<void>();
|
|
625
|
+
let firstExtraction = true;
|
|
626
|
+
const calls = stubExtraction(orchestrator, async (turns) => {
|
|
627
|
+
if (firstExtraction) {
|
|
628
|
+
firstExtraction = false;
|
|
629
|
+
entered.resolve();
|
|
630
|
+
await release.promise;
|
|
631
|
+
}
|
|
632
|
+
return singleFactResult(`race fact for ${turns[0]?.content ?? "empty"}`);
|
|
633
|
+
});
|
|
634
|
+
|
|
635
|
+
assert.notEqual(await buffer.addTurnBounded(key, oldTurn), null);
|
|
636
|
+
await buffer.flushPendingSave({ throwOnFailure: true });
|
|
637
|
+
let now = 0;
|
|
638
|
+
const scheduler = wireBacklogScheduler(orchestrator, {
|
|
639
|
+
ownerId: "race-owner",
|
|
640
|
+
pollMs: 3_600_000,
|
|
641
|
+
retryBaseMs: 1,
|
|
642
|
+
retryMaxMs: 1,
|
|
643
|
+
now: () => now,
|
|
644
|
+
});
|
|
645
|
+
try {
|
|
646
|
+
scheduler.start();
|
|
647
|
+
await entered.promise;
|
|
648
|
+
assert.equal(
|
|
649
|
+
await orchestrator.storage.bufferDrainClaims().drainedTurnsForOwner(key, "not-race-owner"),
|
|
650
|
+
null,
|
|
651
|
+
"another owner cannot authorize live-buffer reconciliation"
|
|
652
|
+
);
|
|
653
|
+
|
|
654
|
+
// Live ingress saves into the owned claim while the model is still
|
|
655
|
+
// extracting its original snapshot.
|
|
656
|
+
assert.notEqual(await buffer.addTurnBounded(key, newerTurn), null);
|
|
657
|
+
await buffer.flushPendingSave({ throwOnFailure: true });
|
|
658
|
+
release.resolve();
|
|
659
|
+
await scheduler.settleForTest();
|
|
660
|
+
|
|
661
|
+
const pending = await orchestrator.storage.bufferDrainClaims().load(key);
|
|
662
|
+
assert.deepEqual(
|
|
663
|
+
pending?.entry.turns.map((item) => item.content),
|
|
664
|
+
["new accepted turn during drain"],
|
|
665
|
+
"the extracted source is removed while newer accepted ingress remains durable"
|
|
666
|
+
);
|
|
667
|
+
assert.equal(pending?.entry.turns[0]?.principalOverride, "principal-race");
|
|
668
|
+
assert.equal(pending?.entry.turns[0]?.writeNamespaceOverride, namespace);
|
|
669
|
+
assert.equal((await memoryFilesContaining(memoryDir, "race fact for original captured turn")).length, 1);
|
|
670
|
+
|
|
671
|
+
now = 2;
|
|
672
|
+
await scheduler.tickForTest();
|
|
673
|
+
await scheduler.settleForTest();
|
|
674
|
+
assert.equal(scheduler.snapshot().counters.processed, 1, "the newer turn also drains");
|
|
675
|
+
assert.deepEqual(calls.map((batch) => batch.map((item) => item.content)), [
|
|
676
|
+
["original captured turn"],
|
|
677
|
+
["new accepted turn during drain"],
|
|
678
|
+
]);
|
|
679
|
+
assert.equal(calls[1]?.[0]?.principalOverride, "principal-race");
|
|
680
|
+
assert.equal(calls[1]?.[0]?.writeNamespaceOverride, namespace);
|
|
681
|
+
assert.equal((await memoryFilesContaining(memoryDir, "race fact for original captured turn")).length, 1);
|
|
682
|
+
assert.equal((await memoryFilesContaining(memoryDir, "race fact for new accepted turn during drain")).length, 1);
|
|
683
|
+
assert.equal(scheduler.snapshot().counters.durableOutputs, 1);
|
|
684
|
+
} finally {
|
|
685
|
+
release.resolve();
|
|
686
|
+
await scheduler.stop();
|
|
687
|
+
}
|
|
688
|
+
} finally {
|
|
689
|
+
await orchestrator?.destroy().catch(() => undefined);
|
|
690
|
+
await cleanupDir(memoryDir);
|
|
691
|
+
}
|
|
692
|
+
});
|
|
693
|
+
|
|
694
|
+
|
|
695
|
+
|
|
696
|
+
test("a drain tombstones every identical accepted occurrence before live-buffer reconciliation", async () => {
|
|
697
|
+
const memoryDir = await mkTempMemoryDir("buffer-reconcile-identical-turns");
|
|
698
|
+
let orchestrator: Orchestrator | undefined;
|
|
699
|
+
try {
|
|
700
|
+
orchestrator = new Orchestrator(makeHarmonicLifecycleConfig(memoryDir));
|
|
701
|
+
const buffer = orchestrator.buffer;
|
|
702
|
+
const key = "duplicate-session";
|
|
703
|
+
const duplicate = turn("identical accepted occurrence", key);
|
|
704
|
+
const newer = {
|
|
705
|
+
...turn("new live turn after duplicate drain", key, "2026-11-05T00:00:00.000Z"),
|
|
706
|
+
acceptedCapture: true,
|
|
707
|
+
writeNamespaceOverride: orchestrator.config.defaultNamespace,
|
|
708
|
+
};
|
|
709
|
+
const claims = orchestrator.storage.bufferDrainClaims();
|
|
710
|
+
const now = new Date().toISOString();
|
|
711
|
+
const lease: DrainLease = {
|
|
712
|
+
ownerId: "duplicate-owner",
|
|
713
|
+
pid: process.pid,
|
|
714
|
+
host: hostname(),
|
|
715
|
+
acquiredAt: now,
|
|
716
|
+
heartbeatAt: now,
|
|
717
|
+
leaseMs: 60_000,
|
|
718
|
+
};
|
|
719
|
+
|
|
720
|
+
await buffer.addTurn(key, duplicate);
|
|
721
|
+
await buffer.addTurn(key, duplicate);
|
|
722
|
+
await buffer.flushPendingSave({ throwOnFailure: true });
|
|
723
|
+
assert.deepEqual(buffer.getTurns(key), [duplicate, duplicate]);
|
|
724
|
+
assert.equal(await claims.claimFromSessionFile(key, lease), "claimed");
|
|
725
|
+
const claimed = await claims.load(key);
|
|
726
|
+
assert.ok(claimed);
|
|
727
|
+
assert.deepEqual(claimed.entry.turns, [duplicate, duplicate]);
|
|
728
|
+
|
|
729
|
+
await claims.writeEntry(key, lease.ownerId, { turns: [], lastExtractionAt: null, extractionCount: 0 }, claimed.entry);
|
|
730
|
+
assert.deepEqual(await claims.drainedTurns(key), { turns: [duplicate, duplicate], retainedTurns: [] });
|
|
731
|
+
assert.equal(await claims.ack(key, lease.ownerId), "acked");
|
|
732
|
+
|
|
733
|
+
assert.notEqual(await buffer.addTurnBounded(key, newer), null);
|
|
734
|
+
await buffer.flushPendingSave({ throwOnFailure: true });
|
|
735
|
+
assert.deepEqual(buffer.getTurns(key), [newer]);
|
|
736
|
+
assert.deepEqual((await orchestrator.storage.loadBuffer()).entries?.[key]?.turns, [newer]);
|
|
737
|
+
} finally {
|
|
738
|
+
await orchestrator?.destroy().catch(() => undefined);
|
|
739
|
+
await cleanupDir(memoryDir);
|
|
740
|
+
}
|
|
741
|
+
});
|
|
742
|
+
|
|
743
|
+
test("ambiguous identical active and retained turns remain pending after cross-list progress", async () => {
|
|
744
|
+
const memoryDir = await mkTempMemoryDir("buffer-reconcile-cross-list-duplicates");
|
|
745
|
+
let orchestrator: Orchestrator | undefined;
|
|
746
|
+
try {
|
|
747
|
+
orchestrator = new Orchestrator(makeHarmonicLifecycleConfig(memoryDir));
|
|
748
|
+
const buffer = orchestrator.buffer;
|
|
749
|
+
const claims = orchestrator.storage.bufferDrainClaims();
|
|
750
|
+
const key = "cross-list-duplicates";
|
|
751
|
+
const same = turn("same occurrence identity in both lists", key);
|
|
752
|
+
const now = new Date().toISOString();
|
|
753
|
+
const lease: DrainLease = {
|
|
754
|
+
ownerId: "cross-list-owner",
|
|
755
|
+
pid: process.pid,
|
|
756
|
+
host: hostname(),
|
|
757
|
+
acquiredAt: now,
|
|
758
|
+
heartbeatAt: now,
|
|
759
|
+
leaseMs: 60_000,
|
|
760
|
+
};
|
|
761
|
+
|
|
762
|
+
await buffer.addTurn(key, same);
|
|
763
|
+
await buffer.retainDeferredTurns(key, [same], 10, [same]);
|
|
764
|
+
await buffer.flushPendingSave({ throwOnFailure: true });
|
|
765
|
+
assert.deepEqual(buffer.getTurns(key), [same, same]);
|
|
766
|
+
assert.equal(await claims.claimFromSessionFile(key, lease), "claimed");
|
|
767
|
+
const claim = await claims.load(key);
|
|
768
|
+
assert.ok(claim);
|
|
769
|
+
assert.deepEqual(claim.entry.turns, [same]);
|
|
770
|
+
assert.deepEqual(claim.entry.retainedTurns, [same]);
|
|
771
|
+
|
|
772
|
+
// One indistinguishable copy survives in the opposite list. Progress
|
|
773
|
+
// alone cannot prove whether that is the active or retained occurrence.
|
|
774
|
+
await claims.writeEntry(
|
|
775
|
+
key,
|
|
776
|
+
lease.ownerId,
|
|
777
|
+
{ turns: [], retainedTurns: [same], lastExtractionAt: null, extractionCount: 0 },
|
|
778
|
+
claim.entry,
|
|
779
|
+
);
|
|
780
|
+
const drained = await claims.drainedTurnsForOwner(key, lease.ownerId);
|
|
781
|
+
assert.deepEqual(drained, { turns: [], retainedTurns: [] });
|
|
782
|
+
assert.deepEqual(
|
|
783
|
+
(await claims.load(key))?.entry,
|
|
784
|
+
{ turns: [same], retainedTurns: [same], lastExtractionAt: null, extractionCount: 0 },
|
|
785
|
+
"both original source-list occurrences remain pending when provenance is ambiguous",
|
|
786
|
+
);
|
|
787
|
+
assert.ok(drained);
|
|
788
|
+
await buffer.save({ key, drained });
|
|
789
|
+
assert.deepEqual(buffer.getTurns(key), [same, same]);
|
|
790
|
+
} finally {
|
|
791
|
+
await orchestrator?.destroy().catch(() => undefined);
|
|
792
|
+
await cleanupDir(memoryDir);
|
|
793
|
+
}
|
|
794
|
+
});
|
|
795
|
+
|
|
796
|
+
test("claim tombstones clear identical active and retained copies on live save", async () => {
|
|
797
|
+
const memoryDir = await mkTempMemoryDir("buffer-reconcile-retained-provenance");
|
|
798
|
+
let orchestrator: Orchestrator | undefined;
|
|
799
|
+
try {
|
|
800
|
+
orchestrator = new Orchestrator(makeHarmonicLifecycleConfig(memoryDir));
|
|
801
|
+
const buffer = orchestrator.buffer;
|
|
802
|
+
const key = "retained-session";
|
|
803
|
+
const source = turn("same source identity", key);
|
|
804
|
+
const newer = {
|
|
805
|
+
...turn("new live turn", key, "2026-11-04T00:00:00.000Z"),
|
|
806
|
+
acceptedCapture: true,
|
|
807
|
+
writeNamespaceOverride: orchestrator.config.defaultNamespace,
|
|
808
|
+
};
|
|
809
|
+
const claims = orchestrator.storage.bufferDrainClaims();
|
|
810
|
+
const now = new Date().toISOString();
|
|
811
|
+
const lease: DrainLease = {
|
|
812
|
+
ownerId: "retained-owner",
|
|
813
|
+
pid: process.pid,
|
|
814
|
+
host: hostname(),
|
|
815
|
+
acquiredAt: now,
|
|
816
|
+
heartbeatAt: now,
|
|
817
|
+
leaseMs: 60_000,
|
|
818
|
+
};
|
|
819
|
+
|
|
820
|
+
await buffer.addTurn(key, source);
|
|
821
|
+
await buffer.retainDeferredTurns(key, [source]);
|
|
822
|
+
assert.deepEqual(buffer.getTurns(key), [source, source]);
|
|
823
|
+
assert.equal(await claims.claimFromSessionFile(key, lease), "claimed");
|
|
824
|
+
const claimed = await claims.load(key);
|
|
825
|
+
assert.ok(claimed);
|
|
826
|
+
assert.deepEqual(claimed.entry, {
|
|
827
|
+
turns: [source],
|
|
828
|
+
retainedTurns: [source],
|
|
829
|
+
lastExtractionAt: null,
|
|
830
|
+
extractionCount: 0,
|
|
831
|
+
});
|
|
832
|
+
await claims.writeEntry(key, lease.ownerId, { turns: [], lastExtractionAt: null, extractionCount: 0 }, claimed.entry);
|
|
833
|
+
assert.deepEqual(await claims.drainedTurns(key), { turns: [source], retainedTurns: [source] });
|
|
834
|
+
assert.equal(await claims.ack(key, lease.ownerId), "acked");
|
|
835
|
+
|
|
836
|
+
// A new same-key save consumes the tombstone through BufferSessionFiles.
|
|
837
|
+
assert.notEqual(await buffer.addTurnBounded(key, newer), null);
|
|
838
|
+
await buffer.flushPendingSave({ throwOnFailure: true });
|
|
839
|
+
assert.deepEqual(buffer.getTurns(key), [newer]);
|
|
840
|
+
assert.deepEqual(buffer.getRetainedDeferredTurns(key), []);
|
|
841
|
+
const persisted = await orchestrator.storage.loadBuffer();
|
|
842
|
+
assert.deepEqual(persisted.entries?.[key]?.turns, [newer]);
|
|
843
|
+
assert.equal(persisted.entries?.[key]?.retainedTurns, undefined);
|
|
844
|
+
} finally {
|
|
845
|
+
await orchestrator?.destroy().catch(() => undefined);
|
|
846
|
+
await cleanupDir(memoryDir);
|
|
847
|
+
}
|
|
848
|
+
});
|
|
849
|
+
test("a lease takeover after the drained snapshot cannot erase newer scoped ingress", async () => {
|
|
850
|
+
const memoryDir = await mkTempMemoryDir("backlog-takeover-reconcile");
|
|
851
|
+
let orchestrator: Orchestrator | undefined;
|
|
852
|
+
try {
|
|
853
|
+
orchestrator = new Orchestrator(makeHarmonicLifecycleConfig(memoryDir));
|
|
854
|
+
const buffer = orchestrator.buffer;
|
|
855
|
+
const claims = orchestrator.storage.bufferDrainClaims();
|
|
856
|
+
const key = "takeover-session";
|
|
857
|
+
const oldTurn: BufferTurn = {
|
|
858
|
+
...turn("old drained turn", key),
|
|
859
|
+
acceptedCapture: true,
|
|
860
|
+
writeNamespaceOverride: orchestrator.config.defaultNamespace,
|
|
861
|
+
};
|
|
862
|
+
const newerTurn: BufferTurn = {
|
|
863
|
+
...turn("new accepted turn", key, "2026-11-03T00:00:00.000Z"),
|
|
864
|
+
acceptedCapture: true,
|
|
865
|
+
writeNamespaceOverride: orchestrator.config.defaultNamespace,
|
|
866
|
+
principalOverride: "new-owner-principal",
|
|
867
|
+
};
|
|
868
|
+
const lease = (ownerId: string): DrainLease => {
|
|
869
|
+
const now = new Date().toISOString();
|
|
870
|
+
return { ownerId, pid: process.pid, host: hostname(), acquiredAt: now, heartbeatAt: now, leaseMs: 60_000 };
|
|
871
|
+
};
|
|
872
|
+
|
|
873
|
+
assert.notEqual(await buffer.addTurnBounded(key, oldTurn), null);
|
|
874
|
+
await buffer.flushPendingSave({ throwOnFailure: true });
|
|
875
|
+
assert.equal(await claims.claimFromSessionFile(key, lease("owner-one")), "claimed");
|
|
876
|
+
const claimed = await claims.load(key);
|
|
877
|
+
assert.ok(claimed);
|
|
878
|
+
await claims.writeEntry(
|
|
879
|
+
key,
|
|
880
|
+
"owner-one",
|
|
881
|
+
{ turns: [], lastExtractionAt: null, extractionCount: 0 },
|
|
882
|
+
claimed.entry
|
|
883
|
+
);
|
|
884
|
+
const ownerOneSnapshot = await claims.drainedTurnsForOwner(key, "owner-one");
|
|
885
|
+
assert.deepEqual(ownerOneSnapshot, { turns: [oldTurn], retainedTurns: [] });
|
|
886
|
+
assert.ok(ownerOneSnapshot);
|
|
887
|
+
|
|
888
|
+
// Simulate takeover after the old owner's fenced read but before its
|
|
889
|
+
// serialized live-buffer mutation. Progress is monotonic and identities
|
|
890
|
+
// are list-specific, so applying this snapshot cannot erase newer work.
|
|
891
|
+
await claims.releaseLease(key, "owner-one");
|
|
892
|
+
assert.equal(await claims.adopt(key, lease("owner-two")), "adopted");
|
|
893
|
+
assert.notEqual(await buffer.addTurnBounded(key, newerTurn), null);
|
|
894
|
+
await buffer.flushPendingSave({ throwOnFailure: true });
|
|
895
|
+
await buffer.save({ key, drained: ownerOneSnapshot });
|
|
896
|
+
assert.deepEqual(buffer.getTurns(key), [], "the old in-memory entry remains handed to the claim");
|
|
897
|
+
|
|
898
|
+
const afterIngress = await claims.load(key);
|
|
899
|
+
assert.deepEqual(afterIngress?.entry.turns, [newerTurn]);
|
|
900
|
+
assert.equal(afterIngress?.entry.turns[0]?.principalOverride, "new-owner-principal");
|
|
901
|
+
assert.equal(afterIngress?.entry.turns[0]?.writeNamespaceOverride, orchestrator.config.defaultNamespace);
|
|
902
|
+
} finally {
|
|
903
|
+
await orchestrator?.destroy().catch(() => undefined);
|
|
904
|
+
await cleanupDir(memoryDir);
|
|
905
|
+
}
|
|
906
|
+
});
|
|
467
907
|
test("a claim decision unsettled mid-rename keeps the key reserved until it settles", async () => {
|
|
468
908
|
const queue = new ExtractionQueueCoordinator(1);
|
|
469
909
|
const state: StubState = {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { DrainedTurnSets } from "./buffer-turn-helpers.js";
|
|
2
|
+
import type { BufferEntryState, BufferState, BufferSurpriseEvent } from "./types.js";
|
|
2
3
|
import type { BufferSessionSaveOutcome } from "./storage/buffer-drain-claims.js";
|
|
3
4
|
|
|
4
5
|
/**
|
|
@@ -26,8 +27,8 @@ export interface BufferStorage {
|
|
|
26
27
|
export interface BufferPersistOutcome {
|
|
27
28
|
/** Sessions whose in-memory entry moved into a drain claim: the caller must evict them (a claimed session no longer lives in this process). */
|
|
28
29
|
handedOver: string[];
|
|
29
|
-
/** Per session, the turns a drain
|
|
30
|
-
drainedTurns: Map<string,
|
|
30
|
+
/** Per session, the turns a drain extracted, kept in their original lists. */
|
|
31
|
+
drainedTurns: Map<string, DrainedTurnSets>;
|
|
31
32
|
}
|
|
32
33
|
|
|
33
34
|
/**
|
|
@@ -73,17 +74,20 @@ export class BufferSessionPersistence {
|
|
|
73
74
|
storage: BufferStorage,
|
|
74
75
|
state: BufferState,
|
|
75
76
|
peekEntry: (key: string) => BufferEntryState | undefined,
|
|
77
|
+
reconcile?: { key: string; drained: DrainedTurnSets },
|
|
76
78
|
): Promise<BufferPersistOutcome> {
|
|
79
|
+
const drainedTurns = new Map<string, DrainedTurnSets>(
|
|
80
|
+
reconcile ? [[reconcile.key, reconcile.drained]] : [],
|
|
81
|
+
);
|
|
77
82
|
const saveSession = storage.saveBufferSession?.bind(storage);
|
|
78
83
|
const deleteSession = storage.deleteBufferSession?.bind(storage);
|
|
79
84
|
if (!saveSession || !deleteSession) {
|
|
80
85
|
await storage.saveBuffer(state);
|
|
81
86
|
this.dirty.clear();
|
|
82
87
|
this.deleted.clear();
|
|
83
|
-
return { handedOver: [], drainedTurns
|
|
88
|
+
return { handedOver: [], drainedTurns };
|
|
84
89
|
}
|
|
85
90
|
const handedOver: string[] = [];
|
|
86
|
-
const drainedTurns = new Map<string, BufferTurn[]>();
|
|
87
91
|
for (const key of [...this.deleted]) {
|
|
88
92
|
await deleteSession(key);
|
|
89
93
|
this.deleted.delete(key);
|
|
@@ -104,7 +108,12 @@ export class BufferSessionPersistence {
|
|
|
104
108
|
if (outcome?.status === "handed_over") handedOver.push(key);
|
|
105
109
|
// A drain tombstone: the turns it extracted must leave this process's
|
|
106
110
|
// copy too, or the owner would extract them a second time.
|
|
107
|
-
if (
|
|
111
|
+
if (
|
|
112
|
+
outcome?.drainedTurns &&
|
|
113
|
+
(outcome.drainedTurns.turns.length > 0 || outcome.drainedTurns.retainedTurns.length > 0)
|
|
114
|
+
) {
|
|
115
|
+
drainedTurns.set(key, outcome.drainedTurns);
|
|
116
|
+
}
|
|
108
117
|
}
|
|
109
118
|
return { handedOver, drainedTurns };
|
|
110
119
|
}
|
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
import type { BufferEntryState, BufferState, BufferTurn } from "./types.js";
|
|
2
2
|
|
|
3
|
+
/** Extracted identities retain whether they came from active or retained turns. */
|
|
4
|
+
export interface DrainedTurnSets {
|
|
5
|
+
turns: BufferTurn[];
|
|
6
|
+
retainedTurns: BufferTurn[];
|
|
7
|
+
}
|
|
8
|
+
|
|
3
9
|
export interface BufferTurnOwner {
|
|
4
10
|
/**
|
|
5
11
|
* Server-resolved principal that owns this session at the authenticated
|
package/src/buffer.ts
CHANGED
|
@@ -300,16 +300,16 @@ export class SmartBuffer {
|
|
|
300
300
|
this.loaded = true;
|
|
301
301
|
}
|
|
302
302
|
|
|
303
|
-
private async saveUnlocked(): Promise<void> {
|
|
304
|
-
const { handedOver, drainedTurns } = await this.persistence.persist(this.storage, this.state, (key) => this.peekEntry(key) ?? undefined);
|
|
303
|
+
private async saveUnlocked(reconcile?: { key: string; drained: import("./buffer-turn-helpers.js").DrainedTurnSets }): Promise<void> {
|
|
304
|
+
const { handedOver, drainedTurns } = await this.persistence.persist(this.storage, this.state, (key) => this.peekEntry(key) ?? undefined, reconcile);
|
|
305
305
|
// A drain tombstone: the turns the drain extracted leave this copy too,
|
|
306
306
|
// so a stale owner never extracts (or persists) them a second time.
|
|
307
307
|
for (const [key, drained] of drainedTurns) {
|
|
308
308
|
const entry = this.peekEntry(key);
|
|
309
309
|
if (!entry) continue;
|
|
310
|
-
entry.turns = subtractBufferTurns(entry.turns, drained);
|
|
310
|
+
entry.turns = subtractBufferTurns(entry.turns, drained.turns);
|
|
311
311
|
if (entry.retainedTurns && entry.retainedTurns.length > 0) {
|
|
312
|
-
entry.retainedTurns = subtractBufferTurns(entry.retainedTurns, drained);
|
|
312
|
+
entry.retainedTurns = subtractBufferTurns(entry.retainedTurns, drained.retainedTurns);
|
|
313
313
|
if (entry.retainedTurns.length === 0) delete entry.retainedTurns;
|
|
314
314
|
}
|
|
315
315
|
if (key === "default") this.state.turns = entry.turns;
|
|
@@ -340,8 +340,8 @@ export class SmartBuffer {
|
|
|
340
340
|
return bufferEntryCounts(this.state);
|
|
341
341
|
}
|
|
342
342
|
|
|
343
|
-
async save(): Promise<void> {
|
|
344
|
-
await this.enqueueMutation(async () => this.saveUnlocked());
|
|
343
|
+
async save(reconcile?: { key: string; drained: import("./buffer-turn-helpers.js").DrainedTurnSets }): Promise<void> {
|
|
344
|
+
await this.enqueueMutation(async () => this.saveUnlocked(reconcile));
|
|
345
345
|
}
|
|
346
346
|
|
|
347
347
|
/**
|
package/src/cli.ts
CHANGED
|
@@ -4895,7 +4895,7 @@ export function registerCli(
|
|
|
4895
4895
|
// No sidecar — attempt in-memory decryption if the secure-store is
|
|
4896
4896
|
// unlocked. decryptCapsuleFileInMemory returns the plaintext gzip
|
|
4897
4897
|
// bytes; we then gunzip and parse exactly like the plaintext path.
|
|
4898
|
-
const {
|
|
4898
|
+
const { gunzipCapsuleArchive } = await import("./transfer/capsule-gunzip.js");
|
|
4899
4899
|
const { parseExportBundle } = await import("./transfer/types.js");
|
|
4900
4900
|
let decryptedBuf: Buffer;
|
|
4901
4901
|
try {
|
|
@@ -4917,7 +4917,7 @@ export function registerCli(
|
|
|
4917
4917
|
process.exitCode = 1;
|
|
4918
4918
|
return;
|
|
4919
4919
|
}
|
|
4920
|
-
const json =
|
|
4920
|
+
const json = gunzipCapsuleArchive(decryptedBuf, "capsule inspect", archivePath);
|
|
4921
4921
|
const parsed2 = parseExportBundle(JSON.parse(json));
|
|
4922
4922
|
if (parsed2.capsuleVersion !== 2) {
|
|
4923
4923
|
process.stderr.write(
|
|
@@ -4929,10 +4929,10 @@ export function registerCli(
|
|
|
4929
4929
|
manifest = (parsed2.bundle as { manifest: Record<string, unknown> }).manifest;
|
|
4930
4930
|
} else {
|
|
4931
4931
|
const { readFile } = await import("node:fs/promises");
|
|
4932
|
-
const {
|
|
4932
|
+
const { gunzipCapsuleArchive } = await import("./transfer/capsule-gunzip.js");
|
|
4933
4933
|
const { parseExportBundle } = await import("./transfer/types.js");
|
|
4934
4934
|
const buf = await readFile(archivePath);
|
|
4935
|
-
const json =
|
|
4935
|
+
const json = gunzipCapsuleArchive(buf, "capsule inspect", archivePath);
|
|
4936
4936
|
const parsed2 = parseExportBundle(JSON.parse(json));
|
|
4937
4937
|
if (parsed2.capsuleVersion !== 2) {
|
|
4938
4938
|
process.stderr.write(
|