@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
|
@@ -831,6 +831,345 @@ test("healthForNamespace stops waiting when namespace availability probe aborts"
|
|
|
831
831
|
assert.equal(backend.disposed, 1);
|
|
832
832
|
});
|
|
833
833
|
|
|
834
|
+
test("healthForNamespace reuses a completed result within the TTL without re-probing", async (t) => {
|
|
835
|
+
t.mock.timers.enable({ apis: ["Date"], now: 1_700_000_000_000 });
|
|
836
|
+
try {
|
|
837
|
+
const created: FakeBackend[] = [];
|
|
838
|
+
const router = new NamespaceSearchRouter(
|
|
839
|
+
config(),
|
|
840
|
+
{ storageFor: async (namespace: string) => ({ dir: `/tmp/remnic/${namespace}` }) },
|
|
841
|
+
() => {
|
|
842
|
+
const backend = new FakeBackend(false, [], { check: "present" }, false, {
|
|
843
|
+
debugStatus: "cached-health",
|
|
844
|
+
});
|
|
845
|
+
backend.statusResult = {
|
|
846
|
+
pendingEmbeddings: 3,
|
|
847
|
+
oldestPendingAgeMs: 1_000,
|
|
848
|
+
totalFiles: 10,
|
|
849
|
+
embeddedFiles: 7,
|
|
850
|
+
};
|
|
851
|
+
created.push(backend);
|
|
852
|
+
return backend;
|
|
853
|
+
},
|
|
854
|
+
);
|
|
855
|
+
|
|
856
|
+
const first = await router.healthForNamespace("shared");
|
|
857
|
+
const second = await router.healthForNamespace("shared");
|
|
858
|
+
|
|
859
|
+
assert.equal(created.length, 1, "second call must not create another backend probe");
|
|
860
|
+
assert.equal(created[0]?.availabilitySignals.length, 1);
|
|
861
|
+
assert.deepEqual(created[0]?.checkCollections, ["openclaw-engram--ns-736861726564"]);
|
|
862
|
+
assert.deepEqual(first, second);
|
|
863
|
+
assert.equal(first.debugStatus, "cached-health");
|
|
864
|
+
assert.equal(first.pendingEmbeddings, 3);
|
|
865
|
+
|
|
866
|
+
first.available = false;
|
|
867
|
+
first.debugStatus = "mutated";
|
|
868
|
+
const third = await router.healthForNamespace("shared");
|
|
869
|
+
assert.equal(third.available, true, "returned health objects must be copies");
|
|
870
|
+
assert.equal(third.debugStatus, "cached-health");
|
|
871
|
+
assert.equal(created.length, 1);
|
|
872
|
+
} finally {
|
|
873
|
+
t.mock.timers.reset();
|
|
874
|
+
}
|
|
875
|
+
});
|
|
876
|
+
|
|
877
|
+
test("healthForNamespace re-probes after the health result TTL elapses", async (t) => {
|
|
878
|
+
t.mock.timers.enable({ apis: ["Date"], now: 1_700_000_000_000 });
|
|
879
|
+
try {
|
|
880
|
+
const created: FakeBackend[] = [];
|
|
881
|
+
const router = new NamespaceSearchRouter(
|
|
882
|
+
config(),
|
|
883
|
+
{ storageFor: async (namespace: string) => ({ dir: `/tmp/remnic/${namespace}` }) },
|
|
884
|
+
() => {
|
|
885
|
+
const backend = new FakeBackend(false, [], { check: "present" });
|
|
886
|
+
backend.statusResult = {
|
|
887
|
+
pendingEmbeddings: created.length,
|
|
888
|
+
oldestPendingAgeMs: 0,
|
|
889
|
+
totalFiles: 1,
|
|
890
|
+
embeddedFiles: 1,
|
|
891
|
+
};
|
|
892
|
+
created.push(backend);
|
|
893
|
+
return backend;
|
|
894
|
+
},
|
|
895
|
+
);
|
|
896
|
+
|
|
897
|
+
const first = await router.healthForNamespace("shared");
|
|
898
|
+
assert.equal(created.length, 1);
|
|
899
|
+
assert.equal(first.pendingEmbeddings, 0);
|
|
900
|
+
|
|
901
|
+
t.mock.timers.tick(10_000);
|
|
902
|
+
const second = await router.healthForNamespace("shared");
|
|
903
|
+
|
|
904
|
+
assert.equal(created.length, 2, "TTL expiry must force a fresh backend probe");
|
|
905
|
+
assert.equal(second.pendingEmbeddings, 1);
|
|
906
|
+
assert.equal(created[0]?.disposed, 1);
|
|
907
|
+
assert.equal(created[1]?.disposed, 1);
|
|
908
|
+
} finally {
|
|
909
|
+
t.mock.timers.reset();
|
|
910
|
+
}
|
|
911
|
+
});
|
|
912
|
+
|
|
913
|
+
test("healthForNamespace clearCache forces a re-probe before the TTL", async () => {
|
|
914
|
+
const created: FakeBackend[] = [];
|
|
915
|
+
const router = new NamespaceSearchRouter(
|
|
916
|
+
config(),
|
|
917
|
+
{ storageFor: async (namespace: string) => ({ dir: `/tmp/remnic/${namespace}` }) },
|
|
918
|
+
() => {
|
|
919
|
+
const backend = new FakeBackend(false, [], { check: "present" });
|
|
920
|
+
backend.statusResult = {
|
|
921
|
+
pendingEmbeddings: created.length + 1,
|
|
922
|
+
oldestPendingAgeMs: 0,
|
|
923
|
+
totalFiles: 1,
|
|
924
|
+
embeddedFiles: 1,
|
|
925
|
+
};
|
|
926
|
+
created.push(backend);
|
|
927
|
+
return backend;
|
|
928
|
+
},
|
|
929
|
+
);
|
|
930
|
+
|
|
931
|
+
const first = await router.healthForNamespace("shared");
|
|
932
|
+
assert.equal(created.length, 1);
|
|
933
|
+
assert.equal(first.pendingEmbeddings, 1);
|
|
934
|
+
|
|
935
|
+
router.clearCache();
|
|
936
|
+
const second = await router.healthForNamespace("shared");
|
|
937
|
+
|
|
938
|
+
assert.equal(created.length, 2, "clearCache must drop the health result cache");
|
|
939
|
+
assert.equal(second.pendingEmbeddings, 2);
|
|
940
|
+
});
|
|
941
|
+
|
|
942
|
+
test("healthForNamespace does not cache a thrown probe", async () => {
|
|
943
|
+
let attempts = 0;
|
|
944
|
+
const created: FakeBackend[] = [];
|
|
945
|
+
const router = new NamespaceSearchRouter(
|
|
946
|
+
config(),
|
|
947
|
+
{
|
|
948
|
+
storageFor: async (namespace: string) => {
|
|
949
|
+
attempts += 1;
|
|
950
|
+
if (attempts === 1) {
|
|
951
|
+
throw new Error("storage unavailable");
|
|
952
|
+
}
|
|
953
|
+
return { dir: `/tmp/remnic/${namespace}` };
|
|
954
|
+
},
|
|
955
|
+
},
|
|
956
|
+
() => {
|
|
957
|
+
const backend = new FakeBackend(false, [], { check: "present" });
|
|
958
|
+
backend.statusResult = {
|
|
959
|
+
pendingEmbeddings: 9,
|
|
960
|
+
oldestPendingAgeMs: 0,
|
|
961
|
+
totalFiles: 1,
|
|
962
|
+
embeddedFiles: 1,
|
|
963
|
+
};
|
|
964
|
+
created.push(backend);
|
|
965
|
+
return backend;
|
|
966
|
+
},
|
|
967
|
+
);
|
|
968
|
+
|
|
969
|
+
await assert.rejects(
|
|
970
|
+
() => router.healthForNamespace("shared"),
|
|
971
|
+
/storage unavailable/,
|
|
972
|
+
);
|
|
973
|
+
assert.equal(created.length, 0, "failed call must not create a backend");
|
|
974
|
+
|
|
975
|
+
const health = await router.healthForNamespace("shared");
|
|
976
|
+
assert.equal(health.pendingEmbeddings, 9);
|
|
977
|
+
assert.equal(created.length, 1);
|
|
978
|
+
|
|
979
|
+
const cached = await router.healthForNamespace("shared");
|
|
980
|
+
assert.equal(created.length, 1, "only the successful result is cached");
|
|
981
|
+
assert.deepEqual(cached, health);
|
|
982
|
+
});
|
|
983
|
+
|
|
984
|
+
test("healthForNamespace does not cache an unavailable answer", async () => {
|
|
985
|
+
const created: FakeBackend[] = [];
|
|
986
|
+
const router = new NamespaceSearchRouter(
|
|
987
|
+
config(),
|
|
988
|
+
{ storageFor: async (namespace: string) => ({ dir: `/tmp/remnic/${namespace}` }) },
|
|
989
|
+
() => {
|
|
990
|
+
const backend = new FakeBackend(false, [], { check: "present" });
|
|
991
|
+
if (created.length === 0) {
|
|
992
|
+
backend.available = false;
|
|
993
|
+
}
|
|
994
|
+
backend.statusResult = {
|
|
995
|
+
pendingEmbeddings: created.length,
|
|
996
|
+
oldestPendingAgeMs: 0,
|
|
997
|
+
totalFiles: 1,
|
|
998
|
+
embeddedFiles: 1,
|
|
999
|
+
};
|
|
1000
|
+
created.push(backend);
|
|
1001
|
+
return backend;
|
|
1002
|
+
},
|
|
1003
|
+
);
|
|
1004
|
+
|
|
1005
|
+
const first = await router.healthForNamespace("shared");
|
|
1006
|
+
assert.equal(first.available, false);
|
|
1007
|
+
assert.equal(created.length, 1);
|
|
1008
|
+
|
|
1009
|
+
const second = await router.healthForNamespace("shared");
|
|
1010
|
+
assert.equal(second.available, true, "recovery must be visible on the next probe");
|
|
1011
|
+
assert.equal(created.length, 2, "unavailable answers must not be cached");
|
|
1012
|
+
});
|
|
1013
|
+
|
|
1014
|
+
test("healthForNamespace does not cache a missing-collection answer", async () => {
|
|
1015
|
+
const created: FakeBackend[] = [];
|
|
1016
|
+
const router = new NamespaceSearchRouter(
|
|
1017
|
+
config(),
|
|
1018
|
+
{ storageFor: async (namespace: string) => ({ dir: `/tmp/remnic/${namespace}` }) },
|
|
1019
|
+
() => {
|
|
1020
|
+
const backend = new FakeBackend(
|
|
1021
|
+
false,
|
|
1022
|
+
[],
|
|
1023
|
+
created.length === 0 ? { check: "missing" } : { check: "present" },
|
|
1024
|
+
);
|
|
1025
|
+
backend.statusResult = {
|
|
1026
|
+
pendingEmbeddings: created.length,
|
|
1027
|
+
oldestPendingAgeMs: 0,
|
|
1028
|
+
totalFiles: 1,
|
|
1029
|
+
embeddedFiles: 1,
|
|
1030
|
+
};
|
|
1031
|
+
created.push(backend);
|
|
1032
|
+
return backend;
|
|
1033
|
+
},
|
|
1034
|
+
);
|
|
1035
|
+
|
|
1036
|
+
const first = await router.healthForNamespace("shared");
|
|
1037
|
+
assert.equal(first.collectionState, "missing");
|
|
1038
|
+
assert.equal(created.length, 1);
|
|
1039
|
+
|
|
1040
|
+
const second = await router.healthForNamespace("shared");
|
|
1041
|
+
assert.equal(second.collectionState, "present", "collection recovery must re-probe");
|
|
1042
|
+
assert.equal(created.length, 2, "missing-collection answers must not be cached");
|
|
1043
|
+
});
|
|
1044
|
+
|
|
1045
|
+
function routerWithDegradedFirstProbe(
|
|
1046
|
+
created: FakeBackend[],
|
|
1047
|
+
firstCheck: CollectionState,
|
|
1048
|
+
degradeFirst: (backend: FakeBackend) => void,
|
|
1049
|
+
): NamespaceSearchRouter {
|
|
1050
|
+
return new NamespaceSearchRouter(
|
|
1051
|
+
config(),
|
|
1052
|
+
{ storageFor: async (namespace: string) => ({ dir: `/tmp/remnic/${namespace}` }) },
|
|
1053
|
+
() => {
|
|
1054
|
+
const isFirst = created.length === 0;
|
|
1055
|
+
const backend = new FakeBackend(false, [], { check: isFirst ? firstCheck : "present" });
|
|
1056
|
+
backend.statusResult = {
|
|
1057
|
+
pendingEmbeddings: 10,
|
|
1058
|
+
oldestPendingAgeMs: 0,
|
|
1059
|
+
totalFiles: 1,
|
|
1060
|
+
embeddedFiles: 1,
|
|
1061
|
+
};
|
|
1062
|
+
if (isFirst) degradeFirst(backend);
|
|
1063
|
+
created.push(backend);
|
|
1064
|
+
return backend;
|
|
1065
|
+
},
|
|
1066
|
+
);
|
|
1067
|
+
}
|
|
1068
|
+
|
|
1069
|
+
test("healthForNamespace does not cache an unknown-collection answer", async () => {
|
|
1070
|
+
const created: FakeBackend[] = [];
|
|
1071
|
+
const router = routerWithDegradedFirstProbe(created, "unknown", () => {});
|
|
1072
|
+
|
|
1073
|
+
const first = await router.healthForNamespace("shared");
|
|
1074
|
+
assert.equal(first.collectionState, "unknown");
|
|
1075
|
+
|
|
1076
|
+
const second = await router.healthForNamespace("shared");
|
|
1077
|
+
assert.equal(second.collectionState, "present", "recovery must be visible on the next probe");
|
|
1078
|
+
assert.equal(created.length, 2, "unknown-collection answers must not be cached");
|
|
1079
|
+
});
|
|
1080
|
+
|
|
1081
|
+
test("healthForNamespace does not cache an answer whose backend status failed", async () => {
|
|
1082
|
+
const created: FakeBackend[] = [];
|
|
1083
|
+
const router = routerWithDegradedFirstProbe(created, "present", (backend) => {
|
|
1084
|
+
backend.statusResult = null;
|
|
1085
|
+
});
|
|
1086
|
+
|
|
1087
|
+
const first = await router.healthForNamespace("shared");
|
|
1088
|
+
assert.equal(first.pendingEmbeddings, null);
|
|
1089
|
+
|
|
1090
|
+
const second = await router.healthForNamespace("shared");
|
|
1091
|
+
assert.equal(second.pendingEmbeddings, 10, "backlog data must be visible on the next probe");
|
|
1092
|
+
assert.equal(created.length, 2, "answers without backlog data must not be cached");
|
|
1093
|
+
});
|
|
1094
|
+
|
|
1095
|
+
test("healthForNamespace does not cache a backlog over the threshold", async () => {
|
|
1096
|
+
const created: FakeBackend[] = [];
|
|
1097
|
+
const router = routerWithDegradedFirstProbe(created, "present", (backend) => {
|
|
1098
|
+
backend.statusResult = {
|
|
1099
|
+
pendingEmbeddings: config().qmdEmbeddingBacklogThreshold + 1,
|
|
1100
|
+
oldestPendingAgeMs: 0,
|
|
1101
|
+
totalFiles: 1,
|
|
1102
|
+
embeddedFiles: 1,
|
|
1103
|
+
};
|
|
1104
|
+
});
|
|
1105
|
+
|
|
1106
|
+
await router.healthForNamespace("shared");
|
|
1107
|
+
|
|
1108
|
+
const second = await router.healthForNamespace("shared");
|
|
1109
|
+
assert.equal(second.pendingEmbeddings, 10, "a drained backlog must be visible on the next probe");
|
|
1110
|
+
assert.equal(created.length, 2, "over-threshold answers must not be cached");
|
|
1111
|
+
});
|
|
1112
|
+
|
|
1113
|
+
test("healthForNamespace dispose drops the health result cache", async () => {
|
|
1114
|
+
const created: FakeBackend[] = [];
|
|
1115
|
+
const router = new NamespaceSearchRouter(
|
|
1116
|
+
config(),
|
|
1117
|
+
{ storageFor: async (namespace: string) => ({ dir: `/tmp/remnic/${namespace}` }) },
|
|
1118
|
+
() => {
|
|
1119
|
+
const backend = new FakeBackend(false, [], { check: "present" });
|
|
1120
|
+
backend.statusResult = {
|
|
1121
|
+
pendingEmbeddings: created.length + 1,
|
|
1122
|
+
oldestPendingAgeMs: 0,
|
|
1123
|
+
totalFiles: 1,
|
|
1124
|
+
embeddedFiles: 1,
|
|
1125
|
+
};
|
|
1126
|
+
created.push(backend);
|
|
1127
|
+
return backend;
|
|
1128
|
+
},
|
|
1129
|
+
);
|
|
1130
|
+
|
|
1131
|
+
const first = await router.healthForNamespace("shared");
|
|
1132
|
+
assert.equal(created.length, 1);
|
|
1133
|
+
assert.equal(first.pendingEmbeddings, 1);
|
|
1134
|
+
|
|
1135
|
+
await router.dispose();
|
|
1136
|
+
const second = await router.healthForNamespace("shared");
|
|
1137
|
+
|
|
1138
|
+
assert.equal(created.length, 2, "dispose must drop the health result cache");
|
|
1139
|
+
assert.equal(second.pendingEmbeddings, 2);
|
|
1140
|
+
});
|
|
1141
|
+
|
|
1142
|
+
test("healthForNamespace disableSearchBackends drops the health result cache", async () => {
|
|
1143
|
+
const created: FakeBackend[] = [];
|
|
1144
|
+
const router = new NamespaceSearchRouter(
|
|
1145
|
+
config(),
|
|
1146
|
+
{ storageFor: async (namespace: string) => ({ dir: `/tmp/remnic/${namespace}` }) },
|
|
1147
|
+
() => {
|
|
1148
|
+
const backend = new FakeBackend(false, [], { check: "present" });
|
|
1149
|
+
backend.statusResult = {
|
|
1150
|
+
pendingEmbeddings: created.length + 1,
|
|
1151
|
+
oldestPendingAgeMs: 0,
|
|
1152
|
+
totalFiles: 1,
|
|
1153
|
+
embeddedFiles: 1,
|
|
1154
|
+
};
|
|
1155
|
+
created.push(backend);
|
|
1156
|
+
return backend;
|
|
1157
|
+
},
|
|
1158
|
+
);
|
|
1159
|
+
|
|
1160
|
+
const first = await router.healthForNamespace("shared");
|
|
1161
|
+
assert.equal(created.length, 1);
|
|
1162
|
+
assert.equal(first.pendingEmbeddings, 1);
|
|
1163
|
+
|
|
1164
|
+
await router.disableSearchBackends();
|
|
1165
|
+
const second = await router.healthForNamespace("shared");
|
|
1166
|
+
|
|
1167
|
+
assert.equal(created.length, 2, "disableSearchBackends must drop the health result cache");
|
|
1168
|
+
assert.equal(second.pendingEmbeddings, 2);
|
|
1169
|
+
});
|
|
1170
|
+
|
|
1171
|
+
|
|
1172
|
+
|
|
834
1173
|
test("ensureNamespaceCollection does not cache aborted namespace backend probes", async () => {
|
|
835
1174
|
const created: FakeBackend[] = [];
|
|
836
1175
|
const router = new NamespaceSearchRouter(
|
package/src/namespaces/search.ts
CHANGED
|
@@ -14,6 +14,9 @@ import { ALL_CATEGORY_DIRS } from "../utils/category-dir.js";
|
|
|
14
14
|
const NESTED_NAMESPACE_FILTER_OVERFETCH_FACTOR = 4;
|
|
15
15
|
const NESTED_NAMESPACE_FILTER_OVERFETCH_MIN = 50;
|
|
16
16
|
|
|
17
|
+
// Short TTL so repeated health polls skip a full QMD re-probe (~1.5s).
|
|
18
|
+
const HEALTH_RESULT_CACHE_TTL_MS = 10_000;
|
|
19
|
+
|
|
17
20
|
export function namespaceCollectionName(
|
|
18
21
|
baseCollection: string,
|
|
19
22
|
namespace: string,
|
|
@@ -72,6 +75,27 @@ export interface NamespaceSearchHealth {
|
|
|
72
75
|
embeddingBacklogThreshold: number;
|
|
73
76
|
}
|
|
74
77
|
|
|
78
|
+
/** True when the pending-embedding count is above a positive backlog threshold. */
|
|
79
|
+
export function embeddingBacklogExceeds(pending: number | null, threshold: number): boolean {
|
|
80
|
+
return threshold > 0 && pending !== null && pending > threshold;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* The one definition of a degraded search answer. The readiness consumer
|
|
85
|
+
* reports it, and `healthForNamespace` refuses to cache it.
|
|
86
|
+
*/
|
|
87
|
+
export function isSearchHealthDegraded(
|
|
88
|
+
health: Pick<NamespaceSearchHealth, "available" | "collectionState" | "pendingEmbeddings">,
|
|
89
|
+
backlogThreshold: number,
|
|
90
|
+
): boolean {
|
|
91
|
+
const active = health.available && health.collectionState !== "missing";
|
|
92
|
+
return (
|
|
93
|
+
!active ||
|
|
94
|
+
health.collectionState === "unknown" ||
|
|
95
|
+
embeddingBacklogExceeds(health.pendingEmbeddings, backlogThreshold)
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
|
|
75
99
|
type NamespaceScopedSearchConfig = PluginConfig & {
|
|
76
100
|
hostEmbeddingProviderScope?: string;
|
|
77
101
|
};
|
|
@@ -84,6 +108,11 @@ type BackendRecordOptions = {
|
|
|
84
108
|
|
|
85
109
|
export class NamespaceSearchRouter {
|
|
86
110
|
private readonly cache = new Map<string, Promise<NamespaceBackendRecord>>();
|
|
111
|
+
private readonly healthResultCache = new Map<
|
|
112
|
+
string,
|
|
113
|
+
{ expiresAt: number; value: NamespaceSearchHealth }
|
|
114
|
+
>();
|
|
115
|
+
|
|
87
116
|
private readonly pendingDisposals = new Set<Promise<void>>();
|
|
88
117
|
|
|
89
118
|
constructor(
|
|
@@ -279,6 +308,11 @@ export class NamespaceSearchRouter {
|
|
|
279
308
|
execution?: SearchExecutionOptions,
|
|
280
309
|
): Promise<NamespaceSearchHealth> {
|
|
281
310
|
const key = namespace.trim() || this.config.defaultNamespace;
|
|
311
|
+
const cached = this.healthResultCache.get(key);
|
|
312
|
+
if (cached && cached.expiresAt > Date.now()) {
|
|
313
|
+
return { ...cached.value };
|
|
314
|
+
}
|
|
315
|
+
|
|
282
316
|
const record = await this.createBackendRecordFor(
|
|
283
317
|
key,
|
|
284
318
|
execution,
|
|
@@ -310,7 +344,7 @@ export class NamespaceSearchRouter {
|
|
|
310
344
|
? "missing"
|
|
311
345
|
: record.collectionState;
|
|
312
346
|
|
|
313
|
-
|
|
347
|
+
const health: NamespaceSearchHealth = {
|
|
314
348
|
collection: record.collection,
|
|
315
349
|
memoryDir: record.memoryDir,
|
|
316
350
|
available: liveRecord?.available ?? record.available,
|
|
@@ -326,6 +360,22 @@ export class NamespaceSearchRouter {
|
|
|
326
360
|
oldestPendingAgeMs: backendStatus?.oldestPendingAgeMs ?? null,
|
|
327
361
|
embeddingBacklogThreshold: this.config.qmdEmbeddingBacklogThreshold,
|
|
328
362
|
};
|
|
363
|
+
|
|
364
|
+
// Cache only a healthy answer that carries backlog data when the backend
|
|
365
|
+
// reports status. A degraded or incomplete answer must re-probe so a
|
|
366
|
+
// readiness poll sees recovery on the next call, not one TTL late.
|
|
367
|
+
const statusMissing = backendStatus === null && "status" in diagnosticBackend;
|
|
368
|
+
if (
|
|
369
|
+
execution?.signal?.aborted !== true &&
|
|
370
|
+
!statusMissing &&
|
|
371
|
+
!isSearchHealthDegraded(health, health.embeddingBacklogThreshold)
|
|
372
|
+
) {
|
|
373
|
+
this.healthResultCache.set(key, {
|
|
374
|
+
expiresAt: Date.now() + HEALTH_RESULT_CACHE_TTL_MS,
|
|
375
|
+
value: { ...health },
|
|
376
|
+
});
|
|
377
|
+
}
|
|
378
|
+
return { ...health };
|
|
329
379
|
} finally {
|
|
330
380
|
const dispose = (record.backend as { dispose?: () => void | Promise<void> }).dispose;
|
|
331
381
|
await dispose?.call(record.backend);
|
|
@@ -358,6 +408,7 @@ export class NamespaceSearchRouter {
|
|
|
358
408
|
|
|
359
409
|
/** Clear cached backend records so the next access re-probes availability. */
|
|
360
410
|
clearCache(): void {
|
|
411
|
+
this.healthResultCache.clear();
|
|
361
412
|
const pendingRecords = Array.from(this.cache.values());
|
|
362
413
|
this.cache.clear();
|
|
363
414
|
const disposal = Promise.allSettled(pendingRecords).then(async (settled) => {
|
|
@@ -379,6 +430,7 @@ export class NamespaceSearchRouter {
|
|
|
379
430
|
/** Release any per-namespace backend handles held by cached records. */
|
|
380
431
|
async dispose(): Promise<void> {
|
|
381
432
|
await this.drainPendingDisposals();
|
|
433
|
+
this.healthResultCache.clear();
|
|
382
434
|
const pendingRecords = Array.from(this.cache.values());
|
|
383
435
|
this.cache.clear();
|
|
384
436
|
const settled = await Promise.allSettled(pendingRecords);
|
|
@@ -395,6 +447,7 @@ export class NamespaceSearchRouter {
|
|
|
395
447
|
* has been switched to NoopSearchBackend for a missing collection.
|
|
396
448
|
*/
|
|
397
449
|
async disableSearchBackends(): Promise<void> {
|
|
450
|
+
this.healthResultCache.clear();
|
|
398
451
|
const settled = await Promise.allSettled(this.cache.values());
|
|
399
452
|
await Promise.allSettled(
|
|
400
453
|
settled.flatMap((entry) => {
|
|
@@ -25,6 +25,7 @@
|
|
|
25
25
|
* every claim retryable; a restarted daemon's scheduler adopts them.
|
|
26
26
|
*/
|
|
27
27
|
|
|
28
|
+
import { randomUUID } from "node:crypto";
|
|
28
29
|
import { hostname } from "node:os";
|
|
29
30
|
import { log } from "../logger.js";
|
|
30
31
|
import type { Orchestrator } from "../orchestrator.js";
|
|
@@ -44,8 +45,8 @@ export interface BacklogSchedulerDeps {
|
|
|
44
45
|
liveBufferCounts():
|
|
45
46
|
| Map<string, { turns: number; retainedTurns: number }>
|
|
46
47
|
| Promise<Map<string, { turns: number; retainedTurns: number }>>;
|
|
47
|
-
/**
|
|
48
|
-
lease(): DrainLease;
|
|
48
|
+
/** Create a fresh lease for a unique owner token. */
|
|
49
|
+
lease(ownerId: string): DrainLease;
|
|
49
50
|
/** Drain one key to quiescence (BufferDrainRunner.drainSession). */
|
|
50
51
|
drainKey(
|
|
51
52
|
key: string,
|
|
@@ -63,6 +64,7 @@ export interface BacklogSchedulerDeps {
|
|
|
63
64
|
}
|
|
64
65
|
|
|
65
66
|
export interface BacklogSchedulerOptions {
|
|
67
|
+
/** Optional label prefix; each scheduler instance adds a unique token. */
|
|
66
68
|
ownerId?: string;
|
|
67
69
|
/** Scan cadence in ms (startup + new-work + retry checks). */
|
|
68
70
|
pollMs?: number;
|
|
@@ -135,7 +137,7 @@ export class BacklogScheduler {
|
|
|
135
137
|
constructor(deps: BacklogSchedulerDeps, options: BacklogSchedulerOptions = {}) {
|
|
136
138
|
this.deps = deps;
|
|
137
139
|
this.options = {
|
|
138
|
-
ownerId: options.ownerId ??
|
|
140
|
+
ownerId: `${options.ownerId ?? "daemon-drain"}-${randomUUID()}`,
|
|
139
141
|
pollMs: options.pollMs ?? DEFAULT_POLL_MS,
|
|
140
142
|
retryBaseMs: options.retryBaseMs ?? DEFAULT_RETRY_BASE_MS,
|
|
141
143
|
retryMaxMs: options.retryMaxMs ?? DEFAULT_RETRY_MAX_MS,
|
|
@@ -333,11 +335,11 @@ export class BacklogScheduler {
|
|
|
333
335
|
const { promise: settled, resolve: settle } = Promise.withResolvers<void>();
|
|
334
336
|
this.inFlight.set(key, { controller, settled });
|
|
335
337
|
try {
|
|
336
|
-
if (!(await this.claimForSubmit(key, needsClaim))) return;
|
|
338
|
+
if (!(await this.claimForSubmit(key, needsClaim, this.options.ownerId))) return;
|
|
337
339
|
// Stopped between the claim decision and the task: leave the claim
|
|
338
340
|
// with its lease (it expires; the next start adopts) — never drain.
|
|
339
341
|
if (this.stopped) return;
|
|
340
|
-
await this.drainClaimedKey(key, controller.signal);
|
|
342
|
+
await this.drainClaimedKey(key, controller.signal, this.options.ownerId);
|
|
341
343
|
} catch (err) {
|
|
342
344
|
// Claim/drain failures are scheduler-level: the claim content is
|
|
343
345
|
// untouched — count failed and back off (never an unhandled rejection
|
|
@@ -357,10 +359,10 @@ export class BacklogScheduler {
|
|
|
357
359
|
}
|
|
358
360
|
|
|
359
361
|
/** after all — the key goes quiet without a task. "claimed": ours. */
|
|
360
|
-
private async claimForSubmit(key: string, needsClaim: boolean): Promise<boolean> {
|
|
362
|
+
private async claimForSubmit(key: string, needsClaim: boolean, ownerId: string): Promise<boolean> {
|
|
361
363
|
if (!needsClaim) return true;
|
|
362
364
|
try {
|
|
363
|
-
const outcome = await this.deps.claims.claimFromSessionFile(key, this.deps.lease());
|
|
365
|
+
const outcome = await this.deps.claims.claimFromSessionFile(key, this.deps.lease(ownerId));
|
|
364
366
|
if (outcome === "empty") {
|
|
365
367
|
this.keys.delete(key);
|
|
366
368
|
return false;
|
|
@@ -375,11 +377,11 @@ export class BacklogScheduler {
|
|
|
375
377
|
/** Drain one claimed key. drainKey reports failures as outcomes; a thrown
|
|
376
378
|
* error still means the claim content is intact — count it failed and
|
|
377
379
|
* back off. */
|
|
378
|
-
private async drainClaimedKey(key: string, signal: AbortSignal): Promise<void> {
|
|
380
|
+
private async drainClaimedKey(key: string, signal: AbortSignal, ownerId: string): Promise<void> {
|
|
379
381
|
this.counters.claimed += 1;
|
|
380
382
|
try {
|
|
381
383
|
const outcome = await this.deps.drainKey(key, {
|
|
382
|
-
ownerId
|
|
384
|
+
ownerId,
|
|
383
385
|
signal,
|
|
384
386
|
});
|
|
385
387
|
this.applyOutcome(key, outcome);
|
|
@@ -480,7 +482,6 @@ export function wireBacklogScheduler(
|
|
|
480
482
|
orchestrator: Orchestrator,
|
|
481
483
|
options: BacklogSchedulerOptions = {}
|
|
482
484
|
): BacklogScheduler {
|
|
483
|
-
const ownerId = options.ownerId ?? `daemon-drain-${process.pid}`;
|
|
484
485
|
const runner = new BufferDrainRunner(orchestrator);
|
|
485
486
|
return new BacklogScheduler(
|
|
486
487
|
{
|
|
@@ -492,7 +493,7 @@ export function wireBacklogScheduler(
|
|
|
492
493
|
await orchestrator.buffer.load();
|
|
493
494
|
return orchestrator.buffer.entryCounts();
|
|
494
495
|
},
|
|
495
|
-
lease: () => {
|
|
496
|
+
lease: (ownerId) => {
|
|
496
497
|
const now = new Date().toISOString();
|
|
497
498
|
return {
|
|
498
499
|
ownerId,
|
|
@@ -508,6 +509,6 @@ export function wireBacklogScheduler(
|
|
|
508
509
|
// claim-backed drain accumulate toward the same threshold).
|
|
509
510
|
breakerSnapshot: () => orchestrator.getExtractionResilienceStatus().breaker,
|
|
510
511
|
},
|
|
511
|
-
|
|
512
|
+
options
|
|
512
513
|
);
|
|
513
514
|
}
|
|
@@ -74,27 +74,35 @@ const DEFAULT_LEASE_MS = 120_000;
|
|
|
74
74
|
* A `BufferStorage` view of one drain claim: the claim file IS the buffer.
|
|
75
75
|
* Writes are the drain's progress (what is left to extract), so every
|
|
76
76
|
* successfully extracted chunk is durably removed before the next one runs.
|
|
77
|
+
*
|
|
78
|
+
* `seen` is the drainer's own view of the claim: the entry it loaded, then the
|
|
79
|
+
* entry it last wrote. Each progress write is diffed against it, so a write
|
|
80
|
+
* records only the turns extracted since the previous one.
|
|
77
81
|
*/
|
|
78
82
|
export class ClaimBufferStorage implements BufferStorage {
|
|
83
|
+
private seen: BufferEntryState;
|
|
84
|
+
|
|
79
85
|
constructor(
|
|
80
86
|
private readonly claims: BufferDrainClaims,
|
|
81
87
|
private readonly key: string,
|
|
82
88
|
private readonly ownerId: string,
|
|
83
|
-
|
|
84
|
-
) {
|
|
89
|
+
entry: BufferEntryState
|
|
90
|
+
) {
|
|
91
|
+
this.seen = entry;
|
|
92
|
+
}
|
|
85
93
|
|
|
86
94
|
async loadBuffer(): Promise<BufferState> {
|
|
87
95
|
const entries: NonNullable<BufferState["entries"]> = Object.create(null);
|
|
88
96
|
entries[this.key] = {
|
|
89
|
-
turns: this.
|
|
90
|
-
retainedTurns: this.
|
|
91
|
-
lastExtractionAt: this.
|
|
92
|
-
extractionCount: this.
|
|
97
|
+
turns: this.seen.turns,
|
|
98
|
+
retainedTurns: this.seen.retainedTurns,
|
|
99
|
+
lastExtractionAt: this.seen.lastExtractionAt,
|
|
100
|
+
extractionCount: this.seen.extractionCount,
|
|
93
101
|
};
|
|
94
102
|
return {
|
|
95
|
-
turns: this.
|
|
96
|
-
lastExtractionAt: this.
|
|
97
|
-
extractionCount: this.
|
|
103
|
+
turns: this.seen.turns,
|
|
104
|
+
lastExtractionAt: this.seen.lastExtractionAt,
|
|
105
|
+
extractionCount: this.seen.extractionCount,
|
|
98
106
|
entries,
|
|
99
107
|
};
|
|
100
108
|
}
|
|
@@ -102,16 +110,21 @@ export class ClaimBufferStorage implements BufferStorage {
|
|
|
102
110
|
async saveBuffer(state: BufferState): Promise<void> {
|
|
103
111
|
// Whole-state fallback (the claim covers one session): persist the entry.
|
|
104
112
|
const entry = state.entries?.[this.key];
|
|
105
|
-
await this.
|
|
106
|
-
this.key,
|
|
107
|
-
this.ownerId,
|
|
108
|
-
entry ?? { turns: [], lastExtractionAt: null, extractionCount: 0 },
|
|
109
|
-
this.entry
|
|
110
|
-
);
|
|
113
|
+
await this.writeProgress(entry ?? { turns: [], lastExtractionAt: null, extractionCount: 0 });
|
|
111
114
|
}
|
|
112
115
|
|
|
113
116
|
async saveBufferSession(key: string, entry: BufferEntryState): Promise<void> {
|
|
114
|
-
|
|
117
|
+
if (key !== this.key) {
|
|
118
|
+
await this.claims.writeEntry(key, this.ownerId, entry, undefined);
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
await this.writeProgress(entry);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
private async writeProgress(entry: BufferEntryState): Promise<void> {
|
|
125
|
+
await this.claims.writeEntry(this.key, this.ownerId, entry, this.seen);
|
|
126
|
+
// Copy the lists: the buffer keeps mutating its own arrays after a save.
|
|
127
|
+
this.seen = { ...entry, turns: [...entry.turns], retainedTurns: entry.retainedTurns ? [...entry.retainedTurns] : undefined };
|
|
115
128
|
}
|
|
116
129
|
|
|
117
130
|
async deleteBufferSession(): Promise<void> {
|
|
@@ -554,14 +567,25 @@ async function runClaimDrain(
|
|
|
554
567
|
options: BufferDrainRunnerOptions,
|
|
555
568
|
heartbeat: NodeJS.Timeout
|
|
556
569
|
): Promise<DrainSessionOutcome> {
|
|
570
|
+
// Reconcile only identities recorded as durably extracted under this
|
|
571
|
+
// owner's claim lease. The claim lock is released before entering the live
|
|
572
|
+
// buffer mutation queue, preserving the buffer→claim lock order used by
|
|
573
|
+
// concurrent ingress saves.
|
|
574
|
+
const reconcileDrainedTurns = async (): Promise<void> => {
|
|
575
|
+
const drained = await ctx.claims.drainedTurnsForOwner(ctx.key, ctx.ownerId);
|
|
576
|
+
if (drained !== null) await ctx.orchestrator.buffer.save({ key: ctx.key, drained });
|
|
577
|
+
};
|
|
578
|
+
|
|
557
579
|
// An emptied claim acks here: the ack verdict is the truth (drained), or
|
|
558
580
|
// live ingress merged between the read and the ack — deferred, source kept.
|
|
559
581
|
if (ctx.turns.length === 0) {
|
|
582
|
+
await reconcileDrainedTurns();
|
|
560
583
|
const outcome = await ackEmptyClaim(ctx.claims, ctx.key, ctx.ownerId, ctx.turnsAtStart);
|
|
561
584
|
options.onEvent?.({ type: "session_finished", key: ctx.key, outcome });
|
|
562
585
|
return outcome;
|
|
563
586
|
}
|
|
564
587
|
const { groupOutcomes, groupError } = await drainScopeGroups(ctx);
|
|
588
|
+
await reconcileDrainedTurns();
|
|
565
589
|
// Heartbeat cleared on EVERY exit path: a rejected drain must not keep
|
|
566
590
|
// renewing the lease forever (cleared in the caller's finally).
|
|
567
591
|
const remaining = await ctx.claims.load(ctx.key);
|
|
@@ -1694,11 +1694,7 @@ export class ExtractionPersistCoordinator {
|
|
|
1694
1694
|
// confirmation, so a disabled deployment never suppresses a write via
|
|
1695
1695
|
// dedup (PR #2016 thread SD-nH).
|
|
1696
1696
|
if (factDedupEnabled) {
|
|
1697
|
-
exactDuplicate = await confirmActiveFactDuplicate(
|
|
1698
|
-
{ hasContentHashDedup: (storage, key) => this.deps.hasContentHashDedup(storage, key) },
|
|
1699
|
-
targetStorage,
|
|
1700
|
-
raceDedupOpts(contentHashDedupKey),
|
|
1701
|
-
);
|
|
1697
|
+
exactDuplicate = await confirmActiveFactDuplicate(targetStorage, raceDedupOpts(contentHashDedupKey));
|
|
1702
1698
|
}
|
|
1703
1699
|
// PR #2016/#3231: finalize committed writes before a fail-closed
|
|
1704
1700
|
// window throw propagates (PRRT_kwDORJXyws6zxG6H).
|
|
@@ -1711,11 +1707,7 @@ export class ExtractionPersistCoordinator {
|
|
|
1711
1707
|
const raceRecheck = (storage: StorageManager, key: string) => () => {
|
|
1712
1708
|
// Gates mirror the persist-time suppression gates (PR #2016 SD-nH; #1645 quarantine).
|
|
1713
1709
|
if (!factDedupEnabled || injectionScreenStatus === "pending_review") return Promise.resolve(false);
|
|
1714
|
-
return confirmActiveFactDuplicate(
|
|
1715
|
-
{ hasContentHashDedup: (s, k) => this.deps.hasContentHashDedup(s, k) },
|
|
1716
|
-
storage,
|
|
1717
|
-
raceDedupOpts(key),
|
|
1718
|
-
);
|
|
1710
|
+
return confirmActiveFactDuplicate(storage, raceDedupOpts(key));
|
|
1719
1711
|
};
|
|
1720
1712
|
// #1671/#1707 — backfill bi-temporal bounds onto the surviving copy
|
|
1721
1713
|
// when a duplicate short-circuits: persist-time hit OR raced duplicate
|