# Nodes — spesifikasjon **Status: Besluttet.** > Alt er noder. Brukere, team, prosjekter, innhold, møter, mediefiler, > kunnskapsgraf-entiteter — alt er rader i `nodes`-tabellen. Hva en > node "er" bestemmes av dens edges, ikke av noden selv. ## Skjema ```sql CREATE TYPE visibility AS ENUM ('hidden', 'discoverable', 'readable', 'open'); CREATE TABLE nodes ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), node_kind TEXT NOT NULL DEFAULT 'content', title TEXT, content TEXT, visibility visibility NOT NULL DEFAULT 'hidden', metadata JSONB NOT NULL DEFAULT '{}', created_at TIMESTAMPTZ NOT NULL DEFAULT now(), created_by UUID REFERENCES nodes(id) ); CREATE INDEX idx_nodes_kind ON nodes (node_kind); CREATE INDEX idx_nodes_created_by ON nodes (created_by); CREATE INDEX idx_nodes_visibility ON nodes (visibility); ``` ## Kolonner ### `id` UUID, generert. Primærnøkkel for alt i systemet. ### `node_kind` Hint om hva noden primært er. Freeform streng — ikke en enum, samme filosofi som edge-typer. Edges kan gi noden flere roller utover dette. Kjente node_kinds: | Kind | Eksempel | |------|----------| | `person` | Vegard, Trond, Bjørn (alias) | | `team` | Podcastteamet | | `collection` | Sidelinja Podcast, Research-gruppa | | `content` | Chatmelding, bloggpost, notat, dagboknotis | | `communication` | Møte, samtale, livesending | | `topic` | Kunnskapsgraf-entitet (Jonas Gahr Støre, Skolepolitikk) | | `media` | CAS-node (lydfil, bilde, video) | | `agent` | AI-agent (Claude, system) | | `system_announcement` | Systemvarsler | | `ai_preset` | AI-verktøy-preset (prompt, modellprofil, kategori) | | `workspace` | Personlig arbeidsflate (én per bruker, auto-provisjonert) | | `work_item` | Oppgave, bug, idé, forslag (se `docs/concepts/arbeidstavlen.md`) | | `cli_tool` | CLI-verktøy med spec, bruk og metadata (se `docs/retninger/unix_filosofi.md`) | Listen vokser organisk etter behov. ### `title` Intern visningsnavn brukt i lister, søkeresultater, mottaksflaten. Universelt — navnet på en person, arbeidstittelen på en bloggpost, navnet på en podcast. - Vegard: `'Vegard'` - Sidelinja: `'Sidelinja Podcast'` - Bloggpost: `'Hvorfor noder er sentrum'` - Chatmelding: `NULL` (har sjelden tittel) **Viktig distinksjon:** `title` er det interne displaynavnet. For publisert innhold er den offentlige overskriften en *egen node* med `title`-edge til artikkelen — fordi den kan ha varianter, A/B-testes, og har en egen forfatter/tidspunkt. Se [publisering](../concepts/publisering.md) § "Presentasjonselementer". ### `content` Ren tekst uten formatering. Brukes til fulltekstsøk og enkel visning. For rike dokumenter (formatert tekst med bilder) genereres `content` automatisk fra `metadata.document` ved lagring. - Bloggpost: teksten uten markup (generert fra `metadata.document`) - Chatmelding: `'Hei, er du klar?'` - Voice memo: `NULL` ved opprettelse, fylles etter transkribering - Brukernode: `NULL` - CAS-node: `NULL` (binærdata lever på disk) For formatert innhold: se `metadata.document` under metadata. ### `visibility` Default synlighet for alle uten eksplisitt edge. Se [noder er sentrum](../retninger/bruker_ikke_workspace.md) for full spesifikasjon av visibility-nivåer og traverseringsregelen. ### `metadata` JSONB for alt typespesifikt som ikke er tittel eller ren tekst: - Person: `{ "display_name": "Vegard", "preferences": { ... } }` - CAS-node: `{ "cas_hash": "abc123", "mime": "audio/mp3", "size_bytes": 84000000 }` - Kommunikasjonsnode: `{ "started_at": "...", "ended_at": "..." }` - Samlings-node: `{ "pruning_profile": "conservative", "theme": "dark" }` - Rikt dokument: `{ "document": { "type": "doc", "content": [...] } }` `metadata.document` inneholder TipTap/ProseMirror JSON for formatert innhold (overskrifter, bilder, blockquotes, etc.). Bilder refererer til CAS-noder via `node_id`. For enkle meldinger (ren tekst) er `document` null — `content` er alt som trengs. Se [universell input](../retninger/universell_input.md) for detaljer. ### `created_at` Tidsstempel, automatisk. ### `created_by` Referanse til noden som opprettet denne noden. For alias-bruk: `created_by` settes til aliasnoden, ikke brukernoden bak. ## Brukernoder En brukernode er en node med `node_kind = 'person'` og en rad i `auth_identities`: ```sql CREATE TABLE auth_identities ( node_id UUID PRIMARY KEY REFERENCES nodes(id) ON DELETE CASCADE, authentik_sub TEXT UNIQUE NOT NULL, email TEXT UNIQUE NOT NULL ); ``` Alt annet — profil, preferanser, relasjoner — er noden og dens edges. Autentiseringstabellen er en tynn bro mellom HTTP-sesjonen og grafen. ## CAS-noder (mediefiler) Binærfiler er noder med `node_kind = 'media'`. Selve biten lever på disk i CAS-lageret. Noden bærer bare metadata. ``` Episode #42 (content) ──has_media──→ CAS-node (media, metadata: { cas_hash: "abc123", mime: "audio/mp3" }) ──has_media──→ CAS-node (media, metadata: { cas_hash: "def456", mime: "text/srt" }) ──belongs_to──→ Sidelinja Podcast (collection) ``` En innholdsnode kan ha mange mediefiler via edges. Pruning-logikken i maskinrommet opererer på CAS-noder — sletter binærfilen fra disk, men noden kan leve videre som tombstone. ## Eierskap og tilgang `created_by` gir redigeringsrett til egne noder. Men sletting og strukturelle endringer scopes av rollen i konteksten: - Du kan alltid redigere noder du opprettet. - Sletting krever `owner` eller `admin` i samlings-noden innholdet tilhører — selv om du opprettet det. - En privat node (ingen samlings-edge) kan slettes fritt av creator. Flere noder kan ha `owner`-tilgang til samme node via tilgangsmatrisen (direkte og transitiv). Forretningslogikk for hva ulike tilgangsnivåer tillater lever i maskinrommet, ikke i databasen.