---
id: w4/m10/t002
title: audit_events table in internal/store + retention cap
worker: worker4
status: done
estimate: 25m
depends_on: [w4/m10/t001]
---

## Objective

Persist audit events in the control-plane store: an `audit_events` migration (id, tenant, caller, verb, resource, outcome, at — indexed by tenant + time), a store-backed `AuditSink`, and a documented retention cap so the table can't grow unbounded.

## Context

- The store (`lego/backend/internal/store`, w1/m2) already runs embedded migrations; follow its migration conventions.
- Tenant column keys to the caller's resolved workspace (`tea-…` id) so w1/m9's scoping applies to audit reads too; `workspace:default` is reserved for store-less/single-operator mode (w1/m9 shipped 2026-07-09 — every prod caller resolves to a real tenant).
- _2026-07-11:_ retention — model the sweep on the now-shipped w8/m4 usage-retention loop (`usageSvc.Run(ctx)` started from `cmd/api/main.go`, knob `BEX_USAGE_RETENTION_MONTHS`), the codebase's established retention pattern — not the projector loop. Knob: `BEX_AUDIT_RETENTION_DAYS` (default documented); if added, mirror it in `CLAUDE.md`'s env table and `.env.example`/`.env.template` per the repo rule.

## Steps

1. Migration for `audit_events` + indexes.
2. Store `AuditSink` implementation; wire it in the composition root — `lego/backend/cmd/api/main.go`, where `BEX_CP_DB_URI` is read and the store opened (no-op otherwise).
3. Retention sweep on the w8/m4 loop pattern + `BEX_AUDIT_RETENTION_DAYS` knob (default documented, names mirrored per the repo rule).

## Files

- `lego/backend/internal/store/migrations/`
- `lego/backend/internal/store/` (sink + retention)
- `lego/backend/cmd/api/main.go` (wiring — the composition root; `internal/api/server.go` is not where the store is opened)

## Acceptance criteria

- [x] Events written by t001's hook land in Postgres with correct tenant/caller/verb/resource/outcome.
- [x] Rows older than the retention window are removed by the sweep.
- [x] Store-less mode: recording is a no-op, nothing errors.

## Out of scope

- Read surface (t003); export/archival to object storage.
