server/docs/setup/migration_safety.md
vegard 024a91e1b3 Dokumentasjon: arkitekturvurdering — sikkerhet, backup, kostnad, nye forslag
Oppdaterer dokumentasjon basert på tre eksterne arkitekturvurderinger:

- RLS Leak Hunter med CI-test og audit-trigger (migration_safety.md)
- pgvector-migrasjon flyttet til Lag 2, WAL-arkivering med pgBackRest (ARCHITECTURE.md, produksjon.md)
- Off-site backup med rclone, Docker cgroups for workers (ARCHITECTURE.md, produksjon.md)
- Kostnadskontroll i AI Gateway: workspace-budsjett, auto-fallback (ai_gateway.md)
- Gjeste-token sikkerhetsdybde: ClamAV, rate limiting, auto-revoke (den_asynkrone_gjesten.md)
- SpacetimeDB fase 1-vurdering: PG LISTEN/NOTIFY som mellomsteg (synkronisering.md)
- Kritiske events (Aha-markører) flushes umiddelbart (synkronisering.md)
- Ekstern helsesjekk, observability-utvidelser (ARCHITECTURE.md)
- Tre nye forslag: Contradiction Detector, Auto-Highlight Reel, Audience Voice Memo

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-15 03:56:21 +01:00

5.4 KiB

Migration Safety Checklist

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 ARCHITECTURE.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).