Dokumenter SpacetimeDB-loven: aldri PG-lekkasje i frontend/workers

CLAUDE.md: Ny seksjon med fire eksplisitte regler for data som frontend viser.
synkronisering.md: §9 Workers som endrer synlig data — riktig vs feil flyt.
adapter_moenster.md: §5 Anti-pattern enrichFromPg — gjentatt 3+ ganger.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
vegard 2026-03-16 17:19:57 +01:00
parent c5bd0c0130
commit 1e065b827d
3 changed files with 85 additions and 4 deletions

View file

@ -82,3 +82,11 @@ CLAUDE.md er eneste startdokument. Alt annet ligger under `docs/`:
- Sjekk alltid relevant doc i `docs/concepts/`, `docs/features/` eller `docs/infra/` før du implementerer
- Sjekk `docs/erfaringer/` for kjente feller før du implementerer med Svelte 5, SpacetimeDB eller adapter-mønsteret
- Etter ferdig implementering av en komponent: dokumenter lærdommer i `docs/erfaringer/`
## SpacetimeDB-loven (les `docs/infra/synkronisering.md` for detaljer)
SpacetimeDB er autoritativ sanntidskilde for data som frontend leser. **All kode** — frontend-adaptere, workers, bakgrunnsjobber — følger disse reglene:
1. **Frontend → SpacetimeDB.** Aldri direkte PG-kall for data som SpacetimeDB eier (chat, kanban, markører). Ingen `enrichFromPg`, ingen PG-polling, ingen metadata-hacks.
2. **Worker → SpacetimeDB → PG.** En worker som endrer data frontend ser (f.eks. AI-vask av meldinger) skriver resultatet til SpacetimeDB via reducer. Sync-workeren persisterer til PG i bakgrunnen. Worker leser fra PG (jobbkø, prompts), men skriver synlig resultat til SpacetimeDB.
3. **Nytt felt = utvid SpacetimeDB-modulen.** Trenger frontend `metadata`, `edited_at` eller `revisjoner`? Legg det til i SpacetimeDB-modulen, ikke hack rundt med PG API-kall fra frontend.
4. **PG er backup, ikke snarvei.** PG-adaptere er fallback for miljøer uten SpacetimeDB. De er ikke en "enklere vei" for nye features.

View file

@ -55,7 +55,28 @@ SpacetimeDB er en varm cache foran PostgreSQL:
- Konsistent datamodell — alt kommer fra én kilde
- Reaksjoner håndteres via SpacetimeDB-tabeller, ikke PG API
## 5. Anbefaling for neste komponent
## 5. Historisk anti-pattern: "PG-lekkasje" i SpacetimeDB-adapteren
Gjentatt feil (mars 2026, minst 3 iterasjoner): Når en ny feature trenger data som SpacetimeDB-modulen ikke har (metadata, edited_at, revisjoner), er det fristende å legge til en `enrichFromPg()`-funksjon som henter fra PG direkte. Dette bryter hele poenget med caching-laget.
**Symptomer:**
- SpacetimeDB-adapteren har `fetch('/api/messages/...')` kall
- Worker skriver til PG først, SpacetimeDB er "best-effort"
- Etter AI-vask vises ikke metadata/revisjoner fordi de bare finnes i PG
- Race conditions mellom SpacetimeDB-oppdateringer og PG-fetch
**Hvorfor det skjer:** Det er raskere å lage en PG API-rute enn å utvide SpacetimeDB-modulen (Rust compile, publish, regenerer bindings). Men det skaper teknisk gjeld som akkumulerer og undergraver hele arkitekturen.
**Riktig løsning når SpacetimeDB mangler et felt:**
1. Legg til feltet i SpacetimeDB Rust-modul (`spacetimedb/src/lib.rs`)
2. Utvid warmup til å laste feltet fra PG
3. Utvid sync til å persistere feltet til PG
4. Worker skriver til SpacetimeDB via reducer
5. Frontend leser kun fra SpacetimeDB
**Aldri:** Legg til `fetch('/api/.../metadata')` i SpacetimeDB-adapteren.
## 6. Anbefaling for neste komponent
Når Kanban eller Whiteboard skal bygges med SpacetimeDB:
@ -64,3 +85,4 @@ Når Kanban eller Whiteboard skal bygges med SpacetimeDB:
3. **Bruk samme factory-mønster.** Felles interface, env-variabel for valg.
4. **Legg til warmup-config** i `channels.config` (eller tilsvarende config-felt).
5. **Test begge adaptere uavhengig** før du integrerer i UI-komponenten.
6. **Sjekk at alle felter frontend trenger finnes i SpacetimeDB-modulen** før du implementerer adapteren. Utvid modulen først hvis nødvendig.

View file

@ -101,7 +101,52 @@ Kanban-kort har en `position`-kolonne (float). To brukere som drar kort samtidig
### 8.3 Chat: Ingen konflikter
Meldinger er append-only. Redigering av egne meldinger er last-write-wins — akseptabelt fordi kun én bruker eier meldingen.
## 9. Implementeringsstatus (mars 2026)
## 9. Workers som endrer data frontend ser
**Kritisk regel:** En worker (Rust) som transformerer data som frontend viser, MÅ skrive resultatet til SpacetimeDB — ikke direkte til PG. PG-oppdatering skjer via sync-worker i bakgrunnen.
### Eksempel: AI-vask av chatmelding
**Riktig flyt:**
```
Frontend viser melding fra SpacetimeDB
→ Bruker trykker ✨
→ SvelteKit oppretter jobb i PG job_queue
→ Worker plukker opp jobb
→ Worker leser prompt + routing fra PG (det er infrastrukturdata, ikke frontend-data)
→ Worker kaller AI Gateway
→ Worker skriver resultat til SpacetimeDB via edit_message reducer
→ SpacetimeDB pusher oppdatering til frontend via onUpdate
→ Sync-worker persisterer til PG i bakgrunnen
```
**Feil flyt (anti-pattern som har blitt implementert gjentatte ganger):**
```
Worker skriver til PG direkte
→ Frontend henter metadata fra PG via enrichFromPg() ← BRYTER ABSTRAKSJONEN
→ SpacetimeDB-oppdatering er "best-effort" ← FEIL PRIORITERING
```
### Hva betyr dette for nye felter?
Når frontend trenger å vise noe nytt (metadata, revisjoner, edited_at), er prosedyren:
1. **Utvid SpacetimeDB-modulen** — legg til feltet i Rust-strukturen
2. **Utvid warmup** — last feltet fra PG ved oppstart
3. **Utvid sync** — persist feltet til PG i bakgrunnen
4. **Worker skriver til SpacetimeDB** — via reducer, aldri direkte PG for synlig data
5. **Frontend leser fra SpacetimeDB** — ingen enrichFromPg, ingen PG API-kall
### Hva kan workers lese fra PG?
Workers kan og bør lese infrastrukturdata direkte fra PG:
- Jobbkø (`job_queue`) — det er jobbens opphavssted
- AI-prompts, modellkonfigurasjon, routing — det er infrastruktur, ikke brukerdata
- Workspace-konfigurasjon
Skillet er: **data frontend viser** → SpacetimeDB. **Data kun worker trenger** → PG direkte.
## 10. Implementeringsstatus (mars 2026)
### Ferdig
- **SpacetimeDB som cache foran PG:** PG er autoritativ, SpacetimeDB er varm cache. Frontend snakker kun med SpacetimeDB.
@ -112,12 +157,18 @@ Meldinger er append-only. Redigering av egne meldinger er last-write-wins — ak
- **PG-fallback** (`web/src/lib/chat/pg.svelte.ts`): Brukes kun når SpacetimeDB ikke er konfigurert. Markert som `readonly: true`.
- **Adapter-mønster:** `ChatConnection`-interface med `send`, `edit`, `delete`, `react` metoder. Factory velger basert på env-variabel.
### Gjenstår
### Gjenstår — brudd på SpacetimeDB-loven som må fikses
- **`enrichFromPg` i spacetime-adapteren** — frontend leser metadata/edited_at fra PG fordi SpacetimeDB-modulen mangler disse feltene. MÅ fjernes ved å utvide SpacetimeDB-modulen.
- **Worker AI-vask skriver til PG direkte** — metadata, body og revisjoner skrives til PG. MÅ endres til å skrive til SpacetimeDB via reducers.
- **Revisjoner kun i PG**`message_revisions`-tabell finnes kun i PG. MÅ speiles i SpacetimeDB-modulen.
- **`ChatMessage` mangler felter** — `metadata` (jsonb), `edited_at` (timestamp) mangler i SpacetimeDB-modulen.
### Gjenstår — andre
- **Workspace-partisjonering (§7):** SpacetimeDB-modulen har `workspace_id`-felt men bruker ikke workspace-token på tilkobling ennå.
- **Pin/konvertering via SpacetimeDB:** Pin og kanban/kalender-konvertering går fortsatt direkte til PG API.
- **Lazy warmup per kanal:** Alle aktive kanaler oppvarmes ved oppstart. Kan optimaliseres til per-kanal ved tilkobling.
## 10. Instruks for Claude Code
## 11. Instruks for Claude Code
- `sync_outbox`-tabellen i SpacetimeDB bør ha et `synced`-flagg og `created_at`-tidsstempel
- Workeren skal bruke jobbkø-infrastrukturen (se `docs/infra/jobbkø.md`) for sin egen helse/observabilitet, men selve pollingen er en egen loop — ikke en vanlig jobb i køen
- Hold sync-payloaden enkel: `{ "table": "chat_messages", "action": "insert", "data": {...} }` — workeren mapper dette til riktig PG-tabell