Provenantie als 1-N junction
Eén instrument-Manifestation, n vindplaatsen. Optie B uit de brainstorm 2026-05-12.
Het patroon
instrument.manifestaties (UNIQUE op content_sha256)
└── één rij per fysieke bytes-blob
instrument.manifestatie_herkomsten (1-N op manifestatie_id)
└── één rij per concrete vindplaats van die blobSchema-shape
CREATE TABLE instrument.manifestaties (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
versie_id UUID NOT NULL REFERENCES instrument.versies(id) ON DELETE CASCADE,
mime_type TEXT NOT NULL,
mime_subtype TEXT,
content_sha256 TEXT NOT NULL UNIQUE, -- uniek per blob
byte_size BIGINT,
ingested_at TIMESTAMPTZ NOT NULL DEFAULT now(),
extraction_provenance JSONB
);
CREATE TABLE instrument.manifestatie_herkomsten (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
manifestatie_id UUID NOT NULL REFERENCES instrument.manifestaties(id) ON DELETE CASCADE,
register TEXT NOT NULL REFERENCES catalogus.registers(id),
source_url TEXT,
source_authority_id UUID REFERENCES gezag.gezagen(id),
akn_uri TEXT,
discovered_via TEXT,
first_seen_at TIMESTAMPTZ NOT NULL DEFAULT now(),
last_seen_at TIMESTAMPTZ,
status TEXT NOT NULL DEFAULT 'active'
CHECK (status IN ('active','gone_404','superseded','deprecated_by_source')),
UNIQUE (manifestatie_id, register, source_url)
);Het alternatief — Optie A
Optie A was: één rij per (blob × vindplaats)-combinatie. Dezelfde PDF op LVBB en op gemeente-website zou twee instrument.manifestaties-rijen geven, beide met identieke content_sha256.
| Aspect | Optie A | Optie B (gekozen) |
|---|---|---|
| Aantal rijen voor één blob met n vindplaatsen | n | 1 + n |
| Blob-duplicaten op Manifestation-laag | Ja | Nee |
| Adviseur-presentatie | "Hier zijn drie versies van het document" (zelfde inhoud) | "Hier is het document, ook te vinden op…" |
| Uitval van één bron | Rij stilzetten; andere rij blijft canoniek | Herkomst-rij stilzetten; manifestatie blijft canoniek |
| Bijna-identieke-bytes edge-case | Verschillende SHA256 → verschillende rijen (per definitie) | SHA256 unique-clash; mitigatie via normalisatie-functie vóór hash |
Edge-case: bijna-identieke bytes
Twee bronnen van dezelfde document kunnen iets verschillende bytes hebben — een PDF met watermerk-pagina, een PDF-creator-header-verschil, een gemeente-eigen briefhoofd. SHA256 over raw bytes geeft dan verschillende hashes voor wat conceptueel "hetzelfde document" is.
Mitigatie: per mime_type een normalisatie-functie die irrelevante metadata uitstript voordat de hash wordt berekend:
application/pdf→ hash over de geëxtraheerde tekst-content (niet de raw bytes)application/xml→ hash over de canonicalized XML (whitespace + namespace-volgorde genormaliseerd)text/html→ hash over de getripte textcontent
Voor de pilot-fase: hash over geëxtraheerde tekst per default. Tweede hash-kolom (bytes_sha256 naast content_sha256) toevoegen als de edge-case in praktijk gaat bijten — geen prematuur ontwerp.
Wat een herkomst-rij betekent
Elke instrument.manifestatie_herkomsten-rij zegt: "deze fysieke blob is vindbaar op deze register-vindplaats, gedetecteerd via deze discovery-route, laatst gezien op dat moment."
Het is niet een classificatie van de blob — het is een vindplaats. Dezelfde blob op LVBB en op gemeente-website krijgt twee herkomst-rijen, gelijkwaardig. De register-as in elke rij verklaart wát voor vindplaats het is (zie registers-taxonomie).
Wanneer een nieuwe blob-rij vs. een nieuwe herkomst-rij?
- Nieuwe blob (
content_sha256verschilt) → nieuweinstrument.manifestaties-rij + minstens ééninstrument.manifestatie_herkomsten-rij - Bestaande blob op nieuwe vindplaats → alleen nieuwe
instrument.manifestatie_herkomsten-rij; manifestatie blijft één rij - Bestaande blob, bestaande vindplaats, update van
last_seen_at→ UPDATE op de bestaande herkomst-rij
Onderliggende ADRs
- ADR-0037 §2-3 —
instrument.manifestatiesdun + provenantie-junction