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
33 changes: 33 additions & 0 deletions .serena/memories/workflow/board-states.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,39 @@ evidence.
A row whose acceptance does not fully resolve **stays In Review with the shortfall
recorded on it.** Promoting it anyway is the exact defect above, reproduced by hand.

### When a row's prose prescribes a repair, its own Acceptance overrules the prose

An acceptance-read is usually run against the tree. Run it against the row's **own
prescriptive prose** too, because the two can disagree and the prose is the half
that gets followed.

Measured 2026-08-22 on CLOUD-858. Its diagnosis section prescribed, for CLOUD-134,
_"converging the dialect is what would make that visible"_ — convert a retired Ready
dialect to the canonical one so `ready-lint` can read it. Executed literally that
produces a block which PASSES the gate. But CLOUD-134's own Decision section says
_"Gate on a named first consumer being concrete"_, and no consumer is named — so the
passing block would have certified an unmet precondition. CLOUD-858's own last
acceptance bullet forbids exactly that: _"a row that genuinely has open questions
leaves the ready queue rather than acquiring a Ready block that papers over them."_

The prescription and the acceptance were in the same body, four paragraphs apart,
and only one of them was wrong. The repair that satisfies both is to converge the
dialect **and** leave the queue — a Ready block that declares the open question,
in Backlog.

Why this recurs rather than being one author's slip: a diagnosis is written while
reading the defect, and an Acceptance while imagining the finished state. Nothing
compares them, and `ready-lint` cannot — it decides a block's clauses against each
other, never a body's prose against its own acceptance. So the check is manual and
cheap: **before executing a prescription a row hands you, read that row's Acceptance
and ask whether the prescribed result satisfies it.** Where they conflict, the
Acceptance is the specification and the prescription is a note.

The tell that you are in this case: the prescription names a mechanical edit
("converge", "rename", "add the label") and the Acceptance names a property
("does not paper over", "no scope moves"). A mechanical edit can always be
performed; whether it produces the property is the question the edit does not ask.

## Two things that trip agents up

1. **"Ready" is not a status.** It is the **Ready block** — text inside the issue
Expand Down
14 changes: 7 additions & 7 deletions install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,11 @@
# and a signature format (CLOUD-264); this is the floor beneath them, and it is
# a gate rather than a sensor.
#
# WHILE THE REPOSITORY IS PRIVATE (CLOUD-205) a GitHub token is required, read
# from BATTEN_GITHUB_TOKEN, GH_TOKEN or GITHUB_TOKEN. Nothing here requires one
# by construction — the same script installs unauthenticated the day the repo
# goes public, which is the "so the flip is cheap" property that decision asks
# the release machinery to keep.
# NO TOKEN IS REQUIRED BY CONSTRUCTION (CLOUD-205), which is the "so the flip is
# cheap" property that decision asks the release machinery to keep. One is still
# read from BATTEN_GITHUB_TOKEN, GH_TOKEN or GITHUB_TOKEN when present, because
# the unauthenticated release API is rate-limited per source address and a busy
# shared runner exhausts it.
#
# Output is pointer-only (non-negotiable rule 4): asset names, a target, a
# destination path. Never a token, never file contents. Exit 0 installed / 1
Expand Down Expand Up @@ -76,7 +76,7 @@ usage() {
BATTEN_TARGET override target detection
BATTEN_INSTALL_DIR destination; default \${XDG_BIN_HOME:-\$HOME/.local/bin}
BATTEN_GITHUB_TOKEN token for the release API (also GH_TOKEN,
GITHUB_TOKEN). Required while the repo is private.
GITHUB_TOKEN). Optional; raises the rate limit.
EOF
}

Expand Down Expand Up @@ -236,7 +236,7 @@ main() {
rel_url="$API/repos/$REPO/releases/latest"
fi
api_get "$rel_url" "application/vnd.github+json" "$tmp/release.json" ||
die 2 "cannot read the release list from $REPO. While this repository is private a token is required — set BATTEN_GITHUB_TOKEN, GH_TOKEN or GITHUB_TOKEN."
die 2 "cannot read the release list from $REPO. If you are being rate-limited, set BATTEN_GITHUB_TOKEN, GH_TOKEN or GITHUB_TOKEN."

flatten "$tmp/release.json" >"$tmp/release.line"
tag=$(json_string "$tmp/release.line" tag_name)
Expand Down
6 changes: 3 additions & 3 deletions mise-tasks/land.sh
Original file line number Diff line number Diff line change
Expand Up @@ -1473,7 +1473,7 @@ $verify_tail"
# is not evidence of anything.
note_phase "deferral-check(lap $lap)"
body=$(gh pr view "$pr" --json body --jq .body 2>/dev/null || true)
if [[ -n "$body" ]] && ! printf '%s' "$body" | mise run deferral-check; then
if [[ -n "$body" ]] && ! mise run deferral-check <<<"$body"; then
die "#$pr defers a decision with no ticket. File it and name the issue in that paragraph, then run land again."
fi

Expand Down Expand Up @@ -1502,7 +1502,7 @@ $verify_tail"
# fetch failed) simply yields no exemption, which is the pre-CLOUD-774
# behaviour and refuses rather than waves through.
note_phase "filed-here-check(lap $lap)"
if ! printf '%s' "$body" | mise run filed-here-check; then
if ! mise run filed-here-check <<<"$body"; then
die "#$pr filed a row that was never groomed to Ready, or names code this branch has open without closing it. Fix it here and close the row, comment on the issue that already owns it, or groom the row and run land again."
fi

Expand All @@ -1516,7 +1516,7 @@ $verify_tail"
# Same body, fetched once above, and the same fail-open reasoning: a body this
# never saw is not evidence that the PR closes nothing.
note_phase "closing-key-check(lap $lap)"
if [[ -n "$body" ]] && ! printf '%s' "$body" | mise run closing-key-check; then
if [[ -n "$body" ]] && ! mise run closing-key-check <<<"$body"; then
die "#$pr names its issue but never closes it, or closes some of the keys its commits served and strands the rest (CLOUD-674) — the gate's own output above names which. Merging either way leaves the board a column behind for at least one row. Write \"Closes <key>\" for every row this PR completes (or DO-NOT-CLOSE if it is not meant to complete them), then run land again."
fi

Expand Down
33 changes: 33 additions & 0 deletions tests/land.bats
Original file line number Diff line number Diff line change
Expand Up @@ -350,6 +350,12 @@ echo "\$*" >>"$BATS_TEST_TMPDIR/misecalls"
# wired to read stdin that nobody pipes to is inert, and it shipped inert once.
# NO BACKTICKS IN THIS HEREDOC: it is unquoted, so a backticked word is command
# substitution and the comment would try to RUN the task it names.
# CLOUD-995: a real gate can exit BEFORE it reads stdin. filed-here-check does
# exactly that under its bypass and on a detached HEAD, and closing-key-check on
# every could-not-read path. Modelled here, ahead of every read, so a case can
# prove the EPIPE it leaves the producer with is not read as a refusal. Only a
# case that asks for it is affected, so what CLOUD-774 pins below is untouched.
if [ -f "$BATS_TEST_TMPDIR/nodrain.\$2" ]; then exit 0; fi
if [ "\$2" = filed-here-check ]; then cat >"$BATS_TEST_TMPDIR/filedhere.stdin"; fi
rc="$BATS_TEST_TMPDIR/rc.mise.\$2"
if [ -f "\$rc" ]; then
Expand Down Expand Up @@ -616,6 +622,9 @@ ready_fails() { : >"$BATS_TEST_TMPDIR/rc.ready"; }
# case skips the check entirely rather than having to opt out of it.
pr_body() { printf '%s' "$1" >"$BATS_TEST_TMPDIR/prbody"; }
task_fails() { echo 1 >"$BATS_TEST_TMPDIR/rc.mise.$1"; }
# CLOUD-995. A task that succeeds WITHOUT reading the stdin it was handed, which
# is what a gate's own early exit looks like from the caller's side.
task_exits_undrained() { : >"$BATS_TEST_TMPDIR/nodrain.$1"; }
# A task that fails with a specific code, for one call only. `verify` answering
# 2 is "main moved while I ran" (CLOUD-318), and a lap that recovers from it is
# only a real lap if the next call succeeds.
Expand Down Expand Up @@ -1264,6 +1273,30 @@ runs_query_403() { : >"$BATS_TEST_TMPDIR/rc.runs"; }
[[ "$(cat "$BATS_TEST_TMPDIR/filedhere.stdin")" == *"Closes CLOUD-900"* ]]
}

# CLOUD-995. THE GATE'S OWN EXIT, not the pipeline's. `land` runs under pipefail,
# so a gate that exits before reading leaves the producer with EPIPE and the
# pipeline reports failure -- and `land` then reports the gate as having REFUSED.
# Two gates do exit early: filed-here-check returns 0 under its bypass and on a
# detached HEAD, closing-key-check exits 2 on every could-not-read path. So the
# bypass reddened the very lap it exists to wave through.
#
# THE BODY HAS TO BE BIGGER THAN THE PIPE BUFFER or the case proves nothing: a
# short one fits in the kernel buffer, the producer completes before the reader
# is gone, and the old piped form passes too. At 100k the write blocks and the
# EPIPE is certain, which is what makes this able to fail against the old shape.
#
# closing-key-check is failed on purpose so the lap stops somewhere nameable;
# without it `land` would run on to the CI poll and never end.
@test "CLOUD-995: a gate that exits before reading stdin is not a refusal" {
pr_body "$(printf 'x%.0s' $(seq 1 100000)) Closes CLOUD-900"
task_exits_undrained filed-here-check
task_fails closing-key-check
run "$LAND"
[ "$status" -eq 1 ]
[[ "$output" != *"filed a row that was never groomed to Ready"* ]]
[[ "$output" == *"names its issue but never closes it"* ]]
}

@test "a body that names its issue but never closes it stops before review is asked for" {
# CLOUD-192's stop, and it sits beside the deferral one for the same reason:
# readying is the commitment to review, and the board move is what tells
Expand Down
Loading