@remnic/core 10.0.85 → 10.0.87
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
package/src/access-http.ts
CHANGED
|
@@ -417,6 +417,11 @@ export class EngramAccessHttpServer extends ReviewDeckAccessHttpBase {
|
|
|
417
417
|
private readonly resourceMetadataUrl?: string;
|
|
418
418
|
private readonly externalRequestHandler?: SupportPassportExternalRequestHandler;
|
|
419
419
|
private readonly writeLimiter: WriteRateLimiter;
|
|
420
|
+
// Reservations taken by ensureWriteRateLimitAvailable and not yet committed
|
|
421
|
+
// by recordWriteRateLimitHit, keyed by request. The per-request release in
|
|
422
|
+
// the connection callback covers every early return and error path, so a
|
|
423
|
+
// failed or exempt write never leaks its slot (issue #3225 C4).
|
|
424
|
+
private readonly pendingWriteQuotas = new WeakMap<IncomingMessage, WriteRateLimitReservation>();
|
|
420
425
|
private readonly mcpServer: EngramMcpServer;
|
|
421
426
|
private server: Server | null = null;
|
|
422
427
|
private boundPort = 0;
|
|
@@ -544,6 +549,13 @@ export class EngramAccessHttpServer extends ReviewDeckAccessHttpBase {
|
|
|
544
549
|
});
|
|
545
550
|
return;
|
|
546
551
|
}
|
|
552
|
+
if (err.name === "ReplayAdmissionRefusedError") {
|
|
553
|
+
// Durable-capture backpressure (#3251/#1908): the request was
|
|
554
|
+
// never accepted — the buffer is at entry capacity. Retryable,
|
|
555
|
+
// not a server fault (503, not 500).
|
|
556
|
+
this.respondJson(res, 503, { error: err.message, code: "buffer_admission_refused" });
|
|
557
|
+
return;
|
|
558
|
+
}
|
|
547
559
|
if (res.headersSent) {
|
|
548
560
|
res.destroy(err as Error);
|
|
549
561
|
return;
|
|
@@ -556,6 +568,9 @@ export class EngramAccessHttpServer extends ReviewDeckAccessHttpBase {
|
|
|
556
568
|
}).finally(() => {
|
|
557
569
|
req.off("aborted", abortDisconnectedRequest);
|
|
558
570
|
res.off("close", abortDisconnectedRequest);
|
|
571
|
+
// A counted write that failed or was exempt (dry run, replay) must
|
|
572
|
+
// not hold its reserved slot past the request (issue #3225 C4).
|
|
573
|
+
this.releasePendingWriteQuota(req);
|
|
559
574
|
});
|
|
560
575
|
});
|
|
561
576
|
});
|
|
@@ -2739,26 +2754,38 @@ export class EngramAccessHttpServer extends ReviewDeckAccessHttpBase {
|
|
|
2739
2754
|
// DELETE /engram/v1/peers/:id?forget=true — destructive full purge (issue #679 completion)
|
|
2740
2755
|
// GET /engram/v1/peers/:id/profile — get peer profile
|
|
2741
2756
|
if (req.method === "GET" && pathname === "/engram/v1/peers") {
|
|
2742
|
-
|
|
2743
|
-
|
|
2744
|
-
|
|
2757
|
+
const op = getOperation("peer_list"); // boundary dispatch (issue #1525)
|
|
2758
|
+
if (!op) throw new EngramAccessInputError("access-boundary: operation not registered: peer_list");
|
|
2759
|
+
// SAFETY: the registered peer_list handler returns { result } from the service.
|
|
2760
|
+
const output = (await op.run({}, {
|
|
2761
|
+
service: this.service,
|
|
2762
|
+
authenticatedPrincipal: this.resolveRequestPrincipal(req),
|
|
2763
|
+
sourceConnector: this.resolveConnector(req),
|
|
2764
|
+
})) as { result: unknown };
|
|
2765
|
+
this.respondJson(res, 200, output.result);
|
|
2745
2766
|
return;
|
|
2746
2767
|
}
|
|
2747
2768
|
|
|
2748
2769
|
const peerProfileMatch = /^\/engram\/v1\/peers\/([^/]+)\/profile$/.exec(pathname);
|
|
2749
2770
|
if (peerProfileMatch) {
|
|
2750
|
-
this.enforceTokenOp("peer_profile_get"); // boundary dispatch (issue #1525)
|
|
2751
2771
|
if (req.method !== "GET") {
|
|
2752
2772
|
this.respondJson(res, 405, { error: "method_not_allowed", code: "method_not_allowed" });
|
|
2753
2773
|
return;
|
|
2754
2774
|
}
|
|
2775
|
+
const op = getOperation("peer_profile_get"); // boundary dispatch (issue #1525)
|
|
2776
|
+
if (!op) throw new EngramAccessInputError("access-boundary: operation not registered: peer_profile_get");
|
|
2755
2777
|
const peerId = decodePeerIdSegment(peerProfileMatch[1] ?? "");
|
|
2756
|
-
|
|
2757
|
-
|
|
2778
|
+
// SAFETY: the registered peer_profile_get handler returns { result } from the service.
|
|
2779
|
+
const output = (await op.run({ id: peerId }, {
|
|
2780
|
+
service: this.service,
|
|
2781
|
+
authenticatedPrincipal: this.resolveRequestPrincipal(req),
|
|
2782
|
+
sourceConnector: this.resolveConnector(req),
|
|
2783
|
+
})) as { result: { found: boolean } };
|
|
2784
|
+
if (!output.result.found) {
|
|
2758
2785
|
this.respondJson(res, 404, { error: "peer_profile_not_found", code: "peer_profile_not_found" });
|
|
2759
2786
|
return;
|
|
2760
2787
|
}
|
|
2761
|
-
this.respondJson(res, 200, result);
|
|
2788
|
+
this.respondJson(res, 200, output.result);
|
|
2762
2789
|
return;
|
|
2763
2790
|
}
|
|
2764
2791
|
|
|
@@ -2767,47 +2794,49 @@ export class EngramAccessHttpServer extends ReviewDeckAccessHttpBase {
|
|
|
2767
2794
|
const peerId = decodePeerIdSegment(peerIdMatch[1] ?? "");
|
|
2768
2795
|
|
|
2769
2796
|
if (req.method === "GET") {
|
|
2770
|
-
|
|
2771
|
-
|
|
2772
|
-
|
|
2797
|
+
const op = getOperation("peer_get"); // boundary dispatch (issue #1525)
|
|
2798
|
+
if (!op) throw new EngramAccessInputError("access-boundary: operation not registered: peer_get");
|
|
2799
|
+
// SAFETY: the registered peer_get handler returns { result } with a found flag.
|
|
2800
|
+
const output = (await op.run({ id: peerId }, {
|
|
2801
|
+
service: this.service,
|
|
2802
|
+
authenticatedPrincipal: this.resolveRequestPrincipal(req),
|
|
2803
|
+
sourceConnector: this.resolveConnector(req),
|
|
2804
|
+
})) as { result: { found: boolean } };
|
|
2805
|
+
if (!output.result.found) {
|
|
2773
2806
|
this.respondJson(res, 404, { error: "peer_not_found", code: "peer_not_found" });
|
|
2774
2807
|
return;
|
|
2775
2808
|
}
|
|
2776
|
-
this.respondJson(res, 200, result);
|
|
2809
|
+
this.respondJson(res, 200, output.result);
|
|
2777
2810
|
return;
|
|
2778
2811
|
}
|
|
2779
2812
|
|
|
2780
2813
|
if (req.method === "PUT") {
|
|
2781
|
-
this.enforceTokenOp("peer_set"); // boundary dispatch (issue #1525)
|
|
2782
2814
|
const body = await this.readJsonBody(req) as Record<string, unknown>;
|
|
2783
|
-
|
|
2784
|
-
|
|
2785
|
-
//
|
|
2786
|
-
|
|
2787
|
-
|
|
2788
|
-
|
|
2789
|
-
if (
|
|
2790
|
-
"displayName" in body &&
|
|
2791
|
-
body.displayName !== undefined &&
|
|
2792
|
-
typeof body.displayName !== "string"
|
|
2793
|
-
) {
|
|
2794
|
-
throw new EngramAccessInputError("displayName must be a string when provided");
|
|
2795
|
-
}
|
|
2796
|
-
if ("notes" in body && body.notes !== undefined && typeof body.notes !== "string") {
|
|
2797
|
-
throw new EngramAccessInputError("notes must be a string when provided");
|
|
2815
|
+
const op = getOperation("peer_set"); // boundary dispatch (issue #1525)
|
|
2816
|
+
if (!op) throw new EngramAccessInputError("access-boundary: operation not registered: peer_set");
|
|
2817
|
+
// The operation schema strips nulls as "absent", so an explicit null
|
|
2818
|
+
// must fail here or the peer would register with default fields.
|
|
2819
|
+
for (const field of ["kind", "displayName", "notes"]) {
|
|
2820
|
+
if (body[field] === null) throw new EngramAccessInputError(`${field} must be a string when provided`);
|
|
2798
2821
|
}
|
|
2799
|
-
|
|
2822
|
+
// Raw body fields: the operation schema rejects malformed types with an
|
|
2823
|
+
// input error instead of silently defaulting them (rule 51).
|
|
2824
|
+
// SAFETY: the registered peer_set handler returns { result } with the upsert outcome.
|
|
2825
|
+
const output = (await op.run({
|
|
2800
2826
|
id: peerId,
|
|
2801
|
-
kind:
|
|
2802
|
-
displayName:
|
|
2803
|
-
notes:
|
|
2804
|
-
}
|
|
2805
|
-
|
|
2827
|
+
kind: body.kind,
|
|
2828
|
+
displayName: body.displayName,
|
|
2829
|
+
notes: body.notes,
|
|
2830
|
+
}, {
|
|
2831
|
+
service: this.service,
|
|
2832
|
+
authenticatedPrincipal: this.resolveRequestPrincipal(req),
|
|
2833
|
+
sourceConnector: this.resolveConnector(req),
|
|
2834
|
+
})) as { result: { created: boolean } };
|
|
2835
|
+
this.respondJson(res, output.result.created ? 201 : 200, output.result);
|
|
2806
2836
|
return;
|
|
2807
2837
|
}
|
|
2808
2838
|
|
|
2809
2839
|
if (req.method === "DELETE") {
|
|
2810
|
-
this.enforceTokenOp("peer_delete"); // boundary dispatch (issue #1525)
|
|
2811
2840
|
// `?forget=true` triggers the destructive full-purge path (issue #679
|
|
2812
2841
|
// completion). The caller must also pass `confirm=yes` in the request
|
|
2813
2842
|
// body; absent confirmation yields 400. Plain DELETE (no ?forget) keeps
|
|
@@ -2824,12 +2853,26 @@ export class EngramAccessHttpServer extends ReviewDeckAccessHttpBase {
|
|
|
2824
2853
|
});
|
|
2825
2854
|
return;
|
|
2826
2855
|
}
|
|
2827
|
-
const
|
|
2828
|
-
|
|
2856
|
+
const op = getOperation("peer_forget"); // boundary dispatch (issue #1525)
|
|
2857
|
+
if (!op) throw new EngramAccessInputError("access-boundary: operation not registered: peer_forget");
|
|
2858
|
+
// SAFETY: the registered peer_forget handler returns { result } with the purge outcome.
|
|
2859
|
+
const output = (await op.run({ id: peerId, confirm: "yes" }, {
|
|
2860
|
+
service: this.service,
|
|
2861
|
+
authenticatedPrincipal: this.resolveRequestPrincipal(req),
|
|
2862
|
+
sourceConnector: this.resolveConnector(req),
|
|
2863
|
+
})) as { result: unknown };
|
|
2864
|
+
this.respondJson(res, 200, output.result);
|
|
2829
2865
|
return;
|
|
2830
2866
|
}
|
|
2831
|
-
const
|
|
2832
|
-
|
|
2867
|
+
const op = getOperation("peer_delete"); // boundary dispatch (issue #1525)
|
|
2868
|
+
if (!op) throw new EngramAccessInputError("access-boundary: operation not registered: peer_delete");
|
|
2869
|
+
// SAFETY: the registered peer_delete handler returns { result } with the delete outcome.
|
|
2870
|
+
const output = (await op.run({ id: peerId }, {
|
|
2871
|
+
service: this.service,
|
|
2872
|
+
authenticatedPrincipal: this.resolveRequestPrincipal(req),
|
|
2873
|
+
sourceConnector: this.resolveConnector(req),
|
|
2874
|
+
})) as { result: unknown };
|
|
2875
|
+
this.respondJson(res, 200, output.result);
|
|
2833
2876
|
return;
|
|
2834
2877
|
}
|
|
2835
2878
|
|
|
@@ -3931,16 +3974,48 @@ export class EngramAccessHttpServer extends ReviewDeckAccessHttpBase {
|
|
|
3931
3974
|
return 200;
|
|
3932
3975
|
}
|
|
3933
3976
|
|
|
3977
|
+
/**
|
|
3978
|
+
* Admit a quota-counted write by RESERVING a limiter slot for the request
|
|
3979
|
+
* (issue #3225 C4). A bare hasCapacity check admits N concurrent requests
|
|
3980
|
+
* against the same committed count; the reservation holds in-flight
|
|
3981
|
+
* capacity until the write commits at recordWriteRateLimitHit or the
|
|
3982
|
+
* request ends. Idempotent per request, so a quota hook that fires after
|
|
3983
|
+
* an earlier reservation is a no-op.
|
|
3984
|
+
*/
|
|
3934
3985
|
protected ensureWriteRateLimitAvailable(req?: IncomingMessage): void {
|
|
3935
|
-
if (!
|
|
3986
|
+
if (!req) {
|
|
3987
|
+
if (!this.writeLimiter.hasCapacity()) {
|
|
3988
|
+
throw new HttpError(429, "write_rate_limited", "write_rate_limited");
|
|
3989
|
+
}
|
|
3990
|
+
return;
|
|
3991
|
+
}
|
|
3992
|
+
if (this.pendingWriteQuotas.has(req)) return;
|
|
3993
|
+
const reservation = this.writeLimiter.reserve(this.principalForRateLimit(req));
|
|
3994
|
+
if (!reservation) {
|
|
3936
3995
|
throw new HttpError(429, "write_rate_limited", "write_rate_limited");
|
|
3937
3996
|
}
|
|
3997
|
+
this.pendingWriteQuotas.set(req, reservation);
|
|
3938
3998
|
}
|
|
3939
3999
|
|
|
4000
|
+
/** Commit a counted write at the durable boundary; consumes the request's reservation. */
|
|
3940
4001
|
protected recordWriteRateLimitHit(req?: IncomingMessage): void {
|
|
4002
|
+
const pending = req ? this.pendingWriteQuotas.get(req) : undefined;
|
|
4003
|
+
if (pending && req) {
|
|
4004
|
+
this.pendingWriteQuotas.delete(req);
|
|
4005
|
+
pending.commit();
|
|
4006
|
+
return;
|
|
4007
|
+
}
|
|
3941
4008
|
this.writeLimiter.record(this.principalForRateLimit(req));
|
|
3942
4009
|
}
|
|
3943
4010
|
|
|
4011
|
+
/** Release an uncommitted reservation after a rejection, failure, or exempt outcome. */
|
|
4012
|
+
private releasePendingWriteQuota(req: IncomingMessage): void {
|
|
4013
|
+
const pending = this.pendingWriteQuotas.get(req);
|
|
4014
|
+
if (!pending) return;
|
|
4015
|
+
this.pendingWriteQuotas.delete(req);
|
|
4016
|
+
pending.release();
|
|
4017
|
+
}
|
|
4018
|
+
|
|
3944
4019
|
protected reserveWriteRateLimitSlot(req?: IncomingMessage): WriteRateLimitReservation {
|
|
3945
4020
|
const reservation = this.writeLimiter.reserve(this.principalForRateLimit(req));
|
|
3946
4021
|
if (!reservation) {
|
package/src/access-mcp.test.ts
CHANGED
|
@@ -109,6 +109,46 @@ function makeToolRequest(name: string, args: Record<string, unknown> = {}): Reco
|
|
|
109
109
|
},
|
|
110
110
|
};
|
|
111
111
|
}
|
|
112
|
+
test("MCP reports buffer admission refusal as retryable and does not return success", async () => {
|
|
113
|
+
let attempts = 0;
|
|
114
|
+
const refusal = new Error("buffer at session-entry cap");
|
|
115
|
+
refusal.name = "ReplayAdmissionRefusedError";
|
|
116
|
+
// SAFETY: the mock overrides only observe; every other MCP dispatch path is unreachable in this test.
|
|
117
|
+
const service = Object.assign({} as EngramAccessService, {
|
|
118
|
+
observe: async () => {
|
|
119
|
+
attempts += 1;
|
|
120
|
+
if (attempts === 1) throw refusal;
|
|
121
|
+
return { ok: true };
|
|
122
|
+
},
|
|
123
|
+
});
|
|
124
|
+
const server = new EngramMcpServer(service);
|
|
125
|
+
const request = makeToolRequest("engram.observe", {
|
|
126
|
+
sessionKey: "capacity-test-session",
|
|
127
|
+
messages: [{ role: "user", content: "retry me" }],
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
const refused = await server.handleRequest(request);
|
|
131
|
+
// SAFETY: a tools/call request always resolves to a JSON-RPC result envelope; only isError and structuredContent are asserted.
|
|
132
|
+
const refusedResult = refused as {
|
|
133
|
+
result?: {
|
|
134
|
+
isError?: boolean;
|
|
135
|
+
structuredContent?: { code?: string; retryable?: boolean };
|
|
136
|
+
};
|
|
137
|
+
};
|
|
138
|
+
assert.equal(refusedResult.result?.isError, true, "refused admission must not be acknowledged as success");
|
|
139
|
+
// SAFETY: same tools/call result envelope as the assertion above.
|
|
140
|
+
assert.deepEqual(refusedResult.result?.structuredContent, {
|
|
141
|
+
code: "buffer_admission_refused",
|
|
142
|
+
retryable: true,
|
|
143
|
+
}, JSON.stringify(refused));
|
|
144
|
+
|
|
145
|
+
const retried = await server.handleRequest(request);
|
|
146
|
+
// SAFETY: same tools/call result envelope as refusedResult above.
|
|
147
|
+
const retriedResult = retried as typeof refusedResult;
|
|
148
|
+
assert.equal(retriedResult.result?.isError, false);
|
|
149
|
+
assert.equal(attempts, 2, "the same request can be retried after capacity is available");
|
|
150
|
+
});
|
|
151
|
+
|
|
112
152
|
|
|
113
153
|
// ──────────────────────────────────────────────────────────────────────────
|
|
114
154
|
// Finding 2 (#396 new): invalid MCP briefing format values must be rejected
|
|
@@ -1295,6 +1335,17 @@ const FLEET_WIDE_MAINTENANCE_TOOLS = [
|
|
|
1295
1335
|
"compounding_promote_candidate",
|
|
1296
1336
|
"compression_guidelines_optimize",
|
|
1297
1337
|
"compression_guidelines_activate",
|
|
1338
|
+
// Issue #3225 B3: work and peer ops run against GLOBAL stores, so a
|
|
1339
|
+
// namespace argument cannot isolate them — same fleet-wide rule.
|
|
1340
|
+
"work_task",
|
|
1341
|
+
"work_project",
|
|
1342
|
+
"work_board",
|
|
1343
|
+
"peer_list",
|
|
1344
|
+
"peer_get",
|
|
1345
|
+
"peer_set",
|
|
1346
|
+
"peer_delete",
|
|
1347
|
+
"peer_profile_get",
|
|
1348
|
+
"peer_forget",
|
|
1298
1349
|
] as const;
|
|
1299
1350
|
|
|
1300
1351
|
test("MCP fleet-wide maintenance ops: namespace-scoped token denied for every op, no side effect (issue #1850 round 10)", async () => {
|
|
@@ -1319,6 +1370,19 @@ test("MCP fleet-wide maintenance ops: namespace-scoped token denied for every op
|
|
|
1319
1370
|
compressionGuidelinesOptimize: track,
|
|
1320
1371
|
compressionGuidelinesActivate: track,
|
|
1321
1372
|
} as unknown as EngramAccessService;
|
|
1373
|
+
// Issue #3225 B3: track the global-store methods the fleet-wide guard must
|
|
1374
|
+
// block; the dispatch reads them off this same object at call time.
|
|
1375
|
+
Object.assign(service, {
|
|
1376
|
+
workTask: track,
|
|
1377
|
+
workProject: track,
|
|
1378
|
+
workBoard: track,
|
|
1379
|
+
peerList: track,
|
|
1380
|
+
peerGet: track,
|
|
1381
|
+
peerSet: track,
|
|
1382
|
+
peerDelete: track,
|
|
1383
|
+
peerProfileGet: track,
|
|
1384
|
+
peerForget: track,
|
|
1385
|
+
});
|
|
1322
1386
|
const server = new EngramMcpServer(service, { principal: "ops" });
|
|
1323
1387
|
try {
|
|
1324
1388
|
for (const tool of FLEET_WIDE_MAINTENANCE_TOOLS) {
|
package/src/access-mcp.ts
CHANGED
|
@@ -84,6 +84,13 @@ type JsonRpcRequest = {
|
|
|
84
84
|
params?: Record<string, unknown>;
|
|
85
85
|
};
|
|
86
86
|
|
|
87
|
+
/** tools/call failure envelope; admission backpressure adds a retryable code. */
|
|
88
|
+
type McpToolErrorResult = {
|
|
89
|
+
content: Array<{ type: string; text: string }>;
|
|
90
|
+
isError: boolean;
|
|
91
|
+
structuredContent?: { code: string; retryable: boolean };
|
|
92
|
+
};
|
|
93
|
+
|
|
87
94
|
function throwMcpAbort(signal: AbortSignal | undefined, message: string): void {
|
|
88
95
|
if (!signal?.aborted) return;
|
|
89
96
|
if (isAbortError(signal.reason)) throw signal.reason;
|
|
@@ -2418,14 +2425,17 @@ export class EngramMcpServer {
|
|
|
2418
2425
|
// Preserve the original AbortError so HTTP can silently end a dead socket.
|
|
2419
2426
|
if (isAbortError(err)) throw err;
|
|
2420
2427
|
const message = mcpToolErrorMessage(err instanceof Error ? err : new Error(String(err)));
|
|
2421
|
-
|
|
2422
|
-
|
|
2423
|
-
|
|
2424
|
-
|
|
2425
|
-
|
|
2426
|
-
|
|
2427
|
-
},
|
|
2428
|
+
const admissionBackpressure =
|
|
2429
|
+
err instanceof Error &&
|
|
2430
|
+
(err.name === "ReplayAdmissionRefusedError" || err.name === "BufferAdmissionRefusedError");
|
|
2431
|
+
const result: McpToolErrorResult = {
|
|
2432
|
+
content: [{ type: "text", text: message }],
|
|
2433
|
+
isError: true,
|
|
2428
2434
|
};
|
|
2435
|
+
if (admissionBackpressure) {
|
|
2436
|
+
result.structuredContent = { code: "buffer_admission_refused", retryable: true };
|
|
2437
|
+
}
|
|
2438
|
+
return { jsonrpc: "2.0", id, result };
|
|
2429
2439
|
}
|
|
2430
2440
|
}
|
|
2431
2441
|
|
|
@@ -32,7 +32,7 @@ import { EngramAccessService } from "./access-service.js";
|
|
|
32
32
|
import { SmartBuffer } from "./buffer.js";
|
|
33
33
|
import { ExtractionDeadlineError } from "./orchestration/extraction-run.js";
|
|
34
34
|
import { runExtractionDeferRetention } from "./orchestration/extraction-run-helpers.js";
|
|
35
|
-
import { TurnIngestionCoordinator, type TurnIngestionDeps } from "./orchestration/turn-ingestion.js";
|
|
35
|
+
import { ReplayAdmissionRefusedError, TurnIngestionCoordinator, type TurnIngestionDeps } from "./orchestration/turn-ingestion.js";
|
|
36
36
|
import { resolveScopeProfilePlan } from "./namespaces/scope-profiles.js";
|
|
37
37
|
import { resolveCodingNamespaceOverlay } from "./coding/coding-namespace.js";
|
|
38
38
|
import type { ResolvedScopeProfilePlan } from "./namespaces/scope-profiles.js";
|
|
@@ -378,6 +378,40 @@ test("#3251 buffered observe append is durable before accept: a failed save reje
|
|
|
378
378
|
await cleanupDir(memoryDir);
|
|
379
379
|
}
|
|
380
380
|
});
|
|
381
|
+
test("#3251 multi-session refusal is atomic: earlier sessions are not partially accepted", async () => {
|
|
382
|
+
const config = parseConfig({
|
|
383
|
+
bufferMaxSessions: 3,
|
|
384
|
+
bufferMaxTurns: 100,
|
|
385
|
+
triggerMode: "every_n",
|
|
386
|
+
extractionMinChars: 1_000_000,
|
|
387
|
+
extractionMinUserTurns: 1000,
|
|
388
|
+
});
|
|
389
|
+
// SAFETY: SeedStorage implements the loadBuffer/saveBuffer surface this fixture drives.
|
|
390
|
+
const buffer = new SmartBuffer(config, new SeedStorage() as StorageManager);
|
|
391
|
+
const occupied = makeReplayTurn("occupied", "existing backlog");
|
|
392
|
+
await buffer.addTurn("occupied", {
|
|
393
|
+
role: occupied.role,
|
|
394
|
+
content: occupied.content,
|
|
395
|
+
timestamp: occupied.timestamp,
|
|
396
|
+
sessionKey: occupied.sessionKey,
|
|
397
|
+
});
|
|
398
|
+
const coordinator = makeTurnIngestionDouble(config, buffer, []);
|
|
399
|
+
|
|
400
|
+
await assert.rejects(
|
|
401
|
+
coordinator.ingestReplayBatch(
|
|
402
|
+
[
|
|
403
|
+
makeReplayTurn("fresh-a", "first in batch"),
|
|
404
|
+
makeReplayTurn("fresh-b", "second in batch"),
|
|
405
|
+
],
|
|
406
|
+
bufferedObserveOptions({ writeNamespaceOverride: "default", sessionOwnerPrincipal: "alice" }),
|
|
407
|
+
),
|
|
408
|
+
ReplayAdmissionRefusedError,
|
|
409
|
+
);
|
|
410
|
+
|
|
411
|
+
assert.equal(buffer.getTurns("fresh-a").length, 0, "refused batch must not durably accept its first session");
|
|
412
|
+
assert.equal(buffer.getTurns("fresh-b").length, 0, "refused batch must not durably accept its second session");
|
|
413
|
+
assert.equal(buffer.getTurns("occupied").length, 1, "pre-existing backlog remains intact");
|
|
414
|
+
});
|
|
381
415
|
|
|
382
416
|
test("#3251 durable observe admission is bounded: at the entry cap a new session is refused, not evicted", async () => {
|
|
383
417
|
// Real SmartBuffer + real coordinator, NO Orchestrator background actors —
|
|
@@ -413,8 +447,10 @@ test("#3251 durable observe admission is bounded: at the entry cap a new session
|
|
|
413
447
|
// (buffer.ts), each holding an accepted, durably pending capture. The
|
|
414
448
|
// seed turns are PINNED accepted captures — cap pressure must refuse new
|
|
415
449
|
// captures, never evict these (#3251 eviction guard; the pre-existing
|
|
416
|
-
// `default` mirror entry also occupies the entries map).
|
|
417
|
-
|
|
450
|
+
// `default` mirror entry also occupies the entries map). 199 seeds +
|
|
451
|
+
// `default` = 200 entries: the reachable cap boundary (durable ingress
|
|
452
|
+
// refuses a 200th seed once pinned entries fill the cap).
|
|
453
|
+
for (let index = 0; index < 199; index += 1) {
|
|
418
454
|
const key = `seed-${index}`;
|
|
419
455
|
await buffer.addTurn(key, {
|
|
420
456
|
role: "user",
|
|
@@ -452,7 +488,7 @@ test("#3251 durable observe admission is bounded: at the entry cap a new session
|
|
|
452
488
|
assert.equal(queued.length, 0, "refused capture must not queue extraction");
|
|
453
489
|
|
|
454
490
|
const snapshot = await buffer.getBufferSnapshot();
|
|
455
|
-
assert.equal(snapshot.bufferedSessionCount,
|
|
491
|
+
assert.equal(snapshot.bufferedSessionCount, 199, "refusal must not evict accepted captures");
|
|
456
492
|
assert.ok(
|
|
457
493
|
buffer.getTurns("seed-0").some((turn) => turn.content.includes("accepted seed capture 0")),
|
|
458
494
|
);
|
|
@@ -14,7 +14,7 @@ import { z } from "zod";
|
|
|
14
14
|
import type { DreamsPhase, RecallDisclosure, RecallPlanMode } from "./types.js";
|
|
15
15
|
import type { RemnicChatGptMemoryInspectorInput } from "./mcp-memory-inspector-app.js";
|
|
16
16
|
import { defineOperation } from "./access-boundary.js";
|
|
17
|
-
import { EngramAccessForbiddenError } from "./access-errors.js";
|
|
17
|
+
import { EngramAccessForbiddenError, rejectReservedArgumentKey } from "./access-errors.js";
|
|
18
18
|
import {
|
|
19
19
|
type ObserveRequest,
|
|
20
20
|
observeRequestSchema,
|
|
@@ -55,11 +55,15 @@ import { normalizeDreamsStatusWindowHours } from "./maintenance/dreams-ledger.js
|
|
|
55
55
|
// Shared helpers
|
|
56
56
|
// ---------------------------------------------------------------------------
|
|
57
57
|
|
|
58
|
+
// Object.create(null): a plain {} lets a copied own `__proto__` member invoke
|
|
59
|
+
// the prototype setter, so an inherited field could reach the zod parse
|
|
60
|
+
// (issue #3225 B1).
|
|
58
61
|
function stripNulls(data: unknown): unknown {
|
|
59
62
|
if (data !== null && typeof data === "object" && !Array.isArray(data)) {
|
|
60
63
|
const obj = data as Record<string, unknown>;
|
|
61
|
-
const cleaned
|
|
64
|
+
const cleaned = Object.create(null);
|
|
62
65
|
for (const [key, value] of Object.entries(obj)) {
|
|
66
|
+
rejectReservedArgumentKey(key);
|
|
63
67
|
if (value !== null) cleaned[key] = value;
|
|
64
68
|
}
|
|
65
69
|
return cleaned;
|
|
@@ -79,8 +83,9 @@ function strictSchema<T extends z.ZodRawShape>(shape: T): z.ZodType<Record<strin
|
|
|
79
83
|
*/
|
|
80
84
|
function stripNullsExcept(data: unknown, preserve: readonly string[]): unknown {
|
|
81
85
|
if (data !== null && typeof data === "object" && !Array.isArray(data)) {
|
|
82
|
-
const cleaned
|
|
86
|
+
const cleaned = Object.create(null);
|
|
83
87
|
for (const [key, value] of Object.entries(data as Record<string, unknown>)) {
|
|
88
|
+
rejectReservedArgumentKey(key);
|
|
84
89
|
if (value !== null || preserve.includes(key)) cleaned[key] = value;
|
|
85
90
|
}
|
|
86
91
|
return cleaned;
|
|
@@ -328,9 +333,15 @@ defineOperation({ name: "identity_anchor_update", description: "Update identity
|
|
|
328
333
|
defineOperation({ name: "memory_identity", description: "Memory identity.", schema: strictSchema({ namespace: S.str }), handler: async (input, ctx) => ({ result: await ctx.service.memoryIdentity({ namespace: optStr(input.namespace), principal: ctx.authenticatedPrincipal }) }) });
|
|
329
334
|
|
|
330
335
|
// === WORK/SHARED ===
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
defineOperation({ name: "
|
|
336
|
+
// SAFETY: optStr yields undefined or a string; workTask rejects any action
|
|
337
|
+
// string outside its union with an input error before touching a store.
|
|
338
|
+
defineOperation({ name: "work_task", description: "Manage work task.", fleetWide: true, schema: strictSchema({ action: S.str, id: S.str, title: S.str, description: S.str, status: S.str, priority: S.str, owner: S.str, assignee: S.str, projectId: S.str, tags: S.strArr, dueAt: S.str }), handler: async (input, ctx) => ({ result: await ctx.service.workTask({ action: (optStr(input.action) as "update" | "list" | "get" | "create" | "delete" | "transition") ?? "list", id: optStr(input.id), title: optStr(input.title), description: optStr(input.description), status: optStr(input.status), priority: optStr(input.priority), owner: optStr(input.owner), assignee: optStr(input.assignee), projectId: optStr(input.projectId), tags: optStrArr(input.tags), dueAt: optStr(input.dueAt) }) }) });
|
|
339
|
+
// SAFETY: optStr yields undefined or a string; workProject rejects any action
|
|
340
|
+
// string outside its union with an input error before touching a store.
|
|
341
|
+
defineOperation({ name: "work_project", description: "Manage work project.", fleetWide: true, schema: strictSchema({ action: S.str, id: S.str, name: S.str, description: S.str, status: S.str, owner: S.str, tags: S.strArr, taskId: S.str, projectId: S.str }), handler: async (input, ctx) => ({ result: await ctx.service.workProject({ action: (optStr(input.action) as "update" | "list" | "get" | "create" | "delete" | "link_task") ?? "list", id: optStr(input.id), name: optStr(input.name), description: optStr(input.description), status: optStr(input.status), owner: optStr(input.owner), tags: optStrArr(input.tags), taskId: optStr(input.taskId), projectId: optStr(input.projectId) }) }) });
|
|
342
|
+
// SAFETY: optStr yields undefined or a string; workBoard rejects any action
|
|
343
|
+
// string outside its union with an input error before touching a store.
|
|
344
|
+
defineOperation({ name: "work_board", description: "Manage work board.", fleetWide: true, schema: strictSchema({ action: S.str, projectId: S.str, snapshotJson: S.str, linkToMemory: S.bool }), handler: async (input, ctx) => ({ result: await ctx.service.workBoard({ action: (optStr(input.action) as "export_markdown" | "export_snapshot" | "import_snapshot") ?? "export_markdown", projectId: optStr(input.projectId) ?? "", snapshotJson: optStr(input.snapshotJson) ?? "", linkToMemory: input.linkToMemory === true }) }) });
|
|
334
345
|
// Issue #2920: the envelope controls (authority/expiresAt/supersedes) are
|
|
335
346
|
// parsed by the ONE canonical surface module shared with the OpenClaw tool
|
|
336
347
|
// (write-output-controls.ts); semantics stay in composeWriteEnvelope. A
|
|
@@ -451,12 +462,12 @@ defineOperation({ name: "memory_summarize_hourly", description: "Hourly summariz
|
|
|
451
462
|
defineOperation({ name: "conversation_index_update", description: "Update conv index.", fleetWide: true, schema: strictSchema({ sessionKey: S.str, hours: S.num, embed: S.bool }), handler: async (input, ctx) => { if (input.sessionKey !== undefined && typeof input.sessionKey !== "string") throw new EngramAccessInputError("sessionKey must be a string when provided"); return { result: await ctx.service.conversationIndexUpdate({ sessionKey: optStr(input.sessionKey), hours: optNum(input.hours), embed: optBool(input.embed) }) }; } });
|
|
452
463
|
defineOperation({ name: "profiling_report", description: "Profiling report.", schema: strictSchema({ format: S.str, limit: S.num }), handler: async (input, ctx) => { if (input.format !== undefined && typeof input.format !== "string") throw new EngramAccessInputError("format must be a string when provided"); if (input.limit !== undefined && typeof input.limit !== "number") throw new EngramAccessInputError("limit must be a number when provided"); return { result: await ctx.service.profilingReport({ format: optStr(input.format), limit: optNum(input.limit) }) }; } });
|
|
453
464
|
defineOperation({ name: "live_connectors_run", description: "Run connectors.", fleetWide: true, schema: strictSchema({ force: S.bool }), handler: async (input, ctx) => ({ result: await ctx.service.liveConnectorsRun({ authenticatedPrincipal: ctx.authenticatedPrincipal, force: input.force === true }, ctx.authenticatedPrincipal) }) });
|
|
454
|
-
defineOperation({ name: "peer_list", description: "List peers.", schema: strictSchema({}), handler: async (_i, ctx) => ({ result: await ctx.service.peerList() }) });
|
|
455
|
-
defineOperation({ name: "peer_get", description: "Get peer.", schema: strictSchema({ id: S.str }), handler: async (input, ctx) => { const id = defStr(input.id, ""); if (!id) throw new EngramAccessInputError("peer_get: id is required"); return { result: await ctx.service.peerGet(id) }; } });
|
|
456
|
-
defineOperation({ name: "peer_set", description: "Set peer.", schema: strictSchema({ id: S.str, kind: S.str, displayName: S.str, notes: S.str }), handler: async (input, ctx) => { const id = defStr(input.id, ""); if (!id) throw new EngramAccessInputError("peer_set: id is required");
|
|
457
|
-
defineOperation({ name: "peer_delete", description: "Delete peer.", schema: strictSchema({ id: S.str }), handler: async (input, ctx) => { const id = defStr(input.id, ""); if (!id) throw new EngramAccessInputError("peer_delete: id is required"); return { result: await ctx.service.peerDelete(id) }; } });
|
|
458
|
-
defineOperation({ name: "peer_profile_get", description: "Get peer profile.", schema: strictSchema({ id: S.str }), handler: async (input, ctx) => { const id = defStr(input.id, ""); if (!id) throw new EngramAccessInputError("peer_profile_get: id is required"); return { result: await ctx.service.peerProfileGet(id) }; } });
|
|
459
|
-
defineOperation({ name: "peer_forget", description: "Forget peer.", schema: strictSchema({ id: S.str, confirm: S.str }), handler: async (input, ctx) => { const id = defStr(input.id, ""); if (!id) throw new EngramAccessInputError("peer_forget: id is required"); const c = optStr(input.confirm) ?? ""; if (c !== "yes") throw new EngramAccessInputError("peer_forget: confirm must be 'yes' to prevent accidental data loss"); return { result: await ctx.service.peerForget(id, { confirm: "yes" }) }; } });
|
|
465
|
+
defineOperation({ name: "peer_list", description: "List peers.", fleetWide: true, schema: strictSchema({}), handler: async (_i, ctx) => ({ result: await ctx.service.peerList() }) });
|
|
466
|
+
defineOperation({ name: "peer_get", description: "Get peer.", fleetWide: true, schema: strictSchema({ id: S.str }), handler: async (input, ctx) => { const id = defStr(input.id, ""); if (!id) throw new EngramAccessInputError("peer_get: id is required"); return { result: await ctx.service.peerGet(id) }; } });
|
|
467
|
+
defineOperation({ name: "peer_set", description: "Set peer.", fleetWide: true, schema: strictSchema({ id: S.str, kind: S.str, displayName: S.str, notes: S.str }), handler: async (input, ctx) => { const id = defStr(input.id, ""); if (!id) throw new EngramAccessInputError("peer_set: id is required"); return { result: await ctx.service.peerSet({ id, kind: optStr(input.kind), displayName: optStr(input.displayName), notes: optStr(input.notes) }) }; } });
|
|
468
|
+
defineOperation({ name: "peer_delete", description: "Delete peer.", fleetWide: true, schema: strictSchema({ id: S.str }), handler: async (input, ctx) => { const id = defStr(input.id, ""); if (!id) throw new EngramAccessInputError("peer_delete: id is required"); return { result: await ctx.service.peerDelete(id) }; } });
|
|
469
|
+
defineOperation({ name: "peer_profile_get", description: "Get peer profile.", fleetWide: true, schema: strictSchema({ id: S.str }), handler: async (input, ctx) => { const id = defStr(input.id, ""); if (!id) throw new EngramAccessInputError("peer_profile_get: id is required"); return { result: await ctx.service.peerProfileGet(id) }; } });
|
|
470
|
+
defineOperation({ name: "peer_forget", description: "Forget peer.", fleetWide: true, schema: strictSchema({ id: S.str, confirm: S.str }), handler: async (input, ctx) => { const id = defStr(input.id, ""); if (!id) throw new EngramAccessInputError("peer_forget: id is required"); const c = optStr(input.confirm) ?? ""; if (c !== "yes") throw new EngramAccessInputError("peer_forget: confirm must be 'yes' to prevent accidental data loss"); return { result: await ctx.service.peerForget(id, { confirm: "yes" }) }; } });
|
|
460
471
|
defineOperation({ name: "console_state", description: "Console state.", schema: strictSchema({ namespace: S.str }), handler: async (input, ctx) => ({ result: await ctx.service.consoleState(optStr(input.namespace), ctx.authenticatedPrincipal) }) });
|
|
461
472
|
defineOperation({ name: "dreams_status", description: "Dreams status.", schema: strictSchema({ windowHours: S.num, namespace: S.str }), handler: async (input, ctx) => { let wh = 24; try { wh = normalizeDreamsStatusWindowHours(input.windowHours); } catch { throw new EngramAccessInputError("dreams_status: windowHours must be a positive integer. Got: " + String(input.windowHours)); } return { result: await ctx.service.dreamsStatus({ windowHours: wh, namespace: optStr(input.namespace), principal: ctx.authenticatedPrincipal }) }; } });
|
|
462
473
|
defineOperation({ name: "dreams_run", description: "Run dreams.", schema: strictSchema({ phase: S.str, dryRun: S.bool, namespace: S.str }), handler: async (input, ctx) => { const VP = ["lightSleep", "rem", "deepSleep"]; const ph = optStr(input.phase) ?? ""; if (!ph || !VP.includes(ph)) throw new EngramAccessInputError("dreams_run: phase must be one of: " + VP.join(", ")); if (input.dryRun !== undefined && typeof input.dryRun !== "boolean") throw new EngramAccessInputError("dreams_run: dryRun must be boolean"); return { result: await ctx.service.dreamsRun({ phase: ph as DreamsPhase, dryRun: input.dryRun === true, namespace: optStr(input.namespace), authenticatedPrincipal: ctx.authenticatedPrincipal }) }; } });
|