Add docs/vs-nfd/: the analysis brief, the as-is assessment (42 findings, all verified against the code), the prioritized action plan rev. 2 with issue references written back to every checkbox, the two-stage issue/ADR brief, and the full reviewed draft used to create the forge state. Add eight proposed ADRs 0019-0026 covering the VS-NfD architecture decisions: no security base functions (par. 52 VSA anchor), HKDF token key separation, external authentication, page classification, read-access audit trail (variant A), reproducible offline deployment, plugin trust model, and backup target restriction. Forge state created alongside this commit: 11 labels, milestones M24-M31, issues #188-#236 (docs-only change, no code touched). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0168Ph5uBmHm8X28CSVpbpnJ
3.3 KiB
ADR 0023: Read-access audit trail limited to classified content
- Status: proposed
- Date: 2026-07-29
Context
The existing audit trail (audit_log, issue #86) is deliberately scoped to
"who changed access or configuration", and its own service comment states
that content activity stays log-only. There is no record of reads.
For "operable in an approved environment", read logging is not a mandatory product feature — evidence collection can be a platform function. In practice platform logging cannot answer the question that matters: a proxy log knows URLs, not classifications, so it cannot say which classified page was read. Leistungsbeschreibungen tend to list this as a must.
Two variants were considered. Variant B logs all reads (18–20 AT) and brings volume, latency and retention problems, plus the requirement that no event may be lost. Variant A logs reads of classified pages only (8–10 AT).
A live editing session is the volume hazard: Yjs sync means continuous traffic per open document.
Decision
Variant A: read events are recorded only for pages with
classification = VS_NFD. Requires ADR 0022.
- All read channels are instrumented, or the feature is worthless: SPA page fetch, public API GET, attachment download, export, no-JS shell, collab WS join.
- A dedup window (session + page + channel within N minutes = one event) keeps Yjs sync from flooding the trail. The recorded event states that it represents a window, so the evidence is not overread.
- Its own table, with time partitioning and its own retention period —
independent of
audit_log, because volume, purpose and legal basis all differ. - Failure is not silent.
AuditServiceswallows write failures by design; for classified reads a lost event is a gap in evidence, so the behaviour is either hard failure or an explicitly documented degradation. Which one is decided in #222 and stated in the security documentation. - Switchable, with a written purpose limitation. Off means nothing is written anywhere; a startup log line states the trail is off so a gap is never ambiguous.
- Variant B is rejected, and the rejection is recorded rather than left open: unbounded volume, the no-loss requirement, and a purpose limitation that is much harder to defend.
Consequences
- The scope limit is the feature's strongest argument in the works-council discussion at the customer: only classified content is observed.
- Reads of unclassified content are not evidenced. Deliberate, and it goes into the residual-risk list.
- The collab WS join is the awkward channel: authorization there is
token-only (signature plus
pageIdmatch) and the collab server has no permission context. Either the event carries what the token asserts, or the api emits it at token issuance. #222 decides and documents; the choice affects what the trail can prove about live sessions. - Retention and partition maintenance are operational obligations that must ship with the feature, not after it.
- Classification at read time is stored with the event: a later reclassification must not rewrite history.
Implementing issues
#222 (instrumentation), #223 (dedup window), #224 (table, retention, partitioning), #225 (switch + purpose limitation). Depends on #204/#205.