Builds the four-seam, three-write-path reference demo backend: case-framework (seam D stand-in), legacy-backend/frontend (SQL Server, seams A/B/C targets), and new-backend (Domain/Application/Infrastructure.*/Api implementing the source resolver, take/release-ownership, write-through translator, and owned assessment flow), wired together via docker-compose with a plain placeholder frontend standing in for the Angular portal until Session 2. All 11 Architecture.Tests pass and scripts/smoke.sh passes end-to-end against a fresh `docker compose up`, covering acceptance criteria 1-3 and 7-22. Fixes two real domain bugs found only once the stack ran for real: the BSN eleven-proof checksum trivially passes all-zero digits, and the adoption mapper silently treated a partial legacy address as absent instead of failing loudly. Also fixes several environment-specific integration issues (rootless Podman/SELinux bind-mount permissions, a buildah NuGet layer-caching bug, SqlClient's invariant-globalization incompatibility, and an nginx path-prefix mismatch for the legacy frontend). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
3.2 KiB
ADR-003: Ownership is taken per case for now — bulk migration is a later, separate capability
Status
Accepted (interim). Superseded in part once bulk migration tooling (see "Future work" below) ships.
Context
The end state for at least some processes — registration cases among them — is a bulk cutover: migrate the whole remaining population in one operation and retire the legacy path for that process on a clean date. That is a real, wanted outcome, not something this design argues against.
What this system cannot do is wait for that bulk-migration tooling to exist before shipping anything of business value. Building a safe bulk migration requires solving problems this demo deliberately defers: what happens to rows that fail adoption (four such failure modes already exist in the seed data — contact, BSN, motivation, and partial-address invariant violations), how a partially-failed batch is reported and retried, and how the cutover is scheduled and communicated. None of that should block getting the read ACL, write-through ACL, and take-ownership mechanics themselves live and earning their keep.
Decision
Ship now with ownership taken one legacy case at a time, via
POST /api/worklist/legacy/{aanvraagId}/take-ownership (§7.5), triggered by
an explicit user action in the portal. This is the interim mechanism, not the
final one for every process.
This is deliberately the right building block either way:
- It is the same adoption logic (mapping, invariant validation, case-framework correlation, atomic persistence, migratie-vlag flip) that a future bulk tool would need to call in a loop — building it per-case first means the bulk tool is an orchestration layer on top of already-proven logic, not a parallel implementation to keep in sync.
- It gives a real, visible answer today for what a bulk migration would
otherwise discover the hard way: which legacy rows fail adoption and why
(surfaced here as a named
422per case, not a batch-job log line). - The read ACL (seam A) and write-through ACL (seam B) must work correctly for a partially-adopted population regardless of how adoption happens — that requirement doesn't change once bulk tooling exists.
Consequences
- Until bulk tooling exists, full legacy retirement for a process happens case-by-case, which is slower than a scheduled cutover — accepted as the cost of shipping the seam mechanics now rather than waiting.
- Reversal (§7.6) stays per-case and gated on
domain_writes_sincefor the same reason a bulk reversal would be unsafe absent a sync (docs/sync-not-implemented.md): undoing adoption after edits would silently discard them. - This demo's non-goals (§3) exclude building the bulk migration tool itself — that's future work, not a rejected idea.
Future work
A bulk migration tool for a given process (e.g. registration cases) can reuse the same take-ownership handler per legacy id, adding: pre-flight reporting of which rows would fail adoption and why (so the four invariant-failure classes seen here are triaged before cutover, not discovered during it), a scheduled cutover window, and a decision on whether failed rows block the cutover or are carved out and finished by hand.