diff --git a/.github/workflows/pr-review-merge-scheduler.yml b/.github/workflows/pr-review-merge-scheduler.yml index 8319ae5be..1cf376ad9 100644 --- a/.github/workflows/pr-review-merge-scheduler.yml +++ b/.github/workflows/pr-review-merge-scheduler.yml @@ -244,6 +244,7 @@ jobs: { printf 'repository=%s\n' "$GITHUB_REPOSITORY" printf 'base_branch=%s\n' "$DEFAULT_BRANCH" + printf 'repository_default_branch=%s\n' "$DEFAULT_BRANCH" } >>"$GITHUB_OUTPUT" exit 0 fi @@ -281,13 +282,15 @@ jobs: live_head_repository="$(jq -r '.head.repo.full_name // empty' <<<"$pull_json")" live_base_branch="$(jq -r '.base.ref // empty' <<<"$pull_json")" live_head_sha="$(jq -r '.head.sha // empty' <<<"$pull_json")" + repository_default_branch="$(gh api "repos/${TARGET_REPOSITORY_INPUT}" --jq '.default_branch // empty')" if [ "$live_number" != "$TARGET_PR_NUMBER" ] || [ "$live_state" != "open" ] || [ "$live_base_repository" != "$TARGET_REPOSITORY_INPUT" ] || [ "$live_head_repository" != "$TARGET_REPOSITORY_INPUT" ] || [ -z "$live_base_branch" ] || + [ -z "$repository_default_branch" ] || ! [[ "$live_head_sha" =~ ^[0-9a-fA-F]{40}$ ]]; then - printf '::error::Targeted scheduler dispatch rejected closed, cross-repository, or malformed live PR metadata. target=%s pr=%s state=%s base_repository=%s head_repository=%s base_branch=%s head_sha=%s\n' "$TARGET_REPOSITORY_INPUT" "$TARGET_PR_NUMBER" "${live_state:-}" "${live_base_repository:-}" "${live_head_repository:-}" "${live_base_branch:-}" "${live_head_sha:-}" + printf '::error::Targeted scheduler dispatch rejected closed, cross-repository, or malformed live PR metadata. target=%s pr=%s state=%s base_repository=%s head_repository=%s base_branch=%s repository_default_branch=%s head_sha=%s\n' "$TARGET_REPOSITORY_INPUT" "$TARGET_PR_NUMBER" "${live_state:-}" "${live_base_repository:-}" "${live_head_repository:-}" "${live_base_branch:-}" "${repository_default_branch:-}" "${live_head_sha:-}" exit 1 fi if [ -n "$TARGET_BASE_BRANCH_INPUT" ] && @@ -299,9 +302,10 @@ jobs: { printf 'repository=%s\n' "$TARGET_REPOSITORY_INPUT" printf 'base_branch=%s\n' "$live_base_branch" + printf 'repository_default_branch=%s\n' "$repository_default_branch" printf 'head_sha=%s\n' "$live_head_sha" } >>"$GITHUB_OUTPUT" - printf 'Validated exact targeted scheduler dispatch for %s#%s at %s on base %s.\n' "$TARGET_REPOSITORY_INPUT" "$TARGET_PR_NUMBER" "$live_head_sha" "$live_base_branch" + printf 'Validated exact targeted scheduler dispatch for %s#%s at %s on base %s; repository default branch is %s.\n' "$TARGET_REPOSITORY_INPUT" "$TARGET_PR_NUMBER" "$live_head_sha" "$live_base_branch" "$repository_default_branch" - name: Resolve trusted scheduler source ref id: trusted_source @@ -482,7 +486,7 @@ jobs: env: GH_TOKEN: ${{ secrets.PR_REVIEW_MERGE_TOKEN || secrets.OPENCODE_APPROVE_TOKEN || steps.scheduler_app_token.outputs.token || github.token }} TARGET_REPOSITORY: ${{ steps.targeted_dispatch.outputs.repository }} - TARGET_DEFAULT_BRANCH: ${{ steps.targeted_dispatch.outputs.base_branch }} + TARGET_DEFAULT_BRANCH: ${{ steps.targeted_dispatch.outputs.repository_default_branch }} SCHEDULER_ACTIONS_TOKEN: ${{ github.event_name == 'repository_dispatch' && github.event.client_payload.target_repository != '' && (secrets.PR_REVIEW_MERGE_TOKEN || secrets.OPENCODE_APPROVE_TOKEN || steps.scheduler_app_token.outputs.token) || github.token }} # Same-repository dispatch credential: when this scheduler runs inside # ContextualWisdomLab/.github (the repository the required workflows are diff --git a/tests/test_opencode_workflow_shell_syntax.py b/tests/test_opencode_workflow_shell_syntax.py index ec6edca40..cd6f620a5 100644 --- a/tests/test_opencode_workflow_shell_syntax.py +++ b/tests/test_opencode_workflow_shell_syntax.py @@ -170,8 +170,17 @@ def test_merge_scheduler_targeted_dispatch_validates_live_exact_pr(tmp_path): """#!/usr/bin/env bash set -euo pipefail test "$1" = api -test "$2" = repos/ContextualWisdomLab/naruon/pulls/1179 -printf '%s\\n' "$FAKE_PULL_JSON" +case "$2" in + repos/ContextualWisdomLab/naruon/pulls/1179) + printf '%s\\n' "$FAKE_PULL_JSON" + ;; + repos/ContextualWisdomLab/naruon) + printf '%s\\n' 'develop' + ;; + *) + exit 1 + ;; +esac """, encoding="utf-8", ) @@ -180,7 +189,7 @@ def test_merge_scheduler_targeted_dispatch_validates_live_exact_pr(tmp_path): "number": 1179, "state": "open", "base": { - "ref": "develop", + "ref": "feat/stack", "repo": {"full_name": "ContextualWisdomLab/naruon"}, }, "head": { @@ -199,7 +208,7 @@ def test_merge_scheduler_targeted_dispatch_validates_live_exact_pr(tmp_path): "DEFAULT_BRANCH": "main", "TARGET_REPOSITORY_INPUT": "ContextualWisdomLab/naruon", "TARGET_PR_NUMBER": "1179", - "TARGET_BASE_BRANCH_INPUT": "develop", + "TARGET_BASE_BRANCH_INPUT": "feat/stack", "ALLOWED_TARGET_REPOSITORIES": ( "ContextualWisdomLab/.github, ContextualWisdomLab/naruon" ), @@ -217,7 +226,8 @@ def test_merge_scheduler_targeted_dispatch_validates_live_exact_pr(tmp_path): assert accepted.returncode == 0, accepted.stderr assert output.read_text(encoding="utf-8").splitlines() == [ "repository=ContextualWisdomLab/naruon", - "base_branch=develop", + "base_branch=feat/stack", + "repository_default_branch=develop", "head_sha=4afd4af7ad343660356791873d940aa2846f40c2", ] diff --git a/tests/test_required_workflow_queue_contract.py b/tests/test_required_workflow_queue_contract.py index 535fd513a..127b8c0dc 100644 --- a/tests/test_required_workflow_queue_contract.py +++ b/tests/test_required_workflow_queue_contract.py @@ -66,6 +66,8 @@ def test_targeted_scheduler_dispatch_is_allowlisted_and_exact_pr_scoped() -> Non assert "TARGET_REPOSITORY_INPUT:" in validation assert "TARGET_PR_NUMBER:" in validation assert "TARGET_BASE_BRANCH_INPUT:" in validation + assert "repository_default_branch=\"$(gh api \"repos/${TARGET_REPOSITORY_INPUT}\" --jq '.default_branch // empty')\"" in validation + assert "repository_default_branch=%s\\n" in validation assert ( "ALLOWED_TARGET_REPOSITORIES: ${{ " "vars.OPENCODE_REPOSITORY_DISPATCH_TARGETS }}" @@ -79,7 +81,7 @@ def test_targeted_scheduler_dispatch_is_allowlisted_and_exact_pr_scoped() -> Non assert "Targeted scheduler dispatch base branch does not match the live PR" in validation assert "TARGET_REPOSITORY: ${{ steps.targeted_dispatch.outputs.repository }}" in inspect assert ( - "TARGET_DEFAULT_BRANCH: ${{ steps.targeted_dispatch.outputs.base_branch }}" + "TARGET_DEFAULT_BRANCH: ${{ steps.targeted_dispatch.outputs.repository_default_branch }}" in inspect ) assert '--repo "$TARGET_REPOSITORY"' in inspect