Repository navigation
Slice 002: a bill of materials in every build, and provenance on every release artifact - #71
Merged
Merged
Conversation
… provenance A reviewer of this tree, or a deployment holding one of its binaries, should be able to answer two questions without asking anyone: what is inside this build, and where did it come from. Until now neither had an answer in the artifact. The bill. `mix trinity.sbom` runs sbom's CycloneDX generator over mix.lock and then corrects two things about what it generated, because a bill without either correction is wrong rather than merely thinner. The first is coverage. mix.lock knows Hex dependencies and knows nothing about the Rust crates the desktop shell links, so a reader who took the bill for the whole picture would conclude there is no Rust in the product. The coverage is therefore written into the document itself as a metadata property, where anyone reading the file alone will find it, rather than into a page beside it. The second is licence identifiers, and it was found by validating rather than by reasoning. CycloneDX checks license.id against the SPDX enumeration and rejects the whole document over one bad value; a Hex package's licences are free text its author typed; and sbom emits a lone licence string as an id without checking it. yamerl declares "BSD 2-Clause" where SPDX spells "BSD-2-Clause", and the official CycloneDX validator refused the first bill this project ever generated over that single field. Any identifier not in the SPDX list vendored at priv/spdx/license-ids.txt is now recorded as a licence name instead, which is where CycloneDX puts a licence it cannot resolve. Nothing is dropped and nothing is guessed: the text is kept, it simply stops claiming to be an identifier it is not. The vendored list carries its source, its ref and its sha256, and going stale can only demote a valid id to a name, never the reverse. The gate generates the bill on every commit, so one that cannot be produced fails the build rather than being missing from a release nobody checked. Provenance. The package workflow uploads the bill beside each platform's binary and attests each artifact through GitHub's workflow identity, recorded in the public transparency log. gh attestation verify then checks the attestation in the same job that produced it and writes the result to the run summary, because an attestation nobody checks is a file rather than an assurance. Transport. One README paragraph states the TLS floor and the one message authentication code Trinity mints, pinned by tests against ssl.versions/0 and against the envelope's source rather than against the sentence. The assurance case is corrected with the rest: C8 stated in writing that this project published no bill of materials, and a gap stated in an assurance case is a promise to close it. What remains is narrower and is still stated: the crate half is slice 121's, and signing is slice 101's. Signed-off-by: Ayla Croft <aylacroft@proton.me>
The bill is derived from mix.lock alone. It is the same document whichever runner writes it, so generating it inside each of the three packaging jobs bought nothing and cost twice: mix trinity.sbom needs the project compiled in the dev environment, which those jobs do not otherwise do, and it gave Windows a chance to produce a bill differing from Linux's for no reason a reader could act on. A job of its own now generates it on Linux and uploads it; each packaging job downloads it and ships it beside its binary, so the bill still travels with every artifact. download-artifact is pinned to the commit for v4.3.0, verified against the tag rather than copied. Signed-off-by: Ayla Croft <aylacroft@proton.me>
…e summary gh attestation verify draws its result with carriage returns and the log viewer collapses them to nothing, so the step ran for four seconds and left no trace a reader could quote. One plain line now records the outcome, reached only when the verification passed: the step runs under set -e with pipefail. Signed-off-by: Ayla Croft <aylacroft@proton.me>
…README The assurance posture table is what a reader evaluating this project for adoption reads first, and it named neither. Supply chain now says a bill ships with every binary and states its own coverage inside the document; provenance now says every binary carries an attestation checked in the run that produced it, so a holder can verify where the bytes were built without trusting the page that told them to. Signed-off-by: Ayla Croft <aylacroft@proton.me>
Listing the binary and the bill as two upload paths made upload-artifact take the workspace as their common ancestor, so the binary moved to burrito_out/ inside the zip. gh run download --name then refuses the whole artifact with "would result in path traversal" on the gh in Ubuntu's archive (2.45.0), while a pre-slice-002 artifact downloads cleanly with the same client. Measured on run 35898448972, not reasoned about. Whoever is at fault for the traversal check, a reviewer's first attempt to fetch a binary must not fail, and the layout every earlier release used is not worth changing to carry one extra file. The bill is downloaded into burrito_out/ instead, so both land at the artifact root. Signed-off-by: Ayla Croft <aylacroft@proton.me>
699a9b1 said gh run download refuses the two-path artifact layout with "would result in path traversal", measured against a pre-slice-002 artifact that downloaded cleanly. The layout was not the cause and the measurement was not a comparison: the old artifact had been fetched with -D and the new one without it. Run the same way, gh 2.45.0 raises that error for either artifact when it extracts into the current directory, and succeeds for either with -D; gh 2.63.2 succeeds for both with or without. It is a bug in the client version Ubuntu ships, it predates this slice, and nothing here caused or fixed it. The layout change stands, on the reason that survives: both files at the root of the artifact is the shape every earlier release used, and moving the binary under burrito_out/ would make consumers absorb a change for no benefit. The comment in package.yml is corrected in place rather than deleted, because a record that quietly loses a wrong claim teaches nothing. Signed-off-by: Ayla Croft <aylacroft@proton.me>
80.31% at d65c48c, level with slice 050's row and above the 80.05% floor the gate holds. The slice adds a Mix task and no application code, so the number moving nowhere is the expected result rather than a happy one. Signed-off-by: Ayla Croft <aylacroft@proton.me>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Two questions a reviewer or a deployment should be able to answer from the artifact alone, without asking anyone: what is inside this build, and where did it come from. Neither had an answer before this slice.
What is inside
mix trinity.sbomrunssbom's CycloneDX generator overmix.lock, then makes two corrections to what it produced.Coverage, stated inside the document.
mix.lockknows Hex dependencies and knows nothing about the Rust crates the desktop shell links. A reader who took the bill for the whole picture would conclude there is no Rust in the product. The coverage statement is therefore ametadata.propertiesentry in the bill itself, where anyone reading the file alone finds it, rather than a paragraph in a page beside it.Licence identifiers, found by validating rather than by reasoning. CycloneDX validates
license.idagainst the SPDX enumeration and rejects the whole document over one bad value. A Hex package'slicensesmetadata is free text its author typed, andsbomemits a lone licence string as anidwithout checking it.yamerldeclaresBSD 2-Clausewhere SPDX spellsBSD-2-Clause, and the official CycloneDX validator refused the first bill this project ever generated over that single field:Any identifier not in the SPDX list vendored at
priv/spdx/license-ids.txtis now recorded as a licencenameinstead, which is where CycloneDX puts a licence it cannot resolve. Nothing is dropped and nothing is guessed: the text is kept, it simply stops claiming to be an identifier it is not. After the fix, the same command exits 0.The vendored list carries its source URL, its ref and its sha256 in its own header, and it fails safe in one direction: an identifier added to SPDX after the date recorded there is demoted to a
name, which is still valid CycloneDX. The reverse mistake is the one that invalidates a bill, and the list prevents it.The gate generates the bill on every commit, so a bill that cannot be produced fails the build rather than being missing from a release nobody checked.
Where it came from
The package workflow uploads the bill beside each platform's binary and attests each artifact through GitHub's workflow identity, recorded in the public transparency log.
gh attestation verifythen checks the attestation in the same job that produced it and writes the result to the run summary, because an attestation nobody checks is a file rather than an assurance.Transport
One README paragraph states the TLS floor and the one message authentication code Trinity mints, pinned by tests against
:ssl.versions/0and against the envelope's source rather than against the sentence, so the line cannot outlive the fact.The assurance case
docs/10-assurance-case.mdC8 stated in writing that this project published no bill of materials. A gap stated in an assurance case is a promise to close it, so C8 is corrected with the rest of the work. What remains is narrower and is still stated: the Rust crate half is slice 121's, and signing is slice 101's.Evidence
mix gateexit 0; 575 passed, 18 excluded; coverage 80.31%.test/supply_chain_test.exscovering AC1 and AC3.