server/ARCHITECTURE.md
vegard a5985ef3f8 Dokumentasjon, erfaringslogg, migrasjoner og infra-oppdateringer
- Omorganiser docs/: konsepter, features, infra og proposals i egne mapper
- Ny docs/erfaringer/ med lærdommer fra chat-implementering (Svelte 5, SpacetimeDB, adapter-mønster)
- Oppdater ARCHITECTURE.md: Lag 1 status, ny §10 Erfaringslogg, SpacetimeDB i lokal dev
- Oppdater synkronisering.md med implementeringsstatus og designvalg
- Oppdater lokal.md med SpacetimeDB og AI Gateway
- Utvid PG-skjema med channels, messages, media_files, message_revisions
- Legg til seed_dev.sql, migration_safety.md, .env.example
- Nye feature-specs: chat, kanban, whiteboard, live_ai, lydmeldinger m.fl.
- Nye konsept-specs: studioet, møterommet, redaksjonen, den asynkrone gjesten m.fl.
- SpacetimeDB og AI Gateway i docker-compose.dev.yml
- collect-docs.sh inkluderer erfaringer/

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-15 01:40:14 +01:00

298 lines
No EOL
21 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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, SpacetimeDB, Caddy, Whisper og AI Gateway 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/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.11.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, Whiteboard, Live transkripsjon, Live AI (faktoid + referent), Visuell graf, AI Research-Klipper, Lydmeldinger & Diktering, Podcast-statistikk, Kunnskaps-Bridge (cross-workspace), Prompt-Laboratorium.
## 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)
- [ ] 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)
- [~] Chat med channels (PG-adapter + SpacetimeDB hybrid-adapter ferdig, sync-worker gjenstår)
- [ ] Kanban (SpacetimeDB ↔ PG synk)
- [ ] 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)
- [ ] 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)
- **Disk/Minne:** Mediamappe-størrelse per workspace, PG-størrelse, SpacetimeDB-minnebruk
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 Ingen eksterne observability-tjenester
All overvåking og varsling skjer 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
## 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.