Skip to content

Commit 0d947b2

Browse files
committed
fix: harden projection repair workflow guards
1 parent c054476 commit 0d947b2

6 files changed

Lines changed: 36 additions & 1 deletion

File tree

.github/workflows/reviewer-bot-sweeper-repair.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ jobs:
7474
mkdir -p "$RUNNER_TEMP/reviewer-bot-repair-output-${GITHUB_RUN_ID}-attempt-${GITHUB_RUN_ATTEMPT}"
7575
uv run --project "$BOT_SRC_ROOT" reviewer-bot
7676
- name: Upload repair artifact
77+
if: ${{ github.event_name == 'workflow_dispatch' && (github.event.inputs.action == 'repair-review-status-labels' || github.event.inputs.action == 'repair-issue314-state-health') }}
7778
uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808
7879
with:
7980
name: reviewer-bot-repair-output-${{ github.run_id }}-attempt-${{ github.run_attempt }}

scripts/reviewer_bot_lib/maintenance.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -250,6 +250,8 @@ def derive_manual_dispatch_projection_policy(request) -> ManualDispatchProjectio
250250
return ManualDispatchProjectionPolicy(action, issue_number, False, True, "targeted_status_label_repair")
251251
if action == "repair-review-status-labels":
252252
return ManualDispatchProjectionPolicy(action, issue_number, True, True, "broad_status_label_repair")
253+
if action == "repair-issue314-state-health":
254+
return ManualDispatchProjectionPolicy(action, issue_number, False, True, "issue314_state_health_repair")
253255
if action in {"preview-check-overdue", "preview-status-label-projection", "preview-issue314-state-health"}:
254256
return ManualDispatchProjectionPolicy(action, issue_number, False, False, "read_only_preview")
255257
return ManualDispatchProjectionPolicy(action, issue_number, True, True, "normal_manual_mutation")

tests/contract/reviewer_bot/test_workflow_files.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -438,6 +438,12 @@ def test_sweeper_repair_workflow_exposes_nonce_identity_and_repair_artifacts():
438438
inputs = on_block["workflow_dispatch"]["inputs"]
439439

440440
assert workflow["run-name"] == "repair ${{ github.event.inputs.action }} issue ${{ github.event.inputs.issue_number }} nonce ${{ github.event.inputs.validation_nonce }}"
441+
upload_step = next(
442+
step
443+
for step in workflow["jobs"]["reviewer-bot-sweeper-repair"]["steps"]
444+
if step["name"] == "Upload repair artifact"
445+
)
446+
assert upload_step["if"] == "${{ github.event_name == 'workflow_dispatch' && (github.event.inputs.action == 'repair-review-status-labels' || github.event.inputs.action == 'repair-issue314-state-health') }}"
441447
assert inputs["action"]["options"] == [
442448
"sync-members",
443449
"show-state",

tests/integration/reviewer_bot/test_app_repair_issue314_state_health.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import pytest
44

5+
from scripts.reviewer_bot_lib import app
56
from tests.fixtures.app_harness import AppHarness
67
from tests.fixtures.reviewer_bot import make_state, make_tracked_review_state
78
from tests.fixtures.reviewer_bot_fakes import RouteGitHubApi
@@ -31,6 +32,7 @@ def test_execute_run_repair_issue314_state_health_removes_closed_rows_through_st
3132
monkeypatch.setenv("ISSUE314_STATE_HEALTH_REPAIR_SUMMARY_PATH", str(summary_path))
3233

3334
state = make_state()
35+
state["status_projection_epoch"] = "stale_projection_epoch"
3436
make_tracked_review_state(
3537
state,
3638
42,
@@ -59,6 +61,11 @@ def test_execute_run_repair_issue314_state_health_removes_closed_rows_through_st
5961
harness.stub_load_state(lambda *, fail_on_unavailable=False: state)
6062
harness.stub_save_state(lambda current: saved.append(json.loads(json.dumps(current))) or True)
6163
harness.stub_sync_status_labels(lambda current, issue_numbers: (_ for _ in ()).throw(AssertionError("issue314 repair should not broad-sync labels")))
64+
monkeypatch.setattr(
65+
app,
66+
"collect_status_projection_repair_items",
67+
lambda bot, state: (_ for _ in ()).throw(AssertionError("issue314 repair broadened through epoch repair")),
68+
)
6269

6370
result = harness.run_execute()
6471

@@ -77,6 +84,7 @@ def test_execute_run_repair_issue314_state_health_removes_closed_rows_through_st
7784
assert payload["output_keys"] == sorted(payload.keys())
7885
assert saved
7986
assert "42" not in saved[-1]["active_reviews"]
87+
assert saved[-1]["status_projection_epoch"] == "stale_projection_epoch"
8088

8189

8290
def test_execute_run_repair_issue314_state_health_emits_summary_only_after_state_save(

tests/unit/reviewer_bot/test_maintenance.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -235,6 +235,16 @@ def test_broad_status_label_repair_collection_is_explicit(monkeypatch):
235235
assert maintenance.collect_status_label_repair_targets(bot, make_state(), request) == (42, 264)
236236

237237

238+
def test_issue314_state_health_repair_policy_does_not_broaden_epoch_repair():
239+
policy = maintenance.derive_manual_dispatch_projection_policy(
240+
SimpleNamespace(action="repair-issue314-state-health", issue_number=314)
241+
)
242+
243+
assert policy.allow_epoch_repair_expansion is False
244+
assert policy.allow_status_label_sync is True
245+
assert policy.reason == "issue314_state_health_repair"
246+
247+
238248
def test_status_label_repair_summary_writes_machine_readable_artifact(monkeypatch, tmp_path):
239249
summary = maintenance.StatusLabelRepairSummary(
240250
schema_version=1,

tests/unit/reviewer_bot/test_reviewer_response_policy.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,13 @@ def test_legacy_response_dict_normalizes_to_typed_decision():
2525

2626

2727
def test_cadence_overlay_projects_reassignment_needed_once():
28-
base = to_reviewer_response_decision({"response_state": "awaiting_reviewer_response"})
28+
base = to_reviewer_response_decision(
29+
{
30+
"response_state": "awaiting_reviewer_response",
31+
"current_scope_key": "reviewer=iglesias|head=head-a|cycle=none|anchor=assigned",
32+
"current_scope_basis": "assigned_at",
33+
}
34+
)
2935
cadence = ReminderCadenceDecision(
3036
issue_number=264,
3137
reviewer="iglesias",
@@ -45,3 +51,5 @@ def test_cadence_overlay_projects_reassignment_needed_once():
4551
assert decision.response_state == "reviewer_reassignment_needed"
4652
assert decision.suppresses_overdue_reminder is True
4753
assert decision.reason == "legacy_duplicate_reminders_exhausted"
54+
assert decision.scope.scope_basis == "reminder_cadence_exhausted"
55+
assert base.scope.scope_basis == "assigned_at"

0 commit comments

Comments
 (0)