diff --git a/.github/workflows/opencode-review.yml b/.github/workflows/opencode-review.yml index 712851c4..dc72cf2c 100644 --- a/.github/workflows/opencode-review.yml +++ b/.github/workflows/opencode-review.yml @@ -3329,8 +3329,9 @@ jobs: if: always() timeout-minutes: 45 env: - GH_TOKEN: ${{ secrets.OPENCODE_APPROVE_TOKEN || steps.opencode_app_token.outputs.token || github.token }} + GH_TOKEN: ${{ secrets.PR_REVIEW_MERGE_TOKEN || secrets.OPENCODE_APPROVE_TOKEN || steps.opencode_app_token.outputs.token || github.token }} CHECK_LOOKUP_GH_TOKEN: ${{ github.token }} + CONFIGURED_REVIEW_WRITE_TOKEN_SOURCE: ${{ secrets.PR_REVIEW_MERGE_TOKEN != '' && 'PR_REVIEW_MERGE_TOKEN' || secrets.OPENCODE_APPROVE_TOKEN != '' && 'OPENCODE_APPROVE_TOKEN' || steps.opencode_app_token.outputs.available == 'true' && 'opencode-app' || 'github-token' }} GH_REPOSITORY: ${{ github.event.pull_request.base.repo.full_name || github.event.inputs.target_repository || github.repository }} STRIX_GITHUB_MODELS_TOKEN: ${{ secrets.STRIX_GITHUB_MODELS_TOKEN || github.token }} # Exposed so the "openai" provider in opencode.jsonc resolves during the @@ -3382,47 +3383,64 @@ jobs: set -euo pipefail echo "::group::OpenCode Review Approval Gate" echo "PR=#${PR_NUMBER} head_sha=${HEAD_SHA} run_id=${RUN_ID} run_attempt=${RUN_ATTEMPT}" + configured_review_write_token="${GH_TOKEN:-}" + configured_review_write_token_source="${CONFIGURED_REVIEW_WRITE_TOKEN_SOURCE:-configured}" + if [ -n "${OPENCODE_APP_TOKEN:-}" ] && [ "${configured_review_write_token:-}" = "${OPENCODE_APP_TOKEN:-}" ]; then + configured_review_write_token_source="opencode-app" + fi + check_lookup_token_source="${configured_review_write_token_source:-configured}" if [ -n "${OPENCODE_APP_TOKEN:-}" ] && [ "${GH_REPOSITORY:-}" != "${GITHUB_REPOSITORY:-}" ]; then GH_TOKEN="$OPENCODE_APP_TOKEN" - fi - check_lookup_token_source="configured" - if [ -n "${OPENCODE_APP_TOKEN:-}" ] && [ "${GH_TOKEN:-}" = "${OPENCODE_APP_TOKEN:-}" ]; then + export GH_TOKEN check_lookup_token_source="opencode-app" - fi - configured_review_write_token="${GH_TOKEN:-}" - if [ -n "${CHECK_LOOKUP_GH_TOKEN:-}" ] && { [ -z "${OPENCODE_APP_TOKEN:-}" ] || [ "${GH_REPOSITORY:-}" = "${GITHUB_REPOSITORY:-}" ]; }; then + elif [ -n "${CHECK_LOOKUP_GH_TOKEN:-}" ]; then GH_TOKEN="$CHECK_LOOKUP_GH_TOKEN" export GH_TOKEN check_lookup_token_source="github-token" fi review_write_token="$GH_TOKEN" review_write_fallback_token="" - review_write_token_source="configured" - if [ -n "${OPENCODE_APP_TOKEN:-}" ] && [ "${GH_REPOSITORY:-}" != "${GITHUB_REPOSITORY:-}" ]; then + review_write_fallback_token_source="" + review_write_token_source="${check_lookup_token_source:-configured}" + if { [ "${configured_review_write_token_source:-}" = "PR_REVIEW_MERGE_TOKEN" ] || [ "${configured_review_write_token_source:-}" = "OPENCODE_APPROVE_TOKEN" ]; } && [ -n "${configured_review_write_token:-}" ]; then + review_write_token="$configured_review_write_token" + review_write_token_source="$configured_review_write_token_source" + if [ -n "${OPENCODE_APP_TOKEN:-}" ] && [ "${GH_REPOSITORY:-}" != "${GITHUB_REPOSITORY:-}" ] && [ "${OPENCODE_APP_TOKEN:-}" != "${review_write_token:-}" ]; then + review_write_fallback_token="$OPENCODE_APP_TOKEN" + review_write_fallback_token_source="opencode-app" + elif [ -n "${CHECK_LOOKUP_GH_TOKEN:-}" ] && [ "${CHECK_LOOKUP_GH_TOKEN:-}" != "${review_write_token:-}" ]; then + review_write_fallback_token="$CHECK_LOOKUP_GH_TOKEN" + review_write_fallback_token_source="github-token" + elif [ -n "${OPENCODE_APP_TOKEN:-}" ] && [ "${OPENCODE_APP_TOKEN:-}" != "${review_write_token:-}" ]; then + review_write_fallback_token="$OPENCODE_APP_TOKEN" + review_write_fallback_token_source="opencode-app" + fi + elif [ -n "${OPENCODE_APP_TOKEN:-}" ] && [ "${GH_REPOSITORY:-}" != "${GITHUB_REPOSITORY:-}" ]; then review_write_token="$OPENCODE_APP_TOKEN" review_write_token_source="opencode-app" - elif [ -n "${CHECK_LOOKUP_GH_TOKEN:-}" ] && [ "${GH_REPOSITORY:-}" = "${GITHUB_REPOSITORY:-}" ]; then - if [ -n "${configured_review_write_token:-}" ] && [ "${configured_review_write_token:-}" != "${CHECK_LOOKUP_GH_TOKEN:-}" ]; then - review_write_token="$configured_review_write_token" - if [ -n "${OPENCODE_APP_TOKEN:-}" ] && [ "${configured_review_write_token:-}" = "${OPENCODE_APP_TOKEN:-}" ]; then - review_write_token_source="opencode-app" - else - review_write_token_source="configured" + if [ -n "${configured_review_write_token:-}" ] && [ "${configured_review_write_token:-}" != "${review_write_token:-}" ]; then + review_write_fallback_token="$configured_review_write_token" + review_write_fallback_token_source="$configured_review_write_token_source" + fi + elif [ -n "${CHECK_LOOKUP_GH_TOKEN:-}" ]; then + review_write_token="$CHECK_LOOKUP_GH_TOKEN" + review_write_token_source="github-token" + if [ -n "${configured_review_write_token:-}" ] && [ "${configured_review_write_token:-}" != "${review_write_token:-}" ]; then + review_write_fallback_token="$configured_review_write_token" + review_write_fallback_token_source="$configured_review_write_token_source" + if [ "${configured_review_write_token_source:-}" = "opencode-app" ]; then + echo "::warning::No PR_REVIEW_MERGE_TOKEN or OPENCODE_APPROVE_TOKEN is configured for same-repository review writes; using github-token primary and opencode-app fallback, which may not satisfy required approving review provenance." fi - review_write_fallback_token="$CHECK_LOOKUP_GH_TOKEN" - else - review_write_token="$CHECK_LOOKUP_GH_TOKEN" - review_write_token_source="github-token" fi - elif [ -n "${OPENCODE_APP_TOKEN:-}" ] && [ "${GH_TOKEN:-}" = "${OPENCODE_APP_TOKEN:-}" ]; then - review_write_token_source="opencode-app" fi if [ -z "${review_write_fallback_token:-}" ] && [ -n "${configured_review_write_token:-}" ] && [ "${configured_review_write_token:-}" != "${review_write_token:-}" ]; then review_write_fallback_token="$configured_review_write_token" + review_write_fallback_token_source="$configured_review_write_token_source" fi overview_comment_token="$review_write_token" echo "check lookup token source=${check_lookup_token_source}" echo "review write token source=${review_write_token_source}" + echo "review write fallback token source=${review_write_fallback_token_source:-none}" app_token_limited_check_lookup() { [ "${check_lookup_token_source:-}" = "opencode-app" ] && [ -n "${OPENCODE_APP_TOKEN:-}" ] diff --git a/scripts/ci/test_strix_quick_gate.sh b/scripts/ci/test_strix_quick_gate.sh index b1671550..4a1b1141 100755 --- a/scripts/ci/test_strix_quick_gate.sh +++ b/scripts/ci/test_strix_quick_gate.sh @@ -872,15 +872,19 @@ assert_opencode_review_uses_codegraph_and_gpt5_fallback() { assert_file_contains "$workflow_file" "## OpenCode Review Overview" "opencode review publishes a visible Review Overview heading" assert_file_contains "$workflow_file" 'gh api -X PATCH "repos/${GH_REPOSITORY}/issues/comments/${overview_comment_id}"' "opencode review updates an existing Review Overview comment instead of duplicating it" assert_file_contains "$workflow_file" "Exchange OpenCode app token for review writes" "opencode review obtains an app token before publishing review writes" - assert_file_contains "$workflow_file" 'GH_TOKEN: ${{ secrets.OPENCODE_APPROVE_TOKEN || steps.opencode_app_token.outputs.token || github.token }}' "opencode approval keeps the configured cross-repo token available for review writes" + assert_file_contains "$workflow_file" 'GH_TOKEN: ${{ secrets.PR_REVIEW_MERGE_TOKEN || secrets.OPENCODE_APPROVE_TOKEN || steps.opencode_app_token.outputs.token || github.token }}' "opencode approval keeps the configured merge or cross-repo token available for review writes before the app token" assert_file_contains "$workflow_file" 'CHECK_LOOKUP_GH_TOKEN: ${{ github.token }}' "opencode approval uses the workflow token for target statusCheckRollup lookups" + assert_file_contains "$workflow_file" 'CONFIGURED_REVIEW_WRITE_TOKEN_SOURCE:' "opencode approval logs which configured review token source is used" assert_file_contains "$workflow_file" '[ "${GH_REPOSITORY:-}" = "${GITHUB_REPOSITORY:-}" ]' "opencode approval does not replace the app token with the workflow token for target-repository check lookups" assert_file_contains "$workflow_file" 'check_lookup_token_source="github-token"' "opencode approval marks target statusCheckRollup lookups as workflow-token reads" assert_file_contains "$workflow_file" 'review_write_token="$GH_TOKEN"' "opencode approval starts review writes from the configured token" assert_file_contains "$workflow_file" 'review_write_token="$OPENCODE_APP_TOKEN"' "opencode approval uses the app token for cross-repository review writes" assert_file_contains "$workflow_file" 'review_write_token="$CHECK_LOOKUP_GH_TOKEN"' "opencode approval uses the workflow token for same-repository review writes" - assert_file_contains "$workflow_file" 'review_write_token="$configured_review_write_token"' "opencode approval prefers configured app review token over same-repository workflow token when available" + assert_file_contains "$workflow_file" 'review_write_token="$configured_review_write_token"' "opencode approval prefers configured merge or approval tokens over app fallback tokens when available" assert_file_contains "$workflow_file" 'review_write_fallback_token="$CHECK_LOOKUP_GH_TOKEN"' "opencode approval keeps same-repository workflow token as review publication fallback" + assert_file_contains "$workflow_file" 'review_write_fallback_token="$OPENCODE_APP_TOKEN"' "opencode approval keeps the app token as cross-repository review publication fallback" + assert_file_contains "$workflow_file" 'review write fallback token source=${review_write_fallback_token_source:-none}' "opencode approval logs the review publication fallback token source" + assert_file_contains "$workflow_file" 'using github-token primary and opencode-app fallback' "opencode approval explains why app-token-only same-repository reviews may not satisfy branch protection" assert_file_not_contains "$workflow_file" 'review_write_token="${OPENCODE_APP_TOKEN:-$GH_TOKEN}"' "opencode approval must not force same-repository review writes through the app token" assert_file_contains "$workflow_file" 'env GH_TOKEN="$review_write_token" gh api -X POST "repos/${GH_REPOSITORY}/pulls/${PR_NUMBER}/reviews"' "opencode review writes use the review write token" assert_file_contains "$workflow_file" 'app_token_limited_check_lookup()' "opencode approval detects app-token-limited GitHub Checks lookups" diff --git a/tests/test_opencode_agent_contract.py b/tests/test_opencode_agent_contract.py index af19fbb1..3a2485dd 100644 --- a/tests/test_opencode_agent_contract.py +++ b/tests/test_opencode_agent_contract.py @@ -353,12 +353,16 @@ def test_workflow_provisions_sandbox_tool_and_reviewer_agent(): assert "Distinguish typing.Protocol, abc abstractmethod" in workflow assert "executable implementation gaps" in workflow assert "CHECK_LOOKUP_GH_TOKEN" in workflow + assert "CONFIGURED_REVIEW_WRITE_TOKEN_SOURCE" in workflow assert "retrying with workflow github token" in workflow assert 'review_write_token="$GH_TOKEN"' in workflow assert 'review_write_token="$OPENCODE_APP_TOKEN"' in workflow assert 'review_write_token="$CHECK_LOOKUP_GH_TOKEN"' in workflow assert 'review_write_token="$configured_review_write_token"' in workflow assert 'review_write_fallback_token="$CHECK_LOOKUP_GH_TOKEN"' in workflow + assert 'review_write_fallback_token="$OPENCODE_APP_TOKEN"' in workflow + assert "review write fallback token source=" in workflow + assert "using github-token primary and opencode-app fallback" in workflow assert 'review_write_token="${OPENCODE_APP_TOKEN:-$GH_TOKEN}"' not in workflow assert 'REVIEW_PUBLISH_RETRY_ATTEMPTS: "3"' in workflow assert "gh_error_is_retryable_publication_failure()" in workflow