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>
2.1 KiB
RB-04 — mask the BSN recorded as AuditEntry.Actor
Status: implemented · 2026-08-27 · Source findings: 07-bio2-compliance.md BIO-005 · 99-backlog.md RB-04
What was wrong
DocumentStore writes one audit row per upload and per user delete, with the acting
citizen's raw BSN as AuditEntry.Actor, persisted to SQLite. The class's own doc comment
says "The audit log holds metadata only (never file content or other PII)" — a BSN in
every row is precisely other PII. Same failure shape as RB-02, in a second store.
What changed
| File | Change |
|---|---|
Data/DocumentStore.cs Add |
Audit("upload", …, Pii.MaskTail(owner, 3)) |
Data/DocumentStore.cs DeleteOwned |
Audit("delete-user", …, Pii.MaskTail(owner, 3)) |
Data/DocumentStore.cs Audit |
doc comment: actors arrive already redacted |
UploadAccessTests.cs |
new The_document_audit_trail_records_a_masked_actor |
Masked at the two call sites, not inside Audit — unlike RB-03, where masking in the
mapper was the point. Audit's third actor is the literal "admin" (from AdminDelete),
and MaskTail("admin", 3) is "**min": masking centrally would mean guessing which
actors are BSNs and which are role names. The contract is stated on Audit instead.
StoredDocument.Owner is untouched, per the ticket. It is the authorization key —
DeleteOwned, ForeignIds and now the RB-01 content check all compare against it — so it
has to stay whole. The BSN remains where it is load-bearing and leaves the trail where it
was only decoration.
Nothing reads DocumentStore.AuditLog today (no endpoint exposes it), so this is a
data-at-rest fix with no response-shape change.
Verification
dotnet format --verify-no-changes clean. dotnet test: 252 passed, 1 failed — the
pre-existing OpenZaakIntegrationTests.Admin_cases_…, which needs a live container.