@remnic/core 10.0.92 → 10.0.94
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 +332 -59
- package/dist/access-authorization-probe.js +2 -283
- package/dist/access-boundary.d.ts +1 -1
- package/dist/access-boundary.d.ts.map +1 -1
- package/dist/access-boundary.js +1 -282
- package/dist/access-cli.js +287 -303
- package/dist/access-cli.js.map +1 -1
- package/dist/access-coding-context-resolution.js +3 -3
- package/dist/access-extraction-force-flush.js +332 -59
- package/dist/access-http-briefing.js +332 -60
- package/dist/access-http-query.js +332 -61
- package/dist/access-http.js +330 -112
- package/dist/access-identity-continuity-surface.js +332 -59
- package/dist/access-lcm-surface.js +332 -59
- package/dist/access-mcp-frames.d.ts +20 -6
- package/dist/access-mcp-frames.d.ts.map +1 -1
- package/dist/access-mcp-frames.js +1 -1
- package/dist/access-mcp.d.ts +5 -0
- package/dist/access-mcp.d.ts.map +1 -1
- package/dist/access-mcp.js +330 -87
- package/dist/access-namespace-preflight.js +2 -282
- package/dist/access-observe-transcript.js +3 -3
- package/dist/access-observe-write-category.js +6 -6
- package/dist/access-observe-write-surface.js +332 -59
- package/dist/access-offline-manifest.js +12 -11
- package/dist/access-operations-batch.js +330 -64
- package/dist/access-operations.js +330 -73
- package/dist/access-recall-concurrency.js +332 -59
- package/dist/access-recall-diagnostics.js +5 -4
- package/dist/access-recall-response.js +332 -59
- package/dist/access-recall-surface.js +332 -59
- package/dist/access-schema.js +2 -2
- package/dist/access-service.js +335 -62
- package/dist/access-surface-catalog.js +6 -283
- package/dist/access-surface-catalog.js.map +1 -1
- package/dist/active-context-transform.js +1 -1
- package/dist/active-memory-bridge.js +3 -2
- package/dist/active-recall.js +36 -37
- package/dist/active-recall.js.map +1 -1
- package/dist/activity/journal-extract.d.ts +13 -1
- package/dist/activity/journal-extract.d.ts.map +1 -1
- package/dist/{auto-sync-3AZW7MXI.js → auto-sync-BO5YZKM6.js} +13 -15
- package/dist/{auto-sync-3AZW7MXI.js.map → auto-sync-BO5YZKM6.js.map} +1 -1
- package/dist/briefing.js +20 -20
- package/dist/buffer.js +16 -16
- package/dist/calibration.js +10 -10
- package/dist/{capsule-merge-2IIXFXN5.js → capsule-merge-6IDAYTKG.js} +6 -6
- package/dist/causal-consolidation.js +23 -23
- package/dist/{chunk-RGUUBAT5.js → chunk-2WCLTWV2.js} +3 -3
- package/dist/{chunk-BQZEGOZ4.js → chunk-2YODOG2O.js} +2 -2
- package/dist/{chunk-WYG5UNDY.js → chunk-3AUDELZL.js} +2 -2
- package/dist/{chunk-MB6ZJDN6.js → chunk-3FBAEPCU.js} +2 -2
- package/dist/{chunk-PIITB2YZ.js → chunk-3ZS3NUEI.js} +4 -4
- package/dist/{chunk-ATPWZ4F7.js → chunk-4EZWSDFL.js} +5 -5
- package/dist/{chunk-ANZZLRUX.js → chunk-4IVRQMZW.js} +2 -2
- package/dist/{chunk-63E37JCN.js → chunk-4RAJT5L2.js} +4 -4
- package/dist/{chunk-45VUHDV2.js → chunk-5CO5G4MN.js} +2 -2
- package/dist/{chunk-LUJCQQ3L.js → chunk-65HWAQ4F.js} +2 -2
- package/dist/{chunk-T67LUYCR.js → chunk-77NTFICT.js} +5 -5
- package/dist/chunk-A2WFUMUN.js +86 -0
- package/dist/chunk-A2WFUMUN.js.map +1 -0
- package/dist/{chunk-UE6RRXMF.js → chunk-A4F7OIC5.js} +2 -2
- package/dist/chunk-AHA5VQGP.js +117 -0
- package/dist/chunk-AHA5VQGP.js.map +1 -0
- package/dist/{chunk-RRJBDXAQ.js → chunk-B377OKUP.js} +2 -2
- package/dist/{chunk-IUKYFTGW.js → chunk-B3DR2JBZ.js} +140 -125
- package/dist/chunk-B3DR2JBZ.js.map +1 -0
- package/dist/{chunk-Z7JIZ42P.js → chunk-BN2EBCLB.js} +3 -3
- package/dist/{chunk-YYOMGOHB.js → chunk-BOK6BN5M.js} +2 -2
- package/dist/{chunk-U6UNF5ZS.js → chunk-BPK32FRM.js} +3 -3
- package/dist/{chunk-OH7QLI6W.js → chunk-BPPUH5JM.js} +5 -5
- package/dist/{chunk-O6ZSWHVR.js → chunk-C2KE5DMD.js} +4 -4
- package/dist/{chunk-47F6WWLQ.js → chunk-C73N66PR.js} +2 -2
- package/dist/{chunk-WRT7GJ2D.js → chunk-CIWMTNHW.js} +4 -4
- package/dist/{chunk-HF3BKUVV.js → chunk-CL4JU3DF.js} +7 -7
- package/dist/{chunk-O55EKIEC.js → chunk-D3ZKHLN2.js} +2 -2
- package/dist/chunk-D5S6RL32.js +67137 -0
- package/dist/chunk-D5S6RL32.js.map +1 -0
- package/dist/{chunk-ETZLQOUR.js → chunk-D5XG2KPS.js} +2 -2
- package/dist/{chunk-CD4BCCNP.js → chunk-DJ7WVEWX.js} +2 -2
- package/dist/{chunk-PWXPRVLK.js → chunk-DNY63QWJ.js} +2 -2
- package/dist/{chunk-4CJTQHEG.js → chunk-ESLOLCD4.js} +3 -3
- package/dist/{chunk-VILPVT4S.js → chunk-EZ47HOJ6.js} +4 -4
- package/dist/{chunk-EFYN5I7Z.js → chunk-F4QFU5IZ.js} +2 -2
- package/dist/{chunk-T6OZW2WR.js → chunk-FA7SLQ5X.js} +7 -1
- package/dist/chunk-FA7SLQ5X.js.map +1 -0
- package/dist/{chunk-BRLXOZLM.js → chunk-FQAQ63QA.js} +729 -17
- package/dist/chunk-FQAQ63QA.js.map +1 -0
- package/dist/{chunk-FSO3G4JJ.js → chunk-FWRXW7CZ.js} +5000 -2748
- package/dist/chunk-FWRXW7CZ.js.map +1 -0
- package/dist/{chunk-XYKBWTWI.js → chunk-FZZMLGWQ.js} +2 -2
- package/dist/{chunk-FNXQZGUS.js → chunk-GBJUTX44.js} +2 -2
- package/dist/{chunk-YLXETSAH.js → chunk-GCZJ7XRI.js} +2 -2
- package/dist/{chunk-UDKWFDV2.js → chunk-HLUHEMN4.js} +27 -3
- package/dist/chunk-HLUHEMN4.js.map +1 -0
- package/dist/{chunk-SUSW74WU.js → chunk-IL3RHBWM.js} +56 -5
- package/dist/chunk-IL3RHBWM.js.map +1 -0
- package/dist/{chunk-JV4S6VDH.js → chunk-ILY5SSOM.js} +2 -2
- package/dist/{chunk-EXHXJETE.js → chunk-IQ55QGY5.js} +2 -2
- package/dist/{chunk-4J363OAA.js → chunk-J7BIFOYP.js} +2 -2
- package/dist/{chunk-QFEDITXM.js → chunk-JNDMIAJ4.js} +2 -2
- package/dist/chunk-JTSSSR4L.js +359 -0
- package/dist/chunk-JTSSSR4L.js.map +1 -0
- package/dist/{chunk-5KBIOI4G.js → chunk-JVBAPAWZ.js} +217 -13
- package/dist/chunk-JVBAPAWZ.js.map +1 -0
- package/dist/{chunk-E5PPE4VA.js → chunk-K2VRKCPA.js} +2 -2
- package/dist/{chunk-MKRYQROF.js → chunk-LGEHAQHK.js} +4 -4
- package/dist/{chunk-7HTUYFR3.js → chunk-LLPMMMWG.js} +27 -10
- package/dist/chunk-LLPMMMWG.js.map +1 -0
- package/dist/{chunk-XYTQJD7F.js → chunk-LMJVP5CM.js} +2 -2
- package/dist/{chunk-E7YZXWIS.js → chunk-LNGELD2M.js} +2 -2
- package/dist/{chunk-FQKBJJYB.js → chunk-LPV72KRQ.js} +2 -2
- package/dist/{chunk-2LCRG2YV.js → chunk-M2XR6B6V.js} +2 -2
- package/dist/{chunk-EQ4U73UH.js → chunk-MLAMXQAW.js} +14 -2
- package/dist/chunk-MLAMXQAW.js.map +1 -0
- package/dist/{chunk-ZU5JDLOH.js → chunk-MMRTPH3J.js} +2 -2
- package/dist/{chunk-TVIVY7KU.js → chunk-NED7IR7B.js} +4 -4
- package/dist/{chunk-6F5MJFXY.js → chunk-NHVAX7MH.js} +2 -2
- package/dist/{chunk-TG3R2SID.js → chunk-NMGHGTVR.js} +5 -5
- package/dist/{chunk-YCNIGRRF.js → chunk-NS7U4ZYU.js} +13 -117
- package/dist/chunk-NS7U4ZYU.js.map +1 -0
- package/dist/{chunk-NGHDW6UU.js → chunk-NUQEREIP.js} +2 -2
- package/dist/{chunk-5IIQU4JZ.js → chunk-OB72KNMF.js} +4 -4
- package/dist/{chunk-RIMK327Y.js → chunk-OUVYYKYU.js} +7 -7
- package/dist/{chunk-QJPJXCS4.js → chunk-P3WRNWWM.js} +157 -4
- package/dist/chunk-P3WRNWWM.js.map +1 -0
- package/dist/{chunk-E6M225BJ.js → chunk-PC5DRBGD.js} +18 -8
- package/dist/chunk-PC5DRBGD.js.map +1 -0
- package/dist/{chunk-RWGRSKN5.js → chunk-PT4DZDJA.js} +2 -2
- package/dist/{chunk-7EPY3IQL.js → chunk-PZJQ46DH.js} +2 -2
- package/dist/{chunk-MXHMNKWG.js → chunk-QDESY76F.js} +3 -3
- package/dist/{chunk-E3A7ELSG.js → chunk-QHARXWTC.js} +2 -2
- package/dist/{chunk-BLJT6JP2.js → chunk-QMFAMOT2.js} +2 -2
- package/dist/{chunk-I6QGI65T.js → chunk-QRMG4HK4.js} +3 -3
- package/dist/{chunk-ZU3NJNO7.js → chunk-QSF5SJAM.js} +4 -4
- package/dist/{chunk-5IYOKDM4.js → chunk-R23JWJD3.js} +5 -5
- package/dist/{chunk-RHQLIHMB.js → chunk-R4BBGPI2.js} +218 -218
- package/dist/chunk-R4BBGPI2.js.map +1 -0
- package/dist/{chunk-NT7F7WJQ.js → chunk-RFUL2ZIQ.js} +291 -93
- package/dist/chunk-RFUL2ZIQ.js.map +1 -0
- package/dist/{chunk-E4IRW2FG.js → chunk-RKABLU5M.js} +233 -40
- package/dist/chunk-RKABLU5M.js.map +1 -0
- package/dist/{chunk-P4WNBSTW.js → chunk-RNG7S3FY.js} +2 -2
- package/dist/{chunk-IPT2OY4B.js → chunk-RQFXZYTF.js} +35 -33
- package/dist/chunk-RQFXZYTF.js.map +1 -0
- package/dist/{chunk-5HFXGXE5.js → chunk-RUTN5MMC.js} +7 -9
- package/dist/{chunk-5HFXGXE5.js.map → chunk-RUTN5MMC.js.map} +1 -1
- package/dist/{chunk-AP6NCP3V.js → chunk-S3S72HRM.js} +8 -6
- package/dist/chunk-S3S72HRM.js.map +1 -0
- package/dist/{chunk-ZGFLIK5H.js → chunk-SPAKBUJY.js} +2 -2
- package/dist/{chunk-ER7XANP3.js → chunk-SQ3GEIEL.js} +4 -4
- package/dist/{chunk-PMJFRDJI.js → chunk-SUTEBYGI.js} +2 -2
- package/dist/{chunk-HVX4LPNY.js → chunk-T4KY6UYF.js} +5 -3
- package/dist/{chunk-HVX4LPNY.js.map → chunk-T4KY6UYF.js.map} +1 -1
- package/dist/{chunk-R6HMAIKM.js → chunk-TOSPNM4T.js} +2 -2
- package/dist/{chunk-RMXS4I66.js → chunk-TVDJEUV5.js} +2 -2
- package/dist/{chunk-PLCYKAWH.js → chunk-TYRWSE4B.js} +2 -2
- package/dist/{chunk-B4QYQFGY.js → chunk-UBEKET2V.js} +383 -36
- package/dist/chunk-UBEKET2V.js.map +1 -0
- package/dist/{chunk-IQWUACVG.js → chunk-UHMHJX5G.js} +3 -3
- package/dist/{chunk-YODUDA6R.js → chunk-UN4RVUG2.js} +22 -22
- package/dist/{chunk-E6Z4FBX6.js → chunk-V2QSNFCT.js} +2 -2
- package/dist/{chunk-NBH6K6G5.js → chunk-V65H7MI6.js} +345 -16
- package/dist/chunk-V65H7MI6.js.map +1 -0
- package/dist/chunk-W54Z2MOP.js +4500 -0
- package/dist/chunk-W54Z2MOP.js.map +1 -0
- package/dist/{chunk-7W3RXOIG.js → chunk-WGGRZYSQ.js} +14 -14
- package/dist/{chunk-RIZ5NUNN.js → chunk-WQ4XKIOX.js} +4 -4
- package/dist/{chunk-QEYTRFR3.js → chunk-X26YOFGS.js} +2 -2
- package/dist/{chunk-ITHZFHST.js → chunk-X2BVB74H.js} +2 -2
- package/dist/{chunk-6F42QONS.js → chunk-X5HGU2EM.js} +4 -4
- package/dist/{chunk-GVGAO3WV.js → chunk-XJ2S5UBF.js} +3 -3
- package/dist/{chunk-IBJ6U753.js → chunk-XKZBZOKV.js} +4 -4
- package/dist/{chunk-65AUYLU3.js → chunk-XPXHQI6N.js} +2 -2
- package/dist/chunk-XRWWCW4H.js +102 -0
- package/dist/chunk-XRWWCW4H.js.map +1 -0
- package/dist/{chunk-HH5LY6RR.js → chunk-YIXJSW5M.js} +5 -5
- package/dist/{chunk-2JSDPUKD.js → chunk-YMFPFXEX.js} +2 -2
- package/dist/{chunk-N4KAE2NB.js → chunk-Z3FTSHT5.js} +3 -3
- package/dist/{chunk-7GLJMDEQ.js → chunk-ZDV5YWID.js} +6 -6
- package/dist/{chunk-645WOU5I.js → chunk-ZDZJNXL6.js} +4 -4
- package/dist/{chunk-6TCXIRP5.js → chunk-ZKE4CO6B.js} +4 -4
- package/dist/cli.js +303 -185
- package/dist/coding/export-okf-codegraph.js +29 -29
- package/dist/compounding/engine.js +23 -24
- package/dist/config.js +36 -37
- package/dist/connectors/codex-materialize-runner.js +20 -20
- package/dist/connectors/index.js +21 -21
- package/dist/connectors/live/google-drive.d.ts +2 -0
- package/dist/connectors/live/google-drive.d.ts.map +1 -1
- package/dist/consolidation-provenance-check.js +4 -4
- package/dist/consolidation-undo.js +5 -5
- package/dist/contradiction/index.js +7 -6
- package/dist/{contradiction-scan-P2MHYFVG.js → contradiction-scan-IBPYGQZF.js} +4 -3
- package/dist/convergence-refresh.js +22 -22
- package/dist/corpus-watermark.js +23 -23
- package/dist/deep-recall-http-glue.js +2 -2
- package/dist/deep-recall-policy-llm.js +4 -4
- package/dist/direct-answer-wiring.js +2 -2
- package/dist/embedding-fallback.js +1 -1
- package/dist/entity-retrieval.js +21 -21
- package/dist/eval-trajectory-store.js +2 -2
- package/dist/eval-trajectory.js +2 -2
- package/dist/event-order-recall.js +2 -2
- package/dist/explicit-capture.d.ts +3 -2
- package/dist/explicit-capture.d.ts.map +1 -1
- package/dist/explicit-capture.js +530 -6
- package/dist/explicit-cue-recall.js +2 -2
- package/dist/external-wiki-access.js +2 -283
- package/dist/extraction-faithfulness.js +4 -4
- package/dist/extraction-judge.js +5 -5
- package/dist/extraction-normalization.js +1 -1
- 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 +3 -3
- package/dist/extraction.js +43 -44
- package/dist/fallback-llm-codex-subscription.js +2 -2
- package/dist/fallback-llm.js +3 -3
- package/dist/{first-start-migration-QFI7B5SH.js → first-start-migration-FILJH3RO.js} +6 -5
- package/dist/{first-start-migration-QFI7B5SH.js.map → first-start-migration-FILJH3RO.js.map} +1 -1
- package/dist/focused-list-recall.js +2 -2
- package/dist/harmonic-retrieval.js +20 -20
- package/dist/index.js +773 -794
- package/dist/lcm/index.js +2 -2
- package/dist/live-connectors-runner.js +2 -3
- package/dist/local-llm-config.js +2 -2
- package/dist/local-llm-helpers.d.ts +4 -0
- package/dist/local-llm-helpers.d.ts.map +1 -1
- package/dist/local-llm-helpers.js +3 -1
- package/dist/local-llm.d.ts +4 -1
- package/dist/local-llm.d.ts.map +1 -1
- package/dist/local-llm.js +4 -4
- package/dist/location/index.js +27 -29
- package/dist/maintenance/memory-governance.js +20 -20
- package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +20 -20
- package/dist/maintenance/rebuild-memory-projection-runner.js +22 -22
- package/dist/maintenance/rebuild-memory-projection.js +21 -21
- package/dist/memory-browse-operations.js +2 -283
- package/dist/memory-browse.js +8 -7
- package/dist/memory-controller.js +2 -2
- package/dist/memory-subject.js +2 -2
- package/dist/namespaces/migrate.js +21 -21
- package/dist/namespaces/search.js +5 -5
- package/dist/namespaces/storage.js +20 -20
- package/dist/offline-sync-changeset-generations.js +1 -1
- package/dist/offline-sync-embedding-generation.js +1 -1
- package/dist/offline-sync-impression-drain.js +6 -6
- package/dist/offline-sync-snapshot-stream.js +1 -1
- package/dist/offline-sync.js +1 -1
- package/dist/operator-doctor-corpus.js +24 -24
- package/dist/operator-toolkit.js +58 -61
- package/dist/orchestration/backlog-scheduler.js +278 -294
- package/dist/orchestration/backlog-scheduler.js.map +1 -1
- package/dist/orchestration/buffer-drain-runner.js +278 -294
- package/dist/orchestration/extraction-run.d.ts +4 -0
- package/dist/orchestration/extraction-run.d.ts.map +1 -1
- package/dist/orchestration/fact-dedup-window.d.ts +1 -1
- package/dist/orchestration/fact-dedup-window.d.ts.map +1 -1
- package/dist/orchestration/maintenance.js +39 -42
- package/dist/orchestrator.d.ts +9 -11
- package/dist/orchestrator.d.ts.map +1 -1
- package/dist/orchestrator.js +290 -307
- package/dist/page-versioning.js +3 -3
- package/dist/qmd-daemon-session.js +2 -2
- package/dist/qmd.js +3 -3
- package/dist/recall-explain-renderer.js +1 -1
- package/dist/recall-navigation-operations.js +2 -283
- package/dist/recall-navigation.js +332 -59
- package/dist/recall-planner-llm.js +3 -3
- package/dist/recall-state-view-admission.js +6 -6
- package/dist/recall-why-service.js +10 -9
- package/dist/recall-why.js +4 -3
- package/dist/recall-xray-cli.js +2 -2
- package/dist/recall-xray-renderer.js +1 -1
- package/dist/reconcile/manifest.js +3 -3
- package/dist/{resolution-N7H3F2E4.js → resolution-OVVIF7ZY.js} +8 -7
- package/dist/response-guidance-recall.js +2 -2
- package/dist/resume-bundles.js +40 -41
- package/dist/salvage-envelope.js +4 -4
- package/dist/search/factory.js +4 -4
- package/dist/search/index.js +4 -4
- package/dist/semantic-consolidation.js +23 -23
- package/dist/semantic-rule-promotion.js +20 -20
- package/dist/semantic-rule-verifier.js +20 -20
- package/dist/shared-context/manager.js +2 -3
- package/dist/source-agent-qualifier.js +32 -33
- package/dist/standup/build.js +20 -20
- package/dist/storage/buffer-drain-claims.d.ts +97 -12
- package/dist/storage/buffer-drain-claims.d.ts.map +1 -1
- package/dist/storage/buffer-drain-claims.js +1 -1
- package/dist/storage/buffer-session-files.d.ts +4 -0
- package/dist/storage/buffer-session-files.d.ts.map +1 -1
- package/dist/storage/buffer-session-files.js +1 -1
- package/dist/storage/content-hash-index.d.ts +10 -0
- package/dist/storage/content-hash-index.d.ts.map +1 -1
- package/dist/storage.d.ts.map +1 -1
- package/dist/storage.js +19 -19
- package/dist/subject-config.js +3 -3
- package/dist/summarizer.d.ts +4 -1
- package/dist/summarizer.d.ts.map +1 -1
- package/dist/summarizer.js +6 -6
- package/dist/support-passport/index.js +53 -297
- package/dist/targeted-fact-recall.js +2 -2
- package/dist/tier-routing.js +3 -2
- package/dist/{tier-stats-FQGQSFET.js → tier-stats-FXEUJN73.js} +6 -5
- package/dist/{tier-stats-FQGQSFET.js.map → tier-stats-FXEUJN73.js.map} +1 -1
- package/dist/transcript.js +2 -2
- package/dist/transfer/capsule-import.js +4 -4
- package/dist/transfer/export-okf.js +20 -20
- package/dist/verified-recall.js +20 -20
- package/dist/wearables/memory-gen.d.ts +5 -1
- package/dist/wearables/memory-gen.d.ts.map +1 -1
- package/dist/wearables/service.d.ts.map +1 -1
- package/dist/who-knows-cli.js +21 -21
- package/dist/who-knows.js +20 -20
- package/dist/write-envelope.js +3 -3
- package/package.json +2 -2
- package/src/access-boundary.ts +5 -1
- package/src/access-category-alias.test.ts +5 -0
- package/src/access-mcp-frames.test.ts +60 -5
- package/src/access-mcp-frames.ts +72 -18
- package/src/access-mcp.test.ts +66 -2
- package/src/access-mcp.ts +19 -13
- package/src/access-service-coding-write.test.ts +7 -0
- package/src/activity/journal-extract-storage.test.ts +31 -0
- package/src/activity/journal-extract.test.ts +1 -0
- package/src/activity/journal-extract.ts +24 -3
- package/src/buffer-drain-handover.test.ts +5 -1
- package/src/connectors/live/google-drive.test.ts +39 -0
- package/src/connectors/live/google-drive.ts +41 -12
- package/src/explicit-capture.test.ts +109 -17
- package/src/explicit-capture.ts +90 -37
- package/src/extraction-quote-grounding.test.ts +20 -0
- package/src/extraction-quote-grounding.ts +21 -11
- package/src/fact-dedup-window.test.ts +37 -0
- package/src/local-llm-helpers.ts +12 -0
- package/src/local-llm-shutdown.test.ts +194 -0
- package/src/local-llm.ts +32 -30
- package/src/orchestration/extraction-run.ts +12 -3
- package/src/orchestration/fact-dedup-window.ts +5 -1
- package/src/orchestrator-connector-dedup.test.ts +9 -5
- package/src/orchestrator.ts +47 -50
- package/src/storage/buffer-drain-claims.test.ts +385 -1
- package/src/storage/buffer-drain-claims.ts +380 -97
- package/src/storage/buffer-session-files.ts +4 -0
- package/src/storage/content-hash-index.ts +27 -2
- package/src/storage-fact-hash-publish-failure.test.ts +87 -0
- package/src/storage.ts +5 -1
- package/src/summarizer-shutdown.test.ts +195 -0
- package/src/summarizer.ts +15 -2
- package/src/wearables/memory-gen.test.ts +20 -0
- package/src/wearables/memory-gen.ts +14 -7
- package/src/wearables/service.ts +14 -1
- package/src/wearables/storage-io.test.ts +25 -0
- package/dist/chunk-2FGLEF7C.js +0 -61
- package/dist/chunk-2FGLEF7C.js.map +0 -1
- package/dist/chunk-3RLK65ZK.js +0 -203
- package/dist/chunk-3RLK65ZK.js.map +0 -1
- package/dist/chunk-4WR2OD7K.js +0 -43
- package/dist/chunk-4WR2OD7K.js.map +0 -1
- package/dist/chunk-5KBIOI4G.js.map +0 -1
- package/dist/chunk-5M5M22GW.js +0 -346
- package/dist/chunk-5M5M22GW.js.map +0 -1
- package/dist/chunk-7HTUYFR3.js.map +0 -1
- package/dist/chunk-AP6NCP3V.js.map +0 -1
- package/dist/chunk-B4QYQFGY.js.map +0 -1
- package/dist/chunk-BJRTMNCB.js +0 -1919
- package/dist/chunk-BJRTMNCB.js.map +0 -1
- package/dist/chunk-BRLXOZLM.js.map +0 -1
- package/dist/chunk-BSACVS6R.js +0 -621
- package/dist/chunk-BSACVS6R.js.map +0 -1
- package/dist/chunk-DKAA2IHQ.js +0 -3644
- package/dist/chunk-DKAA2IHQ.js.map +0 -1
- package/dist/chunk-E4IRW2FG.js.map +0 -1
- package/dist/chunk-E6M225BJ.js.map +0 -1
- package/dist/chunk-EQ4U73UH.js.map +0 -1
- package/dist/chunk-ETSJD5IV.js +0 -361
- package/dist/chunk-ETSJD5IV.js.map +0 -1
- package/dist/chunk-F55YQP7N.js +0 -336
- package/dist/chunk-F55YQP7N.js.map +0 -1
- package/dist/chunk-FHGWKODN.js +0 -2300
- package/dist/chunk-FHGWKODN.js.map +0 -1
- package/dist/chunk-FLRP63GU.js +0 -32219
- package/dist/chunk-FLRP63GU.js.map +0 -1
- package/dist/chunk-FSO3G4JJ.js.map +0 -1
- package/dist/chunk-ID6JGIMD.js +0 -25
- package/dist/chunk-ID6JGIMD.js.map +0 -1
- package/dist/chunk-IGLJNHOX.js +0 -62
- package/dist/chunk-IGLJNHOX.js.map +0 -1
- package/dist/chunk-IPT2OY4B.js.map +0 -1
- package/dist/chunk-IUKYFTGW.js.map +0 -1
- package/dist/chunk-NBH6K6G5.js.map +0 -1
- package/dist/chunk-NT7F7WJQ.js.map +0 -1
- package/dist/chunk-NTRUKAAE.js +0 -223
- package/dist/chunk-NTRUKAAE.js.map +0 -1
- package/dist/chunk-OGXXGHW6.js +0 -223
- package/dist/chunk-OGXXGHW6.js.map +0 -1
- package/dist/chunk-PPCLZ4JU.js +0 -28
- package/dist/chunk-PPCLZ4JU.js.map +0 -1
- package/dist/chunk-QJPJXCS4.js.map +0 -1
- package/dist/chunk-R2LBF4HH.js +0 -729
- package/dist/chunk-R2LBF4HH.js.map +0 -1
- package/dist/chunk-RHQLIHMB.js.map +0 -1
- package/dist/chunk-RRTVGD3J.js +0 -792
- package/dist/chunk-RRTVGD3J.js.map +0 -1
- package/dist/chunk-SUSW74WU.js.map +0 -1
- package/dist/chunk-T6OZW2WR.js.map +0 -1
- package/dist/chunk-UDKWFDV2.js.map +0 -1
- package/dist/chunk-V7IZKJI3.js +0 -22
- package/dist/chunk-V7IZKJI3.js.map +0 -1
- package/dist/chunk-X562NQFA.js +0 -10144
- package/dist/chunk-X562NQFA.js.map +0 -1
- package/dist/chunk-XDSCJOH7.js +0 -3991
- package/dist/chunk-XDSCJOH7.js.map +0 -1
- package/dist/chunk-YCNIGRRF.js.map +0 -1
- package/dist/chunk-ZTTCDQ4I.js +0 -18796
- package/dist/chunk-ZTTCDQ4I.js.map +0 -1
- /package/dist/{capsule-merge-2IIXFXN5.js.map → capsule-merge-6IDAYTKG.js.map} +0 -0
- /package/dist/{chunk-RGUUBAT5.js.map → chunk-2WCLTWV2.js.map} +0 -0
- /package/dist/{chunk-BQZEGOZ4.js.map → chunk-2YODOG2O.js.map} +0 -0
- /package/dist/{chunk-WYG5UNDY.js.map → chunk-3AUDELZL.js.map} +0 -0
- /package/dist/{chunk-MB6ZJDN6.js.map → chunk-3FBAEPCU.js.map} +0 -0
- /package/dist/{chunk-PIITB2YZ.js.map → chunk-3ZS3NUEI.js.map} +0 -0
- /package/dist/{chunk-ATPWZ4F7.js.map → chunk-4EZWSDFL.js.map} +0 -0
- /package/dist/{chunk-ANZZLRUX.js.map → chunk-4IVRQMZW.js.map} +0 -0
- /package/dist/{chunk-63E37JCN.js.map → chunk-4RAJT5L2.js.map} +0 -0
- /package/dist/{chunk-45VUHDV2.js.map → chunk-5CO5G4MN.js.map} +0 -0
- /package/dist/{chunk-LUJCQQ3L.js.map → chunk-65HWAQ4F.js.map} +0 -0
- /package/dist/{chunk-T67LUYCR.js.map → chunk-77NTFICT.js.map} +0 -0
- /package/dist/{chunk-UE6RRXMF.js.map → chunk-A4F7OIC5.js.map} +0 -0
- /package/dist/{chunk-RRJBDXAQ.js.map → chunk-B377OKUP.js.map} +0 -0
- /package/dist/{chunk-Z7JIZ42P.js.map → chunk-BN2EBCLB.js.map} +0 -0
- /package/dist/{chunk-YYOMGOHB.js.map → chunk-BOK6BN5M.js.map} +0 -0
- /package/dist/{chunk-U6UNF5ZS.js.map → chunk-BPK32FRM.js.map} +0 -0
- /package/dist/{chunk-OH7QLI6W.js.map → chunk-BPPUH5JM.js.map} +0 -0
- /package/dist/{chunk-O6ZSWHVR.js.map → chunk-C2KE5DMD.js.map} +0 -0
- /package/dist/{chunk-47F6WWLQ.js.map → chunk-C73N66PR.js.map} +0 -0
- /package/dist/{chunk-WRT7GJ2D.js.map → chunk-CIWMTNHW.js.map} +0 -0
- /package/dist/{chunk-HF3BKUVV.js.map → chunk-CL4JU3DF.js.map} +0 -0
- /package/dist/{chunk-O55EKIEC.js.map → chunk-D3ZKHLN2.js.map} +0 -0
- /package/dist/{chunk-ETZLQOUR.js.map → chunk-D5XG2KPS.js.map} +0 -0
- /package/dist/{chunk-CD4BCCNP.js.map → chunk-DJ7WVEWX.js.map} +0 -0
- /package/dist/{chunk-PWXPRVLK.js.map → chunk-DNY63QWJ.js.map} +0 -0
- /package/dist/{chunk-4CJTQHEG.js.map → chunk-ESLOLCD4.js.map} +0 -0
- /package/dist/{chunk-VILPVT4S.js.map → chunk-EZ47HOJ6.js.map} +0 -0
- /package/dist/{chunk-EFYN5I7Z.js.map → chunk-F4QFU5IZ.js.map} +0 -0
- /package/dist/{chunk-XYKBWTWI.js.map → chunk-FZZMLGWQ.js.map} +0 -0
- /package/dist/{chunk-FNXQZGUS.js.map → chunk-GBJUTX44.js.map} +0 -0
- /package/dist/{chunk-YLXETSAH.js.map → chunk-GCZJ7XRI.js.map} +0 -0
- /package/dist/{chunk-JV4S6VDH.js.map → chunk-ILY5SSOM.js.map} +0 -0
- /package/dist/{chunk-EXHXJETE.js.map → chunk-IQ55QGY5.js.map} +0 -0
- /package/dist/{chunk-4J363OAA.js.map → chunk-J7BIFOYP.js.map} +0 -0
- /package/dist/{chunk-QFEDITXM.js.map → chunk-JNDMIAJ4.js.map} +0 -0
- /package/dist/{chunk-E5PPE4VA.js.map → chunk-K2VRKCPA.js.map} +0 -0
- /package/dist/{chunk-MKRYQROF.js.map → chunk-LGEHAQHK.js.map} +0 -0
- /package/dist/{chunk-XYTQJD7F.js.map → chunk-LMJVP5CM.js.map} +0 -0
- /package/dist/{chunk-E7YZXWIS.js.map → chunk-LNGELD2M.js.map} +0 -0
- /package/dist/{chunk-FQKBJJYB.js.map → chunk-LPV72KRQ.js.map} +0 -0
- /package/dist/{chunk-2LCRG2YV.js.map → chunk-M2XR6B6V.js.map} +0 -0
- /package/dist/{chunk-ZU5JDLOH.js.map → chunk-MMRTPH3J.js.map} +0 -0
- /package/dist/{chunk-TVIVY7KU.js.map → chunk-NED7IR7B.js.map} +0 -0
- /package/dist/{chunk-6F5MJFXY.js.map → chunk-NHVAX7MH.js.map} +0 -0
- /package/dist/{chunk-TG3R2SID.js.map → chunk-NMGHGTVR.js.map} +0 -0
- /package/dist/{chunk-NGHDW6UU.js.map → chunk-NUQEREIP.js.map} +0 -0
- /package/dist/{chunk-5IIQU4JZ.js.map → chunk-OB72KNMF.js.map} +0 -0
- /package/dist/{chunk-RIMK327Y.js.map → chunk-OUVYYKYU.js.map} +0 -0
- /package/dist/{chunk-RWGRSKN5.js.map → chunk-PT4DZDJA.js.map} +0 -0
- /package/dist/{chunk-7EPY3IQL.js.map → chunk-PZJQ46DH.js.map} +0 -0
- /package/dist/{chunk-MXHMNKWG.js.map → chunk-QDESY76F.js.map} +0 -0
- /package/dist/{chunk-E3A7ELSG.js.map → chunk-QHARXWTC.js.map} +0 -0
- /package/dist/{chunk-BLJT6JP2.js.map → chunk-QMFAMOT2.js.map} +0 -0
- /package/dist/{chunk-I6QGI65T.js.map → chunk-QRMG4HK4.js.map} +0 -0
- /package/dist/{chunk-ZU3NJNO7.js.map → chunk-QSF5SJAM.js.map} +0 -0
- /package/dist/{chunk-5IYOKDM4.js.map → chunk-R23JWJD3.js.map} +0 -0
- /package/dist/{chunk-P4WNBSTW.js.map → chunk-RNG7S3FY.js.map} +0 -0
- /package/dist/{chunk-ZGFLIK5H.js.map → chunk-SPAKBUJY.js.map} +0 -0
- /package/dist/{chunk-ER7XANP3.js.map → chunk-SQ3GEIEL.js.map} +0 -0
- /package/dist/{chunk-PMJFRDJI.js.map → chunk-SUTEBYGI.js.map} +0 -0
- /package/dist/{chunk-R6HMAIKM.js.map → chunk-TOSPNM4T.js.map} +0 -0
- /package/dist/{chunk-RMXS4I66.js.map → chunk-TVDJEUV5.js.map} +0 -0
- /package/dist/{chunk-PLCYKAWH.js.map → chunk-TYRWSE4B.js.map} +0 -0
- /package/dist/{chunk-IQWUACVG.js.map → chunk-UHMHJX5G.js.map} +0 -0
- /package/dist/{chunk-YODUDA6R.js.map → chunk-UN4RVUG2.js.map} +0 -0
- /package/dist/{chunk-E6Z4FBX6.js.map → chunk-V2QSNFCT.js.map} +0 -0
- /package/dist/{chunk-7W3RXOIG.js.map → chunk-WGGRZYSQ.js.map} +0 -0
- /package/dist/{chunk-RIZ5NUNN.js.map → chunk-WQ4XKIOX.js.map} +0 -0
- /package/dist/{chunk-QEYTRFR3.js.map → chunk-X26YOFGS.js.map} +0 -0
- /package/dist/{chunk-ITHZFHST.js.map → chunk-X2BVB74H.js.map} +0 -0
- /package/dist/{chunk-6F42QONS.js.map → chunk-X5HGU2EM.js.map} +0 -0
- /package/dist/{chunk-GVGAO3WV.js.map → chunk-XJ2S5UBF.js.map} +0 -0
- /package/dist/{chunk-IBJ6U753.js.map → chunk-XKZBZOKV.js.map} +0 -0
- /package/dist/{chunk-65AUYLU3.js.map → chunk-XPXHQI6N.js.map} +0 -0
- /package/dist/{chunk-HH5LY6RR.js.map → chunk-YIXJSW5M.js.map} +0 -0
- /package/dist/{chunk-2JSDPUKD.js.map → chunk-YMFPFXEX.js.map} +0 -0
- /package/dist/{chunk-N4KAE2NB.js.map → chunk-Z3FTSHT5.js.map} +0 -0
- /package/dist/{chunk-7GLJMDEQ.js.map → chunk-ZDV5YWID.js.map} +0 -0
- /package/dist/{chunk-645WOU5I.js.map → chunk-ZDZJNXL6.js.map} +0 -0
- /package/dist/{chunk-6TCXIRP5.js.map → chunk-ZKE4CO6B.js.map} +0 -0
- /package/dist/{contradiction-scan-P2MHYFVG.js.map → contradiction-scan-IBPYGQZF.js.map} +0 -0
- /package/dist/{resolution-N7H3F2E4.js.map → resolution-OVVIF7ZY.js.map} +0 -0
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import assert from "node:assert/strict";
|
|
2
|
-
import { mkdtempSync, readdirSync, existsSync, readFileSync, writeFileSync, mkdirSync, rmSync, utimesSync } from "node:fs";
|
|
2
|
+
import { mkdtempSync, readdirSync, existsSync, readFileSync, writeFileSync, appendFileSync, mkdirSync, rmSync, statSync, utimesSync } from "node:fs";
|
|
3
|
+
import { randomUUID } from "node:crypto";
|
|
3
4
|
import os from "node:os";
|
|
4
5
|
import { hostname } from "node:os";
|
|
5
6
|
import path from "node:path";
|
|
7
|
+
import { z } from "zod";
|
|
6
8
|
import test from "node:test";
|
|
7
9
|
|
|
8
10
|
import { BufferDrainClaims, type AckOutcome, type DrainLease } from "./buffer-drain-claims.js";
|
|
@@ -23,6 +25,10 @@ function plainIo() {
|
|
|
23
25
|
writeFileSync(filePath, content);
|
|
24
26
|
rmSync(tmp, { force: true });
|
|
25
27
|
},
|
|
28
|
+
append: async (filePath: string, content: string) => {
|
|
29
|
+
mkdirSync(path.dirname(filePath), { recursive: true });
|
|
30
|
+
appendFileSync(filePath, content);
|
|
31
|
+
},
|
|
26
32
|
};
|
|
27
33
|
}
|
|
28
34
|
|
|
@@ -51,6 +57,50 @@ function makeLease(ownerId = "worker-1"): DrainLease {
|
|
|
51
57
|
return { ownerId, pid: process.pid, host: hostname(), acquiredAt: now, heartbeatAt: now, leaseMs: 60_000 };
|
|
52
58
|
}
|
|
53
59
|
|
|
60
|
+
/** The claim body file for `key` inside a setup()'s stateDir. */
|
|
61
|
+
function drainFile(stateDir: string, key: string): string {
|
|
62
|
+
return path.join(stateDir, "buffer", "drain", bufferSessionFileName(key));
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** The append-only inbox next to the claim body. */
|
|
66
|
+
function inboxFile(stateDir: string, key: string): string {
|
|
67
|
+
return `${drainFile(stateDir, key)}.inbox.jsonl`;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** One inbox record as mergeIntoClaim writes it: newline-prefixed JSONL with a
|
|
71
|
+
* unique id, so a torn tail always ends at the next record's leading newline
|
|
72
|
+
* and parses as its own (skipped) line. */
|
|
73
|
+
function record(key: string, entryState: BufferEntryState, id = randomUUID()): string {
|
|
74
|
+
return `\n${JSON.stringify({ id, key, ...entryState })}`;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** plainIo() with call recording on the seam the cost tests assert on. */
|
|
78
|
+
function spyIo() {
|
|
79
|
+
const io = plainIo();
|
|
80
|
+
const readPaths: string[] = [];
|
|
81
|
+
const writePaths: string[] = [];
|
|
82
|
+
const appendPaths: string[] = [];
|
|
83
|
+
return {
|
|
84
|
+
io: {
|
|
85
|
+
read: async (filePath: string) => {
|
|
86
|
+
readPaths.push(filePath);
|
|
87
|
+
return io.read(filePath);
|
|
88
|
+
},
|
|
89
|
+
write: async (filePath: string, content: string) => {
|
|
90
|
+
writePaths.push(filePath);
|
|
91
|
+
return io.write(filePath, content);
|
|
92
|
+
},
|
|
93
|
+
append: async (filePath: string, content: string) => {
|
|
94
|
+
appendPaths.push(filePath);
|
|
95
|
+
return io.append(filePath, content);
|
|
96
|
+
},
|
|
97
|
+
},
|
|
98
|
+
readPaths,
|
|
99
|
+
writePaths,
|
|
100
|
+
appendPaths,
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
|
|
54
104
|
test("claimFromSessionFile moves a session file into the drain dir atomically", async () => {
|
|
55
105
|
const s = setup();
|
|
56
106
|
try {
|
|
@@ -770,3 +820,337 @@ test("a late arrival of an ambiguous identity does not replace a source occurren
|
|
|
770
820
|
s.cleanup();
|
|
771
821
|
}
|
|
772
822
|
});
|
|
823
|
+
|
|
824
|
+
// ─── Append-only claim inbox ────────────────────────────────────────────────
|
|
825
|
+
|
|
826
|
+
test("a kill between the inbox append and any body write loses nothing", async () => {
|
|
827
|
+
const s = setup();
|
|
828
|
+
try {
|
|
829
|
+
s.seedSession("sess-1", entry([turn("a")]));
|
|
830
|
+
await s.claims.claimFromSessionFile("sess-1", makeLease());
|
|
831
|
+
const bodyBefore = readFileSync(drainFile(s.stateDir, "sess-1"), "utf8");
|
|
832
|
+
|
|
833
|
+
// The owner hands its entry in. Whatever kills the process after this
|
|
834
|
+
// call resolves must not lose the handed-over turns: the inbox record is
|
|
835
|
+
// already durable and the claim body is untouched.
|
|
836
|
+
await s.claims.mergeIntoClaim("sess-1", entry([turn("a"), turn("b", "2026-11-01T00:01:00.000Z")]));
|
|
837
|
+
|
|
838
|
+
assert.equal(readFileSync(drainFile(s.stateDir, "sess-1"), "utf8"), bodyBefore, "mergeIntoClaim must not rewrite the claim body");
|
|
839
|
+
// Fresh instance = the next process after the kill: it folds the inbox.
|
|
840
|
+
const revived = new BufferDrainClaims(s.stateDir, plainIo());
|
|
841
|
+
const claim = await revived.load("sess-1");
|
|
842
|
+
assert.deepEqual(claim?.entry.turns.map((t) => t.content), ["a", "b"], "a handed-over record survives a crash before compaction");
|
|
843
|
+
} finally {
|
|
844
|
+
s.cleanup();
|
|
845
|
+
}
|
|
846
|
+
});
|
|
847
|
+
|
|
848
|
+
test("a kill between the body write and the inbox delete replays nothing", async () => {
|
|
849
|
+
const s = setup();
|
|
850
|
+
try {
|
|
851
|
+
s.seedSession("sess-1", entry([turn("t1")]));
|
|
852
|
+
await s.claims.claimFromSessionFile("sess-1", makeLease());
|
|
853
|
+
await s.claims.mergeIntoClaim("sess-1", entry([turn("t2", "2026-11-01T00:02:00.000Z")]));
|
|
854
|
+
const loaded = await s.claims.load("sess-1");
|
|
855
|
+
assert.ok(loaded);
|
|
856
|
+
assert.deepEqual(loaded.entry.turns.map((t) => t.content), ["t1", "t2"]);
|
|
857
|
+
|
|
858
|
+
// The drainer retains t2 for retry (moves it to retainedTurns) and is
|
|
859
|
+
// killed right after the body write lands, before the inbox is deleted.
|
|
860
|
+
// A replay by identity count alone would re-add t2 to `turns` (its count
|
|
861
|
+
// there is now zero); only an exact consumed-record marker prevents that.
|
|
862
|
+
const base = plainIo();
|
|
863
|
+
const crashingIo = {
|
|
864
|
+
...base,
|
|
865
|
+
write: async (filePath: string, content: string) => {
|
|
866
|
+
await base.write(filePath, content);
|
|
867
|
+
if (filePath === drainFile(s.stateDir, "sess-1")) throw new Error("simulated kill after the body write");
|
|
868
|
+
},
|
|
869
|
+
};
|
|
870
|
+
const crashing = new BufferDrainClaims(s.stateDir, crashingIo);
|
|
871
|
+
await assert.rejects(
|
|
872
|
+
crashing.writeEntry("sess-1", "worker-1", { ...entry([turn("t1")]), retainedTurns: [turn("t2", "2026-11-01T00:02:00.000Z")] }, loaded.entry),
|
|
873
|
+
/simulated kill/,
|
|
874
|
+
);
|
|
875
|
+
assert.equal(existsSync(inboxFile(s.stateDir, "sess-1")), true, "fixture: the kill left the inbox behind");
|
|
876
|
+
|
|
877
|
+
const revived = new BufferDrainClaims(s.stateDir, plainIo());
|
|
878
|
+
const claim = await revived.load("sess-1");
|
|
879
|
+
assert.deepEqual(claim?.entry.turns.map((t) => t.content), ["t1"], "the replayed record adds nothing to turns");
|
|
880
|
+
assert.deepEqual(claim?.entry.retainedTurns?.map((t) => t.content), ["t2"], "t2 stays only in retainedTurns");
|
|
881
|
+
assert.equal((await revived.load("sess-1"))?.entry.turns.length, 1, "repeated folds add nothing");
|
|
882
|
+
} finally {
|
|
883
|
+
s.cleanup();
|
|
884
|
+
}
|
|
885
|
+
});
|
|
886
|
+
|
|
887
|
+
test("a drained turn handed in by a stale owner never reappears through the inbox fold", async () => {
|
|
888
|
+
const s = setup();
|
|
889
|
+
try {
|
|
890
|
+
s.seedSession("sess-1", entry([turn("t1")]));
|
|
891
|
+
await s.claims.claimFromSessionFile("sess-1", makeLease());
|
|
892
|
+
const loaded = await s.claims.load("sess-1");
|
|
893
|
+
assert.ok(loaded);
|
|
894
|
+
await s.claims.writeEntry("sess-1", "worker-1", entry([]), loaded.entry);
|
|
895
|
+
|
|
896
|
+
// The owner's in-memory entry still holds t1: handing it in must not
|
|
897
|
+
// resurrect the extracted turn, through the body or the inbox.
|
|
898
|
+
await s.claims.mergeIntoClaim("sess-1", entry([turn("t1")]));
|
|
899
|
+
|
|
900
|
+
const claim = await s.claims.load("sess-1");
|
|
901
|
+
assert.deepEqual(claim?.entry.turns, [], "the stale copy stays subtracted after the fold");
|
|
902
|
+
const revived = new BufferDrainClaims(s.stateDir, plainIo());
|
|
903
|
+
assert.deepEqual((await revived.load("sess-1"))?.entry.turns, [], "a fresh process folds to the same verdict");
|
|
904
|
+
} finally {
|
|
905
|
+
s.cleanup();
|
|
906
|
+
}
|
|
907
|
+
});
|
|
908
|
+
|
|
909
|
+
test("concurrent appends and a progress write lose nothing and duplicate nothing, and a drainer that lost its lease cannot consume segments", async () => {
|
|
910
|
+
const s = setup();
|
|
911
|
+
try {
|
|
912
|
+
s.seedSession("sess-1", entry([turn("t1")]));
|
|
913
|
+
await s.claims.claimFromSessionFile("sess-1", makeLease());
|
|
914
|
+
const loaded = await s.claims.load("sess-1");
|
|
915
|
+
assert.ok(loaded);
|
|
916
|
+
|
|
917
|
+
// Two live owners hand entries in while the drainer writes progress:
|
|
918
|
+
// all three serialize on the claim lock and converge without loss.
|
|
919
|
+
await Promise.all([
|
|
920
|
+
s.claims.mergeIntoClaim("sess-1", entry([turn("t2", "2026-11-01T00:02:00.000Z")])),
|
|
921
|
+
s.claims.mergeIntoClaim("sess-1", entry([turn("t3", "2026-11-01T00:03:00.000Z")])),
|
|
922
|
+
s.claims.writeEntry("sess-1", "worker-1", entry([]), loaded.entry),
|
|
923
|
+
]);
|
|
924
|
+
// One more hand-in after the interleaving: whatever order the lock chose,
|
|
925
|
+
// the inbox now holds an unconsumed record the fenced drainer must not eat.
|
|
926
|
+
await s.claims.mergeIntoClaim("sess-1", entry([turn("t4", "2026-11-01T00:04:00.000Z")]));
|
|
927
|
+
const claim = await s.claims.load("sess-1");
|
|
928
|
+
assert.ok(claim);
|
|
929
|
+
assert.deepEqual(claim.entry.turns.map((t) => t.content).sort(), ["t2", "t3", "t4"], "handed-in turns all survive the interleaving");
|
|
930
|
+
assert.deepEqual(claim.drained?.map((t) => t.content), ["t1"], "the extracted turn stays tombstoned");
|
|
931
|
+
|
|
932
|
+
// A drainer that lost its lease is fenced before it can compact away the
|
|
933
|
+
// merged records.
|
|
934
|
+
await assert.rejects(
|
|
935
|
+
s.claims.writeEntry("sess-1", "intruder", entry([]), claim.entry),
|
|
936
|
+
/no longer owned by intruder/,
|
|
937
|
+
);
|
|
938
|
+
assert.equal(existsSync(inboxFile(s.stateDir, "sess-1")), true, "the fenced drainer consumed no segments");
|
|
939
|
+
const after = await s.claims.load("sess-1");
|
|
940
|
+
assert.ok(after);
|
|
941
|
+
assert.equal(after.entry.turns.length, 3, "the fenced write changed nothing");
|
|
942
|
+
|
|
943
|
+
// The real owner's next progress write compacts: body holds everything,
|
|
944
|
+
// inbox consumed.
|
|
945
|
+
await s.claims.writeEntry("sess-1", "worker-1", entry([]), after.entry);
|
|
946
|
+
assert.equal(existsSync(inboxFile(s.stateDir, "sess-1")), false, "compaction consumes the inbox after the body write");
|
|
947
|
+
const final = await s.claims.load("sess-1");
|
|
948
|
+
assert.deepEqual(final?.entry.turns, [], "all pending turns are drained");
|
|
949
|
+
assert.deepEqual(final?.drained?.map((t) => t.content).sort(), ["t1", "t2", "t3", "t4"]);
|
|
950
|
+
} finally {
|
|
951
|
+
s.cleanup();
|
|
952
|
+
}
|
|
953
|
+
});
|
|
954
|
+
|
|
955
|
+
test("mergeIntoClaim on a large claim never reads or writes the body", async () => {
|
|
956
|
+
const s = setup();
|
|
957
|
+
try {
|
|
958
|
+
const filler = "x".repeat(750_000);
|
|
959
|
+
const stamps = (i: number): string => `2026-11-01T00:${String(Math.floor(i / 60)).padStart(2, "0")}:${String(i % 60).padStart(2, "0")}.000Z`;
|
|
960
|
+
const bigEntry = entry(Array.from({ length: 40 }, (_, i) => turn(`${filler}-${i}`, stamps(i))));
|
|
961
|
+
s.seedSession("sess-1", bigEntry);
|
|
962
|
+
await s.claims.claimFromSessionFile("sess-1", makeLease());
|
|
963
|
+
const bodyPath = drainFile(s.stateDir, "sess-1");
|
|
964
|
+
assert.ok(readFileSync(bodyPath, "utf8").length > 30_000_000, "fixture: the claim body is ~30 MB");
|
|
965
|
+
|
|
966
|
+
const spied = spyIo();
|
|
967
|
+
const claims = new BufferDrainClaims(s.stateDir, spied.io);
|
|
968
|
+
await claims.mergeIntoClaim("sess-1", entry([turn("new", "2026-11-01T01:00:00.000Z")]));
|
|
969
|
+
|
|
970
|
+
assert.equal(spied.readPaths.filter((p) => p === bodyPath).length, 0, "the body is never read");
|
|
971
|
+
assert.equal(spied.writePaths.filter((p) => p === bodyPath).length, 0, "the body is never written");
|
|
972
|
+
assert.deepEqual(spied.appendPaths, [`${bodyPath}.inbox.jsonl`], "exactly one inbox append");
|
|
973
|
+
} finally {
|
|
974
|
+
s.cleanup();
|
|
975
|
+
}
|
|
976
|
+
});
|
|
977
|
+
|
|
978
|
+
test("a torn last inbox record is skipped and does not corrupt following records", async () => {
|
|
979
|
+
const s = setup();
|
|
980
|
+
try {
|
|
981
|
+
s.seedSession("sess-1", entry([turn("a")]));
|
|
982
|
+
await s.claims.claimFromSessionFile("sess-1", makeLease());
|
|
983
|
+
mkdirSync(path.dirname(inboxFile(s.stateDir, "sess-1")), { recursive: true });
|
|
984
|
+
// A valid record, then a torn tail (a kill mid-append, no trailing
|
|
985
|
+
// newline) — and then a record appended after the garbage by the next
|
|
986
|
+
// owner, fused onto the torn bytes by append semantics.
|
|
987
|
+
writeFileSync(
|
|
988
|
+
inboxFile(s.stateDir, "sess-1"),
|
|
989
|
+
`${record("sess-1", entry([turn("b", "2026-11-01T00:01:00.000Z")]))}\n{"turns":[{"rol` +
|
|
990
|
+
record("sess-1", entry([turn("c", "2026-11-01T00:02:00.000Z")])),
|
|
991
|
+
);
|
|
992
|
+
|
|
993
|
+
const claim = await s.claims.load("sess-1");
|
|
994
|
+
assert.deepEqual(claim?.entry.turns.map((t) => t.content), ["a", "b", "c"], "the torn tail is skipped; records around it survive");
|
|
995
|
+
|
|
996
|
+
// A fresh append after the garbage lands cleanly and folds in order.
|
|
997
|
+
await s.claims.mergeIntoClaim("sess-1", entry([turn("d", "2026-11-01T00:03:00.000Z")]));
|
|
998
|
+
const after = await s.claims.load("sess-1");
|
|
999
|
+
assert.deepEqual(after?.entry.turns.map((t) => t.content), ["a", "b", "c", "d"]);
|
|
1000
|
+
} finally {
|
|
1001
|
+
s.cleanup();
|
|
1002
|
+
}
|
|
1003
|
+
});
|
|
1004
|
+
|
|
1005
|
+
test("foldAllInboxes folds every inbox into claim bodies the old format reads", async () => {
|
|
1006
|
+
const s = setup();
|
|
1007
|
+
try {
|
|
1008
|
+
s.seedSession("sess-1", entry([turn("a")]));
|
|
1009
|
+
await s.claims.claimFromSessionFile("sess-1", makeLease());
|
|
1010
|
+
await s.claims.mergeIntoClaim("sess-1", entry([turn("b", "2026-11-01T00:01:00.000Z")]));
|
|
1011
|
+
|
|
1012
|
+
assert.equal(await s.claims.foldAllInboxes(), 1, "one claim folded");
|
|
1013
|
+
assert.equal(existsSync(inboxFile(s.stateDir, "sess-1")), false, "the inbox is consumed");
|
|
1014
|
+
|
|
1015
|
+
// Old-format reader: the plain body file holds every pending turn.
|
|
1016
|
+
const oldView = z
|
|
1017
|
+
.object({ entry: z.object({ turns: z.array(z.object({ content: z.string() })) }) })
|
|
1018
|
+
.parse(JSON.parse(readFileSync(drainFile(s.stateDir, "sess-1"), "utf8")));
|
|
1019
|
+
assert.deepEqual(oldView.entry.turns.map((t) => t.content).sort(), ["a", "b"], "the pre-inbox build sees the folded turns");
|
|
1020
|
+
|
|
1021
|
+
assert.equal(await s.claims.foldAllInboxes(), 0, "a second pass finds nothing to fold");
|
|
1022
|
+
const claim = await s.claims.load("sess-1");
|
|
1023
|
+
assert.deepEqual(claim?.entry.turns.map((t) => t.content), ["a", "b"]);
|
|
1024
|
+
} finally {
|
|
1025
|
+
s.cleanup();
|
|
1026
|
+
}
|
|
1027
|
+
});
|
|
1028
|
+
|
|
1029
|
+
test("listings count pending turns from the sidecar and inbox without parsing claim bodies", async () => {
|
|
1030
|
+
const s = setup();
|
|
1031
|
+
try {
|
|
1032
|
+
s.seedSession("sess-1", entry([turn("a")]));
|
|
1033
|
+
await s.claims.claimFromSessionFile("sess-1", makeLease());
|
|
1034
|
+
await s.claims.mergeIntoClaim("sess-1", entry([turn("b", "2026-11-01T00:01:00.000Z")]));
|
|
1035
|
+
const bodyPath = drainFile(s.stateDir, "sess-1");
|
|
1036
|
+
|
|
1037
|
+
const spied = spyIo();
|
|
1038
|
+
const claims = new BufferDrainClaims(s.stateDir, spied.io);
|
|
1039
|
+
const page = await claims.listPage(undefined, 10);
|
|
1040
|
+
assert.deepEqual(page.entries.map((e) => e.turnCount), [2], "the listing counts body and inbox turns");
|
|
1041
|
+
assert.equal(spied.readPaths.filter((p) => p === bodyPath).length, 0, "the body is never parsed for a count");
|
|
1042
|
+
|
|
1043
|
+
const listed = await claims.list();
|
|
1044
|
+
assert.deepEqual(listed.map((e) => e.turnCount), [2]);
|
|
1045
|
+
|
|
1046
|
+
// Fallback: a missing sidecar (pre-inbox claim) parses the body once and
|
|
1047
|
+
// still counts the inbox.
|
|
1048
|
+
rmSync(`${bodyPath}.count`, { force: true });
|
|
1049
|
+
const fallback = await claims.listPage(undefined, 10);
|
|
1050
|
+
assert.deepEqual(fallback.entries.map((e) => e.turnCount), [2], "the body-parse fallback stays exact");
|
|
1051
|
+
} finally {
|
|
1052
|
+
s.cleanup();
|
|
1053
|
+
}
|
|
1054
|
+
});
|
|
1055
|
+
|
|
1056
|
+
test("release drops the inbox with the claim and a later claim never inherits it", async () => {
|
|
1057
|
+
const s = setup();
|
|
1058
|
+
try {
|
|
1059
|
+
s.seedSession("sess-1", entry([turn("t1")]));
|
|
1060
|
+
await s.claims.claimFromSessionFile("sess-1", makeLease());
|
|
1061
|
+
await s.claims.mergeIntoClaim("sess-1", entry([turn("t2", "2026-11-01T00:02:00.000Z")]));
|
|
1062
|
+
await s.claims.release("sess-1");
|
|
1063
|
+
assert.equal(existsSync(inboxFile(s.stateDir, "sess-1")), false, "release leaves no orphan inbox");
|
|
1064
|
+
|
|
1065
|
+
// A crash can still leave an orphan inbox next to no body; a fresh claim
|
|
1066
|
+
// for the same key must not replay it.
|
|
1067
|
+
mkdirSync(path.dirname(inboxFile(s.stateDir, "sess-1")), { recursive: true });
|
|
1068
|
+
writeFileSync(inboxFile(s.stateDir, "sess-1"), record("sess-1", entry([turn("stale", "2026-11-01T00:09:00.000Z")])));
|
|
1069
|
+
s.seedSession("sess-1", entry([turn("fresh", "2026-11-01T00:10:00.000Z")]));
|
|
1070
|
+
assert.equal(await s.claims.claimFromSessionFile("sess-1", makeLease()), "claimed");
|
|
1071
|
+
assert.deepEqual((await s.claims.load("sess-1"))?.entry.turns.map((t) => t.content), ["fresh"], "the orphan record is not replayed into the new claim");
|
|
1072
|
+
} finally {
|
|
1073
|
+
s.cleanup();
|
|
1074
|
+
}
|
|
1075
|
+
});
|
|
1076
|
+
|
|
1077
|
+
/** Forge the cached counts a same-size, same-mtime rewrite could leave behind. */
|
|
1078
|
+
function forgeSummary(stateDir: string, key: string, counts: { pending: number; drained: number }): void {
|
|
1079
|
+
const bodyPath = drainFile(stateDir, key);
|
|
1080
|
+
const fixed = new Date("2026-11-01T00:00:00.000Z");
|
|
1081
|
+
utimesSync(bodyPath, fixed, fixed);
|
|
1082
|
+
const { size, mtimeMs } = statSync(bodyPath);
|
|
1083
|
+
writeFileSync(`${bodyPath}.count`, JSON.stringify({ key, size, mtimeMs, ...counts }));
|
|
1084
|
+
}
|
|
1085
|
+
|
|
1086
|
+
test("a stale count summary can never make a claim with pending turns read as drained", async () => {
|
|
1087
|
+
const s = setup();
|
|
1088
|
+
try {
|
|
1089
|
+
s.seedSession("sess-1", entry([turn("t1")]));
|
|
1090
|
+
await s.claims.claimFromSessionFile("sess-1", makeLease());
|
|
1091
|
+
await s.claims.releaseLease("sess-1", "worker-1");
|
|
1092
|
+
// The body holds one pending turn and nothing drained; the forged cache
|
|
1093
|
+
// claims the opposite under the body's exact size and mtime.
|
|
1094
|
+
forgeSummary(s.stateDir, "sess-1", { pending: 0, drained: 1 });
|
|
1095
|
+
|
|
1096
|
+
assert.equal(await s.claims.isDrained("sess-1"), false, "the cache alone never answers drained");
|
|
1097
|
+
assert.deepEqual((await s.claims.load("sess-1"))?.entry.turns.map((t) => t.content), ["t1"], "the pending turn is intact");
|
|
1098
|
+
} finally {
|
|
1099
|
+
s.cleanup();
|
|
1100
|
+
}
|
|
1101
|
+
});
|
|
1102
|
+
|
|
1103
|
+
test("save hands over, never releases, when a stale summary claims a pending claim is drained", async () => {
|
|
1104
|
+
const s = setup();
|
|
1105
|
+
try {
|
|
1106
|
+
const files = new BufferSessionFiles(s.stateDir, plainIo());
|
|
1107
|
+
s.seedSession("sess-1", entry([turn("t1")]));
|
|
1108
|
+
await files.claims.claimFromSessionFile("sess-1", makeLease());
|
|
1109
|
+
await files.claims.releaseLease("sess-1", "worker-1");
|
|
1110
|
+
forgeSummary(s.stateDir, "sess-1", { pending: 0, drained: 1 });
|
|
1111
|
+
|
|
1112
|
+
const outcome = await files.save("sess-1", entry([turn("t2", "2026-11-01T00:02:00.000Z")]));
|
|
1113
|
+
|
|
1114
|
+
assert.equal(outcome.status, "handed_over", "the owner hands in instead of consuming a tombstone");
|
|
1115
|
+
assert.equal(files.claims.isClaimed("sess-1"), true, "the claim is not released");
|
|
1116
|
+
assert.equal(existsSync(s.sessionFile("sess-1")), false, "no session file replaced the claim");
|
|
1117
|
+
assert.deepEqual((await files.claims.load("sess-1"))?.entry.turns.map((t) => t.content), ["t1", "t2"], "no turn was lost");
|
|
1118
|
+
} finally {
|
|
1119
|
+
s.cleanup();
|
|
1120
|
+
}
|
|
1121
|
+
});
|
|
1122
|
+
|
|
1123
|
+
test("a pending claim is judged not drained without parsing its body", async () => {
|
|
1124
|
+
const s = setup();
|
|
1125
|
+
try {
|
|
1126
|
+
s.seedSession("sess-1", entry([turn("t1")]));
|
|
1127
|
+
await s.claims.claimFromSessionFile("sess-1", makeLease());
|
|
1128
|
+
await s.claims.releaseLease("sess-1", "worker-1");
|
|
1129
|
+
const spied = spyIo();
|
|
1130
|
+
const claims = new BufferDrainClaims(s.stateDir, spied.io);
|
|
1131
|
+
|
|
1132
|
+
assert.equal(await claims.isDrained("sess-1"), false);
|
|
1133
|
+
assert.equal(spied.readPaths.filter((p) => p === drainFile(s.stateDir, "sess-1")).length, 0, "the hot path never reads the body");
|
|
1134
|
+
} finally {
|
|
1135
|
+
s.cleanup();
|
|
1136
|
+
}
|
|
1137
|
+
});
|
|
1138
|
+
|
|
1139
|
+
test("a finished drain is verified with one real read before it reads as drained", async () => {
|
|
1140
|
+
const s = setup();
|
|
1141
|
+
try {
|
|
1142
|
+
s.seedSession("sess-1", entry([turn("t1")]));
|
|
1143
|
+
await s.claims.claimFromSessionFile("sess-1", makeLease());
|
|
1144
|
+
const loaded = await s.claims.load("sess-1");
|
|
1145
|
+
assert.ok(loaded);
|
|
1146
|
+
await s.claims.writeEntry("sess-1", "worker-1", entry([]), loaded.entry);
|
|
1147
|
+
await s.claims.releaseLease("sess-1", "worker-1");
|
|
1148
|
+
const spied = spyIo();
|
|
1149
|
+
const claims = new BufferDrainClaims(s.stateDir, spied.io);
|
|
1150
|
+
|
|
1151
|
+
assert.equal(await claims.isDrained("sess-1"), true);
|
|
1152
|
+
assert.equal(spied.readPaths.filter((p) => p === drainFile(s.stateDir, "sess-1")).length, 1, "the TRUE answer parses the body once");
|
|
1153
|
+
} finally {
|
|
1154
|
+
s.cleanup();
|
|
1155
|
+
}
|
|
1156
|
+
});
|