feat(hu): make hu and its WASM plugins downloadable and usable - #301
Closed
YuanYuYuan wants to merge 21 commits into
Closed
feat(hu): make hu and its WASM plugins downloadable and usable#301YuanYuYuan wants to merge 21 commits into
YuanYuYuan wants to merge 21 commits into
Conversation
|
YuanYuYuan
force-pushed
the
feat/releasable-hu-plugins
branch
3 times, most recently
from
August 20, 2026 05:36
9d85204 to
b8643be
Compare
`cargo publish --workspace` reads each crate's version from its own manifest, so four manifests carrying their own literal was four chances to publish a release under a version the tag did not name. Inherit from the workspace instead. The readers that parsed a crate manifest for a version now read the workspace one.
A downloaded `hu` had no way to acquire a plugin: discovery reads a directory, and nothing put anything in it. `hu plugin install` takes a local path, a URL, or a name resolved against a release index, and `hu plugin uninstall` reverses it. Every install validates the component before it lands, so a file the host cannot load is refused rather than left to fail at dispatch. A checksum mismatch and a WIT world mismatch are both refusals, and the destination filename is sanitised, so a name taken from a URL cannot escape the plugin directory. The subcommand a plugin provides comes from its filename, so the installer strips the version a release asset carries. Without that, installing the published `hu_meter-0.1.0.wasm` created `hu meter-0_1_0` and left the documented `hu meter` nonexistent.
Three defects that only a shipped artifact could expose. `hu web` panicked on startup. Its plugin routes used axum 0.7 wildcard syntax against axum 0.8, and `Router::route` validates by panicking at run time, so it compiled and died the moment anyone ran it. CI never compiled the feature. The type-hash guard on the disk-schema path compared rendered strings. One renderer is cfg-gated and collapses every hash to a single constant, so the comparison could pass on anything. Compare values, and treat "no hash advertised" as a third state rather than a mismatch, so a peer that advertises none is not refused with advice no message definition can satisfy. Six tests pin the decision. The `hu-meter` plugin and the logger round out the set.
No release has ever contained a `.wasm`. The workflow built the `hu` binary only, and the plugins were built by the test job, exercised, and discarded. A user who downloaded a release got `No WASM plugins found`, and every `hu meter` and `hu monitor` command failed to dispatch. RELEASING.md claimed otherwise. `scripts/build-hu-release.nu` is now the single packaging step: it produces the binary tarballs, both plugins, an offline plugins tarball, a machine-readable index and a SHA256SUMS over the set, and it refuses to build when the tag and the crate version disagree. A tag is three different strings -- the release identity, the version the filenames carry, and the version the binary reports. They coincide for a normal release and diverge for a pre-release, which is why a pre-release is the only tag shape that can catch this class of defect. test-release-version-semantics.sh pins all three, and it needs no runner, no network and no build. The release is created as a draft, promoted only once an install from the published assets succeeds, and returned to draft if that verification fails.
`install-hu.sh` detects the platform, downloads the matching tarball, verifies it against SHA256SUMS, and installs the binary and both plugins. `--offline` does the same from a directory someone handed you, with no network. A checksum mismatch and a missing SHA256SUMS entry are both refusals: an unlisted file is exactly what a substituted file looks like. `curl --fail` keeps an error page from being written to disk and unpacked as a binary. No credential is embedded, and a token is read from the environment only. 22 assertions cover the refusals, since those are the paths that matter and the ones a happy-path test never reaches.
The acceptance bar for this branch: a user who downloads `hu` can run what the docs promise. The suite extracts every `hu` invocation from the tool docs and runs it against an installed binary in a scratch HOME, with no cargo, no repo on PATH and HU_PLUGIN_PATH unset. A command is classified in the doc itself, next to the promise, so a reviewer sees the reason. An unclassified command defaults to running, so the suite cannot quietly degrade into testing nothing. Running it is what found the defects fixed here, and the docs it corrects: `hu web` does not bind 0.0.0.0, the `hu meter hz` sample output was wrong, and a downloaded `hu` cannot publish at all, because message definitions do not ship. The Quick Start now states that prerequisite instead of implying a download is enough.
CI never compiled `web-plugins` and never ran `hiroz-union`'s tests. That is why a run-time panic in `hu web` survived: the file was never compiled, let alone executed. The crate already had 14 tests that had never run once -- `--lib` silently does nothing on a binary-only crate, so it needs `--bins`. Add the docs-repro suite, an aarch64 cross-compile leg, and the release dry-run, so the packaging path is exercised on a pull request instead of first running on a tag.
The disk-schema fallback needs the pieces of the graph and dynamic-message API that were previously internal. The examples gain node names. They were "Pub" and "Sub", so the documented output showing `/talker` and `/listener` could not be reproduced by running them -- a docs promise the code did not keep.
YuanYuYuan
force-pushed
the
feat/releasable-hu-plugins
branch
from
August 20, 2026 08:54
cb031e2 to
53205d5
Compare
build-binaries packages an aarch64-apple-darwin tarball and the release job publishes it, but nothing ever unpacked or ran it: smoke-test-binaries was a single ubuntu-latest job downloading bin-hu-x86_64-linux. A macOS tarball that did not install, or a hu that did not start, would have shipped with every check green. Make the job a matrix over ubuntu-latest and macos-latest, each pulling its own binary artifact and installing it with scripts/install-hu.sh. The plugins artifact is wasm32-wasip2 and platform-independent, so both legs consume the one produced by build-hu-plugins. Passing no target makes install-hu.sh's detect_target pick the tarball, so the macOS leg is also the first thing to exercise its Darwin/arm64 arm against a real release layout. The corrupted-asset refusal now damages the leg's own tarball; against a fixed target the macOS leg would have installed an intact one and proved nothing. Two spellings in the checksum step are Linux-only and had to go, or the macOS leg would have failed before testing anything about the artifact: find's GNU -printf, replaced by -exec basename, and sha256sum, which macOS does not ship. The tool is now picked at run time between sha256sum and shasum -a 256 — the same fallback install-hu.sh already makes, and the two emit and verify an identical format. aarch64-unknown-linux-gnu still has no leg: no GitHub-hosted runner can execute it. That gap is now the only one, and it is visible in the matrix.
release.yml runs only on a `v*` tag, so the macOS smoke leg it now carries cannot run here. Its first execution would be a real release, which is the wrong place to find out that the macOS tarball does not install. This job runs the same scripts on macos-latest: build-hu-release.nu packages a native aarch64-apple-darwin build with the tag-vs-crate guard armed, install-hu.sh installs it through the Darwin/arm64 path and the shasum fallback, and the installed binary must run and discover both plugins. A corrupted tarball must still be refused. It is allowed to fail the run. A red job here is the finding. REVERT BEFORE MERGE. The block is marked in the file.
…job) The macOS smoke job failed packaging with "can't find crate for `core`" while building the WASM plugins. The cause was a silently ignored input, not a toolchain problem. actions-rust-lang/setup-rust-toolchain takes `target`. It was given `targets`, which it reports as an unexpected input with a WARNING and then ignores, so no wasm target was ever installed. release.yml uses dtolnay/rust-toolchain, where `targets` is the correct name. This job copied the action from ci.yml's macOS legs, which need no wasm target, and the input name from release.yml. release.yml itself is correct and is unaffected. Still TEMPORARY. Revert before merge.
Both smoke-test-binaries legs failed on `cd: dist: No such file or directory`. actions/checkout cleans the working directory, and it ran after the two download steps, so it deleted the dist/ they had just populated. The ordering was wrong from the day the job was written. Nothing could say so: release.yml triggers only on a `v*` tag, and no tag had been cut from this branch, so the job had never run. The v0.1.0-rc14 rehearsal is what surfaced it, which is the reason to rehearse before merging rather than after. Same shape as the apt cache key in #308: a step that depended on the checkout ran before it and failed quietly in a way that read as something else.
Four gaps a first-time user hit, each closed at its source. The installer demanded a version. Its own header promised "default: the latest published" and the code died with "HU_VERSION is required" instead. It now asks the release host which release is newest. /releases/latest excludes drafts and pre-releases, so a bare install never lands on a rehearsal tag. The default download URL pointed at a tag prefix this project does not use. GitHub tags releases `v<version>`; the URL was built from `hu-v<version>`, a prefix that belonged to a channel this branch no longer carries, so every default install would have 404'd. `hu plugin install <name>` refused until the user exported a registry URL -- for the plugins the running binary was released with. It now defaults to the index published alongside its own version, pinned to CARGO_PKG_VERSION so a plugin always matches its host. `hu meter echo` subscribed and then printed nothing, which is correct on an idle topic and indistinguishable from a broken tool. It now says what it is waiting for, on stderr so piped output stays clean. Tests: the suite gains the piped `sh -s --` invocation, which no test covered -- every existing one runs the installer as a file, and piping differs in how `$0` resolves and what consumes stdin. Breaking only that path turns exactly the two new assertions red and leaves the other 22 green. 24 passed, 0 failed. Docs updated to match: the quickest path no longer passes a version twice, and the removed channel's tag shape is gone.
…RARY) rc15 showed withdraw-release correctly staying skipped when verification passes. That is half the claim. A job that never runs also never fires, so the other half needs a failure to observe, and the condition reads this job's result — nothing else can produce it. The step is last, so every real assertion still runs and only the exit status changes. Expected on rc16: the release is created, promoted, then returned to draft by withdraw-release. publish-crates stays skipped. REVERT IMMEDIATELY AFTER THE RUN.
…l (TEMPORARY)" This reverts commit cc9aa11.
It existed because release.yml runs only on a `v*` tag, so the macOS smoke leg could not be exercised by a pull request. The v0.1.0-rc15 and rc16 rehearsals ran that leg for real, on both platforms, so this job now duplicates coverage that the release pipeline provides. What it proved while it existed: build-hu-release.nu packages on macOS, install-hu.sh takes the Darwin/arm64 path and the shasum fallback, the installed binary runs and discovers both plugins, and a corrupted macOS tarball is refused. It also caught a typo'd action input -- `targets` given to an action whose input is `target`, dropped with a warning rather than an error.
Comments were 30% of this branch's added lines, and release.yml carried the most. Five blocks ran 12 to 18 lines each, restating history, prior defects and rationale that the pull request and #309 already hold. Two were not merely long. The draft/promote block began "derived from the tag shape, mirroring" and then stopped mid-sentence: the scrub that removed the second release channel took the rest of the line with it. And the installer step stacked two comments that contradicted each other, the first describing a piped invocation the code deliberately stopped using. Each block now states what the line does and what breaks without it. 65 lines out, 19 in.
Four blocks across three files, 12 to 31 lines each, restating defect history the pull request and #309 already hold. Each now states what the code does and what breaks without it. One was damaged rather than merely long. build-hu-release.nu's header read "Both / every release platform calls it, so they cannot drift / cannot drift apart" -- the scrub that removed the second release channel cut a sentence in half and left the remainder duplicated. That is the second such break found; release.yml carried the first. ros.rs 50 comment lines to 22, build-hu-release.nu 22 to 15, test-hu-docs-repro.nu 40 to 29.
The humble interop leg failed to compile hiroz's build script:
libstabby_macros-e78ef2ef160687eb.so: libc.so.6:
version `GLIBC_2.39' not found
A cached proc-macro built against glibc 2.39 cannot load in the humble
container, which is 22.04 and ships 2.35. humble is the only 22.04 image in
the matrix.
Not transient. A rerun restored the same entry, reported "Cache restored
successfully", and failed on the same object hash. The control is the sibling
ROS Tests humble leg: same image, different cache key, passing. So the fault
is the entry, not the image and not the code — the three commits before this
one changed comments only.
Bumping shared-key forces one cold rebuild for this leg. ci.yml already
carries two -v2 keys, so this failure mode has been met before.
The humble leg could not compile hiroz's build script:
libstabby_macros-...so: libc.so.6: version `GLIBC_2.39' not found
The cause is a second cache nobody declared. actions-rust-lang/setup-rust-toolchain
runs its own rust-cache unless told not to, and keys it on the JOB ID. That id
is `interop_test` for every distro in the matrix, so all four legs shared one
target/. humble is the only 22.04 image, so it restored proc-macros the 24.04
legs had built and failed to load them.
The log names it outright:
Restored from cache key "v0-rust-interop_test-Linux-x64-..." full match: true
An earlier commit bumped the shared-key of the *explicit* cache below, which is
already per-distro and was never the problem. That did not help, and the leg
failed again with the same object hash.
`cache: false` leaves only the per-distro cache. The -v2 suffix stays, to
discard anything saved while the shared cache was in use.
Blast radius: the embedded key carries OS and arch but not the container image,
so only a job matrixed over images on one platform can collide. The other four
call sites matrix over ubuntu and macOS, which the key already separates, and
rmw-zenoh-rs.yml pins a single image.
There were two copies, and they had already drifted. The smoke test used a run-time pick between sha256sum and shasum, and `find -exec basename`. The release job used a bare `sha256sum` and GNU-only `find -printf`, so it would fail on any host without coreutils. It works today only because that job runs on ubuntu. Both carried the same "capture the list before the redirect" warning, written twice in different words. scripts/ci/write-sha256sums.sh is now the only implementation. It refuses a missing directory, an empty one, and no argument, and it verifies what it wrote. The release job keeps its two content assertions, which are not shared: a release must cover a hu tarball and the installer. Folding exposed a coverage hole. Replacing the assembly with `true` left the detector green -- nothing asserted the asset set was checksummed at all. Two assertions now pin it, and both were seen failing: deleting a call site turns one red, reintroducing an inline `xargs sha256sum` turns the other red. 53 passed, 0 failed.
This was referenced Aug 20, 2026
This was referenced Aug 20, 2026
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.
Note
Superseded. Closed without merging. This branch shipped as seven smaller pull requests, all merged. Its content is on
main; nothing here is lost.A user can now download
huand its WASM plugins and run them. No repo clone, and no build.Where each part landed
hu plugin install/uninstallhutarget named in the default log filterhu websubscribereturns#297, #298 and #299 are closed by #311.
Why it was split
At 51 files and +4371 lines this mixed several deliverables, and review found defects in the slices that were invisible in the whole. Three examples, each found by reviewing one slice alone:
hu plugin installfrom a signed URL wrote the credential into the installed filenamev*tag would have failed and withdrawn its releaseBreaking changes
Recorded on the pull request that carries each one: the
hu meter echoexit status on #311, and thez_pubsubnode names on #320.