diff --git a/.github/workflows/opencode-review-dispatch.yml b/.github/workflows/opencode-review-dispatch.yml index 3677f408bd..cc0b84dff1 100644 --- a/.github/workflows/opencode-review-dispatch.yml +++ b/.github/workflows/opencode-review-dispatch.yml @@ -7600,14 +7600,14 @@ jobs: && needs.validate-pr-metadata.outputs.target_repository != '' && needs.validate-pr-metadata.outputs.head_sha != '' env: - GH_TOKEN: ${{ secrets.PR_REVIEW_MERGE_TOKEN || secrets.OPENCODE_APPROVE_TOKEN || steps.opencode_app_token.outputs.token || github.token }} + GH_TOKEN: ${{ needs.validate-pr-metadata.outputs.target_repository == github.repository && github.token || secrets.PR_REVIEW_MERGE_TOKEN || secrets.OPENCODE_APPROVE_TOKEN || steps.opencode_app_token.outputs.token || github.token }} GH_REPOSITORY: ${{ needs.validate-pr-metadata.outputs.target_repository }} PR_NUMBER: ${{ needs.validate-pr-metadata.outputs.pr_number }} PR_HEAD_SHA: ${{ needs.validate-pr-metadata.outputs.head_sha }} RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} OPENCODE_MODEL_POOL_OUTCOME: ${{ steps.opencode_review_model_pool.outputs.review_status }} COVERAGE_EVIDENCE_RESULT: ${{ needs.coverage-evidence.result }} - OPENCODE_STATUS_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' }} + OPENCODE_STATUS_TOKEN_SOURCE: ${{ needs.validate-pr-metadata.outputs.target_repository == github.repository && 'github-token' || 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' }} OPENCODE_CHANGED_FILES_FILE: ${{ runner.temp }}/opencode-changed-files.txt OPENCODE_ARTIFACT_MANIFEST_SHA256: ${{ steps.seal_artifacts.outputs.manifest_sha256 }} OPENCODE_SOURCE_WORKDIR: ${{ runner.temp }}/opencode-pr-head diff --git a/CHANGELOG.md b/CHANGELOG.md index 9ca142f308..4f0680a91d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1023,6 +1023,12 @@ Semantic Versioning where the repository publishes a release. ### Fixed +- Prefer the job-scoped `github.token` when the central OpenCode dispatch + publishes a commit status back to the same `.github` repository. The job's + declared `statuses: write` permission now reaches the endpoint instead of an + unrelated OpenCode App installation token that can lack commit-status write + permission; cross-repository status publication keeps the existing explicit + PAT/App credential chain. - Keep the central required-workflow coverage placeholder from superseding a failed repository-dispatch coverage run; coverage retry and merge decisions now use authoritative execution evidence for the central scheduler. @@ -1046,7 +1052,6 @@ Semantic Versioning where the repository publishes a release. `gpt-5.6-luna` was retired. This prevents every consumer repository's required Strix check from failing on a stale central assertion or selecting a nonexistent direct model. - - Publish only the sanitized cumulative Strix report tree, avoiding a later copy of relative scanner output that could reintroduce known internal warning text into uploaded security evidence. diff --git a/docs/doctoring/opencode-same-repository-status-credential.md b/docs/doctoring/opencode-same-repository-status-credential.md new file mode 100644 index 0000000000..e2b32c01b7 --- /dev/null +++ b/docs/doctoring/opencode-same-repository-status-credential.md @@ -0,0 +1,49 @@ +# OpenCode same-repository status credential + +## Operator outcome + +An OpenCode repository-dispatch run targeting `ContextualWisdomLab/.github` +publishes its optional `opencode-review` commit status with the current job's +`github.token`. Cross-repository targets continue to use the configured PAT or +OpenCode App installation token, because `github.token` is limited to the +repository containing the workflow. + +If status publication fails, inspect the logged token-source label and the +endpoint response. Do not weaken the formal exact-head Reviews API verdict or +branch protection: the commit status is complementary evidence. + +## Root cause and decision + +Run 32560612401 declared `statuses: write` for the OpenCode job but selected the +separate OpenCode App token for a same-repository status write. GitHub rejected +`POST /repos/ContextualWisdomLab/.github/statuses/{sha}` with HTTP 403 because +that installation token did not carry commit-status write permission. + +The smallest repair is credential precedence at the existing publication +boundary. Same-repository publication uses `github.token`, whose effective +permissions are already narrowed by the job. Cross-repository publication +retains the established PAT/App chain and the existing neutral path when only a +repository-scoped workflow token is available. No new credential, permission, +provider, retry, or fallback abstraction is introduced. + +This boundary supports SOC 2 and CSAP evidence expectations by preserving +least privilege, explicit credential provenance, exact-head status binding, +and an auditable failure instead of broadening the OpenCode App installation. + +## Verification + +- The contract test requires both `GH_TOKEN` and its logged source to select + `github-token` first only when the target equals the workflow repository. +- The existing cross-repository notice and fail-closed exact-head review path + remain unchanged. +- The complete Python, shell, compilation, docstring, and branch-coverage gates + remain mandatory before merge. + +## APA 7th references + +GitHub. (n.d.). *GITHUB_TOKEN*. GitHub Docs. Retrieved August 22, 2026, from +https://docs.github.com/en/actions/concepts/security/github_token + +GitHub. (n.d.). *Permissions required for GitHub Apps*. GitHub Docs. Retrieved +August 22, 2026, from +https://docs.github.com/en/rest/authentication/permissions-required-for-github-apps diff --git a/tests/test_opencode_agent_contract.py b/tests/test_opencode_agent_contract.py index 2854e0671f..55513c16c7 100644 --- a/tests/test_opencode_agent_contract.py +++ b/tests/test_opencode_agent_contract.py @@ -2399,11 +2399,17 @@ def test_opencode_runs_merge_scheduler_after_review_without_repo_local_dispatch( " - name: Dispatch Noema after current-head OpenCode approval", 1 )[0] assert ( - "GH_TOKEN: ${{ secrets.PR_REVIEW_MERGE_TOKEN || " + "GH_TOKEN: ${{ needs.validate-pr-metadata.outputs.target_repository == " + "github.repository && github.token || secrets.PR_REVIEW_MERGE_TOKEN || " "secrets.OPENCODE_APPROVE_TOKEN || steps.opencode_app_token.outputs.token || " "github.token }}" ) in status_step - assert "OPENCODE_STATUS_TOKEN_SOURCE" in status_step + assert ( + "OPENCODE_STATUS_TOKEN_SOURCE: ${{ " + "needs.validate-pr-metadata.outputs.target_repository == github.repository && " + "'github-token' || secrets.PR_REVIEW_MERGE_TOKEN != '' && " + "'PR_REVIEW_MERGE_TOKEN'" + ) in status_step assert "steps.opencode_app_token.outputs.available == 'true' && 'opencode-app'" in status_step assert "OPENCODE_CHANGED_FILES_FILE" in status_step assert "OPENCODE_ARTIFACT_MANIFEST_SHA256" in status_step diff --git a/tests/test_pr_review_autofix_nvidia_nim_contract.py b/tests/test_pr_review_autofix_nvidia_nim_contract.py index 9f81076199..d10b2f1e26 100644 --- a/tests/test_pr_review_autofix_nvidia_nim_contract.py +++ b/tests/test_pr_review_autofix_nvidia_nim_contract.py @@ -19,7 +19,7 @@ DOCTORING_RECORD = Path("docs/doctoring/hourly-nvidia-nim-autofix.md") CHANGELOG = Path("CHANGELOG.md") REVIEW_DISPATCH_WORKFLOW = Path(".github/workflows/opencode-review-dispatch.yml") -REVIEW_DISPATCH_BLOB_SHA = "3677f408bd6b99577fd7d5923fbd67c91f437ae0" +REVIEW_DISPATCH_BLOB_SHA = "cc0b84dff19195a7e209e9f78cd5ee80bfc58d53" def _workflow_text(path: Path) -> str: