Skip to content

Commit 74c8f6d

Browse files
authored
Corrective Stage 1 Final Follow-Up: close trusted-path and proof gaps (#552)
* fix(reviewer-bot): close final follow-up trusted path gaps Keep trusted status-label and FLS guidance paths on their retained owners, preserve workflow-dispatch projection follow-up, and harden bootstrapped proof so these regressions are caught before merge. * fix(reviewer-bot): align mandatory approver proof harness Keep the mandatory approver equivalence fixtures exercising the retained transport-owned label path so branch CI matches the final follow-up owner model.
1 parent 37e8bde commit 74c8f6d

File tree

14 files changed

+377
-19
lines changed

14 files changed

+377
-19
lines changed

scripts/reviewer_bot_lib/commands.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
from .event_inputs import build_assignment_request as decode_assignment_request
1010
from .guidance import (
1111
get_assignment_failure_comment,
12+
get_fls_audit_guidance,
1213
get_issue_guidance,
1314
get_pr_guidance,
1415
)
@@ -139,7 +140,7 @@ def _apply_assignment_side_effects(
139140
else:
140141
labels = set(request.issue_labels)
141142
guidance = (
142-
bot.get_fls_audit_guidance(reviewer, request.issue_author)
143+
get_fls_audit_guidance(reviewer, request.issue_author)
143144
if bot.FLS_AUDIT_LABEL in labels
144145
else get_issue_guidance(reviewer, request.issue_author)
145146
)

scripts/reviewer_bot_lib/lifecycle.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,9 +58,10 @@ def _semantic_digest(value: str) -> str:
5858

5959

6060
def _write_transition_notice_marker_cutover(bot) -> None:
61-
config_dir = Path(bot.get_config_value("OPENCODE_CONFIG_DIR") or "")
62-
if not config_dir:
61+
config_dir_value = bot.get_config_value("OPENCODE_CONFIG_DIR").strip()
62+
if not config_dir_value:
6363
return
64+
config_dir = Path(config_dir_value)
6465
artifact_path = config_dir / "reviewer-bot" / "maintainability-remediation" / "transition-notice-marker-cutover.json"
6566
artifact_path.parent.mkdir(parents=True, exist_ok=True)
6667
artifact_path.write_text(

scripts/reviewer_bot_lib/maintenance.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,10 @@ def handle_manual_dispatch(bot, state: dict) -> bool:
8787
bot.collect_touched_item(issue_number)
8888
return False
8989
if action == "check-overdue":
90-
return maintenance_schedule.handle_scheduled_check_result(bot, state).state_changed
90+
result = maintenance_schedule.handle_scheduled_check_result(bot, state)
91+
for issue_number in result.touched_items:
92+
bot.collect_touched_item(issue_number)
93+
return result.state_changed
9194
if action == "execute-pending-privileged-command":
9295
source_event_key = request.privileged_source_event_key
9396
if not source_event_key:

scripts/reviewer_bot_lib/reviews.py

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,8 @@ def is_triage_or_higher(bot, username: str) -> bool:
140140
def trigger_mandatory_approver_escalation(bot, state: dict, issue_number: int) -> bool:
141141
from scripts.reviewer_bot_core import mandatory_approver_policy
142142

143+
from . import github_api
144+
143145
review_data = review_state.ensure_review_entry(state, issue_number, create=True)
144146
if review_data is None:
145147
return False
@@ -156,7 +158,7 @@ def trigger_mandatory_approver_escalation(bot, state: dict, issue_number: int) -
156158
state_changed = True
157159
if decision["attempt_label_apply"]:
158160
try:
159-
if bot.add_label_with_status(issue_number, MANDATORY_TRIAGE_APPROVER_LABEL):
161+
if github_api.add_label_with_status(bot, issue_number, MANDATORY_TRIAGE_APPROVER_LABEL):
160162
if decision["record_label_applied_at"]:
161163
review_data["mandatory_approver_label_applied_at"] = str(decision["now"])
162164
state_changed = True
@@ -172,6 +174,8 @@ def trigger_mandatory_approver_escalation(bot, state: dict, issue_number: int) -
172174
def satisfy_mandatory_approver_requirement(bot, state: dict, issue_number: int, approver: str) -> bool:
173175
from scripts.reviewer_bot_core import mandatory_approver_policy
174176

177+
from . import github_api
178+
175179
review_data = review_state.ensure_review_entry(state, issue_number, create=True)
176180
if review_data is None:
177181
return False
@@ -186,7 +190,7 @@ def satisfy_mandatory_approver_requirement(bot, state: dict, issue_number: int,
186190
review_data["mandatory_approver_satisfied_by"] = str(decision["approver"])
187191
review_data["mandatory_approver_satisfied_at"] = str(decision["now"])
188192
try:
189-
bot.remove_label_with_status(issue_number, MANDATORY_TRIAGE_APPROVER_LABEL)
193+
github_api.remove_label_with_status(bot, issue_number, MANDATORY_TRIAGE_APPROVER_LABEL)
190194
except RuntimeError as exc:
191195
_log(bot, "warning", f"Unable to remove escalation label on #{issue_number}: {exc}", issue_number=issue_number, error=str(exc))
192196
bot.github.post_comment(issue_number, MANDATORY_TRIAGE_SATISFIED_TEMPLATE.format(approver=approver))
@@ -332,6 +336,8 @@ def project_status_labels_for_item(
332336

333337

334338
def sync_status_labels(bot, issue_number: int, desired_labels: set[str], actual_labels: Iterable[str]) -> bool:
339+
from . import github_api
340+
335341
actual_status_labels = {label for label in actual_labels if label in STATUS_LABELS}
336342
to_add = desired_labels - actual_status_labels
337343
to_remove = actual_status_labels - desired_labels
@@ -342,11 +348,11 @@ def sync_status_labels(bot, issue_number: int, desired_labels: set[str], actual_
342348
raise RuntimeError(f"Unable to ensure reviewer-bot status label exists: {label}")
343349
changed = False
344350
for label in sorted(to_remove):
345-
if not bot.remove_label_with_status(issue_number, label):
351+
if not github_api.remove_label_with_status(bot, issue_number, label):
346352
raise RuntimeError(f"Unable to remove reviewer-bot status label '{label}' from #{issue_number}")
347353
changed = True
348354
for label in sorted(to_add):
349-
if not bot.add_label_with_status(issue_number, label):
355+
if not github_api.add_label_with_status(bot, issue_number, label):
350356
raise RuntimeError(f"Unable to add reviewer-bot status label '{label}' to #{issue_number}")
351357
changed = True
352358
return changed
@@ -356,7 +362,7 @@ def sync_status_labels_for_items(bot, state: dict, issue_numbers: Iterable[int])
356362
changed = False
357363
for issue_number in sorted({n for n in issue_numbers if isinstance(n, int) and n > 0}):
358364
issue_snapshot = bot.github.get_issue_or_pr_snapshot(issue_number)
359-
desired_labels, metadata = bot.project_status_labels_for_item(issue_number, state, issue_snapshot=issue_snapshot)
365+
desired_labels, metadata = project_status_labels_for_item(bot, issue_number, state, issue_snapshot=issue_snapshot)
360366
if desired_labels is None:
361367
reason = metadata.get("reason") if isinstance(metadata, dict) else "unknown"
362368
raise RuntimeError(f"Failed to derive reviewer-bot status labels for #{issue_number}: {reason}")
@@ -370,7 +376,7 @@ def sync_status_labels_for_items(bot, state: dict, issue_numbers: Iterable[int])
370376
name = label.get("name")
371377
if isinstance(name, str):
372378
actual_labels.add(name)
373-
if bot.sync_status_labels(issue_number, desired_labels, actual_labels):
379+
if sync_status_labels(bot, issue_number, desired_labels, actual_labels):
374380
changed = True
375381
return changed
376382

tests/contract/reviewer_bot/test_fake_runtime_contract.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,20 @@ def test_fake_runtime_review_state_compatibility_surface_is_limited(monkeypatch)
194194
assert hasattr(runtime, name) is False
195195

196196

197+
def test_fake_runtime_adapter_views_do_not_alias_unrelated_retained_roles(monkeypatch):
198+
runtime = FakeReviewerBotRuntime(monkeypatch)
199+
200+
assert runtime.adapters.github is runtime.github
201+
assert runtime.adapters.review_state is not runtime.adapters.commands
202+
assert runtime.adapters.review_state is not runtime.adapters.queue
203+
assert hasattr(runtime.adapters.review_state, "compute_reviewer_response_state")
204+
assert hasattr(runtime.adapters.commands, "parse_command")
205+
assert hasattr(runtime.adapters.queue, "get_next_reviewer")
206+
assert hasattr(runtime.adapters.state_lock, "assert_lock_held")
207+
assert hasattr(runtime.adapters.commands, "get_next_reviewer") is False
208+
assert hasattr(runtime.adapters.queue, "parse_command") is False
209+
210+
197211
def test_fake_runtime_rejects_unknown_handler_names(monkeypatch):
198212
runtime = FakeReviewerBotRuntime(monkeypatch)
199213

tests/contract/reviewer_bot/test_reviewer_board_workflow_contracts.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,3 +28,15 @@ def test_sweeper_repair_workflow_scopes_reviewer_board_env_to_preview_only():
2828
"REVIEWER_BOARD_TOKEN: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.action == 'preview-reviewer-board' && secrets.REVIEWER_BOARD_TOKEN || '' }}"
2929
in workflow_text
3030
)
31+
32+
33+
def test_sweeper_repair_workflow_exports_retained_manual_dispatch_env_contract():
34+
data = yaml.safe_load(Path(".github/workflows/reviewer-bot-sweeper-repair.yml").read_text(encoding="utf-8"))
35+
on_block = data.get("on", data.get(True))
36+
action_input = on_block["workflow_dispatch"]["inputs"]["action"]
37+
workflow_text = Path(".github/workflows/reviewer-bot-sweeper-repair.yml").read_text(encoding="utf-8")
38+
39+
assert "check-overdue" in action_input["options"]
40+
assert "repair-review-status-labels" in action_input["options"]
41+
assert "MANUAL_ACTION: ${{ github.event.inputs.action }}" in workflow_text
42+
assert "ISSUE_NUMBER: ${{ github.event.inputs.issue_number }}" in workflow_text

tests/contract/reviewer_bot/test_runtime_protocols.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,15 @@ def _assert_core_runtime_surface(runtime) -> None:
3838
assert runtime.domain.locks is runtime.locks
3939
assert runtime.domain.handlers is runtime.handlers
4040

41+
for helper_name in (
42+
"project_status_labels_for_item",
43+
"sync_status_labels",
44+
"add_label_with_status",
45+
"remove_label_with_status",
46+
"get_fls_audit_guidance",
47+
):
48+
assert hasattr(runtime, helper_name) is False
49+
4150

4251
def test_fake_runtime_default_lock_state_matches_production_contract(monkeypatch):
4352
runtime = FakeReviewerBotRuntime(monkeypatch)

tests/fixtures/fake_runtime.py

Lines changed: 121 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -107,15 +107,127 @@ def __init__(self, *, state_store, github, locks, handlers, workflow, adapters,
107107
self.compat = compat
108108

109109

110+
class _AdapterView:
111+
def __init__(self, target, allowed: set[str]):
112+
self._target = target
113+
self._allowed = set(allowed)
114+
115+
def __getattr__(self, name: str):
116+
if name not in self._allowed:
117+
raise AttributeError(name)
118+
return getattr(self._target, name)
119+
120+
110121
class FakeRuntimeAdapterServices:
111122
def __init__(self, runtime: "FakeReviewerBotRuntime"):
112123
self._runtime = runtime
113-
self.workflow = runtime.workflow
114-
self.review = runtime.compat.review
115-
self.review_state = runtime.compat.review
116-
self.commands = runtime.compat.review
117-
self.queue = runtime.compat.review
118-
self.automation = runtime.compat.automation
124+
self.github = runtime.github
125+
self.workflow = _AdapterView(
126+
runtime.workflow,
127+
{
128+
"process_pass_until_expirations",
129+
"sync_members_with_queue",
130+
"sync_status_labels_for_items",
131+
"fetch_members",
132+
},
133+
)
134+
self.review_state = _AdapterView(
135+
runtime.compat.review,
136+
{
137+
"maybe_record_head_observation_repair",
138+
"handle_transition_notice",
139+
"ensure_review_entry",
140+
"set_current_reviewer",
141+
"update_reviewer_activity",
142+
"mark_review_complete",
143+
"is_triage_or_higher",
144+
"trigger_mandatory_approver_escalation",
145+
"satisfy_mandatory_approver_requirement",
146+
"compute_reviewer_response_state",
147+
"rebuild_pr_approval_state",
148+
},
149+
)
150+
self.commands = _AdapterView(
151+
SimpleNamespace(
152+
handle_pass_command=runtime.compat.review.handle_pass_command,
153+
handle_pass_until_command=runtime.compat.review.handle_pass_until_command,
154+
handle_label_command=runtime.compat.review.handle_label_command,
155+
handle_sync_members_command=runtime.compat.review.handle_sync_members_command,
156+
handle_queue_command=runtime.compat.review.handle_queue_command,
157+
handle_commands_command=runtime.compat.review.handle_commands_command,
158+
handle_claim_command=runtime.compat.review.handle_claim_command,
159+
handle_release_command=runtime.compat.review.handle_release_command,
160+
handle_rectify_command=runtime.compat.review.handle_rectify_command,
161+
handle_assign_command=runtime.compat.review.handle_assign_command,
162+
handle_assign_from_queue_command=runtime.compat.review.handle_assign_from_queue_command,
163+
handle_accept_no_fls_changes_command=runtime.compat.automation.handle_accept_no_fls_changes_command,
164+
get_commands_help=runtime.compat.review.get_commands_help,
165+
strip_code_blocks=runtime.compat.review.strip_code_blocks,
166+
parse_command=runtime.compat.review.parse_command,
167+
),
168+
{
169+
"handle_pass_command",
170+
"handle_pass_until_command",
171+
"handle_label_command",
172+
"handle_sync_members_command",
173+
"handle_queue_command",
174+
"handle_commands_command",
175+
"handle_claim_command",
176+
"handle_release_command",
177+
"handle_rectify_command",
178+
"handle_assign_command",
179+
"handle_assign_from_queue_command",
180+
"handle_accept_no_fls_changes_command",
181+
"get_commands_help",
182+
"strip_code_blocks",
183+
"parse_command",
184+
},
185+
)
186+
self.queue = _AdapterView(
187+
runtime.compat.review,
188+
{
189+
"get_next_reviewer",
190+
"record_assignment",
191+
"reposition_member_as_next",
192+
},
193+
)
194+
self.automation = _AdapterView(
195+
runtime.compat.automation,
196+
{
197+
"run_command",
198+
"summarize_output",
199+
"list_changed_files",
200+
"get_default_branch",
201+
"find_open_pr_for_branch_status",
202+
"create_pull_request",
203+
"fetch_members",
204+
"handle_accept_no_fls_changes_command",
205+
},
206+
)
207+
self.state_lock = _AdapterView(
208+
runtime.compat.state_lock,
209+
{
210+
"assert_lock_held",
211+
"parse_iso8601_timestamp",
212+
"normalize_lock_metadata",
213+
"get_state_issue",
214+
"clear_lock_metadata",
215+
"get_state_issue_snapshot",
216+
"conditional_patch_state_issue",
217+
"render_state_issue_body",
218+
"get_state_issue_html_url",
219+
"get_lock_ref_display",
220+
"get_lock_ref_snapshot",
221+
"build_lock_metadata",
222+
"create_lock_commit",
223+
"cas_update_lock_ref",
224+
"lock_is_currently_valid",
225+
"renew_state_issue_lease_lock",
226+
"ensure_state_issue_lease_lock_fresh",
227+
"acquire_state_issue_lease_lock",
228+
"release_state_issue_lease_lock",
229+
},
230+
)
119231

120232
def process_pass_until_expirations(self, state: dict):
121233
return self._runtime.workflow.process_pass_until_expirations(state)
@@ -278,6 +390,9 @@ def parse_iso8601_timestamp(self, value: Any):
278390
def normalize_lock_metadata(self, lock_meta: dict | None):
279391
return state_store_module.normalize_lock_metadata(lock_meta)
280392

393+
def assert_lock_held(self, context: str) -> None:
394+
return state_store_module.assert_lock_held(self._runtime, context)
395+
281396
def get_state_issue(self):
282397
return state_store_module.get_state_issue(self._runtime)
283398

0 commit comments

Comments
 (0)