diff --git a/docs/architecture/adr-0028-objecten-holds-the-register.md b/docs/architecture/adr-0028-objecten-holds-the-register.md index 14c7bd4..b521cc8 100644 --- a/docs/architecture/adr-0028-objecten-holds-the-register.md +++ b/docs/architecture/adr-0028-objecten-holds-the-register.md @@ -77,6 +77,38 @@ is not cached, so it is retried on the next approval. - Upgrade path: lift it into a singleton cache (as `CachedZaaktypeCatalog` does for ZGW) if approvals ever get hot enough for that GET to matter. +### The objecttype's UUID is pinned, not server-assigned + +Objecten refuses to store an object whose objecttype it has not been configured with +(`ObjectType with url=… is not configured`), and its configuration identifies an +objecttype **by UUID** — supplied through a static `setup_configuration` file applied +when the container starts, before the `registerrecord-init` one-shot has run. + +Rather than thread a seed-time UUID from one container into another's config, the UUID is +**pinned**: `infra/objecttypen-registerrecord/register.py` creates the objecttype with a +fixed UUID (the Objecttypen API accepts a client-supplied one), and +`infra/objecten/setup_configuration/data.yaml` declares that same UUID. Both sides are +declared up front, both stay idempotent, and neither has to wait for the other. + +The cost is a constant duplicated across two files that must be kept in step; each carries +a comment pointing at the other. + +### Objecten's notifications are off for this slice + +Objecten publishes to a Notificaties API on every write, and `notifications_api_common` +**raises** rather than skipping when that configuration is absent — so with no NRC wiring, +every `POST /api/v2/objects` returns 500 after creating and rolling back the object. + +Objecten → NRC is not wired: there is no broker, no Celery worker, no `objecten` kanaal and +no abonnement for it. Configuring only the client side would make writes succeed while +every message was dropped on the floor — a delivery path that looks wired and isn't. So +`NOTIFICATIONS_DISABLED` is set for Objecten in both compose files instead. + +- ponytail ceiling: Objecten emits no notifications, so nothing downstream can react to a + register write yet. +- Upgrade path: S-19b (#150) needs those notifications to source the projection from + Objecten, and turns them on together with the broker, worker, kanaal and abonnement. + ## Consequences **Positive** @@ -95,6 +127,8 @@ is not cached, so it is retried on the next approval. Nothing repairs that automatically yet. - One more upstream module on the approval path, and one more dev credential (`Acl__Objecten__Token`) in compose. +- Two new hand-kept constants: the pinned objecttype UUID (two files) and the objecttype + name (compose + `register.py`). - Until S-19b lands, the public register is still read from the NRC-derived projection, so the register record is written but not yet read — the two must agree. @@ -110,3 +144,8 @@ and no service reaches Objecten's database. asserts, via `infra/register-record-check.py`, that Objecten holds exactly one `RegisterRecord` for that registration, with status `INGESCHREVEN` and no field outside the public-safe schema. + +Every HTTP exchange the gateway performs was additionally replayed by hand against a live +Objecten + Objecttypen pair while writing this slice — objecttype lookup by name, version +status, `data_attrs` search, create, update, and a rejected write carrying a `bsn`. Both +findings above came out of that replay rather than out of CI. diff --git a/infra/docker-compose.local.yml b/infra/docker-compose.local.yml index 9660876..e35732b 100644 --- a/infra/docker-compose.local.yml +++ b/infra/docker-compose.local.yml @@ -691,6 +691,12 @@ services: CACHE_AXES: objecten-redis:6379/0 DISABLE_2FA: "true" OTEL_SDK_DISABLED: "true" + # S-19a: Objecten refuses every write while its Notificaties config is absent + # (notifications_api_common raises rather than skipping, so POST /objects 500s). Objecten → + # NRC is not wired yet — there is no broker, worker, kanaal or abonnement for it — so turn + # notifications off rather than fake a delivery path that silently drops every message. + # S-19b (#150) sources the projection from Objecten and turns this back on for real. + NOTIFICATIONS_DISABLED: "true" RUN_SETUP_CONFIG: "true" command: /setup_configuration.sh volumes: diff --git a/infra/docker-compose.yml b/infra/docker-compose.yml index 903cd3f..024e879 100644 --- a/infra/docker-compose.yml +++ b/infra/docker-compose.yml @@ -717,6 +717,12 @@ services: CACHE_AXES: objecten-redis:6379/0 DISABLE_2FA: "true" OTEL_SDK_DISABLED: "true" + # S-19a: Objecten refuses every write while its Notificaties config is absent + # (notifications_api_common raises rather than skipping, so POST /objects 500s). Objecten → + # NRC is not wired yet — there is no broker, worker, kanaal or abonnement for it — so turn + # notifications off rather than fake a delivery path that silently drops every message. + # S-19b (#150) sources the projection from Objecten and turns this back on for real. + NOTIFICATIONS_DISABLED: "true" RUN_SETUP_CONFIG: "true" command: /setup_configuration.sh # data.yaml is streamed into this external volume by infra/seed-config.sh before start. diff --git a/infra/objecten/setup_configuration/data.yaml b/infra/objecten/setup_configuration/data.yaml index 1a5b2a3..9f1b858 100644 --- a/infra/objecten/setup_configuration/data.yaml +++ b/infra/objecten/setup_configuration/data.yaml @@ -19,7 +19,18 @@ zgw_consumers: header_key: Authorization header_value: Token 0123456789abcdef0123456789abcdef01234567 -# (2) Static API token peers use to write/read objects. +# (2) Permit the RegisterRecord objecttype (S-19a). Objecten refuses to store an object whose +# objecttype it has not been configured with ("ObjectType with url=… is not configured"), and it +# identifies one by uuid — which is why infra/objecttypen-registerrecord/register.py pins that uuid +# instead of letting Objecttypen assign one. Keep the two in step. +objecttypes_config_enable: true +objecttypes: + items: + - uuid: 1f4b4e26-8b1f-4e2f-9d6c-6a1b7a2f0e01 + name: RegisterRecord + service_identifier: objecttypen + +# (3) Static API token peers use to write/read objects. tokenauth_config_enable: true tokenauth: items: diff --git a/infra/objecttypen-registerrecord/register.py b/infra/objecttypen-registerrecord/register.py index df07aad..fa6e69f 100644 --- a/infra/objecttypen-registerrecord/register.py +++ b/infra/objecttypen-registerrecord/register.py @@ -17,6 +17,11 @@ BASE = os.environ.get("OBJECTTYPEN", "http://objecttypen:8000").rstrip("/") TOKEN = os.environ["OBJECTTYPEN_TOKEN"] SCHEMA_PATH = os.environ.get("SCHEMA", "/config/registerrecord.schema.json") NAME = "RegisterRecord" +# Pinned rather than server-assigned (S-19a): the Objecten API will only accept objects whose +# objecttype it has been configured with *by uuid*, and its own setup_configuration is a static +# file applied before this one-shot runs. A fixed uuid lets both sides be declared up front instead +# of threading a seed-time value between two containers. See infra/objecten/setup_configuration. +UUID = "1f4b4e26-8b1f-4e2f-9d6c-6a1b7a2f0e01" def api(method, path, body=None): @@ -53,6 +58,7 @@ def main(): return 0 ot = existing or api("POST", "/api/v2/objecttypes", { + "uuid": UUID, "name": NAME, "namePlural": "RegisterRecords", "description": schema.get("description", ""),