ADR-011: PostgreSQL som persistensdatabase for Kai v2⚓︎
Revidert 2026-06-04 (AB#20222): oppdatert fra
proposedtilacceptedfor å reflektere faktisk implementasjon. Den opprinnelige skissen forutsatte direkte Npgsql/Dapper mot en enkelt JSONB-tabell (rapport_resultater) i v1-prosjekter. Implementasjonen i v2 bruker EF Core med en relasjonell modell. Avsnittene under er rettet til faktisk løsning; begrunnelsen for selve databasevalget står ved lag.Revidert 2026-07-13 (AB#21271): typenavn oppdatert etter det norske domenespråk-omdøpet (ADR-015).
Kontekst og problemstilling⚓︎
Kai v2 (Kai Platform) trenger persistert lager for systemets relasjonelle tilstand: Sak, Regelresultat, Beslutning, Stadiegjennomgang, workflow-definisjoner og polling-/jobbtilstand. Tidligere kjørte v2 på InMemory-adaptere — trygge kun for én replica, og tilstanden overlevde ikke omstart. Den modusen er nå fjernet (AB#21248); PostgreSQL er eneste datakilde. InMemory-adapterne var den historiske motivasjonen for denne beslutningen.
Vi trenger en database som:
- Lagrer relasjonell domenetilstand per Sak og Virkemiddel
- Støtter lokal utvikling via container uten ekstern avhengighet
- Kjører i Azure i test og produksjon innenfor EU/Norge-region
- Kan utvides til graf-spørrespråk (AgensGraph) på sikt dersom kryssak-regler blir et krav
Fakta lagres separat som blob (ADR-001) og vektorsøk i Azure AI Search — disse er ikke en del av denne persistensbeslutningen.
Relaterte beslutninger: ADR-010 (regelmotor), ADR-013 (hybrid regelmotor), ADR-001 (blob for fakta), ADR-003 (Aspire), ADR-012 (TickerQ).
Nøkkeldrivere⚓︎
- Relasjonell domenetilstand må kunne lagres og hentes per Sak og overleve omstart
- Flere replicas må kunne kjøre trygt mot samme lager
- Data må ikke forlate EU/Norge (compliance) — server provisjoneres i
norwayeast - Lokal utviklingsopplevelse skal ikke kreve ekstern infrastruktur — Aspire orkestrerer en PostgreSQL-container (
pgvector/pgvector:pg18) - Migrasjonsvei til AgensGraph (graf-utvidelse for PostgreSQL) skal ikke blokkeres
- Saksbehandlere skal kunne nå databasen fra egen maskin for feilsøking
Beslutning⚓︎
Azure Database for PostgreSQL flexible server (PG 18) som persistensdatabase, med
EF Core + Npgsql som datatilgangslag og en relasjonell modell (KaiDbContext).
pgvector-utvidelsen aktiveres for fremtidig vektor-/grafbruk.
Operasjonelle valg (AB#20222):
- Nettverk: offentlig endepunkt med brannmur-allowlist + påkrevd TLS. Matcher eksisterende Key Vault-postur (IP-restriksjon, ikke private endpoint), gir saksbehandlere maskin-tilgang og lar CD-agenter kjøre migrasjoner. Privat/VNet ble vurdert men forkastet (strengere enn resten av plattformen; blokkerer laptop-tilgang og pipeline-migrasjoner). NB: offentlig vs. privat er et opprettelses-valg på flexible server og er dyrt å reversere.
Rettelse 2026-06-26: IP-allowlist-posturen over gjelder Postgres og Key Vault. Den overføres ikke til Azure Storage (blob, ADR-001): Storage ignorerer IP-regler for trafikk som stammer fra samme region og ruter intra-Azure-trafikk over private IP-er, så NAT-egress-IP-en gir ikke de VNet-integrerte app-ene tilgang (gir
403 AuthorizationFailure). Blob-kontoen bruker derfor en VNet service-endpoint-regel (Microsoft.Storage) på app-subnettet, slik Key Vault allerede gjør — ikke IP-allowlist alene. - Autentisering: Entra-only (passwordless).password_auth_enabled = false. App-ene (kai-api,kai-worker) autentiserer med managed identity; ingen connection-string-secret eksisterer. Sikkerhetsgrensen er Entra + TLS + brannmur. - Migrasjoner:kai-workeranvender EF Core-migrasjoner ved oppstart (DatabaseMigrationHostedService, ubetinget ved hver worker-oppstart — dev, test og prod). Workeren er utpekt skjema-migrator;kai-apimigrerer aldri. Det finnes ikke noe CD-migrasjonssteg — utrulling shipper kun containeren, og containeren migrerer seg selv. (Vurdert og forkastet: et eget CD-steg medefbundle/psql — deploy-agenten mangler både .NET SDK og psql og kan ikke endres.) - Skjemaendringer skal være bakoverkompatible (expand/contract) slik at uavhengig utrulling avkai-apiogkai-worker(kortvarig versjons-skew mot samme database) er trygt uten å koble utrullingsrekkefølgen.
Skjemaendringer kun via migrasjoner⚓︎
Skjemaet eies av EF-migrasjonene, ikke av manuell DDL. Håndhevingen er lagdelt:
- Kun workeren migrerer —
kai-workeranvender migrasjoner ved oppstart (DatabaseMigrationHostedService);kai-apimigrerer aldri. Bare innsjekkede EF-migrasjoner anvendes — ingen ad hoc-DDL fra appen. - Minste privilegium per MI —
kai-api-identiteten er DML/usage-only (ingen DDL);kai-worker-identiteten har CREATE/DDL påschema publicfordi den er migrator. Begge opprettes som Entra-prinsipaler av engangs-bootstrap (scripts/pg-onetime-bootstrap.sql), kjørt én gang per miljø av en team-admin. - CI-guard — en
HasPendingModelChanges()-test (tests/Enova.Kai.Architecture.Tests) feiler bygget hvis EF-modellen endres uten en matchende migrasjon, så «glemt migrasjon» fanges før merge. - MVP-avgrensning — teammedlemmer er fortsatt PG Entra-admin og kan teknisk kjøre DDL manuelt. Strammere låsing (DML-only for mennesker + break-glass admin) er bevisst utsatt.
Konsekvenser⚓︎
- Positivt: relasjonell modell + EF-migrasjoner gir typet skjema, versjonert historikk og enkel evolusjon
- Positivt: Aspire orkestrerer
pgvector/pgvector:pg18-container lokalt — samme major-versjon som Azure (PG 18), ingen lokal/sky-skew - Positivt: passwordless fjerner en hemmelighet å rotere/lekke
- Positivt: offentlig endepunkt + Entra-auth gir lav friksjon for utvikler-tilgang og pipeline-migrasjoner
- Positivt: AgensGraph er en PostgreSQL-utvidelse — migrasjonsvei åpen uten skjemabrudd
- Negativt: offentlig endepunkt er en større angrepsflate enn privat (avbøtes av Entra-only + TLS + brannmur)
- Negativt: expand/contract krever disiplin på hver skjemaendring
- Nøytralt: Entra-prinsipaler for app-identitetene må bootstrappes (
pgaadauth_create_principal) av en Entra-admin én gang per server
Vurderte alternativer⚓︎
- Azure PostgreSQL flexible server + EF Core (relasjonell) — valgt
- Direkte Npgsql/Dapper mot JSONB — opprinnelig skisse; forlatt fordi en typet relasjonell modell passer domenet (saker, regler, resultater, beslutninger) bedre enn skjemaløs JSONB, og EF-migrasjoner gir versjonert skjemaevolusjon
- AgensGraph i PostgreSQL fra dag én — overkill; ingen kryssak-krav i dag (revisit-trigger under)
Implementasjonsplan⚓︎
Datatilgang og skjema lever i Enova.Kai.Infrastructure/Persistence/:
KaiDbContext—Sak,Regel,Regelresultat,WorkflowDefinitionRecord,PollingProcessedCaseRecord(+ TickerQ-tabeller)- EF-migrasjoner under
Persistence/Migrations/ - PostgreSQL er eneste datakilde — ikke noe
Kai:DataSource-flagg (InMemory-modusen ble fjernet, AB#21248) - Infrastructure-laget registrerer Db-adaptere for alle sentrale ports (inkl.
IBeslutningStore/IStadiegjennomgangStoreetter AB#20222) - Connection-string
kaiinjiseres som app-setting (passwordless), ikke som secret
Verifisering⚓︎
- [ ] Terraform provisjonerer flexible server (PG 18, offentlig + Entra-only + TLS, pgvector) i test og prod
- [ ]
kai-apiogkai-workerkjører mot reell Postgres (verifisert via/health+ smoketests) - [ ] Integrasjonstester (Testcontainers,
pgvector/pgvector:pg18) grønne i CI - [ ] Migrasjoner anvendes av
kai-workerved oppstart (DatabaseMigrationHostedService); ingen separat CD-migrasjonssteg
Tilleggsinformasjon⚓︎
- Revisit-trigger: hvis krav om regelkjøring på tvers av saker materialiserer seg, vurder AgensGraph-utvidelse (
CREATE EXTENSION age) — eksisterende data kan leses som graflag uten skjemabrudd - Leseprojeksjoner for saksbehandlerflaten (
IRuleQueries/ISakQueriesmot reelle regeldata i stedet forFakeData) hører til Feature AB#20170, ikke denne beslutningen