Kernel: a Pause stops approvals and merges, not only Runs

Merged
DO
5d ago
+42-18

Note, added after merge. This pull request was merged with its first two commits. The third, which pins the order of the approver Hold and the Pause (the test, the mutant "a pause hides the approver Hold" and the row in decision 0003), was pushed after the merge and lands in "Kernel: under a Pause, the approver Hold shows first" (#76). Until then, main has 22 of these mutants, not 23.

Closes the first gap of the epic "Epic: nine gaps in the Kernel, found in review": with the Factory paused, or the Pause unreadable, a converged pull request was still approved and an approved one still enqueued.

What changes

  • kernel/decide.py: one helper, _pause(), read in both places an act is decided. In the converged branch it now stops issue_approval and enqueue; before Admission it stops Runs, as before. A Pause defers with paused, an unreadable one with pause_unreadable, the same codes Runs already get.
  • tests/test_decide.py: test_a_pause_stops_approvals_and_merges_too loses @pending, leaves NotYetHeld, and now asserts both sides of the boundary. New test_an_unreadable_pause_stops_approvals_and_merges covers a missing key and None, for approval and for enqueue.
  • tools/mutants.py: one mutant for each place the rule acts ("a pause admits a Run", "a pause lets an approval or a merge through"), one for "an unreadable pause is read as no pause", and one for "a pause hides the approver Hold". 23 of 23 killed.
  • The order, ruled by the Maintainer on this pull request: when an item is both paused and held because its approver is its author or last pusher, the Hold the facts establish is the reason given. test_under_a_pause_a_hold_the_facts_establish_shows_first pins it, and decision 0003 records it. CodeRabbit asked for the opposite order; neither lets an approval through.
  • Documents: "not held yet" is gone from decision 0003 and lesson L-24; the design's state-table note now says a Pause stops approval and enqueuing too, as I-7 does.

Acceptance criteria (from "Kernel: a Pause stops every act, not only Runs")

  • The pinned test passes with @pending removed: under a Pause, defer, reason paused.
  • An unreadable Pause defers in the same way (reason pause_unreadable, as for Runs).
  • Each place the rule acts has its own mutant, and both are killed.
  • No document says not held yet beside this test.

Left open, on purpose

  • A pull request already in the merge queue stays there under a Pause. That is the story "Kernel: what takes a pull request out of the merge queue".
  • A malformed truthy value such as "false" gets paused rather than pause_unreadable. Both defer; lesson L-58 leaves the reading of the variable to adapters/.

Checks and reviews

  • make verify and python3 -m tools.mutants pass on a git archive HEAD export of the last commit. In a checkout that holds Claude worktrees under .claude/worktrees/, make verify fails on those directories; that is unrelated and suggested as its own task.
  • A fresh-context read of the diff: all four criteria met; its two stale-text findings are fixed in the second commit.
  • A security-only pass: only an exact False lets an act through; every other shape of paused, including 0, "" and a missing key, defers. No path reaches act or admit without the Pause check.
  • The second model family's read is the Gemini code review and the other bot reviewers on this pull request, as the Maintainer directed. CodeRabbit has reviewed; the Codex bot stopped at its usage limit; Gemini has not posted yet.
  • SonarCloud's quality gate fails on 0% coverage of new code: the repo produces no coverage report. Not required, and not this story's to fix. Not merged or enqueued; the Maintainer merges.

🤖 Generated with Claude Code

Closes #30