Six of the eighteen public-graph repositories are converted and carry no
credential-bearing lines: iam, s3-csi, playground, notify, world, team-go.
The preflight matters more than the list. Every part of it earned its place by
producing a wrong answer first: GOPRIVATE= empty does not clear GOPRIVATE, it
falls back to `go env`'s stored value and sends our modules direct for a reason
that exists only on the machine running the check; GIT_CONFIG_GLOBAL=/dev/null
is needed because `-c credential.helper=` leaves the system helper in place; and
`go list -m all` walks the unpruned graph, so it reports modules a build never
loads and condemns repositories that are fine.
And CI passing is not evidence here, because the shared pipeline still injects a
GitHub credential into every job — a converted repo can go green on exactly the
credential it was meant to stop needing.
The pattern behind the first count read `<path> <version>` and so skipped every
major-version path. Thirteen modules were missed; twelve are public and
github.com/hanzoai/zrok/v2 v2.0.1 is not, which makes mcp-gateway a repository
that cannot build without this service.
164 of 167 module versions are served by the public proxy. Four repositories
need the proxy, eighteen carry a credential for a graph that is already public.
iam and s3-csi are converted and green.
All three unlogged modules verified against the running service with no GitHub
credential present: each resolves through the proxy and fails direct.
Measured rather than assumed. Of the 157 module versions our namespaces
contribute across 22 Go repositories, 155 are served by the public proxy and
verified by the public log. Two are not, and they are the whole reason any
repository cannot build without this service: hanzoai/thinking v0.1.1 for zen
and zen-gateway, and a hanzoai/voice pseudo-version for ai.
That leaves nineteen repositories carrying a GitHub credential for a module
graph that is already entirely public.
Two ways to get this wrong, both hit here first: `exclude` is not `require`, and
reading it as one invents four repositories that need a proxy for a version that
exists nowhere; and matching a repository name as a substring makes zen-gateway
answer for gateway.
Also records the reachability split. Test gates run in `hanzo` and resolve
through the proxy with no credential. Image builds run in `hanzo-build`, which
is deliberately isolated, and cannot — naming the namespace is the right shape
but is not sufficient, because allow-build-to-s3 names it the same way and
hanzo-build cannot reach s3 either.
An earlier note here recorded that public forge repositories are readable
anonymously. They are not. The check behind it used `git -c credential.helper=`,
which kept reading the system and global helpers, so it measured a credential it
did not know it had. With those out of the way the forge answers 401 for every
repository, public ones included:
curl -o /dev/null -w '%{http_code}' \
https://git.hanzo.ai/hanzoai/ci.git/info/refs?service=git-upload-pack
FORGE_TOKEN is therefore required, and stays required however much of the estate
is published — publishing changes who may read a repository on GitHub, not
whether this forge asks who is asking.
The code keeps tolerating an empty token. That is not a deployment mode; it is
that the URL rewrite must happen either way, because the rewrite is what makes
our names resolve from the forge and dropping it sends them to github.com.
go.mod said `go 1.26`, a floor of 1.26.0, while the Dockerfile pins
golang:1.26.5-bookworm. Nothing imports this — it is a binary — so the directive
constrains only its own build, and naming the patch says which toolchain it is
actually built and tested with rather than one it happens to clear.
The caller goes back to @v1. Pinning an immutable tag there looked like a canary
and is not one: this forge leaves GITHUB_WORKFLOW_REF unset, so build.yml clones
its own bin/ tools from `v1` whatever ref the caller named. A caller pinned
elsewhere gets that build.yml against v1's tools, which is the skew the
pipeline's own tool check exists to catch.
The estate is going fully open source, so the private-module credential problem
is ending rather than being solved. Re-read against that, two reasons survive
and one part of the design does not.
Surviving, and neither depends on who may read a module:
Authority — the forge answers for our module names, so a build does not need
github.com to be up, willing, or still holding the repository.
Retention — one cache, nothing prunes it. hanzoai/s3-go is 404 on GitHub and
alive on the forge; builds only kept working because a cache elsewhere
happened to still hold it.
The 404 rule survives on a stronger argument than the one it was written for. It
was about a module path travelling to a public service. The real reason is that
404 means "ask the next proxy" and the next proxy resolves github.com/hanzoai/x
from github.com — so a 404 does not fail, it silently returns resolution to the
host this exists to make unnecessary, and the build goes GREEN having used it.
Behaviour unchanged; the test says why it is there.
Not surviving: exempting our whole namespace from the public transparency log.
That was privacy. The log is now asked about everything, ours included, and the
check is worth more for our modules than anyone else's — this is the one place
that reads the forge while the log remembers what was published under the same
name. UNLOGGED replaces it: an explicit list, EMPTY by default, naming only what
the log has never seen, shrinking to nothing as repositories are published.
GOPRIVATE is gone from the server. It was redundant beside an explicit per-request
GOPROXY, and it is the exact variable clients are told never to set. A test now
asserts it is never set.
FORGE_TOKEN becomes optional. Public forge repos are anonymously readable —
verified, not assumed — so empty is a working configuration. The URL rewrite
happens either way: reaching the forge and being allowed to read a given
repository there are different questions, and dropping the rewrite would send our
names back to github.com.
Canary the caller on ci v1.0.75, which stopped gover reading `go 1.26` as a
demand for Go 1.26.999 and refusing a builder that compiles it.
A module path is identity, not an address: `module github.com/hanzoai/o11y` is
what every dependent's go.mod names, so the toolchain took a direct VCS fetch to
github.com and needed a credential valid there. This answers for the name and
reads the source from git.hanzo.ai, so a build resolves the module it already
names with no GitHub credential in it.
Resolution is the toolchain's own — module zips have rules about nested modules,
vendor directories, symlinks, file modes and case collisions, and a zip that gets
one wrong hashes differently and fails every caller's go.sum.
Clients set GOPROXY and GONOSUMDB. Not GOPRIVATE: it means "bypass the proxy AND
the checksum database", and bypassing the proxy is what reaches for github.com.
Skipping the public checksum database for our namespaces is not skipping
verification — go.sum still pins every module and is checked against these bytes.
Inside our namespaces a failure answers 502, never 404: in this protocol 404
means "ask the next proxy", and the next proxy is public.