Skip to content

fix(tasks): read a cancelled required check as no verdict, not as red - #302

Merged
wenzowski merged 2 commits into
mainfrom
wenzowski/cloud-363-a-cancelled-required-check-is-read-as-red-so-a-run
Aug 12, 2026
Merged

wenzowski merged 2 commits into
mainfrom
wenzowski/cloud-363-a-cancelled-required-check-is-read-as-red-so-a-run

Conversation

@wenzowski

Copy link
Copy Markdown
Contributor

Fixes the wedge measured on #293 (CLOUD-363).

A cancelled run judged nothing. It is the absence of an answer, exactly like
the draft-era skipped CLOUD-327 taught this repo not to read as a conclusion —
but checks-green's catch-all bucketed it with failure, and the two rules
composed into a trap with no exit.

land lap 1 readied then force-pushed (the deliberate CLOUD-254 order). Both
events reached the same concurrency: ci-${{ github.ref }} group two seconds
apart, and the run on the SHA that would land was the one cancelled:

17:53:15  run 31519869739  head 3eba36a (the ready_for_review event)  -> kept running
17:53:17  run 31519873369  head a3589cc (the force-push event)        -> CANCELLED

ci-wait reported red, land re-drafted and stopped — correct, given "red".
Re-running it could not recover: HEAD was unchanged so the push moved nothing,
the verify receipt still held, and graded_runs counted cancelled as graded,
so the ready that would have replaced those runs was skipped. Two consecutive
invocations died on the identical stale set, neither able to create a single new
check-run. The only escape was a hand-minted SHA — a manual step outside the
loop land exists to drive.

Both halves, or the trap survives the first fix

  • mise-tasks/checks-green — cancelled joins skipped in the no-verdict
    set (exit 3); the catch-all keeps failure/timed_out/action_required. The
    bucket now carries the conclusion beside the name, so a stall with two
    spellings stays diagnosable: required check(s) with no verdict: ci cancelled, cross skipped.
  • mise-tasks/land — graded_runs drops cancelled, so a cancelled set
    reads as "no graded run" and the next lap re-fires the ready. It also gains
    neutral, which checks-green has always graded: the same drift with the
    sign reversed, buying a second run for a head that already carried its verdict.

Recovery works from both entry states. Re-drafted (what land actually leaves):
the pre-push ready block fires on isDraft + zero graded, and CLOUD-255 still
holds because readied=1 suppresses the --undo path. Left ready: the
post-push block fires on an unmoved ref + zero graded and --undo/ready
re-emits ready_for_review. Either way a fresh run appears, and
/commits/<sha>/check-runs defaults to filter=latest, so the new check-runs
supersede the cancelled ones by name.

The precedence is load-bearing now, and says so

#293's set was final failure plus five cancelled. final is a fan-in over
the others, so its failure was a consequence of the cancellations rather than
a judgement on the tree. Testing "no answer" before "red" is what keeps that set
recoverable; promoting red would put the branch straight back in the wedge. A
real failure leaves the no-verdict bucket empty and still exits 1 — CLOUD-363's
second acceptance clause.

Not re-ordering ready/push

The issue names that as a note rather than a task, and this PR keeps it that
way. Making a cancellation recoverable is the smaller, more general fix: it
holds for a manual cancel and a runner reclaim as well as for a concurrent push.

Tests

  • tests/checks-green.bats — a cancelled required check is exit 3 and names the
    word; a partial cancel is not redeemed by a sibling success; the measured
    final failure + five cancelled set is exit 3, not red.
  • tests/ci-wait.bats — the same set through the poll: it holds open rather
    than exiting 1.
  • tests/land.bats — head_is_all_cancelled(); a ready PR whose head is all
    cancelled has its ready re-fired, and the re-drafted PR the wedge leaves
    behind is readied once without --undo. The die/continue coverage counts
    are unchanged.

Refs: CLOUD-363

@linear-code

linear-code Bot commented Aug 11, 2026 •

Copy link
Copy Markdown
CLOUD-363 A `cancelled` required check is read as red, so a run superseded by `land`'s own ready/push race wedges the branch permanently

Why

Measured on PR #293 (CLOUD-346), 2026-08-11. land lap 1 readied the PR and then force-pushed, which is the deliberate order from CLOUD-254 — ready first, so the push's own event is the confirming run. Both events reached the same concurrency: ci-${{ github.ref }} group two seconds apart:

17:53:15  run 31519869739  head 3eba36a (pre-push SHA, the ready_for_review event)  -> kept running
17:53:17  run 31519873369  head a3589cc (the force-push event)                      -> CANCELLED

The run on the SHA that would land was the one cancelled; the run on the stale SHA survived. final then failed on a3589cc because its upstreams were cancelled, and ci-wait reported red:

::error:: CI is not green on a3589cc — final failure, msrv cancelled, ci cancelled,
          cross cancelled, darwin-link (aarch64-apple-darwin) cancelled, commit-lint cancelled.

The red is not a verdict. A cancelled run did not judge anything — it is the absence of an answer, exactly like the draft-era skipped that CLOUD-327 taught this repo not to read as a conclusion. But the predicate's catch-all buckets it with failure:

$2 == "skipped" { skipped = ...; next }
$2 == "success" || $2 == "neutral" { graded++; next }
{ graded++; bad = bad sep4 $3 " " $2; sep4 = ", " }   # <- cancelled lands here

And that wedges the branch, which is the part that makes this urgent. The two rules compose into a trap with no exit:

  1. ci-wait reads the cancelled set as red → land re-drafts the PR and stops (correct, given "red").
  2. Re-running land cannot recover. HEAD is unchanged, so the push is Everything up-to-date; the verify receipt still holds, so nothing re-proves; and land's ready block only fires when the SHA carries no graded run (CLOUD-255) — the cancelled runs are graded by graded_runs' reckoning, so the ready is skipped. Lap 1 goes straight back to the same stale reading and re-drafts again.

Observed exactly that: two consecutive land invocations, both terminating on the identical cancelled set, neither able to create a single new check-run. The only recovery available to an agent is to hand-mint a new SHA (git commit --amend), which is a manual step outside the loop land exists to drive.

Ready

cancelled is not an answer. Move it to the same bucket skipped occupies — one place, since CLOUD-346 collapsed the predicate to a single definition.

  1. mise-tasks/checks-green — the catch-all keeps failure/timed_out/action_required; cancelled joins skipped in the "not an answer" set (exit 3). Name it in the same pointer line, distinguishing the two words so a stall is diagnosable: required check(s) with no verdict: ci cancelled, cross skipped.
  2. mise-tasks/land — graded_runs (line ~129) must agree, or the compose-trap survives the first fix: a cancelled set must read as "no graded run", so the lap re-fires the ready and a real run replaces it. The two read CI_REQUIRED_CHECKS from one place already; they must read cancelled the same way too.
  3. Consider whether the race itself is worth closing separately — the ready and the push both landing in one concurrency group is what produces the cancellation, and CLOUD-254/CLOUD-255 have iterated on that ordering twice. This issue does not propose re-ordering them: making a cancellation recoverable is the smaller, more general fix, and it holds whatever cancels a run (a concurrent push, a manual cancel, a runner reclaim). Filed as a note, not a task.

Test obligation

  • tests/checks-green.bats — a required check cancelled is exit 3 and named; a set mixing cancelled and success is still exit 3, not green; failure stays exit 1.
  • tests/land.bats — a SHA whose required checks are all cancelled re-fires the ready on the next lap rather than re-drafting; the existing lap-count assertions must still hold.

Commit type / bump: fix(tasks). Patch until 0.1.0.

Blockers: none. It sits on top of CLOUD-346's checks-green, which is in flight on PR #293; if that has not landed, this edits ci-wait's awk in the same shape.

Acceptance

  • A branch whose required checks are all cancelled is driven to green by re-running land alone, with no hand-minted SHA and no gh run rerun.
  • failure is still red — a cancellation being recoverable must not make a real failure recoverable.

Review in Linear

@wenzowski
wenzowski marked this pull request as ready for review August 11, 2026 19:02
@wenzowski
wenzowski force-pushed the wenzowski/cloud-363-a-cancelled-required-check-is-read-as-red-so-a-run branch from 955ba46 to 8029893 Compare August 11, 2026 19:18
@wenzowski

Copy link
Copy Markdown
Contributor Author

/fast-forward

@wenzowski

Copy link
Copy Markdown
Contributor Author

Triggered from #302 (comment) by @​wenzowski.

Trying to fast forward main (37bf48d) to wenzowski/cloud-363-a-cancelled-required-check-is-read-as-red-so-a-run (8029893).

Target branch (main):

commit 37bf48d4f645e57937455ef8ca4d791a4859bf4a (HEAD -> main, origin/main)
Author: Claude <noreply@anthropic.com>
Date:   Tue Aug 11 18:58:45 2026 +0000

    feat(spec): emit the derived read-only allowlist beside the tree
    
    `spec::read_only_allowlist` derives §5's agent allowlist from the same walk
    that builds the emitted tree, so an unclassified command cannot leak in — but
    it had no caller outside this crate's own tests. A derivation nothing emits is
    one every consumer has to re-implement, and this filter's failure mode is
    advertising a write-bearing verb as agent-safe: `enforce` may run user-supplied
    commands and `hook` adjudicates someone else's write, and both stay off the
    list only because the filter is `effect == read`.
    
    So emit it. `SpecDocument` flattens the command tree at the document root and
    adds `read_only_allowlist` beside it, which keeps every root key a consumer
    already reads (`path`, `subcommands`, …) exactly where it was.
    
    The pinning test asserts the emitted key IS the `effect == read` filter over
    the emitted tree, recomputed from the same document — not a second copy of the
    path list, which `allowlist_is_exactly_the_read_commands` already holds.
    
    Refs: CLOUD-217

Pull request (wenzowski/cloud-363-a-cancelled-required-check-is-read-as-red-so-a-run):

commit 8029893b96cd9ad620d2b308bde58e8d28c82041 (pull_request/wenzowski/cloud-363-a-cancelled-required-check-is-read-as-red-so-a-run)
Author: Claude <noreply@anthropic.com>
Date:   Tue Aug 11 18:52:17 2026 +0000

    fix(tasks): read a cancelled required check as no verdict, not as red
    
    A cancelled run judged nothing. It is the absence of an answer, exactly
    like the draft-era `skipped` CLOUD-327 taught this repo not to read as a
    conclusion — but `checks-green`'s catch-all bucketed it with `failure`,
    and the two rules composed into a trap with no exit.
    
    Measured on #293: `land` readied then force-pushed, both events reached
    the same `concurrency: ci-<ref>` group two seconds apart, and the run on
    the SHA that would land was the one cancelled. `ci-wait` reported red,
    `land` re-drafted and stopped — correctly, given "red". Re-running it
    could not recover: HEAD was unchanged so the push moved nothing, the
    verify receipt still held, and `graded_runs` counted `cancelled` as
    graded, so the ready that would have replaced those runs was skipped.
    Two consecutive invocations died on the identical stale set, neither
    able to create a single new check-run. The only escape was a hand-minted
    SHA, which is a manual step outside the loop `land` exists to drive.
    
    Both halves move together or the trap survives the first fix:
    
    * `checks-green` — `cancelled` joins `skipped` in the no-verdict set
      (exit 3). The bucket now carries the conclusion beside the name, so a
      stall that has two spellings can still be diagnosed:
      `required check(s) with no verdict: ci cancelled, cross skipped`.
    * `land` — `graded_runs` drops `cancelled`, so a cancelled set reads as
      "no graded run" and the next lap re-fires the ready. It gains
      `neutral`, which `checks-green` has always graded: the same drift
      with the sign reversed, buying a second run for a head that already
      carried its verdict.
    
    The precedence between the two buckets is now load-bearing and says so.
    #293's set was `final failure` plus five `cancelled`; `final` is a
    fan-in, so its failure was a consequence of the cancellations rather
    than a judgement on the tree. Testing "no answer" before "red" is what
    keeps that set recoverable, and a real failure leaves the bucket empty
    and still exits 1.
    
    This does not re-order ready/push. Making a cancellation recoverable is
    the smaller, more general fix: it holds for a manual cancel and a runner
    reclaim too.
    
    Refs: CLOUD-363

Can't fast forward main (37bf48d) to wenzowski/cloud-363-a-cancelled-required-check-is-read-as-red-so-a-run (8029893). main (37bf48d) is not a direct ancestor of wenzowski/cloud-363-a-cancelled-required-check-is-read-as-red-so-a-run (8029893). Branches appear to have diverged at dc3dcc5:

* 37bf48d4f645e57937455ef8ca4d791a4859bf4a feat(spec): emit the derived read-only allowlist beside the tree
| * 8029893b96cd9ad620d2b308bde58e8d28c82041 fix(tasks): read a cancelled required check as no verdict, not as red
|/  
* dc3dcc58c52ae6bc946450ada2ed463225c2cbc2 ci(memory): correct the fan-out cost model and the plan-destination rule

commit dc3dcc58c52ae6bc946450ada2ed463225c2cbc2
Author: Claude <noreply@anthropic.com>
Date:   Tue Aug 11 18:57:18 2026 +0000

    ci(memory): correct the fan-out cost model and the plan-destination rule
    
    Two things this file said that optimise against the wrong cost.
    
    The cost model. It named a rising re-verify rate as the stop signal for
    parallelism. `land` laps unattended — a fast-forward refusal rebases and
    re-verifies with no model turn — so a moved base spends CPU and wall-clock,
    both free here, and zero tokens. Re-verifying IS the loop working. What
    actually costs is narrower: a rebase CONFLICT needs a human, a CI run `main`
    voids burns metered minutes, and tokens are spent only when a session returns
    to the model. Each has its own control, and they are now tabulated. The
    objective is pace of landed work per token, not collision avoidance:
    coordination between sessions is impossible by construction, so collisions are
    designed for in the CSMA/CD sense, and the target is a saturated queue — every
    merge followed by a sibling already rebased, verified and ready behind it.
    
    The plan destination. Telling a child to post its plan as a Linear comment was
    carried over from a planning-only fleet that had no approval channel, where a
    conclusion not written down died with the session. Under plan mode the approval
    UI is the destination and the work lands as commits and PRs, so the instruction
    is now a second authority that goes stale the moment the plan changes under
    review. It survives only for research and decision tickets, whose output is a
    conclusion rather than an artifact.
    
    The admission-control gap this exposes — nothing rations the transition out of
    draft, so N sessions buy N confirming runs for one merge slot — is CLOUD-369,
    not prose here.
    
    Refs: CLOUD-367

Rebase locally, and then force push to wenzowski/cloud-363-a-cancelled-required-check-is-read-as-red-so-a-run.

@wenzowski
wenzowski force-pushed the wenzowski/cloud-363-a-cancelled-required-check-is-read-as-red-so-a-run branch from 8029893 to f14116c Compare August 11, 2026 19:27
@wenzowski

Copy link
Copy Markdown
Contributor Author

/fast-forward

@wenzowski

Copy link
Copy Markdown
Contributor Author

Triggered from #302 (comment) by @​wenzowski.

Trying to fast forward main (ec3639a) to wenzowski/cloud-363-a-cancelled-required-check-is-read-as-red-so-a-run (f14116c).

Target branch (main):

commit ec3639a5f87a043c350ff85159db768a3fe3cfcf (HEAD -> main, origin/main)
Author: Claude <noreply@anthropic.com>
Date:   Tue Aug 11 18:51:35 2026 +0000

    fix(session-start): run doctor inside the hook's synchronous window
    
    `mise install` returning is not "the toolchain is settled". doctor provisions
    what mise does not own — the rustup cross targets above all — and left outside
    the SessionStart window it runs for the first time inside the first
    `mise run verify`, concurrently with whatever the sandbox is still laying down.
    Measured on a cold container 2026-08-07: that first verify died in doctor with a
    `detected conflict` rollback on rust-std-aarch64-apple-darwin, and a second
    doctor, nothing changed but time, exited 0. The message names cross-compilation,
    so it costs the reader a debugging session on a machine where nothing is broken.
    
    This is not a retry. It does not make the race survivable, it empties the window
    the race needs: by the time any task runs, the targets are already installed.
    CLOUD-220's per-toolchain mutex in target-ensure serializes concurrent writers
    and still stands — it covers the warm case (the verify graph racing itself,
    CLOUD-201), this covers the cold one. doctor itself is unchanged.
    
    The step goes through the hook's existing `step` helper, so it inherits the
    loud-failure contract, and runs after mise install (its rustup half needs the
    provisioned toolchain) and before container-preflight (a halt, which wants
    provisioning finished). tests/session-start.bats records every mise invocation
    through its stub and asserts that ordering, rather than grepping the source;
    doctor joins container-preflight in being neutralised there, because its rustup
    half reaches the network and test:bats already depends on `doctor --no-targets`.
    
    Refs: CLOUD-218

Pull request (wenzowski/cloud-363-a-cancelled-required-check-is-read-as-red-so-a-run):

commit f14116c58487d8acf1f1faec969e0336ceb63365 (pull_request/wenzowski/cloud-363-a-cancelled-required-check-is-read-as-red-so-a-run)
Author: Claude <noreply@anthropic.com>
Date:   Tue Aug 11 18:52:17 2026 +0000

    fix(tasks): read a cancelled required check as no verdict, not as red
    
    A cancelled run judged nothing. It is the absence of an answer, exactly
    like the draft-era `skipped` CLOUD-327 taught this repo not to read as a
    conclusion — but `checks-green`'s catch-all bucketed it with `failure`,
    and the two rules composed into a trap with no exit.
    
    Measured on #293: `land` readied then force-pushed, both events reached
    the same `concurrency: ci-<ref>` group two seconds apart, and the run on
    the SHA that would land was the one cancelled. `ci-wait` reported red,
    `land` re-drafted and stopped — correctly, given "red". Re-running it
    could not recover: HEAD was unchanged so the push moved nothing, the
    verify receipt still held, and `graded_runs` counted `cancelled` as
    graded, so the ready that would have replaced those runs was skipped.
    Two consecutive invocations died on the identical stale set, neither
    able to create a single new check-run. The only escape was a hand-minted
    SHA, which is a manual step outside the loop `land` exists to drive.
    
    Both halves move together or the trap survives the first fix:
    
    * `checks-green` — `cancelled` joins `skipped` in the no-verdict set
      (exit 3). The bucket now carries the conclusion beside the name, so a
      stall that has two spellings can still be diagnosed:
      `required check(s) with no verdict: ci cancelled, cross skipped`.
    * `land` — `graded_runs` drops `cancelled`, so a cancelled set reads as
      "no graded run" and the next lap re-fires the ready. It gains
      `neutral`, which `checks-green` has always graded: the same drift
      with the sign reversed, buying a second run for a head that already
      carried its verdict.
    
    The precedence between the two buckets is now load-bearing and says so.
    #293's set was `final failure` plus five `cancelled`; `final` is a
    fan-in, so its failure was a consequence of the cancellations rather
    than a judgement on the tree. Testing "no answer" before "red" is what
    keeps that set recoverable, and a real failure leaves the bucket empty
    and still exits 1.
    
    This does not re-order ready/push. Making a cancellation recoverable is
    the smaller, more general fix: it holds for a manual cancel and a runner
    reclaim too.
    
    Refs: CLOUD-363

Can't fast forward main (ec3639a) to wenzowski/cloud-363-a-cancelled-required-check-is-read-as-red-so-a-run (f14116c). main (ec3639a) is not a direct ancestor of wenzowski/cloud-363-a-cancelled-required-check-is-read-as-red-so-a-run (f14116c). Branches appear to have diverged at 37bf48d:

* f14116c58487d8acf1f1faec969e0336ceb63365 fix(tasks): read a cancelled required check as no verdict, not as red
| * ec3639a5f87a043c350ff85159db768a3fe3cfcf fix(session-start): run doctor inside the hook's synchronous window
|/  
* 37bf48d4f645e57937455ef8ca4d791a4859bf4a feat(spec): emit the derived read-only allowlist beside the tree

commit 37bf48d4f645e57937455ef8ca4d791a4859bf4a
Author: Claude <noreply@anthropic.com>
Date:   Tue Aug 11 18:58:45 2026 +0000

    feat(spec): emit the derived read-only allowlist beside the tree
    
    `spec::read_only_allowlist` derives §5's agent allowlist from the same walk
    that builds the emitted tree, so an unclassified command cannot leak in — but
    it had no caller outside this crate's own tests. A derivation nothing emits is
    one every consumer has to re-implement, and this filter's failure mode is
    advertising a write-bearing verb as agent-safe: `enforce` may run user-supplied
    commands and `hook` adjudicates someone else's write, and both stay off the
    list only because the filter is `effect == read`.
    
    So emit it. `SpecDocument` flattens the command tree at the document root and
    adds `read_only_allowlist` beside it, which keeps every root key a consumer
    already reads (`path`, `subcommands`, …) exactly where it was.
    
    The pinning test asserts the emitted key IS the `effect == read` filter over
    the emitted tree, recomputed from the same document — not a second copy of the
    path list, which `allowlist_is_exactly_the_read_commands` already holds.
    
    Refs: CLOUD-217

Rebase locally, and then force push to wenzowski/cloud-363-a-cancelled-required-check-is-read-as-red-so-a-run.

@wenzowski
wenzowski force-pushed the wenzowski/cloud-363-a-cancelled-required-check-is-read-as-red-so-a-run branch from f14116c to ff37991 Compare August 11, 2026 19:35
@wenzowski

Copy link
Copy Markdown
Contributor Author

/fast-forward

@wenzowski

Copy link
Copy Markdown
Contributor Author

Triggered from #302 (comment) by @​wenzowski.

Trying to fast forward main (f34f99a) to wenzowski/cloud-363-a-cancelled-required-check-is-read-as-red-so-a-run (ff37991).

Target branch (main):

commit f34f99a3a2c842b064087c3deb6e2476f412ed29 (HEAD -> main, origin/main)
Author: Claude <noreply@anthropic.com>
Date:   Tue Aug 11 18:59:53 2026 +0000

    test(findings): hold the no-escalation law over the bytes, not over a struct
    
    CLOUD-80's residual acceptance clause — "escalation on re-touch is absent" —
    was carried by an in-memory test that drives `FindingRecord::upsert` on a
    struct. That test cannot see either way a stored tier could actually move: a
    write path that re-derives it, and a later scan that re-rates the rule. Both
    need a real store dir.
    
    Two store-level tests, asserted over the record file:
    
    - `an_nth_observation_never_re_tiers_the_stored_record` runs the same identity
      through `record` five times, total over `RuleSeverity::ALL` so every
      `AdvisoryTier` is exercised, and compares the persisted `"tier"` line
      byte-for-byte against the first observation's — while asserting the
      occurrence count did move, so it cannot pass on a store that recorded
      nothing.
    - `a_re_rated_rule_never_re_tiers_a_settled_finding` records one identity at
      `warn`, then again at `deny`, and pins that the settled record keeps tier
      `caution` and severity `warn`. Severity is not an identity input, so the
      re-rating routes to the same record; `record` reusing it rather than
      refreshing from the rule now firing was documented and untested.
    
    Negative control run before landing: re-deriving the tier in `record`'s update
    path, plus a count-keyed escalation, fails both new tests and leaves the
    in-memory sibling green.
    
    Refs: CLOUD-80

Pull request (wenzowski/cloud-363-a-cancelled-required-check-is-read-as-red-so-a-run):

commit ff37991e419270d80475f83f68eb843322566c62 (pull_request/wenzowski/cloud-363-a-cancelled-required-check-is-read-as-red-so-a-run)
Author: Claude <noreply@anthropic.com>
Date:   Tue Aug 11 18:52:17 2026 +0000

    fix(tasks): read a cancelled required check as no verdict, not as red
    
    A cancelled run judged nothing. It is the absence of an answer, exactly
    like the draft-era `skipped` CLOUD-327 taught this repo not to read as a
    conclusion — but `checks-green`'s catch-all bucketed it with `failure`,
    and the two rules composed into a trap with no exit.
    
    Measured on #293: `land` readied then force-pushed, both events reached
    the same `concurrency: ci-<ref>` group two seconds apart, and the run on
    the SHA that would land was the one cancelled. `ci-wait` reported red,
    `land` re-drafted and stopped — correctly, given "red". Re-running it
    could not recover: HEAD was unchanged so the push moved nothing, the
    verify receipt still held, and `graded_runs` counted `cancelled` as
    graded, so the ready that would have replaced those runs was skipped.
    Two consecutive invocations died on the identical stale set, neither
    able to create a single new check-run. The only escape was a hand-minted
    SHA, which is a manual step outside the loop `land` exists to drive.
    
    Both halves move together or the trap survives the first fix:
    
    * `checks-green` — `cancelled` joins `skipped` in the no-verdict set
      (exit 3). The bucket now carries the conclusion beside the name, so a
      stall that has two spellings can still be diagnosed:
      `required check(s) with no verdict: ci cancelled, cross skipped`.
    * `land` — `graded_runs` drops `cancelled`, so a cancelled set reads as
      "no graded run" and the next lap re-fires the ready. It gains
      `neutral`, which `checks-green` has always graded: the same drift
      with the sign reversed, buying a second run for a head that already
      carried its verdict.
    
    The precedence between the two buckets is now load-bearing and says so.
    #293's set was `final failure` plus five `cancelled`; `final` is a
    fan-in, so its failure was a consequence of the cancellations rather
    than a judgement on the tree. Testing "no answer" before "red" is what
    keeps that set recoverable, and a real failure leaves the bucket empty
    and still exits 1.
    
    This does not re-order ready/push. Making a cancellation recoverable is
    the smaller, more general fix: it holds for a manual cancel and a runner
    reclaim too.
    
    Refs: CLOUD-363

Can't fast forward main (f34f99a) to wenzowski/cloud-363-a-cancelled-required-check-is-read-as-red-so-a-run (ff37991). main (f34f99a) is not a direct ancestor of wenzowski/cloud-363-a-cancelled-required-check-is-read-as-red-so-a-run (ff37991). Branches appear to have diverged at ec3639a:

* ff37991e419270d80475f83f68eb843322566c62 fix(tasks): read a cancelled required check as no verdict, not as red
| * f34f99a3a2c842b064087c3deb6e2476f412ed29 test(findings): hold the no-escalation law over the bytes, not over a struct
|/  
* ec3639a5f87a043c350ff85159db768a3fe3cfcf fix(session-start): run doctor inside the hook's synchronous window

commit ec3639a5f87a043c350ff85159db768a3fe3cfcf
Author: Claude <noreply@anthropic.com>
Date:   Tue Aug 11 18:51:35 2026 +0000

    fix(session-start): run doctor inside the hook's synchronous window
    
    `mise install` returning is not "the toolchain is settled". doctor provisions
    what mise does not own — the rustup cross targets above all — and left outside
    the SessionStart window it runs for the first time inside the first
    `mise run verify`, concurrently with whatever the sandbox is still laying down.
    Measured on a cold container 2026-08-07: that first verify died in doctor with a
    `detected conflict` rollback on rust-std-aarch64-apple-darwin, and a second
    doctor, nothing changed but time, exited 0. The message names cross-compilation,
    so it costs the reader a debugging session on a machine where nothing is broken.
    
    This is not a retry. It does not make the race survivable, it empties the window
    the race needs: by the time any task runs, the targets are already installed.
    CLOUD-220's per-toolchain mutex in target-ensure serializes concurrent writers
    and still stands — it covers the warm case (the verify graph racing itself,
    CLOUD-201), this covers the cold one. doctor itself is unchanged.
    
    The step goes through the hook's existing `step` helper, so it inherits the
    loud-failure contract, and runs after mise install (its rustup half needs the
    provisioned toolchain) and before container-preflight (a halt, which wants
    provisioning finished). tests/session-start.bats records every mise invocation
    through its stub and asserts that ordering, rather than grepping the source;
    doctor joins container-preflight in being neutralised there, because its rustup
    half reaches the network and test:bats already depends on `doctor --no-targets`.
    
    Refs: CLOUD-218

Rebase locally, and then force push to wenzowski/cloud-363-a-cancelled-required-check-is-read-as-red-so-a-run.

@wenzowski
wenzowski force-pushed the wenzowski/cloud-363-a-cancelled-required-check-is-read-as-red-so-a-run branch 2 times, most recently from 111a7c7 to ba7baac Compare August 11, 2026 19:53
@wenzowski

Copy link
Copy Markdown
Contributor Author

/fast-forward

@wenzowski

Copy link
Copy Markdown
Contributor Author

Triggered from #302 (comment) by @​wenzowski.

Trying to fast forward main (42ef663) to wenzowski/cloud-363-a-cancelled-required-check-is-read-as-red-so-a-run (ba7baac).

Target branch (main):

commit 42ef66342352cf2219fd94ab2503dc228e1dd8b5 (HEAD -> main, origin/main)
Author: Claude <noreply@anthropic.com>
Date:   Tue Aug 11 19:09:46 2026 +0000

    test(session): compare the whole store across a restart, not a subset
    
    The predicate is that a kill-and-restart preserves the findings store
    byte-for-byte, and the assertion was over the fields the test helper
    happens to parse — fingerprint, rule and instance counts. It would not
    have noticed the tier, the presentation or a disposition changing across
    the restart, which are exactly the fields no verb can vary yet and so the
    ones nothing else is watching.
    
    Refs: CLOUD-83

Pull request (wenzowski/cloud-363-a-cancelled-required-check-is-read-as-red-so-a-run):

commit ba7baac290223a5b96837dcb58457248557785df (pull_request/wenzowski/cloud-363-a-cancelled-required-check-is-read-as-red-so-a-run)
Author: Claude <noreply@anthropic.com>
Date:   Tue Aug 11 18:52:17 2026 +0000

    fix(tasks): read a cancelled required check as no verdict, not as red
    
    A cancelled run judged nothing. It is the absence of an answer, exactly
    like the draft-era `skipped` CLOUD-327 taught this repo not to read as a
    conclusion — but `checks-green`'s catch-all bucketed it with `failure`,
    and the two rules composed into a trap with no exit.
    
    Measured on #293: `land` readied then force-pushed, both events reached
    the same `concurrency: ci-<ref>` group two seconds apart, and the run on
    the SHA that would land was the one cancelled. `ci-wait` reported red,
    `land` re-drafted and stopped — correctly, given "red". Re-running it
    could not recover: HEAD was unchanged so the push moved nothing, the
    verify receipt still held, and `graded_runs` counted `cancelled` as
    graded, so the ready that would have replaced those runs was skipped.
    Two consecutive invocations died on the identical stale set, neither
    able to create a single new check-run. The only escape was a hand-minted
    SHA, which is a manual step outside the loop `land` exists to drive.
    
    Both halves move together or the trap survives the first fix:
    
    * `checks-green` — `cancelled` joins `skipped` in the no-verdict set
      (exit 3). The bucket now carries the conclusion beside the name, so a
      stall that has two spellings can still be diagnosed:
      `required check(s) with no verdict: ci cancelled, cross skipped`.
    * `land` — `graded_runs` drops `cancelled`, so a cancelled set reads as
      "no graded run" and the next lap re-fires the ready. It gains
      `neutral`, which `checks-green` has always graded: the same drift
      with the sign reversed, buying a second run for a head that already
      carried its verdict.
    
    The precedence between the two buckets is now load-bearing and says so.
    #293's set was `final failure` plus five `cancelled`; `final` is a
    fan-in, so its failure was a consequence of the cancellations rather
    than a judgement on the tree. Testing "no answer" before "red" is what
    keeps that set recoverable, and a real failure leaves the bucket empty
    and still exits 1.
    
    This does not re-order ready/push. Making a cancellation recoverable is
    the smaller, more general fix: it holds for a manual cancel and a runner
    reclaim too.
    
    Refs: CLOUD-363

Can't fast forward main (42ef663) to wenzowski/cloud-363-a-cancelled-required-check-is-read-as-red-so-a-run (ba7baac). main (42ef663) is not a direct ancestor of wenzowski/cloud-363-a-cancelled-required-check-is-read-as-red-so-a-run (ba7baac). Branches appear to have diverged at 78d313a:

* ba7baac290223a5b96837dcb58457248557785df fix(tasks): read a cancelled required check as no verdict, not as red
| * 42ef66342352cf2219fd94ab2503dc228e1dd8b5 test(session): compare the whole store across a restart, not a subset
| * 497f6d5b98b533c525d6da0dc678604082dfa83a test(session): pin what a warm fork must keep
| * e7165259b0afb55ca82b7d23a72b18bb6987cb88 feat(state): record where a session left the journal
| * 708d12c6e7e58b4653f18373c94798ec9eb33233 feat(session): give a warm fork somewhere to resume from
|/  
* 78d313aea99ff609bf6e24360538bd8ba4543354 fix(lock-complete): name lock-check as the remedy, not the raw regeneration command

commit 78d313aea99ff609bf6e24360538bd8ba4543354
Author: Claude <noreply@anthropic.com>
Date:   Tue Aug 11 18:56:11 2026 +0000

    fix(lock-complete): name lock-check as the remedy, not the raw regeneration command
    
    The unlocked-tool pointer spelled out the regeneration command, which put the
    literal into an executable line — where tests/lock-complete.bats's "makes no
    network call" assertion reads it as this gate fetching. That assertion is a
    coarse string match on purpose and is worth keeping coarse, so the message
    moves instead.
    
    `mise run lock-check` is also the better pointer on its own terms: it is the
    single sanctioned lockfile writer, since `[settings] lockfile = false` denies
    the write to everything else (CLOUD-223).
    
    Refs: CLOUD-333

Rebase locally, and then force push to wenzowski/cloud-363-a-cancelled-required-check-is-read-as-red-so-a-run.

@wenzowski
wenzowski force-pushed the wenzowski/cloud-363-a-cancelled-required-check-is-read-as-red-so-a-run branch 2 times, most recently from 3d5f93a to 47c271d Compare August 11, 2026 20:10
@wenzowski

Copy link
Copy Markdown
Contributor Author

/fast-forward

@wenzowski

Copy link
Copy Markdown
Contributor Author

Triggered from #302 (comment) by @​wenzowski.

Trying to fast forward main (3fde7a8) to wenzowski/cloud-363-a-cancelled-required-check-is-read-as-red-so-a-run (47c271d).

Target branch (main):

commit 3fde7a81593c6a60d838b6006f8db6a638b4738f (HEAD -> main, origin/main)
Author: Claude <noreply@anthropic.com>
Date:   Tue Aug 11 19:30:27 2026 +0000

    fix(bench): keep the workflow's summary block off shellcheck's SC2016
    
    A backtick pair inside single quotes reads as legacy command substitution
    to shellcheck, which actionlint runs over every `run:` block, so the
    summary line failed the gate. Double-quoted with escaped backticks keeps
    the markdown code span and says nothing shellcheck has to guess at.
    
    Prettier's table realignment in README rides along — hk staged it.
    
    Refs: CLOUD-207

Pull request (wenzowski/cloud-363-a-cancelled-required-check-is-read-as-red-so-a-run):

commit 47c271db03a0890617889513b8213eaa7a5b4ee7 (pull_request/wenzowski/cloud-363-a-cancelled-required-check-is-read-as-red-so-a-run)
Author: Claude <noreply@anthropic.com>
Date:   Tue Aug 11 18:52:17 2026 +0000

    fix(tasks): read a cancelled required check as no verdict, not as red
    
    A cancelled run judged nothing. It is the absence of an answer, exactly
    like the draft-era `skipped` CLOUD-327 taught this repo not to read as a
    conclusion — but `checks-green`'s catch-all bucketed it with `failure`,
    and the two rules composed into a trap with no exit.
    
    Measured on #293: `land` readied then force-pushed, both events reached
    the same `concurrency: ci-<ref>` group two seconds apart, and the run on
    the SHA that would land was the one cancelled. `ci-wait` reported red,
    `land` re-drafted and stopped — correctly, given "red". Re-running it
    could not recover: HEAD was unchanged so the push moved nothing, the
    verify receipt still held, and `graded_runs` counted `cancelled` as
    graded, so the ready that would have replaced those runs was skipped.
    Two consecutive invocations died on the identical stale set, neither
    able to create a single new check-run. The only escape was a hand-minted
    SHA, which is a manual step outside the loop `land` exists to drive.
    
    Both halves move together or the trap survives the first fix:
    
    * `checks-green` — `cancelled` joins `skipped` in the no-verdict set
      (exit 3). The bucket now carries the conclusion beside the name, so a
      stall that has two spellings can still be diagnosed:
      `required check(s) with no verdict: ci cancelled, cross skipped`.
    * `land` — `graded_runs` drops `cancelled`, so a cancelled set reads as
      "no graded run" and the next lap re-fires the ready. It gains
      `neutral`, which `checks-green` has always graded: the same drift
      with the sign reversed, buying a second run for a head that already
      carried its verdict.
    
    The precedence between the two buckets is now load-bearing and says so.
    #293's set was `final failure` plus five `cancelled`; `final` is a
    fan-in, so its failure was a consequence of the cancellations rather
    than a judgement on the tree. Testing "no answer" before "red" is what
    keeps that set recoverable, and a real failure leaves the bucket empty
    and still exits 1.
    
    This does not re-order ready/push. Making a cancellation recoverable is
    the smaller, more general fix: it holds for a manual cancel and a runner
    reclaim too.
    
    Refs: CLOUD-363

Can't fast forward main (3fde7a8) to wenzowski/cloud-363-a-cancelled-required-check-is-read-as-red-so-a-run (47c271d). main (3fde7a8) is not a direct ancestor of wenzowski/cloud-363-a-cancelled-required-check-is-read-as-red-so-a-run (47c271d). Branches appear to have diverged at 040fa56:

* 47c271db03a0890617889513b8213eaa7a5b4ee7 fix(tasks): read a cancelled required check as no verdict, not as red
| * 3fde7a81593c6a60d838b6006f8db6a638b4738f fix(bench): keep the workflow's summary block off shellcheck's SC2016
| * 8854916d55b1126fcae162c601404edfc5cc6f9e feat(bench): measure the invocation cost, publish it, and gate it
|/  
* 040fa566b81b0c89387ac7bdb833e0e43d028e1c docs(resolve): mark §5's max_effect as specified, not implemented

commit 040fa566b81b0c89387ac7bdb833e0e43d028e1c
Author: Claude <noreply@anthropic.com>
Date:   Tue Aug 11 19:24:16 2026 +0000

    docs(resolve): mark §5's max_effect as specified, not implemented
    
    Two module headers asserted `max_effect` as an invariant already in the tree:
    `resolve.rs` said the raise-only override rule "extends §5's `max_effect`
    invariant to the config layer", and `verbs.rs` said sharing the effect
    vocabulary keeps "the raise-only `max_effect` rule" meaning one thing across
    the tool. Neither is true today. `max_effect` is per-flag effect annotations
    plus a monotone maximum over them; `Effect` is declared per command and carries
    no ordering, so there is nothing to take a maximum of.
    
    That is the CLOUD-198 class, and `resolve.rs` was the costly instance: it
    presented this layer's raise-only rule as a corollary of a mechanism that
    exists, so a reader checking whether config-layer monotonicity was load-bearing
    on its own found a sentence telling them it was not.
    
    Both now name the invariant as specified-not-implemented and point at CLOUD-27,
    which is where the implementation rides. No test: the Ready block puts the test
    obligation on the implementation path, and a doc comment stating a gap has
    nothing to assert until the gap closes.
    
    Refs: CLOUD-217

Rebase locally, and then force push to wenzowski/cloud-363-a-cancelled-required-check-is-read-as-red-so-a-run.

@wenzowski
wenzowski force-pushed the wenzowski/cloud-363-a-cancelled-required-check-is-read-as-red-so-a-run branch from 47c271d to bfd8395 Compare August 11, 2026 20:19
@wenzowski

Copy link
Copy Markdown
Contributor Author

/fast-forward

@wenzowski

Copy link
Copy Markdown
Contributor Author

Triggered from #302 (comment) by @​wenzowski.

Trying to fast forward main (a2ed37d) to wenzowski/cloud-363-a-cancelled-required-check-is-read-as-red-so-a-run (bfd8395).

Target branch (main):

commit a2ed37d212a129bfc423cf34a378d890ce19a0d2 (HEAD -> main, origin/main)
Author: Claude <noreply@anthropic.com>
Date:   Tue Aug 11 19:23:26 2026 +0000

    fix(lint): add the lint rows to the two committed surface lists, and fix the census's clippy denial
    
    `verify` found three things local iteration had not:
    
    - `spec.rs` pins the emitted row set and the derived read-only allowlist as two
      committed lists. Both are hand-maintained on purpose — a verb added, renamed
      or re-parented has to be stated — so `lint`/`lint brief` join them, the noun
      with its kind because the whole subtree is `read`.
    - The census's positional lookup used `map(..).unwrap_or_else(panic!)`, which is
      `clippy::map_unwrap_or`; a `let ... else` says the same thing.
    - rustfmt over the new module, the surface row and the tests.
    
    Refs: CLOUD-84

Pull request (wenzowski/cloud-363-a-cancelled-required-check-is-read-as-red-so-a-run):

commit bfd83951bb2748a2b88055912d578c6a95b4546e (pull_request/wenzowski/cloud-363-a-cancelled-required-check-is-read-as-red-so-a-run)
Author: Claude <noreply@anthropic.com>
Date:   Tue Aug 11 18:52:17 2026 +0000

    fix(tasks): read a cancelled required check as no verdict, not as red
    
    A cancelled run judged nothing. It is the absence of an answer, exactly
    like the draft-era `skipped` CLOUD-327 taught this repo not to read as a
    conclusion — but `checks-green`'s catch-all bucketed it with `failure`,
    and the two rules composed into a trap with no exit.
    
    Measured on #293: `land` readied then force-pushed, both events reached
    the same `concurrency: ci-<ref>` group two seconds apart, and the run on
    the SHA that would land was the one cancelled. `ci-wait` reported red,
    `land` re-drafted and stopped — correctly, given "red". Re-running it
    could not recover: HEAD was unchanged so the push moved nothing, the
    verify receipt still held, and `graded_runs` counted `cancelled` as
    graded, so the ready that would have replaced those runs was skipped.
    Two consecutive invocations died on the identical stale set, neither
    able to create a single new check-run. The only escape was a hand-minted
    SHA, which is a manual step outside the loop `land` exists to drive.
    
    Both halves move together or the trap survives the first fix:
    
    * `checks-green` — `cancelled` joins `skipped` in the no-verdict set
      (exit 3). The bucket now carries the conclusion beside the name, so a
      stall that has two spellings can still be diagnosed:
      `required check(s) with no verdict: ci cancelled, cross skipped`.
    * `land` — `graded_runs` drops `cancelled`, so a cancelled set reads as
      "no graded run" and the next lap re-fires the ready. It gains
      `neutral`, which `checks-green` has always graded: the same drift
      with the sign reversed, buying a second run for a head that already
      carried its verdict.
    
    The precedence between the two buckets is now load-bearing and says so.
    #293's set was `final failure` plus five `cancelled`; `final` is a
    fan-in, so its failure was a consequence of the cancellations rather
    than a judgement on the tree. Testing "no answer" before "red" is what
    keeps that set recoverable, and a real failure leaves the bucket empty
    and still exits 1.
    
    This does not re-order ready/push. Making a cancellation recoverable is
    the smaller, more general fix: it holds for a manual cancel and a runner
    reclaim too.
    
    Refs: CLOUD-363

Can't fast forward main (a2ed37d) to wenzowski/cloud-363-a-cancelled-required-check-is-read-as-red-so-a-run (bfd8395). main (a2ed37d) is not a direct ancestor of wenzowski/cloud-363-a-cancelled-required-check-is-read-as-red-so-a-run (bfd8395). Branches appear to have diverged at 3fde7a8:

* bfd83951bb2748a2b88055912d578c6a95b4546e fix(tasks): read a cancelled required check as no verdict, not as red
| * a2ed37d212a129bfc423cf34a378d890ce19a0d2 fix(lint): add the lint rows to the two committed surface lists, and fix the census's clippy denial
| * bb83902973a8652f1db3dbcc68820996559d5310 feat(lint): gate a delegation brief on the facts that do not inherit
|/  
* 3fde7a81593c6a60d838b6006f8db6a638b4738f fix(bench): keep the workflow's summary block off shellcheck's SC2016

commit 3fde7a81593c6a60d838b6006f8db6a638b4738f
Author: Claude <noreply@anthropic.com>
Date:   Tue Aug 11 19:30:27 2026 +0000

    fix(bench): keep the workflow's summary block off shellcheck's SC2016
    
    A backtick pair inside single quotes reads as legacy command substitution
    to shellcheck, which actionlint runs over every `run:` block, so the
    summary line failed the gate. Double-quoted with escaped backticks keeps
    the markdown code span and says nothing shellcheck has to guess at.
    
    Prettier's table realignment in README rides along — hk staged it.
    
    Refs: CLOUD-207

Rebase locally, and then force push to wenzowski/cloud-363-a-cancelled-required-check-is-read-as-red-so-a-run.

@wenzowski
wenzowski force-pushed the wenzowski/cloud-363-a-cancelled-required-check-is-read-as-red-so-a-run branch 3 times, most recently from ec49329 to 392b7db Compare August 11, 2026 20:45
@wenzowski

Copy link
Copy Markdown
Contributor Author

/fast-forward

@wenzowski

Copy link
Copy Markdown
Contributor Author

Triggered from #302 (comment) by @​wenzowski.

Trying to fast forward main (6947232) to wenzowski/cloud-363-a-cancelled-required-check-is-read-as-red-so-a-run (392b7db).

Target branch (main):

commit 6947232b13bc17174986a0e3bfe528d848617519 (HEAD -> main, origin/main)
Author: Claude <noreply@anthropic.com>
Date:   Tue Aug 11 19:57:59 2026 +0000

    perf(hk): glob batten-check and macos-link-check, and gate the glob's honesty
    
    `hk check` ran three steps unconditionally, because a step with no `glob` is
    never skipped. `batten-check` is `cargo run -p batten -- check`, so every
    commit paid a debug build whatever it touched: 9.47s cold from a fresh clone
    (CLOUD-212 run 1, D8) against 0.16s for the cheapest gate in the same run, and
    133-141ms warm, forever.
    
    The glob is the union of what `batten check` ACTUALLY reads, which is not the
    two things the obvious reading gives. Read at run_check_with in
    crates/batten/src/lib.rs, it evaluates every `[[rule]]` over that rule's own
    glob — and those reach outside crates/, to mise.toml, .github/workflows/*.yml
    and tests/*.bats — plus `budget::measure_all`, because a declared budget is a
    gate under `check` and not only under `policy budget` (CLOUD-50). That last one
    makes AGENTS.md and .serena/project.yml genuine inputs, so "a Markdown-only
    change cannot change this verdict" is false for the most-edited Markdown file
    in the repo, and a glob written from the obvious reading would have switched
    the gate off for it. Cargo.toml and Cargo.lock are in because the task uses
    `cargo run` deliberately, to judge the engine and the config as the pair that
    ships.
    
    `macos-link-check` globs the manifests alone: its predicate is `cargo metadata
    --filter-platform` over the resolved graph, and nothing else can move that
    graph. `no-docs-tree` deliberately keeps no glob — its input is the whole
    index, including a docs/ path an earlier commit left behind, and at ~95ms that
    is affordable.
    
    Deriving batten-check's list from batten.toml by hand makes hk.pkl a second
    authority over a set the config already defines, and a second authority narrows
    silently: add a rule whose glob names a new path and the step stops running for
    commits that touch only it, with nothing going red. So the glob ships with its
    mechanism (non-negotiable 2). `mise run batten-glob-check` is a pure function
    of the two committed files — every rule glob and every declared budget path
    must be covered verbatim or subsumed by a `P/**` entry, and a config it can
    parse nothing out of is exit 2 rather than a vacuous pass.
    
    Selection is asserted as data, never as a timing: `hk check --plan --json`
    prints each step's status without running it, and tests/hk-selection.bats pins
    both directions over the repository's own hk.pkl — README.md skips
    batten-check, AGENTS.md does not.
    
    Refs: CLOUD-224

Pull request (wenzowski/cloud-363-a-cancelled-required-check-is-read-as-red-so-a-run):

commit 392b7db6723d7b0a56c7d7d45893d0040d5cc00f (pull_request/wenzowski/cloud-363-a-cancelled-required-check-is-read-as-red-so-a-run)
Author: Claude <noreply@anthropic.com>
Date:   Tue Aug 11 18:52:17 2026 +0000

    fix(tasks): read a cancelled required check as no verdict, not as red
    
    A cancelled run judged nothing. It is the absence of an answer, exactly
    like the draft-era `skipped` CLOUD-327 taught this repo not to read as a
    conclusion — but `checks-green`'s catch-all bucketed it with `failure`,
    and the two rules composed into a trap with no exit.
    
    Measured on #293: `land` readied then force-pushed, both events reached
    the same `concurrency: ci-<ref>` group two seconds apart, and the run on
    the SHA that would land was the one cancelled. `ci-wait` reported red,
    `land` re-drafted and stopped — correctly, given "red". Re-running it
    could not recover: HEAD was unchanged so the push moved nothing, the
    verify receipt still held, and `graded_runs` counted `cancelled` as
    graded, so the ready that would have replaced those runs was skipped.
    Two consecutive invocations died on the identical stale set, neither
    able to create a single new check-run. The only escape was a hand-minted
    SHA, which is a manual step outside the loop `land` exists to drive.
    
    Both halves move together or the trap survives the first fix:
    
    * `checks-green` — `cancelled` joins `skipped` in the no-verdict set
      (exit 3). The bucket now carries the conclusion beside the name, so a
      stall that has two spellings can still be diagnosed:
      `required check(s) with no verdict: ci cancelled, cross skipped`.
    * `land` — `graded_runs` drops `cancelled`, so a cancelled set reads as
      "no graded run" and the next lap re-fires the ready. It gains
      `neutral`, which `checks-green` has always graded: the same drift
      with the sign reversed, buying a second run for a head that already
      carried its verdict.
    
    The precedence between the two buckets is now load-bearing and says so.
    #293's set was `final failure` plus five `cancelled`; `final` is a
    fan-in, so its failure was a consequence of the cancellations rather
    than a judgement on the tree. Testing "no answer" before "red" is what
    keeps that set recoverable, and a real failure leaves the bucket empty
    and still exits 1.
    
    This does not re-order ready/push. Making a cancellation recoverable is
    the smaller, more general fix: it holds for a manual cancel and a runner
    reclaim too.
    
    Refs: CLOUD-363

Can't fast forward main (6947232) to wenzowski/cloud-363-a-cancelled-required-check-is-read-as-red-so-a-run (392b7db). main (6947232) is not a direct ancestor of wenzowski/cloud-363-a-cancelled-required-check-is-read-as-red-so-a-run (392b7db). Branches appear to have diverged at 6569aeb:

* 392b7db6723d7b0a56c7d7d45893d0040d5cc00f fix(tasks): read a cancelled required check as no verdict, not as red
| * 6947232b13bc17174986a0e3bfe528d848617519 perf(hk): glob batten-check and macos-link-check, and gate the glob's honesty
|/  
* 6569aeb303e942642dd22feab96f221c67f7027e feat(rules)!: rename the command kind's `run` column to `check`, reserve `fix`

commit 6569aeb303e942642dd22feab96f221c67f7027e
Author: Claude <noreply@anthropic.com>
Date:   Tue Aug 11 19:02:59 2026 +0000

    feat(rules)!: rename the command kind's `run` column to `check`, reserve `fix`
    
    House style §9 specifies the `command` rule kind as a check/fix duality: an
    inspection-only `check` is the gate — the only side enforcement ever runs — and
    an optional mutating `fix` repairs what it condemned. The shipped `Rule` carried
    one `run` column, which does not express the split.
    
    §2 declares no back-compatibility surface and no deprecation aliases, so every
    config written against `run` becomes a breaking migration later. Today the key
    appears in no shipped config at all — only fixtures, tests and the README — so
    the rename costs nothing. It will not stay that way.
    
    `fix` parses and is executed by nothing: serialised fix execution is a
    capability this engine does not have. `enforce` refuses a row declaring one
    rather than running the check side and silently ignoring the repair, which would
    report green over work nobody did. Reserving the key now means an author who
    writes it is writing the final spelling.
    
    `run` survives as a refusal-only field, the shape `OverrideConfig::
    min_batten_version` already uses: "unknown field `run`" reads as a typo, where
    this is a rename with one specific fix, and the refusal has to carry it
    (CLOUD-122). It is deliberately outside `Rule::columns` — that census classifies
    the columns a kind may carry, and no kind carries this one.
    
    Both refusals are exit 1, not the exit 2 the issue's §5/§7 asked for. §7 makes 2
    the policy verdict — a violation found in the repository, or a mediated call
    denied — while a config naming a key or a capability this build does not have is
    the config-or-usage class. §5 spells the structurally identical sibling refusal
    (a rule the read-only half cannot honestly run) as exit 1, and `run_static` has
    implemented it that way since CLOUD-170; the issue's §3 describes that same
    landed refusal as exit 2, which is falsified by the code it describes. Emitting
    2 here would also make an engine gap read to a harness as a policy deny, which
    is what the structural fail-open exists to prevent.
    
    Refs: CLOUD-215

Rebase locally, and then force push to wenzowski/cloud-363-a-cancelled-required-check-is-read-as-red-so-a-run.

@wenzowski
wenzowski force-pushed the wenzowski/cloud-363-a-cancelled-required-check-is-read-as-red-so-a-run branch 2 times, most recently from 8bbef2c to d1f26fe Compare August 11, 2026 21:03
@wenzowski

Copy link
Copy Markdown
Contributor Author

/fast-forward

@wenzowski

Copy link
Copy Markdown
Contributor Author

/fast-forward

@wenzowski

Copy link
Copy Markdown
Contributor Author

Triggered from #302 (comment) by @​wenzowski.

Trying to fast forward main (5596daf) to wenzowski/cloud-363-a-cancelled-required-check-is-read-as-red-so-a-run (fde278b).

Target branch (main):

commit 5596dafa6bdeb8e13dc02a254c387f4fbbc7aa8d (HEAD -> main, origin/main)
Author: Claude <noreply@anthropic.com>
Date:   Tue Aug 11 19:26:47 2026 +0000

    fix(issue-guard): ask the competitor PR which issue it claims, not which it mentions
    
    CLOUD-338 split the claim derivation into `claimed-keys` because which issue a
    branch CLAIMS is a narrower question than which it MENTIONS — a body cites
    related issues as evidence, and conflating the two made this guard refuse its
    own PR twice. That narrowing was applied to this branch and not to the other
    one, so the comparison had a broad half and a narrow half.
    
    Measured: PR #306, `docs(agents): point at the attribution decision record
    (CLOUD-268)`, names CLOUD-133 in one row of an evidence table and nowhere else.
    It refused CLOUD-133's own first PR. Every PR body in this repo cites related
    issues — that is the house style — so the broad half fires on exactly the corpus
    the repo is written to produce.
    
    `claimed-keys` gains explicit sources so the same authority can answer about a
    PR this checkout did not author: `--branch`, `--title` and `--log`, with stdin
    still the body. Passing any of them switches off the git reads entirely rather
    than falling back per source, because a remote PR silently answered from the
    local branch would be a confident verdict about the wrong repository state.
    
    The title joins the branch at precedence 2 as a UNION rather than a precedence
    between them: for a PR you did not author the title is the other self-declaration
    of what the work is, and this repo's own convention ends every title with
    `(CLOUD-<n>)`. A body is not a self-declaration, which is the whole distinction.
    
    Refs: CLOUD-378

Pull request (wenzowski/cloud-363-a-cancelled-required-check-is-read-as-red-so-a-run):

commit fde278b966a3ccaa8b3b4c8311f5c9fdc1b4a1fd (pull_request/wenzowski/cloud-363-a-cancelled-required-check-is-read-as-red-so-a-run)
Author: Claude <noreply@anthropic.com>
Date:   Tue Aug 11 18:52:17 2026 +0000

    fix(tasks): read a cancelled required check as no verdict, not as red
    
    A cancelled run judged nothing. It is the absence of an answer, exactly
    like the draft-era `skipped` CLOUD-327 taught this repo not to read as a
    conclusion — but `checks-green`'s catch-all bucketed it with `failure`,
    and the two rules composed into a trap with no exit.
    
    Measured on #293: `land` readied then force-pushed, both events reached
    the same `concurrency: ci-<ref>` group two seconds apart, and the run on
    the SHA that would land was the one cancelled. `ci-wait` reported red,
    `land` re-drafted and stopped — correctly, given "red". Re-running it
    could not recover: HEAD was unchanged so the push moved nothing, the
    verify receipt still held, and `graded_runs` counted `cancelled` as
    graded, so the ready that would have replaced those runs was skipped.
    Two consecutive invocations died on the identical stale set, neither
    able to create a single new check-run. The only escape was a hand-minted
    SHA, which is a manual step outside the loop `land` exists to drive.
    
    Both halves move together or the trap survives the first fix:
    
    * `checks-green` — `cancelled` joins `skipped` in the no-verdict set
      (exit 3). The bucket now carries the conclusion beside the name, so a
      stall that has two spellings can still be diagnosed:
      `required check(s) with no verdict: ci cancelled, cross skipped`.
    * `land` — `graded_runs` drops `cancelled`, so a cancelled set reads as
      "no graded run" and the next lap re-fires the ready. It gains
      `neutral`, which `checks-green` has always graded: the same drift
      with the sign reversed, buying a second run for a head that already
      carried its verdict.
    
    The precedence between the two buckets is now load-bearing and says so.
    #293's set was `final failure` plus five `cancelled`; `final` is a
    fan-in, so its failure was a consequence of the cancellations rather
    than a judgement on the tree. Testing "no answer" before "red" is what
    keeps that set recoverable, and a real failure leaves the bucket empty
    and still exits 1.
    
    This does not re-order ready/push. Making a cancellation recoverable is
    the smaller, more general fix: it holds for a manual cancel and a runner
    reclaim too.
    
    Refs: CLOUD-363

Can't fast forward main (5596daf) to wenzowski/cloud-363-a-cancelled-required-check-is-read-as-red-so-a-run (fde278b). main (5596daf) is not a direct ancestor of wenzowski/cloud-363-a-cancelled-required-check-is-read-as-red-so-a-run (fde278b). Branches appear to have diverged at bf94e1d:

* fde278b966a3ccaa8b3b4c8311f5c9fdc1b4a1fd fix(tasks): read a cancelled required check as no verdict, not as red
| * 5596dafa6bdeb8e13dc02a254c387f4fbbc7aa8d fix(issue-guard): ask the competitor PR which issue it claims, not which it mentions
|/  
* bf94e1dfe0c7a8b15e9af1dd913266889b6e3531 docs(agents): point at the attribution decision record

commit bf94e1dfe0c7a8b15e9af1dd913266889b6e3531
Author: Alec Wenzowski <alec@wenzowski.com>
Date:   Tue Aug 11 19:00:46 2026 +0000

    docs(agents): point at the attribution decision record
    
    The three commit-metadata surfaces — authorship (accountability), disclosure
    (repo policy data) and provenance (records) — are fixed in a new authoritative
    Linear doc beside the house style and the DoR/DoD. AGENTS.md carries the
    pointer only; the budget is unchanged at 199 lines, paid for by tightening the
    specs preamble, the index preamble and the scope reminder.
    
    Refs: CLOUD-268

Rebase locally, and then force push to wenzowski/cloud-363-a-cancelled-required-check-is-read-as-red-so-a-run.

@wenzowski

Copy link
Copy Markdown
Contributor Author

Rebased onto b26c028 (was 29 commits behind) and force-pushed. The work here was complete; it had gone stale and then collided with CLOUD-436, which landed as d881bf6 a few hours ago and rewrote the same awk this PR edits.

The conflict was semantic, not textual. CLOUD-436 made checks-green judge each required name by its LATEST run (started_at, run id as tiebreak) instead of by the union of every run on the SHA, and gave land's graded_runs the same rule. This PR's cancelled-is-not-an-answer decision had to be re-expressed inside that structure rather than merged beside it:

  • checks-green: cancelled joins skipped in the not-an-answer bucket and in the rank used when two runs of one name cannot be ordered — so an unorderable pair holding a cancelled falls to "no answer" rather than to red. The bucket still carries the conclusion beside the name (ci cancelled, cross skipped), and this PR's rename to required check(s) with no verdict is preserved.
  • land: graded_runs drops cancelled and gains neutral, exactly as authored, now inside the per-name dedup — and cancelled ranks as a non-answer there too, or the two ends would disagree and the compose-trap would survive.
  • One test of mine that asserted the old required check(s) skipped wording now asserts the new one.

Both halves are mutation-proven, since a conflict resolution is exactly where one side gets silently dropped: removing cancelled from the bucket turns 3 of this PR's cases red; inverting the latest-run ordering turns 5 of CLOUD-436's red; letting graded_runs count cancelled again turns both compose-trap cases red. 87/87 across checks-green, ci-wait and land.

Nothing about this PR's intent changed — only where its decision lives.


Generated by Claude Code

@wenzowski
wenzowski force-pushed the wenzowski/cloud-363-a-cancelled-required-check-is-read-as-red-so-a-run branch 5 times, most recently from 73b8a34 to 4203412 Compare August 12, 2026 05:51
claude added 2 commits August 12, 2026 06:02
A cancelled run judged nothing. It is the absence of an answer, exactly
like the draft-era `skipped` CLOUD-327 taught this repo not to read as a
conclusion — but `checks-green`'s catch-all bucketed it with `failure`,
and the two rules composed into a trap with no exit.

Measured on #293: `land` readied then force-pushed, both events reached
the same `concurrency: ci-<ref>` group two seconds apart, and the run on
the SHA that would land was the one cancelled. `ci-wait` reported red,
`land` re-drafted and stopped — correctly, given "red". Re-running it
could not recover: HEAD was unchanged so the push moved nothing, the
verify receipt still held, and `graded_runs` counted `cancelled` as
graded, so the ready that would have replaced those runs was skipped.
Two consecutive invocations died on the identical stale set, neither
able to create a single new check-run. The only escape was a hand-minted
SHA, which is a manual step outside the loop `land` exists to drive.

Both halves move together or the trap survives the first fix:

* `checks-green` — `cancelled` joins `skipped` in the no-verdict set
  (exit 3). The bucket now carries the conclusion beside the name, so a
  stall that has two spellings can still be diagnosed:
  `required check(s) with no verdict: ci cancelled, cross skipped`.
* `land` — `graded_runs` drops `cancelled`, so a cancelled set reads as
  "no graded run" and the next lap re-fires the ready. It gains
  `neutral`, which `checks-green` has always graded: the same drift
  with the sign reversed, buying a second run for a head that already
  carried its verdict.

The precedence between the two buckets is now load-bearing and says so.
fan-in, so its failure was a consequence of the cancellations rather
than a judgement on the tree. Testing "no answer" before "red" is what
keeps that set recoverable, and a real failure leaves the bucket empty
and still exits 1.

This does not re-order ready/push. Making a cancellation recoverable is
the smaller, more general fix: it holds for a manual cancel and a runner
reclaim too.

Refs: CLOUD-363
The no-verdict case went red inside a full parallel gate and passed
standalone — CLOUD-426's class, in the case CLOUD-423 added. The stub
watcher answered immediately, so land group-killed the verify child before
it had appended its own call, and the lap that followed counted one verify
where the case demands two. Whether the assertion held was decided by which
of two forks won, which is exactly the shape this repo cannot afford: a red
gate that reproduces nowhere teaches an agent to re-run until green.

The watcher now waits for the verify it races to have registered before it
answers, whichever way it is about to answer — a bounded wait on a real
event, not a guessed interval, and applied to both levers rather than only
the one that failed. Stressed 8x under a concurrently running full suite:
zero failures. The race-deleted mutant still turns the case red, so the
synchronisation bounds the timing without blunting what it detects.

Refs: CLOUD-426, CLOUD-423

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01X4tzyT3Q3hXo5QFEESENYP
@wenzowski
wenzowski force-pushed the wenzowski/cloud-363-a-cancelled-required-check-is-read-as-red-so-a-run branch from 4203412 to 4de3219 Compare August 12, 2026 06:09
@sonarqubecloud

Copy link
Copy Markdown

@wenzowski

Copy link
Copy Markdown
Contributor Author

/fast-forward

@wenzowski
wenzowski merged commit 4de3219 into main Aug 12, 2026
7 checks passed
@wenzowski
wenzowski deleted the wenzowski/cloud-363-a-cancelled-required-check-is-read-as-red-so-a-run branch August 12, 2026 06:15
wenzowski pushed a commit that referenced this pull request Aug 12, 2026
…wo exhaustions (CLOUD-399)

`LAND_MAX_LAPS` bounds CI matrices (~17 job-minutes each); `LAND_LOCK_MAX_WAITS`
bounds lease waits (a conditional poll, no runner). Both defaulted to 8, which
authorised ~2 runner-hours of metered spend against ~16 minutes of free waiting
— and measured on #302, the expensive budget drained first. Laps to 2, waits to
64, so the defaults express many free waits and few paid laps. Neither is a
clock; both stay env-overridable.

Both exhaustions also ended in `exit 1`, so a saturated fleet ("wait, and land
later") and a runaway branch ("main moves faster than a lap takes") were
indistinguishable to anything but a human reading stderr. They now carry their
own codes through `die_with`, which `die` delegates to so the other eighteen
call sites are unchanged.

The stop-count sensor counts both spellings. Counting only `die "` would have
read the split as two stops removed, and would have let a future `die_with` stop
be added completely uncounted — the exact blindness that assertion exists to
prevent, reintroduced by the change that split the helper.

Refs: CLOUD-399
wenzowski pushed a commit that referenced this pull request Aug 12, 2026
…t once (CLOUD-418, CLOUD-376, CLOUD-470)

These three land together because they cannot be split: the mutation rows added
here name the very cases the other two changes add, and a `#MUTANT` row naming a
case that does not exist yet fails the sweep by design.

CLOUD-418 — `mise run mutant`. The obligation was "a rule ships with a runnable
gate" — a gate that EXISTS. Nothing required evidence it DISCRIMINATES, and a
test passing on both the fixed and the broken code satisfied every other rule
here. That failed four times (CLOUD-235, CLOUD-352, CLOUD-401), the last one
live: a concurrency test written for a real race was green, and stayed green when
the bug was restored. Now a declared one-line corruption is applied to a
throwaway copy and the named case must go red.

The enforced set is data: `$MUTANT_GATES`. A gate IN it with no declaration fails
— that is the anti-vacuity term, without which the task reports success over a
set it never touched. A gate not in it is a filed row (CLOUD-480), never a silent
exemption. Seeded with the five gates this bundle touches, so its
"mutation-checked per CLOUD-418" obligations are cashed rather than cited. It
mutates a copy of the WORKING tree, never the tracked file — mutating in place
staged a mutant into a pushed commit on 2026-08-12 — and refuses two evasions an
inert or misnamed row would otherwise buy. Both were hit on its first run, and
one exposed a test written minutes earlier that did not discriminate.

CLOUD-376 — `CI_ANSWERED_CONCLUSIONS` in mise.toml [env]. `checks-green` and
`land`'s `graded_runs` each hand-maintained a conclusion list, in agreement only
by a paragraph of comment in each file — precisely the guarantee that had already
failed, since `neutral` was missing from one until #302 added it and nothing
detected the gap. The catch-all is gone with it, so an unknown conclusion holds
the poll open instead of being reported red against a head nothing judged.

CLOUD-470 — the declination is asked of `land-lock authorises`, the same verb the
runner's own precondition consults, rather than re-derived from a raw
`conclusion == "cancelled"` read. Two authorities for one fact is the CLOUD-351
shape. What that verb cannot answer is recorded in the code rather than papered
over: it judges the lease now, so a `cancel-in-progress` cancellation is no
longer caught — that is a superseded run, not a declined one.

Refs: CLOUD-418, CLOUD-376, CLOUD-470, CLOUD-480
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants