Infrastructure & Deployment¶
| Field | Value |
|---|---|
| Status | live (current-state; GCP production) |
| Owner | OPS / DIR |
| Applies to | How Partile runs and ships: local/dev topology, GCP production, Cloudflare edge, data-room publication, mobile release path, and superseded AWS staging evidence. |
| Last updated | 2026-06-14 |
| Evidence | partile-ops: infra/envs/prod-gcp/README.md, generated-secrets.tf, locals.tf, main.tf, variables.tf, prod-gcp.auto.tfvars.example; partile-ops: infra/modules/gcp-{artifacts,network,data,security,secrets,runservice,ingress}/; selfheal PR-1 deployed remediation responder facts (infra/remediation/, atelier/selfheal/pr-1); partile-ops: infra/edge/cloudflare/README.md; partile-infra through PR-30 seed/purge tooling; partile-mobile through PR-26 plus physical iPhone auth/product QA; partile-cloudflare PR-5 data-room publication; ../control-register.md (SEC-05/08/09/16, PRIV-15, OPS-01/03/05/06, GOV-07). |
Current State¶
Partile now has a GCP production environment in project partile-prod, region
europe-west2, with public API hostname api.partile.app. The environment is a
low-cost MVP production stack: Cloud Run for the API, Cloud Run Jobs for the
worker tasks, Cloud Run for a private remediation responder in SHADOW mode,
Cloud SQL PostgreSQL on private IP, Memorystore Redis on the private VPC, Secret
Manager for runtime secrets, Cloud Monitoring/Pub/Sub for selected alert
delivery, and Cloudflare in front of the Cloud Run origin.
There is still no real user data. Pre-launch testing may use explicitly
tagged mock accounts (users.is_seed = true) and must purge them before GA. The
old AWS ECS/Fargate staging path is preserved below as superseded evidence and a
not-adopted alternative; it is no longer the current deployment path.
Local / dev topology¶
- Docker Compose still runs
api,worker,postgres,redis, and one-shotmigratefor development. Health remains/health; readiness checks Postgres and Redis. - Config remains env-driven (
APP_ENV,DATABASE_URL,REDIS_URL,RATE_LIMIT_*,RETENTION_*,MATCHING_*,MOBILE_AUTH_REDIRECT_URL, LinkedIn, and AI keys). Values are not committed. - LinkedIn OIDC issuer metadata defaults to
https://www.linkedin.com/oauth, withLINKEDIN_ISSUERoverridable for tests.
GCP production topology¶
flowchart TB
internet[Internet clients] --> cf[Cloudflare DNS and Worker]
cf -->|TLS to run.app origin| api[Cloud Run service api]
subgraph gcp[GCP project partile-prod / europe-west2]
api -->|private ranges via connector| sql[(Cloud SQL PostgreSQL 16 private IP)]
api -->|private VPC| redis[(Memorystore Redis)]
api -->|runtime secret refs| sm[Secret Manager]
api -->|image pull| ar[Artifact Registry Docker repo]
sched[Cloud Scheduler] -->|OAuth run| match[Cloud Run Job matching]
sched -->|OAuth run| retention[Cloud Run Job retention]
operator[Operator] -->|on demand| migrate[Cloud Run Job migrate]
mon[Cloud Monitoring observe alert policies] --> notif[Notification channel 7003417658785730995]
notif --> topic[Pub/Sub topic partile-prod-remediation-alerts]
topic -->|push POST /pubsub| responder[Cloud Run service remediation-responder]
responder -->|structured decision logs| logs[Cloud Logging]
responder -. shadow audit only .-> api
match --> sql
match --> redis
retention --> sql
retention --> redis
migrate --> sql
build[Cloud Build] -->|push image| ar
tf[Terraform] -->|remote state| gcs[(GCS bucket partile-prod-tfstate)]
tf --> api
tf --> sql
tf --> redis
tf --> sm
end
Internet
-> Cloudflare DNS / Worker route api.partile.app/*
-> Worker rewrites origin host to the Cloud Run run.app host
-> Cloud Run service: api (public ingress, managed TLS, min_instances=1)
-> Serverless VPC Access connector
-> Cloud SQL PostgreSQL 16 (private IP only, PSA, backups/PITR)
-> Memorystore Redis (private VPC)
Cloud Scheduler
-> Cloud Run Jobs: matching, retention
Manual/operator
-> Cloud Run Job: migrate
Cloud Monitoring
-> Notification channel projects/partile-prod/notificationChannels/7003417658785730995
-> Pub/Sub topic partile-prod-remediation-alerts
-> Push subscription POST /pubsub
-> Cloud Run service: remediation-responder (private, ENFORCE=false)
Cloud Build
-> Artifact Registry Docker image used by Cloud Run service/jobs
The architecture diagram is currently hand-authored from Terraform evidence in
partile-ops: infra/envs/prod-gcp/. A future ops hardening pass could generate
the diagram from Terraform outputs or state summaries so the picture cannot
drift from infrastructure code.
Runtime services¶
- API: Cloud Run v2 service
api, public ingress,allUsersRun Invoker, managed TLS at the Cloud Run origin, Cloudflare TLS at the edge, one warm minimum instance, and bounded maximum instances. The service uses the VPC connector for private ranges and mounts the Cloud SQL Unix socket. - Worker model: the long-running local
app.workerservice is not deployed as production service. Production uses Cloud Run Jobs:matchingevery two minutes,retentionevery five minutes, andmigrateon demand. - Remediation responder: Cloud Run service
remediation-responder, private, URLhttps://remediation-responder-u5ylen7vtq-nw.a.run.app, environmentENFORCE=false. Cloud Monitoring sends selected observe alerts through notification channelprojects/partile-prod/notificationChannels/7003417658785730995to Pub/Sub topicpartile-prod-remediation-alerts, then a push subscription callsPOST /pubsub. In shadow mode the responder decides and audits but never mutates production. - Images: the backend image is built by Cloud Build and stored in Artifact Registry. The build path is scoped to Artifact Registry write, source/object read, and logging permissions per ops evidence; runtime identities do not need build permissions.
Remediation decision bounds¶
The responder is default-deny. Only two runbook paths are allowlisted:
api_down may decide to restart the last-known-good API revision, and
api_5xx_spike_post_deploy may decide to roll back to the previous revision.
All unmatched alerts escalate. A circuit breaker caps potential action decisions
at <=3 per hour per resource and guards against repeat-fingerprint thrash.
Every decision is logged as structured JSON. Because ENFORCE=false, these are
audit records and rollout evidence only; enforce mode is a future planned change
tracked in ../registers/action-register.md.
Data and network¶
- Cloud SQL: PostgreSQL 16, private IP only (
ipv4_enabled=false), Private Service Access, private Google access, deletion protection, automated backups, point-in-time recovery,ENTERPRISEedition,db-custom-1-3840, and current low-costZONALavailability. - Redis: Memorystore Redis on the private VPC; no public endpoint. Current defaults are the low-cost MVP posture and are not an availability SLA.
- Network: custom VPC/subnet, Private Service Access for managed services, and Serverless VPC Access for Cloud Run egress to private ranges.
- Remote state: Terraform remote state is in private GCS bucket
partile-prod-tfstate, prefixterraform/prod-gcp, with uniform bucket-level access and versioning. The state can contain generated secret material, so the bucket is Tier 2 operational evidence and must remain private.
Secrets and config¶
Terraform creates six Secret Manager secrets and injects them into Cloud Run:
ANTHROPIC_API_KEYDATABASE_URLLINKEDIN_CLIENT_IDLINKEDIN_CLIENT_SECRETREDIS_URLSESSION_SIGNING_SECRET
The operator supplies only ANTHROPIC_API_KEY, LINKEDIN_CLIENT_ID, and
LINKEDIN_CLIENT_SECRET. Terraform generates the database app-user password and
session signing secret with random_password, composes the database and Redis
URLs, and grants Secret Manager accessor only to the API and worker runtime
service accounts. Plain non-secret config is injected as normal Cloud Run
environment variables, including APP_ENV=production,
LINKEDIN_REDIRECT_URI=https://api.partile.app/auth/linkedin/callback, retention
and rate-limit knobs, and RATE_LIMIT_TRUST_PROXY_HEADERS=false.
IAM¶
- Dedicated service accounts exist for the API, worker jobs, and Cloud Scheduler caller.
- API and worker service accounts receive only the runtime project roles needed to read Artifact Registry images, connect to Cloud SQL, write logs, and publish metrics, plus per-secret Secret Manager accessor grants.
- Scheduler invokes only the Cloud Run Job run endpoints via its dedicated OAuth identity.
- Cloud Build is a build-time identity only; it does not run production traffic.
- The remediation responder service account is recorded in source facts as
…-remediation@partile-prod. It hasroles/logging.viewerandroles/monitoring.viewerat project scope, plusroles/run.developerscoped only to theapiCloud Run service. It has no secret, IAM-admin, data-plane, or delete roles. - The Pub/Sub push invoker service account is recorded in source facts as
…-remediate-push@partile-prod. It hasroles/run.invokeron theremediation-responderservice only.
Director production access path¶
The control-plane VM partile-control runs production GCP operations through the
non-snap Cloud SDK at /opt/agent/otto/google-cloud-sdk, authenticated keylessly
through the VM-attached director-agent@partile-prod service account. No GCP
key files are required for this path. The known caveat is privilege separation:
any process running as otto, including executors, can reach the attached
service account via the metadata server. Per-agent identity or equivalent
metadata-server isolation remains an open action before relying on stronger
separation claims.
Cloudflare API edge¶
api.partile.app is fronted by a Cloudflare Worker tracked in
partile-ops: infra/edge/cloudflare/. Cloudflare Free does not provide origin
Host header override for the needed shape, so the Worker rewrites the request URL
host to the Cloud Run run.app origin. Cloud Run still terminates managed TLS at
origin; Cloudflare terminates the public edge connection. Cloudflare API tokens
and account/zone identifiers are operator-supplied through shell/CI secret store
only and are never committed.
Data-room publication path¶
The data-room intranet remains published at dataroom.partile.app using
Cloudflare Pages + Access. The canonical source remains this Git-safe
dataroom/ tree. The data-room publication path is separate from the production
API edge.
Accepted Risks / Current Open Items¶
These are tracked in ../registers/action-register.md:
- Domain Restricted Sharing exception: project-scoped
iam.allowedPolicyMemberDomainswas relaxed to allowallUsersRun Invoker on the public API. This is accepted for the consumer API because app access is protected by LinkedIn OIDC sessions, Cloud SQL is private-IP only, no admin plane exists, and the exception is scoped to the production project. - Pre-launch seed data in production: realistic mock travelers may be loaded
for testing when tagged
users.is_seed = true; they are not real PII and must be purged and verified before GA. - Cloudflare token rotation: an operator note says a Cloudflare API token appeared in chat history. Rotate it; do not record the token value here.
- Staging before GA: production exists first; create a separate staging environment before GA.
- HA before production SLA: current Cloud SQL is
ZONALand Redis is the low-cost posture. Upgrade to regional Cloud SQL / HA Redis before offering a production availability SLA. - Remediation shadow-to-enforce gate: the responder is deployed in shadow
mode only. Turning on
ENFORCE=trueneeds shadow-log review, circuit-breaker evidence, operator approval, and a rollback/change record. - Control-plane privilege separation: the director prod-access path is
keyless, but VM-attached-service-account access is shared by convention among
processes running as
otto; per-agent identity remains open. - Restore drills / SLO / on-call / pentest: Cloud SQL backups and PITR are configured, but restore drills, on-call/SLOs, and third-party security testing remain open.
Superseded AWS Staging Path¶
The earlier AWS path is preserved because it explains previous design decisions and evidence. It is superseded / not adopted for the current production stack.
Intended topology was: ALB with ACM TLS, ECS/Fargate API and worker tasks, RDS
PostgreSQL, ElastiCache Redis/Valkey, ECR images, SSM Parameter Store, and
CloudWatch logs in eu-west-2. Ops PR-7 applied artifacts, including ECR, an
ARM64 backend image, task definitions, SSM references, and ALB listeners. Ops
PR-8 aligned the staging SSM rate-limit contract.
The first AWS live deploy remained blocked by an account-level ECS/Fargate
RunTask hold. No services or migration ran there, and no real user data ever
landed in that AWS stack. Future reviewers should treat it as historical
evidence and a not-adopted alternative, not as the current runtime.
Mobile Release Context¶
The client remains React Native / Expo with EAS as the build/distribution path.
The physical iPhone development build and real LinkedIn partile://
auth/deep-link smoke passed on 2026-06-08 after the LinkedIn issuer hotfix.
Mobile PR-15 through PR-18 implement and physically verify the boarding-pass
gate, camera scan, Photos/Files image import, sanitized confirmation, verified
presence creation, and fail-closed negative paths. Mobile PR-19 through PR-22 add
synthetic parser tests, stable non-sensitive selectors, hard-gated local/dev
automation seams, and Maestro flow scaffolding. Mobile PR-23 through PR-26 add
message composer, directory/stars, request-with-note handling with safety
actions, and profile editing. Android device QA, mobile AI interview UI, and
propose-to-meet remain future/operator-owned.
Links¶
- Controls:
../control-register.md(SEC-05/08/09/16, PRIV-15, OPS-01/02/03/04/05/06, GOV-07). - Open items / accepted risks:
../registers/action-register.md. - Policies:
../policies/backup-and-recovery-policy.md,../policies/change-management-policy.md,../policies/cryptography-and-secrets-policy.md. - Architecture map:
architecture-overview.md. SDLC:sdlc-and-quality.md.