Repository navigation
Description
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 --rebuilddiffs 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 mcpxshows 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. Addgrep -qchecks 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 undernix build, is invisible togo 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.gocomparesManPage("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 builddumb. In Go: every entry inCommands()appears inManPage, every entry appears in each of the three completion scripts, and the.THline 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 topostInstall): pipe each completion script throughbash -n,zsh -nandfish --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'sCommands()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:generatestep or aversion.txtread 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 thecleanSourceWithfilter, and it does not solve the test-binary problem:go buildwithout ldflags still falls back. - B — middle: drop the fallback and make an un-stamped binary say so.
var version = "dev"incmd/mcpx/main.go(which is already the convention used atinternal/pool/pool.go:39,var Version = "dev"). Then a binary built without ldflags reportsdevand can never be confused with a release. Cheap, honest, and leavespackage.nix's two copies still hand-synchronised. - C — recommended: B, plus derive the ldflag from the
versionattribute, plus a test.ldflags = [ "-s" "-w" "-X main.version=${version}" ]removes one of the two Nix copies outright (finalAttrs.versionor alet-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 thatmcpx --versionmatches the derivation's version when run fromresult/bin/mcpx— which is the "handful of assertions againstresult/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
- Should
postInstallkeep generating the man page from the binary at all, or should the release pipeline (amd host fails at evaluation: no outputHashes for p2panda-core-0.7.1 #158) generate and commit it so that the tarball, the nix package and the distro packages all ship the same bytes? Generating from the binary is the right call for correctness and the wrong call for a distro packager who does not want to execute a freshly built cross-compiled binary. This is a real fork and amd host fails at evaluation: no outputHashes for p2panda-core-0.7.1 #158 should decide it, not this issue. - Does
SOURCE_DATE_EPOCHbelong inManPage's signature (explicit, testable) or read from the environment inside it (fewer call sites to change)? Prefer the parameter, but it touchesCmdMan. - Is
lib.platforms.unixplus an unasserted man page enough for a distro package's lintian/rpmlint pass, or does that need a.THdate in a specific format? Unknown; matters only once amd host fails at evaluation: no outputHashes for p2panda-core-0.7.1 #158 picks a distro channel.
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
developing-today commentedon Oct 28, 2026
CODE-822
dezren39 commentedon Oct 28, 2026
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.
d956689tof12c8d1#102 Bump build/buck/preludes/buck2-prelude fromd956689tocb5dc60#108 chore(deps): update github.com/developing-today-forks/charm digest to 15e844d - autoclosed #116 Phase 2: membership, delegation, groups, and server config #172 Phase 3: /id-sync/1 is used by both range and LogSync handshakes #179 Phase 3: sync data phase batches are bounded by count, not bytes #180@dezren39 · 2026-09-30T12:23:34Z
Issues pass (2026-09-30, at
05c78b2). Re-verified in full; two closed-duplicate links repaired.05c78b2and all still hold.internal/cli/manual.go:335still stampstime.Now();package.nix:19,package.nix:61andcmd/mcpx/main.go:18still carry three separate0.1.0literals;internal/testsupport/build.go:63still builds without ldflags;rgover every*_test.gostill finds no reference to
ManPage,CmdManorCmdCompletion. Phase 2: delegated Manage cannot add members #171 and Phase 5: tag event bodies must travel with the event #182 changednothing in this area.
container image and the
result/bin/mcpxassertions that approach 3C plugs into) and Update Rust crate tokio to v1.38.2 [SECURITY] #135(the CI workflow and the rest of the caching design). Phase 5: tag event bodies must travel with the event #182 added a
Containerfilebut no CI jobbuilds or tests it, so 3C's step is still owed.
Parent: #177added to agree with the native sub-issue link.@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 currentmain; each is verified at05c78b2unless marked.result/bin/mcpxassertions are Update Rust crate rand to v0.9.3 [SECURITY] #134's. "Update dependency happy-dom to v21 [SECURITY] #136's and Phase 2: removal: weak is stored but not built #161's" becomes "Update dependency happy-dom to v21 [SECURITY] #136's".Containerfile:40ARG VERSION=0.1.0was added by Phase 5: tag event bodies must travel with the event #182.nix-buildleg is where this issue's assertions would run.dezren39 commentedon Oct 29, 2026
🤖 Agent note · picked up
dezren39 commentedon Oct 29, 2026
🤖 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):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.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").postInstallgenerates the artefacts at build time:package.nix:133-143—mcpx man > …/mcpx.1plus bash/zsh/fish completions viamcpx 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 +
--versionagree. Verification:pkgs/mcpx: go test ./...via CI.7 remaining items
dezren39 commentedon Oct 30, 2026
🤖 Agent note · parked
go test ./internal/cli ./internal/buildcheck, e2e artefacts test), CI checks queued with no runner yet.queue/3140-1895-mcpx-reproducible-man-version-guards· Parked for: 20 minmcpx: bind version in a let so ldflags interpolation evaluates (#1895)
dezren39 commentedon Oct 30, 2026
🤖 Agent note · resumed, Codex finding fixed, parked on CI
versionis now bound in an enclosingletand 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.versionreturns the version and the derivation instantiates;go test ./internal/buildcheck/passes. Reply posted on the Codex thread.dezren39 commentedon Oct 30, 2026
🤖 Agent note · done — merged
awaiting_ci, completedfixed_mergedmainas2eddd329fd0a574730aa2b1f418d6aa220c38a32at 2026-10-30T04:31:04Z (headeb79a585); verified as an ancestor oforigin/main. The issue was already closed by the merge, but still carriedstatus/parked/queue/parked/work/assigned— cleared this shift so nothing looks in-flight.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) andpackage.nix(+14/-2) — single-sourced version in the derivation;root.go+completion_test.goone-line follow-ups.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.user/owned/do-not-mergelabels.