@@ -396,6 +396,7 @@ def test_scheduled_check_backfills_transition_notice_without_reposting(monkeypat
396396 review ["transition_warning_sent" ] = "2026-03-10T00:00:00Z"
397397 monkeypatch .setattr (reviewer_bot .maintenance_module , "sweep_deferred_gaps" , lambda bot , state : False )
398398 monkeypatch .setattr (reviewer_bot .maintenance_module , "maybe_record_head_observation_repair" , lambda bot , issue_number , review_data : False )
399+ monkeypatch .setattr (reviewer_bot , "get_pull_request_reviews" , lambda issue_number : [])
399400 monkeypatch .setattr (reviewer_bot , "get_issue_or_pr_snapshot" , lambda issue_number : {"pull_request" : {}})
400401 posted = []
401402 monkeypatch .setattr (reviewer_bot , "post_comment" , lambda issue_number , body : posted .append (body ) or True )
@@ -418,6 +419,36 @@ def fake_api(method, endpoint, data=None):
418419 assert posted == []
419420
420421
422+ def test_scheduled_check_repairs_missing_reviewer_review_state (monkeypatch ):
423+ state = make_state ()
424+ review = reviewer_bot .ensure_review_entry (state , 42 , create = True )
425+ assert review is not None
426+ review ["current_reviewer" ] = "alice"
427+ review ["active_cycle_started_at" ] = "2026-03-17T09:00:00Z"
428+ monkeypatch .setattr (reviewer_bot .maintenance_module , "sweep_deferred_gaps" , lambda bot , state : False )
429+ monkeypatch .setattr (reviewer_bot .maintenance_module , "maybe_record_head_observation_repair" , lambda bot , issue_number , review_data : False )
430+ monkeypatch .setattr (reviewer_bot .maintenance_module , "check_overdue_reviews" , lambda bot , state : [])
431+ monkeypatch .setattr (reviewer_bot , "get_issue_or_pr_snapshot" , lambda issue_number : {"pull_request" : {}})
432+ monkeypatch .setattr (
433+ reviewer_bot ,
434+ "get_pull_request_reviews" ,
435+ lambda issue_number : [
436+ {
437+ "id" : 10 ,
438+ "state" : "COMMENTED" ,
439+ "submitted_at" : "2026-03-17T10:01:00Z" ,
440+ "commit_id" : "head-1" ,
441+ "user" : {"login" : "alice" },
442+ }
443+ ],
444+ )
445+ assert reviewer_bot .handle_scheduled_check (state ) is True
446+ accepted = review ["reviewer_review" ]["accepted" ]
447+ assert accepted is not None
448+ assert accepted ["semantic_key" ] == "pull_request_review:10"
449+ assert review ["last_reviewer_activity" ] == "2026-03-17T10:01:00Z"
450+
451+
421452def test_issue_edit_by_author_records_contributor_freshness (monkeypatch ):
422453 state = make_state ()
423454 review = reviewer_bot .ensure_review_entry (state , 42 , create = True )
@@ -474,6 +505,127 @@ def test_project_status_labels_uses_commit_id_and_comment_freshness(monkeypatch)
474505 assert metadata ["reason" ] == "review_head_stale"
475506
476507
508+ def test_project_status_labels_uses_live_current_reviewer_review_when_channel_state_missing (monkeypatch ):
509+ state = make_state ()
510+ review = reviewer_bot .ensure_review_entry (state , 42 , create = True )
511+ assert review is not None
512+ review ["current_reviewer" ] = "alice"
513+ review ["active_cycle_started_at" ] = "2026-03-17T09:00:00Z"
514+ monkeypatch .setattr (
515+ reviewer_bot ,
516+ "get_issue_or_pr_snapshot" ,
517+ lambda issue_number : {"number" : issue_number , "state" : "open" , "pull_request" : {}, "labels" : []},
518+ )
519+ monkeypatch .setattr (
520+ reviewer_bot ,
521+ "github_api" ,
522+ lambda method , endpoint , data = None : {"head" : {"sha" : "head-1" }} if endpoint == "pulls/42" else None ,
523+ )
524+ monkeypatch .setattr (
525+ reviewer_bot ,
526+ "get_pull_request_reviews" ,
527+ lambda issue_number : [
528+ {
529+ "id" : 10 ,
530+ "state" : "COMMENTED" ,
531+ "submitted_at" : "2026-03-17T10:01:00Z" ,
532+ "commit_id" : "head-1" ,
533+ "user" : {"login" : "alice" },
534+ }
535+ ],
536+ )
537+ desired_labels , metadata = reviewer_bot .project_status_labels_for_item (42 , state )
538+ assert desired_labels == {reviewer_bot .STATUS_AWAITING_CONTRIBUTOR_RESPONSE_LABEL }
539+ assert metadata ["reason" ] == "completion_missing"
540+
541+
542+ def test_project_status_labels_uses_live_review_fallback_for_stale_head (monkeypatch ):
543+ state = make_state ()
544+ review = reviewer_bot .ensure_review_entry (state , 42 , create = True )
545+ assert review is not None
546+ review ["current_reviewer" ] = "alice"
547+ review ["active_cycle_started_at" ] = "2026-03-17T09:00:00Z"
548+ monkeypatch .setattr (
549+ reviewer_bot ,
550+ "get_issue_or_pr_snapshot" ,
551+ lambda issue_number : {"number" : issue_number , "state" : "open" , "pull_request" : {}, "labels" : []},
552+ )
553+ monkeypatch .setattr (
554+ reviewer_bot ,
555+ "github_api" ,
556+ lambda method , endpoint , data = None : {"head" : {"sha" : "head-2" }} if endpoint == "pulls/42" else None ,
557+ )
558+ monkeypatch .setattr (
559+ reviewer_bot ,
560+ "get_pull_request_reviews" ,
561+ lambda issue_number : [
562+ {
563+ "id" : 10 ,
564+ "state" : "COMMENTED" ,
565+ "submitted_at" : "2026-03-17T10:01:00Z" ,
566+ "commit_id" : "head-1" ,
567+ "user" : {"login" : "alice" },
568+ }
569+ ],
570+ )
571+ desired_labels , metadata = reviewer_bot .project_status_labels_for_item (42 , state )
572+ assert desired_labels == {reviewer_bot .STATUS_AWAITING_REVIEWER_RESPONSE_LABEL }
573+ assert metadata ["reason" ] == "review_head_stale"
574+
575+
576+ def test_project_status_labels_prefers_newer_contributor_comment_over_live_review_fallback (monkeypatch ):
577+ state = make_state ()
578+ review = reviewer_bot .ensure_review_entry (state , 42 , create = True )
579+ assert review is not None
580+ review ["current_reviewer" ] = "alice"
581+ review ["active_cycle_started_at" ] = "2026-03-17T09:00:00Z"
582+ reviewer_bot .reviews_module .accept_channel_event (
583+ review ,
584+ "contributor_comment" ,
585+ semantic_key = "issue_comment:20" ,
586+ timestamp = "2026-03-17T10:05:00Z" ,
587+ actor = "bob" ,
588+ )
589+ monkeypatch .setattr (
590+ reviewer_bot ,
591+ "get_issue_or_pr_snapshot" ,
592+ lambda issue_number : {"number" : issue_number , "state" : "open" , "pull_request" : {}, "labels" : []},
593+ )
594+ monkeypatch .setattr (
595+ reviewer_bot ,
596+ "github_api" ,
597+ lambda method , endpoint , data = None : {"head" : {"sha" : "head-1" }} if endpoint == "pulls/42" else None ,
598+ )
599+ monkeypatch .setattr (
600+ reviewer_bot ,
601+ "get_pull_request_reviews" ,
602+ lambda issue_number : [
603+ {
604+ "id" : 10 ,
605+ "state" : "COMMENTED" ,
606+ "submitted_at" : "2026-03-17T10:01:00Z" ,
607+ "commit_id" : "head-1" ,
608+ "user" : {"login" : "alice" },
609+ }
610+ ],
611+ )
612+ desired_labels , metadata = reviewer_bot .project_status_labels_for_item (42 , state )
613+ assert desired_labels == {reviewer_bot .STATUS_AWAITING_REVIEWER_RESPONSE_LABEL }
614+ assert metadata ["reason" ] == "contributor_comment_newer"
615+
616+
617+ def test_record_reviewer_activity_does_not_regress_timestamp_on_legacy_backfill ():
618+ review = reviewer_bot .ensure_review_entry (make_state (), 42 , create = True )
619+ assert review is not None
620+ review ["last_reviewer_activity" ] = "2026-03-20T10:00:00Z"
621+ review ["transition_warning_sent" ] = "2026-03-21T10:00:00Z"
622+ review ["transition_notice_sent_at" ] = "2026-03-22T10:00:00Z"
623+ reviewer_bot .reviews_module .record_reviewer_activity (review , "2026-03-18T10:00:00Z" )
624+ assert review ["last_reviewer_activity" ] == "2026-03-20T10:00:00Z"
625+ assert review ["transition_warning_sent" ] is None
626+ assert review ["transition_notice_sent_at" ] is None
627+
628+
477629def test_project_status_labels_emits_awaiting_write_approval_only_after_completion (monkeypatch ):
478630 state = make_state ()
479631 review = reviewer_bot .ensure_review_entry (state , 42 , create = True )
0 commit comments