Add sibling publish.yaml jobs (scoop-bucket, homebrew-tap) that render a manifest from the release .sha256 sidecars and idempotently push it to the org-level lazyeval/scoop-bucket and lazyeval/homebrew-tap repos, using the scoped lazyeval-ci bot token (LAZYEVAL_PKG_TOKEN). Render logic lives in dependency-free bash (the CI image has no jq/ruby): scripts/render-scoop-manifest.sh and scripts/render-homebrew-formula.sh. scripts/test-package-renders.sh exercises both: it validates the Scoop JSON with node and asserts fields on both manifests, and additionally runs `ruby -c` on the formula where ruby is present (dev box), skipping it gracefully otherwise. A new ci.yaml `manifests` job runs that test on every push so a render regression surfaces immediately, not at the next manual publish dispatch. The CI image has no ruby, so in CI the gate covers the Scoop JSON (node) and field assertions for both manifests; the formula's Ruby syntax is checked dev-side only (the static heredoc's variable parts cannot introduce syntax errors). - Scoop: x64 (gnu) + arm64 (gnullvm); #/-rename fragment so the bin shim is version-stable; checkver, no autoupdate (the pipeline is the updater). - Homebrew: on_macos/on_linux x arch bare-binary formula; no Windows. Docs: ADR-0056 Amendment 2 (+ README index, requirements D3). Unverified pending real use: scoop/brew install, the HEAD:main branch assumption, macOS Gatekeeper-via-brew on the ad-hoc-signed binary.
9.6 KiB
ADR-0056: crates.io publish-readiness + cargo binstall metadata (D3)
Status
Accepted — prepared 2026-06-17 (plan:
docs/plans/20260616-public-availability.md, step 3a). The crate is made
ready to publish and carries cargo-binstall metadata. The actual
cargo publish is a gated maintainer step (see Ordering). First D3
package-manager mechanism; builds on ADR-0054 (versioned releases),
ADR-0055 (installer), ADR-ci-003 (release assets). Tracked by plan + ADR
(no Gitea issue — user decision).
Context
cargo binstall rdbms-playground (and cargo install) need the crate on
crates.io (user decision, 2026-06-17). The manifest had
publish = false, a readme = "README.md" pointing at a missing
file, and no keywords/categories. Our release assets are bare
binaries (not archives) named rdbms-playground-v<version>-<target>
(.exe on Windows) with .sha256 sidecars (ADR-ci-003); critically the
release target triples differ from users' host triples — we ship the
static *-linux-musl build (hosts are *-linux-gnu) and
*-windows-gnu/-gnullvm (hosts are *-msvc); only macOS matches.
Decision
Publish-readiness (this change):
- Drop
publish = false; addhomepage = "https://relplay.org",keywords,categories = ["command-line-utilities", "database"], and anexclude(/website,/docs,/.gitea,/.codegraph) so the published crate is code-only (585 files/8.3 MiB → 353/913 KiB compressed). - Author
README.md(thereadmetarget + crates.io front page; engine-neutral and "simple/advanced mode" wording per ADR-0002 / the website copy rules), with install instructions (curl|sh, binstall, source, prebuilt). - Add
LICENSE-MITandLICENSE-APACHE(the latter the verbatim canonical text, added by the maintainer; both © Lazy Evaluation Ltd — the publication entity), and aCONTRIBUTING.mdstating the "inbound = outbound" dual-license arrangement (so Apache-2.0 §5 makes the §3 patent grant explicit on the self-hosted forge). Dual license kept (not MIT-only) — user decision after reviewing the patent-grant rationale.
cargo binstall metadata ([package.metadata.binstall], syntax
verified against cargo-binstall SUPPORT.md):
pkg-fmt = "bin"(bare binary),bin-dir = "{ bin }{ binary-ext }", and a basepkg-urlusingv{ version }(the{ version }placeholder excludes the leadingv).- Per-target
overridesmapping the common host triples to the asset we actually publish:x86_64/aarch64-unknown-linux-gnu→ the-muslasset;x86_64/aarch64-pc-windows-msvc→ the-gnu/-gnullvm.exe. macOS needs no override (host triple == asset triple). The docs do not promise automatic musl/gnu or msvc/gnu fallback, hence explicit overrides.
Ordering / gating (important):
cargo publishis irreversible (needs the crates.io token; a version can't be un-published, only yanked) — a deliberate maintainer step, not done here.- binstall's
pkg-urlresolves to a tagged release's assets, so publish at a new tagged version whose release already exists, and publish after that release is built. Do not publish0.1.0— it would diverge from the already-released0.1.0binaries (which predate--version, ADR-0054). The clean path: bump → tag → release builds →cargo publish.
Verification
cargo publish --dry-run --allow-dirtypackages + verify-builds cleanly (353 files, 913 KiB compressed; no metadata errors).cargo metadataconfirms thebinstallblock + all fouroverridesparse.- Unverified: an actual
cargo binstallrun — cargo-binstall isn't a dependency and nothing is on crates.io yet. Validate at the first publish + matching release (especially the windows-msvc→gnu and linux-gnu→musl overrides).
Consequences
- The crate can be published at the next tagged release with
cargo publish(+ the token);cargo install rdbms-playgroundandcargo binstall rdbms-playgroundthen work. - Remaining D3: Scoop, Homebrew (
lazyevaltap), winget (komac/manual) — each a manifest + a per-release bump, tracked in the plan. - Remaining follow-up: run the real
cargo binstallvalidation at the first publish + matching release (the license files, © holder, and CONTRIBUTING are now in place).
Amendment 1 — 2026-06-18: published live + a manual publish workflow
rdbms-playground 0.2.0 is published to crates.io (cargo install and
cargo binstall rdbms-playground both verified working by the user). The
"unverified binstall" caveat is resolved — the per-target overrides
resolve correctly against the v0.2.0 release assets.
How publishing is wired: a new manual workflow_dispatch workflow
(.gitea/workflows/publish.yaml), mirroring release-macos.yaml, takes a
tag input and runs cargo publish (token via the
CARGO_REGISTRY_TOKEN Gitea Actions secret — a crate-scoped,
publish-update token). Not automated on the tag, by decision: the
publish is irreversible (yank-only), keeping the registry token off every
tag push; the release is split (Linux/Windows on the tag, macOS
dispatched), so a human is the natural "all assets are up — go" gate; and
crates.io has no Gitea-Actions trusted-publishing path today, so a stored
token on the self-hosted runner would be the only automated option.
Each registry is its own idempotent job (no inter-job needs) — the
crates.io job skips cleanly if the version is already published (crates.io
API pre-check + cargo publish as the backstop) — so future
Scoop/Homebrew/winget jobs can be added alongside without breaking one
another or re-runs. The first such job's tag-vs-Cargo.toml guard
mirrors release.yaml.
Amendment 2 — 2026-06-19: Scoop bucket + Homebrew tap (D3 §3b/§3c)
Two more package managers wired as sibling publish.yaml jobs
(scoop-bucket, homebrew-tap), following Amendment 1's independent +
idempotent pattern. Each fetches the release's .sha256 sidecars, renders
a manifest, and commits it into a per-manager repo.
Repos — org-level and multi-package. Both live under a new lazyeval
Gitea organisation (created with the oli account, which gives the
git.lazyeval.net/lazyeval/... paths): lazyeval/scoop-bucket and
lazyeval/homebrew-tap. A Scoop bucket and a Homebrew tap are by
definition collections of manifests, so these are reusable for future
tools, not single-package repos. Homebrew's homebrew- repo-name prefix is
mandatory (→ referenced as lazyeval/tap); Scoop's bucket name is free.
Users: scoop bucket add lazyeval <url> (the label is local/arbitrary;
only the URL owner is real) then scoop install rdbms-playground; and
brew tap lazyeval/tap https://git.lazyeval.net/lazyeval/homebrew-tap
(the explicit-URL form — the user/repo shorthand assumes GitHub) then
brew install lazyeval/tap/rdbms-playground.
Credential — a scoped bot user, not an oli PAT. Gitea PATs scope by
permission category, not per-repository (write:repository grants
write to every repo the account can reach — there is no repo picker like
GitHub fine-grained PATs). So an oli token would also be able to push to
oli/rdbms-playground itself. Instead a dedicated bot user lazyeval-ci
is a member of a lazyeval org team with Write to the package repos
only; its write:repository PAT is therefore effectively scoped to those
repos and cannot touch the main project repo. Stored as the
LAZYEVAL_PKG_TOKEN Actions secret on oli/rdbms-playground (where the
workflow runs — not an org secret, which wouldn't reach a user-repo
workflow; not on the target repos, which only receive pushes). Passed via
env: (never inlined), so it stays masked and only materialises in the
clone URL at runtime; pushes go to HEAD:main (assumes the repos default
to main).
Render scripts are dependency-free bash. The CI job container is
node:22-bookworm-slim — no jq, no ruby — so
scripts/render-{scoop-manifest,homebrew-formula}.sh are pure bash
(heredocs, no external deps) taking a version + the relevant hashes and
emitting the manifest on stdout. scripts/test-package-renders.sh is their
test (JSON validated with node — present in the image — plus jq/ruby
when available; field-level assertions). The job validates the rendered
Scoop JSON with node -e JSON.parse before committing.
Manifest specifics.
- Scoop (
rdbms-playground.jsonat bucket root):64bit=x86_64-pc-windows-gnu.exe,arm64=aarch64-pc-windows-gnullvm.exe; each URL carries a#/rdbms-playground.exerename fragment so thebinshim resolves regardless of version. Carriescheckver(letsscoop status/ the community excavator see lag) but noautoupdate— our pipeline is the updater. - Homebrew (
Formula/rdbms-playground.rb):on_macos/on_linux×on_arm/on_intelselecting the four bare-binary assets (macOS direct; Linux = the static-muslbuild). Windows absent — Homebrew has no Windows port.installdrops the single staged binary under a stable name; thetestblock runs--version.
Unverified (validate on first real use): an actual scoop install and
brew install/brew test; the HEAD:main default-branch assumption; and
whether macOS Gatekeeper accepts the ad-hoc-signed mac binary via
brew (execution should be fine — ad-hoc satisfies arm64's signing
requirement and brew's curl download sets no quarantine xattr, unlike a
browser download — but this rides on the still-parked Developer-ID signing
decision). Remaining D3: winget (komac on Linux CI, or a manual PR).