## What & why S-18c, the **final** slice of the S-18 (#19) split (after S-18a #142, S-18b #143). Defines the **RegisterRecord** objecttype — the schema S-19 (#20) will write canonical register records against on approval — and registers it in the Objecttypen API at startup. Closes #141 ### What - **Schema** (`infra/objecttypen-registerrecord/registerrecord.schema.json`): public-safe by construction — `id`, `status` (enum `INGEDIEND`/`INGESCHREVEN`), `reference` only, `additionalProperties: false`, `dataClassification: open`. Mirrors the BFF's `OpenbaarEntry` — **no `bsn`/`naam`** (ADR-0027). - **Registration**: a `registerrecord-init` compose one-shot (stdlib Python on the stack network) POSTs the objecttype + a **published** version over the API once Objecttypen is healthy. The Objecttypen `setup_configuration` (3.4.2) only provisions tokens — no declarative objecttype step — so this follows the ADR-0020 self-seed pattern. **Idempotent**: if a `RegisterRecord` with a version already exists it is a no-op. - **Wiring**: schema + `register.py` streamed into the external `rr-registerrecord-config` volume by `seed-config.sh registerrecord` (main) / bind-mounted (local); added to `SEED`, `CFG_VOLS`, and the CI log-dump. `registerrecord-init` is a one-shot (not in `WAIT_SVCS`). - **Smoke**: `verify-registerrecord` (`run-registerrecord-check.sh` + `registerrecord-check.py`) asserts the objecttype exists, has a **published** version, and that version's schema carries `id`/`status`/`reference`; added as a verify-stack step + a row in the #136 summary. - **ADR-0027**: records the public-safe schema decision (mirror the BFF public view, not the internal projection; API-seeded one-shot). The slice issue #141 flagged the schema as ADR-worthy, so no separate adr-proposal issue was opened. ## Verified locally (end to end, real compose) Seeded `rr-registerrecord-config`, brought Objecttypen up, ran `registerrecord-init` → `registered RegisterRecord <uuid> v1 (published)`. `make verify-registerrecord` → **OK — RegisterRecord v1 published, fields=['id', 'reference', 'status']**. Re-running the one-shot → **no-op** (idempotent). `docker compose config` clean on both files; schema + script + ci.yaml validated. ## Definition of Done - [x] Failing smoke committed first (`test(infra): …`, "no objecttype named RegisterRecord"); implementation makes it pass. - [x] Conventional Commits referencing #141. - [x] CI green (verify-stack registerrecord step — validated locally; runner already unstarved by #145). - [x] `docker compose up` reaches health (one-shot registers after Objecttypen healthy). - [x] Docs: ADR-0027 + demo note. - [x] Closed by the merging PR (`closes #141`). This closes out the S-18 (#19) split — Objecttypen (S-18a) + Objecten (S-18b) + RegisterRecord (S-18c) are all up. Next: **S-19 (#20)** — ACL writes the register record to Objecten on approval, against this schema. 🤖 Generated with [Claude Code](https://claude.com/claude-code)Reviewed-on: #146
This commit was merged in pull request #146.
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.
|
||||
Reference in New Issue
Block a user