4 Commits

Author SHA1 Message Date
claude@clouddev1 cabc8131a9 docs: handoff 74 — road to public availability; v0.2.0 live on crates.io
ci / gate (push) Successful in 3m21s
2026-06-18 22:10:41 +00:00
claude@clouddev1 8ebe213b5d ci: add the publish.yaml workflow file (completes d3af1c4)
d3af1c4 described the manual publish workflow and updated ADR-0056, but
`git commit -am` doesn't stage new untracked files, so publish.yaml
itself was left out. Add it here.
2026-06-18 22:10:21 +00:00
claude@clouddev1 d3af1c413a ci: add a manual publish workflow (crates.io, idempotent + expandable)
A workflow_dispatch publish.yaml (mirrors release-macos.yaml) with a `tag`
input, run by hand once the automated release builds exist. Publishing
stays manual and keeps the registry token off every tag push: it's
irreversible (yank-only), the split release (tag Linux/Windows +
dispatched macOS) makes a human the 'all assets up' gate, and crates.io
has no Gitea-Actions trusted-publishing path. The crates.io job is
idempotent (crates.io API pre-check + cargo publish as backstop) and
independent (no inter-job needs), so future Scoop/Homebrew/winget jobs can
be added alongside without interfering or breaking re-runs. Token via the
CARGO_REGISTRY_TOKEN secret. ADR-0056 Amendment 1 + README index also
record 0.2.0 published + binstall verified.
2026-06-18 22:03:47 +00:00
claude@clouddev1 3c87dbb391 fix(ci): create the profile dir so macOS nix prune keeps the toolchain warm
The prune step's profile path $HOME/.cache/rdbms-ci/toolchain had no
parent dir, so `nix develop --profile` errored ("cannot read directory")
and — swallowed by `|| true` — never created the profile/gc-root. With
nothing rooting the toolchain, nix-collect-garbage deleted the whole
closure every run (~3.8 GiB re-downloaded each dispatch; confirmed in the
run-74 log). Add `mkdir -p` for the parent and drop the `|| true` on the
profile realization so a future breakage fails loudly. The VM stays
bounded as before, but the toolchain now persists across runs.

release-macos.yaml is workflow_dispatch (runs from main's definition), so
this takes effect on the next dispatch — the already-published v0.2.0
macOS binaries are unaffected.
2026-06-18 21:24:34 +00:00
5 changed files with 252 additions and 3 deletions
+79
View File
@@ -0,0 +1,79 @@
# Manual publication workflow (workflow_dispatch) — the outward, irreversible
# release steps a human triggers AFTER the automated `release.yaml` build has
# produced downloadable assets (and they've been eyeballed as good).
#
# Why manual + separate from release.yaml:
# * Publishing to a public registry is irreversible (crates.io versions can
# only be *yanked*, never deleted) — a human pulls this lever, and the
# registry token never sits on every tag push.
# * Our release is split (Linux/Windows on the tag, macOS dispatched), so a
# human is the natural "all assets are up — go" gate. crates.io publish
# reads SOURCE so it doesn't strictly need the release, but binstall's
# metadata points at the release assets — hence run this once builds exist.
#
# Structure: each registry is its OWN job with NO inter-job `needs`, so jobs run
# independently and one failing (or a newly-added one) never breaks another.
# Every job is IDEMPOTENT — re-dispatching when a target is already published is
# a clean no-op. Add Scoop / Homebrew / winget as sibling jobs here later.
name: publish
on:
workflow_dispatch:
inputs:
tag:
description: 'Release tag to publish (e.g. v0.2.0)'
required: true
jobs:
crates-io:
runs-on: ci-public
container:
image: git.lazyeval.net/oli/rdbms-playground-ci:latest
steps:
- uses: actions/checkout@v4
with:
ref: ${{ inputs.tag }}
- name: publish to crates.io (idempotent)
shell: bash
env:
TAG: ${{ inputs.tag }}
# A crate-scoped, publish-update crates.io token, stored as a Gitea
# Actions secret. `cargo publish` reads CARGO_REGISTRY_TOKEN from env.
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
run: |
set -euo pipefail
# Source of truth = the [package] version at the checked-out tag
# (toolchain-free read; same approach as release.yaml's guard, which
# avoids the flake devShell's stdout banner corrupting a parse).
VER=$(grep -m1 '^version = ' Cargo.toml | sed -E 's/^version = "(.*)"/\1/')
[ -n "$VER" ] || { echo "ERROR: could not read version from Cargo.toml" >&2; exit 1; }
if [ "$TAG" != "v$VER" ]; then
echo "ERROR: dispatch tag '$TAG' != 'v$VER' (Cargo.toml at that tag)" >&2
exit 1
fi
# Idempotency: if this version is already on crates.io, no-op.
# (crates.io requires a descriptive User-Agent per its data policy;
# without one the API returns 403.) Only an explicit 200 means
# "already there" — anything else proceeds, and `cargo publish` is the
# final backstop (it refuses to overwrite an existing version).
UA="rdbms-playground-release-ci (oliver@sturmnet.org)"
code=$(curl -sS -o /dev/null -w '%{http_code}' -A "$UA" \
"https://crates.io/api/v1/crates/rdbms-playground/$VER" || echo 000)
if [ "$code" = "200" ]; then
echo "rdbms-playground $VER is already on crates.io — nothing to do."
exit 0
fi
echo "crates.io returned HTTP $code for $VER (not 200) — proceeding to publish."
echo "publishing rdbms-playground $VER to crates.io ..."
nix develop -c cargo publish --locked
echo "published rdbms-playground $VER to crates.io."
# Future manual publication targets go here as independent, idempotent jobs,
# e.g.:
# scoop-bucket: { ... update the lazyeval Scoop bucket manifest ... }
# homebrew-tap: { ... update the lazyeval Homebrew formula ... }
# winget: { ... komac submit, or a manual PR helper ... }
# No `needs:` between them — each checks the target and skips if already done.
+11 -2
View File
@@ -84,12 +84,21 @@ jobs:
# The runner wipes the workspace each run, so cargo target/ never
# accumulates. Bound the persistent nix store by generation: record the
# current devShell as a generation of a persistent profile (in $HOME),
# keep the 2 newest, reclaim what older ones referenced.
# keep the 2 newest, reclaim what older ones referenced — so the
# toolchain stays *warm* across runs and only stale generations are GC'd.
#
# The profile's parent dir MUST exist first, or `nix develop --profile`
# errors ("cannot read directory …") and the profile/gc-root is never
# created — which made `nix-collect-garbage` delete the whole toolchain
# closure every run (re-downloaded ~3.8 GiB each time; the retention was
# silently broken by the swallowed `|| true`). No `|| true` on the
# profile realization now: a future breakage should fail loudly.
if: always()
run: |
echo "--- disk before ---"; df -h / | tail -1
P="$HOME/.cache/rdbms-ci/toolchain"
nix develop --profile "$P" -c true || true
mkdir -p "$(dirname "$P")"
nix develop --profile "$P" -c true
nix-env -p "$P" --delete-generations +2 || true
nix-collect-garbage || true
echo "--- disk after ---"; df -h / | tail -1
@@ -86,3 +86,27 @@ verified against cargo-binstall SUPPORT.md):
- 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`.
+1 -1
View File
@@ -68,4 +68,4 @@ This directory contains the project's ADRs, recorded per
- [ADR-0053 — Contextual `hint` command and keybinding](0053-contextual-hint-command-and-keybinding.md) — **Accepted, implemented 2026-06-15** (Phases AD; closes **A1** + requirements **H2**). Settles the `hint` slot ADR-0003 left "ADR pending"; closes the last open piece of **A1** and tracks requirements **H2**. **Two surfaces:** an **F1 keybinding** that renders a deep hint for the *live* partial input without submitting (the primary path — a submitted `hint` command can't see the buffer it would help with, since Enter empties it), and a submitted **`hint` command** that expands on the *most recent error*. **No topic argument** (contextual only — `help <topic>` already owns explicit reference). Introduces a **tier-3 teaching layer**, deeper than the existing tier-1 (colour / error headline) and tier-2 (ambient one-liner; and the error `hint:`, which is shown **by default** since `Verbosity::Verbose` is the default — `messages short` is the opt-*out*); without it `hint` would just duplicate what's already on screen. Tier-3 content lives in the catalogue under `hint.cmd.<hint_id>` (per command form) and `hint.err.<class>` (per error/diagnostic class), each a structured `what`/`example`/`concept` block rendered via a new `note_hint*` family with `OutputStyleClass::Hint`. **Keyed per-form via a new `hint_ids: &[&str]` field on `CommandNode` mirroring `usage_ids`** (revised in Phase B): a per-*node* key proved too coarse — `add`/`drop`/`show`/`create` are each one node spanning many forms, and a live-input hint for `add 1:n relationship` must be specific to relationships; `hint_key_for_input_in_mode` reuses `usage_key_for_input_in_mode`'s form-word disambiguation, and covers the advanced-SQL forms whose `usage_ids` are empty. Not keyed off `help_id` (it is `None` on the advanced-SQL nodes purely to dedup the `help` list; that parallel gap is issue **#36**). **Clause-concept hints** (`on delete` actions, constraint slots, `with pk`, cardinality) are a recorded **deferred extension** (`hint.concept.<topic>`, issue **#37**) — per-form is the right tier-3 granularity, with position-awareness owned by tier-2 + the live `Next:` line. Runtime `translate_error` classes resolve via stored `last_error_hint_key` (`hint` command / empty-F1). (The second route — pre-submit `diagnostic.*` read live from the walker on the F1 path — is **deferred**, issue **#38**: `Diagnostic` carries no class key.) Adds `AppCommand::Hint`, a `HINT` grammar node + REGISTRY entry, the `hint_ids` field, and `last_error_hint_key`; F1 is a read-only overlay (buffer + completion memo untouched). **Content is the bulk of the work** (the mechanism is ~a day): v1 scope = ~37 command forms + 9 runtime error classes (comprehensive for those, ~57 blocks), authored **exemplars-first** (voice approved in this ADR's `/runda` review, then mass-authored in batches), enforced by a **comprehensiveness coverage test**, with graceful fall-back to tier-2 if a key is ever missing. The **pre-submit-diagnostic route + ~33 `diagnostic.*` blocks were deferred** (issue **#38**) — `Diagnostic` carries no class key, so the route needs a broad change for marginal value (tier-2 already surfaces diagnostics; many duplicate runtime classes). Forks user-chosen: two-surface model; **F1** (vs `?` / a chord); no-arg; comprehensive-for-commands-and-errors scope; exemplars-first; diagnostics deferred. OOS: per-topic `hint <topic>` (rejected — overlaps `help`); always-on tier-3 (rejected — keeps ambient terse); non-`en-US` locales + success-command teaching (deferred); clause-concept hints (issue #37); the diagnostic route (issue #38); the `help`-side advanced-SQL gap (issue #36)
- [ADR-0054 — Release versioning policy + version surfaces (`--version` / `version`)](0054-release-versioning-and-version-surfaces.md) — **Accepted + implemented 2026-06-16** (plan: `docs/plans/20260616-public-availability.md`, step 1 on the road to public availability; no prior issue/`requirements.md` item — an untracked gap). Fixes the **tag↔crate-version decoupling**: `Cargo.toml` built `0.1.0` while `release.yaml` named assets from the git tag, so a binary could report a version different from the asset it shipped in. **Decision:** `Cargo.toml` `version` is the **single source of truth** (read via `env!("CARGO_PKG_VERSION")`, no tag-injection); two surfaces report it through one `cli::version_text()` → catalog `cli.version_line` — a **`--version` / `-V`** CLI flag (mirrors `--help`, prints+exits in `main.rs`) and an in-app **`version`** command (REGISTRY node `app::VERSION`, `AppCommand::Version`, emits via `note_system`); and a **release-CI version guard** (`release.yaml` `test` job reads the `[package]` version from `Cargo.toml` and **fails the release** unless the `v*` tag equals `v<version>`; the guard's parse was later switched from `cargo metadata | node` to a `grep` on Cargo.toml after the former broke on the flake devShell's stdout banner). Release ritual: bump `Cargo.toml` → commit → tag → push. New keys `cli.version_line` + `help.app.version` + `parse.usage.version` + `hint.cmd.version.{what,example}` (the new REGISTRY command pulls in the comprehensiveness coverage gate). Rejected: tag-as-source (makes Cargo.toml lie). Deferred: git-hash/build-date enrichment (behind the same `version_text()` seam); UI placement beyond the command. Tested test-first: CLI parse (`--version`/`-V`/default-off), `version_text()` carries `CARGO_PKG_VERSION`, the in-app command parses + emits. Also corrected a stale `release.yaml` header comment ("macOS is deferred" → built by the dispatched `release-macos.yaml`).
- [ADR-0055 — `curl | sh` install script (`scripts/install.sh`)](0055-curl-sh-install-script.md) — **Accepted + implemented 2026-06-17** (plan: `docs/plans/20260616-public-availability.md`, step 2; tracked by plan + ADR, no Gitea issue — user decision). A one-line installer (`curl -fsSL <gitea-raw>/scripts/install.sh | sh`) so beginners don't hand-pick an asset + `chmod +x`. **POSIX `sh`** (shellcheck-clean), detects `uname` OS/arch → target triple (**Linux → the fully-static `*-musl`** build, macOS → `*-apple-darwin`; `amd64`/`arm64` aliased; **Windows rejected** → Scoop/winget/releases page), resolves the version from the **`releases/latest`** API (or `RDBMS_VERSION` to pin), downloads the asset **and its `.sha256` and verifies it** (mismatch aborts), installs to `~/.local/bin` (`RDBMS_INSTALL_DIR` override) with a PATH hint. Testing seams: `RDBMS_OS`/`RDBMS_ARCH` + `--print-target`. macOS note: `curl` downloads aren't Gatekeeper-quarantined so the ad-hoc binary runs as-is (Developer-ID + notarization is the postponed signing task). **Verified end-to-end against the live public `v0.1.0`** (all platform mappings, pinned + latest, checksum incl. tamper-rejection, install + run). Rejected: website-domain hosting (extra moving part; Gitea raw is simplest); deferred: uploading the script as a release asset, and a **shellcheck CI gate** (shellcheck isn't in the flake — touches ADR-ci-002). **Amendment 1 (2026-06-17):** added a Windows **`scripts/install.ps1`** (`irm | iex`; maps host CPU → our `*-windows-gnu`/`-gnullvm` `.exe`, SHA-256-verifies, installs to `%LOCALAPPDATA%\Programs\…` + user PATH) — user chose both a one-liner *and* Scoop/winget; **written but untested from this env** (no PowerShell — validate on Windows).
- [ADR-0056 — crates.io publish-readiness + `cargo binstall` metadata (D3)](0056-crates-io-and-cargo-binstall.md) — **Prepared 2026-06-17** (plan step 3a; tracked by plan + ADR). Makes the crate **ready to publish** to crates.io (user decision) and adds `cargo-binstall` metadata; the actual `cargo publish` is a **gated, irreversible maintainer step**. Manifest: drops `publish = false`; adds `homepage` (relplay.org), `keywords`, `categories`, and an `exclude` (`/website`,`/docs`,`/.gitea`,`/.codegraph`) trimming the crate from 585 files/8.3 MiB → **353/913 KiB compressed** (code-only). Authors **`README.md`** (engine-neutral, simple/advanced-mode wording; install via curl|sh/binstall/source/prebuilt) and **`LICENSE-MIT`** (© Lazy Evaluation Ltd — *confirm holder*); the canonical **`LICENSE-APACHE`** is deferred to the maintainer (don't ship retyped legal text) — the SPDX `license` field already satisfies crates.io. **binstall** (syntax verified vs cargo-binstall SUPPORT.md): `pkg-fmt = "bin"` (bare binaries), `pkg-url` spelled `v{ version }` (the placeholder omits the `v`), plus per-target **`overrides`** mapping the common host triples to the assets we ship — `*-linux-gnu` → the static `*-linux-musl` build, `*-pc-windows-msvc``*-gnu`/`-gnullvm` `.exe` (macOS matches directly; the docs promise no automatic fallback). **Ordering:** publish at a **new tagged version whose release exists**, after the release — **not `0.1.0`** (diverges from the already-released 0.1.0 binaries that predate `--version`). Verified: `cargo publish --dry-run` packages + verify-builds; `cargo metadata` confirms the binstall block + 4 overrides. **Unverified:** a real `cargo binstall` run (not a dep; nothing on crates.io yet) — validate at first publish. Rejected: cargo-dist (GitHub-centric). Maintainer follow-ups: confirm © holder, add canonical `LICENSE-APACHE`, real binstall validation.
- [ADR-0056 — crates.io publish-readiness + `cargo binstall` metadata (D3)](0056-crates-io-and-cargo-binstall.md) — **Prepared 2026-06-17** (plan step 3a; tracked by plan + ADR). Makes the crate **ready to publish** to crates.io (user decision) and adds `cargo-binstall` metadata; the actual `cargo publish` is a **gated, irreversible maintainer step**. Manifest: drops `publish = false`; adds `homepage` (relplay.org), `keywords`, `categories`, and an `exclude` (`/website`,`/docs`,`/.gitea`,`/.codegraph`) trimming the crate from 585 files/8.3 MiB → **353/913 KiB compressed** (code-only). Authors **`README.md`** (engine-neutral, simple/advanced-mode wording; install via curl|sh/binstall/source/prebuilt) and **`LICENSE-MIT`** (© Lazy Evaluation Ltd — *confirm holder*); the canonical **`LICENSE-APACHE`** is deferred to the maintainer (don't ship retyped legal text) — the SPDX `license` field already satisfies crates.io. **binstall** (syntax verified vs cargo-binstall SUPPORT.md): `pkg-fmt = "bin"` (bare binaries), `pkg-url` spelled `v{ version }` (the placeholder omits the `v`), plus per-target **`overrides`** mapping the common host triples to the assets we ship — `*-linux-gnu` → the static `*-linux-musl` build, `*-pc-windows-msvc``*-gnu`/`-gnullvm` `.exe` (macOS matches directly; the docs promise no automatic fallback). **Ordering:** publish at a **new tagged version whose release exists**, after the release — **not `0.1.0`** (diverges from the already-released 0.1.0 binaries that predate `--version`). Verified: `cargo publish --dry-run` packages + verify-builds; `cargo metadata` confirms the binstall block + 4 overrides. **Unverified:** a real `cargo binstall` run (not a dep; nothing on crates.io yet) — validate at first publish. Rejected: cargo-dist (GitHub-centric). Maintainer follow-ups: confirm © holder, add canonical `LICENSE-APACHE`, real binstall validation. **Amendment 1 (2026-06-18):** `0.2.0` **published live** (crates.io; `cargo install` + `cargo binstall` verified — the unverified-overrides caveat is resolved), via a new **manual `workflow_dispatch`** workflow `.gitea/workflows/publish.yaml` (mirrors `release-macos.yaml`; `tag` input; `cargo publish` with a crate-scoped `CARGO_REGISTRY_TOKEN` secret). Publish stays **manual** by decision — irreversible (keeps the token off every tag push), the split release (tag Linux/Windows + dispatched macOS) makes a human the "all assets up" gate, and crates.io has no Gitea-Actions trusted-publishing path. Each registry is its **own idempotent job** (crates.io job no-ops if the version exists) so Scoop/Homebrew/winget can be added as sibling jobs without interfering.
+137
View File
@@ -0,0 +1,137 @@
# Session handoff — 2026-06-18 (74)
Large session. Continues from handoff-73 (Ctrl-G demo alias). This one ran
the **road to public availability** end to end: a post-merge doc
reconciliation, then versioning, installers, crates.io/binstall, the
`cargo fmt` gate, and an actual **`v0.2.0` release that is now live on
crates.io**. Three new main-sequence ADRs (0054/0055/0056), one CI-ADR
amendment, issue **#35 closed**.
## §1. State
**Branch `main`.** **2509 pass / 0 fail / 1 ignored** (the long-standing
`friendly` doctest); **clippy clean**; **`cargo fmt --check` clean** (the
tree is now stock-rustfmt formatted, and CI gates it). `rdbms-playground
--version` → `rdbms-playground 0.2.0`.
**Released:** **`v0.2.0`** — Gitea release with all six D1 targets
(Linux/Windows via `release.yaml` on the tag; macOS via the dispatched
`release-macos.yaml`, **ad-hoc-signed**). **Published to crates.io**
(`cargo install rdbms-playground` and `cargo binstall rdbms-playground`
both user-verified).
**Push state:** earlier commits were pushed (they triggered CI/releases).
The **most recent commits are unpushed** and matter:
- `3c87dbb` macOS nix-prune fix (takes effect next macOS dispatch).
- `d3af1c4` + `8ebe213` the manual `publish.yaml` workflow — ADR-0056 in
the first, the **workflow file itself in the second** (`git commit -am`
had skipped the new untracked file).
- this handoff.
Push `main` to land them. (Push is the user's step.)
## §2. What shipped (commits `628b250``d3af1c4`)
- **`628b250` doc reconciliation** after the CI + website branch merges:
rewrote CLAUDE.md's stale repo-layout tree; added a Website subproject
note; fixed the CI note; **gitignored `.wrangler/` + `.vscode/`** and
removed a tracked `website/.vscode/`; updated requirements (D1 macOS
runtime-verified, DOC1 canonical-docs-on-website) + ADR-ci-003.
- **`c30a611` ADR-0054 — version surfaces.** `--version`/`-V` + an in-app
**`version`** command, both reading `CARGO_PKG_VERSION` via one
`cli::version_text()`. A **release-CI guard** fails the release unless
the `v*` tag equals `v<Cargo.toml version>`.
- **`ef99e6c` ADR-0055 — `scripts/install.sh`** (curl|sh, POSIX,
shellcheck-clean, checksum-verified, `~/.local/bin`). Verified
end-to-end against the live release. **`install.ps1`** (Windows
`irm|iex`) added later (`e9606b5`) — **written but untested here** (no
PowerShell on this box; validate on Windows).
- **`e9606b5` ADR-0056 — crates.io + binstall prep.** Publish-ready
Cargo.toml (dropped `publish=false`; homepage/keywords/categories/
`exclude`); `README.md`; `LICENSE-MIT`/`LICENSE-APACHE` (dual, © Lazy
Evaluation Ltd) + `CONTRIBUTING.md` (inbound=outbound); the
`[package.metadata.binstall]` block with per-target overrides
(linux-gnu→musl, windows-msvc→gnu/gnullvm; macOS direct).
- **`41b7e9a` + `ec3c7c3`#35 fmt gate.** One mechanical `cargo fmt`
(stock defaults, 102 files, behaviour-preserving) recorded in
`.git-blame-ignore-revs`; `ci.yaml` now gates `fmt --check` (ADR-ci-002
Amendment 1). **Closes #35.**
- **`88830ed`+`bd5be5e` — v0.2.0 bump + the guard bug.** The first
`release.yaml` run **failed** at the version guard: it piped `nix
develop -c cargo metadata` to node, but the **flake devShell prints a
banner to stdout**, corrupting the JSON. Fixed to a toolchain-free
`grep -m1 '^version = ' Cargo.toml`. The `v0.2.0` tag was re-pointed
(Option A) to the fix commit; re-run went green.
- **`3c87dbb` — macOS nix-prune fix.** The prune step's profile dir
(`~/.cache/rdbms-ci`) didn't exist, so `nix develop --profile` errored
(swallowed by `|| true`) → the gc-root was never created → the whole
toolchain (~3.8 GiB) was deleted **and re-downloaded every run**. Added
`mkdir -p` + dropped the `|| true`. Diagnosed from the run-74 log via
`tea actions runs logs 74`.
- **`d3af1c4` — manual `publish.yaml`.** `workflow_dispatch` + `tag`
input (mirrors `release-macos.yaml`). Idempotent `crates-io` job
(crates.io API pre-check + `cargo publish` backstop), independent jobs
so Scoop/Homebrew/winget slot in later. ADR-0056 Amendment 1.
## §3. Live vs manual vs parked
- **Automated on a `v*` tag:** `release.yaml` builds + publishes the four
Linux/Windows targets (+ fmt/clippy/test gate).
- **Manual `workflow_dispatch`:** `release-macos.yaml` (mac binaries —
intermittent runner) and `publish.yaml` (crates.io now; more registries
later). Run them once the tag's build is up.
- **Parked (user decisions):**
- **macOS Developer-ID signing.** The pipeline **ad-hoc-signs**
(`codesign --sign -`). The user's `Apple Development` cert is the
**wrong type** — distribution needs **`Developer ID Application`** +
**notarization** (App Store Connect API key recommended). Fine for
`curl|sh` (no quarantine); matters for browser downloads. Details in
`docs/plans/20260616-public-availability.md`.
- **Remaining D3:** Scoop (`lazyeval` bucket), Homebrew (`lazyeval`
tap), winget (komac on Linux CI, or manual PR) — each a sibling job
in `publish.yaml` + a manifest repo.
## §4. Immediate next steps
1. **Push `main`** (lands `3c87dbb` + `d3af1c4`).
2. **Add the `CARGO_REGISTRY_TOKEN` secret** (crate-scoped,
`publish-update`) so `publish.yaml` works: `tea actions secrets create
CARGO_REGISTRY_TOKEN` (paste at prompt) or the Gitea UI.
3. **Smoke-test `publish.yaml`:** dispatch it for `v0.2.0` — it should
**idempotently skip** ("already on crates.io"), exercising the path
risk-free.
4. The release ritual going forward (ADR-0054): bump `Cargo.toml`
commit → tag `v<x.y.z>` → push tag (Linux/Windows release builds) →
dispatch `release-macos` → dispatch `publish`.
## §5. Gotchas learned (don't relearn the hard way)
- **The flake devShell prints a banner to stdout** — never pipe `nix
develop -c <cmd>` into a parser. Read Cargo.toml directly, etc.
- **Workflow-file source differs by trigger:** a **tag**-triggered run
(`release.yaml`) uses the workflow **at the tagged commit**; a
**`workflow_dispatch`** run (`release-macos`/`publish`) uses the
**default branch** (`main`). So fixing a dispatched workflow only needs
a `main` push; fixing a tag-triggered one needs the tag re-pointed.
- **Version vs tag:** `Cargo.toml` is bare `0.2.0`; the git tag is
`v0.2.0`; the guard checks `tag == "v" + version`; binstall `pkg-url`
spells `v{ version }`.
- **CI logs are reachable** via `tea actions runs logs <id>` (and `tea
actions runs list --output tsv`). Use it instead of guessing from a
step name.
- **crates.io API needs a descriptive User-Agent** (403 without one).
## §6. How to take over
1. Read handoffs 72 → 73 → 74, `CLAUDE.md`, `docs/requirements.md`, and
**`docs/plans/20260616-public-availability.md`** (the GA roadmap with
all decisions + parked items).
2. Confirm green: `cargo test` (**2509 / 1 ignored**), `cargo clippy
--all-targets`, `cargo fmt --check`.
3. ADRs for this arc: **0054** (versioning), **0055** (installer),
**0056** (crates.io/binstall + the publish workflow); CI side
**ADR-ci-002 Amendment 1** (fmt gate), **ADR-ci-003** (release matrix
+ macOS).
4. Workflow unchanged: phased, test-first, `/runda` + DA before commits,
ADR amendment + README index-upkeep for decided-area changes, confirm
commit messages, never push.
5. Consider a `cargo sweep` at this milestone (`target/` grows).