Kernel: a Run that ran and left no outcome is an attempt; one cancelled, or never started, is none (#286)

Merged
DO
7d ago
+237-36

A failed Run that left no outcome is now an attempt when it ran (failure or timed_out), and no attempt when it was cancelled. This follows the working ruling on #286, which takes section 3.10's stricter reading over decision 0029's.

What changes

  • kernel/brakes.py::_attempts. It also counts a completed Run with no outcome whose conclusion is in terms.RAN_AND_DIED (failure, timed_out). A cancelled Run with none counts toward the Run ceiling alone.
  • adapters/records.py. A job that never started (startup_failure) is read as cancelled, not failure, so a broken workflow file never counts against an item.
  • controller/feed.py. The feed counts attempts the way the brakes do.
  • Simulator. A killed Run in sim/world.py and sim/issues.py may now leave no outcome, as section 3.10 says, and the approval invariant counts such Runs.
  • Decision record. A new record replaces 0029 and keeps all of 0029 except this rule and its row. 0029's Status names the new record.
    • It is numbered 0049. Main holds 0046 (#293), and 0047 and 0048 are #296's and #298's, so the context check fails on the gap until those two merge.
  • Design §3.10. The killed-Run sentence is rewritten to match.

Boundaries. Each new boundary has a test and a mutant:

  • A review or an approval that failed or timed out with no outcome is held at max_attempts.
  • An issue's Run that ran and left no outcome counts the same way.
  • A cancelled Run with no outcome is admitted again.
  • A Run still in flight, with the item one attempt short, holds nothing.
  • The port reads startup_failure as cancelled.
  • The feed's count matches the brakes'.

Not verified. That a job killed at its timeout concludes timed_out is still a_killed_job_times_out, waiting on P-18. The record and the design both say so.

Known and accepted. An outcome GitHub has not listed yet, or one past its retention, reads as none, so for that pass a failed Run counts as an attempt. The effect is a Hold, never an action. The record says so.

Reviews, all on c6fed5e, with the fix round after.

  • Claude review (fresh context). It found one major: startup_failure reached the Kernel as failure, so it counted as an attempt. The minors were tests that used conclusions a run record cannot hold, no issue-side test, a vacuous in-flight assertion, the simulator never producing a null outcome, and stale references to 0029. All are answered in 83a58df.
  • Muse (Muse Wonder Full). It found the same startup_failure major, and a minor: the feed still counted attempts by the old rule. Answered in 83a58df and 045b347.
  • Security pass. Not run as a separate review. The change only makes the Factory hold more, never act, and Muse looked for a path to approve, merge or admit and found none.
  • AGY (Gemini 4 Argon). No findings on c6fed5e, and none when it re-read the Kernel fix on 83a58df.
  • Verify. make verify passed on a fresh export of 1cad718, and both simulators pass at CI's 300 seeds. After the rename to 0049 and the merge of main, only the context check's gap fails.
  • Simulator. A killed Run that may leave no outcome changes what each seed draws. Seed 69, the first to lose an approval while queued, now runs in a short run too.

Closes #286

🤖 Generated with Claude Code