Diagnostics rollups: five proposals
How the black-box self-reports and the higher-level diagnostics you specify fit together, and where the Computer's own checks go · 2026-10-23 · robotdiagnostics / io-diagnostics · research and design only, no code changed
Build P3: the expected setup becomes the rig's acceptance spec. First land one contract fix, so a composite's problem is explained by the members it names.
- P3 scores 4.45 / 5. The others: P5 4.00, P4 3.65, P2 3.40, P1 3.20. No single weight moved by ±10 or +20 points changes the winner. P3 grows the file being built now (
expected.toml) from "what should be here" into "what must be true, and when it is checked":- startup: read-only checks at boot, plug and replug;
- continuous: judged on the reports the drivers already send;
- initiated: a person's click runs a check that touches or moves hardware.
- The barrier: knock-on collapse runs down every edge today.
- That is right for
via: a dead adapter explains its silent boards. - It is backwards for a composite, whose verdict is caused by its members. Measured below: the rig row counts one missing board twice, and a spider that reports ERROR hides its failed leg.
- The fix is an additive
caused_byon the composite's record. The chip names the member, and the composite's row keeps its own word.29
- That is right for
- The Computer is the host entity, judged as a leaf beside the tree. That is what the computer lane in flight already does (
tree.pyHOST_SELF).19 Its thresholds are data, because the stock tools disagree on this PC today:/at 8.5 % free is OK for ROShd_monitorand WARN for Netdata.2856
What you already decided
This section quoted Zach's private notes from 2025 and 2026 and is left out of the public copy. Its point survives in the next sections: a diagnostic is the interface's contract test, run once at the bench to accept a part and kept running in deployment to prove the part still meets it.
Three traps every proposal has to close
The tree has one parent edge per entity (part_of or via). summarize() collapses a problem into the nearest failed ancestor on that edge.16 Three probes ran against the real contract code, with no host process and no hardware:
Trap 1: the Computer's checks written on host swallow the rack
$ python3 -B knockon_probe.py --- Computer checks ON host chip counts (independent causes): {'error': 1} knock_on: [('host/usb1/1-3/can/15', 'host')] CAN 15 row: level=error · counted as its own cause: False --- Computer checks on sibling host/computer chip counts (independent causes): {'error': 2} knock_on: [] CAN 15 row: level=error · counted as its own cause: True
A full disk becomes "the cause", and CAN 15's fault 33 becomes its symptom. The computer lane in flight already guards against this: it evaluates the host's own records as a leaf host/:self beside its children. The comment there says "a hot CPU or a crashed computer plugin explains nothing about a dead CAN bus".19
Trap 2: a rig row beside the tree counts one missing board twice
$ python3 -B rig_doublecount_probe.py independent causes counted by the chip: {'warn': 1, 'error': 1} knock_on: []
Two records describe one fact:
- the rig row's
ExpectedMissing, an entityvia hostthat is not an ancestor of the board (expected.py _rig_row);17 - the board's own
BoardNotAnswering, at ERROR in the moteus driver.23
The chip reads "IO 2 ⚠".
Trap 3: a composite that reports ERROR hides its failed member
$ python3 -B spider_probe.py spider self-reports WARN -> chip names: cell/spider/leg3 · counts {'warn': 1, 'error': 1} · knock_on [] spider self-reports ERROR -> chip names: cell/spider · counts {'error': 1} · knock_on [('cell/spider/leg3', 'cell/spider')]
When the spider reports WARN, its record and the leg's are counted as two causes. When it reports ERROR, leg 3 (fault 33), which the spider's own message names, is filed as the spider's symptom.
The shared fix (record v0.2, additive), which every proposal below assumes:
- The field:
DiagnosticStatus.caused_by: tuple[str, ...], a list of entity paths. It is stripped byto_diagnostic_status(), so stock → v0 → stock stays the identity. - The rule:
- A record whose every
caused_bymember is itself a non-OK independent cause counts as those members' consequence. - A record that names nothing, or names an OK member, is its own cause.
- The composite's row keeps its level and message. Only the chip's count and the attention list change.
- A record whose every
- Precedent:
- AUTOSAR DEM files a failure as a CONSECUTIVE FAULT when a parent component is already FAILED, and does not store it (SWS_Dem_01127, 01130). It also allows a DAG of dependencies, not a tree (01120, CONSTR_06106).29
- Boeing's central maintenance computer screens out cascaded faults.36
- Icinga's
--blame --root-cause.32 - Alertmanager's
inhibit_rules.33
- Why not a second tree edge: membership lives on the composite's records. The rig composes things scattered across the connectivity tree (2 adapters, 7 boards, 2 cameras, the PC). ros2_medkit's areas and functions are the same kind of cross-cutting group.5162
Criteria
Weights come from your notes above. Your 2025 scope rule decides which checks get a second life. A check on code that, once fixed, stays fixed is acceptance only. A check on anything that touches the world (wear, wiring, config, firmware, temperature, drift) stays on as a monitor.1
| # | Criterion | Weight | Source |
|---|---|---|---|
| 1 | Black boxes keep their own word. A composite's verdict is the composite's, and the host never overrides a leaf. | 20 | Oct 22 rule; Blackbox Design |
| 2 | One check, two lives. The same check is the bench acceptance test and the deployment monitor (twin, bench, site), with per-context tolerances. | 20 | Changes; June 28; bam_docs; Next 2; Oct 16 |
| 3 | Honest status. "Not run yet" and "can't tell" never read green, and one fact is one cause. | 15 | record v0 lessons 3 and 5; Vibe Tracks |
| 4 | Declared, minimal, stable. Data, not scattered code, that survives swapping a board, planner or arm. | 15 | DP Diagnostics commitments 1 and 3 |
| 5 | Off the shelf. Stock schemas and tools; the stock ROS view keeps working. | 10 | availability before originality |
| 6 | Calm first screen. One answer up top, then computer → entity → diagnostic. | 10 | Oct 23 |
| 7 | A tracer bullet this week, on top of the expected setup in flight. | 10 | speed; the 29 unrun tests |
One scenario, six answers
The scenario comes from today's file17: CAN 15's moteus-c1 is out for replacement, the last comms sweep ran 26 h ago, and / has 8.5 % free. Below is the top of the page under each option.
The five proposals
P1 · Wrapper plugins: every composite is a driver
A rig (or arm, or spider) is a plugin like moteus_servo, with the same describe | diagnose | act CLI. It reads its members' latest records and writes its own. The verdict is code, owned by whoever owns the composite. This is closure under composition taken literally: a composite is a black box like any leaf.
{"record": "status", "path": "host/rig-1-dof-test-rack", "name": "1-DOF test rack: actuators",
"level": "warn", "code": "BoardOut", "caused_by": ["host/usb1/1-5/can/15"],
"message": "6/7 boards: CAN 15 out; single-joint tests on the other six still run"}
# rule in code: one board out -> WARN · CAN 1 out (hard stop + gravity load) or any adapter out -> ERROR
caused_by. The plugin protocol gains a records input; today a plugin gets --device only.diagnose() on the F1–F3 fixtures. On site, the host runs it every period. There is no readiness unless every wrapper writes its own.diagnostic_updater composite tasks26, Apex.Grace fault monitors7, ROS self_test for the on-demand part60, and the existing plugin protocol.- Every composite re-implements counting, staleness and "not run".
bam_diagnosticsfell into exactly this: a check that never ran was reported as OK, "SKIPPING".16 - Arrival skew. Members report at different times, so the wrapper flaps. Your Oct 23 health-bar note makes the same point.14
- A crashed wrapper reads STALE while every leaf is fine.
- The reasoning is invisible on the page.
- You need one wrapper per rig.
P2 · Rollup groups as data: the ROS aggregator's analyzers
One file declares groups and how their members combine. It uses the ROS diagnostic_aggregator analyzer schema plus Icinga Business Process operators. The host computes each group's own record. The groups only combine levels: there are no thresholds.
# ~/.config/io-diagnostics/rollups.yaml (aggregator schema, keyed on paths and ids, never names)
analyzers:
rack:
type: diagnostic_aggregator/AnalyzerGroup
path: 1-DOF test rack
analyzers:
adapters: {type: diagnostic_aggregator/GenericAnalyzer, path: Adapters,
expected: ["usb:0483:5740:XXXXXXXX", "usb:0483:5740:XXXXXXXX"]}
boards: {type: diagnostic_aggregator/GenericAnalyzer, path: Boards,
regex: "can/(1|11|12|13|14|15|21)$", num_items: 7}
cameras: {type: diagnostic_aggregator/GenericAnalyzer, path: Cameras,
expected: ["usb:8086:0b5b:XXXXXXXXXXXX", "usb:8086:0b5b:XXXXXXXXXXXX"]}
computer:
type: diagnostic_aggregator/GenericAnalyzer
path: Computer
expected: [cpu, memory, "disk /", temperature, clock]
caused_by names the members that set it. Leaves are untouched. It sits close to the central configured graph that v0 left out on purpose; it stays short of it only by never remapping a leaf.16io-diagnostics check --group … exits 0/1/2/3, the Monitoring Plugins API that round 3 adopts.18 It is a snapshot ("OK now"), never "accepted".- ROS matches on names, which v0 says machines must never key on.
- ROS's own stale rule is inconsistent:
GenericAnalyzerBasestill turns a partly-stale group into ERROR.27 - Structure only: it cannot say "ratio 0.1" or "reply loss < 1 %".
MIN 6 of 7turns a dead board green (hidden stratification).- A rollup with no written rule is why OpenBMC removed Redfish's
HealthRollupin 2024.61
P3 · The expected setup becomes the acceptance spec: one file, three run modes recommended
Keep everything expected.toml has. Add [[rig.check]] entries that each name the status they read, a pass condition and when they run. The three modes are aerospace's built-in-test modes35:
- startup (power-on BIT): boot, plug and replug, read-only;
- continuous (CBIT): judged on every fresh driver report;
- initiated (IBIT): a person's click runs the action. The result is kept and expires.
Readiness is a separate state from pass/fail, as in OBD-II: ran, not run since boot, or not run since a change.3738
[[rig]] name = "1-DOF test rack" # ... the [[rig.device]] entries exactly as today ... [[rig.check]] name = "firmware current" when = "startup" # every boot, plug and replug; reads only reads = "can/*: firmware" # each expected board's own firmware status pass = "ok" # i.e. no UpdateAvailable [[rig.check]] name = "reply loss" when = "continuous" # judged on the reports the drivers already send reads = "can/*: servo" pass = "reply_loss < 1 %" [[rig.check]] name = "comms sweep" when = "initiated" # a person's click: check_comms on both adapters (query) action = "check_comms" valid_for = "24 h" invalidated_by = ["Restarted", "Plugged", "firmware"] [[rig.check]] name = "CAN 12 pendulum tracking" when = "initiated" action = "tape" # bam_rack_twin hardware tape: MOVES, never scheduled pass = "tracking_rms < 0.5 deg" valid_for = "7 d" [computer] # thresholds as data; defaults = ROS diagnostic_common_diagnostics cpu_warn = "90 %" memory_warn = "90 %" disk_free_warn = "5 %" disk_free_error = "1 %" usb3_links = 2 # both D405s at 5000 Mb/s [profile.twin] # the same checks on the MuJoCo twin, wider where physics differs tracking_rms = "< 1.0 deg"
- Each check is a status on the rig entity, with
source: io_diagnostics.expectedandcaused_bynaming the members it read. - Not run is
unknownwith codeCheckNotRun, the twin of today'sExpectedNotChecked. - An initiated result never goes stale, but it expires:
- Expectations are the consumer's, as in consumer-driven contracts4243. So they live on the rig's row, and a board's row stays its own self-report. Today's
RatioMismatchon the board moves to the rig (decision 2 below). - Later, a node's pass condition can default from its part's robotparts
diagnosticsslot (ok_range).24
[computer]sets the host leaf's thresholds, and rig checks can read it (usb3_links).- On this PC,
k10tempTctl has no chip limits, and Zen 4 is designed to sit at its 95 °C limit under load. So CPU temperature warns on sustained throttling, not on a fixed number.5558 - The NVMe drive reports max 82 / crit 85 °C, used as given.
io-diagnostics accept --rig "1-DOF test rack" --profile bench|site|twin:
- runs the startup and continuous checks now;
- lists the initiated checks, with their dry-run tokens, for a person to run;
- prints JUnit and exits with a Nagios code.
validate at dev, serve → /healthz at deploy, one file.44 It is also the factory/site acceptance pair (IEC 62381: FAT, FIT, SAT, SIT).40passgrows into a mini-language. Cap it at "measurement op value", with no expressions.- The file lives in
~/.config, so the copy a site runs needs a committed twin. - There are two manifests for one rack:
deployment.jsonalready holds UUIDs and firmware history.23 - Validity windows can nag.
- A twin pass is not a site pass, so the profile must show on the row.
P4 · The acceptance suite is code, and its results are records
Rig-level checks are pytest tests in the rig's repo, marked with their entity, mode and validity. A pytest plugin turns each result into a status: pass → ok, fail → error, skip → unknown NotRun, plus typed measurements. Development runs pytest. The robot runs the same suite on a timer (passive marks) or on a click (marks that move hardware).
# bam_ws/src/dev/bam_rack_acceptance/tests/test_one_dof_rack.py
from robotdiagnostics.testing import diagnostic # pytest plugin: results -> diagnostics-record/0 lines
@diagnostic(entity="rig:1-DOF test rack", when="startup")
def test_every_expected_board_answers(rig):
assert rig.answering("can/*") == rig.expected("can/*") # names the missing one
@diagnostic(entity="rig:1-DOF test rack", when="initiated", moves=True, valid_for="7d")
def test_can12_pendulum_tracking(rig, measure):
run = rig.act("can/12", "tape", profile="slow") # a person confirms the token
measure("tracking_rms", run.rms_deg, unit="deg", error_high=0.5)
source: pytest, the run's stamp and values.commit. Vibe Tracks reads the same run (@proves, vt-kpi).25 The verdict is the consumer's, but a test can reach into any box.pytest -m "rack and not moves" --target twin runs in CI; --target bench runs on the rack; a systemd timer runs -m startup on site.- Periodic, not live.
- Test code and its dependencies ship to the robot.
- The ruler drifts unless tests are read-only to builders.
- Two vocabularies (test outcomes and levels) have to be mapped.
- A suite nobody schedules repeats 2025's 29 unrun tests.15
P5 · Capability gates: the top level answers "what can I do now?"
Instead of a colour per composite, declare a handful of named capabilities, each a set of required checks. The top of the page lists which are available and, for each one that isn't, its one cause. The actions' existing preconditions (port_free, board_answers, bus_lock_free) are the first capabilities.
[[capability]]
name = "Track a trajectory on CAN 12"
requires = ["can/12: servo ok", "can/12: config ratio 0.1", "fdcanusb XXXXXXXX: CAN bus ok",
"bus_lock_free", "computer: cpu < 80 %"]
[[capability]]
name = "Record both D405s"
requires = ["D405 ·4411: usb link 5000", "D405 ·6036: usb link 5000",
"computer: disk / free > 50 GB", "camera_free"]
[[capability]]
name = "Site acceptance"
requires = ["1-DOF test rack: all checks passed within 7 d"]
caused_by names the failing requirements. Capabilities sit outside every box, so nobody overrides a leaf.assert caps["Track a trajectory on CAN 12"].available. On site the list is the readiness display, and a behaviour checks it before it runs.- Capabilities multiply into Autoware's central graph.
- A boolean hides degradation.
- It must never become the safety path (record v0 §9).
- It is a new concept to learn.
Scored comparison
| Criterion | W | P1 | P2 | P3 | P4 | P5 |
|---|---|---|---|---|---|---|
| Black boxes keep their word | 20 | 5 | 3 | 4 | 3 | 4 |
| One check, two lives | 20 | 3 | 2 | 5 | 5 | 4 |
| Honest status | 15 | 2 | 4 | 5 | 4 | 4 |
| Declared, minimal, stable | 15 | 2 | 4 | 4 | 3 | 4 |
| Off the shelf | 10 | 3 | 5 | 4 | 4 | 4 |
| Calm first screen | 10 | 4 | 3 | 4 | 3 | 5 |
| Tracer bullet this week | 10 | 3 | 4 | 5 | 3 | 3 |
| Weighted / 5 | 3.20 | 3.40 | 4.45 | 3.65 | 4.00 |
- Sensitivity: moving any one weight by −10, +10 or +20 never dethrones P3. Even with black-box purity weighted at 50, P3 leads 4.35 to P5's 4.00.
- Why P5 comes second rather than first: its
requireslists are P3's checks, and P3 builds on the file already in flight.
The Computer entity, as read on this PC
A passive /proc and /sys snapshot taken at 09:12. No device nodes were opened and io-diagnostics was not run. The stock references are node_exporter's collectors52, the kernel's AER and USB ABI5354, and hwmon55.
$ python3 -B -I computer_snapshot.py cpu: load 1.54/2.16/2.79 on 24 logical CPUs memory: 61.9 GiB total, 40.2% available disk /: 8.5% free temp nvme/Composite: 39.9 C (max 82, crit 85) temp nvme/Sensor 1: 39.9 C (max 65262) # a sentinel, not a limit: must be filtered temp k10temp/Tctl: 68.4 C (no chip limits) temp amdgpu/edge: 47.0 C (no chip limits) usb 1-3 0483:5740 serial XXXXXXXX: speed 12 Mb/s, devnum 10 usb 1-5 0483:5740 serial XXXXXXXX: speed 12 Mb/s, devnum 9 usb 6-1 8086:0b5b serial XXXXXXXXXXXX: speed 5000 Mb/s, devnum 2 usb 6-2 8086:0b5b serial XXXXXXXXXXXX: speed 5000 Mb/s, devnum 3 pcie 0000:01:00.0 (display): x16 of x16, 16.0 GT/s PCIe (max 16.0 GT/s PCIe), TOTAL_ERR_COR 0
| Check on the host leaf | Today | Default rule (stock source) | Verdict |
|---|---|---|---|
| cpu | load 1.5 / 24 | WARN when any core > 90 % (ROS cpu_monitor; it has no ERROR)28 | ok |
| memory | 40 % available | WARN > 90 % used (ROS ram_monitor)28 | ok |
| disk / | 8.5 % free | ROS hd_monitor: WARN < 5 %, ERROR < 1 % free.28 Netdata: WARN > 90 % used.56 | ok by ROS · warn by Netdata |
| temperature: CPU | Tctl 68 °C | No chip limits. Zen 4 is designed to sit at its 95 °C limit under load58, so warn on sustained throttling, never on a fixed number | ok |
| temperature: NVMe | 40 °C | The chip's own max 82 / crit 85 (hwmon temp*_max, _crit) | ok |
| usb links (both D405s) | 5000 Mb/s ×2 | WARN at 480 (USB 2 fallback), already judged on the port rows | ok |
| pcie link + AER (GPU) | x16/x16 · 0 COR | Width below max, or uncorrectable AER > 0, is a warning. Already judged on the PCI rows (tree.py) | ok |
| re-enumeration | devnum 10 / 9 | A rising devnum means the device re-enumerated. Today the kernel logged repeated disconnects on 1-3 between 08:07 and 08:10 (device number 6 → 10). Count them per port per hour, and make that a level only past a declared rate | events today; a level only past the rate |
GPU health (Xid errors, PCIe replays via nvidia-smi or DCGM57) belongs to an NVIDIA driver plugin of its own, under the rule "drivers bring their own diagnostics".
Recommendation and tracer bullet
Build P3, on the caused_by seam.
- Keep P1 as the escape hatch the record already allows. A composite whose verdict can't be written as data gets a wrapper plugin.
- Borrow ROS's "Expected N, found M" wording from P2, and a pytest fixture from P4, so the bench gate is literally
pytest. - Add P5 once you want capabilities beyond the actions' preconditions.
- Barrier, alone (
contract/record.py+tests/test_record.py):- Add
caused_byand the counting rule. The three probes above become regression tests. - Done when: one missing CAN 15 is one cause and the chip names CAN 15; a spider at ERROR names leg 3; stock → v0 → stock is still the identity.
- Land it after the computer lane commits. Its
io_diagnostics/write set is disjoint from this one, but it imports the contract.
- Add
io_diagnostics/expected.py+cli.py:- Add
[[rig.check]]withwhen, and two checks: firmware current (startup), and comms sweep (initiated,check_comms, valid 24 h, reset byRestarted/Plugged/firmware). - Keep readiness in
~/.local/state/io-diagnostics/acceptance.json. - Add the
acceptverb.
- Add
tests/test_accept.py, with the acceptance written first:- Fixture F1 (CAN 15 missing) exits 1, naming CAN 15.
- The full fixture exits 0.
- A sweep that never ran exits 3, UNKNOWN "comms sweep not run".
- A 10-line pytest fixture around
accept, soassert rig.okruns on fixtures in CI and on the rack in your terminal.
What needs you
Each item has my recommendation and the default I'll take if you say nothing.
1. Composites explain upward (caused_by).
Recommend: yes. Default: build it as the barrier.
2. Rig expectations live on the rig's row, and a board shows only a muted "rig expects 0.1".
Recommend: yes. Default: leave RatioMismatch on the board until P3's checks land.
3. Validity windows for click-run checks: comms sweep 24 h, motion checks 7 d, all reset on restart, replug or firmware change.
Default: those.
4. One manifest per rack.
Recommend: deployment.json stays the versioned as-built record, and expected.toml later refers to it by rig id. Default: keep both for now.
5. Spider tolerance for the rack ("6 of 7 is fine").
Recommend: not yet. An acceptance rig with a missing board is WARN ExpectedMissing, as today. Default: as today.
Deliberately left out
- Safety. None of this is a safety path (ISO 14229 via DEM; record v0 §9).16
- Autoware's hazard-per-mode remapping. P5 keeps the useful half, availability, and never remaps a leaf.
- Fleet streaming to a server4, and SLOs and error budgets59. These belong to fleet KPIs, later.
- Hardware. I touched none, did not run io-diagnostics, and wrote no code: a peer lane is editing
host.py,plugins.pyandtree.pyright now. - Probes. The three probe scripts lived in a session scratchpad; their full code is below.
The three probe scripts (pure contract code)
# shared setup
import sys; sys.path.insert(0, "~/io-diagnostics")
from contract.record import DiagnosticStatus, Entity, summarize
NOW = 1761200000.0
# trap 1: host/usb1/1-3 (fdcanusb) -> can (part_of) -> can/15 (via); "disk /" ERROR written on "host"
# vs on a sibling Entity("host/computer", part_of="host"); CAN 15 ERROR MoteusFault.33
# trap 2: Entity("host/rig-1-dof-test-rack", kind="rig", via="host") WARN ExpectedMissing
# + host/usb1/1-5/can/15 ERROR BoardNotAnswering (the driver's level)
# trap 3: cell (group) -> spider (part_of) -> leg1..8 (part_of); leg 3 ERROR MoteusFault.33;
# spider self-reports WARN, then ERROR, "leg 3 failed: walking on 7"
summary = summarize(entities, statuses, NOW)
root = summary["host"].rollup # or summary["cell"].rollup
print(dict(root.counts), list(root.knock_on), root.origin)
Sources
- Changes (Apple Note), 2025-06-27: vault
a private note - June 28 - UR pick up in Sim (Notion, 2025): vault
a private folder - June 29 - Top Level Design / Python calls for To do list (Notion, 2025)
- First deployments (Apple Note), 2025-07-13
- How long before I can actually get a robot moving (Apple Note), 2025-07-22
- Acceptance Tests (bam_core), 2025-07-23: your items 1–29 only
- bam_ros_common, 2025-07-23: the Apex.Grace reaction
~/bam_ws/src/dev/bam_docs/diagnostics.md- Next 2 (Apple Note), 2025-12-04
- Q - What do good sub components look like?
- Daily Note - Oct 14 2026: "Computed, measured, calculated vs entered"
- Daily Note - Oct 16 2026: Vibe Tracks feature tree
- Daily Note - Oct 22 2026: "Feedback on Io Diagonistics"
- Daily Note - Oct 23 2026: "feedback on Digoinastics"
- MOC - Component Testing: your Oct 3 notes and the AI timeline
~/io-diagnostics/docs/diagnostics-record-v0.md: record v0 / v0.1; the black-box rule §3; not a safety path §9~/io-diagnostics/docs/expected-setup.mdandio_diagnostics/expected.py(_rig_row)~/io-diagnostics/docs/round3-brief-2026-10-23.md: CLI as the seam, the Monitoring Plugins API~/io-diagnostics/io_diagnostics/tree.py:HOST_SELF, in the computer lane, uncommitted at 09:06 on 2026-10-23- This report.
- MOC - Diagnostics §7: like_keras
bam_diagnostics ~/bam_archive/v1_bam_ws/src/bam_brain/dev/v3/mdp_machine/docs/diagnostics-architecture.md- bam_ws branch
claude/traj-tracking-sim-to-real-a3da60:src/dev/bam_rack_twin/records/one-dof-test-rack/deployment.json;moteus_servo/src/moteus_servo/diagnostics.py(BoardNotAnsweringis an error) ~/robotparts/docs/seams/robotdiagnostics.md~/vibetracks/docs/peps/0007-feature-tree-computed-spec.md,0005-runs-in-kpis-out.md- ROS diagnostic_aggregator README (ros2)
- ROS generic_analyzer_base.hpp (ros2): report()
- ROS diagnostic_common_diagnostics README (ros2)
- AUTOSAR CP R22-11 SWS Diagnostic Event Manager, §7.3.6 and §7.5.1
- Nagios Core 4: network reachability
- Icinga Business Process: operators
- Icinga Business Process: monitoring, --blame --root-cause
- Prometheus Alertmanager configuration: inhibit_rules
- Datadog composite monitors
- MIL-STD-2165A glossary: BIT, fault detection and isolation, false alarm (the PBIT/CBIT/IBIT mode names are programme usage, not this text)
- Boeing central maintenance computer, US 4943919A
- EPA final rule 66 FR 18156 (2001): OBD readiness
- Texas DPS: "Ready or not"
- IEC 61508-4 proof-test definition (secondary glossary)
- IEC 62381:2024: FAT, FIT, SAT, SIT
- Kubernetes: liveness, readiness and startup probes
- Pact: can-i-deploy
- Martin Fowler: ContractTest (2011)
- Goss: validate, serve /healthz, junit/tap/nagios formats
- Testinfra
- Chef InSpec controls
- OpenHTF: measurements (PASS / FAIL / UNSET / PARTIALLY_SET / SKIPPED, marginal), phases, plugs
- Labgrid: usage, pytest plugin
- Checkly CLI
- Autoware diagnostic graph aggregator
- ros2_medkit
- Prometheus node_exporter
- Linux ABI: PCIe AER statistics
- Linux ABI: sysfs USB
- Linux hwmon sysfs interface
- Netdata health.d/disks.conf
- NVIDIA DCGM feature overview
- AMD community: Ryzen 9 7900X maximum 95 °C (secondary)
- Google SRE book: Service Level Objectives
- ROS self_test README (ros2)
- C - Diagnostics Record (What We Learned Since ROS DiagnosticStatus): Redfish HealthRollup, OpenBMC's 2024 removal
~/io-diagnostics/docs/research/diagnostics-schema-ros.md§3–4: ros2_medkit entities, Autoware's graph
Vault note: a private note · built by Claude Code (Opus 5.5), scribe lane