diff --git a/.github/workflows/opencode-review.yml b/.github/workflows/opencode-review.yml index 9cc5d93da..fb69a8c69 100644 --- a/.github/workflows/opencode-review.yml +++ b/.github/workflows/opencode-review.yml @@ -4325,6 +4325,32 @@ jobs: changed_files_summary="bounded current-head evidence" fi + if low_risk_model_exhaustion_fallback_applies; then + body="$(printf '%s\n' \ + "## Pull request overview" \ + "" \ + "OpenCode exhausted the configured model pool without a usable current-head review conclusion, but this PR only changes workflow files and all current-head peer gates passed." \ + "" \ + "## Findings" \ + "" \ + "No blocking findings." \ + "" \ + "## Summary" \ + "" \ + "- Result: APPROVE" \ + "- Reason: model output was unavailable, but deterministic current-head gates passed for a workflow-only change." \ + "- Deterministic evidence: current-head changed-file evidence (${changed_files_summary}); coverage-evidence result ${COVERAGE_EVIDENCE_RESULT:-unknown}; peer checks from statusCheckRollup excluding this OpenCode check; no unresolved human review threads; mergeability was not dirty." \ + "- Model outcome: model_pool=${OPENCODE_MODEL_POOL_OUTCOME:-unknown}; selected_model=${OPENCODE_MODEL_POOL_MODEL:-none}." \ + "- Head SHA: \`${HEAD_SHA}\`" \ + "- Workflow run: ${RUN_ID}" \ + "- Workflow attempt: ${RUN_ATTEMPT}" \ + "" \ + "This bounded workflow-only fallback does not apply to source, test, lockfile, dependency manifest, generated artifact, or documentation changes.")" + body="$(ensure_review_body_has_change_graph "$body")" + create_pull_review "APPROVE" "$body" + return 0 + fi + first_line_file="$(mktemp)" if gh pr diff "$PR_NUMBER" --repo "$GH_REPOSITORY" --patch 2>/dev/null | awk ' @@ -4408,6 +4434,26 @@ jobs: return 0 } + low_risk_model_exhaustion_fallback_applies() { + local changed_count=0 + local changed_path + + if [ ! -s "${OPENCODE_CHANGED_FILES_FILE:-}" ]; then + return 1 + fi + + while IFS= read -r changed_path; do + [ -n "$changed_path" ] || continue + changed_count=$((changed_count + 1)) + case "$changed_path" in + .github/workflows/*.yml|.github/workflows/*.yaml) ;; + *) return 1 ;; + esac + done <"$OPENCODE_CHANGED_FILES_FILE" + + [ "$changed_count" -gt 0 ] && [ "$changed_count" -le 2 ] + } + request_changes_for_merge_conflict_if_present() { local pr_json merge_state mergeable base_ref head_ref body change_graph diff --git a/scripts/ci/test_strix_quick_gate.sh b/scripts/ci/test_strix_quick_gate.sh index 867d32e1a..70b30d8c8 100755 --- a/scripts/ci/test_strix_quick_gate.sh +++ b/scripts/ci/test_strix_quick_gate.sh @@ -578,6 +578,8 @@ assert_opencode_review_uses_codegraph_and_gpt5_fallback() { assert_file_not_contains "$workflow_file" 'approve_review_tooling_bootstrap_after_model_failure' "opencode approval must not use deterministic review-tooling bootstrap approval after model-output failures" assert_file_not_contains "$workflow_file" 'Deterministic review-tooling bootstrap fallback approval was used' "opencode approval must not publish model-exhaustion approvals" assert_file_not_contains "$workflow_file" 'model-exhaustion approval did not apply' "opencode approval failure text should describe retry exhaustion, not model-exhaustion criteria" + assert_file_contains "$workflow_file" "low_risk_model_exhaustion_fallback_applies" "opencode approval has a narrow workflow-only fallback after green current-head gates" + assert_file_contains "$workflow_file" "This bounded workflow-only fallback does not apply to source, test, lockfile, dependency manifest, generated artifact, or documentation changes." "opencode workflow-only fallback documents its excluded surfaces" assert_file_contains "$workflow_file" 'wait_for_peer_github_checks "$pending_file"' "model-failure hold waits for peer checks before changing review state" assert_file_contains "$workflow_file" 'pending_checks_file="$(mktemp)"' "model-failure hold writes pending-check evidence to a real temp file" assert_file_contains "$workflow_file" 'collect_github_checks_with_retry collect_failed_github_checks "$failed_file"' "model-failure hold rejects current-head failed peer checks" @@ -585,7 +587,7 @@ assert_opencode_review_uses_codegraph_and_gpt5_fallback() { assert_file_contains "$workflow_file" "request_changes_for_merge_conflict_if_present" "model-failure hold still gates on mergeability" assert_file_contains "$workflow_file" 'unresolved_human_threads_file="$(mktemp)"' "model-failure hold writes human-thread evidence to a real temp file" assert_file_contains "$workflow_file" 'collect_unresolved_human_review_threads "$unresolved_threads_file"' "model-failure hold rechecks human review threads" - assert_file_contains "$workflow_file" "No PR approval was posted because model-output failure is not evidence that the PR has no blockers." "model-failure path fails closed instead of publishing model-exhaustion" + assert_file_contains "$workflow_file" "No PR approval was posted because model-output failure is not evidence that the PR has no blockers." "model-failure path fails closed instead of publishing model-exhaustion for non-workflow-only changes" assert_file_contains "$workflow_file" 'Detect central review-process fallback scope' "opencode approval detects central review-process fallback scope before model attempts" assert_file_contains "$workflow_file" 'id: central_review_process_fallback_scope' "opencode approval exposes central review-process fallback scope as a step output" assert_file_not_contains "$workflow_file" 'steps.central_review_process_fallback_scope.outputs.eligible != '\''true'\''' "opencode model pool is not skipped for central review-process diffs" @@ -600,8 +602,8 @@ assert_opencode_review_uses_codegraph_and_gpt5_fallback() { assert_file_contains "$workflow_file" 'scripts/ci/test_strix_quick_gate.sh)' "opencode central review fallback allowlist includes only the central gate self-test" assert_file_contains "$workflow_file" 'wait_for_peer_github_checks "$pending_checks_file"' "opencode model-failure path waits for peer checks before failing closed" assert_file_contains "$workflow_file" 'collect_unresolved_human_review_threads "$unresolved_human_threads_file"' "opencode model-failure path re-queries human threads before failing closed" - assert_file_not_contains "$workflow_file" "model-exhaustion fallback verified this is a no-diff or central review-process-only change" "opencode approval must not publish no-diff or central review model-exhaustions after model failures" - assert_file_not_contains "$workflow_file" "so the approval gate used source-backed current-head evidence instead of model prose" "opencode approval must not publish guarded model-exhaustion after model-output failures" + assert_file_contains "$workflow_file" ".github/workflows/*.yml|.github/workflows/*.yaml" "opencode model-exhaustion fallback is limited to workflow files" + assert_file_contains "$workflow_file" '[ "$changed_count" -gt 0 ] && [ "$changed_count" -le 2 ]' "opencode workflow-only fallback caps changed-file count" assert_file_contains "$workflow_file" "all configured OpenCode model attempts failed to produce a usable current-head control block" "opencode model-output failures fail the check without publishing a review" assert_file_contains "$workflow_file" "no model produced a valid review control block" "opencode model-failure path documents why approval is withheld" assert_file_contains "$workflow_file" 'OPENCODE_MODEL_ATTEMPTS: "2"' "opencode primary and deepseek review paths retry model execution" @@ -855,8 +857,7 @@ assert_opencode_review_uses_codegraph_and_gpt5_fallback() { assert_file_contains "$REPO_ROOT/scripts/ci/emit_opencode_failed_check_fallback_findings.sh" '^CMD \["/app/scripts/docker_entrypoint\.sh"\]' "opencode failed-check fallback maps missing Docker entrypoint reports to the Dockerfile CMD line" assert_file_contains "$workflow_file" "Unrelated speculative findings are invalid when failed-check evidence is present." "opencode review prompt forbids unrelated failed-check findings" assert_file_contains "$workflow_file" "run_failed_check_diagnosis" "opencode approval gate reruns OpenCode diagnosis when checks fail after the initial review" - assert_file_not_contains "$workflow_file" "so the approval gate used source-backed current-head evidence instead of model prose." "opencode approval gate must not record deterministic model-failure approval" - assert_file_not_contains "$workflow_file" "Deterministic fallback approval was used only after model-output instability and did not bypass coverage, failed-check, mergeability, or human-review gates." "opencode approval gate must not document deterministic model-failure approval" + assert_file_contains "$workflow_file" "deterministic current-head gates passed for a workflow-only change" "opencode approval gate records the narrow workflow-only model-failure fallback" assert_file_contains "$workflow_file" "request_changes_after_model_exhaustion" "opencode model-failure path reuses green current-head gates only to decide fail-closed diagnostics" assert_file_contains "$workflow_file" "request_changes_for_merge_conflict_if_present" "opencode approval gate checks mergeability before approving model or fallback output" assert_file_contains "$workflow_file" "Merge Conflict Guidance" "opencode approval gate emits explicit conflict guidance when mergeability is dirty"