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>
51 lines
2.6 KiB
Markdown
51 lines
2.6 KiB
Markdown
# ADR-002: The write-through translator carries no business rules
|
|
|
|
## Status
|
|
Accepted.
|
|
|
|
## Context
|
|
Seam B lets a user edit a **legacy-owned** case's applicant details (name,
|
|
address, contact) from the new portal, without the new system taking
|
|
ownership of that case. The legacy system remains the authority on this data
|
|
until ownership is explicitly taken (§7.5).
|
|
|
|
It is tempting, once a translation layer exists between the portal's request
|
|
shape and legacy's `PUT /api/aanvragen/{id}/gegevens` shape, to also smuggle
|
|
in a validation shortcut or two — "just check the postcode format here too, it
|
|
saves a round trip." That temptation is exactly what this ADR forecloses.
|
|
|
|
## Decision
|
|
`New.Infrastructure.Legacy`'s write-through translator (the type backing
|
|
`ILegacyCaseGateway.UpdateDetailsAsync`) contains **no business rules**: no
|
|
conditionals on request values, no validation beyond null/shape checks, no
|
|
derived values, no defaulting. It only:
|
|
|
|
1. Maps the portal's 9-field request onto legacy's expected shape.
|
|
2. Calls `PUT legacy-backend/api/aanvragen/{id}/gegevens`.
|
|
3. Maps legacy's response — success, or **every** returned field error via the
|
|
`veld`/`code` → portal-field-path table — back into the portal's error
|
|
shape, including a generic fallback for any unrecognized legacy code
|
|
(logged as a warning, never dropped or guessed at).
|
|
|
|
If a rule needs to be enforced on this data from the new portal, that is a
|
|
signal the capability should be taken into ownership instead (§7.5), not
|
|
patched into the translator.
|
|
|
|
## Consequences
|
|
- The portal cannot offer a better validation experience than legacy already
|
|
has for this seam — by design. The `Gevalideerd door het legacy systeem`
|
|
notice on the write-through form (§8.3) exists specifically so the user
|
|
knows why: this is the honest version of a seamless UI, not a limitation to
|
|
hide.
|
|
- Rule 11 in Architecture.Tests (no `New.Api` type both constructs a legacy
|
|
request DTO and touches a `DbContext`) is only a **partial**, structural
|
|
proxy for this constraint — and is already close to vacuous given rule 3
|
|
(legacy DTOs are `internal` to `New.Infrastructure.Legacy` with no
|
|
`InternalsVisibleTo` grant, so `New.Api` cannot even name them). The
|
|
stronger claim this ADR makes — that the translator itself contains no
|
|
conditional business logic — is a **code-review rule**, not a
|
|
machine-enforced one. We say so here rather than implying test coverage
|
|
that does not exist.
|
|
- Any future temptation to "just add one small check" in the translator
|
|
should instead be read as a signal to take that capability into ownership.
|