JobHandler — job scheduling & governance
JobHandler is the scheduler, governance, and observability service for the Primebrick job fabric. It never executes domain work — every job's executor lives in the owning domain service.
Architecture
Code
- Scheduler = the NATS server (
allow_msg_scheduleson theJOBSstream). - Sole writer of
jobs.schedule.*= job-handler viajobs.ctl.schedule.request. - Executor = a durable JetStream consumer inside the domain service.
- Rules =
job.job_rulesdomain data (NOT config) — armed by the rules engine. - Infrastructure jobs = metadata-derived (
@PartitionFieldon an entity → the owning service self-declares its own schedule at boot).
Subjects
| Subject | Purpose |
|---|---|
jobs.schedule.<owner>.<name> | Schedule declaration (stored, last-msg-wins) |
jobs.due.user.<svc>.<action>[.<id>] | Due message — user lane (auth propagates) |
jobs.due.system.<svc>.<action>[.<id>] | Due message — system lane |
jobs.result.<svc>.<name> | Execution report → recorded in job.job_runs |
jobs.cancel.* | Schedule cancellation |
jobs.ctl.schedule.request / jobs.ctl.schedule.cancel | Governed control routes |
Namespace security
A remote caller may only declare inside its own jobs.schedule.<svc>.*
namespace and the due target must land back on its own service
(jobs.due.<lane>.<same-svc>.*). Internal callers (rules engine, admin HTTP
action) own the job namespace and may target any service — that's the point
of admin-managed schedules.
Domain data (schema job)
job_rules— business rules:event_subject→schedule_spec+target_subject.job_schedules— mirror of every armed schedule (kind:infra_derived|rule|manual,schedule_type:recurring|once,status:armed|disarmed).job_runs— execution history: status,duration_ms, error fields, report (jsonb).config_entries— mandatory per-service config (cadence, ports, shield refs).
HTTP surface (via BE /ws/job/* proxy)
| Route | Description |
|---|---|
| `GET /api/v1/entities/job_rule | job_schedule |
POST /api/v1/actions/schedule-job | Arm a manual schedule (admin) |
POST /api/v1/actions/disarm-job | Disarm a schedule (admin) |
POST /api/v1/actions/job-run-stats | Per-schedule execution buckets for sparklines — adaptive bucket_unit (hour/day/week/month chosen so the span lands in ~8–30 buckets) |
Partition maintenance (first real consumer)
@PartitionField on an entity marks it partitioned; @PartitionChildOf marks
the logical children purged leaf→root before each partition drop. The owning
service calls dal.partitionMaintenance(ENTITY_REGISTRY) from its executor —
job-handler only sees subjects, never tables.
Config: partition_maintenance_schedule in the service's config_entries
(default @daily).
Admin UI
/(app)/system/job — recurring / one-time / rules / run-history views with
per-row Kestra-style stacked-bar execution sparklines.