Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions .github/branch-protection-policy.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
{
"branch": "main",
"required_approving_review_count": 1,
"required_status_checks": [
"applitools-core",
"pr-agent",
"deep-agent",
"audit-pr-evidence",
"backend",
"backend-postgres",
"coverage",
"CodeQL",
"codecov-analytics",
"Analyze (actions)",
"Analyze (javascript-typescript)",
"Analyze (python)",
"CodeRabbit",
"dependency-review",
"compose-smoke",
"frontend",
"label",
"codacy-equivalent-zero",
"sonar-branch-zero",
"Seer Code Review",
"SonarCloud Code Analysis"
],
"strict": true,
"require_linear_history": true,
"require_conversation_resolution": false
Comment on lines +4 to +29

Choose a reason for hiding this comment

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

Action required

5. Policy conflicts repo baseline 🐞 Bug ✓ Correctness

The new canonical required_status_checks list omits repo-documented required checks (build-test,
validate-policy-contracts) and sets require_conversation_resolution=false, conflicting with the
existing branch protection baseline documented in the repo. This will either cause unexpected
strict21 preflight failures or weaken protections unless docs/branch protection are updated in
lockstep.
Agent Prompt
### Issue description
The committed canonical policy conflicts with repo documentation and existing workflow check names, which risks breaking preflight or weakening protections.

### Issue Context
`docs/KPI_BASELINE.md` defines the currently expected required checks for `main` and requires conversation resolution.

### Fix Focus Areas
- .github/branch-protection-policy.json[1-30]
- scripts/strict21_preflight.py[15-38]
- docs/KPI_BASELINE.md[54-76]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

}
39 changes: 39 additions & 0 deletions .github/workflows/strict21-preflight.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
name: strict-21 Preflight

on:
workflow_dispatch:
pull_request:
branches:
- main

permissions:
contents: read

jobs:
strict21-preflight:
name: strict-21-preflight
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Run strict-21 preflight
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
STAMP: ${{ github.event.pull_request.number || github.run_id }}
run: |
set -euo pipefail
mkdir -p .tmp/strict21-preflight
python3 scripts/strict21_preflight.py \
--repo "${GITHUB_REPOSITORY}" \
--branch main \
--ref "${GITHUB_SHA}" \
--out-json ".tmp/strict21-preflight/preflight.json" \
--out-md ".tmp/strict21-preflight/preflight.md"
Comment on lines +27 to +32

Choose a reason for hiding this comment

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

Action required

3. Wrong ref for checks 🐞 Bug ⛯ Reliability

The workflow inventories emitted checks using GITHUB_SHA, which on PR events can differ from the
head SHA and can be queried before other check-runs exist, causing flaky false non_compliant
failures. Use the PR head SHA (and/or wait/retry for expected contexts) before concluding missing
emitted checks.
Agent Prompt
### Issue description
The preflight workflow uses `GITHUB_SHA` as the `--ref` for emitted-check inventory. On PR runs this can point at a merge SHA and/or be queried before other checks are created, producing flaky false failures.

### Issue Context
The checker currently queries check-runs/status once for the resolved SHA and immediately compares against canonical contexts.

### Fix Focus Areas
- .github/workflows/strict21-preflight.yml[20-32]
- scripts/strict21_preflight.py[226-249]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

cat .tmp/strict21-preflight/preflight.md

- name: Upload strict-21 artifact
uses: actions/upload-artifact@v4
with:
name: strict21-preflight
path: .tmp/strict21-preflight
Comment on lines +20 to +39

Choose a reason for hiding this comment

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

Action required

1. strict21_preflight.py lacks tests 📘 Rule violation ⛯ Reliability

This PR introduces new CI/tooling behavior (strict-21 preflight gate) but does not add deterministic
automated tests or commit deterministic evidence artifacts to verify the change. Reviewers cannot
reproducibly validate that the new preflight logic behaves as intended from repository artifacts
alone.
Agent Prompt
## Issue description
New strict-21 CI/tooling behavior is added, but the PR does not include deterministic automated test coverage or deterministic, committed evidence artifacts verifying the change.

## Issue Context
The compliance requirement expects tooling/test-impacting changes to be reproducibly verifiable from repository artifacts (tests or committed evidence). The workflow currently uploads only a transient Actions artifact from `.tmp/strict21-preflight`.

## Fix Focus Areas
- .github/workflows/strict21-preflight.yml[20-39]
- scripts/strict21_preflight.py[99-129]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

301 changes: 301 additions & 0 deletions scripts/strict21_preflight.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,301 @@
#!/usr/bin/env python3
from __future__ import annotations

import argparse
import json
import os
from dataclasses import dataclass
from datetime import datetime, timezone
from pathlib import Path
from typing import Any
from urllib.parse import urlparse
from urllib.error import HTTPError, URLError
from urllib.request import Request, urlopen

PERMISSION_HTTP_CODES = {401, 403, 404}
DEFAULT_CANONICAL_CONTEXTS = [
"applitools-core",
"pr-agent",
"deep-agent",
"audit-pr-evidence",
"backend",
"backend-postgres",
"coverage",
"CodeQL",
"codecov-analytics",
"Analyze (actions)",
"Analyze (javascript-typescript)",
"Analyze (python)",
"CodeRabbit",
"dependency-review",
"compose-smoke",
"frontend",
"label",
"codacy-equivalent-zero",
"sonar-branch-zero",
"Seer Code Review",
"SonarCloud Code Analysis",
]


@dataclass
class PreflightResult:
status: str
findings: list[str]
missing_in_branch_protection: list[str]
missing_in_check_runs: list[str]
ref_sha: str | None
http_status: int | None = None
http_error: str | None = None


def _parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser(
description="Strict-21 preflight: compare canonical contexts against branch protection and emitted check-runs."
)
parser.add_argument("--repo", required=True, help="GitHub repository in owner/repo format")
parser.add_argument("--ref", default="main", help="Ref (branch/tag/SHA) used for emitted check context inventory")
parser.add_argument("--branch", default="main", help="Branch used for branch-protection context inventory")
parser.add_argument("--api-base", default="https://api.github.com", help="GitHub API base URL")
parser.add_argument(
"--canonical-contexts",
default="",
help="Optional comma-separated canonical context names; defaults to built-in strict-21 list.",
)
parser.add_argument("--out-json", required=True, help="Output JSON path")
parser.add_argument("--out-md", required=True, help="Output markdown path")
return parser.parse_args()


def _classify_http_status(code: int) -> str:
return "inconclusive_permissions" if code in PERMISSION_HTTP_CODES else "api_error"


def _api_get(api_base: str, repo: str, path: str, token: str) -> dict[str, Any]:
url = f"{api_base.rstrip('/')}/repos/{repo}/{path.lstrip('/')}"
parsed = urlparse(url)
if parsed.scheme not in {"http", "https"} or not parsed.netloc:
raise ValueError(f"Unsupported API URL: {url!r}")
req = Request(
url,
headers={
"Accept": "application/vnd.github+json",
"Authorization": f"Bearer {token}",
"X-GitHub-Api-Version": "2022-11-28",
"User-Agent": "reframe-strict21-preflight",
},
method="GET",
)
with urlopen(req, timeout=30) as resp: # nosec B310 - URL scheme and host are validated above
return json.loads(resp.read().decode("utf-8"))


def _canonical_contexts(raw: str) -> list[str]:
if not raw.strip():
return list(DEFAULT_CANONICAL_CONTEXTS)
return [item.strip() for item in raw.split(",") if item.strip()]


def evaluate_contexts(
*,
canonical_contexts: list[str],
branch_required_checks: list[str],
emitted_contexts: list[str],
ref_sha: str | None,
) -> PreflightResult:
missing_in_branch = [ctx for ctx in canonical_contexts if ctx not in branch_required_checks]
missing_in_emitted = [ctx for ctx in canonical_contexts if ctx not in emitted_contexts]

findings: list[str] = []
if missing_in_branch:
findings.append(
"Canonical contexts missing from branch protection: "
+ ", ".join(missing_in_branch)
)
if missing_in_emitted:
findings.append(
"Canonical contexts missing from emitted checks on ref: "
+ ", ".join(missing_in_emitted)
)

status = "compliant" if not findings else "non_compliant"
return PreflightResult(
status=status,
findings=findings,
missing_in_branch_protection=missing_in_branch,
missing_in_check_runs=missing_in_emitted,
ref_sha=ref_sha,
)


def _collect_emitted_contexts(check_runs: dict[str, Any], status_payload: dict[str, Any]) -> list[str]:
contexts: set[str] = set(_collect_named_values(check_runs.get("check_runs") or [], "name"))
contexts.update(_collect_named_values(status_payload.get("statuses") or [], "context"))
return sorted(contexts)


def _collect_named_values(items: list[dict[str, Any]], field: str) -> list[str]:
values: list[str] = []
for item in items:
value = str(item.get(field) or "").strip()
if value:
values.append(value)
return values


def _render_markdown(payload: dict[str, Any]) -> str:
lines = [
"# strict-21 Preflight",
"",
f"- Status: `{payload['status']}`",
f"- Repo: `{payload['repo']}`",
f"- Branch policy target: `{payload['branch']}`",
f"- Ref target: `{payload['ref']}`",
f"- Resolved ref SHA: `{payload.get('ref_sha') or 'unknown'}`",
f"- Timestamp (UTC): `{payload['timestamp_utc']}`",
"",
"## Findings",
]

findings = payload.get("findings") or []
if findings:
lines.extend(f"- {item}" for item in findings)
else:
lines.append("- None")

lines.extend(
[
"",
"## Missing contexts",
"",
f"- branch protection missing: `{len(payload.get('missing_in_branch_protection') or [])}`",
f"- emitted checks missing: `{len(payload.get('missing_in_check_runs') or [])}`",
]
)

if payload.get("http_status") is not None:
lines.extend(
[
"",
"## API details",
f"- HTTP status: `{payload['http_status']}`",
f"- Message: `{payload.get('http_error') or ''}`",
]
)

return "\n".join(lines) + "\n"


def _missing_token_result() -> tuple[PreflightResult, list[str], list[str]]:
result = PreflightResult(
status="inconclusive_permissions",
findings=["GitHub token missing; strict-21 preflight cannot query branch protection/check-runs."],
missing_in_branch_protection=[],
missing_in_check_runs=[],
ref_sha=None,
)
return result, [], []


def _http_error_result(exc: HTTPError) -> tuple[PreflightResult, list[str], list[str]]:
message = exc.read().decode("utf-8", errors="replace")[:1000]
result = PreflightResult(
status=_classify_http_status(exc.code),
findings=[f"GitHub API request failed (HTTP {exc.code}) while running strict-21 preflight."],
missing_in_branch_protection=[],
missing_in_check_runs=[],
ref_sha=None,
http_status=exc.code,
http_error=message,
)
return result, [], []


def _url_error_result(exc: URLError) -> tuple[PreflightResult, list[str], list[str]]:
result = PreflightResult(
status="api_error",
findings=["Network error while requesting GitHub API for strict-21 preflight."],
missing_in_branch_protection=[],
missing_in_check_runs=[],
ref_sha=None,
http_error=str(exc.reason),
)
return result, [], []


def _run_preflight(
*,
args: argparse.Namespace,
canonical: list[str],
token: str,
) -> tuple[PreflightResult, list[str], list[str]]:
try:
protection = _api_get(args.api_base, args.repo, f"branches/{args.branch}/protection", token)
ref_payload = _api_get(args.api_base, args.repo, f"commits/{args.ref}", token)
ref_sha = str(ref_payload.get("sha") or "").strip() or None
if ref_sha is None:
raise RuntimeError(f"Unable to resolve SHA for ref {args.ref!r}")
check_runs = _api_get(args.api_base, args.repo, f"commits/{ref_sha}/check-runs?per_page=100", token)
status_payload = _api_get(args.api_base, args.repo, f"commits/{ref_sha}/status", token)

branch_required_checks = sorted((protection.get("required_status_checks") or {}).get("contexts") or [])
emitted_contexts = _collect_emitted_contexts(check_runs, status_payload)
Comment on lines +233 to +242

Choose a reason for hiding this comment

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

Action required

4. Branch checks field ignored 🐞 Bug ✓ Correctness

Branch protection required checks are read only from required_status_checks.contexts; if the repo
uses the newer required_status_checks.checks field, the script may treat the required list as empty
and misreport compliance. Parse both contexts and checks to avoid false results.
Agent Prompt
### Issue description
The preflight only reads `required_status_checks.contexts` from the branch protection API response, which can miss required checks if the branch protection config is represented differently.

### Issue Context
A missing/empty `branch_required_checks` list will cause false non-compliance for every canonical context.

### Fix Focus Areas
- scripts/strict21_preflight.py[233-243]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

result = evaluate_contexts(
canonical_contexts=canonical,
branch_required_checks=branch_required_checks,
emitted_contexts=emitted_contexts,
ref_sha=ref_sha,
)
return result, branch_required_checks, emitted_contexts
except HTTPError as exc:
return _http_error_result(exc)
except URLError as exc:
return _url_error_result(exc)


def main() -> int:
args = _parse_args()
out_json = Path(args.out_json)
out_md = Path(args.out_md)
canonical = _canonical_contexts(args.canonical_contexts)

token = (os.environ.get("GITHUB_TOKEN") or "").strip() or (os.environ.get("GH_TOKEN") or "").strip()
now = datetime.now(timezone.utc).isoformat()
if not token:
result, branch_required_checks, emitted_contexts = _missing_token_result()
else:
result, branch_required_checks, emitted_contexts = _run_preflight(
args=args,
canonical=canonical,
token=token,
)

payload = {
"status": result.status,
"repo": args.repo,
"branch": args.branch,
"ref": args.ref,
"ref_sha": result.ref_sha,
"timestamp_utc": now,
"canonical_contexts": canonical,
"branch_protection_required_checks": branch_required_checks,
"emitted_contexts": emitted_contexts,
"missing_in_branch_protection": result.missing_in_branch_protection,
"missing_in_check_runs": result.missing_in_check_runs,
"findings": result.findings,
"http_status": result.http_status,
"http_error": result.http_error,
}

out_json.parent.mkdir(parents=True, exist_ok=True)
out_md.parent.mkdir(parents=True, exist_ok=True)
out_json.write_text(json.dumps(payload, indent=2, sort_keys=True) + "\n", encoding="utf-8")
out_md.write_text(_render_markdown(payload), encoding="utf-8")

if result.status in {"non_compliant", "api_error"}:
return 1
return 0
Comment on lines +262 to +297

Choose a reason for hiding this comment

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

Action required

2. Preflight passes without token 📘 Rule violation ⛯ Reliability

When GITHUB_TOKEN/GH_TOKEN is missing or the API returns permission-related errors, the
preflight sets status to inconclusive_permissions but still exits with code 0. This can produce
a green required check even though required inputs/queries were unavailable, masking missing
required artifacts/diagnostics.
Agent Prompt
## Issue description
The preflight returns exit code 0 for `inconclusive_permissions` (e.g., missing token or 401/403/404), which can silently pass a required check even though required inputs/queries were unavailable.

## Issue Context
Compliance requires failing loudly or explicitly indicating missing required artifacts/inputs instead of reporting success. The script already sets `status="inconclusive_permissions"`, but `main()` does not treat it as a failing status.

## Fix Focus Areas
- scripts/strict21_preflight.py[189-197]
- scripts/strict21_preflight.py[262-297]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



if __name__ == "__main__":
raise SystemExit(main())
Loading