System Architecture¶
Derived from technical/architecture-overview.md
and technical/security-architecture.md
(both last updated 2026-06-14). Production runtime is GCP partile-prod /
europe-west2, fronted by Cloudflare, with LinkedIn as the identity provider.
There is no real user data yet — pre-launch seed data is tagged mock data
that must be purged before GA.
Production topology¶
flowchart LR
%% source: technical/architecture-overview.md (2026-06-14)
subgraph clients["Clients"]
ios["Native iOS app (Swift) — in migration"]
rn["React Native app — running v1"]
end
subgraph edge["Cloudflare edge"]
worker["Worker: api.partile.app<br/>origin host rewrite"]
pages["Pages + Access<br/>data-room publication (gated)"]
end
subgraph gcp["GCP partile-prod (europe-west2)"]
subgraph run["Cloud Run"]
api["API service (FastAPI)<br/>public ingress, app-level auth"]
jobs["Jobs: matching / retention / migrate"]
responder["remediation-responder<br/>private, SHADOW (ENFORCE=false)"]
end
subgraph vpc["Private VPC (not publicly reachable)"]
sql[("Cloud SQL PostgreSQL 16<br/>private IP, backups + PITR")]
redis[("Memorystore Redis<br/>OAuth state, handoff codes, rate limits")]
end
scheduler["Cloud Scheduler"]
secrets["Secret Manager<br/>(6 secret labels; values never in git)"]
monitoring["Cloud Monitoring<br/>uptime + 5xx alert policies"]
pubsub["Pub/Sub<br/>partile-prod-remediation-alerts"]
end
linkedin["LinkedIn OIDC<br/>signed id_token via JWKS"]
anthropic["Anthropic Claude<br/>key-gated, minimized prompts"]
audit["Structured JSON audit log"]
ios -->|"HTTPS, Bearer opaque session"| worker
rn -->|"HTTPS, Bearer opaque session"| worker
worker -->|"HTTPS"| api
api -->|"SQL over private IP/socket"| sql
api -->|"hashed lookups"| redis
api -->|"HTTPS"| linkedin
api -->|"HTTPS"| anthropic
scheduler -->|"OAuth (dedicated SA)"| jobs
jobs --> sql
jobs --> redis
secrets -.->|"per-secret accessor IAM"| api
secrets -.-> jobs
monitoring --> pubsub
pubsub -->|"push POST /pubsub"| responder
responder --> audit
Key properties (all evidenced in the control register):
- Data-plane isolation (SEC-08): Cloud SQL and Redis are private-network only; the only public surface is the consumer API, protected by LinkedIn OIDC sessions and SQL-enforced object authorization (SEC-04).
- Least-privilege IAM (SEC-16): dedicated API, worker, scheduler, and
responder service accounts; the responder has read-only observability roles
plus
run.developerscoped to the API service only. - Shadow remediation (OPS-06): the responder is default-deny, allowlists
two runbook paths, is circuit-breaker bounded, and — with
ENFORCE=false— audits decisions without ever mutating production. - Accepted risk (GOV-07): the API's public
allUsersinvoker binding is a documented, project-scoped exception tracked in the action register.
Authentication flow (LinkedIn OIDC → mobile session)¶
sequenceDiagram
%% source: technical/security-architecture.md (2026-06-14)
autonumber
participant M as Mobile app
participant API as Cloud Run API
participant R as Redis
participant L as LinkedIn OIDC
M->>API: Start sign-in
API->>R: Store single-use CSRF state (short TTL)
API-->>M: Fixed LinkedIn redirect (no open redirect)
M->>L: User authenticates and consents
L-->>API: Callback with code + state
API->>R: Atomic GETDEL consume of state
API->>L: Exchange code and fetch JWKS
API->>API: Verify signed id_token (alg, kid, iss, aud, exp, sub)
API->>R: Store one-time handoff code (120s TTL)
M->>API: Exchange handoff code (never a token in a URL)
API->>API: Issue opaque session token, store SHA-256 hash only
API-->>M: Session token (Keychain on native iOS)
OAuth access/ID/refresh tokens are never persisted; session tokens exist in the database only as hashes (SEC-01/02/03/05/10).
Trust boundaries¶
| # | Boundary | Protection |
|---|---|---|
| 1 | Mobile → API edge | TLS; opaque Bearer sessions; 401/429 handling |
| 2 | Cloudflare → Cloud Run | HTTPS to managed-TLS origin (token rotation is a tracked open action) |
| 3 | Cloud Run → data stores | Serverless VPC connector; private IP/socket; no public reachability |
| 4 | API/jobs → LinkedIn / Anthropic | HTTPS; signed-token verification; key-gated minimized prompts |
| 5 | Scheduler → jobs | OAuth from a dedicated service account |
| 6 | Operator/deploy → GCP | Terraform, GCS remote state, Secret Manager (Tier 2) |
| 7 | Monitoring → responder | Pub/Sub push to a private service; shadow-only |
| 8 | Control-plane VM → GCP | Keyless VM-attached service account (per-agent isolation is a tracked caveat) |
Full boundary table and open gaps:
technical/architecture-overview.md.