Ga naar inhoud

ADR-0005: Alignment with the NORA "bij de Bron" target architecture — what wordsworth adopts, adapts, and declines

Context

The NORA Expertgroep deck "Anatomie van Anonimiseren & Pseudonimiseren bij de Bron" (Haarlem/Zandvoort) describes a municipal target architecture for reversible anonymisation of documents and NEN 7524-style pseudonymisation of datasets. docs/explanation/nora-gap-analysis.md tests wordsworth against it. Most gaps fit wordsworth's invariants and are proposed as OpenSpec changes. Eleven points do not fit and need an explicit decision; this ADR records the recommendation per point so the decisions are made once, in one place.

Decisions (each: deck position → recommendation → why)

D1 — RDFa-embedded ciphertext in the document. Deck: PII encrypted and embedded inline as an RDFa <span> with data-encrypted-value. Recommend: decline inline ciphertext; offer RDFa as an export view. wordsworth's separated mapping store is the reason key rotation never touches documents and the reason the index never sees ciphertext churn. An RDFa export (?view=rdfa) can carry typeof, data-entity-type, data-legal-basis, data-key-id and the token — everything except the ciphertext — and stays interoperable with a DMS that wants RDFa.

D2 — Per-document keys derived from a category key. Deck: document key = KDF(category key, doc id), never stored. Recommend: decline; keep random per-scope keys with rotation + escrow, add the domain dimension (add-domain-keys). Derived per-document keys make rotation a re-derivation of every document and contradict the deck's own "wrapping, not derivation" note. The deck's goal (attribute which key, when, by whom) is met by key_id per mapping plus the key-lifecycle audit stream.

D3 — Algorithm per AVG article (AES-256-GCM / ChaCha20-Poly1305 / RSA-OAEP). Recommend: decline. One AEAD (AES-256-GCM) for all categories; the legal basis is metadata (add-pii-categories-and-ppl), authorisation is the grant. Three ciphers add surface without adding protection — the article is a policy attribute, not a cryptographic one — and RSA-OAEP for bulk field encryption is the clever pitfall (it needs a hybrid scheme anyway). Boring and auditable wins.

D4 — Anonypy as detection layer 2. Recommend: decline (already banned in AGENTS.md). The "Swiss Cheese" property is met by regex + Presidio patterns + NER (OpenAnonymiser); name it as such in docs, measure it with add-pii-detection-eval.

D5 — HSM (FIPS 140-2 L2) master key. Recommend: out of core — OpenBao auto-unseal/HSM is deployment hardening (ADR-0002). No code change.

D6 — TTP key hand-over via PKCS#12. Recommend: adapt — export a domain key as an age-encrypted envelope (the existing escrow format) plus a JSON metadata file; do not implement PKCS#12, which is designed for X.509 private keys/certs and has poor support for symmetric "secret bags". The requirement behind it (a new vendor or a TTP can reproduce the same pseudonyms) is met by exporting the wrapped domain key and the normalisation profile version.

D7 — ABAC / Entra ID / SSO / roles. Recommend: defer, keep the grant capability model and add PPL as shorthand (add-pii-categories-and-ppl). A real caller-auth model for the grant surface is already an open decision (docs/how-to/grants.md); an IdP integration should be one change with its own ADR, not a side effect of this alignment.

D8 — Datasets (CSV) in scope. Deck: half the platform is column-selected pseudonymisation of tabular data. Recommend: accept as a scope extension (add-dataset-pseudonymisation), because the value is that a BSN in a document and the same BSN in a dataset get the same pseudonym under the same domain key — that only works if both paths share one engine. Boundaries: CSV only, no scheduling, no DWH delivery (NiFi's job), no Excel.

D9 — NEN 7524 conformance. Recommend: implement the deck's header format as "NEN 7524-style", label conformance unverified until the norm text has been checked. The repo must not claim compliance with a standard nobody has read.

D10 — Thin-client: detection + Web Crypto encryption inside a Word plugin. Recommend: decline for wordsworth. Keys never leave OpenBao (ADR-0002); a plugin is an experience layer (ADR-0004) that calls the API. Client-side detection would also fork the detector from the audited server path.

D11 — Rendered outputs (DMS new version, Woo portal PDF/docx, TMLO metadata). Recommend: out of core. wordsworth emits de-identified text, JSON and (with add-legible-placeholders) a legible view; re-rendering a docx/PDF with placeholders is a separate render component behind the API, like the console.

Amended 2026-10 (pdf-redaction), narrowly. A redacted rendering of the source PDF is a core output after all: the guarantee a redacted file makes — the text under the box is gone, not merely covered — can only be given by the code that holds the detections, so a separate render component would need to hold that same data and would simply be the core again under another name. GET /documents/{id}/redacted.pdf and POST /documents/{id}/reveal.pdf are therefore part of the API, not a separate service. Every other rendered format (docx and the like) stays out, for the reason above: D11 otherwise stands.

Consequences

  • Eight OpenSpec proposals implement the compatible gaps; the order is in the gap analysis §3.
  • Anything declined here stays declined until this ADR is superseded; a future stakeholder asking for RDFa-inline ciphertext or per-article ciphers is pointed at D1/D3 rather than re-litigating in a PR.
  • The deck remains a reference architecture; wordsworth's contract is AGENTS.md + the specs.

Status update

Proposed 2026-09-03. Accepted 2026-09-03 by Mark (in #wordsworth): D8 (datasets in scope) accepted explicitly; D1–D7 and D9–D11 accepted as recommended. Go given for proposals 1–5 (add-value-normalisation, add-pii-categories-and-ppl, add-detection-confidence, add-legible-placeholders, add-domain-keys); 6–8 stay at propose stage.