synops/docs/setup/migration_safety.md
vegard 00bf5d27ce Arkitekturbeslutninger: noder er sentrum, edges definerer alt
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>
2026-03-17 10:29:54 +01:00

5.9 KiB

Migration Safety Checklist

Merk (v2): Denne sjekklisten er skrevet for v1-arkitekturen der RLS var basert på workspace_id-kolonner og SET app.current_workspace_id. I v2 er workspace-modellen erstattet av en node-basert tilgangsmatrise (se docs/retninger/bruker_ikke_workspace.md). Sjekklisten må skrives om for det nye mønsteret: node_access-matrise, edge-basert tilgang, og RLS-policies som opererer på bruker→node-edges i stedet for workspace-scope.

Seksjonene under er bevart som referanse for v1-mønsteret.

Sjekkliste for alle som kjører PostgreSQL-migrasjoner — lokalt eller i prod.

Før migrering

  • Les migrasjonfilen og forstå hva den gjør
  • Har migrasjonen en tilhørende down-migrering? (påkrevd for skjema-endringer)
  • Tar migrasjonen backup-hensyn? (dropper den kolonner/tabeller med data?)

Etter migrering

RLS-verifisering (KRITISK)

Etter enhver migrering som oppretter eller endrer tabeller med workspace_id:

-- 1. Verifiser at RLS er aktivert på alle workspace-tabeller
SELECT tablename, rowsecurity
FROM pg_tables
WHERE schemaname = 'public'
  AND tablename IN ('nodes', 'graph_edges', 'messages', 'channels',
                     'media_files', 'job_queue', 'message_attachments')
ORDER BY tablename;
-- Forventet: rowsecurity = true for alle

-- 2. Verifiser at policies eksisterer
SELECT tablename, policyname, cmd, qual
FROM pg_policies
WHERE schemaname = 'public'
ORDER BY tablename;
-- Forventet: workspace_isolation_* policy for hver tabell

-- 3. Test isolasjon: sett workspace A, forsøk å lese workspace B
SET app.current_workspace_id = '<workspace_a_uuid>';
SELECT count(*) FROM nodes WHERE workspace_id = '<workspace_b_uuid>';
-- Forventet: 0 rader (RLS blokkerer)

-- 4. Verifiser at superuser IKKE blokkeres (Rust workers trenger dette)
RESET app.current_workspace_id;
SET ROLE postgres;
SELECT count(*) FROM nodes;
-- Forventet: alle rader synlige

Indeksverifisering

-- Sjekk at viktige indekser finnes
SELECT indexname, tablename
FROM pg_indexes
WHERE schemaname = 'public'
  AND tablename IN ('nodes', 'graph_edges', 'messages')
ORDER BY tablename;

Constraint-verifisering

-- Sjekk at foreign keys er intakte
SELECT tc.table_name, tc.constraint_name, tc.constraint_type
FROM information_schema.table_constraints tc
WHERE tc.table_schema = 'public'
  AND tc.constraint_type IN ('FOREIGN KEY', 'CHECK', 'UNIQUE')
ORDER BY tc.table_name;

RLS Leak Hunter (CI-test)

SET app.current_workspace_id er en skjult single point of failure — en glemt SET i en ny feature, en feil i connection-pool, eller en ny tjeneste som kobler til PG uten middleware kan føre til cross-workspace datalekkasje. Denne testen fanger det opp.

Automatisk CI-test (to-workspace leak detection)

Kjøres i migrasjonstester og som egen CI-steg:

-- Opprett to test-workspaces
INSERT INTO workspaces (id, name, slug) VALUES
  ('aaaaaaaa-0000-0000-0000-000000000001', 'Workspace A', 'ws-a'),
  ('aaaaaaaa-0000-0000-0000-000000000002', 'Workspace B', 'ws-b');

-- Seed testdata i begge
INSERT INTO nodes (id, node_type, workspace_id) VALUES
  ('bbbbbbbb-0000-0000-0000-000000000001', 'tema', 'aaaaaaaa-0000-0000-0000-000000000001'),
  ('bbbbbbbb-0000-0000-0000-000000000002', 'tema', 'aaaaaaaa-0000-0000-0000-000000000002');

-- TEST 1: Sett workspace A, forsøk å lese workspace B
SET app.current_workspace_id = 'aaaaaaaa-0000-0000-0000-000000000001';
DO $$
BEGIN
  IF (SELECT count(*) FROM nodes WHERE workspace_id = 'aaaaaaaa-0000-0000-0000-000000000002') > 0 THEN
    RAISE EXCEPTION 'RLS LEAK: Workspace A kan lese Workspace B sine noder!';
  END IF;
END $$;

-- TEST 2: Uten SET (tom current_setting) skal returnere 0 rader
RESET app.current_workspace_id;
DO $$
BEGIN
  -- For vanlig bruker (ikke superuser) bør dette returnere 0
  IF (SELECT count(*) FROM nodes) > 0 AND current_setting('is_superuser') = 'off' THEN
    RAISE EXCEPTION 'RLS LEAK: Uautentisert tilkobling kan lese data!';
  END IF;
END $$;

Audit-trigger (produksjon)

Valgfri trigger som logger mistenkelige queries i prod:

-- Tabell for RLS-audit
CREATE TABLE IF NOT EXISTS rls_audit_log (
    id BIGSERIAL PRIMARY KEY,
    table_name TEXT NOT NULL,
    operation TEXT NOT NULL,
    current_workspace TEXT,
    session_user TEXT NOT NULL,
    query_timestamp TIMESTAMPTZ NOT NULL DEFAULT now()
);

-- Funksjon som logger når current_workspace_id ikke er satt
CREATE OR REPLACE FUNCTION audit_rls_context() RETURNS TRIGGER AS $$
BEGIN
  IF current_setting('app.current_workspace_id', true) IS NULL
     OR current_setting('app.current_workspace_id', true) = '' THEN
    IF current_setting('is_superuser') = 'off' THEN
      INSERT INTO rls_audit_log (table_name, operation, current_workspace, session_user)
      VALUES (TG_TABLE_NAME, TG_OP, current_setting('app.current_workspace_id', true), session_user);
    END IF;
  END IF;
  RETURN NEW;
END;
$$ LANGUAGE plpgsql;

Kjør leak hunter mot ALLE tabeller med workspace_id — ikke bare de som er listet over. Nye tabeller legges til i listen automatisk via introspeksjon:

-- Finn alle tabeller med workspace_id-kolonne (bør alle ha RLS)
SELECT t.tablename
FROM pg_tables t
JOIN information_schema.columns c ON c.table_name = t.tablename
WHERE c.column_name = 'workspace_id'
  AND t.schemaname = 'public'
  AND NOT EXISTS (
    SELECT 1 FROM pg_policies p WHERE p.tablename = t.tablename
  );
-- Forventet: 0 rader. Enhver rad her = tabell med workspace_id UTEN RLS-policy.

Automatisering

Disse sjekkene kjøres automatisk i migrasjonstestene (se docs/arkitektur.md §10.2). Manuell kjøring er kun nødvendig ved prod-migrasjoner til automatiserte tester er på plass. RLS Leak Hunter bør prioriteres som første CI-steg — den beskytter mot den mest alvorlige feilkategorien (cross-workspace datalekkasje).