docs: archive the finished backlogs (RD-30)
Two backlog trees are complete: `docs/project/backlog/` (75 files, every WP done) and `docs/project/refactor-backlog-setup/` (the arc before it). Move both under `docs/project/archive/` with `git mv`, so history stays intact through `git log --follow`. `SHOWCASE-ROADMAP.md` moves with them, because it points at the now-archived backlog README. Add `docs/project/archive/README.md`. It states that these trees are historical and names the two directories that are still live. Repoint every inbound reference named in RD-30's Files table: CLAUDE.md, the root README, both backend READMEs, `LetterHtml.cs`, `a11y.mdx`, the `document-feature` and `new-ssp` skills, and the readable-codebase PLAN, README, and RD-19 ticket. Fix two upward-relative links inside the moved WP files (WP-68, WP-69) that gained a directory level and would otherwise break. Repoint `.prettierignore`'s two agent-prompt exclusions to their new path, so prettier keeps leaving those files' exact wording alone. Mark RD-30 done and check off its acceptance criteria; flip its README row to done. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,63 @@
|
||||
# RB-07 — audit the allow path, not just the denial
|
||||
|
||||
Status: **implemented** · 2026-08-27 · Source findings: `07-bio2-compliance.md` BIO-007 (+ the outstanding half of CQ-004) · `99-backlog.md` RB-07
|
||||
|
||||
## What was wrong
|
||||
|
||||
All five authorization gates called `AuditAuthz(..., allowed: false, ...)` only on the deny
|
||||
branch; the allow branch called `action()` and returned. So `/beheer/audit` — the queryable
|
||||
trail the product ships as its audit surface — could answer "who was turned away" but never
|
||||
"who changed this".
|
||||
|
||||
Nothing recorded: `PUT /admin/flags/{key}`, `PUT /admin/org-template/{subOrgId}`,
|
||||
`POST /admin/org-template/{subOrgId}/rollback/{version}`, `DELETE /admin/cases/{id}`,
|
||||
`DELETE /admin/uploads/{documentId}`, `POST /brief/approve|reject|send`, and
|
||||
`POST /beoordeling/{id}/besluit`. The comment above `OrgAdmin` claimed the endpoints logged
|
||||
their own effect instead; publish and admin case delete do, the other six did not log at all.
|
||||
|
||||
## What changed
|
||||
|
||||
| File | Change |
|
||||
| ------------------------- | ----------------------------------------------------------------------------- |
|
||||
| `Program.cs` × 5 gates | `var ok = Authz.CanX(p); AuditAuthz(ctx, …, ok, p); if (ok) return action();` |
|
||||
| `Program.cs` `FlagsAdmin` | takes a per-call `resource` (see below) |
|
||||
| `Program.cs` `LogBrief` | takes `HttpContext`, writes the audit row alongside the log line |
|
||||
| `Program.cs` besluit | one `aanvraag:besluit` row recording **what** was decided |
|
||||
| `AuthzAuditTests.cs` | allow-path row; the flag key + value; a refused brief transition |
|
||||
| `BriefEndpointTests.cs` | the allow side of `brief:submit` |
|
||||
| `BeoordelingTests.cs` | the `aanvraag:besluit` row |
|
||||
|
||||
**The row is written by the gate, not the endpoint.** That is the point: a new admin
|
||||
endpoint cannot be added that forgets to audit itself. Same reasoning for the brief — every
|
||||
transition already funnelled through `LogBrief` for its log line, so the audit call went
|
||||
there too, which covers `submit`/`approve`/`reject`/`send` in one place and any fifth
|
||||
transition automatically. The decision recorded is the transition's own outcome, so a 403 or
|
||||
a 409 is as visible as a success.
|
||||
|
||||
**`FlagsAdmin` gained a `resource` parameter** — the one deviation from BIO-007's minimal
|
||||
remediation, and the reason is in the finding itself: the toggle endpoint writes no log line
|
||||
of its own, so a constant `"feature-flags"` row would record that a flag changed without
|
||||
recording _which_. It now writes `feature-flags/<key>=<value>`. One call site.
|
||||
`OrgAdmin`/`CasesAdmin` keep their coarse refs because those endpoints do log the specific
|
||||
object; **that asymmetry is deliberate, not an oversight.**
|
||||
|
||||
**The besluit gets a second row.** The `Beoordelen` gate records that a behandelaar was
|
||||
_allowed to act_; `aanvraag:besluit` records _what they decided_
|
||||
(`aanvraag/<id>/Goedkeuren`). Only the first would leave "who rejected this aanvraag"
|
||||
unanswerable, which is the question the trail exists for.
|
||||
|
||||
## Consequences worth knowing
|
||||
|
||||
- **Row volume goes up.** `StamdataAdmin` gates read endpoints, so every admin page load now
|
||||
writes rows. That is what "audit the allow path" means and BIO-007 asks for it explicitly;
|
||||
if `AuthzAuditStore` ever needs retention or sampling, this is the change that made it
|
||||
necessary.
|
||||
- **This unblocks ADR-C-009.** Clause (4) of agent 06's four-part test is "writes are
|
||||
admin-capability-gated **and** audited". Both surfaces now are, so the amendment can be
|
||||
signed without ratifying a control the code does not implement.
|
||||
- **CQ-004's outstanding half is closed.** `PUT /admin/flags/{key}` writes an audit row.
|
||||
|
||||
## Verification
|
||||
|
||||
`dotnet test`: **252 passed, 1 failed** — the pre-existing
|
||||
`OpenZaakIntegrationTests.Admin_cases_…`, which needs a live container.
|
||||
Reference in New Issue
Block a user