Infra & deploys · TrustedRouter Field Manual
Month 1

Infra & deploys

CI/CD, rollout.sh, Terraform, clouds, observability, runbooks.

Video 06 The deploy pipeline · Path filters, image build, staged rollout, verification.

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-proxy repo (.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 --> aca
Diagram 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 --> aca

Facts 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):

  1. Trigger and path filter: :26-37. Manual workflow_dispatch with hotfix: true skips the CI gate and the watchdog baseline (:38-43, :73, :516-524).
  2. 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).
  3. 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).
  4. build-image: Cloud Build pushes ${REGION}-docker.pkg.dev/${PROJECT_ID}/${REPO}/${SERVICE}:${SHA} and uses a registry buildcache tag for layer caching (:109-208). Auth uses WIF to tr-deploy@quill-cloud-proxy, with no JSON keys (:9-12, :133).
  5. 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).
  6. migrate-schema runs idempotent migrate_*.sh steps (money primitives, retention, receipts, outboxes, async-settle admission, fastpath tables) and syncs analytics snapshots (:242-368).
  7. sync-runtime-secrets validates SES credentials and pushes the ClickHouse reader credential (:370-407).
  8. confirm-current-main stops a stale push from deploying over a newer main (:409-448).
  9. 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).
  10. 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 that source_commit, image_reference and image_digest appear in the trust records (:1160-1170).
  11. public-surface-companion deploys the isolated T1 public service and runs outside the mutex (:1262-1338).
  12. verify-cloud-complete runs bash scripts/deploy/verify_gcp_complete.sh with if: always() (:1340-1399). The long comment at :1340-1380 records why an echo-only stub is unacceptable.
  13. 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=true is 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.sh ramps 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 is scripts/deploy/watchdog.py (per-region post-deploy). The default SLO class is router_core (staged_traffic.sh:18).
  • Billing gate. assert_no_billing_5xx.sh fails 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.sh wraps cloud_rollout.py, a generation-CAS journal at gs://tr-deploy-mutex-quill-cloud-proxy/locks/trusted-router-production.json covering 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), but cloud_rollout.py:5 says "Expired/failed reservations are NEVER reclaimed just because time elapsed."
  • Regional hold. TR_DEPLOY_HOLD_REGIONS (comma list or all) 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 of scripts/deploy/cloud_bake_gate.sh notes 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_reason input (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). See docs/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 (local TRUST_FILE or URL), extracts source_commit, image_reference and image_digest (scripts/deploy/rollout.sh:85-98), and injects them as TR_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_FILE default is an operator's laptop path (scripts/deploy/_lib.sh:126). In CI the URL path is used.
  • trust-drift.yml checks 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). Entrypoint scripts/entrypoint.sh runs python -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. Each internal/* package is a "shadow" of a TLA+ spec in proofs/, and tests check it against the TLC state graph. It runs over Spanner and Pub/Sub. Its schema is applied by scripts/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/ holds first-window-plan.json and 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 with REVIEW placeholders and zero SHAs. Its consumers are docs/design/async-settle-shadow-v2-activation.md:44, :99 and docs/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_log and before_breadcrumb all 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 page status.trustedrouter.com is linked from src/trusted_router/dashboard.py:4620 and allowed in CSP at middleware.py:458. The repo does not say who hosts the status page. Trust records are published at trust.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

  1. Run all three CI gates locally: uv run ruff check . && uv run mypy && uv run pytest -q. Never run ruff format .. Coverage must stay ≥70% (AGENTS.md:27-40). Run the full suite, because tests pollute each other through the STORE proxy (:42-45).
  2. Rebase before trusting a failure. The price bot advances main hourly (AGENTS.md:47-49).
  3. 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.
  4. Change config only in ENV_VARS. Hand edits to Cloud Run are overwritten (AGENTS.md:64-66).
  5. 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).
  6. Keep the gates intact: CI-green gate, mutex, staged ramp, canary, billing 5xx gate, completeness check. hotfix=true is for when prod is already sick (deploy.yml:38-43).
  7. One cloud at a time. Respect the GCS mutex. Delete it only after status shows a dead owner, and use --if-generation-match (docs/runbook.md:349-387).
  8. Use TR_DEPLOY_HOLD_REGIONS to freeze a region during an incident. It is read at job start (docs/runbook.md:398-421).
  9. 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).
  10. Never run large unbatched DML on prod Spanner during a rolling deploy (AGENTS.md:68).
  11. Read-only is not load-safe. No LIKE, regex or JSON scans of tr_entities.body. Use python -m scripts.inspect_payment_owner, SPANNER_SYS stats and ClickHouse. Never loosen the 45% CPU alarm (AGENTS.md:70-79).
  12. Money code (reserve/settle/refund) needs differential or conformance tests. Run proof_oracle when touching it (AGENTS.md:38-40, :81-87).
  13. 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).
  14. Cloud Armor rule changes need an owner's credentials before merge, because tr-deploy cannot weaken the edge (infra/cloud_armor.tf:9-14).
  15. Do not enable Cloudflare proxying on the prompt path. It voids attestation (docs/runbook.md:118-119; HANDOFF.md:100).
  16. 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).
  17. A PCR0 re-pin on AWS touches three surfaces: both Fargate task definitions and App Runner tr-eu (HANDOFF.md:119-123).
  18. "Finished" is not "complete". Bring-up scripts must end in verify_cloud_complete.sh (scripts/deploy/verify_cloud_complete.sh:8-25).