03 — User Journeys (Scheduler Module)
- Journey 1 — First Deployment Bring-Up (console-free)
- Journey 2 — Daily Health Check
- Journey 3 — Missed-Run Recovery (after outage / redeploy)
- Journey 4 — Fee Reminder Pipeline Check (console-free)
- Journey 5 — Custom Schedule Lifecycle (operator-created)
- Journey 6 — Incident Postmortem
- Journey map
- Happiness criteria (all journeys)
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 theirfile: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
SchedulerModuleboots;SchedulerService.onModuleInitfires (scheduler.service.ts:43-45).registerDefaults()iterates 10 jobs; each is skipped if an identicalname+patternrepeatable already exists (scheduler.service.ts:128-133).- New jobs are added with
repeat: { pattern, tz: 'UTC' }(scheduler.service.ts:135-147). - Operator verifies:
GET /schedulerreturns the 10 jobs (scheduler.controller.ts:23-28). - 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
- Open Jobs list screen
(proposed)—GET /schedulertoday (scheduler.controller.ts:23-28), plus last-run/failed timestamps(proposed). - Scan status badges: 10 default jobs + any custom ones.
- Spot-check
audit-flush(*/1 * * * *) — a missed minute mark signals Redis/worker trouble. - Drill into a failed row → Job detail with run history
(proposed). - 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"
- 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). - Check BullMQ/Redis for the
0 9 * * *daily-digestentry (scheduler.service.ts:56-62). - Manual trigger
(proposed)— today only possible by re-adding orqueue.add('send-daily-digest', ...)from a script (daily-digest.job.ts:12-25). - Confirm the worker picked it up and restored tenant context
(
report.worker.ts:18-30pattern). - 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
fee-reminderfires at 08:00 UTC (scheduler.service.ts:92-97).FeeReminderJobscans invoices due within 3 days, statusISSUED|PARTIAL(fee-reminder.job.ts:16-23).- One
send-payment-reminderper invoice enqueued (fee-reminder.job.ts:25-41) withamount = totalAmount − paidAmount(:37). - 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"
- Create:
POST /schedulerwith{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). - Verify:
GET /schedulershows the new row (scheduler.controller.ts:23-28). - 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
- Identify schedule:
0 7 * * *→report-generate(scheduler.service.ts:98-104). - Job detail
(proposed): last runs,failedReason. - DLQ record:
originalQueue, originalJobId, failedReason, attemptsMade(dlq.setup.ts:12-21). - Replay from DLQ
(proposed); verify idempotency before retry. - 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-flushruns every minute,scheduler.service.ts:78-83). - Recovery never double-delivers (idempotency gate).