Skip to content

Check each module's upstream library against rbgo's go.mod - #35

Merged
tannevaled merged 1 commit into
mainfrom
upstream-link-guard
Oct 21, 2026
Merged

tannevaled merged 1 commit into
mainfrom
upstream-link-guard

Conversation

@tannevaled

Copy link
Copy Markdown
Contributor

Closes #34.

Each module section names the library it binds, as a GitHub link, and nothing read those. On 2026-10-20, 3 of the 12 were wrong — each naming a library rbgo had stopped using. They were fixed by hand in #32; this is the hole they came through.

The authority is go.mod, and that is the whole design

A table of "which library each module binds", kept in this repository, would only move the problem: a table that repeats the page's mistake agrees with it, and the check passes.

rbgo's go.mod breaks the tie, because a library that is swapped out disappears from it. That is what makes all three real cases detectable without anybody writing the correct answer down anywhere.

It is a necessary condition, not a sufficient one: a page could name a library rbgo requires for an unrelated reason and this would accept it. Catching that needs the importing package rather than the module graph. The doc comment says so — a limit stated is a limit known, and a limit discovered later is a defect.

Verified on the real historical page, not on a fixture

Against content/en as 0fa762f had it, before #32 corrected anything:

/tmp/prefix-root/scientific-stack.md: the Set section links to github.com/go-composites/set, which rbgo does not depend on
/tmp/prefix-root/scientific-stack.md: the Time/Date section links to github.com/go-composites/date, which rbgo does not depend on
/tmp/prefix-root/scientific-stack.md: the BigDecimal section links to github.com/go-composites/bigfloat, which rbgo does not depend on
12 upstream link(s) checked over 11 module section(s) against rbgo's go.mod; 3 disagree

Exactly the three found by hand, named correctly, and nothing else. Against today's page: 12 links, 0 disagree.

Reading go.mod with the module-file parser

golang.org/x/mod/modfile, not a line search. A require block, a single-line require and a // indirect marker are three different shapes, and a hand-rolled reader gets the last one wrong in the direction that matters — silently accepting an indirect dependency as evidence that a binding exists.

asserted why
single-line require, block require, trailing comment the three shapes rbgo's own go.mod uses
// indirect is not a direct requirement an indirect dependency would let a stale attribution pass whenever the old library is still reachable through something else
a go.mod with no requirement is refused it would otherwise report every link on the page as stale at once
an HTML error page is refused a 404 body is still a body

⛔ A separate workflow step, on purpose

This one fetches over the network and numericsurface does not, so a flaky fetch cannot take the interpreter check down with it. A run on the previous branch failed on exactly that — sum.golang.org dropping an HTTP/2 stream — which is why the fetch exits 2 (a refusal) and why its timeout has a test.

internal/surface

The section reader and the module table now live there. A second copy would drift from the first, and the two guards would end up disagreeing about which sections exist. The extraction tests moved with it.

tools/wired caught this tool before the workflow named it — which is the whole reason it exists, and it fired on its own author:

no workflow mentions [upstreamlinks].
A tool nothing calls is indistinguishable from a tool that passes: both are silent.

46 subtests run, 0 skipped. gofmt, go vet, actionlint, credscan clean. Measured 2026-10-21 with go1.27.1, cobra v1.10.2, x/mod v0.42.0.

🤖 Generated with Claude Code

Closes #34.

Each module section names the library it binds, as a GitHub link, and nothing
read those. On 2026-10-20, 3 of the 12 were wrong -- each naming a library rbgo
had STOPPED using. They were fixed by hand in #32; this is the hole they came
through.

THE AUTHORITY IS go.mod, AND THAT IS THE DESIGN. A table of "which library each
module binds", kept in this repository, would only move the problem: a table
that repeats the page's mistake agrees with it, and the check passes. rbgo's
go.mod breaks the tie because a library that is swapped out DISAPPEARS from it
-- which is what makes all three of the real cases detectable without anybody
writing the correct answer down anywhere.

It is a necessary condition, not a sufficient one. A page could name a library
rbgo requires for an unrelated reason and this would accept it; catching that
needs the importing package rather than the module graph. The doc comment says
so, because a limit stated is a limit known.

VERIFIED ON THE REAL HISTORICAL PAGE, not on a fixture. Against content/en as
0fa762f had it, before #32 corrected anything:

  12 upstream link(s) checked over 11 module section(s); 3 disagree
    the Set section links to github.com/go-composites/set
    the Time/Date section links to github.com/go-composites/date
    the BigDecimal section links to github.com/go-composites/bigfloat

Exactly the three found by hand, named correctly, and nothing else. Against
today's page: 12 links, 0 disagree.

The go.mod is read with golang.org/x/mod/modfile rather than by looking for
lines. A require block, a single-line require and a `// indirect` marker are
three different shapes, and a hand-rolled reader gets the last one wrong in the
direction that matters -- silently accepting an indirect dependency as evidence
that a binding exists. A test asserts all four shapes, and that an HTML error
page is refused rather than read as a go.mod with no requirements.

⛔ A SEPARATE workflow step from numericsurface, on purpose: this one fetches
over the network and that one does not, so a flaky fetch cannot take the
interpreter check down with it. A run on this branch failed once on
sum.golang.org dropping an HTTP/2 stream, which is exactly that risk.

internal/surface now holds the section reader and the module table, because a
second copy would drift from the first and the two guards would end up
disagreeing about which sections exist. The extraction tests moved there with
it.

tools/wired caught this tool before the workflow named it, which is the whole
reason it exists -- and it fired on its own author.

46 subtests run, 0 skipped. gofmt, go vet, actionlint, credscan clean.

Measured 2026-10-21 with go1.27.1, cobra v1.10.2, x/mod v0.42.0.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@tannevaled
tannevaled merged commit e1de97a into main Oct 21, 2026
2 checks passed
@tannevaled
tannevaled deleted the upstream-link-guard branch October 21, 2026 06:42
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.

Nothing checks which upstream library each module binds: 3 of 12 links were stale

1 participant