retention: compose the audit detail in the store with the real body count

RetainWithAudit now takes only the actor; the store builds the detail after
body clearing so bodies_cleared is a real number, and still writes the row
before the cap.
This commit is contained in:
Builder
2026-10-13 10:44:01 +11:00
parent d8788ef8e7
commit 453d8a2dc9
3 changed files with 16 additions and 11 deletions

View File

@@ -49,14 +49,19 @@ type RetentionResult struct {
// pass never leaves the database in a mixed state. It is safe to run on a
// schedule: every statement is idempotent.
func (s *Store) Retain(ctx context.Context, p RetentionPolicy) (RetentionResult, error) {
return s.RetainWithAudit(ctx, p, "", "")
return s.retain(ctx, p, false, "")
}
// RetainWithAudit is Retain plus one actions_log row (FR-13), appended inside
// the same transaction before the cap is applied so the audit row is itself
// counted by the cap. An empty detail writes no audit row. This keeps the
// action log bounded even though every run records that it ran.
func (s *Store) RetainWithAudit(ctx context.Context, p RetentionPolicy, actor, detail string) (RetentionResult, error) {
// RetainWithAudit is Retain plus one 'retain' row appended to actions_log
// (FR-13). The row is written inside the same transaction before the cap is
// applied, so the cap counts it and the log stays bounded even though every
// run records that it ran.
func (s *Store) RetainWithAudit(ctx context.Context, p RetentionPolicy, actor string) (RetentionResult, error) {
return s.retain(ctx, p, true, actor)
}
// retain is the shared implementation. audit=false writes no audit row.
func (s *Store) retain(ctx context.Context, p RetentionPolicy, audit bool, actor string) (RetentionResult, error) {
var res RetentionResult
tx, err := s.db.BeginTx(ctx, nil)
@@ -82,7 +87,9 @@ WHERE body_text IS NOT NULL AND body_text <> '' AND received_at < ?`,
}
}
if detail != "" {
if audit {
detail := fmt.Sprintf("retention_days=%d max_rows=%d bodies_cleared=%d",
int(p.BodyMaxAge.Hours()/24), p.ActionsLogMaxRows, res.BodiesCleared)
if _, err := tx.ExecContext(ctx, `
INSERT INTO actions_log (message_id, action, actor, detail)
VALUES (NULL, 'retain', ?, ?)`, nullString(actor), detail); err != nil {

View File

@@ -130,7 +130,7 @@ func TestRetainWithAuditCountsItsOwnRow(t *testing.T) {
}
}
res, err := s.RetainWithAudit(ctx, RetentionPolicy{ActionsLogMaxRows: 3}, "scheduler", "bodies_cleared=0")
res, err := s.RetainWithAudit(ctx, RetentionPolicy{ActionsLogMaxRows: 3}, "scheduler")
if err != nil {
t.Fatalf("RetainWithAudit() error = %v", err)
}