Gå til innhold

ADR-011: PostgreSQL som persistensdatabase for Kai v2⚓︎

Revidert 2026-06-04 (AB#20222): oppdatert fra proposed til accepted for å 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-worker anvender EF Core-migrasjoner ved oppstart (DatabaseMigrationHostedService, ubetinget ved hver worker-oppstart — dev, test og prod). Workeren er utpekt skjema-migrator; kai-api migrerer aldri. Det finnes ikke noe CD-migrasjonssteg — utrulling shipper kun containeren, og containeren migrerer seg selv. (Vurdert og forkastet: et eget CD-steg med efbundle/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 av kai-api og kai-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:

  1. Kun workeren migrererkai-worker anvender migrasjoner ved oppstart (DatabaseMigrationHostedService); kai-api migrerer aldri. Bare innsjekkede EF-migrasjoner anvendes — ingen ad hoc-DDL fra appen.
  2. Minste privilegium per MIkai-api-identiteten er DML/usage-only (ingen DDL); kai-worker-identiteten har CREATE/DDL på schema public fordi 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.
  3. 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.
  4. 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⚓︎

  1. Azure PostgreSQL flexible server + EF Core (relasjonell) — valgt
  2. 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
  3. 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/:

  • KaiDbContextSak, 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/IStadiegjennomgangStore etter AB#20222)
  • Connection-string kai injiseres som app-setting (passwordless), ikke som secret

Verifisering⚓︎

  • [ ] Terraform provisjonerer flexible server (PG 18, offentlig + Entra-only + TLS, pgvector) i test og prod
  • [ ] kai-api og kai-worker kjører mot reell Postgres (verifisert via /health + smoketests)
  • [ ] Integrasjonstester (Testcontainers, pgvector/pgvector:pg18) grønne i CI
  • [ ] Migrasjoner anvendes av kai-worker ved 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/ISakQueries mot reelle regeldata i stedet for FakeData) hører til Feature AB#20170, ikke denne beslutningen