odm-cert-generator 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.
@@ -0,0 +1,18 @@
1
+ __pycache__/
2
+ *.py[cod]
3
+ .pytest_cache/
4
+ build/
5
+ dist/
6
+ *.egg-info/
7
+ .venv/
8
+ venv/
9
+
10
+ # Rendered certificates. Every one carries a real unit's serial number, work
11
+ # order and the name of whoever signed it -- this package exists to put them
12
+ # there. A certificate committed by accident is a disclosure of exactly the data
13
+ # the audit tool refuses to hold, published from the repository that holds it.
14
+ # Nothing here is written by the suite; the tests render into a temporary
15
+ # directory. Anything landing here was produced deliberately.
16
+ certificates/
17
+ *.certificate.json
18
+ *.certificate.pdf
@@ -0,0 +1,202 @@
1
+
2
+ Apache License
3
+ Version 2.0, January 2004
4
+ http://www.apache.org/licenses/
5
+
6
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
7
+
8
+ 1. Definitions.
9
+
10
+ "License" shall mean the terms and conditions for use, reproduction,
11
+ and distribution as defined by Sections 1 through 9 of this document.
12
+
13
+ "Licensor" shall mean the copyright owner or entity authorized by
14
+ the copyright owner that is granting the License.
15
+
16
+ "Legal Entity" shall mean the union of the acting entity and all
17
+ other entities that control, are controlled by, or are under common
18
+ control with that entity. For the purposes of this definition,
19
+ "control" means (i) the power, direct or indirect, to cause the
20
+ direction or management of such entity, whether by contract or
21
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
22
+ outstanding shares, or (iii) beneficial ownership of such entity.
23
+
24
+ "You" (or "Your") shall mean an individual or Legal Entity
25
+ exercising permissions granted by this License.
26
+
27
+ "Source" form shall mean the preferred form for making modifications,
28
+ including but not limited to software source code, documentation
29
+ source, and configuration files.
30
+
31
+ "Object" form shall mean any form resulting from mechanical
32
+ transformation or translation of a Source form, including but
33
+ not limited to compiled object code, generated documentation,
34
+ and conversions to other media types.
35
+
36
+ "Work" shall mean the work of authorship, whether in Source or
37
+ Object form, made available under the License, as indicated by a
38
+ copyright notice that is included in or attached to the work
39
+ (an example is provided in the Appendix below).
40
+
41
+ "Derivative Works" shall mean any work, whether in Source or Object
42
+ form, that is based on (or derived from) the Work and for which the
43
+ editorial revisions, annotations, elaborations, or other modifications
44
+ represent, as a whole, an original work of authorship. For the purposes
45
+ of this License, Derivative Works shall not include works that remain
46
+ separable from, or merely link (or bind by name) to the interfaces of,
47
+ the Work and Derivative Works thereof.
48
+
49
+ "Contribution" shall mean any work of authorship, including
50
+ the original version of the Work and any modifications or additions
51
+ to that Work or Derivative Works thereof, that is intentionally
52
+ submitted to Licensor for inclusion in the Work by the copyright owner
53
+ or by an individual or Legal Entity authorized to submit on behalf of
54
+ the copyright owner. For the purposes of this definition, "submitted"
55
+ means any form of electronic, verbal, or written communication sent
56
+ to the Licensor or its representatives, including but not limited to
57
+ communication on electronic mailing lists, source code control systems,
58
+ and issue tracking systems that are managed by, or on behalf of, the
59
+ Licensor for the purpose of discussing and improving the Work, but
60
+ excluding communication that is conspicuously marked or otherwise
61
+ designated in writing by the copyright owner as "Not a Contribution."
62
+
63
+ "Contributor" shall mean Licensor and any individual or Legal Entity
64
+ on behalf of whom a Contribution has been received by Licensor and
65
+ subsequently incorporated within the Work.
66
+
67
+ 2. Grant of Copyright License. Subject to the terms and conditions of
68
+ this License, each Contributor hereby grants to You a perpetual,
69
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
70
+ copyright license to reproduce, prepare Derivative Works of,
71
+ publicly display, publicly perform, sublicense, and distribute the
72
+ Work and such Derivative Works in Source or Object form.
73
+
74
+ 3. Grant of Patent License. Subject to the terms and conditions of
75
+ this License, each Contributor hereby grants to You a perpetual,
76
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
77
+ (except as stated in this section) patent license to make, have made,
78
+ use, offer to sell, sell, import, and otherwise transfer the Work,
79
+ where such license applies only to those patent claims licensable
80
+ by such Contributor that are necessarily infringed by their
81
+ Contribution(s) alone or by combination of their Contribution(s)
82
+ with the Work to which such Contribution(s) was submitted. If You
83
+ institute patent litigation against any entity (including a
84
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
85
+ or a Contribution incorporated within the Work constitutes direct
86
+ or contributory patent infringement, then any patent licenses
87
+ granted to You under this License for that Work shall terminate
88
+ as of the date such litigation is filed.
89
+
90
+ 4. Redistribution. You may reproduce and distribute copies of the
91
+ Work or Derivative Works thereof in any medium, with or without
92
+ modifications, and in Source or Object form, provided that You
93
+ meet the following conditions:
94
+
95
+ (a) You must give any other recipients of the Work or
96
+ Derivative Works a copy of this License; and
97
+
98
+ (b) You must cause any modified files to carry prominent notices
99
+ stating that You changed the files; and
100
+
101
+ (c) You must retain, in the Source form of any Derivative Works
102
+ that You distribute, all copyright, patent, trademark, and
103
+ attribution notices from the Source form of the Work,
104
+ excluding those notices that do not pertain to any part of
105
+ the Derivative Works; and
106
+
107
+ (d) If the Work includes a "NOTICE" text file as part of its
108
+ distribution, then any Derivative Works that You distribute must
109
+ include a readable copy of the attribution notices contained
110
+ within such NOTICE file, excluding those notices that do not
111
+ pertain to any part of the Derivative Works, in at least one
112
+ of the following places: within a NOTICE text file distributed
113
+ as part of the Derivative Works; within the Source form or
114
+ documentation, if provided along with the Derivative Works; or,
115
+ within a display generated by the Derivative Works, if and
116
+ wherever such third-party notices normally appear. The contents
117
+ of the NOTICE file are for informational purposes only and
118
+ do not modify the License. You may add Your own attribution
119
+ notices within Derivative Works that You distribute, alongside
120
+ or as an addendum to the NOTICE text from the Work, provided
121
+ that such additional attribution notices cannot be construed
122
+ as modifying the License.
123
+
124
+ You may add Your own copyright statement to Your modifications and
125
+ may provide additional or different license terms and conditions
126
+ for use, reproduction, or distribution of Your modifications, or
127
+ for any such Derivative Works as a whole, provided Your use,
128
+ reproduction, and distribution of the Work otherwise complies with
129
+ the conditions stated in this License.
130
+
131
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
132
+ any Contribution intentionally submitted for inclusion in the Work
133
+ by You to the Licensor shall be under the terms and conditions of
134
+ this License, without any additional terms or conditions.
135
+ Notwithstanding the above, nothing herein shall supersede or modify
136
+ the terms of any separate license agreement you may have executed
137
+ with Licensor regarding such Contributions.
138
+
139
+ 6. Trademarks. This License does not grant permission to use the trade
140
+ names, trademarks, service marks, or product names of the Licensor,
141
+ except as required for reasonable and customary use in describing the
142
+ origin of the Work and reproducing the content of the NOTICE file.
143
+
144
+ 7. Disclaimer of Warranty. Unless required by applicable law or
145
+ agreed to in writing, Licensor provides the Work (and each
146
+ Contributor provides its Contributions) on an "AS IS" BASIS,
147
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
148
+ implied, including, without limitation, any warranties or conditions
149
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
150
+ PARTICULAR PURPOSE. You are solely responsible for determining the
151
+ appropriateness of using or redistributing the Work and assume any
152
+ risks associated with Your exercise of permissions under this License.
153
+
154
+ 8. Limitation of Liability. In no event and under no legal theory,
155
+ whether in tort (including negligence), contract, or otherwise,
156
+ unless required by applicable law (such as deliberate and grossly
157
+ negligent acts) or agreed to in writing, shall any Contributor be
158
+ liable to You for damages, including any direct, indirect, special,
159
+ incidental, or consequential damages of any character arising as a
160
+ result of this License or out of the use or inability to use the
161
+ Work (including but not limited to damages for loss of goodwill,
162
+ work stoppage, computer failure or malfunction, or any and all
163
+ other commercial damages or losses), even if such Contributor
164
+ has been advised of the possibility of such damages.
165
+
166
+ 9. Accepting Warranty or Additional Liability. While redistributing
167
+ the Work or Derivative Works thereof, You may choose to offer,
168
+ and charge a fee for, acceptance of support, warranty, indemnity,
169
+ or other liability obligations and/or rights consistent with this
170
+ License. However, in accepting such obligations, You may act only
171
+ on Your own behalf and on Your sole responsibility, not on behalf
172
+ of any other Contributor, and only if You agree to indemnify,
173
+ defend, and hold each Contributor harmless for any liability
174
+ incurred by, or claims asserted against, such Contributor by reason
175
+ of your accepting any such warranty or additional liability.
176
+
177
+ END OF TERMS AND CONDITIONS
178
+
179
+ APPENDIX: How to apply the Apache License to your work.
180
+
181
+ To apply the Apache License to your work, attach the following
182
+ boilerplate notice, with the fields enclosed by brackets "[]"
183
+ replaced with your own identifying information. (Don't include
184
+ the brackets!) The text should be enclosed in the appropriate
185
+ comment syntax for the file format. We also recommend that a
186
+ file or class name and description of purpose be included on the
187
+ same "printed page" as the copyright notice for easier
188
+ identification within third-party archives.
189
+
190
+ Copyright [yyyy] [name of copyright owner]
191
+
192
+ Licensed under the Apache License, Version 2.0 (the "License");
193
+ you may not use this file except in compliance with the License.
194
+ You may obtain a copy of the License at
195
+
196
+ http://www.apache.org/licenses/LICENSE-2.0
197
+
198
+ Unless required by applicable law or agreed to in writing, software
199
+ distributed under the License is distributed on an "AS IS" BASIS,
200
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
201
+ See the License for the specific language governing permissions and
202
+ limitations under the License.
@@ -0,0 +1,39 @@
1
+ odm-cert-generator
2
+ Copyright 2026 James Sheen
3
+
4
+ Licensed under the Apache License, Version 2.0 (the "License"); you may not use
5
+ this file except in compliance with the License. You may obtain a copy of the
6
+ License at
7
+
8
+ http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+ Unless required by applicable law or agreed to in writing, software distributed
11
+ under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
12
+ CONDITIONS OF ANY KIND, either express or implied.
13
+
14
+ DEPENDENCIES
15
+
16
+ This project consumes two packages and is a part of neither:
17
+
18
+ bmc-sensor-audit Apache-2.0 -- its shipped attestation validator is the
19
+ only surface of it this package touches
20
+ fpdf2 LGPL-3.0-only -- read from the installed distribution's
21
+ own License-Expression, not assumed. Used
22
+ as an unmodified library through its public
23
+ API, imported at render time and neither
24
+ vendored nor altered.
25
+
26
+ REDISTRIBUTED THIRD-PARTY CONTENT
27
+
28
+ None. `examples/attestation.json` and `examples/coverage.json` were produced by
29
+ running bmc-sensor-audit 0.1.0 against its own mock BMC in this repository; they
30
+ are outputs of a dependency, not copies of anyone's source. `examples/README.md`
31
+ records exactly how, and which two volatile fields were replaced.
32
+
33
+ NOT AFFILIATED
34
+
35
+ This project renders documents describing hardware audited against OpenBMC
36
+ `entity-manager` declarations and the DMTF Redfish API. It is not affiliated
37
+ with, endorsed by, or part of the OpenBMC project or DMTF. A certificate it
38
+ produces is a record of what one tool checked; it is not a certification issued
39
+ by any standards body.
@@ -0,0 +1,216 @@
1
+ Metadata-Version: 2.5
2
+ Name: odm-cert-generator
3
+ Version: 0.1.0
4
+ Summary: Render a QC certificate from a bmc-sensor-audit attestation.
5
+ Project-URL: Homepage, https://github.com/james-sheen/cert-generator
6
+ Project-URL: Source, https://github.com/james-sheen/cert-generator
7
+ Project-URL: Issues, https://github.com/james-sheen/cert-generator/issues
8
+ Author: James Sheen
9
+ License-Expression: Apache-2.0
10
+ License-File: LICENSE
11
+ License-File: NOTICE
12
+ Keywords: attestation,bmc,certificate,manufacturing,odm,openbmc,qa,quality-control
13
+ Requires-Python: >=3.10
14
+ Requires-Dist: bmc-sensor-audit<0.2,>=0.1.0
15
+ Requires-Dist: fpdf2<3,>=2.7
16
+ Provides-Extra: dev
17
+ Requires-Dist: pytest>=7; extra == 'dev'
18
+ Provides-Extra: verify
19
+ Requires-Dist: pypdf>=4; extra == 'verify'
20
+ Description-Content-Type: text/markdown
21
+
22
+ # cert-generator
23
+
24
+ Render a QC certificate from a [`bmc-sensor-audit`](https://github.com/james-sheen/bmc-sensor-audit)
25
+ attestation.
26
+
27
+ **Released — 0.1.0**, tagged `v0.1.0`, Apache-2.0, on PyPI as
28
+ `odm-cert-generator`.
29
+
30
+ **The distribution and the command differ, and the reason is worth one line.**
31
+ PyPI ultranormalises a name by stripping its separators, so `cert-generator`
32
+ becomes `certgenerator` — which an unrelated project holds — and the upload is
33
+ refused as *too similar to an existing project*. So this publishes as
34
+ `odm-cert-generator` and installs a command still called `cert-generator`. A
35
+ certificate records the **distribution** name, because that is the one that
36
+ resolves on an index.
37
+
38
+ ```
39
+ pip install odm-cert-generator
40
+
41
+ cert-generator render \
42
+ --attestation attestation.json \
43
+ --identity identity.json \
44
+ --coverage coverage.json \
45
+ --out-json certificate.json \
46
+ --out-pdf certificate.pdf
47
+ ```
48
+
49
+ ## What it is for
50
+
51
+ The audit tool judges a machine and deliberately keeps the machine's identity out
52
+ of everything it writes. A certificate has the opposite job: it has to name the
53
+ unit. This renders one from the other, and holds the serial number on its own side
54
+ of the line.
55
+
56
+ Two trust domains, split on purpose. The referee cannot leak what it never sees.
57
+ The certificate can say what a certificate must.
58
+
59
+ ## The certificate never claims a flat pass
60
+
61
+ It will not print "100 % match". It prints the denominator:
62
+
63
+ ```
64
+ 6 invariant(s) checked over 3 entit(ies); 1 finding(s) recorded;
65
+ 3 check(s) declined and therefore not judged
66
+ ```
67
+
68
+ ...and then a section headed **Not part of this judgment**, which is emitted even
69
+ when every list in it is empty — because an absent section reads as *nothing was
70
+ left out*, and that is a claim.
71
+
72
+ That section carries:
73
+
74
+ | | |
75
+ |---|---|
76
+ | the declines | each with the engine's machine-readable reason |
77
+ | `unattested` | problem types the engine would not attest |
78
+ | `unread_feeds` | data that was read and not used |
79
+ | the declaration diff | declared vs. present, when supplied — see below |
80
+ | the engine's boundary | quoted verbatim, not paraphrased |
81
+
82
+ A certificate showing what was *not* checked is one an incoming-inspection team
83
+ can act on. The denominator is the point, not a concession.
84
+
85
+ ## The declaration diff is a second, optional input
86
+
87
+ `attestation/1` records what the engine judged. It does not record what was
88
+ *declared and never showed up*. Run the audit tool against a board declaring four
89
+ sensors where one is absent and the artifact reads `checked: {entities: 3}` — three,
90
+ with nothing on it saying three of four.
91
+
92
+ So `--coverage` takes the JSON from `bmc-sensor-audit coverage --json`, which does
93
+ carry the diff. Supply it and the certificate states declared-vs-present and names
94
+ the absent sensor. Leave it out and the certificate says, on the page:
95
+
96
+ > no declaration diff was supplied, so this certificate cannot state how many
97
+ > declared sensors were present; the attestation counts only entities that reached
98
+ > the engine
99
+
100
+ It is optional because requiring it would make a certificate impossible for anyone
101
+ holding only an attestation. It is *stated* because the smaller denominator must
102
+ not pass for the whole picture.
103
+
104
+ **That coverage artifact is written for a CI log, not a customer.** It names the
105
+ BMC by URL and the configuration by filesystem path. Both are dropped here before
106
+ anything is rendered; see `tests/test_privacy.py`.
107
+
108
+ ## The PDF is a projection of the JSON
109
+
110
+ No number appears in the PDF that is absent from the certificate JSON. The JSON is
111
+ the record; the PDF is how it looks. Anyone holding both can check one against the
112
+ other without trusting this code:
113
+
114
+ ```
115
+ cert-generator verify --certificate certificate.json --pdf certificate.pdf
116
+ ```
117
+
118
+ Shipped rather than kept in a CI script, for the same reason the audit tool ships
119
+ its attestation validator: the person who *receives* the document is the one who
120
+ needs to check it.
121
+
122
+ The suite checks the same property with poppler's `pdftotext` — a reader nobody
123
+ here wrote.
124
+
125
+ ## Identity goes one way
126
+
127
+ Identity flows *into* the certificate and never back toward the audit inputs. This
128
+ package does not construct walks, configurations or supplemental declarations, and
129
+ has no HTTP client. `tests/test_boundary.py` enforces it by parsing this package's
130
+ own imports, because a rule kept by review lasts exactly as long as the reviewer's
131
+ attention.
132
+
133
+ The only thing it may reach for in the audit tool is `validate_attestation` — the
134
+ tool's *shipped* validator, not a second copy of its rules. A shapeless artifact is
135
+ refused, not decorated.
136
+
137
+ ## Exit codes
138
+
139
+ The family's contract, not this tool's invention:
140
+
141
+ | | |
142
+ |---|---|
143
+ | `0` | certificate written; nothing recorded against the unit |
144
+ | `1` | certificate written; findings, or a declared sensor absent |
145
+ | `2` | could not complete — nothing was judged |
146
+
147
+ Precedence is `max`, copied from the audit tool. A run that both found something
148
+ and failed to finish reports `2`, because `2` is the statement about the
149
+ denominator and `1` would let a reader conclude the rest was checked.
150
+
151
+ A certificate is still written when the verdict is `1`. A QC record for a unit that
152
+ failed is a valid document.
153
+
154
+ ## A certificate is not the pipeline's verdict
155
+
156
+ Two records run in parallel and neither contains the other. This one certifies what
157
+ the referee established about a unit: the attestation, the identity, and the
158
+ declaration diff when one is supplied. Conformance and fault injection are not in
159
+ it. Those meet in `odm-qa-pipeline aggregate`, which answers a different question —
160
+ did all four gates run, and what is the worst thing any of them found.
161
+
162
+ Worth stating because reading either as the other goes wrong in both directions: a
163
+ clean certificate says nothing about whether the injection gate ran, and a clean
164
+ pipeline verdict is not a document anyone can hand to a customer.
165
+
166
+ ## The identity block
167
+
168
+ ```json
169
+ {
170
+ "serial": "SN-A17-000482",
171
+ "work_order": "WO-2026-08-1174",
172
+ "station": "FCT-3",
173
+ "signer": "L. Okonkwo",
174
+ "part_number": "PN-88213-B",
175
+ "customer": "Example Hyperscale Inc.",
176
+ "line": "Kaohsiung 2"
177
+ }
178
+ ```
179
+
180
+ The first four are required. The rest are optional and rendered when present.
181
+ Nothing is inferred, and an unrecognised key is **refused** rather than dropped —
182
+ a typo would otherwise vanish silently, and nobody reads a certificate looking for
183
+ the field that is not on it.
184
+
185
+ ## Known limits, written down rather than implied
186
+
187
+ - **Latin-1 only.** The built-in PDF fonts cannot draw characters outside it. An
188
+ identity field containing them is refused, not transliterated: a serial number
189
+ silently rewritten is worse than a failed render. Shipping a Unicode font would
190
+ lift this; it has not been done.
191
+ - **`verify` needs a PDF reader.** poppler's `pdftotext` if it is on `PATH`,
192
+ otherwise `pip install 'odm-cert-generator[verify]'` for pypdf. If neither is
193
+ present the command exits `2` — not finding a reader is not a pass.
194
+ - **Nothing here re-audits the machine.** `verify` proves the page matches the
195
+ record. The record's authority comes from the attestation, and the
196
+ attestation's from the engine.
197
+ - **One page.** Automatic page breaks are off on purpose, so a certificate that
198
+ overflows is visible as overflow rather than quietly paginated. A unit with very
199
+ many findings will need the JSON.
200
+
201
+ ## Where it sits
202
+
203
+ ```
204
+ arbiter-engine the invariant envelope
205
+ ^ pinned >=0.1.6,<0.2
206
+ bmc-sensor-audit the referee: declaration diff, liveness, attestation
207
+ ^ pinned >=0.1.0,<0.2
208
+ cert-generator this: identity, and the honest certificate
209
+ ```
210
+
211
+ `qa-orchestrator` sits beside this one, injecting faults and checking the referee
212
+ caught them. `odm-qa-pipeline` composes all of them.
213
+
214
+ ## Licence
215
+
216
+ Apache-2.0.
@@ -0,0 +1,195 @@
1
+ # cert-generator
2
+
3
+ Render a QC certificate from a [`bmc-sensor-audit`](https://github.com/james-sheen/bmc-sensor-audit)
4
+ attestation.
5
+
6
+ **Released — 0.1.0**, tagged `v0.1.0`, Apache-2.0, on PyPI as
7
+ `odm-cert-generator`.
8
+
9
+ **The distribution and the command differ, and the reason is worth one line.**
10
+ PyPI ultranormalises a name by stripping its separators, so `cert-generator`
11
+ becomes `certgenerator` — which an unrelated project holds — and the upload is
12
+ refused as *too similar to an existing project*. So this publishes as
13
+ `odm-cert-generator` and installs a command still called `cert-generator`. A
14
+ certificate records the **distribution** name, because that is the one that
15
+ resolves on an index.
16
+
17
+ ```
18
+ pip install odm-cert-generator
19
+
20
+ cert-generator render \
21
+ --attestation attestation.json \
22
+ --identity identity.json \
23
+ --coverage coverage.json \
24
+ --out-json certificate.json \
25
+ --out-pdf certificate.pdf
26
+ ```
27
+
28
+ ## What it is for
29
+
30
+ The audit tool judges a machine and deliberately keeps the machine's identity out
31
+ of everything it writes. A certificate has the opposite job: it has to name the
32
+ unit. This renders one from the other, and holds the serial number on its own side
33
+ of the line.
34
+
35
+ Two trust domains, split on purpose. The referee cannot leak what it never sees.
36
+ The certificate can say what a certificate must.
37
+
38
+ ## The certificate never claims a flat pass
39
+
40
+ It will not print "100 % match". It prints the denominator:
41
+
42
+ ```
43
+ 6 invariant(s) checked over 3 entit(ies); 1 finding(s) recorded;
44
+ 3 check(s) declined and therefore not judged
45
+ ```
46
+
47
+ ...and then a section headed **Not part of this judgment**, which is emitted even
48
+ when every list in it is empty — because an absent section reads as *nothing was
49
+ left out*, and that is a claim.
50
+
51
+ That section carries:
52
+
53
+ | | |
54
+ |---|---|
55
+ | the declines | each with the engine's machine-readable reason |
56
+ | `unattested` | problem types the engine would not attest |
57
+ | `unread_feeds` | data that was read and not used |
58
+ | the declaration diff | declared vs. present, when supplied — see below |
59
+ | the engine's boundary | quoted verbatim, not paraphrased |
60
+
61
+ A certificate showing what was *not* checked is one an incoming-inspection team
62
+ can act on. The denominator is the point, not a concession.
63
+
64
+ ## The declaration diff is a second, optional input
65
+
66
+ `attestation/1` records what the engine judged. It does not record what was
67
+ *declared and never showed up*. Run the audit tool against a board declaring four
68
+ sensors where one is absent and the artifact reads `checked: {entities: 3}` — three,
69
+ with nothing on it saying three of four.
70
+
71
+ So `--coverage` takes the JSON from `bmc-sensor-audit coverage --json`, which does
72
+ carry the diff. Supply it and the certificate states declared-vs-present and names
73
+ the absent sensor. Leave it out and the certificate says, on the page:
74
+
75
+ > no declaration diff was supplied, so this certificate cannot state how many
76
+ > declared sensors were present; the attestation counts only entities that reached
77
+ > the engine
78
+
79
+ It is optional because requiring it would make a certificate impossible for anyone
80
+ holding only an attestation. It is *stated* because the smaller denominator must
81
+ not pass for the whole picture.
82
+
83
+ **That coverage artifact is written for a CI log, not a customer.** It names the
84
+ BMC by URL and the configuration by filesystem path. Both are dropped here before
85
+ anything is rendered; see `tests/test_privacy.py`.
86
+
87
+ ## The PDF is a projection of the JSON
88
+
89
+ No number appears in the PDF that is absent from the certificate JSON. The JSON is
90
+ the record; the PDF is how it looks. Anyone holding both can check one against the
91
+ other without trusting this code:
92
+
93
+ ```
94
+ cert-generator verify --certificate certificate.json --pdf certificate.pdf
95
+ ```
96
+
97
+ Shipped rather than kept in a CI script, for the same reason the audit tool ships
98
+ its attestation validator: the person who *receives* the document is the one who
99
+ needs to check it.
100
+
101
+ The suite checks the same property with poppler's `pdftotext` — a reader nobody
102
+ here wrote.
103
+
104
+ ## Identity goes one way
105
+
106
+ Identity flows *into* the certificate and never back toward the audit inputs. This
107
+ package does not construct walks, configurations or supplemental declarations, and
108
+ has no HTTP client. `tests/test_boundary.py` enforces it by parsing this package's
109
+ own imports, because a rule kept by review lasts exactly as long as the reviewer's
110
+ attention.
111
+
112
+ The only thing it may reach for in the audit tool is `validate_attestation` — the
113
+ tool's *shipped* validator, not a second copy of its rules. A shapeless artifact is
114
+ refused, not decorated.
115
+
116
+ ## Exit codes
117
+
118
+ The family's contract, not this tool's invention:
119
+
120
+ | | |
121
+ |---|---|
122
+ | `0` | certificate written; nothing recorded against the unit |
123
+ | `1` | certificate written; findings, or a declared sensor absent |
124
+ | `2` | could not complete — nothing was judged |
125
+
126
+ Precedence is `max`, copied from the audit tool. A run that both found something
127
+ and failed to finish reports `2`, because `2` is the statement about the
128
+ denominator and `1` would let a reader conclude the rest was checked.
129
+
130
+ A certificate is still written when the verdict is `1`. A QC record for a unit that
131
+ failed is a valid document.
132
+
133
+ ## A certificate is not the pipeline's verdict
134
+
135
+ Two records run in parallel and neither contains the other. This one certifies what
136
+ the referee established about a unit: the attestation, the identity, and the
137
+ declaration diff when one is supplied. Conformance and fault injection are not in
138
+ it. Those meet in `odm-qa-pipeline aggregate`, which answers a different question —
139
+ did all four gates run, and what is the worst thing any of them found.
140
+
141
+ Worth stating because reading either as the other goes wrong in both directions: a
142
+ clean certificate says nothing about whether the injection gate ran, and a clean
143
+ pipeline verdict is not a document anyone can hand to a customer.
144
+
145
+ ## The identity block
146
+
147
+ ```json
148
+ {
149
+ "serial": "SN-A17-000482",
150
+ "work_order": "WO-2026-08-1174",
151
+ "station": "FCT-3",
152
+ "signer": "L. Okonkwo",
153
+ "part_number": "PN-88213-B",
154
+ "customer": "Example Hyperscale Inc.",
155
+ "line": "Kaohsiung 2"
156
+ }
157
+ ```
158
+
159
+ The first four are required. The rest are optional and rendered when present.
160
+ Nothing is inferred, and an unrecognised key is **refused** rather than dropped —
161
+ a typo would otherwise vanish silently, and nobody reads a certificate looking for
162
+ the field that is not on it.
163
+
164
+ ## Known limits, written down rather than implied
165
+
166
+ - **Latin-1 only.** The built-in PDF fonts cannot draw characters outside it. An
167
+ identity field containing them is refused, not transliterated: a serial number
168
+ silently rewritten is worse than a failed render. Shipping a Unicode font would
169
+ lift this; it has not been done.
170
+ - **`verify` needs a PDF reader.** poppler's `pdftotext` if it is on `PATH`,
171
+ otherwise `pip install 'odm-cert-generator[verify]'` for pypdf. If neither is
172
+ present the command exits `2` — not finding a reader is not a pass.
173
+ - **Nothing here re-audits the machine.** `verify` proves the page matches the
174
+ record. The record's authority comes from the attestation, and the
175
+ attestation's from the engine.
176
+ - **One page.** Automatic page breaks are off on purpose, so a certificate that
177
+ overflows is visible as overflow rather than quietly paginated. A unit with very
178
+ many findings will need the JSON.
179
+
180
+ ## Where it sits
181
+
182
+ ```
183
+ arbiter-engine the invariant envelope
184
+ ^ pinned >=0.1.6,<0.2
185
+ bmc-sensor-audit the referee: declaration diff, liveness, attestation
186
+ ^ pinned >=0.1.0,<0.2
187
+ cert-generator this: identity, and the honest certificate
188
+ ```
189
+
190
+ `qa-orchestrator` sits beside this one, injecting faults and checking the referee
191
+ caught them. `odm-qa-pipeline` composes all of them.
192
+
193
+ ## Licence
194
+
195
+ Apache-2.0.