The review jam
You will learn
Which of ten cases a stuck pull request is in, and why the earliest matching rule wins.
A pull request can stall for many reasons at once: refused, pushed again, and red on a noise check. review-jam.t27 gives the Queen one answer. jam_case checks 9 facts in a fixed order and the earliest rule that matches wins, so a PR the Queen could not measure is never reported as free, and one that was refused and then pushed is not taken for noise. There are 10 cases, from none to unmeasured.
Try it
Clear facts one by one from the start state and name each case you pass; then find a PR that is in no jam.

Nine facts about a pull request that will not move. Toggle them: the earliest rule that matches wins, and the pull request lands on that rule.
specs/automation/review-jam.t27
// Review jam: which case a stuck pull request is in, and the Queen's action.
// Part of EPIC #5388. Contract: gHashTag/t27#9032 (re-filed from #5700).
// `tri jam` (gHashTag/999-multibots-telegraf) imports these numbers instead
// of hand-copying them.
module review_jam;
// Jam case constants (u8, value = position, starting at 0)
pub const JAM_NONE: u8 = 0;
pub const JAM_NO_REVIEWER: u8 = 1;
pub const JAM_STACKED_ON_OPEN: u8 = 2;
pub const JAM_REFUSED_NOT_FIXED: u8 = 3;
pub const JAM_REFUSED_THEN_PUSHED: u8 = 4;
pub const JAM_LABELED_NOT_MERGED: u8 = 5;
pub const JAM_CONFLICTING: u8 = 6;
pub const JAM_NOISE_RED: u8 = 7;
pub const JAM_DRAFT_OR_FRESH: u8 = 8;
pub const JAM_UNMEASURED: u8 = 9;
// Queen action constants (u8, value = position, starting at 0)
pub const ACT_NOTHING: u8 = 0;
pub const ACT_DISPATCH_REVIEWER: u8 = 1;
pub const ACT_WAIT_FOR_BASE: u8 = 2;
pub const ACT_DISPATCH_CODER: u8 = 3;
pub const ACT_DISPATCH_RE_REVIEW: u8 = 4;
pub const ACT_FINISH_MERGE: u8 = 5;
pub const ACT_REBASE_BY_CODER: u8 = 6;
pub const ACT_NAME_NOISE_AND_REVIEW: u8 = 7;
pub const ACT_WAIT: u8 = 8;
pub const ACT_MEASURE_AGAIN: u8 = 9;
// The jam case of a pull request. The FIRST matching rule wins:
// 1. not measured -> UNMEASURED
// 2. draft or fresh -> DRAFT_OR_FRESH
// 3. stacked on an open PR -> STACKED_ON_OPEN
// 4. conflicting -> CONFLICTING
// 5. labeled reviewed (and still open) -> LABELED_NOT_MERGED
// 6. refused and pushed after -> REFUSED_THEN_PUSHED
// 7. refused -> REFUSED_NOT_FIXED
// 8. only noise checks red -> NOISE_RED
// 9. no reviewer -> NO_REVIEWER
// 10. otherwise -> NONE
pub fn jam_case(measured: bool, draft_or_fresh: bool, stacked_on_open: bool, conflicting: bool, labeled_reviewed: bool, refused: bool, pushed_after_refusal: bool, has_reviewer: bool, only_noise_red: bool) -> u8 {
if !measured {
return JAM_UNMEASURED;
}
if draft_or_fresh {
return JAM_DRAFT_OR_FRESH;
}
if stacked_on_open {
return JAM_STACKED_ON_OPEN;
}
if conflicting {
return JAM_CONFLICTING;
}
if labeled_reviewed {
return JAM_LABELED_NOT_MERGED;
}
if refused && pushed_after_refusal {
return JAM_REFUSED_THEN_PUSHED;
}
if refused {
return JAM_REFUSED_NOT_FIXED;
}
if only_noise_red {
return JAM_NOISE_RED;
}
if !has_reviewer {
return JAM_NO_REVIEWER;
}
JAM_NONE
}
// The Queen's action for a case: the action with the SAME position.
// Any value above 9 is an unknown case, and an unknown case is never
// "nothing to do": it maps to ACT_MEASURE_AGAIN.
pub fn jam_action(case: u8) -> u8 {
if case > 9 {
return ACT_MEASURE_AGAIN;
}
case
}
// Whether the case is the Queen's turn to act: true for every case except
// NONE, DRAFT_OR_FRESH and STACKED_ON_OPEN.
pub fn jam_is_queens_turn(case: u8) -> bool {
case != JAM_NONE && case != JAM_DRAFT_OR_FRESH && case != JAM_STACKED_ON_OPEN
}
// ---- jam_case: one test per case ----
test "unmeasured beats every other flag" {
assert jam_case(false, true, true, true, true, true, true, true, true) == JAM_UNMEASURED;
}
test "unmeasured with every other flag clear" {
assert jam_case(false, false, false, false, false, false, false, false, false) == JAM_UNMEASURED;
}
test "draft or fresh" {
assert jam_case(true, true, false, false, false, false, false, false, false) == JAM_DRAFT_OR_FRESH;
}
test "stacked on open PR" {
assert jam_case(true, false, true, false, false, false, false, true, false) == JAM_STACKED_ON_OPEN;
}
test "conflicting" {
assert jam_case(true, false, false, true, false, false, false, true, false) == JAM_CONFLICTING;
}
test "labeled not merged" {
assert jam_case(true, false, false, false, true, false, false, true, false) == JAM_LABELED_NOT_MERGED;
}
test "refused then pushed" {
assert jam_case(true, false, false, false, false, true, true, true, false) == JAM_REFUSED_THEN_PUSHED;
}
test "refused not fixed" {
assert jam_case(true, false, false, false, false, true, false, true, false) == JAM_REFUSED_NOT_FIXED;
}
test "noise red" {
assert jam_case(true, false, false, false, false, false, false, true, true) == JAM_NOISE_RED;
}
test "no reviewer" {
assert jam_case(true, false, false, false, false, false, false, false, false) == JAM_NO_REVIEWER;
}
test "none" {
assert jam_case(true, false, false, false, false, false, false, true, false) == JAM_NONE;
}
// ---- jam_case: first-match order, each rule against the next one ----
test "draft beats stacked" {
assert jam_case(true, true, true, false, false, false, false, true, false) == JAM_DRAFT_OR_FRESH;
}
test "stacked and unreviewed is stacked, not no reviewer" {
assert jam_case(true, false, true, false, false, false, false, false, false) == JAM_STACKED_ON_OPEN;
}
test "stacked beats conflicting" {
assert jam_case(true, false, true, true, false, false, false, true, false) == JAM_STACKED_ON_OPEN;
}
test "conflicting beats labeled" {
assert jam_case(true, false, false, true, true, false, false, true, false) == JAM_CONFLICTING;
}
test "labeled beats refused then pushed" {
assert jam_case(true, false, false, false, true, true, true, true, false) == JAM_LABELED_NOT_MERGED;
}
test "refused then pushed beats noise red" {
assert jam_case(true, false, false, false, false, true, true, true, true) == JAM_REFUSED_THEN_PUSHED;
}
test "refused not fixed beats noise red" {
assert jam_case(true, false, false, false, false, true, false, true, true) == JAM_REFUSED_NOT_FIXED;
}
test "noise red beats no reviewer" {
assert jam_case(true, false, false, false, false, false, false, false, true) == JAM_NOISE_RED;
}
test "pushed without a refusal is not refused then pushed" {
assert jam_case(true, false, false, false, false, false, true, true, false) == JAM_NONE;
}
// ---- jam_action: same position, unknown -> measure again ----
test "action none to nothing" {
assert jam_action(JAM_NONE) == ACT_NOTHING;
}
test "action no reviewer to dispatch reviewer" {
assert jam_action(JAM_NO_REVIEWER) == ACT_DISPATCH_REVIEWER;
}
test "action stacked to wait for base" {
assert jam_action(JAM_STACKED_ON_OPEN) == ACT_WAIT_FOR_BASE;
}
test "action refused not fixed to dispatch coder" {
assert jam_action(JAM_REFUSED_NOT_FIXED) == ACT_DISPATCH_CODER;
}
test "action refused then pushed to dispatch re-review" {
assert jam_action(JAM_REFUSED_THEN_PUSHED) == ACT_DISPATCH_RE_REVIEW;
}
test "action labeled not merged to finish merge" {
assert jam_action(JAM_LABELED_NOT_MERGED) == ACT_FINISH_MERGE;
}
test "action conflicting to rebase by coder" {
assert jam_action(JAM_CONFLICTING) == ACT_REBASE_BY_CODER;
}
test "action noise red to name noise and review" {
assert jam_action(JAM_NOISE_RED) == ACT_NAME_NOISE_AND_REVIEW;
}
test "action draft or fresh to wait" {
assert jam_action(JAM_DRAFT_OR_FRESH) == ACT_WAIT;
}
test "action unmeasured to measure again" {
assert jam_action(JAM_UNMEASURED) == ACT_MEASURE_AGAIN;
}
test "action first unknown case measures again" {
assert jam_action(10) == ACT_MEASURE_AGAIN;
}
test "action unknown case 200 measures again" {
assert jam_action(200) == ACT_MEASURE_AGAIN;
}
// ---- jam_is_queens_turn ----
test "queens turn for no reviewer" {
assert jam_is_queens_turn(JAM_NO_REVIEWER) == true;
}
test "queens turn for refused not fixed" {
assert jam_is_queens_turn(JAM_REFUSED_NOT_FIXED) == true;
}
test "queens turn for refused then pushed" {
assert jam_is_queens_turn(JAM_REFUSED_THEN_PUSHED) == true;
}
test "queens turn for labeled not merged" {
assert jam_is_queens_turn(JAM_LABELED_NOT_MERGED) == true;
}
test "queens turn for conflicting" {
assert jam_is_queens_turn(JAM_CONFLICTING) == true;
}
test "queens turn for noise red" {
assert jam_is_queens_turn(JAM_NOISE_RED) == true;
}
test "queens turn for unmeasured" {
assert jam_is_queens_turn(JAM_UNMEASURED) == true;
}
test "not queens turn for none" {
assert jam_is_queens_turn(JAM_NONE) == false;
}
test "not queens turn for draft or fresh" {
assert jam_is_queens_turn(JAM_DRAFT_OR_FRESH) == false;
}
test "not queens turn for stacked on open" {
assert jam_is_queens_turn(JAM_STACKED_ON_OPEN) == false;
}
// ---- the two scenarios of #9032, end to end ----
test "scenario bee-reviewed but still open finishes the merge" {
assert jam_action(jam_case(true, false, false, false, true, false, false, true, false)) == ACT_FINISH_MERGE;
}
test "scenario failed read is never reported as free" {
assert jam_action(jam_case(false, false, false, false, false, false, false, true, false)) == ACT_MEASURE_AGAIN;
}
All lessons
Module 1 · The task
An issue becomes work only once it is shaped: the sections it lacks are written or asked for, its boundary names real files, and one filter reads every kind of task.
Module 2 · Who goes next
The order the Queen takes issues in: a label ages one step, at most 3 criticals run as critical, and nothing new starts while finished work waits for review.
Module 3 · A bee's life
From candidate to done: twelve states and sixteen events, a ship in the game that flies home, and a deploy that does not cut a bee short.
Module 4 · The reviewer bee
Finished work is judged on its own clock: enough reviews at once to keep up, a pause that doubles for a row that waits, and a merge gate that zero checks cannot open.
Module 5 · The review jam
A pull request that will not move is in exactly one of ten cases, each with the Queen's move, and a red check is inherited only when master says so.
Module 6 · Keys
A bee's model key can be refused: the code in the body sorts the refusal, a transient one rests the key for seconds, and the bee moves on to the next free key.
Module 7 · Credit and money
Lending a key earns no credit, an operation is sold at twice its cost in half-cent tokens, and tokens burn 30 days after they land.
Module 8 · Agents among themselves
One bot per agent in one group: who is called, a chain three bot messages deep, and the clocks that keep agents from flooding a chat.
Module 9 · Night and the board
What the hive shows: a night watch that opens, comments on and closes issues, the columns of the public board, and the comb that flashes for each event.