Overview
SecurAtlas uses the pg_cron extension to schedule recurring database jobs. These jobs run inside PostgreSQL and can call SQL functions directly or invoke Edge Functions via pg_net.
All cron times are in UTC. The Supabase dashboard shows job history under Database > Extensions > pg_cron.
Active Jobs
| Job Name | Schedule | Time (UTC) | Purpose |
|---|
nightly_risk_recompute | 0 2 * * * | 2:00 AM | Recompute risk scores for all tenants |
securatlas_nightly_integration_sync | 0 3 * * * | 3:00 AM | Trigger integration syncs for all connections |
token_refresh_cron | 0 */6 * * * | Every 6 hours | Refresh expiring OAuth tokens |
Job Definitions
Nightly Risk Recompute
Runs at 2 AM UTC. Recalculates risk scores and inserts snapshots for all active tenants.
SELECT cron.schedule(
'nightly_risk_recompute',
'0 2 * * *',
$$SELECT fn_recompute_all_tenant_risk_scores()$$
);
The fn_recompute_all_tenant_risk_scores() function iterates over all tenants with active subscriptions, computes the composite risk score, and inserts a row in tenant_risk_snapshots.
Nightly Integration Sync
Runs at 3 AM UTC (after risk recompute). Triggers integration syncs for all active connections.
SELECT cron.schedule(
'securatlas_nightly_integration_sync',
'0 3 * * *',
$$SELECT fn_trigger_nightly_sync()$$
);
fn_trigger_nightly_sync()
This function uses pg_net to call the trigger-sync Edge Function for each active connection:
CREATE OR REPLACE FUNCTION fn_trigger_nightly_sync()
RETURNS void AS $$
DECLARE
conn RECORD;
edge_url text;
BEGIN
edge_url := current_setting('app.settings.edge_function_url', true)
|| '/trigger-sync';
FOR conn IN
SELECT id FROM integration_connections
WHERE status = 'active'
LOOP
PERFORM net.http_post(
url := edge_url,
headers := jsonb_build_object(
'Content-Type', 'application/json',
'Authorization', 'Bearer ' || current_setting('app.settings.service_role_key', true)
),
body := jsonb_build_object('connection_id', conn.id)
);
END LOOP;
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;
The pg_net extension sends HTTP requests asynchronously. The function returns immediately; sync completion is tracked in integration_sync_jobs.
Token Refresh
Runs every 6 hours. Refreshes OAuth tokens that are near expiry to prevent sync failures.
SELECT cron.schedule(
'token_refresh_cron',
'0 */6 * * *',
$$SELECT fn_refresh_expiring_tokens()$$
);
Optional: 6-Hour Sync
For tenants that need more frequent integration syncs, an optional 6-hour sync can be enabled:
SELECT cron.schedule(
'frequent_integration_sync',
'0 */6 * * *',
$$SELECT fn_trigger_nightly_sync()$$
);
This uses the same fn_trigger_nightly_sync() function but runs more frequently. Enable this only for tenants on premium plans to manage API rate limits.
Managing Jobs
List All Jobs
SELECT jobid, jobname, schedule, command, active
FROM cron.job
ORDER BY jobname;
Disable a Job
SELECT cron.unschedule('securatlas_nightly_integration_sync');
Re-enable a Job
Re-run the cron.schedule() call with the same job name. If the name already exists, it updates the existing schedule.
Run a Job Manually
Execute the underlying function directly:
-- Manually trigger risk recompute
SELECT fn_recompute_all_tenant_risk_scores();
-- Manually trigger integration sync
SELECT fn_trigger_nightly_sync();
View Job History
SELECT jobid, jobname, start_time, end_time, status, return_message
FROM cron.job_run_details
ORDER BY start_time DESC
LIMIT 20;
Execution Order
Jobs are scheduled to avoid conflicts:
2:00 AM ── Risk Recompute (uses yesterday's data)
3:00 AM ── Integration Sync (pulls fresh data)
Risk recompute runs before the nightly sync intentionally. The sync at 3 AM pulls fresh data, and the next night’s risk recompute at 2 AM will incorporate that data. This avoids recomputing risk on stale data.