- Kind: Actuation (CHAD is also a reader: it polls devices, jobs and test executions through the SDK, but this page is filed under the direction that changes hardware)
- Version: 1
- Fleet Manager owner: Fleet Manager squad, #fleet-manager-chat (individual claimant pending)
- Counterpart owner: unassigned, SRE (CHAD) with FOE co-owning the scheduler, #chad-chat
- Last reviewed: 2026-09-09
What flows
CHAD is the repair-and-return orchestrator. Fleet Manager can publish thin notify-and-pull events for it (ADR-0027) on the 6-segment subject grammar:device.standup-requested, job.state-updated,
device.triage-requested and device.validation-requested. Every one of them is gated on the producer side:
api.eventing.standupEnabled, triageRequestedEnabled and validationRequestedEnabled each default false, job
events are emitted only for workflow types listed in api.eventing.jobWorkflowAllowlist (empty by default), and
the outbox is drained to NATS only when chadEventPublisher.enabled is true (default false). A deployment on chart
defaults publishes nothing; the per-environment overlays turn each event on. Per ADR-0027 the CHAD-side consumer
is likewise a migration target that is disabled by default: CHAD reads job state by polling the API today, and
standup, triage and validation still start from the existing manual or triggered paths. Its SOP
recipes in nscaledev/chad-sops call Fleet Manager workflows by name and create jobs through POST /api/v1/jobs/,
and CHAD’s own code talks to Fleet Manager through the Python SDK in libs/fleet-manager-sdk, which is its
primary interface.
Two clients write: chad-client (production) and chad-dev-client (development) each hold job:create,
job:cancel, test-execution:mark_stale, stale-hold read and undo, device:update, and read scopes on devices,
jobs, test executions, fault snapshots and impairment events (operations:read). chad-test is read-mostly: it
holds device:read, device:update, job:read, job-device-status:read, test-execution:read and
device-fault-snapshot:read, and cannot create or cancel jobs.
Fleet Manager applies eligibility (ADR-0012) and custody to every job and returns failure codes per ADR-0014.
Idempotency is caller-supplied: POST /api/v1/jobs/ accepts an optional idempotency_key scoped to the calling
principal, and a request without one is not idempotent, so CHAD must send a key on every create it may retry.
Fleet Manager does not restrict which workflow types a client may create; any workflow the device is eligible for
can be dispatched by any client holding job:create.
What breaks if this changes
A change to job status semantics breaks CHAD’s polling of job state today, and a change to an event contract will break its consumer once enabled. Job admission already guards exclusive workflows: an incoming exclusive job is rejected under a device-row lock when any non-read-only job is active on those devices, and a non-exclusive job (burn-in, connectivity verification) is rejected when an exclusive job is active. Two non-exclusive jobs may still overlap on the same device set, so a new dispatcher racing operators on burn-in dispatch is the one unguarded case (src/fleet_manager/api/core/workflows/jobs.py, JobDeviceStatus.validate_no_active_workflow_conflicts). A device an
active node swap names as its spare counts as that swap’s device, so a day-zero enrolment CHAD dispatches for it gets the
same 400 conflict until the swap finishes.
Enforcement
- Principal:
chad-client,chad-dev-client,chad-test. - Mechanism: towards Fleet Manager, FM OAuth clients in the hub chart roster; the outbox publisher reaches NATS as
fleet-chad-event-publisher-clientand CHAD consumes asfleet-chad-clienton the Fleet Manager-owned plane (see nats). - Hub roster clients
chad-client,chad-dev-client,chad-testindeployment/charts/hub/values.yaml. - Event contracts:
src/fleet_manager/core/events/notification.py; ADR-0027. - Failure codes: ADR-0014.
Gaps
- No per-client allow-list of workflow types.
- No overlap guard for two non-exclusive jobs on the same device set; exclusive workflows are already guarded.
- CHAD’s own scheduler and its Fleet Manager wake-up consumer are being built; that path is not described here until it is enabled.
Change log
- 2026-09-09 v1: created; describes present behaviour.