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. Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
@@ -346,9 +346,7 @@ func runRetain(cfg *config.Config, logger *slog.Logger) error {
|
||||
}
|
||||
// The audit row is written inside the same transaction, before the cap, so
|
||||
// the cap counts it. This is what keeps actions_log bounded across runs.
|
||||
detail := fmt.Sprintf("bodies_cleared=%%d retention_days=%d max_rows=%d",
|
||||
cfg.RetentionDays, cfg.ActionsLogMaxRows)
|
||||
res, err := st.RetainWithAudit(ctx, policy, "scheduler", detail)
|
||||
res, err := st.RetainWithAudit(ctx, policy, "scheduler")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user