Retention and pruning
Knocker keeps retention intentionally small:
- explicit prune primitives remain the source of truth
- automation runs through Honker scheduling/queue primitives
- each automated run calls the shared core retention-pass primitive
- no richer policy engine
- no calendar or cron semantics
Examples
Section titled “Examples”summary = webhooks.prune_events( statuses=["handled", "ignored"], older_than=1700000000, limit=100,)
orphans = webhooks.prune_orphan_deliveries( older_than=1700000000, limit=100,)
audits = webhooks.list_prune_audits(kind="prune_events", limit=50)
policy = knocker.RetentionPolicy( interval_s=60, event_older_than_s=7 * 24 * 60 * 60, orphan_deliveries_older_than_s=24 * 60 * 60,)await webhooks.run_retention(policy, stop_event=stop)SELECT knocker_prune_events( json_array('handled', 'ignored'), 1700000000, 100, 'knocker.events') AS summary_json;
SELECT knocker_prune_orphan_deliveries( 1700000000, 100, 'knocker.events') AS summary_json;
SELECT knocker_run_retention_pass( json_array('handled', 'ignored'), unixepoch() - (7 * 24 * 60 * 60), 100, unixepoch() - (24 * 60 * 60), 100, 'knocker.events') AS summary_json;
SELECT id, kind, executed_at, events_pruned, deliveries_pruned, summary_jsonFROM knocker_prune_auditsORDER BY executed_at DESC, id DESCLIMIT 50;const summary = webhooks.pruneEvents({ statuses: ["handled", "ignored"], olderThan: 1700000000, limit: 100,});
const orphans = webhooks.pruneOrphanDeliveries({ olderThan: 1700000000, limit: 100,});
const audits = webhooks.listPruneAudits({ kind: "prune_events", limit: 50 });
await webhooks.runRetention({ intervalMs: 60_000, maxRuns: 1, eventOlderThanS: 7 * 24 * 60 * 60, orphanDeliveriesOlderThanS: 24 * 60 * 60,});{:ok, summary} = KnockerSqlite.prune_events(webhooks, %{ statuses: ["handled", "ignored"], older_than: 1_700_000_000, limit: 100 })
{:ok, orphans} = KnockerSqlite.prune_orphan_deliveries(webhooks, %{ older_than: 1_700_000_000, limit: 100 })
{:ok, audits} = KnockerSqlite.list_prune_audits(webhooks, 50)
1 = KnockerSqlite.run_retention(webhooks, %{ max_runs: 1, interval_ms: 60_000, event_older_than_s: 7 * 24 * 60 * 60, orphan_deliveries_older_than_s: 24 * 60 * 60 })summary, err := webhooks.PruneEvents([]string{"handled", "ignored"}, 1700000000, 100)if err != nil { return err}
orphans, err := webhooks.PruneOrphanDeliveries(1700000000, 100)if err != nil { return err}
audits, err := webhooks.ListPruneAudits(50)if err != nil { return err}
eventAge := int64(7 * 24 * 60 * 60)orphanAge := int64(24 * 60 * 60)_, err = webhooks.RunRetention(ctx, knockersqlite.RetentionPolicy{ EventOlderThanS: &eventAge, OrphanDeliveriesOlderThanS: &orphanAge,}, time.Minute, 1)const summary = webhooks.pruneEvents({ statuses: ["handled", "ignored"], olderThan: 1700000000, limit: 100,});
const orphans = webhooks.pruneOrphanDeliveries({ olderThan: 1700000000, limit: 100,});
const audits = webhooks.listPruneAudits({ kind: "prune_events", limit: 50 });
await webhooks.runRetention({ intervalMs: 60_000, maxRuns: 1, eventOlderThanS: 7 * 24 * 60 * 60, orphanDeliveriesOlderThanS: 24 * 60 * 60,});summary = webhooks.prune_events( statuses: %w[handled ignored], older_than: 1_700_000_000, limit: 100,)
orphans = webhooks.prune_orphan_deliveries( older_than: 1_700_000_000, limit: 100,)
audits = webhooks.list_prune_audits(kind: "prune_events", limit: 50)
webhooks.run_retention( interval_s: 60, max_runs: 1, event_older_than_s: 7 * 24 * 60 * 60, orphan_deliveries_older_than_s: 24 * 60 * 60,)Retention Automation
Section titled “Retention Automation”Bindings expose retention helpers over the same core retention-pass primitive. The policy shape is:
- interval
- event statuses
- event age cutoff
- event prune limit
- orphan-delivery age cutoff
- orphan-delivery prune limit
Important boundaries:
- automation is intentionally small
- recurrence comes from Honker Scheduler
- each maintenance job calls the shared core retention-pass primitive
- automated runs write the same durable audit rows as manual runs
- multiple processes can run retention workers against the same SQLite file without duplicate prune runs
Configuration is simpler than runtime:
- multiple runners are acceptable
- one process/instance should still be treated as the source of truth for retention configuration for a given database
Prune Events
Section titled “Prune Events”older_than is strict: rows qualify only when received_at < older_than.
Candidate selection is:
- oldest-first
- bounded by explicit
limit - all-or-nothing inside one transaction per prune call
Returned counts:
events_prunedattempts_pruneddeliveries_prunedlive_jobs_pruned
Prune Orphan Deliveries
Section titled “Prune Orphan Deliveries”This is about the orphan axis only: event_id IS NULL. It is not a generic
“invalid deliveries” delete switch.
Append-Only Carve-Out
Section titled “Append-Only Carve-Out”knocker_deliveries is append-only during normal ingest and operator
inspection. Explicit pruning is the documented retention exception that may
delete old delivery rows.
Prune Audit Trail
Section titled “Prune Audit Trail”Every successful prune call records an audit row in the same transaction.
Each PruneAudit includes:
kind:'prune_events'or'prune_orphan_deliveries'queue_name: the Honker queue pruned againstexecuted_at: unixepoch timestampevents_pruned,deliveries_pruned,attempts_pruned,live_jobs_prunedsummary_json: operator inputs (statuses,older_than,limit)
No-op prunes (zero rows deleted) still write an audit row with zero counts. Audit rows are never targeted by ordinary prune operations.
What Is Deferred
Section titled “What Is Deferred”Still intentionally out of scope:
- pruning
failedordeadevents - dry-run mode
- endpoint-specific retention policy
- local-day or DST-aware retention semantics