Context
Phase 5 of the retention rollout was originally scoped to add a durable audit-log resource:
- New
nvisy-core::audit::AuditEvent type with a kind union (starting with FileDeleted { reason }).
- New fjall keyspace
audit_events keyed by (actor_id, event_id) (UUIDv7 → time-ordered).
Engine::write_audit_event / Engine::list_audit_events methods.
GET /api/v1/audit-events route with cursor pagination.
- Sweeper emits
FileDeleted { reason: RetentionExpired, source_run_id, at } after each successful sweep.
Engine::delete_file (or the DELETE /files/{id} handler) emits FileDeleted { reason: UserRequested }.
- Wires up
RetentionScope::AuditLogs — currently the sweeper's AuditLogs branch defers.
I built a first pass (AuditEvent + AuditRegistry trait + tests) then backed it out because I couldn't answer why we need it today.
The three real use cases
- Compliance evidence. A regulator asks "prove you deleted this file when the retention deadline hit." Audit log is the answer. Without it, "we ran a sweeper that probably deleted things" is unfalsifiable.
- Operator forensics. User says "my file disappeared" — operator queries by actor to see when/why. Today they'd have to correlate
tracing output with fjall snapshots.
- Runtime debugging. "Did the sweeper delete this today?" — audit log is durable;
tracing scrolls.
Load-bearing today?
- (1) — no. Nothing in
Nvisy.toml claims SOC2/GDPR/HIPAA. Retention policies exist on the wire but no consumer is guaranteed to be watching. If nobody's asking "prove it," durable audit is over-engineering.
- (2) — no. No user-visible complaint mechanism exists. No support ticket flow.
- (3) —
tracing::warn! on the sweeper's sweep_row failures already covers this. Persisting to fjall is redundant unless (1) or (2) becomes real.
Also: RetentionScope::AuditLogs
Policy.retention[AuditLogs] was in the schema from day one but it's aspirational — audit events aren't policy-scoped, so "this policy sets audit retention to N days" doesn't cleanly map. The natural home for audit retention is deployment config (server-wide setting), not per-policy. When phase 5 lands, we should either:
- Reinterpret
AuditLogs scope as deployment-wide (ignore the policy value at compile time; use server config), or
- Deprecate the enum variant (leave for wire back-compat, don't use it).
Options when we revisit
- Minimal audit — only
FileDeleted { RetentionExpired } emitted by the sweeper. GET /api/v1/audit-events for read. Skip user-triggered emits (they duplicate DELETE /files/{id} returning success). Skip audit retention (accept audit rows accumulating; file another issue when it matters). Cheapest way to unlock (2) and (3).
- Full audit — plus
UserRequested emits, plus audit retention wired through server config, plus any other event kinds (rule fired, override applied, run cancelled, etc.).
- No audit — keep the current
tracing output as the operator surface. Delete RetentionScope::AuditLogs from nvisy-core to shrink the schema.
Decision needed
Whether to build any of this, and if so which slice. Blocked on a real user asking for it (compliance officer, support workflow, or debugging pain).
What's already in the repo
Nothing. First pass was backed out; only this issue remains.
Context
Phase 5 of the retention rollout was originally scoped to add a durable audit-log resource:
nvisy-core::audit::AuditEventtype with akindunion (starting withFileDeleted { reason }).audit_eventskeyed by(actor_id, event_id)(UUIDv7 → time-ordered).Engine::write_audit_event/Engine::list_audit_eventsmethods.GET /api/v1/audit-eventsroute with cursor pagination.FileDeleted { reason: RetentionExpired, source_run_id, at }after each successful sweep.Engine::delete_file(or theDELETE /files/{id}handler) emitsFileDeleted { reason: UserRequested }.RetentionScope::AuditLogs— currently the sweeper'sAuditLogsbranch defers.I built a first pass (
AuditEvent+AuditRegistrytrait + tests) then backed it out because I couldn't answer why we need it today.The three real use cases
tracingoutput with fjall snapshots.tracingscrolls.Load-bearing today?
Nvisy.tomlclaims SOC2/GDPR/HIPAA. Retention policies exist on the wire but no consumer is guaranteed to be watching. If nobody's asking "prove it," durable audit is over-engineering.tracing::warn!on the sweeper'ssweep_rowfailures already covers this. Persisting to fjall is redundant unless (1) or (2) becomes real.Also:
RetentionScope::AuditLogsPolicy.retention[AuditLogs]was in the schema from day one but it's aspirational — audit events aren't policy-scoped, so "this policy sets audit retention to N days" doesn't cleanly map. The natural home for audit retention is deployment config (server-wide setting), not per-policy. When phase 5 lands, we should either:AuditLogsscope as deployment-wide (ignore the policy value at compile time; use server config), orOptions when we revisit
FileDeleted { RetentionExpired }emitted by the sweeper.GET /api/v1/audit-eventsfor read. Skip user-triggered emits (they duplicateDELETE /files/{id}returning success). Skip audit retention (accept audit rows accumulating; file another issue when it matters). Cheapest way to unlock (2) and (3).UserRequestedemits, plus audit retention wired through server config, plus any other event kinds (rule fired, override applied, run cancelled, etc.).tracingoutput as the operator surface. DeleteRetentionScope::AuditLogsfromnvisy-coreto shrink the schema.Decision needed
Whether to build any of this, and if so which slice. Blocked on a real user asking for it (compliance officer, support workflow, or debugging pain).
What's already in the repo
Nothing. First pass was backed out; only this issue remains.