docs: ADR-0027 + demo note — public-safe RegisterRecord objecttype schema (refs #141)
CI / lint (pull_request) Successful in 1m50s
CI / build (pull_request) Successful in 1m43s
CI / unit (pull_request) Successful in 2m20s
CI / frontend (pull_request) Successful in 5m4s
CI / mutation (pull_request) Successful in 21m0s
CI / verify-stack (pull_request) Canceled after 31m0s
CI / lint (pull_request) Successful in 1m50s
CI / build (pull_request) Successful in 1m43s
CI / unit (pull_request) Successful in 2m20s
CI / frontend (pull_request) Successful in 5m4s
CI / mutation (pull_request) Successful in 21m0s
CI / verify-stack (pull_request) Canceled after 31m0s
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,81 @@
|
||||
# ADR-0027: The RegisterRecord objecttype is public-safe by construction
|
||||
|
||||
- **Status:** Accepted
|
||||
- **Date:** 2026-07-27
|
||||
- **Deciders:** Respellion engineering
|
||||
- **Slice:** S-18c (#141), third of the S-18 (#19) split
|
||||
|
||||
## Context
|
||||
|
||||
S-18 stands up Objecttypen (S-18a) and Objecten (S-18b) as the authoritative
|
||||
register-record store (PRD §"Objecten as the authoritative register record store").
|
||||
S-19 (#20) will, on approval, write the canonical register record to the Objecten API
|
||||
instead of OpenZaak zaak-eigenschappen, and the openbaar (public) register will read it.
|
||||
|
||||
Objecten validates every object against a **objecttype version's JSON schema**. So the
|
||||
schema is a contract: it fixes which fields a register record may carry. The register is
|
||||
read **anonymously** by the openbaar portal (ADR-0010), so the schema is also a
|
||||
disclosure boundary — anything the schema allows can end up public.
|
||||
|
||||
Two questions: **which fields** the schema defines, and **how** the objecttype gets into
|
||||
the Objecttypen API (which has no declarative objecttype step).
|
||||
|
||||
## Decision
|
||||
|
||||
**Define a `RegisterRecord` objecttype whose published schema carries exactly the
|
||||
public-safe fields — `id`, `status`, `reference` — and register it over the API at
|
||||
startup with a one-shot, idempotently.**
|
||||
|
||||
### The schema mirrors the BFF's public projection, not the internal one
|
||||
|
||||
The public-safe field set already exists: the BFF's `OpenbaarEntry`
|
||||
(`services/bff/Bff.Api/DownstreamClients.cs`) — `id`, `status`, `reference` — is what
|
||||
`OpenbaarProjection.PublicView` narrows every row down to, dropping `bsn` and
|
||||
`naamPlaceholder` at the boundary (S-09). The RegisterRecord schema mirrors that record,
|
||||
**not** the internal `RegisterEntry` / `RegisterEntryRow` (which carry bsn/naam):
|
||||
|
||||
| field | type | notes |
|
||||
|-------|------|-------|
|
||||
| `id` | string (required) | zaak id — the entry's stable key |
|
||||
| `status` | string (required) | enum `INGEDIEND` \| `INGESCHREVEN` (`RegistrationStatus`) |
|
||||
| `reference` | string \| null | citizen-facing zaak identificatie (ADR-0012) |
|
||||
|
||||
`additionalProperties: false` so a record can't smuggle a field the schema didn't
|
||||
sanction, and `dataClassification: "open"` records the intent that this objecttype is
|
||||
public. **`bsn` and `naamPlaceholder` are deliberately absent** — public-safe by
|
||||
construction, so S-19 cannot write a personal-data field into the public register even by
|
||||
mistake.
|
||||
|
||||
### Registered over the API by a one-shot, not setup_configuration
|
||||
|
||||
The Objecttypen API's `setup_configuration` (3.4.2) provisions only tokens — it has no
|
||||
declarative step to create an objecttype with a schema. So a `registerrecord-init`
|
||||
compose one-shot (stdlib Python, on the stack network) creates the objecttype + a
|
||||
**published** version over the API once Objecttypen is healthy, following the ADR-0020
|
||||
self-seed pattern. It is **idempotent**: if a `RegisterRecord` with a version already
|
||||
exists it is a no-op, so it is safe on every `up`.
|
||||
|
||||
- ponytail ceiling: no schema-migration/versioning story — a schema change means editing
|
||||
`registerrecord.schema.json` and bumping the version by hand; the one-shot only ever
|
||||
adds v1 if none exists.
|
||||
- Upgrade path: if the schema evolves, have the one-shot diff the published schema and
|
||||
POST a new version, or move to a declarative step once the upstream supports one.
|
||||
|
||||
## Consequences
|
||||
|
||||
**Positive**
|
||||
|
||||
- The public register's disclosure surface is fixed in one reviewed artifact
|
||||
(`registerrecord.schema.json`) and enforced by Objecten's own validation.
|
||||
- Self-seeds on a fresh `make up` / bare local compose; no manual step, no built image.
|
||||
|
||||
**Negative / costs**
|
||||
|
||||
- The public-safe field set now lives in two places — the BFF's `OpenbaarEntry` and this
|
||||
schema — that must be kept in sync by hand (a drift check is a candidate for later).
|
||||
- Hand-managed schema version (ceiling above).
|
||||
|
||||
## Coupling rules touched (CLAUDE.md §8)
|
||||
|
||||
None new. Registration talks to the Objecttypen API over its documented API. S-19 will
|
||||
write records via the ACL (§8.1) — this ADR only fixes the schema they conform to.
|
||||
@@ -5,6 +5,35 @@ copy-pasteable walkthrough against a local `make up` stack.
|
||||
|
||||
---
|
||||
|
||||
## S-18c — RegisterRecord objecttype defined + registered (#141, ADR-0027)
|
||||
|
||||
**Outcome:** a **RegisterRecord** objecttype with a **published** JSON schema is registered in the
|
||||
Objecttypen API at startup. The schema is public-safe by construction — `id`, `status`, `reference`
|
||||
only, mirroring the BFF's `OpenbaarEntry` (no `bsn`/`naam`), `dataClassification: open`. This is the
|
||||
schema S-19 writes register records against on approval. A `registerrecord-init` one-shot creates it
|
||||
over the API once Objecttypen is healthy (the Objecttypen `setup_configuration` has no objecttype
|
||||
step), idempotently.
|
||||
|
||||
```bash
|
||||
make up
|
||||
# The RegisterRecord objecttype exists with a published version:
|
||||
curl -s -H "Authorization: Token 0123456789abcdef0123456789abcdef01234567" \
|
||||
"http://localhost:8020/api/v2/objecttypes" | python3 -c \
|
||||
'import sys,json; o=[x for x in json.load(sys.stdin)["results"] if x["name"]=="RegisterRecord"][0]; print(o["name"], o["dataClassification"], o["versions"])'
|
||||
# → RegisterRecord open ['http://.../objecttypes/<uuid>/versions/1']
|
||||
#
|
||||
# Automated (a CI verify-stack step): asserts the objecttype exists, has a published version, and
|
||||
# that version's schema carries id/status/reference.
|
||||
make verify-registerrecord # → OK — RegisterRecord v1 published, fields=['id', 'reference', 'status']
|
||||
```
|
||||
|
||||
**The path:** `infra/objecttypen-registerrecord/registerrecord.schema.json` (the reviewed public-safe
|
||||
contract) + `register.py` are streamed into an external config volume by `infra/seed-config.sh
|
||||
registerrecord` (bind-mounted locally); the `registerrecord-init` one-shot POSTs the objecttype + a
|
||||
published version. Re-running is a no-op. S-19 (#20) writes records against this schema in Objecten.
|
||||
|
||||
---
|
||||
|
||||
## S-18b — Objecten API up in compose, wired to Objecttypen (#140)
|
||||
|
||||
**Outcome:** the upstream Maykin **Objecten API** runs in the stack — own **PostGIS** DB + redis,
|
||||
|
||||
Reference in New Issue
Block a user