bmc-sensor-audit 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- bmc_sensor_audit-0.1.0/.githooks/commit-msg +13 -0
- bmc_sensor_audit-0.1.0/.githooks/pre-commit +41 -0
- bmc_sensor_audit-0.1.0/.github/ISSUE_TEMPLATE/bug_report.md +27 -0
- bmc_sensor_audit-0.1.0/.github/workflows/canary.yml +155 -0
- bmc_sensor_audit-0.1.0/.github/workflows/checks.yml +137 -0
- bmc_sensor_audit-0.1.0/.gitignore +19 -0
- bmc_sensor_audit-0.1.0/CITATION.cff +33 -0
- bmc_sensor_audit-0.1.0/CODE_OF_CONDUCT.md +20 -0
- bmc_sensor_audit-0.1.0/CONTRIBUTING.md +60 -0
- bmc_sensor_audit-0.1.0/LICENSE +202 -0
- bmc_sensor_audit-0.1.0/NOTICE +46 -0
- bmc_sensor_audit-0.1.0/PKG-INFO +545 -0
- bmc_sensor_audit-0.1.0/README.md +526 -0
- bmc_sensor_audit-0.1.0/SECURITY.md +71 -0
- bmc_sensor_audit-0.1.0/docs/attestation-format.md +114 -0
- bmc_sensor_audit-0.1.0/docs/burn-in.md +118 -0
- bmc_sensor_audit-0.1.0/docs/stage2/s1-threshold-granularity.md +79 -0
- bmc_sensor_audit-0.1.0/docs/stage2/s2-oscillation-not-a-defect.md +67 -0
- bmc_sensor_audit-0.1.0/docs/stage2/s3-corpus-scale.md +77 -0
- bmc_sensor_audit-0.1.0/examples/supplemental/TEMPLATE.json +27 -0
- bmc_sensor_audit-0.1.0/examples/supplemental/ampere-mtjade.json +16 -0
- bmc_sensor_audit-0.1.0/pyproject.toml +55 -0
- bmc_sensor_audit-0.1.0/src/bmc_sensor_audit/__init__.py +6 -0
- bmc_sensor_audit-0.1.0/src/bmc_sensor_audit/cli.py +640 -0
- bmc_sensor_audit-0.1.0/src/bmc_sensor_audit/detect/__init__.py +10 -0
- bmc_sensor_audit-0.1.0/src/bmc_sensor_audit/detect/attestation.py +231 -0
- bmc_sensor_audit-0.1.0/src/bmc_sensor_audit/detect/feeder.py +331 -0
- bmc_sensor_audit-0.1.0/src/bmc_sensor_audit/detect/generator.py +496 -0
- bmc_sensor_audit-0.1.0/src/bmc_sensor_audit/detect/supplemental.py +301 -0
- bmc_sensor_audit-0.1.0/src/bmc_sensor_audit/inventory/__init__.py +1 -0
- bmc_sensor_audit-0.1.0/src/bmc_sensor_audit/inventory/diff.py +380 -0
- bmc_sensor_audit-0.1.0/src/bmc_sensor_audit/inventory/entity_manager.py +561 -0
- bmc_sensor_audit-0.1.0/src/bmc_sensor_audit/inventory/redfish.py +544 -0
- bmc_sensor_audit-0.1.0/src/bmc_sensor_audit/inventory/redfish_properties.json +248 -0
- bmc_sensor_audit-0.1.0/src/bmc_sensor_audit/inventory/redfish_schema.py +110 -0
- bmc_sensor_audit-0.1.0/src/bmc_sensor_audit/inventory/regression.py +266 -0
- bmc_sensor_audit-0.1.0/src/bmc_sensor_audit/inventory/sensor_types.py +122 -0
- bmc_sensor_audit-0.1.0/src/bmc_sensor_audit/report.py +543 -0
- bmc_sensor_audit-0.1.0/src/bmc_sensor_audit/testing/__init__.py +5 -0
- bmc_sensor_audit-0.1.0/src/bmc_sensor_audit/testing/mock_redfish.py +204 -0
- bmc_sensor_audit-0.1.0/tests/fixtures/redfish_witherspoon_2_9_0.json +188 -0
- bmc_sensor_audit-0.1.0/tests/fixtures/stuck_at_qemu_bletchley.json +2166 -0
- bmc_sensor_audit-0.1.0/tests/fixtures/upstream/LICENCE +13 -0
- bmc_sensor_audit-0.1.0/tests/fixtures/upstream/README.md +128 -0
- bmc_sensor_audit-0.1.0/tests/fixtures/upstream/ampere/mtjade.json +2057 -0
- bmc_sensor_audit-0.1.0/tests/fixtures/upstream/asrock/spc621d8hm3.json +492 -0
- bmc_sensor_audit-0.1.0/tests/fixtures/upstream/delta/awf2dc3200w_psu.json +42 -0
- bmc_sensor_audit-0.1.0/tests/fixtures/upstream/intel/8x25_hsbp.json +20 -0
- bmc_sensor_audit-0.1.0/tests/fixtures/upstream/intel/axx1p100hssi_aic.json +33 -0
- bmc_sensor_audit-0.1.0/tests/fixtures/upstream/meta/bletchley/bletchley_baseboard.json +954 -0
- bmc_sensor_audit-0.1.0/tests/fixtures/upstream/meta/bletchley/bletchley_chassis.json +370 -0
- bmc_sensor_audit-0.1.0/tests/fixtures/upstream/meta/bletchley/bletchley_frontpanel.json +94 -0
- bmc_sensor_audit-0.1.0/tests/fixtures/upstream/meta/catalina/catalina_osfp.json +28 -0
- bmc_sensor_audit-0.1.0/tests/fixtures/upstream/meta/fbyv2.json +585 -0
- bmc_sensor_audit-0.1.0/tests/fixtures/upstream/meta/fbyv35.json +856 -0
- bmc_sensor_audit-0.1.0/tests/fixtures/upstream/meta/twinlake.json +32 -0
- bmc_sensor_audit-0.1.0/tests/fixtures/upstream/nvidia/cx7_mezzanine_module.json +100 -0
- bmc_sensor_audit-0.1.0/tests/fixtures/walk_qemu_bletchley.json +407 -0
- bmc_sensor_audit-0.1.0/tests/fixtures/walk_sensors_tree.json +50 -0
- bmc_sensor_audit-0.1.0/tests/fixtures/walk_thermal_power_tree.json +51 -0
- bmc_sensor_audit-0.1.0/tests/test_attestation_format.py +274 -0
- bmc_sensor_audit-0.1.0/tests/test_burn_in_cadence.py +94 -0
- bmc_sensor_audit-0.1.0/tests/test_cli_config_guards.py +302 -0
- bmc_sensor_audit-0.1.0/tests/test_commit_msg_check.py +145 -0
- bmc_sensor_audit-0.1.0/tests/test_community_files.py +219 -0
- bmc_sensor_audit-0.1.0/tests/test_diff.py +259 -0
- bmc_sensor_audit-0.1.0/tests/test_engine_bridge.py +1106 -0
- bmc_sensor_audit-0.1.0/tests/test_entity_manager.py +349 -0
- bmc_sensor_audit-0.1.0/tests/test_feeder.py +417 -0
- bmc_sensor_audit-0.1.0/tests/test_generator.py +472 -0
- bmc_sensor_audit-0.1.0/tests/test_hook_is_enabled.py +127 -0
- bmc_sensor_audit-0.1.0/tests/test_hygiene_check.py +208 -0
- bmc_sensor_audit-0.1.0/tests/test_readme_commands.py +257 -0
- bmc_sensor_audit-0.1.0/tests/test_redfish.py +551 -0
- bmc_sensor_audit-0.1.0/tests/test_regression_gate.py +452 -0
- bmc_sensor_audit-0.1.0/tests/test_sensor_types.py +137 -0
- bmc_sensor_audit-0.1.0/tests/test_strict_fields.py +419 -0
- bmc_sensor_audit-0.1.0/tests/test_supplemental.py +236 -0
- bmc_sensor_audit-0.1.0/tests/test_supplemental_examples.py +142 -0
- bmc_sensor_audit-0.1.0/tests/test_vendored_corpus.py +351 -0
- bmc_sensor_audit-0.1.0/tools/commit_msg_check.py +139 -0
- bmc_sensor_audit-0.1.0/tools/derive_redfish_properties.py +169 -0
- bmc_sensor_audit-0.1.0/tools/hygiene_check.py +248 -0
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
# Check the commit message before it becomes permanent.
|
|
3
|
+
#
|
|
4
|
+
# Activated by the same setting as the pre-commit hook:
|
|
5
|
+
# git config core.hooksPath .githooks
|
|
6
|
+
#
|
|
7
|
+
# A message is the one published surface that can never be corrected. The hygiene
|
|
8
|
+
# check guards files and cannot see the message; this runs the same rules over it.
|
|
9
|
+
#
|
|
10
|
+
# To commit past it knowingly: git commit --no-verify
|
|
11
|
+
# Note that --no-verify also disables the pre-commit hygiene sweep. That is why
|
|
12
|
+
# this hook refuses only on objective problems and merely notes the heuristic one.
|
|
13
|
+
exec python3 "$(git rev-parse --show-toplevel)/tools/commit_msg_check.py" "$1"
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
# Refuse a commit carrying something that should not be published, and one whose
|
|
3
|
+
# README no longer describes the suite.
|
|
4
|
+
#
|
|
5
|
+
# Activate once per clone: git config core.hooksPath .githooks
|
|
6
|
+
#
|
|
7
|
+
# Versioned deliberately. A hook that lives only in .git/hooks exists on one
|
|
8
|
+
# machine, is invisible in review, and is absent from every fresh clone -- which
|
|
9
|
+
# for a repository authored in public means the safety check is missing exactly
|
|
10
|
+
# when a new contributor needs it most.
|
|
11
|
+
#
|
|
12
|
+
# To commit past it knowingly: git commit --no-verify
|
|
13
|
+
# That is a real escape hatch and it should be rare enough to notice.
|
|
14
|
+
set -e
|
|
15
|
+
ROOT="$(git rev-parse --show-toplevel)"
|
|
16
|
+
|
|
17
|
+
python3 "$ROOT/tools/hygiene_check.py"
|
|
18
|
+
|
|
19
|
+
# The README's test count, checked where the drift is created rather than where it
|
|
20
|
+
# is discovered. Dependency-free and about 0.2s.
|
|
21
|
+
#
|
|
22
|
+
# The count it checks is taken from what git TRACKS, not from what the directory
|
|
23
|
+
# holds, and that is the whole reason it can run here at all. The published 289 was
|
|
24
|
+
# measured on a working tree carrying a test file that was never committed: true of
|
|
25
|
+
# that disk, false of this repository. A disk-derived count would make this hook
|
|
26
|
+
# red on any tree with an untracked test in it -- red every run, for a reason that
|
|
27
|
+
# is not a defect, which is how a check teaches people to pass --no-verify. That
|
|
28
|
+
# flag also switches off the hygiene sweep above, so a check nobody can satisfy
|
|
29
|
+
# costs more than the one it was added to provide.
|
|
30
|
+
#
|
|
31
|
+
# A missing pytest fails rather than skips. A gate that converts its own red into a
|
|
32
|
+
# skip removes the failure and the evidence together.
|
|
33
|
+
if ! python3 -c "import pytest" 2>/dev/null; then
|
|
34
|
+
echo "pre-commit: pytest is not installed, so the README count check cannot run." >&2
|
|
35
|
+
echo " python3 -m pip install pytest (or commit with --no-verify)" >&2
|
|
36
|
+
exit 1
|
|
37
|
+
fi
|
|
38
|
+
|
|
39
|
+
exec python3 -m pytest \
|
|
40
|
+
"$ROOT/tests/test_readme_commands.py::TestTheReadmeTestCount" \
|
|
41
|
+
-q -p no:cacheprovider
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: Bug report
|
|
3
|
+
about: A gate that said clean over something it did not audit is the report this project wants most
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
## What happened
|
|
7
|
+
<!-- one sentence -->
|
|
8
|
+
|
|
9
|
+
## What you expected
|
|
10
|
+
|
|
11
|
+
## The configuration
|
|
12
|
+
<!-- the smallest config that shows it, or the name of the vendored one -->
|
|
13
|
+
|
|
14
|
+
## The walk
|
|
15
|
+
<!-- the capture file, or how the target was reached -->
|
|
16
|
+
|
|
17
|
+
## The report and the exit code
|
|
18
|
+
<!-- full text, and `echo $?` immediately after -->
|
|
19
|
+
|
|
20
|
+
## Environment
|
|
21
|
+
<!-- Python; and if [detect] is involved: `pip show arbiter-engine` — presence
|
|
22
|
+
and correctness are different checks -->
|
|
23
|
+
|
|
24
|
+
## Where you stopped looking
|
|
25
|
+
<!-- Optional, and genuinely useful. "I did not audit X" is read here as a lead,
|
|
26
|
+
not a disclaimer -- the largest finding in this repository so far came from
|
|
27
|
+
the half of a report its author left open. -->
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
# Does the engine still behave the way Stage 2 assumes?
|
|
2
|
+
#
|
|
3
|
+
# `pyproject.toml` declares `arbiter-engine>=0.1.6,<0.2`. That is a RANGE, and upstream
|
|
4
|
+
# shipped five releases in forty-eight hours. Everything Stage 2 depends on -- four
|
|
5
|
+
# behavioural pillars and two payload locations -- can therefore change without this
|
|
6
|
+
# repository changing at all, and without anybody choosing it.
|
|
7
|
+
#
|
|
8
|
+
# So this installs the range rather than a pin, deliberately: the job exists to find out
|
|
9
|
+
# when a permitted release moves something. It resolves and prints the version first, so
|
|
10
|
+
# a red run names the release that caused it instead of leaving that to be guessed.
|
|
11
|
+
#
|
|
12
|
+
# It is scheduled, because upstream releases do not correlate with our pushes -- but a
|
|
13
|
+
# schedule-only workflow in a sibling project accumulated zero runs across fifteen
|
|
14
|
+
# pushes, so it ALSO fires when the bridge or the dependency declaration changes. A
|
|
15
|
+
# canary nobody has ever seen sing is not a canary.
|
|
16
|
+
|
|
17
|
+
name: engine canary
|
|
18
|
+
|
|
19
|
+
on:
|
|
20
|
+
schedule:
|
|
21
|
+
- cron: "23 5 * * *"
|
|
22
|
+
workflow_dispatch:
|
|
23
|
+
push:
|
|
24
|
+
paths:
|
|
25
|
+
- "tests/test_engine_bridge.py"
|
|
26
|
+
- "pyproject.toml"
|
|
27
|
+
- "docs/stage2/**"
|
|
28
|
+
- ".github/workflows/canary.yml"
|
|
29
|
+
|
|
30
|
+
jobs:
|
|
31
|
+
canary:
|
|
32
|
+
runs-on: ubuntu-latest
|
|
33
|
+
steps:
|
|
34
|
+
- uses: actions/checkout@v4
|
|
35
|
+
|
|
36
|
+
- uses: actions/setup-python@v5
|
|
37
|
+
with:
|
|
38
|
+
python-version: "3.11"
|
|
39
|
+
|
|
40
|
+
- name: Install the engine from the declared range
|
|
41
|
+
# Not a pinned version. The range is what a consumer actually gets, so the
|
|
42
|
+
# range is what gets tested.
|
|
43
|
+
run: |
|
|
44
|
+
set -euo pipefail
|
|
45
|
+
python3 -m pip install --quiet "arbiter-engine>=0.1.6,<0.2" pytest
|
|
46
|
+
echo "resolved:"
|
|
47
|
+
python3 -m pip show arbiter-engine | sed -n 's/^Version: / arbiter-engine /p'
|
|
48
|
+
|
|
49
|
+
- name: Four pillars and two payload locations
|
|
50
|
+
run: PYTHONPATH=src python3 -m pytest tests/test_engine_bridge.py -q
|
|
51
|
+
|
|
52
|
+
- name: Release test -- generator, feeder and bridge on the pinned floor
|
|
53
|
+
# The composed check: the model generator, the feeder and gate, and the
|
|
54
|
+
# end-to-end bridge, all against the engine the range resolves to. Run
|
|
55
|
+
# together because they only mean anything together -- a generator whose
|
|
56
|
+
# output the engine half-ignores passes its own golden tests.
|
|
57
|
+
run: |
|
|
58
|
+
PYTHONPATH=src python3 -m pytest \
|
|
59
|
+
tests/test_generator.py tests/test_feeder.py tests/test_engine_bridge.py -q
|
|
60
|
+
|
|
61
|
+
- name: Per-run attestation, and a check that it says something
|
|
62
|
+
# `attest` records what was checked, what was DECLINED, and the measurement
|
|
63
|
+
# behind each finding. The module has existed since it was written; nothing
|
|
64
|
+
# produced one on a schedule, so there was no artifact for anyone to read and
|
|
65
|
+
# no history to compare a later run against.
|
|
66
|
+
#
|
|
67
|
+
# Run over the vendored declaration and the vendored capture, so it needs no
|
|
68
|
+
# hardware and no network and produces the same answer for anyone who clones.
|
|
69
|
+
#
|
|
70
|
+
# The exit code is deliberately NOT the gate here: this run finds real
|
|
71
|
+
# regressions in the QEMU capture and exits 1 every time, which is the
|
|
72
|
+
# correct answer to the question `detect` was asked. What is being checked is
|
|
73
|
+
# that the ARTIFACT is worth keeping.
|
|
74
|
+
run: |
|
|
75
|
+
set -euo pipefail
|
|
76
|
+
set +e
|
|
77
|
+
PYTHONPATH=src python3 -m bmc_sensor_audit.cli detect \
|
|
78
|
+
--config tests/fixtures/upstream/meta/bletchley \
|
|
79
|
+
--walk tests/fixtures/walk_qemu_bletchley.json \
|
|
80
|
+
--attest-out attestation.json > /dev/null
|
|
81
|
+
DETECT_STATUS=$?
|
|
82
|
+
set -e
|
|
83
|
+
echo " detect exited ${DETECT_STATUS} (1 is expected: the capture carries real findings)"
|
|
84
|
+
|
|
85
|
+
# An empty or shapeless artifact uploads exactly as well as a good one, and
|
|
86
|
+
# nobody opens it until the day they need it. So it is inspected here --
|
|
87
|
+
# by the SHIPPED validator, not by a copy of the rules living in this file.
|
|
88
|
+
# A rule that exists only inside a `run:` block cannot be called by the
|
|
89
|
+
# person who receives the artifact, which is the one person who needs it.
|
|
90
|
+
PYTHONPATH=src python3 -m bmc_sensor_audit.cli validate-attestation attestation.json
|
|
91
|
+
|
|
92
|
+
# Separate on purpose. The validator checks the FORMAT, and a genuinely
|
|
93
|
+
# clean board legitimately produces an artifact with no findings -- so
|
|
94
|
+
# `findings is non-empty` must never be a format rule or a clean run would
|
|
95
|
+
# fail validation. What follows is an expectation about THIS FIXTURE: the
|
|
96
|
+
# vendored capture has always carried real findings and real declines, and
|
|
97
|
+
# if it stops, the pipeline broke rather than the board healed.
|
|
98
|
+
python3 - <<'FIXTURE'
|
|
99
|
+
import json, sys
|
|
100
|
+
a = json.load(open("attestation.json"))
|
|
101
|
+
if not a["findings"]:
|
|
102
|
+
sys.exit("the vendored capture produced no findings; it always has, so "
|
|
103
|
+
"the pipeline stopped feeding the engine")
|
|
104
|
+
if not a["not_checked"]:
|
|
105
|
+
sys.exit("the vendored capture produced no declines; every run so far "
|
|
106
|
+
"has warmed up on a single walk")
|
|
107
|
+
print(f" fixture expectations hold: {len(a['findings'])} finding(s), "
|
|
108
|
+
f"{len(a['not_checked'])} declined")
|
|
109
|
+
FIXTURE
|
|
110
|
+
|
|
111
|
+
- name: Keep it, so a later run has something to compare against
|
|
112
|
+
uses: actions/upload-artifact@v4
|
|
113
|
+
with:
|
|
114
|
+
name: attestation-${{ github.run_number }}
|
|
115
|
+
path: attestation.json
|
|
116
|
+
# The schedule is daily, so this is roughly three months of drift history.
|
|
117
|
+
retention-days: 90
|
|
118
|
+
|
|
119
|
+
- name: Stage 1 stays dependency-free
|
|
120
|
+
# The engine is an optional extra. If Stage 1 ever acquires a hard dependency
|
|
121
|
+
# on it, that is a design regression and it should fail here rather than be
|
|
122
|
+
# discovered by someone on a bring-up bench with nothing provisioned.
|
|
123
|
+
#
|
|
124
|
+
# This step asserts an EXIT CODE, and the reason is worth writing down. With
|
|
125
|
+
# the engine uninstalled the bridge module skips at import, so pytest collects
|
|
126
|
+
# nothing and exits 5 -- EXIT_NOTESTSCOLLECTED. The previous version piped that
|
|
127
|
+
# into `tail` under `set -o pipefail`, which failed the step on every run this
|
|
128
|
+
# workflow has ever had, while the property it checks was true the whole time.
|
|
129
|
+
# A red for the mechanism rather than the outcome, and one that ran for weeks
|
|
130
|
+
# because nobody reads a job that has never been green.
|
|
131
|
+
#
|
|
132
|
+
# 5 is REQUIRED, not merely tolerated. Exit 0 would mean the bridge tests ran,
|
|
133
|
+
# which would mean the uninstall did not take and this step proved nothing.
|
|
134
|
+
# That vacuous pass is the failure mode worth guarding against here, because
|
|
135
|
+
# it is indistinguishable from success at a glance.
|
|
136
|
+
run: |
|
|
137
|
+
set -euo pipefail
|
|
138
|
+
python3 -m pip uninstall --quiet --yes arbiter-engine
|
|
139
|
+
PYTHONPATH=src python3 -c "import bmc_sensor_audit.cli; print(' Stage 1 imports with no engine present')"
|
|
140
|
+
|
|
141
|
+
set +e
|
|
142
|
+
OUTPUT=$(PYTHONPATH=src python3 -m pytest tests/test_engine_bridge.py -q -rs 2>&1)
|
|
143
|
+
STATUS=$?
|
|
144
|
+
set -e
|
|
145
|
+
echo "$OUTPUT" | tail -3
|
|
146
|
+
|
|
147
|
+
if [ "$STATUS" -ne 5 ]; then
|
|
148
|
+
echo "expected pytest to collect nothing (exit 5, engine absent); got $STATUS" >&2
|
|
149
|
+
exit 1
|
|
150
|
+
fi
|
|
151
|
+
echo "$OUTPUT" | grep -q "arbiter-engine is the optional" || {
|
|
152
|
+
echo "the bridge module skipped for a reason other than the missing engine" >&2
|
|
153
|
+
exit 1
|
|
154
|
+
}
|
|
155
|
+
echo " bridge tests skipped for the right reason, and nothing ran"
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
# The gate that does not depend on anybody's local configuration.
|
|
2
|
+
#
|
|
3
|
+
# The pre-commit hook cannot enable itself. Git refuses to let a repository set
|
|
4
|
+
# its own `core.hooksPath`, and that refusal is correct -- a repo that could
|
|
5
|
+
# would be arbitrary code execution on clone. So the hook is opt-in per clone by
|
|
6
|
+
# design, and it was unset in the authoring clone for the first three commits:
|
|
7
|
+
# the only check this repository had never ran on any of them.
|
|
8
|
+
#
|
|
9
|
+
# This workflow is the answer to that. It runs on the server, on every push and
|
|
10
|
+
# every pull request, and no local setting can switch it off.
|
|
11
|
+
#
|
|
12
|
+
# Deliberately NOT schedule-only. A sibling project has a scheduled workflow that
|
|
13
|
+
# accumulated zero runs across fifteen pushes -- a gate nobody disabled and
|
|
14
|
+
# nobody exercised. On push is what makes a check real.
|
|
15
|
+
|
|
16
|
+
name: checks
|
|
17
|
+
|
|
18
|
+
on:
|
|
19
|
+
push:
|
|
20
|
+
pull_request:
|
|
21
|
+
workflow_dispatch:
|
|
22
|
+
|
|
23
|
+
jobs:
|
|
24
|
+
checks:
|
|
25
|
+
runs-on: ubuntu-latest
|
|
26
|
+
steps:
|
|
27
|
+
- uses: actions/checkout@v4
|
|
28
|
+
with:
|
|
29
|
+
# Full history: the commit-message step needs the commits in this push,
|
|
30
|
+
# and the default depth of 1 would leave it checking only the tip.
|
|
31
|
+
fetch-depth: 0
|
|
32
|
+
|
|
33
|
+
- uses: actions/setup-python@v5
|
|
34
|
+
with:
|
|
35
|
+
python-version: "3.11"
|
|
36
|
+
|
|
37
|
+
- name: Hygiene sweep
|
|
38
|
+
# The same check the hook runs, over the whole tree rather than the
|
|
39
|
+
# staged diff. This is the backstop for every commit made from a clone
|
|
40
|
+
# where the hook was never enabled.
|
|
41
|
+
run: python3 tools/hygiene_check.py --all
|
|
42
|
+
|
|
43
|
+
- name: Commit messages
|
|
44
|
+
# The second hook's backstop. `commit-msg` can be forgotten exactly as
|
|
45
|
+
# `pre-commit` was -- both hang off one `core.hooksPath` that was unset
|
|
46
|
+
# here for three commits -- and a message is the one surface no later
|
|
47
|
+
# commit can correct. So the same check runs on the server.
|
|
48
|
+
run: |
|
|
49
|
+
set -euo pipefail
|
|
50
|
+
BASE="${{ github.event.pull_request.base.sha || github.event.before || '' }}"
|
|
51
|
+
if [ -z "$BASE" ] || [ "$BASE" = "0000000000000000000000000000000000000000" ] \
|
|
52
|
+
|| ! git cat-file -e "$BASE^{commit}" 2>/dev/null; then
|
|
53
|
+
echo "no usable base ref; checking HEAD only"
|
|
54
|
+
LIST=$(git rev-parse HEAD)
|
|
55
|
+
else
|
|
56
|
+
LIST=$(git rev-list "$BASE..${{ github.sha }}")
|
|
57
|
+
echo "checking $(echo "$LIST" | grep -c . ) commit message(s) in $BASE..${{ github.sha }}"
|
|
58
|
+
fi
|
|
59
|
+
FAILED=0
|
|
60
|
+
for SHA in $LIST; do
|
|
61
|
+
git log -1 --format=%B "$SHA" > /tmp/msg.txt
|
|
62
|
+
echo "--- $SHA"
|
|
63
|
+
python3 tools/commit_msg_check.py /tmp/msg.txt || FAILED=1
|
|
64
|
+
done
|
|
65
|
+
exit $FAILED
|
|
66
|
+
|
|
67
|
+
- name: Tests
|
|
68
|
+
# The PACKAGE is not pip-installed first, deliberately. One test asserts that
|
|
69
|
+
# a bare `python3 -m bmc_sensor_audit.cli` FAILS without PYTHONPATH, and an
|
|
70
|
+
# install would put the package on the path and make that test pass for the
|
|
71
|
+
# wrong reason -- turning a real negative into a vacuous one.
|
|
72
|
+
#
|
|
73
|
+
# PyYAML is a different question, and it is installed. The generated-output
|
|
74
|
+
# hygiene pair scans the SERIALISED model, so it needs the serialiser, and on
|
|
75
|
+
# a bare interpreter those two tests failed here from the commit that added
|
|
76
|
+
# them. Gating them and stopping there would have turned that red into a
|
|
77
|
+
# skip -- a leak check that no longer runs anywhere it matters. Installing
|
|
78
|
+
# PyYAML does not put bmc_sensor_audit on the path, so the negative above is
|
|
79
|
+
# untouched.
|
|
80
|
+
#
|
|
81
|
+
# pytest is installed for the same reason, and its absence was worse than a
|
|
82
|
+
# missing dependency: `setup-python` provides a clean interpreter that does
|
|
83
|
+
# not carry pytest, so `python3 -m pytest` exited with `No module named
|
|
84
|
+
# pytest` on every push since this workflow was written. The step failed
|
|
85
|
+
# instantly, no test ever ran on the server, and the two steps below it --
|
|
86
|
+
# the ones that run what the README prints -- never executed at all. A
|
|
87
|
+
# workflow can be red for its whole life without ever having run the thing
|
|
88
|
+
# it exists to run.
|
|
89
|
+
run: |
|
|
90
|
+
set -euo pipefail
|
|
91
|
+
python3 -m pip install --quiet pyyaml pytest
|
|
92
|
+
python3 -m pytest tests/ -q
|
|
93
|
+
|
|
94
|
+
- name: The documented console-script path, from a clean checkout
|
|
95
|
+
# The second path the README prints, which the suite can only check
|
|
96
|
+
# structurally because it must not run pip. Here a clean runner is free,
|
|
97
|
+
# so run it for real: install, leave the checkout, and execute the
|
|
98
|
+
# command a reader would type.
|
|
99
|
+
run: |
|
|
100
|
+
set -euo pipefail
|
|
101
|
+
python3 -m pip install --quiet -e .
|
|
102
|
+
mkdir -p "$RUNNER_TEMP/board" && cd "$RUNNER_TEMP/board"
|
|
103
|
+
printf '%s' '{"Name":"Board","Exposes":[{"Name":"Inlet Temp","Type":"TMP75"}]}' > board.json
|
|
104
|
+
bmc-sensor-audit declare --config board.json | tee out.txt
|
|
105
|
+
grep -q "sensors declared" out.txt
|
|
106
|
+
|
|
107
|
+
- name: A real wheel, from a directory with no checkout
|
|
108
|
+
# The editable install above cannot see this class of defect. `pip install
|
|
109
|
+
# -e .` resolves the package back to the source tree, so a data file the
|
|
110
|
+
# wheel fails to include still loads from the checkout and every check
|
|
111
|
+
# passes -- while the artifact a user actually installs raises on first
|
|
112
|
+
# use. The package now ships `redfish_properties.json`, which is the
|
|
113
|
+
# standard property set `--strict-fields` judges against and the first
|
|
114
|
+
# non-Python file this project depends on at runtime.
|
|
115
|
+
#
|
|
116
|
+
# So: build the wheel, install THAT into a fresh environment, and run the
|
|
117
|
+
# command that needs the data file, from outside the tree.
|
|
118
|
+
run: |
|
|
119
|
+
set -euo pipefail
|
|
120
|
+
python3 -m pip install --quiet build
|
|
121
|
+
python3 -m build --wheel --outdir "$RUNNER_TEMP/dist"
|
|
122
|
+
python3 -m venv "$RUNNER_TEMP/consumer"
|
|
123
|
+
"$RUNNER_TEMP/consumer/bin/pip" install --quiet "$RUNNER_TEMP"/dist/*.whl
|
|
124
|
+
mkdir -p "$RUNNER_TEMP/wheeltest" && cd "$RUNNER_TEMP/wheeltest"
|
|
125
|
+
printf '%s' '{"format":"bmc-sensor-audit/walk/1","fields_observed":true,"sensors":[{"name":"Inlet","path":"/redfish/v1/Chassis/1/Sensors/s0","reading":21.0,"units":"Cel","resource":"Sensor","undeclared":["ThermalZone"],"thresholds":{}}]}' > walk.json
|
|
126
|
+
printf '%s' '{"Name":"Board","Exposes":[{"Name":"Inlet","Type":"TMP75"}]}' > board.json
|
|
127
|
+
"$RUNNER_TEMP/consumer/bin/bmc-sensor-audit" coverage --config board.json --walk walk.json --strict-fields | tee out.txt
|
|
128
|
+
grep -q "ThermalZone" out.txt
|
|
129
|
+
|
|
130
|
+
- name: The documented module path, from a directory with no checkout
|
|
131
|
+
# The first path the README prints. Run from outside the tree so a stray
|
|
132
|
+
# working-directory import cannot make it pass by accident.
|
|
133
|
+
run: |
|
|
134
|
+
set -euo pipefail
|
|
135
|
+
mkdir -p "$RUNNER_TEMP/mod" && cd "$RUNNER_TEMP/mod"
|
|
136
|
+
printf '%s' '{"Name":"Board","Exposes":[{"Name":"Inlet Temp","Type":"TMP75"}]}' > board.json
|
|
137
|
+
PYTHONPATH="$GITHUB_WORKSPACE/src" python3 -m bmc_sensor_audit.cli declare --config board.json
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
__pycache__/
|
|
2
|
+
*.py[cod]
|
|
3
|
+
.pytest_cache/
|
|
4
|
+
build/
|
|
5
|
+
dist/
|
|
6
|
+
*.egg-info/
|
|
7
|
+
.venv/
|
|
8
|
+
venv/
|
|
9
|
+
|
|
10
|
+
# Recorded walks captured from real hardware. A Redfish walk carries serial
|
|
11
|
+
# numbers, asset tags, MAC addresses and BMC hostnames, so a capture is a fleet
|
|
12
|
+
# inventory disclosure. Redact and rename deliberately before committing one;
|
|
13
|
+
# nothing named `*.capture.json` should ever reach a remote by accident.
|
|
14
|
+
*.capture.json
|
|
15
|
+
|
|
16
|
+
# Site-specific hygiene vocabulary. Untracked on purpose: a rule that forbids a
|
|
17
|
+
# private name must spell that name out, so committing it here would publish
|
|
18
|
+
# exactly what it exists to protect. See tools/hygiene_check.py.
|
|
19
|
+
.hygiene-local.json
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
cff-version: 1.2.0
|
|
2
|
+
message: "If you reference bmc-sensor-audit in academic or technical work, please cite as follows."
|
|
3
|
+
title: "bmc-sensor-audit: find the sensors that should be reporting and are not"
|
|
4
|
+
abstract: "Diffs an OpenBMC entity-manager declaration against what a BMC actually
|
|
5
|
+
reports over Redfish, so sensors that went absent, were disabled at the factory,
|
|
6
|
+
or froze at a plausible value are named instead of silently missing. Presence is
|
|
7
|
+
three-valued — reading, disabled, absent — and liveness is judged over
|
|
8
|
+
observation history by the arbiter-engine invariant checker. A run that cannot
|
|
9
|
+
be completed exits distinctly from one that found regressions, so 'could not
|
|
10
|
+
verify' never reads as 'clean'. Parser behaviour is derived by measurement of
|
|
11
|
+
the full upstream configuration corpus, an approach that has so far surfaced
|
|
12
|
+
two defects in the upstream declarations themselves. Released under Apache 2.0."
|
|
13
|
+
type: software
|
|
14
|
+
authors:
|
|
15
|
+
- family-names: "Sheen"
|
|
16
|
+
given-names: "James"
|
|
17
|
+
alias: "james-sheen"
|
|
18
|
+
# Quoted deliberately. CFF 1.2.0 types both of these as strings, and an unquoted
|
|
19
|
+
# date deserialises to a date object in most YAML parsers -- which validates
|
|
20
|
+
# locally and fails the schema.
|
|
21
|
+
version: "0.1.0"
|
|
22
|
+
date-released: "2026-08-21"
|
|
23
|
+
license: Apache-2.0
|
|
24
|
+
repository-code: "https://github.com/james-sheen/bmc-sensor-audit"
|
|
25
|
+
url: "https://github.com/james-sheen/bmc-sensor-audit"
|
|
26
|
+
keywords:
|
|
27
|
+
- openbmc
|
|
28
|
+
- redfish
|
|
29
|
+
- bmc
|
|
30
|
+
- entity-manager
|
|
31
|
+
- sensors
|
|
32
|
+
- hardware-monitoring
|
|
33
|
+
- liveness
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# Code of Conduct
|
|
2
|
+
|
|
3
|
+
bmc-sensor-audit follows the
|
|
4
|
+
[Contributor Covenant Code of Conduct v2.1](https://www.contributor-covenant.org/version/2/1/code_of_conduct/).
|
|
5
|
+
|
|
6
|
+
All participants in this project's issue tracker, discussions, and any
|
|
7
|
+
community forum are expected to abide by these standards. Violations may be
|
|
8
|
+
reported to the maintainers via the private channel described in
|
|
9
|
+
[SECURITY.md](SECURITY.md) — GitHub Private Vulnerability Reporting routes
|
|
10
|
+
confidentially to maintainers and is the appropriate intake for conduct
|
|
11
|
+
reports as well as security disclosures.
|
|
12
|
+
|
|
13
|
+
## Summary
|
|
14
|
+
|
|
15
|
+
- Be kind. Assume good faith.
|
|
16
|
+
- Critique ideas, not people.
|
|
17
|
+
- Adversarial-finding reports describe a gate that passed over something it did
|
|
18
|
+
not audit; they do not describe testers as adversaries.
|
|
19
|
+
- Harassment, abuse, and deliberate dissemination of harmful content are out of
|
|
20
|
+
scope.
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# Contributing
|
|
2
|
+
|
|
3
|
+
The most useful thing you can send is a machine or a configuration where the
|
|
4
|
+
gate said clean over something it did not audit. That is the failure this tool
|
|
5
|
+
exists to prevent, and it is the report this project wants most. The
|
|
6
|
+
second-most useful is a real entity-manager declaration the reader mis-parses
|
|
7
|
+
silently — the upstream corpus has already taught this parser five lessons the
|
|
8
|
+
format documentation does not mention, and there is no reason to believe it is
|
|
9
|
+
done teaching.
|
|
10
|
+
|
|
11
|
+
## What is open right now
|
|
12
|
+
|
|
13
|
+
**Issues: open.** Bug reports, adversarial findings, and questions about
|
|
14
|
+
running the tool against your hardware are all welcome. A report that can be
|
|
15
|
+
acted on here carries four things: the configuration (or the name of the
|
|
16
|
+
vendored one that shows it), the walk or capture it ran against, the full
|
|
17
|
+
report text with the exit code, and — if the `[detect]` extra is involved —
|
|
18
|
+
the installed `arbiter-engine` version, because presence and correctness are
|
|
19
|
+
different checks and a stale engine fails in code-shaped ways.
|
|
20
|
+
|
|
21
|
+
**Real-hardware captures: actively wanted.** Criterion 2's last line closes
|
|
22
|
+
with a capture from physical hardware, and `capture` writes only the parsed
|
|
23
|
+
sensor set by design — serials, asset tags and MAC addresses never reach the
|
|
24
|
+
file. Run one command, attach the output, and say what machine it came from.
|
|
25
|
+
|
|
26
|
+
**Documentation pull requests: open.** If the README describes behaviour the
|
|
27
|
+
package does not have, a PR is the fastest route and it will be merged. Note
|
|
28
|
+
that the README's commands and its test count are themselves under test, so a
|
|
29
|
+
documentation change may fail the suite until the matching assertion moves —
|
|
30
|
+
that is the suite doing its job.
|
|
31
|
+
|
|
32
|
+
**Code pull requests: not being merged yet, and the reason is capacity.**
|
|
33
|
+
This mirrors the sibling engine's policy for the same one-maintainer reason,
|
|
34
|
+
and what ends it is the same: a second reviewer, not a version number.
|
|
35
|
+
|
|
36
|
+
## A report does not have to be complete to be worth sending
|
|
37
|
+
|
|
38
|
+
The best finding this repository has received so far was a lead rather than a
|
|
39
|
+
proof, and the half of it that was left open turned out to be the half that
|
|
40
|
+
mattered: a note that the parser never consulted an entry's `Labels` array, and
|
|
41
|
+
that a sensor declared through `Labels` alone would therefore be invisible. Both
|
|
42
|
+
halves were correct. The first was fixed and the second was recorded as an open
|
|
43
|
+
question — and measuring it later showed it had been hiding most of the rails in
|
|
44
|
+
the vendored corpus, every one of them a sensor whose absence could never have
|
|
45
|
+
been reported.
|
|
46
|
+
|
|
47
|
+
So: if you can see that something is wrong but not how far it goes, send it
|
|
48
|
+
anyway and say where you stopped looking. *I did not audit X* is the most useful
|
|
49
|
+
sentence in a report, and it will be read as a lead rather than as a disclaimer.
|
|
50
|
+
|
|
51
|
+
## Running the suite
|
|
52
|
+
|
|
53
|
+
Enable the hook first — the suite fails without it, deliberately:
|
|
54
|
+
|
|
55
|
+
git config core.hooksPath .githooks
|
|
56
|
+
|
|
57
|
+
The suite has two honest populations: without the `[detect]` extra (Stage 1,
|
|
58
|
+
dependency-free) and with it. Both are green at head; the exact counts live in
|
|
59
|
+
the README's Tests row, which is enforced by a test rather than promised by a
|
|
60
|
+
sentence.
|