# `retention` _(no submodules)_ ## Purpose Enforces the `maxFiles` retention dimension of the rotation policy by deleting the oldest rotated files until at most `maxFiles` remain. Retention runs immediately after each rotation succeeds so the disk footprint is bounded across long-running deployments — an audit directory left to grow unchecked is the most common operator pain point with file-based sinks, and this module's existence is exactly the answer to it. The increment on `api.audit.rotation_evicted_total{sink="file"}` makes eviction observable. Operators alert on an unexpectedly high rate so a runaway rotation cycle (e.g. clock skew, misconfigured `maxAgeMs`) is visible before the retention sweep drops historical data. ## Features - `enforceRetention(fs, basePath, maxFiles, instruments)` — deletes the oldest rotated files until at most `maxFiles` remain. Idempotent: re-running with no surplus is a no-op. - Counter increment per deletion with the canonical `sink="file"` attribute set. - Surfaces `AUDIT_FILE.RETENTION_FAILED` on a delete failure with the offending path in `context.path` and the underlying `cause`. ## Usage: Basic ```ts import { enforceRetention } from "@suaveplan/api-audit-file/retention"; await enforceRetention(fs, "/var/log/audit.log", 30, instruments); ``` ## Usage: Advanced Call from a custom rotation cadence — e.g. an external cron that wants to prune without rotating: ```ts import { enforceRetention } from "@suaveplan/api-audit-file/retention"; // Cron job: every hour, prune to a smaller retention than the sink's // default to claw back disk during high-traffic windows. setInterval(async () => { await enforceRetention(fs, basePath, 10, instruments); }, 60 * 60 * 1000); ``` ## API Reference - `enforceRetention(fs: FsAdapter, basePath: string, maxFiles: number, instruments: AuditFileInstruments): Promise` ## Implementation Notes `listRotatedFiles` returns paths sorted oldest-first by the embedded ISO timestamp; we slice off the first `(count - maxFiles)` entries and unlink them. Sorting is lexicographic on the ISO string, which is also chronological for the `YYYY-MM-DDTHH-MM-SS.sssZ` shape — no extra parsing required. If `unlink` fails midway, the previously-evicted deletions are not undone; the throw surfaces the failure and the next rotation re-attempts retention against whatever survived.