Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
150 changes: 150 additions & 0 deletions pkgs/mcpx/docs/decisions/0005-release-pipeline.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
# 0005 — Release pipeline, version single-sourcing, and the module path

```
number: 0005
status: proposed
date: 2026-10-29
issues: #1881 (this design) · split from triage 2774 · implementation is queue task 3114
code: 7d1b081c
```

> mcpx has no release at all: no tags, no GitHub releases, no release
> workflow, and a version string hard-coded in four places. The install
> page's channels are promises nothing can keep until a release exists.
> This note decides the shape — channel order, the tag and version scheme
> for a package that lives in a monorepo, the static-build policy, and the
> module-path question — so the implementation slices have one answer to
> build to. The module-path recommendation (§4) is the one item Drew may
> want to overrule; everything else follows from the code as it is.

---

## 1. Current state (verified at 7d1b081c)

- No tags and no releases on developing-today/code for mcpx; the only
mcpx workflow builds and tests (`mcpx.yml`) plus vendorHash repair.
- Version `0.1.0` is hard-coded four times: `package.nix` (`version =`),
`package.nix` (`-X main.version=0.1.0`), the fallback
`var version = "0.1.0"` in `cmd/mcpx/main.go`, and
`ARG VERSION=0.1.0` in `Containerfile`.
- `go.mod` declares `module github.com/dezren39/mcpx` and
`package.nix`'s `meta.homepage` points at the same repository, which
returns 404. mcpx actually lives at `pkgs/mcpx` in
developing-today/code. 328 Go files import by the current path.
- A static build works: the SQLite driver is pure Go, `CGO_ENABLED=0`
cross-compiles, and a fully static Linux binary was verified in #182's
container work. The shipped artifacts are not static by choice:
`package.nix` takes the nixpkgs cgo default and the Containerfile sets
`CGO_ENABLED=1` deliberately.
- Unix only (`meta.platforms = lib.platforms.unix`; untagged
`Setpgid`/`Setsid` use). Release targets are therefore the flake's
three systems: x86_64-linux, aarch64-linux, aarch64-darwin.
- The nix package is more than the binary: it generates the man page and
completions and wraps the binary with git/deno/bun/node on PATH. A bare
release binary has no JavaScript runtime; `mcpx run`/`exec` fail until
the user installs one (`mcpx doctor` says so). Every non-nix channel
must state this in its install text.

## 2. Channel order

Build in dependency order; each channel consumes the one before it.

1. **GitHub release binaries + checksums.** The foundation: three static
binaries (`mcpx-<version>-<system>.tar.gz`) and a SHA256SUMS file on a
GitHub release. Everything else downloads these.
2. **`curl | sh` installer.** Per-user by default (installs to
`~/.local/bin`, states the JavaScript-runtime requirement at the
end); a system-wide variant (`sudo`) documented, not default. The
script verifies the checksum before installing — an installer that
skips verification teaches users to pipe unverified binaries.
3. **Container image.** The existing Containerfile, published to GHCR by
the same release workflow, version taken from the release (§3), not
from a fifth copy of the string.
4. **Nix.** Already works (`nix profile install …#mcpx`); the release
does not change it beyond the version source (§3).

Later, and not in the first slice: pwsh (needs Windows binaries, which
need the untagged-syscall work), rpm/pkg (repackage the tarballs once
they exist), the agent-prompt and just-the-plugin channels (they ride on
1–2). **Flatpak is not built**: a sandboxed mcpx cannot spawn the
arbitrary servers, runtimes and binaries that are its entire function;
the sandbox would have to be opened until the packaging is a lie. This
records #1881's recommendation against it as a decision.

## 3. Tags and the version's single source

- **Tag scheme: `mcpx/vX.Y.Z`** on developing-today/code. A path-prefixed
tag is the monorepo-honest scheme: it cannot collide with another
package's releases in the same repository, and the release workflow
triggers on exactly this prefix. (If §4's recommendation is adopted,
Go's own subdirectory-module versioning wants `pkgs/mcpx/vX.Y.Z` tags
for the module; the release workflow can cut both tags on the same
commit — one for humans, one for the Go toolchain — rather than
letting the Go scheme dictate the public one.)
- **Single source: a `pkgs/mcpx/VERSION` file.** `package.nix` reads it
for both its `version` and its `-X main.version=` ldflag; the
Containerfile takes `VERSION` as a build argument whose workflow
callers pass the file's contents (the in-file `ARG` default becomes
the file's value at release time, and a comment in the Containerfile
says the file is the source); the release workflow refuses to publish
when the tag and `VERSION` disagree.
- **`cmd/mcpx/main.go`'s fallback becomes a sentinel** (`0.0.0-dev`), so
a binary built without ldflags announces that fact instead of
impersonating a release. The fallback is the one place a literal
remains, and it is deliberately not a release number.

## 4. The module path — options and recommendation

`github.com/dezren39/mcpx` names a repository that does not exist, so
`go install …@latest` cannot work and `meta.homepage` 404s. Three ways
out:

- **A — Create the repository the path names** (dezren39/mcpx or
developing-today/mcpx) and move mcpx into it. Cleanest end state:
normal tags, normal `go install`, its own Pages and releases. Cost:
mcpx leaves the monorepo it shares tooling, CI and history with; the
move itself is a project, and this repository's other packages that
reference mcpx paths must follow. This is Drew's call, not a worker's.
- **B — Rename the module to where the code lives:**
`github.com/developing-today/code/pkgs/mcpx`. `go install
github.com/developing-today/code/pkgs/mcpx/cmd/mcpx@pkgs/mcpx/vX.Y.Z`
then works from the monorepo, tags follow Go's subdirectory scheme,
and `meta.homepage` points at the real tree. Cost: a mechanical rename
across 328 Go files plus `package.nix` references — large diff, no
design content, scriptable in one pass. If A is ever chosen later, the
same mechanical rename runs again in the other direction; B does not
close A's door.
- **C — Keep the path, publish a release-only mirror.** Releases are cut
to a mirror repository that carries the path's name. `go install`
stays broken (the proxy resolves the module path, not the mirror's
releases), the homepage stays a lie, and two repositories must be kept
in sync forever. Recorded for completeness; not recommended.

**Recommendation: B, now.** It makes every other part of this note true
with one mechanical change and no new repository. A remains available
whenever Drew wants mcpx to stand alone; nothing in B makes A harder.

## 5. Static-build policy for releases

Release binaries are `CGO_ENABLED=0` static builds (the #182-verified
configuration). The caveat is recorded where the install page will read
it: on Linux, pure-Go `os/user` parses `/etc/passwd` instead of asking
NSS, so LDAP/SSSD users are not found, and the pure-Go resolver does not
honour `nsswitch.conf`. On macOS the flag changes nothing observable.
The nix package keeps its own (cgo) build: it targets managed systems
where NSS is the point, and it already exists.
Comment on lines +134 to +135

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Reconcile the Nix build policy with its explicit cgo setting

The Nix package already sets env.CGO_ENABLED = "0" in pkgs/mcpx/package.nix:60, both at this commit and at the cited baseline 7d1b081c; it does not have the cgo build this decision says it preserves. Because this note governs the implementation slices, following this policy would either unexpectedly re-enable cgo despite §2 promising only a version-source change, or leave the stated NSS behavior incorrect. Preserve the actual static configuration in the decision, or explicitly describe enabling cgo as a separate behavior change.

Useful? React with 👍 / 👎.


## 6. Implementation slices (queue task 3114, in order)

1. This design note.
2. Version single-sourcing (§3): VERSION file, package.nix, Containerfile
build-arg, the `0.0.0-dev` fallback sentinel.
3. Rename slice (§4-B), if Drew has not overruled it: module path,
imports, package.nix references, meta.homepage.
4. Release workflow: tag-triggered (`mcpx/v*`) plus `workflow_dispatch`
snapshot mode that builds the three artifacts and checksums without
publishing; the snapshot run is the slice's proof.
5. Installer script + a CI exercise (install a snapshot artifact into a
temp prefix; `mcpx --version` reports the release version).
6. Container publication wiring in the same workflow; update the #1878
install-page status column to what actually shipped.
1 change: 1 addition & 0 deletions pkgs/mcpx/docs/decisions/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ owner.
| [0002](0002-autonomy-dial.md) | One autonomy dial, and a ceiling the daemon sets | accepted | 2026-09-30 | #111 #112 #113 #114 #110 · #181 rows 3, 5 |
| [0003](0003-declared-vs-enforced-capabilities.md) | What mcpx declares, and what it enforces | accepted | 2026-09-30 | #82 #177 · #181 row 10 |
| [0004](0004-scope-resolved-roots.md) | Scope-resolved roots: spawn cwd, `${mcpx.root}` substitution, initialize workspace | accepted | 2026-10-26 | dezren39/nix#817 #818 #819 · touches #308 #309 #864 |
| [0005](0005-release-pipeline.md) | Release pipeline, version single-sourcing, and the module path | proposed | 2026-10-29 | #1881 |

## Status

Expand Down
Loading