docs(adr): land ADR-C-001, ADR-C-003, ADR-C-007 and ADR-C-009
The architect approved the four ADR-fix tickets. All four change what the architecture documents claim. No code changes. ADR-0001, ADR-C-001: the worked example claimed the POC has no real backend. It rewrites against `backend/src/BigRegister.Api`. Every path it named is repointed. The out-of-scope list drops two discharged bullets: 33 `parse*` boundaries exist, and `npm run gen:api` is real. ADR-0001, ADR-C-003: a new section states that the generated client is the wire contract. A hand-written `contracts/*.dto.ts` is the exception for two cases only. The four survivors stay, because NSwag emits every property as optional and flattens `RegistrationStatusDto` into five optional strings. The `parse*` trust boundary stays mandatory, because a generated type is a compile-time claim about the wire and not a runtime guarantee. ADR-0003, ADR-C-007: four paths moved in WP-67 and are repointed. Point 4 kept the principle and changed its example to `skeleton` and `spinner`. Two of its claims were false and the amendment says so: `app-alert` wraps the vendored `.feedback` classes, and `site-header` composes the vendored `.titlebar`. ADR-0004, ADR-C-009: the exception section states a four-part test instead of one named exception. `OrgTemplateStore` and `FeatureFlagStore` both pass it. RB-07 gated this ticket, because clause 4 needs an audited allow path. RB-07 landed that, so the ADR does not ratify a control that the code lacks. Three tickets need a matching CLAUDE.md correction in the same diff. CLAUDE.md section 2 loses the false `alert` example. Section 4 gets the generated-client rule and the four-part test. Two findings were wrong. ADR-C-001 asked to keep an out-of-scope bullet that reads "SessionStore is in-memory". The session persists to `localStorage` now, so the bullet covers multi-tab sync only. ADR-C-007 flagged one half of point 4 and missed that the other half is equally false. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -251,10 +251,10 @@ diff** (CLAUDE.md's own precedence rule: "the docs win — update this file").
|
||||
|
||||
| ID | ADR | What the amendment does | Gates / blocks | CLAUDE.md edit? | Effort | Compliance | Status |
|
||||
| ------------- | ---- | -------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------- | ------ | ------------ | -------- |
|
||||
| **ADR-C-001** | 0001 | Rewrite the worked example against the real backend; drop the 2 discharged out-of-scope bullets (every path it names no longer exists) | nothing | no | S | — | pending |
|
||||
| **ADR-C-003** | 0001 | State that the generated client **is** the wire contract post-codegen; scope `contracts/` to codegen gaps | any ticket deleting the 4 surviving `contracts/*.dto.ts`, or adding a hand-written DTO for a generated endpoint. **No open ticket below is blocked today** — recorded so a future one is. | **yes (§4)** | S | — | pending |
|
||||
| **ADR-C-007** | 0003 | Repoint 5 WP-67-stale paths; replace the **factually false** `app-alert` hand-rolled example (it wraps vendored `.feedback` classes) | nothing | **yes (§2)** | S | — | pending |
|
||||
| **ADR-C-009** | 0004 | Generalise "the org-templates exception" into a stated four-part test; list both passing surfaces | **RB-07.** Clause (4) is "writes are admin-capability-gated **and** audited". Today they are gated and _not_ audited — sign this before RB-07 and the ADR ratifies a control the code does not implement. | **yes (§4)** | S | **SIGN-OFF** | pending |
|
||||
| **ADR-C-001** | 0001 | Rewrite the worked example against the real backend; drop the 2 discharged out-of-scope bullets (every path it names no longer exists) | nothing | no | S | — | **done** |
|
||||
| **ADR-C-003** | 0001 | State that the generated client **is** the wire contract post-codegen; scope `contracts/` to codegen gaps | any ticket deleting the 4 surviving `contracts/*.dto.ts`, or adding a hand-written DTO for a generated endpoint. **No open ticket below is blocked today** — recorded so a future one is. | **yes (§4)** | S | — | **done** |
|
||||
| **ADR-C-007** | 0003 | Repoint 5 WP-67-stale paths; replace the **factually false** `app-alert` hand-rolled example (it wraps vendored `.feedback` classes) | nothing | **yes (§2)** | S | — | **done** |
|
||||
| **ADR-C-009** | 0004 | Generalise "the org-templates exception" into a stated four-part test; list both passing surfaces | **RB-07.** Clause (4) is "writes are admin-capability-gated **and** audited". Today they are gated and _not_ audited — sign this before RB-07 and the ADR ratifies a control the code does not implement. | **yes (§4)** | S | **SIGN-OFF** | **done** |
|
||||
| **ADR-C-005** | 0002 | _(already landed — see "Already done")_ | was the gate on RB-13; now cleared | — | — | — | **done** |
|
||||
|
||||
**No ADR-fix is proposed against ADR-0002 §3's non-sharing rule.** Agent 06 considered it
|
||||
|
||||
@@ -14,14 +14,15 @@
|
||||
|
||||
## Phase 3 — implementation
|
||||
|
||||
| CD batch | Tickets | Status | Notes |
|
||||
| -------- | ---------------------------------------- | ------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| 1 | RB-01, RB-02, RB-03, RB-04, RB-05, RB-06 | **complete** | Six commits on `refactor/adr-c-006-shared-route-guards`, one per ticket, each with `implementation/rb-0N.md`. `npm run ci` green. Every ticket left a test that was verified red without its fix. Carryover: RB-01's residual belongs to **RB-09** (the content endpoint is reached by a plain browser navigation with no identity header — BIO-002); `Pii.MaskTail` now lives in `Domain/People/Pii.cs`, **use it in RB-11** rather than hand-rolling a second masker; RB-06 additionally deleted `SubmissionRules.RejectRegistratie` (judgement call, recorded). |
|
||||
| 2 | RB-07, RB-08, RB-09, RB-10, RB-11 | **complete** | All five merged; `npm run ci` green **on the fixed gate** (see below). **RB-07** unblocks signing **ADR-C-009** and closes CQ-004's outstanding half. **RB-09** makes Production throw at startup when no real `IIdentityProvider` exists — note an environment that is neither Development nor Production (e.g. `Staging`) still fails fast, but at `GetRequiredService`, with a worse message. **RB-10** landed `parseStoredSession` twice, once per app, deliberately — TE-001/BL-002 say extract-to-shared contradicts ADR-0002; **RB-13** differentiates them. **RB-11** corrected a factual error in BIO-012 (the proefbrief error mapping was inlined, not already a separate function) and left the step-up as a literal moved one layer up to its only caller — BIO-006(c) stays a production gate. **RB-01's residual is still open** and is not solved by RB-09: the upload-content link is a plain browser navigation with no credential. |
|
||||
| 3 | RB-12, RB-13, RB-14, RB-15, RB-16, RB-17 | **complete** | All six merged; `npm run ci` green (14 steps — RB-14 added one — backend 260/260). **RB-12 rejected the ticket's binary framing:** of 47 routes only 16 use one of the five admin wrappers; of the remaining 31 only 10 are genuinely public, the other 21 are ownership-scoped inline (`ctx.Zorgverlener()`/`ctx.Caller()`) or use another mechanism. The allow-list therefore carries **a reason per route**, not a blanket "public" label. Known limitation: detection is `.Gate("XAdmin")` metadata declared at mapping time — **a declaration, not a derivation**, so it cannot catch a route that declares a gate it does not have. **This is RB-19's safety net; read `rb-12.md` before starting RB-19.** **RB-13** measured `ssp/auth` ↔ `bhp/auth` duplication at **32 lines each side, down from 168** (backlog expected <40); each app holds only its own `Principal` variant, which is ADR-C-004's own proposed resolution, and ADR-0002's "Known debt" section became an amendment. **RB-14** could not be built as written — `dotnet list package --vulnerable` exits 0 on a High advisory (verified), so a bare `- run:` would have been a gate that enforces nothing; `scripts/dotnet-audit.sh` matches the output instead and is shared by `ci.yml` and `ci-local.sh`. **RB-15** used a third environment name (`Staging`) in its test, since RB-09 makes Production fail to boot at all. | |
|
||||
| 4 | RB-18..RB-23 | not started | RB-19 is the only **High**-risk ticket; it needs RB-12's route-table test first. |
|
||||
| 5 | RB-24..RB-30 | not started | RB-25/26/27 all depend on RB-24. |
|
||||
| 6 | RB-31, RB-32, RB-33 | not started | |
|
||||
| CD batch | Tickets | Status | Notes |
|
||||
| -------- | ------------------------------------------ | ------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| 1 | RB-01, RB-02, RB-03, RB-04, RB-05, RB-06 | **complete** | Six commits on `refactor/adr-c-006-shared-route-guards`, one per ticket, each with `implementation/rb-0N.md`. `npm run ci` green. Every ticket left a test that was verified red without its fix. Carryover: RB-01's residual belongs to **RB-09** (the content endpoint is reached by a plain browser navigation with no identity header — BIO-002); `Pii.MaskTail` now lives in `Domain/People/Pii.cs`, **use it in RB-11** rather than hand-rolling a second masker; RB-06 additionally deleted `SubmissionRules.RejectRegistratie` (judgement call, recorded). |
|
||||
| 2 | RB-07, RB-08, RB-09, RB-10, RB-11 | **complete** | All five merged; `npm run ci` green **on the fixed gate** (see below). **RB-07** unblocks signing **ADR-C-009** and closes CQ-004's outstanding half. **RB-09** makes Production throw at startup when no real `IIdentityProvider` exists — note an environment that is neither Development nor Production (e.g. `Staging`) still fails fast, but at `GetRequiredService`, with a worse message. **RB-10** landed `parseStoredSession` twice, once per app, deliberately — TE-001/BL-002 say extract-to-shared contradicts ADR-0002; **RB-13** differentiates them. **RB-11** corrected a factual error in BIO-012 (the proefbrief error mapping was inlined, not already a separate function) and left the step-up as a literal moved one layer up to its only caller — BIO-006(c) stays a production gate. **RB-01's residual is still open** and is not solved by RB-09: the upload-content link is a plain browser navigation with no credential. |
|
||||
| 3 | RB-12, RB-13, RB-14, RB-15, RB-16, RB-17 | **complete** | All six merged; `npm run ci` green (14 steps — RB-14 added one — backend 260/260). **RB-12 rejected the ticket's binary framing:** of 47 routes only 16 use one of the five admin wrappers; of the remaining 31 only 10 are genuinely public, the other 21 are ownership-scoped inline (`ctx.Zorgverlener()`/`ctx.Caller()`) or use another mechanism. The allow-list therefore carries **a reason per route**, not a blanket "public" label. Known limitation: detection is `.Gate("XAdmin")` metadata declared at mapping time — **a declaration, not a derivation**, so it cannot catch a route that declares a gate it does not have. **This is RB-19's safety net; read `rb-12.md` before starting RB-19.** **RB-13** measured `ssp/auth` ↔ `bhp/auth` duplication at **32 lines each side, down from 168** (backlog expected <40); each app holds only its own `Principal` variant, which is ADR-C-004's own proposed resolution, and ADR-0002's "Known debt" section became an amendment. **RB-14** could not be built as written — `dotnet list package --vulnerable` exits 0 on a High advisory (verified), so a bare `- run:` would have been a gate that enforces nothing; `scripts/dotnet-audit.sh` matches the output instead and is shared by `ci.yml` and `ci-local.sh`. **RB-15** used a third environment name (`Staging`) in its test, since RB-09 makes Production fail to boot at all. | |
|
||||
| 4 | RB-18..RB-23 | in progress | Split into three waves to keep the merge order honest, because three of the six tickets touch `Program.cs`. **Wave A (dispatched, parallel):** RB-18, RB-20, RB-21, RB-22 — no file overlap between them. **Wave B:** RB-23, which must merge after RB-22 (expand/contract pair: the FE must tolerate the 404 before the BE returns it). **Wave C:** RB-19 alone and last — it is the only **High**-risk ticket, it reorders all 48 endpoints in `Program.cs`, and landing it last means it reorders the final content instead of conflicting with RB-18's and RB-23's edits to the same file. RB-19 also needs RB-12's route-table test as its safety net; read `rb-12.md` first, including its stated limitation that detection is a declaration, not a derivation. |
|
||||
| 5 | RB-24..RB-30 | not started | RB-25/26/27 all depend on RB-24. |
|
||||
| 6 | RB-31, RB-32, RB-33 | not started | |
|
||||
| ADR-fix | ADR-C-001, ADR-C-003, ADR-C-007, ADR-C-009 | **complete** | All four signed and landed by the architect on 2026-08-27, in one commit; doc-only, no code touched. Three carried the mandatory matching `CLAUDE.md` edit in the same diff (§4 twice, §2 once). **ADR-C-009's RB-07 gate was satisfied first** — all four clauses of its new test were verified against both `OrgTemplateStore` and `FeatureFlagStore` before signing, so the ADR does not ratify a control the code lacks. **Two findings were wrong and are corrected in the notes:** ADR-C-001 told us to keep an out-of-scope bullet reading "`SessionStore` is in-memory", which RB-10/RB-13 made false (the session now persists to `localStorage`; only multi-tab sync is still open), and ADR-C-007 flagged only the `.alert` half of ADR-0003's point 4 — its "header/side-nav use `.nav` + a local blue bar" clause is equally false (`site-header` composes the vendored `.titlebar`/`.logo__*`). ADR-C-007 also over-listed one path: `public/cibg-huisstijl/` never moved. ADR-C-003's open question was decided explicitly — **the 4 hand-written `contracts/*.dto.ts` stay**, because NSwag emits every property optional and flattens `RegistrationStatusDto` into five optional strings, which would make an illegal state representable (CLAUDE.md §3). Gates released: ADR-C-003 (contracts cleanup) and ADR-C-009 (a third runtime-editable surface). Still pending, untouched: **ADR-C-008 → RB-32** — 9 `CIBG-GAP` markers vs 8 register rows, missing row is `language-switcher`. |
|
||||
|
||||
**Standing caveat for every batch:** `dotnet test` reports one failure,
|
||||
`OpenZaakIntegrationTests.Admin_cases_returns_the_seeded_zaak_mapped_through_real_HTTP_and_JWT`,
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
# ADR-C-001 — rewrite ADR-0001's worked example against the shipped system
|
||||
|
||||
Status: **implemented** · 2026-08-27 · Source finding: `06-adr-conformance.md` §ADR-C-001
|
||||
|
||||
## What was wrong
|
||||
|
||||
ADR-0001's §"Worked example in this POC" opened with _"This POC has no real backend (static
|
||||
mock JSON + fake submit timers), so the 'BFF output' is a static file"_. That premise is
|
||||
false and every path the section cited was gone. The decision itself was intact; only the
|
||||
description had drifted.
|
||||
|
||||
## What changed
|
||||
|
||||
| File | Change |
|
||||
| -------------------------------------------------------- | --------------------------------------------------------------------------- |
|
||||
| `docs/reference/architecture/0001-...md` §Worked example | rewritten against `backend/src/BigRegister.Api`; all six paths repointed |
|
||||
| same file, §Out of scope here | 4 bullets → 2, plus a paragraph recording which two were discharged and why |
|
||||
|
||||
No code changed. No CLAUDE.md edit was required for this finding.
|
||||
|
||||
## Paths corrected, each verified
|
||||
|
||||
| Claimed | Actual |
|
||||
| -------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
||||
| "no real backend … static file" | `backend/src/BigRegister.Api`, `var api = app.MapGroup("/api/v1")` at `Program.cs:168` |
|
||||
| `public/mock/dashboard-view.json` | `GET /api/v1/dashboard-view` (`Program.cs:172`) |
|
||||
| `public/mock/intake-policy.json` | `GET /api/v1/intake/policy` (`Program.cs:193`) |
|
||||
| `src/app/registratie/contracts/dashboard-view.dto.ts` | `apps/ssp/src/app/registratie/contracts/dashboard-view.dto.ts` |
|
||||
| `src/app/registratie/infrastructure/dashboard-view.adapter.ts` | `apps/ssp/.../infrastructure/dashboard-view.adapter.ts`, `parseDashboardView` at `:50` |
|
||||
| `src/app/herregistratie/contracts/intake-policy.dto.ts` | **deleted** — the DTO is now the generated `IntakePolicyDto`; the adapter is `apps/ssp/src/app/herregistratie/infrastructure/intake-policy.adapter.ts` |
|
||||
|
||||
`apps/ssp/public/mock/` does not exist (`ls`: no such directory).
|
||||
|
||||
## The finding was wrong about one out-of-scope bullet
|
||||
|
||||
ADR-C-001 said to _"reduce §Out of scope to the two items still genuinely open (the
|
||||
`BigProfileStore` optimistic-update race, and session persistence / multi-tab sync)"_,
|
||||
carrying the original bullet's parenthetical **"`SessionStore` is in-memory"**. That
|
||||
parenthetical is no longer true, so the bullet could not be kept verbatim.
|
||||
|
||||
- `apps/ssp/src/app/auth/application/session.store.ts:13` reads
|
||||
`parseStoredPrincipal(localStorage.getItem(STORAGE_KEY))`, and `:41` writes it back.
|
||||
Session persistence **has landed** (RB-10 extracted the parser, RB-13 renamed it
|
||||
`parseStoredPrincipal`). The file even carries a `ponytail:` note explaining the choice of
|
||||
`localStorage` over `sessionStorage`.
|
||||
- Multi-tab sync has **not** landed: `grep` for a `storage` event listener across `apps` and
|
||||
`libs` returns nothing.
|
||||
|
||||
The bullet was therefore narrowed to multi-tab sync only, and states that the session itself
|
||||
now persists. Recording this because the finding, taken literally, would have re-asserted a
|
||||
false claim in the same edit that removed two others.
|
||||
|
||||
The other two survivors were verified rather than assumed: `BigProfileStore` still holds
|
||||
`pending` as a bare `signal(false)` with `begin`/`confirm`/`rollback` mutating it
|
||||
(`big-profile.store.ts:61-74`), so the concurrent-submit race is real.
|
||||
|
||||
## Discharged bullets, both verified
|
||||
|
||||
- _"Runtime DTO validation on **every** endpoint (only the dashboard view has it)"_ — 33
|
||||
distinct `export function parse*` boundary functions exist across `apps` and `libs`.
|
||||
- _"Real OpenAPI/TypeSpec codegen toolchain"_ — `npm run gen:api` (`package.json:12`) runs
|
||||
`dotnet swagger tofile` then `nswag run`, emitting
|
||||
`libs/shared/src/infrastructure/api-client.ts` (2329 lines). CI's `api-client-drift` job
|
||||
regenerates and runs `git diff --exit-code` (`.github/workflows/ci.yml:319-321`).
|
||||
|
||||
## Scope discipline
|
||||
|
||||
Descriptive drift only, as the finding states. The decision, the options table, the two
|
||||
policy shapes and the migration sequence are untouched.
|
||||
@@ -0,0 +1,64 @@
|
||||
# ADR-C-003 — state that the generated client is the wire contract
|
||||
|
||||
Status: **implemented** · 2026-08-27 · Source finding: `06-adr-conformance.md` §ADR-C-003
|
||||
|
||||
## What was wrong
|
||||
|
||||
ADR-0001 set "one source of truth that generates types for both sides" as the target state.
|
||||
The code reached it. CLAUDE.md §4 still stated the pre-codegen rule — _"DTO lives in
|
||||
`contracts/`"_ — as standing law, so §4 could be cited to justify both deleting the four
|
||||
survivors and adding new hand-written DTOs for already-generated endpoints.
|
||||
|
||||
## What changed
|
||||
|
||||
| File | Change |
|
||||
| ---------------------------------------- | ------------------------------------------------------------------------------------------------------ |
|
||||
| `docs/reference/architecture/0001-...md` | **new** §"Where the contract lives, after codegen" |
|
||||
| `CLAUDE.md` §4 | the flat "DTO lives in `contracts/`" rule replaced with the generated-client rule + the two exceptions |
|
||||
|
||||
No code changed. Per CLAUDE.md's own precedence rule, the ADR was amended first and
|
||||
CLAUDE.md corrected to match, in one diff.
|
||||
|
||||
## The decision the finding asked for: the four survivors stay
|
||||
|
||||
ADR-C-003 required an explicit, recorded decision on the four remaining hand-written DTOs.
|
||||
**They stay**, all four under exception case 2 ("the generator types the shape too loosely").
|
||||
This is not a preference — adopting the generated shapes would violate CLAUDE.md §3.
|
||||
|
||||
Evidence. NSwag emits every property as optional, and flattens a discriminated union into a
|
||||
bag of optional fields:
|
||||
|
||||
| | generated (`api-client.ts`) | hand-written (`dashboard-view.dto.ts`) |
|
||||
| ----------------------- | ---------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------- |
|
||||
| `DashboardViewDto` | `registration?`, `person?`, `decisions?` — all optional (`:2017`) | all three required |
|
||||
| `RegistrationDto` | six optional fields (`:2202`) | six required fields |
|
||||
| `RegistrationStatusDto` | **one flat record of five optional strings**, `tag?: string` (`:2211`) | a real union of three variants, `tag: 'Geregistreerd' \| 'Geschorst' \| 'Doorgehaald'`, per-variant fields required |
|
||||
|
||||
The generated `RegistrationStatusDto` makes `{ tag: 'Geregistreerd', doorgehaaldOp: '…' }`
|
||||
representable. That is precisely the illegal state CLAUDE.md §3 exists to forbid, and the
|
||||
`parse*` boundary would have to reconstruct the union by hand anyway.
|
||||
|
||||
The ADR therefore records that retiring these four is **not** a cleanup to schedule. It
|
||||
becomes correct only if the backend annotates its DTOs so the generator emits required
|
||||
properties and real unions — which names the actual prerequisite instead of leaving the
|
||||
question open.
|
||||
|
||||
## Verified counts, not carried over from the finding
|
||||
|
||||
- Hand-written `contracts/*.dto.ts`: **4** —
|
||||
`apps/ssp/src/app/registratie/contracts/{brp-address,dashboard-view,duo-diplomas}.dto.ts`
|
||||
and `libs/beheer/src/contracts/stamdata.dto.ts`.
|
||||
- All four duplicate generated types **by the same names**: `BrpAddressDto` (`:1997`),
|
||||
`DashboardViewDto` (`:2017`), `DuoLookupDto` (`:2056`), `DuoDiplomaDto` (`:2047`),
|
||||
`PolicyQuestionDto` (`:2168`), `ManualDiplomaPolicyDto` (`:2106`), `StamdataColumnDto`
|
||||
(`:2246`), `StamdataTableDto` (`:2253`), `StamdataTableSummaryDto` (`:2261`). None is a
|
||||
codegen gap — the finding's "case 1" has no occupant today, which is worth knowing.
|
||||
- The `parse*` boundary is restated as mandatory regardless of type provenance. The amendment
|
||||
says why in one line: a generated type is a compile-time claim about the wire, not a
|
||||
runtime guarantee.
|
||||
|
||||
## Gate released
|
||||
|
||||
ADR-C-003 blocked any ticket that would delete the four `contracts/*.dto.ts` files or add a
|
||||
hand-written DTO for a generated endpoint. No open ticket needed it. The rule is now written
|
||||
down, so a future one can be judged against it rather than against a stale §4.
|
||||
@@ -0,0 +1,65 @@
|
||||
# ADR-C-007 — repoint ADR-0003's WP-67 paths and fix its point 4
|
||||
|
||||
Status: **implemented** · 2026-08-27 · Source finding: `06-adr-conformance.md` §ADR-C-007
|
||||
|
||||
## What was wrong
|
||||
|
||||
Two separate defects in one ADR. Every file path in ADR-0003 predated WP-67's monorepo move,
|
||||
and decision point 4 made a claim about `app-alert` that the code contradicts.
|
||||
|
||||
## What changed
|
||||
|
||||
| File | Change |
|
||||
| ---------------------------------------- | ---------------------------------------------------------------------------------- |
|
||||
| `docs/reference/architecture/0003-...md` | points 1, 2, 4 and both §Consequences bullets rewritten |
|
||||
| `CLAUDE.md` §2 | the `alert` parenthetical corrected to `skeleton`/`spinner` + a denial for `alert` |
|
||||
|
||||
No code changed. ADR first, CLAUDE.md to match, one diff.
|
||||
|
||||
## Paths, each verified
|
||||
|
||||
| Claimed | Actual |
|
||||
| ------------------------ | ---------------------------------------------------------------------- |
|
||||
| `src/styles.scss` | `libs/shared/styles.scss` — one copy, both apps' `angular.json:41,169` |
|
||||
| `src/index.html` | `apps/ssp/src/index.html` **and** `apps/behandelportal/src/index.html` |
|
||||
| `.storybook/` | `.storybook-ssp/` and `.storybook-behandelportal/` |
|
||||
| `src/docs/cibg-gaps.mdx` | `libs/shared/docs/cibg-gaps.mdx` |
|
||||
|
||||
**One path in the finding's list needed no change.** ADR-C-007 implied point 1's
|
||||
`public/cibg-huisstijl/` had moved with the rest. It has not: `public/` is still at the repo
|
||||
root, and both apps' `angular.json` asset entries read `"input": "public"` (`:38`, `:166`).
|
||||
Both Storybook configs serve it as `staticDirs: ['../public']`. Point 1's vendoring path is
|
||||
left as written; only its `index.html` clause changed.
|
||||
|
||||
## Point 4: the finding was right, and understated
|
||||
|
||||
ADR-C-007 flagged the `.alert` half of point 4. Verified: `libs/shared/src/ui/alert/alert.component.ts`
|
||||
documents itself as a _"Thin wrapper over the vendored `.feedback feedback-*` classes"_, its
|
||||
template binds `.feedback-info/-success/-warning/-error`, its only local CSS is a 3-line flex
|
||||
fix, and it carries **no** `CIBG-GAP EXTENSION` marker. `grep` confirms `feedback-error` is
|
||||
present in `public/cibg-huisstijl/css/huisstijl.css` — the class is vendored, so `alert` is not
|
||||
a gap.
|
||||
|
||||
**The finding missed that the same sentence's second claim is also false.** Point 4 said "the
|
||||
header/side-nav use `.nav` + a local blue bar". They do not:
|
||||
|
||||
- `site-header.component.ts` composes the vendored `.titlebar` and `.logo__*` classes
|
||||
(`grep` confirms `titlebar` in the vendored CSS) and its own comment says the titlebar
|
||||
_"keeps its own robijn fill — `--ro-layout` — untouched"_.
|
||||
- `shell.component.ts` emits only `.layout`, `.main`, `.content`, `.skip` — page scaffolding.
|
||||
- No `.nav` class appears in either, and neither carries a gap marker.
|
||||
|
||||
Both corrections are stated in the amended point 4 rather than silently dropped, so a reader
|
||||
comparing the old text against the code can see which claim was retired and why.
|
||||
|
||||
## Replacement example chosen
|
||||
|
||||
`skeleton` and `spinner`, as the finding proposed. Both are in the gap register, both carry
|
||||
markers reading "No loading-skeleton/spinner class in the vendored build", and both are
|
||||
genuinely absent — the cleanest live illustration of the principle point 4 exists to state.
|
||||
|
||||
## Noted, not fixed: the gap register is still one row short
|
||||
|
||||
`grep` finds **9** `CIBG-GAP EXTENSION` markers; `libs/shared/docs/cibg-gaps.mdx` has **8**
|
||||
rows. The missing one is `language-switcher`. That is **ADR-C-008 → RB-32** (batch 6), not
|
||||
this ticket, and it was left alone.
|
||||
@@ -0,0 +1,71 @@
|
||||
# ADR-C-009 — state the runtime-editable-config exception as a test, not a list
|
||||
|
||||
Status: **implemented** · 2026-08-27 · Source finding: `06-adr-conformance.md` §ADR-C-009
|
||||
· Gated on: **RB-07** (satisfied — batch 2)
|
||||
|
||||
## What was wrong
|
||||
|
||||
ADR-0004 said "never runtime-editable" and then named **one** exception in the singular,
|
||||
justified narrowly ("specific to one sub-organization's identity"). WP-47 added a second
|
||||
runtime-editable SQLite surface, `FeatureFlagStore`, whose own doc-comment states the
|
||||
equivalence the ADR did not: _"SQLite-backed like `OrgTemplateStore`, same single-gate
|
||||
idiom."_
|
||||
|
||||
The code is right; the ADR's text was wrong. A closed list of one leaves the next
|
||||
operational-config surface with no principle to test itself against.
|
||||
|
||||
## What changed
|
||||
|
||||
| File | Change |
|
||||
| ---------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `docs/reference/architecture/0004-...md` | §"The deliberate exception: org-templates" → §"The deliberate exception: operational configuration" — a four-part test plus a table of the two passing surfaces |
|
||||
| same file, §Context + the table | `src/locale/*.xlf` → `apps/<app>/src/locale/*.xlf` (two apps since WP-67) |
|
||||
| `CLAUDE.md` §4 | the singular "Org-templates are the deliberate exception" replaced with the four-part test |
|
||||
|
||||
No code changed — the finding says so outright, and verification confirmed it.
|
||||
|
||||
## Why the RB-07 gate was real, verified clause by clause
|
||||
|
||||
Clause (4) of the test is "writes are admin-capability-gated **and** audited". Signing this
|
||||
ADR before RB-07 would have ratified a control the code did not implement. RB-07 has landed,
|
||||
so the clause is now true. Read at `backend/src/BigRegister.Api/Program.cs:863-923`: each of
|
||||
the five gates now computes `var ok = …`, calls `AuditAuthz(ctx, capability, resource, ok,
|
||||
principal)` with the **real** boolean, and only then branches. `FlagsAdmin`'s own comment
|
||||
names this ticket: _"this is the surface CQ-004/ADR-C-009 hinge on."_
|
||||
|
||||
All four clauses were checked against both surfaces rather than assumed:
|
||||
|
||||
| Clause | `OrgTemplateStore` | `FeatureFlagStore` |
|
||||
| ------------------- | ----------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------- |
|
||||
| (1) catalog in code | the `OrgTemplateDto` shape + `OrgTemplateRules` validate before save (`OrgTemplateStore.cs:49`) | `FeatureFlags.Catalog` (`Domain/Features/FeatureFlags.cs:15`) |
|
||||
| (2) fails closed | unknown `subOrgId` → `null` → endpoint 404s (`:44-45,:55-56,:72-73,:94-96`) | `Set` returns false for an unlisted key (`:54`); `IsEnabled` returns false (`:42-43`) |
|
||||
| (3) operational | one sub-organisation's letterhead | an on/off rollout switch |
|
||||
| (4) gated + audited | `OrgAdmin` → `orgtemplate:edit` (`Program.cs:863`) | `FlagsAdmin` → `flags:manage` (`Program.cs:914`) |
|
||||
|
||||
`FeatureFlagStore`'s own comment states clause (1) and (2) explicitly: _"The CATALOG … is
|
||||
code … this store only holds the admin's on/off overrides. An unknown key is never
|
||||
writable/enabled — the code catalog is the authority."_
|
||||
|
||||
## Judgement calls
|
||||
|
||||
- **Clause (2) is about the write/enable path, not every read.** `OrgTemplateStore` has a
|
||||
deliberate read-path fallback for briefs from before WP-23 (`:110-114`, its own `ponytail:`
|
||||
comment): an empty `SubOrgId` falls back to the first seeded sub-org rather than failing a
|
||||
whole screen. That is a preview convenience on a read; the four write entry points all
|
||||
return `null` for an unknown sub-org. The clause is worded "cannot invent a setting,
|
||||
enable a feature, or be written" so this read fallback is not caught by it. Recorded
|
||||
because a reader checking clause (2) against `OrgTemplateStore.cs` will meet that
|
||||
fallback first.
|
||||
- **Org-templates' publish/rollback versioning is mentioned but excluded from the test.** It
|
||||
is stronger than the test requires, and making it a fifth clause would block a legitimate
|
||||
flag-style surface that has nothing to version.
|
||||
- **The stale `src/locale/*.xlf` paths were fixed in the same diff**, though ADR-C-009 did
|
||||
not flag them. They are two occurrences of the same WP-67 drift ADR-C-001 and ADR-C-007
|
||||
exist to correct, in the section being edited, and leaving a known-false path in a document
|
||||
while amending it is the exact failure mode those two findings describe. Scope creep is
|
||||
two words wide here; the alternative is filing a third ticket for it.
|
||||
|
||||
## Gate released
|
||||
|
||||
ADR-C-009 blocked "any ticket proposing a third runtime-editable config surface". Such a
|
||||
ticket can now be judged against a written test rather than by analogy to org-templates.
|
||||
Reference in New Issue
Block a user