Architecture · TrustedRouter Field Manual
Week 1

Architecture

Subsystem map and the end-to-end lifecycle of a chat completion.

Video 02 Life of a request · Auth, privacy routing, reserve, stream, settle, analytics.

Scope: quill-router (this repo) at commit 778120494 (2026-10-19). All paths are relative to the repo root /Users/elevenarms/Code/trustedrouter/quill-router. The attested gateway is in a separate repo, quill-cloud-proxy, which is not checked out here. Anything this doc says about the gateway's internals comes from this repo's contract code, docs and comments, not from gateway source.


1. Overview in one page

TrustedRouter is an OpenAI-, Anthropic- and OpenRouter-compatible LLM router. Its main selling point is that the prompt path is verifiable: prompts go only through an attested enclave, and the control plane never sees prompt or output bytes. Three planes share the work:

Plane Hostname Code Sees prompts? Owns
Attested gateway (API / prompt plane) api.trustedrouter.com (alias api.quillrouter.com, regional api-<region>.quillrouter.com) quill-cloud-proxy (separate repo), running in GCP Confidential Space (plus AWS/Azure variants) Yes, only inside the enclave TLS termination in the enclave, provider calls, streaming, fallback attempts, token counting, inference receipts
Control plane trustedrouter.com (aliases allyrouter.com, uptimerouter.com) this repo, Python/FastAPI, trusted_router.main:app on Cloud Run No (except the browser /chat-proxy, see Gotchas) Accounts, workspaces, API keys, BYOK envelopes, catalog and pricing, routing decisions, credit reserve/settle/refund, billing (Stripe/Adyen/PayPal/Lightning/x402), console UI, public site, status, trust page
Fast path (pilot) internal VPC only fastpath/ (Go) No Lease-based fast admission and batched settlement, so the per-request Spanner reservation leaves the hot path. Pilot on 2 workspaces

The README states the trust boundary directly (README.md:127-131): "api.trustedrouter.com is the attested prompt path and must terminate TLS inside Confidential Space. trustedrouter.com is the control plane and must never serve a production inference fallback." The code enforces it: /chat/completions, /messages, /responses and /embeddings are mounted on the control plane only when environment in {local,test} (src/trusted_router/main.py:880-883, main.py:912-919).

What the control plane does for each production request. The enclave makes two or three server-to-server calls to the control plane's /internal/gateway/* routes (src/trusted_router/routes/internal/gateway.py:2453-2600):

  1. authorize: validate the key hash, resolve the model and routing candidates (applying privacy filters), estimate cost, and reserve credits and key caps atomically in Spanner. It returns authorization_id, route_candidates, any BYOK ciphertext, and the regions.
  2. Optional heartbeat (Stage D): usage heartbeats during long streams.
  3. settle (success) or refund (failure or disconnect): book actual tokens to the hold, write the generation record, and enqueue analytics, activity, broadcast and auto-refill work.

Prompt and output bytes never cross this boundary. Only hashes, token counts and metadata do.

Storage in production is TR_STORAGE_BACKEND=spanner-clickhouse (scripts/deploy/rollout.sh:274). Spanner is the system of record for money and identity. ClickHouse holds request analytics, fed through a durable Spanner outbox. Postgres-wire backends (Azure/AWS standalone clouds) and an in-memory store (tests/local) implement the same Store protocol.


2. System context (C4 level 1)

flowchart LR
    dev["Developer app / SDK<br/>OpenAI, Anthropic or OpenRouter client"]
    browser["Browser user<br/>console, chat playground"]
    subgraph TR["TrustedRouter"]
        gw["Attested gateway<br/>api.trustedrouter.com<br/>quill-cloud-proxy in Confidential Space"]
        cp["Control plane<br/>trustedrouter.com<br/>FastAPI trusted_router.main:app on Cloud Run"]
        fp["Fast path pilot<br/>fastpath/ Go service, VPC only"]
        sp[("Cloud Spanner<br/>money, identity, outboxes")]
        ch[("ClickHouse<br/>request analytics")]
    end
    prov["Upstream LLM providers<br/>OpenAI, Anthropic, Vertex, Bedrock, Tinfoil, ..."]
    pay["Payments<br/>Stripe, Adyen, PayPal, Lightning, x402"]
    obs["Observability<br/>Sentry and Axiom, control plane only"]
    sinks["Customer broadcast sinks<br/>PostHog, OTLP webhooks"]

    dev -- "HTTPS, TLS ends in enclave" --> gw
    browser -- "HTTPS" --> cp
    gw -- "/internal/gateway authorize, settle, refund<br/>metadata only" --> cp
    gw -- "prompt and completion" --> prov
    cp -- "lease admit and settle, pilot only" --> fp
    cp --> sp
    fp --> sp
    cp -- "outbox drain" --> ch
    cp -- "checkout sessions" --> pay
    pay -- "signed webhooks" --> cp
    cp --> obs
    cp -- "metadata broadcast outbox" --> sinks
    cp -- "/chat-proxy byte pipe, browser only" --> gw
Diagram source
flowchart LR
    dev["Developer app / SDK<br/>OpenAI, Anthropic or OpenRouter client"]
    browser["Browser user<br/>console, chat playground"]
    subgraph TR["TrustedRouter"]
        gw["Attested gateway<br/>api.trustedrouter.com<br/>quill-cloud-proxy in Confidential Space"]
        cp["Control plane<br/>trustedrouter.com<br/>FastAPI trusted_router.main:app on Cloud Run"]
        fp["Fast path pilot<br/>fastpath/ Go service, VPC only"]
        sp[("Cloud Spanner<br/>money, identity, outboxes")]
        ch[("ClickHouse<br/>request analytics")]
    end
    prov["Upstream LLM providers<br/>OpenAI, Anthropic, Vertex, Bedrock, Tinfoil, ..."]
    pay["Payments<br/>Stripe, Adyen, PayPal, Lightning, x402"]
    obs["Observability<br/>Sentry and Axiom, control plane only"]
    sinks["Customer broadcast sinks<br/>PostHog, OTLP webhooks"]

    dev -- "HTTPS, TLS ends in enclave" --> gw
    browser -- "HTTPS" --> cp
    gw -- "/internal/gateway authorize, settle, refund<br/>metadata only" --> cp
    gw -- "prompt and completion" --> prov
    cp -- "lease admit and settle, pilot only" --> fp
    cp --> sp
    fp --> sp
    cp -- "outbox drain" --> ch
    cp -- "checkout sessions" --> pay
    pay -- "signed webhooks" --> cp
    cp --> obs
    cp -- "metadata broadcast outbox" --> sinks
    cp -- "/chat-proxy byte pipe, browser only" --> gw

Multi-cloud: each cloud (GCP, AWS, Azure) is a standalone TrustedRouter. Only identity federates; credits, keys and analytics stay in their own cloud (docs/storage-portability/multi-cloud-separation.md). Peer planes resolve keys lazily from the home plane (src/trusted_router/services/federation.py) and forward deferred debt home (src/trusted_router/services/home_settlement.py).


3. One image, six process "surfaces"

One container image serves several process roles, chosen by TR_SERVICE_SURFACE (src/trusted_router/config.py:438-445). create_app() registers only the routes that belong to the chosen surface (main.py:709-909).

Surface Purpose Notable routes Deploy script
combined Local/test default. In prod it is the legacy pre-split service, permitted only with TR_ALLOW_DEPLOYED_COMBINED_SURFACE=true (main.py:235-244) everything scripts/deploy/rollout.sh:408-410
public Anonymous marketing site, catalog, docs, pre-serialized OpenAPI register_public_routes, catalog, /openapi.json scripts/deploy/public_surface.sh
actions Anonymous public actions, memory-backed register_public_action_routes —
control Authenticated account plane: console, keys, billing, OAuth, MCP, chat proxy, webhooks register_console_routes, register_key_routes, ... trust jobs use it
internal Machine-to-machine gateway billing authority: /internal/gateway/*, observer internals register_gateway_internal_routes scripts/deploy/internal_surface.sh
observer Regional/standalone-cloud status and observer endpoints (Postgres) register_observer_internal_routes azure_control_plane.sh, aws_eu_control_plane.sh

The six-service split is in progress (issue #712 per config.py:446-449). Until it completes, the legacy combined service still runs in production next to the split public and internal companions.


4. Subsystem map

src/trusted_router/ has 371 top-level modules and 533 .py files once subpackages are included (routes/ 77, services/ 62, synthetic/ 22, content/, evals/). Grouped by prefix and responsibility:

Subsystem Key modules Responsibility
App shell main.py, middleware.py, serve.py, config.py, errors.py, schemas.py, types.py FastAPI factory, surface-based route registration, middleware chain (request id, rate limit, read-only mode, security headers, gzip, markdown negotiation), uvicorn runner with a SIGTERM drain, the ~2.4k-line Settings
AuthN/AuthZ and keys auth.py, scopes.py, security.py, storage_keys.py, storage_gcp_keys.py, storage_auth_*, oauth_provider.py, wallet_auth.py, routes/keys.py, routes/auth.py, routes/oauth*.py Principal resolution: dev header, then session cookie, then bearer sk-tr-… or trsess-…. Keys are salted SHA-256 with a high-entropy lookup hash. Management vs inference scopes. OAuth "Sign in with TrustedRouter" and app keys
BYOK byok_crypto.py, key_management.py, storage_byok.py, storage_gcp_byok.py, byok_aad_backfill.py, byok_v1_attestations.py, routes/byok.py Envelope-encrypt customer provider keys (per-secret DEK wrapped by KMS). The gateway receives ciphertext plus byok_cache_key
Catalog and pricing catalog.py, catalog_data.py (4.5k lines), catalog_registry.py, catalog_ingest.py, catalog_privacy.py, catalog_capabilities.py, pricing.py, provider_lifecycle.py, model_aliases.py, data/*.json Builds MODELS/MODEL_ENDPOINTS at import time from the OpenRouter snapshot and provider manifests. Uniform markup (cost x1.055, $0.01/M floor). Privacy tiers per endpoint. Retirements and price cutovers
Routing routing.py, routing_candidates.py, routing_state.py, provider_ranking.py, provider_reliability.py, request_capabilities.py, model_regions.py, regions.py, enclave_regions.py Parse OpenRouter-style provider{order,only,ignore,allow_fallbacks,min_privacy,zdr,data_collection,sort,max_price,require_parameters}. Expand meta-models (auto, zdr, e2e, eu, cheap, tiers). Filter and sort endpoint candidates. Process-local latency/throughput hints
Providers and adapters (local/test inference only) providers.py, provider_adapters.py, provider_streaming.py, provider_types.py, adapter.py, provider_payloads.py ProviderClient for OpenAI-compatible, Anthropic and Gemini. Converts between Messages/Responses and Chat formats. In production the enclave makes the real provider calls
Inference routes (local/test) routes/inference.py, services/inference.py, services/inference_quota.py, services/inference_errors.py Local implementation of /chat/completions, /messages, /responses, /embeddings, with the same reserve/settle semantics, so the dev loop needs no enclave
Gateway contract (prod hot path) routes/internal/gateway.py (6k lines), gateway_boot.py, gateway_timing.py, receipt_keys.py, stage_d.py, stage_d_policy.py, client_context.py, request_tags.py authorize, heartbeat, settle, refund, disposition, register-boot, validate, key, resolve-custom-model. Boot-signed requests (X-TR-Boot-Auth). Stage D heartbeat cohort
Money core money.py, storage_gcp_authorize.py, storage_gcp_counter_dml.py, storage_gcp_counters.py, storage_gcp_credit_shards.py, credit_debt.py, typed_balance.py, spend_windows.py, strict_budget.py, billing_policy.py Integer microdollars. One-transaction authorize (credit DML, key-cap DML, tr_reservation INSERT, authorization INSERT). 16 credit shards per workspace. Debt rules. Daily/weekly/monthly key windows
Settle durability storage_gcp_settle_outbox.py, services/settle_outbox_*.py, services/auto_refill*.py, storage_gcp_legacy_reservations.py Durable settle outbox (enabled in prod, rollout.sh:550) so a settle that fails partway is never lost or reaped for free. Auto-refill handoff to the payment-owning process
Async settle (flag-off, shadow) async_settle_*.py (98 modules, 86 of them async_settle_shadow_v2_*), services/async_settle*.py, billing_snapshot.py, async_settle_ticket.py, routes/settlements.py Future: an enclave settle that returns within 2 s and finalizes durably later. Today TR_ASYNC_SETTLE_ENABLED=false and shadow-v2 evidence collection runs only for the probe workspace (rollout.sh:660-664)
Fast path bridge services/fastpath_frontdoor.py, services/fastpath_settle.py, fastpath/ (Go) Pilot: the control plane asks the Go front door to admit under a regional lease instead of a Spanner reservation. off, busy or invalid means take today's path
Speculation (shadow) speculation_protocol.py, services/speculation_shadow.py, storage_gcp_speculation_shadow.py, routes/internal/speculation.py Ed25519 JWS wire contract for speculative provider starts. Shadow only (TR_SPECULATIVE_PROVIDER_SHADOW_ENABLED=false)
Meta / "fusion" models catalog_data.py:2773-2830 (trustedrouter/synth, fusion, fusion-code, selector, mapreduce, polyphemus-1.0), polyphemus.py, evals/fusion_*.py Router-defined virtual models. The control plane resolves candidates and billing (decide_route_endpoint_candidates). Multi-model orchestration runs in the gateway
Storage abstraction store_protocol.py (~97 methods), storage.py (InMemoryStore, STORE proxy, create_store), storage_gcp.py (7.4k), storage_postgres.py (7.9k), storage_models.py, storage_errors.py Store protocol. The tests/conformance/ suite is the executable contract across the memory, postgres and spanner-pg backends
Analytics analytics_sink.py, storage_gcp_operational_analytics_outbox.py, storage_operational_analytics.py, operational_analytics*.py, provider_analytics.py, clickhouse/ (top-level drain/rollup jobs) A durable Spanner outbox feeds ClickHouse with one-way surrogate IDs and no content. Best-effort benchmark sink. Rollups and Parquet archive
Broadcast storage_broadcast.py, services/broadcast*.py, routes/broadcast.py, routes/internal/broadcast_queue.py Customer-configured PostHog/OTLP export, metadata-only unless include_content is set (and then only the enclave gets the destination)
Billing and payments services/stripe_billing.py, adyen_billing.py, paypal_billing.py, lightning.py, x402_billing.py, routes/billing.py, routes/internal/webhook.py, credit_transfer.py, partner_billing.py, custom_model_billing.py, app_markup_billing.py Top-ups and idempotent webhook crediting. App and creator markups, payouts
Trust and fraud trust_tiers.py, trust_*.py, *_trust_history.py, verification*.py, phone_verification.py, veriff_verify.py, signup_gate.py Account trust tiers from payment, refund and dispute history. Identity verification gates
Custom / user models custom_model_rules.py, user_model_rules.py, storage_*custom_models.py, storage_*user_models.py, services/user_model_*.py Custom models (hidden prompt plus markup) and user-provided models (BYO endpoint with owner payouts)
Web UI / public site dashboard.py (6.6k), homepage.py, routes/console/*, routes/public.py, templates/, static/, seo_*.py, og.py, markdown_negotiation.py, content/, frontend/ Server-rendered Jinja console and marketing site, docs, llms.txt, markdown for agents
Synthetic monitoring and ops synthetic/* (probes, status, leaderboard, remediator, drift), routes/internal/synthetic.py, sentry_config.py, axiom_config.py In-process probe loop, /status, burn-rate SLOs (router_core vs control_plane), remediator
Media image_generation.py, speech.py, transcription.py, video_billing.py, storage_*video_jobs.py, routes/internal/video_jobs.py Route types images, audio.speech, audio.transcriptions, videos, with their own reservation rules
Growth / marketing acquisition.py, marketing_*.py, google_ads_*.py, social_rewards*.py, competitor_comparisons.py, token_exchange.py Funnel attribution, ads conversions, comparison pages

Component diagram (control plane internals)

flowchart TB
    subgraph MW["middleware.py register_http_middleware"]
        m1["UnreadRequestBodyClose"] --> m2["security headers, CSP report-only"] --> m3["rate_limit, process-local"] --> m4["read_only guard"] --> m5["request_id"] --> m6["internal auth before body"] --> m7["GZip, MarkdownNegotiation"]
    end
    subgraph R["Routers, selected by TR_SERVICE_SURFACE"]
        pub["routes/public.py, catalog"]
        con["routes/console/*, keys, billing, byok, oauth"]
        gwr["routes/internal/gateway.py<br/>authorize, heartbeat, settle, refund"]
        inf["routes/inference.py<br/>local and test only"]
        hooks["routes/internal/webhook.py<br/>Stripe, Adyen, PayPal"]
    end
    auth["auth.py principal_from_request"]
    rt["routing.py and routing_candidates.py"]
    cat["catalog.py, catalog_registry.py, catalog_privacy.py<br/>MODELS, MODEL_ENDPOINTS built at import"]
    rs["routing_state.py ROUTING_STATE ranking hints"]
    fpc["services/fastpath_frontdoor.py"]
    store["storage.py STORE proxy implementing store_protocol.Store"]
    gcp["storage_gcp.py SpannerStore<br/>storage_gcp_authorize.py one-txn authorize"]
    pg["storage_postgres.py PostgresStore"]
    mem["storage.py InMemoryStore"]
    sob["storage_gcp_settle_outbox.py"]
    oa["operational analytics outbox"]
    bc["broadcast outbox"]
    sink["analytics_sink.py best-effort ClickHouse sink"]
    provs["providers.py and provider_adapters.py<br/>local inference only"]

    MW --> R
    con --> auth
    inf --> auth
    gwr --> rt
    inf --> rt
    rt --> cat
    gwr --> rs
    gwr --> fpc
    gwr --> store
    inf --> provs
    inf --> store
    con --> store
    hooks --> store
    store --> gcp
    store --> pg
    store --> mem
    gwr --> sob
    gcp --> oa
    gwr --> bc
    gwr --> sink
Diagram source
flowchart TB
    subgraph MW["middleware.py register_http_middleware"]
        m1["UnreadRequestBodyClose"] --> m2["security headers, CSP report-only"] --> m3["rate_limit, process-local"] --> m4["read_only guard"] --> m5["request_id"] --> m6["internal auth before body"] --> m7["GZip, MarkdownNegotiation"]
    end
    subgraph R["Routers, selected by TR_SERVICE_SURFACE"]
        pub["routes/public.py, catalog"]
        con["routes/console/*, keys, billing, byok, oauth"]
        gwr["routes/internal/gateway.py<br/>authorize, heartbeat, settle, refund"]
        inf["routes/inference.py<br/>local and test only"]
        hooks["routes/internal/webhook.py<br/>Stripe, Adyen, PayPal"]
    end
    auth["auth.py principal_from_request"]
    rt["routing.py and routing_candidates.py"]
    cat["catalog.py, catalog_registry.py, catalog_privacy.py<br/>MODELS, MODEL_ENDPOINTS built at import"]
    rs["routing_state.py ROUTING_STATE ranking hints"]
    fpc["services/fastpath_frontdoor.py"]
    store["storage.py STORE proxy implementing store_protocol.Store"]
    gcp["storage_gcp.py SpannerStore<br/>storage_gcp_authorize.py one-txn authorize"]
    pg["storage_postgres.py PostgresStore"]
    mem["storage.py InMemoryStore"]
    sob["storage_gcp_settle_outbox.py"]
    oa["operational analytics outbox"]
    bc["broadcast outbox"]
    sink["analytics_sink.py best-effort ClickHouse sink"]
    provs["providers.py and provider_adapters.py<br/>local inference only"]

    MW --> R
    con --> auth
    inf --> auth
    gwr --> rt
    inf --> rt
    rt --> cat
    gwr --> rs
    gwr --> fpc
    gwr --> store
    inf --> provs
    inf --> store
    con --> store
    hooks --> store
    store --> gcp
    store --> pg
    store --> mem
    gwr --> sob
    gcp --> oa
    gwr --> bc
    gwr --> sink

5. Request lifecycle: production /v1/chat/completions

5.1 Step by step

  1. Client → enclave. The client calls https://api.trustedrouter.com/v1/chat/completions with Authorization: Bearer sk-tr-v1-…. TLS terminates inside the Confidential Space enclave. The enclave parses the body, derives estimated_input_tokens, input_modalities and requested_parameters, and hashes the API key. The raw key and the prompt stay inside the enclave.

  2. Enclave → control plane POST /internal/gateway/authorize (routes/internal/gateway.py:2492). Body is GatewayAuthorizeRequest (src/trusted_router/schemas.py:366-431): api_key_lookup_hash, model, models, provider, token estimates, stream, region, tags, metadata, idempotency_key, route_type, and so on. - Middleware: internal_auth_before_body_middleware checks the internal token before the body is read (middleware.py:153-161, _internal_auth_before_body at middleware.py:473). Then request id and the process-local rate limit. - Per-key admission: _AUTHORIZE_ADMISSION.try_acquire(subject) returns 429 when one key has too many authorizations in flight. The handler then moves to a worker thread (gateway.py:534-587). All gateway storage IO is synchronous and runs through run_in_threadpool. - Internal auth: require_internal_gateway compares the bearer or x-trustedrouter-internal-token with TR_INTERNAL_GATEWAY_TOKEN in constant time, and is fail-closed outside local/test (routes/internal/_shared.py:53-65). - Key + workspace snapshot (gateway.py:856-940): one folded read loads the key, workspace, BYOK rows and boot record. It rejects disabled or expired keys (401) and wrong-scope keys, and verifies X-TR-Boot-Auth if present (gateway_boot.py). The workspace must be billing-active (assert_workspace_billing_active). Tags merge from the key and the request. - Idempotency replay: a matching idempotency_key plus request_fingerprint returns the original authorization, which prevents double reservation (gateway.py:~1200-1320). - Model resolution and routing: normalize_routing_inputs(body_dict, settings, resolved_region) (gateway.py:1321, routing.py:926) gives NormalizedRoutingInputs(model_ids, preferences).

    • provider_route_preferences (routing.py:633-746) validates every provider.* field. An unknown field returns 400 with the exact name. min_privacy maps through PRIVACY_TIER_ALIASES (catalog_data.py:61-89): any/standard=0, no_store=1, zdr=2, confidential/e2e/e2ee=3. A typo is a 400 so the request can't silently weaken. provider.zdr=true adds the ZDR requirement.
    • Meta-model aliases (trustedrouter/auto, zdr, e2e, eu, tiers, …) expand to ordered pools (routing_candidates.py:138-420). trustedrouter/zdr and trustedrouter/e2e carry implicit privacy floors (ROUTING_MODEL_MIN_PRIVACY_TIERS).
    • Dispatch is by route type: chat → chat_route_endpoint_candidates (routing.py:253-289), or embeddings, decide (fusion/selector), image, speech, transcription, video, fetch, user model (gateway.py:~1410-1470).
    • Privacy filter: _apply_endpoint_provider_filters keeps only endpoints where endpoint_meets_privacy_requirement(endpoint, req) holds for every requirement (catalog_privacy.py:231-251). Confidential needs verified confidential compute and provider E2EE and explicit ZDR, and never the model's own vendor. Unknown values fail closed. If nothing passes, the request gets a 400 ("No route candidates match the requested provider filters"). It never falls back to a weaker route.
    • data_collection=deny is a soft filter. It is relaxed when it would empty the set (_filter_candidates_soft_data_collection, routing.py:~1080-1106). min_privacy is never relaxed.
    • BYOK eligibility per candidate (_byok_configs_for_candidates, _eligible_gateway_endpoint_candidates), service-tier filtering, then ROUTING_STATE.rank(...) reorders using process-local latency/throughput hints (routing_state.py:71-179; hints only, never an authority). allow_fallbacks=false trims to one candidate (gateway.py:~1580-1597).
    • Estimate: _endpoint_cost_microdollars over the candidates (the max across candidates when auto fallback is possible), plus receipt fee, custom-model markup, app markup and additional hosted-tool cost (gateway.py:~1595-1690).
    • Fast path pilot: for pilot workspaces with credit-only candidates, _fastpath_admission calls the Go front door. If admitted, the typed store reserves nothing and the lease holds the money (gateway.py:~1820-1835, 5087).
    • Reserve (the money step): _typed_store.authorize_gateway_typed(...) (gateway.py:~1866, storage_gcp.py:5479, implemented in storage_gcp_authorize.py). One Spanner read-write transaction runs the idempotency read, then conditional credit DML on a credit shard, then pause/strict-budget checks, then conditional key-cap DML, then the tr_reservation INSERT, then the gateway_authorization INSERT (storage_gcp_authorize.py:1-14). Insufficient balance returns 402. If a peer plane has deferred settlement enabled, a local refusal may fall back to the home plane.
    • Response: GatewayAuthorizeData (schemas.py:325-348) with authorization_id, prospective generation_id, route_candidates (provider, endpoint, upstream model, inference location, BYOK ciphertext and cache key), regions, broadcast_destinations, usage_type, timing phases (key_lookup_ms, routing_ms, store_ms, post_commit_ms).
  3. Enclave → provider. The enclave tries route_candidates in order. A failure before any output token moves on to the next candidate, and it records route_fallbacks and route_failures. It streams SSE back to the client and counts tokens.

  4. Optional POST /internal/gateway/heartbeat (Stage D, gateway.py:611-680). Boot-signed usage heartbeats during long streams. If the final settle is lost, the reaper can book a reaped_snapshot charge from the frozen Stage D pricing snapshot (stage_d.py) instead of releasing the hold for free. Eligibility depends on a signed accepted-image policy (stage_d_policy.py).

  5. Enclave → POST /internal/gateway/settle (gateway.py:2552) with GatewaySettleRequest (schemas.py:566+): authorization_id, actual input/output/cache/reasoning tokens, selected_model/selected_endpoint, finish_reason, streamed, elapsed_seconds, first_token_seconds, route_fallbacks, and client context. - AsyncSettlementRoute (routes/settlements.py) intercepts only when async-settle protection is on and the gateway sent X-TR-Settlement-Mode: async-v1|sync. Otherwise it is the legacy handler. - _settle_gateway_with_admission_sync (gateway.py:3721) applies per-key settle admission (503 + Retry-After when overloaded). Then _settle_gateway_authorization (gateway.py:3776):

    • One-shot: a settle on an already-settled authorization returns already_settled and never charges twice (_already_settled_gateway_data, gateway.py:5271).
    • Prompt-cache normalization (normalized_prompt_accounting, services/settle_outbox_apply.py:112). Anthropic input_tokens exclude cached tokens, while OpenAI and Gemini prompt counts include them.
    • Actual cost in microdollars from the authorized endpoint's frozen terms, then a Generation.from_settle_body(...) metadata record. No content.
    • Durable settle outbox (TR_SETTLE_OUTBOX_ENABLED=true in prod): enqueue the settle intent first (storage_gcp_settle_outbox.py), then typed_finalize_gateway_authorization atomically books usage, releases the unused hold, writes the bounded tr_generation record and the activity/analytics outbox rows, and marks the outbox row done. If finalize fails, the outbox drain (services/settle_outbox_drain.py) replays it. Deterministic generation IDs make replays idempotent.
    • Post-commit side effects: auto-refill scheduling (Stripe, owned by the control surface), creator/app/custom-model payouts, metadata broadcast enqueue, and benchmark samples to the best-effort analytics_sink (analytics_sink.py, a bounded queue that drops rows rather than block).
    • Fast path authorizations are settled through _settle_leased_authorization (gateway.py:5181).
  6. Refund (POST /internal/gateway/refund, gateway.py:2561) uses the same _settle_gateway_authorization(success=False). It releases the reservation and key-cap hold after a provider failure or client disconnect before output. Expired holds are reaped (storage_gcp_authorize.py:1224), and that is why the settle outbox and Stage D exist.

  7. Analytics. Spanner outbox rows are drained into ClickHouse by the clickhouse/ingest_* jobs, using one-way workspace/key surrogates (storage_gcp_operational_analytics_outbox.py:1-12). ClickHouse failures never affect inference or settle (docs/clickhouse-reliability.md).

5.2 Sequence diagram (streaming chat with settle)

sequenceDiagram
    autonumber
    participant C as Client SDK
    participant G as Attested gateway quill-cloud-proxy
    participant CP as Control plane internal surface
    participant RT as routing.py and catalog
    participant FP as fastpath front door pilot
    participant SP as Spanner
    participant P as Upstream provider
    participant CH as ClickHouse

    C->>G: POST /v1/chat/completions stream true, Bearer sk-tr
    Note over G: TLS ends in enclave, key hashed, tokens estimated
    G->>CP: POST /internal/gateway/authorize key hash, model, provider prefs, estimates
    CP->>CP: internal token check, per-key admission, run_in_threadpool
    CP->>SP: folded read of key, workspace, BYOK, boot
    CP->>RT: normalize_routing_inputs, min_privacy filter, sort, rank
    RT-->>CP: ordered route candidates or 400 fail closed
    opt pilot workspace
        CP->>FP: admit under lease
        FP-->>CP: admitted or off, busy, invalid
    end
    CP->>SP: one RW txn: credit shard DML, key cap DML, tr_reservation, authorization
    SP-->>CP: committed or 402 insufficient credits
    CP-->>G: authorization_id, generation_id, route_candidates, BYOK ciphertext
    loop candidates until first token
        G->>P: provider request with operator key or decrypted BYOK
        P-->>G: error before output, so try next candidate
    end
    P-->>G: SSE chunks
    G-->>C: SSE chunks then DONE
    opt Stage D long stream
        G->>CP: POST /internal/gateway/heartbeat boot signed usage
    end
    alt success
        G->>CP: POST /internal/gateway/settle actual tokens, selected endpoint, timings
        CP->>SP: enqueue settle outbox intent
        CP->>SP: typed finalize: book usage, release remainder, generation record, analytics outbox
        CP-->>G: settled, or already_settled on retry
    else provider failure or disconnect
        G->>CP: POST /internal/gateway/refund
        CP->>SP: release reservation and key cap hold
    end
    CP-)CH: outbox drain jobs and best-effort benchmark sink
Diagram source
sequenceDiagram
    autonumber
    participant C as Client SDK
    participant G as Attested gateway quill-cloud-proxy
    participant CP as Control plane internal surface
    participant RT as routing.py and catalog
    participant FP as fastpath front door pilot
    participant SP as Spanner
    participant P as Upstream provider
    participant CH as ClickHouse

    C->>G: POST /v1/chat/completions stream true, Bearer sk-tr
    Note over G: TLS ends in enclave, key hashed, tokens estimated
    G->>CP: POST /internal/gateway/authorize key hash, model, provider prefs, estimates
    CP->>CP: internal token check, per-key admission, run_in_threadpool
    CP->>SP: folded read of key, workspace, BYOK, boot
    CP->>RT: normalize_routing_inputs, min_privacy filter, sort, rank
    RT-->>CP: ordered route candidates or 400 fail closed
    opt pilot workspace
        CP->>FP: admit under lease
        FP-->>CP: admitted or off, busy, invalid
    end
    CP->>SP: one RW txn: credit shard DML, key cap DML, tr_reservation, authorization
    SP-->>CP: committed or 402 insufficient credits
    CP-->>G: authorization_id, generation_id, route_candidates, BYOK ciphertext
    loop candidates until first token
        G->>P: provider request with operator key or decrypted BYOK
        P-->>G: error before output, so try next candidate
    end
    P-->>G: SSE chunks
    G-->>C: SSE chunks then DONE
    opt Stage D long stream
        G->>CP: POST /internal/gateway/heartbeat boot signed usage
    end
    alt success
        G->>CP: POST /internal/gateway/settle actual tokens, selected endpoint, timings
        CP->>SP: enqueue settle outbox intent
        CP->>SP: typed finalize: book usage, release remainder, generation record, analytics outbox
        CP-->>G: settled, or already_settled on retry
    else provider failure or disconnect
        G->>CP: POST /internal/gateway/refund
        CP->>SP: release reservation and key cap hold
    end
    CP-)CH: outbox drain jobs and best-effort benchmark sink

5.3 The local/test path (same semantics, no enclave)

In local/test, routes/inference.py mounts /chat/completions and the other inference routes (routes/inference.py:150-420):

  • InferencePrincipal → require_inference_key → principal_from_request (auth.py:90-177, auth.py:361).
  • provider_route_preferences + chat_route_candidates or chat_route_endpoint_candidates, then _local_routes_meeting_privacy_floor (routes/inference.py:106).
  • More than one candidate, or trustedrouter/auto, means meta-route rollover (run_chat_candidates or _candidate_stream_bytes). Otherwise a single candidate (run_chat or run_chat_stream).
  • services/inference.py:_run_stream (services/inference.py:162-218) opens reserved_quota(...) (services/inference_quota.py:84), which reserves the key limit and then credits. It streams from ProviderClient.stream_chat, computes the actual cost, calls ticket.settle(actual_cost), and runs STORE.add_generation(...). An exception refunds (QuotaTicket, inference_quota.py:42-80).
  • Live upstream calls happen only with TR_ENABLE_LIVE_PROVIDERS=true (default false, config.py:486) and keys from the local keys file.

5.4 Anthropic /v1/messages

  • Client config: ANTHROPIC_BASE_URL=https://api.trustedrouter.com (no /v1, the SDK adds it), ANTHROPIC_API_KEY=sk-tr-… (README.md:62-64).
  • Production: the enclave serves /v1/messages. The control-plane contract is the same authorize/settle/refund. Model and provider filters go through the same chat_route_endpoint_candidates and privacy logic. The settle path normalizes Anthropic cache accounting, where input_tokens excludes cache_read_input_tokens and cache_creation_input_tokens (schemas.py:574-578). Coverage registry: openrouter_coverage.py:99.
  • Local/test (routes/inference.py:307-351): _require_messages_model (:929), then the privacy floor, then messages_to_chat_body (adapter.py:83-99), which turns Anthropic system into a leading system chat message and copies allowed params. The request then runs on the same run_chat / run_messages_stream, and the result is wrapped by _anthropic_messages_envelope (routes/inference.py:506). On the outbound side, chat_to_anthropic (adapter.py:102+) rebuilds Anthropic payloads for the Anthropic provider adapter (provider_adapters.py:338-465).
  • /v1/responses works the same way through responses_to_chat_body and _responses_api_envelope.

6. Control plane vs attested gateway vs fastpath/

flowchart LR
    subgraph ENC["Attested gateway, quill-cloud-proxy"]
        e1["TLS termination and attestation endpoint /attestation"]
        e2["Parse request, estimate tokens, hash key"]
        e3["Provider calls, fallback loop, SSE streaming"]
        e4["Decrypt BYOK in memory, short TTL cache"]
        e5["Inference receipts signed with boot key"]
    end
    subgraph CPL["Control plane, this repo"]
        c1["Key and workspace validation"]
        c2["Model, alias and privacy routing decision"]
        c3["Reserve, settle, refund in Spanner"]
        c4["Generation records, activity, analytics outbox"]
        c5["Console, billing, catalog, status, trust page"]
    end
    subgraph FPL["fastpath, Go, pilot"]
        f1["Front door admits under regional lease"]
        f2["Owner and auditor batch settlement"]
        f3["TLA plus shadows in proofs/"]
    end
    e2 -- "authorize" --> c1
    c1 --> c2 --> c3
    e3 -- "settle or refund" --> c3
    c3 --> c4
    c3 -- "pilot workspaces only" --> f1
    f1 --> f2
Diagram source
flowchart LR
    subgraph ENC["Attested gateway, quill-cloud-proxy"]
        e1["TLS termination and attestation endpoint /attestation"]
        e2["Parse request, estimate tokens, hash key"]
        e3["Provider calls, fallback loop, SSE streaming"]
        e4["Decrypt BYOK in memory, short TTL cache"]
        e5["Inference receipts signed with boot key"]
    end
    subgraph CPL["Control plane, this repo"]
        c1["Key and workspace validation"]
        c2["Model, alias and privacy routing decision"]
        c3["Reserve, settle, refund in Spanner"]
        c4["Generation records, activity, analytics outbox"]
        c5["Console, billing, catalog, status, trust page"]
    end
    subgraph FPL["fastpath, Go, pilot"]
        f1["Front door admits under regional lease"]
        f2["Owner and auditor batch settlement"]
        f3["TLA plus shadows in proofs/"]
    end
    e2 -- "authorize" --> c1
    c1 --> c2 --> c3
    e3 -- "settle or refund" --> c3
    c3 --> c4
    c3 -- "pilot workspaces only" --> f1
    f1 --> f2
Concern Attested gateway Control plane fastpath
Prompt and output bytes Handles them, never logs them Never (prod) Never
Who chooses the provider Executes the ordered candidates and decides fallback at runtime Decides the candidate set and order, applies privacy floors —
Money Reports actual usage Authoritative reserve/settle/refund Lease admission for pilot workspaces. Python still owns key, model, estimate, authorization row and generation record (services/fastpath_frontdoor.py:1-22)
Trust anchor Hardware attestation, published image digest Ordinary Cloud Run, Sentry/Axiom allowed VPC-internal
Billing authority it trusts Only https://trustedrouter.com is baked into the measured image (docs/design/fast-admission-pilot.md) — Called by the control plane in the pilot, not by the enclave
Status Live in us-central1, us-east4, europe-west4, us-west1 (README.md:436-445) 4 Cloud Run regions behind a global LB Pilot: TR_FASTPATH_PILOT_ENABLED=true for 2 workspaces (rollout.sh:630-632)

The long-term design (docs/design/fast-admission-and-batched-settlement.md) has the enclave call the fast path directly, removing the control plane from request latency. That has not been built.


7. Must-read files (in reading order)

# Path:line Why
1 AGENTS.md:1 Hard rules: the three CI gates, the STORE monkeypatch trap, money-code test rules, Spanner query safety, auto-deploy on push to main
2 README.md:127 Trust boundary, internal gateway contract (:501-515), scale and multi-region shape
3 src/trusted_router/main.py:227 create_app(): surface-gated route registration, background loops, exception→HTTP mapping, inference gate at :912
4 src/trusted_router/config.py:438 service_surface and the ~2.4k-line Settings. Every TR_* flag lives here
5 src/trusted_router/middleware.py:113 Middleware order and why (raw ASGI vs BaseHTTPMiddleware, internal auth before body)
6 src/trusted_router/auth.py:90 principal_from_request: credential precedence and sk-tr-/trsess- prefix dispatch
7 src/trusted_router/routes/internal/gateway.py:1 The production hot path. Authorize :534/:856, settle :590/:3776, refund :2561, route registration :2453
8 src/trusted_router/schemas.py:366 GatewayAuthorizeRequest and GatewaySettleRequest (:566): the wire contract with quill-cloud-proxy
9 src/trusted_router/routing.py:633 provider_route_preferences: every OpenRouter routing knob, min_privacy parsing
10 src/trusted_router/routing.py:253 chat_route_endpoint_candidates and the filter/sort helpers (:1080-1300)
11 src/trusted_router/catalog_privacy.py:231 endpoint_meets_privacy_requirement: the exact meaning of ZDR and confidential
12 src/trusted_router/catalog_data.py:61 Privacy tier constants and aliases. Meta-model IDs at :2472-2830
13 src/trusted_router/catalog_registry.py:1 Import-time construction of MODELS/MODEL_ENDPOINTS and uniform pricing (money-critical)
14 src/trusted_router/routing_candidates.py:138 Meta-model pools: auto, zdr, e2e, eu, cheap, monitor, tiers
15 src/trusted_router/storage_gcp_authorize.py:1 The one-transaction authorize. Reaper at :1224
16 src/trusted_router/storage.py:3880 _StoreProxy/STORE (:3947) and create_store (:3976). Backend selection
17 src/trusted_router/store_protocol.py:1 The Store contract: reserve :913, settle :922, authorize_gateway_typed :1162, typed_finalize_gateway_authorization :1215
18 src/trusted_router/storage_gcp_settle_outbox.py:1 Durable settle outbox (exactly-once via INSERT-as-claim plus a lease fence)
19 src/trusted_router/routes/inference.py:150 Local inference routes. The easiest way to see the whole lifecycle in one process
20 src/trusted_router/services/inference_quota.py:84 reserved_quota / QuotaTicket: reserve→settle/refund in miniature
21 src/trusted_router/adapter.py:1 Chat ↔ Messages ↔ Responses conversions
22 src/trusted_router/providers.py:142 ProviderClient and its adapters (provider_adapters.py)
23 src/trusted_router/gateway_boot.py:1 Enclave boot registration and per-request X-TR-Boot-Auth signatures
24 src/trusted_router/services/fastpath_frontdoor.py:1 Fast path pilot bridge and its fallback semantics
25 src/trusted_router/money.py:1 Microdollar units and signup credit. Never floats
26 src/trusted_router/analytics_sink.py:1 Why analytics must never affect a request
27 src/trusted_router/routes/chat_proxy.py:1 The one place prompt bytes cross the control plane (browser playground)
28 docs/storage-portability/multi-cloud-separation.md:1 Identity federates, credits do not
29 docs/design/durable-settle-outbox.md:1 Why a lost settle used to mean free inference

8. Glossary

Term Meaning
Workspace The billing and tenancy unit. Owns credits, API keys, BYOK configs and members. api_key.workspace_id is the trusted attribution, never a header (gateway.py:890)
Management key vs inference key Key scopes. Signup issues a one-time management key. Inference requires an inference-scoped key (auth.py:312-345, scopes.py)
Lookup hash / key hash API keys are stored as salted SHA-256. A separate high-entropy lookup hash allows point reads. The enclave sends hashes, not raw keys
Microdollars Integer money unit, 1 USD = 1,000,000 (money.py:5). All ledger math is integer
Reserve Pre-authorization hold of the estimated cost on workspace credit (and key cap) before the provider call. In prod it is one Spanner transaction inside authorize
Authorization The gateway_authorization row created at authorize. Carries frozen terms, candidates and the reservation id. Its terminal dispositions are settled, refunded, reaped_snapshot, and so on
Settle Book actual usage against the authorization. Releases the unused remainder and records the generation. One-shot and idempotent
Refund Release a reservation without charging (provider failure, disconnect)
Reaper Background release of expired holds. Historically it released without charging, which is why the settle outbox and Stage D snapshots exist
Settle outbox Durable Spanner table recording settle intents so an interrupted settle can be replayed exactly once
Async settle (v1) / shadow-v2 A planned mode where the enclave's settle returns fast and finalization is deferred. It currently only collects shadow evidence (flag off)
Fast path / lease Go service (fastpath/) that admits requests against a pre-granted regional credit lease and batches settlement. Pilot only
Stage D Heartbeat cohort. Boot-signed enclaves send usage heartbeats so long streams can be billed from a frozen pricing snapshot if the final settle is lost
Boot / boot key Per-boot enclave identity registered at /internal/gateway/spend-lease/register-boot. Signs authorize, heartbeat and disposition requests, plus inference receipts
Inference receipt Client-verifiable signed receipt from the enclave. A receipt fee (basis points) is frozen on the authorization
BYOK Bring Your Own Key: a customer's provider key, envelope-encrypted (KMS-wrapped DEK). The enclave gets the ciphertext and decrypts it in memory
ZDR Zero Data Retention, min_privacy=zdr (tier 2): the endpoint has an explicit zero-retention policy
No-store Tier 1: the provider does not store request or response content
Confidential / E2E / E2EE Tier 3: verified confidential compute and provider-side E2EE and explicit ZDR, never served by the model's own vendor. Fails closed
min_privacy Hard request-level privacy floor in provider.min_privacy. Never relaxed
data_collection=deny Soft retention preference, relaxed if it would leave no candidates
Meta-model A router-defined virtual model (trustedrouter/auto, zdr, e2e, eu, cheap, free, monitor, frontier, fusion, selector, polyphemus-1.0, …) that expands to an ordered candidate pool or an orchestration
Candidate / route candidate One (Model, ModelEndpoint) pair the gateway may try, in order
Endpoint A specific provider deployment of a model, with price, region, privacy flags and capabilities
Surface Process role selected by TR_SERVICE_SURFACE (combined, public, actions, control, internal, observer)
Home plane / peer plane Multi-cloud: keys are issued on the home plane, and peers federate them lazily with zero local balance
Custom model / user model Custom: a catalog model plus a hidden prompt and markup. User-provided: a BYO endpoint whose owner earns payouts
Broadcast Customer-configured metadata export (PostHog, OTLP) through a durable outbox
router_core vs control_plane SLO Status separates prompt-path availability from dashboard/billing availability
Conformance suite tests/conformance/, the executable Store contract across backends

9. Gotchas

  1. Prod control plane does not serve inference. _control_plane_inference_enabled returns true only for local/test (main.py:912-919). If you debug a prod chat bug in routes/inference.py, you are reading the wrong code. Prod goes through the enclave → routes/internal/gateway.py.
  2. /chat-proxy/v1/chat/completions terminates TLS on the control plane. It forwards bytes unparsed to the attested API for the browser playground only (routes/chat_proxy.py:1-30). SDK traffic must never use it.
  3. The STORE proxy. monkeypatch.setattr(STORE, …) poisons later tests (once 136 failures). Patch the backend class (AGENTS.md:42-45). typed_billing_store() exists because isinstance on the proxy lies (storage.py:3954-3968).
  4. The catalog is built at import time. MODELS and MODEL_ENDPOINTS come from data/*.json when the module loads. An hourly price-refresh bot commits to main, so rebase before trusting a failure (AGENTS.md:47-49).
  5. Money code needs differential/conformance tests. Run the proof oracle when touching settle/billing: uv run pytest -q -n 4 -m proof_oracle tests/test_async_settle_proof_oracle.py (AGENTS.md:39-40, 81-87).
  6. Async settle is huge and dormant. 98 modules (async_settle_shadow_v2_* alone is 86) are shadow/evidence machinery behind TR_ASYNC_SETTLE_ENABLED=false. Do not mistake it for the live settle path, which is _settle_gateway_authorization.
  7. Config is code. Cloud Run env vars come from the ENV_VARS arrays in scripts/deploy/rollout.sh and the companion surface scripts. Setting them by hand is overwritten on the next rollout. Pushing src/** to main auto-deploys (AGENTS.md:60-66).
  8. Combined surface is fail-closed in deploys. A missing TR_SERVICE_SURFACE defaults to combined, and that raises outside local/test unless the migration bridge flag is set (main.py:235-244, rollout.sh:12-15).
  9. Gateway storage is synchronous. Every gateway handler runs in run_in_threadpool to avoid head-of-line blocking. AnyIO's 40-token pool is shared, which is safe only because Cloud Run concurrency is ~8 (gateway.py:2454-2467).
  10. Rate limiting is process-local by design (middleware.py:144-146). Fleet-wide limits belong at the front door. (The README's "rate limits use the configured store" line is out of date.)
  11. min_privacy vs data_collection. The first is a hard filter and the second is soft. ZDR and confidential compute are separate dimensions: a TEE without a retention promise does not satisfy ZDR (routing.py:1131-1135).
  12. Anthropic cache-token semantics differ. Anthropic's input_tokens excludes cached tokens, while OpenAI and Gemini include them. Settle normalizes this (schemas.py:574-578, settle_outbox_apply.py:112).
  13. Never scan tr_entities.body in prod Spanner, even read-only with LIMIT. Use scripts.inspect_payment_owner and ClickHouse (AGENTS.md:70-79). A body scan caused the September 6 CPU incident.
  14. The README mentions Bigtable for activity. The current write path is the Spanner tr_generation bounded record plus the Spanner analytics outbox → ClickHouse (storage_gcp_generation_records.py:1-6, docs/storage-portability/analytics-ingestion.md). Treat Bigtable references as historical unless the code says otherwise.
  15. Unknown routing fields return 400 and unimplemented OpenRouter fields return 501 not_supported_in_alpha. Nothing is silently ignored (README.md:334-336).
  16. Each cloud is its own TrustedRouter. An AWS-issued key does not work on GCP, and credits don't move without an explicit transfer (multi-cloud-separation.md).
  17. Analytics must never affect money. The benchmark sink drops rows when its queue is full, and the outbox enqueue rides the settle commit only on the one-commit happy path (AGENTS.md:83-87, analytics_sink.py:1-25).