Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 31 additions & 30 deletions scripts/ci/pr_review_merge_scheduler.py
Original file line number Diff line number Diff line change
Expand Up @@ -1282,6 +1282,28 @@ def dismiss_pull_request_review(
return False



def require_unmodified_pr_head(
repo: str,
pr: dict[str, Any],
*,
action_name: str,
) -> tuple[str, str, str]:
"""Validate the PR head matches the live GitHub state before mutation."""
repo = validate_github_repository(repo)
number = str(int(pr["number"]))
expected_head = validate_git_sha(pr["headRefOid"])
live_head = run_github_read(
["gh", "api", f"repos/{repo}/pulls/{number}", "--jq", ".head.sha"]
).strip()
if live_head != expected_head:
raise RuntimeError(
f"PR head changed before {action_name}; "
f"expected {expected_head}, observed {live_head or '<missing>'}"
)
return repo, number, expected_head


def dismiss_stale_opencode_approvals(
repo: str,
pr: dict[str, Any],
Expand All @@ -1296,17 +1318,9 @@ def dismiss_stale_opencode_approvals(
return len(review_ids), 0

require_github_actions_mutation_actor("dismiss-stale-opencode-approval")
repo = validate_github_repository(repo)
number = str(int(pr["number"]))
expected_head = validate_git_sha(pr["headRefOid"])
live_head = run_github_read(
["gh", "api", f"repos/{repo}/pulls/{number}", "--jq", ".head.sha"]
).strip()
if live_head != expected_head:
raise RuntimeError(
"PR head changed before stale approval dismissal; "
f"expected {expected_head}, observed {live_head or '<missing>'}"
)
repo, number, expected_head = require_unmodified_pr_head(
repo, pr, action_name="stale approval dismissal"
)

dismissed = 0
for review_id in review_ids:
Expand Down Expand Up @@ -1344,17 +1358,9 @@ def dismiss_stale_opencode_change_requests(repo: str, pr: dict[str, Any], *, dry
return len(review_ids)

require_github_actions_mutation_actor("dismiss-stale-opencode-review")
repo = validate_github_repository(repo)
number = str(int(pr["number"]))
expected_head = validate_git_sha(pr["headRefOid"])
live_head = run_github_read(
["gh", "api", f"repos/{repo}/pulls/{number}", "--jq", ".head.sha"]
).strip()
if live_head != expected_head:
raise RuntimeError(
"PR head changed before stale review dismissal; "
f"expected {expected_head}, observed {live_head or '<missing>'}"
)
repo, number, expected_head = require_unmodified_pr_head(
repo, pr, action_name="stale review dismissal"
)

for review_id in review_ids:
message = (
Expand Down Expand Up @@ -1623,15 +1629,10 @@ def restamp_pr_head_for_last_push_approval(repo: str, pr: dict[str, Any], *, dry
if not same_repository_head(repo, pr):
raise RuntimeError("last-push approval head refresh only supports same-repository PR heads")

number = str(int(pr["number"]))
head = validate_git_sha(pr["headRefOid"])
repo, number, head = require_unmodified_pr_head(
repo, pr, action_name="last-push approval head refresh"
)
head_ref = validate_git_ref(pr["headRefName"])
live_head = run(["gh", "api", f"repos/{repo}/pulls/{number}", "--jq", ".head.sha"]).strip()
if live_head != head:
raise RuntimeError(
"PR head changed before last-push approval head refresh; "
f"expected {head}, observed {live_head or '<missing>'}"
)

current_commit = json.loads(run(["gh", "api", f"repos/{repo}/git/commits/{head}"]))
tree = current_commit.get("tree") or {}
Expand Down
8 changes: 8 additions & 0 deletions tests/test_opencode_existing_approval_gate.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,10 @@ def trusted_adversarial_artifacts(tmp_path, monkeypatch):
source_path = source_root / ".github" / "workflows" / "opencode-review.yml"
runner_temp.mkdir()
source_path.parent.mkdir(parents=True)
runner_temp.chmod(0o755)
source_root.chmod(0o755)
source_path.parent.chmod(0o755)
source_path.parent.parent.chmod(0o755)
source_path.write_bytes(b"\n".join(SOURCE_LINES) + b"\n")

changed_files = runner_temp / "opencode-changed-files.txt"
Expand All @@ -61,6 +65,10 @@ def trusted_adversarial_artifacts(tmp_path, monkeypatch):
),
encoding="utf-8",
)
changed_files.chmod(0o644)
manifest.chmod(0o644)
source_path.chmod(0o644)

monkeypatch.setenv("RUNNER_TEMP", str(runner_temp))
monkeypatch.setenv("OPENCODE_SOURCE_WORKDIR", str(source_root))
monkeypatch.setenv("OPENCODE_CHANGED_FILES_FILE", str(changed_files))
Expand Down
5 changes: 5 additions & 0 deletions tests/test_opencode_security_boundaries.py
Original file line number Diff line number Diff line change
Expand Up @@ -300,6 +300,11 @@ def trusted_dispatch_status_artifacts(
),
encoding="utf-8",
)
changed_files.chmod(0o644)
manifest.chmod(0o644)
runner_temp.chmod(0o755)
source_root.chmod(0o755)

monkeypatch.setenv("RUNNER_TEMP", str(runner_temp))
monkeypatch.setenv("OPENCODE_SOURCE_WORKDIR", str(source_root))
monkeypatch.setenv("OPENCODE_CHANGED_FILES_FILE", str(changed_files))
Expand Down
Loading