Skip to content

Prove a dependency added to a tree reaches the notices document - #136

Merged
iderex merged 2 commits into
mainfrom
supply-chain/a-tree-with-a-dependency-added
Aug 16, 2026
Merged

Prove a dependency added to a tree reaches the notices document#136
iderex merged 2 commits into
mainfrom
supply-chain/a-tree-with-a-dependency-added

Conversation

@iderex

@iderex iderex commented Aug 16, 2026

Copy link
Copy Markdown
Contributor

Refs #37

What this changes

It adds cmd/notices/tree_test.go, which builds one tree twice and holds the
difference between the two notices documents. The second tree differs from the
first by a require line and an import. The first document must not name the
module and must carry the sentence saying there is nothing to disclose; the
second must name it and reproduce the text it shipped.

It changes nothing that ships. The only file is a test.

The module the tree depends on is invented and served out of a directory the
test lays out as a module proxy. A test that borrowed something real would
depend on that thing staying fetchable and staying licensed the way it was on
the day the test was written, and both of those are facts about somebody else's
repository.

What failure it prevents

A notices document that describes nothing while reading as finished.

buildOf reads the module table out of the binary and turns it into what the
render consumes. A version of it that reads the table and drops what it finds
produces a document saying the binary contains no third-party module, which is
the sentence an operator would take as an answer rather than as a gap.

Nothing here measured that before. internal/notices proves the render against
module sets its cases write out in full, so a defect between the binary and the
render leaves every case green. The neighbouring test in this package reads a
binary built from this repository, which has no third-party dependency, so it
exercises only the empty half.

What was run

I proved the new test bites by deleting the line that collects the modules,
replacing build.Deps = append(build.Deps, module) in cmd/notices/main.go
with a discard, and running the whole suite:

$ go test -count=1 ./cmd/... ./internal/...
FAIL	github.com/Flowfin/lab/cmd/notices	7.336s
ok  	github.com/Flowfin/lab/cmd/pullrequest	0.505s
ok  	github.com/Flowfin/lab/internal/check	1.734s
ok  	github.com/Flowfin/lab/internal/contexts	0.651s
ok  	github.com/Flowfin/lab/internal/hardware	0.528s
ok  	github.com/Flowfin/lab/internal/invariants	1.095s
ok  	github.com/Flowfin/lab/internal/notices	0.649s
ok  	github.com/Flowfin/lab/internal/prose	0.791s
ok  	github.com/Flowfin/lab/internal/pullrequest	0.489s

Named, that run reddens one test and no other:

$ go test -count=1 -v ./cmd/notices
--- PASS: TestTheModuleTableOfARealBinaryReachesTheDocument (1.69s)
--- PASS: TestABrokenInvocationIsNotARefusal (1.49s)
--- FAIL: TestATreeWithADependencyAddedProducesADocumentThatListsIt (3.98s)

The line was restored and the commands CONTRIBUTING.md names were run at the
commit being pushed:

$ go build ./cmd/... ./internal/...
$ go vet ./cmd/... ./internal/...
$ gofmt -l cmd internal
$ go test -count=1 ./cmd/... ./internal/...
ok  	github.com/Flowfin/lab/cmd/contexts	0.364s
ok  	github.com/Flowfin/lab/cmd/lab	0.851s
ok  	github.com/Flowfin/lab/cmd/notices	6.839s
ok  	github.com/Flowfin/lab/cmd/pullrequest	0.384s
ok  	github.com/Flowfin/lab/internal/check	0.624s
ok  	github.com/Flowfin/lab/internal/contexts	0.371s
ok  	github.com/Flowfin/lab/internal/hardware	0.381s
ok  	github.com/Flowfin/lab/internal/invariants	0.650s
ok  	github.com/Flowfin/lab/internal/notices	0.368s
ok  	github.com/Flowfin/lab/internal/prose	0.390s
ok  	github.com/Flowfin/lab/internal/pullrequest	0.395s

$ go run ./cmd/lab check .
examined .
1 experiment directory walked, 1 record read
18 decision records read
the time this run read is 2026-08-16T19:50:46Z
0 refused

gofmt -l printed nothing, which is its passing result.

What this does not do

It does not close #37. Two of that issue's three legs ask for both artefacts to
be generated by the release build and attached to the release, and there is no
release workflow in this tree and no release. This retires the third leg only.

It adds no bill of materials. That artefact belongs with the release build for
the reason #37 gives, which is that it is generated at build time from the
build.

It opens no connection, and that is a property of how the test is written rather
than something watched. The proxy is a directory, the checksum database is off,
the toolchain is pinned to the installed one so a go directive is never a
download, and the module cache is a temporary directory. The three variables
that send a build past a proxy are emptied rather than inherited, because any of
them matching this module turns the module path into a domain and fetches over
https. Nothing traced the process.

It builds with whatever toolchain runs the suite, so it needs one installed.
That is already true of the test beside it, which builds this repository's
runner.

Nobody but me has read this change.

Why this was held, and what released it

One check on the head this section was written against was red, and it was red
for a reason this change did not cause. Audit workflows (zizmor) reported two
findings, both in
.github/workflows/codeql.yml, on lines this branch does not contain:

$ git diff --name-only origin/main...HEAD -- .github/
$ git diff --name-only origin/main...HEAD
cmd/notices/tree_test.go

The branch touched one file and it was not a workflow, so the audit was
reporting the same two lines it would report on the default branch. What had
moved was the tag a pin comment names, upstream. Issue #137 carried the finding
with the commands behind it.

Every other check on that head was green, including the three test jobs that
run the test this change adds:

$ gh pr checks 136
CodeQL (go)                     pass
DCO sign-off                    pass
Reject Trojan Source Unicode    pass
build (darwin/amd64)            pass
build (darwin/arm64)            pass
build (linux/amd64)             pass
build (linux/arm64)             pass
build (windows/amd64)           pass
build (windows/arm64)           pass
dependency-review               pass
format                          pass
headless and unelevated         pass
invariants                      pass
prose format                    pass
pull request                    pass
records                         pass
required contexts               pass
test (darwin/arm64)             pass
test (linux/amd64)              pass
test (windows/amd64)            pass
vet                             pass
Audit workflows (zizmor)        fail

#137 has since landed, in #138, merged as
109f78bdb7d4c7503ac44fd56ba7a3a05bede593. It moved no pin and changed only the
comment beside two of them, so the repair stayed out of this change and this
change still carries one topic.

The default branch is green on that audit again:

$ gh run list --workflow=zizmor.yml --branch main --limit 1 --json conclusion --jq '.[].conclusion'
success

The default branch was then merged into this one rather than this one being
rebased onto it, so the history above is the history that was reviewed and no
commit was rewritten. The merge commit is 7374b6b, and the file this change
adds is still the only file it adds:

$ git diff --name-only origin/main...HEAD
cmd/notices/tree_test.go

The suite was run again at that merge, and the section above stands as written
rather than being edited into a change that never had a red check.

Issue #37 asks for a run against a tree with a dependency added producing a
notices file that lists it. Nothing in this repository measured that. The render
is proved against module sets a case writes out in full, and the command's own
suite reads the module table of a binary built from this repository, which has
no third-party dependency and therefore exercises only the half where there is
nothing to disclose.

This builds one tree twice, the second differing from the first by a require
line and an import, and holds the difference between the two documents. The
first must not name the module and must say there is nothing to disclose; the
second must name it and carry the text it shipped.

The failure it prevents is a document that describes nothing while looking
finished. A buildOf that reads the module table and drops what it finds produces
a notices file saying the binary contains no third-party module, which is the
sentence an operator would read as an answer. I proved that by deleting the line
that collects them:

    go test -count=1 ./cmd/... ./internal/...

With `build.Deps = append(build.Deps, module)` replaced by a discard, this test
is the only one in the module that reddens. internal/notices stays green because
its cases construct the module set themselves, and the neighbouring test in this
package stays green because the binary it reads has no dependencies.

The test opens no connection. The module is served out of a directory this test
lays out as a module proxy, the checksum database is off, the toolchain is
pinned to the installed one so a go directive is never a download, and the
module cache is a temporary directory, so the licence text in the document came
from this run rather than from what the machine had downloaded before it. The
three variables that send a build past a proxy are emptied rather than
inherited, because any of them matching this module turns the module path into a
domain and fetches over https.

Signed-off-by: Nils Lehnen <30603423+iderex@users.noreply.github.com>
@iderex
iderex merged commit 82c245f into main Aug 16, 2026
25 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Generate the third-party notices and the bill of materials

1 participant