- Sett opp docker-compose.dev.yml med PostgreSQL, Redis, Caddy og Whisper - Benchmarket faster-whisper (small/medium/large-v3) med norsk tale - Besluttet medium + initial_prompt som standard, SRT som master-format - Ny feature-spec: AI Gateway (LiteLLM) med BYOK og Promptfoo-testing - Definert dataklassifisering (kritisk/gjenskapbar/avledet/flyktig) - Konkretisert backup-strategi med pg_dump, rsync og restore-prosedyre - Splittet repos: sidelinja/server (kode) + sidelinja/sidelinja (innhold) - Oppdatert lokal.md: utviklingsmiljø for kode, ikke prod-replika - Dokumentert transkripsjonspipeline: Whisper SRT → Git → PG (avledet) - Live AI-assistent: small-modell, flyktig logg med 30d TTL Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
200 lines
No EOL
15 KiB
Markdown
200 lines
No EOL
15 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). 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.
|
|
* **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 + fil-backup |
|
|
| 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 |
|
|
|
|
#### 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, Caddy og Whisper lokalt. Volumene er flyktige (`.docker-data/`, gitignored).
|
|
* **SvelteKit HMR:** Kjøres utenfor Docker for rask iterasjon.
|
|
* **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/features/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/features/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 (Sanntid & Arbeidsflyt):** In-memory database for live chat, status på episoder, og live-oppdateringer i studio. Klienten (Svelte) lytter direkte på SpacetimeDB. Strategisk avhengighet, men all persistent data synkes til PostgreSQL — ved eventuelt bortfall kan sanntidslaget erstattes uten tap av data.
|
|
3. **Synkronisering:** Event-drevet med ~5 sek forsinkelse. SpacetimeDB er autoritativ for sanntidsdata (chat, kanban), PostgreSQL for persistent data (kunnskapsgraf, metadata). Detaljer i `docs/features/synkronisering.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 (Feature Ideas)
|
|
Dette er hovedkonseptene plattformen skal støtte. **Merk: Detaljerte tekniske spesifikasjoner, flytskjemaer og datastrukturer for hver av disse ligger i mappen `docs/features/`.**
|
|
|
|
* **Live AI-Assistent i Studio:** Sanntidstranskripsjon via mikrofonene som lytter etter nøkkelord (Named Entity Recognition). Gjør asynkrone oppslag i PostgreSQL og dytter relevante "Faktoider" live til Svelte-grensesnittet via SpacetimeDB mens programlederne snakker.
|
|
* **AI Research-Klipper ("Ctrl+A workflow"):** Et verktøy der redaksjonen limer inn rotete nyhetsartikler. AI-en (OpenRouter) renser, oppsummerer, og trekker ut Aktører og Faktoider som lagres i Kunnskapsgrafen.
|
|
* **Produktivitetssuiten:** En Svelte/SpacetimeDB-basert flate for Kanban-styring av episoder, trådet chat knyttet til Temaer, og kollaborative show notes.
|
|
* **Valgomat:** En publikumsrettet, avansert og interaktiv valgomat drevet av SpacetimeDB for umiddelbar respons og vekting av svar.
|
|
* **Podcast-Statistikk (Privacy First):** Batch-prosessering i Rust som tygger Caddy JSON-logger, dedupliserer lyttere, fjerner bots og lagrer ferdig statistikk i PostgreSQL.
|
|
* **Podcastfabrikken:** System for automatisk og manuell publisering (Whisper transkripsjon, metadata via OpenRouter) og versjonshåndtering/cache-busting ved oppdatering av eksisterende episoder.
|
|
|
|
## 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)
|
|
- [ ] PostgreSQL-skjema (nodes, graph_edges, job_queue)
|
|
- [ ] SpacetimeDB grunnoppsett
|
|
- [ ] SvelteKit skjelett med Authentik-integrasjon
|
|
- [ ] AI Gateway (LiteLLM) oppsett + config
|
|
- [ ] Git-repostruktur for transkripsjoner (SRT-filer)
|
|
|
|
### Lag 2 — Kjernekomponenter (krever Lag 1)
|
|
- [ ] Jobbko-worker (Rust)
|
|
- [ ] Kunnskapsgraf CRUD (SvelteKit server-side)
|
|
- [ ] Produktivitetssuiten: Chat + Kanban (SpacetimeDB <-> PG synk)
|
|
- [ ] Promptfoo testsett for forste jobbtyper (norsk testdata)
|
|
|
|
### Lag 3 — Features (krever Lag 2)
|
|
- [ ] Podcastfabrikken (Whisper SRT -> Git -> PG-avledede formater + episodeside)
|
|
- [ ] AI Research-Klipper (kunnskapsgraf + jobbko + AI Gateway)
|
|
- [ ] Podcast-Statistikk (jobbko + episoder)
|
|
|
|
### Lag 4 — Avansert (krever Lag 3)
|
|
- [ ] Live AI-Assistent (fylt kunnskapsgraf + LiveKit + Whisper small)
|
|
- [ ] 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 Produktivitetssuiten (intern chat), slik at redaksjonen ser det i sin daglige arbeidsflate
|
|
|
|
### 9.4 Ingen eksterne tjenester
|
|
All overvåking og varsling skjer internt i Sidelinja-suiten. Ingen avhengighet til Discord, Slack eller andre tredjepartstjenester.
|
|
|
|
## 10. 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/features/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. |