synops/docs/concepts/den_asynkrone_gjesten.md
vegard 00bf5d27ce Arkitekturbeslutninger: noder er sentrum, edges definerer alt
Grunnleggende arkitekturbeslutninger tatt og dokumentert:

- Alt er noder (brukere, team, innhold, mediefiler, samlings-noder)
- Edges definerer hva en node er (freeform typer, metadata i JSONB)
- Materialisert tilgangsmatrise (node_access) erstatter workspace-RLS
- Visibility (hidden/discoverable/readable/open) på noder
- Aliaser via usynlige system-edges
- Maskinrommet eier all skriving (SpacetimeDB først, PG asynk)
- SpacetimeDB holder hele grafen, PG er persistent backup
- Node- og edge-skjema spesifisert (docs/primitiver/)

Fjernet workspace-konseptet fra hele dokumentasjonen (~40 filer).
Fem retninger besluttet, én åpen (rom, ikke forum).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-17 10:29:54 +01:00

124 lines
6.3 KiB
Markdown

# Konsept: Den Asynkrone Gjesten
**Filsti:** `docs/concepts/den_asynkrone_gjesten.md`
## 1. Konsept
Mange interessante gjester har ikke tid til å stille i studio. Den Asynkrone Gjesten lar redaksjonen sende en unik lenke til en gjest som kan svare på spørsmål via tale — fra mobilen, når det passer dem. Svarene lander direkte i redaksjonens arbeidsflyt, transkriberes automatisk, og kan brukes i podcasten.
## 2. Brukeropplevelse
### 2.1 Redaksjonens side
1. Redaksjonen oppretter en "Gjestesesjon" knyttet til et Tema.
2. Legger inn spørsmål (tekst) som gjesten skal svare på.
3. Systemet genererer en unik, tidsbegrenset URL.
4. URL-en sendes til gjesten via e-post, SMS eller chat.
5. Gjestens svar (lydmeldinger) dukker opp i Tema-chatten som `voice_memo`-meldinger, automatisk transkribert.
6. Redaksjonen triagerer svarene — kan tagge, klippe inn i episode, eller bruke som research.
### 2.2 Gjestens side
1. Gjesten åpner lenken i mobilnettleseren. Ingen app, ingen konto, ingen registrering.
2. Ser en enkel, ren flate: podcast-logo, spørsmålene fra redaksjonen, og en opptaksknapp per spørsmål.
3. Trykker record, snakker, trykker stopp. Kan lytte tilbake og ta om igjen.
4. Ved innsending lastes lydfilene opp og gjesten ser en bekreftelse.
5. Lenken utløper etter gitt tid eller antall besøk.
### 2.3 Minimal friksjon
- Ingen Authentik-innlogging — tilgang via signert token
- Ingen app — ren PWA/nettleser
- Ingen redigering — gjesten snakker bare
- Responsivt, mobil-first design
## 3. Komponenter
| Feature | Rolle |
|---|---|
| Lydmeldinger | Opptakskomponent gjenbrukes (se `docs/features/lydmeldinger.md`) |
| Chat (channels) | Svarene lander i en channel knyttet til Temaet |
| Live transkripsjon | Whisper transkriberer via jobbkø (se `docs/features/live_transkripsjon.md`) |
| Podcastfabrikken | Lydklipp kan trekkes inn som segment (se `docs/concepts/podcastfabrikken.md`) |
## 4. Autentisering: Gjeste-tokens
### 4.1 Datamodell
```sql
guest_tokens (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
node_id UUID NOT NULL REFERENCES nodes(id) ON DELETE CASCADE, -- Samlings- eller tema-node
guest_name TEXT NOT NULL, -- Visningsnavn ("Erna Solberg")
questions JSONB NOT NULL, -- [{ "sort": 1, "text": "Hva tenker du om...?" }]
token TEXT UNIQUE NOT NULL, -- Kryptografisk sikker, URL-safe token
expires_at TIMESTAMPTZ NOT NULL,
max_recordings SMALLINT DEFAULT 10, -- Maks antall opptak
recordings_count SMALLINT DEFAULT 0,
created_by TEXT NOT NULL REFERENCES users(authentik_id),
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
)
```
### 4.2 Sikkerhet
- Token er kryptografisk tilfeldig (256-bit, URL-safe base64).
- SvelteKit validerer token ved hvert request: sjekker expiry, recordings_count < max_recordings, og node-tilhørighet.
- Gjestens meldinger merkes med `author_id = NULL` og `metadata.guest_name` + `metadata.guest_token_id` for sporbarhet.
- Ingen tilgang til andre noder eller funksjoner.
- Tokenet kan revokeres manuelt av redaksjonen.
### 4.2b Sikkerhetsdybde (mot token-lekkasje og misbruk)
Et lekket gjeste-token gir direkte filopplasting uten autentisering dette er høyrisiko. Følgende tiltak begrenser skadepotensialet:
| Tiltak | Implementering | Formål |
|---|---|---|
| **Rate limiting per token** | SvelteKit middleware: maks 1 opplasting per 30 sek per token | Forhindrer spam/flooding |
| **Filtype-validering** | SvelteKit: kun `audio/*` MIME-typer aksepteres, filstørrelse maks 50 MB | Blokkerer malware-opplasting |
| **Malware-scanning** | ClamAV sidecar-container scanner opplastede filer før de lagres | Fanger kjent malware |
| **Auto-revoke** | Token deaktiveres automatisk når `recordings_count >= max_recordings` | Begrenser eksponering |
| **IP-logging** | Logger klient-IP per opplasting i `guest_token_usage`-tabell | Sporbarhet ved misbruk |
| **Geo-begrensning** (valgfritt) | Caddy-nivå: blokker requests fra uventede geolokasjoner | Reduserer angrepsflate |
**ClamAV Docker-oppsett:**
```yaml
clamav:
image: clamav/clamav:latest
restart: unless-stopped
volumes:
- /srv/synops/media:/scan:ro
networks:
- sidelinja-net
```
SvelteKit kaller ClamAV via `clamdscan` (socket) etter filopplasting, før filen flyttes til endelig plassering. Infiserte filer slettes umiddelbart og tokenet flagges for manuell gjennomgang.
**Fremtidig hardening — prosess-isolasjon:**
Ved økt eksponering (mange aktive guest-tokens, offentlige lenker) bør opplastede filer prosesseres i en isolert kontekst per token. Mulige tilnærminger:
- Firejail/bubblewrap-sandbox for Whisper-prosessering av gjeste-audio
- Dedikert temp-mappe per token som slettes etter prosessering
- Docker sidecar-container for uautentisert filopplasting med egne cgroups
Dette er komplementært til ClamAV (som fanger kjent malware) sandboxing beskytter mot ukjente angrep. Implementeres når gjeste-tokens eksponeres bredere enn redaksjonell bruk.
### 4.3 Flyt (teknisk)
```
Gjest åpner URL med token
→ SvelteKit validerer token
→ Viser spørsmål + opptaksknapp
→ Gjest tar opp svar
→ SvelteKit streamer lydfil til CAS (content-addressable store)
→ Oppretter message (voice_memo) i channelen
→ Oppretter whisper_transcribe-jobb i jobbkøen
→ Inkrementerer recordings_count
→ Redaksjonen ser svaret i Tema-chatten
```
## 5. Dataklassifisering
| Data | Kategori | Detaljer |
|---|---|---|
| Gjestens lydopptak | Kritisk (backup) | Unikt innhold |
| Guest tokens | Flyktig (TTL) | Utløper automatisk, slett expired tokens periodisk |
| Spørsmål (JSONB) | Kritisk (PG) | Redaksjonelt innhold |
## 6. Instruks for Claude Code
* `guest_tokens`-tabellen er **ikke** en node i grafen den er ren tilgangsstyring.
* Gjeste-UI er en egen SvelteKit-rute (`/guest/[token]`) med minimal layout (ingen navbar).
* Gjenbruk lydmeldinger-komponenten ikke bygg en egen opptaksflyt.
* Meldinger fra gjester har `author_id = NULL`. Frontend håndtere dette gracefully (vis `guest_name` i stedet).
* Tokenet skal **aldri** gi tilgang til å lese andre meldinger i channelen gjesten kan kun skrive.
* Tilgang er styrt via node_access. Token bærer node_id eksplisitt.