Grunnleggende arkitekturbeslutninger tatt og dokumentert: - Alt er noder (brukere, team, innhold, mediefiler, samlings-noder) - Edges definerer hva en node er (freeform typer, metadata i JSONB) - Materialisert tilgangsmatrise (node_access) erstatter workspace-RLS - Visibility (hidden/discoverable/readable/open) på noder - Aliaser via usynlige system-edges - Maskinrommet eier all skriving (SpacetimeDB først, PG asynk) - SpacetimeDB holder hele grafen, PG er persistent backup - Node- og edge-skjema spesifisert (docs/primitiver/) Fjernet workspace-konseptet fra hele dokumentasjonen (~40 filer). Fem retninger besluttet, én åpen (rom, ikke forum). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
172 lines
6.6 KiB
Markdown
172 lines
6.6 KiB
Markdown
# Arkitektur — Synops
|
|
|
|
## Visjon
|
|
|
|
Synops er en plattform for redaksjonelt arbeid og podcast-produksjon.
|
|
Ikke en webapp med features — en plattform med primitiver som kan bli
|
|
hva som helst. Sidelinja (podcastredaksjonen) er en tenant som bruker
|
|
Synops.
|
|
|
|
Alt er noder og edges i en graf. En bruker er en node. Et team er en
|
|
node. En mediefil er en node. Hva noe "er" bestemmes av edges, ikke
|
|
av noden selv. Maskinrommet eier alle skrivinger. Frontend er et tynt
|
|
lag som leser grafen fra SpacetimeDB.
|
|
|
|
## Lagmodell
|
|
|
|
```
|
|
┌─────────────────────────────────────┐
|
|
│ GUI (SvelteKit) │
|
|
│ Visninger: spørringer mot STDB │
|
|
└────────┬──────────────────┬─────────┘
|
|
│ intensjoner │ les (sanntid)
|
|
│ │ direkte WebSocket
|
|
┌────────▼────────┐ ┌──────▼──────────┐
|
|
│ Maskinrommet │ │ SpacetimeDB │
|
|
│ (Rust) │ │ Hele grafen │
|
|
│ Eier alle │ │ (noder+edges) │
|
|
│ skrivinger │ └─────────────────┘
|
|
└──┬─────┬─────┬──┘
|
|
│ │ │
|
|
▼ ▼ ▼
|
|
┌─────┐┌─────┐┌─────┐┌─────────────┐
|
|
│ PG ││STDB ││ CAS ││ Whisper, │
|
|
│(bak)││(skr)││ ││ LiteLLM, │
|
|
│ ││ ││ ││ LiveKit ... │
|
|
└─────┘└─────┘└─────┘└─────────────┘
|
|
```
|
|
|
|
### Skrivestien
|
|
GUI → intensjon → Maskinrommet (Rust) → SpacetimeDB (instant) → PG (async)
|
|
|
|
Frontend sender intensjoner (ikke data). Maskinrommet validerer,
|
|
skriver til SpacetimeDB først for umiddelbar oppdatering, deretter
|
|
persisterer til PG asynkront. Maskinrommet leser edges og bestemmer
|
|
hvilke tjenester som trigges.
|
|
|
|
### Lesestien (sanntid)
|
|
SpacetimeDB → GUI (direkte WebSocket)
|
|
|
|
SpacetimeDB holder hele grafen — alle noder og edges. Frontend
|
|
abonnerer via WebSocket med edge-filtre. Visninger er spørringer
|
|
mot STDB, ikke forhåndsdefinerte API-endepunkter.
|
|
|
|
### Lesestien (tunge spørringer)
|
|
GUI → Maskinrommet (Rust) → PG
|
|
|
|
Søk, statistikk, semantisk søk (pgvector), graftraversering
|
|
(AGE/Cypher). For operasjoner der STDB ikke er egnet.
|
|
|
|
## Datamodell
|
|
|
|
### Alt er noder
|
|
Én `nodes`-tabell. Alt er noder: brukere, team, meldinger, oppgaver,
|
|
notater, mediefiler, kommunikasjonsrom, samlings-noder. En bruker er
|
|
en node som tilfeldigvis kan logge inn.
|
|
|
|
Felles skjema: id, innhold, created_at, content_hash (→ CAS).
|
|
Modalitetsspesifikk metadata i JSONB.
|
|
|
|
### Edges definerer alt
|
|
Én `edges`-tabell. Edge-typer er frie strenger. Hva en node "er"
|
|
bestemmes utelukkende av dens edges:
|
|
|
|
- Node + edge til kanal = chatmelding
|
|
- Node + edge til board + status-edge = kanban-kort
|
|
- Node + edge til dato = kalenderoppføring
|
|
- Node + edge til kun bruker = privat notat
|
|
- Node uten edges = løs tanke
|
|
|
|
### Visninger er spørringer
|
|
Visninger er spørringer mot SpacetimeDB med edge-filtre:
|
|
- Chat = noder med kanal-edge, sortert på tid
|
|
- Kanban = noder med board-edge, gruppert på status
|
|
- Kalender = noder med dato-edge, på tidslinje
|
|
- Mottaksflate = noder med edge til deg, vektet på relevans
|
|
|
|
Ingen forhåndsdefinerte visningstyper. Nye visninger er nye filtre.
|
|
|
|
## Input
|
|
|
|
Én universell input-komponent, gjenbrukt overalt. Fanger tekst, lyd,
|
|
bilde, AI, URL. Konteksten (hvor du er) bestemmer hvilke edges som
|
|
legges til. Output er alltid en node.
|
|
|
|
## Struktur uten workspaces
|
|
|
|
Ingen workspace-velger. Ingen `workspace_id`. Samlings-noder gir
|
|
struktur: et team er en samlings-node, et prosjekt er en samlings-node.
|
|
Du ser noder du har tilgang til via dine edges.
|
|
|
|
### Aliaser
|
|
En bruker kan ha alias-noder (f.eks. for ulike roller). Koblet med
|
|
system-edges som er usynlige for traversering.
|
|
|
|
## Maskinrommet
|
|
|
|
Rust-tjeneste med tre operasjoner: **fang**, **prosesser**, **lever**.
|
|
|
|
Eier alle skrivinger. Frontend sender intensjoner, maskinrommet
|
|
validerer og utfører. Edge-drevet ressursorkestrering: maskinrommet
|
|
leser edges og bestemmer hvilke tjenester som spinnes opp.
|
|
|
|
Forvalter også CAS (binærlagring) med intelligent pruning basert
|
|
på modalitet, edges og aksessmønstre.
|
|
|
|
## Sikkerhet
|
|
|
|
### Synlighet (visibility)
|
|
Noder har en visibility-egenskap med fire nivåer:
|
|
- **hidden** — usynlig, kun tilgjengelig via system-edges
|
|
- **discoverable** — kan finnes, men innhold skjult
|
|
- **readable** — innhold lesbart for de med tilgang
|
|
- **open** — tilgjengelig for alle med traverseringssti
|
|
|
|
Traversering respekterer visibility. Du kan ikke følge edges gjennom
|
|
noder du ikke har lov til å se.
|
|
|
|
### Materialisert tilgangsmatrise
|
|
`node_access`-tabell som cacher beregnet tilgang fra edge-grafen.
|
|
Oppdateres ved edge-endring, ikke ved lesing. Rask oppslag — ingen
|
|
rekursiv graftraversering per forespørsel.
|
|
|
|
### Privat er default
|
|
Input uten mottaker-edge er automatisk privat. Ingen ser det.
|
|
Deling er å legge til edges.
|
|
|
|
## Datalag
|
|
|
|
### PostgreSQL
|
|
Persistent backup og arkiv. Alle noder og edges. Fulltekstsøk,
|
|
pgvector (semantisk søk), JSONB. Apache AGE for Cypher ved behov.
|
|
|
|
### SpacetimeDB
|
|
Holder hele grafen — alle noder og edges. Frontend abonnerer via
|
|
WebSocket. Maskinrommet skriver hit først for umiddelbar oppdatering,
|
|
PG synkroniseres asynkront.
|
|
|
|
### CAS (Content-Addressable Store)
|
|
Binærdata (lyd, bilde, video) lagret med hash. TTL basert på
|
|
modalitet, edges og aksesslog. Generert innhold (TTS, thumbnails)
|
|
er en cache som regenereres on-demand.
|
|
|
|
## Teknologivalg
|
|
|
|
| Rolle | Teknologi | Begrunnelse |
|
|
|-------|-----------|-------------|
|
|
| Orkestrator | Rust | Ytelse, typesikkerhet, eier alle skrivinger |
|
|
| Frontend | SvelteKit | PWA, SSR, tynt lag mot STDB |
|
|
| Database | PostgreSQL | Persistent backup, pgvector, fulltekstsøk, AGE |
|
|
| Sanntid | SpacetimeDB | Hele grafen, WebSocket-subscriptions, ~10μs |
|
|
| Binærlagring | CAS (filsystem) | Enkel, deduplisering, ingen ekstern avhengighet |
|
|
| AI Gateway | LiteLLM | Multi-provider, BYOK, OpenRouter fallback |
|
|
| STT | faster-whisper | Lokal, god norsk kvalitet |
|
|
| TTS | ElevenLabs (→ lokal) | Kommersiell start, lokal når kvaliteten holder |
|
|
| Auth | Authentik | SSO, OIDC, self-hosted |
|
|
| Reverse proxy | Caddy | Auto-TLS, enkel config |
|
|
| Lyd/video | LiveKit | WebRTC, self-hosted |
|
|
|
|
## Retninger
|
|
|
|
Arkitekturen er basert på vedtatte retninger dokumentert i
|
|
`docs/retninger/`. Se `docs/retninger/README.md` for oversikt.
|