Field diagnostics
hpx doctor --bundle lets a customer or field engineer hand a maintainer
one sanitised archive instead of a screen share. This page is the design
behind it, for anyone extending the collectors or auditing what leaves the
building; how to produce one is on Troubleshooting.
Diagram
Section titled “Diagram”- Collectorsone section each
- checks
- compatibility
- dependencies
- modules
- config
- probes
- ports
- host
- Redactiondiagnostics/redact.py
- paths
- URLs
- token shapes
- secret assignments
- serial numbers
- Manifesthpx.support-bundle-manifest v1
- sections
- artifacts
- redaction counts
- Archive
- deterministic zip
- content fingerprint
- verify
Text description of the diagram
- Collectors (one section each): checks, compatibility, dependencies, modules, config, probes, ports, host
- Redaction (
diagnostics/redact.py): paths, URLs, token shapes, secret assignments, serial numbers - Manifest (
hpx.support-bundle-manifestv1): sections, artifacts, redaction counts - Archive: deterministic zip, content fingerprint, verify
The blocks
Section titled “The blocks”Collectors
Section titled “Collectors”collect_support_bundle(options) runs one collector per section. None of
them can fail the bundle: a missing --workspace, an unresolvable
--config, absent hardware or no network each mark only their own section
unavailable, with a reason, and the rest is still collected
(SupportBundleSection.available in results/support_bundle.py).
| Section | Source | Present |
|---|---|---|
checks |
hostenv.doctor.inspect_environment(include_versions=True) |
always |
compatibility |
deps.compatibility.load_compatibility_baseline() |
always, offline |
dependencies and nsx.lock |
deps.dependencies.read_dependency_lock_provenance() plus a redacted copy of the lock text |
with --workspace |
modules |
the baseline’s qualified modules, plus the resolved modules from the same provenance read | always; the resolved half with --workspace |
config |
config.load_config() serialised |
with --config |
probes |
target.probe.jlink.list_connected_probes() |
unless --no-probes |
ports |
transport.ports.list_serial_ports() |
unless --no-ports |
host |
platform.system(), .release(), .machine(), .python_version(), sys.platform |
always, in the manifest |
The dependency collector reuses the pipeline’s own provenance reader; it
never parses nsx.lock itself and never resolves, synchronises or mutates
a workspace. The hostname (platform.node()) and any username are
deliberately not collected: neither helps diagnose a toolchain or build
problem and both identify a person more than the default redaction is
designed to catch.
What is never read
Section titled “What is never read”Models, firmware and generated sources, ELF and binary outputs, raw
proprietary payloads, credentials and secret environment values are never
read, not merely redacted afterwards. The archive writer and
verify_support_bundle() enforce one allow-list: every member is named
exactly nsx.lock or ends in .json, and an absolute path, a ..
segment, a backslash, a NUL byte or a duplicate name is rejected before
any bytes are trusted.
Redaction
Section titled “Redaction”diagnostics/redact.py is conservative and deterministic. Absolute paths
keep only their final component, except a path that is itself a home
directory, whose final component is the account name. URL credentials are
stripped and every query value is redacted except a short allow-list of
plainly harmless names; file:// paths go through path redaction. Known
token shapes (GitHub PAT, AWS access key, Slack token, JWT, HTTP bearer)
are replaced wherever they appear, including inside a URL. Secret-looking
KEY=VALUE and KEY: VALUE text is replaced, and any JSON mapping value
whose key looks secret-shaped is always replaced regardless of the value.
Device serial numbers are redacted structurally, by field name (serial,
serial_number), and then by literal substitution wherever the same value
recurs (a USB hwid string, a device path derived from it), so a sibling
field cannot leak what structural redaction caught. --raw-probe-ids is
the one opt-out; it keeps real probe and port serials and prints a warning.
Every pass returns a RedactionCounts; the manifest’s redaction object
sums them so a reviewer sees which categories were found and rewritten,
and how often. That is an audit trail of what redaction did, not a
certificate that nothing sensitive remains: review a bundle before sharing
it as you would any diagnostic output.
Manifest
Section titled “Manifest”SupportBundleManifest (hpx.support-bundle-manifest, schema version 1)
is a distinct contract from the run’s ResultManifest: a support bundle
has no run status, validity or comparability. It reuses ResultArtifact
for its member entries because that shape (path, size, SHA-256) is the
same either way, and carries the host record, the section availability and
reasons, the redaction counts and a generated_at timestamp.
Archive
Section titled “Archive”write_support_bundle() writes a ZIP_STORED archive: members sorted,
manifest.json last, a fixed 1980-01-01 timestamp and create_system = 0
on every entry, so the archive encodes neither the host clock nor its
operating system. Storage is uncompressed because DEFLATE’s exact bytes
depend on the zlib build doing the compressing, and the members are small
JSON. The file name comes from content_fingerprint(), a SHA-256 over
every other member’s name and digest, excluding manifest.json because
its timestamp differs run to run. Two bundles from identical inputs
therefore have byte-identical members and the same name, so a diff
between two bundles is a diff between two environments.
verify_support_bundle() re-derives every artifact’s size and digest,
requires the declared and actual member sets to match exactly, and applies
the member-name rules above before trusting anything else in the archive.
Where the code lives
Section titled “Where the code lives”| Block | Path under src/helia_profiler/ |
|---|---|
| collectors, archive, verify | diagnostics/support_bundle.py |
| redaction | diagnostics/redact.py |
| manifest model | results/support_bundle.py |
| CLI entry | cli/inspect_cmds.py (hpx doctor --bundle) |
| sources reused | hostenv/doctor.py, deps/compatibility.py, deps/dependencies.py, target/probe/jlink.py, transport/ports.py |
What next
Section titled “What next”Troubleshooting for when to produce a bundle and what to attach with it; the issue codes for the run-side diagnostics the bundle does not carry.