# 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. ## 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):** maskinrommet (Rust), SvelteKit (når klar). **Docker:** PostgreSQL, SpacetimeDB, Authentik, Caddy, LiteLLM, faster-whisper. Maskinrommet kjører native fordi det trenger tilgang til `claude` CLI og hele vertsmaskinens verktøy. Caddy (Docker) når maskinrommet via `host.docker.internal`. ## Teknologivalg | Rolle | Teknologi | Kjøremodus | Begrunnelse | |-------|-----------|------------|-------------| | Orkestrator | Rust | Native (systemd) | Ytelse, typesikkerhet, eier alle skrivinger, trenger host-tilgang | | Frontend | SvelteKit | Native (systemd) | PWA, SSR, tynt lag mot STDB | | Database | PostgreSQL | Docker | Versjonsstyring, enkel oppgradering | | Sanntid | SpacetimeDB | Docker | Eksperimentelt, offisielt image | | 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 | Docker | Auto-TLS, kan tas native senere | | 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.