@@ -143,6 +143,11 @@ PR_SHADOW_COMPENSATION_DESCRIPTION = (
|
||||
"shadowed by successful push status on same SHA; see "
|
||||
".gitea/scripts/status-reaper.py)"
|
||||
)
|
||||
GOVERNANCE_SHADOW_COMPENSATION_DESCRIPTION = (
|
||||
"Compensated by status-reaper (non-required pull_request/pull_request_review "
|
||||
"governance shadow overridden by successful pull_request_target status; see "
|
||||
".gitea/scripts/status-reaper.py)"
|
||||
)
|
||||
CANCELLED_PUSH_COMPENSATION_DESCRIPTION = (
|
||||
"Compensated by status-reaper (push run was cancelled/superseded; "
|
||||
"Gitea 1.22.6 reports cancelled runs as failure statuses)"
|
||||
@@ -153,6 +158,20 @@ CANCELLED_DESCRIPTION = "Has been cancelled"
|
||||
# default-branch workflow runs.
|
||||
PUSH_SUFFIX = " (push)"
|
||||
PULL_REQUEST_SUFFIX = " (pull_request)"
|
||||
PULL_REQUEST_TARGET_SUFFIX = " (pull_request_target)"
|
||||
PULL_REQUEST_REVIEW_SUFFIX = " (pull_request_review)"
|
||||
|
||||
# Governance workflows whose non-required `(pull_request)` / `(pull_request_review)`
|
||||
# shadows may be compensated when the trusted `(pull_request_target)` variant is
|
||||
# green. This is an EXACT active allowlist — every other workflow is preserved,
|
||||
# even if it has no `push:` trigger, to avoid masking real failures.
|
||||
GOVERNANCE_SHADOW_ALLOWLIST = frozenset(
|
||||
{"sop-checklist", "qa-review", "security-review"}
|
||||
)
|
||||
# Retired workflows whose historical shadow contexts still appear on old commits
|
||||
# and must remain compensatable even though the workflow YAML has been removed.
|
||||
# They are treated as known non-push when absent from the trigger map.
|
||||
GOVERNANCE_SHADOW_RETIRED_ALLOWLIST = frozenset({"sop-tier-check"})
|
||||
|
||||
# --------------------------------------------------------------------------
|
||||
# Conductor snapshot (operator-config#158)
|
||||
@@ -488,6 +507,51 @@ def push_equivalent_context(context: str) -> str | None:
|
||||
return f"{workflow_name} / {job_name}{PUSH_SUFFIX}"
|
||||
|
||||
|
||||
def target_equivalent_context(context: str, source_suffix: str) -> str | None:
|
||||
"""Return the matching `(pull_request_target)` context for a suffixed context.
|
||||
|
||||
Handles `(pull_request)` and `(pull_request_review)` governance shadows.
|
||||
"""
|
||||
parsed = parse_suffixed_context(context, source_suffix)
|
||||
if parsed is None:
|
||||
return None
|
||||
workflow_name, job_name = parsed
|
||||
return f"{workflow_name} / {job_name}{PULL_REQUEST_TARGET_SUFFIX}"
|
||||
|
||||
|
||||
def is_governance_shadow_context(
|
||||
context: str, workflow_trigger_map: dict[str, bool]
|
||||
) -> bool:
|
||||
"""True if `context` is a compensatable governance shadow.
|
||||
|
||||
Active governance workflows (`sop-checklist`, `qa-review`, `security-review`)
|
||||
are compensatable only when their trigger map entry is explicitly `False`.
|
||||
Retired workflows (`sop-tier-check`) may be absent from the trigger map
|
||||
because their YAML was removed; they are treated as known non-push so their
|
||||
historical shadow contexts remain compensatable.
|
||||
|
||||
Workflows that DO have a `push:` trigger are excluded even if they are in an
|
||||
allowlist — their PR/review status is an independent gate signal. Unknown
|
||||
workflows or workflows not in any allowlist are preserved (fail-closed).
|
||||
"""
|
||||
for suffix in (PULL_REQUEST_SUFFIX, PULL_REQUEST_REVIEW_SUFFIX):
|
||||
parsed = parse_suffixed_context(context, suffix)
|
||||
if parsed is not None:
|
||||
workflow_name, _job_name = parsed
|
||||
if workflow_name in GOVERNANCE_SHADOW_RETIRED_ALLOWLIST:
|
||||
# Retired workflow: absent from the trigger map is expected.
|
||||
# Only a push-triggered retired workflow is preserved.
|
||||
has_push = workflow_trigger_map.get(workflow_name)
|
||||
return has_push is not True
|
||||
if workflow_name not in GOVERNANCE_SHADOW_ALLOWLIST:
|
||||
return False
|
||||
# Active allowlist workflow: require an explicit known-no-push entry.
|
||||
# If the parser ever misses the workflow, fail-closed (preserve).
|
||||
has_push = workflow_trigger_map.get(workflow_name)
|
||||
return has_push is False
|
||||
return False
|
||||
|
||||
|
||||
# --------------------------------------------------------------------------
|
||||
# Compensating POST
|
||||
# --------------------------------------------------------------------------
|
||||
@@ -562,6 +626,8 @@ def reap(
|
||||
"compensated_pr_shadowed_by_push_success": 0,
|
||||
"compensated_cancelled_push": 0,
|
||||
"preserved_pr_without_push_success": 0,
|
||||
"compensated_governance_shadow": 0,
|
||||
"preserved_governance_without_target_success": 0,
|
||||
"compensated_contexts": [],
|
||||
}
|
||||
|
||||
@@ -594,6 +660,36 @@ def reap(
|
||||
counters["preserved_non_failure"] += 1
|
||||
continue
|
||||
|
||||
# Governance shadow compensation (#2770 / #2767).
|
||||
# Non-required `(pull_request)` and `(pull_request_review)` contexts
|
||||
# emitted by governance workflows (sop-checklist, qa-review,
|
||||
# security-review, retired sop-tier-check) are informational shadows
|
||||
# of the required `(pull_request_target)` context. When the trusted
|
||||
# target context succeeded, the shadow must not keep the aggregate
|
||||
# commit status red. CI workflows that also have a `push:` trigger are
|
||||
# excluded — their `(pull_request)` status is an independent gate.
|
||||
if is_governance_shadow_context(context, workflow_trigger_map):
|
||||
source_suffix = (
|
||||
PULL_REQUEST_SUFFIX
|
||||
if context.endswith(PULL_REQUEST_SUFFIX)
|
||||
else PULL_REQUEST_REVIEW_SUFFIX
|
||||
)
|
||||
target_equivalent = target_equivalent_context(context, source_suffix)
|
||||
if target_equivalent is not None and target_equivalent in successful_contexts:
|
||||
post_compensating_status(
|
||||
sha,
|
||||
context,
|
||||
s.get("target_url"),
|
||||
description=GOVERNANCE_SHADOW_COMPENSATION_DESCRIPTION,
|
||||
dry_run=dry_run,
|
||||
)
|
||||
counters["compensated"] += 1
|
||||
counters["compensated_governance_shadow"] += 1
|
||||
counters["compensated_contexts"].append(context)
|
||||
else:
|
||||
counters["preserved_governance_without_target_success"] += 1
|
||||
continue
|
||||
|
||||
# Default-branch `pull_request` contexts can be stale shadows of
|
||||
# the exact same workflow/job already proven by the successful
|
||||
# `push` context on the same SHA. Compensate only that narrow
|
||||
@@ -618,7 +714,7 @@ def reap(
|
||||
|
||||
# Only `(push)`-suffix contexts hit the hardcoded-suffix bug.
|
||||
# Other failed contexts are preserved unless handled by the
|
||||
# pull-request-shadow rule above.
|
||||
# governance-shadow or pull-request-shadow rules above.
|
||||
if not context.endswith(PUSH_SUFFIX):
|
||||
counters["preserved_non_push_suffix"] += 1
|
||||
continue
|
||||
@@ -766,6 +862,8 @@ def reap_branch(
|
||||
"compensated_pr_shadowed_by_push_success": 0,
|
||||
"compensated_cancelled_push": 0,
|
||||
"preserved_pr_without_push_success": 0,
|
||||
"compensated_governance_shadow": 0,
|
||||
"preserved_governance_without_target_success": 0,
|
||||
"compensated_per_sha": {},
|
||||
"sha_api_errors": 0,
|
||||
"skipped": True,
|
||||
@@ -783,6 +881,8 @@ def reap_branch(
|
||||
"compensated_pr_shadowed_by_push_success": 0,
|
||||
"compensated_cancelled_push": 0,
|
||||
"preserved_pr_without_push_success": 0,
|
||||
"compensated_governance_shadow": 0,
|
||||
"preserved_governance_without_target_success": 0,
|
||||
"compensated_per_sha": {},
|
||||
"sha_api_errors": 0,
|
||||
}
|
||||
@@ -825,6 +925,8 @@ def reap_branch(
|
||||
"compensated_pr_shadowed_by_push_success",
|
||||
"compensated_cancelled_push",
|
||||
"preserved_pr_without_push_success",
|
||||
"compensated_governance_shadow",
|
||||
"preserved_governance_without_target_success",
|
||||
):
|
||||
aggregate[key] += per_sha[key]
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import importlib.util
|
||||
import json
|
||||
import pathlib
|
||||
import pytest
|
||||
import urllib.error
|
||||
|
||||
|
||||
@@ -250,3 +251,339 @@ def test_get_combined_status_self_fetches_when_sha_not_in_snapshot(monkeypatch):
|
||||
assert combined["state"] == "success"
|
||||
finally:
|
||||
os.unlink(path)
|
||||
|
||||
|
||||
def test_reap_compensates_governance_shadow_when_target_passed(monkeypatch):
|
||||
mod = load_reaper()
|
||||
posted = []
|
||||
|
||||
def fake_post(sha, context, target_url, *, description="", dry_run=False):
|
||||
posted.append((sha, context, target_url, description, dry_run))
|
||||
|
||||
monkeypatch.setattr(mod, "post_compensating_status", fake_post)
|
||||
|
||||
# sop-checklist has no push trigger, so its failed (pull_request) shadow is
|
||||
# noise when the required (pull_request_target) context is green.
|
||||
counters = mod.reap(
|
||||
{"sop-checklist": False, "qa-review": False, "security-review": False},
|
||||
{
|
||||
"statuses": [
|
||||
{
|
||||
"context": "sop-checklist / all-items-acked (pull_request)",
|
||||
"status": "failure",
|
||||
"target_url": "https://git.example.test/sop-pr",
|
||||
},
|
||||
{
|
||||
"context": "sop-checklist / all-items-acked (pull_request_target)",
|
||||
"status": "success",
|
||||
},
|
||||
{
|
||||
"context": "qa-review / approved (pull_request_review)",
|
||||
"status": "failure",
|
||||
"target_url": "https://git.example.test/qa-pr-review",
|
||||
},
|
||||
{
|
||||
"context": "qa-review / approved (pull_request_target)",
|
||||
"status": "success",
|
||||
},
|
||||
],
|
||||
},
|
||||
"db3b7a93e31adc0cb072a6d177d92dd73275a191",
|
||||
)
|
||||
|
||||
assert counters["compensated_governance_shadow"] == 2
|
||||
assert counters["preserved_governance_without_target_success"] == 0
|
||||
assert posted == [
|
||||
(
|
||||
"db3b7a93e31adc0cb072a6d177d92dd73275a191",
|
||||
"sop-checklist / all-items-acked (pull_request)",
|
||||
"https://git.example.test/sop-pr",
|
||||
mod.GOVERNANCE_SHADOW_COMPENSATION_DESCRIPTION,
|
||||
False,
|
||||
),
|
||||
(
|
||||
"db3b7a93e31adc0cb072a6d177d92dd73275a191",
|
||||
"qa-review / approved (pull_request_review)",
|
||||
"https://git.example.test/qa-pr-review",
|
||||
mod.GOVERNANCE_SHADOW_COMPENSATION_DESCRIPTION,
|
||||
False,
|
||||
),
|
||||
]
|
||||
|
||||
|
||||
def test_reap_preserves_governance_shadow_when_target_missing_or_failed(monkeypatch):
|
||||
mod = load_reaper()
|
||||
posted = []
|
||||
monkeypatch.setattr(
|
||||
mod,
|
||||
"post_compensating_status",
|
||||
lambda sha, context, target_url, *, description="", dry_run=False: posted.append(
|
||||
context
|
||||
),
|
||||
)
|
||||
|
||||
counters = mod.reap(
|
||||
{"sop-checklist": False},
|
||||
{
|
||||
"statuses": [
|
||||
{
|
||||
"context": "sop-checklist / all-items-acked (pull_request)",
|
||||
"status": "failure",
|
||||
},
|
||||
# target context failed → preserve the shadow as a real signal.
|
||||
{
|
||||
"context": "sop-checklist / all-items-acked (pull_request_target)",
|
||||
"status": "failure",
|
||||
},
|
||||
],
|
||||
},
|
||||
"db3b7a93e31adc0cb072a6d177d92dd73275a191",
|
||||
)
|
||||
|
||||
assert counters["compensated_governance_shadow"] == 0
|
||||
assert counters["preserved_governance_without_target_success"] == 1
|
||||
assert posted == []
|
||||
|
||||
|
||||
def test_reap_preserves_ci_pull_request_failure_even_when_target_passed(monkeypatch):
|
||||
mod = load_reaper()
|
||||
posted = []
|
||||
monkeypatch.setattr(
|
||||
mod,
|
||||
"post_compensating_status",
|
||||
lambda sha, context, target_url, *, description="", dry_run=False: posted.append(
|
||||
context
|
||||
),
|
||||
)
|
||||
|
||||
# A CI workflow that also has a push trigger is NOT a governance shadow;
|
||||
# its (pull_request) failure is an independent gate signal and must be
|
||||
# preserved even if a (pull_request_target) variant happens to be green.
|
||||
counters = mod.reap(
|
||||
{"CI": True},
|
||||
{
|
||||
"statuses": [
|
||||
{
|
||||
"context": "CI / Platform (Go) (pull_request)",
|
||||
"status": "failure",
|
||||
},
|
||||
{
|
||||
"context": "CI / Platform (Go) (pull_request_target)",
|
||||
"status": "success",
|
||||
},
|
||||
],
|
||||
},
|
||||
"db3b7a93e31adc0cb072a6d177d92dd73275a191",
|
||||
)
|
||||
|
||||
assert counters["compensated_governance_shadow"] == 0
|
||||
assert counters["preserved_pr_without_push_success"] == 1
|
||||
assert posted == []
|
||||
|
||||
|
||||
def test_reap_preserves_non_governance_no_push_shadow_when_target_passed(monkeypatch):
|
||||
mod = load_reaper()
|
||||
posted = []
|
||||
monkeypatch.setattr(
|
||||
mod,
|
||||
"post_compensating_status",
|
||||
lambda sha, context, target_url, *, description="", dry_run=False: posted.append(
|
||||
context
|
||||
),
|
||||
)
|
||||
|
||||
# A no-push workflow that is NOT in the governance allowlist must be
|
||||
# preserved even when its (pull_request_target) variant is green.
|
||||
counters = mod.reap(
|
||||
{"custom-audit": False},
|
||||
{
|
||||
"statuses": [
|
||||
{
|
||||
"context": "custom-audit / check (pull_request_review)",
|
||||
"status": "failure",
|
||||
},
|
||||
{
|
||||
"context": "custom-audit / check (pull_request_target)",
|
||||
"status": "success",
|
||||
},
|
||||
],
|
||||
},
|
||||
"db3b7a93e31adc0cb072a6d177d92dd73275a191",
|
||||
)
|
||||
|
||||
assert counters["compensated_governance_shadow"] == 0
|
||||
assert counters["compensated"] == 0
|
||||
assert posted == []
|
||||
|
||||
|
||||
def test_reap_compensates_retired_sop_tier_check_shadow_when_target_passed(monkeypatch):
|
||||
mod = load_reaper()
|
||||
posted = []
|
||||
|
||||
def fake_post(sha, context, target_url, *, description="", dry_run=False):
|
||||
posted.append((sha, context, target_url, description, dry_run))
|
||||
|
||||
monkeypatch.setattr(mod, "post_compensating_status", fake_post)
|
||||
|
||||
counters = mod.reap(
|
||||
{"sop-tier-check": False},
|
||||
{
|
||||
"statuses": [
|
||||
{
|
||||
"context": "sop-tier-check / tier-verify (pull_request)",
|
||||
"status": "failure",
|
||||
"target_url": "https://git.example.test/tier-pr",
|
||||
},
|
||||
{
|
||||
"context": "sop-tier-check / tier-verify (pull_request_target)",
|
||||
"status": "success",
|
||||
},
|
||||
],
|
||||
},
|
||||
"db3b7a93e31adc0cb072a6d177d92dd73275a191",
|
||||
)
|
||||
|
||||
assert counters["compensated_governance_shadow"] == 1
|
||||
assert counters["preserved_governance_without_target_success"] == 0
|
||||
assert posted == [
|
||||
(
|
||||
"db3b7a93e31adc0cb072a6d177d92dd73275a191",
|
||||
"sop-tier-check / tier-verify (pull_request)",
|
||||
"https://git.example.test/tier-pr",
|
||||
mod.GOVERNANCE_SHADOW_COMPENSATION_DESCRIPTION,
|
||||
False,
|
||||
),
|
||||
]
|
||||
|
||||
|
||||
def test_reap_compensates_retired_sop_tier_check_when_missing_from_trigger_map(monkeypatch):
|
||||
"""The retired sop-tier-check workflow file is intentionally removed, so the
|
||||
real workflow trigger map will not contain it. It must still be compensatable
|
||||
because it is explicitly allowlisted as a retired governance shadow."""
|
||||
mod = load_reaper()
|
||||
posted = []
|
||||
|
||||
def fake_post(sha, context, target_url, *, description="", dry_run=False):
|
||||
posted.append((sha, context, target_url, description, dry_run))
|
||||
|
||||
monkeypatch.setattr(mod, "post_compensating_status", fake_post)
|
||||
|
||||
counters = mod.reap(
|
||||
# Deliberately omit sop-tier-check from the trigger map.
|
||||
{},
|
||||
{
|
||||
"statuses": [
|
||||
{
|
||||
"context": "sop-tier-check / tier-verify (pull_request)",
|
||||
"status": "failure",
|
||||
"target_url": "https://git.example.test/tier-pr",
|
||||
},
|
||||
{
|
||||
"context": "sop-tier-check / tier-verify (pull_request_target)",
|
||||
"status": "success",
|
||||
},
|
||||
],
|
||||
},
|
||||
"db3b7a93e31adc0cb072a6d177d92dd73275a191",
|
||||
)
|
||||
|
||||
assert counters["compensated_governance_shadow"] == 1
|
||||
assert counters["preserved_governance_without_target_success"] == 0
|
||||
assert posted == [
|
||||
(
|
||||
"db3b7a93e31adc0cb072a6d177d92dd73275a191",
|
||||
"sop-tier-check / tier-verify (pull_request)",
|
||||
"https://git.example.test/tier-pr",
|
||||
mod.GOVERNANCE_SHADOW_COMPENSATION_DESCRIPTION,
|
||||
False,
|
||||
),
|
||||
]
|
||||
|
||||
|
||||
def test_reap_preserves_active_governance_shadow_when_missing_from_trigger_map(monkeypatch):
|
||||
"""Active governance workflows must be explicitly known-no-push in the trigger
|
||||
map. If the parser/discovery misses them, the reaper must fail-closed and
|
||||
preserve their shadow rather than auto-green it."""
|
||||
mod = load_reaper()
|
||||
posted = []
|
||||
monkeypatch.setattr(
|
||||
mod,
|
||||
"post_compensating_status",
|
||||
lambda sha, context, target_url, *, description="", dry_run=False: posted.append(
|
||||
context
|
||||
),
|
||||
)
|
||||
|
||||
counters = mod.reap(
|
||||
# Deliberately omit qa-review from the trigger map.
|
||||
{},
|
||||
{
|
||||
"statuses": [
|
||||
{
|
||||
"context": "qa-review / approved (pull_request_review)",
|
||||
"status": "failure",
|
||||
},
|
||||
{
|
||||
"context": "qa-review / approved (pull_request_target)",
|
||||
"status": "success",
|
||||
},
|
||||
],
|
||||
},
|
||||
"db3b7a93e31adc0cb072a6d177d92dd73275a191",
|
||||
)
|
||||
|
||||
assert counters["compensated_governance_shadow"] == 0
|
||||
assert counters["compensated"] == 0
|
||||
assert posted == []
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"context",
|
||||
[
|
||||
"gate-check-v3 / gate (pull_request)",
|
||||
"reserved-path-review / check (pull_request_review)",
|
||||
"lint-required-no-paths / lint (pull_request)",
|
||||
"lint-required-context-exists-in-bp / lint (pull_request_review)",
|
||||
"audit-force-merge / audit (pull_request)",
|
||||
"status-reaper / reap (pull_request_review)",
|
||||
"umbrella-reaper / reap (pull_request)",
|
||||
],
|
||||
)
|
||||
def test_reap_preserves_named_non_governance_no_push_shadows(context, monkeypatch):
|
||||
"""Real merge-control/lint/audit workflows that are NOT in the governance
|
||||
allowlist must be preserved even when they have no push trigger and their
|
||||
(pull_request_target) variant is green. Auto-greening these would mask real
|
||||
failures."""
|
||||
mod = load_reaper()
|
||||
posted = []
|
||||
monkeypatch.setattr(
|
||||
mod,
|
||||
"post_compensating_status",
|
||||
lambda sha, context, target_url, *, description="", dry_run=False: posted.append(
|
||||
context
|
||||
),
|
||||
)
|
||||
|
||||
workflow_name = context.split(" / ", 1)[0]
|
||||
counters = mod.reap(
|
||||
{workflow_name: False},
|
||||
{
|
||||
"statuses": [
|
||||
{
|
||||
"context": context,
|
||||
"status": "failure",
|
||||
},
|
||||
{
|
||||
"context": context.replace(
|
||||
" (pull_request)", " (pull_request_target)"
|
||||
).replace(" (pull_request_review)", " (pull_request_target)"),
|
||||
"status": "success",
|
||||
},
|
||||
],
|
||||
},
|
||||
"db3b7a93e31adc0cb072a6d177d92dd73275a191",
|
||||
)
|
||||
|
||||
assert counters["compensated_governance_shadow"] == 0
|
||||
assert counters["compensated"] == 0
|
||||
assert posted == []
|
||||
|
||||
Reference in New Issue
Block a user