Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

03 — User Journeys (Scheduler Module)

Journeys are written for the operator/admin personas (02). Screens referenced are the console (proposed) set (05/06); steps backed by real endpoints are tagged with their file:line. Flows that need no UI today (journey 1, 4) are marked "console-free".


Journey 1 — First Deployment Bring-Up (console-free)

Persona: Platform Operator · Trigger: fresh deploy of the API

  1. SchedulerModule boots; SchedulerService.onModuleInit fires (scheduler.service.ts:43-45).
  2. registerDefaults() iterates 10 jobs; each is skipped if an identical name+pattern repeatable already exists (scheduler.service.ts:128-133).
  3. New jobs are added with repeat: { pattern, tz: 'UTC' } (scheduler.service.ts:135-147).
  4. Operator verifies: GET /scheduler returns the 10 jobs (scheduler.controller.ts:23-28).
  5. Success = log line Registered 10 default repeatable jobs (scheduler.service.ts:125).

Exit: all 10 rows visible; next occurrence times sane in UTC.

Journey 2 — Daily Health Check

Persona: Platform Operator · Trigger: start of day

  1. Open Jobs list screen (proposed)GET /scheduler today (scheduler.controller.ts:23-28), plus last-run/failed timestamps (proposed).
  2. Scan status badges: 10 default jobs + any custom ones.
  3. Spot-check audit-flush (*/1 * * * *) — a missed minute mark signals Redis/worker trouble.
  4. Drill into a failed row → Job detail with run history (proposed).
  5. If a run failed 3× it lands in the DLQ (dlq.setup.ts:8-9) → DLQ viewer (proposed).

Exit: all green, or a ticket opened with failedReason from the DLQ.

Journey 3 — Missed-Run Recovery (after outage / redeploy)

Persona: On-Call Engineer · Trigger: "schools say no digest today"

  1. Confirm the repeatable survived the redeploy: GET /scheduler (scheduler.controller.ts:23-28) — repeatables can vanish if Redis was flushed, so re-registration depends on boot order (scheduler.service.ts:43-45).
  2. Check BullMQ/Redis for the 0 9 * * * daily-digest entry (scheduler.service.ts:56-62).
  3. Manual trigger (proposed) — today only possible by re-adding or queue.add('send-daily-digest', ...) from a script (daily-digest.job.ts:12-25).
  4. Confirm the worker picked it up and restored tenant context (report.worker.ts:18-30 pattern).
  5. Idempotency risk: a manual re-trigger must not double-send — worker idempotency via IdempotencyService (bullmq.module.ts:76) is the guard.

Exit: digest delivered once; root cause (Redis flush, worker down) recorded; alerting rule (proposed) added.

Journey 4 — Fee Reminder Pipeline Check (console-free)

Persona: Platform Operator · Trigger: end of month

  1. fee-reminder fires at 08:00 UTC (scheduler.service.ts:92-97).
  2. FeeReminderJob scans invoices due within 3 days, status ISSUED|PARTIAL (fee-reminder.job.ts:16-23).
  3. One send-payment-reminder per invoice enqueued (fee-reminder.job.ts:25-41) with amount = totalAmount − paidAmount (:37).
  4. Worker sends; failures retry 3× exponential 5 s (bullmq.module.ts:60-65), then DLQ (dlq.setup.ts:8-21).

Exit: reminder count matches due-invoice count; DLQ empty.

Journey 5 — Custom Schedule Lifecycle (operator-created)

Persona: Platform Operator · Trigger: "rebuild dashboards off-peak"

  1. Create: POST /scheduler with {pattern, queue, jobName, payload?, tz?} (scheduler.controller.ts:30-35); cron validated as 5 fields (create-schedule.dto.ts:20-24), queue whitelisted (dto:26-28).
  2. Verify: GET /scheduler shows the new row (scheduler.controller.ts:23-28).
  3. Later, remove: DELETE /scheduler?queue=&name=&pattern= (scheduler.controller.ts:37-46) → removeRepeatable(..., { pattern, tz: 'UTC' }) (scheduler.service.ts:188-196).

Exit: row gone; no ghost repeats (BullMQ dedupes by name+pattern+queue).

Journey 6 — Incident Postmortem

Persona: On-Call Engineer · Trigger: a tenant missed attendance-report-daily

  1. Identify schedule: 0 7 * * *report-generate (scheduler.service.ts:98-104).
  2. Job detail (proposed): last runs, failedReason.
  3. DLQ record: originalQueue, originalJobId, failedReason, attemptsMade (dlq.setup.ts:12-21).
  4. Replay from DLQ (proposed); verify idempotency before retry.
  5. Patch the root cause; amend QA checklist (14).

Exit: documented timeline + fix; report re-delivered.

Journey map

deploy ─► bring-up ─► daily check ─► incident ─► recovery ─► postmortem
  │          │             │            │            │             │
 register   GET /scheduler  badges,     manual       re-trigger    DLQ replay
 defaults   10 jobs        DLQ scan     trigger      (idempotent)  + fix

Happiness criteria (all journeys)

  • Any job's next run is predictable from the schedule table (01 §2).
  • A failed job is visible within one polling cycle (audit-flush runs every minute, scheduler.service.ts:78-83).
  • Recovery never double-delivers (idempotency gate).