Skip to content

fix(audit): retention TTL on runtime-written audit rows (v0.1.25.15)#108

Merged
amavashev merged 1 commit into
mainfrom
fix/audit-log-retention-ttl-v0.1.25.15
Apr 18, 2026
Merged

fix(audit): retention TTL on runtime-written audit rows (v0.1.25.15)#108
amavashev merged 1 commit into
mainfrom
fix/audit-log-retention-ttl-v0.1.25.15

Conversation

@amavashev
Copy link
Copy Markdown
Collaborator

Summary

Closes the retention-TTL gap surfaced by the post-v0.1.25.14 alignment audit against cycles-server-admin. Runtime's AuditRepository.log() previously wrote audit:log:{id} keys with no EXPIRE, so runtime-written rows persisted indefinitely until Redis memory-eviction — silently failing to participate in the 400-day retention tier admin applies to authenticated audit rows.

  • LOG_AUDIT_LUA now reads ARGV[4] as an optional TTL in seconds (same shape as admin's Lua, minus sentinel branching).
  • New audit.retention.days config (default 400, env AUDIT_RETENTION_DAYS). 0 = indefinite retention for legal-hold / archive-store deployments.
  • New audit.sweep.cron config (default 0 0 3 * * *, env AUDIT_SWEEP_CRON). Daily @Scheduled sweep prunes stale ZSET pointers on audit:logs:_all + per-tenant indexes. Mirrors admin's sweepStaleIndexEntries() so runtime is self-healing regardless of whether admin is colocated.

Runtime only writes real-tenant audit rows (never the admin-plane __admin__ / __unauth__ sentinels), so a single-tier TTL is sufficient — unlike admin's two-tier setup.

No wire change. No API change. Admin's reader is already null-body-tolerant for TTL-expired pointers.

Test plan

  • New AuditRepositoryTest — 7 cases (TTL passed as ARGV[4] = 400×86400, retention=0 passes "0", logId + timestamp set, log() failure non-fatal, sweeper removes global + per-tenant pointers, sweeper no-op when retention=0, sweep failure non-fatal).
  • Full data module: 365 / 365 green.
  • Full api module: 152 / 152 green.
  • Manual Redis smoke: write an audit row via the admin-on-behalf-of release path, redis-cli TTL audit:log:<id> returns a positive integer near 34560000 (400 × 86400).
  • Parallel-sweep check: run alongside admin's sweep against the same Redis — no errors, no double-deleted pointers.

Runtime's AuditRepository.log() wrote audit:log:{id} keys with no EXPIRE,
so runtime-written rows persisted indefinitely until Redis eviction —
silently failing to participate in the 400-day retention tier admin
applies to authenticated audit rows.

Fix:
- LOG_AUDIT_LUA now reads ARGV[4] as an optional TTL in seconds
  (same shape as cycles-server-admin's script, minus sentinel branching).
- audit.retention.days config (default 400, env AUDIT_RETENTION_DAYS).
  0 = indefinite retention for legal-hold / archive-store deployments.
- audit.sweep.cron config (default 0 0 3 * * *) — daily @scheduled
  sweep of stale ZSET pointers (audit:logs:_all + per-tenant indexes).
  Mirrors admin's sweepStaleIndexEntries() so runtime is self-healing
  regardless of whether admin is colocated on the same Redis.

Runtime never writes the admin-plane __admin__ / __unauth__ sentinels,
so a single-tier TTL is sufficient (unlike admin's two-tier setup).

Tests:
- New AuditRepositoryTest — 7 cases: TTL passed as ARGV[4],
  retention=0 passes "0", logId + timestamp set, log() failure
  non-fatal, sweeper removes global + per-tenant pointers, sweeper
  no-op when retention=0, sweep failure non-fatal.
- Full data module: 365 / 365 green. Full api module: 152 / 152 green.
@amavashev amavashev merged commit f5a5773 into main Apr 18, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant