Skip to content

fix(rivet): a resolved finding is release-ready — v0.7.0 was one artifact from cuttable - #367

Merged
avrabe merged 5 commits into
mainfrom
fix/resolved-findings-are-ready
Sep 10, 2026
Merged

fix(rivet): a resolved finding is release-ready — v0.7.0 was one artifact from cuttable#367
avrabe merged 5 commits into
mainfrom
fix/resolved-findings-are-ready

Conversation

@avrabe

@avrabe avrabe commented Sep 9, 2026

Copy link
Copy Markdown
Contributor

#342 said the readiness verdict was wrong in both directions. #363 fixed the permissive
one (Cuttable with nothing verified). This is the other, which I identified three days ago
and then did not chase: v0.7.0 shipped on 2026-08-26 and its burn-down still called it
not-cuttable.

The blocker is FIND-OS-TIMER-SLEEP-CONTRACT-001, a finding at resolved. Findings
have no verified state — their lifecycle is active → resolved, and resolved is
terminal. The rule was demanding a status this artifact type never reaches. The finding
wasn't unfinished; the rule asked the wrong question of it.

Checked before widening anything

ready-when: [resolved] is rivet's mechanism for this, but it would be a leak if
requirements used the status too. Every artifact at resolved in gale is a finding:

FIND-OS-TIMER-SLEEP-CONTRACT-001   finding
GAP-ZMS-5 / GAP-ZMS-8 / GAP-ZMS-10 finding
4 result(s)

Only one verdict moved

release before after
v0.7.0 ✗ NOT cuttable ✓ Cuttable — the release that had already shipped
v0.5.0 ✗ NOT (3) ✗ NOT (3)
v0.7.1 ✗ NOT (6) ✗ NOT (6)
v0.7.2 ✗ NOT (6) ✗ NOT (6)
v0.9.0 ✗ NOT (3) ✗ NOT (3)
v0.8.0 ✓ Cuttable ✓ Cuttable

That's the discrimination test. A change that flipped several would be widening the ready
set rather than correcting a type mismatch.

Kill-criterion: a requirement or verification reaches release-ready via resolved.

🤖 Generated with Claude Code

https://claude.ai/code/session_011QG86sovTbfnPNY9SfhSmo

…fact from cuttable

#342 said the readiness verdict was wrong in BOTH directions. #363 fixed the permissive
direction (Cuttable with nothing verified). This is the other one, which I identified
three days ago and then did not chase: v0.7.0 SHIPPED on 2026-08-26 and its burn-down
still called it not-cuttable.

The blocker is FIND-OS-TIMER-SLEEP-CONTRACT-001, a `finding` at `resolved`. Findings do
not have a `verified` state — their lifecycle is active -> resolved, and `resolved` IS the
terminal one. So the readiness rule was demanding a status this artifact type never
reaches. It is not that the finding was unfinished; it is that the rule asked the wrong
question of it.

`ready-when: [resolved]` is rivet's mechanism for exactly this. Checked before using it
that it cannot leak: every artifact at `resolved` in gale is a finding —

  FIND-OS-TIMER-SLEEP-CONTRACT-001   finding
  GAP-ZMS-5 / GAP-ZMS-8 / GAP-ZMS-10 finding
  4 result(s)

so no requirement or verification can slip through on this status.

Measured across every release, and only ONE verdict moved:

  v0.7.0   NOT cuttable  ->  Cuttable       the release that had already shipped
  v0.5.0   NOT (3)       ->  NOT (3)        unchanged
  v0.7.1   NOT (6)       ->  NOT (6)        unchanged
  v0.7.2   NOT (6)       ->  NOT (6)        unchanged
  v0.9.0   NOT (3)       ->  NOT (3)        unchanged
  v0.8.0   Cuttable      ->  Cuttable       unchanged

That is the discrimination test: a change that flipped several would be widening the ready
set, not correcting a type mismatch.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011QG86sovTbfnPNY9SfhSmo
@codecov

codecov Bot commented Sep 9, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

avrabe added a commit that referenced this pull request Sep 9, 2026
…ded" installing nothing (#370)

#367 failed on `llvm-lto-test (msgq)`, a Zephyr kernel test, on a PR that touches
only rivet YAML. That is the shape of a flake, and it is not one.

## What actually happened

    Installing 'arm-zephyr-eabi' GNU toolchain ...
    ERROR: GNU toolchain download failed
    FATAL ERROR: command "/root/zephyr-sdk-1.0.1/setup.sh -t arm-zephyr-eabi -h" failed
      (x3, ~10s apart)

The step that ran it — `Initialize west workspace` — **passed**. The job failed
one step later, in `Setup SDK paths`, looking for a toolchain that had never been
installed. Confirmed against the job's own step conclusions: step 7 success,
step 8 failure, step 9 skipped.

## Why the step passed

    for attempt in 1 2 3; do CMD && break || sleep 5; done

When every attempt fails the last command executed is `sleep`, which succeeds, so
the loop's exit status is 0. `bash -e` — which is what Actions runs `run:` under —
does not help: a failing command inside an `&&`/`||` list is exempt from `set -e`,
and the list's status is the final `sleep`. Verified rather than reasoned:

    $ bash -e -c 'for attempt in 1 2 3; do false && break || sleep 0.1; done'; echo $?
    0

So no `west update` or `west sdk install` anywhere in this repo could fail its own
step. 28 of them, across 10 workflows, in one-line and multi-line spellings.

## The fix

    n=0; until CMD; do n=$((n+1));
      if [ $n -ge 3 ]; then echo "::error::CMD failed after 3 attempts" >&2; exit 1; fi;
      sleep $((N*n)); done

Exits non-zero when the attempts run out, and the backoff now GROWS. The old
form slept a fixed 5s, so three attempts landed inside ~22 seconds — one throttle
window, which is no retry at all against a rate-limited asset host. All three
paths verified: all-fail exits 1, first-try-success exits 0 without sleeping,
fail-then-succeed exits 0.

## The guard

tools/check-retry-loops.py sweeps every workflow for the shape and fails on it,
because this will be reintroduced by anyone copying a neighbouring line. It
matches `&& break ||` generally, not just the sleep spelling — `|| true` is the
same defect with a shorter name.

Negative control, run on the real tree: reintroducing one loop in coverage.yml
fails the gate at the right file and line; removing it returns green. Its
--self-test pins all three cases including the `|| true` variant.

Wired as `retry-loops` in kill-criteria.yml. NOT added to required-contexts.txt
in this PR — a new gate earns a merge cycle before it becomes a merge blocker,
the same way wcet-sidecar is waiting.

## What this does not claim

This does not explain WHY the SDK's toolchain fetch failed. It failed 0.4s after
starting while the 70MB SDK tarball downloaded fine at 185MB/s in the same step,
which points at an instant refusal from the asset host rather than a slow
network, and the same job passed on #369 at the same minute. That is still
intermittent and still upstream. What changes is that it can no longer pass
silently and reappear as a confusing error in a later step.


Claude-Session: https://claude.ai/code/session_011QG86sovTbfnPNY9SfhSmo

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
@avrabe
avrabe merged commit 6073115 into main Sep 10, 2026
77 checks passed
@avrabe
avrabe deleted the fix/resolved-findings-are-ready branch September 10, 2026 07:27
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.

1 participant