Developer Portal — How It Works
End-to-end sequence flows for the meta-platform that configures, deploys, monitors, and manages multiple VertexAI RAG app instances on Cloud Run. Each diagram is an independent Mermaid sequence chart — open this file in any browser (no build step).
Developer submits form → Cloud Build → new Cloud Run service + DNS.
End user query path: auth, embeddings, vector search, LLM stream, tracing.
Sight kill-switch: Firestore update + hot config reload (~seconds).
Observe dashboard: parallel fetch from Logging, Monitoring, Langfuse, BQ.
Traffic shift rollback to a prior Cloud Run revision.
Persona edit — lists affected apps; redeploy required for propagation.
Invite developers, portal settings, global audit — admin role only.
Flow A: Developer deploys a new app
From the portal form through immutable config snapshot, Secret Manager, Cloud Build, Artifact Registry, Cloud Run deploy, domain mapping, and live URL back to the developer.
sequenceDiagram
autonumber
participant Dev as Developer
(browser)
participant FE as Portal Frontend
participant BE as Portal Backend
participant FS as Firestore
(portal-state)
participant SM as Secret Manager
participant CB as Cloud Build
participant AR as Artifact Registry
participant CR as Cloud Run
participant DNS as Cloud DNS
participant App as RAG App
(new revision)
Dev->>FE: Fill "New App" form
(name, slug, persona, LLM/RAG cfg)
FE->>BE: POST /api/apps (Firebase JWT)
BE->>FS: create apps/{app_id}
BE->>FS: write config_snapshots/{snap_id}
(immutable copy)
BE->>SM: create per-app secrets
(OPENAI_API_KEY, etc.)
BE->>CB: trigger build
subs: _APP_ID, _TEMPLATE_VERSION, _SNAPSHOT_ID
BE-->>FE: 202 Accepted (build_id)
FE-->>Dev: "Deploying… (3-8 min)"
CB->>BE: GET /internal/snapshots/{snap_id}
BE-->>CB: snapshot JSON
CB->>CB: git checkout vertexai-rag @ tag
CB->>CB: docker build
CB->>AR: docker push rag:{app_id}-{sha}
CB->>CR: gcloud run deploy rag-{app_id}
--set-env-vars APP_ID, PORTAL_API_URL
--set-secrets ...
CR->>App: cold start
App->>BE: GET /internal/apps/{app_id}/config
(OIDC token)
BE-->>App: snapshot config
App->>App: cache config, ready
CR-->>CB: revision live
CB->>DNS: domain mapping {slug}.apps.yourdomain.com
CB->>BE: POST /internal/builds/{build_id}/complete
(revision name)
BE->>FS: update apps/{app_id}.current_revision
+ deployment_events
BE-->>FE: SSE / poll: status=live, url=...
FE-->>Dev: "Live at https://{slug}.apps.yourdomain.com"
Flow B: End user hits a deployed app (runtime serving path)
Technician opens the app subdomain. Sight middleware gates availability; authenticated RAG queries flow through Vertex embeddings, Firestore vector search, streamed LLM response, Langfuse traces, and structured Cloud Logging.
sequenceDiagram
autonumber
participant EU as End User
(technician)
participant DNS as DNS / LB
participant App as RAG App
(rag-technician)
participant Sight as Sight
Middleware
participant Auth as Firebase
Identity Platform
participant RAG as RAG Service
participant VAI as Vertex AI
(embeddings)
participant LLM as OpenAI / Gemini
participant FS as Firestore
(shared)
participant LF as Langfuse
participant CL as Cloud Logging
EU->>DNS: GET technician.apps.yourdomain.com
DNS->>App: route to rag-technician revision
App->>Sight: check sight_enabled
alt sight off
Sight-->>EU: 503 "App offline"
else sight on
App->>Auth: verify ID token (tenant: technician-abc)
Auth-->>App: claims (uid, email)
EU->>App: POST /api/rag/query {q, conversation_id}
App->>RAG: query()
RAG->>VAI: embed query
VAI-->>RAG: vector
RAG->>FS: vector search (filter app_id + tenant_id)
FS-->>RAG: chunks
RAG->>LLM: chat completion
(@observe -> Langfuse trace)
LLM-->>RAG: tokens (streamed)
RAG-->>App: SSE stream
App-->>EU: SSE stream
RAG->>LF: trace {app_id, user_id_hash, conv_id}
App->>CL: structured log {app_id, request_id}
end
Flow C: Sight toggle (hot config update, no rebuild)
Developer disables Sight from the portal. Backend updates Firestore and pushes config to the live app via OIDC-authenticated reload — no Cloud Build required.
sequenceDiagram
autonumber
participant Dev as Developer
participant FE as Portal FE
participant BE as Portal Backend
participant FS as Firestore
participant App as RAG App
(live)
Dev->>FE: Toggle "Sight" OFF on app detail page
FE->>BE: POST /api/apps/{app_id}/sight {enabled: false}
BE->>FS: update apps/{app_id}.sight_enabled = false
BE->>FS: write deployment_events (type=config_update)
BE->>App: POST https://rag-{app_id}.../admin/reload-config
(OIDC token, audience=app URL)
App->>BE: GET /internal/apps/{app_id}/config
BE-->>App: updated config (sight_enabled=false)
App->>App: swap cache atomically
App-->>BE: 200 OK
BE-->>FE: 200 OK, latency
FE-->>Dev: "Sight disabled in 1.2s"
Note over App: subsequent requests return 503
Flow D: Observability aggregation (developer views one app)
Single observe page fans out in parallel to Cloud Monitoring, Logging, Error Reporting, Langfuse, and BigQuery billing export — aggregated by the portal backend.
sequenceDiagram
autonumber
participant Dev as Developer
participant FE as Portal FE
participant BE as Portal Backend
participant LOG as Cloud Logging API
participant ERR as Error Reporting API
participant MON as Cloud Monitoring API
participant LF as Langfuse API
participant BQ as BigQuery
(billing export)
Dev->>FE: Open /apps/technician/observe
par Health
FE->>BE: GET /api/apps/technician/health
BE->>MON: uptime_check_result filter=rag-technician
MON-->>BE: ok/fail series
BE-->>FE: 200 OK
and Logs
FE->>BE: GET /api/apps/technician/logs?level=ERROR&window=1h
BE->>LOG: entries.list
filter: resource.labels.service_name="rag-technician"
LOG-->>BE: log entries
BE-->>FE: log entries
and Crashes
FE->>BE: GET /api/apps/technician/errors
BE->>ERR: groupStats.list filter=service:rag-technician
ERR-->>BE: error groups
BE-->>FE: error groups
and Metrics
FE->>BE: GET /api/apps/technician/metrics?type=latency_p95
BE->>MON: timeSeries.list metric=request_latencies
MON-->>BE: time series
BE-->>FE: chart data
and AI traces
FE->>BE: GET /api/apps/technician/traces?tag=app_id:technician
BE->>LF: GET /api/public/traces?tags=app_id:technician
LF-->>BE: traces
BE-->>FE: traces
and Cost
FE->>BE: GET /api/apps/technician/cost?range=30d
BE->>BQ: SELECT cost FROM billing_export WHERE service=rag-technician
BQ-->>BE: rows
BE-->>FE: cost summary
end
FE-->>Dev: render dashboard
Flow E: Rollback to previous revision
One-click rollback shifts 100% traffic to a prior Cloud Run revision and records the event in Firestore deployment history.
sequenceDiagram
autonumber
participant Dev as Developer
participant FE as Portal FE
participant BE as Portal Backend
participant CR as Cloud Run Admin API
participant FS as Firestore
Dev->>FE: Click "Rollback to rag-technician-00041"
FE->>BE: POST /api/apps/technician/rollback {revision: "00041"}
BE->>CR: services.patch traffic=100%->00041
CR-->>BE: ok (new revision config)
BE->>FS: update apps/technician.current_revision = 00041
+ deployment_events(type=rollback)
BE-->>FE: 200 OK
FE-->>Dev: "Rolled back in 18s"
Flow F: Persona update propagates to apps using it
Editing a shared persona updates Firestore and returns affected app IDs. Changes do not auto-deploy — developers must redeploy (or hot-reload prompt-only changes).
sequenceDiagram
autonumber
participant Dev as Developer
participant FE as Portal FE
participant BE as Portal Backend
participant FS as Firestore
Dev->>FE: Edit persona "Field Technician" system prompt
FE->>BE: PATCH /api/personas/field-technician
BE->>FS: update personas/field-technician
BE->>FS: find apps where persona_id=field-technician
FS-->>BE: [technician, technician-eu]
BE-->>FE: 200 OK + affected_apps:[...]
FE-->>Dev: "2 apps use this persona. Redeploy now?"
Note over Dev,BE: Persona edits do NOT auto-propagate.
Developer must explicitly redeploy each app
(or hot-reload if change is prompt-only).
Flow G: Portal admin operations
Admins sign in via Firebase (invite-only portal-developers tenant). The backend
loads developers/{uid}.role and exposes admin APIs only when
role === admin. Developers see apps and personas; admins additionally manage
team access, portal-wide integrations, and cross-app audit.
/settings (GCP project link, Langfuse, billing export), view global audit,
force Sight off on any app.
sequenceDiagram
autonumber
participant Admin as Portal Admin
(browser)
participant FE as Portal Frontend
participant Auth as Firebase Auth
participant BE as Portal Backend
participant FS as Firestore
(developers, settings)
participant SM as Secret Manager
Admin->>FE: Open portal.yourdomain.com
FE->>Auth: signInWithEmailLink / SSO
Auth-->>FE: ID token (JWT)
FE->>BE: GET /api/me Authorization Bearer JWT
BE->>FS: developers/{uid}
FS-->>BE: role=admin
BE-->>FE: { role: admin, permissions }
FE-->>Admin: Admin nav + dashboard
Admin->>FE: Settings → Developers → Invite
FE->>BE: POST /api/admin/developers/invite {email, role}
BE->>BE: assert role=admin
BE->>FS: create developers/{pending_uid} invited
BE->>Auth: send invite email
BE-->>FE: 201 Created
FE-->>Admin: Invite sent
Admin->>FE: Settings → Portal → Save
FE->>BE: PATCH /api/admin/settings
BE->>FS: portal_settings/global
BE->>SM: rotate portal secrets if keys changed
BE-->>FE: 200 OK
FE-->>Admin: Settings saved
Admin->>FE: Audit → all apps, last 7d
FE->>BE: GET /api/admin/audit?from=...
BE->>FS: query deployment_events
FS-->>BE: events
BE-->>FE: audit rows
FE-->>Admin: deploy / rollback / config_update table
Admin UI — wireframe screens
Low-fidelity mockups for the admin slice of the portal. Same shell as developer views;
extra nav items appear only after GET /api/me returns role: admin.
| Role | |
|---|---|
| admin@co | admin |
| dev@co | developer |
| Time | App | Event |
|---|---|---|
| 10:02 | tech | deploy_success |
| 09:41 | sales | rollback |
| 09:15 | wh | config_update |
Shown when a developer hits an admin URL. Nav hides Admin section.