cbm-attach 0.1.1__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 (35) hide show
  1. cbm_attach-0.1.1/LICENSE +202 -0
  2. cbm_attach-0.1.1/PKG-INFO +288 -0
  3. cbm_attach-0.1.1/README.md +264 -0
  4. cbm_attach-0.1.1/pyproject.toml +41 -0
  5. cbm_attach-0.1.1/setup.cfg +4 -0
  6. cbm_attach-0.1.1/src/cbm_attach/__init__.py +3 -0
  7. cbm_attach-0.1.1/src/cbm_attach/__main__.py +3 -0
  8. cbm_attach-0.1.1/src/cbm_attach/artifacts.py +397 -0
  9. cbm_attach-0.1.1/src/cbm_attach/body.py +173 -0
  10. cbm_attach-0.1.1/src/cbm_attach/cli.py +904 -0
  11. cbm_attach-0.1.1/src/cbm_attach/evidence.py +63 -0
  12. cbm_attach-0.1.1/src/cbm_attach/gates.py +240 -0
  13. cbm_attach-0.1.1/src/cbm_attach/intoto.py +40 -0
  14. cbm_attach-0.1.1/src/cbm_attach/reasons.py +171 -0
  15. cbm_attach-0.1.1/src/cbm_attach/reports.py +482 -0
  16. cbm_attach-0.1.1/src/cbm_attach/uploader.py +281 -0
  17. cbm_attach-0.1.1/src/cbm_attach.egg-info/PKG-INFO +288 -0
  18. cbm_attach-0.1.1/src/cbm_attach.egg-info/SOURCES.txt +33 -0
  19. cbm_attach-0.1.1/src/cbm_attach.egg-info/dependency_links.txt +1 -0
  20. cbm_attach-0.1.1/src/cbm_attach.egg-info/entry_points.txt +2 -0
  21. cbm_attach-0.1.1/src/cbm_attach.egg-info/top_level.txt +1 -0
  22. cbm_attach-0.1.1/tests/test_b1_identity.py +92 -0
  23. cbm_attach-0.1.1/tests/test_b2_readiness.py +49 -0
  24. cbm_attach-0.1.1/tests/test_b3_cbp_rc.py +79 -0
  25. cbm_attach-0.1.1/tests/test_e2e_mock.py +249 -0
  26. cbm_attach-0.1.1/tests/test_exit_codes.py +653 -0
  27. cbm_attach-0.1.1/tests/test_gate_before_upload.py +170 -0
  28. cbm_attach-0.1.1/tests/test_hashes.py +131 -0
  29. cbm_attach-0.1.1/tests/test_intoto.py +65 -0
  30. cbm_attach-0.1.1/tests/test_known_fail_silent.py +63 -0
  31. cbm_attach-0.1.1/tests/test_not_uploaded_body.py +124 -0
  32. cbm_attach-0.1.1/tests/test_packaging.py +47 -0
  33. cbm_attach-0.1.1/tests/test_schema.py +75 -0
  34. cbm_attach-0.1.1/tests/test_uploader.py +143 -0
  35. cbm_attach-0.1.1/tests/test_workflow_static.py +132 -0
@@ -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,288 @@
1
+ Metadata-Version: 2.4
2
+ Name: cbm-attach
3
+ Version: 0.1.1
4
+ Summary: Sidecar attachment attestation for release evidence
5
+ Author: Junxiang Pan
6
+ License: Apache-2.0
7
+ Project-URL: Homepage, https://github.com/JunxiangPan/cbm
8
+ Project-URL: Repository, https://github.com/JunxiangPan/cbm
9
+ Project-URL: Issues, https://github.com/JunxiangPan/cbm/issues
10
+ Keywords: attestation,supply-chain,release-evidence,in-toto
11
+ Classifier: Development Status :: 3 - Alpha
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: License :: OSI Approved :: Apache Software License
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.10
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Topic :: Security
19
+ Classifier: Topic :: Software Development :: Build Tools
20
+ Requires-Python: >=3.10
21
+ Description-Content-Type: text/markdown
22
+ License-File: LICENSE
23
+ Dynamic: license-file
24
+
25
+ # cbm-attach
26
+
27
+ [![cbm-ci](https://github.com/JunxiangPan/cbm/actions/workflows/ci.yml/badge.svg)](https://github.com/JunxiangPan/cbm/actions/workflows/ci.yml)
28
+ [![License: Apache-2.0](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](LICENSE)
29
+
30
+ **For** release engineers who already run CBE/CBP gates and need a defensible
31
+ sidecar attachment decision.
32
+
33
+ **Problem:** a release can be "green" while the optional CBM artifact was never
34
+ built, was built from a different hash, or was attached with a stale manifest.
35
+ `cbm-attach` consumes the existing evidence, verifies the canonical hashes and
36
+ the build-once wheel, and emits a machine-readable report plus a human-readable
37
+ body fragment. It never re-runs CBE/CBP and is not a gate in their `needs`
38
+ graph.
39
+
40
+ **Start:**
41
+
42
+ ```bash
43
+ pip install cbm-attach
44
+ cbm-attach --help
45
+ ```
46
+
47
+ Until the PyPI release is available, install the exact released wheel:
48
+
49
+ ```bash
50
+ pip install https://github.com/JunxiangPan/cbm/releases/download/v0.1.0/cbm_attach-0.1.0-py3-none-any.whl
51
+ cbm-attach --help
52
+ ```
53
+
54
+ From a checkout:
55
+
56
+ ```bash
57
+ python -m pip install -e .
58
+ cbm-attach --help
59
+ ```
60
+
61
+ The import package remains `cbm_attach`; the distribution and CLI are
62
+ `cbm-attach`.
63
+
64
+ ## Current status: v0.1.0 sidecar released
65
+
66
+ CBM v0.1.0 is a **sidecar attachment** tool, not a complete product
67
+ certification and not an A-F live E2E proof. Its GitHub Release is published:
68
+
69
+ https://github.com/JunxiangPan/cbm/releases/tag/v0.1.0
70
+
71
+ - **B1 CONTRACT_ACTIVE** — contract-v1.7.1 §5.1 active; identity tests retained.
72
+ - **B2 UPLOADED_VERIFIED** — real CBE report accepted; claims and F3 configs
73
+ PASS; exact artifact `72254704…` matches CBP manifest `0e0d37bd…`; CBM gate
74
+ `PASS`, upload `UPLOADED_VERIFIED`.
75
+ - **B3 FIELD_LEVEL_COMPLETE** — CBP 1.2.2 object manifest, base scope,
76
+ artifact/evidence hashes.
77
+ - **B4 UPLOADED_VERIFIED** — object artifact repository matched; fail-closed
78
+ boundaries retained.
79
+ - **B5 FIXED** — CBS body generator emits `CBM: NOT_UPLOADED` +
80
+ `EVIDENCE_ABSENT`.
81
+
82
+ **128 offline/static tests pass.** The source is Apache-2.0. No A-F live E2E
83
+ or full-product certification is claimed. Evidence lives in
84
+ `audit/v1.2.0-rc/`.
85
+
86
+ ## Trust root
87
+
88
+ CBE and CBP `evidence_sha256` values are recomputed from canonical JSON before
89
+ the gate decides; a format-valid but wrong hash exits 3
90
+ `EVIDENCE_HASH_MISMATCH`. CBM's own report is sealed the same way. Raw-byte
91
+ report hashes remain separate from canonical evidence hashes.
92
+
93
+ ## in-toto and generic evidence
94
+
95
+ `verify` and `upload` write an in-toto v1 Statement next to the CBM report
96
+ (`cbm-report.statement.json` by default, or `--statement PATH`). Its
97
+ `predicateType` is `https://cbm.dev/attachment/v1`; the existing
98
+ `cbm-report.json` is preserved inside the predicate.
99
+
100
+ For non-CBM subjects, `cbm_attach.evidence` exposes `EvidenceInput`,
101
+ `load_evidence()` and `identity_errors_for(subject_package_id=..., producers=...)`.
102
+ This is an adapter layer; the four-piece CBE/CBP/CBM path is unchanged.
103
+
104
+ ## Non-goals
105
+
106
+ - CBM does not run CBE or CBP, and does not re-compute their gates.
107
+ - CBM does not prove code security.
108
+ - CBM does not rebuild the package. It verifies the build-once artifact and
109
+ refuses an artifact that is not listed in `WHEEL_SHA256` and
110
+ `RELEASE_ASSETS.sha256`. CBM 0.1.0 supports `artifact_type=wheel` only; a
111
+ zip-bundle is rejected as an input error.
112
+ - CBM does not write the whole CBS Release body. It emits one marker-delimited
113
+ fragment; the CBS body generator merges it.
114
+
115
+ ## Two-axis status
116
+
117
+ | Situation | `cbm_gate_status` | `upload_status` | exit |
118
+ | --- | --- | --- | --- |
119
+ | not claimed, no failure | `NOT_UPLOADED` | `NOT_UPLOADED` | 0 |
120
+ | CBE/CBP FAIL blocks upload | `FAIL` | `NOT_UPLOADED` | 3 |
121
+ | own hash/upload verification failure | `FAIL` | `NOT_UPLOADED` or `UPLOADED_UNVERIFIED` | 3 |
122
+ | infrastructure failure | `SKIPPED_INFRA` | `NOT_UPLOADED` | 3 |
123
+ | missing/invalid input | `PRECONDITION_FAIL` | `NOT_UPLOADED` | 2 |
124
+ | skip ratio above 20% | `FAIL` | `NOT_UPLOADED` | 4 |
125
+ | uploaded and digest verified | `PASS` | `UPLOADED_VERIFIED` | 0 |
126
+
127
+ The Release body labels the attachment from `upload_status`, so a gate failure
128
+ is still shown as `NOT_UPLOADED` without being laundered into PASS.
129
+
130
+ ## Commands
131
+
132
+ ```bash
133
+ # verify: consume reports, enforce the gate, write report/body/decision
134
+ python -m cbm_attach verify \
135
+ --version v0.1.0 --tag v0.1.0 --release-tag v1.7.0 \
136
+ --declared-attached true --upload-target github-release \
137
+ --cbe-report cbe.json --cbp-report cbp.json --build-once-dir build-once \
138
+ --output cbm-evidence/cbm-report.json \
139
+ --body cbm-evidence/cbm-body.md \
140
+ --decision cbm-evidence/cbm-decision.json
141
+
142
+ # upload: re-verify the build-once artifact, then upload idempotently
143
+ python -m cbm_attach upload \
144
+ --report cbm-evidence/cbm-report.json \
145
+ --decision cbm-evidence/cbm-decision.json \
146
+ --build-once-dir build-once \
147
+ --upload-target github-release \
148
+ --repository owner/repo --release-tag v1.7.0 \
149
+ --output cbm-evidence/cbm-report.final.json \
150
+ --body cbm-evidence/cbm-body.final.md
151
+
152
+ # finalize: choose the best report, regenerate the body fragment
153
+ python -m cbm_attach finalize \
154
+ --input cbm-evidence --output cbm-evidence-out/cbm-report.json \
155
+ --body cbm-evidence-out/cbm-body.md --declared-attached true
156
+ ```
157
+
158
+ `verify --fetch` and `upload --fetch` download run artifacts through `gh api`.
159
+ `artifact_repository` must hold all three run artifacts (CBM build-once, CBE
160
+ report, CBP manifest); split repositories need an input extension in a later
161
+ version. Cross-repository download needs a token with `actions:read` on the
162
+ artifact repository; cross-repository release upload needs `contents:write` on
163
+ the release repository. Use a scoped GitHub App/PAT, not a broad personal token.
164
+
165
+ GitHub artifact downloads are repository-bound. The v1.7 release train must
166
+ upload the **CBM build-once artifact, CBE report and CBP manifest to the same
167
+ `artifact_repository`**. If CBE/CBP upload in their own repositories, CBM cannot
168
+ download them; `verify --fetch` will exit 2 with `MISSING_BUILD_ONCE_ARTIFACT`,
169
+ `MISSING_CBE_REPORT` or `MISSING_CBP_REPORT`. This is a release-train
170
+ orchestration requirement, not something CBM can fix alone. It should be added
171
+ to CONTRACT.md §11.
172
+
173
+ Audited on 2026-11-08: the checked-in CBS mock train
174
+ (`reusable-v17-release.yml`) uploads only `v17-mock-artifact` in the CBS repo
175
+ and has no CBM job; CBP `ci.yml` uploads `quality-manifest-${run_id}` in the
176
+ **CBP** repo. The artifacts are not co-located, so the same-repo contract is
177
+ **not implemented yet**; this is **blocker B4**, and it must be solved together
178
+ with **B3** (CBP P0-06), not separately. CBM v0.1.0 takes one
179
+ `artifact_repository`; split repos need a copy step or a v0.2.0 input
180
+ extension. Required names:
181
+ `cbm-build-once`, `cbe-report`, `cbp-quality-manifest` (the actual CBP artifact
182
+ may be `quality-manifest-<run_id>`; pass it as `cbp_report_artifact`). See
183
+ `docs/INTEGRATION_REQUIREMENTS.md`.
184
+
185
+ CBM 0.1.0 supports `artifact_type=wheel` only. A CBS `artifact_type=zip-bundle`
186
+ requires CBM 0.2.0. CBM does not sign and does not consume CBS's
187
+ `update-manifest.sig`; that is a CBS release asset and must be listed in CBS's
188
+ own `RELEASE_ASSETS.sha256` per §11. CBM reads only the CBM build-once
189
+ artifact's manifest.
190
+
191
+ ## Workflow integration (v0.1.0)
192
+
193
+ The reusable workflow is `.github/workflows/cbm-attach.yml`. It has three jobs:
194
+
195
+ - `verify` consumes CBE/CBP reports, enforces the gate, and writes a **draft**
196
+ `cbm-evidence/cbm-report.json` plus `cbm-evidence/cbm-body.md`.
197
+ - `upload` runs only when `needs.verify.result == 'success'` **and**
198
+ `needs.verify.outputs.decision == 'UPLOAD'`; it re-verifies the build-once
199
+ artifact, uploads it, and writes `cbm-evidence/cbm-report.final.json` plus
200
+ `cbm-evidence/cbm-body.final.md`. `needs.verify.result` is `failure` for exit
201
+ 2/3/4, so missing input, gate failure and skip-over-threshold all skip upload.
202
+ - `finalize` runs with `if: ${{ !cancelled() }}`, checks out the CBM tooling
203
+ independently of verify's artifact, recovers every evidence artifact, and
204
+ writes `cbm-evidence-out/cbm-report.json` plus
205
+ `cbm-evidence-out/cbm-body.md` (a marker-delimited fragment). A checkout or
206
+ GitHub API outage can still prevent finalize from writing evidence. In that
207
+ case CBS body generator supplies the `CBM: NOT_UPLOADED` +
208
+ `EVIDENCE_ABSENT` record. CBS v1.7.0-integration `fa1740f` implements this;
209
+ B5 is fixed and the test is flipped. See `docs/V17_BLOCKERS.md` and
210
+ `docs/CBS_BODY_REQUIREMENT.md`.
211
+
212
+ The CBS body generator must consume the **finalized** `cbm-evidence-out`
213
+ artifact, not the verify draft. The verify draft says `NOT_UPLOADED` because the
214
+ upload has not happened yet; that is honest, but it is not the final attachment
215
+ status.
216
+
217
+ CBM does **not** edit the CBS Release body in v0.1.0. The fragment includes
218
+ `<!-- CBM:START -->` / `<!-- CBM:END -->` so the CBS generator can merge one
219
+ block idempotently. If a future asynchronous step updates the body, it must do a
220
+ latest-body read-modify-write, replace exactly the marker block, retry on
221
+ conflict, and never touch the body when the upload failed. `body.py` already
222
+ provides `replace_marker_block` for that future path.
223
+
224
+ If `finalize` finds no report at all, it writes
225
+ `cbm_gate_status=SKIPPED_INFRA`, `reason=EVIDENCE_ABSENT` (detail
226
+ `WORKFLOW_FAILED_BEFORE_EVIDENCE`) and `upload_status=NOT_UPLOADED`; it never
227
+ writes PASS. If the verify or upload job
228
+ failed after writing `UPLOAD_PENDING`, `finalize` turns that stale pending
229
+ report into `FAIL` / `UPLOAD_FAILED` (or `EVIDENCE_INVALID` for a
230
+ PASS-without-upload contradiction).
231
+
232
+ ## Evidence hashes
233
+
234
+ `cbe_report_hash` and `cbp_report_hash` are SHA-256 over the **raw bytes** of
235
+ the report that CBM read. They are not the report's `evidence_sha256` field and
236
+ they are not a canonicalised JSON hash. `evidence_sha256` on the CBM report is
237
+ the SHA-256 of the canonical JSON of the report without that field.
238
+
239
+ ## Tests
240
+
241
+ ```bash
242
+ python -m pytest
243
+ ```
244
+
245
+ **128 tests pass**: exit-code mapping, gate-before-upload (including a mock
246
+ uploader that records calls), NOT_UPLOADED body rendering, hashes, workflow
247
+ static audit, schema/report lockstep, and a mock end-to-end
248
+ verify/upload/finalize flow. These are offline/static tests, not real E2E.
249
+
250
+ `tests/test_known_fail_silent.py` is now the flipped B5 regression test: it
251
+ asserts the fixed CBS output (`CBM: NOT_UPLOADED` + `EVIDENCE_ABSENT`, no
252
+ `- none`) and the exact phrase when CBM is declared attached. It still checks the
253
+ probe's missing/import/unexpected modes; a missing CBS checkout skips with
254
+ `SKIP_REASON=CBS_BODY_GENERATOR_NOT_FOUND`, and an unimportable file fails with
255
+ `FAIL_REASON=CBS_BODY_GENERATOR_UNIMPORTABLE`.
256
+
257
+ **128 passed = 128 normal tests; no `KNOWN_FAIL_SILENT` canary remains.** Do not
258
+ reintroduce the old `- none` assertion as an accepted state.
259
+
260
+ Probe CLI:
261
+
262
+ ```bash
263
+ python tools/check_b5.py --check-upstream
264
+ ```
265
+
266
+ | Output | Exit | Meaning |
267
+ | --- | --- | --- |
268
+ | `STATUS=B5_MAYBE_FIXED` | 0 | expected fixed state; B5 test is green |
269
+ | `STATUS=KNOWN_FAIL_SILENT` | 0 | old/failed CBS behavior; B5 test fails as a regression |
270
+ | `SKIP_REASON=CBS_BODY_GENERATOR_NOT_FOUND` | 0 | CBS checkout missing; test skips |
271
+ | `FAIL_REASON=CBS_BODY_GENERATOR_UNIMPORTABLE` | 3 | CBS file exists but import/build fails; test fails |
272
+ | `FAIL_REASON=UNEXPECTED_OUTPUT` | 2 | output is neither old defect nor fixed; inspect |
273
+
274
+ ## Open contract points
275
+
276
+ 1. **§5.1 report identity.** CBE/CBP reports currently use `package_id`
277
+ ambiguously. CBM requires `package_id == "cbm"` and treats a producer name
278
+ there as `REPORT_IDENTITY_MISMATCH` (exit 3, no upload).
279
+ 2. **§5.2 CBP body hash scope.** CBM requires `release_body_hash` and
280
+ `body_hash_scope: "base"` in the CBP quality manifest; missing or `"full"`
281
+ exits 2.
282
+
283
+ The proposals are in
284
+ [`docs/CONTRACT_PROPOSAL_5.1.md`](docs/CONTRACT_PROPOSAL_5.1.md); the full
285
+ blocker list is [`docs/V17_BLOCKERS.md`](docs/V17_BLOCKERS.md). They are
286
+ change-control inputs for the CBS/CBP owners; this repository does not edit the
287
+ frozen contract. **CBM 0.1.0 sidecar upload is verified; B3 field-level and B5
288
+ are complete; full product/A-F live E2E is not claimed.**