Files
rdbms-playground/docs/adr/0056-crates-io-and-cargo-binstall.md
T
claude@clouddev1 6d54c1e96c
ci / gate (push) Successful in 1m59s
ci / manifests (push) Successful in 4s
ci(publish): wire Scoop bucket + Homebrew tap jobs (D3 §3b/§3c)
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.
2026-06-19 21:30:18 +00:00

9.6 KiB
Raw Blame History

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; add homepage = "https://relplay.org", keywords, categories = ["command-line-utilities", "database"], and an exclude (/website, /docs, /.gitea, /.codegraph) so the published crate is code-only (585 files/8.3 MiB → 353/913 KiB compressed).
  • Author README.md (the readme target + 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-MIT and LICENSE-APACHE (the latter the verbatim canonical text, added by the maintainer; both © Lazy Evaluation Ltd — the publication entity), and a CONTRIBUTING.md stating 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 base pkg-url using v{ version } (the { version } placeholder excludes the leading v).
  • Per-target overrides mapping the common host triples to the asset we actually publish: x86_64/aarch64-unknown-linux-gnu → the -musl asset; 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 publish is 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-url resolves 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 publish 0.1.0 — it would diverge from the already-released 0.1.0 binaries (which predate --version, ADR-0054). The clean path: bump → tag → release builds → cargo publish.

Verification

  • cargo publish --dry-run --allow-dirty packages + verify-builds cleanly (353 files, 913 KiB compressed; no metadata errors).
  • cargo metadata confirms the binstall block + all four overrides parse.
  • Unverified: an actual cargo binstall run — 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-playground and cargo binstall rdbms-playground then work.
  • Remaining D3: Scoop, Homebrew (lazyeval tap), winget (komac/manual) — each a manifest + a per-release bump, tracked in the plan.
  • Remaining follow-up: run the real cargo binstall validation 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-slimno 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.json at bucket root): 64bit = x86_64-pc-windows-gnu.exe, arm64 = aarch64-pc-windows-gnullvm.exe; each URL carries a #/rdbms-playground.exe rename fragment so the bin shim resolves regardless of version. Carries checkver (lets scoop status / the community excavator see lag) but no autoupdate — our pipeline is the updater.
  • Homebrew (Formula/rdbms-playground.rb): on_macos/on_linux × on_arm/on_intel selecting the four bare-binary assets (macOS direct; Linux = the static -musl build). Windows absent — Homebrew has no Windows port. install drops the single staged binary under a stable name; the test block 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).