Skip to content

mcpx: the nix derivation's generated man page, completions and version are unverified, and one of them is not reproducible #1895

Closed

Description

@dezren39

Relocated from dezren39/darwin-nix#178 on 2026-10-28 (placement audit): mcpx's single source lives in developing-today/code (pkgs/mcpx); re-home tracked at #309. Original body below; issue references inside it are historical (dezren39/nix numbering).

Parent: #177

Summary

pkgs/mcpx/package.nix postInstall produces three artefacts from the binary it just built — a man page, three shell completion scripts, and a version string injected by ldflags. Nothing asserts any of them, one of them is not reproducible, and the version exists in three places that no test keeps in agreement. These are release prerequisites: #158 designs tags, release artefacts and install channels on top of a derivation whose generated half is currently unchecked, and a release that ships a man page dated by whichever day the builder ran is a release that cannot be rebuilt bit-for-bit.

This issue owns the three defects in postInstall and the version fan-out. It does not own the platform matrix (#136), the CI workflow shape and caching design (#135), the container image (#134), or the release pipeline (#158). (It previously named #161 and #168; both were closed as duplicates — the survivors are #134 and #135.)

What was verified

All checks at 8001f23, which is one commit ahead of the d0b7e01 the earlier issues were written against; nothing in this area changed between them. Every row was re-checked at 05c78b2 in the 2026-09-30 issues pass and every one still holds: internal/cli/manual.go:335 still stamps time.Now(); package.nix:19 and :61 and cmd/mcpx/main.go:18 still carry three separate 0.1.0 literals; internal/testsupport/build.go:63 still builds without ldflags; and rg over every *_test.go still finds no reference to ManPage, CmdMan or CmdCompletion. #171 and #182 changed nothing here.

Claim How verified Result
The man page stamps a build date internal/cli/manual.go:335 — date := time.Now().UTC().Format("2006-01-02"), written into .TH MCPX 1 %q ... at :337 VERIFIED
postInstall runs the generator at build time package.nix postInstall: $out/bin/mcpx man > $out/share/man/man1/mcpx.1 VERIFIED
Nothing asserts the man page or the completions rg -n 'ManPage|CmdMan|CmdCompletion|--version|a\.Version' --glob '*_test.go' in pkgs/mcpx → zero matches VERIFIED
The version is hard-coded in three places package.nix:19 version = "0.1.0"; package.nix:61 "-X main.version=0.1.0"; cmd/mcpx/main.go:18 var version = "0.1.0" (its own comment at :17 says it "is overridden at build time with -ldflags"). rg '0\.1\.0' finds two further echoes that are not build inputs: plugin/opencode/skills/mcpx-daemon/SKILL.md:35 (documentation example) and plugin/opencode/mcpx/daemon.test.ts:132 (test fixture) VERIFIED — exactly three build-relevant sites, plus two documentation/test echoes
The test binary is built without those ldflags internal/testsupport/build.go:63 — exec.Command("go", "build", "-o", mcpxBin, "./cmd/mcpx"), no -ldflags VERIFIED
No CGO_ENABLED is set rg -n 'CGO|env =' package.nix → no matches; buildGoModule therefore inherits the nixpkgs default VERIFIED (the static-linking consequences are #136's; and the premise there was wrong — CGO_ENABLED=0 yields a fully static linux binary, verified in #182, so #136 is a policy decision. This issue only records that package.nix sets no CGO_ENABLED.)

Current state (with file refs)

1. The man page is not reproducible

// internal/cli/manual.go:333-337
func ManPage(version string) string {
	var b strings.Builder
	date := time.Now().UTC().Format("2006-01-02")

	fmt.Fprintf(&b, ".TH MCPX 1 %q %q \"mcpx manual\"\n", date, "mcpx "+version)

package.nix runs mcpx man inside postInstall, so $out/share/man/man1/mcpx.1 embeds the UTC date of the build. Everything else in the derivation is deterministic: Go builds reproducibly, ldflags = [ "-s" "-w" ... ] strips, and the vendor tree is a fixed-output derivation. time.Now() is the only wall-clock input.

Consequences, in increasing order of how much they will matter later:

  • nix build .#mcpx --rebuild diffs across a UTC midnight boundary. Nothing runs that today, so nobody has seen it.
  • A content-addressed or signed release artefact (amd host fails at evaluation: no outputHashes for p2panda-core-0.7.1 #158) is not byte-stable, so "the tarball I built matches the tarball CI published" cannot be asserted.
  • man mcpx shows the build date where a reader expects the documentation date, which is the wrong fact: a binary substituted from a cache shows the cache's build day.

The convention for exactly this is SOURCE_DATE_EPOCH, which nixpkgs already exports in every build (it is set to 1 in the stdenv unless overridden) and which every reproducible-build toolchain honours.

2. Neither generated artefact is asserted

# package.nix postInstall
$out/bin/mcpx man > $out/share/man/man1/mcpx.1

installShellCompletion --cmd mcpx \
  --bash <($out/bin/mcpx completion bash) \
  --zsh <($out/bin/mcpx completion zsh) \
  --fish <($out/bin/mcpx completion fish)

mcpx man and mcpx completion <shell> are executed at build time and their output is never inspected, by the build or by any test. The build fails only if the process exits non-zero. A ManPage that returned the empty string, or a completion zsh that emitted a syntactically invalid #compdef block, would produce a green nix build and a broken installed package. rg over every _test.go in the tree returns no reference to ManPage, CmdMan or CmdCompletion.

This is worse than it looks because both generators read Commands(), which is itself incomplete — seven dispatched commands are missing from it (#159 item 1; re-measured in the sibling review issue). So the man page and the completions are known to be wrong, and there is no mechanism by which anyone would find out.

3. The version has three sources and no arbiter

Site Value Who reads it
package.nix:19 version = "0.1.0" the derivation name, nix metadata, any release artefact named from it
package.nix:61 "-X main.version=0.1.0" the installed binary's --version, /v1/health, the OpenAPI info.version, the man page's .TH title
cmd/mcpx/main.go:18 var version = "0.1.0" any binary built without ldflags — including every end-to-end test, via internal/testsupport/build.go:63

They agree today only because nobody has changed one. The first bump breaks the property silently in two directions: the derivation name and the binary's self-report can diverge, and the e2e suite keeps exercising a binary that reports the pre-bump version while the packaged one reports the new one. internal/api/openapi.go and internal/cli/openapi.go both take version as a parameter, so a wrong value propagates into the published specification too.

Approaches

1. Reproducible man page — no real choice

Honour SOURCE_DATE_EPOCH: parse it when set, fall back to time.Now() when it is not, so an interactive mcpx man still shows today. Five lines in internal/cli/manual.go. The only sub-decision is what to show when the variable is absent from a release build, and the answer is that a release build always has it.

2. Asserting the generated artefacts

  • A — least recommended: assert in postInstall. Add grep -q checks to the nix expression (grep -q '^\.TH MCPX' mcpx.1, non-empty completions). It works, and it puts test logic in the one place that only runs under nix build, is invisible to go test, cannot be run locally without a full derivation rebuild, and is written in bash inside a Nix string. Every future assertion accretes there.
  • B — middle: a Go golden test. internal/cli/manual_test.go compares ManPage("test") against a checked-in golden file. Catches regressions in content, and catches nothing about the completions actually being valid shell, because a golden file of a zsh script only proves it did not change.
  • C — recommended: a Go presence test plus a shell-parser smoke test, and keep nix build dumb. In Go: every entry in Commands() appears in ManPage, every entry appears in each of the three completion scripts, and the .TH line carries the version it was given. In CI (this belongs to Update Rust crate tokio to v1.38.2 [SECURITY] #135's job graph, not to postInstall): pipe each completion script through bash -n, zsh -n and fish --no-execute, which is the only check that distinguishes "non-empty" from "valid". Once Root flake check fails on nixosConfigurations due to missing clan-core.clanModules #159's Commands() gap is closed, the same presence test is what stops it reopening. Recommended because it moves the assertion to where it can be run in a second by anyone, and because the shell-parse check is the one that finds the failure mode that actually ships.

3. Single-sourcing the version

  • A — least recommended: a //go:generate step or a version.txt read by both. A file both Nix and Go read is genuinely single-sourced, but it adds a build input that has to be kept in the cleanSourceWith filter, and it does not solve the test-binary problem: go build without ldflags still falls back.
  • B — middle: drop the fallback and make an un-stamped binary say so. var version = "dev" in cmd/mcpx/main.go (which is already the convention used at internal/pool/pool.go:39, var Version = "dev"). Then a binary built without ldflags reports dev and can never be confused with a release. Cheap, honest, and leaves package.nix's two copies still hand-synchronised.
  • C — recommended: B, plus derive the ldflag from the version attribute, plus a test. ldflags = [ "-s" "-w" "-X main.version=${version}" ] removes one of the two Nix copies outright (finalAttrs.version or a let-bound name). var version = "dev" removes the third. That leaves exactly one literal, which amd host fails at evaluation: no outputHashes for p2panda-core-0.7.1 #158 then replaces with the git tag. Add one e2e assertion that mcpx --version matches the derivation's version when run from result/bin/mcpx — which is the "handful of assertions against result/bin/mcpx" that Update Rust crate rand to v0.9.3 [SECURITY] #134 identifies as the cheapest win in its area (formerly Phase 2: removal: weak is stored but not built #161, closed as a duplicate), so the two should land together.

Open questions

Ease · Impact · Related

Ease: S (the man-page date and the version fan-out are a handful of lines each; the presence test is the bulk, and it is one file) · Impact: med (nothing is broken for a user today; everything here is a prerequisite that gets more expensive the longer #158 waits on it)

Related: #158 — this is largely a prerequisite for it: a release pipeline needs a reproducible derivation, one version source and generated artefacts it can trust. #134 owns the container image and the "run assertions against result/bin/mcpx" step that item 3C plugs into (formerly #161, closed as a duplicate). #182 added a Containerfile; nothing in CI builds or tests it yet, so that step is still owed. #159 item 5 records the man-page date as a documentation problem; this issue adds the nix-reproducibility consequence and the fix shape. #159 item 16 and #158 both record the three version sites; this issue adds the test-binary divergence via internal/testsupport/build.go:63. #136 owns the platform matrix and the static-binary question, which is why CGO_ENABLED is only recorded here and not decided. #135 owns the CI job graph the shell-parse check belongs in, and the rest of the caching design (its cache-dependency-path line landed in #182). #144/#145 own generating a reference from Commands(), which shares the presence test. #49 — the same "built but never exercised" gap for every other flake package.

Security/auth deferred; see tracker.

Activity

  1. developing-today commented on Oct 28, 2026

    @developing-today
    Contributor
  2. dezren39 commented on Oct 28, 2026

    @dezren39
    MemberAuthor

    History carried over from dezren39/darwin-nix#178 (verbatim, oldest first):


    @dezren39 · 2026-09-30T07:22:03Z

    Integration pass. Placed in the DAG rooted at #181.


    @dezren39 · 2026-09-30T12:23:34Z

    Issues pass (2026-09-30, at 05c78b2). Re-verified in full; two closed-duplicate links repaired.


    @dezren39 · 2026-09-30T12:26:19Z

    Issues pass (2026-09-30, at 05c78b2) -- for whoever lands this. This issue is in flight, so its body was not edited. These came out of re-checking every other issue against current main; each is verified at 05c78b2 unless marked.

  3. added
    bugSomething isn't working
    enhancementNew feature or request
    queue/queuedHas a queued task in the Supabase agent queue
    on Oct 28, 2026
  4. added
    work/assignedQueue worker lifecycle state
    queue/claimedQueue worker lifecycle state
    and removed
    queue/queuedHas a queued task in the Supabase agent queue
    on Oct 29, 2026
  5. dezren39 commented on Oct 29, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · picked up

  6. dezren39 commented on Oct 29, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · done

    Triage verdict — CONFIRMED at current main, not a duplicate; fix follow-up enqueued (task 3140)

    Re-verified at main 58621026 (2026-10-29, GitHub API raw file reads):

    • Man page stamps the build date: pkgs/mcpx/internal/cli/manual.go:608 — date := time.Now().UTC().Format("2006-01-02") (line moved from :335 in the body; claim unchanged). Not reproducible.
    • Version in three build sites, all still 0.1.0: pkgs/mcpx/package.nix:23 (version = "0.1.0"), package.nix:77 (-X main.version=0.1.0), pkgs/mcpx/cmd/mcpx/main.go:19 (var version = "0.1.0").
    • postInstall generates the artefacts at build time: package.nix:133-143 — mcpx man > …/mcpx.1 plus bash/zsh/fish completions via mcpx completion …. The body's "nothing asserts them" finding (rg over *_test.go: no ManPage/CmdMan/CmdCompletion references) was not contradicted by anything found this triage.

    Dedupe: Distinct from #1981 (packaging test: install the built artifact + MCP handshake) and #1881 (release pipeline/channels, which depends on this). No other open issue owns the postInstall generated-artefact defects.

    Follow-up enqueued: task 3140 (small_fix) — reproducible man date (SOURCE_DATE_EPOCH or equivalent), single-source version or a disagreement guard test, and guard tests asserting man page + completions + --version agree. Verification: pkgs/mcpx: go test ./... via CI.

  7. 7 remaining items

  8. added
    queue/claimedQueue worker lifecycle state
    queue/parkedQueue task is parked (waiting on CI, time, or an event)
    status/parkedDeliberately paused with a recorded resume condition
    and removed
    queue/claimedQueue worker lifecycle state
    on Oct 30, 2026
  9. dezren39 commented on Oct 30, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · parked

  10. dezren39 commented on Oct 30, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · resumed, Codex finding fixed, parked on CI

    • Agent: queue worker (repo-worker-code), Lane E
    • Session / ID: queue task 3140 (resumed from pr_opened checkpoint)
    • State: PR #2537 — Codex P1 dispositioned and fixed in eb79a58: version is now bound in an enclosing let and inherited into the derivation (the non-recursive buildGo127Module attrset left ${version} in ldflags unbound, so the package failed to evaluate). Verified on the laptop: nix eval .#packages.x86_64-linux.mcpx.version returns the version and the derivation instantiates; go test ./internal/buildcheck/ passes. Reply posted on the Codex thread.
    • Resume condition: re-read mcpx: reproducible man page, single-source version, guard tests for generated artefacts #2537 checks for head eb79a58; merge when green (checks queued with no runner yet, same as the previous round).
  11. dezren39 commented on Oct 30, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · done — merged

    • Agent: openchamber scheduled lane (queue worker repo-worker-code)
    • Session / ID: queue task 3140 (small_fix, generated artefacts) — resumed from awaiting_ci, completed fixed_merged
    • Shipped: PR mcpx: reproducible man page, single-source version, guard tests for generated artefacts #2537 merged to main as 2eddd329fd0a574730aa2b1f418d6aa220c38a32 at 2026-10-30T04:31:04Z (head eb79a585); verified as an ancestor of origin/main. The issue was already closed by the merge, but still carried status/parked / queue/parked / work/assigned — cleared this shift so nothing looks in-flight.
    • What landed: internal/buildcheck/version_test.go (+55) — the version guard test; internal/cli/generated_artefacts_test.go (+103) — the guard tests for generated artefacts; internal/cli/manual.go + completion.go — deterministic man page and completions (the non-reproducibility fix); internal/e2e/artefacts_test.go (+56) and package.nix (+14/-2) — single-sourced version in the derivation; root.go + completion_test.go one-line follow-ups.
    • Observation for the next sweep, not a blocker: the PR also contains bin/sessions.json (+197/-15), which looks like a generated session index caught in the commit rather than part of the fix. Worth a glance to decide whether it should have been committed.
    • Check state at merge: basic jobs were QUEUED on the PR head when I read it; the merge queue accepted it on its own merge-group validation. No held / user/owned / do-not-merge labels.
    • Links: mcpx: reproducible man page, single-source version, guard tests for generated artefacts #2537 · 2eddd32
  12. added and removed
    status/parkedDeliberately paused with a recorded resume condition
    queue/parkedQueue task is parked (waiting on CI, time, or an event)
    work/assignedQueue worker lifecycle state
    on Oct 30, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions