diff --git a/.trinity/seals/automation_review_jam.json b/.trinity/seals/automation_review_jam.json index 3baf1b921e..c6fae5c7fe 100644 --- a/.trinity/seals/automation_review_jam.json +++ b/.trinity/seals/automation_review_jam.json @@ -1,24 +1,25 @@ { - "built_by": "t27c-bootstrap@0.5.2+0d573722c", + "built_by": "t27c-bootstrap@0.5.2+cd8a65666", "closure": [], "config": "gen=c,rust,verilog,zig;tests=zig", - "gen_hash_c": "sha256:a53272245acd53b9e42a86c67bf9159b1c30ef5d48ded84ec549a060240cfd27", - "gen_hash_rust": "sha256:9a17dd922d08c6b4093efe761c5d35500666880182272ff02a7abe770e22dd7d", - "gen_hash_verilog": "sha256:3c8adb026c197093c8c1a8d983db20e5510d058b7f0c5dd514046c671b5faf5e", - "gen_hash_zig": "sha256:5ac1fd03c0a29ef6cfc05bc0ec3a5008cf57a44a48ea5ef1e66b26d68a6be89c", + "gen_hash_c": "sha256:7bd51fdd573256d1feec4c938d2569cbd4eb2745f9ba7b65edb18a8feeea9990", + "gen_hash_rust": "sha256:7f00198ac01c2a58b421d96e8d9eac0a1d7595f12038a2e8c8e4b9ea393c0f09", + "gen_hash_verilog": "sha256:7715176baed689595224e6b0f72e2b4fef711b775a7ac80c32f265391fff79a5", + "gen_hash_zig": "sha256:93df76a874717e69a6c84d8b39818b68f52ee548694e507aad41119c1e45ec32", "module": "review_jam", "ring": 12, "seal_schema": 2, - "sealed_at": "2026-10-09T20:22:48Z", + "sealed_at": "2026-10-17T14:13:10Z", "sealed_by": "t27c-bootstrap@0.5.2", - "spec_hash": "sha256:e1c98dcb48ff491bb006b420f16543c94def9ba52950cc62c54433ea186f9a1e", + "spec_hash": "sha256:4cc0c0e1fa6d6abd3c9b8a7658f8a2d31a8fba24bb7ad4efcf60bf194497a662", "spec_path": "specs/automation/review-jam.t27", "tests": { "failed": 0, "failing": [], "forced": false, - "passed": 27, - "total": 27 + "passed": 44, + "total": 44, + "vacuous": 0 }, "toolchain": { "test_runner": "sha256:1982ae948e24d00c5d221ccec12dfce1eb01204b53e37b0f857f93b16d64629d", diff --git a/specs/automation/review-jam.t27 b/specs/automation/review-jam.t27 index 9eeba322c6..80cd798a66 100644 --- a/specs/automation/review-jam.t27 +++ b/specs/automation/review-jam.t27 @@ -1,9 +1,11 @@ -// Review jam case detection and Queen action mapping -// Part of EPIC #5388 - gHashTag/t27#5700 +// 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, position = value, starting at 0) +// 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; @@ -15,7 +17,7 @@ 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, position = value, starting at 0) +// 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; @@ -27,18 +29,17 @@ pub const ACT_NAME_NOISE_AND_REVIEW: u8 = 7; pub const ACT_WAIT: u8 = 8; pub const ACT_MEASURE_AGAIN: u8 = 9; -// Determine the jam case based on PR state -// First matching rule wins, in this order: -// 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 +// 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; @@ -70,9 +71,9 @@ pub fn jam_case(measured: bool, draft_or_fresh: bool, stacked_on_open: bool, con JAM_NONE } -// Map jam case to Queen action -// Case maps to action with same position (NONE -> NOTHING, NO_REVIEWER -> DISPATCH_REVIEWER, etc.) -// Any value above 9 -> ACT_MEASURE_AGAIN (unknown case is never "nothing to do") +// 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; @@ -80,44 +81,48 @@ pub fn jam_action(case: u8) -> u8 { case } -// Determine if it's the Queen's turn to act -// True for every case except NONE, DRAFT_OR_FRESH and STACKED_ON_OPEN +// 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 } -// Tests - one per case, plus edge cases +// ---- 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, false, false) == JAM_STACKED_ON_OPEN; + 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, false, false) == JAM_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, false, false) == JAM_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, false, false) == JAM_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, false, false) == JAM_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, false, true) == JAM_NOISE_RED; + assert jam_case(true, false, false, false, false, false, false, true, true) == JAM_NOISE_RED; } test "no reviewer" { @@ -128,70 +133,142 @@ test "none" { assert jam_case(true, false, false, false, false, false, false, true, false) == JAM_NONE; } -test "unknown case maps to measure again" { - assert jam_action(200) == ACT_MEASURE_AGAIN; -} +// ---- jam_case: first-match order, each rule against the next one ---- -test "queen's turn for labeled not merged" { - assert jam_is_queens_turn(JAM_LABELED_NOT_MERGED) == true; +test "draft beats stacked" { + assert jam_case(true, true, true, false, false, false, false, true, false) == JAM_DRAFT_OR_FRESH; } -test "queen's turn for no reviewer" { - assert jam_is_queens_turn(JAM_NO_REVIEWER) == true; +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 "queen's turn for conflicting" { - assert jam_is_queens_turn(JAM_CONFLICTING) == true; +test "stacked beats conflicting" { + assert jam_case(true, false, true, true, false, false, false, true, false) == JAM_STACKED_ON_OPEN; } -test "queen's turn for refused then pushed" { - assert jam_is_queens_turn(JAM_REFUSED_THEN_PUSHED) == true; +test "conflicting beats labeled" { + assert jam_case(true, false, false, true, true, false, false, true, false) == JAM_CONFLICTING; } -test "queen's turn for refused not fixed" { - assert jam_is_queens_turn(JAM_REFUSED_NOT_FIXED) == true; +test "labeled beats refused then pushed" { + assert jam_case(true, false, false, false, true, true, true, true, false) == JAM_LABELED_NOT_MERGED; } -test "queen's turn for noise red" { - assert jam_is_queens_turn(JAM_NOISE_RED) == true; +test "refused then pushed beats noise red" { + assert jam_case(true, false, false, false, false, true, true, true, true) == JAM_REFUSED_THEN_PUSHED; } -test "queen's turn for unmeasured" { - assert jam_is_queens_turn(JAM_UNMEASURED) == true; +test "refused not fixed beats noise red" { + assert jam_case(true, false, false, false, false, true, false, true, true) == JAM_REFUSED_NOT_FIXED; } -test "not queen's turn for none" { - assert jam_is_queens_turn(JAM_NONE) == false; +test "noise red beats no reviewer" { + assert jam_case(true, false, false, false, false, false, false, false, true) == JAM_NOISE_RED; } -test "not queen's turn for draft or fresh" { - assert jam_is_queens_turn(JAM_DRAFT_OR_FRESH) == false; +test "pushed without a refusal is not refused then pushed" { + assert jam_case(true, false, false, false, false, false, true, true, false) == JAM_NONE; } -test "not queen's turn for stacked on open" { - assert jam_is_queens_turn(JAM_STACKED_ON_OPEN) == false; +// ---- jam_action: same position, unknown -> measure again ---- + +test "action none to nothing" { + assert jam_action(JAM_NONE) == ACT_NOTHING; } -test "specific scenario: labeled reviewed but still open" { - assert jam_case(true, false, false, false, true, false, false, true, false) == JAM_LABELED_NOT_MERGED; +test "action no reviewer to dispatch reviewer" { + assert jam_action(JAM_NO_REVIEWER) == ACT_DISPATCH_REVIEWER; } -test "specific scenario: unmeasured with any other flags" { - assert jam_case(false, true, true, true, true, true, true, true, true) == JAM_UNMEASURED; +test "action stacked to wait for base" { + assert jam_action(JAM_STACKED_ON_OPEN) == ACT_WAIT_FOR_BASE; } -test "action mapping: none to nothing" { - assert jam_action(JAM_NONE) == ACT_NOTHING; +test "action refused not fixed to dispatch coder" { + assert jam_action(JAM_REFUSED_NOT_FIXED) == ACT_DISPATCH_CODER; } -test "action mapping: no reviewer to dispatch reviewer" { - assert jam_action(JAM_NO_REVIEWER) == ACT_DISPATCH_REVIEWER; +test "action refused then pushed to dispatch re-review" { + assert jam_action(JAM_REFUSED_THEN_PUSHED) == ACT_DISPATCH_RE_REVIEW; } -test "action mapping: labeled not merged to finish merge" { +test "action labeled not merged to finish merge" { assert jam_action(JAM_LABELED_NOT_MERGED) == ACT_FINISH_MERGE; } -test "action mapping: unmeasured to measure again" { +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; -} \ No newline at end of file +} + +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; +}