The release job ran `npm run check` and `npm test` on a bare runner
with no `age` installed — ci.yml apt-installs it because the secrets
tests round-trip a real age identity, so the first real tag push would
have died at `npm test` and minted no release. The job now does exactly
what the flow (cast#96 / box#83) assigns it: `npm ci && npm run build
&& npm prune --omit=dev`, tar, `gh release create` — check and tests
already gated the merge commit the tag points at.
Also aligns CHANGELOG.md with the family preamble ("History before
0.1.0 lives in git") and with the workflow's actual build steps.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
cast gets the family's release flow (box#83's shape), plus the piece
unique to cast: because cast compiles, the source tarball is not the
package — so release.yml builds ONCE in CI and attaches cast-X.Y.Z.tgz,
and the installer's default channel extracts that asset instead of
running npm ci + tsc on the operator's machine.
- cast --version: package.json is the single source of truth (no VERSION
file); prints the install root too, rig-style.
- CHANGELOG.md with Unreleased; release notes are the curated section
(scripts/changelog-section.sh), never the auto-generated PR list.
- release.yml on a bare X.Y.Z tag: assert tag == package.json version,
check + build + test, prune, tar the runnable tree, gh release create.
- install.sh channels: unset → latest release asset (resolved via the
releases/latest redirect — no API, no token); CAST_REF=X.Y.Z → that
tag's asset; CAST_REF=<branch> → build-from-source, the old path.
- Tests drive the REAL install.sh offline via curl/npm PATH shims (all
three channels, plus the broken-asset and no-release refusals), and
the real changelog-section.sh against fixture changelogs.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>