# Architecture — Accessible Video Processing Platform

<!-- SCOPE: architecture | owner: ln-112 | generated: 2026-04-29 -->

## System Overview

Three-tier monorepo: React SPA frontend → FastAPI backend → Celery worker pool. Persistent stores are MongoDB Atlas (documents) and Redis (queue + cache). All AI processing happens asynchronously in Celery tasks. All file I/O is via GCS signed URLs.

```
Browser → Apache → FastAPI (sync surface)
                 → Celery Workers (async AI pipeline)
                 → MongoDB Atlas (job state)
                 → Redis (task queue + rate limit state)
                 → GCS (video + VTT files)
```

---

## Job State Machine

16 states. Transitions are one-directional except for the QC feedback loop.

| State | Description | Triggered by |
|-------|-------------|-------------|
| CREATED | Job record created | Upload complete |
| INGESTING | Worker has picked up the job | Celery task start |
| AI_PROCESSING | Gemini 2.5 Pro generating VTT | Ingestion complete |
| PENDING_QC | VTT ready for reviewer | AI processing done |
| QC_FEEDBACK | Reviewer sent feedback, not rejected | Reviewer action |
| APPROVED_ENGLISH | English content approved | QC approve (EN) |
| APPROVED_SOURCE | Source language approved | QC approve (source) |
| TRANSLATING | Google Translate + transcreation running | Approval triggers |
| TTS_GENERATING | Per-cue audio synthesis in progress | Translation done |
| TTS_FAILED | TTS service error — manual retry required | ElevenLabs/Google error |
| RENDERING_VIDEO | FFmpeg compositing accessible video | TTS done |
| RENDER_FAILED | FFmpeg error — manual retry required | FFmpeg error |
| RENDERING_QC | Rendering complete, awaiting final QC | Render done |
| PENDING_FINAL_REVIEW | PM reviewing final deliverables | QC approved |
| REJECTED | Job permanently rejected | Reviewer action |
| COMPLETED | Client notified, signed URLs delivered | PM final approval |

**Terminal states:** COMPLETED, REJECTED.
**Manual-retry states:** TTS_FAILED, RENDER_FAILED.
**Feedback loop:** QC_FEEDBACK → (fix) → PENDING_QC.

---

## Component Map

### Backend (`backend/app/`)

| Layer | Path | Responsibility |
|-------|------|---------------|
| API routes | `api/v1/routes_*.py` | HTTP + WebSocket endpoints, RBAC enforcement |
| Core | `core/security.py` | JWT encode/decode, password hashing |
| Core | `core/authz.py` | RBAC permission checks, `MembershipContext` |
| Core | `core/dependencies.py` | FastAPI DI — `get_current_user`, `get_database` |
| Core | `core/config.py` | Pydantic settings from env vars |
| Models | `models/job.py` | Job document schema + `JobStatus` enum (16 states) |
| Models | `models/user.py` | User document with roles |
| Services | `services/gemini.py` | Gemini 2.5 Pro API wrapper |
| Services | `services/gcs.py` | GCS V4 signed URLs, upload/download |
| Services | `services/language_qc.py` | Per-language QC state machine |
| Services | `services/glossary_service.py` | Hybrid exact + vector glossary retrieval |
| Services | `services/audit_logger.py` | Audit trail — all state-changing actions |
| Services | `services/microsoft_auth.py` | Microsoft SSO JWKS validation |
| Services | `services/websocket.py` | WebSocket connection manager |
| Tasks | `tasks/ingest_and_ai.py` | Main ingestion Celery task |
| Tasks | `tasks/translate_and_synthesize.py` | Translation + TTS pipeline |
| Tasks | `tasks/ffmpeg_operations.py` | Video rendering |
| Middleware | `middleware/rate_limiting.py` | Redis-backed request throttling |
| Middleware | `middleware/validation.py` | MongoDB injection protection |

### Frontend (`frontend/src/`)

| Layer | Path | Responsibility |
|-------|------|---------------|
| Routes | `routes/auth/` | Login, refresh, Microsoft SSO |
| Routes | `routes/jobs/` | Job list, job detail, VTT editor |
| Routes | `routes/admin/` | QC dashboard, audit log, user management |
| Routes | `routes/org/` | Organisation settings, invite members |
| Hooks | `hooks/useJob.tsx` | Job state + API calls |
| Hooks | `hooks/useJobStatusWebSocket.ts` | WS connection with backoff reconnect |
| Contexts | `contexts/GlobalWebSocketContext.tsx` | WS singleton per session |
| Contexts | `contexts/NotificationContext.tsx` | Toast notifications |
| Lib | `lib/auth.ts` | JWT in-memory store, refresh flow |
| Lib | `lib/api.ts` | Axios instance with auth interceptor |
| Components | `components/VttEditor.tsx` | Inline VTT editing with preview |
| Components | `components/VideoWithCaptions.tsx` | Multi-language video player |
| Components | `components/Layout/Sidebar.tsx` | Role-aware navigation |

---

## Auth Architecture

| Token | Storage | TTL | Purpose |
|-------|---------|-----|---------|
| Access token | JS memory (React context) | 15 min | Bearer for all API calls |
| Refresh token | HttpOnly cookie | 7 days | Obtain new access tokens |

**Token flow:** Login → both tokens issued → access token in memory → on expiry, silent refresh via cookie → new access token in memory. On logout, both tokens revoked.

**Critical:** `get_current_user()` in `dependencies.py` must reject refresh tokens used as Bearer tokens (type check on payload).

---

## RBAC Matrix

| Resource | CLIENT | REVIEWER | LINGUIST | PM | ADMIN |
|----------|--------|---------|---------|-----|-------|
| Upload video | ✓ | — | — | — | ✓ |
| View own jobs | ✓ | ✓ | — | ✓ | ✓ |
| View all jobs | — | ✓ | — | ✓ | ✓ |
| Edit VTT | — | ✓ | ✓ | — | ✓ |
| QC approve/reject | — | ✓ | — | — | ✓ |
| Assign linguist | — | — | — | ✓ | ✓ |
| Final review | — | — | — | ✓ | ✓ |
| User management | — | — | — | — | ✓ |
| Audit log | — | — | — | ✓ | ✓ |

Implementation: `authz.py` → `MembershipContext`, `require_org_role(role)`, `require_platform_admin()`.

---

## Security Model

| Control | Implementation | File |
|---------|---------------|------|
| Rate limiting | Redis-backed, 5 req/5 min on login | `middleware/rate_limiting.py` |
| Input validation | MongoDB operator blocklist + Pydantic | `middleware/validation.py` |
| File access | GCS V4 signed URLs, 24h expiry | `services/gcs.py` |
| Audit trail | Every state-changing action logged | `services/audit_logger.py` |
| Secrets | GCP Secret Manager in production | `core/secrets_config.py` |
| Error messages | Generic HTTP errors — no internal detail | `routes_auth.py` |
| Token type check | Reject refresh tokens as Bearer | `core/dependencies.py` |

**Known gaps (from security audit 2026-04-29):** Login endpoint currently bypasses rate limiting (debugging artifact — must be fixed before launch). Microsoft SSO uses synchronous `requests.get()` in async context.

---

## Glossary Retrieval (Hybrid)

Two-pass retrieval for translation prompt injection:

| Pass | Method | Threshold | Limit |
|------|--------|-----------|-------|
| 1 — Exact | String match on source term | — | All matches |
| 2 — Vector | Atlas Vector Search on embedding | ≥ 0.75 similarity | Top 20 |

Merged result: exact matches first, then vector matches, deduplicated, truncated to 50 terms. Injected as a block in the Gemini translation prompt.

**Index:** `glossary_embedding_index` in MongoDB Atlas.

---

## WebSocket Architecture

- Server: `services/websocket.py` — `ConnectionManager` class, org-scoped broadcasts
- Client: `hooks/useJobStatusWebSocket.ts` — exponential backoff reconnect
- Auth: WS upgrade requires valid access token
- Events: `broadcast_to_org(org_id, event)` — no cross-tenant leakage

---

## Maintenance

**Update triggers:** New job state added, auth flow change, new service integrated, RBAC change.
**Verification:** State machine table matches `JobStatus` enum in `models/job.py`. RBAC matrix matches `authz.py` role checks.

<!-- END SCOPE: architecture -->
