Skip to content

build(toolchain): bump Zig to dev.2085 — @hasDecl is now pub-only - #351

Merged
dbgeek merged 1 commit into
mainfrom
build/zig-dev-2085-hasdecl-pub
Sep 10, 2026
Merged

dbgeek merged 1 commit into
mainfrom
build/zig-dev-2085-hasdecl-pub

Conversation

@dbgeek

@dbgeek dbgeek commented Sep 10, 2026

Copy link
Copy Markdown
Owner

What broke

nix flake update moved the zig-overlay pin 0.17.0-dev.1786+75044cb040.17.0-dev.2085+5e36170b5, and every zig build — including zig build install-agent — died with 26 comptime errors:

src/grants.zig:186:13: error: type 'grants.FakeProbes' is not Grant Probes: missing method 'micGranted'
src/session.zig:382:13: error: type 'session.FakeTransport' is not a Transport: missing method 'init'
src/local_backend.zig:94:13: error: type 'local_backend.FakeHelper' is not a Helper: missing method 'isReady'
...

Why

Upstream Zig narrowed @hasDecl to report only pub declarations. This is a deliberate, documented language change — the langref shipped inside each compiler says so:

Zig langref wording @hasDecl(Priv,"m") from Priv's own file
dev.1786 (before) "has a declaration matching name" true
dev.2085 (after) "has a public declaration matching name" false

Verified by differential probe on both toolchains, not inferred.

Five files define comptime contract assertions — assertProbes, assertHelper, assertTransport, and assertDeps (undo + insertion_runner) — that check a type's method surface with @hasDecl. Every production adapter already declares those methods pub. Every test fake declared them bare fn, which was legal only because the assertion lives in the same file as the fake, and same-file visibility used to satisfy @hasDecl. Under the new rule the fakes stop matching their own contracts and each assertion fires @compileError.

A tell that the seam was always fragile: std.meta.hasFn lives in a different file and so already returned false for private decls on dev.1786. These hand-rolled @hasDecl checks were leaning on a visibility rule std's own equivalent never offered.

The fix

pub on the contract methods of FakeProbes, FakeDeps ×2, FakeHelper, and FakeTransport, plus FakeTransport's Reader handle. 46 lines, pub and nothing else — no runtime behaviour change, no production code touched. Test-only helpers like FakeProbes.requested stay private, so the pub marker now says precisely which members are the contract.

Verified

Command Result
zig build exit 0
zig build test exit 0, all suites OK
zig build install-agent exit 0, signed + installed pair-1789068005-16478

⚠️ This bump is deliberately partial

docs/toolchain.md requires the compiler and websocket.zig to move as one atomic change. This PR does step 1 and the fallout only. Outstanding:

  • Step 2 — bump websocket.zig. docs/research/websocket-zig-bump.md (untracked on main at time of writing) recommends folding the pin bump to bfe761959b05030eaf4943fcf0fd5ecd1daca68a into "the next compiler bump" — which is this one. Not done here. Note that research proved the current pin 4b475a8 compiles under dev.1786; it has not been re-proved under dev.2085.
  • Step 3 — raise the floor. minimum_zig_version is 0.17.0-dev.1267+300116b02 in all six build.zig.zon files. This drift is pre-existing (it was already two nightlies stale before this PR), not introduced here. docs/toolchain.md's "currently pinned pair" table is stale for the same reason.
  • Step 4 — the live re-prove. No wss round-trip against wss://api.openai.com/v1/realtime was run. zig build test does not exercise the live transport.

Happy to do any of these in follow-ups; they were left out to keep this PR to the diagnosed break.

One thing worth a second look

src/local_backend.zig:194:

if (comptime !@hasDecl(Helper, "usesModel")) return true;

This is the only optional @hasDecl in the tree — it degrades silently rather than erroring. Under the new semantics it would have quietly reported "installation still valid" forever for any Helper with a non-pub usesModel, instead of failing to compile. It's saved only because usesModel is also in assertHelper's required list, so the hard assertion fires first. That redundancy is load-bearing, and the line is now dead weight — left alone here, but it's the shape of bug this bump could have caused instead of a clean compile failure.

🤖 Generated with Claude Code

https://claude.ai/code/session_01JGU4Zq5h2Hf7pTXDuQNa3w

`nix flake update` moved the zig-overlay pin from 0.17.0-dev.1786+75044cb04
to 0.17.0-dev.2085+5e36170b5, and every `zig build` broke with 26 comptime
errors of the form:

    src/grants.zig:186:13: error: type 'grants.FakeProbes' is not Grant
    Probes: missing method 'micGranted'

Cause: upstream Zig narrowed `@hasDecl` to report only *public*
declarations. The langref shipped with each compiler states the change
outright — dev.1786 says "has a declaration matching name", dev.2085 says
"has a **public** declaration matching name". Confirmed by differential
probe on both toolchains: `@hasDecl(T, "m")` for a non-pub `m`, queried
from T's own file, returns true on dev.1786 and false on dev.2085.

The five comptime contract assertions (`assertProbes`, `assertHelper`,
`assertTransport`, `assertDeps` x2) check a type's method surface with
`@hasDecl`. Every production adapter already declares those methods `pub`;
the test fakes declared them bare `fn`, which was legal only because the
assertion sits in the same file as the fake and same-file visibility used
to satisfy `@hasDecl`. Under the new rule the fakes stop matching their own
contracts and each assertion fires `@compileError`.

Fix: `pub` on the contract methods of FakeProbes, FakeDeps (undo and
insertion_runner), FakeHelper, and FakeTransport, plus FakeTransport's
`Reader` handle. Nothing else changes — no runtime behaviour, no production
code. Test-only helpers such as `FakeProbes.requested` stay private, so the
`pub` marker now says precisely which members are the contract.

That the seam was always fragile is visible in std: `std.meta.hasFn` lives
in a different file and therefore already returned false for private decls
on dev.1786. These hand-rolled checks were leaning on a visibility rule
std's own helper never offered.

Worth knowing for the next reader: `local_backend.zig:194`'s
`if (comptime !@hasDecl(Helper, "usesModel")) return true;` is the one
*optional* `@hasDecl` in the tree — it degrades silently instead of
erroring, and under the new semantics it would have quietly reported
"installation still valid" forever rather than failing to compile. It is
saved only because `usesModel` is also in `assertHelper`'s required list,
so the hard assertion fires first. That redundancy is load-bearing.

Verified: `zig build`, `zig build test`, and `zig build install-agent` all
exit 0 on dev.2085 (install signed pair-1789068005-16478).

Per docs/toolchain.md this bump is deliberately partial — see the PR body.
@dbgeek
dbgeek merged commit b49c617 into main Sep 10, 2026
4 checks passed
@dbgeek
dbgeek deleted the build/zig-dev-2085-hasdecl-pub branch September 10, 2026 19:35
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.

1 participant