@yolk-sdk/emulators 0.1.0-canary.96
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/LICENSE +21 -0
- package/README.md +2135 -0
- package/dist/anthropic.d.mts +269 -0
- package/dist/anthropic.d.mts.map +1 -0
- package/dist/anthropic.mjs +177 -0
- package/dist/anthropic.mjs.map +1 -0
- package/dist/chat-completions.d.mts +288 -0
- package/dist/chat-completions.d.mts.map +1 -0
- package/dist/chat-completions.mjs +451 -0
- package/dist/chat-completions.mjs.map +1 -0
- package/dist/codex.d.mts +265 -0
- package/dist/codex.d.mts.map +1 -0
- package/dist/codex.mjs +199 -0
- package/dist/codex.mjs.map +1 -0
- package/dist/dropbox/api.d.mts +63 -0
- package/dist/dropbox/api.d.mts.map +1 -0
- package/dist/dropbox/api.mjs +565 -0
- package/dist/dropbox/api.mjs.map +1 -0
- package/dist/dropbox/state.d.mts +136 -0
- package/dist/dropbox/state.d.mts.map +1 -0
- package/dist/dropbox/state.mjs +209 -0
- package/dist/dropbox/state.mjs.map +1 -0
- package/dist/dropbox.d.mts +79 -0
- package/dist/dropbox.d.mts.map +1 -0
- package/dist/dropbox.mjs +124 -0
- package/dist/dropbox.mjs.map +1 -0
- package/dist/email-fixtures.d.mts +36 -0
- package/dist/email-fixtures.d.mts.map +1 -0
- package/dist/email-fixtures.mjs +1080 -0
- package/dist/email-fixtures.mjs.map +1 -0
- package/dist/email.d.mts +160 -0
- package/dist/email.d.mts.map +1 -0
- package/dist/email.mjs +608 -0
- package/dist/email.mjs.map +1 -0
- package/dist/emulator-compose.d.mts +40 -0
- package/dist/emulator-compose.d.mts.map +1 -0
- package/dist/emulator-compose.mjs +91 -0
- package/dist/emulator-compose.mjs.map +1 -0
- package/dist/emulator-http.d.mts +53 -0
- package/dist/emulator-http.d.mts.map +1 -0
- package/dist/emulator-http.mjs +117 -0
- package/dist/emulator-http.mjs.map +1 -0
- package/dist/emulator-kernel.d.mts +176 -0
- package/dist/emulator-kernel.d.mts.map +1 -0
- package/dist/emulator-kernel.mjs +413 -0
- package/dist/emulator-kernel.mjs.map +1 -0
- package/dist/fixture-route.d.mts +129 -0
- package/dist/fixture-route.d.mts.map +1 -0
- package/dist/fixture-route.mjs +257 -0
- package/dist/fixture-route.mjs.map +1 -0
- package/dist/fortnox/api.d.mts +92 -0
- package/dist/fortnox/api.d.mts.map +1 -0
- package/dist/fortnox/api.mjs +750 -0
- package/dist/fortnox/api.mjs.map +1 -0
- package/dist/fortnox/state.d.mts +533 -0
- package/dist/fortnox/state.d.mts.map +1 -0
- package/dist/fortnox/state.mjs +612 -0
- package/dist/fortnox/state.mjs.map +1 -0
- package/dist/fortnox.d.mts +128 -0
- package/dist/fortnox.d.mts.map +1 -0
- package/dist/fortnox.mjs +403 -0
- package/dist/fortnox.mjs.map +1 -0
- package/dist/gateway-evaluate-recordings.d.mts +12 -0
- package/dist/gateway-evaluate-recordings.d.mts.map +1 -0
- package/dist/gateway-evaluate-recordings.mjs +128 -0
- package/dist/gateway-evaluate-recordings.mjs.map +1 -0
- package/dist/gateway.d.mts +272 -0
- package/dist/gateway.d.mts.map +1 -0
- package/dist/gateway.mjs +400 -0
- package/dist/gateway.mjs.map +1 -0
- package/dist/github/api.d.mts +63 -0
- package/dist/github/api.d.mts.map +1 -0
- package/dist/github/api.mjs +527 -0
- package/dist/github/api.mjs.map +1 -0
- package/dist/github/state.d.mts +272 -0
- package/dist/github/state.d.mts.map +1 -0
- package/dist/github/state.mjs +303 -0
- package/dist/github/state.mjs.map +1 -0
- package/dist/github.d.mts +77 -0
- package/dist/github.d.mts.map +1 -0
- package/dist/github.mjs +180 -0
- package/dist/github.mjs.map +1 -0
- package/dist/google/calendar.d.mts +10 -0
- package/dist/google/calendar.d.mts.map +1 -0
- package/dist/google/calendar.mjs +252 -0
- package/dist/google/calendar.mjs.map +1 -0
- package/dist/google/drive.d.mts +8 -0
- package/dist/google/drive.d.mts.map +1 -0
- package/dist/google/drive.mjs +252 -0
- package/dist/google/drive.mjs.map +1 -0
- package/dist/google/gmail.d.mts +10 -0
- package/dist/google/gmail.d.mts.map +1 -0
- package/dist/google/gmail.mjs +591 -0
- package/dist/google/gmail.mjs.map +1 -0
- package/dist/google/shared.d.mts +107 -0
- package/dist/google/shared.d.mts.map +1 -0
- package/dist/google/shared.mjs +146 -0
- package/dist/google/shared.mjs.map +1 -0
- package/dist/google/state.d.mts +385 -0
- package/dist/google/state.d.mts.map +1 -0
- package/dist/google/state.mjs +496 -0
- package/dist/google/state.mjs.map +1 -0
- package/dist/google.d.mts +75 -0
- package/dist/google.d.mts.map +1 -0
- package/dist/google.mjs +210 -0
- package/dist/google.mjs.map +1 -0
- package/dist/linkedin-search/api.d.mts +45 -0
- package/dist/linkedin-search/api.d.mts.map +1 -0
- package/dist/linkedin-search/api.mjs +181 -0
- package/dist/linkedin-search/api.mjs.map +1 -0
- package/dist/linkedin-search/state.d.mts +123 -0
- package/dist/linkedin-search/state.d.mts.map +1 -0
- package/dist/linkedin-search/state.mjs +261 -0
- package/dist/linkedin-search/state.mjs.map +1 -0
- package/dist/linkedin-search.d.mts +69 -0
- package/dist/linkedin-search.d.mts.map +1 -0
- package/dist/linkedin-search.mjs +158 -0
- package/dist/linkedin-search.mjs.map +1 -0
- package/dist/mcp/api.d.mts +44 -0
- package/dist/mcp/api.d.mts.map +1 -0
- package/dist/mcp/api.mjs +557 -0
- package/dist/mcp/api.mjs.map +1 -0
- package/dist/mcp/recordings.d.mts +40 -0
- package/dist/mcp/recordings.d.mts.map +1 -0
- package/dist/mcp/recordings.mjs +2390 -0
- package/dist/mcp/recordings.mjs.map +1 -0
- package/dist/mcp/state.d.mts +71 -0
- package/dist/mcp/state.d.mts.map +1 -0
- package/dist/mcp/state.mjs +83 -0
- package/dist/mcp/state.mjs.map +1 -0
- package/dist/mcp.d.mts +84 -0
- package/dist/mcp.d.mts.map +1 -0
- package/dist/mcp.mjs +241 -0
- package/dist/mcp.mjs.map +1 -0
- package/dist/messages.d.mts +183 -0
- package/dist/messages.d.mts.map +1 -0
- package/dist/messages.mjs +532 -0
- package/dist/messages.mjs.map +1 -0
- package/dist/microsoft/api.d.mts +47 -0
- package/dist/microsoft/api.d.mts.map +1 -0
- package/dist/microsoft/api.mjs +178 -0
- package/dist/microsoft/api.mjs.map +1 -0
- package/dist/microsoft/calendar.d.mts +30 -0
- package/dist/microsoft/calendar.d.mts.map +1 -0
- package/dist/microsoft/calendar.mjs +271 -0
- package/dist/microsoft/calendar.mjs.map +1 -0
- package/dist/microsoft/drive.d.mts +36 -0
- package/dist/microsoft/drive.d.mts.map +1 -0
- package/dist/microsoft/drive.mjs +298 -0
- package/dist/microsoft/drive.mjs.map +1 -0
- package/dist/microsoft/graph.d.mts +198 -0
- package/dist/microsoft/graph.d.mts.map +1 -0
- package/dist/microsoft/graph.mjs +264 -0
- package/dist/microsoft/graph.mjs.map +1 -0
- package/dist/microsoft/mail.d.mts +48 -0
- package/dist/microsoft/mail.d.mts.map +1 -0
- package/dist/microsoft/mail.mjs +488 -0
- package/dist/microsoft/mail.mjs.map +1 -0
- package/dist/microsoft/state.d.mts +480 -0
- package/dist/microsoft/state.d.mts.map +1 -0
- package/dist/microsoft/state.mjs +625 -0
- package/dist/microsoft/state.mjs.map +1 -0
- package/dist/microsoft.d.mts +168 -0
- package/dist/microsoft.d.mts.map +1 -0
- package/dist/microsoft.mjs +483 -0
- package/dist/microsoft.mjs.map +1 -0
- package/dist/node.d.mts +47 -0
- package/dist/node.d.mts.map +1 -0
- package/dist/node.mjs +178 -0
- package/dist/node.mjs.map +1 -0
- package/dist/notion/api.d.mts +51 -0
- package/dist/notion/api.d.mts.map +1 -0
- package/dist/notion/api.mjs +507 -0
- package/dist/notion/api.mjs.map +1 -0
- package/dist/notion/state.d.mts +329 -0
- package/dist/notion/state.d.mts.map +1 -0
- package/dist/notion/state.mjs +432 -0
- package/dist/notion/state.mjs.map +1 -0
- package/dist/notion.d.mts +72 -0
- package/dist/notion.d.mts.map +1 -0
- package/dist/notion.mjs +127 -0
- package/dist/notion.mjs.map +1 -0
- package/dist/openai.d.mts +165 -0
- package/dist/openai.d.mts.map +1 -0
- package/dist/openai.mjs +141 -0
- package/dist/openai.mjs.map +1 -0
- package/dist/opencode-recordings.d.mts +7 -0
- package/dist/opencode-recordings.d.mts.map +1 -0
- package/dist/opencode-recordings.mjs +222 -0
- package/dist/opencode-recordings.mjs.map +1 -0
- package/dist/opencode.d.mts +70 -0
- package/dist/opencode.d.mts.map +1 -0
- package/dist/opencode.mjs +217 -0
- package/dist/opencode.mjs.map +1 -0
- package/dist/r2-fixtures.d.mts +36 -0
- package/dist/r2-fixtures.d.mts.map +1 -0
- package/dist/r2-fixtures.mjs +245 -0
- package/dist/r2-fixtures.mjs.map +1 -0
- package/dist/r2-guard.d.mts +43 -0
- package/dist/r2-guard.d.mts.map +1 -0
- package/dist/r2-guard.mjs +215 -0
- package/dist/r2-guard.mjs.map +1 -0
- package/dist/r2.d.mts +166 -0
- package/dist/r2.d.mts.map +1 -0
- package/dist/r2.mjs +517 -0
- package/dist/r2.mjs.map +1 -0
- package/dist/responses.d.mts +231 -0
- package/dist/responses.d.mts.map +1 -0
- package/dist/responses.mjs +556 -0
- package/dist/responses.mjs.map +1 -0
- package/dist/route-evidence.d.mts +45 -0
- package/dist/route-evidence.d.mts.map +1 -0
- package/dist/route-evidence.mjs +56 -0
- package/dist/route-evidence.mjs.map +1 -0
- package/dist/router.d.mts +93 -0
- package/dist/router.d.mts.map +1 -0
- package/dist/router.mjs +259 -0
- package/dist/router.mjs.map +1 -0
- package/dist/stateful-core.d.mts +17 -0
- package/dist/stateful-core.d.mts.map +1 -0
- package/dist/stateful-core.mjs +41 -0
- package/dist/stateful-core.mjs.map +1 -0
- package/dist/stateful-emulator.d.mts +565 -0
- package/dist/stateful-emulator.d.mts.map +1 -0
- package/dist/stateful-emulator.mjs +1228 -0
- package/dist/stateful-emulator.mjs.map +1 -0
- package/dist/stateful-secrets.d.mts +84 -0
- package/dist/stateful-secrets.d.mts.map +1 -0
- package/dist/stateful-secrets.mjs +216 -0
- package/dist/stateful-secrets.mjs.map +1 -0
- package/dist/subscription-usage-recordings.d.mts +9 -0
- package/dist/subscription-usage-recordings.d.mts.map +1 -0
- package/dist/subscription-usage-recordings.mjs +53 -0
- package/dist/subscription-usage-recordings.mjs.map +1 -0
- package/dist/subscription-usage.d.mts +53 -0
- package/dist/subscription-usage.d.mts.map +1 -0
- package/dist/subscription-usage.mjs +21 -0
- package/dist/subscription-usage.mjs.map +1 -0
- package/dist/telegram/api.d.mts +34 -0
- package/dist/telegram/api.d.mts.map +1 -0
- package/dist/telegram/api.mjs +257 -0
- package/dist/telegram/api.mjs.map +1 -0
- package/dist/telegram/state.d.mts +128 -0
- package/dist/telegram/state.d.mts.map +1 -0
- package/dist/telegram/state.mjs +154 -0
- package/dist/telegram/state.mjs.map +1 -0
- package/dist/telegram.d.mts +62 -0
- package/dist/telegram.d.mts.map +1 -0
- package/dist/telegram.mjs +127 -0
- package/dist/telegram.mjs.map +1 -0
- package/dist/todoist/api.d.mts +55 -0
- package/dist/todoist/api.d.mts.map +1 -0
- package/dist/todoist/api.mjs +415 -0
- package/dist/todoist/api.mjs.map +1 -0
- package/dist/todoist/state.d.mts +275 -0
- package/dist/todoist/state.d.mts.map +1 -0
- package/dist/todoist/state.mjs +345 -0
- package/dist/todoist/state.mjs.map +1 -0
- package/dist/todoist.d.mts +68 -0
- package/dist/todoist.d.mts.map +1 -0
- package/dist/todoist.mjs +181 -0
- package/dist/todoist.mjs.map +1 -0
- package/dist/xai.d.mts +267 -0
- package/dist/xai.d.mts.map +1 -0
- package/dist/xai.mjs +252 -0
- package/dist/xai.mjs.map +1 -0
- package/package.json +157 -0
- package/src/anthropic.ts +289 -0
- package/src/chat-completions.ts +924 -0
- package/src/codex.ts +296 -0
- package/src/dropbox/api.ts +1084 -0
- package/src/dropbox/state.ts +364 -0
- package/src/dropbox.ts +203 -0
- package/src/email-fixtures.ts +1297 -0
- package/src/email.ts +1108 -0
- package/src/emulator-compose.ts +147 -0
- package/src/emulator-http.ts +184 -0
- package/src/emulator-kernel.ts +844 -0
- package/src/fixture-route.ts +561 -0
- package/src/fortnox/api.ts +1352 -0
- package/src/fortnox/state.ts +798 -0
- package/src/fortnox.ts +801 -0
- package/src/gateway-evaluate-recordings.ts +153 -0
- package/src/gateway.ts +530 -0
- package/src/github/api.ts +986 -0
- package/src/github/state.ts +439 -0
- package/src/github.ts +271 -0
- package/src/google/calendar.ts +486 -0
- package/src/google/drive.ts +471 -0
- package/src/google/gmail.ts +1011 -0
- package/src/google/shared.ts +321 -0
- package/src/google/state.ts +686 -0
- package/src/google.ts +298 -0
- package/src/linkedin-search/api.ts +363 -0
- package/src/linkedin-search/state.ts +373 -0
- package/src/linkedin-search.ts +241 -0
- package/src/mcp/api.ts +995 -0
- package/src/mcp/recordings.ts +2665 -0
- package/src/mcp/state.ts +125 -0
- package/src/mcp.ts +319 -0
- package/src/messages.ts +844 -0
- package/src/microsoft/api.ts +426 -0
- package/src/microsoft/calendar.ts +491 -0
- package/src/microsoft/drive.ts +541 -0
- package/src/microsoft/graph.ts +550 -0
- package/src/microsoft/mail.ts +833 -0
- package/src/microsoft/state.ts +822 -0
- package/src/microsoft.ts +982 -0
- package/src/node.ts +293 -0
- package/src/notion/api.ts +976 -0
- package/src/notion/state.ts +512 -0
- package/src/notion.ts +210 -0
- package/src/openai.ts +198 -0
- package/src/opencode-recordings.ts +257 -0
- package/src/opencode.ts +271 -0
- package/src/r2-fixtures.ts +295 -0
- package/src/r2-guard.ts +323 -0
- package/src/r2.ts +890 -0
- package/src/responses.ts +901 -0
- package/src/route-evidence.ts +90 -0
- package/src/router.ts +490 -0
- package/src/stateful-core.ts +70 -0
- package/src/stateful-emulator.ts +2571 -0
- package/src/stateful-secrets.ts +299 -0
- package/src/subscription-usage-recordings.ts +77 -0
- package/src/subscription-usage.ts +68 -0
- package/src/telegram/api.ts +437 -0
- package/src/telegram/state.ts +229 -0
- package/src/telegram.ts +227 -0
- package/src/todoist/api.ts +736 -0
- package/src/todoist/state.ts +456 -0
- package/src/todoist.ts +316 -0
- package/src/xai.ts +341 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stateful-emulator.mjs","names":[],"sources":["../src/stateful-emulator.ts"],"sourcesContent":["/**\n * Shared wrapper of the fixture-only stateful connector emulators (internal; used by `/dropbox`,\n * `/notion`, `/todoist`, `/telegram`, `/github`, `/google`, `/linkedin-search`, and `/mcp`, not by\n * the earlier `/fortnox` and `/microsoft` emulators, which keep their own).\n *\n * The emulator state lives in an `@emulators/core` custom runtime that the Node-only subpath\n * creates and hands in (`src/stateful-core.ts`; this module imports no Node builtin and never\n * imports the core); the request ledger, status faults, credential handling, and the\n * `/_emulate/*` control plane live here, because the core reserves `/_emulate`.\n *\n * The owner rule: response behaviour comes only from the committed conformance fixtures. Every\n * request is answered by its route or with one ledgered 400 not-emulated\n * (`{ error: { type: 'not_emulated', message } }`, `notEmulated` in the ledger). Precedence per\n * request:\n *\n * 1. route match on the raw path (path parameters decoded once; unknown routes and methods, and\n * invalid percent-encoding, are not emulated), and the origin the route is recorded on;\n * 2. a non-empty `Authorization: Bearer` credential (never compared against anything, stored,\n * forwarded, or ledgered) and the emulator's header rules;\n * 3. the body the route takes (none, JSON with an `application/json` media type, or raw bytes);\n * 4. the route's request-shape check, which reads no state;\n * 5. in the core runtime, the route's plan: a pure, state-reading eligibility check that refuses\n * what the state cannot answer the way a fixture does, and otherwise returns a commit;\n * 6. the first matching status fault, decided only for an eligible request (nothing is written);\n * 7. the commit, the only step that writes.\n *\n * A request that is not emulated (steps 1 to 5) never uses up a fault. The plan, the fault\n * decision, and the commit run synchronously together, so no other request interleaves. A route\n * that throws answers an evidence-tagged 500 emulator error\n * (`responseError` in the ledger); a closed emulator answers 503. No recovery answer reads the\n * injectable clock, so a failing clock cannot change it. Every response of a matched route\n * carries `x-emulator-evidence: unverified` when the route is unverified. Ledgered bodies and\n * query parameters have credential-named keys redacted.\n *\n * Fail-closed mode (opt-in, `failClosed`; used by `/github`, `/google`, and `/linkedin-search`):\n * every route parameter has a raw pattern (matched in full), and a request is recognised only when\n * its raw path is exactly an emulated route shape under that route's method and any `Authorization`\n * header is exactly `Bearer <at least 8 non-space characters>` (a recognisable bearer, below).\n * Every other request is ledgered and answered with constant text only (`/<unrecognised>`, a\n * standard method or `<other>`, an empty query, no body, a constant reason). A recognised bearer\n * must match the RFC 6750 `b64token` syntax exactly (`^[A-Za-z0-9\\-._~+/]+=*$`, at least 8\n * characters), start with a character in `[G-Zg-z\\-._~+/]` other than `n`, `r`, `t`, `u`, and hold\n * at least one character outside the JSON-number alphabet `[0-9.eE+-]` (every GitHub and Google\n * token form does: `ghp_…`, `github_pat_…`, `gho_…`, `ya29.…`). So no number's text can contain it;\n * it holds no escape introducer (`%`, `\\`, `\"`), so no escape starts inside it; and its first\n * character is no hex digit and no JSON escape letter, so no stray `%`, `\\`, or partial escape to\n * its left can complete with it, and its characters always decode in place. An `Authorization`\n * header with any other value is unrecognisable. A recognised request that repeats the bearer value\n * in its raw path, any path segment, the raw query or any query key or value, any recorded header,\n * or its body is refused and ledgered with constant text only: a standard method, the path\n * `/<unrecognised>`, its route template, an empty query, no headers or body, and a constant reason\n * (`the query repeats the credential`, for example). Each part is checked through the closure of\n * two total, lexical transforms that cannot fail: a tolerant percent-decode (every `%XX` below\n * `%80` becomes its ASCII character; any other `%` sequence is left as it is) and a tolerant\n * JSON-unescape (in any text, whether or not it parses as JSON, `\\uXXXX` below `\\u0080` and `\\\"`,\n * `\\\\`, `\\/`, `\\b`, `\\f`, `\\n`, `\\r`, `\\t` become their characters). Starting from each part's raw\n * text, either transform is applied to every text of the previous step, deduplicated, until no new\n * text appears (a fixpoint), and every text is checked for the bearer as a substring; both\n * transforms never lengthen a text and shorten it whenever they change it. So any depth of\n * percent-encoding or JSON escaping, in any order, is seen through in every part: the raw path and\n * each raw path segment, the raw query and each query key and value (already decoded once by\n * `URLSearchParams`), each recorded header, and the raw body. The work is capped at 64 rounds, 1024\n * distinct texts, or 8 Mi characters read by the transforms, whichever comes first; a part whose\n * closure hits a cap before its fixpoint counts as repeating the credential and is refused with the\n * same constant entry (uncertainty refuses, it never admits; so any part over 4 Mi characters is\n * always refused). Any other recognised request has the bearer value scrubbed from its ledgered\n * fields and every not-emulated reason (plan-time reasons included); its recorded query is keyed by\n * recorded key; a key recorded more than once lists its values in order (as a JSON array); and\n * recorded headers and query keys and values that start like JSON (`{`, `[`, `\"`) are recorded\n * parsed with credential-named keys redacted at any depth, or as `<redacted>` when they do not\n * parse, whatever the header's declared format. Empty query components (a bare `?`, a stray `&`)\n * are refused. Routes check their own query and body keys with `exactQuery` and `exactBodyKeys`,\n * whose reasons never echo a request's own key (`exactQuery`'s opt-in `rawNames`, used by\n * `/linkedin-search`, also refuses a parameter name in any but its plain form, comparing the raw\n * names the wrapper hands routes as `rawQuery`). A template parameter written `{name+}` spans one\n * or more path segments (each decoded once, none may decode to a `/`). The credential helpers live\n * in `src/stateful-secrets.ts`. A route may also name decoded views of its raw body\n * (`decodedViews`, opt-in; `/google` gives the base64url-decoded MIME of a Gmail draft's\n * `message.raw`, which the provider's own wire format wraps): in fail-closed mode each view goes\n * through the same fixpoint check as the raw body, before anything is recorded, a fault is decided,\n * or anything is committed, and a hit is the same constant credential-repeat entry. A view may\n * throw to refuse a body it cannot check completely: that request is ledgered as the same constant\n * entry, as a repeat when the `DecodedViewRefusal` it threw carries cleanly decoded text holding\n * the bearer, else with the refusal's reason when the route declares it in `viewRefusalReasons` (a\n * constant the route owns, scrubbed defensively), else with\n * `the request body cannot be checked for the credential`. A route without `decodedViews` is\n * checked exactly as before. An emulator may also opt in to a per-origin bearer digest\n * (`bearerDigest`, fail-closed mode only; `/linkedin-search` uses it): routes then see a one-way\n * digest of the bearer for the origin the request arrived on (`EmulatedRequest.bearerDigest`),\n * never the bearer, so a seed can mark a key as rejected on one origin by its digest, and the\n * bearer still never reaches the state, the ledger, or `/_emulate/*`. A digest that throws or\n * repeats the bearer answers the 500 emulator error (`responseError`). Without it, routes see no\n * digest, as before.\n *\n * More opt-ins serve `/mcp`, whose JSON-RPC methods share one HTTP endpoint; a route or emulator\n * that does not take them behaves exactly as before. A route may answer several manifest rows\n * (`variants`, for example `RPC <origin><path>#<method>`): its admission names the row, which\n * becomes the request's ledger route, its coverage row, and what a fault's `match.route` compares\n * (a request refused before admission keeps the route template). A fault's `match.route` must name\n * a manifest row (a variant, or the template of a route without variants); any other value is\n * rejected when the fault is added, and `match.method` is always the HTTP method. A plan may return\n * a `StreamedCommit`: the answer it prepares, and the `commit` that writes. Faults are decided\n * against the prepared answer, and a faulted request never runs `commit`; an emulator built with\n * `makeChunkedStatefulEmulator` also takes `truncate-after-chunks` faults, which send the prepared\n * answer cut short and write nothing (one that cannot take effect answers 500 and is not used up).\n * In fail-closed mode: `constantRefusals` ledgers every refusal with constant text only\n * (`/<unrecognised>`, no query, headers, or body), so request text reaches the ledger only once a\n * route admitted the request; `guardAllHeaders` checks every request header name and value but\n * `Authorization` for a credential repeat, not only the recorded ones; and `guardOutput` checks a\n * streamed commit's prepared answer (every header and chunk) and its `persisted` texts (minted ids,\n * for example) for the bearer before any fault is decided or anything is committed, refusing a hit\n * with the constant credential-repeat entry, so no answer or stored value repeats the bearer, while\n * routes still see only its digest. `uniqueJsonKeys` refuses a JSON body in which an object repeats\n * a key (after unescaping). Routes also see every request header name\n * (`EmulatedRequest.headerNames`), never a credential value.\n *\n * Resolved mode (opt-in, `resolveRequest`; not with `failClosed`; used by `/todoist` and\n * `/telegram`) serves an emulator whose credential the wrapper cannot find by itself (a Telegram\n * bot token in a path segment) or that has its own credential rules (a Todoist bearer of any 8\n * non-space characters): the emulator resolves every request itself, failing closed\n * (`StatefulResolution`): an unrecognised request is ledgered with constant text only (the method\n * as sent when standard, else `<other>`), a request it refuses before its body is read keeps its\n * scrubbed fields, and a recognised one names its route, credential-free parameters, ledger path,\n * guarded path, and the credential values to guard. Those values are scrubbed from everything the\n * ledger keeps or a refusal answers (plan-time reasons included), and a query, guarded path, or\n * body that repeats one (raw or percent-decoded once, and in any parsed JSON key, string, or\n * number) is refused with a constant reason before anything else is checked. The ledger records\n * the resolution's path and the query as sent (credential-named keys redacted), faults'\n * `match.path` compares that path, and routes and the core see it too (routes see no header\n * names and read no header but `content-type`), so the credential never reaches a route or the\n * core. The request body is read once, after the query and path checks (so a query or path refusal\n * leaves it unread, and any later refusal finds it read); a body that is already consumed or\n * locked is refused as unreadable; a refusal reason that cannot be percent-encoded (an unpaired\n * surrogate) is a handler failure (500). Fault answers and refusals are decided in the core with\n * the commit, but returned by the wrapper itself, so a reset or a close before they are read never\n * cancels them (only a commit's answer is the core's own); a core that answers without running\n * the dispatch (closed meanwhile) gives the 500 handler failure (`responseError`\n * `the route handler answered no eligibility verdict`). Resolved mode records no request\n * header (`recordHeaders` must be empty). More opt-ins, off by default: a `json-or-empty` route\n * body (JSON of any media type, or none), `makeHeaderlessStatefulEmulator` (ledger entries without\n * a `headers` field), and `errorTexts` (the texts of the recovery answers).\n *\n * @experimental\n */\nimport { Data, Predicate, Result } from 'effect'\nimport * as Schema from 'effect/Schema'\nimport {\n EmulatorHeaderRecord,\n answeredOutsideCore,\n emulatorJobHeader,\n handlerFailedHeader,\n handlerFailedResponse,\n isCredentialHeaderName,\n isCredentialQueryKey,\n redactCredentialFields,\n redactCredentialQuery,\n redactedCredentialValue\n} from './emulator-http.ts'\nimport {\n emulatorEvidenceHeader,\n emulatorRouteKey,\n type EmulatorEvidence,\n type EmulatorRouteEvidence\n} from './route-evidence.ts'\nimport {\n isRecognisableBearerValue,\n jsonRepeatsSecret,\n repeatsSecret,\n scrubSecrets,\n textRepeatsSecret,\n unrecognisedLedgerPath,\n unrecognisedMethod\n} from './stateful-secrets.ts'\n\n/** A request the emulator does not emulate, with the reason (answered 400 not-emulated). */\nexport class NotEmulated extends Data.TaggedClass('NotEmulated')<{ readonly reason: string }> {}\n\n/**\n * What a route's decoded view throws to refuse a body with a reason of its own (fail-closed mode;\n * see `StatefulRouteBinding.decodedViews`). `reason` must be one of the route's declared\n * `viewRefusalReasons` (a constant the route owns, never derived from the request); `decoded` holds\n * any text the view did decode cleanly, which is still checked for the bearer first.\n */\nexport class DecodedViewRefusal extends Data.TaggedError('DecodedViewRefusal')<{\n readonly reason: string\n readonly decoded: ReadonlyArray<string>\n}> {}\n\nexport const notEmulated = (reason: string): NotEmulated => new NotEmulated({ reason })\n\nexport const isNotEmulated = (value: unknown): value is NotEmulated => value instanceof NotEmulated\n\nexport const jsonResponse = (\n status: number,\n body: unknown,\n headers: HeadersInit = {}\n): Response => {\n const responseHeaders = new Headers(headers)\n\n if (!responseHeaders.has('content-type')) {\n responseHeaders.set('content-type', 'application/json')\n }\n\n return new Response(JSON.stringify(body), { status, headers: responseHeaders })\n}\n\n/** The one answer for everything no fixture records. */\nexport const notEmulatedResponse = (reason: string): Response =>\n jsonResponse(400, { error: { type: 'not_emulated', message: `Not emulated: ${reason}` } })\n\nconst emulatorError = (status: number, message: string, headers: HeadersInit = {}): Response =>\n jsonResponse(status, { error: { message, type: 'emulator_error' } }, headers)\n\n// Fault statuses are errors only (400-599), so no control answers a success no fixture records.\n// One range check, so a rejection names the range actually accepted (400-599 holds no 204, 205,\n// or 3xx, which the shared response-status schema otherwise excludes).\nconst FaultStatus = Schema.Int.check(Schema.isBetween({ minimum: 400, maximum: 599 }))\n\nconst FaultCount = Schema.Int.check(Schema.isGreaterThanOrEqualTo(1))\n\nconst ChunkCount = Schema.Int.check(Schema.isGreaterThanOrEqualTo(0))\n\n/**\n * Optional fault filter; an omitted field matches every request. `method` is the HTTP method\n * (never a manifest row's `RPC`). `path` is the raw request path (in resolved mode, the\n * resolution's ledger path); ending in `*`, a prefix. `route` is a manifest row path, compared\n * with the request's ledger route: the template of its matched route, or the manifest variant its\n * route admitted it as (see `StatefulRouteBinding.variants`).\n * A `route` that names no manifest row of the emulator could never match, so adding the fault\n * rejects it (a route with variants is matched by its variant rows, never its template).\n */\nexport const StatefulFaultMatch = Schema.Struct({\n method: Schema.optionalKey(Schema.String),\n path: Schema.optionalKey(Schema.String),\n route: Schema.optionalKey(Schema.String)\n})\n\nexport type StatefulFaultMatch = typeof StatefulFaultMatch.Type\n\n/**\n * A status fault: answer matching requests with this status (400-599), headers, and body before\n * the route runs, so nothing is written. The body defaults to an emulator-fault body\n * (`{ error: { type: 'emulator_fault', message } }`), never a guessed provider envelope. `count`\n * limits how many requests it answers (omitted: all). `match.path` is the raw request path.\n * Invalid header names or values, `location`, and framing headers are rejected.\n */\nexport const StatefulFault = Schema.Struct({\n kind: Schema.Literal('status'),\n status: FaultStatus,\n headers: Schema.optionalKey(EmulatorHeaderRecord),\n body: Schema.optionalKey(Schema.Json),\n match: Schema.optionalKey(StatefulFaultMatch),\n count: Schema.optionalKey(FaultCount)\n})\n\nexport type StatefulFault = typeof StatefulFault.Type\n\n/**\n * Opt-in (`makeChunkedStatefulEmulator`): send the first `chunks` body chunks of the answer a\n * streamed commit prepared (`StreamedCommit`), then close the body cleanly (a truncated answer).\n * It is decided where a status fault is, after the plan, and a truncated request writes nothing:\n * its `commit` never runs (no state, counter, or runtime change). One that cannot take effect (the\n * answer is not streamed, or has no more than `chunks` chunks) answers the 500 emulator error and\n * is not used up, never a silent no-op.\n */\nexport const StatefulTruncateFault = Schema.Struct({\n kind: Schema.Literal('truncate-after-chunks'),\n chunks: ChunkCount,\n match: Schema.optionalKey(StatefulFaultMatch),\n count: Schema.optionalKey(FaultCount)\n})\n\nexport type StatefulTruncateFault = typeof StatefulTruncateFault.Type\n\n/** A status fault, or (`makeChunkedStatefulEmulator`) a truncation fault. */\nexport const StatefulStreamFault = Schema.Union([StatefulFault, StatefulTruncateFault])\n\nexport type StatefulStreamFault = typeof StatefulStreamFault.Type\n\nexport type StatefulFaultState<Fault = StatefulFault> = {\n readonly id: number\n readonly fault: Fault\n /** Remaining matching requests; `undefined` for an unlimited fault. */\n readonly remaining: number | undefined\n readonly applied: number\n}\n\nexport type StatefulLedgerEntry = {\n /** 1-based arrival order since the last ledger clear or reset. */\n readonly seq: number\n readonly method: string\n /**\n * Raw request path; in fail-closed mode with guarded secrets scrubbed, in resolved mode the\n * resolution's ledger path (scrubbed), and `/<unrecognised>` for an unrecognised request.\n */\n readonly path: string\n /** Path template of the matched route. */\n readonly route?: string\n /** Query parameters; the values of credential-named keys are `<redacted>`. */\n readonly query: Readonly<Record<string, string>>\n /** Parsed JSON request body, with credential-named keys redacted at any depth. */\n readonly body?: Schema.Json\n /** Length of a raw (non-JSON) request body. */\n readonly bodyBytes?: number\n /**\n * The non-credential request headers the emulator records (lower-case names); a JSON header is\n * recorded with credential-named keys redacted at any depth, or as `<redacted>` when unparseable.\n */\n readonly headers: Readonly<Record<string, string>>\n readonly status: number\n /** Evidence of the matched route; `unknown-route` for requests on no route. */\n readonly evidence: EmulatorEvidence | 'unknown-route'\n /** Why the request was answered 400 not-emulated. */\n readonly notEmulated?: string\n /** Set when a fault answered the request (`truncate-after-chunks` cut its streamed body). */\n readonly fault?: 'status' | 'truncate-after-chunks'\n /** Set when the emulator could not build the response, or a route threw (answered 500). */\n readonly responseError?: string\n}\n\nexport type StatefulRouteCoverage = EmulatorRouteEvidence & {\n /** Ledger requests on this route since the last ledger clear or reset. */\n readonly requests: number\n}\n\nexport type StatefulCoverage = {\n readonly routes: ReadonlyArray<StatefulRouteCoverage>\n /** Ledger requests on no route. */\n readonly unknownRouteRequests: number\n /** Ledger requests answered 400 not-emulated (on a route or not). */\n readonly notEmulatedRequests: number\n}\n\n/**\n * A ledger entry of an emulator built with `makeHeaderlessStatefulEmulator`, which records no\n * request header and takes status faults only: the entry carries no `headers` field, no\n * `bodyBytes` (it has no `bytes` routes), and only a `status` fault.\n */\nexport type StatefulHeaderlessLedgerEntry = Omit<\n StatefulLedgerEntry,\n 'headers' | 'bodyBytes' | 'fault'\n> & {\n /** Set when a fault answered the request. */\n readonly fault?: 'status'\n}\n\n/** One routed API request, as a route sees it. */\nexport type EmulatedRequest = {\n readonly method: string\n /** Raw (still percent-encoded) path; in resolved mode, the resolution's ledger path. */\n readonly path: string\n /**\n * Path parameters, percent-decoded once; in resolved mode (`resolveRequest`), the parameters the\n * resolution supplies (never a credential).\n */\n readonly params: Readonly<Record<string, string>>\n readonly query: URLSearchParams\n /**\n * The raw query (after `?`, before `#`, never decoded; `''` without one), as the wrapper saw it.\n * Absent on a request built elsewhere (then `exactQuery`'s `rawNames` refuses any parameter).\n */\n readonly rawQuery?: string | undefined\n /**\n * A non-credential request header (credential headers always read as `undefined`; in resolved\n * mode, every header but `content-type` does).\n */\n readonly header: (name: string) => string | undefined\n /**\n * The lower-case names of every request header, credential headers included (names only, never\n * a credential value); none in resolved mode. Absent on a request built elsewhere.\n */\n readonly headerNames?: ReadonlyArray<string> | undefined\n /** Parsed JSON body (`json` routes, and `json-or-empty` routes with a body). */\n readonly json: Schema.Json | undefined\n /** Raw body (`bytes` routes). */\n readonly bytes: Uint8Array | undefined\n /**\n * Fail-closed mode with `bearerDigest` only: the digest of the recognised bearer for the origin\n * the request arrived on (never the bearer itself); `undefined` otherwise.\n */\n readonly bearerDigest?: string | undefined\n}\n\nexport type RunContext<Env> = {\n readonly env: Env\n /** Ledger sequence number of the request (for synthetic request ids). */\n readonly seq: number\n}\n\n/** The writing part of an eligible request: applies its change and answers. */\nexport type Commit = () => Response\n\n/** A streamed answer: its status, headers, and body chunks (sent one per pull). */\nexport type StreamedAnswer = {\n readonly status: number\n readonly headers: Readonly<Record<string, string>>\n /** The body chunks; none answers no body at all. */\n readonly chunks: ReadonlyArray<string>\n}\n\n/**\n * A commit whose answer the plan prepares: `answer` is sent (one chunk per pull) only after\n * `commit` has written the request's change. Because the answer exists before anything is\n * written, the wrapper decides faults against it: a status fault or a `truncate-after-chunks`\n * fault (`makeChunkedStatefulEmulator`) answers without running `commit`, so a faulted request\n * writes nothing. `persisted` lists the texts `commit` will store (minted ids, for example), which\n * the opt-in output guard (`guardOutput`) checks together with the answer.\n */\nexport type StreamedCommit = {\n readonly answer: StreamedAnswer\n readonly commit: () => void\n readonly persisted: ReadonlyArray<string>\n}\n\n/** A streamed commit: the prepared `answer`, and the write (none by default) it commits. */\nexport const streamedCommit = (\n answer: StreamedAnswer,\n commit: () => void = () => undefined,\n persisted: ReadonlyArray<string> = []\n): StreamedCommit => ({ answer, commit, persisted })\n\n/** What a plan returns: a commit, a streamed commit, or not emulated. */\nexport type Planned = Commit | StreamedCommit | NotEmulated\n\n/** An admitted request: its pure, state-reading eligibility check, returning the commit. */\nexport type Admission<State, Env> = {\n readonly plan: (state: State, context: RunContext<Env>) => Planned\n /**\n * The path of the manifest variant the request was admitted as (`StatefulRouteBinding.variants`):\n * required when the route has variants, absent otherwise.\n */\n readonly variant?: string\n}\n\n/**\n * The body a route takes: none; JSON with an `application/json` media type; JSON of any media type\n * or none (`json-or-empty`: an empty body is no body, any other must be valid JSON, and the route\n * checks the media type itself); or raw bytes.\n */\nexport type RouteBody = 'none' | 'json' | 'json-or-empty' | 'bytes'\n\n/** Route parts that are not evidence: the recorded origin and the raw parameter patterns. */\nexport type StatefulRouteBinding = {\n /** The origin a fixture records the route on; another origin is not emulated. Omitted: any. */\n readonly origin?: string\n /**\n * Raw (still percent-encoded) patterns of the path parameters; a parameter that does not match\n * its pattern makes the path match no route. Fail-closed emulators give every parameter one.\n */\n readonly params?: Readonly<Record<string, RegExp>>\n /**\n * Fail-closed mode only: texts the route derives from its raw body that the provider's wire\n * format encodes in a way the percent and JSON closure cannot see through (for example base64url\n * content). Each view is checked for the bearer like the raw body. A body that holds no such\n * content yields no view. A view may throw to refuse a body whose encoded content it cannot\n * check completely (for example content the route would refuse anyway); the request is then\n * ledgered as the constant credential-repeat entry shape, before anything is recorded, a fault is\n * decided, or anything is committed, with this reason:\n *\n * - `the request body repeats the credential` when the throw is a `DecodedViewRefusal` whose\n * cleanly `decoded` texts hold the bearer (a repeat, not a refusal);\n * - otherwise its `reason` when that is one of `viewRefusalReasons` (scrubbed of the bearer,\n * defensively);\n * - otherwise (an undeclared reason, or any other throw)\n * `the request body cannot be checked for the credential`.\n *\n * Omitted: none.\n */\n readonly decodedViews?: (body: string) => ReadonlyArray<string>\n /**\n * The constant reasons a decoded view may refuse a body with (`DecodedViewRefusal`); any other\n * reason is replaced by the uncheckable reason. Owned by the route, never derived from a request.\n */\n readonly viewRefusalReasons?: ReadonlyArray<string>\n /**\n * Opt-in: the manifest rows this route answers INSTEAD of its own (for example one row per\n * JSON-RPC method on one HTTP endpoint, `{ method: 'RPC', path: '<origin><path>#<method>' }`).\n * Each admission names its row (`Admission.variant`), which becomes the request's ledger route,\n * its coverage row, and what a fault's `match.route` compares; a request refused before it is\n * admitted keeps the route's own template. Every variant carries the route's evidence, and no\n * variant path repeats another manifest row or a route template (checked at build). Omitted:\n * the route is its own manifest row, as before.\n */\n readonly variants?: ReadonlyArray<EmulatorRouteEvidence>\n}\n\nexport type StatefulRoute<State, Env> = EmulatorRouteEvidence &\n StatefulRouteBinding & {\n readonly body: RouteBody\n /** The request-shape check (reads no state): an admission, or not emulated. */\n readonly admit: (request: EmulatedRequest, env: Env) => Admission<State, Env> | NotEmulated\n }\n\n/**\n * A route of the table: its evidence (and recorded origin), the body it takes, its request-shape\n * check `admit` (no state), and `plan`, which only ever sees an admitted input: it reads the state\n * without writing and answers not emulated or the commit that writes.\n */\nexport const statefulRoute = <State, Env, Input>(\n evidence: EmulatorRouteEvidence & StatefulRouteBinding,\n body: RouteBody,\n admit: (request: EmulatedRequest, env: Env) => Input | NotEmulated,\n plan: (state: State, input: Input, context: RunContext<Env>) => Planned\n): StatefulRoute<State, Env> => ({\n ...evidence,\n body,\n admit: (request, env) => {\n const input = admit(request, env)\n\n return isNotEmulated(input) ? input : { plan: (state, context) => plan(state, input, context) }\n }\n})\n\n/** Route evidence without the handler parts. */\nexport const routeEvidence = <State, Env>({\n body: _body,\n admit: _admit,\n origin: _origin,\n params: _params,\n decodedViews: _decodedViews,\n viewRefusalReasons: _viewRefusalReasons,\n variants: _variants,\n ...evidence\n}: StatefulRoute<State, Env>): EmulatorRouteEvidence => evidence\n\n/** The manifest rows of a route: its variants, or the route itself (`routeEvidence`). */\nexport const routeManifest = <State, Env>(\n route: StatefulRoute<State, Env>\n): ReadonlyArray<EmulatorRouteEvidence> => route.variants ?? [routeEvidence(route)]\n\n/** A percent-decoded path segment, or `undefined` for invalid percent-encoding. */\nexport const decodeSegment = (segment: string): string | undefined => {\n try {\n return decodeURIComponent(segment)\n } catch {\n return undefined\n }\n}\n\n// `{name}` is one path segment; `{name+}` is one or more segments.\nconst templatePattern = (template: string): RegExp =>\n new RegExp(\n `^${template\n .split(/(\\{[A-Za-z]+\\+?\\})/)\n .map(part =>\n /^\\{[A-Za-z]+\\}$/.test(part)\n ? '([^/]+)'\n : /^\\{[A-Za-z]+\\+\\}$/.test(part)\n ? '(.+)'\n : part.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')\n )\n .join('')}$`\n )\n\nconst templateNames = (template: string): ReadonlyArray<string> =>\n [...template.matchAll(/\\{([A-Za-z]+)\\+?\\}/g)].map(match => match[1] ?? '')\n\n/**\n * A multi-segment parameter decoded segment by segment (each once), or `undefined` for an empty\n * segment, invalid percent-encoding, or a segment that decodes to a `/`.\n */\nconst decodeSegments = (raw: string): string | undefined => {\n const segments = raw.split('/').map(segment => {\n const decoded = segment === '' ? undefined : decodeSegment(segment)\n\n return decoded === undefined || decoded.includes('/') ? undefined : decoded\n })\n\n return segments.some(segment => segment === undefined) ? undefined : segments.join('/')\n}\n\nexport type MatchedRoute<State, Env> = {\n readonly route: StatefulRoute<State, Env>\n readonly params: Readonly<Record<string, string>>\n}\n\n/**\n * `pattern` required to match a whole raw parameter: `^(?:source)$` with its flags, so alternation\n * and lazy quantifiers are tried against the whole value, never against a prefix of it.\n */\nconst wholePattern = (pattern: RegExp): RegExp =>\n new RegExp(`^(?:${pattern.source})$`, pattern.flags)\n\n/**\n * A matcher over a route table: the route answering `method` + raw `path`, with its parameters\n * decoded once, or `undefined` (also for a parameter that is not valid percent-encoding). A raw\n * parameter pattern must match the whole raw parameter; a pattern with the `g` or `y` flag (whose\n * matches depend on earlier ones) is refused when the matcher is built.\n */\nexport const routeMatcher = <State, Env>(routes: ReadonlyArray<StatefulRoute<State, Env>>) => {\n const keys = routes.map(route => emulatorRouteKey(route.method, route.path))\n const duplicate = keys.find((key, index) => keys.indexOf(key) !== index)\n\n if (duplicate !== undefined) {\n throw new Error(`duplicate emulator route ${duplicate}`)\n }\n\n for (const route of routes) {\n for (const [name, pattern] of Object.entries(route.params ?? {})) {\n if (pattern.global || pattern.sticky) {\n const where = `${route.method} ${route.path} parameter ${name}`\n\n throw new Error(`raw patterns take no g or y flag (${where})`)\n }\n }\n }\n\n const compiled = routes.map(route => ({\n route,\n pattern: templatePattern(route.path),\n names: templateNames(route.path),\n // Compiled when the matcher is built, so no request compiles a RegExp.\n whole: new Map(\n Object.entries(route.params ?? {}).map(([name, raw]) => [name, wholePattern(raw)] as const)\n ),\n multi: new Set([...route.path.matchAll(/\\{([A-Za-z]+)\\+\\}/g)].map(match => match[1] ?? ''))\n }))\n\n return (method: string, path: string): MatchedRoute<State, Env> | undefined => {\n for (const candidate of compiled) {\n if (candidate.route.method !== method.toUpperCase()) continue\n\n const match = candidate.pattern.exec(path)\n\n if (match === null) continue\n\n const raws = candidate.names.map((name, index) => [name, match[index + 1] ?? ''] as const)\n\n // A raw parameter outside its pattern (matched in full) is no shape of this route.\n if (\n raws.some(([name, raw]) => {\n const pattern = candidate.whole.get(name)\n\n return pattern !== undefined && !pattern.test(raw)\n })\n ) {\n continue\n }\n\n const params: Record<string, string> = {}\n\n for (const [name, raw] of raws) {\n const value = candidate.multi.has(name) ? decodeSegments(raw) : decodeSegment(raw)\n\n if (value === undefined) return undefined\n\n params[name] = value\n }\n\n return { route: candidate.route, params }\n }\n\n return undefined\n }\n}\n\n/** The template parameters of a route that have no raw pattern (fail-closed emulators need one). */\nexport const unpatternedParams = <State, Env>(\n route: StatefulRoute<State, Env>\n): ReadonlyArray<string> => templateNames(route.path).filter(name => !route.params?.[name])\n\nconst decodeJsonText = Schema.decodeUnknownResult(Schema.fromJsonString(Schema.Json))\n\n/** Parsed JSON, or `undefined` for invalid JSON. */\nexport const parseJsonText = (text: string): Schema.Json | undefined => {\n const result = decodeJsonText(text)\n\n return Result.isSuccess(result) ? result.success : undefined\n}\n\nexport const isJsonObject = (value: Schema.Json | undefined): value is Schema.JsonObject =>\n value !== undefined && value !== null && Predicate.isObject(value) && !Array.isArray(value)\n\n/**\n * True when any object of a valid JSON text repeats a key, compared after JSON unescaping (so\n * `\"id\"` and `\"\\u0069d\"` are one key). `JSON.parse` would keep the last value silently.\n */\nexport const repeatsJsonKey = (text: string): boolean => {\n // One entry per open object or array: an object's keys so far, and whether a key comes next.\n const open: Array<{ readonly keys: Set<string> | undefined; expectKey: boolean }> = []\n let index = 0\n\n while (index < text.length) {\n const character = text[index]\n\n if (character === '\"') {\n let end = index + 1\n\n while (end < text.length && text[end] !== '\"') end += text[end] === '\\\\' ? 2 : 1\n\n const top = open.at(-1)\n\n if (top?.keys !== undefined && top.expectKey) {\n const key = parseJsonText(text.slice(index, end + 1))\n\n if (!Predicate.isString(key) || top.keys.has(key)) return true\n\n top.keys.add(key)\n top.expectKey = false\n }\n\n index = end + 1\n continue\n }\n\n if (character === '{') open.push({ keys: new Set(), expectKey: true })\n else if (character === '[') open.push({ keys: undefined, expectKey: false })\n else if (character === '}' || character === ']') open.pop()\n else if (character === ',') {\n const top = open.at(-1)\n\n if (top?.keys !== undefined) top.expectKey = true\n }\n\n index += 1\n }\n\n return false\n}\n\n/** The media type of a `content-type` value (lower-case, without parameters). */\nexport const mediaType = (value: string | undefined): string =>\n (value ?? '').split(';')[0]?.trim().toLowerCase() ?? ''\n\n/**\n * A JSON object with exactly the `required` keys plus any of the `optional` ones, or not\n * emulated (naming the first missing or unknown key).\n */\nexport const exactObject = (\n value: Schema.Json | undefined,\n label: string,\n required: ReadonlyArray<string>,\n optional: ReadonlyArray<string> = []\n): Schema.JsonObject | NotEmulated => {\n if (!isJsonObject(value)) return notEmulated(`${label} must be a JSON object`)\n\n const unknown = Object.keys(value).find(key => !required.includes(key) && !optional.includes(key))\n\n if (unknown !== undefined) return notEmulated(`${label} key '${unknown}' is not emulated`)\n\n const missing = required.find(key => !(key in value))\n\n return missing === undefined\n ? value\n : notEmulated(`${label} without '${missing}' is not emulated`)\n}\n\n/**\n * Constant-text shape check (for fail-closed emulators): a JSON object with exactly the `required`\n * keys plus any of the `optional` ones, or not emulated. Unlike `exactObject`, a reason never\n * echoes a request's own key; it names only `label` and a missing key from the route's own list.\n */\nexport const exactBodyKeys = (\n value: Schema.Json | undefined,\n label: string,\n required: ReadonlyArray<string>,\n optional: ReadonlyArray<string> = []\n): Schema.JsonObject | NotEmulated => {\n if (!isJsonObject(value)) return notEmulated(`${label} must be a JSON object`)\n\n const keys = Object.keys(value)\n\n if (keys.some(key => !required.includes(key) && !optional.includes(key))) {\n return notEmulated(`${label} has a key this route does not take`)\n }\n\n const missing = required.find(key => !keys.includes(key))\n\n return missing === undefined\n ? value\n : notEmulated(`${label} without '${missing}' is not emulated`)\n}\n\n/** Options of `exactQuery`. */\nexport type ExactQueryOptions = {\n /**\n * Opt-in: every raw parameter name (before `URLSearchParams` decodes it) must be written exactly\n * as the route names it, so a percent-encoded or `+`-spaced spelling of an accepted name is not\n * emulated. Omitted: names are compared decoded, as before.\n */\n readonly rawNames?: boolean\n}\n\n/** The raw parameter names of a raw query, in order (`[]` for an empty query). */\nconst rawQueryNames = (raw: string): ReadonlyArray<string> =>\n raw === '' ? [] : raw.split('&').map(component => component.split('=', 1)[0] ?? '')\n\n/**\n * Constant-text query check (for fail-closed emulators): exactly the `required` query keys plus\n * any of the `optional` ones, each once, as a record of their values; or not emulated. A reason\n * never echoes a request's own key; it names only a missing key from the route's own list. With\n * `rawNames`, every raw parameter name must also be the plain name it decodes to.\n */\nexport const exactQuery = (\n request: EmulatedRequest,\n required: ReadonlyArray<string>,\n optional: ReadonlyArray<string> = [],\n options: ExactQueryOptions = {}\n): Readonly<Record<string, string>> | NotEmulated => {\n const keys = [...request.query.keys()]\n\n if (keys.length !== new Set(keys).size) {\n return notEmulated('repeated query parameters are not emulated')\n }\n\n if (keys.some(key => !required.includes(key) && !optional.includes(key))) {\n return notEmulated('a query parameter this route does not take is not emulated')\n }\n\n if (options.rawNames === true) {\n const raw = rawQueryNames(request.rawQuery ?? '')\n\n if (raw.length !== keys.length || raw.some((name, index) => name !== keys[index])) {\n return notEmulated('a query parameter name in any but its plain form is not emulated')\n }\n }\n\n const missing = required.find(key => !keys.includes(key))\n\n return missing === undefined\n ? Object.fromEntries(request.query)\n : notEmulated(`requests without query parameter ${missing} are not emulated on this route`)\n}\n\n/** An integer in `[minimum, maximum]`, or not emulated. */\nexport const integerIn = (\n value: Schema.Json | undefined,\n label: string,\n minimum: number,\n maximum: number\n): number | NotEmulated =>\n Predicate.isNumber(value) && Number.isSafeInteger(value) && value >= minimum && value <= maximum\n ? value\n : notEmulated(`${label} must be an integer from ${minimum} to ${maximum}`)\n\n/** Error-input kinds of the JS API. */\nexport type StatefulInputKind = 'seed' | 'fault' | 'option'\n\n/** The core runtime the Node subpath creates (state, snapshot, restore), as the wrapper uses it. */\nexport type StatefulCore<State> = {\n readonly fetch: (request: Request) => Promise<Response>\n readonly baseUrl: string\n readonly snapshot: () => State\n readonly restore: (state: State) => Promise<void>\n readonly close: () => Promise<void>\n}\n\n/**\n * What the core runs for a request the wrapper forwarded: `(state, request)` to a response. The\n * Node subpath registers it as the core app's only route.\n */\nexport type CoreDispatch<State> = (state: State, request: Request) => Response\n\n/** A request header the ledger records. */\nexport type RecordedHeader = { readonly name: string; readonly json: boolean }\n\n/**\n * Fail-closed mode: the constant reasons ledgered and answered for an unrecognised request (no\n * emulated route shape) and for an unrecognisable `Authorization` header. Nothing the request\n * carries is ledgered or answered for either.\n */\nexport type StatefulFailClosed = {\n readonly unrecognised: string\n readonly unrecognisedAuthorization: string\n}\n\n/**\n * Resolved mode: what the emulator's `resolveRequest` decides for one API request, failing closed.\n *\n * - `unrecognised`: the raw request matches no emulated route shape exactly (or its credential\n * cannot be extracted). It is ledgered and answered with constant text only\n * (`/<unrecognised>`, the method as sent when it is a standard one, else `<other>`, an empty\n * query, no headers or body, the constant `reason`).\n * - `refused`: a recognised route refuses the request before its body is read (a missing\n * credential, a query key it does not take); the entry keeps the request's scrubbed fields.\n * - `route`: a recognised request, handed to `route` with the credential-free `params`.\n *\n * For `refused` and `route`, `ledgerPath` is the path the ledger records (and a fault's\n * `match.path` compares), with the credential already replaced by the emulator if the path carries\n * it; `secrets` are the credential values the emulator took from the recognised shape (a path\n * segment, a header), which the wrapper scrubs from everything it ledgers or answers and refuses\n * to see repeated. `guardedPath` is the request text besides the query that must not repeat a\n * secret (the path without the segment that carries the credential, for example).\n */\nexport type StatefulResolution<State, Env> =\n | { readonly kind: 'unrecognised'; readonly reason: string }\n | {\n readonly kind: 'refused'\n readonly route: StatefulRoute<State, Env>\n readonly ledgerPath: string\n readonly reason: string\n readonly secrets: ReadonlyArray<string>\n }\n | {\n readonly kind: 'route'\n readonly route: StatefulRoute<State, Env>\n readonly params: Readonly<Record<string, string>>\n readonly ledgerPath: string\n readonly guardedPath: string\n readonly secrets: ReadonlyArray<string>\n }\n\n/**\n * The emulator-owned texts of its recovery answers\n * (`{ error: { type: 'emulator_error', message } }`): `failed` when a response cannot be built or a\n * route throws (500), `closed` after `close` (503), and `unhandled` when handling the request\n * itself fails (500).\n */\nexport type StatefulErrorTexts = {\n readonly failed: string\n readonly closed: string\n readonly unhandled: string\n}\n\nconst defaultErrorTexts: StatefulErrorTexts = {\n failed: 'the emulator could not build the response',\n closed: 'the emulator is closed',\n unhandled: 'the emulator failed to handle the request'\n}\n\nexport type StatefulEmulatorConfig<State, Env> = {\n readonly routes: ReadonlyArray<StatefulRoute<State, Env>>\n readonly env: Env\n readonly initial: State\n /** Decode and build a seed; a string is why it is invalid. */\n readonly buildSeed: (input: unknown) => State | string\n /** Header rules every request must meet (after the credential); a string is not emulated. */\n readonly requestProblem?: (header: (name: string) => string | undefined) => string | undefined\n /**\n * Non-credential request headers the ledger records (lower-case names). A `json` header is\n * parsed and its credential-named keys redacted at any depth; an unparseable value is recorded\n * as `<redacted>`. A credential header name is refused when the emulator is built.\n */\n readonly recordHeaders: ReadonlyArray<RecordedHeader>\n /**\n * Opt-in fail-closed mode (see the module header): constant-text ledger entries for unrecognised\n * requests and Authorization headers, and the bearer value guarded as a secret. Every route\n * parameter must have a raw pattern (checked when the emulator is built).\n */\n readonly failClosed?: StatefulFailClosed\n /**\n * Opt-in resolved mode (not with `failClosed`; checked when the emulator is built), for an\n * emulator whose credential the wrapper cannot find by itself (a token in a path segment) or\n * that has its own credential rules: the emulator resolves every API request itself\n * (`StatefulResolution`) instead of the wrapper's route match and `Authorization` handling, and\n * names the credential values to guard. The wrapper then records the resolution's ledger path\n * and the query as sent (credential-named keys redacted), both scrubbed of the secrets, matches a\n * fault's `match.path` against that ledger path, and refuses, before the request headers are\n * checked or the body is parsed, a recognised request that repeats a secret: the raw query, the\n * `guardedPath`, or the body, each raw or percent-decoded once (`repeatsSecret`), and a JSON body\n * also in any key, string, or number once parsed and as it would be recorded\n * (`jsonRepeatsSecret`). Those refusals keep the request's scrubbed fields and never record the\n * body. Routes see the ledger path and no header names; fault answers and refusals are returned\n * outside the core; `recordHeaders` must be empty (checked at build). See the module header.\n * Omitted: the wrapper matches routes itself, as before.\n */\n readonly resolveRequest?: (request: Request, url: URL) => StatefulResolution<State, Env>\n /**\n * Opt-in: the texts of the emulator's recovery answers (`StatefulErrorTexts`). Omitted: the\n * default texts, as before.\n */\n readonly errorTexts?: StatefulErrorTexts\n /**\n * Opt-in, fail-closed mode only (checked when the emulator is built): a one-way digest of a\n * recognised bearer for the origin the request arrived on, handed to routes as\n * `EmulatedRequest.bearerDigest`, never the bearer itself. A route compares it with digests its\n * state holds (for example of the keys a seed marks as rejected on one origin), so a credential\n * can change an answer without the bearer reaching the state, the ledger, or `/_emulate/*`.\n * Taking the origin makes the digest per origin: one key gives different digests on two origins.\n * A digest that throws or repeats the bearer (through the closure) answers the 500 emulator\n * error before the route's shape check (no fault used, nothing written). Omitted: routes see no\n * digest, as before.\n */\n readonly bearerDigest?: (bearer: string, origin: string) => string\n /**\n * Opt-in, fail-closed mode only (checked at build): every refusal is ledgered with constant text\n * only, like an unrecognised request (`/<unrecognised>`, a standard method or `<other>`, an\n * empty query, no headers or body), keeping only its constant route (template or variant) and\n * reason, so a ledger entry holds request text only once the route admitted the request. Route\n * reasons must then be constants. Omitted: refusals keep the request's recorded fields, as\n * before.\n */\n readonly constantRefusals?: boolean\n /**\n * Opt-in, fail-closed mode only (checked at build): every request header other than\n * `Authorization`, its name and its value, is checked for a credential repeat through the\n * closure (not only the recorded headers), independently of what the ledger records; a repeat is\n * refused with the constant credential-repeat entry (`a request header repeats the credential`).\n * Omitted: only the recorded headers are checked, as before.\n */\n readonly guardAllHeaders?: boolean\n /**\n * Opt-in, fail-closed mode only (checked at build): a recognised request's prepared output must\n * not repeat its bearer. Every plan answers a `StreamedCommit` (a plain `Commit` answers the 500\n * emulator error), and before any fault is decided or anything is committed, every answer header\n * name and value, every answer chunk, and every `persisted` text are checked for the bearer\n * through the closure; a hit is refused with the constant credential-repeat entry\n * (`the answer would repeat the credential`), no fault used and nothing written. Routes still see\n * only the bearer's digest. Omitted: answers are not checked, as before.\n */\n readonly guardOutput?: boolean\n /**\n * Opt-in: a JSON body in which any object repeats a key (after JSON unescaping, so `\"id\"` and\n * `\"\\u0069d\"` are one key) is not emulated (`a JSON body with a repeated key is not emulated`),\n * instead of `JSON.parse` keeping the last value. Omitted: the last value wins, as before.\n */\n readonly uniqueJsonKeys?: boolean\n /** Clear runtime data (cursors) on reset and seed. */\n readonly clearRuntime: () => void\n /** Extra `/_emulate/state` fields (runtime data). */\n readonly runtimeState: () => Schema.JsonObject\n /** The `/_emulate/seed` answer for a new state. */\n readonly seedSummary: (state: State) => Schema.JsonObject\n readonly inputInvalid: (input: StatefulInputKind, reason: string) => Error\n}\n\nexport type StatefulEmulatorApi<State, Seed, Fault = StatefulFault, Entry = StatefulLedgerEntry> = {\n /**\n * The fetch handler (API routes and `/_emulate/*`); a request arrives on the origin of its URL.\n * Never rejects.\n */\n readonly fetch: (request: Request) => Promise<Response>\n /**\n * The fetch handler for requests that arrive on `origin` whatever their URL says (serve it on\n * a loopback server behind `EmulatedHttpClient`, which rewrites the origin). Never rejects.\n */\n readonly fetchOn: (origin: string) => (request: Request) => Promise<Response>\n readonly ledger: {\n readonly entries: () => ReadonlyArray<Entry>\n readonly clear: () => void\n }\n readonly faults: {\n /** Add a fault; throws the emulator's input-invalid error for an invalid fault. */\n readonly add: (fault: Fault) => StatefulFaultState<Fault>\n readonly list: () => ReadonlyArray<StatefulFaultState<Fault>>\n readonly clear: () => void\n }\n /** Restore the current seed and clear the ledger, faults, and runtime data (cursors). */\n readonly reset: () => Promise<void>\n /**\n * Replace the state with a new seed, which becomes what `reset` restores (runtime data is\n * cleared). Rejects with the emulator's input-invalid error for an invalid seed.\n */\n readonly seed: (seed: Seed) => Promise<void>\n /** A deep copy of the current state. */\n readonly snapshot: () => State\n readonly coverage: () => StatefulCoverage\n /** Close the core runtime. Later requests answer 503. Idempotent. */\n readonly close: () => Promise<void>\n}\n\nconst strict = { onExcessProperty: 'error' } as const\n\nconst decodeFault = Schema.decodeUnknownResult(StatefulFault, strict)\n\nconst decodeFaultList = Schema.decodeUnknownResult(\n Schema.Union([StatefulFault, Schema.Struct({ faults: Schema.Array(StatefulFault) })]),\n strict\n)\n\nconst decodeStreamFault = Schema.decodeUnknownResult(StatefulStreamFault, strict)\n\nconst decodeStreamFaultList = Schema.decodeUnknownResult(\n Schema.Union([StatefulStreamFault, Schema.Struct({ faults: Schema.Array(StatefulStreamFault) })]),\n strict\n)\n\nconst issueMessage = (issue: Schema.SchemaError['issue']): string =>\n new Schema.SchemaError(issue).message\n\n// A non-empty bearer credential. The value is never checked, stored, forwarded, or ledgered.\nconst bearerPattern = /^bearer\\s+\\S+/i\n\n/**\n * Fail-closed mode: exactly `Bearer `, one space, and one token of at least 8 non-space characters\n * (no other scheme spelling, no extra words, so combined duplicate headers never match). The token\n * must also match the RFC 6750 `b64token` syntax, start with a character that completes no escape\n * (not a hex digit, not `n`, `r`, `t`, `u`), and hold a character outside the JSON-number alphabet\n * (`isRecognisableBearerValue`, which says why). The value is guarded, never compared against\n * anything, stored, or ledgered.\n */\nconst recognisableBearerPattern = /^Bearer ([^\\s]{8,})$/\n\n/** The fail-closed bearer of an `Authorization` header, or `undefined` when unrecognisable. */\nconst recognisableBearer = (authorization: string | null): string | undefined => {\n const bearer = recognisableBearerPattern.exec(authorization ?? '')?.[1]\n\n return bearer !== undefined && isRecognisableBearerValue(bearer) ? bearer : undefined\n}\n\n/** The raw query of a request URL (after `?`, before `#`), or `undefined` when it has none. */\nconst rawQuery = (requestUrl: string): string | undefined => {\n const withoutFragment = requestUrl.split('#', 1)[0] ?? ''\n const start = withoutFragment.indexOf('?')\n\n return start === -1 ? undefined : withoutFragment.slice(start + 1)\n}\n\n/** True for a bare `?` or an empty `&`-separated component (`URLSearchParams` drops both). */\nconst hasEmptyQueryComponent = (requestUrl: string): boolean => {\n const query = rawQuery(requestUrl)\n\n return query !== undefined && query.split('&').some(component => component === '')\n}\n\n/**\n * Fail-closed mode: a query key or value or a header value that starts like a JSON object, array,\n * or string (`{`, `[`, `\"`) is recorded parsed with credential-named keys redacted at any depth, or\n * whole as `<redacted>` when it does not parse; any other text (numbers included) is recorded\n * unchanged. A request whose text repeats a guarded secret never gets here: it is ledgered with\n * constant text only.\n */\nconst recordedJsonLooking = (text: string): string => {\n const trimmed = text.trimStart()\n\n if (!trimmed.startsWith('{') && !trimmed.startsWith('[') && !trimmed.startsWith('\"')) {\n return text\n }\n\n const parsed = parseJsonText(text)\n\n return parsed === undefined\n ? redactedCredentialValue\n : JSON.stringify(redactCredentialFields(parsed))\n}\n\n/**\n * Fail-closed mode: the recorded query, built from every original pair (never one value per key)\n * and keyed by recorded key: credential-named keys have their values redacted, keys and values are\n * recorded as `recordedJsonLooking` does, and a key recorded more than once (two original keys may\n * record alike, for example two unparseable JSON-looking keys as `<redacted>`) lists its values in\n * order, as a JSON array.\n */\nconst recordedQueryPairs = (\n query: URLSearchParams,\n scrub: (text: string) => string\n): Readonly<Record<string, string>> => {\n const values = new Map<string, Array<string>>()\n\n for (const [key, value] of query) {\n const recordedKey = scrub(recordedJsonLooking(key))\n\n const recordedValue = scrub(\n isCredentialQueryKey(key) ? redactedCredentialValue : recordedJsonLooking(value)\n )\n\n values.set(recordedKey, [...(values.get(recordedKey) ?? []), recordedValue])\n }\n\n return Object.fromEntries(\n [...values].map(([key, list]) => [\n key,\n list.length === 1 ? (list[0] ?? '') : JSON.stringify(list)\n ])\n )\n}\n\n/** The constant reason of a body a route's decoded view refuses to check (the view threw). */\nconst uncheckableBodyReason = 'the request body cannot be checked for the credential'\n\nconst missingBearerReason =\n 'requests without Authorization: Bearer <token of at least 8 characters> are not emulated'\n\nconst pathMatches = (pattern: string, path: string): boolean =>\n pattern.endsWith('*') ? path.startsWith(pattern.slice(0, -1)) : pattern === path\n\nconst faultMatches = (\n fault: StatefulStreamFault,\n method: string,\n path: string,\n route: string | undefined\n): boolean =>\n (fault.match?.method === undefined || fault.match.method.toUpperCase() === method) &&\n (fault.match?.path === undefined || pathMatches(fault.match.path, path)) &&\n (fault.match?.route === undefined || fault.match.route === route)\n\nconst faultBody = (status: number): Schema.Json => ({\n error: { type: 'emulator_fault', message: `Emulator fault: status ${status}.` }\n})\n\nconst readText = (request: Request): Promise<string | undefined> =>\n request.text().then(\n text => text,\n () => undefined\n )\n\nconst readBytes = (request: Request): Promise<Uint8Array | undefined> =>\n request.arrayBuffer().then(\n buffer => new Uint8Array(buffer),\n () => undefined\n )\n\nconst isControlPath = (path: string): boolean =>\n path === '/_emulate' || path.startsWith('/_emulate/')\n\ntype MutableLedgerEntry = {\n seq: number\n method: string\n path: string\n route?: string\n query: Readonly<Record<string, string>>\n body?: Schema.Json\n bodyBytes?: number\n headers: Record<string, string>\n status: number\n evidence: EmulatorEvidence | 'unknown-route'\n notEmulated?: string\n fault?: 'status' | 'truncate-after-chunks'\n responseError?: string\n}\n\ntype MutableFaultState = {\n readonly id: number\n readonly fault: StatefulStreamFault\n remaining: number | undefined\n applied: number\n}\n\n/** How the first matching fault shapes an eligible request's answer. */\ntype FaultDecision =\n | { readonly kind: 'none' }\n | { readonly kind: 'answer'; readonly response: Response }\n | { readonly kind: 'truncate'; readonly after: number }\n\ntype Job<State, Env> = {\n readonly admission: Admission<State, Env>\n /** Ledger sequence number of the request (for synthetic request ids). */\n readonly seq: number\n /**\n * Decides the first matching fault for an answer of `chunks` streamed chunks (`undefined` for an\n * answer that is not streamed), using it up when it applies.\n */\n readonly decideFault: (chunks: number | undefined) => FaultDecision\n /** Guarded credential values, scrubbed from a plan's not-emulated reason. */\n readonly secrets: ReadonlyArray<string>\n notEmulated?: string\n /** Set when `guardOutput` refused the prepared output (a constant credential-repeat entry). */\n credentialRepeat?: boolean\n /** Resolved mode: the fault's answer, decided in the core but returned outside it. */\n faultAnswer?: Response\n /** Set when the core ran the dispatch for this job (it may answer without it, when closed). */\n dispatched?: boolean\n}\n\nconst withEvidence = (response: Response, evidence: EmulatorEvidence): Response => {\n const headers = new Headers(response.headers)\n\n if (evidence === 'unverified') {\n headers.set(emulatorEvidenceHeader, 'unverified')\n }\n\n return new Response(response.body, {\n status: response.status,\n statusText: response.statusText,\n headers\n })\n}\n\n/**\n * The recorded query: credential-named keys redacted, and any value that parses as a JSON object or\n * array (Dropbox's browser-style `arg` parameter, for example) recorded with its credential-named\n * keys redacted at any depth, and a JSON-looking value that does not parse recorded as `<redacted>`,\n * as a recorded JSON header is.\n */\nconst recordedQuery = (query: URLSearchParams): Readonly<Record<string, string>> =>\n Object.fromEntries(\n Object.entries(redactCredentialQuery(query)).map(([key, value]) => {\n if (!value.trimStart().startsWith('{') && !value.trimStart().startsWith('[')) {\n return [key, value]\n }\n\n // A JSON-looking value that does not parse cannot be redacted field by field: record it\n // whole as redacted, as a recorded JSON header is.\n const parsed = parseJsonText(value)\n\n return [\n key,\n parsed === undefined\n ? redactedCredentialValue\n : JSON.stringify(redactCredentialFields(parsed))\n ]\n })\n )\n\n/** A recorded header value: JSON with credential-named keys redacted, or the plain value. */\nconst recordedHeaderValue = (header: RecordedHeader, value: string): string => {\n if (!header.json) return value\n\n const parsed = parseJsonText(value)\n\n return parsed === undefined\n ? redactedCredentialValue\n : JSON.stringify(redactCredentialFields(parsed))\n}\n\n/** The request text a ledger entry records (constant in a `constantRefusals` refusal). */\ntype RecordedFields = Pick<MutableLedgerEntry, 'method' | 'path' | 'query' | 'headers'> & {\n body?: Schema.Json\n bodyBytes?: number\n}\n\n/** `constantRefusals`: a refused request's entry keeps only constant text. */\nconst blankEntry = (entry: MutableLedgerEntry) => {\n entry.method = unrecognisedMethod(entry.method.toUpperCase())\n entry.path = unrecognisedLedgerPath\n entry.query = {}\n entry.headers = {}\n delete entry.body\n delete entry.bodyBytes\n}\n\nconst snapshotEntry = (entry: MutableLedgerEntry): StatefulLedgerEntry => ({\n ...entry,\n query: { ...entry.query },\n headers: { ...entry.headers }\n})\n\n/** Whether an entry records no truncation fault (only a status fault, if any). */\nconst hasStatusFaultOnly = <Entry extends { readonly fault?: MutableLedgerEntry['fault'] }>(\n entry: Entry\n): entry is Entry & { readonly fault?: 'status' } => entry.fault !== 'truncate-after-chunks'\n\n/** `makeHeaderlessStatefulEmulator`: the entry without its (always empty) `headers` field. */\nconst snapshotHeaderlessEntry = ({\n headers: _headers,\n bodyBytes: _bodyBytes,\n ...entry\n}: MutableLedgerEntry): StatefulHeaderlessLedgerEntry => {\n const copy = { ...entry, query: { ...entry.query } }\n\n if (hasStatusFaultOnly(copy)) return copy\n\n // Unreachable: a headerless emulator decodes status faults only.\n throw new Error('a headerless stateful emulator records status faults only')\n}\n\n/**\n * Resolved mode: the constant reason when the raw query or the resolution's `guardedPath` repeats a\n * guarded secret (`repeatsSecret`: raw, and percent-decoded once), or `undefined`. Checked before\n * the body is read, so such a refusal leaves the request body unread.\n */\nconst partsCredentialRepeat = (\n url: URL,\n guardedPath: string,\n secrets: ReadonlyArray<string>\n): string | undefined => {\n if (repeatsSecret(url.search, secrets)) return 'the query repeats the credential'\n\n return repeatsSecret(guardedPath, secrets) ? 'the request path repeats the credential' : undefined\n}\n\n/**\n * Resolved mode: the constant reason when the body text repeats a guarded secret, or `undefined`:\n * `repeatsSecret` on the raw text, and for a body that parses as JSON also `jsonRepeatsSecret`\n * (every key, string, and number) and as it would be recorded. A body that cannot be read or parsed\n * is left to the route's body check.\n */\nconst bodyCredentialRepeat = (\n text: string | undefined,\n secrets: ReadonlyArray<string>\n): string | undefined => {\n if (text === undefined || text === '') return undefined\n\n const repeat = 'the request body repeats the credential'\n\n if (repeatsSecret(text, secrets)) return repeat\n\n const json = parseJsonText(text)\n\n // Normalised forms (`\\u0051` escapes, `1.2345678e7` numbers) only show once parsed.\n return json !== undefined &&\n (jsonRepeatsSecret(json, secrets) || repeatsSecret(JSON.stringify(json), secrets))\n ? repeat\n : undefined\n}\n\n/** The body as `Request.text()` reads it: UTF-8, a leading byte order mark dropped. */\nconst bodyText = (bytes: Uint8Array): string => new TextDecoder().decode(bytes)\n\nconst snapshotFault = (state: MutableFaultState): StatefulFaultState<StatefulStreamFault> => ({\n ...state\n})\n\nconst isStreamedCommit = (planned: Commit | StreamedCommit): planned is StreamedCommit =>\n !Predicate.isFunction(planned)\n\n/** The constant reason of a prepared output that would repeat the bearer (`guardOutput`). */\nconst outputRepeatReason = 'the answer would repeat the credential'\n\n/**\n * `guardOutput`: whether a streamed commit's answer (every header name and value, every chunk, and\n * the whole body) or any text it would persist repeats a guarded secret, through the closure.\n */\nconst outputRepeatsSecret = (planned: StreamedCommit, secrets: ReadonlyArray<string>): boolean => {\n const { answer } = planned\n\n return [\n ...Object.entries(answer.headers).flat(),\n ...answer.chunks,\n answer.chunks.join(''),\n ...planned.persisted\n ].some(text => textRepeatsSecret(text, secrets))\n}\n\n/**\n * A streamed answer's body, strictly pull-driven (one chunk per pull), closed cleanly after\n * `after` chunks when a truncation fault applies; no body at all for an answer without chunks.\n */\nconst streamedResponse = (answer: StreamedAnswer, after: number | undefined): Response => {\n if (answer.chunks.length === 0) {\n return new Response(null, { status: answer.status, headers: answer.headers })\n }\n\n const encoder = new TextEncoder()\n const sent = after === undefined ? answer.chunks : answer.chunks.slice(0, after)\n let next = 0\n\n const body = new ReadableStream<Uint8Array>(\n {\n pull: controller => {\n const chunk = sent[next]\n\n if (chunk === undefined) {\n controller.close()\n\n return\n }\n\n next += 1\n controller.enqueue(encoder.encode(chunk))\n }\n },\n { highWaterMark: 0 }\n )\n\n return new Response(body, { status: answer.status, headers: answer.headers })\n}\n\n/**\n * Build the wrapper over a core runtime (`makeStatefulEmulator`, with truncation faults\n * `makeChunkedStatefulEmulator`, or without ledgered headers `makeHeaderlessStatefulEmulator`).\n * `createCore` receives the dispatch the core must run for every request (its only route) and\n * returns the runtime adapter; `snapshotLedgerEntry` copies a ledger entry for readers.\n */\nconst buildStatefulEmulator = async <State, Env, Seed, Entry>(\n config: StatefulEmulatorConfig<State, Env>,\n createCore: (dispatch: CoreDispatch<State>) => Promise<StatefulCore<State>>,\n chunkFaults: boolean,\n snapshotLedgerEntry: (entry: MutableLedgerEntry) => Entry\n): Promise<StatefulEmulatorApi<State, Seed, StatefulStreamFault, Entry>> => {\n const credentialHeader = config.recordHeaders.find(header => isCredentialHeaderName(header.name))\n\n if (credentialHeader !== undefined) {\n throw new Error(`the ledger never records the credential header ${credentialHeader.name}`)\n }\n\n if (config.resolveRequest !== undefined && config.failClosed !== undefined) {\n throw new Error('resolveRequest (resolved mode) and failClosed exclude each other')\n }\n\n // Resolved mode checks no request header for a credential repeat, so it records none.\n if (config.resolveRequest !== undefined && config.recordHeaders.length > 0) {\n throw new Error('resolveRequest (resolved mode) records no request header (recordHeaders)')\n }\n\n const resolveRequest = config.resolveRequest\n const errorTexts = config.errorTexts ?? defaultErrorTexts\n\n if (config.bearerDigest !== undefined && config.failClosed === undefined) {\n throw new Error('bearerDigest needs fail-closed mode (failClosed)')\n }\n\n for (const option of ['constantRefusals', 'guardAllHeaders', 'guardOutput'] as const) {\n if (config[option] === true && config.failClosed === undefined) {\n throw new Error(`${option} needs fail-closed mode (failClosed)`)\n }\n }\n\n const constantRefusals = config.constantRefusals === true\n const templates = new Set(config.routes.map(route => route.path))\n const variantRows = new Set<string>()\n\n for (const route of config.routes) {\n if (route.variants === undefined) continue\n\n if (route.variants.length === 0) {\n throw new Error(`route ${route.method} ${route.path} has an empty variants list`)\n }\n\n for (const variant of route.variants) {\n const where = `variant ${variant.method} ${variant.path} of ${route.method} ${route.path}`\n\n if (variant.evidence !== route.evidence) {\n throw new Error(`${where} must carry its route's evidence`)\n }\n\n if (templates.has(variant.path) || variantRows.has(variant.path)) {\n throw new Error(`${where} repeats a route template or another variant path`)\n }\n\n variantRows.add(variant.path)\n }\n }\n\n if (config.failClosed !== undefined) {\n for (const route of config.routes) {\n const missing = unpatternedParams(route)\n\n if (missing.length > 0) {\n const where = `fail-closed route ${route.method} ${route.path}`\n\n throw new Error(`${where} needs raw patterns for ${missing.join(', ')}`)\n }\n }\n }\n\n const match = routeMatcher(config.routes)\n const manifest = config.routes.flatMap(routeManifest)\n const manifestPaths = new Set(manifest.map(row => row.path))\n const jobs = new Map<number, Job<State, Env>>()\n let nextJobId = 1\n\n // Plan, fault decision, and commit run synchronously together: no request interleaves.\n const dispatch: CoreDispatch<State> = (state, request) => {\n const job = jobs.get(Number(request.headers.get(emulatorJobHeader) ?? 'NaN'))\n\n if (job === undefined) return handlerFailedResponse()\n\n job.dispatched = true\n\n try {\n const planned = job.admission.plan(state, { env: config.env, seq: job.seq })\n\n if (isNotEmulated(planned)) {\n // Resolved mode keeps every refusal reason percent-encodable: one that is not (an unpaired\n // surrogate) is a handler failure.\n if (\n resolveRequest !== undefined &&\n Result.isFailure(Result.try(() => encodeURIComponent(planned.reason)))\n ) {\n return handlerFailedResponse()\n }\n\n const reason = scrubSecrets(planned.reason, job.secrets)\n\n job.notEmulated = reason\n\n // Resolved mode: the wrapper answers the refusal itself, outside the core.\n return resolveRequest === undefined ? notEmulatedResponse(reason) : answeredOutsideCore()\n }\n\n const streamed = isStreamedCommit(planned)\n\n // `guardOutput`: the prepared answer and the texts the commit would store must not repeat\n // the bearer; checked before any fault is decided or anything is written.\n if (config.guardOutput === true) {\n if (!streamed) return handlerFailedResponse()\n\n if (outputRepeatsSecret(planned, job.secrets)) {\n job.notEmulated = outputRepeatReason\n job.credentialRepeat = true\n\n return notEmulatedResponse(outputRepeatReason)\n }\n }\n\n const decision = job.decideFault(streamed ? planned.answer.chunks.length : undefined)\n\n if (decision.kind === 'answer') {\n if (resolveRequest === undefined) return decision.response\n\n // Resolved mode: the fault's answer is returned outside the core, so a reset or a close\n // before it is read never cancels it.\n job.faultAnswer = decision.response\n\n return answeredOutsideCore()\n }\n\n if (!streamed) return planned()\n\n // A truncated answer is the prepared one cut short; its commit never runs.\n if (decision.kind === 'truncate') return streamedResponse(planned.answer, decision.after)\n\n planned.commit()\n\n return streamedResponse(planned.answer, undefined)\n } catch {\n return handlerFailedResponse()\n }\n }\n\n const core = await createCore(dispatch)\n\n let baseline: State = config.initial\n let entries: Array<MutableLedgerEntry> = []\n let faultStates: Array<MutableFaultState> = []\n let nextSeq = 1\n let nextFaultId = 1\n let closed = false\n\n const clearLedger = () => {\n entries = []\n nextSeq = 1\n }\n\n /** Why a fault's `match.route` could never match (it names no manifest row), or `undefined`. */\n const matchRouteProblem = (fault: StatefulStreamFault): string | undefined => {\n const route = fault.match?.route\n\n return route === undefined || manifestPaths.has(route)\n ? undefined\n : 'match.route must name a manifest row of this emulator'\n }\n\n const addFault = (input: unknown): StatefulFaultState<StatefulStreamFault> | string => {\n const decoded = chunkFaults ? decodeStreamFault(input) : decodeFault(input)\n\n if (Result.isFailure(decoded)) {\n return issueMessage(decoded.failure.issue)\n }\n\n const problem = matchRouteProblem(decoded.success)\n\n if (problem !== undefined) return problem\n\n const state: MutableFaultState = {\n id: nextFaultId++,\n fault: decoded.success,\n remaining: decoded.success.count,\n applied: 0\n }\n\n faultStates.push(state)\n\n return snapshotFault(state)\n }\n\n const takeFault = (\n method: string,\n path: string,\n route: string | undefined\n ): MutableFaultState | undefined =>\n faultStates.find(\n state =>\n (state.remaining === undefined || state.remaining > 0) &&\n faultMatches(state.fault, method, path, route)\n )\n\n const spend = (state: MutableFaultState) => {\n state.applied += 1\n\n if (state.remaining !== undefined) {\n state.remaining -= 1\n }\n }\n\n /** Build the fault's response first: a response that cannot be built must not consume it. */\n const applyFault = (state: MutableFaultState, fault: StatefulFault): Response => {\n const headers = new Headers(fault.headers ?? {})\n const body = fault.body ?? faultBody(fault.status)\n\n if (!headers.has('content-type')) {\n headers.set('content-type', Predicate.isString(body) ? 'text/plain' : 'application/json')\n }\n\n const response = new Response(Predicate.isString(body) ? body : JSON.stringify(body), {\n status: fault.status,\n headers\n })\n\n spend(state)\n\n return response\n }\n\n const reset = async () => {\n clearLedger()\n faultStates = []\n config.clearRuntime()\n await core.restore(baseline)\n }\n\n const reseed = async (input: unknown): Promise<State | string> => {\n const next = config.buildSeed(input)\n\n if (Predicate.isString(next)) {\n return next\n }\n\n await core.restore(next)\n config.clearRuntime()\n baseline = next\n\n return next\n }\n\n const coverage = (): StatefulCoverage => ({\n routes: manifest.map(route => ({\n ...route,\n requests: entries.filter(\n entry =>\n entry.route === route.path &&\n (variantRows.has(route.path) || entry.method.toUpperCase() === route.method)\n ).length\n })),\n unknownRouteRequests: entries.filter(entry => entry.evidence === 'unknown-route').length,\n notEmulatedRequests: entries.filter(entry => entry.notEmulated !== undefined).length\n })\n\n /** The ledgered 400; the reason is scrubbed of the request's guarded secrets first. */\n const refuse = (\n entry: MutableLedgerEntry,\n reason: string,\n secrets: ReadonlyArray<string> = []\n ): Response => {\n const safe = scrubSecrets(reason, secrets)\n\n entry.notEmulated = safe\n\n return notEmulatedResponse(safe)\n }\n\n /**\n * Fail-closed mode: the constant reason when any part of a recognised request repeats a guarded\n * secret, or `undefined`. Checked, each through the fixpoint closure of `textRepeatsSecret` (a\n * capped closure counts as a repeat): the raw path and every path segment, the raw query and\n * every decoded query key and value, every recorded header, and the body.\n */\n const credentialRepeat = (\n request: Request,\n url: URL,\n body: string | undefined,\n secrets: ReadonlyArray<string>,\n binding: StatefulRouteBinding\n ): string | undefined => {\n if (\n textRepeatsSecret(url.pathname, secrets) ||\n url.pathname.split('/').some(segment => textRepeatsSecret(segment, secrets))\n ) {\n return 'the request path repeats the credential'\n }\n\n if (\n textRepeatsSecret(url.search, secrets) ||\n [...url.searchParams].some(\n ([key, value]) => textRepeatsSecret(key, secrets) || textRepeatsSecret(value, secrets)\n )\n ) {\n return 'the query repeats the credential'\n }\n\n if (\n config.recordHeaders.some(recorded =>\n textRepeatsSecret(request.headers.get(recorded.name) ?? '', secrets)\n )\n ) {\n return 'a recorded request header repeats the credential'\n }\n\n if (config.guardAllHeaders === true) {\n const headers: Array<string> = []\n\n request.headers.forEach((value, name) => {\n if (name !== 'authorization') headers.push(name, value)\n })\n\n if (headers.some(text => textRepeatsSecret(text, secrets))) {\n return 'a request header repeats the credential'\n }\n }\n\n if (body === undefined) return undefined\n\n const repeat = 'the request body repeats the credential'\n\n if (textRepeatsSecret(body, secrets)) return repeat\n\n // The route's decoded views of its body (base64url content, for example) are checked too. A\n // view that throws refuses the body (uncertainty refuses, it never admits): a repeat when what\n // it decoded cleanly holds the bearer, else its declared constant reason, else the uncheckable\n // reason, so a request without the bearer is never told it repeats it.\n const { decodedViews, viewRefusalReasons = [] } = binding\n const views = Result.try(() => (decodedViews === undefined ? [] : decodedViews(body)))\n\n if (Result.isFailure(views)) {\n const refusal = views.failure\n\n if (!(refusal instanceof DecodedViewRefusal)) return uncheckableBodyReason\n\n if (refusal.decoded.some(view => textRepeatsSecret(view, secrets))) return repeat\n\n return viewRefusalReasons.includes(refusal.reason)\n ? scrubSecrets(refusal.reason, secrets)\n : uncheckableBodyReason\n }\n\n return views.success.some(view => textRepeatsSecret(view, secrets)) ? repeat : undefined\n }\n\n /** Everything after the route match: credential, headers, body, shape, then the core. */\n const routed = async (\n request: Request,\n url: URL,\n entry: MutableLedgerEntry,\n matched: MatchedRoute<State, Env>,\n secrets: ReadonlyArray<string>,\n arrivedOn: string,\n recorded: RecordedFields,\n /** Resolved mode: the request text besides the query that must not repeat a secret. */\n guardedPath: string | undefined\n ): Promise<Response> => {\n // Resolved mode: routes read the content type only, never a header that may carry a secret.\n const header = (name: string): string | undefined =>\n isCredentialHeaderName(name) ||\n (guardedPath !== undefined && name.toLowerCase() !== 'content-type')\n ? undefined\n : (request.headers.get(name) ?? undefined)\n\n const refused = (reason: string) => refuse(entry, reason, secrets)\n\n // Resolved mode reads the request body once, after the query and path checks, and every later\n // step reuses that read (`undefined`: the body is unreadable, consumed or locked included).\n let resolvedBody: { readonly bytes: Uint8Array | undefined } | undefined\n\n if (guardedPath !== undefined) {\n // Resolved mode: the emulator resolved the credential; nothing may repeat it.\n const partsRepeat = partsCredentialRepeat(url, guardedPath, secrets)\n\n if (partsRepeat !== undefined) return refused(partsRepeat)\n\n resolvedBody = { bytes: await readBytes(request) }\n\n const bodyRepeat = bodyCredentialRepeat(\n resolvedBody.bytes === undefined ? undefined : bodyText(resolvedBody.bytes),\n secrets\n )\n\n if (bodyRepeat !== undefined) return refused(bodyRepeat)\n } else if (config.failClosed === undefined) {\n if (!bearerPattern.test(request.headers.get('authorization') ?? '')) {\n return refused('a non-empty Authorization: Bearer credential is required')\n }\n } else {\n // The header is absent here (an unrecognisable one never reaches a route).\n if (secrets.length === 0) return refused(missingBearerReason)\n\n // A request repeating the credential never gets here (see `credentialRepeat`).\n if (hasEmptyQueryComponent(request.url)) {\n return refused('empty query components (a bare ? or a stray &) are not emulated')\n }\n }\n\n const problem = config.requestProblem?.(header)\n\n if (problem !== undefined) return refused(problem)\n\n // Resolved mode reuses the one body read it already made. Every other emulator awaits\n // `readText(request)` directly, so its request scheduling is exactly what it was.\n const resolvedText = (): string | undefined =>\n resolvedBody?.bytes === undefined ? undefined : bodyText(resolvedBody.bytes)\n\n let json: Schema.Json | undefined\n let bytes: Uint8Array | undefined\n\n switch (matched.route.body) {\n case 'none': {\n const text = resolvedBody === undefined ? await readText(request) : resolvedText()\n\n if (text === undefined || text !== '') {\n return refused('this route takes no request body')\n }\n\n break\n }\n\n case 'json': {\n const text = resolvedBody === undefined ? await readText(request) : resolvedText()\n\n if (mediaType(header('content-type')) !== 'application/json') {\n return refused('this route takes a content-type: application/json body')\n }\n\n const parsed = text === undefined ? undefined : parseJsonText(text)\n\n if (parsed === undefined) return refused('the request body is not valid JSON')\n\n if (config.uniqueJsonKeys === true && text !== undefined && repeatsJsonKey(text)) {\n return refused('a JSON body with a repeated key is not emulated')\n }\n\n // Redacted before it is stored: a refused request's body stays in the ledger too (with\n // `constantRefusals`, only once the route admits the request).\n recorded.body = redactCredentialFields(parsed)\n\n if (!constantRefusals) entry.body = recorded.body\n\n json = parsed\n\n break\n }\n\n case 'json-or-empty': {\n const text = resolvedBody === undefined ? await readText(request) : resolvedText()\n\n if (text === undefined) return refused('the request body is unreadable')\n\n if (text === '') break\n\n const parsed = parseJsonText(text)\n\n if (parsed === undefined) return refused('the request body is not JSON')\n\n if (config.uniqueJsonKeys === true && repeatsJsonKey(text)) {\n return refused('a JSON body with a repeated key is not emulated')\n }\n\n recorded.body = redactCredentialFields(parsed)\n\n if (!constantRefusals) entry.body = recorded.body\n\n json = parsed\n\n break\n }\n\n case 'bytes': {\n bytes = resolvedBody === undefined ? await readBytes(request) : resolvedBody.bytes\n\n if (bytes === undefined) return refused('the request body is unreadable')\n\n recorded.bodyBytes = bytes.byteLength\n\n if (!constantRefusals) entry.bodyBytes = recorded.bodyBytes\n\n break\n }\n }\n\n // Fail-closed mode only (checked at build): the per-origin digest of the bearer, never the\n // bearer. A digest that cannot be made, or that repeats the bearer, is a handler failure.\n let bearerDigest: string | undefined\n const [bearer] = secrets\n\n if (config.bearerDigest !== undefined && bearer !== undefined) {\n const digest = Result.try(() => config.bearerDigest?.(bearer, arrivedOn))\n\n if (\n Result.isFailure(digest) ||\n !Predicate.isString(digest.success) ||\n textRepeatsSecret(digest.success, secrets)\n ) {\n entry.responseError = 'the bearer digest failed'\n\n return emulatorError(500, errorTexts.failed)\n }\n\n bearerDigest = digest.success\n }\n\n // Resolved mode: routes see the ledgered path and no header names, never the credential a\n // path segment or a header name may carry.\n const admitted = matched.route.admit(\n {\n method: request.method.toUpperCase(),\n path: guardedPath === undefined ? url.pathname : entry.path,\n params: matched.params,\n query: url.searchParams,\n rawQuery: rawQuery(request.url) ?? '',\n header,\n headerNames: guardedPath === undefined ? [...request.headers.keys()] : [],\n json,\n bytes,\n bearerDigest\n },\n config.env\n )\n\n if (isNotEmulated(admitted)) return refused(admitted.reason)\n\n // A route with variants admits a request as exactly one of them; a route without variants\n // admits it as none of them.\n const variants = matched.route.variants\n const variant = admitted.variant\n\n if (\n variants === undefined ? variant !== undefined : !variants.some(row => row.path === variant)\n ) {\n entry.responseError = 'the route admitted the request as no row of its manifest'\n\n return emulatorError(500, errorTexts.failed)\n }\n\n if (variant !== undefined) entry.route = variant\n\n // `constantRefusals`: the request text is recorded only now that the route admitted it.\n if (constantRefusals) {\n entry.method = recorded.method\n entry.path = recorded.path\n entry.query = recorded.query\n entry.headers = recorded.headers\n\n if (recorded.body !== undefined) entry.body = recorded.body\n\n if (recorded.bodyBytes !== undefined) entry.bodyBytes = recorded.bodyBytes\n }\n\n const method = request.method.toUpperCase()\n const jobId = nextJobId++\n // Resolved mode: faults match, and the core sees, the ledgered path (never the credential).\n const requestPath = guardedPath === undefined ? url.pathname : entry.path\n\n const job: Job<State, Env> = {\n admission: admitted,\n seq: entry.seq,\n secrets,\n decideFault: chunks => {\n const state = takeFault(method, requestPath, entry.route)\n\n if (state === undefined) return { kind: 'none' }\n\n const fault = state.fault\n\n if (fault.kind === 'status') {\n const response = applyFault(state, fault)\n\n entry.fault = 'status'\n\n return { kind: 'answer', response }\n }\n\n // A truncation that cannot take effect answers 500 and is not used up (never a no-op).\n if (chunks === undefined || fault.chunks >= chunks) {\n entry.responseError =\n `emulator fault cannot apply: truncate-after-chunks after ${fault.chunks} chunk(s) ` +\n 'needs a streamed answer of more chunks'\n\n return { kind: 'answer', response: emulatorError(500, 'emulator fault cannot apply') }\n }\n\n spend(state)\n entry.fault = 'truncate-after-chunks'\n\n return { kind: 'truncate', after: fault.chunks }\n }\n }\n\n jobs.set(jobId, job)\n\n try {\n const response = await core.fetch(\n new Request(new URL(requestPath, core.baseUrl), {\n method: 'POST',\n headers: { [emulatorJobHeader]: String(jobId) }\n })\n )\n\n if (response.headers.has(handlerFailedHeader)) {\n entry.responseError = 'the route handler failed'\n\n return emulatorError(500, errorTexts.failed)\n }\n\n // Resolved mode: a core that answered without running the dispatch (closed meanwhile)\n // decided nothing about the request.\n if (guardedPath !== undefined && job.dispatched !== true) {\n entry.responseError = 'the route handler answered no eligibility verdict'\n\n return emulatorError(500, errorTexts.failed)\n }\n\n if (job.notEmulated !== undefined) {\n entry.notEmulated = job.notEmulated\n\n if (constantRefusals || job.credentialRepeat === true) blankEntry(entry)\n\n // The constant credential-repeat entry keeps the route template, as a request repeat does.\n if (job.credentialRepeat === true) entry.route = matched.route.path\n\n // Resolved mode: the wrapper answers the refusal itself, outside the core.\n if (guardedPath !== undefined) return notEmulatedResponse(job.notEmulated)\n }\n\n return job.faultAnswer ?? response\n } finally {\n jobs.delete(jobId)\n }\n }\n\n /**\n * Fail closed: an unrecognised request is ledgered and answered with constant text only. `method`\n * is the request method upper-cased (fail-closed mode) or as sent (resolved mode).\n */\n const unrecognised = (method: string, reason: string): Response => {\n entries.push({\n seq: nextSeq++,\n method: unrecognisedMethod(method),\n path: unrecognisedLedgerPath,\n query: {},\n headers: {},\n status: 400,\n evidence: 'unknown-route',\n notEmulated: reason\n })\n\n return notEmulatedResponse(reason)\n }\n\n const emulatedApi = async (request: Request, url: URL, arrivedOn: string): Promise<Response> => {\n const resolution = resolveRequest?.(request, url)\n\n // Resolved mode: the emulator's own resolution fails closed with constant text only.\n if (resolution?.kind === 'unrecognised') return unrecognised(request.method, resolution.reason)\n\n const matched: MatchedRoute<State, Env> | undefined =\n resolution === undefined\n ? match(request.method, url.pathname)\n : {\n route: resolution.route,\n params: resolution.kind === 'route' ? resolution.params : {}\n }\n\n const failClosed = config.failClosed\n let secrets: ReadonlyArray<string> = resolution?.secrets ?? []\n\n if (failClosed !== undefined) {\n const authorization = request.headers.get('authorization')\n const bearer = recognisableBearer(authorization)\n\n // Its credential cannot be extracted and scrubbed: nothing of the request is kept.\n if (authorization !== null && bearer === undefined) {\n return unrecognised(request.method.toUpperCase(), failClosed.unrecognisedAuthorization)\n }\n\n if (matched === undefined) {\n return unrecognised(request.method.toUpperCase(), failClosed.unrecognised)\n }\n\n secrets = bearer === undefined ? [] : [bearer]\n\n if (secrets.length > 0) {\n // Read from a copy: the route reads the body again.\n const body = request.body === null ? undefined : await readText(request.clone())\n const reason = credentialRepeat(request, url, body, secrets, matched.route)\n\n // Nothing of a request that repeats the credential is kept, in any part: it is ledgered\n // and answered with constant text only (its route template is constant too).\n if (reason !== undefined) {\n entries.push({\n seq: nextSeq++,\n method: unrecognisedMethod(request.method.toUpperCase()),\n path: unrecognisedLedgerPath,\n route: matched.route.path,\n query: {},\n headers: {},\n status: 400,\n evidence: matched.route.evidence,\n notEmulated: reason\n })\n\n return withEvidence(notEmulatedResponse(reason), matched.route.evidence)\n }\n }\n }\n\n const scrub = (text: string): string => scrubSecrets(text, secrets)\n\n // Query keys and values are scrubbed of the request's secrets before they are recorded. In\n // fail-closed mode every original pair is recorded (no pair repeats a secret by now): a key\n // that occurs more than once is recorded as a JSON array of its values, in order. Resolved\n // mode records the values as sent (credential-named keys redacted).\n const query =\n failClosed === undefined\n ? Object.fromEntries(\n Object.entries(\n resolution === undefined\n ? recordedQuery(url.searchParams)\n : redactCredentialQuery(url.searchParams)\n ).map(([key, value]) => [scrub(key), scrub(value)])\n )\n : recordedQueryPairs(url.searchParams, scrub)\n\n const recorded: RecordedFields = {\n method: scrub(request.method),\n path: scrub(resolution?.ledgerPath ?? url.pathname),\n query,\n headers: {}\n }\n\n for (const header of config.recordHeaders) {\n const value = request.headers.get(header.name)\n\n if (value === null) continue\n\n const text = recordedHeaderValue(header, value)\n\n // Fail-closed mode recognises a JSON-looking value whatever the header's declared format.\n recorded.headers[header.name] = scrub(\n failClosed === undefined ? text : recordedJsonLooking(text)\n )\n }\n\n const evidence = matched?.route.evidence ?? 'unknown-route'\n\n // `constantRefusals`: constant text until the route admits the request.\n const entry: MutableLedgerEntry = constantRefusals\n ? {\n seq: nextSeq++,\n method: unrecognisedMethod(request.method.toUpperCase()),\n path: unrecognisedLedgerPath,\n query: {},\n headers: {},\n status: 0,\n evidence\n }\n : resolution === undefined\n ? {\n seq: nextSeq++,\n method: recorded.method,\n path: recorded.path,\n query: recorded.query,\n headers: { ...recorded.headers },\n status: 0,\n evidence\n }\n : // Resolved mode: the route is known from the start (it keeps this position).\n {\n seq: nextSeq++,\n method: recorded.method,\n path: recorded.path,\n route: resolution.route.path,\n query: recorded.query,\n headers: { ...recorded.headers },\n status: 0,\n evidence\n }\n\n entries.push(entry)\n\n if (matched === undefined) {\n entry.status = 400\n\n return refuse(entry, 'no emulated route for this method and path')\n }\n\n entry.route = matched.route.path\n\n if (matched.route.origin !== undefined && matched.route.origin !== arrivedOn) {\n entry.status = 400\n\n return withEvidence(\n refuse(entry, `this route is recorded on ${matched.route.origin} only`, secrets),\n matched.route.evidence\n )\n }\n\n // Resolved mode: the emulator refused the request before its body is read.\n if (resolution?.kind === 'refused') {\n const tagged = withEvidence(\n refuse(entry, resolution.reason, secrets),\n resolution.route.evidence\n )\n\n entry.status = tagged.status\n\n return tagged\n }\n\n const guardedPath = resolution?.guardedPath\n\n // Error recovery still answers through the route: the fallback 500 is evidence-tagged and\n // the ledger records the status actually sent.\n const response = await routed(\n request,\n url,\n entry,\n matched,\n secrets,\n arrivedOn,\n recorded,\n guardedPath\n ).catch(() => {\n entry.responseError = 'the emulator could not build or produce the response'\n\n return emulatorError(500, errorTexts.failed)\n })\n\n const tagged = withEvidence(response, matched.route.evidence)\n\n entry.status = tagged.status\n\n return tagged\n }\n\n const controlPlane = async (request: Request, path: string): Promise<Response> => {\n const method = request.method\n const allow = (methods: string) => emulatorError(405, 'method not allowed', { allow: methods })\n\n const jsonBody = async (): Promise<Schema.Json | undefined> => {\n const text = await readText(request)\n\n return text === undefined ? undefined : parseJsonText(text)\n }\n\n switch (path) {\n case '/_emulate/ledger':\n if (method === 'GET') {\n return jsonResponse(200, { entries: entries.map(snapshotLedgerEntry) })\n }\n\n if (method === 'DELETE') {\n const cleared = entries.length\n\n clearLedger()\n\n return jsonResponse(200, { cleared })\n }\n\n return allow('GET, DELETE')\n\n case '/_emulate/faults': {\n if (method === 'GET') {\n return jsonResponse(200, { faults: faultStates.map(snapshotFault) })\n }\n\n if (method === 'DELETE') {\n const cleared = faultStates.length\n\n faultStates = []\n\n return jsonResponse(200, { cleared })\n }\n\n if (method !== 'POST') {\n return allow('GET, POST, DELETE')\n }\n\n const input = await jsonBody()\n\n const decoded = chunkFaults ? decodeStreamFaultList(input) : decodeFaultList(input)\n\n if (Result.isFailure(decoded)) {\n return emulatorError(400, `invalid fault: ${issueMessage(decoded.failure.issue)}`)\n }\n\n const faults = 'faults' in decoded.success ? decoded.success.faults : [decoded.success]\n const problem = faults.map(matchRouteProblem).find(Predicate.isString)\n\n // Checked before any fault is added: a list is added whole or not at all.\n if (problem !== undefined) return emulatorError(400, `invalid fault: ${problem}`)\n\n return jsonResponse(201, { faults: faults.map(fault => addFault(fault)) })\n }\n\n case '/_emulate/reset':\n if (method !== 'POST') {\n return allow('POST')\n }\n\n await reset()\n\n return jsonResponse(200, { reset: true })\n\n case '/_emulate/state':\n if (method !== 'GET') {\n return allow('GET')\n }\n\n return jsonResponse(200, {\n state: core.snapshot(),\n ...config.runtimeState(),\n faults: faultStates.map(snapshotFault),\n ledgerEntries: entries.length\n })\n\n case '/_emulate/seed': {\n if (method !== 'POST') {\n return allow('POST')\n }\n\n const next = await reseed((await jsonBody()) ?? null)\n\n if (Predicate.isString(next)) {\n return emulatorError(400, `invalid seed: ${next}`)\n }\n\n return jsonResponse(200, { seeded: true, ...config.seedSummary(next) })\n }\n\n case '/_emulate/coverage':\n if (method !== 'GET') {\n return allow('GET')\n }\n\n return jsonResponse(200, coverage())\n\n default:\n return emulatorError(404, 'unknown control-plane route')\n }\n }\n\n /**\n * Last resort when handling itself fails (for example an unparseable request URL): a 500\n * emulator error, tagged with the matched route's evidence when there is one (in resolved mode,\n * whose own resolution may be what failed, never).\n */\n const lastResort = (request: Request): Response => {\n const path = URL.canParse(request.url) ? new URL(request.url).pathname : undefined\n const failed = emulatorError(500, errorTexts.unhandled)\n\n if (path === undefined || isControlPath(path) || resolveRequest !== undefined) return failed\n\n const matched = match(request.method, path)\n\n return matched === undefined ? failed : withEvidence(failed, matched.route.evidence)\n }\n\n const handle = async (request: Request, origin: string | undefined): Promise<Response> => {\n if (closed) {\n return emulatorError(503, errorTexts.closed)\n }\n\n const url = new URL(request.url)\n\n return isControlPath(url.pathname)\n ? controlPlane(request, url.pathname)\n : emulatedApi(request, url, origin ?? url.origin)\n }\n\n const fetchOn =\n (origin: string | undefined) =>\n (request: Request): Promise<Response> =>\n handle(request, origin).catch(() => lastResort(request))\n\n return {\n fetch: fetchOn(undefined),\n fetchOn: origin => fetchOn(origin),\n ledger: {\n entries: () => entries.map(snapshotLedgerEntry),\n clear: clearLedger\n },\n faults: {\n add: fault => {\n const added = addFault(fault)\n\n if (Predicate.isString(added)) {\n throw config.inputInvalid('fault', added)\n }\n\n return added\n },\n list: () => faultStates.map(snapshotFault),\n clear: () => {\n faultStates = []\n }\n },\n reset,\n seed: async input => {\n const next = await reseed(input)\n\n if (Predicate.isString(next)) {\n throw config.inputInvalid('seed', next)\n }\n },\n snapshot: () => core.snapshot(),\n coverage,\n close: () => {\n closed = true\n\n return core.close()\n }\n }\n}\n\n/** The fault states of an emulator without truncation faults (its decoder takes status faults). */\nconst statusFaultStates = (\n states: ReadonlyArray<StatefulFaultState<StatefulStreamFault>>\n): ReadonlyArray<StatefulFaultState> =>\n states.flatMap(state => {\n const fault = state.fault\n\n return fault.kind === 'status' ? [{ ...state, fault }] : []\n })\n\n/** The API of an emulator without truncation faults: its fault methods take status faults only. */\nconst withStatusFaults = <State, Seed, Entry>(\n api: StatefulEmulatorApi<State, Seed, StatefulStreamFault, Entry>,\n inputInvalid: (input: StatefulInputKind, reason: string) => Error\n): StatefulEmulatorApi<State, Seed, StatefulFault, Entry> => ({\n ...api,\n faults: {\n add: fault => {\n const [added] = statusFaultStates([api.faults.add(fault)])\n\n if (added === undefined) throw inputInvalid('fault', 'a status fault is required')\n\n return added\n },\n list: () => statusFaultStates(api.faults.list()),\n clear: api.faults.clear\n }\n})\n\n/**\n * Build the wrapper over a core runtime. `createCore` receives the dispatch the core must run for\n * every request (its only route) and returns the runtime adapter. Faults are status faults only.\n */\nexport const makeStatefulEmulator = async <State, Env, Seed>(\n config: StatefulEmulatorConfig<State, Env>,\n createCore: (dispatch: CoreDispatch<State>) => Promise<StatefulCore<State>>\n): Promise<StatefulEmulatorApi<State, Seed>> =>\n withStatusFaults(\n await buildStatefulEmulator<State, Env, Seed, StatefulLedgerEntry>(\n config,\n createCore,\n false,\n snapshotEntry\n ),\n config.inputInvalid\n )\n\n/**\n * `makeStatefulEmulator` for an emulator that records no request header (opt-in): it takes no\n * `recordHeaders`, and its ledger entries carry no `headers` field\n * (`StatefulHeaderlessLedgerEntry`). Faults are status faults only.\n */\nexport const makeHeaderlessStatefulEmulator = async <State, Env, Seed>(\n config: Omit<StatefulEmulatorConfig<State, Env>, 'recordHeaders'>,\n createCore: (dispatch: CoreDispatch<State>) => Promise<StatefulCore<State>>\n): Promise<StatefulEmulatorApi<State, Seed, StatefulFault, StatefulHeaderlessLedgerEntry>> =>\n withStatusFaults(\n await buildStatefulEmulator<State, Env, Seed, StatefulHeaderlessLedgerEntry>(\n { ...config, recordHeaders: [] },\n createCore,\n false,\n snapshotHeaderlessEntry\n ),\n config.inputInvalid\n )\n\n/**\n * `makeStatefulEmulator` that also takes `truncate-after-chunks` faults\n * (`StatefulTruncateFault`), which cut a streamed answer (`StreamedCommit`). Opt-in: the other\n * emulators keep status faults only.\n */\nexport const makeChunkedStatefulEmulator = <State, Env, Seed>(\n config: StatefulEmulatorConfig<State, Env>,\n createCore: (dispatch: CoreDispatch<State>) => Promise<StatefulCore<State>>\n): Promise<StatefulEmulatorApi<State, Seed, StatefulStreamFault>> =>\n buildStatefulEmulator<State, Env, Seed, StatefulLedgerEntry>(\n config,\n createCore,\n true,\n snapshotEntry\n )\n\n/** Throws `inputInvalid('option', ...)` unless every drill knob is a known boolean (or absent). */\nexport const checkBooleanDrills = (\n drills: object | undefined,\n knobs: ReadonlyArray<string>,\n inputInvalid: (input: StatefulInputKind, reason: string) => Error\n): void => {\n for (const [key, value] of Object.entries(drills ?? {})) {\n if (!knobs.includes(key)) {\n throw inputInvalid('option', `unknown drill knob ${key}`)\n }\n\n if (value !== undefined && !Predicate.isBoolean(value)) {\n throw inputInvalid('option', `drills.${key} must be a boolean`)\n }\n }\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import * as Schema from "effect/Schema";
|
|
2
|
+
|
|
3
|
+
//#region src/stateful-secrets.d.ts
|
|
4
|
+
/** The ledgered path of every unrecognised request (constant: nothing from the request). */
|
|
5
|
+
declare const unrecognisedLedgerPath = "/<unrecognised>";
|
|
6
|
+
/** The ledgered method of an unrecognised request: a standard method, or `<other>`. */
|
|
7
|
+
declare const unrecognisedMethod: (method: string) => string;
|
|
8
|
+
/** `value` percent-decoded once, or unchanged when it is not valid percent-encoding. */
|
|
9
|
+
declare const decodedOrRaw: (value: string) => string;
|
|
10
|
+
/** True when `text`, raw or percent-decoded, contains a secret. */
|
|
11
|
+
declare const repeatsSecret: (text: string, secrets: ReadonlyArray<string>) => boolean;
|
|
12
|
+
/**
|
|
13
|
+
* True when any object key, string value, or number of `value` (raw or percent-decoded) holds a
|
|
14
|
+
* secret. Numbers are checked as JavaScript prints them (`1.2345678e7` parses to `12345678`). Used
|
|
15
|
+
* by the wrapper's resolved mode (Todoist, Telegram), unchanged.
|
|
16
|
+
*/
|
|
17
|
+
declare const jsonRepeatsSecret: (value: Schema.Json, secrets: ReadonlyArray<string>) => boolean;
|
|
18
|
+
/**
|
|
19
|
+
* True when a bearer value matches the RFC 6750 `b64token` syntax exactly (at least 8
|
|
20
|
+
* characters), starts with a character that completes no escape (`firstCharacter`), and holds a
|
|
21
|
+
* character outside the JSON-number alphabet `[0-9.eE+-]`, so it is never the text of a number
|
|
22
|
+
* and no escape can be shifted across its characters.
|
|
23
|
+
*/
|
|
24
|
+
declare const isRecognisableBearerValue: (value: string) => boolean;
|
|
25
|
+
/**
|
|
26
|
+
* The work caps of `textRepeatsSecret`. The closure runs to a fixpoint; these only bound the work
|
|
27
|
+
* of a pathological text, and hitting one counts as a credential repeat (uncertainty refuses, it
|
|
28
|
+
* never admits). Measured margins: every fixture converges in a few rounds and texts; a 64 KiB
|
|
29
|
+
* body of densely nested escapes converges in about 25 rounds, under 100 texts, and about 2.4 Mi
|
|
30
|
+
* characters read; a pathological 64 KiB body hits a cap in about 100 ms. The character cap is
|
|
31
|
+
* spent on reading too, so any part over 4 Mi characters is always refused, escapes or not (far
|
|
32
|
+
* above GitHub's 65 536-character body limit).
|
|
33
|
+
*/
|
|
34
|
+
declare const secretClosureCaps: {
|
|
35
|
+
/** Rounds of the breadth-first walk. */readonly rounds: 64; /** Distinct texts seen. */
|
|
36
|
+
readonly texts: 1024; /** Characters the transforms read, over all texts (keeps the worst large body near 100 ms). */
|
|
37
|
+
readonly characters: number;
|
|
38
|
+
};
|
|
39
|
+
/**
|
|
40
|
+
* Tolerant percent-decode, total: every `%XX` whose value is below `0x80` becomes its ASCII
|
|
41
|
+
* character; every other `%` sequence (a stray `%`, `%E9`, `%zz`) is left as it is. It never
|
|
42
|
+
* throws, so an encoded fragment next to `100%` or `%E9` is still decoded.
|
|
43
|
+
*/
|
|
44
|
+
declare const tolerantPercentDecode: (text: string) => string;
|
|
45
|
+
/**
|
|
46
|
+
* Tolerant JSON-unescape, total and lexical: within any text, whether or not it parses as JSON,
|
|
47
|
+
* `\uXXXX` below `\u0080` and `\"`, `\\`, `\/`, `\b`, `\f`, `\n`, `\r`, `\t` become their
|
|
48
|
+
* characters (one left-to-right pass); every other sequence is left as it is. Being lexical, it
|
|
49
|
+
* sees every string of a JSON text, duplicate keys and overwritten values included.
|
|
50
|
+
*/
|
|
51
|
+
declare const tolerantJsonUnescape: (text: string) => string;
|
|
52
|
+
/** What the closure of a text found: the secret, its fixpoint without the secret, or a cap. */
|
|
53
|
+
type SecretClosureOutcome = 'repeats' | 'clear' | 'capped';
|
|
54
|
+
/** What the closure of a text found: a matching text, its fixpoint without one, or a cap. */
|
|
55
|
+
type TextClosureOutcome = 'matches' | 'clear' | 'capped';
|
|
56
|
+
/**
|
|
57
|
+
* The closure of `text` against a predicate: starting from the raw text, a breadth-first walk
|
|
58
|
+
* applying either total transform (`tolerantPercentDecode`, `tolerantJsonUnescape`) to every text
|
|
59
|
+
* of the previous round, deduplicated, until no new text appears (a fixpoint); every text is
|
|
60
|
+
* tested with `matches`. Both transforms never lengthen a text and strictly shorten it when they
|
|
61
|
+
* change it, so every branch ends. `capped` when a cap of `secretClosureCaps` is hit first. No
|
|
62
|
+
* JSON is parsed and nothing can fail (unless `matches` throws).
|
|
63
|
+
*/
|
|
64
|
+
declare const textClosureOutcome: (text: string, matches: (candidate: string) => boolean) => TextClosureOutcome;
|
|
65
|
+
/**
|
|
66
|
+
* The closure of `text` against `secrets` (`textClosureOutcome`, every text checked for a secret
|
|
67
|
+
* as a substring). Numbers need no handling: a recognisable bearer never fits the JSON-number
|
|
68
|
+
* alphabet.
|
|
69
|
+
*/
|
|
70
|
+
declare const secretClosureOutcome: (text: string, secrets: ReadonlyArray<string>) => SecretClosureOutcome;
|
|
71
|
+
/**
|
|
72
|
+
* True when `text` repeats a secret anywhere in its closure (`secretClosureOutcome`), or when the
|
|
73
|
+
* closure hits a work cap before its fixpoint: fail closed, uncertainty counts as a repeat.
|
|
74
|
+
*/
|
|
75
|
+
declare const textRepeatsSecret: (text: string, secrets: ReadonlyArray<string>) => boolean;
|
|
76
|
+
/**
|
|
77
|
+
* `text` with every secret (raw and percent-encoded) replaced by `<redacted>`; when a secret still
|
|
78
|
+
* shows after that (for example in another percent-encoding), the whole text is `<redacted>`.
|
|
79
|
+
* Applied to everything the ledger keeps or a refusal answers.
|
|
80
|
+
*/
|
|
81
|
+
declare const scrubSecrets: (text: string, secrets: ReadonlyArray<string>) => string;
|
|
82
|
+
//#endregion
|
|
83
|
+
export { SecretClosureOutcome, TextClosureOutcome, decodedOrRaw, isRecognisableBearerValue, jsonRepeatsSecret, repeatsSecret, scrubSecrets, secretClosureCaps, secretClosureOutcome, textClosureOutcome, textRepeatsSecret, tolerantJsonUnescape, tolerantPercentDecode, unrecognisedLedgerPath, unrecognisedMethod };
|
|
84
|
+
//# sourceMappingURL=stateful-secrets.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stateful-secrets.d.mts","names":[],"sources":["../src/stateful-secrets.ts"],"mappings":";;;;cAuBa,sBAAA;AAAb;AAAA,cAaa,kBAAA,GAAsB,MAAc;;cAIpC,YAAA,GAAgB,KAAa;AAjBP;AAAA,cAgCtB,aAAA,GAAiB,IAAA,UAAc,OAAA,EAAS,aAAa;;;;AAnBjB;AAIjD;cA6Ba,iBAAA,GAAqB,KAAA,EAAO,MAAA,CAAO,IAAA,EAAM,OAAA,EAAS,aAAa;;;AA7BlC;AAe1C;;;cA6Ea,yBAAA,GAA6B,KAAa;;;;;AA7EmB;AAc1E;;;;cA8Ea,iBAAA;EA9EmC,6DAAe;EAAA,sBAAqB;EAAA;;;;;AA+D7B;AAevD;cAca,qBAAA,GAAyB,IAAY;;;;;;;cAyBrC,oBAAA,GAAwB,IAAY;AAzBjD;AAAA,KA0CY,oBAAA;;KAGA,kBAAA;AA7CsC;AAyBlD;;;;AAAiD;AAiBjD;;AA1CkD,cAuDrC,kBAAA,GACX,IAAA,UACA,OAAA,GAAU,SAAA,yBACT,kBAmCF;;AAnD+B;AAGhC;;;cAuDa,oBAAA,GACX,IAAA,UACA,OAAA,EAAS,aAAA,aACR,oBAUF;AApE6B;AAU9B;;;AAV8B,cA0EjB,iBAAA,GAAqB,IAAA,UAAc,OAAA,EAAS,aAAa;;;;;;cAQzD,YAAA,GAAgB,IAAA,UAAc,OAAA,EAAS,aAAa"}
|