# Scheduled jobs

One cron tick settles six concurrent jobs, then indexes Document Text within the same deadline and scoped pool.

## What runs [#what-runs]

The Worker scheduled handler calls `runScheduledJobs` on the one-minute cron. The program runs audits, an hourly auth cleanup (rate-limit rows, sessions and verifications a day past expiry), imports, upload recovery, webhooks, billing, push and reminder alerts concurrently. Reminder alerts work during each data region's morning hour only, sending each due window once and never retrying a send ([alerts](/docs/features/service#alerts-us17-rf14)). Revaluations work during 03:00 UTC only and ask each enabled market-value provider for the current cars of its market; none is enabled yet ([reference values](/docs/features/garage#reference-values-rf38)). A typed failure or defect in one job does not interrupt its siblings. After these jobs settle, document maintenance and indexing run with their own 15-second budget inside the remaining global deadline. Indexing processes one document per tick; larger backlogs need multiple ticks. Failures become a single scheduled execution error identifying the failed jobs.

Document conversion runs in a browser Web Worker. Cron validates and indexes submitted Markdown; it does not run AnyDoc. [ADR-0008](/docs/adr/0008-documents-and-large-uploads) records the memory evidence behind this ordering and the 192 KiB text cap. A global timeout before the earlier jobs settle skips document work.

The tick acquires one job pool in an Effect scope. Its 25-second deadline interrupts work; fetch cancellation and drained database promises settle before the pool closes. An interrupted summary has empty counters, while durable rows and traces retain completed work. The Cinematic Studio runs beside the tick on the same pool with its own 150-second budget (`STUDIO_JOB_MS`): an image model takes longer than 25 seconds, and a scheduled Worker may run 15 minutes.

## Run locally [#run-locally]

```sh
pnpm jobs:run
# The cron's stand-in beside `pnpm dev:web` (Studio requests, imports, reminders):
pnpm jobs:run --every 60
# Against a dev database that is not local (refused without the flag):
pnpm jobs:run --every 60 --any-db
# Production-runtime recovery and network-fault verification:
node scripts/test-local-postgres.mjs exec node scripts/test-local-jobs.mjs
```

The CLI uses the configured database (`apps/web/.env`) and refuses one that is not on this machine unless you pass `--any-db`: every job runs, so pushes and emails go out from that database too. Use the disposable wrapper for tests. The workerd harness calls `/cdn-cgi/handler/scheduled` with `--test-scheduled` and uses a local R2 fixture and TCP fault proxy. It verifies replay and connection cleanup, not Cloudflare's production scheduling latency.

## Add a job [#add-a-job]

Return an Effect from the new job, register it in the composed program and summary, and use the scoped database passed to it. Model durable admission, claim ownership, replay and terminal failure before adding retry. Keep external write attempts idempotent where possible; do not add in-round retries to sends.

Test deadlines with `@effect/vitest` and TestClock using production values. Changes to queue persistence or deadlines must repeat the fault tests in [ADR-0006](/docs/adr/0006-effect-persisted-queue).

## Key files [#key-files]

- [`apps/web/src/server.ts`](https://github.com/CarlosZiegler/autofolio/blob/main/apps/web/src/server.ts)
- [`packages/api/src/platform/jobs/run.ts`](https://github.com/CarlosZiegler/autofolio/blob/main/packages/api/src/platform/jobs/run.ts)
- [`packages/api/src/platform/jobs/failure.ts`](https://github.com/CarlosZiegler/autofolio/blob/main/packages/api/src/platform/jobs/failure.ts)
- [`packages/db/src/job-db.ts`](https://github.com/CarlosZiegler/autofolio/blob/main/packages/db/src/job-db.ts)
- [`scripts/test-local-jobs.mjs`](https://github.com/CarlosZiegler/autofolio/blob/main/scripts/test-local-jobs.mjs)

