Skip to content

feat(reminders): dogfood jobs' DB-stored cron schedules with a nightly stale-task sweep - #76

Merged
rodrigobnogueira merged 2 commits into
mainfrom
feat/dogfood-cron-schedules
Jul 31, 2026
Merged

feat(reminders): dogfood jobs' DB-stored cron schedules with a nightly stale-task sweep#76
rodrigobnogueira merged 2 commits into
mainfrom
feat/dogfood-cron-schedules

Conversation

@rodrigobnogueira

Copy link
Copy Markdown
Contributor

@nest-native/jobs 0.2.0 shipped DB-stored cron schedules as its headline feature, but this app only exercised the enqueue-driven half of the library. Since the app exists to put the libraries under realistic pressure, that gap was worth closing.

What it adds

A nightly stale-task sweep: every task still open more than 14 days after creation gets a task.stale.flagged audit entry — reusing the app's existing convention that an audit entry stands in for a notification (same as the assignment reminder).

What makes it worth having here is what it exercises, not what it does:

  • Nobody enqueues it. The claimer inserts each occurrence itself from a job_schedules row, in the same transaction that advances the schedule — a genuinely different path from the assignment reminder, which a projection enqueues.
  • The schedule is declared by a boot-time upsert, the pattern the library's docs recommend. The spec pins the two properties that make that safe in a real deployment: an omitted enabled preserves an operator's runtime disable across a redeploy, and an unchanged cron preserves a pending catch-up instead of skipping it.
  • The schedule carries a uniqueKey, so a slow night cannot stack runs — the spec proves a second occurrence is suppressed while one is still active.
  • The handler is idempotent and reads the DB itself rather than trusting a payload, because schedule delivery is still at-least-once.

Schedules are opt-in, so app.module now passes a scheduleStore; the job_schedules table is imported from the library so the app and the engine share byte-identical DDL (the same precedent as the jobs and messaging tables). Migration 0006 is drizzle-kit generated.

Verification

Per the repo guidelines (Docker was available, so full mode was run):

  • npm run test:fullbase suite 90/90 green, including the 4 new schedule specs.
  • The gated live-Kafka spec fails — but identically on main with these changes stashed, so it is pre-existing and unrelated. It fails at file level with no assertion output; CI never runs it (it self-skips without a broker). Flagging it rather than papering over it.
  • typecheck, lint, complexity:check, build, security:audit all clean.

No library friction to report upstream this time — the boot-upsert + uniqueKey combination covered the app's needs without reaching past the public API.

…y stale-task sweep

@nest-native/jobs 0.2.0 shipped DB-stored cron schedules as its headline
feature, but this app — whose whole job is to put the libraries under
realistic pressure — only exercised the enqueue-driven half. It now
exercises the recurring half too.

The sweep flags every task still 'open' more than 14 days after creation
by recording a task.stale.flagged audit entry, following the existing
convention that an audit entry stands in for a notification. What makes
it worth having here is what it exercises rather than what it does:

- nobody enqueues it. The claimer inserts each occurrence itself from a
  job_schedules row, in the same transaction that advances the schedule.
- the schedule is declared by a boot-time upsert, the documented
  pattern — and the spec pins the two properties that make that safe: an
  omitted 'enabled' preserves an operator's runtime disable across a
  redeploy, and an unchanged cron preserves a pending catch-up.
- the schedule carries a uniqueKey, so a slow night cannot stack runs;
  the spec proves a second occurrence is suppressed while one is active.
- the handler is idempotent and reads the DB itself rather than trusting
  a payload, because schedule delivery is still at-least-once.

Schedules are opt-in: app.module now passes a scheduleStore, and the
job_schedules table comes from the library so the app and engine share
byte-identical DDL (same precedent as the jobs and messaging tables).
The migration step prepares ./ci.db, but that DATABASE_URL was scoped to
that step alone — so the generator, which boots the whole app, fell back
to a fresh unmigrated temp database. That was invisible while nothing
wrote at bootstrap; RemindersModule declaring its cron schedule at boot
surfaced it as 'no such table: job_schedules'.

Giving the step the database the previous step just migrated is what the
migration step was there for.
@rodrigobnogueira
rodrigobnogueira merged commit 8d64f05 into main Jul 31, 2026
1 check passed
@rodrigobnogueira
rodrigobnogueira deleted the feat/dogfood-cron-schedules branch July 31, 2026 03:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant