DIVE-3512: cut buzz-cli releases, a verified install path, and two defects found on the way - #3
Merged
Merged
Conversation
…nicking on wss://
Two defects that together made it impossible to pair a handset with the Buzz
mobile app against an HTTPS relay. Both measured against a live relay.
1. THE ENVELOPE
resolve_payload returned a bare bech32 nsec as PayloadType::Nsec in both arms.
Mobile _processPayload begins with `jsonDecode(payload) as Map<String, dynamic>`
and nothing in mobile/lib branches on payload_type, so a bare nsec dies on the
leading `n` with `FormatException: Unexpected character (at character 1)`.
That is verbatim what a real store build produced on a real handset.
--envelope-relay <https url> now emits the same shape the DESKTOP client sends
(desktop/src-tauri/src/commands/pairing.rs:145-148, PayloadType::Custom at :215):
{"relayUrl","pubkey","nsec"}. pubkey is DERIVED from the transferred nsec so the
two cannot drift. Without the flag the payload is unchanged, so CLI-to-CLI
interop testing keeps working exactly as before.
2. wss:// PANICKED BEFORE ANY PAIRING COULD START
thread 'main' panicked at rustls-0.23.42/src/crypto/mod.rs:249:14
Could not automatically determine the process-level CryptoProvider
Both ring and aws-lc-rs are reachable in the workspace, so rustls refuses to
choose. Plain ws:// never reaches that code path — which is why previous interop
testing, all of it against a plaintext relay, was structurally blind to it: the
defect exists only past the TLS an HTTPS deployment adds. Pin the ring provider
and install it at the top of main().
VERIFIED END TO END, not by inspection:
- cargo test -p buzz-pairing-cli: 7 passed, 0 failed, including an arm that
pins the bare-nsec failure and one that proves the envelope decodes.
- A full NIP-AB pairing driven over wss:// against a real relay: SAS matched on
both sides, target logged "Received custom payload!" carrying the three-field
envelope.
- Those exact transferred bytes then replayed through the mobile app's own
sequence: jsonDecode -> map; _validateRelayUrl accepts (https, public);
_validateCredentials NIP-42 handshake accepted the transferred identity in
46ms against the app's 8s budget.
- A real handset paired and got into the app.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
(cherry picked from commit 28e125f)
Adds a release rail for the server-side CLI. Upstream's release.yml is the desktop app: it triggers on desktop-v* tags, bundles on macOS, and is guarded to block/buzz, so it neither runs here nor produces /usr/local/bin/buzz. This is a small separate workflow with the same guard pointed at 5dive-ai/buzz. Builds on ubuntu-22.04 rather than ubuntu-latest so the glibc the artifact links against is old enough to run on 22.04 and 24.04 boxes alike, and records the commit, toolchain, sha256 and BuildID of every binary it emits. The install script verifies the download against the release's SHA256SUMS before touching /usr/local/bin, refuses to overwrite an existing binary without --force, and leaves the provenance on the box. The tree is current upstream main plus our one buzz-pairing-cli commit, so the release also carries buzz-pair with the JSON pairing envelope.
workflow_dispatch resolves only against the default branch, so as written the recipe could not be run until it had already been merged. A path-scoped push trigger on this branch builds and uploads the run artifact without cutting a release; cutting one still takes a deliberate dispatch that names a tag.
Our pairing commit (28e125f) added rustls as a direct dependency of buzz-pairing-cli to pin the CryptoProvider, but never updated Cargo.lock. The crate was already in the lock as a transitive dependency, so the omission is invisible to a plain `cargo build` and only surfaces under --locked, which is what a release build should use: error: cannot update the lock file ... because --locked was passed That means no tree carrying this patch can be built reproducibly, including 5dive-ai/buzz's own main, and upstream CI would reject the patch on the same grounds if it were ever sent there.
Lifted verbatim from 5dive-ai/5dive-chat PR #3 (merged 3e871d9), which fixed the identical defect on the sibling fork. It applied to this tree with no conflicts. A fork inherits upstream's publish workflows, and their IMAGE_NAME falls through to `ghcr.io/block/*` whenever the GHCR_IMAGE / GHCR_SPRIG_IMAGE override variables are unset — which they are here (actions/variables total_count = 0). So the first ever push to this fork's main fired six inherited workflows, and Sprig image attempted a write to ghcr.io/block/buzz-sprig as 5dive-bot. It failed on a missing credential. That is a missing credential, not a control: the boundary held by accident. Every registry write in docker.yml and sprig-image.yml now fires only on a release tag push or a deliberate workflow_dispatch, the same shape helm-chart.yml already used here. Builds on main and PRs are untouched, so the compile signal survives. sprig.yml additionally learns to CREATE its rolling release rather than only edit it, because a fork inherits no releases and `gh release edit` exits 1 the first time. Audited the rest: push-gateway-helm-chart.yml triggers on push-chart-v* tags only and helm-chart.yml already gates its publish job on chart-v*, so those two need no change.
The paths filter meant a commit that changed Cargo.lock or a crate did not rebuild, which is precisely the change most worth rebuilding on. The branch is short-lived; every push to it should be exercised.
…ldID The build went green and the smoke failed, on my own step: `buzz --version` is a usage error, because buzz-cli's clap command sets no `version` attribute. Fixing that upstream-side would be a one-word divergence for a string the workspace stamps 0.1.0 on every crate, so it is not worth carrying; the binary's real identity is its BuildID, which readelf reads out of the file and which the release already records. So the install script now prints BuildID rather than a version that would always have read 'version unavailable', and the smoke is stronger than the one that failed: it requires the CLI to DISPATCH, not merely print help, by running a subcommand against an unreachable relay and demanding exit 2 — the CLI's own documented 'relay/network error' code — and it requires both binaries to carry a readable BuildID at all.
…exit code
My previous smoke demanded exit 2 from `buzz channel list` against an
unreachable relay and got 1. The binary was right and the assertion was wrong:
with no BUZZ_PRIVATE_KEY the call is rejected as bad input and never reaches
the network, which is exactly what exit 1 means in the CLI's own table.
So assert the contract the CLI actually documents — errors are
{"error","message"} JSON on stderr — across two cases, one without an
identity and one with a throwaway key against a dead port, and print both
observed payloads rather than asserting blind again.
The panic check is the part worth keeping: the defect our pairing patch fixes
is a rustls CryptoProvider PANIC on wss://, and a panic is precisely what does
not produce that JSON. This makes the release smoke a regression guard for the
class of bug the patch exists to fix.
…the class
The run went green and proved nothing. Both error-contract checks invoked
`buzz channel list`; the subcommand is `channels`, so both died in clap with
"unrecognized subcommand 'channel'". A usage error is still {error,message}
JSON on stderr, so every assertion I wrote was satisfied without the CLI
dispatching anything or touching the network.
Fixed the subcommand, and added the guard that matters more than the fix: a
check whose stderr is a clap usage error now FAILS as a broken smoke rather
than passing as a good binary. The unreachable-relay case additionally
requires an error category other than user_error, so it cannot be satisfied by
anything that stops at argument validation.
The artifacts from the vacuous run were otherwise sound — buzz 16788888 bytes
sha256 01b478d6..., buzz-pair 6675408 bytes sha256 b68699ad... — but a release
should not be cut off a smoke that graded nothing.
With the subcommand fixed the check finally reached real code and reported
rc=3 auth_error — {"error":"auth_error","message":"BUZZ_PRIVATE_KEY is
required..."} — where I had asserted 1. The CLI classifies an absent key as an
auth failure rather than as bad input, which is not what its own exit-code
table reads like at a glance.
That is the guard working: two runs ago this same case 'passed' at rc=1 while
dying in clap. Pinning the observed 3 rather than relaxing the assertion,
because a caller branching on this code would care if it moved.
Both contract checks now reach real code and report what they should:
no private key: rc=3 {"error":"auth_error",...}
unreachable relay: rc=2 {"error":"network_error","message":"tcp connect
error: Connection refused (os error 111)","retryable":true}
The second one opens a socket to a dead port and fails there, which is the
dispatch evidence the earlier vacuous run never had.
The step still died, on the follow-up category check: it piped the CLI into jq
under `set -o pipefail`, so the pipeline inherited the CLI's exit 2 — the very
outcome being measured — and `set -e` killed the step. Capture into a variable
first, parse second.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
DIVE-3512: a release rail for buzz-cli, a verified install path, and two defects found on the way
Customer boxes are configured with
"buzz_path": "/usr/local/bin/buzz"and nobox has ever had that file —
5dive agent buzz status <name>returns rc 3 onevery correctly-wired seat. This adds the build and distribution path that
closes that, on top of current upstream rather than on our 63-commit-stale
fork.
What the branch is
Current upstream
block/buzzmain (f956e6f) plus our one pairing commitcherry-picked onto it, plus this work. The cherry-pick was the question main
asked to answer before building anything, and the answer is clean: zero
conflicts, and
git diff <picked> ourfork/main -- crates/buzz-pairing-cliis empty, so the rebased crate is byte-identical to the fork's. Building from
upstream+patch loses nothing and picks up 63 commits of upstream movement.
Two defects this turned up, both worth more than the artifact
1. Our pairing patch cannot build reproducibly, and never could.
28e125faddedrustlsas a direct dependency ofbuzz-pairing-clito pinthe CryptoProvider, and never updated
Cargo.lock.rustlswas already in thelock as a transitive dependency, so a plain
cargo buildresolves it and theomission is invisible. Only
--lockedsurfaces it:This is live on
5dive-ai/buzzmain today, not only on this branch, andupstream CI would reject the patch on exactly these grounds if it were ever
sent there. The fix here is the missing half of that commit, not build
scaffolding:
rustlsis added tobuzz-pairing-cli's dependency list inCargo.lock, and the release build keeps--lockedrather than dropping theflag to buy a green tick.
2. This fork's inherited workflows publish into Block's namespace.
Syncing this fork's main to upstream was the first ever push to
mainhere,and it fired six inherited workflows.
IMAGE_NAMEfalls through toghcr.io/block/*wheneverGHCR_IMAGE/GHCR_SPRIG_IMAGEare unset, andactions/variableson this repo istotal_count = 0, so "Sprig image"attempted a write to
ghcr.io/block/buzz-sprigas5dive-bot. It failed on amissing credential — which is a missing credential, not a control.
The fix is ported verbatim from the sibling fork's
5dive-chatPR #3(DIVE-3531, merged
3e871d9c); it applied here with no conflicts. Everyregistry write in
docker.ymlandsprig-image.ymlnow fires only on arelease tag push or a deliberate
workflow_dispatch— the same shapehelm-chart.ymlalready used in this repo. Builds onmainand on PRs stillrun, so the compile signal is intact; they simply cannot write.
sprig.ymladditionally learns to create its rolling release rather than only edit it,
because a fork inherits no releases and
gh release editexits 1 the firsttime.
The two
ghcrwriters that port does not touch were audited rather thanassumed:
push-gateway-helm-chart.ymltriggers onpush-chart-v*tags only,so a
mainpush cannot fire it, andhelm-chart.ymlalready gates its publishjob. Neither needs a change.
The release rail
.github/workflows/buzz-cli-release.ymlbuildsbuzzandbuzz-pairforLinux x86-64. It is a new workflow rather than a change to
release.ymlbecause upstream's release rail is the desktop app: it triggers on
desktop-v*tags, bundles on macOS, and every job is guarded to
block/buzz. It neitherfires here nor emits a server-side CLI. This one carries the same guard pointed
at
5dive-ai/buzz, so a re-fork cannot fire it either.ubuntu-22.04, notubuntu-latest. The artifact is dynamically linked;built on 24.04 (glibc 2.39) it will not start on a 22.04 box. Built on 22.04
(glibc 2.35) it runs on both.
buzz-cli, but the pairing patch isentirely inside
buzz-pairing-cliandbuzz-pairis how a handset joins.Shipping only half leaves the mobile client with no counterpart.
toolchain, and each binary's size, sha256 and BuildID into a
PROVENANCE.txtthat ships with the release. An artifact that cannot say what commit produced
it must not reach a customer box.
and uploads the run artifact and stops.
cli-v*was checked against all fivetag prefixes this repo triggers on (
relay-v*,sprig-v*,chart-v*,push-chart-v*,desktop-v*) and collides with none; this workflow carriesno tag trigger at all.
The install path
scripts/install-buzz-cli.shdownloads a named release, verifies both binariesagainst the release's
SHA256SUMSbefore anything is copied into place,prints the provenance it is about to install, and records it at
/var/lib/buzz/installed-provenance.txtso the box itself knows what it isrunning.
It refuses two things on purpose. It will not accept "latest" — a tag is
required, because an installed build nobody can name is the defect this whole
row exists to fix. And it will not overwrite an existing binary without
--force, printing that binary's BuildID instead: there is an unattributedbuzzof unknown provenance on the control-plane host today, and the scriptshould make a human look at it rather than silently replace it.
Not covered here
The last step of DONE MEANS —
5dive agent buzz status <name>going rc 3 torc 0 on a customer box — is not demonstrated by this PR. It needs shell on
sure-redwood, and the ssh key for that exists on two seats on the controlplane, neither of which is the one that wrote this. It is handed back as its
own row naming that capability.