From d6a726219bb317f12748006acbce050b27715bbc Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 1 Sep 2026 18:09:58 +0900 Subject: [PATCH 1/4] test(automation): require OIDC fallback for organization loop --- ..._commercial_readiness_loop_secret_scope.py | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/tests/test_organization_commercial_readiness_loop_secret_scope.py b/tests/test_organization_commercial_readiness_loop_secret_scope.py index b47c2cadc2..7e3e8e47aa 100644 --- a/tests/test_organization_commercial_readiness_loop_secret_scope.py +++ b/tests/test_organization_commercial_readiness_loop_secret_scope.py @@ -19,3 +19,26 @@ def test_maintainer_token_is_scoped_only_to_the_dispatch_step() -> None: assert "PR_REVIEW_MERGE_TOKEN" not in before_dispatch assert "GH_TOKEN:" not in before_dispatch assert "env:\n GH_TOKEN: ${{ secrets.PR_REVIEW_MERGE_TOKEN }}" in dispatch_step + + +def test_missing_maintainer_secret_uses_bounded_job_oidc_exchange() -> None: + """A protected scheduled pass must not die solely because the PAT is absent.""" + source = WORKFLOW_PATH.read_text(encoding="utf-8") + _, dispatch_step = source.split( + " - name: Coordinate one bounded fleet pass\n", maxsplit=1 + ) + + assert "id-token: write" in source + assert "api.opencode.ai:443" in source + assert "OIDC_AUDIENCE: opencode-github-action" in dispatch_step + assert "OPENCODE_API_BASE_URL: https://api.opencode.ai" in dispatch_step + assert "ACTIONS_ID_TOKEN_REQUEST_TOKEN" in dispatch_step + assert "ACTIONS_ID_TOKEN_REQUEST_URL" in dispatch_step + assert "--connect-timeout 10" in dispatch_step + assert "--max-time 30" in dispatch_step + assert "/exchange_github_app_token" in dispatch_step + assert 'export GH_TOKEN="$app_token"' in dispatch_step + assert "::add-mask::$oidc_token" in dispatch_step + assert "::add-mask::$app_token" in dispatch_step + assert "github.token" not in dispatch_step + assert "GITHUB_TOKEN" not in dispatch_step From 17da9671c27b726a0af855d6640ba38f7729e51a Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 1 Sep 2026 18:11:08 +0900 Subject: [PATCH 2/4] fix(automation): add bounded OIDC maintainer fallback --- ...organization-commercial-readiness-loop.yml | 64 ++++++++++++++++++- 1 file changed, 63 insertions(+), 1 deletion(-) diff --git a/.github/workflows/organization-commercial-readiness-loop.yml b/.github/workflows/organization-commercial-readiness-loop.yml index 521495617a..fbe7efa434 100644 --- a/.github/workflows/organization-commercial-readiness-loop.yml +++ b/.github/workflows/organization-commercial-readiness-loop.yml @@ -18,6 +18,9 @@ jobs: github.ref == format('refs/heads/{0}', github.event.repository.default_branch) runs-on: ubuntu-24.04 timeout-minutes: 25 + permissions: + contents: read + id-token: write env: FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true" ORGANIZATION: ContextualWisdomLab @@ -32,6 +35,7 @@ jobs: egress-policy: block allowed-endpoints: >- api.github.com:443 + api.opencode.ai:443 github.com:443 objects.githubusercontent.com:443 release-assets.githubusercontent.com:443 @@ -53,10 +57,68 @@ jobs: - name: Coordinate one bounded fleet pass env: GH_TOKEN: ${{ secrets.PR_REVIEW_MERGE_TOKEN }} + OIDC_AUDIENCE: opencode-github-action + OPENCODE_API_BASE_URL: https://api.opencode.ai shell: bash --noprofile --norc -e -o pipefail {0} run: | + set -euo pipefail + + exchange_unavailable() { + echo "::error::OpenCode app token exchange unavailable: $1" + exit 1 + } + + if [ -z "${GH_TOKEN:-}" ]; then + if [ -z "${ACTIONS_ID_TOKEN_REQUEST_TOKEN:-}" ] || [ -z "${ACTIONS_ID_TOKEN_REQUEST_URL:-}" ]; then + exchange_unavailable "OIDC request environment is missing." + fi + + request_url="${ACTIONS_ID_TOKEN_REQUEST_URL}" + separator="&" + case "$request_url" in + *\?*) ;; + *) separator="?" ;; + esac + + if ! oidc_response="$( + curl -fsS \ + --connect-timeout 10 \ + --max-time 30 \ + -H "Authorization: Bearer ${ACTIONS_ID_TOKEN_REQUEST_TOKEN}" \ + "${request_url}${separator}audience=${OIDC_AUDIENCE}" + )"; then + exchange_unavailable "OIDC token request did not complete." + fi + if ! oidc_token="$( + jq -er '.value | select(type == "string" and length > 0)' \ + <<<"$oidc_response" 2>/dev/null + )"; then + exchange_unavailable "OIDC token response was malformed or empty." + fi + echo "::add-mask::$oidc_token" + + if ! token_response="$( + curl -fsS \ + --connect-timeout 10 \ + --max-time 30 \ + -X POST \ + -H "Authorization: Bearer ${oidc_token}" \ + "${OPENCODE_API_BASE_URL}/exchange_github_app_token" + )"; then + exchange_unavailable "app token request did not complete." + fi + if ! app_token="$( + jq -er '.token | select(type == "string" and length > 0)' \ + <<<"$token_response" 2>/dev/null + )"; then + exchange_unavailable "app token response was malformed or empty." + fi + echo "::add-mask::$app_token" + export GH_TOKEN="$app_token" + fi + if [ -z "${GH_TOKEN:-}" ]; then - echo "::error::PR_REVIEW_MERGE_TOKEN is required; neither the reviewer credential nor repository-scoped GITHUB_TOKEN is accepted." + echo "::error::PR_REVIEW_MERGE_TOKEN or the job-bound OpenCode App token exchange is required; neither reviewer credentials nor repository-scoped GITHUB_TOKEN are accepted." exit 1 fi echo "::add-mask::$GH_TOKEN" From f319ea8aabc95ee1ad66363d2cadda2590dc7ac2 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 1 Sep 2026 18:12:11 +0900 Subject: [PATCH 3/4] docs(automation): record organization-loop credential RCA --- .../organization-commercial-readiness-loop.md | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/docs/doctoring/organization-commercial-readiness-loop.md b/docs/doctoring/organization-commercial-readiness-loop.md index 76ef1fce5a..bde6539aba 100644 --- a/docs/doctoring/organization-commercial-readiness-loop.md +++ b/docs/doctoring/organization-commercial-readiness-loop.md @@ -10,7 +10,17 @@ The coordinator may dispatch at most one review-repair workflow and one product- A single workflow cannot safely write every repository merely because it runs in the organization `.github` repository. GitHub's default `GITHUB_TOKEN` is scoped to the repository containing the workflow; cross-repository Actions dispatch therefore requires an explicitly provisioned user or GitHub App credential with the required repository and Actions permissions. This control does not make every repository directly writable. It only considers repositories the live API reports as organization-owned, non-fork, enabled, non-archived, default-branch-bearing, and writable by the authenticated installation. -The central job therefore refuses both repository-scoped and reviewer-scoped token fallbacks. It requires the maintainer-scoped `PR_REVIEW_MERGE_TOKEN`; `OPENCODE_APPROVE_TOKEN` remains isolated to the reviewer credential chain and `GITHUB_TOKEN` is not accepted for cross-repository coordination. The maintainer token is exposed only to the final dispatch shell step, not checkout, setup, artifact upload, or other third-party actions. The coordinator itself receives neither `NVIDIA_NIM_API_KEY` nor `COPILOT_GITHUB_TOKEN`. Model credentials remain inside separately reviewed repository-local or central workers. +The central job prefers the maintainer-scoped `PR_REVIEW_MERGE_TOKEN`. If that secret is absent on the protected default-branch scheduled run, the job may use its job-bound GitHub OIDC identity to request the existing short-lived OpenCode GitHub App installation token. The fallback is limited to `id-token: write` on the coordinator job, the exact `api.opencode.ai` endpoint, bounded network timeouts, strict non-empty JSON token fields, and token masking. `OPENCODE_APPROVE_TOKEN`, repository-scoped `GITHUB_TOKEN`, reviewer credentials, model-provider keys, and `COPILOT_GITHUB_TOKEN` are not accepted as coordinator fallbacks. The selected maintainer credential is exposed only to the final dispatch shell step, not checkout, setup, artifact upload, or other third-party actions. Model credentials remain inside separately reviewed repository-local or central workers. + +## 2026-09-01 protected-main credential failure RCA + +Scheduled protected-main run `33483275421` checked out exact central source `5686de41660d51a7a7f22b8840dfa6ccfe5ff3f1` and failed before the coordinator process started. The `Coordinate one bounded fleet pass` step showed an empty `GH_TOKEN` and exited on the PAT-only guard with `PR_REVIEW_MERGE_TOKEN is required`. This is missing configuration/credential availability, not a downstream repository defect, model/provider outage, network failure, or a substantive test/security finding. Because the coordinator never started, the JSON fleet receipt was not created and the subsequent `if: always()` artifact upload failed independently with `No files were found`. + +Protected `main` still carried the same PAT-only source after that run. The smallest repair keeps `PR_REVIEW_MERGE_TOKEN` as the first choice and, only when it is absent, exchanges the protected scheduled job's GitHub OIDC identity for the already established OpenCode GitHub App installation token. The exchange follows the existing central worker trust pattern: `api.opencode.ai:443` is the only added blocked-egress endpoint; both HTTP calls use 10-second connect and 30-second total timeouts; malformed or empty responses fail closed; both temporary tokens are masked; and neither the repository token nor reviewer/model credentials become mutation authority. + +The broader DDD automation branch in PR #1545 independently carried the same credential-recovery design, but coupled it to unrelated architecture-contract work and was not mergeable on the current protected base during this incident. The focused current-main repair deliberately extracts only the credential boundary so recovery of the production schedule is not coupled to that larger feature. PR #1545 may later absorb the integrated fallback when it reconciles with protected main. + +A pull-request quality run proves the static workflow contract and full coordinator test suite. It cannot prove a real protected-default-branch OIDC exchange because pull-request code must not receive a production job-bound mutation identity. Operational acceptance therefore requires a post-integration scheduled run on protected `main` whose exact source contains the fallback, reaches the coordinator rather than the missing-PAT guard, emits its deterministic JSON receipt, and preserves all downstream fail-closed governance. ## Dynamic repository-writer lease From 46261f201ba5d1dde7c40023dc6bd7097240abb9 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 1 Sep 2026 18:13:11 +0900 Subject: [PATCH 4/4] test(automation): distinguish diagnostics from token binding --- ...est_organization_commercial_readiness_loop_secret_scope.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_organization_commercial_readiness_loop_secret_scope.py b/tests/test_organization_commercial_readiness_loop_secret_scope.py index 7e3e8e47aa..aa50efe00c 100644 --- a/tests/test_organization_commercial_readiness_loop_secret_scope.py +++ b/tests/test_organization_commercial_readiness_loop_secret_scope.py @@ -40,5 +40,5 @@ def test_missing_maintainer_secret_uses_bounded_job_oidc_exchange() -> None: assert 'export GH_TOKEN="$app_token"' in dispatch_step assert "::add-mask::$oidc_token" in dispatch_step assert "::add-mask::$app_token" in dispatch_step - assert "github.token" not in dispatch_step - assert "GITHUB_TOKEN" not in dispatch_step + assert "${{ github.token }}" not in dispatch_step + assert "GITHUB_TOKEN:" not in dispatch_step