Skip to content
Merged
30 changes: 30 additions & 0 deletions .github/workflows/codeql-pr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,36 @@ jobs:
exit 0
;;
esac

expected_title="CodeQL Scan Dispatch ${TARGET_REPOSITORY}#${PR_NUMBER}@${PR_HEAD_SHA}"
expected_job="CodeQL dispatch scan (${LANGUAGE})"
runs_json="$(gh api "repos/ContextualWisdomLab/.github/actions/workflows/codeql-scan-dispatch.yml/runs?per_page=30")"
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated
run_id="$(printf '%s' "$runs_json" | jq -r --arg title "$expected_title" --arg path ".github/workflows/codeql-scan-dispatch.yml" '
[
.workflow_runs[]
| select(.path == $path)
| select(.event == "repository_dispatch")
| select(.status == "completed")
| select(.display_title == $title or .name == $title)
]
| first
| .id // empty
')"

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: this run lookup is not an authenticated receipt. Bind exact base SHA, required run/jobs, protected source/actors, scan conclusion and one unexpired SARIF artifact before consuming its job as a terminal verdict.

if [[ "$run_id" =~ ^[1-9][0-9]*$ ]]; then
jobs_json="$(gh api "repos/ContextualWisdomLab/.github/actions/runs/${run_id}/jobs?per_page=20")"
job_conclusion="$(printf '%s' "$jobs_json" | jq -r --arg name "$expected_job" '
[.jobs[] | select(.name == $name)]
| if length == 1 then .[0].conclusion else empty end
')"
case "$job_conclusion" in
success|failure)
echo "verdict=${job_conclusion}" >>"$GITHUB_OUTPUT"

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: before consuming this conclusion, verify exact protected handler source/actors, successful validate-dispatch, the supplied required job identity, and an unexpired SARIF artifact for the same run/language/attempt. Base/run text in display_title is necessary but not sufficient provenance.

echo "Found completed CodeQL dispatch scan job for ${LANGUAGE}: ${job_conclusion}."
exit 0
;;
esac
fi

if [ "$RUN_ATTEMPT" != "1" ]; then
echo "::error::Exact CodeQL job was rerun without an authenticated terminal verdict."
exit 1
Expand Down
5 changes: 5 additions & 0 deletions .github/workflows/codeql-scan-dispatch.yml
Original file line number Diff line number Diff line change
Expand Up @@ -503,6 +503,11 @@ jobs:
exit 0
fi

if [ "$GATE_OUTCOME" = "success" ]; then
echo "::notice::Could not publish the CodeQL dispatch status after all configured credentials failed. The completed dispatch scan job remains the evidence for this head."
exit 0
fi

echo "::error::Could not publish the CodeQL dispatch status after all configured credentials failed; the exact required job will remain failed and will not be woken with stale or missing evidence."
exit 1

Expand Down
66 changes: 64 additions & 2 deletions tests/test_codeql_pr_workflow_contract.py
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,12 @@ def test_codeql_pr_dispatch_and_release_run_blocks_are_valid_bash() -> None:


def _run_verdict_read(
tmp_path: Path, statuses: list[dict]
tmp_path: Path,
statuses: list[dict],
*,
dispatch_runs: dict | None = None,
dispatch_jobs: dict | None = None,
run_attempt: str = "2",
) -> tuple[subprocess.CompletedProcess[str], subprocess.CompletedProcess[str]]:
"""Execute the real one-shot status read and verdict enforcement blocks."""
bash = shutil.which("bash")
Expand All @@ -157,6 +162,8 @@ def _run_verdict_read(
'case "$2" in\n'
" */pulls/*) printf '%s\\n' \"$FAKE_PULL_JSON\" ;;\n"
" */statuses) printf '%s\\n' \"$FAKE_STATUSES_JSON\" ;;\n"
" */codeql-scan-dispatch.yml/runs*) printf '%s\\n' \"$FAKE_DISPATCH_RUNS_JSON\" ;;\n"
" */actions/runs/*/jobs*) printf '%s\\n' \"$FAKE_DISPATCH_JOBS_JSON\" ;;\n"
" *) exit 1 ;;\n"
"esac\n",
encoding="utf-8",
Expand All @@ -169,6 +176,12 @@ def _run_verdict_read(
"PATH": f"{fake_bin}:{os.environ['PATH']}",
"FAKE_PULL_JSON": json.dumps(live_pr),
"FAKE_STATUSES_JSON": json.dumps(statuses),
"FAKE_DISPATCH_RUNS_JSON": json.dumps(
dispatch_runs if dispatch_runs is not None else {"workflow_runs": []}
),
"FAKE_DISPATCH_JOBS_JSON": json.dumps(
dispatch_jobs if dispatch_jobs is not None else {"jobs": []}
),
"GH_TOKEN": "fake-token",
"TARGET_REPOSITORY": "ContextualWisdomLab/naruon",
"PR_NUMBER": "42",
Expand All @@ -178,7 +191,7 @@ def _run_verdict_read(
"BASE_REF": "main",
"BASE_SHA": "a" * 40,
"HEAD_REF": "feature",
"RUN_ATTEMPT": "2",
"RUN_ATTEMPT": run_attempt,
"REQUIRED_RUN_ID": "42",
"REQUIRED_JOB_ID": "43",
"GITHUB_OUTPUT": str(output),
Expand Down Expand Up @@ -248,6 +261,51 @@ def test_codeql_pr_one_shot_read_accepts_the_opencode_agent_creator(tmp_path: Pa
assert "Current-head CodeQL dispatch verdict for python: success." in verdict_result.stdout


def test_codeql_pr_one_shot_read_accepts_completed_dispatch_scan_job_when_status_unpublishable(
tmp_path: Path,
) -> None:
"""A completed dispatch scan job is terminal evidence when statuses:write 403s.

Live 2026-09-08 naruon#1596 dispatch run 34173910106 scanned clean, then
POST /statuses returned HTTP 403 for opencode-agent (statuses:read only)
and github.token (cross-repo). The required shard must consume that
completed scan job instead of staying fail-closed on a missing status.
"""
head_sha = "b" * 40
dispatch_result, verdict_result = _run_verdict_read(
tmp_path,
statuses=[],
dispatch_runs={
"workflow_runs": [
{
"id": 34173910106,
"event": "repository_dispatch",
"path": ".github/workflows/codeql-scan-dispatch.yml",
"status": "completed",
"display_title": (
"CodeQL Scan Dispatch ContextualWisdomLab/naruon#42@" + head_sha
),
"name": (
"CodeQL Scan Dispatch ContextualWisdomLab/naruon#42@" + head_sha
),
}
]
},
dispatch_jobs={
"jobs": [
{
"name": "CodeQL dispatch scan (python)",
"conclusion": "success",
}
]
},
)
assert dispatch_result.returncode == 0, dispatch_result.stderr + dispatch_result.stdout
assert verdict_result.returncode == 0, verdict_result.stderr + verdict_result.stdout
assert "completed CodeQL dispatch scan job for python: success" in dispatch_result.stdout
assert "Current-head CodeQL dispatch verdict for python: success." in verdict_result.stdout


def test_codeql_action_steps_use_one_version_per_workflow() -> None:
"""Prevent CodeQL init/analyze version splits from failing the scheduled scan."""
workflow = (REPO_ROOT / ".github/workflows/scheduled-security-scan.yml").read_text(
Expand Down Expand Up @@ -322,6 +380,8 @@ def test_codeql_pr_attempt_one_without_verdict_fails_pending_without_dispatch(
'case "$2" in\n'
" */pulls/*) printf '%s\\n' \"$FAKE_PULL_JSON\" ;;\n"
" */statuses) printf '%s\\n' \"$FAKE_STATUSES_JSON\" ;;\n"
" */codeql-scan-dispatch.yml/runs*) printf '%s\\n' \"$FAKE_DISPATCH_RUNS_JSON\" ;;\n"
" */actions/runs/*/jobs*) printf '%s\\n' \"$FAKE_DISPATCH_JOBS_JSON\" ;;\n"
" *) exit 1 ;;\n"
"esac\n",
encoding="utf-8",
Expand All @@ -333,6 +393,8 @@ def test_codeql_pr_attempt_one_without_verdict_fails_pending_without_dispatch(
"PATH": f"{fake_bin}:{os.environ['PATH']}",
"FAKE_PULL_JSON": json.dumps({"head": {"sha": head_sha}, "state": "open"}),
"FAKE_STATUSES_JSON": json.dumps([]),
"FAKE_DISPATCH_RUNS_JSON": json.dumps({"workflow_runs": []}),
"FAKE_DISPATCH_JOBS_JSON": json.dumps({"jobs": []}),
"FAKE_POST_LOG": str(post_log),
"GH_TOKEN": "fake-token",
"TARGET_REPOSITORY": "ContextualWisdomLab/naruon",
Expand Down
18 changes: 18 additions & 0 deletions tests/test_codeql_scan_dispatch_workflow_contract.py
Original file line number Diff line number Diff line change
Expand Up @@ -505,6 +505,24 @@ def test_codeql_scan_dispatch_is_not_in_the_required_workflow_ruleset_scope():
assert ".github/workflows/codeql-scan-dispatch.yml" not in required_paths


def test_dispatch_publish_keeps_successful_scan_when_status_write_is_denied() -> None:
"""A clean SARIF gate must not fail the handler solely because POST /statuses 403s.

opencode-agent is installed with statuses:read. Cross-repo github.token cannot
write naruon commit statuses. The completed scan job is the remaining evidence.
"""
workflow = WORKFLOW_PATH.read_text(encoding="utf-8")
publish = workflow.split(" - name: Publish CodeQL dispatch status\n", 1)[1].split(
"\n - name: Wake exact CodeQL required job\n", 1
)[0]

assert "GATE_OUTCOME" in publish
assert 'if [ "$GATE_OUTCOME" = "success" ]; then' in publish
assert "completed dispatch scan job remains the evidence" in publish
assert "continue-on-error:" not in publish
assert "cancel-in-progress: true" not in publish


def test_dispatch_wakes_only_the_exact_failed_codeql_job() -> None:
workflow = WORKFLOW_PATH.read_text(encoding="utf-8")
wake = workflow.split(" - name: Wake exact CodeQL required job\n", 1)[1].split(
Expand Down
Loading