release-assets.json manifest
Every published GitHub release uploads a release-assets.json file
alongside the per-target archives. It is the consumer contract for
downstream packagers — fetch-harn.sh scripts, npm CLI postinstall
hooks, Homebrew/Scoop formula generators —
so they can verify and download the right binary without scraping the
releases API.
The manifest is generated by
scripts/build_release_assets_manifest.harn, invoked from the
Generate release-assets.json step in
.github/workflows/build-release-binaries.yml. The manifest is in the
expected-assets list, so a release with a broken or missing manifest
fails the workflow early.
Schema#
{
"version": "0.8.21",
"tag": "v0.8.21",
"release_url": "https://github.com/burin-labs/harn/releases/tag/v0.8.21",
"assets": {
"x86_64-pc-windows-msvc": {
"filename": "harn-x86_64-pc-windows-msvc.zip",
"url": "https://github.com/burin-labs/harn/releases/download/v0.8.21/harn-x86_64-pc-windows-msvc.zip",
"sha256": "<lowercase hex>",
"size": 12345678,
"format": "zip",
"binaries": ["harn.exe", "harn-dap.exe", "harn-lsp.exe"]
},
"aarch64-apple-darwin": {
"filename": "harn-aarch64-apple-darwin.tar.gz",
"url": "...",
"sha256": "...",
"size": 12345678,
"format": "tar.gz",
"binaries": ["harn", "harn-dap", "harn-lsp"]
},
"x86_64-unknown-linux-gnu": {
"filename": "harn-x86_64-unknown-linux-gnu.tar.gz",
"url": "...",
"sha256": "...",
"size": 12345678,
"format": "tar.gz",
"binaries": ["harn", "harn-dap", "harn-lsp", "harn-container-probe"]
}
}
}
| Key | Meaning |
|---|---|
version | Semver without the v prefix. Matches Cargo.toml's workspace version. |
tag | Git tag, with the v prefix. |
release_url | Human-readable releases-page URL. |
assets | Map keyed by Rust target triple. |
assets[triple].filename | Archive file name. |
assets[triple].url | Deterministic GitHub-releases download URL. |
assets[triple].sha256 | Lowercase hex SHA-256 of the archive bytes. |
assets[triple].size | Archive size in bytes. |
assets[triple].format | "tar.gz" or "zip". |
assets[triple].binaries | List of files unpacked from the archive. |
Linux archives also include harn-container-probe, which the release
container image uses for its distroless health check. Installer scripts
may ignore that extra binary unless they are assembling the container
root filesystem.
Generated Agents API SDK clients are a separate release contract. They are
not entries in release-assets.json. Downstream SDK repositories pin a
Harn tag and download harn-sdk-python.tar.gz plus
harn-sdk-typescript.tar.gz from that GitHub release. The retrieval path
and fail-closed missing-language check live in spec/openapi-sdk-surface.md.
The currently published target triples are:
aarch64-apple-darwinx86_64-apple-darwinx86_64-unknown-linux-gnuaarch64-unknown-linux-gnux86_64-pc-windows-msvc
Stable URLs#
The manifest is published as a release asset with two stable URLs:
- Pinned:
https://github.com/burin-labs/harn/releases/download/<tag>/release-assets.json - Latest:
https://github.com/burin-labs/harn/releases/latest/download/release-assets.json
The pinned URL is the one downstream packagers should embed in checked-in version files; the latest URL is for tooling that wants to follow the moving target.
Integrity metadata versus build provenance#
SHA256SUMS and release-assets.json are consumer integrity metadata. They
let a downloader detect corruption or substitution relative to the metadata it
fetched, but they do not prove which source revision or workflow produced an
archive.
Before merge, build-release-binaries.yml candidate_only builds, signs,
notarizes, packages, and attests the five-target matrix against the exact
candidate SHA, then uploads fail-closed run-scoped evidence plus
candidate-archive-manifest-<sha>.json. That path certifies the candidate and
never mutates a GitHub release.
After the Release PR squash-merges, the signed tag selects that main commit.
The tag-triggered workflow builds the five release archives from the immutable
tag source, regenerates SHA256SUMS / release-assets.json, and publishes the
container. Candidate archives are evidence about the pre-merge tree, not a
substitute for the source commit that actually landed.
Historical release tags remain immutable at their original commits. The main-ancestry invariant applies to tags created after this release-flow cutover; automation never moves an existing public tag to retrofit it.
Every new release archive therefore also has a GitHub artifact attestation with
predicate type
https://harnlang.com/attestations/release-archive/v1. Candidate-phase
predicates bind the archive digest and filename to the target triple, source
commit, workflow run/job, and exact build-policy revision without a tag.
Finalization accepts only archives whose attestations resolve to the same
GitHub-verified signed tag commit. Recovery runs may combine archives from
multiple workflow runs, but all five retain that one immutable source identity.
Releases through v0.10.40 predate attestations and fail closed by default. A
recovery operator may provide legacy_provenance_override only as an explicit
workflow-dispatch input. Later releases cannot use the override. The JSON must
bind the exact tag and peeled source commit, include an audit reason, and list
the SHA-256 of each unattested archive:
{
"tag": "v0.10.16",
"sourceCommit": "ceaa1b048cef75cb1b280cb4bc7213014f4fe755",
"reason": "Compared with retained artifacts from the original successful jobs.",
"archives": {
"harn-aarch64-apple-darwin.tar.gz": "<lowercase sha256>"
}
}
Attested rebuilt archives are still verified normally; the override admits only the listed legacy bytes and is surfaced as a workflow warning.
Adding a new target triple#
- Add the target and its warm, primary, recovery, standard, and fast runner
labels to
.github/release-runner-policy.json, then extendscripts/tests/release_runner_matrix_test.harn. - Add the same target to the
EXPECTED_ASSETSlist in the workflow'ssetupjob and to the per-step expected lists in thereleasejob. - Add a new entry to the
TARGETSmap inscripts/build_release_assets_manifest.harnwith the matching filename, format, and binary names. - Make a note in
CHANGELOG.mdso downstream consumers know to bump their pinned manifest URL.
The runner policy owns build-matrix targets and TARGETS owns manifest
packaging. The workflow fails loudly if those contracts drift because the
resolver tests and expected-assets checks cover both sides.