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.
Files changed (83) hide show
  1. bmc_sensor_audit-0.1.0/.githooks/commit-msg +13 -0
  2. bmc_sensor_audit-0.1.0/.githooks/pre-commit +41 -0
  3. bmc_sensor_audit-0.1.0/.github/ISSUE_TEMPLATE/bug_report.md +27 -0
  4. bmc_sensor_audit-0.1.0/.github/workflows/canary.yml +155 -0
  5. bmc_sensor_audit-0.1.0/.github/workflows/checks.yml +137 -0
  6. bmc_sensor_audit-0.1.0/.gitignore +19 -0
  7. bmc_sensor_audit-0.1.0/CITATION.cff +33 -0
  8. bmc_sensor_audit-0.1.0/CODE_OF_CONDUCT.md +20 -0
  9. bmc_sensor_audit-0.1.0/CONTRIBUTING.md +60 -0
  10. bmc_sensor_audit-0.1.0/LICENSE +202 -0
  11. bmc_sensor_audit-0.1.0/NOTICE +46 -0
  12. bmc_sensor_audit-0.1.0/PKG-INFO +545 -0
  13. bmc_sensor_audit-0.1.0/README.md +526 -0
  14. bmc_sensor_audit-0.1.0/SECURITY.md +71 -0
  15. bmc_sensor_audit-0.1.0/docs/attestation-format.md +114 -0
  16. bmc_sensor_audit-0.1.0/docs/burn-in.md +118 -0
  17. bmc_sensor_audit-0.1.0/docs/stage2/s1-threshold-granularity.md +79 -0
  18. bmc_sensor_audit-0.1.0/docs/stage2/s2-oscillation-not-a-defect.md +67 -0
  19. bmc_sensor_audit-0.1.0/docs/stage2/s3-corpus-scale.md +77 -0
  20. bmc_sensor_audit-0.1.0/examples/supplemental/TEMPLATE.json +27 -0
  21. bmc_sensor_audit-0.1.0/examples/supplemental/ampere-mtjade.json +16 -0
  22. bmc_sensor_audit-0.1.0/pyproject.toml +55 -0
  23. bmc_sensor_audit-0.1.0/src/bmc_sensor_audit/__init__.py +6 -0
  24. bmc_sensor_audit-0.1.0/src/bmc_sensor_audit/cli.py +640 -0
  25. bmc_sensor_audit-0.1.0/src/bmc_sensor_audit/detect/__init__.py +10 -0
  26. bmc_sensor_audit-0.1.0/src/bmc_sensor_audit/detect/attestation.py +231 -0
  27. bmc_sensor_audit-0.1.0/src/bmc_sensor_audit/detect/feeder.py +331 -0
  28. bmc_sensor_audit-0.1.0/src/bmc_sensor_audit/detect/generator.py +496 -0
  29. bmc_sensor_audit-0.1.0/src/bmc_sensor_audit/detect/supplemental.py +301 -0
  30. bmc_sensor_audit-0.1.0/src/bmc_sensor_audit/inventory/__init__.py +1 -0
  31. bmc_sensor_audit-0.1.0/src/bmc_sensor_audit/inventory/diff.py +380 -0
  32. bmc_sensor_audit-0.1.0/src/bmc_sensor_audit/inventory/entity_manager.py +561 -0
  33. bmc_sensor_audit-0.1.0/src/bmc_sensor_audit/inventory/redfish.py +544 -0
  34. bmc_sensor_audit-0.1.0/src/bmc_sensor_audit/inventory/redfish_properties.json +248 -0
  35. bmc_sensor_audit-0.1.0/src/bmc_sensor_audit/inventory/redfish_schema.py +110 -0
  36. bmc_sensor_audit-0.1.0/src/bmc_sensor_audit/inventory/regression.py +266 -0
  37. bmc_sensor_audit-0.1.0/src/bmc_sensor_audit/inventory/sensor_types.py +122 -0
  38. bmc_sensor_audit-0.1.0/src/bmc_sensor_audit/report.py +543 -0
  39. bmc_sensor_audit-0.1.0/src/bmc_sensor_audit/testing/__init__.py +5 -0
  40. bmc_sensor_audit-0.1.0/src/bmc_sensor_audit/testing/mock_redfish.py +204 -0
  41. bmc_sensor_audit-0.1.0/tests/fixtures/redfish_witherspoon_2_9_0.json +188 -0
  42. bmc_sensor_audit-0.1.0/tests/fixtures/stuck_at_qemu_bletchley.json +2166 -0
  43. bmc_sensor_audit-0.1.0/tests/fixtures/upstream/LICENCE +13 -0
  44. bmc_sensor_audit-0.1.0/tests/fixtures/upstream/README.md +128 -0
  45. bmc_sensor_audit-0.1.0/tests/fixtures/upstream/ampere/mtjade.json +2057 -0
  46. bmc_sensor_audit-0.1.0/tests/fixtures/upstream/asrock/spc621d8hm3.json +492 -0
  47. bmc_sensor_audit-0.1.0/tests/fixtures/upstream/delta/awf2dc3200w_psu.json +42 -0
  48. bmc_sensor_audit-0.1.0/tests/fixtures/upstream/intel/8x25_hsbp.json +20 -0
  49. bmc_sensor_audit-0.1.0/tests/fixtures/upstream/intel/axx1p100hssi_aic.json +33 -0
  50. bmc_sensor_audit-0.1.0/tests/fixtures/upstream/meta/bletchley/bletchley_baseboard.json +954 -0
  51. bmc_sensor_audit-0.1.0/tests/fixtures/upstream/meta/bletchley/bletchley_chassis.json +370 -0
  52. bmc_sensor_audit-0.1.0/tests/fixtures/upstream/meta/bletchley/bletchley_frontpanel.json +94 -0
  53. bmc_sensor_audit-0.1.0/tests/fixtures/upstream/meta/catalina/catalina_osfp.json +28 -0
  54. bmc_sensor_audit-0.1.0/tests/fixtures/upstream/meta/fbyv2.json +585 -0
  55. bmc_sensor_audit-0.1.0/tests/fixtures/upstream/meta/fbyv35.json +856 -0
  56. bmc_sensor_audit-0.1.0/tests/fixtures/upstream/meta/twinlake.json +32 -0
  57. bmc_sensor_audit-0.1.0/tests/fixtures/upstream/nvidia/cx7_mezzanine_module.json +100 -0
  58. bmc_sensor_audit-0.1.0/tests/fixtures/walk_qemu_bletchley.json +407 -0
  59. bmc_sensor_audit-0.1.0/tests/fixtures/walk_sensors_tree.json +50 -0
  60. bmc_sensor_audit-0.1.0/tests/fixtures/walk_thermal_power_tree.json +51 -0
  61. bmc_sensor_audit-0.1.0/tests/test_attestation_format.py +274 -0
  62. bmc_sensor_audit-0.1.0/tests/test_burn_in_cadence.py +94 -0
  63. bmc_sensor_audit-0.1.0/tests/test_cli_config_guards.py +302 -0
  64. bmc_sensor_audit-0.1.0/tests/test_commit_msg_check.py +145 -0
  65. bmc_sensor_audit-0.1.0/tests/test_community_files.py +219 -0
  66. bmc_sensor_audit-0.1.0/tests/test_diff.py +259 -0
  67. bmc_sensor_audit-0.1.0/tests/test_engine_bridge.py +1106 -0
  68. bmc_sensor_audit-0.1.0/tests/test_entity_manager.py +349 -0
  69. bmc_sensor_audit-0.1.0/tests/test_feeder.py +417 -0
  70. bmc_sensor_audit-0.1.0/tests/test_generator.py +472 -0
  71. bmc_sensor_audit-0.1.0/tests/test_hook_is_enabled.py +127 -0
  72. bmc_sensor_audit-0.1.0/tests/test_hygiene_check.py +208 -0
  73. bmc_sensor_audit-0.1.0/tests/test_readme_commands.py +257 -0
  74. bmc_sensor_audit-0.1.0/tests/test_redfish.py +551 -0
  75. bmc_sensor_audit-0.1.0/tests/test_regression_gate.py +452 -0
  76. bmc_sensor_audit-0.1.0/tests/test_sensor_types.py +137 -0
  77. bmc_sensor_audit-0.1.0/tests/test_strict_fields.py +419 -0
  78. bmc_sensor_audit-0.1.0/tests/test_supplemental.py +236 -0
  79. bmc_sensor_audit-0.1.0/tests/test_supplemental_examples.py +142 -0
  80. bmc_sensor_audit-0.1.0/tests/test_vendored_corpus.py +351 -0
  81. bmc_sensor_audit-0.1.0/tools/commit_msg_check.py +139 -0
  82. bmc_sensor_audit-0.1.0/tools/derive_redfish_properties.py +169 -0
  83. 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.