# Developer Portal — Sequence Flows

> Mermaid allows **one diagram per parse**. Each flow below is a separate ` ```mermaid ` block (valid in Markdown preview and GitHub). Per-flow `.mmd` sources live in [`./flows/`](./flows/).

## Flow A: Developer deploys a new app

```mermaid
sequenceDiagram
    autonumber
    participant Dev as Developer<br/>(browser)
    participant FE as Portal Frontend
    participant BE as Portal Backend
    participant FS as Firestore<br/>(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<br/>(new revision)

    Dev->>FE: Fill "New App" form<br/>(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}<br/>(immutable copy)
    BE->>SM: create per-app secrets<br/>(OPENAI_API_KEY, etc.)
    BE->>CB: trigger build<br/>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}<br/>--set-env-vars APP_ID, PORTAL_API_URL<br/>--set-secrets ...
    CR->>App: cold start
    App->>BE: GET /internal/apps/{app_id}/config<br/>(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<br/>(revision name)
    BE->>FS: update apps/{app_id}.current_revision<br/>+ 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)

```mermaid
sequenceDiagram
    autonumber
    participant EU as End User<br/>(technician)
    participant DNS as DNS / LB
    participant App as RAG App<br/>(rag-technician)
    participant Sight as Sight<br/>Middleware
    participant Auth as Firebase<br/>Identity Platform
    participant RAG as RAG Service
    participant VAI as Vertex AI<br/>(embeddings)
    participant LLM as OpenAI / Gemini
    participant FS as Firestore<br/>(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<br/>(@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)

```mermaid
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<br/>(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<br/>(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)

```mermaid
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<br/>(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<br/>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

```mermaid
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<br/>+ deployment_events(type=rollback)
    BE-->>FE: 200 OK
    FE-->>Dev: "Rolled back in 18s"
```

## Flow F: Persona update propagates to apps using it

```mermaid
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.<br/>Developer must explicitly redeploy each app<br/>(or hot-reload if change is prompt-only).
```

## Flow G: Portal admin operations

> Interactive viewer with wireframe screens: [`flow.html`](./flow.html#admin-screens). Source: [`flows/flow-g-admin.mmd`](./flows/flow-g-admin.mmd).

```mermaid
sequenceDiagram
    autonumber
    participant Admin as Portal Admin<br/>(browser)
    participant FE as Portal Frontend
    participant Auth as Firebase Auth
    participant BE as Portal Backend
    participant FS as Firestore<br/>(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: developer}
    BE->>BE: assert role=admin
    BE->>FS: create developers/{pending_uid} invited
    BE->>Auth: send invite email (Firebase)
    BE-->>FE: 201 Created
    FE-->>Admin: "Invite sent to dev@company.com"

    Admin->>FE: Settings → Portal → Save Langfuse + GCP
    FE->>BE: PATCH /api/admin/settings {langfuse_host, billing_bq_dataset, ...}
    BE->>FS: portal_settings/global
    BE->>SM: rotate portal-wide secrets if keys changed
    BE-->>FE: 200 OK
    FE-->>Admin: "Settings saved"

    Admin->>FE: Audit → filter all apps, last 7d
    FE->>BE: GET /api/admin/audit?from=...&app_id=*
    BE->>FS: query deployment_events (ordered by timestamp)
    FS-->>BE: events[]
    BE-->>FE: audit rows
    FE-->>Admin: table: deploy / rollback / config_update
```
