Terraform blir enda vägen, och backup ett obligatoriskt val
Kustomize- och Argo CD-vägen är borttagen. Den beskrev samma system en
gång till och kunde inte köras samtidigt som Terraform utan att de
motarbetade varandra — selfHeal återställde det Terraform ändrade och
prune tog bort det Terraform skapade. Kvar utanför Terraform är bara
postgres-init.sql, som läses av både definitionen och integrationstestet
så att schemat inte kan glida isär från det som testas.
Säkerhetskopieringen var en förhoppning: en StatefulSet med en volym och
ingen kopia. Går volymen förlorad är det inte "data" som försvinner utan
varje ärendes bevisvärde — vad som kontrollerades, av vem, när, med
vilken evidens — och det går inte att återskapa i efterhand.
databas_lage är därför ett obligatoriskt val utan standardvärde:
extern managerad Postgres, leverantörens backup och PITR
(rekommenderat i produktion)
cnpg CloudNativePG i klustret: basbackup 02:30, kontinuerlig
WAL-arkivering till objektlagring, PITR och failover
inbyggd en volym, ingen backup — spärras av en precondition när
miljön är produktion
Preconditions fångar felkonfiguration vid plan i stället för vid drift:
extern utan anslutning, cnpg utan backupmål eller nycklar, inbyggd i
produktion.
Driftsättningen är nu två åtskilda flöden. Publicera bygger och taggar
bilderna vid varje main-push; Driftsätt startas för hand med en tagg mot
en GitHub-miljö som kan kräva godkännande, kör fmt/init/validate/plan/
apply, skriver ut kartan och rökkontrollerar hälsa och API-spec. En bild
i registret är inte samma sak som en bild som kör. Rollback är att köra
Driftsätt igen med en tidigare tagg. CI kör dessutom terraform validate
på varje PR — den kontroll jag inte kunde köra själv.
Två fel hittade vid egengranskning av definitionen: schemafilen delades
på semikolon, vilket hade klippt itu plpgsql-funktionen med
append-only-triggern (nu hela filen via postInitApplicationSQLRefs), och
null-satta fält i kubernetes_manifest utelämnas nu i stället.
Verifierat: 87 vitest-tester, typkontroll, eslint, OpenAPI-validering,
terraform fmt, statisk referenskontroll av modulen och integrationstest
mot riktig Postgres. terraform validate kunde inte köras här —
registry.terraform.io är blockerad av sessionens egress-policy, därav
CI-jobbet.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012EQg3rJsrQ1ZNTvkzmQAtt
This commit is contained in:
@@ -0,0 +1,96 @@
|
||||
-- Guidad Felsökning: schema för den självhostade plattformen.
|
||||
-- Multi-tenant: varje organisation är en egen tenant — all ärendedata är
|
||||
-- organisationsknuten och API:t släpper aldrig data över organisationsgränsen.
|
||||
-- Append-only garanteras med triggers: historik kan inte ändras eller
|
||||
-- raderas oavsett vilken roll som ansluter.
|
||||
|
||||
create extension if not exists pgcrypto;
|
||||
|
||||
create table if not exists organisationer (
|
||||
id uuid primary key default gen_random_uuid(),
|
||||
namn text not null,
|
||||
-- Vad som visas när ett ärende startas (objekttyper,
|
||||
-- identifieringsmetoder) — sätts av systemadministratören.
|
||||
installningar jsonb not null default '{}'::jsonb,
|
||||
skapad timestamptz not null default now()
|
||||
);
|
||||
alter table organisationer add column if not exists installningar jsonb not null default '{}'::jsonb;
|
||||
|
||||
-- Märkesspecifika kopplingar per organisation. Uppgifterna lagras
|
||||
-- krypterade (AES-256-GCM, nyckel ur INTEGRATION_NYCKEL) och lämnar
|
||||
-- aldrig servern i klartext — klienten ser bara maskerade värden.
|
||||
create table if not exists integrationer (
|
||||
organisation_id uuid not null references organisationer(id),
|
||||
leverantor text not null,
|
||||
uppgifter_krypt text not null,
|
||||
aktiv boolean not null default true,
|
||||
skapad timestamptz not null default now(),
|
||||
uppdaterad timestamptz not null default now(),
|
||||
senast_testad timestamptz,
|
||||
senaste_status text,
|
||||
primary key (organisation_id, leverantor)
|
||||
);
|
||||
|
||||
create table if not exists anvandare (
|
||||
id uuid primary key default gen_random_uuid(),
|
||||
organisation_id uuid not null references organisationer(id),
|
||||
epost text unique not null,
|
||||
losen_hash text not null,
|
||||
namn text not null,
|
||||
roll text not null default 'tekniker'
|
||||
check (roll in ('tekniker', 'arbetsledare', 'admin')),
|
||||
skapad timestamptz not null default now()
|
||||
);
|
||||
|
||||
create table if not exists felsokning_arenden (
|
||||
id text primary key,
|
||||
organisation_id uuid not null references organisationer(id),
|
||||
nummer integer not null,
|
||||
skapad timestamptz not null,
|
||||
delningskod text unique,
|
||||
metodik_id text,
|
||||
skapad_av uuid references anvandare(id),
|
||||
insatt timestamptz not null default now()
|
||||
);
|
||||
create index if not exists felsokning_arenden_org_idx
|
||||
on felsokning_arenden (organisation_id, skapad desc);
|
||||
|
||||
create table if not exists felsokning_handelser (
|
||||
id text primary key,
|
||||
arende_id text not null references felsokning_arenden(id),
|
||||
tidpunkt timestamptz not null,
|
||||
anvandare text not null,
|
||||
handelse jsonb not null,
|
||||
insatt timestamptz not null default now()
|
||||
);
|
||||
create index if not exists felsokning_handelser_arende_idx
|
||||
on felsokning_handelser (arende_id, tidpunkt);
|
||||
|
||||
create or replace function forbjud_andring() returns trigger
|
||||
language plpgsql as $$
|
||||
begin
|
||||
raise exception 'Loggen är append-only — historik kan inte ändras eller raderas';
|
||||
end $$;
|
||||
|
||||
drop trigger if exists handelser_append_only on felsokning_handelser;
|
||||
create trigger handelser_append_only
|
||||
before update or delete on felsokning_handelser
|
||||
for each row execute function forbjud_andring();
|
||||
|
||||
drop trigger if exists arenden_append_only on felsokning_arenden;
|
||||
create trigger arenden_append_only
|
||||
before update or delete on felsokning_arenden
|
||||
for each row execute function forbjud_andring();
|
||||
|
||||
-- Live Share-delningar: återkallbara länkar med behörighetsnivå.
|
||||
-- (Åtkomststyrning, inte journal — därför ingen append-only-trigger:
|
||||
-- återkallelse sätter aterkallad-tidpunkten.)
|
||||
create table if not exists delningar (
|
||||
kod text primary key,
|
||||
arende_id text not null references felsokning_arenden(id),
|
||||
niva text not null check (niva in ('kund', 'partner', 'intern')),
|
||||
skapad_av uuid references anvandare(id),
|
||||
skapad timestamptz not null default now(),
|
||||
aterkallad timestamptz
|
||||
);
|
||||
create index if not exists delningar_arende_idx on delningar (arende_id);
|
||||
Reference in New Issue
Block a user