@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
package/src/access-mcp.ts
CHANGED
|
@@ -30,7 +30,9 @@ import {
|
|
|
30
30
|
validateRequest,
|
|
31
31
|
} from "./access-schema.js";
|
|
32
32
|
import { EngramAccessInputError, type EngramAccessRecallResponse, type EngramAccessService } from "./access-service.js";
|
|
33
|
-
import { unknownToolError } from "./access-errors.js";
|
|
33
|
+
import { EngramAccessForbiddenError, unknownToolError } from "./access-errors.js";
|
|
34
|
+
import { log } from "./logger.js";
|
|
35
|
+
import { McpFrameReader } from "./access-mcp-frames.js";
|
|
34
36
|
import "./access-operations.js";
|
|
35
37
|
import { validateBriefingFormat } from "./briefing.js";
|
|
36
38
|
import { processChatMessage } from "./chat/chat-factory.js";
|
|
@@ -88,6 +90,19 @@ function throwMcpAbort(signal: AbortSignal | undefined, message: string): void {
|
|
|
88
90
|
throw abortError(message);
|
|
89
91
|
}
|
|
90
92
|
|
|
93
|
+
/**
|
|
94
|
+
* Map tools/call failures to a client-safe message. Unexpected backend/fs
|
|
95
|
+
* errors become a stable generic string; detail stays in the server log.
|
|
96
|
+
* Intended EngramAccessInputError / EngramAccessForbiddenError stay readable.
|
|
97
|
+
*/
|
|
98
|
+
function mcpToolErrorMessage(err: Error): string {
|
|
99
|
+
if (err instanceof EngramAccessInputError || err instanceof EngramAccessForbiddenError) {
|
|
100
|
+
return err.message;
|
|
101
|
+
}
|
|
102
|
+
log.error("engram MCP tools/call internal error", err);
|
|
103
|
+
return "internal error";
|
|
104
|
+
}
|
|
105
|
+
|
|
91
106
|
type McpRequestOptions = {
|
|
92
107
|
principalOverride?: string;
|
|
93
108
|
namespaceOverride?: string;
|
|
@@ -406,7 +421,7 @@ function parseMcpRequest<N extends SchemaName>(schemaName: N, args: Record<strin
|
|
|
406
421
|
function optionalNonEmptyString(value: unknown, label: string): string | undefined {
|
|
407
422
|
if (value === undefined || value === null || value === "") return undefined;
|
|
408
423
|
if (typeof value !== "string") {
|
|
409
|
-
throw new
|
|
424
|
+
throw new EngramAccessInputError(`${label} expects a string; got ${JSON.stringify(value)}`);
|
|
410
425
|
}
|
|
411
426
|
return value;
|
|
412
427
|
}
|
|
@@ -420,11 +435,11 @@ function optionalNonEmptyString(value: unknown, label: string): string | undefin
|
|
|
420
435
|
function optionalPositiveInteger(value: unknown, label: string): number | undefined {
|
|
421
436
|
if (value === undefined || value === null || value === "") return undefined;
|
|
422
437
|
if (typeof value !== "number" && typeof value !== "string") {
|
|
423
|
-
throw new
|
|
438
|
+
throw new EngramAccessInputError(`${label} expects a positive integer; got ${JSON.stringify(value)}`);
|
|
424
439
|
}
|
|
425
440
|
const parsed = typeof value === "number" ? value : Number(value);
|
|
426
441
|
if (!Number.isFinite(parsed) || parsed <= 0 || !Number.isInteger(parsed)) {
|
|
427
|
-
throw new
|
|
442
|
+
throw new EngramAccessInputError(`${label} expects a positive integer; got ${JSON.stringify(value)}`);
|
|
428
443
|
}
|
|
429
444
|
return parsed;
|
|
430
445
|
}
|
|
@@ -447,7 +462,7 @@ async function getMcpServerVersion(): Promise<string> {
|
|
|
447
462
|
}
|
|
448
463
|
|
|
449
464
|
export class EngramMcpServer {
|
|
450
|
-
private
|
|
465
|
+
private readonly frames = new McpFrameReader();
|
|
451
466
|
private flushTask: Promise<void> | null = null;
|
|
452
467
|
private readonly tools: McpTool[];
|
|
453
468
|
private readonly resources: McpResource[];
|
|
@@ -2402,7 +2417,7 @@ export class EngramMcpServer {
|
|
|
2402
2417
|
// Cancellation is transport control flow, not a JSON-RPC tool error.
|
|
2403
2418
|
// Preserve the original AbortError so HTTP can silently end a dead socket.
|
|
2404
2419
|
if (isAbortError(err)) throw err;
|
|
2405
|
-
const message = err instanceof Error ? err
|
|
2420
|
+
const message = mcpToolErrorMessage(err instanceof Error ? err : new Error(String(err)));
|
|
2406
2421
|
return {
|
|
2407
2422
|
jsonrpc: "2.0",
|
|
2408
2423
|
id,
|
|
@@ -2426,7 +2441,7 @@ export class EngramMcpServer {
|
|
|
2426
2441
|
|
|
2427
2442
|
async runStdio(input: Readable, output: Writable): Promise<void> {
|
|
2428
2443
|
input.on("data", (chunk) => {
|
|
2429
|
-
this.
|
|
2444
|
+
this.frames.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
|
|
2430
2445
|
this.scheduleFlush(output);
|
|
2431
2446
|
});
|
|
2432
2447
|
await new Promise<void>((resolve, reject) => {
|
|
@@ -2455,7 +2470,7 @@ export class EngramMcpServer {
|
|
|
2455
2470
|
if (this.flushTask === task) {
|
|
2456
2471
|
this.flushTask = null;
|
|
2457
2472
|
}
|
|
2458
|
-
if (this.
|
|
2473
|
+
if (this.frames.hasUnread()) {
|
|
2459
2474
|
this.scheduleFlush(output);
|
|
2460
2475
|
}
|
|
2461
2476
|
});
|
|
@@ -2463,30 +2478,15 @@ export class EngramMcpServer {
|
|
|
2463
2478
|
}
|
|
2464
2479
|
|
|
2465
2480
|
private async flushBuffer(output: Writable): Promise<void> {
|
|
2466
|
-
|
|
2467
|
-
|
|
2468
|
-
|
|
2469
|
-
|
|
2470
|
-
const headers = headerText.split("\r\n");
|
|
2471
|
-
const contentLengthHeader = headers.find((line) => line.toLowerCase().startsWith("content-length:"));
|
|
2472
|
-
if (!contentLengthHeader) {
|
|
2473
|
-
this.buffer = Buffer.alloc(0);
|
|
2474
|
-
return;
|
|
2475
|
-
}
|
|
2476
|
-
const contentLength = Number.parseInt(contentLengthHeader.split(":")[1]?.trim() ?? "0", 10);
|
|
2477
|
-
if (!Number.isFinite(contentLength) || contentLength < 0) {
|
|
2478
|
-
this.buffer = Buffer.alloc(0);
|
|
2479
|
-
return;
|
|
2481
|
+
for (let frame = this.frames.next(); frame; frame = this.frames.next()) {
|
|
2482
|
+
if (frame.kind === "too-large") {
|
|
2483
|
+
this.writeMessage(output, { jsonrpc: "2.0", id: null, error: { code: -32600, message: "frame too large" } });
|
|
2484
|
+
continue;
|
|
2480
2485
|
}
|
|
2481
|
-
const messageStart = headerEnd + 4;
|
|
2482
|
-
const messageEnd = messageStart + contentLength;
|
|
2483
|
-
if (this.buffer.length < messageEnd) return;
|
|
2484
|
-
const body = this.buffer.slice(messageStart, messageEnd).toString("utf-8");
|
|
2485
|
-
this.buffer = this.buffer.slice(messageEnd);
|
|
2486
|
-
|
|
2487
2486
|
let parsed: JsonRpcRequest;
|
|
2488
2487
|
try {
|
|
2489
|
-
|
|
2488
|
+
// SAFETY: handleRequest defaults missing fields; a non-object body throws and scheduleFlush answers -32700.
|
|
2489
|
+
parsed = JSON.parse(frame.text) as JsonRpcRequest;
|
|
2490
2490
|
} catch {
|
|
2491
2491
|
this.writeMessage(output, {
|
|
2492
2492
|
jsonrpc: "2.0",
|
|
@@ -2620,7 +2620,7 @@ export class EngramMcpServer {
|
|
|
2620
2620
|
caps?.ops === undefined || capabilityAllowsOp(caps, MCP_MIGRATED_OPERATIONS[toLegacyToolName(t.name)] ?? "")
|
|
2621
2621
|
)
|
|
2622
2622
|
.map((t) => t.name);
|
|
2623
|
-
throw new
|
|
2623
|
+
throw new EngramAccessInputError(unknownToolError(name, visible));
|
|
2624
2624
|
}
|
|
2625
2625
|
const op = getOperation(migrated);
|
|
2626
2626
|
if (!op) {
|
|
@@ -51,6 +51,7 @@ import {
|
|
|
51
51
|
queueExplicitCaptureForReview,
|
|
52
52
|
} from "./explicit-capture.js";
|
|
53
53
|
import { log } from "./logger.js";
|
|
54
|
+
import { embeddingBacklogExceeds, isSearchHealthDegraded } from "./namespaces/search.js";
|
|
54
55
|
import { recordObjectiveStateSnapshotsFromObservedMessages } from "./objective-state-writers.js";
|
|
55
56
|
import type { Orchestrator } from "./orchestrator.js";
|
|
56
57
|
import { displayErrorDetail } from "./runtime/better-sqlite.js";
|
|
@@ -289,7 +290,7 @@ export class AccessObserveWriteSurface {
|
|
|
289
290
|
/* status probe failed — non-fatal */
|
|
290
291
|
}
|
|
291
292
|
}
|
|
292
|
-
if (
|
|
293
|
+
if (embeddingBacklogExceeds(pendingEmbeddings, threshold)) {
|
|
293
294
|
degraded = true;
|
|
294
295
|
degradedReason = `embedding-backlog: ${pendingEmbeddings} pending > threshold ${threshold}`;
|
|
295
296
|
}
|
|
@@ -355,14 +356,13 @@ export class AccessObserveWriteSurface {
|
|
|
355
356
|
const pending = health.pendingEmbeddings ?? null;
|
|
356
357
|
const oldest = health.oldestPendingAgeMs ?? null;
|
|
357
358
|
const threshold = this.deps.orchestrator.config.qmdEmbeddingBacklogThreshold;
|
|
358
|
-
const backlogDegraded =
|
|
359
|
-
const degraded = !active || health.collectionState === "unknown" || backlogDegraded;
|
|
359
|
+
const backlogDegraded = embeddingBacklogExceeds(pending, threshold);
|
|
360
360
|
const mode = !active ? "fallback" : health.daemonMode === true ? "daemon" : "cli";
|
|
361
361
|
|
|
362
362
|
return {
|
|
363
363
|
enabled: true,
|
|
364
364
|
active,
|
|
365
|
-
degraded,
|
|
365
|
+
degraded: isSearchHealthDegraded({ ...health, pendingEmbeddings: pending }, threshold),
|
|
366
366
|
mode,
|
|
367
367
|
collection: health.collection || fallbackCollection,
|
|
368
368
|
collectionState: health.collectionState,
|
|
@@ -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 = {
|