CI Pipeline / best-practices (push) Successful in 4s
CI Pipeline / documentation (push) Successful in 2s
Auto-label / priority-gate (pull_request) Skipped
Auto-label / auto-label (pull_request) Successful in 1s
CI Pipeline / canary (push) Successful in 28s
CI Pipeline / lint (push) Successful in 31s
CI Pipeline / unit-tests (push) Successful in 37s
CI Pipeline / type-check (push) Successful in 25s
CI Pipeline / gitea-offline (push) Successful in 13s
CI Pipeline / required-checks (push) Skipped
CI Pipeline / best-practices (pull_request) Successful in 2s
CI Pipeline / documentation (pull_request) Successful in 2s
CI Pipeline / lint (pull_request) Successful in 28s
CI Pipeline / canary (pull_request) Successful in 24s
Spec gate (headless) / spec-gate (pull_request) Successful in 6s
CI Pipeline / type-check (pull_request) Successful in 23s
CI Pipeline / gitea-offline (pull_request) Successful in 14s
CI Pipeline / unit-tests (pull_request) Successful in 36s
CI Pipeline / required-checks (pull_request) Successful in 0s
Behind-base self-heal previously forced a full CI re-run even when the update's tree diff touched no code path — redundant by construction (identical code-path trees cannot produce different test outcomes). - merge_robot emits UPDATED protocol line after self-heal - scripts/carry_forward_check.py classifies the tested-head→new-head tree diff: CARRY_FORWARD (docs-only) vs RERUN_REQUIRED (any code-path touch) - workflow consumes it: CARRY_FORWARD republishes the required-checks aggregate citing tree identity; RERUN_REQUIRED waits for CI as today - policy recorded: first pass = full pipeline; base movement with unchanged code paths = carry; bugs/mistakes = new commits = full re-run Red-first: 2 new tests (classification + companion script contract).
64 lines
2.0 KiB
Python
64 lines
2.0 KiB
Python
#!/usr/bin/env python3
|
|
"""carry_forward_check.py — classify a behind-base update's tree diff.
|
|
|
|
Nick's policy (2026-09-08): pipeline checks re-run only when contributed
|
|
code changes. The merge-robot's self-heal updates a stale branch; this
|
|
classifier decides whether the prior green may be CARRIED FORWARD to the
|
|
new head (docs-only movement) or a full re-run is REQUIRED (any code-path
|
|
touch — semantic interactions are real; #210/#212 proved it live).
|
|
|
|
Usage (workflow step):
|
|
git fetch origin <tested_head> <new_head>
|
|
CHANGED=$(git diff --name-only <tested_head> <new_head>)
|
|
python3 scripts/carry_forward_check.py classify <<< "$CHANGED"
|
|
# prints CARRY_FORWARD or RERUN_REQUIRED; exit 0 either way
|
|
|
|
The evidence contract: CARRY_FORWARD asserts the code-path tree is
|
|
IDENTICAL between the tested head and the new head, so prior test outcomes
|
|
necessarily still hold. The workflow republishes the required-checks
|
|
aggregate citing this identity. Any code-path touch → RERUN_REQUIRED.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import sys
|
|
|
|
CODE_PATHS = (
|
|
"src/",
|
|
"tests/",
|
|
"scripts/",
|
|
"shared/",
|
|
"config/",
|
|
".gitea/",
|
|
"henxels.yaml",
|
|
"henxels_checks.py",
|
|
"pyproject.toml",
|
|
"requirements-ci.txt",
|
|
"lint-baseline.json",
|
|
)
|
|
|
|
|
|
def touches_code_path(changed_files: list[str], code_paths=CODE_PATHS) -> bool:
|
|
for f in changed_files:
|
|
for cp in code_paths:
|
|
if f == cp or f.startswith(cp):
|
|
return True
|
|
return False
|
|
|
|
|
|
def classify(changed_files: list[str], tested_head: str = "", new_head: str = "") -> str:
|
|
return "RERUN_REQUIRED" if touches_code_path(changed_files) else "CARRY_FORWARD"
|
|
|
|
|
|
def main_with(argv: list[str]) -> int:
|
|
if len(argv) >= 2 and argv[1] == "classify":
|
|
files = [ln.strip() for ln in sys.stdin.read().splitlines() if ln.strip()]
|
|
print(classify(files))
|
|
return 0
|
|
print("usage: carry_forward_check.py classify (file list on stdin)", file=sys.stderr)
|
|
return 2
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main_with(sys.argv))
|