14 KiB
Framing — Distribution Stage 1: binaries on tag
Revision 2. Status: approved with amendments (revision 1 →
2 records them). Scouted against githubsucks/main @ c5f7501 (#209).
.github/workflows/ contains exactly one workflow and it is test-only.
There is no release job, no artifact upload, no tags-to-binaries path.
Installing pmacs means git clone plus knowing the feature-flag matrix.
Scope is deliberately one stage: produce binaries when a tag is pushed, and attach them to a GitHub Release. Channels, rollback, update-in-place, signing, and §17's first-launch tool detection are all out of scope and named in §5. This establishes a baseline, not the arc.
Revision 1 → 2, all from review:
- Q#D2 decided with a correction to revision 1's reasoning. It is
ubuntu-22.04with an explicit glibc ≥ 2.35 floor — and revision 1 was wrong to imply an older runner reaches RHEL 9. It does not (§1.6). - Q#D1 decided, with acceptance strengthened to assert both binaries report 1.1.0 (§1.3, acceptance 4).
- The binary-exclusion mechanism is now two-layered, not one:
explicit
--bintargets and an explicit staged asset list (§1.2). - Revision 1 mis-stated
pmacs --gpu's failure mode, and the corrected version changes why co-location is required rather than whether (§1.2a). - Both runners are pinned, not just Linux (§1.8).
- The RC is cut after merge from the merge SHA and the final tag reuses that same verified SHA (§7).
0. Coherence impact (COHERENCE §20)
Squarely §17 / §20 Priority 8: it completes journey step 1 without adding an interaction island, a config surface, or a background-work model.
- Journey step touched: step 1 (install) — the one step no prior arc could reach.
- Concern: §17 Distribution, graded "missing — zero release machinery exists." This moves it to Partial, not Strong: binaries on tag is the first of the seven things §17 asks for.
- Interaction islands added: none.
- Config registry adoption: none — this stage introduces no setting.
- Background-work attribution: none — no worker, job, or process.
- Why now: §20 P8 says every other priority's value is invisible until this exists, and its prerequisite — a tree whose tests actually run — was only satisfied by #209. Before that, a release would have shipped from a corpus half of which CI had never compiled.
1. Ground truth (measured at c5f7501, 2026-08-01)
1.1 What exists
Nothing. One workflow, ci.yml, test-only; the only release strings
in it are cargo test --release flags.
1.2 The binary set is NOT what a glob would produce
Cargo.toml declares two [[bin]] targets, pmacs and pmacs-audit.
But cargo also auto-discovers src/bin/*.rs, so a release build
additionally produces pmacs_fake_lsp and pmacs_fake_mcp — test
fixtures whose purpose is to be spawned by acceptance suites. Plus
pmacs-gpu from its own package.
Five binaries can land in target/release, and three must never
ship.
| binary | ship? | why |
|---|---|---|
pmacs |
yes | the editor |
pmacs-gpu |
yes | the GPU frontend |
pmacs-audit |
no | audits pmacs source against the v1.0 lint rules — a contributor tool with no answer to "what is this for" |
pmacs_fake_lsp |
never | test fixture |
pmacs_fake_mcp |
never | test fixture |
Exclusion is two-layered, deliberately. Avoiding an upload glob is not sufficient on its own:
- Build explicit
--bintargets —--bin pmacsand thepmacs-gpupackage — so the unwanted binaries are not produced by the release build at all. - Stage an explicit asset list — copy named files into a staging directory and archive that, so the archive's contents are a decision rather than a directory's residue.
Layer 1 without layer 2 still archives whatever a cached
target/release happens to hold from an earlier step; layer 2 without
layer 1 relies on a list nobody re-checks when a new src/bin/*.rs
appears. Acceptance 2 asserts the complete member list, the
executable bits, and the absence of all three excluded binaries.
1.2a Why pmacs and pmacs-gpu must be co-located — corrected
Revision 1 said separating them makes --gpu "silently fail." That is
wrong, and the real behaviour is better. gpu_binary
(src/main.rs:304) prefers a co-located pmacs-gpu when that path
is_file(), otherwise falls back to the bare name pmacs-gpu for a
PATH lookup; if neither resolves, the error names both — "sibling
… is absent and PATH lookup for pmacs-gpu failed".
So the requirement is not "otherwise it breaks quietly." It is that
a release archive must be self-contained: a user who unpacks it
somewhere not on PATH gets a working --gpu only if the two binaries
sit together. The fallback is a convenience for installed layouts, not a
substitute for shipping them as a unit.
1.3 The version is already incoherent, and a tag makes it visible
| crate | now | after |
|---|---|---|
pmacs |
1.0.0 | 1.1.0 |
pmacs-gpu |
0.0.1 | 1.1.0 |
pmacs-protocol |
1.0.0 | 1.0.0 (unchanged) |
pmacs-protocol stays: it is the wire crate, carries publish-shaped
metadata, and versions on its own schedule — the protocol is v21 and
independent of the editor's release number.
Both shipped binaries report their own env!("CARGO_PKG_VERSION"):
pmacs --version at src/main.rs:393, and pmacs-gpu --version at
pmacs-gpu/src/main.rs:667, which prints pmacs-gpu <ver> (protocol v21) — the protocol number is worth carrying into the release notes.
Tagging v1.1.0 against the tree as it stands would publish a release
containing a pmacs reporting 1.0.0 and a pmacs-gpu reporting
0.0.1. The workflow must refuse rather than publish (acceptance
4), and acceptance asserts the binaries' output, not the manifests.
1.4 The existing v1.0.0 tag is stale and is not re-used
v1.0.0 is pushed and points at d3fa632 — the old release mirror's
head, 1,025 commits behind main. There is also an M8 milestone
tag. A v*-triggered workflow does not retroactively build for either,
and re-tagging would rewrite a published ref. The first real release is
v1.1.0, preceded by v1.1.0-rc.1 (§7).
1.5 CRDT is not optional for a useful release
default = ["luajit"]; luajit and lua54 are mutually exclusive;
crdt is opt-in on the root package, activated workspace-wide as
pmacs/crdt. README's documented build is the one to ship:
cargo build --release --workspace --features pmacs/crdt
This is not a size/quality trade — a non-CRDT build refuses --gpu
outright. run_gpu opens with a guard printing "pmacs: --gpu
requires pmacs built with --features crdt". #209 established the same
thing on the wire side: InstanceCapabilities::default advertises
multi_frontend / crdt_replica / semantic_render only under the
feature. A non-CRDT release ships an editor that cannot use the GPU
frontend shipped beside it.
1.6 The glibc floor — decided, and revision 1's reasoning corrected
Linux releases build on pinned ubuntu-22.04, with a stated support
floor of glibc ≥ 2.35.
Revision 1 implied that moving off ubuntu-latest buys reach "several
distro generations" including RHEL 9. It does not, and the arithmetic
matters:
| target | glibc | covered by a 22.04 build? |
|---|---|---|
| Ubuntu 22.04 (jammy) | 2.35 | yes — this is the floor |
| Debian 12 (bookworm) | 2.36 | yes |
| Ubuntu 24.04 | 2.39 | yes |
| RHEL 9 | 2.34 | NO — below the floor |
RHEL 9 is older than the floor, so it needs a lower-glibc container or cross-build. That is not a one-word runner change and is parked (§5). Stating the floor explicitly is what keeps this honest: a user on RHEL 9 should read "not supported yet," not discover a loader error.
References: runner images, jammy libc6, bookworm libc6.
1.7 Runtime dependencies are documented but never checked
README names /bin/sh, stty, coreutils, git and tar, and tells
packagers to encode them. Nothing checks any of them at runtime.
Out of scope here (§5), but the release notes carry the list rather than
assume a downloader reads the README.
pmacs-gpu additionally needs a working Vulkan/Metal adapter; there is
no software-rasterizer fallback in a shipped binary.
1.8 Runner pinning, and what is NOT established
Both runners are pinned, not just Linux: macos-latest currently
resolves to macos-15 (ARM64) and will drift — the same silent-choice
problem as §1.6, in the other direction, where a future move could
change the minimum supported macOS without a commit to point at.
- No release has ever been produced, so nothing here is verified end-to-end. Unlike #209 there is no existing behaviour to measure against; the RC is the first evidence.
- No Intel macOS (§5). arm64 only.
- No signing or notarization. A downloaded macOS binary is Gatekeeper-quarantined and needs an explicit override. Expected for an unsigned baseline; it belongs in the release notes rather than in a claim that macOS "just works".
2. Decisions (all questions resolved at approval)
- Q#D1 — versions.
pmacsandpmacs-gpu→ 1.1.0;pmacs-protocolstays 1.0.0. Acceptance asserts both shipped binaries report 1.1.0 and the tag isv1.1.0. - Q#D2 — Linux runner. Pinned
ubuntu-22.04; support floor glibc ≥ 2.35, covering Ubuntu 22.04 and Debian 12 but not RHEL 9. - Q#D3 —
pmacs-audit. Does not ship. - Q#D4 — Intel macOS. Not in the baseline. macOS runner pinned.
- Q#D5 — checksums. Ship them.
- Q#D6 — tests on release. Do not re-run the suite; assert the
tagged commit is an ancestor of
main, which catches the real mistake (tagging a branch) at negligible cost.
3. Bets
- Bet 1 — the release build is the CI build with different flags and will succeed first time.
- Bet 2 — the artifact, not the build, is where this goes wrong. The plausible failures are packaging-shaped: a stray binary, a split archive, a version mismatch, a glibc floor nobody notices.
- Bet 3 — one RC is enough evidence. A release either produces two runnable, correctly-versioned binaries at a URL or it does not.
4. Acceptance
- Pushing a tag matching
v*produces a GitHub Release with attached artifacts; no other trigger produces one.v1.1.0-rc.1is marked prerelease. - Each archive's complete member list is asserted, along with
executable bits, and the absence of
pmacs-audit,pmacs_fake_lspandpmacs_fake_mcp. Verified by listing the downloaded archive, not by trusting the build command. pmacsandpmacs-gpuare in the same directory within the archive, so an unpacked release is self-contained (§1.2a).- Version coherence, asserted from the binaries: the workflow
refuses to publish when the tag disagrees with the root crate
version, and the downloaded
pmacs --versionandpmacs-gpu --versionboth report 1.1.0. Verified by a deliberate mismatch, not by inspection. - The tagged commit is an ancestor of
main. - Each artifact is a CRDT build, verified by running the shipped binary rather than trusting the flag (§1.5).
- The Linux binary runs on Ubuntu 22.04 and Debian 12 — verified in containers — and its glibc floor is stated in the release notes.
SHA256SUMScovers every published artifact and verifies against the downloads.- Release notes carry the runtime dependencies (§1.7), the glibc floor, the arm64-only macOS scope, and the unsigned/Gatekeeper caveat.
README.md's install section offers the download path before from-source.
5. Parked (explicitly out of scope)
Named so they read as decisions. All are §17 requirements this stage does not meet:
- RHEL 9 and older glibc — needs a container or cross-build (§1.6).
- Intel macOS.
- Release channels, stable/nightly.
- Update-in-place and rollback.
- Signing and notarization.
- Reproducible builds.
- Package managers — Homebrew, AUR, nixpkgs, distro packages.
- Windows. Unsupported anywhere in the tree today.
- First-launch experience — §17's config-directory creation and optional-tool detection. That is §18 onboarding work.
- Protocol/package-API compatibility reporting.
6. Gates
The standing CLAUDE.md suite applies unchanged; this stage adds no
Rust logic and no test, beyond the version bumps which the whole suite
covers. Its real verification is the RC artifact — acceptance 2, 3,
4, 6, 7 and 8 are all performed against a downloaded archive, because
that is the only thing that tests a release.
7. Branch plan
One branch, distribution-stage1:
- Bump
pmacsandpmacs-gputo 1.1.0 (Q#D1). - Add
.github/workflows/release.yml—on: push: tags: ['v*'], pinnedubuntu-22.04+ pinned macOS runner, explicit--bintargets, explicit staged asset list, the tag/version assertion, the ancestor check, andSHA256SUMS. - Update
README.mdso download precedes build-from-source, and state the glibc floor and macOS scope. - Merge.
- Tag
v1.1.0-rc.1from the merge SHA, marked prerelease. Verify acceptance 2–8 against the published artifacts. - Tag
v1.1.0from that same verified SHA.
Steps 5 and 6 are the point of the ordering. A tag on a branch would publish a release from unmerged code, so the RC necessarily follows the merge — and it is cut from the merge SHA, so the final tag can reuse the exact commit the RC verified. If the RC finds a defect, it is fixed in a new PR and another RC is cut; the final tag is never the first live execution of this workflow.