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:
@@ -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