# Synkronisering: SpacetimeDB ↔ PostgreSQL

## Konsept

SpacetimeDB holder hele grafen (alle noder og edges) i minne.
PostgreSQL er persistent backup og arkiv. Skriving går til begge.
Lesing går fra SpacetimeDB.

```
GUI (SvelteKit)
  │ skriv                    │ les (sanntid)
  ▼                          ▼
Maskinrommet (Rust)          SpacetimeDB ──→ GUI
  │                            ▲
  ├──→ PostgreSQL (persistent) │
  └──→ SpacetimeDB ───────────┘
```

## Hvorfor hele grafen i SpacetimeDB

Node- og edge-skjemaet er minimalt: åtte kolonner på nodes, åtte
på edges. For en liten brukerbase er hele grafen triviell å holde
i minne. Dette forenkler alt:

- Ingen sync-logikk for å bestemme hva som er "aktivt"
- Ingen henting fra PG når noen åpner noe gammelt
- Frontend har alltid alt tilgjengelig via WebSocket
- Én lesekilde — ingen tvetydighet

Når dette *blir* problematisk (hundretusenvis av noder, minnepress),
innføres eviction basert på aksessmønstre. Men det er en
optimaliseringsbeslutning, ikke en arkitekturbeslutning. Modellen
endres ikke.

## Skrivestien

Maskinrommet validerer, så skriver i to steg:

1. **Validering** — tilgangssjekk, unique-constraints, forretningsregler
2. **SpacetimeDB først** — frontend oppdateres umiddelbart (~10μs)
3. **PG asynkront** — persistent backup i bakgrunnen

Frontend er allerede oppdatert mens PG-skrivingen skjer. Brukeren
merker ingenting.

Hvis PG-skrivingen feiler: maskinrommet logger og prøver igjen.
SpacetimeDB har dataen — den er bare ikke persistert ennå. Ingen
datatap så lenge SpacetimeDB kjører.

## Lesestien

Frontend leser **kun fra SpacetimeDB** via WebSocket-subscriptions.
Ingen PG-kall fra frontend for data som vises i sanntid.

Unntak: tunge spørringer som ikke passer sanntidslaget — statistikk,
fulltekstsøk, pgvector-søk, AGE-traverseringer. Disse går
GUI → Maskinrommet → PG.

## Oppvarming (PG → SpacetimeDB)

Ved oppstart av SpacetimeDB laster maskinrommet hele grafen fra PG:

1. Alle noder
2. Alle edges
3. `node_access`-matrisen

Rekkefølge: noder først (edges refererer til noder).
CAS-noder lastes uten binærinnhold (bare metadata).

## Feilhåndtering

| Scenario | Konsekvens | Håndtering |
|----------|-----------|------------|
| SpacetimeDB krasjer | Sanntid forsvinner, upersistert data kan tapes | Restart + oppvarming fra PG. Tap begrenset til skrivinger som ikke rakk PG. |
| PG nede | Persistering stopper | SpacetimeDB serverer lesing og mottar skrivinger. PG-backlog tas igjen ved recovery. |
| Maskinrommet krasjer | Ingen skriving | Frontend ser siste state. Restart plukker opp. |

## SpacetimeDB som utbyttbar

SpacetimeDB er en sanntidscache, ikke en avhengighet. Hvis den
fjernes fra stacken:

- **Sanntid:** PG `LISTEN/NOTIFY` → SvelteKit SSE
- **Skriving:** Maskinrommet → PG direkte
- **Lesing:** Maskinrommet → PG → GUI

Denne fallbacken trenger ikke implementeres, men arkitekturen
skal aldri gjøre den umulig.

## Tilgangsmatrise i SpacetimeDB

`node_access`-matrisen lastes inn i SpacetimeDB ved oppvarming.
Når maskinrommet oppdaterer matrisen i PG (ved edge-endring),
oppdaterer den også SpacetimeDB. SpacetimeDB-modulen bruker
matrisen for å filtrere subscriptions — klienter ser kun noder
de har tilgang til.

## Konflikthåndtering

SpacetimeDB er single-threaded per modul — reducer-funksjoner
serialiseres automatisk. Ingen klassiske race conditions.

Samtidig redigering (to brukere endrer samme node): maskinrommet
serialiserer via SpacetimeDB. Last-write-wins. SpacetimeDB
kringkaster resultatet til alle klienter. PG oppdateres asynkront
med det endelige resultatet.
