Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions .github/workflows/release-artifacts.yml
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,32 @@ jobs:
# 11s on v0.0.152 and the release published with zero binaries.
version: 2026.9.1
cache: false
# NARROWED, BECAUSE "INSTALL EVERYTHING" COST A PLATFORM BINARY
# (CLOUD-1786). Unset, this step installs every `[tools]` entry on
# every leg — and on v0.0.160 the aarch64-unknown-linux-gnu leg died
# resolving `zizmor`, a GitHub Actions linter with no part in building
# a binary, against Sigstore's TUF CDN before compilation started. The
# release shipped without that platform. `renovate` and its 611 npm
# packages are on the same path.
#
# THE UNION OF WHAT A LEG USES, not a per-leg list, because the matrix
# mixes build tools and `install_args` is one string:
#
# rust the compiler, and the rustup that adds the target
# cargo-auditable every non-`cross` leg builds through it
# zig, cargo-zigbuild the three `zigbuild` legs' linker chain
# syft `sbom-binary` on every non-`cross` leg
# gh the two `gh release upload` steps
#
# `cross` is deliberately absent: `taiki-e/install-action` provides it
# below, and it is not a `[tools]` entry — `ci-tools-check` would refuse
# a name here that resolves to none.
#
# This does NOT unpin anything. Naming tools selects which pinned
# versions are installed, so `darwin-link` still links against the same
# `zig` this builds with, which is the shared-pin property the comment
# below depends on.
install_args: rust zig github:rust-cross/cargo-zigbuild github:rust-secure-code/cargo-auditable aqua:anchore/syft aqua:cli/cli
- name: Install cross
if: matrix.build-tool == 'cross'
uses: taiki-e/install-action@91ddec75689c4c78665b598d188dc821c5a43e5c # v2
Expand Down
35 changes: 35 additions & 0 deletions .github/workflows/release-plz.yml
Original file line number Diff line number Diff line change
Expand Up @@ -459,6 +459,41 @@ jobs:
env:
GITHUB_TOKEN: ${{ secrets.RELEASE_PLZ_TOKEN || secrets.GITHUB_TOKEN }}
steps:
# THE `||` IS A SILENT DOWNGRADE, AND THIS IS WHERE IT STOPS BEING ONE
# (CLOUD-94). `${{ secrets.RELEASE_PLZ_TOKEN || secrets.GITHUB_TOKEN }}`
# appears three times below and three more in `auto-release-land.yml`. With
# the secret absent, every one of them silently becomes the default job
# token and the run reports green — indistinguishable from a correct
# release, and less capable in the one way that matters: GitHub fires NO
# workflow events for the default token, so a tag cut under it reaches no
# `release-artifacts` run and publishes with no binaries. That is exactly
# how v0.0.159, v0.0.160 and v0.0.161 shipped (CLOUD-1789).
#
# THE FALLBACK IS KEPT, because on a fork or a secretless checkout it is
# correct — there the default token is the only credential there is, and
# refusing would remove a capability rather than protect anything. What is
# refused is the fallback being taken SILENTLY on the canonical repository,
# which is the only place a release is actually cut.
#
# The same shape as "Release tracking requires its credential" below, and
# for its reason stated there: the secret is asserted to exist, nothing in
# this tree can confirm it, so a step is where a wrong assertion surfaces.
# FIRST, before anything consumes the credential — a guard after the
# checkout it guards would be reporting on work already done.
#
# `github.event.repository.fork` rather than a repository name: a literal
# here would go stale on a rename and says nothing a fork check does not.
# The App migration does not retire this guard — a missing
# APP_ID/APP_PRIVATE_KEY lands in the identical `||`.
- name: The release credential must not fall back silently
if: github.event.repository.fork != true
env:
RELEASE_PLZ_TOKEN: ${{ secrets.RELEASE_PLZ_TOKEN }}
run: |
if [ -z "$RELEASE_PLZ_TOKEN" ]; then
echo "::error:: RELEASE_PLZ_TOKEN is empty, so every credential below would fall back to the default job token. GitHub fires no workflow events for it, so the tag this cuts would reach no release-artifacts run and publish with no binaries (CLOUD-1789). Set the secret; do not remove this guard." >&2
exit 1
fi
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
with:
fetch-depth: 0
Expand Down
104 changes: 104 additions & 0 deletions batten.toml
Original file line number Diff line number Diff line change
Expand Up @@ -1361,6 +1361,70 @@ If the row has never been read here, nothing is stored yet: read it \
(`get_issue <key>`) and the capture mints itself. Every route hands over \
bytes the tracker returned, never a re-typed copy; do NOT re-type one by hand."""

# CLOUD-1390's refusing half — the punt receipt.
#
# `record_state` mints a completion verdict at every `Stop`, and where the turn
# ended with work off its landing target `unlanded_pointer` writes
# `unlanded-nudged.<branch>` into the receipt store. That has been true since
# CLOUD-1372. What it bought was a NUDGE — advisory text at the end of a turn —
# and this session measured an agent reasoning past it twice in a row, then
# stopping a third time with four rows built and nothing landed.
#
# THE REFUSAL CANNOT LIVE AT `Stop`, which is what made this a column rather than
# a rule. CLOUD-97 and CLOUD-219 each ruled out a deny on that event, and for the
# same reason: committed-and-pushed is the only state that survives a container
# reclaim, so the path that ends a turn must stay free. A gate there would be a
# gate on the one action whose cost is already paid by losing the work.
#
# So the observation is made where the evidence is and the refusal lands where a
# refusal is allowed: the NEXT MEDIATED WRITE. That is the same displacement
# `claim read unread` above makes — the precondition is due before the work is
# touched, and no command shape can express it — one surface over.
#
# `while_marker` is what the other two modifier columns could not say.
# `when_absent` and `when_present` condition on a projection of THIS call; a punt
# is a property of the turn BEFORE it, and nothing the harness hands over carries
# it.
#
# checks = ["verify"], key = "head"
# The toll is proving the abandoned work is green, on the bytes it was
# abandoned at. Not "land first": landing needs CI and a merge, which a write
# cannot perform, and a row whose remedy its own subject cannot run is a dead
# gate. Verifying is the step immediately before landing and points the same
# direction.
#
# `key = "head"` is the expiry contract `check read unread` states: the receipt
# attests to THESE BYTES. Keyed to the branch instead, one verify run would pay
# for every subsequent punt on that branch — a one-time toll, which is not a
# gate. Keyed to the head, each punt costs its own.
#
# Spent by landing, and only by landing: `retire_branch` sweeps `unlanded-nudged`
# with the other three branch-keyed families, and `Rule::validate_marker` refuses
# at load any `while_marker` naming a family that sweep does not carry — so the
# deny cannot outlive the work it was about.
#
# NO OVERRIDE ROUTE AND NO `bypass_env`, deliberately. CLOUD-1311 measured that a
# prose escape hatch is the thing being fixed rather than a safety valve on it:
# the punt this refuses is itself a well-argued sentence, so a field accepting a
# well-argued sentence gates nothing. The honest escape is the one the remedy
# names.
[[rule]]
id = "turn mint ahead"
kind = "receipt"
scope = "mediated_call"
severity = "deny"
trigger = "write"
while_marker = "unlanded-nudged"
checks = ["verify"]
key = "head"
reason = """
The last turn ended with work committed nowhere but this container, and a \
reclaim ends it. Before writing more: run `mise run verify` (background it), \
then `mise run linear-check`, then `mise run land` — which drives the loop and \
clears this. If the work is genuinely not ready to land, commit and push what \
exists first; the receipt attests to the head you stopped at, so a push is what \
makes stopping safe rather than what makes it final."""

# The two read-shaped receipts, minted from the result that earned them
# (CLOUD-1024). Both rows below DEMAND one of these; these two rows are what
# writes them, and they sit here so the writer and the reader are read together.
Expand Down Expand Up @@ -8944,6 +9008,26 @@ trailer_allow = []
# The accountable identity `batten attribution identity` writes into the
# repo-local git config. Consumer-specific by nature: who answers for this
# repository's commits is a property of this repository.
# Who is permitted to have cut a release tag (CLOUD-1794).
#
# SEPARATE FROM `[attribution.identity]` BECAUSE THEY MEASURABLY DIFFER HERE, and
# the first draft of this gate conflated them and refused every release in the
# repository -- the good ones included. `identity` below is what `attribution
# identity` writes into a clone's git config, so commits carry it. A tag is cut by
# whatever credential `release-plz.yml` holds, which renders as that ACCOUNT's
# identity: every tag from v0.0.155 to v0.0.162 carries the address below and not
# `identity`'s, and both are legitimate in this history.
#
# An ALLOW list rather than a deny list, so an unexpected credential is refused
# whether or not anyone predicted it -- which is the whole point, since what broke
# the pipeline was the CI job token nobody had thought to name. It also means no
# bot identity is written here at all.
#
# A LIST because the accountable credential changes: CLOUD-94 migrates this from a
# personal token to an org-owned App, and that transition wants both valid briefly
# rather than the gate switched off for a day.
tag_identity_allow = ['^Alec Wenzowski <alec@button\.is>$']

[attribution.identity]
name = "Alec Wenzowski"
email = "alec@wenzowski.com"
Expand Down Expand Up @@ -12129,6 +12213,26 @@ id = "task read first"
kind = "document"
target = "mise.toml"

[[verdict]]
id = "job list loose"
gloss = "a release leg provisions every pinned tool, so any one of them can cost a platform binary"
class = """
Left unset, the provisioning step installs every pinned tool, and a release \
leg's success then depends on every one of them resolving — including tools with \
no part in building a binary. Measured on v0.0.160: the aarch64 Linux leg died \
resolving a workflow linter against its registry before compilation started, and \
the release shipped without that architecture. The failure has no local symptom \
and no bearing on the change that caused it, which is why the list is asserted \
rather than left to whoever last edited the job. What the list must CONTAIN is \
deliberately not this class's business — that is the manifest's answer, and a \
second one here would be a second authority.
"""

[[verdict.route]]
id = "workflow read first"
kind = "document"
target = ".github/workflows/release-artifacts.yml"

[[verdict]]
id = "job list missing"
gloss = "a pull-request job is silently unrequired, so green can be reported without it"
Expand Down
73 changes: 70 additions & 3 deletions completions/batten.bash
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,9 @@ _batten() {
batten__subcmd__attribution,identity)
cmd="batten__subcmd__attribution__subcmd__identity"
;;
batten__subcmd__attribution,tagger)
cmd="batten__subcmd__attribution__subcmd__tagger"
;;
batten__subcmd__attribution__subcmd__help,check)
cmd="batten__subcmd__attribution__subcmd__help__subcmd__check"
;;
Expand All @@ -163,6 +166,9 @@ _batten() {
batten__subcmd__attribution__subcmd__help,identity)
cmd="batten__subcmd__attribution__subcmd__help__subcmd__identity"
;;
batten__subcmd__attribution__subcmd__help,tagger)
cmd="batten__subcmd__attribution__subcmd__help__subcmd__tagger"
;;
batten__subcmd__capture,find)
cmd="batten__subcmd__capture__subcmd__find"
;;
Expand Down Expand Up @@ -514,6 +520,9 @@ _batten() {
batten__subcmd__help__subcmd__attribution,identity)
cmd="batten__subcmd__help__subcmd__attribution__subcmd__identity"
;;
batten__subcmd__help__subcmd__attribution,tagger)
cmd="batten__subcmd__help__subcmd__attribution__subcmd__tagger"
;;
batten__subcmd__help__subcmd__capture,find)
cmd="batten__subcmd__help__subcmd__capture__subcmd__find"
;;
Expand Down Expand Up @@ -1480,7 +1489,7 @@ _batten() {
return 0
;;
batten__subcmd__attribution)
opts="-q -v -y -h --strictness --fail-on-warning --config-from --config-in --silent --quiet --verbose --debug --trace --log-level --no-color --no-input --yes --help check identity help"
opts="-q -v -y -h --strictness --fail-on-warning --config-from --config-in --silent --quiet --verbose --debug --trace --log-level --no-color --no-input --yes --help check tagger identity help"
if [[ ${cur} == -* || ${COMP_CWORD} -eq 2 ]] ; then
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
return 0
Expand Down Expand Up @@ -1548,7 +1557,7 @@ _batten() {
return 0
;;
batten__subcmd__attribution__subcmd__help)
opts="check identity help"
opts="check tagger identity help"
if [[ ${cur} == -* || ${COMP_CWORD} -eq 3 ]] ; then
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
return 0
Expand Down Expand Up @@ -1603,6 +1612,20 @@ _batten() {
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
return 0
;;
batten__subcmd__attribution__subcmd__help__subcmd__tagger)
opts=""
if [[ ${cur} == -* || ${COMP_CWORD} -eq 4 ]] ; then
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
return 0
fi
case "${prev}" in
*)
COMPREPLY=()
;;
esac
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
return 0
;;
batten__subcmd__attribution__subcmd__identity)
opts="-q -v -y -h --strictness --fail-on-warning --config-from --config-in --silent --quiet --verbose --debug --trace --log-level --no-color --no-input --yes --help"
if [[ ${cur} == -* || ${COMP_CWORD} -eq 3 ]] ; then
Expand Down Expand Up @@ -1633,6 +1656,36 @@ _batten() {
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
return 0
;;
batten__subcmd__attribution__subcmd__tagger)
opts="-J -q -v -y -h --json --strictness --fail-on-warning --config-from --config-in --silent --quiet --verbose --debug --trace --log-level --no-color --no-input --yes --help"
if [[ ${cur} == -* || ${COMP_CWORD} -eq 3 ]] ; then
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
return 0
fi
case "${prev}" in
--strictness)
COMPREPLY=($(compgen -W "permissive standard strict" -- "${cur}"))
return 0
;;
--config-from)
COMPREPLY=($(compgen -f "${cur}"))
return 0
;;
--config-in)
COMPREPLY=($(compgen -f "${cur}"))
return 0
;;
--log-level)
COMPREPLY=($(compgen -W "silent quiet normal verbose debug trace" -- "${cur}"))
return 0
;;
*)
COMPREPLY=()
;;
esac
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
return 0
;;
batten__subcmd__baseline)
opts="-n -q -v -y -h --prune --dry-run --strictness --fail-on-warning --config-from --config-in --silent --quiet --verbose --debug --trace --log-level --no-color --no-input --yes --help"
if [[ ${cur} == -* || ${COMP_CWORD} -eq 2 ]] ; then
Expand Down Expand Up @@ -3592,7 +3645,7 @@ _batten() {
return 0
;;
batten__subcmd__help__subcmd__attribution)
opts="check identity"
opts="check tagger identity"
if [[ ${cur} == -* || ${COMP_CWORD} -eq 3 ]] ; then
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
return 0
Expand Down Expand Up @@ -3633,6 +3686,20 @@ _batten() {
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
return 0
;;
batten__subcmd__help__subcmd__attribution__subcmd__tagger)
opts=""
if [[ ${cur} == -* || ${COMP_CWORD} -eq 4 ]] ; then
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
return 0
fi
case "${prev}" in
*)
COMPREPLY=()
;;
esac
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
return 0
;;
batten__subcmd__help__subcmd__baseline)
opts=""
if [[ ${cur} == -* || ${COMP_CWORD} -eq 3 ]] ; then
Expand Down
Loading
Loading