Skip to content
Merged
95 changes: 92 additions & 3 deletions .github/workflows/opencode-review.yml
Original file line number Diff line number Diff line change
Expand Up @@ -895,15 +895,30 @@ jobs:
' "$clean_output" >"$comment_body_file"

if [ ! -s "$comment_body_file" ]; then
echo "OpenCode output did not include the required sentinel."
cat "$clean_output"
exit 0
if python3 scripts/ci/opencode_review_normalize_output.py \
"$HEAD_SHA" "$RUN_ID" "$RUN_ATTEMPT" "$clean_output"; then
cp "$clean_output" "$comment_body_file"
else
echo "OpenCode output did not include the required sentinel."
cat "$clean_output"
exit 0
fi
fi

gate_status=0
gate_result="$(
bash scripts/ci/opencode_review_approve_gate.sh "$HEAD_SHA" "$RUN_ID" "$RUN_ATTEMPT" "$comment_body_file" "$normalized_comment_json"
)" || gate_status=$?
if [ "$gate_status" -ne 0 ]; then
if python3 scripts/ci/opencode_review_normalize_output.py \
"$HEAD_SHA" "$RUN_ID" "$RUN_ATTEMPT" "$clean_output"; then
cp "$clean_output" "$comment_body_file"
gate_status=0
gate_result="$(
bash scripts/ci/opencode_review_approve_gate.sh "$HEAD_SHA" "$RUN_ID" "$RUN_ATTEMPT" "$comment_body_file" "$normalized_comment_json"
)" || gate_status=$?
fi
fi
printf 'OpenCode comment gate result: %s (exit %s)\n' "$gate_result" "$gate_status"
if [ "$gate_status" -eq 0 ]; then
{
Expand Down Expand Up @@ -1299,6 +1314,72 @@ jobs:
} >"$body_file"
}

build_external_failed_check_body() {
local failed_checks_file="$1"
local classification_file="$2"
local body_file="$3"
local reason
local signals

reason="$(jq -r '.reason // "external GitHub check failure"' "$classification_file")"
signals="$(
jq -r '
(.signals // [])
| map(tostring | ltrimstr("- ") | "- " + .)
| join("\n")
' "$classification_file"
)"
if [ -z "$signals" ]; then
signals="- external check failure was classified without additional signals"
fi

{
printf '## Pull request overview\n\n'
printf 'OpenCode completed its review pass, but the only failed current-head check is external infrastructure rather than a source-backed repository defect.\n\n'
printf '## Findings\n\n'
printf 'No blocking source finding was submitted. Re-run the failed workflow job so the required GitHub check can report a clean current-head result.\n\n'
printf '## Verification\n\n'
printf -- '- Result: EXTERNAL_CHECK_FAILURE\n'
printf -- '- Reason: %s\n\n' "$reason"
printf '## Gate evidence\n\n'
printf -- "- Head SHA: \`%s\`\n" "$HEAD_SHA"
printf -- '- Workflow run: %s\n' "$RUN_ID"
printf -- '- Workflow attempt: %s\n\n' "$RUN_ATTEMPT"
printf 'Failed checks:\n'
cat "$failed_checks_file"
printf '\n\nExternal infrastructure signals:\n%s\n' "$signals"
} >"$body_file"
}

stop_for_external_failed_check_if_needed() {
local failed_checks_file="$1"
local evidence_file="$2"
local body_file="$3"
local classification_file
local classification

classification_file="$(mktemp)"
if ! python3 scripts/ci/classify_failed_check_evidence.py "$evidence_file" >"$classification_file"; then
rm -f "$classification_file"
return 1
fi

if ! classification="$(
jq -r '.classification // empty' "$classification_file" 2>/dev/null
)"; then
rm -f "$classification_file"
return 1
fi
if [ "$classification" != "external_infrastructure" ]; then
rm -f "$classification_file"
return 1
fi

build_external_failed_check_body "$failed_checks_file" "$classification_file" "$body_file"
rm -f "$classification_file"
stop_approval_without_review "EXTERNAL_CHECK_FAILURE" "$(cat "$body_file")"
}

normalize_opencode_output() {
local output_file="$1"

Expand Down Expand Up @@ -1708,6 +1789,9 @@ jobs:
if ! scripts/ci/collect_failed_check_evidence.sh "$failed_check_evidence_file"; then
printf "Failed GitHub Check evidence could not be collected for current head \`%s\`.\n" "$HEAD_SHA" >"$failed_check_evidence_file"
fi
if stop_for_external_failed_check_if_needed "$failed_checks_file" "$failed_check_evidence_file" "$failed_check_review_body_file"; then
:
fi
if run_failed_check_diagnosis "$failed_checks_file" "$failed_check_evidence_file" "$failed_check_review_body_file"; then
create_pull_review "REQUEST_CHANGES" "$(cat "$failed_check_review_body_file")"
else
Expand Down Expand Up @@ -1835,6 +1919,9 @@ jobs:
if ! scripts/ci/collect_failed_check_evidence.sh "$failed_check_evidence_file"; then
printf "Failed GitHub Check evidence could not be collected for current head \`%s\`.\n" "$HEAD_SHA" >"$failed_check_evidence_file"
fi
if stop_for_external_failed_check_if_needed "$failed_checks_file" "$failed_check_evidence_file" "$failed_check_review_body_file"; then
:
fi
if run_failed_check_diagnosis "$failed_checks_file" "$failed_check_evidence_file" "$failed_check_review_body_file"; then
create_pull_review "REQUEST_CHANGES" "$(cat "$failed_check_review_body_file")"
else
Expand Down Expand Up @@ -1891,6 +1978,8 @@ jobs:
if scripts/ci/validate_opencode_failed_check_review.sh "$control_json" "$failed_checks_file" "$failed_check_evidence_file"; then
format_request_changes_body "$control_json" "$failed_check_review_body_file"
create_pull_review "REQUEST_CHANGES" "$(cat "$failed_check_review_body_file")"
elif stop_for_external_failed_check_if_needed "$failed_checks_file" "$failed_check_evidence_file" "$failed_check_review_body_file"; then
:
elif run_failed_check_diagnosis "$failed_checks_file" "$failed_check_evidence_file" "$failed_check_review_body_file"; then
create_pull_review "REQUEST_CHANGES" "$(cat "$failed_check_review_body_file")"
else
Expand Down
43 changes: 37 additions & 6 deletions scripts/checks/normalize_scorecard_sarif.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,41 @@ def is_non_blocking_scorecard_result(result: object) -> bool:
)


def downgrade_non_blocking_scorecard_result(result: dict) -> int:
"""Keep a non-blocking Scorecard result visible without tripping gates."""
rewritten = 0
if result.get("level") != "note":
result["level"] = "note"
rewritten += 1

properties = result.get("properties")
if not isinstance(properties, dict):
properties = {}
result["properties"] = properties
rewritten += 1
if properties.get("bandscopeNonBlockingScorecardSignal") is not True:
properties["bandscopeNonBlockingScorecardSignal"] = True
rewritten += 1

locations = result.get("locations")
if isinstance(locations, list) and locations:
return rewritten

result["locations"] = [
{
"physicalLocation": {
"artifactLocation": {"uri": SCORECARD_WORKFLOW_URI},
"region": {"startLine": 1},
"properties": {
"bandscopeNonBlockingScorecardSignal": True,
"bandscopeRepositoryLevelFinding": True,
},
}
}
]
return rewritten + 1


def normalize_scorecard_sarif(source: Path, target: Path) -> int:
"""Normalize Scorecard SARIF locations/results and return the change count."""
sarif = json.loads(source.read_text(encoding="utf-8"))
Expand All @@ -35,14 +70,11 @@ def normalize_scorecard_sarif(source: Path, target: Path) -> int:
results = run.get("results", [])
if not isinstance(results, list):
continue
retained_results = []
for result in results:
if is_non_blocking_scorecard_result(result):
rewritten += 1
continue
retained_results.append(result)
if not isinstance(result, dict):
continue
if is_non_blocking_scorecard_result(result):
rewritten += downgrade_non_blocking_scorecard_result(result)
locations = result.get("locations", [])
if not isinstance(locations, list):
continue
Expand Down Expand Up @@ -74,7 +106,6 @@ def normalize_scorecard_sarif(source: Path, target: Path) -> int:
)
properties["bandscopeRepositoryLevelFinding"] = True
rewritten += 1
run["results"] = retained_results

target.write_text(
json.dumps(sarif, indent=2, sort_keys=True) + "\n", encoding="utf-8"
Expand Down
Loading