Evidence: the daily job
For whoever deploys RiskFlow. Most evidence is re-checked the moment something changes. A few things only change with the clock (an expiry date, a grace period, a due date), so one job runs every day to catch them. What the checks mean for users is in Evidence.
What it is
| Path | GET /api/v1/internal/grc-evidence |
| When | Daily at 05:00 UTC. Set under crons in apps/backend/vercel.json |
| Needs | CRON_SECRET on the API project (riskflow-api). Vercel sends it as Authorization: Bearer <secret>. Without it the call gets a 404 |
It needs no new environment variables beyond CRON_SECRET, which the other jobs already use.
What it does, in order
- Expiry sweep. Finds evidence whose expiry date passed since the last run. For each item it writes
compliance.evidence_expiredto the audit log (once per item, labelled as the system), then re-checks only the controls that item is attached to. A control whose status changes also getscompliance.control_status_changed. - Time-based re-check. Re-checks every control that has a policy or training attached. A 30-day grace period ending, a due date passing, a policy turning a year old, or a completion turning 12 months old can change those controls with no one doing anything.
Both steps share one time budget.
Fits inside Vercel's limit
Vercel functions stop at 300 seconds. The job:
- works in batches of 100;
- stops starting new batches after about 4 minutes (240 seconds);
- leaves anything it didn't reach for the next day's run.
It is safe to call twice. An item that was already recorded as expired is not recorded again, so calling it by hand, or Vercel retrying it, doesn't make duplicate audit entries.
The response
{
"expired": 3,
"controlsChanged": 2,
"failedTeams": 0,
"remaining": false,
"recheck": { "checked": 41, "changed": 1, "failedTeams": 0, "remaining": false }
}
The top-level fields are the expiry sweep, and recheck is the time-based re-check.
| Field | Meaning |
|---|---|
expired | Items this run recorded as expired |
controlsChanged / changed | Controls whose status changed because of it |
checked | Controls the re-check looked at |
failedTeams | Teams that threw an error. They are logged and skipped, so one broken team can't block the others. Their items are retried on the next run. Should be 0 |
remaining | true when the time budget ran out and some work is left for the next run |
Running it by hand
curl -H "Authorization: Bearer $CRON_SECRET" https://api.vodazok.com/api/v1/internal/grc-evidence
Use it after a deploy that changes the rules, or to check that the secret is right. A wrong or missing secret returns 404 on purpose.
If something looks wrong
| Symptom | Likely reason |
|---|---|
| Every call returns 404 | CRON_SECRET is missing or wrong on the API project, or the Authorization header is missing |
failedTeams (either one) is above 0 | Look at the API logs for that run. The other teams were still checked |
| A control still shows Passing after its expiry date | It is under a day old. The daily job hasn't run yet. Any change to the control's evidence re-checks it at once |
remaining is true every day | The 4-minute budget ran out. The rest is picked up the next day. If it happens every day, tell the developers |
Other scheduled jobs are listed in Frameworks and Controls.