Job catalogue
Every async job runs through the same BullMQ queue (scani-jobs),
consumed by apps/backend/worker. Wire names live in
packages/business/jobs/src/job-names.ts; descriptors in
packages/business/jobs/src/scheduled-jobs/ (for repeatable jobs)
or packages/business/jobs/src/user-jobs/ (for user-initiated
jobs); processors in apps/backend/worker/src/processors/.
Scheduled jobs use the advisory-lock wrapper — two overlapping fires of the same name silently no-op rather than race.
The reconcilers and probes all run on the same quarter-hour cadence on purpose: aligning them means their advisory locks batch into one wake, so the database can scale to zero between runs instead of being nudged awake four times an hour.
Scheduled jobs
Section titled “Scheduled jobs”Scheduled jobs — declared but not registered
Section titled “Scheduled jobs — declared but not registered”A descriptor can exist in packages/business/jobs/src/scheduled-jobs/
without being listed in SCHEDULED_JOB_DESCRIPTORS, and then the worker
never registers it: the jobs below do not run. They are listed here
because the file is in the tree and a reader who finds it deserves to know
which half is missing — not because they are live. The table above is the
list of live jobs.
There are none right now. The table below is empty, and that is the
correct steady state — a descriptor belongs here only for as long as its
processor is unwritten. payment-due-reminder was the last one; it moved to
the live table above when PaymentDueReminderProcessor landed in the same
commit that registered it (SC-226).
The heading stays even when empty, because scripts/check-docs.ts reads both
tables and fails if either is missing — and a page that silently loses the
distinction is how a job that never runs gets read as one that does.
User-initiated jobs
Section titled “User-initiated jobs”Enqueued by the api in response to a user action. They use a stable per-user job ID so the user can see “in flight” status in the SPA.
Retry policies
Section titled “Retry policies”Defined in packages/business/jobs/src/retry-policies.ts:
DLQ (dead-letter queue)
Section titled “DLQ (dead-letter queue)”Jobs that exhaust their retries land in scani-dlq. The
dlq-depth-probe job alarms when depth grows. Operators replay
via the HMAC-gated jobs.dlqReplay endpoint on the api.
Adding a job
Section titled “Adding a job”See Adding a scheduled job for the three-place change required.
See also
Section titled “See also”- Why BullMQ + Postgres advisory locks
- Adding a scheduled job
- Portfolio value rollup — what the nightly chain produces.
- Observability — which jobs emit log-based metrics.