S-18c · RegisterRecord objecttype defined + registered (closes #141) #146
@@ -285,7 +285,7 @@ jobs:
|
||||
# Log dump must precede teardown (which removes the containers).
|
||||
- name: Dump container logs on failure
|
||||
if: failure()
|
||||
run: docker compose -f infra/docker-compose.yml logs --no-color --tail=100 oz-init openzaak nrc-init nrc-web nrc-celery nrc-beat flowable-db flowable-rest flowable-init keycloak acl bff domain projection-db event-subscriber projection-api self-service openbaar behandel beheer objecttypen-db objecttypen-redis objecttypen-init objecttypen objecten-db objecten-redis objecten-init objecten tempo prometheus grafana 2>&1 || true
|
||||
run: docker compose -f infra/docker-compose.yml logs --no-color --tail=100 oz-init openzaak nrc-init nrc-web nrc-celery nrc-beat flowable-db flowable-rest flowable-init keycloak acl bff domain projection-db event-subscriber projection-api self-service openbaar behandel beheer objecttypen-db objecttypen-redis objecttypen-init objecttypen objecten-db objecten-redis objecten-init objecten registerrecord-init tempo prometheus grafana 2>&1 || true
|
||||
- name: Tear down
|
||||
if: always()
|
||||
run: make down
|
||||
|
||||
@@ -18,7 +18,7 @@ WAIT_SVCS := openzaak nrc-web acl bff domain event-subscriber projection-api se
|
||||
# volumes are `external`, so compose won't remove them — CFG_VOLS lists them for
|
||||
# explicit teardown. See docs/runbooks/gitea-actions-gotchas.md.
|
||||
SEED := bash infra/seed-config.sh
|
||||
CFG_VOLS := rr-oz-config rr-nrc-config rr-kc-realms rr-fl-bpmn rr-objecttypen-config rr-objecten-config
|
||||
CFG_VOLS := rr-oz-config rr-nrc-config rr-kc-realms rr-fl-bpmn rr-objecttypen-config rr-objecten-config rr-registerrecord-config
|
||||
# Local-only stack: same services but config is bind-mounted (no seed step), so a
|
||||
# plain `docker compose -f infra/docker-compose.local.yml up` works on any local
|
||||
# engine. This is the no-make / Windows-friendly path. See that file's header.
|
||||
@@ -94,14 +94,14 @@ mutation:
|
||||
# podman-compose, and needing no `--wait` flag or host port access. The one-shots
|
||||
# (oz-init, flowable-init) aren't polled; they just need to have run.
|
||||
smoke:
|
||||
$(SEED) oz nrc kc fl objecttypen objecten
|
||||
$(SEED) oz nrc kc fl objecttypen objecten registerrecord
|
||||
docker compose -f $(COMPOSE) up -d --build
|
||||
bash -c 'WAIT_TIMEOUT=420 bash infra/wait-healthy.sh $(WAIT_SVCS); rc=$$?; docker compose -f $(COMPOSE) down --volumes; docker volume rm -f $(CFG_VOLS) >/dev/null 2>&1; exit $$rc'
|
||||
|
||||
## up: seed config volumes and start the full stack (use instead of bare
|
||||
## `docker compose up`, which can't self-seed the external config volumes)
|
||||
up:
|
||||
$(SEED) oz nrc kc fl objecttypen objecten
|
||||
$(SEED) oz nrc kc fl objecttypen objecten registerrecord
|
||||
docker compose -f $(COMPOSE) up -d --build
|
||||
|
||||
## down: stop and remove the local stack (incl. the external config volumes)
|
||||
@@ -139,7 +139,7 @@ changelog:
|
||||
## verify-up: bring the FULL stack up and wait for health (CI verify-stack step 1;
|
||||
## subsumes the old compose-smoke health gate — the DoD "up reaches green" check).
|
||||
verify-up:
|
||||
$(SEED) oz nrc kc fl objecttypen objecten
|
||||
$(SEED) oz nrc kc fl objecttypen objecten registerrecord
|
||||
docker compose -f $(COMPOSE) up -d --build
|
||||
WAIT_TIMEOUT=420 bash infra/wait-healthy.sh $(WAIT_SVCS)
|
||||
|
||||
@@ -205,7 +205,7 @@ verify-registerrecord:
|
||||
## tear down (always). For fast single-concern local iteration use `integration`
|
||||
## (oz-only) or `verify-notifications` (oz+nrc) instead.
|
||||
verify:
|
||||
$(SEED) oz nrc kc fl objecttypen objecten
|
||||
$(SEED) oz nrc kc fl objecttypen objecten registerrecord
|
||||
docker compose -f $(COMPOSE) up -d --build
|
||||
@bash -c 'set -e; rc=0; \
|
||||
WAIT_TIMEOUT=420 bash infra/wait-healthy.sh $(WAIT_SVCS) \
|
||||
|
||||
@@ -624,6 +624,21 @@ services:
|
||||
condition: service_completed_successfully
|
||||
networks: [cg]
|
||||
|
||||
# ── RegisterRecord objecttype (S-18c) — API-seeded one-shot (local variant) ─
|
||||
registerrecord-init:
|
||||
image: docker.io/library/python:3-slim
|
||||
environment:
|
||||
OBJECTTYPEN: http://objecttypen:8000
|
||||
OBJECTTYPEN_TOKEN: ${OBJECTTYPEN_TOKEN:-0123456789abcdef0123456789abcdef01234567}
|
||||
SCHEMA: /config/registerrecord.schema.json
|
||||
command: python /config/register.py
|
||||
volumes:
|
||||
- ./objecttypen-registerrecord:/config:ro,z
|
||||
depends_on:
|
||||
objecttypen:
|
||||
condition: service_healthy
|
||||
networks: [cg]
|
||||
|
||||
# ── Objecten API (S-18b) — bind-mounted config (local variant) ─────────────
|
||||
objecten-db:
|
||||
image: docker.io/postgis/postgis:17-3.5
|
||||
|
||||
@@ -640,6 +640,25 @@ services:
|
||||
condition: service_completed_successfully
|
||||
networks: [cg]
|
||||
|
||||
# ── RegisterRecord objecttype (S-18c) — API-seeded one-shot ────────────────
|
||||
# The Objecttypen setup_configuration (3.4.2) can only provision tokens — no declarative objecttype
|
||||
# step — so this one-shot creates the RegisterRecord objecttype + a published version over the API
|
||||
# once Objecttypen is healthy (idempotent; ADR-0020 self-seed, ADR-0027 schema). The schema + script
|
||||
# are streamed into the external config volume by infra/seed-config.sh, like the *-init volumes.
|
||||
registerrecord-init:
|
||||
image: docker.io/library/python:3-slim
|
||||
environment:
|
||||
OBJECTTYPEN: http://objecttypen:8000
|
||||
OBJECTTYPEN_TOKEN: ${OBJECTTYPEN_TOKEN:-0123456789abcdef0123456789abcdef01234567}
|
||||
SCHEMA: /config/registerrecord.schema.json
|
||||
command: python /config/register.py
|
||||
volumes:
|
||||
- registerrecord-config:/config:ro
|
||||
depends_on:
|
||||
objecttypen:
|
||||
condition: service_healthy
|
||||
networks: [cg]
|
||||
|
||||
# ── Objecten API (S-18b) — upstream Maykin image, verbatim ─────────────────
|
||||
# The authoritative object store. Same shape as Objecttypen (own DB + redis, an `-init` that runs
|
||||
# setup_configuration from the external config volume, a health-checked web). Two differences: the
|
||||
@@ -782,6 +801,9 @@ volumes:
|
||||
objecttypen-config:
|
||||
external: true
|
||||
name: rr-objecttypen-config
|
||||
registerrecord-config:
|
||||
external: true
|
||||
name: rr-registerrecord-config
|
||||
objecten-config:
|
||||
external: true
|
||||
name: rr-objecten-config
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
#!/usr/bin/env python3
|
||||
"""S-18c (#141): register the RegisterRecord objecttype + a published version in the Objecttypen API.
|
||||
|
||||
Run by the `registerrecord-init` compose one-shot once Objecttypen is healthy. The Objecttypen API's
|
||||
setup_configuration (3.4.2) can only provision tokens — it has no declarative objecttype step — so
|
||||
the objecttype is created over the API here (the ADR-0020 self-seed pattern), idempotently: if a
|
||||
"RegisterRecord" objecttype with a published version already exists, it's a no-op. Stdlib only.
|
||||
"""
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
import urllib.error
|
||||
import urllib.request
|
||||
|
||||
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"
|
||||
|
||||
|
||||
def api(method, path, body=None):
|
||||
data = json.dumps(body).encode() if body is not None else None
|
||||
req = urllib.request.Request(
|
||||
f"{BASE}{path}", data=data, method=method,
|
||||
headers={"Authorization": f"Token {TOKEN}", "Content-Type": "application/json"},
|
||||
)
|
||||
with urllib.request.urlopen(req, timeout=15) as r:
|
||||
return json.load(r) if r.length != 0 else {}
|
||||
|
||||
|
||||
def wait_ready():
|
||||
"""Objecttypen depends_on health already, but tolerate a slow first request."""
|
||||
for _ in range(20):
|
||||
try:
|
||||
api("GET", "/api/v2/objecttypes")
|
||||
return
|
||||
except (urllib.error.URLError, ConnectionError, TimeoutError):
|
||||
time.sleep(3)
|
||||
api("GET", "/api/v2/objecttypes") # last try, let it raise
|
||||
|
||||
|
||||
def main():
|
||||
schema = json.load(open(SCHEMA_PATH))
|
||||
wait_ready()
|
||||
|
||||
existing = next(
|
||||
(o for o in api("GET", "/api/v2/objecttypes").get("results", []) if o.get("name") == NAME),
|
||||
None,
|
||||
)
|
||||
if existing and existing.get("versions"):
|
||||
print(f"RegisterRecord already registered ({len(existing['versions'])} version(s)) — no-op")
|
||||
return 0
|
||||
|
||||
ot = existing or api("POST", "/api/v2/objecttypes", {
|
||||
"name": NAME,
|
||||
"namePlural": "RegisterRecords",
|
||||
"description": schema.get("description", ""),
|
||||
"dataClassification": "open", # public-safe: the openbaar register may show it
|
||||
})
|
||||
uuid = ot["uuid"]
|
||||
ver = api("POST", f"/api/v2/objecttypes/{uuid}/versions", {
|
||||
"status": "published",
|
||||
"jsonSchema": schema,
|
||||
})
|
||||
print(f"registered RegisterRecord {uuid} v{ver.get('version')} ({ver.get('status')})")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"title": "RegisterRecord",
|
||||
"description": "Public-safe register entry shown in the openbaar (public) register. Mirrors the BFF's OpenbaarEntry (services/bff/Bff.Api/DownstreamClients.cs) — deliberately NO bsn or naam. S-19 (#20) writes records against this schema in the Objecten API on approval. See ADR-0027.",
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": ["id", "status"],
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string",
|
||||
"description": "Zaak id — the register entry's stable primary key (the projection key)."
|
||||
},
|
||||
"status": {
|
||||
"type": "string",
|
||||
"enum": ["INGEDIEND", "INGESCHREVEN"],
|
||||
"description": "Registration lifecycle status (RegistrationStatus)."
|
||||
},
|
||||
"reference": {
|
||||
"type": ["string", "null"],
|
||||
"description": "Citizen-facing zaak identificatie shown publicly (ADR-0012)."
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -13,7 +13,7 @@
|
||||
# subcommand. Fixed-name `external` volumes keep the names deterministic across
|
||||
# both runtimes. See docs/runbooks/gitea-actions-gotchas.md.
|
||||
#
|
||||
# Usage: seed-config.sh <key> [<key> ...] where key ∈ { oz, nrc, kc, fl, objecttypen, objecten }
|
||||
# Usage: seed-config.sh <key> [<key> ...] where key ∈ { oz, nrc, kc, fl, objecttypen, objecten, registerrecord }
|
||||
set -euo pipefail
|
||||
|
||||
here="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
@@ -33,7 +33,7 @@ populate() { # volume source(file or dir/.)
|
||||
echo " seeded $vol"
|
||||
}
|
||||
|
||||
[ "$#" -gt 0 ] || { echo "usage: seed-config.sh <oz|nrc|kc|fl|objecttypen|objecten> ..." >&2; exit 2; }
|
||||
[ "$#" -gt 0 ] || { echo "usage: seed-config.sh <oz|nrc|kc|fl|objecttypen|objecten|registerrecord> ..." >&2; exit 2; }
|
||||
|
||||
# The registratie process (BPMN) and its diploma-eligibility DMN are deployed as SEPARATE Flowable
|
||||
# deployments — the process engine and the DMN engine each own theirs (S-13, ADR-0016). flowable-rest
|
||||
@@ -51,6 +51,7 @@ for key in "$@"; do
|
||||
kc) populate rr-kc-realms "$here/keycloak/realms/." ;;
|
||||
objecttypen) populate rr-objecttypen-config "$here/objecttypen/setup_configuration/." ;;
|
||||
objecten) populate rr-objecten-config "$here/objecten/setup_configuration/." ;;
|
||||
registerrecord) populate rr-registerrecord-config "$here/objecttypen-registerrecord/." ;;
|
||||
fl) d="$(mktemp -d)"; stage_flowable_workflows "$d"; populate rr-fl-bpmn "$d/." ;;
|
||||
*) echo "unknown seed key: $key" >&2; exit 2 ;;
|
||||
esac
|
||||
|
||||
Reference in New Issue
Block a user