docs(architecture): document the tapir serve web surface (ADR-011/012)
The C4 view predated the web transport. Added a "Web surface" section with a component diagram and prose covering: tapir serve (HTMX+Templ over the unchanged store), oidc authenticate-only session, registration gate (users + user_identities), per-user YouTube web connect callback, account disconnect/delete (ADR-013 tapir-side only), immediate summarization via background goroutine + HTMX status poll, and auto/manual summarize mode (migration 006). Relabeled the L2 http node to tapir serve. Corrected the out-of-scope isolation bullet: RLS is live (ADR-012, migration 003), not deferred. ADR-003 stance preserved — the engine/ports/sinks core is untouched; the web is a new transport. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -45,7 +45,7 @@ adapter behind an interface (Clean Architecture ports & adapters).
|
|||||||
```mermaid
|
```mermaid
|
||||||
graph TB
|
graph TB
|
||||||
subgraph tapir["Tapir (Go)"]
|
subgraph tapir["Tapir (Go)"]
|
||||||
http["HTTP server<br/>OAuth callbacks +<br/>user-facing API"]
|
http["tapir serve<br/>(HTMX+Templ web surface:<br/>read summaries, connect,<br/>account, summarize)"]
|
||||||
watcher["Watcher<br/>detects new videos<br/>(WebSub + poll)"]
|
watcher["Watcher<br/>detects new videos<br/>(WebSub + poll)"]
|
||||||
engine["Summarization engine<br/>(use-case core)"]
|
engine["Summarization engine<br/>(use-case core)"]
|
||||||
resolver["Transcript resolver<br/>(captions-first)"]
|
resolver["Transcript resolver<br/>(captions-first)"]
|
||||||
@@ -95,6 +95,66 @@ two codebases (ADR-003).
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## Web surface — `tapir serve` (Stage 1, ADR-011 → ADR-012)
|
||||||
|
|
||||||
|
A later transport added over the **unchanged** engine/ports/sinks core (ADR-003): `tapir serve`
|
||||||
|
is an HTMX+Templ reader/writer (`internal/web`) over the existing `store`. It added no business
|
||||||
|
logic to the engine — it reads the store and, for one action, kicks the existing engine. ADR-011
|
||||||
|
shipped it single-user; ADR-012 opened multi-user with DB-enforced (RLS) isolation.
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
graph TB
|
||||||
|
browser["Browser<br/>(Dex-authenticated user)"]
|
||||||
|
subgraph web["internal/web (tapir serve)"]
|
||||||
|
oidc["oidc<br/>Dex OIDC session<br/>(authenticate-only)"]
|
||||||
|
gate["registration gate<br/>new subject -> /register"]
|
||||||
|
pages["summary list + detail<br/>(read) + actions"]
|
||||||
|
connect["/oauth/youtube/callback<br/>per-user token connect"]
|
||||||
|
account["account<br/>(disconnect, delete)"]
|
||||||
|
summarize["Summarize button<br/>-> background goroutine"]
|
||||||
|
end
|
||||||
|
store[("store<br/>(Postgres, RLS per user)")]
|
||||||
|
engine["Summarization engine<br/>(unchanged core)"]
|
||||||
|
secrets["SecretStore<br/>(per-user token refs)"]
|
||||||
|
|
||||||
|
browser --> oidc
|
||||||
|
oidc --> gate
|
||||||
|
gate --> pages
|
||||||
|
pages --> store
|
||||||
|
connect --> secrets
|
||||||
|
connect --> store
|
||||||
|
account --> store
|
||||||
|
account --> secrets
|
||||||
|
summarize -->|background| engine
|
||||||
|
summarize -->|HTMX status poll| store
|
||||||
|
engine --> store
|
||||||
|
```
|
||||||
|
|
||||||
|
- **Dex OIDC session layer** (`internal/web/oidc`) — **authenticate-only** (ADR-012). It proves
|
||||||
|
*who*; authorization/isolation is the DB's job (RLS), not the session's.
|
||||||
|
- **Registration gate** — a Dex subject with no `users` row is routed to `/register`, which
|
||||||
|
creates the `users` row + the `user_identities` mapping (migration 004). Returning subjects
|
||||||
|
pass straight through.
|
||||||
|
- **Web-initiated YouTube connect** — `/oauth/youtube/connect` → `/oauth/youtube/callback`
|
||||||
|
persists a **per-user** refresh-token ref (`youtube/<userID>/refresh_token`) via `SecretStore`
|
||||||
|
and a `video_connections` row (ADR-006, migration 005). Distinct from the CLI `tapir auth`.
|
||||||
|
- **Account management** — `/account` offers disconnect and **delete account**. Delete removes
|
||||||
|
only Tapir-side state (cascade across the user's tables + secret refs); the shared Dex identity
|
||||||
|
is left intact (ADR-013).
|
||||||
|
- **Immediate summarization** — the web "Summarize" button (`POST /v/{id}/summarize`) fires the
|
||||||
|
engine in a **background goroutine** inside `serve`; the page HTMX-polls `/v/{id}/status`,
|
||||||
|
showing a Charmbracelet spinner while in-flight (and an honest "queued/waiting" state under
|
||||||
|
rate-limiting — ADR-014).
|
||||||
|
- **Summarization mode** — `users.auto_summarize` (migration 006). Auto: every new video is
|
||||||
|
summarized. Manual (default): new videos appear unsummarized; the button sets
|
||||||
|
`videos.summarize_requested`, which the next `tapir run` processes and clears. Both the click
|
||||||
|
path and the batch `tapir run` drive the same unchanged engine.
|
||||||
|
|
||||||
|
The engine, ports, and sink adapters are **untouched** by all of the above — the web surface only
|
||||||
|
reads the store and triggers the existing engine. Adding it changed wiring, not the core (ADR-003).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Sequence — core use case: new video summarized
|
## Sequence — core use case: new video summarized
|
||||||
|
|
||||||
```mermaid
|
```mermaid
|
||||||
@@ -191,6 +251,8 @@ Gherkin features in `docs/use-cases/`).
|
|||||||
|
|
||||||
- Audio-download + speech-to-text resolver (ADR-007) — would be an additional `VideoSource`
|
- Audio-download + speech-to-text resolver (ADR-007) — would be an additional `VideoSource`
|
||||||
fallback path, drawn when built.
|
fallback path, drawn when built.
|
||||||
- Multi-tenant isolation primitives (per-tenant Postgres role, NetworkPolicy, tenant label)
|
- Per-user isolation is **live, not deferred**: Postgres RLS `FORCE`d on every user-owned table
|
||||||
— activate at Stage 1 (ADR-002); single-user Stage 0 doesn't exercise them.
|
(ADR-012, migration 003), realising ADR-002's per-tenant intent at the DB layer. The coarser
|
||||||
|
multi-tenant primitives (per-namespace NetworkPolicy, Kyverno, tenant label) remain a
|
||||||
|
Stage-2 hardening item, not exercised yet.
|
||||||
- Public SaaS surface (sign-up, billing) — Future C, not built (ADR-008).
|
- Public SaaS surface (sign-up, billing) — Future C, not built (ADR-008).
|
||||||
|
|||||||
Reference in New Issue
Block a user