From 18acaa59b0af19cc84206866b6bae17c2ba897ef Mon Sep 17 00:00:00 2001 From: Alec Wenzowski Date: Sat, 22 Aug 2026 23:27:18 +0000 Subject: [PATCH 1/4] docs(memory): a row's prescription can contradict its own acceptance CLOUD-858 prescribed converging CLOUD-134's Ready dialect. Executed literally that yields a block which PASSES ready-lint, while CLOUD-858's own last acceptance bullet forbids a block that papers over open questions -- and CLOUD-134 has one, its Decision section gating on a named consumer that does not exist. The prescription and the acceptance sat four paragraphs apart in one body. Nothing compares them and ready-lint structurally cannot: it decides a block's clauses against each other, never a body's prose against its own acceptance. Refs: CLOUD-858 --- .serena/memories/workflow/board-states.md | 33 +++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/.serena/memories/workflow/board-states.md b/.serena/memories/workflow/board-states.md index cf5c4dade..f7b65a916 100644 --- a/.serena/memories/workflow/board-states.md +++ b/.serena/memories/workflow/board-states.md @@ -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 From e28b882944226d6277a94c35aa710250c46d7350 Mon Sep 17 00:00:00 2001 From: Alec Wenzowski Date: Sun, 23 Aug 2026 17:41:59 +0000 Subject: [PATCH 2/4] docs(install): the installer no longer tells the reader the repo is private MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three sites in install.sh: the header comment, the --help text, and the failure message when the release API cannot be read. All three said a GitHub token is required while the repository is private. CLOUD-869 corrected the same claim across README.md, AGENTS.md and the sandbox memory, and those have since landed. install.sh was missed because that row's acceptance greps are scoped to markdown and to the tracker links, so no predicate on the row could see a shell string — including the --help text, which is the copy a stranger actually reads. The token is optional by construction and always was. What it buys is a higher rate limit on the unauthenticated release API, which is what all three sites now say. Refs: CLOUD-869 --- install.sh | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/install.sh b/install.sh index 131bd0270..1fe34d63a 100755 --- a/install.sh +++ b/install.sh @@ -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 @@ -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 } @@ -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) From dd3e9c1c57628eb085eb06a66d1c2be1c0df6dfa Mon Sep 17 00:00:00 2001 From: Alec Wenzowski Date: Sun, 23 Aug 2026 18:52:08 +0000 Subject: [PATCH 3/4] fix(land): a gate that exits before reading is not a refusal `land` runs under `pipefail` and piped the PR body into three gates. Two of them can exit before they read it: `filed-here-check` returns 0 at its line 173 when BATTEN_FILED_HERE_BYPASS is set, and again on a missing git dir or a detached HEAD, but does not read stdin until 288; `closing-key-check` exits 2 on three could-not-read paths before its own read. When that happens the producer takes EPIPE and pipefail promotes it to a failed pipeline, so `land` reports the gate as having refused. The bypass therefore inverts the gate it is meant to switch off: setting BATTEN_FILED_HERE_BYPASS makes the lap die with "filed a row that was never groomed to Ready". Reproduced directly: a 200KB body piped to a consumer that exits 0 without reading reddens the pipeline under pipefail. Here-strings remove the pipeline, so the status is the gate's own and no longer depends on whether it drains first. `land.sh:1033` already prescribes exactly this for the same reason. This does NOT fix the parallel-suite failure that led here. That one does not reproduce in isolation -- 30/30 green on unmodified main once the harness ran through mise -- and belongs with CLOUD-426 and CLOUD-449. Refs: CLOUD-995 --- mise-tasks/land.sh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/mise-tasks/land.sh b/mise-tasks/land.sh index 6a5f0f10a..77b65a505 100755 --- a/mise-tasks/land.sh +++ b/mise-tasks/land.sh @@ -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 @@ -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 @@ -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 \" for every row this PR completes (or DO-NOT-CLOSE if it is not meant to complete them), then run land again." fi From ebf4f0772915859e6b59bda197d39e45597fd6b7 Mon Sep 17 00:00:00 2001 From: Alec Wenzowski Date: Sun, 23 Aug 2026 18:56:19 +0000 Subject: [PATCH 4/4] test(land): pin that a gate exiting before it reads is not a refusal The fix in the previous commit had no case. This adds one, and it is shown able to fail: against the piped shape it reddens with bash's own diagnosis, "printf: write error: Broken pipe", followed by land reporting a gate that never refused anything. A new stub lever, task_exits_undrained, models the thing the real gates do -- filed-here-check returns 0 under its bypass before it reads at line 288, closing-key-check exits 2 on its could-not-read paths. The lever sits ahead of every read in the stub so the EPIPE is real, and only a case that asks for it is affected, so what CLOUD-774 pins is untouched. The body is 100k because the size is load-bearing. A short body fits the kernel pipe buffer, the producer finishes before the reader is gone, and the old shape passes -- which is also why the original failure only appeared under the parallel suite, where scheduling decided the race. Bigger than the buffer makes the EPIPE certain and the case deterministic in both directions. Refs: CLOUD-995 --- tests/land.bats | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/tests/land.bats b/tests/land.bats index 506cedac5..eb75070a6 100644 --- a/tests/land.bats +++ b/tests/land.bats @@ -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 @@ -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. @@ -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