From 6a3b2f0bd18311c918fb812f5ab5209897e160ca Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 4 Aug 2026 21:23:07 +0900 Subject: [PATCH 01/30] test(ci): require hourly NVIDIA NIM review autofix --- ...t_pr_review_autofix_nvidia_nim_contract.py | 67 +++++++++++++++++++ 1 file changed, 67 insertions(+) create mode 100644 tests/test_pr_review_autofix_nvidia_nim_contract.py diff --git a/tests/test_pr_review_autofix_nvidia_nim_contract.py b/tests/test_pr_review_autofix_nvidia_nim_contract.py new file mode 100644 index 000000000..87b62d47c --- /dev/null +++ b/tests/test_pr_review_autofix_nvidia_nim_contract.py @@ -0,0 +1,67 @@ +"""Contract tests for the scheduled OpenCode review-autofix model boundary.""" + +from pathlib import Path + + +AUTOFIX_WORKFLOW = Path(".github/workflows/pr-review-autofix.yml") +FIX_SCHEDULER_WORKFLOW = Path(".github/workflows/pr-review-fix-scheduler.yml") + + +def _workflow_text(path: Path) -> str: + """Read one central workflow as UTF-8 text for static trust-boundary checks.""" + + return path.read_text(encoding="utf-8") + + +def test_review_fix_scheduler_runs_once_each_hour() -> None: + """Keep the actionable-review repair loop on the approved hourly cadence.""" + + scheduler = _workflow_text(FIX_SCHEDULER_WORKFLOW) + + assert 'cron: "23 * * * *"' in scheduler + assert 'cron: "23 */2 * * *"' not in scheduler + + +def test_scheduled_autofix_uses_only_nvidia_nim() -> None: + """Require the write-capable OpenCode autofix agent to use NVIDIA NIM only.""" + + workflow = _workflow_text(AUTOFIX_WORKFLOW) + + required_fragments = ( + '"model": "nvidia-nim/mistralai/mistral-nemotron"', + '"small_model": "nvidia-nim/nvidia/nemotron-3-nano-30b-a3b"', + '"enabled_providers": ["nvidia-nim"]', + '"nvidia-nim": {', + '"npm": "@ai-sdk/openai-compatible"', + '"baseURL": "https://integrate.api.nvidia.com/v1"', + '"apiKey": "{env:NVIDIA_API_KEY}"', + 'NVIDIA_API_KEY: ${{ secrets.NVIDIA_NIM_API_KEY }}', + 'MODEL: nvidia-nim/mistralai/mistral-nemotron', + ) + for fragment in required_fragments: + assert fragment in workflow, fragment + + forbidden_fragments = ( + 'STRIX_GITHUB_MODELS_TOKEN:', + 'MODEL: github-models/', + 'USE_GITHUB_TOKEN:', + '"enabled_providers": ["github-models"]', + '"apiKey": "{env:STRIX_GITHUB_MODELS_TOKEN}"', + '"baseURL": "https://models.github.ai/inference"', + ) + for fragment in forbidden_fragments: + assert fragment not in workflow, fragment + + +def test_nvidia_nim_secret_is_scoped_to_the_agent_execution_step() -> None: + """Prevent the NVIDIA model credential from leaking into setup or mutation steps.""" + + workflow = _workflow_text(AUTOFIX_WORKFLOW) + binding = 'NVIDIA_API_KEY: ${{ secrets.NVIDIA_NIM_API_KEY }}' + + assert workflow.count(binding) == 1 + run_step = workflow.index(" - name: Run OpenCode review autofix") + next_step = workflow.index(" - name: Validate changed files", run_step) + assert binding in workflow[run_step:next_step] + assert binding not in workflow[:run_step] + assert binding not in workflow[next_step:] From 0cbbed06ebb9ef63430bf63b8fa02a234a9f5956 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 4 Aug 2026 21:26:10 +0900 Subject: [PATCH 02/30] test(ci): cover both NVIDIA NIM autofix executions --- ...t_pr_review_autofix_nvidia_nim_contract.py | 20 +++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/tests/test_pr_review_autofix_nvidia_nim_contract.py b/tests/test_pr_review_autofix_nvidia_nim_contract.py index 87b62d47c..bcb996f53 100644 --- a/tests/test_pr_review_autofix_nvidia_nim_contract.py +++ b/tests/test_pr_review_autofix_nvidia_nim_contract.py @@ -53,15 +53,19 @@ def test_scheduled_autofix_uses_only_nvidia_nim() -> None: assert fragment not in workflow, fragment -def test_nvidia_nim_secret_is_scoped_to_the_agent_execution_step() -> None: - """Prevent the NVIDIA model credential from leaking into setup or mutation steps.""" +def test_nvidia_nim_secret_is_scoped_to_agent_execution_steps() -> None: + """Prevent the NVIDIA credential from leaking beyond the two OpenCode runs.""" workflow = _workflow_text(AUTOFIX_WORKFLOW) binding = 'NVIDIA_API_KEY: ${{ secrets.NVIDIA_NIM_API_KEY }}' + ordinary_start = workflow.index(" - name: Run OpenCode review autofix") + ordinary_end = workflow.index(" - name: Validate changed files", ordinary_start) + conflict_start = workflow.index( + " - name: Merge base branch and resolve conflicts with OpenCode" + ) - assert workflow.count(binding) == 1 - run_step = workflow.index(" - name: Run OpenCode review autofix") - next_step = workflow.index(" - name: Validate changed files", run_step) - assert binding in workflow[run_step:next_step] - assert binding not in workflow[:run_step] - assert binding not in workflow[next_step:] + assert workflow.count(binding) == 2 + assert binding in workflow[ordinary_start:ordinary_end] + assert binding in workflow[conflict_start:] + assert binding not in workflow[:ordinary_start] + assert binding not in workflow[ordinary_end:conflict_start] From 3b94c9b437ec87125d5929f7fe58dfc5abe1f7e5 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 4 Aug 2026 21:29:04 +0900 Subject: [PATCH 03/30] chore(ci): add deterministic NVIDIA NIM autofix patcher --- .github/scripts/patch_pr_752.py | 250 ++++++++++++++++++++++++++++++++ 1 file changed, 250 insertions(+) create mode 100644 .github/scripts/patch_pr_752.py diff --git a/.github/scripts/patch_pr_752.py b/.github/scripts/patch_pr_752.py new file mode 100644 index 000000000..cff8bb4bd --- /dev/null +++ b/.github/scripts/patch_pr_752.py @@ -0,0 +1,250 @@ +"""Apply the bounded NVIDIA NIM migration for the central PR autofix workflow.""" + +from pathlib import Path + + +WORKFLOW_PATH = Path(".github/workflows/pr-review-autofix.yml") +DOCTORING_PATH = Path("docs/doctoring/hourly-nvidia-nim-autofix.md") + + +def replace_once(text: str, old: str, new: str, label: str) -> str: + """Replace one exact reviewed block and fail closed on source drift.""" + + count = text.count(old) + if count != 1: + raise SystemExit(f"{label}: expected one exact block, found {count}") + return text.replace(old, new, 1) + + +def apply() -> None: + """Migrate only model authentication/configuration and write its design record.""" + + workflow = WORKFLOW_PATH.read_text(encoding="utf-8") + old_provider = ''' "model": "github-models/openai/gpt-5", + "small_model": "github-models/deepseek/deepseek-v3-0324", + "enabled_providers": ["github-models"], + "permission": { + "edit": "allow", + "bash": "deny", + "read": "allow", + "grep": "allow", + "glob": "allow", + "list": "allow", + "task": "deny", + "webfetch": "deny", + "websearch": "deny", + "lsp": "deny", + "external_directory": "deny" + }, + "agent": { + "ci-autofix": { + "description": "Conservative CI pull request review autofix agent", + "mode": "primary", + "prompt": "{file:./autofix-prompt.md}", + "steps": 12, + "permission": { + "edit": "allow", + "bash": "deny", + "read": "allow", + "grep": "allow", + "glob": "allow", + "list": "allow", + "task": "deny", + "webfetch": "deny", + "websearch": "deny", + "lsp": "deny", + "external_directory": "deny" + } + } + }, + "provider": { + "github-models": { + "npm": "@ai-sdk/openai-compatible", + "name": "GitHub Models", + "options": { + "baseURL": "https://models.github.ai/inference", + "apiKey": "{env:STRIX_GITHUB_MODELS_TOKEN}" + }, + "models": { + "openai/gpt-5": { + "name": "OpenAI GPT-5", + "tool_call": true, + "reasoning": true, + "options": { + "reasoningEffort": "high" + }, + "variants": { + "high": { + "reasoningEffort": "high" + } + }, + "limit": { + "context": 200000, + "output": 100000 + } + }, + "deepseek/deepseek-v3-0324": { + "name": "DeepSeek V3 0324", + "tool_call": true, + "limit": { + "context": 128000, + "output": 4096 + } + } + } + } + }''' + new_provider = ''' "model": "nvidia-nim/mistralai/mistral-nemotron", + "small_model": "nvidia-nim/nvidia/nemotron-3-nano-30b-a3b", + "enabled_providers": ["nvidia-nim"], + "permission": { + "edit": "allow", + "bash": "deny", + "read": "allow", + "grep": "allow", + "glob": "allow", + "list": "allow", + "task": "deny", + "webfetch": "deny", + "websearch": "deny", + "lsp": "deny", + "external_directory": "deny" + }, + "agent": { + "ci-autofix": { + "description": "Conservative CI pull request review autofix agent", + "mode": "primary", + "prompt": "{file:./autofix-prompt.md}", + "steps": 12, + "permission": { + "edit": "allow", + "bash": "deny", + "read": "allow", + "grep": "allow", + "glob": "allow", + "list": "allow", + "task": "deny", + "webfetch": "deny", + "websearch": "deny", + "lsp": "deny", + "external_directory": "deny" + } + } + }, + "provider": { + "nvidia-nim": { + "npm": "@ai-sdk/openai-compatible", + "name": "NVIDIA NIM", + "options": { + "baseURL": "https://integrate.api.nvidia.com/v1", + "apiKey": "{env:NVIDIA_API_KEY}" + }, + "models": { + "mistralai/mistral-nemotron": { + "name": "Mistral Nemotron", + "tool_call": true, + "limit": { + "context": 131072, + "output": 4096 + } + }, + "nvidia/nemotron-3-nano-30b-a3b": { + "name": "Nemotron 3 Nano 30B A3B", + "tool_call": true, + "limit": { + "context": 262144, + "output": 16384 + } + } + } + } + }''' + workflow = replace_once( + workflow, old_provider, new_provider, "OpenCode provider configuration" + ) + + old_ordinary_env = ''' env: + STRIX_GITHUB_MODELS_TOKEN: ${{ secrets.STRIX_GITHUB_MODELS_TOKEN || github.token }} + GITHUB_TOKEN: ${{ secrets.PR_REVIEW_MERGE_TOKEN || secrets.OPENCODE_APPROVE_TOKEN || steps.target_app_token.outputs.token || github.token }} + MODEL: github-models/openai/gpt-5 + USE_GITHUB_TOKEN: "true" + SHARE: "false"''' + new_ordinary_env = ''' env: + NVIDIA_API_KEY: ${{ secrets.NVIDIA_NIM_API_KEY }} + GITHUB_TOKEN: ${{ secrets.PR_REVIEW_MERGE_TOKEN || secrets.OPENCODE_APPROVE_TOKEN || steps.target_app_token.outputs.token || github.token }} + MODEL: nvidia-nim/mistralai/mistral-nemotron + SHARE: "false"''' + workflow = replace_once( + workflow, + old_ordinary_env, + new_ordinary_env, + "ordinary OpenCode execution environment", + ) + + old_conflict_env = ''' env: + STRIX_GITHUB_MODELS_TOKEN: ${{ secrets.STRIX_GITHUB_MODELS_TOKEN || github.token }} + GITHUB_TOKEN: ${{ secrets.PR_REVIEW_MERGE_TOKEN || secrets.OPENCODE_APPROVE_TOKEN || steps.target_app_token.outputs.token || github.token }} + GH_TOKEN: ${{ secrets.PR_REVIEW_MERGE_TOKEN || secrets.OPENCODE_APPROVE_TOKEN || steps.target_app_token.outputs.token || github.token }} + MODEL: github-models/openai/gpt-5 + USE_GITHUB_TOKEN: "true" + SHARE: "false"''' + new_conflict_env = ''' env: + NVIDIA_API_KEY: ${{ secrets.NVIDIA_NIM_API_KEY }} + GITHUB_TOKEN: ${{ secrets.PR_REVIEW_MERGE_TOKEN || secrets.OPENCODE_APPROVE_TOKEN || steps.target_app_token.outputs.token || github.token }} + GH_TOKEN: ${{ secrets.PR_REVIEW_MERGE_TOKEN || secrets.OPENCODE_APPROVE_TOKEN || steps.target_app_token.outputs.token || github.token }} + MODEL: nvidia-nim/mistralai/mistral-nemotron + SHARE: "false"''' + workflow = replace_once( + workflow, + old_conflict_env, + new_conflict_env, + "conflict-resolution OpenCode execution environment", + ) + WORKFLOW_PATH.write_text(workflow, encoding="utf-8") + + doctoring = '''# Hourly NVIDIA NIM review-autofix boundary + +## Decision + +The central `PR Review Fix Scheduler` dispatches at minute 23 of every hour and retains its one-hour same-head retry boundary. The dispatched write-capable OpenCode autofix workflow uses only the NVIDIA NIM OpenAI-compatible provider. Its primary model is `mistralai/mistral-nemotron`; its small model is `nvidia/nemotron-3-nano-30b-a3b`. + +This change is deliberately isolated from `opencode-review-dispatch.yml`. The existing read-only review agent keeps its own model pool, secret scoping, approval credentials, and repository policy. The autofix agent receives `secrets.NVIDIA_NIM_API_KEY` as `NVIDIA_API_KEY` only in the two steps that execute OpenCode: ordinary review repair and merge-conflict resolution. GitHub mutation credentials remain separate and continue to authorize only repository reads or writes. + +## Product and MSA boundary + +The scheduler, feedback collector, model execution, validation, and GitHub mutation stages remain independently replaceable central services. Target repositories consume the automation through repository-dispatch metadata and do not need to embed provider credentials or model configuration. The agent retains its conservative file allowlist, denied shell/tool permissions, exact-head checks, and fail-closed push guard. + +## Verification contract + +Static tests require the hourly cron inherited from the central baseline, the single `nvidia-nim` provider, the official NVIDIA API base URL, environment-only credential resolution, exact model IDs, and secret visibility limited to the two OpenCode execution steps. They reject GitHub Models provider configuration, GitHub Models model authentication, and `USE_GITHUB_TOKEN` fallback in the write-capable autofix workflow. + +The selected primary model is documented by NVIDIA as suitable for agentic workflows, coding, instruction following, and function calling. The small model is documented as supporting coding, reasoning, instruction following, and tool calling. These catalog claims inform provider selection; they are not treated as evidence that any individual repair is correct. Repository tests, security checks, independent review, and branch protection remain authoritative. + +## Standards alignment + +The design supports NIST SSDF practices for protecting development environments and verifying software artifacts by narrowing credential exposure, separating model authentication from mutation authorization, and retaining independent verification before merge. It also follows the SLSA 1.2 source-track direction by preserving review and source-management controls. No formal NIST or SLSA conformance claim is made. + +## References + +Anomaly Co. (n.d.). *Providers*. OpenCode. Retrieved August 4, 2026, from https://opencode.ai/docs/providers/ + +Booth, H., Ogata, M., Kent, K., Souppaya, M., & Dodson, D. (2025). *Secure software development framework (SSDF) version 1.2: Recommendations for mitigating the risk of software vulnerabilities* (Initial Public Draft NIST SP 800-218 Rev. 1). National Institute of Standards and Technology. https://csrc.nist.gov/pubs/sp/800/218/r1/ipd + +NVIDIA Corporation. (n.d.). *API reference for NVIDIA NIM for large language models*. Retrieved August 4, 2026, from https://docs.nvidia.com/nim/large-language-models/latest/api-reference.html + +NVIDIA Corporation. (2025). *Mistral-Nemotron* [Model card]. https://build.nvidia.com/mistralai/mistral-nemotron + +NVIDIA Corporation. (2026). *Nemotron-3-Nano-30B-A3B* [Model catalog]. https://build.nvidia.com/nvidia/nemotron-3-nano-30b-a3b + +SLSA Community. (2025, November 24). *Announcing SLSA v1.2*. https://slsa.dev/blog/2025/11/announce-slsa-v1.2 + +Souppaya, M., Scarfone, K., & Dodson, D. (2022). *Secure software development framework (SSDF) version 1.1: Recommendations for mitigating the risk of software vulnerabilities* (NIST SP 800-218). National Institute of Standards and Technology. https://doi.org/10.6028/NIST.SP.800-218 +''' + DOCTORING_PATH.parent.mkdir(parents=True, exist_ok=True) + if DOCTORING_PATH.exists() and DOCTORING_PATH.read_text(encoding="utf-8") != doctoring: + raise SystemExit(f"{DOCTORING_PATH}: existing content does not match") + DOCTORING_PATH.write_text(doctoring, encoding="utf-8") + + +if __name__ == "__main__": + apply() From a34e28c7bfcd0c7d4a25fbd73c48cc7279446a3f Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 4 Aug 2026 21:29:35 +0900 Subject: [PATCH 04/30] ci: validate and apply NVIDIA NIM autofix migration --- .github/workflows/patch-pr-752-nim.yml | 125 +++++++++++++++++++++++++ 1 file changed, 125 insertions(+) create mode 100644 .github/workflows/patch-pr-752-nim.yml diff --git a/.github/workflows/patch-pr-752-nim.yml b/.github/workflows/patch-pr-752-nim.yml new file mode 100644 index 000000000..6610fe774 --- /dev/null +++ b/.github/workflows/patch-pr-752-nim.yml @@ -0,0 +1,125 @@ +name: Apply validated PR 752 NVIDIA NIM autofix migration + +on: + push: + branches: + - fix/hourly-nvidia-nim-autofix + paths: + - .github/workflows/patch-pr-752-nim.yml + +permissions: + contents: read + +concurrency: + group: patch-pr-752-nim-${{ github.ref }} + cancel-in-progress: false + +jobs: + validate: + if: github.actor != 'github-actions[bot]' + runs-on: ubuntu-latest + timeout-minutes: 15 + outputs: + result_digest: ${{ steps.digest.outputs.value }} + steps: + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + ref: ${{ github.sha }} + persist-credentials: false + + - name: Verify immutable patch source + run: echo 'ef6cae492b84b715856f0f6b1038bb07f7d069d994bca83991dbfe7a4f884ed8 .github/scripts/patch_pr_752.py' | sha256sum --check --strict + + - name: Prove contract is red before production migration + shell: bash + run: | + set +e + python -m pytest -q tests/test_pr_review_autofix_nvidia_nim_contract.py \ + > "$RUNNER_TEMP/nim-red.log" 2>&1 + status=$? + set -e + cat "$RUNNER_TEMP/nim-red.log" + test "$status" -ne 0 + grep -F 'nvidia-nim/mistralai/mistral-nemotron' "$RUNNER_TEMP/nim-red.log" + + - name: Apply bounded provider migration + run: python .github/scripts/patch_pr_752.py + + - name: Verify focused contract and Python syntax + run: | + python -m pytest -q tests/test_pr_review_autofix_nvidia_nim_contract.py + python -m py_compile \ + .github/scripts/patch_pr_752.py \ + tests/test_pr_review_autofix_nvidia_nim_contract.py + git diff --check + + - name: Bind exact validated result + id: digest + shell: bash + run: | + set -euo pipefail + value="$({ + printf '%s\0' '.github/workflows/pr-review-autofix.yml' + cat .github/workflows/pr-review-autofix.yml + printf '%s\0' 'docs/doctoring/hourly-nvidia-nim-autofix.md' + cat docs/doctoring/hourly-nvidia-nim-autofix.md + } | sha256sum | cut -d' ' -f1)" + test -n "$value" + echo "value=$value" >> "$GITHUB_OUTPUT" + + publish: + needs: validate + if: needs.validate.result == 'success' + runs-on: ubuntu-latest + timeout-minutes: 10 + permissions: + contents: write + steps: + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + ref: ${{ github.sha }} + fetch-depth: 0 + persist-credentials: false + + - name: Reproduce exact validated result without executing target repository code + env: + EXPECTED_DIGEST: ${{ needs.validate.outputs.result_digest }} + shell: bash + run: | + set -euo pipefail + echo 'ef6cae492b84b715856f0f6b1038bb07f7d069d994bca83991dbfe7a4f884ed8 .github/scripts/patch_pr_752.py' | sha256sum --check --strict + python .github/scripts/patch_pr_752.py + actual="$({ + printf '%s\0' '.github/workflows/pr-review-autofix.yml' + cat .github/workflows/pr-review-autofix.yml + printf '%s\0' 'docs/doctoring/hourly-nvidia-nim-autofix.md' + cat docs/doctoring/hourly-nvidia-nim-autofix.md + } | sha256sum | cut -d' ' -f1)" + test -n "$EXPECTED_DIGEST" + test "$actual" = "$EXPECTED_DIGEST" + git diff --check + + - name: Commit validated result and remove temporary patch machinery + env: + GH_TOKEN: ${{ github.token }} + SOURCE_HEAD: ${{ github.sha }} + shell: bash + run: | + set -euo pipefail + rm \ + .github/scripts/patch_pr_752.py \ + .github/workflows/patch-pr-752-nim.yml + git config user.name github-actions[bot] + git config user.email 41898282+github-actions[bot]@users.noreply.github.com + git add \ + .github/workflows/pr-review-autofix.yml \ + docs/doctoring/hourly-nvidia-nim-autofix.md \ + .github/scripts/patch_pr_752.py \ + .github/workflows/patch-pr-752-nim.yml + git diff --cached --check + git commit -m 'fix(ci): use NVIDIA NIM for scheduled OpenCode autofix' + remote_url="https://x-access-token:${GH_TOKEN}@github.com/${GITHUB_REPOSITORY}.git" + git push \ + --force-with-lease="refs/heads/fix/hourly-nvidia-nim-autofix:${SOURCE_HEAD}" \ + "$remote_url" \ + HEAD:refs/heads/fix/hourly-nvidia-nim-autofix From 42967a4856e823ac21464005a4455944b783ea00 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 4 Aug 2026 21:33:01 +0900 Subject: [PATCH 05/30] docs(doctoring): define NVIDIA NIM autofix boundary --- docs/doctoring/hourly-nvidia-nim-autofix.md | 74 +++++++++++++++++++++ 1 file changed, 74 insertions(+) create mode 100644 docs/doctoring/hourly-nvidia-nim-autofix.md diff --git a/docs/doctoring/hourly-nvidia-nim-autofix.md b/docs/doctoring/hourly-nvidia-nim-autofix.md new file mode 100644 index 000000000..8c568d62e --- /dev/null +++ b/docs/doctoring/hourly-nvidia-nim-autofix.md @@ -0,0 +1,74 @@ +# Hourly NVIDIA NIM Review-Autofix Boundary + +## Decision + +The write-capable scheduled pull-request autofix agent uses OpenCode with the NVIDIA NIM API and the organization Actions secret `NVIDIA_NIM_API_KEY`. The independent read-only review agent remains unchanged and continues to use its existing credential and model-pool contract. + +This separation is intentional. Review and repair have different privileges: the review path publishes a verdict, while the autofix path may modify and push a same-repository pull-request branch. Sharing or silently replacing the review credential would couple two independent controls and weaken incident containment. + +## Central MSA ownership + +`ContextualWisdomLab/.github` owns the scheduler, dispatch authorization, model-provider configuration, credential binding, and fail-closed repair contract. Leaf repositories receive the behavior through the central reusable workflow and do not copy provider credentials or scheduler implementation. + +The central scheduler established by the baseline repair runs once per hour, dispatches at most one repair per invocation, and binds privileged implementation to the immutable called-workflow source. The NVIDIA migration changes only the model transport used by the write-capable autofix worker. + +## Provider contract + +The pinned OpenCode runtime is configured with one enabled provider, `nvidia-nim`, using the OpenAI-compatible adapter and the NVIDIA hosted endpoint: + +```text +https://integrate.api.nvidia.com/v1 +``` + +The primary repair model is `mistralai/mistral-nemotron`; the small model used for bounded helper work is `nvidia/nemotron-3-nano-30b-a3b`. NVIDIA documents both model identifiers and the OpenAI-compatible `/v1/chat/completions` endpoint. Mistral-Nemotron is selected for agentic coding and tool-calling capability; Nemotron 3 Nano is selected as a lower-active-parameter helper model rather than as a fallback provider. + +Only the `nvidia-nim` provider is enabled. GitHub Models configuration, model identifiers, base URLs, and model-auth fallbacks are absent from the scheduled autofix execution path. + +## Credential boundary + +The organization secret is bound as: + +```yaml +NVIDIA_API_KEY: ${{ secrets.NVIDIA_NIM_API_KEY }} +``` + +It is present only on the two steps that execute OpenCode: ordinary review-feedback repair and merge-conflict repair. Earlier metadata collection, checkout, context preparation, validation, commit, and push steps do not receive the NVIDIA credential. + +The workflow passes the key through an environment variable and OpenCode substitutes `{env:NVIDIA_API_KEY}` into the provider configuration. The key is never written to repository files, command arguments, generated prompts, or logs. A missing secret is a fatal configuration error; the workflow does not fall back to `GITHUB_TOKEN`, the GitHub Models token, or another provider. + +GitHub notes that a missing secret expression resolves to an empty string and recommends environment-variable delivery rather than command-line delivery. The explicit preflight therefore prevents an ambiguous unauthenticated provider request and preserves fail-closed behavior. + +## Repair sandbox and write boundary + +The model transport change does not expand agent permissions. OpenCode continues to deny shell, task, web-fetch, web-search, language-server, and external-directory access. It may read, search, list, and edit only the validated same-repository pull-request worktree and only paths authorized by current actionable review context. The workflow validates the live base/head metadata before execution, validates changed files afterward, and refuses to push if the head moved. + +GitHub repository credentials and the NVIDIA model credential remain separate. The existing short-lived GitHub App/OIDC exchange and branch-write token chain are not used for model authentication. Conversely, `NVIDIA_NIM_API_KEY` is not used for GitHub reads or writes. + +## Verification contract + +Automated tests must prove all of the following: + +1. The repair scheduler retains the approved hourly cron expression. +2. The OpenCode configuration enables only `nvidia-nim`. +3. Primary and small model identifiers match NVIDIA's published identifiers. +4. The provider uses the OpenAI-compatible package, NVIDIA base URL, and environment substitution. +5. Exactly two OpenCode execution steps receive `NVIDIA_API_KEY` from `secrets.NVIDIA_NIM_API_KEY`. +6. GitHub Models credentials, providers, model identifiers, base URLs, and `USE_GITHUB_TOKEN` model-auth fallback are absent from the autofix workflow. +7. The read-only review workflow is unchanged by this migration. +8. The exact current head passes the repository's complete test, statement/branch coverage, docstring, workflow, security, OpenCode, Noema, and branch-protection gates. + +## Rollback + +Rollback is a normal revert of the NVIDIA transport commit. A rollback must not reintroduce an implicit GitHub-token model-auth fallback or modify the independent review-agent credential system. If NVIDIA NIM is unavailable, scheduled autofix must fail closed while review, checks, and manual maintenance remain available. + +## References + +GitHub, Inc. (n.d.). *Secrets reference*. GitHub Docs. Retrieved August 4, 2026, from https://docs.github.com/en/actions/reference/security/secrets + +NVIDIA Corporation. (n.d.-a). *LLM APIs*. NVIDIA API Catalog. Retrieved August 4, 2026, from https://docs.api.nvidia.com/nim/reference/llm-apis + +NVIDIA Corporation. (n.d.-b). *Mistralai / mistral-nemotron*. NVIDIA API Catalog. Retrieved August 4, 2026, from https://docs.api.nvidia.com/nim/reference/mistralai-mistral-nemotron + +NVIDIA Corporation. (n.d.-c). *NVIDIA / nemotron-3-nano-30b-a3b*. NVIDIA API Catalog. Retrieved August 4, 2026, from https://docs.api.nvidia.com/nim/re/reference/nvidia-nemotron-3-nano-30b-a3b + +OpenCode. (2026, July 28). *Providers*. https://opencode.ai/docs/providers From c9bf3fdb185b9712e2abe2ed8efe0343c531de68 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 4 Aug 2026 21:34:20 +0900 Subject: [PATCH 06/30] ci: verify and apply NVIDIA NIM autofix migration --- .../one-shot-apply-nvidia-nim-autofix.yml | 201 ++++++++++++++++++ 1 file changed, 201 insertions(+) create mode 100644 .github/workflows/one-shot-apply-nvidia-nim-autofix.yml diff --git a/.github/workflows/one-shot-apply-nvidia-nim-autofix.yml b/.github/workflows/one-shot-apply-nvidia-nim-autofix.yml new file mode 100644 index 000000000..3dedb7b48 --- /dev/null +++ b/.github/workflows/one-shot-apply-nvidia-nim-autofix.yml @@ -0,0 +1,201 @@ +name: One-shot apply NVIDIA NIM autofix + +on: + push: + branches: [fix/hourly-nvidia-nim-autofix] + +concurrency: + group: one-shot-apply-nvidia-nim-autofix-${{ github.ref }} + cancel-in-progress: true + +permissions: + contents: write + +jobs: + apply-and-verify: + if: github.repository == 'ContextualWisdomLab/.github' + runs-on: ubuntu-24.04 + env: + FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true + steps: + - name: Checkout exact branch head + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + ref: fix/hourly-nvidia-nim-autofix + fetch-depth: 0 + + - name: Set up Python + uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.2.0 + with: + python-version: '3.14' + + - name: Apply the bounded provider migration + run: | + set -euo pipefail + python3 <<'PY' + from pathlib import Path + + path = Path('.github/workflows/pr-review-autofix.yml') + text = path.read_text(encoding='utf-8') + + replacements = { + '"model": "github-models/openai/gpt-5"': + '"model": "nvidia-nim/mistralai/mistral-nemotron"', + '"small_model": "github-models/deepseek/deepseek-v3-0324"': + '"small_model": "nvidia-nim/nvidia/nemotron-3-nano-30b-a3b"', + '"enabled_providers": ["github-models"]': + '"enabled_providers": ["nvidia-nim"]', + ' STRIX_GITHUB_MODELS_TOKEN: ${{ secrets.STRIX_GITHUB_MODELS_TOKEN || github.token }}\n': + ' NVIDIA_API_KEY: ${{ secrets.NVIDIA_NIM_API_KEY }}\n', + ' MODEL: github-models/openai/gpt-5\n': + ' MODEL: nvidia-nim/mistralai/mistral-nemotron\n', + ' USE_GITHUB_TOKEN: "true"\n': '', + } + expected_counts = { + '"model": "github-models/openai/gpt-5"': 1, + '"small_model": "github-models/deepseek/deepseek-v3-0324"': 1, + '"enabled_providers": ["github-models"]': 1, + ' STRIX_GITHUB_MODELS_TOKEN: ${{ secrets.STRIX_GITHUB_MODELS_TOKEN || github.token }}\n': 2, + ' MODEL: github-models/openai/gpt-5\n': 2, + ' USE_GITHUB_TOKEN: "true"\n': 2, + } + for old, expected_count in expected_counts.items(): + actual_count = text.count(old) + if actual_count != expected_count: + raise SystemExit( + f'expected {expected_count} occurrences of {old!r}, found {actual_count}' + ) + text = text.replace(old, replacements[old]) + + provider_start_marker = ( + ' "provider": {\n' + ' "github-models": {' + ) + provider_start = text.index(provider_start_marker) + root_close_marker = ( + '\n }\' >"${OPENCODE_AUTOFIX_WORKDIR}/opencode.jsonc"' + ) + provider_end = text.index(root_close_marker, provider_start) + new_provider = ''' "provider": { + "nvidia-nim": { + "npm": "@ai-sdk/openai-compatible", + "name": "NVIDIA NIM", + "options": { + "baseURL": "https://integrate.api.nvidia.com/v1", + "apiKey": "{env:NVIDIA_API_KEY}" + }, + "models": { + "mistralai/mistral-nemotron": { + "name": "Mistral Nemotron", + "tool_call": true, + "limit": { + "context": 128000, + "output": 4096 + } + }, + "nvidia/nemotron-3-nano-30b-a3b": { + "name": "Nemotron 3 Nano 30B A3B", + "tool_call": true, + "reasoning": true, + "limit": { + "context": 128000, + "output": 32768 + } + } + } + } + }''' + text = text[:provider_start] + new_provider + text[provider_end:] + + guard = ''' set -euo pipefail + if [ -z "${NVIDIA_API_KEY:-}" ]; then + echo "::error::NVIDIA_NIM_API_KEY is required for scheduled OpenCode autofix." + exit 1 + fi +''' + + ordinary_start = text.index(' - name: Run OpenCode review autofix\n') + ordinary_end = text.index(' - name: Validate changed files\n', ordinary_start) + ordinary = text[ordinary_start:ordinary_end] + if ordinary.count(' set -euo pipefail\n') != 1: + raise SystemExit('ordinary OpenCode step did not contain exactly one shell preamble') + ordinary = ordinary.replace(' set -euo pipefail\n', guard, 1) + text = text[:ordinary_start] + ordinary + text[ordinary_end:] + + conflict_start = text.index( + ' - name: Merge base branch and resolve conflicts with OpenCode\n' + ) + conflict = text[conflict_start:] + if conflict.count(' set -euo pipefail\n') != 1: + raise SystemExit('conflict OpenCode step did not contain exactly one shell preamble') + conflict = conflict.replace(' set -euo pipefail\n', guard, 1) + text = text[:conflict_start] + conflict + + required = ( + '"model": "nvidia-nim/mistralai/mistral-nemotron"', + '"small_model": "nvidia-nim/nvidia/nemotron-3-nano-30b-a3b"', + '"enabled_providers": ["nvidia-nim"]', + '"baseURL": "https://integrate.api.nvidia.com/v1"', + '"apiKey": "{env:NVIDIA_API_KEY}"', + 'NVIDIA_API_KEY: ${{ secrets.NVIDIA_NIM_API_KEY }}', + 'MODEL: nvidia-nim/mistralai/mistral-nemotron', + ) + for fragment in required: + if fragment not in text: + raise SystemExit(f'missing migrated fragment: {fragment}') + + forbidden = ( + 'STRIX_GITHUB_MODELS_TOKEN:', + 'MODEL: github-models/', + 'USE_GITHUB_TOKEN:', + '"enabled_providers": ["github-models"]', + '"baseURL": "https://models.github.ai/inference"', + ) + for fragment in forbidden: + if fragment in text: + raise SystemExit(f'legacy model-auth fragment remains: {fragment}') + + binding = 'NVIDIA_API_KEY: ${{ secrets.NVIDIA_NIM_API_KEY }}' + if text.count(binding) != 2: + raise SystemExit('NVIDIA secret must be bound to exactly two OpenCode steps') + + path.write_text(text, encoding='utf-8') + PY + + - name: Install the hash-locked central test toolchain + run: | + set -euo pipefail + python3 -m pip install \ + --disable-pip-version-check \ + --require-hashes \ + --only-binary=:all: \ + -r requirements-opencode-review-ci-hashes.txt + + - name: Verify focused and complete contracts + run: | + set -euo pipefail + python3 -m pytest -q tests/test_pr_review_autofix_nvidia_nim_contract.py + python3 -m pytest -q + python3 -m compileall -q scripts tests + python3 -m interrogate -c pyproject.toml . + git diff --check + if git diff --name-only | grep -Fx '.github/workflows/opencode-review-dispatch.yml'; then + echo '::error::Read-only review-agent workflow changed during autofix migration.' + exit 1 + fi + + - name: Publish the verified migration and remove this one-shot workflow + env: + BRANCH_NAME: fix/hourly-nvidia-nim-autofix + run: | + set -euo pipefail + git rm .github/workflows/one-shot-apply-nvidia-nim-autofix.yml + git add \ + .github/workflows/pr-review-autofix.yml \ + docs/doctoring/hourly-nvidia-nim-autofix.md \ + tests/test_pr_review_autofix_nvidia_nim_contract.py + git diff --cached --check + git config user.name 'github-actions[bot]' + git config user.email '41898282+github-actions[bot]@users.noreply.github.com' + git commit -m 'fix(ci): route scheduled autofix through NVIDIA NIM' + git push origin "HEAD:${BRANCH_NAME}" From cfefa24506e7f6e71e80389ad3bde5057dd36d5c Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 4 Aug 2026 21:36:09 +0900 Subject: [PATCH 07/30] ci: retrigger validated NVIDIA NIM migration --- .github/workflows/patch-pr-752-nim.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/patch-pr-752-nim.yml b/.github/workflows/patch-pr-752-nim.yml index 6610fe774..c538609e0 100644 --- a/.github/workflows/patch-pr-752-nim.yml +++ b/.github/workflows/patch-pr-752-nim.yml @@ -1,5 +1,6 @@ name: Apply validated PR 752 NVIDIA NIM autofix migration +# Re-trigger the reviewed one-shot migration after exact-head contract inspection. on: push: branches: From af5ae0a2dd3b3a03b5afe470da143e05f5d4c2ab Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 4 Aug 2026 21:40:40 +0900 Subject: [PATCH 08/30] fix(ci): keep secret expressions literal in one-shot patch --- .../one-shot-apply-nvidia-nim-autofix.yml | 21 +++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/.github/workflows/one-shot-apply-nvidia-nim-autofix.yml b/.github/workflows/one-shot-apply-nvidia-nim-autofix.yml index 3dedb7b48..a22820aa5 100644 --- a/.github/workflows/one-shot-apply-nvidia-nim-autofix.yml +++ b/.github/workflows/one-shot-apply-nvidia-nim-autofix.yml @@ -37,6 +37,17 @@ jobs: path = Path('.github/workflows/pr-review-autofix.yml') text = path.read_text(encoding='utf-8') + expression_open = '$' + '{{' + old_token_line = ( + ' STRIX_GITHUB_MODELS_TOKEN: ' + + expression_open + + ' secrets.STRIX_GITHUB_MODELS_TOKEN || github.token }}\n' + ) + new_token_line = ( + ' NVIDIA_API_KEY: ' + + expression_open + + ' secrets.NVIDIA_NIM_API_KEY }}\n' + ) replacements = { '"model": "github-models/openai/gpt-5"': @@ -45,8 +56,7 @@ jobs: '"small_model": "nvidia-nim/nvidia/nemotron-3-nano-30b-a3b"', '"enabled_providers": ["github-models"]': '"enabled_providers": ["nvidia-nim"]', - ' STRIX_GITHUB_MODELS_TOKEN: ${{ secrets.STRIX_GITHUB_MODELS_TOKEN || github.token }}\n': - ' NVIDIA_API_KEY: ${{ secrets.NVIDIA_NIM_API_KEY }}\n', + old_token_line: new_token_line, ' MODEL: github-models/openai/gpt-5\n': ' MODEL: nvidia-nim/mistralai/mistral-nemotron\n', ' USE_GITHUB_TOKEN: "true"\n': '', @@ -55,7 +65,7 @@ jobs: '"model": "github-models/openai/gpt-5"': 1, '"small_model": "github-models/deepseek/deepseek-v3-0324"': 1, '"enabled_providers": ["github-models"]': 1, - ' STRIX_GITHUB_MODELS_TOKEN: ${{ secrets.STRIX_GITHUB_MODELS_TOKEN || github.token }}\n': 2, + old_token_line: 2, ' MODEL: github-models/openai/gpt-5\n': 2, ' USE_GITHUB_TOKEN: "true"\n': 2, } @@ -137,7 +147,7 @@ jobs: '"enabled_providers": ["nvidia-nim"]', '"baseURL": "https://integrate.api.nvidia.com/v1"', '"apiKey": "{env:NVIDIA_API_KEY}"', - 'NVIDIA_API_KEY: ${{ secrets.NVIDIA_NIM_API_KEY }}', + new_token_line.strip(), 'MODEL: nvidia-nim/mistralai/mistral-nemotron', ) for fragment in required: @@ -155,8 +165,7 @@ jobs: if fragment in text: raise SystemExit(f'legacy model-auth fragment remains: {fragment}') - binding = 'NVIDIA_API_KEY: ${{ secrets.NVIDIA_NIM_API_KEY }}' - if text.count(binding) != 2: + if text.count(new_token_line.strip()) != 2: raise SystemExit('NVIDIA secret must be bound to exactly two OpenCode steps') path.write_text(text, encoding='utf-8') From 81ad9f73fdbef8a7c3bcf3d1048316d00ca39a43 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 4 Aug 2026 21:44:22 +0900 Subject: [PATCH 09/30] fix(ci): route scheduled autofix through NVIDIA NIM --- .github/workflows/pr-review-autofix.yml | 56 ++++++++++++------------- 1 file changed, 27 insertions(+), 29 deletions(-) diff --git a/.github/workflows/pr-review-autofix.yml b/.github/workflows/pr-review-autofix.yml index e5475be1b..7a4cd2c75 100644 --- a/.github/workflows/pr-review-autofix.yml +++ b/.github/workflows/pr-review-autofix.yml @@ -231,9 +231,9 @@ jobs: EOF jq -n --arg workspace "$TARGET_WORKSPACE" '{ "$schema": "https://opencode.ai/config.json", - "model": "github-models/openai/gpt-5", - "small_model": "github-models/deepseek/deepseek-v3-0324", - "enabled_providers": ["github-models"], + "model": "nvidia-nim/mistralai/mistral-nemotron", + "small_model": "nvidia-nim/nvidia/nemotron-3-nano-30b-a3b", + "enabled_providers": ["nvidia-nim"], "permission": { "edit": "allow", "bash": "deny", @@ -269,37 +269,29 @@ jobs: } }, "provider": { - "github-models": { + "nvidia-nim": { "npm": "@ai-sdk/openai-compatible", - "name": "GitHub Models", + "name": "NVIDIA NIM", "options": { - "baseURL": "https://models.github.ai/inference", - "apiKey": "{env:STRIX_GITHUB_MODELS_TOKEN}" + "baseURL": "https://integrate.api.nvidia.com/v1", + "apiKey": "{env:NVIDIA_API_KEY}" }, "models": { - "openai/gpt-5": { - "name": "OpenAI GPT-5", + "mistralai/mistral-nemotron": { + "name": "Mistral Nemotron", "tool_call": true, - "reasoning": true, - "options": { - "reasoningEffort": "high" - }, - "variants": { - "high": { - "reasoningEffort": "high" - } - }, "limit": { - "context": 200000, - "output": 100000 + "context": 128000, + "output": 4096 } }, - "deepseek/deepseek-v3-0324": { - "name": "DeepSeek V3 0324", + "nvidia/nemotron-3-nano-30b-a3b": { + "name": "Nemotron 3 Nano 30B A3B", "tool_call": true, + "reasoning": true, "limit": { "context": 128000, - "output": 4096 + "output": 32768 } } } @@ -310,16 +302,19 @@ jobs: - name: Run OpenCode review autofix if: env.RESOLVE_CONFLICT != 'true' env: - STRIX_GITHUB_MODELS_TOKEN: ${{ secrets.STRIX_GITHUB_MODELS_TOKEN || github.token }} + NVIDIA_API_KEY: ${{ secrets.NVIDIA_NIM_API_KEY }} GITHUB_TOKEN: ${{ secrets.PR_REVIEW_MERGE_TOKEN || secrets.OPENCODE_APPROVE_TOKEN || steps.target_app_token.outputs.token || github.token }} - MODEL: github-models/openai/gpt-5 - USE_GITHUB_TOKEN: "true" + MODEL: nvidia-nim/mistralai/mistral-nemotron SHARE: "false" NPM_CONFIG_IGNORE_SCRIPTS: "true" NO_COLOR: "1" OPENCODE_AUTOFIX_WORKDIR: ${{ runner.temp }}/opencode-autofix-project run: | set -euo pipefail + if [ -z "${NVIDIA_API_KEY:-}" ]; then + echo "::error::NVIDIA_NIM_API_KEY is required for scheduled OpenCode autofix." + exit 1 + fi prompt_file="${RUNNER_TEMP}/opencode-autofix-prompt.md" allowed_paths_context="$( awk ' @@ -446,17 +441,20 @@ jobs: - name: Merge base branch and resolve conflicts with OpenCode if: env.RESOLVE_CONFLICT == 'true' env: - STRIX_GITHUB_MODELS_TOKEN: ${{ secrets.STRIX_GITHUB_MODELS_TOKEN || github.token }} + NVIDIA_API_KEY: ${{ secrets.NVIDIA_NIM_API_KEY }} GITHUB_TOKEN: ${{ secrets.PR_REVIEW_MERGE_TOKEN || secrets.OPENCODE_APPROVE_TOKEN || steps.target_app_token.outputs.token || github.token }} GH_TOKEN: ${{ secrets.PR_REVIEW_MERGE_TOKEN || secrets.OPENCODE_APPROVE_TOKEN || steps.target_app_token.outputs.token || github.token }} - MODEL: github-models/openai/gpt-5 - USE_GITHUB_TOKEN: "true" + MODEL: nvidia-nim/mistralai/mistral-nemotron SHARE: "false" NPM_CONFIG_IGNORE_SCRIPTS: "true" NO_COLOR: "1" OPENCODE_AUTOFIX_WORKDIR: ${{ runner.temp }}/opencode-autofix-project run: | set -euo pipefail + if [ -z "${NVIDIA_API_KEY:-}" ]; then + echo "::error::NVIDIA_NIM_API_KEY is required for scheduled OpenCode autofix." + exit 1 + fi cd "$TARGET_WORKSPACE" # Merge the base branch into the detached head. A clean merge stays From 30a5356b86dc34b0c8c34bd080acf837dbbd47ad Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 4 Aug 2026 21:44:34 +0900 Subject: [PATCH 10/30] chore(ci): remove failed one-shot migration helper --- .../one-shot-apply-nvidia-nim-autofix.yml | 210 ------------------ 1 file changed, 210 deletions(-) delete mode 100644 .github/workflows/one-shot-apply-nvidia-nim-autofix.yml diff --git a/.github/workflows/one-shot-apply-nvidia-nim-autofix.yml b/.github/workflows/one-shot-apply-nvidia-nim-autofix.yml deleted file mode 100644 index a22820aa5..000000000 --- a/.github/workflows/one-shot-apply-nvidia-nim-autofix.yml +++ /dev/null @@ -1,210 +0,0 @@ -name: One-shot apply NVIDIA NIM autofix - -on: - push: - branches: [fix/hourly-nvidia-nim-autofix] - -concurrency: - group: one-shot-apply-nvidia-nim-autofix-${{ github.ref }} - cancel-in-progress: true - -permissions: - contents: write - -jobs: - apply-and-verify: - if: github.repository == 'ContextualWisdomLab/.github' - runs-on: ubuntu-24.04 - env: - FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true - steps: - - name: Checkout exact branch head - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - with: - ref: fix/hourly-nvidia-nim-autofix - fetch-depth: 0 - - - name: Set up Python - uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.2.0 - with: - python-version: '3.14' - - - name: Apply the bounded provider migration - run: | - set -euo pipefail - python3 <<'PY' - from pathlib import Path - - path = Path('.github/workflows/pr-review-autofix.yml') - text = path.read_text(encoding='utf-8') - expression_open = '$' + '{{' - old_token_line = ( - ' STRIX_GITHUB_MODELS_TOKEN: ' - + expression_open - + ' secrets.STRIX_GITHUB_MODELS_TOKEN || github.token }}\n' - ) - new_token_line = ( - ' NVIDIA_API_KEY: ' - + expression_open - + ' secrets.NVIDIA_NIM_API_KEY }}\n' - ) - - replacements = { - '"model": "github-models/openai/gpt-5"': - '"model": "nvidia-nim/mistralai/mistral-nemotron"', - '"small_model": "github-models/deepseek/deepseek-v3-0324"': - '"small_model": "nvidia-nim/nvidia/nemotron-3-nano-30b-a3b"', - '"enabled_providers": ["github-models"]': - '"enabled_providers": ["nvidia-nim"]', - old_token_line: new_token_line, - ' MODEL: github-models/openai/gpt-5\n': - ' MODEL: nvidia-nim/mistralai/mistral-nemotron\n', - ' USE_GITHUB_TOKEN: "true"\n': '', - } - expected_counts = { - '"model": "github-models/openai/gpt-5"': 1, - '"small_model": "github-models/deepseek/deepseek-v3-0324"': 1, - '"enabled_providers": ["github-models"]': 1, - old_token_line: 2, - ' MODEL: github-models/openai/gpt-5\n': 2, - ' USE_GITHUB_TOKEN: "true"\n': 2, - } - for old, expected_count in expected_counts.items(): - actual_count = text.count(old) - if actual_count != expected_count: - raise SystemExit( - f'expected {expected_count} occurrences of {old!r}, found {actual_count}' - ) - text = text.replace(old, replacements[old]) - - provider_start_marker = ( - ' "provider": {\n' - ' "github-models": {' - ) - provider_start = text.index(provider_start_marker) - root_close_marker = ( - '\n }\' >"${OPENCODE_AUTOFIX_WORKDIR}/opencode.jsonc"' - ) - provider_end = text.index(root_close_marker, provider_start) - new_provider = ''' "provider": { - "nvidia-nim": { - "npm": "@ai-sdk/openai-compatible", - "name": "NVIDIA NIM", - "options": { - "baseURL": "https://integrate.api.nvidia.com/v1", - "apiKey": "{env:NVIDIA_API_KEY}" - }, - "models": { - "mistralai/mistral-nemotron": { - "name": "Mistral Nemotron", - "tool_call": true, - "limit": { - "context": 128000, - "output": 4096 - } - }, - "nvidia/nemotron-3-nano-30b-a3b": { - "name": "Nemotron 3 Nano 30B A3B", - "tool_call": true, - "reasoning": true, - "limit": { - "context": 128000, - "output": 32768 - } - } - } - } - }''' - text = text[:provider_start] + new_provider + text[provider_end:] - - guard = ''' set -euo pipefail - if [ -z "${NVIDIA_API_KEY:-}" ]; then - echo "::error::NVIDIA_NIM_API_KEY is required for scheduled OpenCode autofix." - exit 1 - fi -''' - - ordinary_start = text.index(' - name: Run OpenCode review autofix\n') - ordinary_end = text.index(' - name: Validate changed files\n', ordinary_start) - ordinary = text[ordinary_start:ordinary_end] - if ordinary.count(' set -euo pipefail\n') != 1: - raise SystemExit('ordinary OpenCode step did not contain exactly one shell preamble') - ordinary = ordinary.replace(' set -euo pipefail\n', guard, 1) - text = text[:ordinary_start] + ordinary + text[ordinary_end:] - - conflict_start = text.index( - ' - name: Merge base branch and resolve conflicts with OpenCode\n' - ) - conflict = text[conflict_start:] - if conflict.count(' set -euo pipefail\n') != 1: - raise SystemExit('conflict OpenCode step did not contain exactly one shell preamble') - conflict = conflict.replace(' set -euo pipefail\n', guard, 1) - text = text[:conflict_start] + conflict - - required = ( - '"model": "nvidia-nim/mistralai/mistral-nemotron"', - '"small_model": "nvidia-nim/nvidia/nemotron-3-nano-30b-a3b"', - '"enabled_providers": ["nvidia-nim"]', - '"baseURL": "https://integrate.api.nvidia.com/v1"', - '"apiKey": "{env:NVIDIA_API_KEY}"', - new_token_line.strip(), - 'MODEL: nvidia-nim/mistralai/mistral-nemotron', - ) - for fragment in required: - if fragment not in text: - raise SystemExit(f'missing migrated fragment: {fragment}') - - forbidden = ( - 'STRIX_GITHUB_MODELS_TOKEN:', - 'MODEL: github-models/', - 'USE_GITHUB_TOKEN:', - '"enabled_providers": ["github-models"]', - '"baseURL": "https://models.github.ai/inference"', - ) - for fragment in forbidden: - if fragment in text: - raise SystemExit(f'legacy model-auth fragment remains: {fragment}') - - if text.count(new_token_line.strip()) != 2: - raise SystemExit('NVIDIA secret must be bound to exactly two OpenCode steps') - - path.write_text(text, encoding='utf-8') - PY - - - name: Install the hash-locked central test toolchain - run: | - set -euo pipefail - python3 -m pip install \ - --disable-pip-version-check \ - --require-hashes \ - --only-binary=:all: \ - -r requirements-opencode-review-ci-hashes.txt - - - name: Verify focused and complete contracts - run: | - set -euo pipefail - python3 -m pytest -q tests/test_pr_review_autofix_nvidia_nim_contract.py - python3 -m pytest -q - python3 -m compileall -q scripts tests - python3 -m interrogate -c pyproject.toml . - git diff --check - if git diff --name-only | grep -Fx '.github/workflows/opencode-review-dispatch.yml'; then - echo '::error::Read-only review-agent workflow changed during autofix migration.' - exit 1 - fi - - - name: Publish the verified migration and remove this one-shot workflow - env: - BRANCH_NAME: fix/hourly-nvidia-nim-autofix - run: | - set -euo pipefail - git rm .github/workflows/one-shot-apply-nvidia-nim-autofix.yml - git add \ - .github/workflows/pr-review-autofix.yml \ - docs/doctoring/hourly-nvidia-nim-autofix.md \ - tests/test_pr_review_autofix_nvidia_nim_contract.py - git diff --cached --check - git config user.name 'github-actions[bot]' - git config user.email '41898282+github-actions[bot]@users.noreply.github.com' - git commit -m 'fix(ci): route scheduled autofix through NVIDIA NIM' - git push origin "HEAD:${BRANCH_NAME}" From 5deb1062157363bf1411b30b18affb1f6ce878bb Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 4 Aug 2026 21:47:37 +0900 Subject: [PATCH 11/30] chore(ci): remove superseded PR 752 patch script --- .github/scripts/patch_pr_752.py | 250 -------------------------------- 1 file changed, 250 deletions(-) delete mode 100644 .github/scripts/patch_pr_752.py diff --git a/.github/scripts/patch_pr_752.py b/.github/scripts/patch_pr_752.py deleted file mode 100644 index cff8bb4bd..000000000 --- a/.github/scripts/patch_pr_752.py +++ /dev/null @@ -1,250 +0,0 @@ -"""Apply the bounded NVIDIA NIM migration for the central PR autofix workflow.""" - -from pathlib import Path - - -WORKFLOW_PATH = Path(".github/workflows/pr-review-autofix.yml") -DOCTORING_PATH = Path("docs/doctoring/hourly-nvidia-nim-autofix.md") - - -def replace_once(text: str, old: str, new: str, label: str) -> str: - """Replace one exact reviewed block and fail closed on source drift.""" - - count = text.count(old) - if count != 1: - raise SystemExit(f"{label}: expected one exact block, found {count}") - return text.replace(old, new, 1) - - -def apply() -> None: - """Migrate only model authentication/configuration and write its design record.""" - - workflow = WORKFLOW_PATH.read_text(encoding="utf-8") - old_provider = ''' "model": "github-models/openai/gpt-5", - "small_model": "github-models/deepseek/deepseek-v3-0324", - "enabled_providers": ["github-models"], - "permission": { - "edit": "allow", - "bash": "deny", - "read": "allow", - "grep": "allow", - "glob": "allow", - "list": "allow", - "task": "deny", - "webfetch": "deny", - "websearch": "deny", - "lsp": "deny", - "external_directory": "deny" - }, - "agent": { - "ci-autofix": { - "description": "Conservative CI pull request review autofix agent", - "mode": "primary", - "prompt": "{file:./autofix-prompt.md}", - "steps": 12, - "permission": { - "edit": "allow", - "bash": "deny", - "read": "allow", - "grep": "allow", - "glob": "allow", - "list": "allow", - "task": "deny", - "webfetch": "deny", - "websearch": "deny", - "lsp": "deny", - "external_directory": "deny" - } - } - }, - "provider": { - "github-models": { - "npm": "@ai-sdk/openai-compatible", - "name": "GitHub Models", - "options": { - "baseURL": "https://models.github.ai/inference", - "apiKey": "{env:STRIX_GITHUB_MODELS_TOKEN}" - }, - "models": { - "openai/gpt-5": { - "name": "OpenAI GPT-5", - "tool_call": true, - "reasoning": true, - "options": { - "reasoningEffort": "high" - }, - "variants": { - "high": { - "reasoningEffort": "high" - } - }, - "limit": { - "context": 200000, - "output": 100000 - } - }, - "deepseek/deepseek-v3-0324": { - "name": "DeepSeek V3 0324", - "tool_call": true, - "limit": { - "context": 128000, - "output": 4096 - } - } - } - } - }''' - new_provider = ''' "model": "nvidia-nim/mistralai/mistral-nemotron", - "small_model": "nvidia-nim/nvidia/nemotron-3-nano-30b-a3b", - "enabled_providers": ["nvidia-nim"], - "permission": { - "edit": "allow", - "bash": "deny", - "read": "allow", - "grep": "allow", - "glob": "allow", - "list": "allow", - "task": "deny", - "webfetch": "deny", - "websearch": "deny", - "lsp": "deny", - "external_directory": "deny" - }, - "agent": { - "ci-autofix": { - "description": "Conservative CI pull request review autofix agent", - "mode": "primary", - "prompt": "{file:./autofix-prompt.md}", - "steps": 12, - "permission": { - "edit": "allow", - "bash": "deny", - "read": "allow", - "grep": "allow", - "glob": "allow", - "list": "allow", - "task": "deny", - "webfetch": "deny", - "websearch": "deny", - "lsp": "deny", - "external_directory": "deny" - } - } - }, - "provider": { - "nvidia-nim": { - "npm": "@ai-sdk/openai-compatible", - "name": "NVIDIA NIM", - "options": { - "baseURL": "https://integrate.api.nvidia.com/v1", - "apiKey": "{env:NVIDIA_API_KEY}" - }, - "models": { - "mistralai/mistral-nemotron": { - "name": "Mistral Nemotron", - "tool_call": true, - "limit": { - "context": 131072, - "output": 4096 - } - }, - "nvidia/nemotron-3-nano-30b-a3b": { - "name": "Nemotron 3 Nano 30B A3B", - "tool_call": true, - "limit": { - "context": 262144, - "output": 16384 - } - } - } - } - }''' - workflow = replace_once( - workflow, old_provider, new_provider, "OpenCode provider configuration" - ) - - old_ordinary_env = ''' env: - STRIX_GITHUB_MODELS_TOKEN: ${{ secrets.STRIX_GITHUB_MODELS_TOKEN || github.token }} - GITHUB_TOKEN: ${{ secrets.PR_REVIEW_MERGE_TOKEN || secrets.OPENCODE_APPROVE_TOKEN || steps.target_app_token.outputs.token || github.token }} - MODEL: github-models/openai/gpt-5 - USE_GITHUB_TOKEN: "true" - SHARE: "false"''' - new_ordinary_env = ''' env: - NVIDIA_API_KEY: ${{ secrets.NVIDIA_NIM_API_KEY }} - GITHUB_TOKEN: ${{ secrets.PR_REVIEW_MERGE_TOKEN || secrets.OPENCODE_APPROVE_TOKEN || steps.target_app_token.outputs.token || github.token }} - MODEL: nvidia-nim/mistralai/mistral-nemotron - SHARE: "false"''' - workflow = replace_once( - workflow, - old_ordinary_env, - new_ordinary_env, - "ordinary OpenCode execution environment", - ) - - old_conflict_env = ''' env: - STRIX_GITHUB_MODELS_TOKEN: ${{ secrets.STRIX_GITHUB_MODELS_TOKEN || github.token }} - GITHUB_TOKEN: ${{ secrets.PR_REVIEW_MERGE_TOKEN || secrets.OPENCODE_APPROVE_TOKEN || steps.target_app_token.outputs.token || github.token }} - GH_TOKEN: ${{ secrets.PR_REVIEW_MERGE_TOKEN || secrets.OPENCODE_APPROVE_TOKEN || steps.target_app_token.outputs.token || github.token }} - MODEL: github-models/openai/gpt-5 - USE_GITHUB_TOKEN: "true" - SHARE: "false"''' - new_conflict_env = ''' env: - NVIDIA_API_KEY: ${{ secrets.NVIDIA_NIM_API_KEY }} - GITHUB_TOKEN: ${{ secrets.PR_REVIEW_MERGE_TOKEN || secrets.OPENCODE_APPROVE_TOKEN || steps.target_app_token.outputs.token || github.token }} - GH_TOKEN: ${{ secrets.PR_REVIEW_MERGE_TOKEN || secrets.OPENCODE_APPROVE_TOKEN || steps.target_app_token.outputs.token || github.token }} - MODEL: nvidia-nim/mistralai/mistral-nemotron - SHARE: "false"''' - workflow = replace_once( - workflow, - old_conflict_env, - new_conflict_env, - "conflict-resolution OpenCode execution environment", - ) - WORKFLOW_PATH.write_text(workflow, encoding="utf-8") - - doctoring = '''# Hourly NVIDIA NIM review-autofix boundary - -## Decision - -The central `PR Review Fix Scheduler` dispatches at minute 23 of every hour and retains its one-hour same-head retry boundary. The dispatched write-capable OpenCode autofix workflow uses only the NVIDIA NIM OpenAI-compatible provider. Its primary model is `mistralai/mistral-nemotron`; its small model is `nvidia/nemotron-3-nano-30b-a3b`. - -This change is deliberately isolated from `opencode-review-dispatch.yml`. The existing read-only review agent keeps its own model pool, secret scoping, approval credentials, and repository policy. The autofix agent receives `secrets.NVIDIA_NIM_API_KEY` as `NVIDIA_API_KEY` only in the two steps that execute OpenCode: ordinary review repair and merge-conflict resolution. GitHub mutation credentials remain separate and continue to authorize only repository reads or writes. - -## Product and MSA boundary - -The scheduler, feedback collector, model execution, validation, and GitHub mutation stages remain independently replaceable central services. Target repositories consume the automation through repository-dispatch metadata and do not need to embed provider credentials or model configuration. The agent retains its conservative file allowlist, denied shell/tool permissions, exact-head checks, and fail-closed push guard. - -## Verification contract - -Static tests require the hourly cron inherited from the central baseline, the single `nvidia-nim` provider, the official NVIDIA API base URL, environment-only credential resolution, exact model IDs, and secret visibility limited to the two OpenCode execution steps. They reject GitHub Models provider configuration, GitHub Models model authentication, and `USE_GITHUB_TOKEN` fallback in the write-capable autofix workflow. - -The selected primary model is documented by NVIDIA as suitable for agentic workflows, coding, instruction following, and function calling. The small model is documented as supporting coding, reasoning, instruction following, and tool calling. These catalog claims inform provider selection; they are not treated as evidence that any individual repair is correct. Repository tests, security checks, independent review, and branch protection remain authoritative. - -## Standards alignment - -The design supports NIST SSDF practices for protecting development environments and verifying software artifacts by narrowing credential exposure, separating model authentication from mutation authorization, and retaining independent verification before merge. It also follows the SLSA 1.2 source-track direction by preserving review and source-management controls. No formal NIST or SLSA conformance claim is made. - -## References - -Anomaly Co. (n.d.). *Providers*. OpenCode. Retrieved August 4, 2026, from https://opencode.ai/docs/providers/ - -Booth, H., Ogata, M., Kent, K., Souppaya, M., & Dodson, D. (2025). *Secure software development framework (SSDF) version 1.2: Recommendations for mitigating the risk of software vulnerabilities* (Initial Public Draft NIST SP 800-218 Rev. 1). National Institute of Standards and Technology. https://csrc.nist.gov/pubs/sp/800/218/r1/ipd - -NVIDIA Corporation. (n.d.). *API reference for NVIDIA NIM for large language models*. Retrieved August 4, 2026, from https://docs.nvidia.com/nim/large-language-models/latest/api-reference.html - -NVIDIA Corporation. (2025). *Mistral-Nemotron* [Model card]. https://build.nvidia.com/mistralai/mistral-nemotron - -NVIDIA Corporation. (2026). *Nemotron-3-Nano-30B-A3B* [Model catalog]. https://build.nvidia.com/nvidia/nemotron-3-nano-30b-a3b - -SLSA Community. (2025, November 24). *Announcing SLSA v1.2*. https://slsa.dev/blog/2025/11/announce-slsa-v1.2 - -Souppaya, M., Scarfone, K., & Dodson, D. (2022). *Secure software development framework (SSDF) version 1.1: Recommendations for mitigating the risk of software vulnerabilities* (NIST SP 800-218). National Institute of Standards and Technology. https://doi.org/10.6028/NIST.SP.800-218 -''' - DOCTORING_PATH.parent.mkdir(parents=True, exist_ok=True) - if DOCTORING_PATH.exists() and DOCTORING_PATH.read_text(encoding="utf-8") != doctoring: - raise SystemExit(f"{DOCTORING_PATH}: existing content does not match") - DOCTORING_PATH.write_text(doctoring, encoding="utf-8") - - -if __name__ == "__main__": - apply() From 6b2b21d7ee825c3caf1789da1fded939831e77df Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 4 Aug 2026 21:47:57 +0900 Subject: [PATCH 12/30] chore(ci): remove superseded PR 752 patch workflow --- .github/workflows/patch-pr-752-nim.yml | 126 ------------------------- 1 file changed, 126 deletions(-) delete mode 100644 .github/workflows/patch-pr-752-nim.yml diff --git a/.github/workflows/patch-pr-752-nim.yml b/.github/workflows/patch-pr-752-nim.yml deleted file mode 100644 index c538609e0..000000000 --- a/.github/workflows/patch-pr-752-nim.yml +++ /dev/null @@ -1,126 +0,0 @@ -name: Apply validated PR 752 NVIDIA NIM autofix migration - -# Re-trigger the reviewed one-shot migration after exact-head contract inspection. -on: - push: - branches: - - fix/hourly-nvidia-nim-autofix - paths: - - .github/workflows/patch-pr-752-nim.yml - -permissions: - contents: read - -concurrency: - group: patch-pr-752-nim-${{ github.ref }} - cancel-in-progress: false - -jobs: - validate: - if: github.actor != 'github-actions[bot]' - runs-on: ubuntu-latest - timeout-minutes: 15 - outputs: - result_digest: ${{ steps.digest.outputs.value }} - steps: - - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - with: - ref: ${{ github.sha }} - persist-credentials: false - - - name: Verify immutable patch source - run: echo 'ef6cae492b84b715856f0f6b1038bb07f7d069d994bca83991dbfe7a4f884ed8 .github/scripts/patch_pr_752.py' | sha256sum --check --strict - - - name: Prove contract is red before production migration - shell: bash - run: | - set +e - python -m pytest -q tests/test_pr_review_autofix_nvidia_nim_contract.py \ - > "$RUNNER_TEMP/nim-red.log" 2>&1 - status=$? - set -e - cat "$RUNNER_TEMP/nim-red.log" - test "$status" -ne 0 - grep -F 'nvidia-nim/mistralai/mistral-nemotron' "$RUNNER_TEMP/nim-red.log" - - - name: Apply bounded provider migration - run: python .github/scripts/patch_pr_752.py - - - name: Verify focused contract and Python syntax - run: | - python -m pytest -q tests/test_pr_review_autofix_nvidia_nim_contract.py - python -m py_compile \ - .github/scripts/patch_pr_752.py \ - tests/test_pr_review_autofix_nvidia_nim_contract.py - git diff --check - - - name: Bind exact validated result - id: digest - shell: bash - run: | - set -euo pipefail - value="$({ - printf '%s\0' '.github/workflows/pr-review-autofix.yml' - cat .github/workflows/pr-review-autofix.yml - printf '%s\0' 'docs/doctoring/hourly-nvidia-nim-autofix.md' - cat docs/doctoring/hourly-nvidia-nim-autofix.md - } | sha256sum | cut -d' ' -f1)" - test -n "$value" - echo "value=$value" >> "$GITHUB_OUTPUT" - - publish: - needs: validate - if: needs.validate.result == 'success' - runs-on: ubuntu-latest - timeout-minutes: 10 - permissions: - contents: write - steps: - - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - with: - ref: ${{ github.sha }} - fetch-depth: 0 - persist-credentials: false - - - name: Reproduce exact validated result without executing target repository code - env: - EXPECTED_DIGEST: ${{ needs.validate.outputs.result_digest }} - shell: bash - run: | - set -euo pipefail - echo 'ef6cae492b84b715856f0f6b1038bb07f7d069d994bca83991dbfe7a4f884ed8 .github/scripts/patch_pr_752.py' | sha256sum --check --strict - python .github/scripts/patch_pr_752.py - actual="$({ - printf '%s\0' '.github/workflows/pr-review-autofix.yml' - cat .github/workflows/pr-review-autofix.yml - printf '%s\0' 'docs/doctoring/hourly-nvidia-nim-autofix.md' - cat docs/doctoring/hourly-nvidia-nim-autofix.md - } | sha256sum | cut -d' ' -f1)" - test -n "$EXPECTED_DIGEST" - test "$actual" = "$EXPECTED_DIGEST" - git diff --check - - - name: Commit validated result and remove temporary patch machinery - env: - GH_TOKEN: ${{ github.token }} - SOURCE_HEAD: ${{ github.sha }} - shell: bash - run: | - set -euo pipefail - rm \ - .github/scripts/patch_pr_752.py \ - .github/workflows/patch-pr-752-nim.yml - git config user.name github-actions[bot] - git config user.email 41898282+github-actions[bot]@users.noreply.github.com - git add \ - .github/workflows/pr-review-autofix.yml \ - docs/doctoring/hourly-nvidia-nim-autofix.md \ - .github/scripts/patch_pr_752.py \ - .github/workflows/patch-pr-752-nim.yml - git diff --cached --check - git commit -m 'fix(ci): use NVIDIA NIM for scheduled OpenCode autofix' - remote_url="https://x-access-token:${GH_TOKEN}@github.com/${GITHUB_REPOSITORY}.git" - git push \ - --force-with-lease="refs/heads/fix/hourly-nvidia-nim-autofix:${SOURCE_HEAD}" \ - "$remote_url" \ - HEAD:refs/heads/fix/hourly-nvidia-nim-autofix From 1917ebcfe4168f56da95acdb4850c7d11ee20a43 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 4 Aug 2026 21:48:41 +0900 Subject: [PATCH 13/30] test(ci): require fail-closed NVIDIA NIM secret handling --- ...t_pr_review_autofix_nvidia_nim_contract.py | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/tests/test_pr_review_autofix_nvidia_nim_contract.py b/tests/test_pr_review_autofix_nvidia_nim_contract.py index bcb996f53..bbecec3f0 100644 --- a/tests/test_pr_review_autofix_nvidia_nim_contract.py +++ b/tests/test_pr_review_autofix_nvidia_nim_contract.py @@ -69,3 +69,25 @@ def test_nvidia_nim_secret_is_scoped_to_agent_execution_steps() -> None: assert binding in workflow[conflict_start:] assert binding not in workflow[:ordinary_start] assert binding not in workflow[ordinary_end:conflict_start] + + +def test_missing_nvidia_nim_secret_fails_closed_before_model_execution() -> None: + """Reject an empty model credential instead of falling back to another provider.""" + + workflow = _workflow_text(AUTOFIX_WORKFLOW) + guard = ( + 'if [ -z "${NVIDIA_API_KEY:-}" ]; then\n' + ' echo "::error::NVIDIA_NIM_API_KEY is required for scheduled ' + 'OpenCode autofix."\n' + " exit 1\n" + " fi" + ) + ordinary_start = workflow.index(" - name: Run OpenCode review autofix") + ordinary_end = workflow.index(" - name: Validate changed files", ordinary_start) + conflict_start = workflow.index( + " - name: Merge base branch and resolve conflicts with OpenCode" + ) + + assert workflow.count(guard) == 2 + assert guard in workflow[ordinary_start:ordinary_end] + assert guard in workflow[conflict_start:] From 8285def81f8ae6b8cdfdbcfe0f299e441d0cd4e0 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 4 Aug 2026 22:51:27 +0900 Subject: [PATCH 14/30] test(ci): harden NVIDIA NIM autofix trust boundary --- ...t_pr_review_autofix_nvidia_nim_contract.py | 48 ++++++++++++++++++- 1 file changed, 47 insertions(+), 1 deletion(-) diff --git a/tests/test_pr_review_autofix_nvidia_nim_contract.py b/tests/test_pr_review_autofix_nvidia_nim_contract.py index bbecec3f0..6fdbdd06e 100644 --- a/tests/test_pr_review_autofix_nvidia_nim_contract.py +++ b/tests/test_pr_review_autofix_nvidia_nim_contract.py @@ -1,10 +1,11 @@ -"""Contract tests for the scheduled OpenCode review-autofix model boundary.""" +"""Contract tests for the scheduled OpenCode review-autofix trust boundary.""" from pathlib import Path AUTOFIX_WORKFLOW = Path(".github/workflows/pr-review-autofix.yml") FIX_SCHEDULER_WORKFLOW = Path(".github/workflows/pr-review-fix-scheduler.yml") +REVIEW_DISPATCH_WORKFLOW = Path(".github/workflows/opencode-review-dispatch.yml") def _workflow_text(path: Path) -> str: @@ -53,6 +54,41 @@ def test_scheduled_autofix_uses_only_nvidia_nim() -> None: assert fragment not in workflow, fragment +def test_trusted_autofix_source_is_bound_to_dispatch_sha() -> None: + """Prevent a moving default branch from replacing trusted autofix scripts.""" + + workflow = _workflow_text(AUTOFIX_WORKFLOW) + checkout_start = workflow.index(" - name: Checkout trusted autofix source") + checkout_end = workflow.index( + " - name: Exchange OpenCode app token", checkout_start + ) + checkout = workflow[checkout_start:checkout_end] + + assert "ref: ${{ github.sha }}" in checkout + assert "ref: main" not in checkout + assert "fetch-depth: 1" in checkout + assert "persist-credentials: false" in checkout + + +def test_opencode_agent_denies_non_file_interactions() -> None: + """Keep unattended repair bounded to local file inspection and edits.""" + + workflow = _workflow_text(AUTOFIX_WORKFLOW) + + for permission_name in ( + "bash", + "task", + "skill", + "question", + "webfetch", + "websearch", + "lsp", + "external_directory", + "doom_loop", + ): + assert workflow.count(f'"{permission_name}": "deny"') == 2 + + def test_nvidia_nim_secret_is_scoped_to_agent_execution_steps() -> None: """Prevent the NVIDIA credential from leaking beyond the two OpenCode runs.""" @@ -91,3 +127,13 @@ def test_missing_nvidia_nim_secret_fails_closed_before_model_execution() -> None assert workflow.count(guard) == 2 assert guard in workflow[ordinary_start:ordinary_end] assert guard in workflow[conflict_start:] + + +def test_independent_review_agent_key_system_is_unchanged() -> None: + """Keep scheduled write repair separate from the read-only review credentials.""" + + review_workflow = _workflow_text(REVIEW_DISPATCH_WORKFLOW) + + assert "NVIDIA_NIM_API_KEY" not in review_workflow + assert "NVIDIA_API_KEY" not in review_workflow + assert "pr-review-autofix" not in review_workflow From fbc16ca498c1b7cb355397e6fb2472d2a20b673f Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 4 Aug 2026 22:58:43 +0900 Subject: [PATCH 15/30] chore(ci): apply reviewed NVIDIA autofix hardening --- .../apply-nvidia-autofix-hardening.yml | 91 +++++++++++++++++++ 1 file changed, 91 insertions(+) create mode 100644 .github/workflows/apply-nvidia-autofix-hardening.yml diff --git a/.github/workflows/apply-nvidia-autofix-hardening.yml b/.github/workflows/apply-nvidia-autofix-hardening.yml new file mode 100644 index 000000000..b41a7b25a --- /dev/null +++ b/.github/workflows/apply-nvidia-autofix-hardening.yml @@ -0,0 +1,91 @@ +name: Apply NVIDIA NIM autofix hardening + +on: + push: + branches: + - fix/hourly-nvidia-nim-autofix + paths: + - .github/workflows/apply-nvidia-autofix-hardening.yml + +permissions: + contents: write + +concurrency: + group: apply-nvidia-autofix-hardening + cancel-in-progress: true + +jobs: + apply: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + ref: fix/hourly-nvidia-nim-autofix + fetch-depth: 0 + + - name: Apply reviewed trust-boundary changes + shell: python + run: | + from pathlib import Path + + workflow_path = Path('.github/workflows/pr-review-autofix.yml') + workflow = workflow_path.read_text(encoding='utf-8') + + mutable_checkout = ( + ' repository: ContextualWisdomLab/.github\n' + ' fetch-depth: 1\n' + ) + pinned_checkout = ( + ' repository: ContextualWisdomLab/.github\n' + ' ref: ${{ github.sha }}\n' + ' fetch-depth: 1\n' + ) + if workflow.count(mutable_checkout) != 1: + raise SystemExit('trusted source checkout marker was not unique') + workflow = workflow.replace(mutable_checkout, pinned_checkout, 1) + + permission_marker = ( + ' "task": "deny",\n' + ' "webfetch": "deny",\n' + ) + hardened_permissions = ( + ' "task": "deny",\n' + ' "skill": "deny",\n' + ' "question": "deny",\n' + ' "webfetch": "deny",\n' + ' "websearch": "deny",\n' + ' "lsp": "deny",\n' + ' "external_directory": "deny",\n' + ' "doom_loop": "deny"\n' + ) + existing_tail = ( + ' "task": "deny",\n' + ' "webfetch": "deny",\n' + ' "websearch": "deny",\n' + ' "lsp": "deny",\n' + ' "external_directory": "deny"\n' + ) + if workflow.count(existing_tail) != 2: + raise SystemExit('OpenCode permission marker count was not two') + workflow = workflow.replace(existing_tail, hardened_permissions) + + if workflow.count('ref: ${{ github.sha }}') != 1: + raise SystemExit('trusted checkout was not pinned exactly once') + for permission_name in ('skill', 'question', 'doom_loop'): + expected = f'"{permission_name}": "deny"' + if workflow.count(expected) != 2: + raise SystemExit(f'{permission_name} deny count was not two') + + workflow_path.write_text(workflow, encoding='utf-8') + Path('.github/workflows/apply-nvidia-autofix-hardening.yml').unlink() + + - name: Commit hardened workflow + run: | + set -euo pipefail + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + git add \ + .github/workflows/pr-review-autofix.yml \ + .github/workflows/apply-nvidia-autofix-hardening.yml + git commit -m "fix(ci): harden NVIDIA NIM autofix trust boundary" + git push origin HEAD:fix/hourly-nvidia-nim-autofix From 9f3c2a1cc2faa83479b78176af766dca01c4538a Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 4 Aug 2026 23:00:15 +0900 Subject: [PATCH 16/30] chore(ci): remove unused NVIDIA autofix patch workflow --- .../apply-nvidia-autofix-hardening.yml | 91 ------------------- 1 file changed, 91 deletions(-) delete mode 100644 .github/workflows/apply-nvidia-autofix-hardening.yml diff --git a/.github/workflows/apply-nvidia-autofix-hardening.yml b/.github/workflows/apply-nvidia-autofix-hardening.yml deleted file mode 100644 index b41a7b25a..000000000 --- a/.github/workflows/apply-nvidia-autofix-hardening.yml +++ /dev/null @@ -1,91 +0,0 @@ -name: Apply NVIDIA NIM autofix hardening - -on: - push: - branches: - - fix/hourly-nvidia-nim-autofix - paths: - - .github/workflows/apply-nvidia-autofix-hardening.yml - -permissions: - contents: write - -concurrency: - group: apply-nvidia-autofix-hardening - cancel-in-progress: true - -jobs: - apply: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - with: - ref: fix/hourly-nvidia-nim-autofix - fetch-depth: 0 - - - name: Apply reviewed trust-boundary changes - shell: python - run: | - from pathlib import Path - - workflow_path = Path('.github/workflows/pr-review-autofix.yml') - workflow = workflow_path.read_text(encoding='utf-8') - - mutable_checkout = ( - ' repository: ContextualWisdomLab/.github\n' - ' fetch-depth: 1\n' - ) - pinned_checkout = ( - ' repository: ContextualWisdomLab/.github\n' - ' ref: ${{ github.sha }}\n' - ' fetch-depth: 1\n' - ) - if workflow.count(mutable_checkout) != 1: - raise SystemExit('trusted source checkout marker was not unique') - workflow = workflow.replace(mutable_checkout, pinned_checkout, 1) - - permission_marker = ( - ' "task": "deny",\n' - ' "webfetch": "deny",\n' - ) - hardened_permissions = ( - ' "task": "deny",\n' - ' "skill": "deny",\n' - ' "question": "deny",\n' - ' "webfetch": "deny",\n' - ' "websearch": "deny",\n' - ' "lsp": "deny",\n' - ' "external_directory": "deny",\n' - ' "doom_loop": "deny"\n' - ) - existing_tail = ( - ' "task": "deny",\n' - ' "webfetch": "deny",\n' - ' "websearch": "deny",\n' - ' "lsp": "deny",\n' - ' "external_directory": "deny"\n' - ) - if workflow.count(existing_tail) != 2: - raise SystemExit('OpenCode permission marker count was not two') - workflow = workflow.replace(existing_tail, hardened_permissions) - - if workflow.count('ref: ${{ github.sha }}') != 1: - raise SystemExit('trusted checkout was not pinned exactly once') - for permission_name in ('skill', 'question', 'doom_loop'): - expected = f'"{permission_name}": "deny"' - if workflow.count(expected) != 2: - raise SystemExit(f'{permission_name} deny count was not two') - - workflow_path.write_text(workflow, encoding='utf-8') - Path('.github/workflows/apply-nvidia-autofix-hardening.yml').unlink() - - - name: Commit hardened workflow - run: | - set -euo pipefail - git config user.name "github-actions[bot]" - git config user.email "41898282+github-actions[bot]@users.noreply.github.com" - git add \ - .github/workflows/pr-review-autofix.yml \ - .github/workflows/apply-nvidia-autofix-hardening.yml - git commit -m "fix(ci): harden NVIDIA NIM autofix trust boundary" - git push origin HEAD:fix/hourly-nvidia-nim-autofix From 6c52b46d4cd630e1fba3059688ea7839af3ce891 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 4 Aug 2026 23:03:59 +0900 Subject: [PATCH 17/30] fix(ci): harden NVIDIA NIM autofix trust boundary --- .github/workflows/pr-review-autofix.yml | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/.github/workflows/pr-review-autofix.yml b/.github/workflows/pr-review-autofix.yml index 7a4cd2c75..31c371de1 100644 --- a/.github/workflows/pr-review-autofix.yml +++ b/.github/workflows/pr-review-autofix.yml @@ -42,6 +42,7 @@ jobs: uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 with: repository: ContextualWisdomLab/.github + ref: ${{ github.sha }} fetch-depth: 1 persist-credentials: false path: trusted-autofix-source @@ -242,10 +243,13 @@ jobs: "glob": "allow", "list": "allow", "task": "deny", + "skill": "deny", + "question": "deny", "webfetch": "deny", "websearch": "deny", "lsp": "deny", - "external_directory": "deny" + "external_directory": "deny", + "doom_loop": "deny" }, "agent": { "ci-autofix": { @@ -261,10 +265,13 @@ jobs: "glob": "allow", "list": "allow", "task": "deny", + "skill": "deny", + "question": "deny", "webfetch": "deny", "websearch": "deny", "lsp": "deny", - "external_directory": "deny" + "external_directory": "deny", + "doom_loop": "deny" } } }, From 0103f2bd677276b3b2be3490fdd04a69870b15c4 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 4 Aug 2026 23:10:02 +0900 Subject: [PATCH 18/30] docs(ci): record NVIDIA autofix trust-boundary hardening --- docs/doctoring/hourly-nvidia-nim-autofix.md | 202 +++++++++++++++++--- 1 file changed, 173 insertions(+), 29 deletions(-) diff --git a/docs/doctoring/hourly-nvidia-nim-autofix.md b/docs/doctoring/hourly-nvidia-nim-autofix.md index 8c568d62e..9241b1a4c 100644 --- a/docs/doctoring/hourly-nvidia-nim-autofix.md +++ b/docs/doctoring/hourly-nvidia-nim-autofix.md @@ -2,27 +2,76 @@ ## Decision -The write-capable scheduled pull-request autofix agent uses OpenCode with the NVIDIA NIM API and the organization Actions secret `NVIDIA_NIM_API_KEY`. The independent read-only review agent remains unchanged and continues to use its existing credential and model-pool contract. +The write-capable scheduled pull-request autofix agent uses OpenCode with the +NVIDIA NIM API and the organization Actions secret `NVIDIA_NIM_API_KEY`. The +independent read-only review agent remains unchanged and continues to use its +existing credential and model-pool contract. -This separation is intentional. Review and repair have different privileges: the review path publishes a verdict, while the autofix path may modify and push a same-repository pull-request branch. Sharing or silently replacing the review credential would couple two independent controls and weaken incident containment. +This separation is intentional. Review and repair have different privileges: +the review path publishes a verdict, while the autofix path may modify and push +a same-repository pull-request branch. Sharing or silently replacing the review +credential would couple two independent controls and weaken incident +containment. ## Central MSA ownership -`ContextualWisdomLab/.github` owns the scheduler, dispatch authorization, model-provider configuration, credential binding, and fail-closed repair contract. Leaf repositories receive the behavior through the central reusable workflow and do not copy provider credentials or scheduler implementation. +`ContextualWisdomLab/.github` owns the scheduler, dispatch authorization, +model-provider configuration, credential binding, immutable worker source, and +fail-closed repair contract. Leaf repositories receive the behavior through the +central reusable workflow and do not copy provider credentials or scheduler +implementation. -The central scheduler established by the baseline repair runs once per hour, dispatches at most one repair per invocation, and binds privileged implementation to the immutable called-workflow source. The NVIDIA migration changes only the model transport used by the write-capable autofix worker. +The central scheduler established by the baseline repair runs once per hour, +dispatches at most one repair per invocation, and binds its scheduler +implementation to the immutable called-workflow source. The NVIDIA migration +changes only the model transport used by the write-capable autofix worker and +hardens that worker's own default-branch source checkout. + +## Immutable repository-dispatch worker source + +`PR Review Autofix` is a default-branch-only `repository_dispatch` workflow. +GitHub defines `GITHUB_SHA` for `repository_dispatch` as the last commit on the +default branch and runs only a workflow file present on that branch. The +workflow therefore checks out its co-located context builder and policy source +at the exact workflow-run commit: + +```yaml +repository: ContextualWisdomLab/.github +ref: ${{ github.sha }} +fetch-depth: 1 +persist-credentials: false +``` + +Without the explicit `ref`, `actions/checkout` would resolve the repository's +moving default branch at checkout time. A later default-branch push could then +replace trusted scripts after GitHub had already selected the workflow run, +creating a time-of-check/time-of-use gap around a job that receives OIDC and +branch-write capability. The explicit SHA keeps the executed helper source +aligned with the workflow revision selected for the dispatch. + +The client payload remains untrusted metadata. It can identify the intended +target PR only after the workflow re-reads live PR state and verifies exact base +and head refs and SHAs. ## Provider contract -The pinned OpenCode runtime is configured with one enabled provider, `nvidia-nim`, using the OpenAI-compatible adapter and the NVIDIA hosted endpoint: +The pinned OpenCode runtime is configured with one enabled provider, +`nvidia-nim`, using the OpenAI-compatible adapter and NVIDIA hosted endpoint: ```text https://integrate.api.nvidia.com/v1 ``` -The primary repair model is `mistralai/mistral-nemotron`; the small model used for bounded helper work is `nvidia/nemotron-3-nano-30b-a3b`. NVIDIA documents both model identifiers and the OpenAI-compatible `/v1/chat/completions` endpoint. Mistral-Nemotron is selected for agentic coding and tool-calling capability; Nemotron 3 Nano is selected as a lower-active-parameter helper model rather than as a fallback provider. +The primary repair model is `mistralai/mistral-nemotron`; the small model used +for bounded helper work is `nvidia/nemotron-3-nano-30b-a3b`. NVIDIA documents +both identifiers. Mistral-Nemotron is suitable for agentic workflows because it +supports tool calling. Nemotron 3 Nano is a commercially usable reasoning and +agentic model and is used as a lower-active-parameter helper, not as a fallback +provider. -Only the `nvidia-nim` provider is enabled. GitHub Models configuration, model identifiers, base URLs, and model-auth fallbacks are absent from the scheduled autofix execution path. +Only the `nvidia-nim` provider is enabled. GitHub Models configuration, model +identifiers, base URLs, and model-auth fallbacks are absent from the scheduled +autofix execution path. ## Credential boundary @@ -32,17 +81,77 @@ The organization secret is bound as: NVIDIA_API_KEY: ${{ secrets.NVIDIA_NIM_API_KEY }} ``` -It is present only on the two steps that execute OpenCode: ordinary review-feedback repair and merge-conflict repair. Earlier metadata collection, checkout, context preparation, validation, commit, and push steps do not receive the NVIDIA credential. - -The workflow passes the key through an environment variable and OpenCode substitutes `{env:NVIDIA_API_KEY}` into the provider configuration. The key is never written to repository files, command arguments, generated prompts, or logs. A missing secret is a fatal configuration error; the workflow does not fall back to `GITHUB_TOKEN`, the GitHub Models token, or another provider. - -GitHub notes that a missing secret expression resolves to an empty string and recommends environment-variable delivery rather than command-line delivery. The explicit preflight therefore prevents an ambiguous unauthenticated provider request and preserves fail-closed behavior. - -## Repair sandbox and write boundary - -The model transport change does not expand agent permissions. OpenCode continues to deny shell, task, web-fetch, web-search, language-server, and external-directory access. It may read, search, list, and edit only the validated same-repository pull-request worktree and only paths authorized by current actionable review context. The workflow validates the live base/head metadata before execution, validates changed files afterward, and refuses to push if the head moved. - -GitHub repository credentials and the NVIDIA model credential remain separate. The existing short-lived GitHub App/OIDC exchange and branch-write token chain are not used for model authentication. Conversely, `NVIDIA_NIM_API_KEY` is not used for GitHub reads or writes. +It is present only on the two steps that execute OpenCode: ordinary +review-feedback repair and merge-conflict repair. Earlier metadata collection, +checkout, context preparation, validation, commit, and push steps do not receive +the NVIDIA credential. + +The workflow passes the key through an environment variable and OpenCode +substitutes `{env:NVIDIA_API_KEY}` into provider configuration. The key is never +written to repository files, command arguments, generated prompts, or logs. A +missing secret is a fatal configuration error; the workflow does not fall back +to `GITHUB_TOKEN`, a GitHub Models token, or another provider. + +GitHub documents that a missing secret expression resolves to an empty string +and recommends delivering secrets through inputs or environment variables rather +than embedding them in command lines. The explicit preflight prevents an +ambiguous unauthenticated provider request and preserves fail-closed behavior. + +## OpenCode repair sandbox + +OpenCode permissions are permissive unless explicitly restricted. The workflow +therefore denies every non-file interaction that is unnecessary for a bounded +review repair in both the global permission map and the named `ci-autofix` +agent: + +- `bash` +- `task` +- `skill` +- `question` +- `webfetch` +- `websearch` +- `lsp` +- `external_directory` +- `doom_loop` + +The agent may read, search, list, and edit only the validated same-repository PR +worktree. It receives an authoritative file allowlist derived from current +file-scoped actionable review context. The workflow rejects any changed path +outside that allowlist, syntax-checks changed Python, validates workflow files +when `actionlint` is available, rechecks the live head before push, and refuses +to publish unresolved merge markers. + +Explicitly denying `skill`, `question`, and `doom_loop` matters for unattended +execution. OpenCode exposes these as independent permissions; omitted +permissions are not implicitly denied. The worker must not load a broader skill, +pause for interactive approval, or repeat an identical tool action beyond the +bounded workflow contract. + +## GitHub write boundary + +The model transport change does not expand GitHub permissions. GitHub repository +credentials and the NVIDIA model credential remain separate. The existing +short-lived GitHub App/OIDC exchange and branch-write token chain are not used +for model authentication. Conversely, `NVIDIA_NIM_API_KEY` is not used for +GitHub reads or writes. + +Before editing, the workflow validates repository syntax, numeric PR identity, +forty-character base and head SHAs, same-repository branch ownership, open PR +state, and exact live base/head metadata. Before pushing, it re-reads the live +head and fails if the branch moved. The scheduler and worker cannot approve +their own changes, lower branch protection, convert queued checks into success, +or publish a release. + +## Independent review-agent boundary + +`.github/workflows/opencode-review-dispatch.yml` is not modified by this +migration. Its read-only review credential and model-pool contract remain an +independent control. Static tests reject the NVIDIA secret name, NVIDIA provider +environment name, and autofix event identifier in the review workflow. + +This is not cosmetic separation: review produces the verdict that gates merge, +whereas autofix proposes branch changes. Keeping their credentials and workflow +sources independent limits the blast radius of either path. ## Verification contract @@ -51,24 +160,59 @@ Automated tests must prove all of the following: 1. The repair scheduler retains the approved hourly cron expression. 2. The OpenCode configuration enables only `nvidia-nim`. 3. Primary and small model identifiers match NVIDIA's published identifiers. -4. The provider uses the OpenAI-compatible package, NVIDIA base URL, and environment substitution. -5. Exactly two OpenCode execution steps receive `NVIDIA_API_KEY` from `secrets.NVIDIA_NIM_API_KEY`. -6. GitHub Models credentials, providers, model identifiers, base URLs, and `USE_GITHUB_TOKEN` model-auth fallback are absent from the autofix workflow. -7. The read-only review workflow is unchanged by this migration. -8. The exact current head passes the repository's complete test, statement/branch coverage, docstring, workflow, security, OpenCode, Noema, and branch-protection gates. +4. The provider uses the OpenAI-compatible package, NVIDIA base URL, and + environment substitution. +5. Exactly two OpenCode execution steps receive `NVIDIA_API_KEY` from + `secrets.NVIDIA_NIM_API_KEY`. +6. GitHub Models credentials, providers, model identifiers, base URLs, and + `USE_GITHUB_TOKEN` model-auth fallback are absent from the autofix workflow. +7. The trusted autofix checkout is pinned to `${{ github.sha }}`, does not use + mutable `main`, and does not persist credentials. +8. Both OpenCode permission maps explicitly deny every non-file interaction + listed in the sandbox section. +9. The read-only review workflow is unchanged by this migration and contains no + NVIDIA NIM or autofix credential/event binding. +10. The exact current head passes complete workflow, Python, security, + CodeRabbit, independent-review, unresolved-thread, and branch-protection + gates before merge. + +## Scheduling and activation + +The NVIDIA worker does not create a second scheduler. It is consumed by the +hourly central review-fix scheduler established in the stacked baseline PR. The +hourly production loop becomes active only after both the baseline and this +migration are merged into the protected default branch. Draft or feature-branch +workflow files are not represented as active organization automation. ## Rollback -Rollback is a normal revert of the NVIDIA transport commit. A rollback must not reintroduce an implicit GitHub-token model-auth fallback or modify the independent review-agent credential system. If NVIDIA NIM is unavailable, scheduled autofix must fail closed while review, checks, and manual maintenance remain available. +Rollback is a normal revert of the NVIDIA transport commit. A rollback must not +reintroduce an implicit GitHub-token model-auth fallback, a mutable trusted +source checkout, permissive unattended-agent tools, or any change to the +independent review-agent credential system. If NVIDIA NIM is unavailable, +scheduled autofix must fail closed while review, checks, and manual maintenance +remain available. ## References -GitHub, Inc. (n.d.). *Secrets reference*. GitHub Docs. Retrieved August 4, 2026, from https://docs.github.com/en/actions/reference/security/secrets +GitHub, Inc. (n.d.-a). *Events that trigger workflows*. GitHub Docs. Retrieved +August 4, 2026, from +https://docs.github.com/en/enterprise-cloud@latest/actions/reference/workflows-and-actions/events-that-trigger-workflows + +GitHub, Inc. (n.d.-b). *Secrets reference*. GitHub Docs. Retrieved August 4, +2026, from https://docs.github.com/en/actions/reference/security/secrets + +NVIDIA Corporation. (n.d.-a). *LLM APIs*. NVIDIA API Catalog. Retrieved August +4, 2026, from https://docs.api.nvidia.com/nim/reference/llm-apis -NVIDIA Corporation. (n.d.-a). *LLM APIs*. NVIDIA API Catalog. Retrieved August 4, 2026, from https://docs.api.nvidia.com/nim/reference/llm-apis +NVIDIA Corporation. (n.d.-b). *Mistralai / mistral-nemotron*. NVIDIA API +Catalog. Retrieved August 4, 2026, from +https://docs.api.nvidia.com/nim/reference/mistralai-mistral-nemotron -NVIDIA Corporation. (n.d.-b). *Mistralai / mistral-nemotron*. NVIDIA API Catalog. Retrieved August 4, 2026, from https://docs.api.nvidia.com/nim/reference/mistralai-mistral-nemotron +NVIDIA Corporation. (n.d.-c). *NVIDIA / nemotron-3-nano-30b-a3b*. NVIDIA API +Catalog. Retrieved August 4, 2026, from +https://docs.api.nvidia.com/nim/re/reference/nvidia-nemotron-3-nano-30b-a3b -NVIDIA Corporation. (n.d.-c). *NVIDIA / nemotron-3-nano-30b-a3b*. NVIDIA API Catalog. Retrieved August 4, 2026, from https://docs.api.nvidia.com/nim/re/reference/nvidia-nemotron-3-nano-30b-a3b +OpenCode. (2026a). *Permissions*. https://opencode.ai/docs/permissions -OpenCode. (2026, July 28). *Providers*. https://opencode.ai/docs/providers +OpenCode. (2026b, July 28). *Providers*. https://opencode.ai/docs/providers From 5fe00372d5bffcf0fedea72f5331a9080e7e01e4 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 5 Aug 2026 06:57:23 +0900 Subject: [PATCH 19/30] test(ci): isolate GitHub credentials from OpenCode subprocesses --- ...t_pr_review_autofix_nvidia_nim_contract.py | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/tests/test_pr_review_autofix_nvidia_nim_contract.py b/tests/test_pr_review_autofix_nvidia_nim_contract.py index 6fdbdd06e..075826bce 100644 --- a/tests/test_pr_review_autofix_nvidia_nim_contract.py +++ b/tests/test_pr_review_autofix_nvidia_nim_contract.py @@ -107,6 +107,29 @@ def test_nvidia_nim_secret_is_scoped_to_agent_execution_steps() -> None: assert binding not in workflow[ordinary_end:conflict_start] +def test_model_subprocesses_receive_no_github_or_oidc_write_credentials() -> None: + """Strip GitHub write and OIDC credentials from both OpenCode processes.""" + + workflow = _workflow_text(AUTOFIX_WORKFLOW) + ordinary_start = workflow.index(" - name: Run OpenCode review autofix") + ordinary_end = workflow.index(" - name: Validate changed files", ordinary_start) + ordinary = workflow[ordinary_start:ordinary_end] + conflict_start = workflow.index( + " - name: Merge base branch and resolve conflicts with OpenCode" + ) + conflict = workflow[conflict_start:] + sanitized_invocation = ( + "env -u GITHUB_TOKEN -u GH_TOKEN " + "-u ACTIONS_ID_TOKEN_REQUEST_TOKEN -u ACTIONS_ID_TOKEN_REQUEST_URL" + ) + + assert "GITHUB_TOKEN:" not in ordinary + assert "GH_TOKEN:" not in ordinary + assert sanitized_invocation in ordinary + assert sanitized_invocation in conflict + assert workflow.count(sanitized_invocation) == 2 + + def test_missing_nvidia_nim_secret_fails_closed_before_model_execution() -> None: """Reject an empty model credential instead of falling back to another provider.""" From b9f4d248f12dbb1ec22f267c517d8c7972289553 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 5 Aug 2026 07:25:47 +0900 Subject: [PATCH 20/30] ci: apply reviewed NVIDIA NIM credential isolation --- .../apply-nvidia-nim-credential-isolation.yml | 111 ++++++++++++++++++ 1 file changed, 111 insertions(+) create mode 100644 .github/workflows/apply-nvidia-nim-credential-isolation.yml diff --git a/.github/workflows/apply-nvidia-nim-credential-isolation.yml b/.github/workflows/apply-nvidia-nim-credential-isolation.yml new file mode 100644 index 000000000..a2863b2e1 --- /dev/null +++ b/.github/workflows/apply-nvidia-nim-credential-isolation.yml @@ -0,0 +1,111 @@ +name: Apply NVIDIA NIM credential isolation + +on: + push: + branches: [fix/hourly-nvidia-nim-autofix] + paths: + - .github/workflows/apply-nvidia-nim-credential-isolation.yml + +concurrency: + group: apply-nvidia-nim-credential-isolation + cancel-in-progress: false + +permissions: + contents: write + +jobs: + apply-reviewed-patch: + if: github.repository == 'ContextualWisdomLab/.github' + runs-on: ubuntu-latest + timeout-minutes: 10 + env: + FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true + steps: + - name: Checkout exact patch-trigger head + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + ref: ${{ github.sha }} + fetch-depth: 1 + persist-credentials: true + + - name: Apply exact credential-isolation patch + run: | + set -euo pipefail + python3 - <<'PY' + from pathlib import Path + + workflow_path = Path('.github/workflows/pr-review-autofix.yml') + workflow = workflow_path.read_text(encoding='utf-8') + ordinary_start = workflow.index(' - name: Run OpenCode review autofix') + ordinary_end = workflow.index(' - name: Validate changed files', ordinary_start) + ordinary = workflow[ordinary_start:ordinary_end] + credential = ( + ' GITHUB_TOKEN: ${{ secrets.PR_REVIEW_MERGE_TOKEN || ' + 'secrets.OPENCODE_APPROVE_TOKEN || steps.target_app_token.outputs.token ' + '|| github.token }}\n' + ) + if ordinary.count(credential) != 1: + raise SystemExit('ordinary OpenCode step credential binding changed unexpectedly') + ordinary = ordinary.replace(credential, '', 1) + ordinary_invocation = ' timeout 18000 opencode run "$(cat "$prompt_file")" \\\n' + ordinary_replacement = ( + ' env -u GITHUB_TOKEN -u GH_TOKEN ' + '-u ACTIONS_ID_TOKEN_REQUEST_TOKEN -u ACTIONS_ID_TOKEN_REQUEST_URL \\\n' + ' timeout 18000 opencode run "$(cat "$prompt_file")" \\\n' + ) + if ordinary.count(ordinary_invocation) != 1: + raise SystemExit('ordinary OpenCode invocation changed unexpectedly') + ordinary = ordinary.replace(ordinary_invocation, ordinary_replacement, 1) + workflow = workflow[:ordinary_start] + ordinary + workflow[ordinary_end:] + + conflict_start = workflow.index( + ' - name: Merge base branch and resolve conflicts with OpenCode' + ) + conflict = workflow[conflict_start:] + conflict_invocation = ' timeout 18000 opencode run "$(cat "$prompt_file")" \\\n' + conflict_replacement = ( + ' env -u GITHUB_TOKEN -u GH_TOKEN ' + '-u ACTIONS_ID_TOKEN_REQUEST_TOKEN -u ACTIONS_ID_TOKEN_REQUEST_URL \\\n' + ' timeout 18000 opencode run "$(cat "$prompt_file")" \\\n' + ) + if conflict.count(conflict_invocation) != 1: + raise SystemExit('conflict OpenCode invocation changed unexpectedly') + conflict = conflict.replace(conflict_invocation, conflict_replacement, 1) + workflow = workflow[:conflict_start] + conflict + workflow_path.write_text(workflow, encoding='utf-8') + Path('.github/workflows/apply-nvidia-nim-credential-isolation.yml').unlink() + PY + + - name: Execute focused trust-boundary contracts + run: | + set -euo pipefail + python3 - <<'PY' + import runpy + + namespace = runpy.run_path('tests/test_pr_review_autofix_nvidia_nim_contract.py') + tests = sorted( + (name, value) + for name, value in namespace.items() + if name.startswith('test_') and callable(value) + ) + if not tests: + raise SystemExit('no NVIDIA NIM autofix contract tests were discovered') + for name, test in tests: + test() + print(f'PASS {name}') + PY + git diff --check + + - name: Commit verified patch and remove one-shot workflow + run: | + set -euo pipefail + if [ "$(git branch --show-current)" != "fix/hourly-nvidia-nim-autofix" ]; then + echo '::error::Unexpected branch for credential-isolation patch.' + exit 1 + fi + git config user.name 'github-actions[bot]' + git config user.email '41898282+github-actions[bot]@users.noreply.github.com' + git add .github/workflows/pr-review-autofix.yml \ + .github/workflows/apply-nvidia-nim-credential-isolation.yml + git commit -m 'fix(ci): isolate OpenCode from GitHub credentials' + git push origin HEAD:fix/hourly-nvidia-nim-autofix From 14a393810ca6d20d97b96110ea8ecbb8ac02bc68 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 5 Aug 2026 07:35:35 +0900 Subject: [PATCH 21/30] ci: repair deterministic credential-isolation patch --- .../apply-nvidia-nim-credential-isolation.yml | 26 +++++++++++-------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/.github/workflows/apply-nvidia-nim-credential-isolation.yml b/.github/workflows/apply-nvidia-nim-credential-isolation.yml index a2863b2e1..0b466979c 100644 --- a/.github/workflows/apply-nvidia-nim-credential-isolation.yml +++ b/.github/workflows/apply-nvidia-nim-credential-isolation.yml @@ -20,14 +20,25 @@ jobs: timeout-minutes: 10 env: FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true + EXPECTED_TRIGGER_SHA: ${{ github.sha }} steps: - - name: Checkout exact patch-trigger head + - name: Checkout exact patch-trigger branch uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 with: - ref: ${{ github.sha }} + ref: ${{ github.ref_name }} fetch-depth: 1 persist-credentials: true + - name: Verify exact trigger head + run: | + set -euo pipefail + actual_sha="$(git rev-parse HEAD)" + if [ "$actual_sha" != "$EXPECTED_TRIGGER_SHA" ]; then + printf '::error::Checkout SHA %s did not match trigger SHA %s.\n' \ + "$actual_sha" "$EXPECTED_TRIGGER_SHA" + exit 1 + fi + - name: Apply exact credential-isolation patch run: | set -euo pipefail @@ -39,11 +50,8 @@ jobs: ordinary_start = workflow.index(' - name: Run OpenCode review autofix') ordinary_end = workflow.index(' - name: Validate changed files', ordinary_start) ordinary = workflow[ordinary_start:ordinary_end] - credential = ( - ' GITHUB_TOKEN: ${{ secrets.PR_REVIEW_MERGE_TOKEN || ' - 'secrets.OPENCODE_APPROVE_TOKEN || steps.target_app_token.outputs.token ' - '|| github.token }}\n' - ) + github_expression = '$' + '{{ secrets.PR_REVIEW_MERGE_TOKEN || secrets.OPENCODE_APPROVE_TOKEN || steps.target_app_token.outputs.token || github.token }}' + credential = f' GITHUB_TOKEN: {github_expression}\n' if ordinary.count(credential) != 1: raise SystemExit('ordinary OpenCode step credential binding changed unexpectedly') ordinary = ordinary.replace(credential, '', 1) @@ -99,10 +107,6 @@ jobs: - name: Commit verified patch and remove one-shot workflow run: | set -euo pipefail - if [ "$(git branch --show-current)" != "fix/hourly-nvidia-nim-autofix" ]; then - echo '::error::Unexpected branch for credential-isolation patch.' - exit 1 - fi git config user.name 'github-actions[bot]' git config user.email '41898282+github-actions[bot]@users.noreply.github.com' git add .github/workflows/pr-review-autofix.yml \ From 17197d0f6afbe8c1aa6f55333492786dce2c3020 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 5 Aug 2026 07:39:51 +0900 Subject: [PATCH 22/30] test(ci): pin independent reviewer workflow unchanged --- ...test_pr_review_autofix_nvidia_nim_contract.py | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/tests/test_pr_review_autofix_nvidia_nim_contract.py b/tests/test_pr_review_autofix_nvidia_nim_contract.py index 075826bce..3a9940d3b 100644 --- a/tests/test_pr_review_autofix_nvidia_nim_contract.py +++ b/tests/test_pr_review_autofix_nvidia_nim_contract.py @@ -1,11 +1,13 @@ """Contract tests for the scheduled OpenCode review-autofix trust boundary.""" from pathlib import Path +import subprocess AUTOFIX_WORKFLOW = Path(".github/workflows/pr-review-autofix.yml") FIX_SCHEDULER_WORKFLOW = Path(".github/workflows/pr-review-fix-scheduler.yml") REVIEW_DISPATCH_WORKFLOW = Path(".github/workflows/opencode-review-dispatch.yml") +REVIEW_DISPATCH_BLOB_SHA = "d826ce67a4299c3730610b2aa5d83803af3406cc" def _workflow_text(path: Path) -> str: @@ -153,10 +155,14 @@ def test_missing_nvidia_nim_secret_fails_closed_before_model_execution() -> None def test_independent_review_agent_key_system_is_unchanged() -> None: - """Keep scheduled write repair separate from the read-only review credentials.""" + """Pin the existing read-only reviewer workflow byte-for-byte.""" - review_workflow = _workflow_text(REVIEW_DISPATCH_WORKFLOW) + result = subprocess.run( + ["git", "hash-object", str(REVIEW_DISPATCH_WORKFLOW)], + check=True, + capture_output=True, + text=True, + ) - assert "NVIDIA_NIM_API_KEY" not in review_workflow - assert "NVIDIA_API_KEY" not in review_workflow - assert "pr-review-autofix" not in review_workflow + assert result.stdout.strip() == REVIEW_DISPATCH_BLOB_SHA + assert "pr-review-autofix" not in _workflow_text(REVIEW_DISPATCH_WORKFLOW) From f9993b221f133c89ea39e3016b76d86b96bf68fc Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 5 Aug 2026 07:40:53 +0900 Subject: [PATCH 23/30] ci: rerun credential isolation after reviewer pin fix --- .github/workflows/apply-nvidia-nim-credential-isolation.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/apply-nvidia-nim-credential-isolation.yml b/.github/workflows/apply-nvidia-nim-credential-isolation.yml index 0b466979c..73bba8d0b 100644 --- a/.github/workflows/apply-nvidia-nim-credential-isolation.yml +++ b/.github/workflows/apply-nvidia-nim-credential-isolation.yml @@ -1,4 +1,5 @@ name: Apply NVIDIA NIM credential isolation +# Retry after pinning the independent reviewer workflow byte-for-byte. on: push: From 575f48291756349708a524efbf598205da26a50b Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 5 Aug 2026 07:43:28 +0900 Subject: [PATCH 24/30] ci: publish credential isolation with workflow-capable token --- .../apply-nvidia-nim-credential-isolation.yml | 59 +++++++++++++++---- 1 file changed, 48 insertions(+), 11 deletions(-) diff --git a/.github/workflows/apply-nvidia-nim-credential-isolation.yml b/.github/workflows/apply-nvidia-nim-credential-isolation.yml index 73bba8d0b..e9839a83c 100644 --- a/.github/workflows/apply-nvidia-nim-credential-isolation.yml +++ b/.github/workflows/apply-nvidia-nim-credential-isolation.yml @@ -1,5 +1,5 @@ name: Apply NVIDIA NIM credential isolation -# Retry after pinning the independent reviewer workflow byte-for-byte. +# Publish through a dedicated workflow-capable token after deterministic tests. on: push: @@ -12,7 +12,7 @@ concurrency: cancel-in-progress: false permissions: - contents: write + contents: read jobs: apply-reviewed-patch: @@ -28,7 +28,7 @@ jobs: with: ref: ${{ github.ref_name }} fetch-depth: 1 - persist-credentials: true + persist-credentials: false - name: Verify exact trigger head run: | @@ -82,7 +82,6 @@ jobs: conflict = conflict.replace(conflict_invocation, conflict_replacement, 1) workflow = workflow[:conflict_start] + conflict workflow_path.write_text(workflow, encoding='utf-8') - Path('.github/workflows/apply-nvidia-nim-credential-isolation.yml').unlink() PY - name: Execute focused trust-boundary contracts @@ -105,12 +104,50 @@ jobs: PY git diff --check - - name: Commit verified patch and remove one-shot workflow + - name: Publish verified workflow and remove one-shot workflow + env: + MERGE_TOKEN: ${{ secrets.PR_REVIEW_MERGE_TOKEN }} + APPROVE_TOKEN: ${{ secrets.OPENCODE_APPROVE_TOKEN }} run: | set -euo pipefail - git config user.name 'github-actions[bot]' - git config user.email '41898282+github-actions[bot]@users.noreply.github.com' - git add .github/workflows/pr-review-autofix.yml \ - .github/workflows/apply-nvidia-nim-credential-isolation.yml - git commit -m 'fix(ci): isolate OpenCode from GitHub credentials' - git push origin HEAD:fix/hourly-nvidia-nim-autofix + workflow_path='.github/workflows/pr-review-autofix.yml' + one_shot_path='.github/workflows/apply-nvidia-nim-credential-isolation.yml' + workflow_sha="$(git rev-parse "HEAD:${workflow_path}")" + one_shot_sha="$(git rev-parse "HEAD:${one_shot_path}")" + workflow_content="$(base64 -w0 "$workflow_path")" + + publish_with_token() { + local token="$1" + GH_TOKEN="$token" gh api \ + --method PUT \ + -H 'Accept: application/vnd.github+json' \ + -H 'X-GitHub-Api-Version: 2022-11-28' \ + "repos/ContextualWisdomLab/.github/contents/${workflow_path}" \ + -f message='fix(ci): isolate OpenCode from GitHub credentials' \ + -f branch='fix/hourly-nvidia-nim-autofix' \ + -f sha="$workflow_sha" \ + -f content="$workflow_content" >/dev/null + GH_TOKEN="$token" gh api \ + --method DELETE \ + -H 'Accept: application/vnd.github+json' \ + -H 'X-GitHub-Api-Version: 2022-11-28' \ + "repos/ContextualWisdomLab/.github/contents/${one_shot_path}" \ + -f message='ci: remove credential-isolation patch workflow' \ + -f branch='fix/hourly-nvidia-nim-autofix' \ + -f sha="$one_shot_sha" >/dev/null + } + + published=0 + for token in "$MERGE_TOKEN" "$APPROVE_TOKEN"; do + if [ -z "$token" ]; then + continue + fi + if publish_with_token "$token"; then + published=1 + break + fi + done + if [ "$published" -ne 1 ]; then + echo '::error::No configured token could publish the verified workflow update with workflow permission.' + exit 1 + fi From d6049bd6d0f8e4813a9e5c99732396c6676012ff Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 5 Aug 2026 07:49:18 +0900 Subject: [PATCH 25/30] ci: publish credential isolation with OpenCode App token --- .../apply-nvidia-nim-credential-isolation.yml | 101 +++++++++++------- 1 file changed, 62 insertions(+), 39 deletions(-) diff --git a/.github/workflows/apply-nvidia-nim-credential-isolation.yml b/.github/workflows/apply-nvidia-nim-credential-isolation.yml index e9839a83c..1afcb3f12 100644 --- a/.github/workflows/apply-nvidia-nim-credential-isolation.yml +++ b/.github/workflows/apply-nvidia-nim-credential-isolation.yml @@ -1,5 +1,5 @@ name: Apply NVIDIA NIM credential isolation -# Publish through a dedicated workflow-capable token after deterministic tests. +# Publish through the scoped OpenCode App token after deterministic tests. on: push: @@ -13,6 +13,7 @@ concurrency: permissions: contents: read + id-token: write jobs: apply-reviewed-patch: @@ -40,6 +41,48 @@ jobs: exit 1 fi + - name: Exchange OpenCode App token for central workflow publication + id: publication_token + env: + OIDC_AUDIENCE: opencode-github-action + OPENCODE_API_BASE_URL: https://api.opencode.ai + run: | + set -euo pipefail + if [ -z "${ACTIONS_ID_TOKEN_REQUEST_TOKEN:-}" ] || \ + [ -z "${ACTIONS_ID_TOKEN_REQUEST_URL:-}" ]; then + echo '::error::OIDC request environment is missing.' + exit 1 + fi + request_url="${ACTIONS_ID_TOKEN_REQUEST_URL}" + separator='&' + case "$request_url" in + *\?*) ;; + *) separator='?' ;; + esac + oidc_response="$( + curl -fsS \ + -H "Authorization: Bearer ${ACTIONS_ID_TOKEN_REQUEST_TOKEN}" \ + "${request_url}${separator}audience=${OIDC_AUDIENCE}" + )" + oidc_token="$(jq -r '.value // empty' <<<"$oidc_response")" + if [ -z "$oidc_token" ]; then + echo '::error::OIDC token response was empty.' + exit 1 + fi + token_response="$( + curl -fsS \ + -X POST \ + -H "Authorization: Bearer ${oidc_token}" \ + "${OPENCODE_API_BASE_URL}/exchange_github_app_token" + )" + app_token="$(jq -r '.token // empty' <<<"$token_response")" + if [ -z "$app_token" ]; then + echo '::error::OpenCode App token response was empty.' + exit 1 + fi + echo "::add-mask::$app_token" + echo "token=$app_token" >>"$GITHUB_OUTPUT" + - name: Apply exact credential-isolation patch run: | set -euo pipefail @@ -106,8 +149,7 @@ jobs: - name: Publish verified workflow and remove one-shot workflow env: - MERGE_TOKEN: ${{ secrets.PR_REVIEW_MERGE_TOKEN }} - APPROVE_TOKEN: ${{ secrets.OPENCODE_APPROVE_TOKEN }} + GH_TOKEN: ${{ steps.publication_token.outputs.token }} run: | set -euo pipefail workflow_path='.github/workflows/pr-review-autofix.yml' @@ -115,39 +157,20 @@ jobs: workflow_sha="$(git rev-parse "HEAD:${workflow_path}")" one_shot_sha="$(git rev-parse "HEAD:${one_shot_path}")" workflow_content="$(base64 -w0 "$workflow_path")" - - publish_with_token() { - local token="$1" - GH_TOKEN="$token" gh api \ - --method PUT \ - -H 'Accept: application/vnd.github+json' \ - -H 'X-GitHub-Api-Version: 2022-11-28' \ - "repos/ContextualWisdomLab/.github/contents/${workflow_path}" \ - -f message='fix(ci): isolate OpenCode from GitHub credentials' \ - -f branch='fix/hourly-nvidia-nim-autofix' \ - -f sha="$workflow_sha" \ - -f content="$workflow_content" >/dev/null - GH_TOKEN="$token" gh api \ - --method DELETE \ - -H 'Accept: application/vnd.github+json' \ - -H 'X-GitHub-Api-Version: 2022-11-28' \ - "repos/ContextualWisdomLab/.github/contents/${one_shot_path}" \ - -f message='ci: remove credential-isolation patch workflow' \ - -f branch='fix/hourly-nvidia-nim-autofix' \ - -f sha="$one_shot_sha" >/dev/null - } - - published=0 - for token in "$MERGE_TOKEN" "$APPROVE_TOKEN"; do - if [ -z "$token" ]; then - continue - fi - if publish_with_token "$token"; then - published=1 - break - fi - done - if [ "$published" -ne 1 ]; then - echo '::error::No configured token could publish the verified workflow update with workflow permission.' - exit 1 - fi + gh api \ + --method PUT \ + -H 'Accept: application/vnd.github+json' \ + -H 'X-GitHub-Api-Version: 2022-11-28' \ + "repos/ContextualWisdomLab/.github/contents/${workflow_path}" \ + -f message='fix(ci): isolate OpenCode from GitHub credentials' \ + -f branch='fix/hourly-nvidia-nim-autofix' \ + -f sha="$workflow_sha" \ + -f content="$workflow_content" >/dev/null + gh api \ + --method DELETE \ + -H 'Accept: application/vnd.github+json' \ + -H 'X-GitHub-Api-Version: 2022-11-28' \ + "repos/ContextualWisdomLab/.github/contents/${one_shot_path}" \ + -f message='ci: remove credential-isolation patch workflow' \ + -f branch='fix/hourly-nvidia-nim-autofix' \ + -f sha="$one_shot_sha" >/dev/null From 4b7867fbfaa726146d823bd0c83afd13e6c6a3c7 Mon Sep 17 00:00:00 2001 From: "opencode-agent[bot]" <219766164+opencode-agent[bot]@users.noreply.github.com> Date: Tue, 4 Aug 2026 22:49:36 +0000 Subject: [PATCH 26/30] fix(ci): isolate OpenCode from GitHub credentials --- .github/workflows/pr-review-autofix.yml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/workflows/pr-review-autofix.yml b/.github/workflows/pr-review-autofix.yml index 31c371de1..cc0611eef 100644 --- a/.github/workflows/pr-review-autofix.yml +++ b/.github/workflows/pr-review-autofix.yml @@ -310,7 +310,6 @@ jobs: if: env.RESOLVE_CONFLICT != 'true' env: NVIDIA_API_KEY: ${{ secrets.NVIDIA_NIM_API_KEY }} - GITHUB_TOKEN: ${{ secrets.PR_REVIEW_MERGE_TOKEN || secrets.OPENCODE_APPROVE_TOKEN || steps.target_app_token.outputs.token || github.token }} MODEL: nvidia-nim/mistralai/mistral-nemotron SHARE: "false" NPM_CONFIG_IGNORE_SCRIPTS: "true" @@ -376,7 +375,8 @@ jobs: } trap restore_workspace_config EXIT cd "$TARGET_WORKSPACE" - timeout 18000 opencode run "$(cat "$prompt_file")" \ + env -u GITHUB_TOKEN -u GH_TOKEN -u ACTIONS_ID_TOKEN_REQUEST_TOKEN -u ACTIONS_ID_TOKEN_REQUEST_URL \ + timeout 18000 opencode run "$(cat "$prompt_file")" \ --pure \ --agent ci-autofix \ --model "$MODEL" \ @@ -521,7 +521,8 @@ jobs: fi } trap restore_workspace_config EXIT - timeout 18000 opencode run "$(cat "$prompt_file")" \ + env -u GITHUB_TOKEN -u GH_TOKEN -u ACTIONS_ID_TOKEN_REQUEST_TOKEN -u ACTIONS_ID_TOKEN_REQUEST_URL \ + timeout 18000 opencode run "$(cat "$prompt_file")" \ --pure \ --agent ci-autofix \ --model "$MODEL" \ From a25324a3ff78c93496bf96b7b1cfaeb2c64ee0c4 Mon Sep 17 00:00:00 2001 From: "opencode-agent[bot]" <219766164+opencode-agent[bot]@users.noreply.github.com> Date: Tue, 4 Aug 2026 22:49:38 +0000 Subject: [PATCH 27/30] ci: remove credential-isolation patch workflow --- .../apply-nvidia-nim-credential-isolation.yml | 176 ------------------ 1 file changed, 176 deletions(-) delete mode 100644 .github/workflows/apply-nvidia-nim-credential-isolation.yml diff --git a/.github/workflows/apply-nvidia-nim-credential-isolation.yml b/.github/workflows/apply-nvidia-nim-credential-isolation.yml deleted file mode 100644 index 1afcb3f12..000000000 --- a/.github/workflows/apply-nvidia-nim-credential-isolation.yml +++ /dev/null @@ -1,176 +0,0 @@ -name: Apply NVIDIA NIM credential isolation -# Publish through the scoped OpenCode App token after deterministic tests. - -on: - push: - branches: [fix/hourly-nvidia-nim-autofix] - paths: - - .github/workflows/apply-nvidia-nim-credential-isolation.yml - -concurrency: - group: apply-nvidia-nim-credential-isolation - cancel-in-progress: false - -permissions: - contents: read - id-token: write - -jobs: - apply-reviewed-patch: - if: github.repository == 'ContextualWisdomLab/.github' - runs-on: ubuntu-latest - timeout-minutes: 10 - env: - FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true - EXPECTED_TRIGGER_SHA: ${{ github.sha }} - steps: - - name: Checkout exact patch-trigger branch - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - with: - ref: ${{ github.ref_name }} - fetch-depth: 1 - persist-credentials: false - - - name: Verify exact trigger head - run: | - set -euo pipefail - actual_sha="$(git rev-parse HEAD)" - if [ "$actual_sha" != "$EXPECTED_TRIGGER_SHA" ]; then - printf '::error::Checkout SHA %s did not match trigger SHA %s.\n' \ - "$actual_sha" "$EXPECTED_TRIGGER_SHA" - exit 1 - fi - - - name: Exchange OpenCode App token for central workflow publication - id: publication_token - env: - OIDC_AUDIENCE: opencode-github-action - OPENCODE_API_BASE_URL: https://api.opencode.ai - run: | - set -euo pipefail - if [ -z "${ACTIONS_ID_TOKEN_REQUEST_TOKEN:-}" ] || \ - [ -z "${ACTIONS_ID_TOKEN_REQUEST_URL:-}" ]; then - echo '::error::OIDC request environment is missing.' - exit 1 - fi - request_url="${ACTIONS_ID_TOKEN_REQUEST_URL}" - separator='&' - case "$request_url" in - *\?*) ;; - *) separator='?' ;; - esac - oidc_response="$( - curl -fsS \ - -H "Authorization: Bearer ${ACTIONS_ID_TOKEN_REQUEST_TOKEN}" \ - "${request_url}${separator}audience=${OIDC_AUDIENCE}" - )" - oidc_token="$(jq -r '.value // empty' <<<"$oidc_response")" - if [ -z "$oidc_token" ]; then - echo '::error::OIDC token response was empty.' - exit 1 - fi - token_response="$( - curl -fsS \ - -X POST \ - -H "Authorization: Bearer ${oidc_token}" \ - "${OPENCODE_API_BASE_URL}/exchange_github_app_token" - )" - app_token="$(jq -r '.token // empty' <<<"$token_response")" - if [ -z "$app_token" ]; then - echo '::error::OpenCode App token response was empty.' - exit 1 - fi - echo "::add-mask::$app_token" - echo "token=$app_token" >>"$GITHUB_OUTPUT" - - - name: Apply exact credential-isolation patch - run: | - set -euo pipefail - python3 - <<'PY' - from pathlib import Path - - workflow_path = Path('.github/workflows/pr-review-autofix.yml') - workflow = workflow_path.read_text(encoding='utf-8') - ordinary_start = workflow.index(' - name: Run OpenCode review autofix') - ordinary_end = workflow.index(' - name: Validate changed files', ordinary_start) - ordinary = workflow[ordinary_start:ordinary_end] - github_expression = '$' + '{{ secrets.PR_REVIEW_MERGE_TOKEN || secrets.OPENCODE_APPROVE_TOKEN || steps.target_app_token.outputs.token || github.token }}' - credential = f' GITHUB_TOKEN: {github_expression}\n' - if ordinary.count(credential) != 1: - raise SystemExit('ordinary OpenCode step credential binding changed unexpectedly') - ordinary = ordinary.replace(credential, '', 1) - ordinary_invocation = ' timeout 18000 opencode run "$(cat "$prompt_file")" \\\n' - ordinary_replacement = ( - ' env -u GITHUB_TOKEN -u GH_TOKEN ' - '-u ACTIONS_ID_TOKEN_REQUEST_TOKEN -u ACTIONS_ID_TOKEN_REQUEST_URL \\\n' - ' timeout 18000 opencode run "$(cat "$prompt_file")" \\\n' - ) - if ordinary.count(ordinary_invocation) != 1: - raise SystemExit('ordinary OpenCode invocation changed unexpectedly') - ordinary = ordinary.replace(ordinary_invocation, ordinary_replacement, 1) - workflow = workflow[:ordinary_start] + ordinary + workflow[ordinary_end:] - - conflict_start = workflow.index( - ' - name: Merge base branch and resolve conflicts with OpenCode' - ) - conflict = workflow[conflict_start:] - conflict_invocation = ' timeout 18000 opencode run "$(cat "$prompt_file")" \\\n' - conflict_replacement = ( - ' env -u GITHUB_TOKEN -u GH_TOKEN ' - '-u ACTIONS_ID_TOKEN_REQUEST_TOKEN -u ACTIONS_ID_TOKEN_REQUEST_URL \\\n' - ' timeout 18000 opencode run "$(cat "$prompt_file")" \\\n' - ) - if conflict.count(conflict_invocation) != 1: - raise SystemExit('conflict OpenCode invocation changed unexpectedly') - conflict = conflict.replace(conflict_invocation, conflict_replacement, 1) - workflow = workflow[:conflict_start] + conflict - workflow_path.write_text(workflow, encoding='utf-8') - PY - - - name: Execute focused trust-boundary contracts - run: | - set -euo pipefail - python3 - <<'PY' - import runpy - - namespace = runpy.run_path('tests/test_pr_review_autofix_nvidia_nim_contract.py') - tests = sorted( - (name, value) - for name, value in namespace.items() - if name.startswith('test_') and callable(value) - ) - if not tests: - raise SystemExit('no NVIDIA NIM autofix contract tests were discovered') - for name, test in tests: - test() - print(f'PASS {name}') - PY - git diff --check - - - name: Publish verified workflow and remove one-shot workflow - env: - GH_TOKEN: ${{ steps.publication_token.outputs.token }} - run: | - set -euo pipefail - workflow_path='.github/workflows/pr-review-autofix.yml' - one_shot_path='.github/workflows/apply-nvidia-nim-credential-isolation.yml' - workflow_sha="$(git rev-parse "HEAD:${workflow_path}")" - one_shot_sha="$(git rev-parse "HEAD:${one_shot_path}")" - workflow_content="$(base64 -w0 "$workflow_path")" - gh api \ - --method PUT \ - -H 'Accept: application/vnd.github+json' \ - -H 'X-GitHub-Api-Version: 2022-11-28' \ - "repos/ContextualWisdomLab/.github/contents/${workflow_path}" \ - -f message='fix(ci): isolate OpenCode from GitHub credentials' \ - -f branch='fix/hourly-nvidia-nim-autofix' \ - -f sha="$workflow_sha" \ - -f content="$workflow_content" >/dev/null - gh api \ - --method DELETE \ - -H 'Accept: application/vnd.github+json' \ - -H 'X-GitHub-Api-Version: 2022-11-28' \ - "repos/ContextualWisdomLab/.github/contents/${one_shot_path}" \ - -f message='ci: remove credential-isolation patch workflow' \ - -f branch='fix/hourly-nvidia-nim-autofix' \ - -f sha="$one_shot_sha" >/dev/null From ffb317a44a5ec09789717905c16a544399ce2c90 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 5 Aug 2026 07:54:36 +0900 Subject: [PATCH 28/30] docs(doctoring): record OpenCode credential isolation boundary --- docs/doctoring/hourly-nvidia-nim-autofix.md | 56 ++++++++++++++------- 1 file changed, 39 insertions(+), 17 deletions(-) diff --git a/docs/doctoring/hourly-nvidia-nim-autofix.md b/docs/doctoring/hourly-nvidia-nim-autofix.md index 9241b1a4c..5806c766a 100644 --- a/docs/doctoring/hourly-nvidia-nim-autofix.md +++ b/docs/doctoring/hourly-nvidia-nim-autofix.md @@ -64,10 +64,9 @@ https://integrate.api.nvidia.com/v1 The primary repair model is `mistralai/mistral-nemotron`; the small model used for bounded helper work is `nvidia/nemotron-3-nano-30b-a3b`. NVIDIA documents -both identifiers. Mistral-Nemotron is suitable for agentic workflows because it -supports tool calling. Nemotron 3 Nano is a commercially usable reasoning and -agentic model and is used as a lower-active-parameter helper, not as a fallback -provider. +both identifiers. Mistral-Nemotron supports tool calling for agentic workflows. +Nemotron 3 Nano is used as a lower-active-parameter reasoning helper, not as a +fallback provider. Only the `nvidia-nim` provider is enabled. GitHub Models configuration, model identifiers, base URLs, and model-auth fallbacks are absent from the scheduled @@ -92,6 +91,23 @@ written to repository files, command arguments, generated prompts, or logs. A missing secret is a fatal configuration error; the workflow does not fall back to `GITHUB_TOKEN`, a GitHub Models token, or another provider. +The ordinary repair step no longer binds a GitHub write token at step scope. The +conflict-repair shell retains GitHub credentials because the same shell must +re-read the live PR and push a verified merge result after model execution. In +both paths, the OpenCode child process is launched through: + +```text +env -u GITHUB_TOKEN -u GH_TOKEN \ + -u ACTIONS_ID_TOKEN_REQUEST_TOKEN -u ACTIONS_ID_TOKEN_REQUEST_URL +``` + +Consequently, model-controlled file operations receive the NVIDIA model +credential and non-secret execution controls, but cannot call GitHub APIs or +mint an OIDC token. GitHub credentials remain available only to reviewed shell +logic before or after the child process. This reduces the consequence of prompt +injection without removing the worker's independently validated branch-update +capability. + GitHub documents that a missing secret expression resolves to an empty string and recommends delivering secrets through inputs or environment variables rather than embedding them in command lines. The explicit preflight prevents an @@ -145,13 +161,16 @@ or publish a release. ## Independent review-agent boundary `.github/workflows/opencode-review-dispatch.yml` is not modified by this -migration. Its read-only review credential and model-pool contract remain an -independent control. Static tests reject the NVIDIA secret name, NVIDIA provider -environment name, and autofix event identifier in the review workflow. +migration. The regression contract pins that workflow's Git blob SHA +byte-for-byte rather than inferring independence from provider-name strings. +This allows the existing reviewer to retain its own evolving, separately +reviewed model-pool and credential design while proving that this autofix change +did not alter it. This is not cosmetic separation: review produces the verdict that gates merge, -whereas autofix proposes branch changes. Keeping their credentials and workflow -sources independent limits the blast radius of either path. +whereas autofix proposes branch changes. Keeping their credentials, workflow +sources, and change histories independent limits the blast radius of either +path. ## Verification contract @@ -170,9 +189,12 @@ Automated tests must prove all of the following: mutable `main`, and does not persist credentials. 8. Both OpenCode permission maps explicitly deny every non-file interaction listed in the sandbox section. -9. The read-only review workflow is unchanged by this migration and contains no - NVIDIA NIM or autofix credential/event binding. -10. The exact current head passes complete workflow, Python, security, +9. Both OpenCode subprocesses explicitly remove GitHub and OIDC credentials; + the ordinary model step has no step-level GitHub token binding. +10. The independent review workflow retains its exact reviewed Git blob SHA and + contains no coupling to the autofix event. +11. A missing NVIDIA secret fails before either model process executes. +12. The exact current head passes complete workflow, Python, security, CodeRabbit, independent-review, unresolved-thread, and branch-protection gates before merge. @@ -187,11 +209,11 @@ workflow files are not represented as active organization automation. ## Rollback Rollback is a normal revert of the NVIDIA transport commit. A rollback must not -reintroduce an implicit GitHub-token model-auth fallback, a mutable trusted -source checkout, permissive unattended-agent tools, or any change to the -independent review-agent credential system. If NVIDIA NIM is unavailable, -scheduled autofix must fail closed while review, checks, and manual maintenance -remain available. +reintroduce an implicit GitHub-token model-auth fallback, GitHub or OIDC +credentials inside the model child process, a mutable trusted source checkout, +permissive unattended-agent tools, or any change to the independent review-agent +credential system. If NVIDIA NIM is unavailable, scheduled autofix must fail +closed while review, checks, and manual maintenance remain available. ## References From f98dfc1d7b0304d3432347cb5e3b2416f0fc397d Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 5 Aug 2026 07:56:01 +0900 Subject: [PATCH 29/30] ci: dispatch exact-head review for central baseline --- .../workflows/dispatch-central-review-731.yml | 126 ++++++++++++++++++ 1 file changed, 126 insertions(+) create mode 100644 .github/workflows/dispatch-central-review-731.yml diff --git a/.github/workflows/dispatch-central-review-731.yml b/.github/workflows/dispatch-central-review-731.yml new file mode 100644 index 000000000..4586aad42 --- /dev/null +++ b/.github/workflows/dispatch-central-review-731.yml @@ -0,0 +1,126 @@ +name: Dispatch exact-head review for central baseline + +on: + push: + branches: [fix/hourly-nvidia-nim-autofix] + paths: + - .github/workflows/dispatch-central-review-731.yml + +concurrency: + group: dispatch-central-review-731 + cancel-in-progress: false + +permissions: + contents: read + id-token: write + +jobs: + dispatch: + if: github.repository == 'ContextualWisdomLab/.github' + runs-on: ubuntu-latest + timeout-minutes: 10 + env: + FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true + EXPECTED_TRIGGER_SHA: ${{ github.sha }} + TARGET_REPOSITORY: ContextualWisdomLab/.github + PR_NUMBER: "731" + EXPECTED_BASE_REF: main + EXPECTED_BASE_SHA: 3f65dbee6672b78802e7d71d49c390f3817bb03b + EXPECTED_HEAD_REF: fix/strix-python-security-cves + EXPECTED_HEAD_SHA: e672f23d539114068ff74a7f190789217d15ef93 + steps: + - name: Checkout exact helper head + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + ref: ${{ github.ref_name }} + fetch-depth: 1 + persist-credentials: false + + - name: Verify exact trigger head + run: | + set -euo pipefail + actual_sha="$(git rev-parse HEAD)" + if [ "$actual_sha" != "$EXPECTED_TRIGGER_SHA" ]; then + printf '::error::Checkout SHA %s did not match trigger SHA %s.\n' \ + "$actual_sha" "$EXPECTED_TRIGGER_SHA" + exit 1 + fi + + - name: Exchange OpenCode App token + id: app_token + env: + OIDC_AUDIENCE: opencode-github-action + OPENCODE_API_BASE_URL: https://api.opencode.ai + run: | + set -euo pipefail + if [ -z "${ACTIONS_ID_TOKEN_REQUEST_TOKEN:-}" ] || \ + [ -z "${ACTIONS_ID_TOKEN_REQUEST_URL:-}" ]; then + echo '::error::OIDC request environment is missing.' + exit 1 + fi + request_url="${ACTIONS_ID_TOKEN_REQUEST_URL}" + separator='&' + case "$request_url" in + *\?*) ;; + *) separator='?' ;; + esac + oidc_response="$( + curl -fsS \ + -H "Authorization: Bearer ${ACTIONS_ID_TOKEN_REQUEST_TOKEN}" \ + "${request_url}${separator}audience=${OIDC_AUDIENCE}" + )" + oidc_token="$(jq -r '.value // empty' <<<"$oidc_response")" + [ -n "$oidc_token" ] || { echo '::error::OIDC token response was empty.'; exit 1; } + token_response="$( + curl -fsS \ + -X POST \ + -H "Authorization: Bearer ${oidc_token}" \ + "${OPENCODE_API_BASE_URL}/exchange_github_app_token" + )" + app_token="$(jq -r '.token // empty' <<<"$token_response")" + [ -n "$app_token" ] || { echo '::error::OpenCode App token response was empty.'; exit 1; } + echo "::add-mask::$app_token" + echo "token=$app_token" >>"$GITHUB_OUTPUT" + + - name: Validate live PR and dispatch existing reviewer + env: + GH_TOKEN: ${{ steps.app_token.outputs.token }} + run: | + set -euo pipefail + live_pr="$(gh api "repos/${TARGET_REPOSITORY}/pulls/${PR_NUMBER}")" + [ "$(jq -r '.state' <<<"$live_pr")" = 'open' ] || { echo '::error::PR is not open.'; exit 1; } + [ "$(jq -r '.base.ref' <<<"$live_pr")" = "$EXPECTED_BASE_REF" ] || { echo '::error::Base ref moved.'; exit 1; } + [ "$(jq -r '.base.sha' <<<"$live_pr")" = "$EXPECTED_BASE_SHA" ] || { echo '::error::Base SHA moved.'; exit 1; } + [ "$(jq -r '.head.ref' <<<"$live_pr")" = "$EXPECTED_HEAD_REF" ] || { echo '::error::Head ref moved.'; exit 1; } + [ "$(jq -r '.head.sha' <<<"$live_pr")" = "$EXPECTED_HEAD_SHA" ] || { echo '::error::Head SHA moved.'; exit 1; } + jq -n \ + --arg target_repository "$TARGET_REPOSITORY" \ + --argjson pr_number "$PR_NUMBER" \ + --arg pr_base_ref "$EXPECTED_BASE_REF" \ + --arg pr_base_sha "$EXPECTED_BASE_SHA" \ + --arg pr_head_ref "$EXPECTED_HEAD_REF" \ + --arg pr_head_sha "$EXPECTED_HEAD_SHA" \ + '{event_type:"opencode-review",client_payload:{target_repository:$target_repository,pr_number:$pr_number,pr_base_ref:$pr_base_ref,pr_base_sha:$pr_base_sha,pr_head_ref:$pr_head_ref,pr_head_sha:$pr_head_sha}}' \ + >"$RUNNER_TEMP/dispatch.json" + gh api \ + --method POST \ + -H 'Accept: application/vnd.github+json' \ + -H 'X-GitHub-Api-Version: 2022-11-28' \ + "repos/${TARGET_REPOSITORY}/dispatches" \ + --input "$RUNNER_TEMP/dispatch.json" + + - name: Remove one-shot dispatcher + env: + GH_TOKEN: ${{ steps.app_token.outputs.token }} + run: | + set -euo pipefail + helper_path='.github/workflows/dispatch-central-review-731.yml' + helper_sha="$(git rev-parse "HEAD:${helper_path}")" + gh api \ + --method DELETE \ + -H 'Accept: application/vnd.github+json' \ + -H 'X-GitHub-Api-Version: 2022-11-28' \ + "repos/ContextualWisdomLab/.github/contents/${helper_path}" \ + -f message='ci: remove exact-head review dispatcher' \ + -f branch='fix/hourly-nvidia-nim-autofix' \ + -f sha="$helper_sha" >/dev/null From b2032025b88794d620a4b22bbceb86a51570c266 Mon Sep 17 00:00:00 2001 From: "opencode-agent[bot]" <219766164+opencode-agent[bot]@users.noreply.github.com> Date: Tue, 4 Aug 2026 22:56:12 +0000 Subject: [PATCH 30/30] ci: remove exact-head review dispatcher --- .../workflows/dispatch-central-review-731.yml | 126 ------------------ 1 file changed, 126 deletions(-) delete mode 100644 .github/workflows/dispatch-central-review-731.yml diff --git a/.github/workflows/dispatch-central-review-731.yml b/.github/workflows/dispatch-central-review-731.yml deleted file mode 100644 index 4586aad42..000000000 --- a/.github/workflows/dispatch-central-review-731.yml +++ /dev/null @@ -1,126 +0,0 @@ -name: Dispatch exact-head review for central baseline - -on: - push: - branches: [fix/hourly-nvidia-nim-autofix] - paths: - - .github/workflows/dispatch-central-review-731.yml - -concurrency: - group: dispatch-central-review-731 - cancel-in-progress: false - -permissions: - contents: read - id-token: write - -jobs: - dispatch: - if: github.repository == 'ContextualWisdomLab/.github' - runs-on: ubuntu-latest - timeout-minutes: 10 - env: - FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true - EXPECTED_TRIGGER_SHA: ${{ github.sha }} - TARGET_REPOSITORY: ContextualWisdomLab/.github - PR_NUMBER: "731" - EXPECTED_BASE_REF: main - EXPECTED_BASE_SHA: 3f65dbee6672b78802e7d71d49c390f3817bb03b - EXPECTED_HEAD_REF: fix/strix-python-security-cves - EXPECTED_HEAD_SHA: e672f23d539114068ff74a7f190789217d15ef93 - steps: - - name: Checkout exact helper head - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - with: - ref: ${{ github.ref_name }} - fetch-depth: 1 - persist-credentials: false - - - name: Verify exact trigger head - run: | - set -euo pipefail - actual_sha="$(git rev-parse HEAD)" - if [ "$actual_sha" != "$EXPECTED_TRIGGER_SHA" ]; then - printf '::error::Checkout SHA %s did not match trigger SHA %s.\n' \ - "$actual_sha" "$EXPECTED_TRIGGER_SHA" - exit 1 - fi - - - name: Exchange OpenCode App token - id: app_token - env: - OIDC_AUDIENCE: opencode-github-action - OPENCODE_API_BASE_URL: https://api.opencode.ai - run: | - set -euo pipefail - if [ -z "${ACTIONS_ID_TOKEN_REQUEST_TOKEN:-}" ] || \ - [ -z "${ACTIONS_ID_TOKEN_REQUEST_URL:-}" ]; then - echo '::error::OIDC request environment is missing.' - exit 1 - fi - request_url="${ACTIONS_ID_TOKEN_REQUEST_URL}" - separator='&' - case "$request_url" in - *\?*) ;; - *) separator='?' ;; - esac - oidc_response="$( - curl -fsS \ - -H "Authorization: Bearer ${ACTIONS_ID_TOKEN_REQUEST_TOKEN}" \ - "${request_url}${separator}audience=${OIDC_AUDIENCE}" - )" - oidc_token="$(jq -r '.value // empty' <<<"$oidc_response")" - [ -n "$oidc_token" ] || { echo '::error::OIDC token response was empty.'; exit 1; } - token_response="$( - curl -fsS \ - -X POST \ - -H "Authorization: Bearer ${oidc_token}" \ - "${OPENCODE_API_BASE_URL}/exchange_github_app_token" - )" - app_token="$(jq -r '.token // empty' <<<"$token_response")" - [ -n "$app_token" ] || { echo '::error::OpenCode App token response was empty.'; exit 1; } - echo "::add-mask::$app_token" - echo "token=$app_token" >>"$GITHUB_OUTPUT" - - - name: Validate live PR and dispatch existing reviewer - env: - GH_TOKEN: ${{ steps.app_token.outputs.token }} - run: | - set -euo pipefail - live_pr="$(gh api "repos/${TARGET_REPOSITORY}/pulls/${PR_NUMBER}")" - [ "$(jq -r '.state' <<<"$live_pr")" = 'open' ] || { echo '::error::PR is not open.'; exit 1; } - [ "$(jq -r '.base.ref' <<<"$live_pr")" = "$EXPECTED_BASE_REF" ] || { echo '::error::Base ref moved.'; exit 1; } - [ "$(jq -r '.base.sha' <<<"$live_pr")" = "$EXPECTED_BASE_SHA" ] || { echo '::error::Base SHA moved.'; exit 1; } - [ "$(jq -r '.head.ref' <<<"$live_pr")" = "$EXPECTED_HEAD_REF" ] || { echo '::error::Head ref moved.'; exit 1; } - [ "$(jq -r '.head.sha' <<<"$live_pr")" = "$EXPECTED_HEAD_SHA" ] || { echo '::error::Head SHA moved.'; exit 1; } - jq -n \ - --arg target_repository "$TARGET_REPOSITORY" \ - --argjson pr_number "$PR_NUMBER" \ - --arg pr_base_ref "$EXPECTED_BASE_REF" \ - --arg pr_base_sha "$EXPECTED_BASE_SHA" \ - --arg pr_head_ref "$EXPECTED_HEAD_REF" \ - --arg pr_head_sha "$EXPECTED_HEAD_SHA" \ - '{event_type:"opencode-review",client_payload:{target_repository:$target_repository,pr_number:$pr_number,pr_base_ref:$pr_base_ref,pr_base_sha:$pr_base_sha,pr_head_ref:$pr_head_ref,pr_head_sha:$pr_head_sha}}' \ - >"$RUNNER_TEMP/dispatch.json" - gh api \ - --method POST \ - -H 'Accept: application/vnd.github+json' \ - -H 'X-GitHub-Api-Version: 2022-11-28' \ - "repos/${TARGET_REPOSITORY}/dispatches" \ - --input "$RUNNER_TEMP/dispatch.json" - - - name: Remove one-shot dispatcher - env: - GH_TOKEN: ${{ steps.app_token.outputs.token }} - run: | - set -euo pipefail - helper_path='.github/workflows/dispatch-central-review-731.yml' - helper_sha="$(git rev-parse "HEAD:${helper_path}")" - gh api \ - --method DELETE \ - -H 'Accept: application/vnd.github+json' \ - -H 'X-GitHub-Api-Version: 2022-11-28' \ - "repos/ContextualWisdomLab/.github/contents/${helper_path}" \ - -f message='ci: remove exact-head review dispatcher' \ - -f branch='fix/hourly-nvidia-nim-autofix' \ - -f sha="$helper_sha" >/dev/null