synops/docs/primitiver/nodes.md
vegard cad3f4b699 Fullfører oppgave 18.1: AI-preset node-type
Implementerer node_kind 'ai_preset' med metadata-validering og
8 standardprompter som seed-data.

Validering i maskinrommet (create_node + update_node):
- prompt (påkrevd, ikke-tom streng)
- model_profile (flash | standard)
- category (standard | custom)
- default_direction (tool_to_node | node_to_tool | both)
- icon (påkrevd, ikke-tom streng)
- color (påkrevd, hex-farge #RRGGBB)

Seed-presets: Rens tekst, Korrektør, Oppsummering, Oversett,
Skriv om for publisering, Trekk ut fakta, Forenkle, Endre tone.

8 enhetstester for valideringsfunksjonen.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-18 06:13:09 +00:00

5.5 KiB

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

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)

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 § "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 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 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:

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.