Local setup & config
Verified Day-1 setup on macOS plus the full environment variable reference.
Every command below was run on a real Mac by the research agent on 2026-10-19 against
main@778120494: macOS 26 (Darwin 25.6.0) on Apple Silicon, uv 0.11.6, Node v24.14.1, Docker CLI 29.6.1. ✅ = ran and passed exactly as written. ⚠️ = problem found; details are given next to it. The repo stayed clean (git statusempty) after every step.
0. TL;DR (copy-paste, ~2 minutes on a warm machine)
cd quill-router
uv sync # ✅ ~10 s (238 packages resolved)
uv run ruff check . && uv run mypy # ✅ ~0.5 s + ~8–10 s
uv run pytest -q -x tests/conformance -k memory # ✅ fast memory-backend contract
uv run uvicorn trusted_router.main:app --port 18080 --reload # ✅ starts in about 1 s
You do not need a .env, cloud credentials, Docker, or provider keys to run the app
or most of the test suite. The defaults are local-safe: TR_ENVIRONMENT=local,
TR_STORAGE_BACKEND=memory and TR_ENABLE_LIVE_PROVIDERS=false, so providers are mocked.
1. Prerequisites
| Tool | Version | Why | Verified here |
|---|---|---|---|
| Python | 3.12 (.python-version; requires-python >=3.11; mypy targets 3.12; Docker image is python3.12-bookworm-slim) |
Runtime | ✅ uv auto-provisioned CPython 3.12.13 |
| uv | any recent version (CI uses astral-sh/setup-uv@v7 with version: latest) |
Package/venv manager. Always run things through uv run. |
✅ 0.11.6 |
| Node + npm | CI pins Node 20 (actions/setup-node, node-version: "20") |
ESLint, Stylelint, tsc (dashboard TS → committed JS), Playwright |
✅ Node 24.14.1 also works |
| Playwright Chromium | @playwright/test ^1.62.1 |
Browser smoke tests (tests/browser/, tests-e2e/) |
✅ npx playwright install chromium (~95 MiB, about 12 s) |
| Docker | any | Only needed for the Postgres / pgadapter / Spanner-emulator conformance backends | ⚠️ CLI is installed, but the daemon was not running on this Mac (see Troubleshooting T1). Not needed for anything else. |
| gcloud / AWS / Azure CLIs | — | Not needed for local dev. Deploys go through CI (see 04-infra-deploy). | n/a |
Install hints (macOS):
curl -LsSf https://astral.sh/uv/install.sh | sh # uv
brew install node@20 # or nvm install 20
# Docker Desktop / OrbStack / colima: only if you will run postgres/spanner conformance
2. Python environment
uv sync
✅ Result: Resolved 238 packages, installed the dev group (the default) into .venv/.
Time: 9.8 s wall-clock with a warm uv cache. A second run takes 19 ms (Checked 214 packages).
Notes
* The dev dependency group includes docling and sec-parser, which pull a large
ML stack (torch, transformers). A cold-cache first sync therefore downloads a lot
more than the production image needs. Production uses uv sync --frozen --no-dev.
* CI runs uv sync --frozen. Use --frozen too if you want to be sure you are not
silently changing uv.lock.
* Do not pip install into the venv by hand. uv run re-syncs to the lockfile.
3. The three CI gates (from AGENTS.md: "run all three before claiming done")
uv run ruff check . && uv run mypy && uv run pytest -q
| Gate | Command | Result on this Mac | Time |
|---|---|---|---|
| Lint | uv run ruff check . |
✅ All checks passed! |
0.55 s |
| Types | uv run mypy |
✅ Success: no issues found in 533 source files |
9.6 s (warm) / 8.3 s with a cold --cache-dir. mypy is not slow here, because of follow_imports = "silent" and files = ["src/trusted_router"]. |
| Tests | uv run pytest -q |
Not run in full on purpose (multi-hour on one machine). Collection alone: 38,221 / 40,067 tests collected (1,846 deselected) in 29 s | — |
Rules worth tattooing (AGENTS.md):
* Never run ruff format .. CI only runs ruff check, and formatting produces a huge unrelated diff.
* Coverage must stay ≥ 70 %. CI shards the suite with pytest-split and runs -n 4 --dist loadgroup.
* The default addopts deselects proof_oracle (over an hour of differentials) and provider_health (live provider state).
If you touch async settle or billing, run the oracle explicitly:
uv run pytest -q -n 4 -m proof_oracle tests/test_async_settle_proof_oracle.py.
* CI's lint job also runs uv run python scripts/update_model_changes.py --check. ✅ It exited 0 here.
3a. Fast, targeted pytest runs (what to use during the inner loop)
# Storage contract against the in-memory backend (postgres/spanner params auto-skip without DSNs)
uv run pytest -q -p no:cacheprovider -x \
tests/conformance/test_store_semantics.py tests/conformance/test_key_management.py -k memory
✅ 90 passed, 14 skipped, 416 deselected, 6 warnings in 1.07s (10 s wall-clock including uv and imports)
uv run pytest -q -p no:cacheprovider tests/test_billing.py tests/test_inference_errors.py
✅ 82 passed, 142 warnings in 7.72s
uv run pytest --collect-only -q # count / sanity-check collection
✅ 29 s
Tips
* -n 4 (xdist) is available for bigger subsets.
* tests/conftest.py forces TR_STORAGE_BACKEND=memory, and the operator keys file is
ignored under pytest unless TR_ALLOW_LOCAL_KEY_FILE_IN_TESTS=1. Tests are hermetic by default.
* Pollution trap (issue #333): never monkeypatch.setattr(STORE, ...). STORE is a
__getattr__ proxy. Patch the backend class instead. Run the full suite before you merge.
* The warnings you will see are FastAPI on_event deprecation notices. They are harmless.
4. Run the app locally and exercise it end-to-end
uv run uvicorn trusted_router.main:app --port 18080 # add --reload while editing
✅ Log output:
INFO: Started server process [9685]
INFO: Waiting for application startup.
INFO: Application startup complete.
INFO: Uvicorn running on http://127.0.0.1:18080 (Press CTRL+C to quit)
Ready in under 1 s. (Production instead runs python -m trusted_router.serve, which reads
PORT, WEB_CONCURRENCY/UVICORN_WORKERS, UVICORN_PROXY_HEADERS,
UVICORN_FORWARDED_ALLOW_IPS and UVICORN_TIMEOUT_KEEP_ALIVE, and keeps the listener
open briefly after SIGTERM.)
4a. Health and catalog ✅
curl -s localhost:18080/health # {"status":"ok"} 200, ~2 ms
curl -s localhost:18080/v1/health # {"status":"ok"} 200
curl -s localhost:18080/v1/models/count # {"data":{"count":692}}
curl -s localhost:18080/v1/models | jq '.data[:5][].id'
# "trustedrouter/auto" "trustedrouter/free" "trustedrouter/cheap" "trustedrouter/fast" "trustedrouter/green"
/, /docs, /status and /openapi.json all return 200. /healthz is not a route. It
returns a markdown-flavoured 404 that points agents at llms.txt and sitemap.xml.
4b. Local auth: no OAuth needed ✅
In local/test you can act as any user by sending the x-trustedrouter-user header
(this is what scripts/smoke_e2e.py and the tests do). The first call auto-creates a
"Personal Workspace".
B=http://127.0.0.1:18080/v1
H='x-trustedrouter-user: dev@example.com'
curl -s $B/workspaces -H "$H"
KEY=$(curl -s -X POST $B/keys -H "$H" -H 'content-type: application/json' \
-d '{"name":"local dev"}' | jq -r .key) # sk-tr-v1-...
4c. Fund the workspace, then chat ✅ (the non-obvious bit)
A brand-new local workspace has $0, so the first chat returns
402 {"error":{"code":402,"message":"Insufficient credits","type":"insufficient_credits"}}.
Trial credit is only granted on the OAuth/email signup paths. With no TR_STRIPE_SECRET_KEY,
/billing/checkout returns a "mode":"mock" session, and in local/test the Stripe webhook
accepts unsigned JSON. So you can credit yourself like this:
WS=$(curl -s $B/workspaces -H "$H" | jq -r '.data[0].id')
curl -s -X POST $B/internal/stripe/webhook -H 'content-type: application/json' -d "{
\"id\":\"evt_local_1\",\"type\":\"checkout.session.completed\",
\"data\":{\"object\":{\"amount_total\":2500,\"payment_intent\":\"pi_local_1\",
\"payment_status\":\"paid\",\"metadata\":{\"workspace_id\":\"$WS\"}}}}"
# {"data":{"credited":true,"credited_microdollars":25000000,"event_id":"evt_local_1",...}}
curl -s $B/credits -H "$H" # available: 25.0
(The event is idempotent by id. Change evt_local_N to top up again.)
curl -s $B/chat/completions -H "authorization: Bearer $KEY" -H 'content-type: application/json' \
-d '{"model":"trustedrouter/auto","messages":[{"role":"user","content":"hello"}]}'
✅ Response (trimmed):
{"object":"chat.completion","model":"z-ai/glm-5.3-flash",
"choices":[{"message":{"role":"assistant","content":"TrustedRouter response from z-ai/glm-5.3-flash."},"finish_reason":"stop"}],
"usage":{"prompt_tokens":1,"completion_tokens":11,"total_tokens":12},
"trustedrouter":{"content_stored":false,"requested_model":"trustedrouter/auto","selected_model":"z-ai/glm-5.3-flash","rollover_failures":[]}}
Streaming ("stream":true) ✅ emits an event: trustedrouter.route SSE frame and then
standard chat.completion.chunk deltas. GET /v1/activity?group_by=none ✅ shows the
metadata-only row (cost_microdollars: 3, no prompt content).
How the fake provider works: services/inference.py builds
ProviderClient(LocalKeyFile(settings.local_keys_file), live=settings.enable_live_providers).
With live=False (the default, and also in production, where real inference happens
in the attested gateway, quill-cloud-proxy), providers.py returns
mock_text(model.id) → "TrustedRouter response from <model>." No network calls are made.
Set TR_ENABLE_LIVE_PROVIDERS=true and put real keys in the keys file (section 6) to call
real upstreams from your laptop.
Other observations:
* No auth → 401 Missing Authentication header ✅
* cerebras/llama3.1-8b → 400 model_not_supported. That model is no longer chat-capable in the catalog (see T3).
Stop the server with Ctrl-C (or pkill -f "uvicorn trusted_router.main:app").
4d. scripts/smoke_e2e.py ⚠️ stale
TR_SMOKE_BASE_URL=http://127.0.0.1:18080/v1 uv run python scripts/smoke_e2e.py
⚠️ E2E smoke failed: expected 54 OpenRouter route classifications, got 95. The script
(last touched 2026-05-04) hard-codes 54 coverage rows and uses cerebras/llama3.1-8b. It
also assumes new workspaces have credit. Treat it as reference reading, not a working check.
Use the curl flow above instead.
5. Frontend / npm
Root package.json (trusted-router-frontend). The dashboard TypeScript in frontend/src/
compiles into committed JS under src/trusted_router/static/.
| Script | Command | What it does | Verified |
|---|---|---|---|
| install | npm ci |
213 packages | ✅ 0.6 s |
lint |
npm run lint → eslint frontend/src |
TS lint | ✅ clean, 0.8 s |
lint:css |
npm run lint:css → stylelint src/trusted_router/static/*.css |
CSS lint | ✅ clean, 0.6 s |
typecheck |
npm run typecheck → tsc --noEmit |
Type check only | ✅ clean, 0.5 s |
build |
npm run build → tsc |
Writes src/trusted_router/static/*.js. CI runs npx tsc and then git diff --exit-code -- src/trusted_router/static/dashboard.js src/trusted_router/static/token-index.js. If you edit .ts, commit the regenerated .js. |
✅ the output matched the committed JS (no diff) |
test:browser |
npm run test:browser → playwright test (playwright.config.js, testDir: tests/browser) |
Homepage/console/chat smoke. Its webServer starts uvicorn on :18081 with TR_ENVIRONMENT=test TR_STORAGE_BACKEND=memory TR_RATE_LIMIT_ENABLED=false and fake OAuth client IDs |
✅ npx playwright test tests/browser/homepage.spec.js --project=chromium → 9 passed (5.4 s) |
build:world-map |
node scripts/build-world-svg.mjs |
Regenerates the world SVG asset | not run (it writes tracked files) |
First-time browser install: npx playwright install chromium ✅ (CI uses --with-deps chromium).
tests-e2e/ is a separate npm project (its own package.json and playwright.config.ts)
for the /chat playground, with 19 specs. It mocks /v1/chat/completions via page.route():
cd tests-e2e && npm install && npm run install:browsers # chromium webkit firefox
npm test -- --project=chromium # also: test:headed, test:debug, test:ui
(Not run by this agent. The README says the server auto-starts via webServer.)
6. Configuration: how settings are resolved
All runtime config lives in one Pydantic Settings class:
src/trusted_router/config.py::Settings (2,388 lines, 354 fields). It is built by
get_settings() (no caching side effects).
env_prefix="TR_": fieldstorage_backend↔ envTR_STORAGE_BACKEND.env_file=".env"(relative to the CWD, gitignored)..env.exampleis the template.extra="ignore": unknownTR_*vars are silently ignored, so a typo does nothing.hide_input_in_errors=True: validation errors don't echo secret values.- Validators fail closed. For example,
TR_ENVIRONMENT=productionalone refuses to start:TR_SERVICE_SURFACE=combined is restricted to local/test unless the temporary TR_ALLOW_DEPLOYED_COMBINED_SURFACE=true migration bridge is explicitly enabled✅ (verified).TR_REMEDIATOR_MODEmust be exactlyoff|observe|act.
Source priority (highest wins), from Settings.settings_customise_sources:
- init kwargs (tests)
- process environment (
TR_*) .envin the CWD- operator keys file
~/.quill_cloud_keys.private(_LocalKeyFileSource). Only for the 53 secret/integration fields listed in_LOCAL_KEY_FALLBACKS, looked up by the un-prefixed upper-case name (e.g.STRIPE_SECRET_KEY,GOOGLE_CLIENT_ID,SENTRY_DSN). Skipped entirely under pytest. - file secrets (pydantic default; unused)
- code defaults in
config.py
The operator keys file
- Dotenv-style, never committed. Parsed by
secrets.LocalKeyFile(KEY=value,#comments, quotes stripped). Values are never logged. - README documents
/Users/jperla/claude/.quill_cloud_keys.private. That is Joseph's path. For you it is~/.quill_cloud_keys.private(the code default). It does not exist on this Mac, and nothing needs it. - Two consumers:
1. Settings fallback (above): integration secrets such as Stripe, OAuth, SES, PayPal, Adyen, Sentry,
INTERNAL_GATEWAY_TOKENandBOOTSTRAP_MANAGEMENT_KEY. 2. Provider client (providers.py::_secret): provider keys such asANTHROPIC_API_KEY,OPENAI_API_KEY,GEMINI_API_KEY,CEREBRAS_API_KEY,DEEPSEEK_API_KEYandMISTRAL_API_KEY. It checks the keys file first and then the bare process env. These are notTR_-prefixed. - Aliases (
secrets.KEY_ALIASES):CLAUDE_API_KEY→Anthropic,CHATGPT_API_KEY→OpenAI,STRIPE_KEY→STRIPE_SECRET_KEY,ZHIPU_API_KEY/Z_AI_API_KEY→ZAI_API_KEY,TOGETHERAI_API_KEY→Together,DASHSCOPE_API_KEY→Alibaba, and Vertex project/region/token aliases. - Vertex in production uses the service account and ADC, not a long-lived key in this file.
- ⚠️ Gotcha (code-verified):
TR_LOCAL_KEYS_FILEchanges the path the provider client reads, but_LocalKeyFileSourcereadssettings_cls.model_fields["local_keys_file"].default, which is always~/.quill_cloud_keys.private. So the Settings fallback for Stripe, OAuth and the rest ignoresTR_LOCAL_KEYS_FILE..env.examplesets it to Joseph's path. If you copy.env.exampleverbatim, provider keys are read from a non-existent file, while integration secrets still come from~/.quill_cloud_keys.private.
Production: config-as-code
- Runtime env for Cloud Run is the
ENV_VARSarray inscripts/deploy/rollout.sh(~120TR_*entries; joined with^|^and passed as--set-env-vars). Setting a Cloud Run env var by hand is a no-op. The next rollout overwrites it. To change prod config, editrollout.shin a PR. - Notable prod values:
TR_ENVIRONMENT=production,TR_SERVICE_SURFACE=combinedwithTR_ALLOW_DEPLOYED_COMBINED_SURFACE(temporary bridge until #712),TR_RATE_LIMIT_ENABLED=false(no trusted edge client identity yet),TR_ENABLE_LIVE_PROVIDERS=false,TR_REMEDIATOR_IN_PROCESS_ENABLED=false,TR_SIGNUP_TRIAL_CREDIT_MICRODOLLARS=300000, andTR_STORAGE_BACKEND=${STORAGE_BACKEND}(spanner-clickhouseper README). - Secrets come from GCP Secret Manager via
--update-secrets: - Mandatory
SECRET_ENVS:TR_SENTRY_DSN,TR_STRIPE_SECRET_KEY,TR_STRIPE_WEBHOOK_SECRET,TR_INTERNAL_GATEWAY_TOKEN, and the async-settle Ed25519 PEM mounted as a file at/secrets/async-settle/ticket.pem. A missing one fails the deploy. - About 67 optional ones, added through
add_secret_env_if_existsonly when the secret exists in the inventory; otherwise they are explicitly--remove-secrets'd. These include provider keys (ANTHROPIC_API_KEY…ZERO_G_API_KEY), OAuth client ID/secret, Twilio/Telnyx/Veriff, PayPal/Routable/Adyen, AWS keys,AXIOM_API_TOKENand the federation/operator tokens. - Non-GCP deployments (AWS/Azure) have their own workflows (
deploy-aws-control-plane.yml,deploy-azure-control-plane.yml) and use Workload Identity Federation (TR_GOOGLE_EXTERNAL_CREDENTIAL_CONFIGinscripts/entrypoint.sh, which refuses key material). See 04-infra-deploy.
flowchart TD
D["Code defaults<br/>src/trusted_router/config.py::Settings<br/>(354 fields, local-safe)"]
K["Operator keys file<br/>~/.quill_cloud_keys.private<br/>(un-prefixed names; 53 secret fields only;<br/>skipped under pytest)"]
E[".env in CWD<br/>(gitignored; template: .env.example)"]
P["Process env TR_*<br/>(shell, playwright webServer, CI)"]
I["Init kwargs<br/>(tests: Settings(...))"]
S(["Settings instance<br/>get_settings()"])
D --> K --> E --> P --> I --> S
subgraph PROD["Production (Cloud Run, GCP)"]
R["scripts/deploy/rollout.sh<br/>ENV_VARS array → --set-env-vars<br/>(config-as-code; manual edits overwritten)"]
SM["GCP Secret Manager<br/>SECRET_ENVS + add_secret_env_if_exists<br/>→ --update-secrets (env vars + PEM file mount)"]
R --> P2["Container env TR_* + provider keys"]
SM --> P2
end
P2 -. same precedence slot as .-> P
PK["Provider API keys<br/>ANTHROPIC_API_KEY, OPENAI_API_KEY ...<br/>(keys file first, then bare env)"] --> PC(["ProviderClient<br/>live only if TR_ENABLE_LIVE_PROVIDERS=true"])
K -. provider keys .-> PKDiagram source
flowchart TD
D["Code defaults<br/>src/trusted_router/config.py::Settings<br/>(354 fields, local-safe)"]
K["Operator keys file<br/>~/.quill_cloud_keys.private<br/>(un-prefixed names; 53 secret fields only;<br/>skipped under pytest)"]
E[".env in CWD<br/>(gitignored; template: .env.example)"]
P["Process env TR_*<br/>(shell, playwright webServer, CI)"]
I["Init kwargs<br/>(tests: Settings(...))"]
S(["Settings instance<br/>get_settings()"])
D --> K --> E --> P --> I --> S
subgraph PROD["Production (Cloud Run, GCP)"]
R["scripts/deploy/rollout.sh<br/>ENV_VARS array → --set-env-vars<br/>(config-as-code; manual edits overwritten)"]
SM["GCP Secret Manager<br/>SECRET_ENVS + add_secret_env_if_exists<br/>→ --update-secrets (env vars + PEM file mount)"]
R --> P2["Container env TR_* + provider keys"]
SM --> P2
end
P2 -. same precedence slot as .-> P
PK["Provider API keys<br/>ANTHROPIC_API_KEY, OPENAI_API_KEY ...<br/>(keys file first, then bare env)"] --> PC(["ProviderClient<br/>live only if TR_ENABLE_LIVE_PROVIDERS=true"])
K -. provider keys .-> PKRead the top chain as "lowest priority → highest priority". Each later source overrides the one before it.
7. Env var reference
7a. The ones you actually care about locally
| Env var | Default | Purpose | Required locally? |
|---|---|---|---|
TR_ENVIRONMENT |
local |
local/test unlock dev affordances: the x-trustedrouter-user header, the unsigned Stripe webhook, mock checkout, combined surface. Anything else is treated as deployed and fails closed. |
No (default is right) |
TR_STORAGE_BACKEND |
memory |
memory (in-process and lost on restart), postgres, spanner/spanner-clickhouse (prod) |
No |
TR_ENABLE_LIVE_PROVIDERS |
false |
true makes real upstream calls using the keys file / env |
No (true only to hit real models) |
TR_LOCAL_KEYS_FILE |
~/.quill_cloud_keys.private |
Path for provider keys (see the gotcha above) | No |
TR_SERVICE_SURFACE |
combined |
Process role: combined\|public\|actions\|control\|internal\|observer |
No |
TR_RATE_LIMIT_ENABLED |
true |
Per-IP (240/min), per-key (1200/min) and internal (6000/min) limits. Turn it off for load or browser tests (Playwright does). | No |
TR_RATE_LIMIT_IP_PER_WINDOW / _KEY_ / _INTERNAL_ / _WINDOW_SECONDS |
240 / 1200 / 6000 / 60 | Limits | No |
TR_POSTGRES_DSN |
"" |
Postgres-wire system of record (Azure, AWS DSQL, Spanner-PG) | Only with postgres backend |
TR_GCP_PROJECT_ID / TR_SPANNER_INSTANCE_ID / TR_SPANNER_DATABASE_ID |
quill-cloud-proxy / None / None |
Spanner target | Only with spanner backends (never point at prod) |
TR_INTERNAL_GATEWAY_TOKEN |
None | Protects /v1/internal/gateway/* (authorize/settle/refund) |
No. Required outside local. |
TR_BOOTSTRAP_MANAGEMENT_KEY |
None | Seeds a management API key at startup | No |
TR_STRIPE_SECRET_KEY / TR_STRIPE_WEBHOOK_SECRET |
None | Real Stripe. Unset means mock checkout and an unsigned local webhook. | No |
TR_GOOGLE_CLIENT_ID/_SECRET, TR_GITHUB_CLIENT_ID/_SECRET |
None | OAuth sign-in. Each is independently optional. | No (use the header instead) |
TR_SENTRY_DSN |
None | Error reporting. Disabled in local unless TR_SENTRY_LOCAL_ENABLED. |
No |
TR_SIGNUP_TRIAL_CREDIT_MICRODOLLARS |
300000 | Starter credit on OAuth/email signup | No |
TR_EMAIL_SIGNUP_ENABLED |
false |
Plain-email POST /v1/signup (credit-farming vector, so off by default) |
No |
TR_ALLOW_LOCAL_KEY_FILE_IN_TESTS |
unset | 1 lets pytest read the keys file |
No |
TR_CONFORMANCE_POSTGRES_DSN |
unset | Enables the postgres conformance params |
Only for conformance |
TR_CONFORMANCE_SPANNER_PG_DSN |
unset | Enables the spanner-pg conformance params |
Only for conformance |
TR_SMOKE_BASE_URL, TR_SMOKE_USER, TR_SMOKE_INTERNAL_TOKEN |
http://127.0.0.1:18080/v1, smoke@example.com, — |
scripts/smoke_e2e.py inputs |
No |
PORT, WEB_CONCURRENCY/UVICORN_WORKERS, UVICORN_* |
— | trusted_router.serve (container entrypoint) |
No (use uvicorn directly) |
Provider keys ANTHROPIC_API_KEY, OPENAI_API_KEY, GEMINI_API_KEY, … |
— | Upstream auth (no TR_ prefix) |
Only with live providers |
.env.example (18 lines) contents, annotated: TR_ENVIRONMENT=local,
TR_ENABLE_LIVE_PROVIDERS=false, TR_LOCAL_KEYS_FILE=/Users/jperla/... (⚠️ change it or drop it),
TR_STORAGE_BACKEND=memory, and Spanner/Bigtable IDs (inert with memory;
TR_BIGTABLE_* are not Settings fields any more, so extra="ignore" drops them). The rest are
empty secrets plus the rate-limit numbers. ✅ Loading it verbatim gives
local memory False … True combined and import trusted_router.main succeeds.
7b. Direct os.environ reads outside Settings
Most are in ops scripts, the synthetic monitor and deploy tooling, not in the request path:
* Storage bootstrap scripts: TR_STORAGE_BACKEND, TR_GCP_PROJECT_ID, TR_SPANNER_INSTANCE_ID, TR_SPANNER_DATABASE_ID (20+ reads each in scripts/), TR_POSTGRES_DSN, TR_SPANNER_POOL_SIZE, TR_SCHEMA_*.
* Synthetic monitor (synthetic/cli.py, image_generation.py, video_generation.py): TR_SYNTHETIC_*, TR_MONITOR_API_KEY, TR_EMBEDDINGS_*.
* Deploy mutex/outcome: TR_DEPLOY_*. Smoke: TR_SMOKE_*, TR_API_BASE, TR_API_KEY.
* serve.py: PORT, WEB_CONCURRENCY, UVICORN_*. entrypoint.sh: TR_GOOGLE_EXTERNAL_CREDENTIAL_CONFIG.
* Provider keys via providers.py::_secret (keys file, then env), plus DATABRICKS_HOST/TOKEN, VERTEX_PROJECT_ID, etc.
* CLOUDSDK_AUTH_CREDENTIAL_FILE_OVERRIDE, GITHUB_TOKEN and GITHUB_STEP_SUMMARY in CI/ops scripts.
7c. Full Settings reference (all 354 fields, auto-generated)
Generated by introspecting Settings.model_fields and taking the first sentence of the
comment directly above each field in config.py. Secret-looking fields never show values:
they read None/"" or "(non-empty code default)". "Keys-file fallback = yes" means the
field can come from ~/.quill_cloud_keys.private under its un-prefixed upper-case name.
Grouping is by name prefix, so it is approximate. Required for local: none of them. Every
field has a default, and the defaults target local + memory. Production requirements are
enforced by the validators (production_is_fail_closed, operational_analytics_problems, …)
and supplied by rollout.sh + Secret Manager.
Core runtime & surfaces (37)
| Env var | Default | Purpose (from code comment, first sentence) | Keys-file fallback |
|---|---|---|---|
TR_ENVIRONMENT |
"local" |
||
TR_RELEASE |
"local" |
||
TR_SOURCE_REVISION |
"" |
||
TR_SERVICE_NAME |
"trusted-router" |
||
TR_SERVICE_SURFACE |
"combined" |
One image serves several deliberately disjoint process roles. | |
TR_ALLOW_DEPLOYED_COMBINED_SURFACE |
False |
Temporary compatibility bridge for the pre-split GCP service. | |
TR_API_BASE_URL |
"https://api.trustedrouter.com/v1" |
||
TR_TRUSTED_DOMAIN |
"trustedrouter.com" |
||
TR_TRUSTED_DOMAIN_ALIASES |
"allyrouter.com,uptimerouter.com" |
Additional first-party control-plane domains. | |
TR_SECURITY_CONTACT_EMAIL |
"security@trustedrouter.com" |
||
TR_SUPPORT_EMAIL |
"help@trustedrouter.com" |
||
TR_ENABLE_LIVE_PROVIDERS |
False |
||
TR_LOCAL_KEYS_FILE |
"~/.quill_cloud_keys.private" |
||
TR_PUBLIC_CLIENT_OBSERVED_ENABLED |
False |
Client-observed figures include upstream provider-caused failures and can understate a healthy gateway. | |
TR_SIGNUP_TRIAL_CREDIT_MICRODOLLARS |
300000 |
Starter credit granted exactly once with a new email/social OAuth account's first workspace. | |
TR_SIWE_DOMAIN |
None |
MetaMask uses public-key crypto (no shared secret). | yes |
TR_RETIREMENT_NOTICES_MODE |
"off" |
Daily route-retirement notices. | |
TR_RETIREMENT_NOTICES_PREVIEW_EMAIL |
None |
||
TR_STABLECOIN_CHECKOUT_ENABLED |
True |
||
TR_USER_MODELS_DISPATCH_ENABLED |
False |
Whether the gateway will AUTHORIZE requests against user-provided models. | |
TR_SPECULATIVE_PROVIDER_SHADOW_ENABLED |
False |
PR3 observes only; | |
TR_SPEND_LEASE_ACCEPTED_GCP_IMAGE_DIGESTS |
"" |
Audited break-glass addition to the signed Stage D runtime policy. | |
TR_SPEND_LEASE_TRUST_ELIGIBILITY_ENABLED |
False |
Arms the authorize-time billing-pause gate (trust_eligibility .billing_paused_tx). | |
TR_MAX_WORKSPACES_PER_OWNER |
25 |
||
TR_OPERATOR_TOKEN |
"" |
||
TR_OPERATOR_IDENTITIES |
"" |
||
TR_REAP_SNAPSHOT_BOOKING_ENABLED |
False |
Decision 70's billing change. | |
TR_READ_ONLY |
False |
Operational read-only flag. | |
TR_REGIONS |
"us-central1,us-east4,europe-west4,us-west1" |
Attested gateway regions. | |
TR_PRIMARY_REGION |
"us-central1" |
||
TR_SERVING_REGION |
"" |
TR_SERVING_REGION identifies this process's deployment, not the primary Spanner/journal region. | |
TR_REGIONAL_API_HOSTNAME_TEMPLATE |
"api-{region}.quillrouter.com" |
||
TR_USER_CREDIT_TRANSFER_DAILY_CAP_MICRODOLLARS |
100000000 |
Same-plane user-to-user transfers are final. | |
TR_MAX_REQUEST_BODY_BYTES |
4194304 |
||
TR_MAX_IN_FLIGHT_REQUEST_BODY_BYTES |
67108864 |
||
TR_MAX_CONCURRENT_REQUEST_BODIES |
16 |
||
TR_REQUEST_BODY_READ_TIMEOUT_SECONDS |
30.0 |
Storage backends (Spanner/Postgres/ClickHouse) (25)
| Env var | Default | Purpose (from code comment, first sentence) | Keys-file fallback |
|---|---|---|---|
TR_STORAGE_BACKEND |
"memory" |
||
TR_POSTGRES_DSN |
"" |
Postgres-wire system of record for the non-GCP deployments. | |
TR_POSTGRES_IAM_AUTH |
"" |
Aurora DSQL passwords are short-lived IAM tokens. | |
TR_POSTGRES_IAM_REGION |
"" |
Normally inferred from <id>.dsql.<region>.on.aws. |
|
TR_GCP_PROJECT_ID |
"quill-cloud-proxy" |
||
TR_SPANNER_INSTANCE_ID |
None |
||
TR_SPANNER_DATABASE_ID |
None |
||
TR_GENERATION_RECORDS_ENABLED |
False |
||
TR_CLICKHOUSE_URL |
"" |
Legacy in-process ClickHouse mirror. | |
TR_CLICKHOUSE_USER |
"" |
||
TR_CLICKHOUSE_PASSWORD |
"" |
||
TR_CLICKHOUSE_BENCHMARK_TABLE |
"provider_benchmark_samples" |
||
TR_PROVIDER_ANALYTICS_CLICKHOUSE_URL |
"" |
Private, read-only provider portal connection. | |
TR_PROVIDER_ANALYTICS_CLICKHOUSE_USER |
"tr_provider_read" |
||
TR_PROVIDER_ANALYTICS_CLICKHOUSE_PASSWORD |
"" |
||
TR_PROVIDER_ANALYTICS_CLICKHOUSE_DATABASE |
"tr" |
||
TR_PROVIDER_ANALYTICS_CLICKHOUSE_TABLE |
"provider_benchmark_samples" |
||
TR_OPERATIONAL_ANALYTICS_CLICKHOUSE_URL |
"" |
||
TR_OPERATIONAL_ANALYTICS_CLICKHOUSE_USER |
"tr_control_read" |
||
TR_OPERATIONAL_ANALYTICS_CLICKHOUSE_PASSWORD |
"" |
||
TR_OPERATIONAL_ANALYTICS_CLICKHOUSE_DATABASE |
"tr" |
||
TR_OPERATIONAL_ANALYTICS_OUTBOX_ENABLED |
False |
Durable tenant-activity and synthetic-status stream. | |
TR_OPERATIONAL_ANALYTICS_SINK |
"outbox" |
HOW operational telemetry leaves the process. | |
TR_OPERATIONAL_ANALYTICS_CLICKHOUSE_WRITE_USER |
"tr" |
||
TR_OPERATIONAL_ANALYTICS_CLICKHOUSE_WRITE_PASSWORD |
"" |
Security, auth & OAuth (49)
| Env var | Default | Purpose (from code comment, first sentence) | Keys-file fallback |
|---|---|---|---|
TR_SOCIAL_REWARDS_ENABLED |
False |
Enabled explicitly in the primary deployment configuration. | |
TR_SOCIAL_REWARDS_PLANE_ID |
"" |
||
TR_SOCIAL_REWARDS_ISSUER_ID |
"gcp-primary" |
||
TR_SOCIAL_REWARDS_YOUTUBE_URL |
"" |
YouTube stays hidden and unclaimable until an official channel is configured. | |
TR_SOCIAL_REWARDS_CLAIMS_PER_MINUTE |
30 |
Shared account request throttle; | |
TR_GOOGLE_DATA_MANAGER_ENABLED |
False |
Metadata-only Google Ads conversion delivery. | |
TR_GOOGLE_DATA_MANAGER_ACCOUNT_ID |
None |
||
TR_GOOGLE_DATA_MANAGER_LOGIN_ACCOUNT_ID |
None |
||
TR_GOOGLE_DATA_MANAGER_SIGNUP_ACTION_ID |
None |
||
TR_GOOGLE_DATA_MANAGER_ACTIVATED_ACTION_ID |
None |
||
TR_GOOGLE_DATA_MANAGER_PURCHASE_ACTION_ID |
None |
||
TR_GOOGLE_DATA_MANAGER_KMS_KEY_NAME |
None |
||
TR_GOOGLE_DATA_MANAGER_BATCH_SIZE |
500 |
||
TR_GOOGLE_DATA_MANAGER_LEASE_SECONDS |
300 |
||
TR_GOOGLE_DATA_MANAGER_MAX_ATTEMPTS |
20 |
||
TR_GOOGLE_DATA_MANAGER_TIMEOUT_SECONDS |
20.0 |
||
TR_GOOGLE_DATA_MANAGER_REPAIR_LOOKBACK_DAYS |
90 |
||
TR_GOOGLE_DATA_MANAGER_STATUS_POLL_ATTEMPTS |
6 |
||
TR_GOOGLE_DATA_MANAGER_STATUS_POLL_SECONDS |
2.0 |
||
TR_ATTRIBUTION_COOKIE_KEY |
None |
Split public/control services may hold the base64-encoded, already-derived cookie key without receiving the legacy root that also authori… | yes |
TR_ATTRIBUTION_COOKIE_SECRET |
None |
yes | |
TR_INTERNAL_GATEWAY_TOKEN |
None |
yes | |
TR_BOOTSTRAP_MANAGEMENT_KEY |
None |
yes | |
TR_BYOK_KMS_KEY_NAME |
None |
yes | |
TR_BYOK_ENVELOPE_KEY_B64 |
None |
yes | |
TR_BYOK_ENVELOPE_KEY_REF |
"trustedrouter/byok-envelope-key/v1" |
yes | |
TR_BYOK_REGISTRATION_ENABLED |
True |
BYOK key REGISTRATION wraps the secret with the byok-envelope KMS key. | |
TR_GOOGLE_CLIENT_ID |
None |
OAuth providers — independently optional. | yes |
TR_GOOGLE_CLIENT_SECRET |
None |
yes | |
TR_GOOGLE_OAUTH_REDIRECT_URL |
None |
yes | |
TR_GOOGLE_OAUTH_LOGIN_AVAILABLE |
None |
The public service must render login links without receiving the OAuth client secrets owned by the control service. | |
TR_GOOGLE_ALIAS_CREDENTIALS_JSON |
"{}" |
Backup domains use independent provider credentials so login remains available even if the canonical domain or its OAuth app is unavailable. | yes |
TR_GITHUB_CLIENT_ID |
None |
yes | |
TR_GITHUB_CLIENT_SECRET |
None |
yes | |
TR_GITHUB_OAUTH_REDIRECT_URL |
None |
yes | |
TR_GITHUB_OAUTH_LOGIN_AVAILABLE |
None |
||
TR_GITHUB_ALIAS_CREDENTIALS_JSON |
"{}" |
GitHub OAuth Apps permit exactly one callback URL. | yes |
TR_FEDERATION_PEER_TOKEN |
"" |
--- Lazy API-key federation ------------------------------------------- HOME side: the token peers present to /internal/federation/resolv… | |
TR_FEDERATION_HOME_BASE_URL |
"" |
PEER side: the home plane this deployment resolves unknown keys from, and the token it presents. | |
TR_FEDERATION_HOME_TOKEN |
"" |
||
TR_FEDERATION_CREDIT_INBOUND_TOKEN |
"" |
INBOUND: what this plane requires to be credited. | |
TR_FEDERATION_CREDIT_PEER_BASE_URL |
"" |
OUTBOUND: the plane this deployment may push credits TO, and the token it presents there. | |
TR_FEDERATION_CREDIT_PEER_TOKEN |
"" |
||
TR_FEDERATION_DEFERRED_SETTLEMENT_ENABLED |
False |
OFF by default. | |
TR_FEDERATION_DEFERRED_MAX_OUTSTANDING_MICRODOLLARS |
25000000 |
The bound on how much unsettled debt one workspace may run up on this plane. | |
TR_FEDERATION_DEFERRED_AUTHORIZATION_TTL_SECONDS |
7200 |
How long a deferred authorization may sit unsettled before the reaper reclaims its estimate and releases the key-limit escrow. | |
TR_FEDERATION_SETTLEMENT_INBOUND_TOKENS |
"" |
HOME side: which peers may apply usage to this plane's ledger, as "plane=token,plane=token". | |
TR_FEDERATION_SETTLEMENT_HOME_TOKEN |
"" |
PEER side: the token this plane presents to the home plane's apply-usage endpoint. | |
TR_FEDERATION_SETTLEMENT_WORKSPACE_DAILY_CAP_MICRODOLLARS |
100000000 |
HOME's aggregate clamp: maximum usage one peer may apply to one workspace per UTC day. |
Rate limiting & admission (7)
| Env var | Default | Purpose (from code comment, first sentence) | Keys-file fallback |
|---|---|---|---|
TR_RATE_LIMIT_ENABLED |
True |
||
TR_RATE_LIMIT_WINDOW_SECONDS |
60 |
||
TR_RATE_LIMIT_IP_PER_WINDOW |
240 |
||
TR_RATE_LIMIT_KEY_PER_WINDOW |
1200 |
||
TR_RATE_LIMIT_INTERNAL_PER_WINDOW |
6000 |
||
TR_SETTLE_PER_KEY_INFLIGHT_LIMIT |
16 |
Settlement has a wider gate because it closes already-issued holds, but one hot key must not consume every Spanner session in an instance. | |
TR_RATE_LIMIT_CLIENT_IP_MODE |
"untrusted" |
Only front doors that overwrite X-TrustedRouter-Client-IP may opt into edge_header. |
Billing & payments (Stripe/PayPal/Adyen/Routable/x402/Veriff) (49)
| Env var | Default | Purpose (from code comment, first sentence) | Keys-file fallback |
|---|---|---|---|
TR_STRIPE_WEBHOOK_SECRET |
None |
yes | |
TR_STRIPE_SECRET_KEY |
None |
yes | |
TR_STRIPE_CARD_FEE_BASIS_POINTS |
290 |
Standard US Stripe processing schedules. | |
TR_STRIPE_CARD_FEE_FIXED_CENTS |
30 |
||
TR_STRIPE_STABLECOIN_FEE_BASIS_POINTS |
150 |
||
TR_STRIPE_STABLECOIN_FEE_FIXED_CENTS |
0 |
||
TR_STRIPE_ACH_FEE_BASIS_POINTS |
80 |
||
TR_STRIPE_ACH_FEE_FIXED_CENTS |
0 |
||
TR_STRIPE_ACH_FEE_MAX_CENTS |
500 |
||
TR_PAYPAL_CLIENT_ID |
None |
yes | |
TR_PAYPAL_CLIENT_SECRET |
None |
yes | |
TR_PAYPAL_WEBHOOK_ID |
None |
yes | |
TR_PAYPAL_API_BASE_URL |
"https://api-m.paypal.com" |
yes | |
TR_ROUTABLE_ENABLED |
False |
Creator USD cash-outs. | yes |
TR_ROUTABLE_API_TOKEN |
None |
yes | |
TR_ROUTABLE_WEBHOOK_SECRET |
None |
yes | |
TR_ROUTABLE_COMPANY_ID |
None |
yes | |
TR_ROUTABLE_TEAM_MEMBER_ID |
None |
yes | |
TR_ROUTABLE_WITHDRAW_FROM_ACCOUNT_ID |
None |
yes | |
TR_ROUTABLE_API_BASE_URL |
"https://api.routable.com" |
yes | |
TR_ADYEN_ENABLED |
False |
Adyen is staged dark until the test merchant, HMAC webhook, and end-to-end checkout canary are all green. | |
TR_ADYEN_API_KEY |
None |
yes | |
TR_ADYEN_CLIENT_KEY |
None |
yes | |
TR_ADYEN_HMAC_KEY |
None |
yes | |
TR_ADYEN_REFERENCE_KEY |
None |
yes | |
TR_ADYEN_MERCHANT_ACCOUNT |
None |
yes | |
TR_ADYEN_ENVIRONMENT |
"test" |
||
TR_ADYEN_LIVE_ENDPOINT_PREFIX |
None |
yes | |
TR_ADYEN_CHECKOUT_API_VERSION |
72 |
||
TR_ADYEN_WEB_VERSION |
"6.41.0" |
||
TR_ADYEN_CARD_FEE_BASIS_POINTS |
0 |
Keep processor pricing explicit. | |
TR_ADYEN_CARD_FEE_FIXED_CENTS |
0 |
||
TR_X402_ENABLED |
False |
||
TR_X402_ALLOW_MOCK_PAYMENTS |
False |
||
TR_X402_NETWORK |
"base" |
||
TR_X402_NETWORK_ID |
"eip155:8453" |
||
TR_X402_STRIPE_API_VERSION |
"2026-03-04.preview" |
||
TR_X402_MAX_FUND_DOLLARS |
"500" |
||
TR_X402_RATE_LIMIT_WINDOW_SECONDS |
60 |
||
TR_X402_RATE_LIMIT_KEY_PER_WINDOW |
10 |
||
TR_X402_RATE_LIMIT_WORKSPACE_PER_WINDOW |
30 |
||
TR_X402_SETTLE_RATE_LIMIT_PER_WINDOW |
30 |
||
TR_X402_SETTLE_WORKSPACE_PER_WINDOW |
120 |
||
TR_VERIFF_ENABLED |
False |
yes | |
TR_VERIFF_API_KEY |
None |
yes | |
TR_VERIFF_SHARED_SECRET_KEY |
None |
yes | |
TR_VERIFF_BASE_URL |
"https://stationapi.veriff.com" |
yes | |
TR_TRUST_STRIPE_ACCOUNT_ID |
"" |
Same account pin used by the deployed backfill and reconciliation jobs. | |
TR_LIGHTNING_FUNDING_TOKEN |
"" |
Money path: settle/outbox/async settle/fastpath (50)
| Env var | Default | Purpose (from code comment, first sentence) | Keys-file fallback |
|---|---|---|---|
TR_SPECULATION_SHADOW_WORKSPACES |
[] |
||
TR_SPECULATION_SHADOW_ROUTES |
[] |
||
TR_SPECULATION_SHADOW_IMAGES |
[] |
||
TR_SPECULATION_SHADOW_PRODUCERS |
[] |
||
TR_SPECULATION_SHADOW_PRODUCER |
"" |
||
TR_SPECULATION_SHADOW_SLOTS |
{} |
||
TR_SPECULATION_SHADOW_IMAGE_POLICY_VERSION |
0 |
||
TR_SPECULATION_SHADOW_POLICY_EXPIRES_AT |
0 |
||
TR_SPECULATION_SHADOW_PLANE |
"gcp" |
||
TR_SPECULATION_SHADOW_ISSUER |
"" |
||
TR_SPECULATION_SHADOW_AUDIENCE |
"" |
||
TR_SPECULATION_SHADOW_KID |
"" |
||
TR_SPECULATION_SHADOW_PRIVATE_KEY_FILE |
"" |
Independently provisioned mounted file. | |
TR_ASYNC_SETTLE_SHADOW_ATTACHMENT_BUCKET |
"" |
Dormant async v1 authorize metadata; | |
TR_ASYNC_SETTLE_SHADOW_POLICY |
"" |
||
TR_ASYNC_SETTLE_SHADOW_HEALTH_REGION |
"" |
||
TR_ASYNC_SETTLE_SHADOW_WORKSPACES |
"" |
||
TR_ASYNC_SETTLE_PILOT_WORKSPACES |
"" |
||
TR_ASYNC_SETTLE_ENABLED |
False |
||
TR_ASYNC_SETTLE_PROTECTION |
False |
||
TR_ASYNC_SETTLE_TICKET_KID |
"" |
||
TR_ASYNC_SETTLE_TICKET_ISSUER |
"" |
||
TR_ASYNC_SETTLE_TICKET_AUDIENCE |
"" |
||
TR_ASYNC_SETTLE_TICKET_PRIVATE_KEY_FILE |
"" |
Independently mounted purpose key. | |
TR_ASYNC_SETTLE_PILOT_CAP_MICRO |
0 |
||
TR_ASYNC_SETTLE_TICKET_TTL_SECONDS |
300 |
||
TR_ASYNC_SETTLE_AUTHORITY_EPOCH |
0 |
Operator-owned local settlement epoch, NOT the billing pause epoch or shadow grant generation. | |
TR_STAGE_D_HEARTBEAT_ENABLED |
True |
Stage D is inert until an attested enclave calls the new endpoint. | |
TR_STAGE_D_REQUIRE_HEARTBEAT_DECLARATION |
False |
Off until every accepted enclave image declares its heartbeats at boot registration. | |
TR_STAGE_D_ELIGIBILITY_ENABLED |
False |
Emergency kill added 2026-09-03: declares no request Stage D eligible. | |
TR_STAGE_D_PILOT_WORKSPACE_IDS |
"45819281-0ce9-4811-a0cd-c660ab3a116d" |
||
TR_FASTPATH_PILOT_ENABLED |
False |
The fast path's pilot (docs/design/fast-admission-pilot.md): an authorize for a workspace in the set, from a control plane in one of the … | |
TR_FASTPATH_PILOT_WORKSPACES |
"" |
||
TR_FASTPATH_PILOT_REGIONS |
"us-central1" |
||
TR_FASTPATH_FRONT_DOOR_TIMEOUT_SECONDS |
1.5 |
The front door answers an admitted authorize in a few milliseconds; | |
TR_STAGE_D_POLICY_REFRESH_SECONDS |
60 |
||
TR_STAGE_D_POLICY_CERT_IDENTITY |
"https://github.com/Lore-Hex/quill-cloud-prox… |
||
TR_STAGE_D_POLICY_OIDC_ISSUER |
"https://token.actions.githubusercontent.com" |
||
TR_SETTLE_OUTBOX_FAST_DRAIN_ENABLED |
False |
Durable settle outbox (docs/design/durable-settle-outbox.md). | |
TR_SETTLE_OUTBOX_POLL_INTERVAL_SECONDS |
300 |
||
TR_SETTLE_OUTBOX_HEALTH_PUBLISH_INTERVAL_SECONDS |
2 |
||
TR_SETTLE_OUTBOX_CLAIM_BATCH |
500 |
||
TR_SETTLE_OUTBOX_WORKER_CONCURRENCY |
1 |
||
TR_SETTLE_OUTBOX_LEASE_SECONDS |
300 |
||
TR_SETTLE_OUTBOX_PASS_BUDGET_SECONDS |
240 |
||
TR_SETTLE_OUTBOX_ENABLED |
False |
||
TR_STAGE_D_PROBE_API_KEY |
"" |
Second internal probe credential, used by the enclave rollout gate for the Stage D cohort. | |
TR_ASYNC_SETTLE_SHADOW_PROBE_API_KEY |
"" |
yes | |
TR_ASYNC_SETTLE_SHADOW_PROBE_REPETITIONS |
1 |
||
TR_ASYNC_SETTLE_SHADOW_PROBE_CADENCE_MINUTES |
4 |
Trust / attestation (24)
| Env var | Default | Purpose (from code comment, first sentence) | Keys-file fallback |
|---|---|---|---|
TR_TRUST_GCP_SOURCE_COMMIT |
None |
||
TR_TRUST_GCP_IMAGE_REFERENCE |
None |
||
TR_TRUST_GCP_IMAGE_DIGEST |
None |
||
TR_TRUST_GCP_RELEASE_URL |
"" |
Production control-plane trust aliases resolve the current gateway release from this canonical, independently published record. | |
TR_TRUST_GCP_RELEASE_FALLBACK_URLS |
"" |
Independently hosted copies keep the backup-domain trust pages live when the canonical DNS zone is unavailable. | |
TR_TRUST_AWS_SOURCE_COMMIT |
None |
Both are SETS, not scalars. | |
TR_TRUST_AWS_IMAGE_REFERENCE |
None |
||
TR_TRUST_AWS_PCR0 |
None |
||
TR_TRUST_AWS_ACCEPTED_PCR0S |
"" |
||
TR_TRUST_AZURE_SOURCE_COMMIT |
None |
||
TR_TRUST_AZURE_IMAGE_REFERENCE |
None |
||
TR_TRUST_AZURE_HOSTDATA |
None |
||
TR_TRUST_AZURE_ACCEPTED_HOSTDATA |
"" |
||
TR_TRUST_AZURE_ATTESTATION_ISSUERS |
"" |
Azure serves from more than one region and each region has its own MAA instance, so the issuer a verifier sees depends on which region an… | |
TR_TRUST_AWS_RELEASE_URL |
"" |
Where each plane's AUTHORITATIVE record lives. | |
TR_TRUST_AZURE_RELEASE_URL |
"" |
||
TR_TRUST_QUALIFYING_PROVIDERS |
"stripe,x402" |
||
TR_TRUST_TIER3_MIN_DAYS |
30 |
||
TR_TRUST_TIER3_MIN_PAID_MICRODOLLARS |
50000000 |
||
TR_TRUST_TIER_JOB_CONCURRENCY |
1 |
Workspaces the trust-tier job recomputes at once. | |
TR_TRUST_TIER_SHADOW_ENABLED |
True |
Shadow the per-workspace pass with one bulk snapshot (trust_tier_bulk.py) and log any workspace it acted on that the bulk selection missed. | |
TR_TRUST_RECONCILE_INTERVAL_SECONDS |
900 |
||
TR_TRUST_RECONCILE_MAX_AGE_SECONDS |
3600 |
||
TR_ATTESTATION_EXPECTED_PCR0 |
None |
Pin the enclave's PCR0 (EIF measurement). |
Observability (Sentry/Axiom/synthetic/remediator) (45)
| Env var | Default | Purpose (from code comment, first sentence) | Keys-file fallback |
|---|---|---|---|
TR_CLIENT_EVENTS_ENABLED |
False |
Client-observed reliability beacons remain off until the ClickHouse node runbook has been completed. | |
TR_CLIENT_EVENTS_SUCCESS_SAMPLE_RATE |
0.01 |
Sampling policy returned to SDKs for ordinary successful requests. | |
TR_CLIENT_EVENTS_PAUSE_SECONDS |
0 |
A positive value pauses SDK delivery without reading or storing a body. | |
TR_CLIENT_EVENTS_SYNTHETIC_WORKSPACE_IDS |
[] |
Workspaces whose beacons are synthetic regardless of the client bit. | |
TR_CLIENT_EVENTS_MAX_BODY_BYTES |
65536 |
Hard request and abuse-control bounds for the fire-and-forget endpoint. | |
TR_CLIENT_EVENTS_KEY_PER_MINUTE |
60 |
||
TR_CLIENT_EVENTS_WORKSPACE_PER_MINUTE |
300 |
||
TR_CLIENT_EVENTS_WRITE_CONCURRENCY |
4 |
Bounds blocking outbox writes so telemetry cannot consume the money path's pool. | |
TR_CLIENT_EVENTS_FLUSH_SECONDS |
30 |
Flush cadence returned to SDKs in the accepted policy. | |
TR_SENTRY_DSN |
None |
yes | |
TR_SENTRY_TRACES_SAMPLE_RATE |
0.05 |
||
TR_SENTRY_LOCAL_ENABLED |
False |
||
TR_AXIOM_DATASET |
"trusted-router-logs" |
Axiom log shipping. | |
TR_AXIOM_URL |
"https://api.axiom.co" |
||
TR_AXIOM_LOG_LEVEL |
"INFO" |
Levels at and above this go to Axiom. | |
TR_SENTRY_FLOODGATE_ENABLED |
True |
||
TR_SENTRY_FLOODGATE_WINDOW_SECONDS |
3600 |
||
TR_SENTRY_FLOODGATE_MAX_EVENTS_PER_FINGERPRINT |
3 |
||
TR_SENTRY_FLOODGATE_MAX_EVENTS_PER_WINDOW |
50 |
||
TR_SENTRY_FLOODGATE_MAX_FINGERPRINTS |
2048 |
||
TR_SYNTHETIC_MONITOR_REGION |
None |
||
TR_SYNTHETIC_MONITOR_API_KEY |
None |
yes | |
TR_SYNTHETIC_MONITOR_MODEL |
"trustedrouter/monitor" |
||
TR_SYNTHETIC_CONTROL_PLANE_BASE_URL |
None |
Exact HTTPS control-plane origin synthetic canaries exercise. | |
TR_SYNTHETIC_CANONICAL_ATTESTED |
False |
The canonical target serves a self-signed cert minted inside the TEE (AWS Nitro standalone deployments): probes skip CA verification and … | |
TR_SYNTHETIC_SCHEDULER_INTERVAL_SECONDS |
0 |
0 = disabled (the default, so GCP/AWS keep their existing schedulers and nothing double-runs). | |
TR_SYNTHETIC_SCHEDULER_ROTATION_COUNT |
8 |
Completions per pass. | |
TR_SYNTHETIC_REGIONAL_PROBES_ENABLED |
True |
Per-region alias/Cloud-Run probing is a GCP-topology concept: it derives api-{region}.quillrouter.com aliases and *.run.app direct URLs f… | |
TR_SYNTHETIC_IMAGE_PROBE_ENABLED |
True |
Image generation is sampled by a SEPARATE scheduled job (-m trusted_router.synthetic.image_generation), not by the 1-minute synthetic run. | |
TR_SYNTHETIC_STATUS_PROBE_TYPES |
"" |
Public readers describe the monitor plane, not their own credentials. | |
TR_SYNTHETIC_CONTROL_PLANE_HEALTH_URL |
None |
Explicit control-plane /health URL attached to the canonical target. | |
TR_SYNTHETIC_GATEWAY_REGION_TARGETS |
"" |
Empty = today's behaviour, exactly one canonical target. | |
TR_SYNTHETIC_MONITOR_TIMEOUT_SECONDS |
20.0 |
Per-probe HTTP timeout for real provider-effective synthetic checks. | |
TR_SYNTHETIC_RUN_DEADLINE_SECONDS |
240.0 |
Hard wall-clock ceiling for one admitted synthetic run. | |
TR_SYNTHETIC_REMEDIATOR_DEADLINE_SECONDS |
15.0 |
Remediation is an observe-only monitor read running through an internal HTTP surface during the service split. | |
TR_SYNTHETIC_MONITOR_MONTHLY_GRANT_DOLLARS |
200.0 |
Monthly self-funding for the monitor workspace, applied lazily on its own gateway-authorize path (synthetic/funding.py). | |
TR_SYNTHETIC_STATUS_SAMPLE_LIMIT |
5000 |
||
TR_SYNTHETIC_STATUS_RAW_RETENTION_DAYS |
14 |
||
TR_SYNTHETIC_STATUS_ROLLUP_RETENTION_MONTHS |
24 |
||
TR_SYNTHETIC_STATUS_US_URL |
"https://status-us.trustedrouter.com/status.j… |
||
TR_SYNTHETIC_STATUS_EU_URL |
"https://status-eu.trustedrouter.com/status.j… |
||
TR_REMEDIATOR_MODE |
"observe" |
Fleet peer list ("name=base_url,..."): every deployment watches every deployment's public status page — its own included, which proves th… | |
TR_REMEDIATOR_INTERVAL_SECONDS |
120 |
||
TR_REMEDIATOR_IN_PROCESS_ENABLED |
True |
Cloud Run request-based CPU may pause background coroutines between requests. | |
TR_SYNTHETIC_FLEET_PEERS |
"gcp=https://trustedrouter.com,aws=https://aw… |
Notifications & email (SES/Twilio/Telnyx/ops chat) (33)
| Env var | Default | Purpose (from code comment, first sentence) | Keys-file fallback |
|---|---|---|---|
TR_OPS_CHAT_WEBHOOK_URLS |
"" |
Optional three-cloud Matrix support fanout. | |
TR_OPS_CHAT_WEBHOOK_SECRET |
None |
||
TR_OPS_CHAT_WEBHOOK_TIMEOUT_SECONDS |
3.0 |
||
TR_AWS_ACCESS_KEY_ID |
None |
Amazon SES — used for optional wallet email attach/verification. | yes |
TR_AWS_SECRET_ACCESS_KEY |
None |
yes | |
TR_AWS_REGION |
"us-east-1" |
yes | |
TR_SES_FROM_EMAIL |
None |
yes | |
TR_SES_FROM_NAME |
"TrustedRouter" |
yes | |
TR_SES_CONFIGURATION_SET |
"trustedrouter-default" |
Configuration set used on every SendEmail call so SES emits bounce + complaint events to our SNS topic (subscribed at /internal/ses/notif… | |
TR_SES_ALERT_FROM_EMAIL |
"alerts@alerts.trustedrouter.com" |
Operational alerts use a dedicated authenticated subdomain and configuration set. | yes |
TR_SES_ALERT_FROM_NAME |
"TrustedRouter Alerts" |
yes | |
TR_SES_ALERT_CONFIGURATION_SET |
"trustedrouter-alerts" |
yes | |
TR_SES_NOTICES_FROM_EMAIL |
"notices@notices.trustedrouter.com" |
Customer product notices (route retirements) send from their own authenticated subdomain and configuration set, separate from sign-in mai… | yes |
TR_SES_NOTICES_FROM_NAME |
"TrustedRouter" |
yes | |
TR_SES_NOTICES_CONFIGURATION_SET |
"trustedrouter-notices" |
yes | |
TR_NOTIFY_ENABLED |
False |
---- notifications (email / sms / voice to the account owner) ---------- Delivery credentials. | |
TR_NOTIFY_SMS_AVAILABLE |
False |
Flip to True once A2P 10DLC is approved on the primary SMS carrier; | |
TR_TELNYX_API_KEY |
None |
||
TR_TELNYX_FROM_NUMBER |
None |
||
TR_NOTIFY_VOICE_REPEAT_UNANSWERED |
True |
The ORGANIZATION id from Telnyx /v2/whoami — not the number's connection id and not the TeXML application id, both of which answer 404 here. | |
TR_NOTIFY_SMS_PRIMARY_CARRIER |
"twilio" |
||
TR_NOTIFY_VOICE_PRIMARY_CARRIER |
"telnyx" |
||
TR_TELNYX_TEXML_ACCOUNT_ID |
None |
||
TR_TELNYX_TEXML_APPLICATION_ID |
None |
The TeXML application, which is where the outbound voice profile hangs. | |
TR_TWILIO_ACCOUNT_SID |
None |
||
TR_TWILIO_AUTH_TOKEN |
None |
||
TR_TWILIO_API_KEY_SID |
None |
||
TR_TWILIO_API_KEY_SECRET |
None |
||
TR_TWILIO_FROM_NUMBER |
None |
||
TR_NOTIFY_PRICE_MICRODOLLARS |
20000 |
$0.02 is positive on every path, thinnest on a Twilio VOICE fallback (~$0.006 margin on a one-minute minimum). | |
TR_NOTIFY_PUSH_PRICE_MICRODOLLARS |
0 |
It is also the only channel with no carrier and no regulator in the path, which makes it the one we can offer on day one without 10DLC or… | |
TR_NOTIFY_MAX_PER_HOUR |
30 |
Per-workspace ceilings. | |
TR_NOTIFY_MAX_VOICE_PER_HOUR |
4 |
Legal entity metadata (14)
| Env var | Default | Purpose (from code comment, first sentence) | Keys-file fallback |
|---|---|---|---|
TR_LEGAL_ENTITY_NAME |
"Lore Hex Corp" |
||
TR_LEGAL_ENTITY_TYPE |
"Delaware C Corporation" |
||
TR_LEGAL_ENTITY_ADDRESS |
"1111 Brickell Ave, Floor 10, Miami, FL 33131" |
||
TR_LEGAL_ENTITY_STREET |
"1111 Brickell Ave, Floor 10" |
Structured components of the same address. | |
TR_LEGAL_ENTITY_CITY |
"Miami" |
||
TR_LEGAL_ENTITY_REGION |
"FL" |
||
TR_LEGAL_ENTITY_POSTAL_CODE |
"33131" |
||
TR_LEGAL_ENTITY_COUNTRY |
"US" |
||
TR_LEGAL_ENTITY_PHONE |
"+1-305-239-7350" |
||
TR_LEGAL_ENTITY_EIN |
"41-5339728" |
||
TR_LEGAL_ENTITY_DUNS |
"144992055" |
||
TR_LEGAL_ENTITY_DATE_ESTABLISHED |
None |
||
TR_LEGAL_SIGNATORY_NAME |
"Joseph Perla" |
||
TR_LEGAL_SIGNATORY_TITLE |
"CEO" |
Other (21)
| Env var | Default | Purpose (from code comment, first sentence) | Keys-file fallback |
|---|---|---|---|
TR_ANALYTICS_OUTBOX_ENABLED |
False |
Stage 1 live analytics outbox. | |
TR_NEW_SIGNUPS_ENABLED |
True |
Global, creation-only operational brake. | |
TR_EMAIL_SIGNUP_ENABLED |
False |
Plain-email POST /v1/signup (no OAuth) is a credit-farming vector: an open endpoint that mints a management key + trial credit for ANY … |
|
TR_ENABLE_SENTRY_TEST_ROUTE |
False |
||
TR_GATEWAY_AUTHORIZE_MAX_IN_FLIGHT_PER_KEY |
4 |
A contended Spanner authorize can occupy a worker for the full RPC budget. | |
TR_OBSERVER_INTERNAL_TOKEN |
None |
Dedicated to the externally reachable observer/status processes. | |
TR_CHECKOUT_CARD_FEE_MINIMUM_CENTS |
80 |
Card-style rails have a purchase-fee floor. | |
TR_AUTH_SESSION_TTL_SECONDS |
2592000 |
||
TR_OAUTH_AUTHORIZATION_CODE_TTL_SECONDS |
600 |
||
TR_PARTNER_INQUIRY_EMAIL |
None |
Destination for TrustedOS partner-inquiry form submissions (/trustedos). | |
TR_ACTIVATION_REMINDER_INTERVAL_SECONDS |
0 |
Durable post-signup activation reminders. | |
TR_MULTI_REGION_ENABLED |
True |
||
TR_PHONE_VERIFICATION_REQUIRES_FUNDING |
None |
||
TR_CUSTOM_MODELS_REQUIRE_VERIFICATION |
None |
||
TR_IDENTITY_SESSION_STALE_AFTER_DAYS |
7 |
||
TR_HEARTBEAT_GRACE_SECONDS |
300 |
||
TR_REQUEST_RECORD_WRITE_MODE |
"legacy" |
Expand/contract switch for per-request Spanner records. | |
TR_BROADCAST_INLINE_DRAIN_ENABLED |
False |
Local/test drains broadcast jobs opportunistically after settlement so tests and demos are deterministic. | |
TR_MARKETING_REGIONS |
"us-central1,europe-west4,us-east4,us-west1,a… |
||
TR_EXTERNAL_LIVE_REGIONS |
"aws-eu-west-1,aws-eu-west-3,azure-australiae… |
Non-GCP deployments that have PASSED their own end-to-end smoke (verify_deployment.sh --expect-monitor). | |
TR_AUTO_MODEL_ORDER |
"" |
IDs follow OpenRouter naming exactly to line up with the ingest snapshot — moonshotai/... not kimi/..., mistralai/... not `mistral/… |
8. Troubleshooting (real issues hit during verification)
| # | Symptom | Cause | Fix |
|---|---|---|---|
| T1 | failed to connect to the docker API at unix:///Users/…/.docker/run/docker.sock … no such file or directory |
Docker CLI installed but the daemon was not running | Start Docker Desktop, OrbStack or colima. You only need it for the postgres/spanner conformance backends (commands in AGENTS.md, ports 5433/5434, TR_CONFORMANCE_*_DSN). Not verified on this Mac. |
| T2 | POST /v1/chat/completions → 402 Insufficient credits on a fresh local workspace |
Header-auth users get no trial credit, and the memory store starts at $0 |
Credit yourself with the unsigned local Stripe webhook in §4c |
| T3 | 400 model_not_supported: cerebras/llama3.1-8b |
The catalog moved on, and old examples (README smoke script) still use this model | Use trustedrouter/auto or pick an ID from /v1/models |
| T4 | smoke_e2e.py: expected 54 OpenRouter route classifications, got 95 |
The script's hard-coded expectations are stale | Use the §4 curl flow. Fixing the script is a good first PR. |
| T5 | Provider keys "not found" even though TR_LOCAL_KEYS_FILE points at your file / you copied .env.example |
.env.example hard-codes Joseph's path. The Settings fallback always reads the default path anyway (§6 gotcha). |
Put the file at ~/.quill_cloud_keys.private, or export the provider keys in the shell |
| T6 | Changing a TR_* var "does nothing" |
Typo (extra="ignore"), or the var is not a Settings field (e.g. TR_BIGTABLE_* in .env.example), or the shell env is overriding .env |
uv run python -c "from trusted_router.config import get_settings as g; print(g().<field>)" |
| T7 | App refuses to start: TR_SERVICE_SURFACE=combined is restricted to local/test… |
You set TR_ENVIRONMENT to something other than local/test |
Keep local for dev. Deployed surfaces are configured only in rollout.sh. |
| T8 | /healthz 404 |
The route doesn't exist | Use /health or /v1/health |
| T9 | Local bursts get 429 | TR_RATE_LIMIT_ENABLED=true by default (240 req/min per IP) |
TR_RATE_LIMIT_ENABLED=false for load or browser testing |
| T10 | Your keys file is ignored in tests | By design: _running_under_pytest() skips it |
TR_ALLOW_LOCAL_KEY_FILE_IN_TESTS=1 (rarely what you want) |
| T11 | CI frontend job fails on git diff --exit-code |
You edited frontend/src/*.ts without committing the rebuilt JS |
npx tsc, then commit src/trusted_router/static/*.js |
| T12 | Lots of on_event is deprecated warnings in pytest |
FastAPI lifespan deprecation | Harmless |
| T13 | A test fails on your branch but passes on main |
main moves fast (an hourly price-refresh bot commits to it) |
Rebase first (AGENTS.md) |
9. Safety reminders for local work
- Local dev needs zero cloud credentials. If something asks for gcloud auth, stop and check
TR_STORAGE_BACKEND. - Never point
TR_SPANNER_*/TR_POSTGRES_DSNat production. AGENTS.md: unindexedtr_entities.bodysearches caused the 2026-09-06 CPU incident. Usepython -m scripts.inspect_payment_ownerfor Stripe attribution and ClickHouse for analytics. - Pushing to
mainauto-deploys the control plane whensrc/**,scripts/deploy/**,frontend/src/**,Dockerfile,pyproject.tomloruv.lockchange. - Money code (
reserve/settle/refund) changes need a differential or conformance test, never review-by-eye.
10. Verification log (raw timings)
| Step | Command | Outcome | Wall time |
|---|---|---|---|
| sync | uv sync |
✅ 238 resolved | 9.8 s (warm cache) |
| lint | uv run ruff check . |
✅ | 0.55 s |
| types | uv run mypy |
✅ 533 files | 9.6 s warm / 8.3 s cold cache |
| conformance subset | pytest tests/conformance/{store_semantics,key_management} -k memory |
✅ 90 passed / 14 skipped | 10.2 s |
| unit subset | pytest tests/test_billing.py tests/test_inference_errors.py |
✅ 82 passed | 10.4 s |
| collect | pytest --collect-only -q |
✅ 38,221 selected | 31.6 s |
| catalog check | scripts/update_model_changes.py --check |
✅ exit 0 | — |
| server | uvicorn … --port 18080 → health, models, keys, webhook credit, chat, stream, activity |
✅ | about 1 s startup |
| smoke script | scripts/smoke_e2e.py |
⚠️ stale assertion (54 vs 95) | — |
| npm | npm ci, lint, lint:css, typecheck, npx tsc (no diff) |
✅ | < 1 s each |
| playwright | npx playwright install chromium + homepage.spec.js |
✅ 9 passed | 12 s + 5.7 s |
| docker conformance | postgres / pgadapter | ⚠️ not run (daemon down) | — |