teleo-infrastructure/docs/observatory-read-adapter-gcp-staging.md
twentyOne2x 36fe0d3cc2
Some checks are pending
CI / lint-and-test (push) Waiting to run
Harden Observatory private staging deploy gate (#163)
Merge the T2 readiness and least-privilege staging gate. This does not deploy Cloud Run, mutate Cloud SQL, or change production routing.
2026-07-15 09:55:49 +02:00

9.9 KiB

GCP Observatory Read Adapter

Definition of Working

  • Working target: an authenticated GCP staging API returns one canonical claim with source/evidence provenance and a separately labeled proposal ledger.
  • Operator path: GET /v1/claims/sample or GET /v1/claims/{uuid} with the server-only X-Api-Key value.
  • Done means: the response names teleo_canonical, the dedicated IAM database principal and kb_observatory_read, reports a read-only transaction and denied write privileges, and live anonymous/write HTTP attempts return 401/405.
  • Not done: the VPS teleo-pg, a public Cloud SQL address, a browser-visible key, a mock-only receipt, or a production Vercel repoint.
  • Required tier: T3_live_readonly.

Existing Connector Boundary

The current living-ip/livingip-web main branch reads claims through src/lib/api.ts. It selects NEXT_PUBLIC_API_URL and otherwise falls back to http://77.42.65.182:8081, then calls the legacy /api/claims routes. That is the VPS-local/public Observatory path. It is not safe to replace that public environment variable with this protected API: the response shape differs and the adapter key must never enter a NEXT_PUBLIC_ variable or browser bundle.

The existing Teleo route GET /api/kb/claims is the compatibility reference for route-scoped authentication, but its default database/container settings still describe the VPS-local process. This service is a separate GCP runtime; it does not change Argus or PR #148's Leo runtime files.

Runtime Contract

  • Cloud Run service: observatory-read-adapter-staging in europe-west6.
  • Direct VPC egress: teleo-staging-net / teleo-staging-europe-west6.
  • Cloud SQL: teleo-501523:europe-west6:teleo-pgvector-standby, private IP.
  • Database: teleo_canonical only.
  • Build identity: sa-artifact-builder@teleo-501523.iam.gserviceaccount.com; image build/push only.
  • Staging deploy/canary identity: sa-observatory-deployer@teleo-501523.iam.gserviceaccount.com; an exact workflow/main-only WIF provider, a five-permission Cloud Run custom role, repository read, exact runtime actAs, and exact secret access. Never grant deploy permissions to the artifact builder.
  • Runtime identity: sa-observatory-read-adapter@teleo-501523.iam.gserviceaccount.com.
  • IAM database user: sa-observatory-read-adapter@teleo-501523.iam.
  • Database authorization role: kb_observatory_read (NOLOGIN).
  • API authentication: route-scoped key from Secret Manager secret observatory-read-api-key-staging; the value is not in Git or deploy logs.
  • No mutation routes exist. Authenticated unsupported methods return 405.

The Cloud SQL Python Connector uses Application Default Credentials from the Cloud Run service identity, automatic IAM database authentication, and private IP. Every request starts a read-only transaction and refuses the response if the database, IAM principal, authorization membership, required reads, or effective write-denial checks drift.

One-Time Staging Bootstrap

These are staging mutations. Run them only with an authenticated GCP operator; they do not route production traffic.

  1. Reauthenticate the project operator, then run the first full direct check:

    gcloud projects describe teleo-501523 --format=value(projectId)
    python3 ops/check_gcp_infra_readiness.py
    
  2. Review the exact dry-run packet. It enables only required APIs, creates separate staging deploy/runtime identities, creates the dedicated observatory-read-adapter-main WIF provider, keeps the artifact builder narrow, scopes Cloud SQL connect/login to teleo-pgvector-standby, and scopes secret access to observatory-read-api-key-staging:

    python3 ops/plan_observatory_read_adapter_gcp.py
    python3 ops/plan_observatory_read_adapter_gcp.py --format shell
    

    The shell output is unexecuted and uses set -euo pipefail. Apply it only after review with an authenticated teleo-501523 administrator. The deployer custom role contains only run.services.create, run.services.update, run.services.get, run.services.setIamPolicy, and run.operations.get; it contains no delete permission. Do not add run.admin, run.developer, cloudsql.admin, secretmanager.admin, compute.admin, Editor, or Owner to either lane identity.

  3. Add the dedicated runtime service account as a Cloud SQL IAM service-account user on teleo-pgvector-standby.

  4. From the existing private GCP VM database-admin path, run ops/observatory_read_role.sql against teleo_canonical with OBSERVATORY_DB_IAM_USER=sa-observatory-read-adapter@teleo-501523.iam. Do not read, copy, or retain the administrator password.

  5. Create observatory-read-api-key-staging, add a generated whitespace-free 32-byte-or-longer value through stdin only when no enabled version exists, and grant that secret's accessor role only to the runtime service account and the dedicated staging deploy/canary identity. Deployment pins the exact enabled version; it does not inject latest into Cloud Run.

  6. Dispatch the read-only preflight from merged main:

    gh workflow run gcp-observatory-read-adapter.yml \
      --repo living-ip/teleo-infrastructure \
      --ref main \
      -f action=preflight_staging
    

    Require a passing observatory-read-adapter-preflight artifact before a bounded action=deploy_staging dispatch. The preflight verifies the exact WIF condition, custom-role permission sets, project/resource IAM bindings, Cloud Run service agent, private VPC/subnet relationship, private-only Cloud SQL network and IAM authentication, trimmed IAM database user, exact secret policy/value shape, and the run-unique immutable image. It also rejects any artifact-builder deploy, runtime actAs, secret, or service binding.

The workflow always builds a run-unique immutable image, deploys at most two Cloud Run instances, and retains the positive response plus anonymous 401 and authenticated POST 405 receipts. The retained positive receipt contains identity/provenance, counts, and proposal/live state only; claim text, source URLs, excerpts, unknown future provenance fields, and secret values are removed by an explicit tested allowlist. The live gate binds the response to the exact project, private instance, database principal, Git revision, Cloud Run image digest, runtime identity, and pinned secret version. It does not modify Vercel. The app-level X-Api-Key remains the staging authentication boundary, so anonymous Cloud Run invocation reaches the adapter and returns the required 401 instead of a platform-generated status.

Current T2 Receipt And Exact T3 Gate

GitHub Actions run 29389090997 proves the existing living-ip-github WIF provider can federate as the artifact builder, run the checker, upload its artifact, and clean up credentials. The checker reported 8 pass, 10 blocked, 0 fail. Eight blocks are missing inventory permissions on the artifact-builder identity; two are missing restore/replication proof. This is partial OIDC/T2 evidence, not T3 and not authorization to deploy with that identity.

The exact external action is for billy@livingip.xyz to reauthenticate gcloud and ADC, prove gcloud projects describe teleo-501523, run python3 ops/check_gcp_infra_readiness.py, review and execute the planner's shell output as a project IAM/API administrator, then apply ops/observatory_read_role.sql from the existing private database-admin path. The general readiness identity and artifact-builder identity are not deployer substitutes. After those actions, dispatch preflight_staging; only a passing artifact permits deploy_staging. A passing preflight remains T2 setup proof: the fail-closed live claim and negative receipts are what establish T3.

The IAM basis is the Google Cloud deployment contract for Cloud Run (Cloud Run access, Artifact Registry repository read, and exact runtime actAs), Secret Manager resource-level accessor grants, Direct VPC access through the default Cloud Run service agent, and conditional Cloud SQL client access to one named instance:

Unexecuted Cutover Packet

Production cutover requires a separate exact authorization and a living-ip/livingip-web change. Do not execute these steps from this lane.

  1. Add server-only Vercel secrets OBSERVATORY_CANONICAL_API_URL and OBSERVATORY_CANONICAL_API_KEY to a protected preview environment. The URL targets the GCP service; neither setting may use NEXT_PUBLIC_.
  2. Add a Next.js server route or server action that calls the GCP API, validates livingip.observatory-canonical-claim.v1, and maps it to the Observatory UI. The browser calls only the same-origin Next.js route.
  3. Prove preview Deployment Protection, anonymous denial, source/evidence rendering, proposal/live labels, and no browser key exposure.
  4. Obtain exact production-repoint authorization naming the reviewed livingip-web commit and Vercel project before changing production values.

Unexecuted Rollback Packet

  1. Revert the authorized livingip-web connector commit or disable its server-side feature flag.
  2. Restore the prior production Vercel values and redeploy the last accepted production revision.
  3. Verify /claims again uses the prior /api/claims contract.
  4. Leave the private Cloud SQL instance and adapter role intact while receipts are reviewed; scale the staging Cloud Run service to zero if isolation is required.
  5. Delete the staging service/role/secret only under a separate cleanup decision. Rollback never adds a Cloud SQL public IP and never redirects the adapter to VPS teleo-pg.