## What & why S-15b, second of the S-15 (#16) split, on top of S-15a (#133). A beheerder edits the ACL's ZGW **default-fill** values from the beheer portal, and the next zaak is stamped with the new values — no restart. Closes #131 ### The vertical portal → BFF `GET/PUT /beheer/default-fill` (medewerker realm + `beheerder` role) → ACL `GET/PUT /default-fill` → a runtime-mutable in-memory store the ACL reads **per zaak**. - **ACL**: `IDefaultFillStore` / `InMemoryDefaultFillStore` (thread-safe, seeded from `Acl:Defaults`); `AclService` reads `fill.Current` per zaak (not cached at construction); `GET`/`PUT /default-fill` with required-field validation. - **BFF**: `IAclClient` gains `GetDefaultFillAsync`/`UpdateDefaultFillAsync`; `GET`/`PUT /beheer/default-fill` behind the `beheerder` policy. OpenAPI + generated client regenerated. - **Frontend**: a *Default-fill* editor page in the beheer app (load → edit → save, with saved/failure states) + nav between Catalogus and Default-fill. ### Scope decision → ADR-0026 Only the **three ZGW fill fields** (bronorganisatie, verantwoordelijke organisatie, vertrouwelijkheidaanduiding) are editable. The S-27 catalog-resolution keys stay **static config** — editing them would desync the zaaktype-URL cache (ADR-0021), and they're catalogus wiring, not "default fill". The store is **in-memory** (seeded from config): an edit reverts on restart. That's the reference-app-appropriate ceiling (no DB added to the stateless ACL); upgrade path documented. Recorded in **ADR-0026**. ## Verified locally lint (`dotnet format`) ✓ · .NET unit — acl 60 / bff 45 / domain 152 / event-subscriber 19 / acceptance 17 ✓ · frontend lint+test (8 projects) ✓ · beheer build ✓. Clean full-solution build (caught + fixed the acceptance `AclService` ctor drift). TDD red→green per layer (ACL store, ACL endpoints, BFF, frontend). ## Definition of Done - [x] Failing test committed before each implementation (red→green per layer). - [x] Conventional Commits referencing #131. - [ ] CI green — see note below. - [x] Docs: ADR-0026 + S-15b demo note. - [x] Demo note in `docs/demo-script.md`. ## Note on CI The bulk validates in the fast jobs (lint/build/unit/frontend/mutation). The **verify-stack e2e** (incl. the new `default-fill.spec.ts`) can't go green until the pre-existing **verify-stack bring-up failure on the 1.27/2.0.0 runner** is resolved (that fails on plain `main` too — unrelated to this PR). Additive change; no existing e2e touched. 🤖 Generated with [Claude Code](https://claude.com/claude-code)Reviewed-on: #138
2.7 KiB
ADR-0026: Runtime-mutable ACL default-fill (in-memory store, seeded from config)
- Status: Accepted
- Date: 2026-07-24
- Deciders: Respellion engineering
- Slice: S-15b (#131), second of the S-15 (#16) split
Context
ADR-0003 made the ACL default-fill the ZGW-mandatory fields it stamps on every
zaak, supplied as static configuration (Acl:Defaults, read once at startup as an
immutable singleton). S-15b lets a beheerder edit those values from the portal
and have the next zaak reflect them — so the defaults must become mutable at runtime.
Two questions: what is editable, and where the mutable state lives.
Decision
Make the three ZGW default-fill fields a runtime-mutable, in-memory store
(IDefaultFillStore), seeded from Acl:Defaults at startup. The ACL reads it per
zaak; the beheer PUT /default-fill replaces it.
Only the three ZGW fill fields are editable
Acl:Defaults also carries the S-27 catalog-resolution keys (ZaaktypeIdentificatie,
InformatieobjecttypeOmschrijving). Those feed the resolved-URL cache
(CachedZaaktypeCatalog, ADR-0021); editing them at runtime would leave a stale cache
and is catalogus wiring, not "default fill". So they stay static config and are
out of scope for the CRUD. The editable set is exactly Bronorganisatie,
VerantwoordelijkeOrganisatie, Vertrouwelijkheidaanduiding (DefaultFillSettings).
In-memory, not persisted
The store is a thread-safe in-memory singleton. An edit is lost on restart, when it reverts to the configured env. That is acceptable for this reference app: the slice demonstrates the pattern (beheer edits config that the ACL honours), not durable config management. The ACL stays stateless — no DB, no EF, no migration, no extra compose service.
- ponytail ceiling: no persistence, no audit trail, no optimistic concurrency.
- Upgrade path: back
IDefaultFillStorewith a DB (or an Objecten record) if durable, audited, multi-instance config is needed — the port stays the same.
Consequences
Positive
- Demoable end to end (edit in portal → next zaak reflects it) with minimal moving parts.
- The read path is per-zaak, so no restart and no cache concerns for the ZGW fields.
Negative / costs
- Edits don't survive a restart and aren't shared across replicas (single-instance assumption). Documented ceiling above.
- Two sources of default config now (static keys on
AclDefaults, mutable fields in the store) — a deliberate split by editability.
Coupling rules touched (CLAUDE.md §8)
None new. The BFF→ACL edge already exists (ADR-0025); this adds a read/write pair on it. The ACL remains the owner of the ZGW-facing config.