feat(hu): ship the plugins a release needs to be usable - #312
Merged
Merged
Conversation
|
YuanYuYuan
force-pushed
the
feat/hu-release-pipeline
branch
from
August 21, 2026 08:18
feb9a5c to
d4f172d
Compare
YuanYuYuan
force-pushed
the
chore/hu-version-hygiene
branch
from
August 21, 2026 08:18
06b7336 to
c7b3218
Compare
No release has ever contained a .wasm file, so a downloaded hu could not run any documented `hu meter` or `hu monitor` command: plugin dispatch had nothing to dispatch to. RELEASING.md claimed the pipeline built them. Adds the artifact set (binary tarballs, both plugins, an offline plugins tarball, a JSON index, the installer and SHA256SUMS), one packaging script both release platforms call so they cannot drift, a POSIX installer that verifies every download and refuses a mismatch, and `hu plugin install` / `uninstall` from a path, a URL or the release index. Discovery also learns a prefix-relative directory. Installing with a --prefix wrote plugins where discovery never looked, so the install reported success and `hu plugin list` was empty.
YuanYuYuan
force-pushed
the
feat/hu-release-pipeline
branch
from
August 21, 2026 19:34
d4f172d to
099c423
Compare
F1 — the release workflow called a script this branch does not carry. `smoke-test-release-install` ran `scripts/test-hu-docs-repro.nu`, which the docs-coverage branch adds, not this one. Every `v*` tag would build, promote the release, fail that step, withdraw the release back to draft and skip crates.io. No release could complete. The workflow triggers on tags only, so no pull request ever executed those lines. The docs-reproduction step moves to the branch that owns the script, so each lands with what it needs. The job keeps its two real checks: an install from the published assets, and the documented installer URL serving this tag's installer. F2 — `hu plugin install` could never authenticate. `http_get` appended `-H` after `--`. curl reads everything after `--` as a URL, so the flag and the header became two more URL operands. Measured with curl 8.21.0: exit 3 and `Could not resolve host: -H` with the header after `--`, exit 0 with it before. Over http(s) curl also resolved a name derived from the token. Every URL and registry install failed whenever HU_RELEASE_TOKEN was set, and the error blamed the download. No test set that variable, which is why nothing caught it. `curl_args` now builds the list, and three tests pin the order.
There was a problem hiding this comment.
Pull request overview
Adds complete hu release packaging, installation, plugin management, documentation, and release verification.
Changes:
- Packages binaries, WASM plugins, registry indexes, checksums, and installer assets.
- Adds plugin install/uninstall commands with validation and metadata.
- Expands release automation, smoke tests, and installation documentation.
Reviewed changes
Copilot reviewed 15 out of 16 changed files in this pull request and generated 18 comments.
Show a summary per file
| File | Description |
|---|---|
.github/workflows/release.yml |
Builds, publishes, verifies, and withdraws releases. |
Cargo.lock |
Records the checksum dependency. |
RELEASING.md |
Updates release procedures. |
crates/hiroz-union/Cargo.toml |
Adds sha2. |
crates/hiroz-union/src/main.rs |
Exposes version and plugin management commands. |
crates/hiroz-union/src/plugin/install.rs |
Implements plugin installation and removal. |
crates/hiroz-union/src/plugin/mod.rs |
Exports the installer module. |
crates/hiroz-union/src/plugin/wasm/mod.rs |
Adds prefix-relative discovery and shared helpers. |
crates/hiroz-union/tests/plugin_install.rs |
Adds plugin refusal-path tests. |
crates/hiroz-union/wit/v0.1/hu-plugin.wit |
Clarifies transport failures. |
docs/tools/hu-install.md |
Documents installation workflows. |
docs/tools/hu-plugins.md |
Documents plugin management. |
mkdocs.yml |
Adds installation documentation navigation. |
scripts/build-hu-release.nu |
Builds the release artifact set. |
scripts/ci/write-sha256sums.sh |
Generates release checksums. |
scripts/install-hu.sh |
Installs verified binaries and plugins. |
Suppressed comments (5)
crates/hiroz-union/src/plugin/install.rs:348
nameis inserted into a path without validation. For example,hu plugin uninstall /tmp/victimmakes the third candidate/tmp/victim.wasm, so this command can delete a.wasmfile outside the managed plugin directory. Reject any name changed by the existing sanitizer before constructing candidates.
pub fn uninstall(name: &str) -> Result<PathBuf> {
let dir = install_dir()?;
let candidates = [
dir.join(format!("hu_{name}.wasm")),
dir.join(format!("hu-{name}.wasm")),
crates/hiroz-union/src/plugin/install.rs:264
- A direct URL install records the literal
"url"as its plugin version, so both text and JSON listings exposeurlas if it were a release version. Keep the URL as provenance, but represent the unavailable version as absent rather than inventing a version value.
return accept(&bytes, name, expected.as_deref(), source, "url");
RELEASING.md:50
- The documented default behavior is not implemented:
scripts/test-release-workflow.nu:18still setsDEFAULT_TAG = "v0.0.0-smoke-test". With the new core-version guard, running the command shown above fails packaging as soon as the workspace version differs from 0.0.0. Update the script to derive the workspace version as claimed (and update its usage examples).
This pushes `v<crate-version>-smoke-test` — e.g. `v0.1.0-smoke-test` — waits for all CI jobs to pass (builds, smoke tests, release creation), then reports the result. The script requires `gh` CLI authenticated to the repo.
The tag carries the current workspace version deliberately. `build-hu-release.nu` cross-checks a tag's core version against it and fails the build on a mismatch, so a fixed tag like `v0.0.0-smoke-test` dies at the first packaging step. The `-smoke-test` suffix makes it a semver pre-release, so it publishes as a pre-release and skips the crates.io step.
RELEASING.md:19
- This independent cadence is not supported by the workflow. It triggers on
v*, strips only a leadingvwhen deriving the crate version, and every non-prerelease run executescargo publish --workspace; ahu-v*tag fails the core-version guard, while reusingv<workspace-version>cannot create a second release and would republish the same crates. Either implement a hu-specific tag path that skips workspace publication or remove this claim.
`hu` keeps an independent release *cadence* through its own `hu-v*` tags — you can cut a `hu` release between workspace releases — but not an independent *number*.
docs/tools/hu-install.md:68
SHA256SUMSnow covers every release asset, so this command fails with “No such file” for all wheels, libraries, and other-platform archives a manualhuinstaller did not download. Show how to select and verify only the two downloaded tarball entries, or explicitly require downloading every listed asset.
Verify the download first. `sha256sum -c` exits non-zero on a mismatch, so stop here if it does — do not extract a file that failed this check:
<!-- repro: skip needs the release tarballs and their SHA256SUMS already downloaded -->
```bash
sha256sum -c SHA256SUMS
</details>
---
💡 <a href="/ZettaScaleLabs/hiroz/new/main?filename=.github/skills/code-review/SKILL.md" class="Link--inTextBlock" target="_blank" rel="noopener noreferrer">Add a `code-review` agent skill</a> or configure MCP servers for context-aware, tailored reviews. <a href="https://docs.github.com/en/copilot/how-tos/use-copilot-agents/request-a-code-review/use-code-review#mcp-servers-and-agent-skills" class="Link--inTextBlock" target="_blank" rel="noopener noreferrer">Learn more in the docs.</a>
Metadata was recorded in the wrong fields. `accept` took the path or URL as `source` and the literal "local"/"url" as `version`, so `hu plugin list` printed `VERSION local` and `source_label` classified the entry by sniffing a path. `source` now holds the kind, `version` is an `Option` that only the registry index fills, and `origin` holds where it came from with the userinfo and query string removed -- a signed asset URL carries a token there, and `--json` prints it verbatim. `hu plugin list` matched metadata by name alone. Discovery returns the same name from `$HU_PLUGIN_PATH`, the executable-relative prefix and the managed directory, so a development build was labelled with the installed plugin's version. It now matches the managed file path too. Other findings, each independent: - An empty `HU_RELEASE_TOKEN` was treated as a credential and sent as `Authorization: token `, turning an anonymous public download into a 401. The shell installer already treated empty as absent. - The registry index declared a schema version that nothing checked, so `schema: 2` was read as schema 1 whenever the JSON still deserialised. - `uninstall` discarded the result of persisting the install record. The file was already gone, so a failed write left an entry that the next install of that name would inherit, and the command reported success. - The "installed elsewhere" error named `$HU_PLUGIN_PATH` even when the plugin sat in the executable-relative prefix and that variable was unset. It now names the directory that actually holds the file. - `resolve_latest` sent no credential, so a private host answered 401 before any authorised download was attempted. It also had no timeout. - `HU_RELEASE_BASE` without a version asked this project's API for its newest release and named files after it. The installer now refuses that combination instead of 404ing against a correct directory. - One handler served EXIT, INT and TERM. Returning 0 from a signal trap consumes the signal, so a Ctrl-C between two commands deleted the staging directories and let the install continue. The signal traps now exit. - `withdraw-release` compared the verification result to 'failure'. A cancelled or skipped job reports neither, so an unverified release stayed public with the Latest badge. Any non-success now withdraws it. - RELEASING.md promised an enforcement script that does not exist, and documented a `hu-v*` tag namespace that no workflow accepts. The installer's comments and its pre-release example said `hu-v` too. Not changed: the `find -maxdepth` report. That is a BSD extension macOS provides, and the script already avoids the GNU-only spellings.
Every test in this file was a refusal. No test had ever completed an install and looked at the result, which is why the metadata defects in the previous commit were found by review instead. The obstacle was real but did not apply. `hiroz-union` cannot build a plugin, because the plugins are a separate, excluded, wasm32-wasip2 workspace. `validate_plugin_static` only compiles the file as a component, so an 8-byte empty component -- the magic, the component version and the layer -- drives install, list and uninstall end to end. It exports nothing and can never be dispatched, and no test claims it can. Adds five: a local install lands the file and records provenance, a URL install verifies its sidecar, a signed URL's query string never reaches installed.json, a registry install records the version the index states, and uninstall removes the file and the record together. `registry_install_without_a_registry_says_how_to_configure_one` removed the registry override, so it fell back to the real GitHub index and made an external request. It also would have started failing, rather than erroring, once a release exists for this CARGO_PKG_VERSION. It now points at a loopback port that serves 404. Also corrects the module doc. It said the success paths lived at the end of scripts/ci/hu-tests.sh. That script contains no `hu plugin install` line, and grepping for one is how the claim was caught.
The tarball's README told the reader to run `sha256sum -c SHA256SUMS`. That file covers every asset in the release, and a tarball holds only some of them, so the command reports the absent ones as failures. It now gives `--ignore-missing` and a single-file form for older coreutils. Applies the ASD-STE100 sentence rules to the comments this branch adds. Measured over the added comment lines: passive constructions 10 to 4, sentences joining two independent clauses 1 to 0, longest 15 words.
`url_install_does_not_persist_a_credential_from_the_source_url` sent `?token=SUPERSECRET`, and the fixture server routed on the whole request target. The route table therefore missed and the server answered 404. It now routes on the path, as a real asset host does with a query string it does not recognise. `uninstall_explains_when_the_plugin_lives_on_hu_plugin_path` asserted the old wording. That message named `$HU_PLUGIN_PATH` unconditionally, which is wrong when the plugin sits in the executable-relative prefix and the variable is unset. The message now names the directory that holds the file, so the test asserts on that directory and on the absence of the "not installed" wording.
`hu plugin install <url>` derived the file name from the whole URL. A signed asset URL carries its token in the query string, so the installed file was named `hu_meter.wasm?token=...` -- the credential written into a path on disk, where nothing redacts it. The previous commit stopped the token reaching `installed.json`; it still reached the filesystem. The sidecar URL was wrong for the same reason. Appending `.sha256` to the full URL asks for `....wasm?token=....sha256`, which a host that ignores unknown query parameters answers with the asset itself. The installer then read the component bytes as a checksum and refused the install. Both now use the URL's path, with the query re-attached for the sidecar request. The credential test asserts the installed filename as well as the record, because a filename is what the record fix did not cover.
This was referenced Aug 22, 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.
Replaces #301, together with the slices already merged. This now targets
main.The version-hygiene slice it used to stack on is merged as #310, so the
sha2dependency it needs is already inmain.#313 and #315 sit on this branch. Both retarget to
mainwhen it merges.What this does
Makes a downloaded
huable to run its own plugin subcommands.scripts/build-hu-release.nudist/. It is the single place that names and packages an asset, so a second release platform cannot drift from this one. Fails the build when the tag's core version disagrees with the cratescripts/install-hu.shSHA256SUMSand refuses a mismatch.--offlineneeds no network and no credentialhu plugin install/uninstall.wasmgiven as a path or a URL declares no world, so nothing checks oneWhat fails without this
No release has ever contained a
.wasmfile. On a machine that downloaded one:RELEASING.mdclaimed the pipeline builthu_meter.wasmandhu_monitor.wasm. It did not.Defects found by running the installed artifact
Every one lived in a path no test executed, and none would have failed a build.
EXITtrap ended in a falsy command underset -eSHA256SUMSwritten as./namematched nothinghu_meter-0.1.0.wasmcreated the subcommandmeter-0_1_0hu meternonexistent--prefix /opt/huwrote plugins to/opt/hu/share/hu/pluginsand discovery only looked under$HOME, so the install reported success andhu plugin listwas emptyinstall-hu.shwas never publishedBreaking Changes
hu-install.md--versionto pin oneThe existing
v*release keeps its behaviour and gains the plugin assets, the index and the installer.