ci(bundle): compose and publish the bundle manifest #3
Reference in New Issue
Block a user
No description provided.
Delete Branch "ci/bundle-compose"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Phase 0 item 3 of
docs/installer/PLAN.md(§7.1–§7.3): "Compose job (read both repos' latest releases → run the two gates → publishbundle.json), the nightly cron, and the dispatch step appended to each component's release workflow."Companion PRs (the dispatch half): RunicGateway/link#25, RunicGateway/servuo-plugins#9. Docs: RunicGateway/docs#85.
Why
The installer resolves what to install from the bundle — it hardcodes no versions and does not resolve "latest" at run time — so this has to exist before Phase 1 code is useful. It was blocked until both components had releases; they now do (
linkv1.1.0,servuo-pluginsv0.1.1), which is what unblocked it.What landed
.gitea/workflows/bundle.yml— triggered byworkflow_dispatch(POSTed by each component's release job) and a nightly cron. It resolves both components' latest releases, runs the two compose-time gates, and commitsbundles/current.json.The first real bundle is committed, so the manifest exists ahead of the binary that reads it:
Note
link.assetsis a map keyed by platform, not the singlesha256PLAN.md §7.1 sketched: link publishes a Linux binary and a Windows.exeand the installer runs on both, so one hash could only ever have described one of them.The decisions §7 left open
Bundles are committed under
bundles/, not published as Gitea releases. This was the one genuinely open question. This repo's own releases are the installer binaries, and/releases/latestreturns whichever release is newest regardless of kind — interleaving bundle releases would make "latest" intermittently resolve to a release carrying no installer binary. Committing also gives a reviewable diff and a git history of the compat matrix, and needs no new branch-protection exception:release.yml's version-bump commit already requires the CI user to be able to push tomain. The installer's fetch stays a plain anonymousGETon a public repo, which it must be — the shard host has no Gitea credentials.Gate 1 reads the sidecar's protocol from source at the release tag, not from the binary.
--print-config(Phase 0.2) would answer authoritatively, but only for releases fromv1.1.0onward, and--bundle <tag>has to be able to recompose a bundle from an older pair. Readingsidecar/src/main.rsat the tag the release was built from works uniformly, needs no execution of a downloaded artifact, and does not provision a throwaway config whose auth token would then be sitting in a CI log. A constant that has moved or been renamed is a hard failure — treating "could not read" as "matches" is exactly how a mismatched pair ships.Gate 2 records the hash CI computed itself, after verifying the download against the publishing repo's
SHA256SUMS. It also asserts the reverse direction — an asset with noSHA256SUMSentry — becausesha256sum -cpasses right over a file the sums file does not mention. These artifacts are deliberately unsigned, so the checksum is the entire trust anchor; a hash copied from a file nobody verified would make the chain decorative.Release metadata is read anonymously, on purpose. Those are exactly the requests the shipped installer makes on a host with no credentials, so a repo flipped to private fails CI here instead of on an operator's machine.
An unrecognized asset name is a hard failure. link's binaries are mapped onto platform keys by suffix; adding a target (aarch64, macOS) to its release workflow therefore reddens this job rather than silently omitting the new binary from every bundle.
A run that changes nothing writes nothing. The comparison excludes
bundleandgenerated, which are metadata about the run — without that the nightly cron would commit a dated duplicate of the same matrix every morning.Stale components are dispatched, never awaited (§7.3). Gitea's dispatch endpoint returns no run handle, so a waiting job would have to guess which run is its own while holding a runner idle. "Ahead of its release" counts only releasable commits and excludes merge commits, whose subject is
Merge pull request '<the real subject>'— without that exclusion a merge of adocs:branch whose title quotes afix:would re-dispatch, every night, a release workflow that correctly declines to run.Verification
The workflow's compose steps were extracted and run against the live releases in a Linux container (matching the runner, not Git Bash):
bundles/bundle-2026.08.04.jsoncommitted here;changed=falseand writes nothing;SHA256SUMS;protocol=3fromsidecar/src/main.rs@v1.1.0andprotocol=3frommanifest.jsoninside the overlay tarball;manifest.versionvs the release tag) passes onv0.1.1.Stale-detection was tested against real compare data and synthetic ranges:
link v1.0.0...mainfeat:; the merge quoting it and theci:correctly ignored)link v1.1.0...mainservuo-plugins v0.1.1...mainrefactor!:, bodyBREAKING CHANGE,fix:+ noisedocs:+ its merge commitAll four workflow YAML files parse.
AI-assisted contribution
Co-Authored-Bytrailer.