Optional Oban worker that hard-deletes expired, unaccepted invitation rows past the configured retention window (D-11).
Invariants
- Only deletes
accepted_at IS NULLrows. Accepted invitations are preserved indefinitely by this worker — they are forensic history. Host apps with stricter retention policies should configureSigra.Auditretention, not this worker. - Retention window is
expires_at < now() - retention_days * 86400 s. Default 30 days past expiry (see@org_config_schema :invitation_cleanup_retention_days). - Tenant-aware via
@behaviour Sigra.Workers: the reconstructed%Scope{}carriesorganization_idfrom args so any per-batch audit event (optional) is tagged with tenant context. Q3 RESOLVED in 17-RESEARCH.md — this earns the behaviour overhead because the rows being deleted carryorganization_id. - Inline fallback via direct-callable
cleanup/3for hosts without Oban (mirrorsSigra.Workers.AuditCleanup.cleanup/3precedent).
Scheduling
Host apps opt in by adding this worker to their Oban cron config:
config :my_app, Oban,
plugins: [
{Oban.Plugins.Cron,
crontab: [
{"0 3 * * *", Sigra.Workers.CleanupExpiredInvitations,
args: %{
"organization_id" => nil,
"actor_id" => nil,
"repo" => "MyApp.Repo",
"invitation_schema" => "MyApp.Organizations.OrganizationInvitation",
"scope_module" => "MyApp.Accounts.Scope",
"organization_schema" => "MyApp.Organizations.Organization",
"retention_days" => 30
}}
]}
]Sigra does NOT auto-register this cron — host apps opt in by design.
Summary
Functions
Direct callable for the inline fallback path.
Functions
@spec cleanup(module(), module(), pos_integer()) :: {non_neg_integer(), nil}
Direct callable for the inline fallback path.
Host apps without Oban can call this from their own scheduler
(periodic GenServer, cron hit, test setup, etc.). Matches the
precedent set by Sigra.Workers.AuditCleanup.cleanup/3.
Deletes invitations where expires_at < now() - retention_days days
AND accepted_at IS NULL. Returns {count_deleted, nil}.