@remnic/core 10.0.85 → 10.0.86
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 +72 -71
- package/dist/access-authorization-probe.js +73 -72
- package/dist/access-boundary.d.ts.map +1 -1
- package/dist/access-boundary.js +72 -71
- package/dist/access-cli.js +157 -155
- package/dist/access-cli.js.map +1 -1
- package/dist/access-errors.d.ts +14 -0
- package/dist/access-errors.d.ts.map +1 -1
- package/dist/access-errors.js +5 -1
- package/dist/access-extraction-force-flush.js +72 -71
- package/dist/access-http-briefing.js +73 -72
- package/dist/access-http-query.js +74 -73
- package/dist/access-http.d.ts +12 -0
- package/dist/access-http.d.ts.map +1 -1
- package/dist/access-http.js +95 -94
- package/dist/access-identity-continuity-surface.js +72 -71
- package/dist/access-lcm-surface.js +72 -71
- package/dist/access-mcp.d.ts.map +1 -1
- package/dist/access-mcp.js +82 -81
- package/dist/access-memory-search-fanout.js +2 -2
- package/dist/access-namespace-preflight.js +72 -71
- package/dist/access-observe-write-category.js +10 -9
- package/dist/access-observe-write-surface.js +72 -71
- package/dist/access-offline-manifest.js +8 -8
- package/dist/access-operations-batch.js +73 -72
- package/dist/access-operations.js +80 -79
- package/dist/access-recall-concurrency.js +72 -71
- package/dist/access-recall-diagnostics.js +2 -2
- package/dist/access-recall-response.js +72 -71
- package/dist/access-recall-surface.js +72 -71
- package/dist/access-schema.d.ts.map +1 -1
- package/dist/access-schema.js +9 -8
- package/dist/access-service.d.ts +3 -3
- package/dist/access-service.d.ts.map +1 -1
- package/dist/access-service.js +72 -71
- package/dist/access-surface-catalog.js +74 -73
- package/dist/access-surface-catalog.js.map +1 -1
- package/dist/access-token-capabilities.js +2 -2
- package/dist/active-recall.js +32 -32
- package/dist/adapters/index.js +7 -7
- package/dist/adapters/registry.js +3 -3
- package/dist/briefing-deadline.d.ts +2 -2
- package/dist/briefing-deadline.d.ts.map +1 -1
- package/dist/briefing-deadline.js +1 -1
- package/dist/briefing.js +23 -23
- package/dist/buffer-admission.d.ts +22 -0
- package/dist/buffer-admission.d.ts.map +1 -0
- package/dist/buffer-admission.js +13 -0
- package/dist/buffer-eviction.d.ts +4 -5
- package/dist/buffer-eviction.d.ts.map +1 -1
- package/dist/buffer-eviction.js +1 -1
- package/dist/buffer-turn-helpers.d.ts +7 -0
- package/dist/buffer-turn-helpers.d.ts.map +1 -1
- package/dist/buffer-turn-helpers.js +9 -1
- package/dist/buffer-types.d.ts +24 -1
- package/dist/buffer-types.d.ts.map +1 -1
- package/dist/buffer.d.ts +17 -18
- package/dist/buffer.d.ts.map +1 -1
- package/dist/buffer.js +12 -8
- package/dist/calibration.js +2 -2
- package/dist/{capsule-crypto-GWVG7LGC.js → capsule-crypto-7FJQINUR.js} +2 -2
- package/dist/capsule-crypto-7FJQINUR.js.map +1 -0
- package/dist/capsule-import-gate.d.ts +11 -0
- package/dist/capsule-import-gate.d.ts.map +1 -0
- package/dist/capsule-import-gate.js +12 -0
- package/dist/capsule-import-gate.js.map +1 -0
- package/dist/causal-behavior.js +2 -2
- package/dist/causal-chain.js +2 -2
- package/dist/causal-consolidation.js +25 -25
- package/dist/causal-retrieval.js +2 -2
- package/dist/causal-trajectory.js +1 -1
- package/dist/{chunk-73YVMZ7D.js → chunk-2G4TV2ML.js} +11 -11
- package/dist/{chunk-BMIM6ESC.js → chunk-2JG2NI7C.js} +38 -38
- package/dist/{chunk-IR4C3DP2.js → chunk-2K3AUDH7.js} +2 -2
- package/dist/{chunk-6KFFRMPQ.js → chunk-2N4OUSIA.js} +2 -2
- package/dist/{chunk-OWHERGF2.js → chunk-2NLLXCJG.js} +2 -2
- package/dist/{chunk-44FQCC5D.js → chunk-2NUDHVYM.js} +2 -2
- package/dist/{chunk-FK4WC6VK.js → chunk-2U4LMSEX.js} +3 -3
- package/dist/{chunk-LT3CMO65.js → chunk-2XOFVTTT.js} +2 -2
- package/dist/{chunk-I6ORDT54.js → chunk-32CA7B6L.js} +2 -2
- package/dist/{chunk-TQVECGUQ.js → chunk-32YX7EJ3.js} +3 -3
- package/dist/{chunk-H4D5YURJ.js → chunk-3CTKEXXF.js} +120 -118
- package/dist/{chunk-H4D5YURJ.js.map → chunk-3CTKEXXF.js.map} +1 -1
- package/dist/{chunk-HLWXCDUQ.js → chunk-3DAWHP5K.js} +5 -5
- package/dist/{chunk-GBOYOL5L.js → chunk-3PJEDHLD.js} +2 -2
- package/dist/{chunk-ZWTHVUT5.js → chunk-3UZ2VMKD.js} +2 -2
- package/dist/{chunk-2XYDL2FG.js → chunk-3Y5X6YFH.js} +2 -2
- package/dist/{chunk-23B4HLWG.js → chunk-4GMI5HFH.js} +9 -9
- package/dist/{chunk-AP3TSV6S.js → chunk-4LCVA2LW.js} +3 -3
- package/dist/{chunk-MZCGHPIJ.js → chunk-4X7HQKAG.js} +2 -2
- package/dist/{chunk-YNBMBL4C.js → chunk-4XYAWK5H.js} +2 -2
- package/dist/{chunk-2JAT4S7N.js → chunk-5SOSQTVE.js} +2 -2
- package/dist/{chunk-G3T6YMBO.js → chunk-64S6X4IU.js} +7 -7
- package/dist/{chunk-NIV7RPWQ.js → chunk-6BCWFCDG.js} +7 -7
- package/dist/{chunk-V27FK6D6.js → chunk-6XTUZKCZ.js} +2 -2
- package/dist/{chunk-KASJJGRG.js → chunk-6ZELOK6O.js} +3 -8
- package/dist/chunk-6ZELOK6O.js.map +1 -0
- package/dist/{chunk-2ASEDFOA.js → chunk-73UZZFJC.js} +3 -3
- package/dist/{chunk-UHPHH74F.js → chunk-77PDJ4OE.js} +3 -3
- package/dist/{chunk-BATPQNRG.js → chunk-7GSL4LBG.js} +2 -2
- package/dist/{chunk-B6SPPYGJ.js → chunk-7IWRLYFJ.js} +2 -2
- package/dist/{chunk-BUKK5SWA.js → chunk-7LCGFUMB.js} +4 -4
- package/dist/{chunk-ZXF3O7P4.js → chunk-ARRTLPRC.js} +2 -2
- package/dist/{chunk-S5CQI2KF.js → chunk-B3GKQ6DU.js} +2 -2
- package/dist/{chunk-2URKYLMK.js → chunk-B56LORLY.js} +2 -2
- package/dist/{chunk-4IWEPQEV.js → chunk-BIVKPQ6Q.js} +2 -2
- package/dist/{chunk-DM4RRTN7.js → chunk-BLSHFIR6.js} +7 -6
- package/dist/chunk-BLSHFIR6.js.map +1 -0
- package/dist/{chunk-4DPLCYEK.js → chunk-C2HORX76.js} +3 -3
- package/dist/{chunk-ELWI2R6H.js → chunk-CC4V7HNP.js} +22 -22
- package/dist/{chunk-LGEH473S.js → chunk-CJCTKL6Y.js} +9 -1
- package/dist/chunk-CJCTKL6Y.js.map +1 -0
- package/dist/{chunk-WFDWLWDK.js → chunk-CN7Q3T2J.js} +4 -4
- package/dist/{chunk-GJVGFKOU.js → chunk-CYCEROPR.js} +3 -3
- package/dist/{chunk-CCU7URRP.js → chunk-D5EV6Y2L.js} +3 -3
- package/dist/{chunk-N6II66E5.js → chunk-DT7SNJH6.js} +3 -3
- package/dist/{chunk-FRM4UTDR.js → chunk-DZ7DPDVW.js} +8 -6
- package/dist/chunk-DZ7DPDVW.js.map +1 -0
- package/dist/{chunk-MVOJDLXS.js → chunk-EA2LA5RI.js} +2 -2
- package/dist/{chunk-WBN66HPI.js → chunk-ELHJK7LM.js} +6 -6
- package/dist/{chunk-GKYIRTNX.js → chunk-EQ4U73UH.js} +45 -14
- package/dist/chunk-EQ4U73UH.js.map +1 -0
- package/dist/{chunk-ONN2E5QD.js → chunk-EUBDYOLD.js} +6 -6
- package/dist/{chunk-YNDLCWXS.js → chunk-EZ25VE3G.js} +4 -4
- package/dist/{chunk-3LHPTBWG.js → chunk-F74G4KIH.js} +2 -2
- package/dist/{chunk-X7REO4CH.js → chunk-FF3EFLUT.js} +7 -5
- package/dist/chunk-FF3EFLUT.js.map +1 -0
- package/dist/chunk-FKBGK3NC.js +62 -0
- package/dist/chunk-FKBGK3NC.js.map +1 -0
- package/dist/{chunk-X2KO4FK5.js → chunk-FNFURZBZ.js} +5 -5
- package/dist/chunk-FNFURZBZ.js.map +1 -0
- package/dist/{chunk-MYCCO6AA.js → chunk-G54BOKZ6.js} +2 -2
- package/dist/{chunk-JFH6JAGY.js → chunk-G67EUQAB.js} +5 -5
- package/dist/{chunk-TV65NR7L.js → chunk-GULUWOA5.js} +2 -2
- package/dist/{chunk-6DANGCMO.js → chunk-HFUKXOUW.js} +2 -2
- package/dist/{chunk-Y3IYKNBY.js → chunk-HLN2HOXX.js} +2 -2
- package/dist/{chunk-Y43X3E24.js → chunk-IBDWYRDR.js} +3 -3
- package/dist/{chunk-XSFQN7TP.js → chunk-IBGU6AN6.js} +15 -14
- package/dist/chunk-IBGU6AN6.js.map +1 -0
- package/dist/{chunk-AJG2TAMR.js → chunk-IEEV27BC.js} +2 -2
- package/dist/{chunk-FZPDRCOB.js → chunk-IJ3OEECW.js} +4 -4
- package/dist/{chunk-D6THW7PA.js → chunk-IPD5GC7C.js} +2 -2
- package/dist/{chunk-VCVEMQJK.js → chunk-JM2YZKCQ.js} +2 -2
- package/dist/{chunk-H34INI3P.js → chunk-KVSOEVAF.js} +34 -11
- package/dist/chunk-KVSOEVAF.js.map +1 -0
- package/dist/chunk-M3BEJJNV.js +31 -0
- package/dist/chunk-M3BEJJNV.js.map +1 -0
- package/dist/{chunk-OSTFDWZJ.js → chunk-NIVKG3DS.js} +4 -4
- package/dist/{chunk-VZELUMKK.js → chunk-NQP4ZYGG.js} +2 -2
- package/dist/{chunk-3ODZLCHC.js → chunk-NT7F7WJQ.js} +2 -2
- package/dist/{chunk-YO2JEX2Z.js → chunk-OGBQMY46.js} +2 -2
- package/dist/{chunk-O55AGDTI.js → chunk-OL553AWT.js} +10 -10
- package/dist/{chunk-CLVILSBR.js → chunk-OZNBZJ3S.js} +22 -22
- package/dist/{chunk-RNGDKFY2.js → chunk-P452SEKO.js} +2 -2
- package/dist/{chunk-JX3E4XCW.js → chunk-PDOOUXCS.js} +2 -2
- package/dist/{chunk-V45TEVDA.js → chunk-PKZ274PI.js} +3 -3
- package/dist/{chunk-MNYQVYJ4.js → chunk-QD5R6U5J.js} +2 -2
- package/dist/{chunk-DI5ET2IE.js → chunk-QEZ3DW5O.js} +9 -9
- package/dist/{chunk-WYPCJK6K.js → chunk-QKERFEJS.js} +2 -2
- package/dist/{chunk-BLMTOIBK.js → chunk-QKES7GU6.js} +2 -2
- package/dist/{chunk-PCQWKMOL.js → chunk-QNSZYTSG.js} +2 -2
- package/dist/{chunk-433EXTC4.js → chunk-ROGO7FRA.js} +5 -4
- package/dist/{chunk-433EXTC4.js.map → chunk-ROGO7FRA.js.map} +1 -1
- package/dist/{chunk-MHSBOEH7.js → chunk-ROKDHO74.js} +2 -2
- package/dist/{chunk-BLGQUZFB.js → chunk-RWGRSKN5.js} +7 -5
- package/dist/chunk-RWGRSKN5.js.map +1 -0
- package/dist/{chunk-3VU2FDTU.js → chunk-SYKJBS7S.js} +10 -10
- package/dist/{chunk-5BE3FLKL.js → chunk-TAUHAP5J.js} +4 -4
- package/dist/{chunk-QCP75JGO.js → chunk-TFA6HPOA.js} +5 -5
- package/dist/{chunk-SGHV6LZ7.js → chunk-TQ4SCPGQ.js} +67 -51
- package/dist/chunk-TQ4SCPGQ.js.map +1 -0
- package/dist/{chunk-E7V5AK7I.js → chunk-TRMZHGIX.js} +8 -8
- package/dist/{chunk-WBDIZWJQ.js → chunk-TSS3D7ZY.js} +2 -2
- package/dist/{chunk-HVFX2N2G.js → chunk-UAUV3BWS.js} +65 -83
- package/dist/chunk-UAUV3BWS.js.map +1 -0
- package/dist/{chunk-OMYRPMQC.js → chunk-US7KPQA5.js} +2 -2
- package/dist/{chunk-KHZVAISH.js → chunk-UWRHHRDK.js} +2 -2
- package/dist/{chunk-WLNWFHX3.js → chunk-UXXK523X.js} +40 -2
- package/dist/{chunk-WLNWFHX3.js.map → chunk-UXXK523X.js.map} +1 -1
- package/dist/{chunk-SDESKFKT.js → chunk-UYHDMCY2.js} +2 -2
- package/dist/{chunk-YAAKVTZA.js → chunk-VKILERSL.js} +2 -2
- package/dist/{chunk-2TGIKMMZ.js → chunk-WXDTOE6F.js} +2 -2
- package/dist/{chunk-UVYI6VIX.js → chunk-X7Y7WX73.js} +1 -1
- package/dist/{chunk-P225W4RQ.js → chunk-XU37EJTW.js} +22 -22
- package/dist/chunk-XU37EJTW.js.map +1 -0
- package/dist/{chunk-QA3Y2O6H.js → chunk-YAVIBVCU.js} +2 -2
- package/dist/{chunk-CMRHXPLQ.js → chunk-YL4UKMD7.js} +117 -51
- package/dist/chunk-YL4UKMD7.js.map +1 -0
- package/dist/{chunk-GYNRNKMW.js → chunk-YMJRLT7S.js} +28 -8
- package/dist/chunk-YMJRLT7S.js.map +1 -0
- package/dist/{chunk-E5QEF3JO.js → chunk-YRZPBN46.js} +3 -3
- package/dist/{chunk-FMFCQRDM.js → chunk-ZANNIXXE.js} +3 -3
- package/dist/{chunk-3KGCHTTK.js → chunk-ZBFLV657.js} +3 -3
- package/dist/{chunk-VEFGHRKV.js → chunk-ZOJUWVAU.js} +1 -1
- package/dist/cli.js +117 -116
- package/dist/coding/export-okf-codegraph.js +21 -21
- package/dist/coding/pre-action-gate.js +2 -2
- package/dist/compounding/engine.js +23 -23
- package/dist/config.js +32 -32
- package/dist/connectors/codex-materialize-runner.js +22 -22
- package/dist/connectors/index.js +26 -26
- package/dist/convergence-refresh.js +24 -24
- package/dist/conversation-index/backend.js +2 -2
- package/dist/corpus-watermark.js +26 -26
- package/dist/dashboard-runtime.js +2 -2
- package/dist/deep-recall-config.js +2 -2
- package/dist/deep-recall-http-glue.js +11 -11
- package/dist/entity-retrieval.js +23 -23
- package/dist/explicit-capture.js +1 -1
- package/dist/external-wiki-access.js +73 -72
- package/dist/extraction-quote-grounding.d.ts +5 -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.d.ts +3 -1
- package/dist/extraction-source-grounding-rules.d.ts.map +1 -1
- package/dist/extraction-source-grounding-rules.js +2 -2
- package/dist/extraction-source-grounding.d.ts +1 -1
- package/dist/extraction-source-grounding.d.ts.map +1 -1
- package/dist/extraction-source-grounding.js +3 -3
- package/dist/extraction.d.ts.map +1 -1
- package/dist/extraction.js +40 -40
- package/dist/harmonic-retrieval.js +22 -22
- package/dist/index.js +196 -194
- package/dist/lcm/index.js +3 -3
- package/dist/local-llm.js +2 -2
- package/dist/location/index.js +25 -25
- package/dist/maintenance/memory-governance.js +22 -22
- package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +22 -22
- package/dist/maintenance/rebuild-memory-projection-runner.js +24 -24
- package/dist/maintenance/rebuild-memory-projection.js +23 -23
- package/dist/memory-browse-http-glue.js +2 -2
- package/dist/memory-browse-operations.js +73 -72
- package/dist/memory-browse.js +5 -5
- package/dist/namespaces/migrate.js +23 -23
- package/dist/namespaces/search.d.ts +1 -0
- package/dist/namespaces/search.d.ts.map +1 -1
- package/dist/namespaces/search.js +10 -8
- package/dist/namespaces/storage.js +22 -22
- package/dist/offline-sync-changeset-generations.js +3 -3
- package/dist/offline-sync-embedding-generation.js +2 -2
- package/dist/offline-sync-impression-drain.js +2 -2
- package/dist/offline-sync-snapshot-stream.js +6 -6
- package/dist/offline-sync.js +5 -5
- package/dist/operator-doctor-corpus.js +29 -29
- package/dist/operator-doctor-replica.js +4 -4
- package/dist/operator-toolkit.js +44 -44
- package/dist/orchestration/backlog-scheduler.js +158 -156
- package/dist/orchestration/backlog-scheduler.js.map +1 -1
- package/dist/orchestration/buffer-drain-runner.js +158 -156
- package/dist/orchestration/maintenance.js +36 -36
- package/dist/orchestration/turn-ingestion.d.ts +2 -9
- package/dist/orchestration/turn-ingestion.d.ts.map +1 -1
- package/dist/orchestrator.js +159 -157
- package/dist/qmd.js +4 -4
- package/dist/recall-navigation-http-glue.js +2 -2
- package/dist/recall-navigation-operations.js +73 -72
- package/dist/recall-navigation.js +72 -71
- package/dist/recall-state-view-admission.js +5 -5
- package/dist/recall-why-service.js +5 -5
- package/dist/replica-divergence.js +3 -3
- package/dist/resume-bundles.js +33 -33
- package/dist/search/factory.js +7 -7
- package/dist/search/index.js +11 -11
- package/dist/semantic-consolidation.js +23 -23
- package/dist/semantic-rule-promotion.js +22 -22
- package/dist/semantic-rule-verifier.js +22 -22
- package/dist/source-agent-qualifier.d.ts +2 -0
- package/dist/source-agent-qualifier.d.ts.map +1 -1
- package/dist/source-agent-qualifier.js +33 -33
- package/dist/standup/build.js +23 -23
- package/dist/storage/buffer-drain-claims.js +2 -2
- package/dist/storage/buffer-session-files.js +2 -2
- package/dist/storage.js +23 -23
- package/dist/summarizer.js +3 -3
- package/dist/support-passport/index.js +75 -74
- package/dist/tokens.js +3 -3
- package/dist/transfer/backup.js +2 -2
- package/dist/transfer/capsule-export.js +2 -2
- package/dist/transfer/capsule-import.js +2 -2
- package/dist/transfer/export-okf.js +22 -22
- package/dist/verified-recall.js +22 -22
- package/dist/who-knows-cli.js +24 -24
- package/dist/who-knows.js +23 -23
- package/package.json +2 -2
- package/src/access-boundary.ts +15 -0
- package/src/access-errors.ts +22 -0
- package/src/access-fleet-wide-work-peer.test.ts +236 -0
- package/src/access-http-peer-fleet-wide.test.ts +192 -0
- package/src/access-http.test.ts +249 -4
- package/src/access-http.ts +114 -39
- package/src/access-mcp.test.ts +64 -0
- package/src/access-mcp.ts +17 -7
- package/src/access-observe-replay-durability.test.ts +40 -4
- package/src/access-operations-batch.ts +23 -12
- package/src/access-proto-injection.test.ts +340 -0
- package/src/access-schema.ts +31 -5
- package/src/access-service-capsule-import.test.ts +235 -0
- package/src/access-service.ts +6 -45
- package/src/briefing-deadline.ts +5 -4
- package/src/buffer-admission.ts +45 -0
- package/src/buffer-eviction-backpressure.test.ts +211 -0
- package/src/buffer-eviction.ts +8 -14
- package/src/buffer-incremental-persistence.test.ts +25 -37
- package/src/buffer-session.test.ts +32 -16
- package/src/buffer-turn-helpers.ts +41 -0
- package/src/buffer-types.ts +28 -1
- package/src/buffer.ts +77 -85
- package/src/capsule-import-gate.ts +109 -0
- package/src/extraction-quote-grounding.test.ts +76 -0
- package/src/extraction-quote-grounding.ts +49 -13
- package/src/extraction-source-grounding-rules.ts +6 -1
- package/src/extraction-source-grounding.ts +2 -0
- package/src/extraction.ts +3 -4
- package/src/namespaces/search.test.ts +141 -1
- package/src/namespaces/search.ts +39 -9
- package/src/orchestration/turn-ingestion.ts +38 -39
- package/src/recall-navigation-operations.ts +9 -2
- package/src/source-agent-qualifier.ts +3 -0
- package/dist/chunk-BLGQUZFB.js.map +0 -1
- package/dist/chunk-CMRHXPLQ.js.map +0 -1
- package/dist/chunk-DM4RRTN7.js.map +0 -1
- package/dist/chunk-FRM4UTDR.js.map +0 -1
- package/dist/chunk-GKYIRTNX.js.map +0 -1
- package/dist/chunk-GYNRNKMW.js.map +0 -1
- package/dist/chunk-H34INI3P.js.map +0 -1
- package/dist/chunk-HVFX2N2G.js.map +0 -1
- package/dist/chunk-KASJJGRG.js.map +0 -1
- package/dist/chunk-LGEH473S.js.map +0 -1
- package/dist/chunk-P225W4RQ.js.map +0 -1
- package/dist/chunk-SGHV6LZ7.js.map +0 -1
- package/dist/chunk-X2KO4FK5.js.map +0 -1
- package/dist/chunk-X7REO4CH.js.map +0 -1
- package/dist/chunk-XSFQN7TP.js.map +0 -1
- /package/dist/{capsule-crypto-GWVG7LGC.js.map → buffer-admission.js.map} +0 -0
- /package/dist/{chunk-73YVMZ7D.js.map → chunk-2G4TV2ML.js.map} +0 -0
- /package/dist/{chunk-BMIM6ESC.js.map → chunk-2JG2NI7C.js.map} +0 -0
- /package/dist/{chunk-IR4C3DP2.js.map → chunk-2K3AUDH7.js.map} +0 -0
- /package/dist/{chunk-6KFFRMPQ.js.map → chunk-2N4OUSIA.js.map} +0 -0
- /package/dist/{chunk-OWHERGF2.js.map → chunk-2NLLXCJG.js.map} +0 -0
- /package/dist/{chunk-44FQCC5D.js.map → chunk-2NUDHVYM.js.map} +0 -0
- /package/dist/{chunk-FK4WC6VK.js.map → chunk-2U4LMSEX.js.map} +0 -0
- /package/dist/{chunk-LT3CMO65.js.map → chunk-2XOFVTTT.js.map} +0 -0
- /package/dist/{chunk-I6ORDT54.js.map → chunk-32CA7B6L.js.map} +0 -0
- /package/dist/{chunk-TQVECGUQ.js.map → chunk-32YX7EJ3.js.map} +0 -0
- /package/dist/{chunk-HLWXCDUQ.js.map → chunk-3DAWHP5K.js.map} +0 -0
- /package/dist/{chunk-GBOYOL5L.js.map → chunk-3PJEDHLD.js.map} +0 -0
- /package/dist/{chunk-ZWTHVUT5.js.map → chunk-3UZ2VMKD.js.map} +0 -0
- /package/dist/{chunk-2XYDL2FG.js.map → chunk-3Y5X6YFH.js.map} +0 -0
- /package/dist/{chunk-23B4HLWG.js.map → chunk-4GMI5HFH.js.map} +0 -0
- /package/dist/{chunk-AP3TSV6S.js.map → chunk-4LCVA2LW.js.map} +0 -0
- /package/dist/{chunk-MZCGHPIJ.js.map → chunk-4X7HQKAG.js.map} +0 -0
- /package/dist/{chunk-YNBMBL4C.js.map → chunk-4XYAWK5H.js.map} +0 -0
- /package/dist/{chunk-2JAT4S7N.js.map → chunk-5SOSQTVE.js.map} +0 -0
- /package/dist/{chunk-G3T6YMBO.js.map → chunk-64S6X4IU.js.map} +0 -0
- /package/dist/{chunk-NIV7RPWQ.js.map → chunk-6BCWFCDG.js.map} +0 -0
- /package/dist/{chunk-V27FK6D6.js.map → chunk-6XTUZKCZ.js.map} +0 -0
- /package/dist/{chunk-2ASEDFOA.js.map → chunk-73UZZFJC.js.map} +0 -0
- /package/dist/{chunk-UHPHH74F.js.map → chunk-77PDJ4OE.js.map} +0 -0
- /package/dist/{chunk-BATPQNRG.js.map → chunk-7GSL4LBG.js.map} +0 -0
- /package/dist/{chunk-B6SPPYGJ.js.map → chunk-7IWRLYFJ.js.map} +0 -0
- /package/dist/{chunk-BUKK5SWA.js.map → chunk-7LCGFUMB.js.map} +0 -0
- /package/dist/{chunk-ZXF3O7P4.js.map → chunk-ARRTLPRC.js.map} +0 -0
- /package/dist/{chunk-S5CQI2KF.js.map → chunk-B3GKQ6DU.js.map} +0 -0
- /package/dist/{chunk-2URKYLMK.js.map → chunk-B56LORLY.js.map} +0 -0
- /package/dist/{chunk-4IWEPQEV.js.map → chunk-BIVKPQ6Q.js.map} +0 -0
- /package/dist/{chunk-4DPLCYEK.js.map → chunk-C2HORX76.js.map} +0 -0
- /package/dist/{chunk-ELWI2R6H.js.map → chunk-CC4V7HNP.js.map} +0 -0
- /package/dist/{chunk-WFDWLWDK.js.map → chunk-CN7Q3T2J.js.map} +0 -0
- /package/dist/{chunk-GJVGFKOU.js.map → chunk-CYCEROPR.js.map} +0 -0
- /package/dist/{chunk-CCU7URRP.js.map → chunk-D5EV6Y2L.js.map} +0 -0
- /package/dist/{chunk-N6II66E5.js.map → chunk-DT7SNJH6.js.map} +0 -0
- /package/dist/{chunk-MVOJDLXS.js.map → chunk-EA2LA5RI.js.map} +0 -0
- /package/dist/{chunk-WBN66HPI.js.map → chunk-ELHJK7LM.js.map} +0 -0
- /package/dist/{chunk-ONN2E5QD.js.map → chunk-EUBDYOLD.js.map} +0 -0
- /package/dist/{chunk-YNDLCWXS.js.map → chunk-EZ25VE3G.js.map} +0 -0
- /package/dist/{chunk-3LHPTBWG.js.map → chunk-F74G4KIH.js.map} +0 -0
- /package/dist/{chunk-MYCCO6AA.js.map → chunk-G54BOKZ6.js.map} +0 -0
- /package/dist/{chunk-JFH6JAGY.js.map → chunk-G67EUQAB.js.map} +0 -0
- /package/dist/{chunk-TV65NR7L.js.map → chunk-GULUWOA5.js.map} +0 -0
- /package/dist/{chunk-6DANGCMO.js.map → chunk-HFUKXOUW.js.map} +0 -0
- /package/dist/{chunk-Y3IYKNBY.js.map → chunk-HLN2HOXX.js.map} +0 -0
- /package/dist/{chunk-Y43X3E24.js.map → chunk-IBDWYRDR.js.map} +0 -0
- /package/dist/{chunk-AJG2TAMR.js.map → chunk-IEEV27BC.js.map} +0 -0
- /package/dist/{chunk-FZPDRCOB.js.map → chunk-IJ3OEECW.js.map} +0 -0
- /package/dist/{chunk-D6THW7PA.js.map → chunk-IPD5GC7C.js.map} +0 -0
- /package/dist/{chunk-VCVEMQJK.js.map → chunk-JM2YZKCQ.js.map} +0 -0
- /package/dist/{chunk-OSTFDWZJ.js.map → chunk-NIVKG3DS.js.map} +0 -0
- /package/dist/{chunk-VZELUMKK.js.map → chunk-NQP4ZYGG.js.map} +0 -0
- /package/dist/{chunk-3ODZLCHC.js.map → chunk-NT7F7WJQ.js.map} +0 -0
- /package/dist/{chunk-YO2JEX2Z.js.map → chunk-OGBQMY46.js.map} +0 -0
- /package/dist/{chunk-O55AGDTI.js.map → chunk-OL553AWT.js.map} +0 -0
- /package/dist/{chunk-CLVILSBR.js.map → chunk-OZNBZJ3S.js.map} +0 -0
- /package/dist/{chunk-RNGDKFY2.js.map → chunk-P452SEKO.js.map} +0 -0
- /package/dist/{chunk-JX3E4XCW.js.map → chunk-PDOOUXCS.js.map} +0 -0
- /package/dist/{chunk-V45TEVDA.js.map → chunk-PKZ274PI.js.map} +0 -0
- /package/dist/{chunk-MNYQVYJ4.js.map → chunk-QD5R6U5J.js.map} +0 -0
- /package/dist/{chunk-DI5ET2IE.js.map → chunk-QEZ3DW5O.js.map} +0 -0
- /package/dist/{chunk-WYPCJK6K.js.map → chunk-QKERFEJS.js.map} +0 -0
- /package/dist/{chunk-BLMTOIBK.js.map → chunk-QKES7GU6.js.map} +0 -0
- /package/dist/{chunk-PCQWKMOL.js.map → chunk-QNSZYTSG.js.map} +0 -0
- /package/dist/{chunk-MHSBOEH7.js.map → chunk-ROKDHO74.js.map} +0 -0
- /package/dist/{chunk-3VU2FDTU.js.map → chunk-SYKJBS7S.js.map} +0 -0
- /package/dist/{chunk-5BE3FLKL.js.map → chunk-TAUHAP5J.js.map} +0 -0
- /package/dist/{chunk-QCP75JGO.js.map → chunk-TFA6HPOA.js.map} +0 -0
- /package/dist/{chunk-E7V5AK7I.js.map → chunk-TRMZHGIX.js.map} +0 -0
- /package/dist/{chunk-WBDIZWJQ.js.map → chunk-TSS3D7ZY.js.map} +0 -0
- /package/dist/{chunk-OMYRPMQC.js.map → chunk-US7KPQA5.js.map} +0 -0
- /package/dist/{chunk-KHZVAISH.js.map → chunk-UWRHHRDK.js.map} +0 -0
- /package/dist/{chunk-SDESKFKT.js.map → chunk-UYHDMCY2.js.map} +0 -0
- /package/dist/{chunk-YAAKVTZA.js.map → chunk-VKILERSL.js.map} +0 -0
- /package/dist/{chunk-2TGIKMMZ.js.map → chunk-WXDTOE6F.js.map} +0 -0
- /package/dist/{chunk-UVYI6VIX.js.map → chunk-X7Y7WX73.js.map} +0 -0
- /package/dist/{chunk-QA3Y2O6H.js.map → chunk-YAVIBVCU.js.map} +0 -0
- /package/dist/{chunk-E5QEF3JO.js.map → chunk-YRZPBN46.js.map} +0 -0
- /package/dist/{chunk-FMFCQRDM.js.map → chunk-ZANNIXXE.js.map} +0 -0
- /package/dist/{chunk-3KGCHTTK.js.map → chunk-ZBFLV657.js.map} +0 -0
- /package/dist/{chunk-VEFGHRKV.js.map → chunk-ZOJUWVAU.js.map} +0 -0
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import { mkdtemp, readdir, readFile, rm } from "node:fs/promises";
|
|
3
|
+
import { tmpdir } from "node:os";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import test from "node:test";
|
|
6
|
+
|
|
7
|
+
import { EngramAccessHttpServer } from "./access-http.js";
|
|
8
|
+
import { EngramAccessService } from "./access-service.js";
|
|
9
|
+
import { Orchestrator } from "./orchestrator.js";
|
|
10
|
+
import { parseConfig } from "./config.js";
|
|
11
|
+
import { writePeer, writePeerProfile } from "./peers/index.js";
|
|
12
|
+
|
|
13
|
+
// Issue #3225 B3: the HTTP peer routes dispatch to global stores, so REST
|
|
14
|
+
// must enforce the same operation policy as MCP — a namespace-scoped token
|
|
15
|
+
// (any namespaces axis, including []) must be denied before any file in the
|
|
16
|
+
// store changes, while ops-scoped and legacy tokens keep working.
|
|
17
|
+
|
|
18
|
+
const PEER_ID = "b3-target-peer";
|
|
19
|
+
const NOW = "2026-11-11T00:00:00.000Z";
|
|
20
|
+
|
|
21
|
+
async function createService(): Promise<{ root: string; service: EngramAccessService; cleanup: () => Promise<void> }> {
|
|
22
|
+
const root = await mkdtemp(path.join(tmpdir(), "remnic-http-peer-fleet-wide-"));
|
|
23
|
+
const orchestrator = new Orchestrator(
|
|
24
|
+
parseConfig({ memoryDir: root, namespacesEnabled: false, defaultNamespace: "global" }),
|
|
25
|
+
);
|
|
26
|
+
const storage = await orchestrator.getStorage();
|
|
27
|
+
await storage.ensureDirectories();
|
|
28
|
+
await writePeer(root, {
|
|
29
|
+
id: PEER_ID,
|
|
30
|
+
kind: "agent",
|
|
31
|
+
displayName: "seeded peer",
|
|
32
|
+
createdAt: NOW,
|
|
33
|
+
updatedAt: NOW,
|
|
34
|
+
});
|
|
35
|
+
await writePeerProfile(root, {
|
|
36
|
+
peerId: PEER_ID,
|
|
37
|
+
updatedAt: NOW,
|
|
38
|
+
fields: { communication_style: "seeded" },
|
|
39
|
+
provenance: {},
|
|
40
|
+
});
|
|
41
|
+
return { root, service: new EngramAccessService(orchestrator), cleanup: () => rm(root, { recursive: true, force: true }) };
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** Sorted relpath:content lines for every file under root (mutation detector). */
|
|
45
|
+
async function snapshotDir(root: string): Promise<string[]> {
|
|
46
|
+
const entries = await readdir(root, { recursive: true, withFileTypes: true });
|
|
47
|
+
const lines: string[] = [];
|
|
48
|
+
for (const entry of entries) {
|
|
49
|
+
if (!entry.isFile()) continue;
|
|
50
|
+
const abs = path.join(entry.parentPath, entry.name);
|
|
51
|
+
const content = await readFile(abs, "utf8");
|
|
52
|
+
lines.push(`${path.relative(root, abs)}:${content}`);
|
|
53
|
+
}
|
|
54
|
+
return lines.sort();
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
test("HTTP peer routes deny namespace-scoped tokens before the stores, REST policy matches MCP (issue #3225 B3)", async () => {
|
|
58
|
+
const { root, service, cleanup } = await createService();
|
|
59
|
+
const server = new EngramAccessHttpServer({
|
|
60
|
+
service,
|
|
61
|
+
port: 0,
|
|
62
|
+
authToken: "legacy-operator",
|
|
63
|
+
authTokenEntriesGetter: () => [
|
|
64
|
+
{ token: "ns-scoped", capabilities: { version: 1, namespaces: ["ns_a"] } },
|
|
65
|
+
{ token: "ns-empty", capabilities: { version: 1, namespaces: [] } },
|
|
66
|
+
{
|
|
67
|
+
token: "ops-peer",
|
|
68
|
+
capabilities: {
|
|
69
|
+
version: 1,
|
|
70
|
+
ops: ["peer_list", "peer_get", "peer_set", "peer_delete", "peer_profile_get", "peer_forget"],
|
|
71
|
+
},
|
|
72
|
+
},
|
|
73
|
+
],
|
|
74
|
+
adminConsoleEnabled: false,
|
|
75
|
+
});
|
|
76
|
+
const status = await server.start();
|
|
77
|
+
const call = (token: string, pathname: string, init?: RequestInit) =>
|
|
78
|
+
fetch(`http://127.0.0.1:${status.port}${pathname}`, {
|
|
79
|
+
...init,
|
|
80
|
+
headers: { "content-type": "application/json", authorization: `Bearer ${token}`, ...(init?.headers ?? {}) },
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
try {
|
|
84
|
+
const before = await snapshotDir(root);
|
|
85
|
+
|
|
86
|
+
// Namespace-scoped token (single namespace): every peer route is denied
|
|
87
|
+
// BEFORE the store is touched — including the destructive purge.
|
|
88
|
+
assert.equal((await call("ns-scoped", "/engram/v1/peers")).status, 403, "peer_list denied");
|
|
89
|
+
assert.equal((await call("ns-scoped", `/engram/v1/peers/${PEER_ID}`)).status, 403, "peer_get denied");
|
|
90
|
+
assert.equal(
|
|
91
|
+
(await call("ns-scoped", `/engram/v1/peers/${PEER_ID}`, { method: "PUT", body: JSON.stringify({ displayName: "hijack" }) })).status,
|
|
92
|
+
403,
|
|
93
|
+
"peer_set denied",
|
|
94
|
+
);
|
|
95
|
+
assert.equal((await call("ns-scoped", `/engram/v1/peers/${PEER_ID}`, { method: "DELETE" })).status, 403, "peer_delete denied");
|
|
96
|
+
assert.equal(
|
|
97
|
+
(
|
|
98
|
+
await call("ns-scoped", `/engram/v1/peers/${PEER_ID}?forget=true`, {
|
|
99
|
+
method: "DELETE",
|
|
100
|
+
body: JSON.stringify({ confirm: "yes" }),
|
|
101
|
+
})
|
|
102
|
+
).status,
|
|
103
|
+
403,
|
|
104
|
+
"peer_forget denied",
|
|
105
|
+
);
|
|
106
|
+
assert.equal((await call("ns-scoped", `/engram/v1/peers/${PEER_ID}/profile`)).status, 403, "peer_profile_get denied");
|
|
107
|
+
|
|
108
|
+
// Empty namespace allow-list: denied too.
|
|
109
|
+
assert.equal((await call("ns-empty", "/engram/v1/peers")).status, 403, "empty namespace list denied");
|
|
110
|
+
|
|
111
|
+
// Denials never reached the stores — no file read effect, no mutation.
|
|
112
|
+
assert.deepEqual(await snapshotDir(root), before, "denied requests must not change any peer file");
|
|
113
|
+
|
|
114
|
+
// An ops-scoped token without a namespace axis keeps full peer access.
|
|
115
|
+
const listed = await call("ops-peer", "/engram/v1/peers");
|
|
116
|
+
assert.equal(listed.status, 200, "ops-scoped token lists peers");
|
|
117
|
+
// SAFETY: the peer_list REST route returns the service envelope { peers }.
|
|
118
|
+
const listedBody = (await listed.json()) as { peers: { id: string }[] };
|
|
119
|
+
assert.deepEqual(listedBody.peers.map((p) => p.id), [PEER_ID], "ops-scoped token sees the seeded peer");
|
|
120
|
+
|
|
121
|
+
const created = await call("ops-peer", "/engram/v1/peers/ops-peer-1", {
|
|
122
|
+
method: "PUT",
|
|
123
|
+
body: JSON.stringify({ displayName: "ops token peer" }),
|
|
124
|
+
});
|
|
125
|
+
assert.equal(created.status, 201, "ops-scoped token registers a peer");
|
|
126
|
+
// SAFETY: the peer_set REST route returns the upsert outcome { created, peer }.
|
|
127
|
+
const createdBody = (await created.json()) as { created: boolean; peer: { displayName: string } };
|
|
128
|
+
assert.equal(createdBody.created, true);
|
|
129
|
+
assert.equal(createdBody.peer.displayName, "ops token peer");
|
|
130
|
+
|
|
131
|
+
const purged = await call("ops-peer", "/engram/v1/peers/ops-peer-1?forget=true", {
|
|
132
|
+
method: "DELETE",
|
|
133
|
+
body: JSON.stringify({ confirm: "yes" }),
|
|
134
|
+
});
|
|
135
|
+
assert.equal(purged.status, 200, "ops-scoped token purges a peer");
|
|
136
|
+
await purged.text();
|
|
137
|
+
const afterPurge = await snapshotDir(root);
|
|
138
|
+
assert.ok(
|
|
139
|
+
!afterPurge.some((line) => line.startsWith("peers/ops-peer-1/")),
|
|
140
|
+
"the destructive purge removed the peer directory",
|
|
141
|
+
);
|
|
142
|
+
|
|
143
|
+
// Legacy unrestricted token (static authToken, no capability record).
|
|
144
|
+
const legacyList = await call("legacy-operator", "/engram/v1/peers");
|
|
145
|
+
assert.equal(legacyList.status, 200, "legacy token lists peers");
|
|
146
|
+
await legacyList.text();
|
|
147
|
+
|
|
148
|
+
// Peer-forget confirmation behavior is unchanged: no confirm body, no purge.
|
|
149
|
+
const unconfirmed = await call("legacy-operator", `/engram/v1/peers/${PEER_ID}?forget=true`, {
|
|
150
|
+
method: "DELETE",
|
|
151
|
+
body: JSON.stringify({}),
|
|
152
|
+
});
|
|
153
|
+
assert.equal(unconfirmed.status, 400, "missing confirm still fails with 400");
|
|
154
|
+
assert.match(await unconfirmed.text(), /confirm_required/);
|
|
155
|
+
const seededIntact = await snapshotDir(root);
|
|
156
|
+
assert.ok(
|
|
157
|
+
seededIntact.some((line) => line.startsWith(`peers/${PEER_ID}/identity.md`)),
|
|
158
|
+
"the seeded peer survived the rejected confirmation",
|
|
159
|
+
);
|
|
160
|
+
|
|
161
|
+
// A malformed PUT body still fails loudly instead of silently defaulting.
|
|
162
|
+
const malformed = await call("legacy-operator", "/engram/v1/peers/malformed-peer", {
|
|
163
|
+
method: "PUT",
|
|
164
|
+
body: JSON.stringify({ displayName: 5 }),
|
|
165
|
+
});
|
|
166
|
+
assert.equal(malformed.status, 400, "a non-string displayName is rejected");
|
|
167
|
+
assert.match(await malformed.text(), /displayName/);
|
|
168
|
+
assert.ok(
|
|
169
|
+
!(await snapshotDir(root)).some((line) => line.startsWith("peers/malformed-peer/")),
|
|
170
|
+
"the rejected write created no peer file",
|
|
171
|
+
);
|
|
172
|
+
|
|
173
|
+
// An explicit null is a malformed field too (rule 51): the operation
|
|
174
|
+
// schema strips nulls, so the route must reject them before dispatch
|
|
175
|
+
// instead of registering the peer with default kind/name/notes.
|
|
176
|
+
for (const field of ["kind", "displayName", "notes"]) {
|
|
177
|
+
const nullField = await call("legacy-operator", `/engram/v1/peers/null-${field}-peer`, {
|
|
178
|
+
method: "PUT",
|
|
179
|
+
body: JSON.stringify({ [field]: null }),
|
|
180
|
+
});
|
|
181
|
+
assert.equal(nullField.status, 400, `an explicit null ${field} is rejected`);
|
|
182
|
+
assert.match(await nullField.text(), new RegExp(field));
|
|
183
|
+
assert.ok(
|
|
184
|
+
!(await snapshotDir(root)).some((line) => line.startsWith(`peers/null-${field}-peer/`)),
|
|
185
|
+
`the null ${field} request created no peer file`,
|
|
186
|
+
);
|
|
187
|
+
}
|
|
188
|
+
} finally {
|
|
189
|
+
await server.stop();
|
|
190
|
+
await cleanup();
|
|
191
|
+
}
|
|
192
|
+
});
|
package/src/access-http.test.ts
CHANGED
|
@@ -13,8 +13,8 @@ import {
|
|
|
13
13
|
import { peerManifestRevision } from "./reconcile/manifest.js";
|
|
14
14
|
import {
|
|
15
15
|
EngramAccessInputError,
|
|
16
|
+
EngramAccessService,
|
|
16
17
|
type EngramAccessRecallRequest,
|
|
17
|
-
type EngramAccessService,
|
|
18
18
|
type EngramAccessBriefingRequest,
|
|
19
19
|
} from "./access-service.js";
|
|
20
20
|
import {
|
|
@@ -815,6 +815,48 @@ test("HTTP coding-context endpoint accepts projectTag shorthand", async () => {
|
|
|
815
815
|
}
|
|
816
816
|
});
|
|
817
817
|
|
|
818
|
+
test("HTTP observe answers 503 buffer_admission_refused when durable capture hits the entry cap", async () => {
|
|
819
|
+
const refusal = new Error("buffer at session-entry cap");
|
|
820
|
+
refusal.name = "ReplayAdmissionRefusedError";
|
|
821
|
+
// SAFETY: this fixture drives only the observe route; the remaining service members are unreachable.
|
|
822
|
+
const service = Object.assign({} as EngramAccessService, {
|
|
823
|
+
observe: async () => {
|
|
824
|
+
throw refusal;
|
|
825
|
+
},
|
|
826
|
+
});
|
|
827
|
+
const server = new EngramAccessHttpServer({
|
|
828
|
+
service,
|
|
829
|
+
port: 0,
|
|
830
|
+
authToken: "test-token",
|
|
831
|
+
adminConsoleEnabled: false,
|
|
832
|
+
});
|
|
833
|
+
|
|
834
|
+
const status = await server.start();
|
|
835
|
+
try {
|
|
836
|
+
const response = await fetch(
|
|
837
|
+
`http://127.0.0.1:${status.port}/engram/v1/observe`,
|
|
838
|
+
{
|
|
839
|
+
method: "POST",
|
|
840
|
+
headers: {
|
|
841
|
+
authorization: "Bearer test-token",
|
|
842
|
+
"content-type": "application/json",
|
|
843
|
+
},
|
|
844
|
+
body: JSON.stringify({
|
|
845
|
+
sessionKey: "capacity-test-session",
|
|
846
|
+
messages: [{ role: "user", content: "retry me" }],
|
|
847
|
+
}),
|
|
848
|
+
},
|
|
849
|
+
);
|
|
850
|
+
assert.equal(response.status, 503, "admission backpressure is retryable, not a server fault");
|
|
851
|
+
assert.deepEqual(await response.json(), {
|
|
852
|
+
error: "buffer at session-entry cap",
|
|
853
|
+
code: "buffer_admission_refused",
|
|
854
|
+
});
|
|
855
|
+
} finally {
|
|
856
|
+
await server.stop();
|
|
857
|
+
}
|
|
858
|
+
});
|
|
859
|
+
|
|
818
860
|
test("HTTP briefing endpoint dispatches through the briefing operation", async () => {
|
|
819
861
|
const calls: unknown[] = [];
|
|
820
862
|
const service = {
|
|
@@ -2996,11 +3038,11 @@ test("HTTP scoped-token namespace allow-list is enforced on every namespace rout
|
|
|
2996
3038
|
"scoped memory_get: the allowed namespace reaches the service",
|
|
2997
3039
|
);
|
|
2998
3040
|
|
|
2999
|
-
// ── a
|
|
3041
|
+
// ── a global-store route is fleet-wide: a namespace-scoped token is denied (issue #3225 B3) ──
|
|
3000
3042
|
assert.equal(
|
|
3001
3043
|
(await request("scoped-ns-a", "/engram/v1/peers")).status,
|
|
3002
|
-
|
|
3003
|
-
"a
|
|
3044
|
+
403,
|
|
3045
|
+
"peer routes touch global stores, so a namespace-scoped token is denied",
|
|
3004
3046
|
);
|
|
3005
3047
|
|
|
3006
3048
|
// ── unrestricted token: omitting namespace still reaches the default tenant ──
|
|
@@ -6515,3 +6557,206 @@ test("a budget above the backend cap still gets the rows it asked for", async ()
|
|
|
6515
6557
|
"the excluded hit was replaced, not subtracted",
|
|
6516
6558
|
);
|
|
6517
6559
|
});
|
|
6560
|
+
|
|
6561
|
+
/**
|
|
6562
|
+
* Prototype-backed service stub for the write-quota tests: the constructor's
|
|
6563
|
+
* surface getters read only `orchestrator.config`, and each test overrides the
|
|
6564
|
+
* single member its route dispatches.
|
|
6565
|
+
*/
|
|
6566
|
+
function quotaStubService(): EngramAccessService {
|
|
6567
|
+
// SAFETY: the prototype supplies the untouched service surface; each quota test overrides only the member its route dispatches.
|
|
6568
|
+
const service = Object.create(EngramAccessService.prototype) as EngramAccessService;
|
|
6569
|
+
// SAFETY: startup reads only orchestrator.config from the service; an empty config keeps every surface flag false.
|
|
6570
|
+
Reflect.set(service, "orchestrator", { config: {} });
|
|
6571
|
+
return service;
|
|
6572
|
+
}
|
|
6573
|
+
|
|
6574
|
+
test("concurrent distinct-payload writes cannot overshoot the write quota (issue #3225 C4)", async () => {
|
|
6575
|
+
// Checking hasCapacity before the operation and calling record after it
|
|
6576
|
+
// left a window where N in-flight writes all pass against the same
|
|
6577
|
+
// committed count. Admission must reserve a slot, so only LIMIT of TOTAL
|
|
6578
|
+
// overlapping writes proceed and the rest get 429.
|
|
6579
|
+
const LIMIT = 2;
|
|
6580
|
+
const TOTAL = 6;
|
|
6581
|
+
let dispatches = 0;
|
|
6582
|
+
const firstDispatched = Promise.withResolvers<void>();
|
|
6583
|
+
const releaseWrites = Promise.withResolvers<void>();
|
|
6584
|
+
const service = quotaStubService();
|
|
6585
|
+
service.capsuleExport = async (request) => {
|
|
6586
|
+
dispatches += 1;
|
|
6587
|
+
if (dispatches === 1) firstDispatched.resolve();
|
|
6588
|
+
await releaseWrites.promise;
|
|
6589
|
+
// SAFETY: the route serializes this stub result into the HTTP response; the test asserts statuses, not this shape.
|
|
6590
|
+
return { ok: true, name: request.name } as never;
|
|
6591
|
+
};
|
|
6592
|
+
const server = new EngramAccessHttpServer({
|
|
6593
|
+
service,
|
|
6594
|
+
port: 0,
|
|
6595
|
+
authToken: "test-token",
|
|
6596
|
+
adminConsoleEnabled: false,
|
|
6597
|
+
writeRateLimitMaxRequests: LIMIT,
|
|
6598
|
+
});
|
|
6599
|
+
const status = await server.start();
|
|
6600
|
+
const post = (index: number) =>
|
|
6601
|
+
fetch(`http://127.0.0.1:${status.port}/engram/v1/capsules/export`, {
|
|
6602
|
+
method: "POST",
|
|
6603
|
+
headers: {
|
|
6604
|
+
authorization: "Bearer test-token",
|
|
6605
|
+
"content-type": "application/json",
|
|
6606
|
+
},
|
|
6607
|
+
body: JSON.stringify({ name: `quota-race-${index}` }),
|
|
6608
|
+
});
|
|
6609
|
+
try {
|
|
6610
|
+
const first = post(0);
|
|
6611
|
+
await firstDispatched.promise;
|
|
6612
|
+
const rest = Array.from({ length: TOTAL - 1 }, (_, index) => post(index + 1));
|
|
6613
|
+
// The first write is admitted and held open inside the service, so every
|
|
6614
|
+
// other request must run its admission check against an unrecorded
|
|
6615
|
+
// window. Loopback arrival needs a bounded grace; the overlap cannot be
|
|
6616
|
+
// driven deterministically because admission is server-internal.
|
|
6617
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
6618
|
+
releaseWrites.resolve();
|
|
6619
|
+
const responses = await Promise.all([first, ...rest]);
|
|
6620
|
+
const statuses = responses.map((response) => response.status).sort((a, b) => a - b);
|
|
6621
|
+
assert.deepEqual(
|
|
6622
|
+
statuses,
|
|
6623
|
+
[...Array<number>(LIMIT).fill(200), ...Array<number>(TOTAL - LIMIT).fill(429)],
|
|
6624
|
+
`expected exactly ${LIMIT} admitted writes and ${TOTAL - LIMIT} rejections, got ${JSON.stringify(statuses)}`,
|
|
6625
|
+
);
|
|
6626
|
+
} finally {
|
|
6627
|
+
releaseWrites.resolve();
|
|
6628
|
+
await server.stop();
|
|
6629
|
+
}
|
|
6630
|
+
});
|
|
6631
|
+
|
|
6632
|
+
test("a failed write releases its reserved quota slot (issue #3225 C4)", async () => {
|
|
6633
|
+
let calls = 0;
|
|
6634
|
+
const service = quotaStubService();
|
|
6635
|
+
service.capsuleExport = async (request) => {
|
|
6636
|
+
calls += 1;
|
|
6637
|
+
if (calls === 1) throw new Error("export backend failed");
|
|
6638
|
+
// SAFETY: the route serializes this stub result into the HTTP response; the test asserts statuses, not this shape.
|
|
6639
|
+
return { ok: true, name: request.name } as never;
|
|
6640
|
+
};
|
|
6641
|
+
const server = new EngramAccessHttpServer({
|
|
6642
|
+
service,
|
|
6643
|
+
port: 0,
|
|
6644
|
+
authToken: "test-token",
|
|
6645
|
+
adminConsoleEnabled: false,
|
|
6646
|
+
writeRateLimitMaxRequests: 1,
|
|
6647
|
+
});
|
|
6648
|
+
const status = await server.start();
|
|
6649
|
+
const post = (name: string) =>
|
|
6650
|
+
fetch(`http://127.0.0.1:${status.port}/engram/v1/capsules/export`, {
|
|
6651
|
+
method: "POST",
|
|
6652
|
+
headers: {
|
|
6653
|
+
authorization: "Bearer test-token",
|
|
6654
|
+
"content-type": "application/json",
|
|
6655
|
+
},
|
|
6656
|
+
body: JSON.stringify({ name }),
|
|
6657
|
+
});
|
|
6658
|
+
try {
|
|
6659
|
+
// The first export fails in the backend; the slot it reserved at
|
|
6660
|
+
// admission must be released, so the retry is admitted and only then
|
|
6661
|
+
// does the window fill.
|
|
6662
|
+
assert.equal((await post("quota-fail-1")).status, 500);
|
|
6663
|
+
assert.equal((await post("quota-fail-2")).status, 200);
|
|
6664
|
+
assert.equal((await post("quota-fail-3")).status, 429);
|
|
6665
|
+
} finally {
|
|
6666
|
+
await server.stop();
|
|
6667
|
+
}
|
|
6668
|
+
});
|
|
6669
|
+
|
|
6670
|
+
test("a replayed observe never 429s and never consumes a second quota slot (issue #1434, #3225 C4)", async () => {
|
|
6671
|
+
const stored = new Map<string, { sessionKey: string }>();
|
|
6672
|
+
let hookCalls = 0;
|
|
6673
|
+
const service = quotaStubService();
|
|
6674
|
+
service.observe = async (request, hooks) => {
|
|
6675
|
+
const key = request.idempotencyKey?.trim();
|
|
6676
|
+
const cached = key ? stored.get(key) : undefined;
|
|
6677
|
+
if (cached) {
|
|
6678
|
+
// SAFETY: the route only forwards this result into the HTTP response and reads its dryRun/idempotencyReplay flags.
|
|
6679
|
+
return { ...cached, idempotencyReplay: true } as never;
|
|
6680
|
+
}
|
|
6681
|
+
// Mirror the real service contract: the quota hook fires exactly once,
|
|
6682
|
+
// immediately before an actual write commits, never on a replay.
|
|
6683
|
+
await hooks?.enforceWriteQuota?.();
|
|
6684
|
+
hookCalls += 1;
|
|
6685
|
+
const response = { sessionKey: request.sessionKey };
|
|
6686
|
+
stored.set(key ?? "", response);
|
|
6687
|
+
// SAFETY: the route only forwards this result into the HTTP response and reads its dryRun/idempotencyReplay flags.
|
|
6688
|
+
return response as never;
|
|
6689
|
+
};
|
|
6690
|
+
const server = new EngramAccessHttpServer({
|
|
6691
|
+
service,
|
|
6692
|
+
port: 0,
|
|
6693
|
+
authToken: "test-token",
|
|
6694
|
+
adminConsoleEnabled: false,
|
|
6695
|
+
writeRateLimitMaxRequests: 1,
|
|
6696
|
+
});
|
|
6697
|
+
const status = await server.start();
|
|
6698
|
+
const post = (idempotencyKey: string) =>
|
|
6699
|
+
fetch(`http://127.0.0.1:${status.port}/engram/v1/observe`, {
|
|
6700
|
+
method: "POST",
|
|
6701
|
+
headers: {
|
|
6702
|
+
authorization: "Bearer test-token",
|
|
6703
|
+
"content-type": "application/json",
|
|
6704
|
+
},
|
|
6705
|
+
body: JSON.stringify({
|
|
6706
|
+
sessionKey: "quota-replay",
|
|
6707
|
+
messages: [{ role: "user", content: "hello" }],
|
|
6708
|
+
idempotencyKey,
|
|
6709
|
+
}),
|
|
6710
|
+
});
|
|
6711
|
+
try {
|
|
6712
|
+
assert.equal((await post("quota-replay-1")).status, 202);
|
|
6713
|
+
const replay = await post("quota-replay-1");
|
|
6714
|
+
assert.equal(replay.status, 202);
|
|
6715
|
+
// SAFETY: our own server serialized this body; the test reads only the idempotencyReplay flag.
|
|
6716
|
+
const replayBody = (await replay.json()) as { idempotencyReplay?: boolean };
|
|
6717
|
+
assert.equal(replayBody.idempotencyReplay, true);
|
|
6718
|
+
assert.equal(hookCalls, 1, "the replay must not re-run the quota hook");
|
|
6719
|
+
// The window still holds exactly the first write's slot: a different
|
|
6720
|
+
// payload is rejected, proving the replay consumed nothing.
|
|
6721
|
+
assert.equal((await post("quota-replay-2")).status, 429);
|
|
6722
|
+
} finally {
|
|
6723
|
+
await server.stop();
|
|
6724
|
+
}
|
|
6725
|
+
});
|
|
6726
|
+
|
|
6727
|
+
test("a dreams dry run neither 429s nor consumes quota at a full window (#3225 C4)", async () => {
|
|
6728
|
+
const service = quotaStubService();
|
|
6729
|
+
service.dreamsRun = async (options) => {
|
|
6730
|
+
// SAFETY: the route serializes this stub result into the HTTP response and reads its dryRun flag.
|
|
6731
|
+
return { phase: options.phase, dryRun: options.dryRun === true } as never;
|
|
6732
|
+
};
|
|
6733
|
+
const server = new EngramAccessHttpServer({
|
|
6734
|
+
service,
|
|
6735
|
+
port: 0,
|
|
6736
|
+
authToken: "test-token",
|
|
6737
|
+
adminConsoleEnabled: false,
|
|
6738
|
+
writeRateLimitMaxRequests: 1,
|
|
6739
|
+
});
|
|
6740
|
+
const status = await server.start();
|
|
6741
|
+
const post = (body: Record<string, boolean>) =>
|
|
6742
|
+
fetch(`http://127.0.0.1:${status.port}/engram/v1/dreams/run`, {
|
|
6743
|
+
method: "POST",
|
|
6744
|
+
headers: {
|
|
6745
|
+
authorization: "Bearer test-token",
|
|
6746
|
+
"content-type": "application/json",
|
|
6747
|
+
},
|
|
6748
|
+
body: JSON.stringify({ phase: "lightSleep", ...body }),
|
|
6749
|
+
});
|
|
6750
|
+
try {
|
|
6751
|
+
assert.equal((await post({})).status, 200);
|
|
6752
|
+
// The window is full; the dry run must pass and leave the count alone.
|
|
6753
|
+
const dry = await post({ dryRun: true });
|
|
6754
|
+
assert.equal(dry.status, 200);
|
|
6755
|
+
// SAFETY: our own server serialized this body; the test reads only the dryRun flag.
|
|
6756
|
+
const dryBody = (await dry.json()) as { dryRun?: boolean };
|
|
6757
|
+
assert.equal(dryBody.dryRun, true);
|
|
6758
|
+
assert.equal((await post({})).status, 429);
|
|
6759
|
+
} finally {
|
|
6760
|
+
await server.stop();
|
|
6761
|
+
}
|
|
6762
|
+
});
|