The HIL pre-power checklist (2026 edition)
Every autopsy in this series ends the same way: the failure was typed, visible, and cheap to catch — before current flowed. This is the checklist those failures produced. Twenty checks, walked straight out of the open catalog, each line tagged with its rule ID so when a line fails you know exactly which failure mode you're standing in front of. Print it, tape it to the rack, run it before every power-up.
A bench checklist has one job: catch the mistakes that looked right in review. Not the missing cable — anyone can see a missing cable. The renamed channel, the unit that drifted, the interlock that resolves to nothing. Two severities: an ERROR blocks power-up; a WARNING is advisory — read it, decide, and write down why you proceeded.
SECTION 1 · SCHEMAIs this a bench config at all?
- ERRORThe config defines a non-empty channel list. An empty export that lints "clean" gates a campaign on nothing. schema.channels
- ERROREvery channel has a name field. Unnamed channels share identity in downstream tooling — two signals, one name, no alarm. schema.name
SECTION 2 · UNIQUEDoes every signal have exactly one identity?
- ERRORNo duplicate channel names. A renamed channel colliding with an existing one means sequences now drive the wrong line. unique.name
SECTION 3 · PINDoes every pin have exactly one owner?
- ERRORNo physical pin is allocated to two channels. A stimulus shorted into a measurement line. The canonical bench-killer: two engineers, one spreadsheet, one pin. pin.conflict
SECTION 4 · LIMITSInside the ratings, always?
- ERROREvery channel's drive voltage is under its own rating (voltage_v ≤ max_voltage_v). Nominal drive configured above the rail the hardware can take — the 28 V / 12 V autopsy lives here. limits.voltage
- ERRORAll voltage and current limits are positive numbers. A negative limit from a units slip (mA entered as A) disables the very check meant to protect the channel. limits.positive
SECTION 5 · INTERLOCKHazardous means cuttable?
- ERROREvery channel rated ≥ 24 V references an interlock loop. A 28 V bus with nothing that cuts it when a run goes wrong — the first thing a safety officer asks about. interlock.missing
- WARNLower-voltage drive channels have interlocks too. Advisory — but remember the "harmless" 12 V actuator line that wasn't. interlock.missing/low
- ERROREvery interlock reference resolves to a defined loop. A channel pointing at estop_loop_2 when only estop_loop_1 exists is protection that looks present and is absent. interlock.undefined
- ERROREvery name in every cut-list resolves to a defined channel. The e-stop that cut bus_pwr — a typo for bus_power — and therefore cut nothing. The e-stop autopsy lives here. interlock.cuts
SECTION 6 · TIMINGWill the data mean anything?
- ERRORSample rate ≥ 2× each channel's fastest signal frequency. A 400 Hz signal sampled at 700 Hz looks fine. It is aliased. Weeks of campaign — void. timing.nyquist
- WARNAt least 5× oversampling on signals you need to reconstruct. Nyquist-compliant data that still can't reproduce the waveform anyone needed. timing.oversampling
- ERRORAll sample rates are positive. timing.rate
- ERRORAll step durations are positive. timing.duration
- ERRORTotal sequence time fits inside campaign_window_s. Discovering the overrun when the bench times out at hour eleven of a twelve-hour window is not a schedule slip — it's a config bug. timing.budget
SECTION 7 · STIMULUSNothing overdriven?
- ERROREvery step's amplitude_v stays under the target channel's max_voltage_v. 14 V into a 12 V-rated channel: one keystroke over the rail. stimulus.overdrive
- WARNNo stimulus step targets an input-only channel. At best a no-op; at worst, two drivers fighting on one line. stimulus.passive
SECTION 8 · SEQUENCESteps point at real things?
- ERROREvery step references a defined channel. A step pointing at a channel renamed last revision: the bench halts mid-campaign — or worse, doesn't. sequence.reference
- WARNNo sequence is empty. The "ran fine" campaign that ran nothing. sequence.empty
- ERROREvery step validates against the schema — known action, required fields present. A misspelled action is silently skipped by a lenient runner. sequence.steps
The sixty-second version
If the bench config is a file — and it should be a file, versioned like firmware — all twenty checks above run in about a second, the same way every time, including at 11 pm:
$ standguard.py lint bench_rigA.json --strict [PASS] schema (2) · unique (1) · pin (1) [PASS] limits (2) [PASS] interlock (4) [PASS] timing (5) [PASS] stimulus (2) · sequence (3) 20 checks · 0 errors · 0 warnings · verdict: CLEAR TO POWER (exit 0)
Wire it into CI and the gate becomes automatic: the pipeline refuses to power the rig until the config is clean — the same discipline you already trust for firmware, finally applied to the bench that tests it.
The checklist is not the product — the checklist is the scar tissue. Every rule ID on this page exists because someone, somewhere, learned it with hardware. If your bench has a scar that isn't here, that's rule twenty-one waiting to be written.
Take it
Print this page (it's formatted for paper), paste the twenty lines into your bench SOP, and run it manually if that's where you are today. The full catalog with failure modes is at standguard.dev/checks, and the machine-readable spec is in the open repo. When the manual version starts slipping — and it slips at exactly the moments it matters most — that's what the linter is for.
The catalog is open. The linter is in early access.
Twenty checks, eight categories, one auditable file that runs fully offline. Every rule ID in this checklist is documented in the spec.