Bitemporal model voor dso_artikel
Onder de pointer-method-architectuur wijst canonical.instrument_versions.content_refs naar de bron-specifieke article-store. Voor DSO is dat canonical.dso_artikel. Maar dso_artikel zelf moet bitemporal-append-only zijn — anders breekt de invariant dat een instrument-versie wijst naar onveranderlijke article-tekst. ADR-0027 lost dat op.
Het probleem
OzonSyncService.ts:337-340 gebruikt:
INSERT INTO canonical.dso_artikel (...)
VALUES (...)
ON CONFLICT (regeling_id, w_id) DO UPDATE SET
inhoud = EXCLUDED.inhoud,
...Bij elke sync wordt inhoud overschreven. Article-tekst-historie gaat verloren. Als instrument-versie X wijst naar dso_artikel(regeling_id, w_id), en versie Y wijzigt dat artikel, dan wijst de oude pointer ineens naar de nieuwe tekst — historisch klopt het niet meer.
De vier temporal-kolommen
ADR-0027 introduceert het bitemporal-model voor dso_artikel:
| Kolom | Wat het zegt |
|---|---|
valid_from | Wanneer de juridische geldigheid van dit artikel-versie begint (bekendmakings-datum) |
valid_to | Wanneer de geldigheid eindigt (volgende versie's valid_from, of NULL voor de huidige versie) |
transaction_from | Wanneer wij deze rij hebben geregistreerd |
transaction_to | Wanneer wij deze rij hebben gecorrigeerd / vervangen (NULL voor de actuele) |
Twee tijdsassen:
- Valid time — wanneer geldt het artikel volgens de wet?
- Transaction time — wanneer wist Databank het?
Bitemporal beantwoordt vragen als "wat was volgens onze gegevens op datum X de wettelijke werkelijkheid op datum Y?" — onmisbaar voor historische BOPA-toetsingen en juridische audit-trails.
Append-only
Bij een nieuwe voorkomen van een regeling:
- Voor artikelen waarvan
inhoudis gewijzigd: nieuwe rij metvalid_from = voorkomen.beginInwerkingentransaction_from = now(). De oude rij krijgtvalid_to = voorkomen.beginInwerkingentransaction_toblijft NULL. - Voor artikelen waarvan
inhoudongewijzigd is: geen nieuwe rij — de bestaande rij blijft staan, valid_to wordt verlengd naar het volgende voorkomen.
Geen UPDATEs op inhoud. Ooit. De ON CONFLICT DO UPDATE SET inhoud constructie wordt vervangen door fingerprint-detectie + conditional-INSERT.
De active_in_force matview
Consumers willen meestal "de huidige geldige versie", niet bitemporal-predicaten in hun queries. ADR-0027 specificeert een matview active_in_force die dso_artikel filtert op valid_to IS NULL AND transaction_to IS NULL. Voor 95% van de read-queries hoeft de complexity van bitemporal niet door te lekken.
Waarom dit gating-werk is
Zonder ADR-0027 kan WI-FRBR-011 (DSO instrument-canary, het projecteren van DSO naar FRBR) niet veilig landen. De instrument_versions.content_refs pointer zou wijzen naar een artikel-rij waarvan de inhoud op elk moment kan worden overschreven. Phase 0a van ADR-0032 (de migration-plan) sequenceert ADR-0027 expliciet vóór WI-FRBR-011.
Open punten
- IMOW-satelliet temporaliteit — ADR-0027 v1 maakt alleen
dso_artikelbitemporal. De IMOW-tabellen (dso_regelvooriedereen,dso_activiteit,dso_locatie, etc.) blijven current-snapshot. Open: of wijzigingen waarin een artikel-activiteit-binding wijzigt ook historie nodig hebben. - Performance bij grote her-syncs — bij ~1.000-4.000 artikelen per regeling en duizenden regelingen vergt fingerprint-detectie miljoenen vergelijkingen per full-resync. Voorzien: eerst voorkomen-tijdstipRegistratie vergelijken (cheap filter), pas fingerprint als die is doorgegroeid.
- Schema-misnoemer:
dso_artikel.regeling_idisTEXTen houdt een AKN-URI, niet een numerieke FK naardso_regeling.id. Hernoemen naarregeling_identificatiein dezelfde migratie die bitemporal-kolommen toevoegt.
Onderliggende ADRs en docs
- ADR-0027 — bitemporal
dso_artikel(nog niet geshipped — issue #4158) - ADR-0032 — strangler-fig migration plan (sequenceert ADR-0027 vóór WI-FRBR-011)
docs/handoffs/2026-05-05-architecture-audit.md§6 —ON CONFLICT DO UPDATEals wrong-turndocs/handoffs/2026-05-05-dso-ozon-problem-statement.md— volledige design-context met gm1721-voorbeeld