Infra & deploys
CI/CD, rollout.sh, Terraform, clouds, observability, runbooks.
Source repo: quill-router (TrustedRouter control plane) at main @ 778120494, read on 2026-10-19.
Every claim cites path:line relative to the repo root. Nothing here was checked against live
production. Where docs and code disagree, both are noted.
Trust boundary in one sentence. This repo deploys the control plane (billing, keys, routing metadata, website/console). The attested prompt path (Confidential Space / Nitro / SEV-SNP enclaves) is deployed from the separate
quill-cloud-proxyrepo (.github/workflows/deploy.yml:1-7). If a control-plane deploy goes wrong, the site goes down or keys stop working. Prompt confidentiality is not affected.
1. Deployment topology
flowchart TB
user["Clients / SDKs / browsers"]
subgraph CF["Cloudflare + Google Cloud DNS (both authoritative)"]
dns["DNS for trustedrouter.com; DNS-only LB for gateway regions (no orange-cloud on prompt path)"]
end
subgraph GCP["GCP project quill-cloud-proxy (primary cloud)"]
armor["Cloud Armor policies: legacy_edge, public_edge, internal_edge, lightning_funding"]
glb["Global HTTPS LB: trusted-router-control-map, backend trusted-router-control-backend"]
subgraph CR["Cloud Run service trusted-router (+ trusted-router-public, trusted-router-internal)"]
cr1["us-central1 PRIMARY, min 8"]
cr2["us-east4, min 8"]
cr3["europe-west4, min 8"]
cr4["southamerica-east1, min 2, gateway billing failover"]
end
cs["Confidential Space attested gateways: api.trustedrouter.com; us-central1, us-east4, europe-west4, us-west1"]
spanner[("Spanner trusted-router-nam6, ENTERPRISE_PLUS, autoscale 1000-10000 PU, 45 pct CPU target")]
ch[("ClickHouse tr-clickhouse-1..3, us-central1-a/b/c, 3 Keeper voters")]
fp["Fastpath Go nodes x2 us-central1 + Pub/Sub settle log (dark install)"]
jobs["Cloud Run Jobs: synthetic monitor, trust reconciler/tier jobs"]
gcs[("GCS: deploy mutex journal, tfstate, fastpath releases")]
end
subgraph AWS["AWS account 330422590279 (EU, standalone)"]
ga["Global Accelerator tr-eu-control-plane to NLB"]
ecs["ECS Fargate tr-cp-euw1 / tr-cp-euw3"]
nitro["Nitro enclaves quill-enclave-asg: eu-west-1 x2, eu-west-3 x2; api-aws.trustedrouter.com"]
dsql[("Aurora DSQL")]
awsch[("ClickHouse eu-west-3 + eu-north-1")]
apprun["App Runner tr-eu: synthetic probes via EventBridge 1-min rule"]
end
subgraph AZ["Azure (standalone)"]
aca["Container App control plane, uaenorth"]
snp["SEV-SNP enclaves uaenorth + australiaeast; api-azure.trustedrouter.com"]
pg[("Postgres Flexible Server")]
azch[("ClickHouse")]
end
user --> dns
dns --> glb
dns --> cs
dns --> ga
dns --> aca
dns --> snp
armor --- glb
glb --> cr1
glb --> cr2
glb --> cr3
glb --> cr4
cs -->|"/internal/gateway authorize + settle"| glb
cr1 --> spanner
cr2 --> spanner
cr3 --> spanner
cr4 --> spanner
cr1 -->|"outbox to ingester"| ch
fp --> spanner
ga --> ecs
ecs --> dsql
ecs --> awsch
nitro --> ecs
aca --> pg
aca --> azch
snp --> acaDiagram source
flowchart TB
user["Clients / SDKs / browsers"]
subgraph CF["Cloudflare + Google Cloud DNS (both authoritative)"]
dns["DNS for trustedrouter.com; DNS-only LB for gateway regions (no orange-cloud on prompt path)"]
end
subgraph GCP["GCP project quill-cloud-proxy (primary cloud)"]
armor["Cloud Armor policies: legacy_edge, public_edge, internal_edge, lightning_funding"]
glb["Global HTTPS LB: trusted-router-control-map, backend trusted-router-control-backend"]
subgraph CR["Cloud Run service trusted-router (+ trusted-router-public, trusted-router-internal)"]
cr1["us-central1 PRIMARY, min 8"]
cr2["us-east4, min 8"]
cr3["europe-west4, min 8"]
cr4["southamerica-east1, min 2, gateway billing failover"]
end
cs["Confidential Space attested gateways: api.trustedrouter.com; us-central1, us-east4, europe-west4, us-west1"]
spanner[("Spanner trusted-router-nam6, ENTERPRISE_PLUS, autoscale 1000-10000 PU, 45 pct CPU target")]
ch[("ClickHouse tr-clickhouse-1..3, us-central1-a/b/c, 3 Keeper voters")]
fp["Fastpath Go nodes x2 us-central1 + Pub/Sub settle log (dark install)"]
jobs["Cloud Run Jobs: synthetic monitor, trust reconciler/tier jobs"]
gcs[("GCS: deploy mutex journal, tfstate, fastpath releases")]
end
subgraph AWS["AWS account 330422590279 (EU, standalone)"]
ga["Global Accelerator tr-eu-control-plane to NLB"]
ecs["ECS Fargate tr-cp-euw1 / tr-cp-euw3"]
nitro["Nitro enclaves quill-enclave-asg: eu-west-1 x2, eu-west-3 x2; api-aws.trustedrouter.com"]
dsql[("Aurora DSQL")]
awsch[("ClickHouse eu-west-3 + eu-north-1")]
apprun["App Runner tr-eu: synthetic probes via EventBridge 1-min rule"]
end
subgraph AZ["Azure (standalone)"]
aca["Container App control plane, uaenorth"]
snp["SEV-SNP enclaves uaenorth + australiaeast; api-azure.trustedrouter.com"]
pg[("Postgres Flexible Server")]
azch[("ClickHouse")]
end
user --> dns
dns --> glb
dns --> cs
dns --> ga
dns --> aca
dns --> snp
armor --- glb
glb --> cr1
glb --> cr2
glb --> cr3
glb --> cr4
cs -->|"/internal/gateway authorize + settle"| glb
cr1 --> spanner
cr2 --> spanner
cr3 --> spanner
cr4 --> spanner
cr1 -->|"outbox to ingester"| ch
fp --> spanner
ga --> ecs
ecs --> dsql
ecs --> awsch
nitro --> ecs
aca --> pg
aca --> azch
snp --> acaFacts behind the diagram:
| Fact | Source |
|---|---|
GCP project quill-cloud-proxy, primary region us-central1, service trusted-router |
scripts/deploy/_lib.sh:8, :22, :102 |
Control-plane regions us-central1,us-east4,europe-west4,southamerica-east1; warm set excludes São Paulo |
scripts/deploy/_lib.sh:30, :37 |
| Min instances 8/8/8/2; concurrency 8; max 12 per revision (sized against the 45% Spanner CPU alarm); memory 2Gi | scripts/deploy/_lib.sh:46-63, :88 |
Ingress internal-and-cloud-load-balancing, so run.app cannot bypass Cloud Armor |
scripts/deploy/_lib.sh:93 |
Enclave regions us-central1, us-east4, europe-west4, us-west1; us-west1 is gateway-only with no Cloud Run |
src/trusted_router/enclave_regions.py:7, :19; scripts/deploy/_lib.sh:10-21 |
LB backend trusted-router-control-backend, Serverless NEG per region attached by rollout.sh |
scripts/deploy/rollout.sh:1-5, :1036-1110 |
Spanner trusted-router-nam6, nam6, ENTERPRISE_PLUS, autoscaler 1000-10000 PU, 45% HP CPU target, prevent_destroy |
infra/spanner_trusted_router.tf:10-38 |
| Cloud Armor policies, which tr-deploy can attach but cannot edit | infra/cloud_armor.tf:1-25, :30, :161, :229, :285 |
| Each cloud is a standalone deployment. Identity federates. Credits, keys and analytics stay in their own cloud. | docs/storage-portability/multi-cloud-separation.md §1 |
| GCP/AWS/Azure gateways, TEEs and control planes; AWS Fargate behind Global Accelerator | docs/storage-portability/HANDOFF.md:66-77 |
AWS "what runs where" (enclave ASG, ECS, App Runner tr-eu runs synthetics) |
docs/storage-portability/HANDOFF.md:103-123 |
| ClickHouse GCP cluster: 3 nodes, us-central1-a/b/c | docs/storage-portability/HANDOFF.md:33, :175-189 |
| AWS ClickHouse eu-west-3 (Paris) + eu-north-1 (Stockholm) | scripts/deploy/aws_eu_clickhouse.sh:28, scripts/deploy/aws_eu_north_clickhouse.sh:53 |
| Azure: Container Apps in uaenorth on Postgres Flexible Server; enclaves uaenorth + australiaeast | scripts/deploy/azure_control_plane.sh:2, :66, :111 |
Control-plane URLs per cloud: trustedrouter.com / aws. / azure. |
scripts/deploy/cloud_rollout.py:36-40 |
| Cloudflare and Google Cloud DNS are both authoritative; region drain goes through Cloudflare DNS-only LB with no proxying on the prompt path | docs/runbook.md:112-125, :1324-1370 |
| Fastpath: 2 nodes in us-central1, no external IP, empty workspace allow-list | infra/fastpath.tf:1-24, :231 |
2. Push-to-main to production
flowchart TD
push["git push / merge to main"]
push --> ci["ci.yml: lint, mypy, spanner-emulator, frontend, 12+8 test shards, coverage 70 pct, proof-oracle, browser, fastpath Go, TLA+ proofs"]
push --> filt{"deploy.yml path filter: src, scripts/deploy, scripts/entrypoint.sh, frontend/src, Dockerfile, .gcloudignore, pyproject, uv.lock, deploy.yml"}
filt -->|"no match: docs-only"| nodeploy["No deploy"]
filt -->|"match"| gate["gate-on-ci: poll ci.yml for SAME SHA until success (hotfix skips)"]
push --> build["build-image: Cloud Build to Artifact Registry, tag = SHA"]
ci -->|"success on main"| fpr["fastpath-release.yml: build Go binaries, upload to GCS"]
gate --> admit["admit-cloud: acquire GCS deploy mutex (two-cloud journal)"]
build --> admit
admit --> mig["migrate-schema: idempotent Spanner DDL + analytics snapshot sync"]
admit --> sec["sync-runtime-secrets"]
mig --> cur["confirm-current-main: skip if a newer main exists"]
sec --> cur
cur --> warm["deploy: rollout.sh --no-traffic in all 4 regions in parallel (primary alone reconciles LB)"]
warm --> hold{"TR_DEPLOY_HOLD_REGIONS?"}
hold -->|"held"| skipr["traffic untouched in held region"]
hold -->|"not held"| ramp["staged_traffic.sh us-central1: 10 to 50 to 100 pct with watchdog"]
ramp --> canary["3-min canary watch us-central1"]
canary -->|"trip"| rb["auto rollback us-central1 + abort"]
canary --> bill["billing-path 5xx gate (assert_no_billing_5xx.sh)"]
bill -->|"5xx"| rb
bill --> sec2["rollout-secondaries: ramp europe-west4, us-east4, southamerica-east1 serially under the same mutex fence"]
sec2 --> jobs["deploy synthetic monitor job, opt-in trust jobs, Data Manager check"]
jobs --> smoke["smoke test prod incl. /trust image_digest + source_commit"]
smoke --> pub["public-surface-companion: deploy + smoke T1 public service"]
smoke --> vcc["verify-cloud-complete: verify_gcp_complete.sh"]
pub --> fin["finalize-cloud: release mutex"]
vcc --> fin
fin --> post["workflow_run fan-out: prod-smoke, trust-drift, reconcile-cloud-releases (AWS/Azure catch-up)"]Diagram source
flowchart TD
push["git push / merge to main"]
push --> ci["ci.yml: lint, mypy, spanner-emulator, frontend, 12+8 test shards, coverage 70 pct, proof-oracle, browser, fastpath Go, TLA+ proofs"]
push --> filt{"deploy.yml path filter: src, scripts/deploy, scripts/entrypoint.sh, frontend/src, Dockerfile, .gcloudignore, pyproject, uv.lock, deploy.yml"}
filt -->|"no match: docs-only"| nodeploy["No deploy"]
filt -->|"match"| gate["gate-on-ci: poll ci.yml for SAME SHA until success (hotfix skips)"]
push --> build["build-image: Cloud Build to Artifact Registry, tag = SHA"]
ci -->|"success on main"| fpr["fastpath-release.yml: build Go binaries, upload to GCS"]
gate --> admit["admit-cloud: acquire GCS deploy mutex (two-cloud journal)"]
build --> admit
admit --> mig["migrate-schema: idempotent Spanner DDL + analytics snapshot sync"]
admit --> sec["sync-runtime-secrets"]
mig --> cur["confirm-current-main: skip if a newer main exists"]
sec --> cur
cur --> warm["deploy: rollout.sh --no-traffic in all 4 regions in parallel (primary alone reconciles LB)"]
warm --> hold{"TR_DEPLOY_HOLD_REGIONS?"}
hold -->|"held"| skipr["traffic untouched in held region"]
hold -->|"not held"| ramp["staged_traffic.sh us-central1: 10 to 50 to 100 pct with watchdog"]
ramp --> canary["3-min canary watch us-central1"]
canary -->|"trip"| rb["auto rollback us-central1 + abort"]
canary --> bill["billing-path 5xx gate (assert_no_billing_5xx.sh)"]
bill -->|"5xx"| rb
bill --> sec2["rollout-secondaries: ramp europe-west4, us-east4, southamerica-east1 serially under the same mutex fence"]
sec2 --> jobs["deploy synthetic monitor job, opt-in trust jobs, Data Manager check"]
jobs --> smoke["smoke test prod incl. /trust image_digest + source_commit"]
smoke --> pub["public-surface-companion: deploy + smoke T1 public service"]
smoke --> vcc["verify-cloud-complete: verify_gcp_complete.sh"]
pub --> fin["finalize-cloud: release mutex"]
vcc --> fin
fin --> post["workflow_run fan-out: prod-smoke, trust-drift, reconcile-cloud-releases (AWS/Azure catch-up)"]Step-by-step citations (all .github/workflows/deploy.yml unless noted):
- Trigger and path filter:
:26-37. Manualworkflow_dispatchwithhotfix: trueskips the CI gate and the watchdog baseline (:38-43,:73,:516-524). - Concurrency: group
deploy-trusted-router,cancel-in-progress: false. One deploy runs at a time, with one pending slot that a newer push can replace. The GCS mutex is what actually serializes traffic changes (:45-49). - gate-on-ci polls ci.yml's conclusion for the same SHA (
:57-107). ci.yml and deploy.yml start in parallel on push, so without this gate a red commit could roll out everywhere (:17-21). - build-image: Cloud Build pushes
${REGION}-docker.pkg.dev/${PROJECT_ID}/${REPO}/${SERVICE}:${SHA}and uses a registrybuildcachetag for layer caching (:109-208). Auth uses WIF totr-deploy@quill-cloud-proxy, with no JSON keys (:9-12,:133). - admit-cloud takes the production mutex with
deploy_mutex.sh acquire(:209-240). The wait is long because a quill-cloud-proxy gateway rollout can hold the lease for about 2h20m (:213-216). - migrate-schema runs idempotent
migrate_*.shsteps (money primitives, retention, receipts, outboxes, async-settle admission, fastpath tables) and syncs analytics snapshots (:242-368). - sync-runtime-secrets validates SES credentials and pushes the ClickHouse reader credential (
:370-407). - confirm-current-main stops a stale push from deploying over a newer main (
:409-448). - deploy:
- Re-asserts the mutex (
:500-501) and captures pre-deploy revisions for rollback (:526). - Warms all 4 regions with no traffic in parallel (:544-600). Only the primary process may mutate the global LB (TR_DEPLOY_RECONCILE_LB,:594-597). - Resolves the primary baseline and honours holds (:657-712). - Staged 10/50/100 ramp in us-central1 (:713-723), then a 3-min canary (:734-742) with automatic rollback (:744-764). - Billing-path 5xx gate (:766-788). - Never-ramped regions have their staged-probe tags swept on failure, because a tagged revision keeps its minimum instances, which costs money (:882-901). - rollout-secondaries:
- Imports the same mutex fence (
:918-937) and ramps the secondaries serially (:958-970). The order is europe-west4, us-east4, southamerica-east1 (scripts/deploy/ramp_secondaries.sh:237). - Then deploys the synthetic monitor Cloud Run Job (
:972), the opt-in trust jobs (:994) and the Google Data Manager guard (:1005). - Smoke-tests prod (
:1026-1183), including asserting thatsource_commit,image_referenceandimage_digestappear in the trust records (:1160-1170).
- Imports the same mutex fence (
- public-surface-companion deploys the isolated T1 public service and runs outside the mutex (
:1262-1338). - verify-cloud-complete runs
bash scripts/deploy/verify_gcp_complete.shwithif: always()(:1340-1399). The long comment at:1340-1380records why an echo-only stub is unacceptable. - finalize-cloud releases the mutex and records success or failure (
:1401-1428).
The runbook's definition of done: "deployed means the primary is live" (all four regions warm, plus us-central1 ramp and canary, about 9 min). Full convergence is the later rollout-secondaries + verify-cloud-complete (docs/runbook.md:395-410). AGENTS.md:56-58 adds that a queued or partially completed rollout must never be called deployed.
3. GitHub Actions workflows
| Workflow | Trigger | What it does |
|---|---|---|
ci.yml |
push/PR to main, dispatch (:3-11) |
The release gate. Jobs: lint (ruff check, mypy, model-changes check, :18-32); spanner-emulator (GoogleSQL conformance, prod SQL acceptance, schema drift, :34-100); frontend (TS build matches committed JS, ESLint, Stylelint, :102-118); production-import (--no-dev import of trusted_router.main, :120-130); test, 12 shards via pytest-split with -n 4 and --dist loadgroup, 25-min cap (:132-284, shards :180); coverage, which combines shards and enforces ≥70% once (:286-332); test-post-cutover, 8 shards with the lifecycle clock pinned past cutovers (:348-415); proof-oracle, the frozen-main differential, 6 shards × 2 clocks, no coverage (:419-457); browser (Playwright smoke, :459-503); byok-format-ordering (:505-542); fastpath, fastpath-spanner, fastpath-walks (Go 1.24 module and TLC, :544-670); proofs (./proofs/check.sh, :672-684). Split rationale: wall time becomes the slowest job, and deploy gates on the whole run (:13-16). |
deploy.yml |
push to main with path filter; dispatch hotfix (:26-43) |
GCP Cloud Run control-plane deploy (section 2). |
prod-smoke.yml |
workflow_run after deploy, cron */30, dispatch (:1-14) |
pytest tests/test_smoke_prod.py against trustedrouter.com. Never fails PRs. Uses TR_STRIPE_WEBHOOK_SECRET for the webhook drift guard (:17-45). |
trust-drift.yml |
daily 07:23 cron, workflow_run after deploy, dispatch (:3-38) |
scripts/verify_trust_measurements.py --strict compares published enclave measurements with live attestations (:108-112). Was hourly and changed to daily, because drift only comes from deploys or enclave rebuilds (:4-22). |
refresh-prices.yml |
hourly at :37, dispatch (:20-27) |
The price bot. Pulls provider keys from Secret Manager, refreshes prices with LLM "self-heal" for broken parsers, sanity-checks price spikes, coverage audit, routing evidence (:74-332). Pushes a temp branch, dispatches ci.yml on it (GITHUB_TOKEN pushes don't trigger workflows), then advances protected main with the checked SHA (:346-420). Runs one at a time (about 30-45 min). This is why main moves hourly (AGENTS.md:47-49). |
fastpath-release.yml |
workflow_run of CI success on main (:7-11) |
Builds fastpath, fastpathctl, loadgen, fastpathwatch statically, writes SHA256SUMS, and uploads to gs://tr-fastpath-releases-quill-cloud-proxy/fastpath/<sha>/. Nodes pick up the release pinned in infra/fastpath.tf:19. |
deploy-aws-control-plane.yml |
dispatch only; inputs release_sha, mode (deploy/drain-install), promotion_only, bake_override_reason (:1-25) |
Rolls ECS one region at a time via aws_ecs_control_plane.sh (:108-114). That script requires CI green for the exact SHA, the right AWS account and a clean checkout (scripts/deploy/aws_ecs_control_plane.sh:44-61), and rolls a failed region back to its previous task definition (:75-81). |
deploy-azure-control-plane.yml |
dispatch only; release_sha, bake_override_reason, promotion_only (:1-16) |
Requires green CI, then Azure OIDC + GCP WIF, then azure_control_plane.sh (:50-138). |
reconcile-cloud-releases.yml |
after deploy, cron :07/:22/:37/:52, dispatch | scripts.deploy.reconcile_cloud_releases dispatches stale AWS/Azure planes to the last fully verified GCP release (scripts/deploy/reconcile_cloud_releases.py:2). |
cloud-staleness.yml |
daily 06:17, dispatch (max_age_hours=72) |
Flags any plane whose serving release is stale. |
infra-apply.yml |
PR/push touching infra/**, dispatch (terraform/status/configure-retention/recover × gcp/aws/azure) |
terraform fmt/validate on PR. On merge it plans and applies the whole root with no plan gate (infra/README.md:38-46). It also has a mode that inspects or reconciles the deploy journal (:111). |
gateway-reliability.yml |
dispatch with APPLY |
gateway_reliability.sh --apply reconciles alert policies. |
typed-audit.yml |
daily 11:43 | Typed billing invariant audit + production schema audit against the emulator. |
provider-catalog-health.yml |
hourly :17 | pytest -m provider_health (live provider state, kept out of CI). |
check-analytics-freshness.yml |
every 6h, push on own paths, dispatch | Out-of-band backstop for per-cloud outbox-to-ClickHouse drains. Exists because AWS-EU went 15 days and 470k rows without a drain (:4-15). |
check-archive-freshness.yml |
daily 07:30 | ClickHouse daily archive presence. |
check-client-telemetry-freshness.yml |
06:45/18:45 | Client telemetry freshness via status.json. |
cloud-security-baseline.yml |
daily 08:15 | GCP/Azure security-baseline drift scripts. |
check-repo-security-coverage.yml |
weekly Mon 07:40 | Repo security settings coverage. |
dependency-scan.yml |
PR/push/daily 09:23 | Vulnerability scan with a new-vs-existing diff, uploaded to code scanning. |
daily-embeddings-probe.yml |
daily 13:17 | Live embeddings probe (Cohere optional). |
mirror-repo.yml |
daily 05:41 | Mirrors the repo to GCS (GitHub-outage insurance). |
proofs-guards.yml |
PR/push on proofs/** |
TLA+ guard-table sweep, 16 shards. |
reshard-billing-workspace.yml |
dispatch (status/prepare/finish/online-split, APPLY) |
Billing workspace shard changes. |
deploy-growth-sync.yml |
dispatch with APPLY |
Marketing analytics worker. |
token-exchange-sites.yml |
PR/push on token-exchange paths | Token Exchange static sites and evidence tests. |
lightning-experiment.yml |
PR/push on lightning paths | LightningRouter experiment checks. |
refresh-test-durations.yml |
dispatch | Refreshes .test_durations used for shard balancing. |
4. ENV_VARS inventory (config-as-code)
ENV_VARS is defined at scripts/deploy/rollout.sh:401-676. It is joined with | (:677) and
applied with --set-env-vars plus TR_SERVING_REGION per region (:851). Setting a Cloud Run
env var by hand does nothing, because the next rollout overwrites it (AGENTS.md:64-66).
Secrets are separate: they are listed in SECRET_ENVS and pulled from Secret Manager (:101-112).
Names only below. No values are listed.
| Group | Env var names (rollout.sh line) |
|---|---|
| Identity / surface / release | TR_ENVIRONMENT 407, TR_SERVICE_SURFACE 408 (=combined, legacy bridge), TR_OPERATOR_IDENTITIES 409, TR_ALLOW_DEPLOYED_COMBINED_SURFACE 410, TR_RELEASE 417, TR_SOURCE_REVISION 418, TR_GCP_PROJECT_ID 434, TR_REGIONS 477, TR_PRIMARY_REGION 478, (+TR_SERVING_REGION injected 851) |
| Routing / domains / providers | TR_API_BASE_URL 423, TR_TRUSTED_DOMAIN 424, TR_TRUSTED_DOMAIN_ALIASES 425, TR_ENABLE_LIVE_PROVIDERS 422, TR_REMEDIATOR_IN_PROCESS_ENABLED 421, VERTEX_PROJECT_ID 480, VERTEX_LOCATION 481, TR_USER_MODELS_DISPATCH_ENABLED 464 |
| Speculation shadow (off) | TR_SPECULATIVE_PROVIDER_SHADOW_ENABLED 403, TR_SPECULATION_SHADOW_WORKSPACES 404, TR_SPECULATION_SHADOW_ROUTES 405, TR_SPECULATION_SHADOW_IMAGES 406 |
| Rate limiting / admission | TR_RATE_LIMIT_ENABLED 415, TR_SETTLE_PER_KEY_INFLIGHT_LIMIT 416 |
| Storage (Spanner) | TR_STORAGE_BACKEND 426, TR_SPANNER_INSTANCE_ID 472, TR_SPANNER_DATABASE_ID 473, TR_SPANNER_POOL_SIZE 479, TR_GENERATION_RECORDS_ENABLED 474, TR_REQUEST_RECORD_WRITE_MODE 608 |
| KMS / encryption | TR_BYOK_KMS_KEY_NAME 475, TR_GOOGLE_DATA_MANAGER_KMS_KEY_NAME 476 |
| Trust / attestation pins | TR_TRUST_GCP_SOURCE_COMMIT 482, TR_TRUST_GCP_IMAGE_REFERENCE 483, TR_TRUST_GCP_IMAGE_DIGEST 484, TR_TRUST_GCP_RELEASE_URL 485, TR_TRUST_GCP_RELEASE_FALLBACK_URLS 486, TR_TRUST_AWS_RELEASE_URL 494, TR_TRUST_AZURE_RELEASE_URL 495, TR_SPEND_LEASE_ACCEPTED_GCP_IMAGE_DIGESTS 643 |
| Trust reconciler / spend lease / heartbeat | TR_SPEND_LEASE_TRUST_ELIGIBILITY_ENABLED 637, TR_TRUST_STRIPE_ACCOUNT_ID 638, TR_TRUST_QUALIFYING_PROVIDERS 639, TR_TRUST_RECONCILE_INTERVAL_SECONDS 640, TR_TRUST_RECONCILE_MAX_AGE_SECONDS 641, TR_HEARTBEAT_GRACE_SECONDS 642 |
| Auth / OAuth / SIWE | TR_GOOGLE_OAUTH_REDIRECT_URL 496, TR_GITHUB_OAUTH_REDIRECT_URL 497, TR_SIWE_DOMAIN 498 |
| Signup / growth | TR_SIGNUP_TRIAL_CREDIT_MICRODOLLARS 429, TR_SOCIAL_REWARDS_ENABLED 431, TR_SOCIAL_REWARDS_PLANE_ID 432, TR_SOCIAL_REWARDS_ISSUER_ID 433, TR_ACTIVATION_REMINDER_INTERVAL_SECONDS 511 |
| Identity verification / SMS | TR_NOTIFY_ENABLED 443, TR_NOTIFY_SMS_AVAILABLE 444, TR_VERIFF_ENABLED 455, TR_VERIFF_BASE_URL 465, TR_CUSTOM_MODELS_REQUIRE_VERIFICATION 456, TR_TELNYX_FROM_NUMBER 466, TR_TELNYX_TEXML_ACCOUNT_ID 467, TR_TELNYX_TEXML_APPLICATION_ID 468, TR_TWILIO_FROM_NUMBER 471 |
| Email (SES) / notices / support | TR_AWS_REGION 499, TR_SES_FROM_EMAIL 500, TR_SES_FROM_NAME 501, TR_SES_ALERT_FROM_EMAIL 502, TR_SES_ALERT_FROM_NAME 503, TR_SES_ALERT_CONFIGURATION_SET 504, TR_SES_NOTICES_FROM_EMAIL 505, TR_SES_NOTICES_FROM_NAME 506, TR_SES_NOTICES_CONFIGURATION_SET 507, TR_RETIREMENT_NOTICES_MODE 514, TR_RETIREMENT_NOTICES_PREVIEW_EMAIL 515, TR_SUPPORT_EMAIL 516, TR_PARTNER_INQUIRY_EMAIL 537 |
| Payments (Adyen / Routable / checkout) | TR_ADYEN_ENABLED 519, TR_ADYEN_ENVIRONMENT 520, TR_ADYEN_MERCHANT_ACCOUNT 521, TR_ADYEN_CHECKOUT_API_VERSION 522, TR_ADYEN_WEB_VERSION 523, TR_ADYEN_CARD_FEE_BASIS_POINTS 525, TR_ADYEN_CARD_FEE_FIXED_CENTS 526, TR_ROUTABLE_ENABLED 530, TR_ROUTABLE_API_BASE_URL 531, TR_CHECKOUT_CARD_FEE_MINIMUM_CENTS 532 |
| Ops alerting / logs | TR_OPS_CHAT_WEBHOOK_URLS 533, TR_AXIOM_DATASET 541, TR_AXIOM_URL 542 |
| Settle outbox (money path) | TR_SETTLE_OUTBOX_ENABLED 550, TR_SETTLE_OUTBOX_FAST_DRAIN_ENABLED 551, TR_SETTLE_OUTBOX_POLL_INTERVAL_SECONDS 552, TR_SETTLE_OUTBOX_HEALTH_PUBLISH_INTERVAL_SECONDS 553, TR_SETTLE_OUTBOX_CLAIM_BATCH 554, TR_SETTLE_OUTBOX_WORKER_CONCURRENCY 555, TR_SETTLE_OUTBOX_LEASE_SECONDS 556, TR_SETTLE_OUTBOX_PASS_BUDGET_SECONDS 557, TR_REAP_SNAPSHOT_BOOKING_ENABLED 562 |
| Analytics / ClickHouse | TR_ANALYTICS_OUTBOX_ENABLED 567, TR_OPERATIONAL_ANALYTICS_OUTBOX_ENABLED 568, TR_CLIENT_EVENTS_ENABLED 578, TR_PROVIDER_ANALYTICS_CLICKHOUSE_URL 582, TR_PROVIDER_ANALYTICS_CLICKHOUSE_USER 583, TR_PROVIDER_ANALYTICS_CLICKHOUSE_DATABASE 584, TR_PROVIDER_ANALYTICS_CLICKHOUSE_TABLE 585, TR_OPERATIONAL_ANALYTICS_CLICKHOUSE_URL 586, TR_OPERATIONAL_ANALYTICS_CLICKHOUSE_USER 587, TR_OPERATIONAL_ANALYTICS_CLICKHOUSE_DATABASE 588, TR_OPERATIONAL_ANALYTICS_SINK 602, TR_OPERATIONAL_ANALYTICS_CLICKHOUSE_WRITE_USER 603 |
| Stage D / fastpath pilots | TR_STAGE_D_HEARTBEAT_ENABLED 609, TR_STAGE_D_ELIGIBILITY_ENABLED 619, TR_STAGE_D_PILOT_WORKSPACE_IDS 626, TR_FASTPATH_PILOT_WORKSPACES 631, TR_FASTPATH_PILOT_ENABLED 632 |
| Async settlement (shadow v2; enable=false) | TR_ASYNC_SETTLE_SHADOW_ATTACHMENT_BUCKET 659, TR_ASYNC_SETTLE_SHADOW_POLICY 660, TR_ASYNC_SETTLE_SHADOW_HEALTH_REGION 661, TR_ASYNC_SETTLE_SHADOW_WORKSPACES 662, TR_ASYNC_SETTLE_ENABLED 663, TR_ASYNC_SETTLE_PROTECTION 664, TR_ASYNC_SETTLE_TICKET_PRIVATE_KEY_FILE 671, TR_ASYNC_SETTLE_TICKET_KID 672, TR_ASYNC_SETTLE_TICKET_ISSUER 673, TR_ASYNC_SETTLE_TICKET_AUDIENCE 674, TR_ASYNC_SETTLE_AUTHORITY_EPOCH 675 |
Secrets (SECRET_ENVS, names only) at scripts/deploy/rollout.sh:101-112: TR_SENTRY_DSN,
TR_STRIPE_SECRET_KEY, TR_STRIPE_WEBHOOK_SECRET, TR_INTERNAL_GATEWAY_TOKEN, plus the
async-settle Ed25519 key mounted as a file at /secrets/async-settle/ticket.pem. The deploy
fails if it is missing. Optional secrets are appended conditionally (:113-135).
Sizing knobs come from _lib.sh rather than ENV_VARS: TR_CONTROL_PLANE_REGIONS, TR_WARM_REGIONS,
TR_CLOUD_RUN_MIN_INSTANCES_BY_REGION, TR_CLOUD_RUN_CONCURRENCY, TR_CLOUD_RUN_MAX_INSTANCES,
TR_CLOUD_RUN_MEMORY, TR_CLOUD_RUN_INGRESS (scripts/deploy/_lib.sh:30-93).
Deploy-control vars (read by scripts, not by the app): TR_DEPLOY_TARGET_REGIONS, TR_DEPLOY_NO_TRAFFIC,
TR_DEPLOY_RECONCILE_LB (deploy.yml:555-561); TR_DEPLOY_HOLD_REGIONS (repo variable,
deploy.yml:482, :916); TR_DEPLOY_MUTEX_* (scripts/deploy/deploy_mutex.sh);
TR_CLOUD_BAKE_HOURS, TR_CLOUD_DEPLOY_MODE, TR_CLOUD_BAKE_OVERRIDE (docs/runbook.md:425-470);
TR_STAGED_WATCHDOG_SKIP_BASELINE, TR_BILLING_5XX_GATE_SKIP (hotfix only, deploy.yml:722, :770).
5. Rollout control-plane mechanics
- Fail-closed entry. rollout.sh refuses to run unless
TR_ALLOW_DEPLOYED_COMBINED_SURFACE=trueis set, which is a temporary bridge until the six-service cutover (#712) (scripts/deploy/rollout.sh:7-16). It also refuses unless the exact 40-char source revision matches the checkout and the release (:21-31). - Staged traffic.
staged_traffic.shramps 10, 50 and 100% with a synthetic watch between steps. If a watch trips, traffic goes back to 100% on the old revision (scripts/deploy/staged_traffic.sh:2-12). The watchdog isscripts/deploy/watchdog.py(per-region post-deploy). The default SLO class isrouter_core(staged_traffic.sh:18). - Billing gate.
assert_no_billing_5xx.shfails the rollout if the candidate revision logged any billing-path 5xx, because Cloud Run readiness cannot see exhausted retries (scripts/deploy/assert_no_billing_5xx.sh:2-3). - Deploy mutex / two-cloud coordinator.
deploy_mutex.shwrapscloud_rollout.py, a generation-CAS journal atgs://tr-deploy-mutex-quill-cloud-proxy/locks/trusted-router-production.jsoncovering gcp/aws/azure (scripts/deploy/cloud_rollout.py:1-41).- It is fleet-wide and lets one cloud roll out at a time. The enclave gateway rollouts in quill-cloud-proxy share it (
docs/runbook.md:349-357). - Inspect it with
deploy_mutex.sh status. Manual removal is break-glass only, with--if-generation-match(docs/runbook.md:368-385). - Conflict to resolve: the runbook says "locks expire after 90 minutes" (
docs/runbook.md:386-387), butcloud_rollout.py:5says "Expired/failed reservations are NEVER reclaimed just because time elapsed." - Regional hold.
TR_DEPLOY_HOLD_REGIONS(comma list orall) is a GitHub repo variable. Held regions still get a no-traffic warm, but their traffic is not touched. It is read at job start, so changing it does not affect a running job (scripts/deploy/_deploy_hold.sh;docs/runbook.md:398-421). - Bake ladder / lifeboat (applies to AWS and Azure):
- When one cloud gets fresh code, another must keep serving a commit at least 24h old, the "lifeboat" (
docs/runbook.md:425-447). - The candidate may reach a second cloud only after 24h on first-parent main. Both paths require status.json
overall_status=up. - GCP auto-deploys and can never be the lifeboat.
- Canary mode:
TR_CLOUD_DEPLOY_MODE=canary. The header ofscripts/deploy/cloud_bake_gate.shnotes that the default rolling mode now uses the atomic two-cloud guard, and the ladder applies in promote/canary mode. - Override with a recorded reason using the
bake_override_reasoninput (deploy-aws-control-plane.yml:20-24). - Completeness gate. "Deployed" and "complete" are different states.
verify_cloud_complete.sh {aws|azure|gcp}is the executable definition of done (scripts/deploy/verify_cloud_complete.sh:1-30). It exists because AWS-EU served for 15 days with no analytics drain. - Break-glass.
scripts/deploy/break_glass_deploy.sh [--apply]ships from a laptop when GitHub Actions is down. It runs rollout.sh and a smoke check. It has no CI gate, no migrations, no staged canary or auto-rollback, and no secret sync or jobs, all listed in its header (:1-37,:111-136). Seedocs/runbook-github-outage.md. - Image and attestation digest.
- The control-plane image is tagged with the commit SHA (
deploy.yml:123-127). - rollout.sh reads the enclave's
gcp-release.json(localTRUST_FILEor URL), extractssource_commit,image_referenceandimage_digest(scripts/deploy/rollout.sh:85-98), and injects them asTR_TRUST_GCP_*(:482-486). - AWS and Azure measurements are no longer pinned here. Each plane publishes its own record, and the control plane mirrors it by URL (
:487-495). - Note: the
TRUST_FILEdefault is an operator's laptop path (scripts/deploy/_lib.sh:126). In CI the URL path is used. trust-drift.ymlchecks published values against live attestations daily and after each deploy.- Dockerfile.
uv:python3.12-bookworm-slim. Dependency layers sit before the source copy so the build cache works. Bytecode is precompiled because cold starts measured p50 34.6s / p95 217s without it (Dockerfile:19-29). Entrypointscripts/entrypoint.shrunspython -m trusted_router.serve, whose listener stays open briefly after SIGTERM to avoid zero-latency 500s (Dockerfile:31-40).
6. Terraform (infra/)
There is one root, with state in gs://tr-infra-tfstate-quill-cloud-proxy/infra (infra/backend.tf). It owns only static plumbing. Release procedures stay in scripts/deploy/ (infra/README.md:1-36).
| File | Manages |
|---|---|
cloud_armor.tf |
4 Cloud Armor policies, imported to mirror live state. tr-deploy cannot edit rules, so rule changes need an owner apply before merge (:1-25). |
control_lb_certificates.tf, control_lb_certificate_map.tf, control_lb_proxy.tf |
Certificate Manager certs, DNS authorizations (Cloud DNS + Route53 _acme-challenge), cert map, HTTPS target proxy. |
spanner_trusted_router.tf |
Spanner instance and autoscaler, plus a narrow custom role for capacity changes. |
gcp_wif.tf |
GitHub WIF provider allowlist. Its "adoption safety check" says the first plan must show no changes. |
fastpath.tf, fastpath_alerts.tf, fastpath_deploy_roles.tf, fastpath_node_startup.sh.tftpl |
Pub/Sub settle-log/records topics, auditor/stager/archive subscriptions, archive and release buckets, node SA, envelope-key secret, Spanner DB IAM, 2 GCE nodes, alert policies. |
logging_sampling.tf |
Log exclusions that sample successful Cloud Run requests and enclave lifecycle logs. |
aws_deploy_role.tf |
GitHub OIDC deploy role for AWS. |
aws_synthetic_monitoring.tf |
EventBridge tr-eu-synthetic-1min, DLQ, alarms. |
aws_security_alarms.tf, aws_config.tf |
CIS root-usage and unauthorized-API alarms, AWS Config recorder. |
ses_notices.tf |
SESv2 notices identity, config set, DKIM/SPF/DMARC records. |
The key hazard: every infra apply re-asserts the whole root. On 2026-09-21 a one-line WIF change also reverted a hand edit and re-enabled a DSQL read path costing about $500/day for 27 minutes (infra/README.md:38-63).
7. fastpath/ and release/
fastpath/is the Go service for the fast-admission / batched-settlement design. Eachinternal/*package is a "shadow" of a TLA+ spec inproofs/, and tests check it against the TLC state graph. It runs over Spanner and Pub/Sub. Its schema is applied byscripts/deploy/migrate_fastpath.sh(fastpath/README.md:1-50).- Production state is "P0 dark install": 2 nodes, empty allow-list, nothing admits until a workspace is switched on (
infra/fastpath.tf:1-12). - Release pipeline: CI green, then
fastpath-release.yml, then GCS. Terraform pins a commit (infra/fastpath.tf:19), and a node picks it up when reset or replaced. - Runbooks:
docs/runbooks/fastpath-*.md. release/holdsfirst-window-plan.jsonand its schema. It is a reviewed plan for the async-settle shadow-v2 "first window": router and enclave SHAs/image digests, workspace, attachment bucket, per-region caps, surge and publishers. The checked-in copy is a template withREVIEWplaceholders and zero SHAs. Its consumers aredocs/design/async-settle-shadow-v2-activation.md:44,:99anddocs/design/async-settle-shadow-v2.md:3697.
8. Observability
- Sentry:
src/trusted_router/sentry_config.py. send_default_pii=False(:240).before_send,before_send_logandbefore_breadcrumball run_scrub(:301-331).- Key-name denylist
SENSITIVE_KEYS(:21), substring and prefix scrubbing (:43-95), and bytes redacted wholesale (:586). - A floodgate rate-limits event floods (
:453-500). A noise filter drops untrusted 405s (:334-370). - The DSN is a secret (
TR_SENTRY_DSN). - GCP alert policies (YAML, applied by scripts):
scripts/deploy/spanner-alerts/: api-failures, backup-workflow, capacity, contention, high-priority-cpu (45%,high-priority-cpu.yaml:11-19), latency.scripts/deploy/gateway-alerts/: billing-5xx ("any internal gateway billing 5xx"), billing-pressure, client-invisible-outage, client-telemetry-stale, control-plane-memory.scripts/deploy/clickhouse-alerts/: disk-capacity, node-availability.- Fastpath alerts are in Terraform (
infra/fastpath_alerts.tf). - Paging policy: router-core 5m/1h burn rate ≥14.4× pages immediately; 6h ≥6× pages in waking hours; 24h ≥3× creates a review item (
docs/runbook.md:105-110). - Synthetic monitoring:
- GCP: Cloud Run Jobs deployed by
scripts/deploy/synthetic.sh(uptime probes kept separate from the throughput benchmark,:1-5), redeployed every rollout (deploy.yml:972). - AWS: EventBridge 1-min rule to App Runner
tr-eu/internal/synthetic/run(HANDOFF.md:110-117;infra/aws_synthetic_monitoring.tf). - Status:
trustedrouter.com/status.json(per-cloud status, used by bake gates and freshness checks). Public pagestatus.trustedrouter.comis linked fromsrc/trusted_router/dashboard.py:4620and allowed in CSP atmiddleware.py:458. The repo does not say who hosts the status page. Trust records are published attrust.trustedrouter.com. - Logs: Axiom (
TR_AXIOM_*), plus Cloud Logging with sampled exclusions (infra/logging_sampling.tf). - Scheduled health checks: prod-smoke every 30 min, trust-drift, analytics/archive/telemetry freshness, cloud-staleness, typed-audit, provider-catalog-health (section 3).
9. Runbook and incident index
docs/runbook.md (symptom → action; 1638 lines)
| Anchor (line) | Summary |
|---|---|
| router-core-page (83) | Router-core four-nines burn-rate page: triage and thresholds |
| region-drain (112) | Drain one gateway region via Cloudflare DNS-only LB, then verify SDK failover |
| storage-degraded (135) | Spanner or ClickHouse degraded |
| provider-502 / -429 / -401 (182/213/232) | Upstream provider error classes |
| gateway-auth-400 (268) | "gateway authorization failed" on a provider smoke |
| eu-rollback, min-ready (300/317) | GCP enclave deploy rollback loops / gcloud flag issue |
| bot-doesnt-deploy (328) | Price bot commits but the catalog stays stale |
| deployment-mutex (349) | Fleet-wide GCS mutex, status, break-glass delete, regional hold |
| cloud-bake-ladder (425) | 24h lifeboat rule, canary mode |
| stale-status (500) | Status page shows a region down when it is healthy |
| missing-model / new-provider / new-model (551/573/606) | Catalog operations |
| rotate-key, phala-revive (620/632) | Provider key rotation |
| azure-deploy (681) | Deploying the Azure control plane |
| stop-remediator (723) | TR_REMEDIATOR_MODE=off instead of editing Terraform-owned targets |
| settle-outbox (808) | Flip, verify, monitor, roll back the settle outbox |
| credit-ledger (1051) | Typed credit-ledger operations |
| authorize-deadlock-burst (1141) | Sentry deadlock/wounded bursts on authorize |
| reshard-interrupted (1210) | Workspace 503 "billing is paused" after an interrupted reshard |
| dns-vendor-split (1324) | Cloudflare vs Cloud DNS dual-authoritative NS symptoms |
| adding-a-cloud (1441) | When a new cloud may be called done |
| useful-one-liners (1549), billing-pause-gate (1602), route-health-postgres (1615) | Misc |
Other runbooks
| File | Summary |
|---|---|
docs/runbook-github-outage.md |
GitHub down vs repo gone: different answers. Points to the mirror and break-glass. |
docs/runbooks/ddos-edge-hardening.md |
Deployment contract for public edge protection and emergency controls |
docs/runbooks/gateway-billing-edge.md |
/internal/gateway* routing through Iowa and São Paulo |
docs/runbooks/fastpath-p0.md / -p1.md |
Fast path dark install; synthetic load on one internal workspace |
docs/runbooks/fastpath-deploy.md |
Deploying fastpath nodes without losing held leases |
docs/runbooks/fastpath-watch.md / -alerts.md |
Stage watch with baseline; journal-log alerts |
docs/runbooks/fastpath-turn-off.md |
Disable for a workspace, or stop the fleet |
docs/runbooks/fastpath-key-rotation.md |
Rotate the envelope sealing key |
docs/runbooks/ses-notices.md / ses-reputation.md |
Notices sender authorization; SES reputation response (transactional only) |
docs/runbooks/identity-verification-support.md |
Failed Veriff checks ($5 per attempt) |
docs/speculation-shadow-runbook.md |
Speculative-invocation shadow (ships disabled) |
docs/spanner-reliability.md, docs/clickhouse-reliability.md |
Canonical storage reliability runbooks |
docs/provider-independent-deploys.md |
Provider health is separate from release gating |
docs/operations/service-surfaces.md, public-surface-split.md, internal-surface-split.md |
Service-surface bulkheads (combined, public, internal) |
docs/operations/allyrouter-alias.md, uptimerouter-alias.md |
Independent first-party domain aliases for resilience |
docs/incidents/
| File | One-line summary |
|---|---|
| 2026-09-05-shared-trace-settlement | Stage D made gateway_request_id unique; multi-call requests sharing a trace blocked settlement |
| 2026-09-06-spanner-operator-search-cpu | Three ad hoc wildcard Stripe-ID searches of tr_entities spiked Spanner CPU (origin of the AGENTS.md query rules) |
| 2026-09-08-legacy-billing-shard-contention | A workspace resharded from 1 to 16 hit legacy billing shard lock contention |
| 2026-09-24-spanner-overrun-rollup-scan | Hourly reservation-overrun rollup scan tripped the HP CPU alert |
| 2026-09-24-us-east4-cloud-run-start-stall | us-east4 instances failed to start for 16 min with no deploy in progress |
| 2026-09-25-credit-rebalance-convoy | Starter-credit rebalance convoy caused billing-path 5xx alerts |
| 2026-09-26-regional-ledger-grant-storm | Regional ledger slowdown amplified into a Spanner grant storm |
| 2026-09-30-provider-service-tier-settlement | Settle 500s: OpenAI service tier on a non-OpenAI endpoint |
| 2026-10-03-provider-configuration | New config alerts exposed failing routes that had been excluded from uptime |
| 2026-10-04-gateway-authorization | EU authorize 503s from authorize_strict_budget_busy |
| 2026-10-04-starter-credit-fragmentation | New workspace authorize 503s from fragmented starter credit |
| 2026-10-05-provider-recovery | Adapter parsed only 3 models; retirements blocked discovery of replacements |
| 2026-10-10-shadow-* (4 files) + spanner-shadow-budget | Async-settle shadow: regional read deadlines, SDK retry integration, shutdown-write failure, short release label rejected, txn budget alerts |
| 2026-10-11-oauth-invalid-code | Invalid Google OAuth codes returned 500 instead of 4xx |
| 2026-10-11-shadow-budget-followup | Shadow audit after the regional release reached 100% |
| 2026-10-11-spanner-commit-latency-minute | One-minute 7× commit latency on nam6 paged API failures |
| 2026-10-13-shadow-missing-health-alerts | Unqualified shadow observer caused Spanner API-failure alerts |
| 2026-10-15-gcp-billing-alerts | GCP billing / CIS route-change alerts investigation |
| 2026-10-18-cloud-alerts | Commit-p99 >1s alerts, recovered events vs verified repairs |
| 2026-10-18-fastpath-lease-locks | Fast-path renewal vs auditor commit contention drove commit p99 |
| 2026-10-18-settlelog-shutdown | Fastpath settle-log subscription shutdown hang timed out the Go CI gate |
10. Deploy safety rules
- Run all three CI gates locally:
uv run ruff check . && uv run mypy && uv run pytest -q. Never runruff format .. Coverage must stay ≥70% (AGENTS.md:27-40). Run the full suite, because tests pollute each other through theSTOREproxy (:42-45). - Rebase before trusting a failure. The price bot advances main hourly (
AGENTS.md:47-49). - Pushing to main deploys if a path in the filter changed (
AGENTS.md:60-62;deploy.yml:26-37). Docs-only changes do not deploy. - Change config only in
ENV_VARS. Hand edits to Cloud Run are overwritten (AGENTS.md:64-66). - Never call a queued or partial rollout "deployed". Verify behaviour and prod health after deploy, and report the exact blocker if a gate stops it (
AGENTS.md:51-58). - Keep the gates intact: CI-green gate, mutex, staged ramp, canary, billing 5xx gate, completeness check.
hotfix=trueis for when prod is already sick (deploy.yml:38-43). - One cloud at a time. Respect the GCS mutex. Delete it only after
statusshows a dead owner, and use--if-generation-match(docs/runbook.md:349-387). - Use
TR_DEPLOY_HOLD_REGIONSto freeze a region during an incident. It is read at job start (docs/runbook.md:398-421). - Bake ladder: fresh code must not serve on every cloud at once. Keep a ≥24h lifeboat, and record any override reason (
docs/runbook.md:425-447). - Never run large unbatched DML on prod Spanner during a rolling deploy (
AGENTS.md:68). - Read-only is not load-safe. No
LIKE, regex or JSON scans oftr_entities.body. Usepython -m scripts.inspect_payment_owner,SPANNER_SYSstats and ClickHouse. Never loosen the 45% CPU alarm (AGENTS.md:70-79). - Money code (reserve/settle/refund) needs differential or conformance tests. Run
proof_oraclewhen touching it (AGENTS.md:38-40,:81-87). - Terraform has a single owner. An infra merge re-applies the whole root. Mirror any emergency hand edit in a PR within the hour, and check the apply log for unexpected resources (
infra/README.md:38-63). - Cloud Armor rule changes need an owner's credentials before merge, because tr-deploy cannot weaken the edge (
infra/cloud_armor.tf:9-14). - Do not enable Cloudflare proxying on the prompt path. It voids attestation (
docs/runbook.md:118-119;HANDOFF.md:100). - Break-glass skips everything except rollout and smoke. Write down the prior revision and run migrations by hand (
scripts/deploy/break_glass_deploy.sh:13-30). - A PCR0 re-pin on AWS touches three surfaces: both Fargate task definitions and App Runner
tr-eu(HANDOFF.md:119-123). - "Finished" is not "complete". Bring-up scripts must end in
verify_cloud_complete.sh(scripts/deploy/verify_cloud_complete.sh:8-25).