From e30ce15fd2e53c43b24c6a782a306e82209d2b0d Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 20 Aug 2026 22:23:27 +0900 Subject: [PATCH 01/13] feat: route autofix through contextual orchestrator --- .../hourly-nvidia-nim-review-repair.yml | 10 +- .github/workflows/pr-review-autofix.yml | 87 ++++++++++------ CHANGELOG.md | 6 +- docs/automation/hourly-review-repair.md | 13 ++- .../contextual-orchestrator-autofix.md | 98 +++++++++++++++++++ docs/doctoring/hourly-nvidia-nim-autofix.md | 5 + ...tofix_contextual_orchestrator_contract.py} | 60 +++++++----- ...review_autofix_writer_security_contract.py | 13 +-- 8 files changed, 216 insertions(+), 76 deletions(-) create mode 100644 docs/doctoring/contextual-orchestrator-autofix.md rename tests/{test_pr_review_autofix_nvidia_nim_contract.py => test_pr_review_autofix_contextual_orchestrator_contract.py} (86%) diff --git a/.github/workflows/hourly-nvidia-nim-review-repair.yml b/.github/workflows/hourly-nvidia-nim-review-repair.yml index 702942708..faa64f0f4 100644 --- a/.github/workflows/hourly-nvidia-nim-review-repair.yml +++ b/.github/workflows/hourly-nvidia-nim-review-repair.yml @@ -37,7 +37,7 @@ on: - tests/test_pr_review_fix_scheduler.py - tests/test_pr_review_fix_scheduler_source_pin.py - tests/test_pr_review_autofix_context_head_binding.py - - tests/test_pr_review_autofix_nvidia_nim_contract.py + - tests/test_pr_review_autofix_contextual_orchestrator_contract.py - tests/test_pr_review_autofix_writer_security_contract.py - docs/automation/hourly-review-repair.md - docs/doctoring/bandscope-hourly-review-caller.md @@ -47,7 +47,7 @@ on: - docs/doctoring/fast-mlsirm-hourly-review-caller.md - docs/doctoring/github-hourly-conflict-repair.md - docs/doctoring/governance-risk-compliance-hourly-review-caller.md - - docs/doctoring/hourly-nvidia-nim-autofix.md + - docs/doctoring/contextual-orchestrator-autofix.md - docs/doctoring/nonnest2-hourly-review-caller.md - docs/doctoring/originweave-hourly-review-caller.md - docs/doctoring/quarantine-sandbox-hourly-review-caller.md @@ -87,7 +87,7 @@ on: - tests/test_pr_review_fix_scheduler.py - tests/test_pr_review_fix_scheduler_source_pin.py - tests/test_pr_review_autofix_context_head_binding.py - - tests/test_pr_review_autofix_nvidia_nim_contract.py + - tests/test_pr_review_autofix_contextual_orchestrator_contract.py - tests/test_pr_review_autofix_writer_security_contract.py - docs/automation/hourly-review-repair.md - docs/doctoring/bandscope-hourly-review-caller.md @@ -97,7 +97,7 @@ on: - docs/doctoring/fast-mlsirm-hourly-review-caller.md - docs/doctoring/github-hourly-conflict-repair.md - docs/doctoring/governance-risk-compliance-hourly-review-caller.md - - docs/doctoring/hourly-nvidia-nim-autofix.md + - docs/doctoring/contextual-orchestrator-autofix.md - docs/doctoring/nonnest2-hourly-review-caller.md - docs/doctoring/originweave-hourly-review-caller.md - docs/doctoring/quarantine-sandbox-hourly-review-caller.md @@ -166,6 +166,6 @@ jobs: tests/test_pr_review_fix_scheduler.py \ tests/test_pr_review_fix_scheduler_source_pin.py \ tests/test_pr_review_autofix_context_head_binding.py \ - tests/test_pr_review_autofix_nvidia_nim_contract.py \ + tests/test_pr_review_autofix_contextual_orchestrator_contract.py \ tests/test_pr_review_autofix_writer_security_contract.py git diff --check diff --git a/.github/workflows/pr-review-autofix.yml b/.github/workflows/pr-review-autofix.yml index 786357722..a0f916203 100644 --- a/.github/workflows/pr-review-autofix.yml +++ b/.github/workflows/pr-review-autofix.yml @@ -279,9 +279,9 @@ jobs: EOF jq -n --arg workspace "$TARGET_WORKSPACE" '{ "$schema": "https://opencode.ai/config.json", - "model": "nvidia-nim/mistralai/mistral-small-4-119b-2603", - "small_model": "nvidia-nim/nvidia/nemotron-3-nano-30b-a3b", - "enabled_providers": ["nvidia-nim"], + "model": "contextual-orchestrator/contextual-orchestrator", + "small_model": "contextual-orchestrator/contextual-orchestrator", + "enabled_providers": ["contextual-orchestrator"], "permission": { "edit": { "*": "allow", @@ -306,7 +306,7 @@ jobs: "ci-autofix": { "description": "Conservative CI pull request review autofix agent", "mode": "primary", - "model": "nvidia-nim/mistralai/mistral-small-4-119b-2603", + "model": "contextual-orchestrator/contextual-orchestrator", "reasoningEffort": "high", "prompt": "{file:./autofix-prompt.md}", "steps": 12, @@ -333,32 +333,23 @@ jobs: } }, "provider": { - "nvidia-nim": { + "contextual-orchestrator": { "npm": "@ai-sdk/openai-compatible", - "name": "NVIDIA NIM", + "name": "Contextual Orchestrator", "options": { - "baseURL": "https://integrate.api.nvidia.com/v1", - "apiKey": "{env:NVIDIA_API_KEY}" + "baseURL": "{env:CONTEXTUAL_ORCHESTRATOR_BASE_URL}", + "apiKey": "{env:CONTEXTUAL_ORCHESTRATOR_TOKEN}", + "headers": { + "X-Contextual-Orchestrator-Tool-Loop": "v1" + } }, "models": { - "mistralai/mistral-small-4-119b-2603": { - "name": "Mistral Small 4 119B 2603", - "tool_call": true, - "reasoning": true, - "options": { - "reasoningEffort": "high" - }, - "limit": { - "context": 128000, - "output": 4096 - } - }, - "nvidia/nemotron-3-nano-30b-a3b": { - "name": "Nemotron 3 Nano 30B A3B", + "contextual-orchestrator": { + "name": "Contextual Orchestrator (auto-routed)", "tool_call": true, "reasoning": true, "limit": { - "context": 128000, + "context": 200000, "output": 32768 } } @@ -370,18 +361,35 @@ jobs: - name: Run OpenCode review autofix if: env.RESOLVE_CONFLICT != 'true' env: - NVIDIA_API_KEY: ${{ secrets.NVIDIA_NIM_API_KEY }} - MODEL: nvidia-nim/mistralai/mistral-small-4-119b-2603 + CONTEXTUAL_ORCHESTRATOR_BASE_URL: ${{ vars.CONTEXTUAL_ORCHESTRATOR_BASE_URL }} + CONTEXTUAL_ORCHESTRATOR_TOKEN: ${{ secrets.CONTEXTUAL_ORCHESTRATOR_TOKEN }} + MODEL: contextual-orchestrator/contextual-orchestrator 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." + if [ -z "${CONTEXTUAL_ORCHESTRATOR_BASE_URL:-}" ] || + [ -z "${CONTEXTUAL_ORCHESTRATOR_TOKEN:-}" ]; then + echo "::error::CONTEXTUAL_ORCHESTRATOR_BASE_URL and CONTEXTUAL_ORCHESTRATOR_TOKEN are required for scheduled OpenCode autofix." exit 1 fi + python3 - "$CONTEXTUAL_ORCHESTRATOR_BASE_URL" <<'PY' + import sys + from urllib.parse import urlparse + + parsed = urlparse(sys.argv[1]) + if ( + parsed.scheme != "https" + or not parsed.hostname + or parsed.username + or parsed.password + or parsed.query + or parsed.fragment + ): + raise SystemExit("CONTEXTUAL_ORCHESTRATOR_BASE_URL must be an HTTPS URL without credentials or query data") + PY prompt_file="${RUNNER_TEMP}/opencode-autofix-prompt.md" allowed_paths_zlist="${RUNNER_TEMP}/pr-review-autofix-allowed-paths.zlist" allowed_paths_context="$( @@ -542,11 +550,12 @@ jobs: - name: Merge base branch and resolve conflicts with OpenCode if: env.RESOLVE_CONFLICT == 'true' env: - NVIDIA_API_KEY: ${{ secrets.NVIDIA_NIM_API_KEY }} + CONTEXTUAL_ORCHESTRATOR_BASE_URL: ${{ vars.CONTEXTUAL_ORCHESTRATOR_BASE_URL }} + CONTEXTUAL_ORCHESTRATOR_TOKEN: ${{ secrets.CONTEXTUAL_ORCHESTRATOR_TOKEN }} GITHUB_TOKEN: ${{ secrets.PR_REVIEW_MERGE_TOKEN || secrets.OPENCODE_APPROVE_TOKEN || steps.target_app_token.outputs.token }} GH_TOKEN: ${{ secrets.PR_REVIEW_MERGE_TOKEN || secrets.OPENCODE_APPROVE_TOKEN || steps.target_app_token.outputs.token }} MUTATION_CREDENTIAL_AVAILABLE: ${{ secrets.PR_REVIEW_MERGE_TOKEN != '' || secrets.OPENCODE_APPROVE_TOKEN != '' || steps.target_app_token.outputs.available == 'true' }} - MODEL: nvidia-nim/mistralai/mistral-small-4-119b-2603 + MODEL: contextual-orchestrator/contextual-orchestrator SHARE: "false" NPM_CONFIG_IGNORE_SCRIPTS: "true" NO_COLOR: "1" @@ -557,10 +566,26 @@ jobs: echo "::error::Conflict-resolution mutation requires PR_REVIEW_MERGE_TOKEN, OPENCODE_APPROVE_TOKEN, or the exchanged OpenCode app token; github.token remains read-only." exit 1 fi - if [ -z "${NVIDIA_API_KEY:-}" ]; then - echo "::error::NVIDIA_NIM_API_KEY is required for scheduled OpenCode autofix." + if [ -z "${CONTEXTUAL_ORCHESTRATOR_BASE_URL:-}" ] || + [ -z "${CONTEXTUAL_ORCHESTRATOR_TOKEN:-}" ]; then + echo "::error::CONTEXTUAL_ORCHESTRATOR_BASE_URL and CONTEXTUAL_ORCHESTRATOR_TOKEN are required for scheduled OpenCode autofix." exit 1 fi + python3 - "$CONTEXTUAL_ORCHESTRATOR_BASE_URL" <<'PY' + import sys + from urllib.parse import urlparse + + parsed = urlparse(sys.argv[1]) + if ( + parsed.scheme != "https" + or not parsed.hostname + or parsed.username + or parsed.password + or parsed.query + or parsed.fragment + ): + raise SystemExit("CONTEXTUAL_ORCHESTRATOR_BASE_URL must be an HTTPS URL without credentials or query data") + PY cd "$TARGET_WORKSPACE" # Merge the base branch into the detached head. A clean merge stays diff --git a/CHANGELOG.md b/CHANGELOG.md index 7d2f9f24d..49403b60c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,7 +14,7 @@ Semantic Versioning where the repository publishes a release. - Added a dedicated OriginWeave hourly caller that invokes the product-neutral central scheduler with the exact repository, protected `main` branch, one-dispatch budget, two-hour same-head retry floor, non-cancelling single-flight heartbeat, job-scoped OIDC, and only the established scheduler credentials. - Added a trusted pull-request comment router for `@cwl-noema-review` and review-only `@opencode-agent` dispatches, with an organization sweep, exact-head receipts, repository allowlisting, fixed runners, immutable checkout pins, and a permanent 100% statement/branch/docstring quality gate. - Added exact-base `uv.lock` materialization that reconstructs standalone nested projects with a checksum-pinned official `uv` exporter, isolated frozen/offline execution, strict exact-pin and SHA-256 output validation, and complete Python 3.10/3.14 quality evidence. -- Added a permanent exact-head contract workflow for the hourly review-repair scheduler, immutable reusable-workflow source, NVIDIA NIM model boundary, credential isolation, and fail-closed unattended-agent permissions. +- Added a permanent exact-head contract workflow for the hourly review-repair scheduler, immutable reusable-workflow source, contextual-orchestrator gateway boundary, credential isolation, and fail-closed unattended-agent permissions. - Added a dedicated Clearfolio hourly caller that invokes the product-neutral central scheduler with the exact repository, protected base branch, one-dispatch budget, one-hour retry floor, single-flight concurrency, and only the established scheduler credentials. - Added a dedicated DiskSage hourly caller that invokes the same product-neutral RCA and remediation-feasibility scheduler with an exact repository target, one-dispatch budget, two-hour same-head retry floor, non-cancelling single-flight heartbeat, and explicit established scheduler credentials. - Added a dedicated fast-mlsirm hourly caller that preserves Rust-owned psychometric arithmetic while dispatching at most one exact-head, root-cause-driven repair with a two-hour same-head retry floor. @@ -26,7 +26,7 @@ Semantic Versioning where the repository publishes a release. - Run the bounded Clearfolio PR review-feedback repair caller at minute 23 of every hour while keeping the shared scheduler free of product-specific timers and repository names for modular reuse by naruon, contextual-orchestrator, Inkspan, and other CWL services. - Run the bounded DiskSage repair heartbeat at minute 37 of every hour, dispatch no more than one exact-head repair, and wait two hours before redispatching an unchanged head so legitimate OpenCode or NVIDIA NIM latency does not create duplicate writers. - Run the bounded fast-mlsirm repair heartbeat at minute 49 of every hour with one-dispatch scope and a two-hour same-head floor, without weakening true-parameter recovery, CPU/GPU parity, skipped-test, or Rust-ownership gates. -- Use NVIDIA NIM `mistralai/mistral-small-4-119b-2603` with explicit high reasoning for scheduled repair and `nvidia/nemotron-3-nano-30b-a3b` for bounded helper work instead of GitHub Models in the write-capable autofix worker. +- Route write-capable scheduled repair through contextual-orchestrator's KV-backed automatic model discovery instead of copying provider model credentials into the central worker; retain the independent read-only reviewer credential chain. - Apply one NUL-delimited exact-path and complete pre/post-worktree verification contract to both ordinary review repair and merge-conflict repair rather than relying on a visible post-model diff for the ordinary path. ### Changed @@ -60,7 +60,7 @@ Semantic Versioning where the repository publishes a release. - Keep the Clearfolio caller and reusable scheduler read-only at workflow and job scope; authorize mutation only through explicitly mapped `PR_REVIEW_MERGE_TOKEN`, `OPENCODE_APPROVE_TOKEN`, or the short-lived OpenCode GitHub App token exchanged from OIDC, with explicit pre-write guards and no `github.token` mutation fallback. - Keep the DiskSage caller read-only and pass only the established scheduler credentials; do not inherit secrets, expose the NVIDIA NIM model credential to the queue scanner, use a GitHub Copilot token, or grant the caller repository mutation permissions. - Keep the fast-mlsirm caller read-only and model-secret-free; preserve independent approval, exact-head evidence, and Rust production-arithmetic ownership while centralizing only bounded review repair. -- Bind `NVIDIA_NIM_API_KEY` only to the two OpenCode model execution steps, fail closed when the secret is absent, and remove GitHub and Actions OIDC credentials from both model subprocesses. The decision record now cites CWE-367 so a later default-branch push cannot replace privileged repair helpers after `repository_dispatch` has already selected the workflow revision. +- Bind `CONTEXTUAL_ORCHESTRATOR_BASE_URL` and `CONTEXTUAL_ORCHESTRATOR_TOKEN` only to the two OpenCode model execution steps, fail closed when either is absent, and remove GitHub and Actions OIDC credentials from both model subprocesses. The decision record now cites CWE-367 so a later default-branch push cannot replace privileged repair helpers after `repository_dispatch` has already selected the workflow revision. - Recorded the org control-plane architecture, including the hourly NVIDIA NIM repair gate, so agents reconstruct the write-capable worker trust boundary from the repo instead of private memory. - Deny unnecessary non-file OpenCode interactions and preserve the independent read-only reviewer workflow and its credential/model-pool contract byte-for-byte. - Pin the repository-dispatch autofix helper checkout to the exact workflow-run SHA rather than a moving default branch. diff --git a/docs/automation/hourly-review-repair.md b/docs/automation/hourly-review-repair.md index 7f15e42c3..d5b8a51ff 100644 --- a/docs/automation/hourly-review-repair.md +++ b/docs/automation/hourly-review-repair.md @@ -10,7 +10,8 @@ engine**. contextual-orchestrator, Inkspan, or another CWL service with an explicit repository and base branch. - `pr-review-autofix.yml` is the bounded write-capable worker. It uses OpenCode - with NVIDIA NIM and does not approve or merge pull requests. + through contextual-orchestrator's KV-backed gateway and does not approve or + merge pull requests. Merge eligibility remains owned by the separate merge scheduler, branch protection, required checks, independent review, and unresolved-thread policy. @@ -36,8 +37,9 @@ not overlap its successor. At most one repair dispatch is created per run. The caller passes only the established `PR_REVIEW_MERGE_TOKEN` and `OPENCODE_APPROVE_TOKEN` scheduler credentials. It does not receive or forward -`NVIDIA_NIM_API_KEY`; the model credential is scoped exclusively to the two -OpenCode execution steps in the separately reviewed autofix worker. +`CONTEXTUAL_ORCHESTRATOR_TOKEN`; gateway URL/token values are scoped +exclusively to the two OpenCode execution steps in the separately reviewed +autofix worker, while upstream provider keys remain in the gateway KV. ## Reusable target-selection contract @@ -173,7 +175,8 @@ organization-level queue inspection and bounded repair dispatch. When a scheduled run fails, classify the result before rerunning: - no actionable file-scoped feedback: expected no-op; -- missing `NVIDIA_NIM_API_KEY`: central secret configuration failure; +- missing `CONTEXTUAL_ORCHESTRATOR_BASE_URL` or + `CONTEXTUAL_ORCHESTRATOR_TOKEN`: central gateway configuration failure; - head changed: safe optimistic-concurrency refusal; inspect the new head rather than retrying predecessor evidence; - out-of-scope or ignored-path change: treat as a security failure and preserve @@ -199,7 +202,7 @@ Permanent tests prove: - the dispatch budget and same-head retry floor remain one; - caller and reusable-workflow secrets are explicit and never use `secrets: inherit`; -- immutable source, NVIDIA-only model authentication, child-process credential +- immutable source, gateway-only model authentication, child-process credential stripping, live-head guards, and independent reviewer identity remain intact; - ordinary and conflict repair share the complete ignored-inclusive snapshot and NUL-delimited allowlist boundary; diff --git a/docs/doctoring/contextual-orchestrator-autofix.md b/docs/doctoring/contextual-orchestrator-autofix.md new file mode 100644 index 000000000..dd735b9e9 --- /dev/null +++ b/docs/doctoring/contextual-orchestrator-autofix.md @@ -0,0 +1,98 @@ +# Contextual Orchestrator Review-Autofix Boundary + +## Decision + +The write-capable scheduled pull-request autofix agent uses OpenCode through +the organization contextual-orchestrator gateway. The gateway URL is an +Actions variable, its bearer token is an Actions secret, and upstream provider +credentials remain in the gateway's KV registry. The gateway starts with +automatic model discovery and selects the provider model; the worker never +receives raw NVIDIA NIM, OpenAI, OpenRouter, or Bytez provider keys. + +The independent read-only review agent remains unchanged and keeps its existing +credential and model-pool contract. 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. + +## Central MSA ownership + +`ContextualWisdomLab/.github` owns scheduler dispatch authorization, gateway +configuration, credential binding, immutable worker source, and the fail-closed +repair contract. Leaf repositories consume the reusable workflow and do not +copy provider credentials or scheduler implementation. + +## Provider and tool-loop contract + +The generated OpenCode configuration enables only +`contextual-orchestrator/contextual-orchestrator` and points at +`CONTEXTUAL_ORCHESTRATOR_BASE_URL`. It sends the explicit +`X-Contextual-Orchestrator-Tool-Loop: v1` header so OpenCode owns the bounded +function-call loop while the gateway owns provider selection. Streaming tool +loops fail closed until the gateway exposes a shape-preserving streaming relay. + +The gateway's `--auto-discover-model-agents` process resolves registered +provider credentials from its KV registry and excludes unavailable providers. +No provider key is copied into the repository, generated OpenCode config, +prompt, command argument, or ordinary worker log. Missing gateway URL/token +configuration fails closed before model execution. + +## Immutable repository-dispatch worker source + +`PR Review Autofix` is a default-branch-only `repository_dispatch` workflow. It +checks out central helper source at the exact workflow-run SHA with +`persist-credentials: false`, then validates the target PR's live repository, +open state, same-repository branch, base ref/SHA, and head ref/SHA before any +model or write operation. + +## Exact ordinary and conflict repair write scope + +Ordinary and conflict repair share the complete pre/post worktree snapshot, +including ignored paths, file modes, regular-file hashes, and symbolic-link +targets. The ordinary allowlist is NUL-delimited and derived from current-head +file-scoped actionable review context; conflict repair uses Git's exact +unresolved paths. The verifier rejects out-of-scope, ignored, dangling, +external, metadata-race, and Git-control-file changes. + +Both OpenCode permission maps allow reviewed repository file edits but deny +`.git` and `.git/*`, as well as shell, web, task, and external-directory +interactions. Privileged commits and pushes use `core.hooksPath=/dev/null` and +an explicit revalidated repository URL. Child processes receive no GitHub or +Actions OIDC write credentials. + +## RCA, approval, and rollback + +The worker establishes exact-head root-cause analysis and remediation +feasibility before editing. Queued, pending, stale, failed, or synthetic check +evidence never becomes success. The worker cannot approve, merge, lower branch +protection, change reviewer identity, or turn an external gateway failure into +a repository edit. Every pushed head must be reviewed and checked again. + +Rollback is a reviewed source change. It must preserve ordinary and conflict +repair scope, review-derived control-plane path exclusion, `.git` denial, +ignored-path inventory, hook suppression, explicit push destination, gateway +authentication, and independent approval. + +## Verification + +The focused quality workflow checks the gateway-only provider, explicit +tool-loop header, URL/token scope to the two OpenCode steps, child-process +credential stripping, immutable source pin, exact write scope, and unchanged +independent review-agent workflow. It also retains 100% statement/branch and +public-docstring gates for the trusted helpers. + +## APA 7th references + +Git Project. (2026). *git-ls-files*. Retrieved August 20, 2026, from +https://git-scm.com/docs/git-ls-files + +Git Project. (2026). *githooks*. Retrieved August 20, 2026, from +https://git-scm.com/docs/githooks + +GitHub. (n.d.). *Secrets reference*. Retrieved August 20, 2026, from +https://docs.github.com/en/actions/reference/security/secrets + +OpenCode. (n.d.). *Permissions*. Retrieved August 20, 2026, from +https://opencode.ai/docs/permissions + +ContextualWisdomLab. (2026). *Contextual-orchestrator gateway-only provider +contract and automatic model discovery* [Internal architecture records]. diff --git a/docs/doctoring/hourly-nvidia-nim-autofix.md b/docs/doctoring/hourly-nvidia-nim-autofix.md index 6b05c6bd6..d23c1542a 100644 --- a/docs/doctoring/hourly-nvidia-nim-autofix.md +++ b/docs/doctoring/hourly-nvidia-nim-autofix.md @@ -1,5 +1,10 @@ # Hourly NVIDIA NIM Review-Autofix Boundary +> Superseded on August 20, 2026 by +> [Contextual Orchestrator Review-Autofix Boundary](contextual-orchestrator-autofix.md). +> The NIM material below is retained as historical provenance only; it is not +> current worker configuration. + ## Decision Materialize accepts only exact SHA-256 pins or a bounded relative `-r` include; a lone `--require-hashes` line is not lock evidence. diff --git a/tests/test_pr_review_autofix_nvidia_nim_contract.py b/tests/test_pr_review_autofix_contextual_orchestrator_contract.py similarity index 86% rename from tests/test_pr_review_autofix_nvidia_nim_contract.py rename to tests/test_pr_review_autofix_contextual_orchestrator_contract.py index 1bbd98750..ca4661059 100644 --- a/tests/test_pr_review_autofix_nvidia_nim_contract.py +++ b/tests/test_pr_review_autofix_contextual_orchestrator_contract.py @@ -17,7 +17,7 @@ ".github/workflows/clearfolio-hourly-review-repair.yml" ) AUTOMATION_GUIDE = Path("docs/automation/hourly-review-repair.md") -DOCTORING_RECORD = Path("docs/doctoring/hourly-nvidia-nim-autofix.md") +DOCTORING_RECORD = Path("docs/doctoring/contextual-orchestrator-autofix.md") CHANGELOG = Path("CHANGELOG.md") REVIEW_DISPATCH_WORKFLOW = Path(".github/workflows/opencode-review-dispatch.yml") REVIEW_DISPATCH_BLOB_SHA = "83f6830d5c21a324b4dbcd4e5c21a07968994b81" @@ -36,25 +36,30 @@ def test_review_fix_caller_runs_once_each_hour() -> None: assert "uses: ./.github/workflows/pr-review-fix-scheduler.yml" in caller -def test_scheduled_autofix_uses_only_nvidia_nim() -> None: - """Require the write-capable OpenCode autofix agent to use NVIDIA NIM only.""" +def test_scheduled_autofix_uses_only_contextual_orchestrator() -> None: + """Require the writer to use the gateway-owned provider contract only.""" workflow = _workflow_text(AUTOFIX_WORKFLOW) required_fragments = ( - '"model": "nvidia-nim/mistralai/mistral-small-4-119b-2603"', - '"small_model": "nvidia-nim/nvidia/nemotron-3-nano-30b-a3b"', - '"enabled_providers": ["nvidia-nim"]', - '"nvidia-nim": {', - '"mistralai/mistral-small-4-119b-2603": {', - '"reasoningEffort": "high"', + '"model": "contextual-orchestrator/contextual-orchestrator"', + '"small_model": "contextual-orchestrator/contextual-orchestrator"', + '"enabled_providers": ["contextual-orchestrator"]', + '"contextual-orchestrator": {', + '"contextual-orchestrator": {', '"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-small-4-119b-2603', + '"baseURL": "{env:CONTEXTUAL_ORCHESTRATOR_BASE_URL}"', + '"apiKey": "{env:CONTEXTUAL_ORCHESTRATOR_TOKEN}"', + '"X-Contextual-Orchestrator-Tool-Loop": "v1"', + 'CONTEXTUAL_ORCHESTRATOR_BASE_URL: ${{ vars.CONTEXTUAL_ORCHESTRATOR_BASE_URL }}', + 'CONTEXTUAL_ORCHESTRATOR_TOKEN: ${{ secrets.CONTEXTUAL_ORCHESTRATOR_TOKEN }}', + 'MODEL: contextual-orchestrator/contextual-orchestrator', + 'parsed.scheme != "https"', + 'CONTEXTUAL_ORCHESTRATOR_BASE_URL must be an HTTPS URL without credentials or query data', ) for fragment in required_fragments: assert fragment in workflow, fragment forbidden_fragments = ( + 'NVIDIA_NIM_API_KEY', + 'NVIDIA_API_KEY:', 'mistralai/mistral-nemotron', 'STRIX_GITHUB_MODELS_TOKEN:', 'MODEL: github-models/', @@ -99,20 +104,22 @@ def test_opencode_agent_denies_non_file_interactions() -> None: 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.""" +def test_contextual_gateway_credentials_are_scoped_to_agent_execution_steps() -> None: + """Prevent gateway URL/token values from leaking beyond model execution.""" workflow = _workflow_text(AUTOFIX_WORKFLOW) - binding = 'NVIDIA_API_KEY: ${{ secrets.NVIDIA_NIM_API_KEY }}' + base_binding = 'CONTEXTUAL_ORCHESTRATOR_BASE_URL: ${{ vars.CONTEXTUAL_ORCHESTRATOR_BASE_URL }}' + token_binding = 'CONTEXTUAL_ORCHESTRATOR_TOKEN: ${{ secrets.CONTEXTUAL_ORCHESTRATOR_TOKEN }}' 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) == 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] + for binding in (base_binding, token_binding): + 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] def test_model_subprocesses_receive_no_github_or_oidc_write_credentials() -> None: @@ -136,12 +143,13 @@ def test_model_subprocesses_receive_no_github_or_oidc_write_credentials() -> Non 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.""" +def test_missing_contextual_gateway_configuration_fails_closed_before_model_execution() -> None: + """Reject missing gateway configuration instead of falling back to a provider.""" workflow = _workflow_text(AUTOFIX_WORKFLOW) guard = ( - 'if [ -z "${NVIDIA_API_KEY:-}" ]; then\n' - ' echo "::error::NVIDIA_NIM_API_KEY is required for scheduled ' + 'if [ -z "${CONTEXTUAL_ORCHESTRATOR_BASE_URL:-}" ] ||\n' + ' [ -z "${CONTEXTUAL_ORCHESTRATOR_TOKEN:-}" ]; then\n' + ' echo "::error::CONTEXTUAL_ORCHESTRATOR_BASE_URL and CONTEXTUAL_ORCHESTRATOR_TOKEN are required for scheduled ' 'OpenCode autofix."\n' " exit 1\n" " fi" @@ -231,7 +239,7 @@ def test_operator_doctoring_and_changelog_record_exact_write_scope() -> None: assert "Ignored build caches are outside the comparison" not in doctoring assert "Git Project. (2026). *git-ls-files*" in doctoring assert "Git Project. (2026). *githooks*" in doctoring - assert "OpenCode. (2026a). *Permissions*" in doctoring + assert "OpenCode. (n.d.). *Permissions*" in doctoring assert "ignored-path inventory" in changelog assert "model-mutable Git metadata" in changelog diff --git a/tests/test_pr_review_autofix_writer_security_contract.py b/tests/test_pr_review_autofix_writer_security_contract.py index 58ea05877..75d0bf716 100644 --- a/tests/test_pr_review_autofix_writer_security_contract.py +++ b/tests/test_pr_review_autofix_writer_security_contract.py @@ -6,7 +6,7 @@ _AUTOFIX_WORKFLOW = Path(".github/workflows/pr-review-autofix.yml") -_TARGET_MODEL = "nvidia-nim/mistralai/mistral-small-4-119b-2603" +_TARGET_MODEL = "contextual-orchestrator/contextual-orchestrator" def _workflow_text() -> str: @@ -30,15 +30,16 @@ def _step_header(workflow: str, step_name: str) -> str: return step[:run_start] -def test_writer_uses_supported_nvidia_mistral_small_with_high_reasoning() -> None: - """Pin the write-capable model and its deliberate high-reasoning budget.""" +def test_writer_uses_contextual_gateway_with_client_owned_tool_loop() -> None: + """Pin gateway model selection and the explicit OpenCode tool-loop contract.""" workflow = _workflow_text() assert f'"model": "{_TARGET_MODEL}"' in workflow - assert '"mistralai/mistral-small-4-119b-2603": {' in workflow + assert '"contextual-orchestrator": {' in workflow assert workflow.count(f"MODEL: {_TARGET_MODEL}") == 2 - assert '"reasoningEffort": "high"' in workflow - assert "nvidia-nim/mistralai/mistral-nemotron" not in workflow + assert '"X-Contextual-Orchestrator-Tool-Loop": "v1"' in workflow + assert '"apiKey": "{env:CONTEXTUAL_ORCHESTRATOR_TOKEN}"' in workflow + assert "NVIDIA_NIM_API_KEY" not in workflow assert "COPILOT_GITHUB_TOKEN" not in workflow From 40b192af67d52c90089718d56ce90ce0c63265ea Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 21 Aug 2026 03:53:33 +0900 Subject: [PATCH 02/13] docs(control-plane): align repair boundary with gateway --- AGENTS.md | 6 ++-- ARCHITECTURE.md | 28 +++++++++++-------- CLAUDE.md | 8 ++++-- README.md | 2 +- .../organization_commercial_readiness_loop.py | 3 +- 5 files changed, 27 insertions(+), 20 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index bd6a96a11..3ca1f0890 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -3,7 +3,7 @@ > **Agents: read the master context FIRST.** Before any work, read [`docs/CWL-MASTER-CONTEXT.md`](docs/CWL-MASTER-CONTEXT.md) (mission · naruon-as-platform + inter-component UML · cross-cutting disciplines · conventions · roadmap · current state), the live **GitHub Project #1** (work/roadmap source of truth), the full spec **ContextualWisdomLab/naruon#974**, and operate the Project per [`docs/agent-github-project-protocol.md`](docs/agent-github-project-protocol.md). The repo/Project — not any private agent memory — is the source of truth. -Materialize accepts only exact SHA-256 pins or a bounded relative `-r` include (no `.`/`..`); a lone `--require-hashes` directive is not trust evidence. See [`docs/doctoring/hourly-nvidia-nim-autofix.md`](docs/doctoring/hourly-nvidia-nim-autofix.md). +Materialize accepts only exact SHA-256 pins or a bounded relative `-r` include (no `.`/`..`); a lone `--require-hashes` directive is not trust evidence. The current write-capable repair boundary is the contextual-orchestrator gateway; the former NVIDIA NIM record is historical provenance in [`docs/doctoring/hourly-nvidia-nim-autofix.md`](docs/doctoring/hourly-nvidia-nim-autofix.md), while the active contract is [`docs/doctoring/contextual-orchestrator-autofix.md`](docs/doctoring/contextual-orchestrator-autofix.md). Conflict-scope roots fail closed when the immediate parent directory is a symbolic link. -OriginWeave hourly NVIDIA NIM repair is a thin caller at minute 10. See [`docs/doctoring/originweave-hourly-review-caller.md`](docs/doctoring/originweave-hourly-review-caller.md). -nonnest2 hourly NVIDIA NIM repair is a thin caller at minute 16. See [`docs/doctoring/nonnest2-hourly-review-caller.md`](docs/doctoring/nonnest2-hourly-review-caller.md). +OriginWeave hourly review repair is a thin caller at minute 10. See [`docs/doctoring/originweave-hourly-review-caller.md`](docs/doctoring/originweave-hourly-review-caller.md). +nonnest2 hourly review repair is a thin caller at minute 16. See [`docs/doctoring/nonnest2-hourly-review-caller.md`](docs/doctoring/nonnest2-hourly-review-caller.md). diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index 7d2bfb4a4..e3c094712 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -39,7 +39,7 @@ only established scheduler credentials, and grants job-scoped only established scheduler credentials, and grants job-scoped `id-token: write`. The reusable engine stays product-neutral. -## Hourly NVIDIA NIM repair gate +## Contextual-orchestrator hourly repair gate ```mermaid flowchart TD @@ -47,7 +47,7 @@ flowchart TD Sched["Central reusable scheduler"] Bind{"Exact-head, same-repo, writer authority, sealed paths?"} Worker["repository_dispatch worker at github.sha"] - NIM["NVIDIA NIM repair model"] + Gateway["Contextual-orchestrator KV gateway\nautomatic model discovery"] Recheck{"Post-edit exact-head revalidation?"} Push["Push same-repository head"] Hold["Leave the tree unchanged"] @@ -56,15 +56,17 @@ flowchart TD Sched --> Bind Bind -->|"no"| Hold Bind -->|"yes"| Worker - Worker --> NIM - NIM --> Recheck + Worker --> Gateway + Gateway --> Recheck Recheck -->|"no"| Hold Recheck -->|"yes"| Push ``` The worker checks out helpers at `${{ github.sha }}` so a later default-branch push cannot replace privileged scripts after dispatch (CWE-367). Repair binds -`NVIDIA_NIM_API_KEY`, never `COPILOT_GITHUB_TOKEN`. +only the gateway URL and token for the two OpenCode execution steps; upstream +provider credentials remain in the gateway KV, and `COPILOT_GITHUB_TOKEN` is +never used. Product callers stagger Clearfolio at minute 23, DiskSage at minute 37, and fast-mlsirm at minute 49. Each caller is read-only, dispatches at most one @@ -81,7 +83,7 @@ sequenceDiagram participant MS as Merge scheduler PR->>RW: pull_request_target on trusted base - RW->>OC: bounded evidence + NVIDIA NIM / OpenCode + RW->>OC: bounded evidence + contextual-orchestrator / OpenCode OC->>SV: PoC command in isolated copy SV-->>OC: redacted stdout/stderr + command metadata OC-->>PR: APPROVE or request changes @@ -98,9 +100,10 @@ sequenceDiagram - Logs and review receipts redact credential shapes (tokens, bearer values, known provider prefixes). They do not mask operational PII that the control plane must process. -- LLM and scheduled agents bind `NVIDIA_NIM_API_KEY` (env may be - `NVIDIA_API_KEY`). They never use `COPILOT_GITHUB_TOKEN`. Existing - review-agent key schemes stay unchanged. +- The write-capable LLM path binds only the contextual-orchestrator gateway + URL/token; provider keys remain in the gateway KV. It never uses + `COPILOT_GITHUB_TOKEN`, and the existing independent review-agent key scheme + stays unchanged. - Rust remains the psychometric arithmetic owner. Repair never substitutes Python for scoring math. @@ -122,7 +125,8 @@ trusted `uv` exporter is downloaded from the literal GitHub Releases URL for — bot/agent exact-head review and merge procedure. - [`PR_GOVERNANCE_AUDIT.md`](PR_GOVERNANCE_AUDIT.md) — live review/merge contract. -- [`docs/doctoring/hourly-nvidia-nim-autofix.md`](docs/doctoring/hourly-nvidia-nim-autofix.md) - — current increment's repair-worker decision and APA 7th citations. +- [`docs/doctoring/contextual-orchestrator-autofix.md`](docs/doctoring/contextual-orchestrator-autofix.md) + — current repair-worker decision and APA 7th citations; the NVIDIA NIM record + is historical provenance only. - [`docs/doctoring/fast-mlsirm-hourly-review-caller.md`](docs/doctoring/fast-mlsirm-hourly-review-caller.md) - — product-specific psychometric repair heartbeat and scientific gates. \ No newline at end of file + — product-specific psychometric repair heartbeat and scientific gates. diff --git a/CLAUDE.md b/CLAUDE.md index 6ec3d494c..5ee7beb15 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -69,7 +69,7 @@ Details: `docs/pr-review-and-merge-procedure.md` and `PR_GOVERNANCE_AUDIT.md`. - `docs/` — master context, Project protocol, `org-required-workflow-rollout.md`, `scorecard-governance.md`, SBOM inventory. Doctoring records live under `docs/doctoring/`. [`ARCHITECTURE.md`](ARCHITECTURE.md) is the control-plane - diagram for review, hourly NVIDIA NIM repair, and merge trust boundaries. + diagram for review, contextual-orchestrator repair, and merge trust boundaries. - `.jules/` — recorded performance (`bolt.md`) and security (`sentinel.md`) learnings from past work on `scripts/ci/`; worth scanning before optimizing or hardening those scripts. @@ -118,8 +118,10 @@ repeatable compile command. - **100% coverage and 100% docstrings on `scripts/ci/`** are hard gates, not aspirations. New helper code needs matching tests and docstrings. - **Product hourly callers** stay thin. Do not hard-code OriginWeave, naruon, or Keyverse - into `pr-review-fix-scheduler.yml`. The model credential remains `NVIDIA_NIM_API_KEY` - on the worker, never `COPILOT_GITHUB_TOKEN`. + into `pr-review-fix-scheduler.yml`. The write-capable worker routes through the + contextual-orchestrator KV gateway; upstream provider credentials stay in that gateway, + and the independent read-only reviewer keeps its existing credential chain. Never use + `COPILOT_GITHUB_TOKEN`. - **`pull_request_target` trust boundary.** The required review workflows run the *base branch's* trusted scripts. A PR that edits the trusted review workflows can fail its own checks until the base branch catches up; a same-head manual `workflow_dispatch` Strix run may supply review evidence diff --git a/README.md b/README.md index 94e91fe4d..48b0e7a02 100644 --- a/README.md +++ b/README.md @@ -75,7 +75,7 @@ Checked-in operator facts: so it keeps the workflow files; siblings should not. - Public profile, DIKW checkpoints, project catalog, and the existing APA 7th DIKW citations stay in [profile/README.md](profile/README.md#references). -- Control-plane trust boundaries and the hourly NVIDIA NIM repair gate are +- Control-plane trust boundaries and the contextual-orchestrator repair gate are diagrammed in [ARCHITECTURE.md](ARCHITECTURE.md). If live organization ruleset inspection reports a different ref or a missing diff --git a/scripts/ci/organization_commercial_readiness_loop.py b/scripts/ci/organization_commercial_readiness_loop.py index c00cfa1e0..1ede18a00 100644 --- a/scripts/ci/organization_commercial_readiness_loop.py +++ b/scripts/ci/organization_commercial_readiness_loop.py @@ -239,6 +239,7 @@ class GitHubClient: """Use the GitHub CLI as an authenticated, bounded REST transport.""" def __init__(self, token: str, *, timeout_seconds: int = 60) -> None: + """Initialize the client with one non-empty GitHub credential.""" if not token: raise GitHubError("GH_TOKEN is required for organization coordination") self._token = token @@ -853,4 +854,4 @@ def main( if __name__ == "__main__": # pragma: no cover - exercised through main() - raise SystemExit(main()) \ No newline at end of file + raise SystemExit(main()) From 10f1d99fb14dcfe8633c7deb01253c06d34931d6 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 21 Aug 2026 05:02:17 +0900 Subject: [PATCH 03/13] docs: align gateway discovery record with implementation --- docs/doctoring/contextual-orchestrator-autofix.md | 2 +- .../test_pr_review_autofix_contextual_orchestrator_contract.py | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/doctoring/contextual-orchestrator-autofix.md b/docs/doctoring/contextual-orchestrator-autofix.md index dd735b9e9..49484954a 100644 --- a/docs/doctoring/contextual-orchestrator-autofix.md +++ b/docs/doctoring/contextual-orchestrator-autofix.md @@ -30,7 +30,7 @@ The generated OpenCode configuration enables only function-call loop while the gateway owns provider selection. Streaming tool loops fail closed until the gateway exposes a shape-preserving streaming relay. -The gateway's `--auto-discover-model-agents` process resolves registered +At gateway startup, its automatic model-discovery process resolves registered provider credentials from its KV registry and excludes unavailable providers. No provider key is copied into the repository, generated OpenCode config, prompt, command argument, or ordinary worker log. Missing gateway URL/token diff --git a/tests/test_pr_review_autofix_contextual_orchestrator_contract.py b/tests/test_pr_review_autofix_contextual_orchestrator_contract.py index ca4661059..2f1c20d75 100644 --- a/tests/test_pr_review_autofix_contextual_orchestrator_contract.py +++ b/tests/test_pr_review_autofix_contextual_orchestrator_contract.py @@ -240,6 +240,8 @@ def test_operator_doctoring_and_changelog_record_exact_write_scope() -> None: assert "Git Project. (2026). *git-ls-files*" in doctoring assert "Git Project. (2026). *githooks*" in doctoring assert "OpenCode. (n.d.). *Permissions*" in doctoring + assert "At gateway startup, its automatic model-discovery process" in doctoring + assert "--auto-discover-model-agents" not in doctoring assert "ignored-path inventory" in changelog assert "model-mutable Git metadata" in changelog From 8505f919e9f71d8ece968640fe63bcbee80e0342 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 20 Aug 2026 13:22:08 -0700 Subject: [PATCH 04/13] fix(autofix): clarify gateway trust boundaries --- .../hourly-nvidia-nim-review-repair.yml | 4 +- ARCHITECTURE.md | 8 +++- ...utofix_contextual_orchestrator_contract.py | 39 +++++++++++++++++++ 3 files changed, 48 insertions(+), 3 deletions(-) diff --git a/.github/workflows/hourly-nvidia-nim-review-repair.yml b/.github/workflows/hourly-nvidia-nim-review-repair.yml index faa64f0f4..04ac33b8f 100644 --- a/.github/workflows/hourly-nvidia-nim-review-repair.yml +++ b/.github/workflows/hourly-nvidia-nim-review-repair.yml @@ -111,7 +111,7 @@ concurrency: jobs: contract: - name: Hourly cadence, immutable source, NIM credential, and conflict scope + name: Hourly cadence, immutable source, gateway credential, and conflict scope runs-on: ubuntu-24.04 timeout-minutes: 20 steps: @@ -132,7 +132,7 @@ jobs: run: >- python -m pip install --disable-pip-version-check --require-hashes -r requirements-opencode-review-ci-hashes.txt - - name: Verify hourly scheduler and NVIDIA NIM autofix contracts + - name: Verify hourly scheduler and contextual-orchestrator autofix contracts run: | set -euo pipefail python -m pytest -q \ diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index e3c094712..ef7230f5d 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -79,14 +79,20 @@ sequenceDiagram participant PR as Pull request participant RW as Required workflows participant OC as OpenCode reviewer + participant AF as Write-capable autofix worker + participant GW as contextual-orchestrator gateway participant SV as sandboxed_verify / web E2E participant MS as Merge scheduler PR->>RW: pull_request_target on trusted base - RW->>OC: bounded evidence + contextual-orchestrator / OpenCode + RW->>OC: bounded evidence + independent reviewer credential OC->>SV: PoC command in isolated copy SV-->>OC: redacted stdout/stderr + command metadata OC-->>PR: APPROVE or request changes + PR->>AF: actionable review finding + AF->>GW: bounded repair prompt + GW-->>AF: provider response + AF-->>PR: bounded source patch MS->>PR: merge only on current-head approval + green checks ``` diff --git a/tests/test_pr_review_autofix_contextual_orchestrator_contract.py b/tests/test_pr_review_autofix_contextual_orchestrator_contract.py index 2f1c20d75..467257bad 100644 --- a/tests/test_pr_review_autofix_contextual_orchestrator_contract.py +++ b/tests/test_pr_review_autofix_contextual_orchestrator_contract.py @@ -18,6 +18,10 @@ ) AUTOMATION_GUIDE = Path("docs/automation/hourly-review-repair.md") DOCTORING_RECORD = Path("docs/doctoring/contextual-orchestrator-autofix.md") +ARCHITECTURE = Path("ARCHITECTURE.md") +AUTOFIX_CONTRACT_WORKFLOW = Path( + ".github/workflows/hourly-nvidia-nim-review-repair.yml" +) CHANGELOG = Path("CHANGELOG.md") REVIEW_DISPATCH_WORKFLOW = Path(".github/workflows/opencode-review-dispatch.yml") REVIEW_DISPATCH_BLOB_SHA = "83f6830d5c21a324b4dbcd4e5c21a07968994b81" @@ -53,6 +57,11 @@ def test_scheduled_autofix_uses_only_contextual_orchestrator() -> None: 'CONTEXTUAL_ORCHESTRATOR_TOKEN: ${{ secrets.CONTEXTUAL_ORCHESTRATOR_TOKEN }}', 'MODEL: contextual-orchestrator/contextual-orchestrator', 'parsed.scheme != "https"', + 'or not parsed.hostname', + 'or parsed.username', + 'or parsed.password', + 'or parsed.query', + 'or parsed.fragment', 'CONTEXTUAL_ORCHESTRATOR_BASE_URL must be an HTTPS URL without credentials or query data', ) for fragment in required_fragments: @@ -73,6 +82,36 @@ def test_scheduled_autofix_uses_only_contextual_orchestrator() -> None: assert fragment not in workflow, fragment +def test_autofix_contract_check_uses_gateway_terminology() -> None: + """Keep the hosted contract check aligned with the gateway it validates.""" + workflow = _workflow_text(AUTOFIX_CONTRACT_WORKFLOW) + assert ( + "Hourly cadence, immutable source, gateway credential, and conflict scope" + in workflow + ) + assert ( + "Verify hourly scheduler and contextual-orchestrator autofix contracts" + in workflow + ) + assert "NIM credential" not in workflow + assert "NVIDIA NIM autofix contracts" not in workflow + + +def test_architecture_separates_reviewer_and_write_capable_gateway_flows() -> None: + """Prevent the independent reviewer from appearing to use the writer gateway.""" + architecture = _workflow_text(ARCHITECTURE) + assert ( + "RW->>OC: bounded evidence + independent reviewer credential" in architecture + ) + assert "participant AF as Write-capable autofix worker" in architecture + assert "participant GW as contextual-orchestrator gateway" in architecture + assert "AF->>GW: bounded repair prompt" in architecture + assert ( + "RW->>OC: bounded evidence + contextual-orchestrator / OpenCode" + not in architecture + ) + + 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) From b085e53f6be994d01ed0505b9ec4a82afe50d2dd Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 21 Aug 2026 05:41:06 +0900 Subject: [PATCH 05/13] fix: align gateway contract documentation --- .github/workflows/hourly-nvidia-nim-review-repair.yml | 4 ++-- ARCHITECTURE.md | 9 ++++++++- ...pr_review_autofix_contextual_orchestrator_contract.py | 5 +++++ tests/test_pr_review_fix_hourly_contract.py | 8 ++++++++ 4 files changed, 23 insertions(+), 3 deletions(-) diff --git a/.github/workflows/hourly-nvidia-nim-review-repair.yml b/.github/workflows/hourly-nvidia-nim-review-repair.yml index faa64f0f4..884c4bdd3 100644 --- a/.github/workflows/hourly-nvidia-nim-review-repair.yml +++ b/.github/workflows/hourly-nvidia-nim-review-repair.yml @@ -111,7 +111,7 @@ concurrency: jobs: contract: - name: Hourly cadence, immutable source, NIM credential, and conflict scope + name: Hourly cadence, immutable source, contextual-orchestrator gateway, and conflict scope runs-on: ubuntu-24.04 timeout-minutes: 20 steps: @@ -132,7 +132,7 @@ jobs: run: >- python -m pip install --disable-pip-version-check --require-hashes -r requirements-opencode-review-ci-hashes.txt - - name: Verify hourly scheduler and NVIDIA NIM autofix contracts + - name: Verify hourly scheduler and contextual-orchestrator gateway contracts run: | set -euo pipefail python -m pytest -q \ diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index e3c094712..bc3b37d7d 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -79,14 +79,21 @@ sequenceDiagram participant PR as Pull request participant RW as Required workflows participant OC as OpenCode reviewer + participant AW as OpenCode autofix worker + participant GW as Contextual-orchestrator gateway participant SV as sandboxed_verify / web E2E participant MS as Merge scheduler PR->>RW: pull_request_target on trusted base - RW->>OC: bounded evidence + contextual-orchestrator / OpenCode + RW->>OC: bounded evidence + independent review credential OC->>SV: PoC command in isolated copy SV-->>OC: redacted stdout/stderr + command metadata OC-->>PR: APPROVE or request changes + RW->>AW: exact-head repair context after changes requested + AW->>GW: scoped gateway URL/token request + GW-->>AW: bounded model output + AW->>SV: verify candidate edit in isolated copy + SV-->>AW: tests and changed-path evidence MS->>PR: merge only on current-head approval + green checks ``` diff --git a/tests/test_pr_review_autofix_contextual_orchestrator_contract.py b/tests/test_pr_review_autofix_contextual_orchestrator_contract.py index ca4661059..c8d4b8419 100644 --- a/tests/test_pr_review_autofix_contextual_orchestrator_contract.py +++ b/tests/test_pr_review_autofix_contextual_orchestrator_contract.py @@ -53,6 +53,11 @@ def test_scheduled_autofix_uses_only_contextual_orchestrator() -> None: 'CONTEXTUAL_ORCHESTRATOR_TOKEN: ${{ secrets.CONTEXTUAL_ORCHESTRATOR_TOKEN }}', 'MODEL: contextual-orchestrator/contextual-orchestrator', 'parsed.scheme != "https"', + 'or not parsed.hostname', + 'or parsed.username', + 'or parsed.password', + 'or parsed.query', + 'or parsed.fragment', 'CONTEXTUAL_ORCHESTRATOR_BASE_URL must be an HTTPS URL without credentials or query data', ) for fragment in required_fragments: diff --git a/tests/test_pr_review_fix_hourly_contract.py b/tests/test_pr_review_fix_hourly_contract.py index 072ba4d8b..d7a4f09e7 100644 --- a/tests/test_pr_review_fix_hourly_contract.py +++ b/tests/test_pr_review_fix_hourly_contract.py @@ -254,6 +254,14 @@ def test_contract_workflow_tracks_scheduler_implementation() -> None: assert text.count("scripts/ci/pr_review_fix_scheduler.py") == 2 +def test_contract_workflow_names_the_gateway_contract() -> None: + """The focused gate describes its current gateway contract, not its predecessor.""" + text = _read(_CONTRACT_WORKFLOW) + + assert "Hourly cadence, immutable source, contextual-orchestrator gateway, and conflict scope" in text + assert "Verify hourly scheduler and contextual-orchestrator gateway contracts" in text + + def test_autofix_agent_performs_rca_before_selecting_a_remediation() -> None: """The writer must diagnose the exact-head cause before it edits the tree.""" text = _read(_AUTOFIX_WORKFLOW) From c366b8d0b99a2d231bd30bdd8e1d1c01b7213036 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 21 Aug 2026 08:25:23 +0900 Subject: [PATCH 06/13] fix(autofix): require discovered gateway model --- .github/workflows/pr-review-autofix.yml | 20 +++++++++++++++++++ ...utofix_contextual_orchestrator_contract.py | 17 ++++++++++++++++ 2 files changed, 37 insertions(+) diff --git a/.github/workflows/pr-review-autofix.yml b/.github/workflows/pr-review-autofix.yml index a0f916203..23732b3c5 100644 --- a/.github/workflows/pr-review-autofix.yml +++ b/.github/workflows/pr-review-autofix.yml @@ -390,6 +390,16 @@ jobs: ): raise SystemExit("CONTEXTUAL_ORCHESTRATOR_BASE_URL must be an HTTPS URL without credentials or query data") PY + models_response="$(curl -fsS \ + --connect-timeout 10 \ + --max-time 30 \ + -H "Authorization: Bearer ${CONTEXTUAL_ORCHESTRATOR_TOKEN}" \ + "${CONTEXTUAL_ORCHESTRATOR_BASE_URL%/}/models")" + if ! jq -e '(.data | type == "array") and (.data | length >= 1)' \ + >/dev/null <<<"$models_response"; then + echo "::error::Contextual Orchestrator gateway returned no discovered models." + exit 1 + fi prompt_file="${RUNNER_TEMP}/opencode-autofix-prompt.md" allowed_paths_zlist="${RUNNER_TEMP}/pr-review-autofix-allowed-paths.zlist" allowed_paths_context="$( @@ -586,6 +596,16 @@ jobs: ): raise SystemExit("CONTEXTUAL_ORCHESTRATOR_BASE_URL must be an HTTPS URL without credentials or query data") PY + models_response="$(curl -fsS \ + --connect-timeout 10 \ + --max-time 30 \ + -H "Authorization: Bearer ${CONTEXTUAL_ORCHESTRATOR_TOKEN}" \ + "${CONTEXTUAL_ORCHESTRATOR_BASE_URL%/}/models")" + if ! jq -e '(.data | type == "array") and (.data | length >= 1)' \ + >/dev/null <<<"$models_response"; then + echo "::error::Contextual Orchestrator gateway returned no discovered models." + exit 1 + fi cd "$TARGET_WORKSPACE" # Merge the base branch into the detached head. A clean merge stays diff --git a/tests/test_pr_review_autofix_contextual_orchestrator_contract.py b/tests/test_pr_review_autofix_contextual_orchestrator_contract.py index 467257bad..d0c347b86 100644 --- a/tests/test_pr_review_autofix_contextual_orchestrator_contract.py +++ b/tests/test_pr_review_autofix_contextual_orchestrator_contract.py @@ -203,6 +203,23 @@ def test_missing_contextual_gateway_configuration_fails_closed_before_model_exec assert guard in workflow[conflict_start:] +def test_contextual_gateway_readiness_requires_an_authenticated_discovered_model() -> None: + """Do not start either writer when the gateway has no auto-discovered model.""" + workflow = _workflow_text(AUTOFIX_WORKFLOW) + readiness = ( + 'models_response="$(curl -fsS \\\n' + ' --connect-timeout 10 \\\n' + ' --max-time 30 \\\n' + ' -H "Authorization: Bearer ${CONTEXTUAL_ORCHESTRATOR_TOKEN}" \\\n' + ' "${CONTEXTUAL_ORCHESTRATOR_BASE_URL%/}/models")"' + ) + assert workflow.count(readiness) == 2 + assert workflow.count("jq -e '(.data | type == \"array\") and (.data | length >= 1)'") == 2 + assert workflow.count( + "Contextual Orchestrator gateway returned no discovered models." + ) == 2 + + def test_independent_review_agent_key_system_is_unchanged() -> None: """Pin the existing read-only reviewer workflow byte-for-byte.""" result = subprocess.run( From 19feada715613422182ab79893af5893be04b4ef Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 21 Aug 2026 08:29:13 +0900 Subject: [PATCH 07/13] fix(autofix): require usable discovered model ids --- .github/workflows/pr-review-autofix.yml | 4 ++-- ...test_pr_review_autofix_contextual_orchestrator_contract.py | 4 +++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/.github/workflows/pr-review-autofix.yml b/.github/workflows/pr-review-autofix.yml index 23732b3c5..68e4b7aa5 100644 --- a/.github/workflows/pr-review-autofix.yml +++ b/.github/workflows/pr-review-autofix.yml @@ -395,7 +395,7 @@ jobs: --max-time 30 \ -H "Authorization: Bearer ${CONTEXTUAL_ORCHESTRATOR_TOKEN}" \ "${CONTEXTUAL_ORCHESTRATOR_BASE_URL%/}/models")" - if ! jq -e '(.data | type == "array") and (.data | length >= 1)' \ + if ! jq -e '(.data | type == "array") and ([.data[]? | select((.id? | type) == "string" and (.id | length > 0))] | length >= 1)' \ >/dev/null <<<"$models_response"; then echo "::error::Contextual Orchestrator gateway returned no discovered models." exit 1 @@ -601,7 +601,7 @@ jobs: --max-time 30 \ -H "Authorization: Bearer ${CONTEXTUAL_ORCHESTRATOR_TOKEN}" \ "${CONTEXTUAL_ORCHESTRATOR_BASE_URL%/}/models")" - if ! jq -e '(.data | type == "array") and (.data | length >= 1)' \ + if ! jq -e '(.data | type == "array") and ([.data[]? | select((.id? | type) == "string" and (.id | length > 0))] | length >= 1)' \ >/dev/null <<<"$models_response"; then echo "::error::Contextual Orchestrator gateway returned no discovered models." exit 1 diff --git a/tests/test_pr_review_autofix_contextual_orchestrator_contract.py b/tests/test_pr_review_autofix_contextual_orchestrator_contract.py index d0c347b86..a59ef5982 100644 --- a/tests/test_pr_review_autofix_contextual_orchestrator_contract.py +++ b/tests/test_pr_review_autofix_contextual_orchestrator_contract.py @@ -214,7 +214,9 @@ def test_contextual_gateway_readiness_requires_an_authenticated_discovered_model ' "${CONTEXTUAL_ORCHESTRATOR_BASE_URL%/}/models")"' ) assert workflow.count(readiness) == 2 - assert workflow.count("jq -e '(.data | type == \"array\") and (.data | length >= 1)'") == 2 + assert workflow.count( + "jq -e '(.data | type == \"array\") and ([.data[]? | select((.id? | type) == \"string\" and (.id | length > 0))] | length >= 1)'" + ) == 2 assert workflow.count( "Contextual Orchestrator gateway returned no discovered models." ) == 2 From 2cc2209d786153c5577a484c327d483be4823ec0 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 21 Aug 2026 11:40:38 +0900 Subject: [PATCH 08/13] fix(strix): retry stale single-line source evidence --- scripts/ci/strix_quick_gate.sh | 19 +++++-- scripts/ci/test_strix_quick_gate.sh | 84 +++++++++++++++++++++++++++++ 2 files changed, 100 insertions(+), 3 deletions(-) diff --git a/scripts/ci/strix_quick_gate.sh b/scripts/ci/strix_quick_gate.sh index 0f37f3460..7506c0b62 100755 --- a/scripts/ci/strix_quick_gate.sh +++ b/scripts/ci/strix_quick_gate.sh @@ -3362,6 +3362,7 @@ is_hallucinated_endpoint_finding() { vulnerability_file_has_absent_source_snippets() { local vuln_file="$1" + local single_line_only="${2:-0}" if [ ! -f "$vuln_file" ] || [ -L "$vuln_file" ]; then return 1 fi @@ -3376,7 +3377,7 @@ vulnerability_file_has_absent_source_snippets() { local resolved_scan_target="" resolved_scan_target="$(resolve_current_target_path "$TARGET_PATH" 2>/dev/null || true)" - if python3 - "$vuln_file" "$REPO_ROOT" "$resolved_scan_target" "$location_records_file" <<'PY' + if python3 - "$vuln_file" "$REPO_ROOT" "$resolved_scan_target" "$location_records_file" "$single_line_only" <<'PY' from pathlib import Path import re import sys @@ -3385,6 +3386,7 @@ vuln_path = Path(sys.argv[1]) repo_root = Path(sys.argv[2]) scan_target = Path(sys.argv[3]) if sys.argv[3] else None records_path = Path(sys.argv[4]) +single_line_only = sys.argv[5] == "1" record_paths = { line.split("\t", 1)[0].strip().replace("\\", "/") @@ -3502,19 +3504,23 @@ def meaningful_lines(lang: str, raw_lines: list[str]) -> list[str]: checked_blocks = 0 stale_blocks = 0 +non_single_line_blocks = 0 for source_path, lang, raw_lines in blocks: source_lines = source_by_path.get(source_path) if source_lines is None: continue snippet_lines = meaningful_lines(lang, raw_lines) - if len(snippet_lines) < 2: + if len(snippet_lines) < 1: + continue + if single_line_only and len(snippet_lines) != 1: + non_single_line_blocks += 1 continue checked_blocks += 1 present = sum(1 for line in snippet_lines if line in source_lines) if present * 2 < len(snippet_lines): stale_blocks += 1 -if checked_blocks > 0 and stale_blocks == checked_blocks: +if checked_blocks > 0 and stale_blocks == checked_blocks and non_single_line_blocks == 0: raise SystemExit(0) raise SystemExit(1) PY @@ -3528,6 +3534,10 @@ PY return 1 } +vulnerability_file_has_absent_single_line_source_snippets() { + vulnerability_file_has_absent_source_snippets "$1" 1 +} + source_file_has_encrypted_runner_registration_token() { local source_file="$1" python3 - "$source_file" <<'PY' @@ -3777,6 +3787,9 @@ vulnerability_file_reports_generic_github_actions_workflow_insecurity() { vulnerability_file_is_retryable_model_inconsistency() { local vuln_file="$1" + if vulnerability_file_has_absent_single_line_source_snippets "$vuln_file"; then + return 0 + fi if ! vulnerability_file_is_below_threshold "$vuln_file"; then return 1 fi diff --git a/scripts/ci/test_strix_quick_gate.sh b/scripts/ci/test_strix_quick_gate.sh index ac9ce1d8b..258467c0e 100755 --- a/scripts/ci/test_strix_quick_gate.sh +++ b/scripts/ci/test_strix_quick_gate.sh @@ -4075,6 +4075,37 @@ EOS ;; esac ;; + pr-stale-single-line-snippet-fallback-success) + case "${STRIX_LLM:-}" in + vertex_ai/stale-single-line-primary) + mkdir -p "$STRIX_REPORTS_DIR/fake-stale-single-line/vulnerabilities" + cat >"$STRIX_REPORTS_DIR/fake-stale-single-line/vulnerabilities/vuln-0001.md" <<'EOS' +# Hardcoded test credential in workflow + +**Severity:** HIGH +**Target:** .github/workflows/hourly-nvidia-nim-review-repair.yml + +## Code Analysis + +**Location 1:** `.github/workflows/hourly-nvidia-nim-review-repair.yml` (line 30) + Hardcoded credential + ```yaml + LOB_API_KEY: stale_model_claim + ``` +EOS + echo "Penetration test failed: stale single-line workflow snippet" + exit 1 + ;; + vertex_ai/fallback-one) + echo "scan ok after stale single-line snippet fallback" + exit 0 + ;; + *) + echo "Error: stale-single-line scenario unexpected model (${STRIX_LLM:-})" >&2 + exit 39 + ;; + esac + ;; pr-stale-source-plus-real-finding-blocks) case "${STRIX_LLM:-}" in vertex_ai/stale-source-primary) @@ -5239,6 +5270,16 @@ async def get_snapshot(schema_snapshot_uuid, user, session): return {"status": "not_found", "snapshot_json": None} data = await session.get("SchemaSnapshotData", schema_snapshot_uuid) return {"status": snap.status, "snapshot_json": data.snapshot_json if data else None} +EOS + elif [ "$scenario" = "pr-stale-single-line-snippet-fallback-success" ]; then + mkdir -p "$repo_root_dir/.github/workflows" + cat >"$repo_root_dir/.github/workflows/hourly-nvidia-nim-review-repair.yml" <<'EOS' +name: hourly review repair +jobs: + review: + runs-on: ubuntu-latest + env: + SAFE_WORKFLOW_VALUE: configured-secret-reference EOS elif [ "$scenario" = "pr-stale-source-plus-real-finding-blocks" ]; then mkdir -p "$repo_root_dir/backend/db" "$repo_root_dir/backend/api" @@ -6231,6 +6272,28 @@ run_filtered_gate_case_if_requested() { "pull_request" \ "backend/app/api/snapshots.py" ;; + pr-stale-single-line-snippet-fallback-success) + run_gate_case "pr-stale-single-line-snippet-fallback-success" \ + "vertex_ai/stale-single-line-primary" \ + "vertex_ai/fallback-one vertex_ai/fallback-two" \ + "0" \ + "scan ok after stale single-line snippet fallback" \ + "2" \ + "vertex_ai/stale-single-line-primary|vertex_ai/fallback-one" \ + "|" \ + "vertex_ai" \ + "__DEFAULT__" \ + "" \ + "0" \ + "HIGH" \ + "0" \ + "" \ + "" \ + "1200" \ + "0" \ + "pull_request" \ + ".github/workflows/hourly-nvidia-nim-review-repair.yml" + ;; pull-request-target-modified-file-pr-head-tree-lookup-failure) run_pull_request_target_aborts_on_pr_head_blob_failure_case \ "pull-request-target-modified-file-pr-head-tree-lookup-failure" \ @@ -10161,6 +10224,27 @@ run_gate_case "pr-stale-snapshot-snippet-fallback-success" \ "pull_request" \ "backend/app/api/snapshots.py" +run_gate_case "pr-stale-single-line-snippet-fallback-success" \ + "vertex_ai/stale-single-line-primary" \ + "vertex_ai/fallback-one vertex_ai/fallback-two" \ + "0" \ + "scan ok after stale single-line snippet fallback" \ + "2" \ + "vertex_ai/stale-single-line-primary|vertex_ai/fallback-one" \ + "|" \ + "vertex_ai" \ + "__DEFAULT__" \ + "" \ + "0" \ + "HIGH" \ + "0" \ + "" \ + "" \ + "1200" \ + "0" \ + "pull_request" \ + ".github/workflows/hourly-nvidia-nim-review-repair.yml" + run_gate_case "pr-stale-source-plus-real-finding-blocks" \ "vertex_ai/stale-source-primary" \ "vertex_ai/fallback-one vertex_ai/fallback-two" \ From 4888370952b6c2d14f71a52a47027b6ad8b48fcb Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 21 Aug 2026 12:25:37 +0900 Subject: [PATCH 09/13] test(autofix): keep reviewer contract composable --- ...utofix_contextual_orchestrator_contract.py | 27 +++++++++++-------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/tests/test_pr_review_autofix_contextual_orchestrator_contract.py b/tests/test_pr_review_autofix_contextual_orchestrator_contract.py index a59ef5982..9ee124e11 100644 --- a/tests/test_pr_review_autofix_contextual_orchestrator_contract.py +++ b/tests/test_pr_review_autofix_contextual_orchestrator_contract.py @@ -3,7 +3,6 @@ import hashlib from pathlib import Path import re -import subprocess import pytest @@ -24,7 +23,6 @@ ) CHANGELOG = Path("CHANGELOG.md") REVIEW_DISPATCH_WORKFLOW = Path(".github/workflows/opencode-review-dispatch.yml") -REVIEW_DISPATCH_BLOB_SHA = "83f6830d5c21a324b4dbcd4e5c21a07968994b81" def _workflow_text(path: Path) -> str: @@ -223,15 +221,22 @@ def test_contextual_gateway_readiness_requires_an_authenticated_discovered_model def test_independent_review_agent_key_system_is_unchanged() -> None: - """Pin the existing read-only reviewer workflow byte-for-byte.""" - result = subprocess.run( - ["git", "hash-object", str(REVIEW_DISPATCH_WORKFLOW)], - check=True, - capture_output=True, - text=True, - ) - assert result.stdout.strip() == REVIEW_DISPATCH_BLOB_SHA - assert "pr-review-autofix" not in _workflow_text(REVIEW_DISPATCH_WORKFLOW) + """Keep review-write credentials separate while allowing gateway wiring.""" + workflow = _workflow_text(REVIEW_DISPATCH_WORKFLOW) + for expression in ( + "GH_TOKEN: $" + "{{ secrets.PR_REVIEW_MERGE_TOKEN || secrets.OPENCODE_APPROVE_TOKEN || github.token }}", + "GH_TOKEN: $" + "{{ secrets.OPENCODE_APPROVE_TOKEN || github.token }}", + "GH_TOKEN: $" + "{{ steps.opencode_app_token.outputs.token || secrets.PR_REVIEW_MERGE_TOKEN || secrets.OPENCODE_APPROVE_TOKEN || github.token }}", + ): + assert expression in workflow + assert "pr-review-autofix" not in workflow + assert "COPILOT_GITHUB_TOKEN" not in workflow + + model_step_start = workflow.index(" - name: Run OpenCode PR Review model pool") + model_step_end = workflow.index(" - name: Publish OpenCode review outcome", model_step_start) + model_step = workflow[model_step_start:model_step_end] + assert "PR_REVIEW_MERGE_TOKEN" not in model_step + assert "OPENCODE_APPROVE_TOKEN" not in model_step def test_ordinary_autofix_uses_the_same_exact_write_scope_as_conflict_repair() -> None: From 77705310e7a8f3b23dbb6dbdd28bb683d3cfb7b6 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 21 Aug 2026 17:08:19 +0900 Subject: [PATCH 10/13] fix(autofix): skip no-op gateway probe --- .github/workflows/pr-review-autofix.yml | 55 ++++++++++--------- ...utofix_contextual_orchestrator_contract.py | 32 +++++------ 2 files changed, 44 insertions(+), 43 deletions(-) diff --git a/.github/workflows/pr-review-autofix.yml b/.github/workflows/pr-review-autofix.yml index 68e4b7aa5..0dca8520b 100644 --- a/.github/workflows/pr-review-autofix.yml +++ b/.github/workflows/pr-review-autofix.yml @@ -576,6 +576,34 @@ jobs: echo "::error::Conflict-resolution mutation requires PR_REVIEW_MERGE_TOKEN, OPENCODE_APPROVE_TOKEN, or the exchanged OpenCode app token; github.token remains read-only." exit 1 fi + cd "$TARGET_WORKSPACE" + + # Merge the base branch into the detached head. A clean merge stays + # staged (--no-commit); a conflicting merge leaves git conflict + # markers for OpenCode to resolve. + echo "Merging base ${PR_BASE_REF} (${PR_BASE_SHA}) into head ${PR_HEAD_SHA} for conflict resolution." + git merge --no-commit --no-ff "$PR_BASE_SHA" || echo "Merge produced conflicts to resolve." + conflicted_files="$(git diff --name-only --diff-filter=U || true)" + printf 'Conflicted files:\n%s\n' "${conflicted_files:-}" + + # No-op success path: the base is already contained in the head + # (git reports "Already up to date"), so there is no merge in + # progress, no conflicts, and nothing to resolve or push. This + # happens when the PR stopped being conflicted between scheduler + # dispatch and worker execution (e.g. the head was refreshed). + if ! git rev-parse -q --verify MERGE_HEAD >/dev/null 2>&1; then + if [ -z "$conflicted_files" ] \ + && git diff --quiet \ + && git diff --cached --quiet \ + && [ -z "$(git ls-files --others --exclude-standard)" ]; then + echo "Base ${PR_BASE_REF} (${PR_BASE_SHA}) is already merged into head ${PR_HEAD_SHA}; no conflict resolution needed. Exiting as a no-op." + exit 0 + fi + echo "::error::Base merge left no merge in progress but the working tree is not clean; refusing to continue." + git status --short + exit 1 + fi + if [ -z "${CONTEXTUAL_ORCHESTRATOR_BASE_URL:-}" ] || [ -z "${CONTEXTUAL_ORCHESTRATOR_TOKEN:-}" ]; then echo "::error::CONTEXTUAL_ORCHESTRATOR_BASE_URL and CONTEXTUAL_ORCHESTRATOR_TOKEN are required for scheduled OpenCode autofix." @@ -606,33 +634,6 @@ jobs: echo "::error::Contextual Orchestrator gateway returned no discovered models." exit 1 fi - cd "$TARGET_WORKSPACE" - - # Merge the base branch into the detached head. A clean merge stays - # staged (--no-commit); a conflicting merge leaves git conflict - # markers for OpenCode to resolve. - echo "Merging base ${PR_BASE_REF} (${PR_BASE_SHA}) into head ${PR_HEAD_SHA} for conflict resolution." - git merge --no-commit --no-ff "$PR_BASE_SHA" || echo "Merge produced conflicts to resolve." - conflicted_files="$(git diff --name-only --diff-filter=U || true)" - printf 'Conflicted files:\n%s\n' "${conflicted_files:-}" - - # No-op success path: the base is already contained in the head - # (git reports "Already up to date"), so there is no merge in - # progress, no conflicts, and nothing to resolve or push. This - # happens when the PR stopped being conflicted between scheduler - # dispatch and worker execution (e.g. the head was refreshed). - if ! git rev-parse -q --verify MERGE_HEAD >/dev/null 2>&1; then - if [ -z "$conflicted_files" ] \ - && git diff --quiet \ - && git diff --cached --quiet \ - && [ -z "$(git ls-files --others --exclude-standard)" ]; then - echo "Base ${PR_BASE_REF} (${PR_BASE_SHA}) is already merged into head ${PR_HEAD_SHA}; no conflict resolution needed. Exiting as a no-op." - exit 0 - fi - echo "::error::Base merge left no merge in progress but the working tree is not clean; refusing to continue." - git status --short - exit 1 - fi if [ -n "$conflicted_files" ]; then conflicted_paths_file="${RUNNER_TEMP}/opencode-conflicted-files.zlist" diff --git a/tests/test_pr_review_autofix_contextual_orchestrator_contract.py b/tests/test_pr_review_autofix_contextual_orchestrator_contract.py index 9ee124e11..47d3c9d43 100644 --- a/tests/test_pr_review_autofix_contextual_orchestrator_contract.py +++ b/tests/test_pr_review_autofix_contextual_orchestrator_contract.py @@ -3,6 +3,7 @@ import hashlib from pathlib import Path import re +import subprocess import pytest @@ -23,6 +24,7 @@ ) CHANGELOG = Path("CHANGELOG.md") REVIEW_DISPATCH_WORKFLOW = Path(".github/workflows/opencode-review-dispatch.yml") +REVIEW_DISPATCH_BLOB_SHA = "83f6830d5c21a324b4dbcd4e5c21a07968994b81" def _workflow_text(path: Path) -> str: @@ -218,25 +220,23 @@ def test_contextual_gateway_readiness_requires_an_authenticated_discovered_model assert workflow.count( "Contextual Orchestrator gateway returned no discovered models." ) == 2 + conflict_start = workflow.index( + " - name: Merge base branch and resolve conflicts with OpenCode" + ) + conflict = workflow[conflict_start:] + assert conflict.index("no conflict resolution needed") < conflict.index(readiness) def test_independent_review_agent_key_system_is_unchanged() -> None: - """Keep review-write credentials separate while allowing gateway wiring.""" - workflow = _workflow_text(REVIEW_DISPATCH_WORKFLOW) - for expression in ( - "GH_TOKEN: $" + "{{ secrets.PR_REVIEW_MERGE_TOKEN || secrets.OPENCODE_APPROVE_TOKEN || github.token }}", - "GH_TOKEN: $" + "{{ secrets.OPENCODE_APPROVE_TOKEN || github.token }}", - "GH_TOKEN: $" + "{{ steps.opencode_app_token.outputs.token || secrets.PR_REVIEW_MERGE_TOKEN || secrets.OPENCODE_APPROVE_TOKEN || github.token }}", - ): - assert expression in workflow - assert "pr-review-autofix" not in workflow - assert "COPILOT_GITHUB_TOKEN" not in workflow - - model_step_start = workflow.index(" - name: Run OpenCode PR Review model pool") - model_step_end = workflow.index(" - name: Publish OpenCode review outcome", model_step_start) - model_step = workflow[model_step_start:model_step_end] - assert "PR_REVIEW_MERGE_TOKEN" not in model_step - assert "OPENCODE_APPROVE_TOKEN" not in model_step + """Pin the existing read-only reviewer workflow byte-for-byte.""" + result = subprocess.run( + ["git", "hash-object", str(REVIEW_DISPATCH_WORKFLOW)], + check=True, + capture_output=True, + text=True, + ) + assert result.stdout.strip() == REVIEW_DISPATCH_BLOB_SHA + assert "pr-review-autofix" not in _workflow_text(REVIEW_DISPATCH_WORKFLOW) def test_ordinary_autofix_uses_the_same_exact_write_scope_as_conflict_repair() -> None: From b3f5d089b6239ce628b014a24e2c4b2b0f45dcf1 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 21 Aug 2026 17:20:43 +0900 Subject: [PATCH 11/13] fix(autofix): probe gateway only for conflicts --- .github/workflows/pr-review-autofix.yml | 3 +++ ...w_autofix_contextual_orchestrator_contract.py | 16 ++++++++++++---- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/.github/workflows/pr-review-autofix.yml b/.github/workflows/pr-review-autofix.yml index 0dca8520b..9fd626e32 100644 --- a/.github/workflows/pr-review-autofix.yml +++ b/.github/workflows/pr-review-autofix.yml @@ -604,6 +604,7 @@ jobs: exit 1 fi + contextual_gateway_readiness() { if [ -z "${CONTEXTUAL_ORCHESTRATOR_BASE_URL:-}" ] || [ -z "${CONTEXTUAL_ORCHESTRATOR_TOKEN:-}" ]; then echo "::error::CONTEXTUAL_ORCHESTRATOR_BASE_URL and CONTEXTUAL_ORCHESTRATOR_TOKEN are required for scheduled OpenCode autofix." @@ -634,8 +635,10 @@ jobs: echo "::error::Contextual Orchestrator gateway returned no discovered models." exit 1 fi + } if [ -n "$conflicted_files" ]; then + contextual_gateway_readiness conflicted_paths_file="${RUNNER_TEMP}/opencode-conflicted-files.zlist" conflict_scope_snapshot="${RUNNER_TEMP}/opencode-conflict-workspace-before.json" git diff --name-only -z --diff-filter=U >"$conflicted_paths_file" diff --git a/tests/test_pr_review_autofix_contextual_orchestrator_contract.py b/tests/test_pr_review_autofix_contextual_orchestrator_contract.py index 47d3c9d43..cb74215d7 100644 --- a/tests/test_pr_review_autofix_contextual_orchestrator_contract.py +++ b/tests/test_pr_review_autofix_contextual_orchestrator_contract.py @@ -198,9 +198,12 @@ def test_missing_contextual_gateway_configuration_fails_closed_before_model_exec conflict_start = workflow.index( " - name: Merge base branch and resolve conflicts with OpenCode" ) - assert workflow.count(guard) == 2 + guard_message = ( + "CONTEXTUAL_ORCHESTRATOR_BASE_URL and CONTEXTUAL_ORCHESTRATOR_TOKEN are required for scheduled OpenCode autofix." + ) + assert workflow.count(guard_message) == 2 assert guard in workflow[ordinary_start:ordinary_end] - assert guard in workflow[conflict_start:] + assert guard_message in workflow[conflict_start:] def test_contextual_gateway_readiness_requires_an_authenticated_discovered_model() -> None: @@ -213,7 +216,7 @@ def test_contextual_gateway_readiness_requires_an_authenticated_discovered_model ' -H "Authorization: Bearer ${CONTEXTUAL_ORCHESTRATOR_TOKEN}" \\\n' ' "${CONTEXTUAL_ORCHESTRATOR_BASE_URL%/}/models")"' ) - assert workflow.count(readiness) == 2 + assert workflow.count(readiness.splitlines()[0]) == 2 assert workflow.count( "jq -e '(.data | type == \"array\") and ([.data[]? | select((.id? | type) == \"string\" and (.id | length > 0))] | length >= 1)'" ) == 2 @@ -224,7 +227,12 @@ def test_contextual_gateway_readiness_requires_an_authenticated_discovered_model " - name: Merge base branch and resolve conflicts with OpenCode" ) conflict = workflow[conflict_start:] - assert conflict.index("no conflict resolution needed") < conflict.index(readiness) + readiness_anchor = readiness.splitlines()[0] + readiness_function = conflict.index("contextual_gateway_readiness() {") + assert conflict.index("no conflict resolution needed") < readiness_function + assert readiness_anchor in conflict[readiness_function:] + conflict_branch = conflict.index('if [ -n "$conflicted_files" ]; then') + assert "contextual_gateway_readiness\n" in conflict[conflict_branch:] def test_independent_review_agent_key_system_is_unchanged() -> None: From e381b3c6a49de371169010e980201972a9e71d22 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 21 Aug 2026 17:37:15 +0900 Subject: [PATCH 12/13] fix(strix): enforce severity before stale finding fallback --- scripts/ci/strix_quick_gate.sh | 6 +- scripts/ci/test_strix_quick_gate.sh | 102 ++++++++++++++++++++++++---- 2 files changed, 91 insertions(+), 17 deletions(-) diff --git a/scripts/ci/strix_quick_gate.sh b/scripts/ci/strix_quick_gate.sh index 7506c0b62..cfc667910 100755 --- a/scripts/ci/strix_quick_gate.sh +++ b/scripts/ci/strix_quick_gate.sh @@ -3787,12 +3787,12 @@ vulnerability_file_reports_generic_github_actions_workflow_insecurity() { vulnerability_file_is_retryable_model_inconsistency() { local vuln_file="$1" - if vulnerability_file_has_absent_single_line_source_snippets "$vuln_file"; then - return 0 - fi if ! vulnerability_file_is_below_threshold "$vuln_file"; then return 1 fi + if vulnerability_file_has_absent_single_line_source_snippets "$vuln_file"; then + return 0 + fi if vulnerability_file_has_absent_endpoint_finding "$vuln_file"; then return 0 fi diff --git a/scripts/ci/test_strix_quick_gate.sh b/scripts/ci/test_strix_quick_gate.sh index 258467c0e..f6a7e7c38 100755 --- a/scripts/ci/test_strix_quick_gate.sh +++ b/scripts/ci/test_strix_quick_gate.sh @@ -4075,14 +4075,14 @@ EOS ;; esac ;; - pr-stale-single-line-snippet-fallback-success) + pr-low-single-line-below-threshold) case "${STRIX_LLM:-}" in vertex_ai/stale-single-line-primary) mkdir -p "$STRIX_REPORTS_DIR/fake-stale-single-line/vulnerabilities" cat >"$STRIX_REPORTS_DIR/fake-stale-single-line/vulnerabilities/vuln-0001.md" <<'EOS' # Hardcoded test credential in workflow -**Severity:** HIGH +**Severity:** LOW **Target:** .github/workflows/hourly-nvidia-nim-review-repair.yml ## Code Analysis @@ -4106,6 +4106,37 @@ EOS ;; esac ;; + pr-stale-single-line-high-blocks) + case "${STRIX_LLM:-}" in + vertex_ai/stale-single-line-high-primary) + mkdir -p "$STRIX_REPORTS_DIR/fake-stale-single-line-high/vulnerabilities" + cat >"$STRIX_REPORTS_DIR/fake-stale-single-line-high/vulnerabilities/vuln-0001.md" <<'EOS' +# Hardcoded test credential in workflow + +**Severity:** HIGH +**Target:** .github/workflows/hourly-nvidia-nim-review-repair.yml + +## Code Analysis + +**Location 1:** `.github/workflows/hourly-nvidia-nim-review-repair.yml` (line 30) + Hardcoded credential + ```yaml + LOB_API_KEY: stale_model_claim + ``` +EOS + echo "Penetration test failed: high stale single-line workflow snippet" + exit 1 + ;; + vertex_ai/fallback-one|vertex_ai/fallback-two) + echo "Error: high stale single-line findings must remain blocking" >&2 + exit 40 + ;; + *) + echo "Error: high stale-single-line scenario unexpected model (${STRIX_LLM:-})" >&2 + exit 41 + ;; + esac + ;; pr-stale-source-plus-real-finding-blocks) case "${STRIX_LLM:-}" in vertex_ai/stale-source-primary) @@ -5271,7 +5302,7 @@ async def get_snapshot(schema_snapshot_uuid, user, session): data = await session.get("SchemaSnapshotData", schema_snapshot_uuid) return {"status": snap.status, "snapshot_json": data.snapshot_json if data else None} EOS - elif [ "$scenario" = "pr-stale-single-line-snippet-fallback-success" ]; then + elif [ "$scenario" = "pr-low-single-line-below-threshold" ] || [ "$scenario" = "pr-stale-single-line-high-blocks" ]; then mkdir -p "$repo_root_dir/.github/workflows" cat >"$repo_root_dir/.github/workflows/hourly-nvidia-nim-review-repair.yml" <<'EOS' name: hourly review repair @@ -6272,15 +6303,37 @@ run_filtered_gate_case_if_requested() { "pull_request" \ "backend/app/api/snapshots.py" ;; - pr-stale-single-line-snippet-fallback-success) - run_gate_case "pr-stale-single-line-snippet-fallback-success" \ + pr-low-single-line-below-threshold) + run_gate_case "pr-low-single-line-below-threshold" \ "vertex_ai/stale-single-line-primary" \ "vertex_ai/fallback-one vertex_ai/fallback-two" \ "0" \ - "scan ok after stale single-line snippet fallback" \ - "2" \ - "vertex_ai/stale-single-line-primary|vertex_ai/fallback-one" \ - "|" \ + "Strix findings are below configured fail threshold 'HIGH'; allowing pipeline continuation." \ + "1" \ + "vertex_ai/stale-single-line-primary" \ + "" \ + "vertex_ai" \ + "__DEFAULT__" \ + "" \ + "0" \ + "HIGH" \ + "0" \ + "" \ + "" \ + "1200" \ + "0" \ + "pull_request" \ + ".github/workflows/hourly-nvidia-nim-review-repair.yml" + ;; + pr-stale-single-line-high-blocks) + run_gate_case "pr-stale-single-line-high-blocks" \ + "vertex_ai/stale-single-line-high-primary" \ + "vertex_ai/fallback-one vertex_ai/fallback-two" \ + "1" \ + "Strix finding intersects files changed in this pull request." \ + "1" \ + "vertex_ai/stale-single-line-high-primary" \ + "" \ "vertex_ai" \ "__DEFAULT__" \ "" \ @@ -10224,14 +10277,35 @@ run_gate_case "pr-stale-snapshot-snippet-fallback-success" \ "pull_request" \ "backend/app/api/snapshots.py" -run_gate_case "pr-stale-single-line-snippet-fallback-success" \ +run_gate_case "pr-low-single-line-below-threshold" \ "vertex_ai/stale-single-line-primary" \ "vertex_ai/fallback-one vertex_ai/fallback-two" \ "0" \ - "scan ok after stale single-line snippet fallback" \ - "2" \ - "vertex_ai/stale-single-line-primary|vertex_ai/fallback-one" \ - "|" \ + "Strix findings are below configured fail threshold 'HIGH'; allowing pipeline continuation." \ + "1" \ + "vertex_ai/stale-single-line-primary" \ + "" \ + "vertex_ai" \ + "__DEFAULT__" \ + "" \ + "0" \ + "HIGH" \ + "0" \ + "" \ + "" \ + "1200" \ + "0" \ + "pull_request" \ + ".github/workflows/hourly-nvidia-nim-review-repair.yml" + +run_gate_case "pr-stale-single-line-high-blocks" \ + "vertex_ai/stale-single-line-high-primary" \ + "vertex_ai/fallback-one vertex_ai/fallback-two" \ + "1" \ + "Strix finding intersects files changed in this pull request." \ + "1" \ + "vertex_ai/stale-single-line-high-primary" \ + "" \ "vertex_ai" \ "__DEFAULT__" \ "" \ From 01643acdc70de78077fd4db8ddc6342bdb6bb24e Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 21 Aug 2026 17:55:00 +0900 Subject: [PATCH 13/13] fix(autofix): isolate single-line snippet detection --- scripts/ci/strix_quick_gate.sh | 9 +++++---- ...ew_autofix_contextual_orchestrator_contract.py | 2 +- tests/test_strix_nvidia_nim_not_found_fallback.py | 15 +++++++++++++++ 3 files changed, 21 insertions(+), 5 deletions(-) diff --git a/scripts/ci/strix_quick_gate.sh b/scripts/ci/strix_quick_gate.sh index cfc667910..76e720e06 100755 --- a/scripts/ci/strix_quick_gate.sh +++ b/scripts/ci/strix_quick_gate.sh @@ -3510,10 +3510,11 @@ for source_path, lang, raw_lines in blocks: if source_lines is None: continue snippet_lines = meaningful_lines(lang, raw_lines) - if len(snippet_lines) < 1: - continue - if single_line_only and len(snippet_lines) != 1: - non_single_line_blocks += 1 + if single_line_only: + if len(snippet_lines) != 1: + non_single_line_blocks += 1 + continue + elif len(snippet_lines) < 2: continue checked_blocks += 1 present = sum(1 for line in snippet_lines if line in source_lines) diff --git a/tests/test_pr_review_autofix_contextual_orchestrator_contract.py b/tests/test_pr_review_autofix_contextual_orchestrator_contract.py index cb74215d7..d5542d02c 100644 --- a/tests/test_pr_review_autofix_contextual_orchestrator_contract.py +++ b/tests/test_pr_review_autofix_contextual_orchestrator_contract.py @@ -216,7 +216,7 @@ def test_contextual_gateway_readiness_requires_an_authenticated_discovered_model ' -H "Authorization: Bearer ${CONTEXTUAL_ORCHESTRATOR_TOKEN}" \\\n' ' "${CONTEXTUAL_ORCHESTRATOR_BASE_URL%/}/models")"' ) - assert workflow.count(readiness.splitlines()[0]) == 2 + assert workflow.count(readiness) == 2 assert workflow.count( "jq -e '(.data | type == \"array\") and ([.data[]? | select((.id? | type) == \"string\" and (.id | length > 0))] | length >= 1)'" ) == 2 diff --git a/tests/test_strix_nvidia_nim_not_found_fallback.py b/tests/test_strix_nvidia_nim_not_found_fallback.py index a48f3092d..546b99205 100644 --- a/tests/test_strix_nvidia_nim_not_found_fallback.py +++ b/tests/test_strix_nvidia_nim_not_found_fallback.py @@ -171,6 +171,21 @@ def test_not_found_skips_same_model_and_enters_cross_model_fallback(self) -> Non self.assertIn("is_nvidia_nim_not_found_error", retryable) self.assertNotIn("is_nvidia_nim_not_found_error", same_model_retry) + def test_general_source_snippet_mode_skips_single_line_blocks(self) -> None: + """Keep single-line evidence exclusive to its dedicated detector.""" + + gate_source = STRIX_GATE.read_text(encoding="utf-8") + self.assertIn( + "if single_line_only:\n" + " if len(snippet_lines) != 1:", + gate_source, + ) + self.assertIn( + "elif len(snippet_lines) < 2:\n" + " continue", + gate_source, + ) + def test_workflow_uses_available_free_first_nvidia_plan(self) -> None: """Prefer a documented hosted NIM and another NIM before GitHub."""