synops/docs/arkitektur.md
vegard b5aa5bb243 Fjern SpacetimeDB komplett (oppgave 22.4)
SpacetimeDB er nå helt fjernet fra Synops. Sanntid håndteres av
PG LISTEN/NOTIFY + WebSocket i portvokteren (maskinrommet).

Kode fjernet:
- spacetimedb/ Rust-modul og spacetime.json
- maskinrommet/src/stdb.rs (HTTP-klient for STDB-reducers)
- frontend module_bindings/ (23 auto-genererte filer)
- spacetimedb npm-avhengighet fra package.json
- scripts/test-sanntid.sh (testet STDB-flyt)

Infrastruktur:
- Docker-container stoppet og fjernet fra docker-compose.yml
- Caddy: fjernet /spacetime/* reverse proxy
- maskinrommet-env.sh: fjernet STDB_IP og SPACETIMEDB_*-variabler
- .env.example: fjernet SpacetimeDB-seksjoner

Dokumentasjon oppdatert:
- CLAUDE.md: stack, lagmodell, kjerneprinsipper, driftsmodell
- docs/arkitektur.md: skrivestien, lesestien, datalag, teknologivalg
- docs/retninger/datalaget.md: migrasjonshistorikk, status "fjernet"
- 37 andre docs oppdatert (features, concepts, infra, ops, retninger)
- Alle kode-kommentarer med STDB-referanser oppdatert

Verifisert: maskinrommet bygger og starter OK, frontend bygger OK,
helsesjekk returnerer 200. Caddy reloadet.
2026-03-18 13:39:09 +00:00

9.5 KiB

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 leser sanntidsoppdateringer via WebSocket (PG LISTEN/NOTIFY).

Lagmodell

┌─────────────────────────────────────────┐
│  GUI (SvelteKit) — spatial canvas       │
│  Verktøy-paneler i BlockShell           │
│  Drag-and-drop mellom paneler           │
└────────┬──────────────────┬─────────────┘
         │ intensjoner       │ les (sanntid)
         │                   │ WebSocket
┌────────▼──────────────────┘
│  Maskinrommet (Rust)
│  Orkestrerer + WebSocket-hub
│  PG LISTEN/NOTIFY → WS → GUI
└──┬───────────┬──┘
   │ spawner   │
   ▼           ▼
┌─────────┐ ┌─────┐┌─────┐┌─────────────┐
│ CLI-    │ │ PG  ││ CAS ││ Whisper,    │
│ verktøy │ │     ││     ││ LiteLLM,   │
│ (tools/)│ │     ││     ││ LiveKit ... │
└─────────┘ └─────┘└─────┘└─────────────┘
  ▲ Claude bruker de samme

Skrivestien

GUI → intensjon → Maskinrommet (Rust) → PG → NOTIFY → WebSocket → GUI

Frontend sender intensjoner (ikke data). Maskinrommet validerer og skriver til PG. PG NOTIFY-triggere sender endringer via WebSocket til alle tilkoblede klienter i sanntid. Maskinrommet leser edges og bestemmer hvilke tjenester som trigges.

Lesestien (sanntid)

PG NOTIFY → Maskinrommet (WebSocket-hub) → GUI

Maskinrommet lytter på PG LISTEN/NOTIFY-kanaler og videresender relevante endringer via WebSocket til tilkoblede klienter, filtrert på tilgangsmatrisen.

Lesestien (tunge spørringer)

GUI → Maskinrommet (Rust) → PG

Søk, statistikk, semantisk søk (pgvector), graftraversering (AGE/Cypher).

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 node-/edge-stores 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.

Arbeidsflaten — spatial canvas

Brukergrensesnittet er et spatial canvas der verktøy (chat, kanban, kalender, editor, studio) plasseres som paneler i en fri flate.

Tre lag

  1. Personlig flate — brukerens standard arbeidsflate
  2. Node-flate — default-oppsett fra node-eier/traits
  3. Brukerens tilpasning — overrides lagret per bruker per node

Drag-and-drop som primær interaksjon

To retninger, to semantikker:

  • Dra ut av kontekst → ny node. Innhold som dras fra et verktøy til et annet skaper en ny node med source_material-edge tilbake til originalen. Originalen er uendret.
  • Dra verktøy inn i kontekst → transformer. Et verktøy (AI, editor) som mottar en node anvender sin funksjon direkte på originalen.

Kompatibilitetsmatrisen bestemmer hva som kan dras hvor. Ref: docs/retninger/arbeidsflaten.md, docs/features/universell_overfoering.md

Arbeidsflaten er en node

Canvaset er selv en node med edges til panelene det viser. Layout (posisjon, størrelse, z-index) er metadata på edgen. Arbeidsflater kan nestes — en flate kan vises som panel i en annen.

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 som orkestrerer, ikke gjør arbeid selv.

Eier alle skrivinger. Frontend sender intensjoner, maskinrommet validerer og utfører. Selve prosesseringen delegeres til CLI-verktøy i tools/ — maskinrommet spawner riktig verktøy fra jobbkøen.

Kjerneansvar (forblir i maskinrommet):

  • Auth + tilgangskontroll
  • Intentions (validering, edge-logikk, PG-skriving)
  • Jobbkø (polling, retry, dead letter)
  • CAS-forvaltning og pruning

Alt annet (transkribering, rendering, AI, lyd, RSS) er CLI-verktøy. Claude bruker de samme verktøyene direkte fra terminalen — nyttig for debugging, testing og utvikling.

Ref: docs/retninger/unix_filosofi.md

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

Eneste datakilde. Alle noder og edges. Fulltekstsøk, pgvector (semantisk søk), JSONB. Apache AGE for Cypher ved behov. PG LISTEN/NOTIFY-triggere sender sanntidsoppdateringer.

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.

Driftsmodell: hybrid native + Docker

Egenutviklet kode kjører native på hosten via systemd. Tredjepartstjenester kjører i Docker. Prinsipp: Docker for det vi ikke bygger selv, native for det vi har full kontroll over.

Native (systemd): Caddy, maskinrommet (Rust), SvelteKit. Docker: PostgreSQL, Authentik, LiteLLM, faster-whisper.

Maskinrommet og Caddy kjører native fordi de trenger direkte tilgang til host-ressurser (CLI-verktøy, TLS-konfig). Docker-tjenester eksponerer porter på localhost.

Teknologivalg

Rolle Teknologi Kjøremodus Begrunnelse
Orkestrator Rust Native (systemd) Orkestrerer CLI-verktøy, eier auth+edges, trenger host-tilgang
CLI-verktøy Rust/Shell Native Prosessering (transcribe, render, audio, AI). Delt mellom maskinrommet og Claude
Frontend SvelteKit Native (systemd) PWA, SSR, spatial canvas med verktøy-paneler
Database PostgreSQL Docker Versjonsstyring, enkel oppgradering
Sanntid PG LISTEN/NOTIFY + WebSocket Native (i maskinrommet) Ingen ekstra avhengighet, sanntid fra PG
Binærlagring CAS (filsystem) Native Enkel, deduplisering, ingen ekstern avhengighet
AI Gateway LiteLLM Docker Ferdig image, sjelden oppdatering
AI Agent Claude Code CLI Native Chat-deltaker, spawnes av maskinrommet
STT faster-whisper Docker Modellhåndtering, ferdig image
TTS ElevenLabs (→ lokal) Kommersiell start, lokal når kvaliteten holder
Auth Authentik Docker Kompleks stack (server + worker + Redis)
Reverse proxy Caddy Native (systemd) Auto-TLS, direkte tilgang til konfig
Lyd/video LiveKit Docker WebRTC, self-hosted

Traits — samlingsnoder med evner

Samlingsnoder berikes med traits — navngitte evner som aktiverer spesifikk funksjonalitet i frontend og backend. En samling med publishing-trait blir et nettmagasin. Legg til podcast og den blir et podcaststudio. Fjern chat og diskusjonsfunksjonen forsvinner.

Traits er komposisjon, ikke typer. Forhåndsdefinerte pakker (nettmagasin, podcaststudio, redaksjon osv.) er bare snarveier for vanlige kombinasjoner — brukeren kan tilpasse fritt etterpå.

Rendret innhold (HTML for publiserte artikler, feeds, OG-bilder) lagres i CAS som avledede representasjoner. Caddy serverer direkte fra disk uten å treffe applikasjonslagene.

Full spesifikasjon: docs/primitiver/traits.md Publiseringsflyt: docs/concepts/publisering.md

Retninger

Arkitekturen er basert på vedtatte retninger dokumentert i docs/retninger/. Se docs/retninger/README.md for oversikt.