diff --git a/.github/workflows/hourly-nvidia-nim-review-repair.yml b/.github/workflows/hourly-nvidia-nim-review-repair.yml index 9eb450619..9a90c9320 100644 --- a/.github/workflows/hourly-nvidia-nim-review-repair.yml +++ b/.github/workflows/hourly-nvidia-nim-review-repair.yml @@ -39,7 +39,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 @@ -49,7 +49,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/orgmetra-hourly-review-caller.md - docs/doctoring/originweave-hourly-review-caller.md @@ -92,7 +92,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 @@ -102,7 +102,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/orgmetra-hourly-review-caller.md - docs/doctoring/originweave-hourly-review-caller.md @@ -117,7 +117,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: @@ -138,7 +138,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 \ @@ -173,6 +173,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..9fd626e32 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,16 +361,43 @@ 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 + 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[]? | 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 fi prompt_file="${RUNNER_TEMP}/opencode-autofix-prompt.md" @@ -542,11 +560,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 +576,6 @@ 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." - exit 1 - fi cd "$TARGET_WORKSPACE" # Merge the base branch into the detached head. A clean merge stays @@ -589,7 +604,41 @@ 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." + 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 + 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[]? | 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 + 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/AGENTS.md b/AGENTS.md index 2df633f49..934ae8273 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -3,8 +3,13 @@ > **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). -The materialization contract is also covered by [`docs/doctoring/exact-artifact-sbom-attestation.md`](docs/doctoring/exact-artifact-sbom-attestation.md). +OriginWeave hourly review repair is a thin caller at minute 10; its historical +NVIDIA NIM-only worker description remains in the doctoring record. 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; its historical +NVIDIA NIM-only worker description remains in the doctoring record. See +[`docs/doctoring/nonnest2-hourly-review-caller.md`](docs/doctoring/nonnest2-hourly-review-caller.md). +The materialization contract is also covered by +[`docs/doctoring/exact-artifact-sbom-attestation.md`](docs/doctoring/exact-artifact-sbom-attestation.md). diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index c48db831f..119983296 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 @@ -96,14 +98,21 @@ 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 + NVIDIA NIM / 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 + RW->>AF: exact-head repair context after changes requested + AF->>GW: bounded repair prompt with scoped gateway URL/token + GW-->>AF: bounded model output + AF->>SV: verify candidate edit in isolated copy + SV-->>AF: tests and changed-path evidence MS->>PR: merge only on current-head approval + green checks ``` @@ -117,9 +126,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. - Downloaded SBOM and distribution bytes are inert. The signing job does @@ -143,8 +153,9 @@ 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. - [`docs/doctoring/exact-artifact-sbom-attestation.md`](docs/doctoring/exact-artifact-sbom-attestation.md) diff --git a/CHANGELOG.md b/CHANGELOG.md index e42afe76a..7cc6bfc9d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,7 +14,7 @@ Semantic Versioning where the repository publishes a release. - 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 an organization-owned reusable exact-artifact SBOM attestation boundary that validates inert six-file wheel/sdist evidence, binds CycloneDX 1.7 predicates to exact SHA-256 subjects, signs through least-privilege GitHub artifact attestations, and exports online and offline verification bundles. - 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. @@ -27,7 +27,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 @@ -64,7 +64,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/CLAUDE.md b/CLAUDE.md index 02d6b3d84..b7db030e7 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -69,8 +69,8 @@ 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, exact-artifact SBOM attestation, - and merge trust boundaries. + diagram for review, contextual-orchestrator repair, historical hourly NVIDIA + NIM callers, exact-artifact SBOM attestation, 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. @@ -119,8 +119,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/docs/automation/hourly-review-repair.md b/docs/automation/hourly-review-repair.md index 722724958..9bcb8601b 100644 --- a/docs/automation/hourly-review-repair.md +++ b/docs/automation/hourly-review-repair.md @@ -12,7 +12,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. Orgmetra's caller remains provider-neutral. The intended model boundary is the contextual-orchestrator gateway: provider keys stay in its KV registry and @@ -44,8 +45,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. ## Orgmetra execution contract @@ -199,7 +201,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 @@ -225,7 +228,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..49484954a --- /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. + +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 +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/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()) diff --git a/scripts/ci/strix_quick_gate.sh b/scripts/ci/strix_quick_gate.sh index 0f37f3460..76e720e06 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,24 @@ 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 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) 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 +3535,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' @@ -3780,6 +3791,9 @@ vulnerability_file_is_retryable_model_inconsistency() { 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 ac9ce1d8b..f6a7e7c38 100755 --- a/scripts/ci/test_strix_quick_gate.sh +++ b/scripts/ci/test_strix_quick_gate.sh @@ -4075,6 +4075,68 @@ EOS ;; esac ;; + 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:** LOW +**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-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) @@ -5239,6 +5301,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-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 +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 +6303,50 @@ run_filtered_gate_case_if_requested() { "pull_request" \ "backend/app/api/snapshots.py" ;; + 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" \ + "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__" \ + "" \ + "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 +10277,48 @@ run_gate_case "pr-stale-snapshot-snippet-fallback-success" \ "pull_request" \ "backend/app/api/snapshots.py" +run_gate_case "pr-low-single-line-below-threshold" \ + "vertex_ai/stale-single-line-primary" \ + "vertex_ai/fallback-one vertex_ai/fallback-two" \ + "0" \ + "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__" \ + "" \ + "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" \ diff --git a/tests/test_pr_review_autofix_nvidia_nim_contract.py b/tests/test_pr_review_autofix_contextual_orchestrator_contract.py similarity index 71% rename from tests/test_pr_review_autofix_nvidia_nim_contract.py rename to tests/test_pr_review_autofix_contextual_orchestrator_contract.py index 1bbd98750..d5542d02c 100644 --- a/tests/test_pr_review_autofix_nvidia_nim_contract.py +++ b/tests/test_pr_review_autofix_contextual_orchestrator_contract.py @@ -17,7 +17,11 @@ ".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") +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" @@ -36,25 +40,35 @@ 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"', + '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: assert fragment in workflow, fragment forbidden_fragments = ( + 'NVIDIA_NIM_API_KEY', + 'NVIDIA_API_KEY:', 'mistralai/mistral-nemotron', 'STRIX_GITHUB_MODELS_TOKEN:', 'MODEL: github-models/', @@ -68,6 +82,36 @@ def test_scheduled_autofix_uses_only_nvidia_nim() -> 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) @@ -99,20 +143,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 +182,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" @@ -151,9 +198,41 @@ def test_missing_nvidia_nim_secret_fails_closed_before_model_execution() -> None 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: + """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[]? | select((.id? | type) == \"string\" and (.id | length > 0))] | length >= 1)'" + ) == 2 + 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:] + 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: @@ -231,7 +310,9 @@ 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 "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 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 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."""