From 5f8d748549309b8962a1e5d42a86c9ad546442c6 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Sun, 16 Aug 2026 16:12:05 +0000 Subject: [PATCH 1/3] ci: switch local OpenCode from GitHub Models to NVIDIA NIM ContextualWisdomLab no longer uses GitHub Models. Point opencode.jsonc at nvidia-nim only, with NVIDIA_API_KEY and the Nemotron Super default. Co-authored-by: Seongho Bae --- opencode.jsonc | 60 ++++++------------- .../tests/test_supply_chain_policy.py | 30 ++++++++-- 2 files changed, 44 insertions(+), 46 deletions(-) diff --git a/opencode.jsonc b/opencode.jsonc index 888aa2375..eeca34f1b 100644 --- a/opencode.jsonc +++ b/opencode.jsonc @@ -1,8 +1,8 @@ { "$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/nvidia/llama-3.3-nemotron-super-49b-v1.5", + "small_model": "nvidia-nim/meta/llama-3.3-70b-instruct", + "enabled_providers": ["nvidia-nim"], "mcp": { "codegraph": { "type": "local", @@ -37,56 +37,32 @@ } }, "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", + "nvidia/llama-3.3-nemotron-super-49b-v1.5": { + "name": "NVIDIA Llama 3.3 Nemotron Super 49B v1.5", "tool_call": true, "reasoning": true, + "options": { + "reasoningEffort": "high" + }, "limit": { - "context": 200000, - "output": 100000 + "context": 131072, + "output": 32768 } }, - "deepseek/deepseek-r1-0528": { - "name": "DeepSeek R1 0528", + "meta/llama-3.3-70b-instruct": { + "name": "Meta Llama 3.3 70B Instruct", "tool_call": true, - "reasoning": true, - "limit": { - "context": 128000, - "output": 4096 - } - }, - "deepseek/deepseek-v3-0324": { - "name": "DeepSeek V3 0324", - "tool_call": true, - "limit": { - "context": 128000, - "output": 4096 - } - }, - "openai/o3": { - "name": "OpenAI o3", - "tool_call": true, - "reasoning": true, - "limit": { - "context": 200000, - "output": 100000 - } - }, - "openai/o4-mini": { - "name": "OpenAI o4-mini", - "tool_call": true, - "reasoning": true, "limit": { - "context": 200000, - "output": 100000 + "context": 131072, + "output": 16384 } } } diff --git a/services/analysis-engine/tests/test_supply_chain_policy.py b/services/analysis-engine/tests/test_supply_chain_policy.py index ab43df89f..df27f41df 100644 --- a/services/analysis-engine/tests/test_supply_chain_policy.py +++ b/services/analysis-engine/tests/test_supply_chain_policy.py @@ -5073,14 +5073,36 @@ def test_opencode_approval_write_failure_updates_overview_only() -> None: assert "source-backed repository findings" in policy +def test_opencode_uses_nvidia_nim_only() -> None: + """Ensure local OpenCode is NVIDIA NIM only and does not use GitHub Models.""" + repo_root = Path(__file__).resolve().parents[3] + opencode_text = (repo_root / "opencode.jsonc").read_text(encoding="utf-8") + opencode_config = json.loads(opencode_text) + + assert opencode_config["model"] == "nvidia-nim/nvidia/llama-3.3-nemotron-super-49b-v1.5" + assert opencode_config["enabled_providers"] == ["nvidia-nim"] + assert set(opencode_config["provider"]) == {"nvidia-nim"} + + nim_provider = opencode_config["provider"]["nvidia-nim"] + assert nim_provider["options"]["baseURL"] == "https://integrate.api.nvidia.com/v1" + assert nim_provider["options"]["apiKey"] == "{env:NVIDIA_API_KEY}" + assert "nvidia/llama-3.3-nemotron-super-49b-v1.5" in nim_provider["models"] + + leftover_tokens = ( + "github-models", + "STRIX_GITHUB_MODELS_TOKEN", + "COPILOT_GITHUB_TOKEN", + "openai/gpt-5", + "models.github.ai", + ) + for leftover in leftover_tokens: + assert leftover not in opencode_text + + def test_pr_review_merge_scheduler_uses_central_mutation_credential() -> None: """Ensure mechanical PR queue handling uses the central mutation credential.""" - repo_root = Path(__file__).resolve().parents[3] policy = central_required_workflow_policy_text() - opencode_config = (repo_root / "opencode.jsonc").read_text(encoding="utf-8") - assert '"openai/o3"' in opencode_config - assert '"openai/o4-mini"' in opencode_config assert_local_review_workflows_removed() assert "selected workflow mutation" in policy assert "credential, not by a maintainer's local `gh` session" in policy From 8f3f1631a69c73b27100a35ca4296fc7ab3e933c Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Sun, 16 Aug 2026 16:19:20 +0000 Subject: [PATCH 2/3] test(ci): lock local OpenCode small_model to NVIDIA NIM Pin small_model and leftover GitHub Models ids in the supply-chain contract, and document that local OpenCode binds NVIDIA_API_KEY while CI maps the NVIDIA_NIM_API_KEY secret. Co-authored-by: Seongho Bae --- AGENTS.md | 7 +++++++ docs/workflow/pr-review-merge-scheduler.md | 15 +++++++++++---- .../tests/test_supply_chain_policy.py | 4 ++++ 3 files changed, 22 insertions(+), 4 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index fca448ce9..5c98b43a5 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -98,3 +98,10 @@ This section applies to any agent (Claude, Codex, Cursor, opencode, ...) working - Do not justify risky debug defaults as temporary shortcuts. - Do not defer dependency review, SBOM generation, or supply-chain checks to a later phase. - Do not defer Windows or macOS build enforcement to a later phase. + +## Local OpenCode contract +- Root `opencode.jsonc` is NVIDIA NIM only: `enabled_providers` is `["nvidia-nim"]`. +- The provider binds `{env:NVIDIA_API_KEY}` to `https://integrate.api.nvidia.com/v1`. That env name is the NVIDIA/OpenCode client contract. +- The organization GitHub secret is `NVIDIA_NIM_API_KEY`. CI maps that secret onto process env `NVIDIA_API_KEY`. Do not rename the local OpenCode binding to `{env:NVIDIA_NIM_API_KEY}`. +- Never introduce `COPILOT_GITHUB_TOKEN`, `github-models`, or `STRIX_GITHUB_MODELS_TOKEN` in `opencode.jsonc`. +- Do not change existing review-agent credential names or routing. Central OpenCode Review and the PR review/merge scheduler stay in `ContextualWisdomLab/.github`. diff --git a/docs/workflow/pr-review-merge-scheduler.md b/docs/workflow/pr-review-merge-scheduler.md index adcb9baeb..cb6dcaedf 100644 --- a/docs/workflow/pr-review-merge-scheduler.md +++ b/docs/workflow/pr-review-merge-scheduler.md @@ -16,6 +16,12 @@ workflow `GITHUB_TOKEN`, depending on which credential can perform the guarded r The local repository may keep product CI, security, release, and build workflows. It must not restore repo-local copies of `opencode-review.yml`, `pr-review-merge-scheduler.yml`, or their `scripts/ci` helper implementations. +Local developer OpenCode (`opencode.jsonc`) is a separate trust boundary from those central +review workflows. It uses NVIDIA NIM only, binds `{env:NVIDIA_API_KEY}` at +`https://integrate.api.nvidia.com/v1`, and must not restore GitHub Models, Copilot tokens, or +review-agent secrets. The organization secret name remains `NVIDIA_NIM_API_KEY`; CI maps that +secret onto `NVIDIA_API_KEY` for the OpenCode client. + ## Behavior - Inspect non-draft PRs targeting the repository default branch, currently `develop`. @@ -44,8 +50,8 @@ repo-local copies of `opencode-review.yml`, `pr-review-merge-scheduler.yml`, or ## Security Notes -- Attack surface: organization required workflows with write access to PR comments, PR branch updates, and normal merges. -- Trust boundary touched: GitHub repository governance, PR review state, status checks, and CodeRabbit review requests. +- Attack surface: organization required workflows with write access to PR comments, PR branch updates, and normal merges; local `opencode.jsonc` that calls NVIDIA NIM over HTTPS. +- Trust boundary touched: GitHub repository governance, PR review state, status checks, CodeRabbit review requests, and the local OpenCode provider allowlist (`nvidia-nim` + `{env:NVIDIA_API_KEY}`). - Realistic threats: spammed review comments, merging a PR with unresolved conversations, merging without required checks, or hiding conflicts behind automation. - Mitigations: central required workflow source pinning, idempotent per-head review comment marker, explicit unresolved-thread check, retry-bounded GitHub API reads, required-check verification @@ -53,5 +59,6 @@ repo-local copies of `opencode-review.yml`, `pr-review-merge-scheduler.yml`, or - Remaining risk: CodeRabbit and GitHub check state can be delayed or stale; the scheduler therefore only advances eligible PRs and leaves code-fix work to agents or maintainers. - Test points: organization ruleset inheritance, current-head OpenCode approval, unresolved review thread count, required-check rollup, approved behind PR, approved conflict-free PR, approved dirty PR, - external failed-check classification, provider/runtime failure summary, and Strix evidence lookup - scope diagnostics. + external failed-check classification, provider/runtime failure summary, Strix evidence lookup + scope diagnostics, and local `opencode.jsonc` NIM-only contract (`small_model`, `{env:NVIDIA_API_KEY}`, + no GitHub Models / Copilot leftovers). diff --git a/services/analysis-engine/tests/test_supply_chain_policy.py b/services/analysis-engine/tests/test_supply_chain_policy.py index df27f41df..cc52572ef 100644 --- a/services/analysis-engine/tests/test_supply_chain_policy.py +++ b/services/analysis-engine/tests/test_supply_chain_policy.py @@ -5080,6 +5080,7 @@ def test_opencode_uses_nvidia_nim_only() -> None: opencode_config = json.loads(opencode_text) assert opencode_config["model"] == "nvidia-nim/nvidia/llama-3.3-nemotron-super-49b-v1.5" + assert opencode_config["small_model"] == "nvidia-nim/meta/llama-3.3-70b-instruct" assert opencode_config["enabled_providers"] == ["nvidia-nim"] assert set(opencode_config["provider"]) == {"nvidia-nim"} @@ -5087,12 +5088,15 @@ def test_opencode_uses_nvidia_nim_only() -> None: assert nim_provider["options"]["baseURL"] == "https://integrate.api.nvidia.com/v1" assert nim_provider["options"]["apiKey"] == "{env:NVIDIA_API_KEY}" assert "nvidia/llama-3.3-nemotron-super-49b-v1.5" in nim_provider["models"] + assert "meta/llama-3.3-70b-instruct" in nim_provider["models"] leftover_tokens = ( "github-models", "STRIX_GITHUB_MODELS_TOKEN", "COPILOT_GITHUB_TOKEN", "openai/gpt-5", + "openai/o3", + "openai/o4-mini", "models.github.ai", ) for leftover in leftover_tokens: From acf1f37e79dbc1f5df259583011962ce2edb82cf Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Sun, 16 Aug 2026 16:29:01 +0000 Subject: [PATCH 3/3] test(ci): drop unsupported NIM reasoningEffort knob Nemotron Super 49B v1.5 uses chat_template_kwargs, not OpenAI-style reasoningEffort. Lock the absence, keep {env:NVIDIA_API_KEY}, and document the local-vs-central OpenCode split. Co-authored-by: Seongho Bae --- AGENTS.md | 1 + CLAUDE.md | 1 + .../doctoring/opencode-nvidia-nim-contract.md | 51 +++++++++++++++++++ docs/workflow/pr-review-merge-scheduler.md | 8 +-- opencode.jsonc | 3 -- .../test_opencode_nim_secret_contract.py | 42 +++++++++++++++ .../tests/test_supply_chain_policy.py | 4 ++ 7 files changed, 103 insertions(+), 7 deletions(-) create mode 100644 docs/doctoring/opencode-nvidia-nim-contract.md create mode 100644 services/analysis-engine/tests/test_opencode_nim_secret_contract.py diff --git a/AGENTS.md b/AGENTS.md index 5c98b43a5..8798e0d03 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -104,4 +104,5 @@ This section applies to any agent (Claude, Codex, Cursor, opencode, ...) working - The provider binds `{env:NVIDIA_API_KEY}` to `https://integrate.api.nvidia.com/v1`. That env name is the NVIDIA/OpenCode client contract. - The organization GitHub secret is `NVIDIA_NIM_API_KEY`. CI maps that secret onto process env `NVIDIA_API_KEY`. Do not rename the local OpenCode binding to `{env:NVIDIA_NIM_API_KEY}`. - Never introduce `COPILOT_GITHUB_TOKEN`, `github-models`, or `STRIX_GITHUB_MODELS_TOKEN` in `opencode.jsonc`. +- Do not set OpenAI-style `reasoningEffort` on `nvidia/llama-3.3-nemotron-super-49b-v1.5`. NIM uses `chat_template_kwargs` for that model (see `docs/doctoring/opencode-nvidia-nim-contract.md`). - Do not change existing review-agent credential names or routing. Central OpenCode Review and the PR review/merge scheduler stay in `ContextualWisdomLab/.github`. diff --git a/CLAUDE.md b/CLAUDE.md index 82c2c704a..9dfb55d05 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -73,3 +73,4 @@ Supporting packages: - Lockfiles (`package-lock.json`, `uv.lock`, `Cargo.lock`) are committed and must stay in sync; GitHub Actions are SHA-pinned. Adding a direct dependency requires the admission rationale defined in `AGENTS.md` and `docs/security/dependency-policy.md`. - CI beyond quickcheck: `gate / ci / rust-check` (Tauri cargo check on macOS) and `build-baseline` Windows/macOS amd64+arm64 native builds are merge gates, alongside CodeQL, dependency-review, sbom, bandit, trivy, secret-scan, and security-audit workflows. Do not weaken or skip them. - Version metadata lives in `VERSION`, the root `package.json`, and `CHANGELOG.md`; release flow is tag-driven (see `docs/operations/deploy-runbook.md`). +- Local developer OpenCode is NVIDIA NIM only. The binding, secret split, leftover-token deny-list, and `reasoningEffort` ban live in `AGENTS.md` (`Local OpenCode contract`) and `docs/doctoring/opencode-nvidia-nim-contract.md`. diff --git a/docs/doctoring/opencode-nvidia-nim-contract.md b/docs/doctoring/opencode-nvidia-nim-contract.md new file mode 100644 index 000000000..59f17ac9e --- /dev/null +++ b/docs/doctoring/opencode-nvidia-nim-contract.md @@ -0,0 +1,51 @@ +# Local OpenCode NVIDIA NIM contract + +Next action: keep root `opencode.jsonc` on NVIDIA NIM only. Bind `{env:NVIDIA_API_KEY}` to +`https://integrate.api.nvidia.com/v1`. Do not rename that local binding to the organization +secret name `NVIDIA_NIM_API_KEY`. Do not put OpenAI-style `reasoningEffort` on +`nvidia/llama-3.3-nemotron-super-49b-v1.5`. + +## Why this lock exists + +Local developer OpenCode is a separate trust boundary from central OpenCode Review and the +PR review/merge scheduler in `ContextualWisdomLab/.github`. Those workflows keep their own +credential names. This repository only records the local client allowlist. + +NVIDIA NIM reasoning models use `chat_template_kwargs` (for example `enable_thinking`) rather +than the OpenAI `reasoning_effort` field. NVIDIA documents `reasoning_effort` as a Chat +Completions knob for GPT-OSS models on multi-LLM NIM, not for Llama 3.3 Nemotron Super 49B +v1.5 (NVIDIA, n.d.-a; NVIDIA, n.d.-b). Forwarding `reasoningEffort` from OpenCode can be +ignored or rejected. The contract therefore forbids the field. + +The organization GitHub secret remains `NVIDIA_NIM_API_KEY`. CI maps that secret onto process +env `NVIDIA_API_KEY` because that is the NVIDIA/OpenCode client binding. + +## Held values + +- `enabled_providers`: `["nvidia-nim"]` +- default model: `nvidia-nim/nvidia/llama-3.3-nemotron-super-49b-v1.5` +- `small_model`: `nvidia-nim/meta/llama-3.3-70b-instruct` +- `apiKey`: `{env:NVIDIA_API_KEY}` +- leftover deny-list: `github-models`, `STRIX_GITHUB_MODELS_TOKEN`, `COPILOT_GITHUB_TOKEN`, + `openai/gpt-5`, `openai/o3`, `openai/o4-mini`, `models.github.ai` + +## References + +NVIDIA. (n.d.-a). *Use reasoning models with NVIDIA NIM for LLMs*. +https://docs.nvidia.com/nim/large-language-models/latest/reasoning-model.html + +NVIDIA. (n.d.-b). *nvidia / llama-3.3-nemotron-super-49b-v1.5*. +https://docs.api.nvidia.com/nim/reference/nvidia-llama-3_3-nemotron-super-49b-v1_5 + +## Security Notes + +- Attack surface: local OpenCode HTTPS calls to `https://integrate.api.nvidia.com/v1` using a + process-env API key. +- Trust boundary: untrusted prompts and repository contents sent to the provider; trusted + repo-controlled provider allowlist; secret name `NVIDIA_NIM_API_KEY` stays in GitHub and is + mapped onto `NVIDIA_API_KEY` only at process start. +- Mitigations: single enabled provider, no GitHub Models or Copilot token fallback, leftover + string deny-list, and an explicit ban on `reasoningEffort` plus `{env:NVIDIA_NIM_API_KEY}` + inside `opencode.jsonc`. +- Test points: `test_opencode_uses_nvidia_nim_only` and + `test_opencode_uses_the_canonical_nvidia_nim_contract`. diff --git a/docs/workflow/pr-review-merge-scheduler.md b/docs/workflow/pr-review-merge-scheduler.md index cb6dcaedf..927738d16 100644 --- a/docs/workflow/pr-review-merge-scheduler.md +++ b/docs/workflow/pr-review-merge-scheduler.md @@ -50,8 +50,8 @@ secret onto `NVIDIA_API_KEY` for the OpenCode client. ## Security Notes -- Attack surface: organization required workflows with write access to PR comments, PR branch updates, and normal merges; local `opencode.jsonc` that calls NVIDIA NIM over HTTPS. -- Trust boundary touched: GitHub repository governance, PR review state, status checks, CodeRabbit review requests, and the local OpenCode provider allowlist (`nvidia-nim` + `{env:NVIDIA_API_KEY}`). +- Attack surface: organization required workflows with write access to PR comments, PR branch updates, and normal merges. Local developer OpenCode (`opencode.jsonc`) is a separate HTTPS client to NVIDIA NIM and is not invoked by this scheduler. +- Trust boundary touched: GitHub repository governance, PR review state, status checks, and CodeRabbit review requests. The local OpenCode provider allowlist (`nvidia-nim` + `{env:NVIDIA_API_KEY}`) is recorded here so agents do not restore GitHub Models or review-agent secrets into this repository. - Realistic threats: spammed review comments, merging a PR with unresolved conversations, merging without required checks, or hiding conflicts behind automation. - Mitigations: central required workflow source pinning, idempotent per-head review comment marker, explicit unresolved-thread check, retry-bounded GitHub API reads, required-check verification @@ -60,5 +60,5 @@ secret onto `NVIDIA_API_KEY` for the OpenCode client. - Test points: organization ruleset inheritance, current-head OpenCode approval, unresolved review thread count, required-check rollup, approved behind PR, approved conflict-free PR, approved dirty PR, external failed-check classification, provider/runtime failure summary, Strix evidence lookup - scope diagnostics, and local `opencode.jsonc` NIM-only contract (`small_model`, `{env:NVIDIA_API_KEY}`, - no GitHub Models / Copilot leftovers). + scope diagnostics, and the separate local `opencode.jsonc` NIM-only contract (`small_model`, + `{env:NVIDIA_API_KEY}`, no `reasoningEffort`, no GitHub Models / Copilot leftovers). diff --git a/opencode.jsonc b/opencode.jsonc index eeca34f1b..d4d22d611 100644 --- a/opencode.jsonc +++ b/opencode.jsonc @@ -49,9 +49,6 @@ "name": "NVIDIA Llama 3.3 Nemotron Super 49B v1.5", "tool_call": true, "reasoning": true, - "options": { - "reasoningEffort": "high" - }, "limit": { "context": 131072, "output": 32768 diff --git a/services/analysis-engine/tests/test_opencode_nim_secret_contract.py b/services/analysis-engine/tests/test_opencode_nim_secret_contract.py new file mode 100644 index 000000000..e74f861b8 --- /dev/null +++ b/services/analysis-engine/tests/test_opencode_nim_secret_contract.py @@ -0,0 +1,42 @@ +"""Regression contract for the repository's NVIDIA NIM OpenCode configuration.""" + +from __future__ import annotations + +import json +from pathlib import Path + + +def test_opencode_uses_the_canonical_nvidia_nim_contract() -> None: + """Require the reviewed NIM models, process credential alias, and option boundary.""" + repo_root = Path(__file__).resolve().parents[3] + opencode_text = (repo_root / "opencode.jsonc").read_text(encoding="utf-8") + config = json.loads(opencode_text) + + assert config["model"] == "nvidia-nim/nvidia/llama-3.3-nemotron-super-49b-v1.5" + assert config["small_model"] == "nvidia-nim/meta/llama-3.3-70b-instruct" + assert config["enabled_providers"] == ["nvidia-nim"] + assert set(config["provider"]) == {"nvidia-nim"} + + provider = config["provider"]["nvidia-nim"] + assert provider["options"]["baseURL"] == "https://integrate.api.nvidia.com/v1" + # Central workflows source GitHub Secret NVIDIA_NIM_API_KEY and intentionally + # expose it to this OpenCode-compatible client through the NVIDIA_API_KEY + # process alias. The repository config must consume the alias, not rename + # the organization secret contract. + assert provider["options"]["apiKey"] == "{env:NVIDIA_API_KEY}" + assert "{env:NVIDIA_NIM_API_KEY}" not in opencode_text + + primary_model = provider["models"]["nvidia/llama-3.3-nemotron-super-49b-v1.5"] + assert "reasoningEffort" not in primary_model.get("options", {}) + assert "meta/llama-3.3-70b-instruct" in provider["models"] + + for forbidden_token in ( + "github-models", + "STRIX_GITHUB_MODELS_TOKEN", + "COPILOT_GITHUB_TOKEN", + "openai/gpt-5", + "openai/o3", + "openai/o4-mini", + "models.github.ai", + ): + assert forbidden_token not in opencode_text diff --git a/services/analysis-engine/tests/test_supply_chain_policy.py b/services/analysis-engine/tests/test_supply_chain_policy.py index cc52572ef..70e8c394c 100644 --- a/services/analysis-engine/tests/test_supply_chain_policy.py +++ b/services/analysis-engine/tests/test_supply_chain_policy.py @@ -5090,6 +5090,10 @@ def test_opencode_uses_nvidia_nim_only() -> None: assert "nvidia/llama-3.3-nemotron-super-49b-v1.5" in nim_provider["models"] assert "meta/llama-3.3-70b-instruct" in nim_provider["models"] + primary_model = nim_provider["models"]["nvidia/llama-3.3-nemotron-super-49b-v1.5"] + assert "reasoningEffort" not in primary_model.get("options", {}) + assert "{env:NVIDIA_NIM_API_KEY}" not in opencode_text + leftover_tokens = ( "github-models", "STRIX_GITHUB_MODELS_TOKEN",