Oppdaterer dokumentasjon basert på tre eksterne arkitekturvurderinger: - RLS Leak Hunter med CI-test og audit-trigger (migration_safety.md) - pgvector-migrasjon flyttet til Lag 2, WAL-arkivering med pgBackRest (ARCHITECTURE.md, produksjon.md) - Off-site backup med rclone, Docker cgroups for workers (ARCHITECTURE.md, produksjon.md) - Kostnadskontroll i AI Gateway: workspace-budsjett, auto-fallback (ai_gateway.md) - Gjeste-token sikkerhetsdybde: ClamAV, rate limiting, auto-revoke (den_asynkrone_gjesten.md) - SpacetimeDB fase 1-vurdering: PG LISTEN/NOTIFY som mellomsteg (synkronisering.md) - Kritiske events (Aha-markører) flushes umiddelbart (synkronisering.md) - Ekstern helsesjekk, observability-utvidelser (ARCHITECTURE.md) - Tre nye forslag: Contradiction Detector, Auto-Highlight Reel, Audience Voice Memo Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
346 lines
No EOL
26 KiB
Markdown
346 lines
No EOL
26 KiB
Markdown
# Sidelinja - Architecture Decision Record & System Overview
|
||
|
||
**Dette dokumentet definerer den overordnede arkitekturen, teknologistacken og datamodellen for Sidelinja-suiten. AI-agenter (som Claude Code) SKAL lese og forstå dette dokumentet før de foreslår endringer, skriver kode eller gjør arkitektoniske valg.**
|
||
|
||
## 1. Visjon og Konsept
|
||
Sidelinja er ikke bare en podcast-host; det er et **redaksjonelt operativsystem** og en **kunnskapsgraf**. Målet er å bygge en plattform som sømløst integrerer research, asynkron kommunikasjon (chat), sanntids innspilling (Lyd/Video) og automatisert publisering. Visjonen inkluderer også at plattformen skal fungere som en "live co-host" (virtuell assistent) under innspilling ved å boble opp relevant informasjon fra kunnskapsgrafen i sanntid. Systemet er bygget for full datakontroll, eierskap og minimal bruk av lukkede tredjepartstjenester.
|
||
|
||
## 2. Infrastruktur og DevOps
|
||
* **Produksjonsserver:** Hetzner VPS (Ubuntu, 8 vCPU, 16 GB RAM, 320 GB SSD). Kapasiteten er tilstrekkelig for nåværende behov. Ved behov kan VPS-en dobles (16 vCPU, 32 GB). Mest CPU-krevende tjenester er faster-whisper og LiveKit under samtidig bruk — disse bør overvåkes først ved kapasitetsproblemer.
|
||
* **CPU-ressursstyring:** `faster-whisper` (medium) bruker ~18 min på 30 min lyd og kan stjele CPU fra LiveKit under live-innspilling (risiko for audio glitches). To-lags beskyttelse:
|
||
1. **Docker cgroups (harde grenser):** `docker-compose.yml` skal sette `deploy.resources.limits` på worker-containere: maks 4 CPU og 8 GB RAM for Whisper-workers, slik at LiveKit og PostgreSQL alltid har garantert kapasitet.
|
||
2. **Applikasjonsnivå (dynamisk):** Rust-workeren implementerer en "Resource Governor" som reduserer Whisper-tråder ytterligere (f.eks. `--threads 2`) når et LiveKit-rom er aktivt. Sjekkes via LiveKit room-status i jobbkøen.
|
||
* **Diskstrategi:** 320 GB SSD fylles raskt med råopptak og MP3-er. Tre tiltak:
|
||
1. **Block Storage:** Mediafiler serveres fra en separat Hetzner Block Storage-volum montert på `/srv/sidelinja/media/`, skalerbart uavhengig av OS-disken.
|
||
2. **S3-abstraksjon:** SvelteKit sin filopplasting bør abstrahere lagring bak et S3-kompatibelt grensesnitt (Hetzner Object Storage eller Cloudflare R2), slik at vi kan flytte til ekstern lagring uten å endre applikasjonskode. Caddy kan proxy-e eller redirecte til S3 for servering.
|
||
3. **Arkiveringspolicy:** Råopptak eldre enn 6 mnd flyttes automatisk til Object Storage via nattlig jobb. Kun ferdig-redigerte MP3-er beholdes lokalt for rask servering.
|
||
* **Orkestrering:** Docker / Docker Compose. Alle tjenester kjører i isolerte containere på et internt Docker-nettverk.
|
||
* **Reverse Proxy & Webserver:** **Caddy**. Håndterer all innkommende trafikk for flere domener, automatisk HTTPS (Let's Encrypt), og ruting til interne containere. Port 80/443 er de *eneste* portene som er eksponert mot internett.
|
||
* **Domener:**
|
||
- `sidelinja.org` — Hovedapplikasjon (SvelteKit, media, SpacetimeDB, LiveKit)
|
||
- `auth.sidelinja.org` — Authentik SSO (felles for alle domener)
|
||
- `git.sidelinja.org` — Forgejo
|
||
- `vegard.info` — Separat nettsted, deler SSO med Sidelinja
|
||
* **Kildekode og CI/CD:** **Forgejo** (Selv-hostet Git). To repos:
|
||
- `sidelinja/server` — app-kode, infra, arkitektur, config
|
||
- `sidelinja/sidelinja` — podcastinnhold (transkripsjoner, show notes, research)
|
||
* **Utvikling og Utrulling:** Kode skrives og testes lokalt i WSL2. Infrastruktur-config (docker-compose, Caddy, Authentik) endres direkte i prod. Deploy: push til Forgejo → SSH pull på server → `docker compose up -d --build`.
|
||
|
||
### 2.1 Serverstruktur (Produksjon)
|
||
All persistent data, konfigurasjon og kildekode monteres via Docker Bind Mounts til en fast struktur på vertssystemet, typisk `/srv/sidelinja/`. Dette muliggjør granulert backup.
|
||
|
||
/srv/sidelinja/
|
||
├── docker-compose.yml # Orkestrering
|
||
├── .env # Miljøvariabler (IKKE i Git)
|
||
├── config/ # Konfigurasjonsfiler (Caddy, Authentik, etc.)
|
||
├── data/ # Databaser (Postgres, SpacetimeDB, Forgejo)
|
||
├── media/ # Lydfiler (podcast, råopptak)
|
||
└── logs/ # Caddy access-logger, app-logger
|
||
|
||
### 2.2 Dataklassifisering og backup-strategi
|
||
Alle data i Sidelinja faller i én av fire kategorier. Nye komponenter og features MÅ klassifisere sin data etter dette skjemaet.
|
||
|
||
#### Kategori 1: Kritisk — krever backup
|
||
Data som ikke kan gjenskapes. Tap = permanent informasjonstap.
|
||
|
||
| Data | Lagring | Backup |
|
||
|---|---|---|
|
||
| PostgreSQL (kunnskapsgraf, brukere, metadata, episoder) | `data/postgres/` | Daglig pg_dump + WAL-arkivering (PITR) |
|
||
| Lydfiler (MP3, råopptak) | `media/` | Daglig fil-backup |
|
||
| `.env` (hemmeligheter) | `/srv/sidelinja/.env` | Manuell kopi, ikke i Git |
|
||
|
||
#### Kategori 2: Gjenskapbar fra Git
|
||
Data som lever i Forgejo og kan klones/pulles på nytt.
|
||
|
||
| Data | Lagring | Restore |
|
||
|---|---|---|
|
||
| Kildekode | Git (Forgejo) | `git clone` |
|
||
| Transkripsjoner (SRT master) | Git (Forgejo) | `git clone` |
|
||
| PG-skjema + migrasjoner | Git (Forgejo) | Kjør migrasjoner |
|
||
| Config-filer (Caddyfile, etc.) | Git (Forgejo) | `git clone` |
|
||
| Forgejo-data (repos, issues) | `data/forgejo/` | **Daglig backup som sikkerhetsnett** — kan gjenskapes men tidkrevende |
|
||
|
||
#### Kategori 3: Avledet — kan regenereres
|
||
Data som er deterministisk avledet fra kategori 1 eller 2. Tåler tap — regenereres automatisk.
|
||
|
||
| Data | Kilde | Regenerering |
|
||
|---|---|---|
|
||
| Ren tekst (transkripsjoner) | SRT i Git | Rust-worker parser SRT |
|
||
| Segmenter (tidsstemplet, grafkoblet) | SRT i Git | Rust-worker parser SRT |
|
||
| Full-text søkeindeks | SRT i Git | Rebuild fra Git |
|
||
| SRT → PG-cache | SRT i Git | Reimport via webhook |
|
||
| Redis-cache | Applikasjonsdata | Regenereres automatisk |
|
||
| Caddy-sertifikater | Let's Encrypt | Regenereres automatisk |
|
||
|
||
#### Kategori 4: Flyktig — tåler tap, TTL-styrt
|
||
Arbeidsdata med begrenset levetid. Ryddes automatisk.
|
||
|
||
| Data | Lagring | TTL | Formål |
|
||
|---|---|---|---|
|
||
| Live-transkripsjonslogg | PostgreSQL | 30 dager | Feilsøking av live-assistent |
|
||
| Caddy access-logger | `logs/caddy/` | 90 dager | Podcast-statistikk (batch-prosesseres først) |
|
||
| Jobbkø-historikk (fullførte jobber) | PostgreSQL | 30 dager | Feilsøking |
|
||
| Whisper-modeller | `.docker-data/` (lokal) | Ingen TTL | Re-download fra HuggingFace ved behov |
|
||
|
||
#### Off-site backup (kritisk)
|
||
Lokal backup på samme server beskytter kun mot logiske feil (slettet fil, korrupt dump). Ved fysisk diskfeil eller nodefeil hos Hetzner tapes både produksjon og backup. Kategori 1-data **må** pushes ut av serveren:
|
||
|
||
| Data | Mål | Verktøy | Frekvens |
|
||
|---|---|---|---|
|
||
| PostgreSQL-dumper | Hetzner Object Storage (S3-kompatibel) | `rclone sync` | Daglig etter pg_dump |
|
||
| Lydfiler (media/) | Hetzner Object Storage | `rclone sync` (inkrementell) | Daglig |
|
||
| `.env` | Kryptert kopi i Object Storage | `gpg -c` + `rclone` | Ved endring |
|
||
|
||
**Retensjon off-site:** 90 dager for PG-dumper, ubegrenset for media. Kostnad: ~€5/mnd for 100 GB på Hetzner Object Storage.
|
||
|
||
#### PostgreSQL WAL-arkivering (Point-In-Time Recovery)
|
||
Daglig pg_dump kl. 03:00 betyr opptil 24 timers datatap ved korrupsjon midt på dagen. For å redusere dette til minutter, settes opp kontinuerlig WAL-arkivering:
|
||
|
||
* **Verktøy:** pgBackRest eller WAL-G (foretrukket for S3-kompatibel lagring)
|
||
* **Flyt:** PostgreSQL streamer WAL-segmenter kontinuerlig til Hetzner Object Storage. Ved behov kan databasen gjenopprettes til et vilkårlig tidspunkt (PITR).
|
||
* **Konfigurasjon:** `archive_mode = on`, `archive_command` peker på pgBackRest/WAL-G som pusher til S3.
|
||
* **Full backup:** Ukentlig full backup via pgBackRest, daglige inkrementelle. WAL-segmenter fyller gapet.
|
||
* **Recovery:** `pgbackrest restore --target-time="2026-03-15 13:59:00"` gjenoppretter til minuttet før krasjet.
|
||
* **Kostnad:** Minimal — WAL-segmenter er komprimerte og kompakte. ~1-5 GB/mnd avhengig av skriveaktivitet.
|
||
|
||
#### Retningslinjer for nye komponenter
|
||
Når en ny feature eller komponent introduserer data:
|
||
1. **Klassifiser** — hvilken kategori faller dataen i?
|
||
2. **Dokumenter** — legg til i tabellen over
|
||
3. **Implementer TTL** for kategori 4 — aldri la flyktig data vokse ubegrenset
|
||
4. **Aldri dupliser kilde** — avledede data (kategori 3) skal kunne slettes og regenereres
|
||
5. **Aldri backup avledet data** — det er bortkastet plass og skaper falsk trygghet
|
||
|
||
### 2.3 Lokalt Utviklingsmiljø
|
||
Det lokale miljøet (WSL2) er et **kodeutviklingsmiljø**, ikke en replika av prod. Infrastruktur-config (docker-compose, Caddy, Authentik) testes direkte i prod. **Komplett oppsett: `docs/setup/lokal.md`.**
|
||
|
||
* **Docker Compose Dev:** `docker-compose.dev.yml` spinner opp PostgreSQL, Redis, SpacetimeDB, Caddy, Whisper og AI Gateway lokalt. Volumene er flyktige (`.docker-data/`, gitignored).
|
||
* **Docker Compose Prod:** `/srv/sidelinja/docker-compose.yml` kjører PostgreSQL, Redis, Caddy, Authentik, Forgejo og SvelteKit (`web`-container bygget fra `web/Dockerfile`).
|
||
* **SvelteKit HMR:** Kjøres utenfor Docker lokalt for rask iterasjon. I prod bygges som Docker-container med adapter-node.
|
||
* **Rust Workers:** Kompileres og kjøres lokalt med `cargo run`.
|
||
* **AI Gateway / Whisper:** Lokale instanser for eksperimentering og prompt-testing.
|
||
* **Forgejo/Authentik:** Kjører IKKE lokalt — push direkte til prod-Forgejo.
|
||
|
||
## 3. Teknologistack
|
||
Vi følger et "Best tool for the job"-prinsipp, med en sterk preferanse for minnesikkerhet, ytelse og rene grensesnitt.
|
||
|
||
* **Backend/Automasjon:** **Rust**. Brukes som bakgrunnsworkers (jobbkø), logg-parsing og SpacetimeDB-moduler. Rust er *ikke* en API-server — SvelteKit server-side håndterer all HTTP-kommunikasjon og PG-tilgang direkte (se `docs/infra/api_grensesnitt.md`).
|
||
* **Frontend / UI:** **SvelteKit** (med TypeScript). Bygges som en PWA. Valgt for ytelse og enkel integrasjon med WebRTC og vanilla JS-biblioteker.
|
||
* **Sanntids Lyd/Video:** **LiveKit** (Selv-hostet). Håndterer WebRTC, fler-bruker videochat og opptak i det virtuelle "studioet".
|
||
* **AI / Prosessering:** `faster-whisper` (lokal transkripsjon) og **LiteLLM** (AI Gateway — sentralisert ruting til Gemini, Claude, Grok, OpenRouter). All AI-kode peker på `http://ai-gateway:4000/v1`, aldri direkte til leverandører. Se `docs/infra/ai_gateway.md`.
|
||
* **SSO / Autentisering:** **Authentik** (Selv-hostet). Sentralisert rollestyring.
|
||
|
||
## 4. Den To-delte Databasestrategien
|
||
1. **PostgreSQL (Historikk & Kunnskapsgraf):** Én sentralisert instans i Docker for brukerkontoer, Git-metadata, aggregert statistikk og Kunnskapsgrafen (Artikler, Faktoider).
|
||
2. **SpacetimeDB (Sanntidsbuffer):** In-memory database for live chat, status på episoder, og live-oppdateringer i studio. Klienten (Svelte) lytter direkte på SpacetimeDB. SpacetimeDB er en **ren sanntidsbuffer** — all data synkes til PostgreSQL innen ~5 sek. PostgreSQL-skjemaet dekker alle SpacetimeDB-tabeller, slik at SpacetimeDB kan erstattes med PG `LISTEN/NOTIFY` + SSE uten arkitekturendring. Se `docs/infra/synkronisering.md` §1.1–1.2.
|
||
3. **Synkronisering:** Event-drevet med ~5 sek forsinkelse. SpacetimeDB er autoritativ for sanntidsdata (chat, kanban), PostgreSQL for persistent data (kunnskapsgraf, metadata). Detaljer i `docs/infra/synkronisering.md`.
|
||
|
||
### 4.1 Workspace-modellen (Multi-tenancy)
|
||
Sidelinja er designet for multi-tenancy fra dag én. Hver organisasjon/podcast opererer i sin egen **workspace** — en streng datasilo som sikrer full isolasjon av kunnskap og arbeidsflyt.
|
||
|
||
#### Prinsipp: Ingenting deles på tvers
|
||
* **PostgreSQL:** Supertabellen `nodes` og `graph_edges` har obligatorisk `workspace_id`. Row-Level Security (RLS) sikrer at spørringer aldri lekker data mellom workspaces. SvelteKit setter `SET app.current_workspace_id` ved tilkobling.
|
||
* **SpacetimeDB:** WebSocket-tilkoblinger bærer et `workspace_id`-token. Modulen partisjonerer minnet og kringkaster kun til klienter i samme workspace.
|
||
* **Mediefiler:** Lagres i `/srv/sidelinja/media/{workspace_slug}/`. Caddy ruter basert på domene.
|
||
* **Transkripsjoner:** Ett Forgejo-repo per workspace for SRT-filer.
|
||
* **Jobbkø:** Alle jobber i `job_queue` merkes med `workspace_id`. Rust-workers kjører som superuser (bypasser RLS) og isolerer via applikasjonskode.
|
||
* **AI-prompts:** Whisper `initial_prompt` og LLM system-prompts lagres i `workspaces.settings` (JSONB) per workspace.
|
||
|
||
#### Tilgangsstyring
|
||
Authentik styrer gruppetilhørighet. SvelteKit mapper innlogget brukers Authentik-grupper til tilgjengelige workspaces via `workspace_members`-tabellen. UI-et har en global "Workspace-switcher" som tømmer lokal state (hard reset) ved bytte for å forhindre visuell datalekkasje.
|
||
|
||
#### Globale ressurser (unntak fra silo)
|
||
* `relation_types` — deles på tvers (systemdefinerte relasjonstyper).
|
||
* `users` — Authentik-IDer er globale, workspace-tilhørighet styres via `workspace_members`.
|
||
* Valgomat-modulen er publikumsrettet og opererer potensielt på tvers av workspaces. Eierskapsmodellen avklares ved implementering (se `docs/concepts/valgomaten.md`).
|
||
|
||
## 5. Datamodell: Kunnskapsgrafen
|
||
Systemet er bygget rundt **Temaer** og **Aktører**, ikke episoder. Dette bygger et asynkront research-arkiv. Alle entiteter arver UUID fra en felles `nodes`-supertabell som gir ekte FK-integritet i grafmodellen (detaljer i `docs/features/kunnskapsgraf_og_relasjoner.md`).
|
||
* **Tema (Saker):** Levende konsepter ("Skolepolitikk").
|
||
* **Aktør (Entity):** Personer eller organisasjoner ("Jonas Gahr Støre").
|
||
* **Faktoide (Factoid):** En atomisk bit med informasjon koblet til Aktører/Temaer ("Søkte jobb i AP i 2011").
|
||
* **Episode:** Et tidsbegrenset prosjekt ("Episode 42") som samler et utvalg av aktuelle *Temaer*.
|
||
* **Segment:** En tidsavgrenset del av en episode med egen transkripsjon, koblet til Temaer/Aktører i grafen. Muliggjør presise oppslag ("hva sa vi om X i Episode 42?").
|
||
* **Research-klipp:** Råtekst renset av AI, koblet til Temaer/Aktører.
|
||
|
||
**Transkripsjoner — eierskapsmodell:**
|
||
* **Git (Forgejo)** er kilde til sannhet. Master-formatet er **SRT** (SubRip) — et etablert undertekstformat med tidsstempler som er redigerbart, diffbart og lett å parse. Whisper leverer SRT direkte (`response_format=srt`).
|
||
* **PostgreSQL** lagrer alt avledet fra SRT-kilden:
|
||
- **Ren tekst** — strippes fra SRT for lesbart publiseringsdokument
|
||
- **Segmenter** — tidsstemplede utdrag koblet til Aktører/Temaer i kunnskapsgrafen
|
||
- **Full-text søkeindeks** — for oppslag på tvers av episoder
|
||
* **Flyt:** Whisper → SRT → Git commit → Forgejo webhook → Rust-worker parser SRT → avledede formater i PG. SvelteKit serverer SRT fra Git og avledet innhold fra PG.
|
||
|
||
## 6. Podcast Hosting og Distribusjon
|
||
* **Lagring:** MP3-filer lagres flatt i `/srv/sidelinja/media/`. Ingen lydfiler i databaser.
|
||
* **Servering:** Caddy serverer media-mappen. MÅ ha `Accept-Ranges: bytes` aktivert for podcast-streaming.
|
||
* **RSS-Feed:** Genereres av SvelteKit og leveres statisk eller dynamisk med aggressiv caching.
|
||
|
||
## 7. Planlagte Funksjoner
|
||
Detaljerte spesifikasjoner ligger i `docs/concepts/` (brukeropplevelser) og `docs/features/` (byggeklosser).
|
||
|
||
### Konsepter (brukeropplevelser)
|
||
* **Studioet:** Podcast-innspilling med LiveKit, live AI faktoid-oppslag og Aha-markør.
|
||
* **Møterommet:** LiveKit-basert møterom med AI-referent, off-the-record, whiteboard, scratchpad og søkbar historikk.
|
||
* **Redaksjonen:** Daglig arbeidsflate med trådet chat (channels), Kanban, show notes og AI research-klipper.
|
||
* **Podcastfabrikken:** Automatisert publiseringspipeline — Whisper, AI-metadata, RSS, cache-busting.
|
||
* **Kunnskapsgrafen:** Visuell utforsking og redigering av kunnskapsnettverk (D3.js/Vis.js).
|
||
* **Valgomaten:** Publikumsrettet, crowdsourced valgomat med PCA og Sidelinja Explorer.
|
||
* **Den Asynkrone Gjesten:** Tidsbegrenset lenke til gjester for asynkrone lydopptak som lander i redaksjonens arbeidsflyt.
|
||
|
||
### Features (byggeklosser)
|
||
Chat (channels), Kanban, Kalender, Notater/Scratchpad, Whiteboard, Live transkripsjon, Live AI (faktoid + referent), Visuell graf, AI Research-Klipper, Lydmeldinger & Diktering, Podcast-statistikk, Kunnskaps-Bridge (cross-workspace), Prompt-Laboratorium, Graf-vedlikehold (nattlig jobb som finner isolerte noder og foreslår koblinger basert på co-occurrence i transkripsjoner).
|
||
|
||
## 8. Bygge-rekkefølge (Avhengighetskart)
|
||
|
||
### Lag 0 — Infrastruktur
|
||
- [x] Produksjonsserver: PostgreSQL, Caddy, Authentik, Forgejo, Redis
|
||
- [x] Lokalt utviklingsmiljo: docker-compose.dev.yml (PostgreSQL, Redis, Caddy)
|
||
- [x] Rust toolchain (lokal + server)
|
||
- [x] faster-whisper-server (lokal, testet med medium + prompt)
|
||
|
||
### Lag 1 — Fundament (ingen avhengigheter)
|
||
- [x] Workspace-modell (workspaces, workspace_members, RLS-policies)
|
||
- [x] PostgreSQL-skjema (nodes m/workspace_id, graph_edges, job_queue, messages, channels, media_files)
|
||
- [x] SpacetimeDB grunnoppsett (Docker, Rust WASM-modul, TypeScript-bindings)
|
||
- [x] SvelteKit skjelett med Authentik-integrasjon + Workspace-switcher
|
||
- [x] AI Gateway (LiteLLM) oppsett + config
|
||
- [ ] Git-repostruktur for transkripsjoner (ett repo per workspace)
|
||
|
||
### Lag 2 — Kjernekomponenter (krever Lag 1)
|
||
- [ ] Jobbkø-worker (Rust)
|
||
- [ ] Kunnskapsgraf CRUD (SvelteKit server-side)
|
||
- [ ] pgvector-migrasjon (0005): `CREATE EXTENSION vector;` + embedding-kolonner på nodes — gjøres tidlig for å unngå smertefull migrasjon i Lag 4
|
||
- [ ] RLS Leak Hunter i CI (se `docs/setup/migration_safety.md`)
|
||
- [~] Chat med channels (PG-adapter + SpacetimeDB hybrid-adapter ferdig, sync-worker gjenstår)
|
||
- [~] Kanban (PG-adapter ferdig med drag & drop, redigeringsmodal, CRUD API. SpacetimeDB-sync gjenstår)
|
||
- [~] Kalender (PG-adapter ferdig med månedsvisning, fargekoder, heldags/tidshendelser. SpacetimeDB-sync gjenstår)
|
||
- [~] Notater/Scratchpad (PG-adapter ferdig med auto-save, debounce, tittel+innhold. Rich text og SpacetimeDB-sync gjenstår)
|
||
- [ ] Lydmeldinger & Diktering (opptak + Whisper + AI-opprydding)
|
||
- [ ] Prompt-Laboratorium (prompt-testing mot egne data)
|
||
- [ ] Promptfoo testsett for første jobbtyper (norsk testdata)
|
||
|
||
### Lag 3 — Features (krever Lag 2)
|
||
- [ ] Podcastfabrikken (Whisper SRT → Git → PG-avledede formater + episodeside)
|
||
- [ ] AI Research-Klipper (kunnskapsgraf + jobbkø + AI Gateway)
|
||
- [ ] Podcast-Statistikk (jobbkø + episoder)
|
||
- [ ] Whiteboard (sanntids frihåndstavle i SpacetimeDB)
|
||
- [ ] Den Asynkrone Gjesten (gjeste-tokens + lydmeldinger)
|
||
|
||
### Lag 4 — Avansert (krever Lag 3)
|
||
- [ ] Studioet: Live AI-Assistent (fylt kunnskapsgraf + LiveKit + Whisper small)
|
||
- [ ] Møterommet: AI-Referent (LiveKit + Whisper + møte-oppsummering)
|
||
- [ ] Visuell Kunnskapsgraf (D3.js/Vis.js graf-visning)
|
||
- [ ] Kunnskaps-Bridge (pgvector, cross-workspace discovery)
|
||
- [ ] Graf-vedlikehold (nattlig jobb: finn isolerte noder, foreslå koblinger basert på co-occurrence)
|
||
- [ ] Valgomat (selvstendig, lav prioritet)
|
||
|
||
## 9. Observabilitet
|
||
|
||
### 9.1 Helse
|
||
Alle Docker-containere skal ha `healthcheck` definert i `docker-compose.yml`:
|
||
- PostgreSQL: `pg_isready`
|
||
- SpacetimeDB: TCP-sjekk mot intern port
|
||
- Caddy: `curl -f http://localhost/health`
|
||
- SvelteKit: `curl -f http://localhost:3000/health`
|
||
- Rust Workers: Heartbeat-rad i `job_queue` (en `worker_heartbeat`-jobb som re-enqueuer seg selv hvert minutt — fravær betyr død worker)
|
||
|
||
### 9.2 Logging
|
||
- **Format:** Strukturert JSON fra alle komponenter (Rust, SvelteKit, Caddy)
|
||
- **Plassering:** `/srv/sidelinja/logs/` med undermapper per tjeneste
|
||
- **Rotasjon:** Standard Linux logrotate, daglig rotasjon, 30 dagers retensjon
|
||
- Caddy podcast-logger behandles separat av statistikk-workeren (se `docs/features/podcast_statistikk.md`)
|
||
|
||
### 9.3 Jobbkø-overvåking
|
||
- Admin-visning i SvelteKit som viser `job_queue`-status (pending, running, error-count)
|
||
- Feilede jobber (`status = 'error'`) poster automatisk en varslingsmelding til et dedikert system-tema i Redaksjonens chat, slik at redaksjonen ser det i sin daglige arbeidsflate
|
||
|
||
### 9.4 Observability Dashboard
|
||
SvelteKit-appen inkluderer en intern admin-side (`/admin/observability`) som samler:
|
||
- **Container-status:** Healthcheck-resultater fra Docker (via `docker compose ps` / Docker socket)
|
||
- **Jobbkø:** Pending/running/error-count med sparkline-grafer (siste 24t)
|
||
- **AI Gateway:** Token-bruk per jobbtype, kostnad per workspace, failover-hendelser (fra LiteLLMs innebygde logging). Inkluderer workspace-budsjett status (se `docs/infra/ai_gateway.md` §6).
|
||
- **Disk/Minne:** Mediamappe-størrelse per workspace, PG-størrelse, SpacetimeDB-minnebruk (med graf over tid)
|
||
- **Sikkerhet:** Siste secret-rotasjon timestamp (`.env`-endringer), RLS Leak Hunter siste kjøring, antall aktive guest-tokens
|
||
- **SpacetimeDB:** Minnebruk-graf, `sync_outbox`-størrelse (indikerer sync-etterslep), tilkoblede klienter per workspace
|
||
|
||
Ingen eksterne tjenester (Prometheus, Grafana) — alt bygges som SvelteKit-sider med data hentet server-side fra PG, Docker og LiteLLM. Konsistent med self-hosted-filosofien.
|
||
|
||
### 9.5 Ekstern helsesjekk (utenfor stacken)
|
||
Intern overvåking er verdiløs hvis hele serveren er nede. En ekstern uptime-monitor **utenfor** Hetzner-stacken skal polle følgende endepunkter og varsle ved feil:
|
||
|
||
| Endepunkt | Sjekk | Varsel |
|
||
|---|---|---|
|
||
| `https://sidelinja.org/api/health` | HTTP 200 | E-post/push ved 2 min nedetid |
|
||
| `https://auth.sidelinja.org` | HTTP 200 | E-post/push ved 2 min nedetid |
|
||
| `sidelinja.org:443` | SSL-utløp < 7 dager | E-post |
|
||
|
||
**Implementering:** Bruk en gratis/billig ekstern tjeneste (UptimeRobot, Hetrixtools, eller lignende) — dette er det eneste unntaket fra self-hosted-filosofien, da en helsesjekk per definisjon må leve utenfor systemet den overvåker.
|
||
|
||
### 9.6 Ingen andre eksterne observability-tjenester
|
||
Utover ekstern helsesjekk (§9.5) skjer all overvåking og varsling internt i Sidelinja-suiten. Ingen avhengighet til Discord, Slack eller andre tredjepartstjenester.
|
||
|
||
## 10. Erfaringslogg
|
||
Mappen `docs/erfaringer/` samler praktiske lærdommer fra implementering — ikke hva vi valgte, men hva vi lærte som ikke er åpenbart fra koden. Formålet er å treffe raskere blink med neste komponent. Nye komponenter BØR legge til erfaringer etter ferdig implementering.
|
||
|
||
Innhold per mars 2025:
|
||
- `svelte5_reaktivitet.md` — $state-getters, SSR-feller, polling-mønster
|
||
- `spacetimedb_integrasjon.md` — SDK-konvensjoner, BigInt, Rust borrow-feller
|
||
- `adapter_moenster.md` — Hybrid PG+SpacetimeDB, anti-patterns, anbefaling for neste komponent
|
||
- `authentik_oidc.md` — Sub-claim er SHA256, @auth/sveltekit JWT-quirks, redirect URI
|
||
|
||
## 11. Testing og Utrulling
|
||
|
||
### 11.1 Teststrategi
|
||
Sidelinja har tre testnivåer. Alle nye features MÅ dekke de relevante nivåene.
|
||
|
||
| Nivå | Hva | Verktøy | Kjøres |
|
||
|---|---|---|---|
|
||
| **Enhetstester** | Rust-logikk (jobbkø, sync, parsing) | `cargo test` | Lokalt + CI |
|
||
| **Migrasjonstester** | PG-skjema: opp-migrering mot tom DB + seed + spørringer | `psql` + testskript | Lokalt + CI |
|
||
| **Prompt-regresjon** | LLM-prompts mot norske testsett | Promptfoo | Lokalt + CI (månedlig cron) |
|
||
|
||
**E2E-tester** (SvelteKit → PG → SpacetimeDB) innføres når Lag 2 er stabilt. Inntil da er manuell testing i dev-miljøet tilstrekkelig — kodebasen er liten nok til at det er effektivt.
|
||
|
||
### 11.2 Migrasjonstesting
|
||
Migrasjonsfiler i `migrations/` testes automatisk:
|
||
1. Spin opp en tom PostgreSQL-container
|
||
2. Kjør alle migrasjoner sekvensielt
|
||
3. Kjør seed-data (fixture) med testdata
|
||
4. Verifiser: RLS-policies blokkerer cross-workspace-tilgang, indekser eksisterer, constraints holder
|
||
5. Kjør `pg_dump --schema-only` og diff mot forrige kjente state
|
||
|
||
### 11.3 Utrullingsprosedyre (Deployment Checklist)
|
||
```
|
||
1. [ ] Alle tester grønne lokalt (cargo test, migrasjonstester, promptfoo)
|
||
2. [ ] Push til Forgejo (main branch)
|
||
3. [ ] SSH til prod: ssh sidelinja@157.180.81.26
|
||
4. [ ] cd /srv/sidelinja && git pull
|
||
5. [ ] Nye migrasjoner? → Kjør mot prod-PG (manuelt, verifiser først)
|
||
6. [ ] docker compose up -d --build
|
||
7. [ ] Verifiser healthchecks: docker compose ps (alle "healthy")
|
||
8. [ ] Smoke-test: curl https://sidelinja.org/health
|
||
9. [ ] Sjekk logs: docker compose logs --tail=50 <tjeneste>
|
||
```
|
||
|
||
**Rollback:** `git revert` + ny deploy. Migrasjoner som endrer skjema MÅ ha en tilhørende down-migrering.
|
||
|
||
### 11.4 Rate Limiting
|
||
Ressurskrevende endepunkter beskyttes mot overbruk:
|
||
|
||
| Endepunkt | Begrensning | Mekanisme |
|
||
|---|---|---|
|
||
| LiveKit (romopprettelse) | Maks 5 aktive rom per workspace | Applikasjonslogikk i SvelteKit |
|
||
| Whisper (transkripsjon) | Maks 3 samtidige jobber per workspace | Jobbkø-constraint (`max_concurrent_per_workspace`) |
|
||
| AI Gateway (LLM-kall) | LiteLLMs innebygde rate limiting | `max_parallel_requests` i config.yaml |
|
||
| Filopplasting (media) | Maks 500 MB per fil, 5 GB per workspace/dag | SvelteKit middleware |
|
||
|
||
Caddy håndterer generell rate limiting (DDoS-beskyttelse) via `rate_limit`-direktivet.
|
||
|
||
## 12. AI Agent Guidelines (Instrukser for Claude Code)
|
||
* **Start her:** Når du settes til å bygge en ny komponent, sikre alltid at det lokale utviklingsmiljøet (`docker-compose.dev.yml`) kjører først.
|
||
* **Dokumentasjonsstandard:** Når du skal implementere en ny funksjon (feature), sjekk ALLTID om det finnes et dokument i `docs/features/<feature-navn>.md` først. Oppdater disse dokumentene hvis arkitekturen for funksjonen endres.
|
||
* **Ingen "gh" CLI:** Vi bruker Forgejo. For Pull Requests/Issues, bruk `tea` CLI.
|
||
* **Deployment:** Kod og test lokalt i WSL. Push til Forgejo, logg inn via SSH for å pulle kode og restarte containere/tjenester (`docker compose up -d`).
|
||
* **Asynkron AI:** Tyngre jobber (Whisper, OpenRouter) skal aldri blokkere web-forespørsler. Alle bakgrunnsjobber kjøres via den felles PostgreSQL-baserte jobbkøen (se `docs/infra/jobbkø.md`).
|
||
* **Sikkerhet:** Forsøk aldri å eksponere databaseporter ut mot internett i Docker Compose-filer (hverken lokalt eller i prod). Port 80/443 (Caddy) er de eneste inngangsportene. |