Kernel: an unknown merge state defers

Merged
DO
5d ago
+56-9

Closes the third gap of the epic "Epic: nine gaps in the Kernel, found in review": mergeable was read only when it was false, so a merge state GitHub had not worked out yet converged, was approved, and admitted a review Run.

Stacks on "Kernel: a Pause stops approvals and merges, not only Runs" (#67), and will be rebased onto main once that merges.

What changes

  • kernel/derive.py: only an exact true is mergeable and only an exact false is a conflict. Anything else (null, a missing key, a malformed value such as "UNKNOWN") is unreadable: no State is derived, and the Decision defers with reason mergeable_unreadable, as an unreadable Pause or capacity reading does.
  • The order is pinned: the merge state is read after a merged, closed, held, draft or queued item, each of which keeps its own State and reason. GitHub reports a null merge state on merged and closed pull requests, so read first it would leave them deferring for ever. test_an_unknown_mergeable_is_read_after_what_needs_no_merge holds this, with a mutant that reads the merge state first.
  • tests/test_decide.py: test_an_unknown_mergeable_never_converges loses @pending, leaves NotYetHeld, and now asserts both sides of the boundary: a deferral with an unknown merge state, and issue_approval or enqueue with a known one. New test_an_unknown_mergeable_admits_nothing shows no review Run is admitted either. New test_only_true_or_false_is_a_known_merge_state covers a missing key, "UNKNOWN", "true" and 1. New test_a_conflict_goes_back_to_the_author pins the conflict rule, which nothing tested before.
  • tools/mutants.py: "an unknown merge state is read as mergeable", "only a null merge state is unknown", "a conflict is not sent back to the author" and "the merge state is read before a merged, closed, held, draft or queued item". 26 of 26 killed.
  • Documents: "not held yet" is gone from lesson L-07. The design's §3.3 row for mergeable names null, and §4.6 has the case of a merge state GitHub has not worked out yet.

Acceptance criteria (from "Kernel: an unknown merge state defers")

  • test_an_unknown_mergeable_never_converges passes with @pending removed: an unknown merge state is unreadable, and the Decision is defer.
  • The mutant for it is killed.
  • No document says not held yet beside this test.

Left open, on purpose

  • A missing mergeable defers rather than crashing, as a missing Pause does. The facts schema already rejects it, so no counter-example is added.
  • A merge state that never resolves defers with attention none, so nobody is told. That is the story "When waiting becomes the Maintainer's to see".
  • The simulator does not inject a null merge state. sim/world.py is shared with four other stories in the epic.

Checks and reviews

  • make verify and python3 -m tools.mutants pass on a git archive HEAD copy, which avoids the nested worktrees of "Verify passes in a checkout that holds other worktrees" (#64).
  • The new tests were run before the rule and failed as expected; the conflict test passed throughout, because it pins existing behaviour. The order mutant survived every test until the order test was added.
  • The second model family's read is the Gemini code review and the other bot reviewers on this pull request, as the Maintainer directed. Not merged or enqueued; the Maintainer merges.

Closes #31

🤖 Generated with Claude Code