From 8dbbadb4d35bfa543e429bd3d6e84742d9bebe37 Mon Sep 17 00:00:00 2001 From: jhfnetboy Date: Fri, 9 Oct 2026 21:02:20 +0700 Subject: [PATCH] fix(ci): strict preflight refuses an APPROVE body that names no full head SHA DSR (CC-124 902e531e), independently re-verifying #452: an APPROVE whose body contains no 40-hex SHA exited 0 in strict mode - the leg only WARNed and fell back to commit timestamps, which a backdated committer date defeats silently. Merges therefore still needed a manual "does the body name the full head" check on top of preflight 0. Strict mode now FAILs when the newest APPROVE body names no full SHA; --ci keeps the WARN (it is a report, and every fresh approval starts there). A body naming a different full SHA already failed. Fixtures (+4, suite now 52): - no SHA / only an 8-char head prefix -> FAIL (both exit 0 on origin/main: the pre-fix column, i.e. fail-open closed by this commit) - different full SHA -> FAIL (unchanged), full head -> PASS (control) 52/52 on this head; origin/main script: 50/52, the two misses being exactly the two new fail-open cells. Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_0177oe2fF7ywx8M9VSKcEbij --- scripts/merge-preflight.sh | 18 +++++++++++++++--- scripts/test_merge_preflight_strict.py | 21 ++++++++++++++++++--- 2 files changed, 33 insertions(+), 6 deletions(-) diff --git a/scripts/merge-preflight.sh b/scripts/merge-preflight.sh index 9e7a3655..b207b47c 100755 --- a/scripts/merge-preflight.sh +++ b/scripts/merge-preflight.sh @@ -208,9 +208,21 @@ print("\n".join(out))' 2>/dev/null) || latefp="__PARSE_FAILED__" fail=1 fi else - echo "WARN the approval body names no SHA - falling back to timestamps, which a" - echo " backdated committer date defeats silently. This leg does not" - echo " establish that the approval covers this head." + # Strict mode refuses: without a full SHA written by the approver, only the + # timestamp legs below speak for "this approval covers this head", and a + # backdated committer date defeats them silently. DSR measured on #452 that + # an APPROVE body with no SHA exited 0 here. --ci keeps the WARN: it is a + # report, not the gate, and every fresh approval starts there. + if [ "$CI_MODE" -eq 1 ]; then + echo "WARN the approval body names no SHA - falling back to timestamps, which a" + echo " backdated committer date defeats silently. This leg does not" + echo " establish that the approval covers this head." + else + echo "FAIL the approval body names no full 40-hex SHA. The approver must write" + echo " the exact head ($head) in the APPROVE body; timestamps alone" + echo " cannot establish that the approval covers this head." + fail=1 + fi fi api cdates '[.[].commit.committer.date]|join("\n")' \ "repos/$REPO/pulls/$PR/commits" --paginate || { echo "PREFLIGHT FAIL — do not merge $PR"; exit 4; } diff --git a/scripts/test_merge_preflight_strict.py b/scripts/test_merge_preflight_strict.py index 639ed1d6..f8697273 100644 --- a/scripts/test_merge_preflight_strict.py +++ b/scripts/test_merge_preflight_strict.py @@ -174,8 +174,8 @@ def rs_rule(*ctx): # (name, base, mutate, expect_exit_zero, leg_regex, env) S = [] -def sc(name, base, mut, ok, leg, env=None): - S.append((name, base, mut, ok, leg, env or {})) +def sc(name, base, mut, ok, leg, env=None, extra=None): + S.append((name, base, mut, ok, leg, env or {}, extra)) # --- positive controls ------------------------------------------------------- sc("unprotected, no rulesets", U, lambda f: None, True, INFO_NONE) @@ -256,6 +256,19 @@ def runs_with(build): sc("required-set union: jq prints then exits 7", P, lambda f: None, False, r"^FAIL could not evaluate", {"FAKE_JQ_FAIL_ON": "--argjson runs"}) +# --- approval body must name the full head (strict) ---------------------------- +REV = f"repos/{REPO}/pulls/{PR}/reviews" +def body(text): + return lambda f: f["api"][REV]["pages"][0][0].update(body=text) +sc("approval body names no SHA", U, body("APPROVE looks good"), False, INFO_NONE, + extra=r"^FAIL the approval body names no full 40-hex SHA") +sc("approval body names only an 8-char head prefix", U, body("APPROVE at " + HEAD[:8]), False, INFO_NONE, + extra=r"^FAIL the approval body names no full 40-hex SHA") +sc("approval body names a different full SHA", U, body("APPROVE at " + "b" * 40), False, INFO_NONE, + extra=r"^FAIL the approval body names bbbbbbbbbbbb") +sc("approval body names the full head (control)", U, lambda f: None, True, INFO_NONE, + extra=r"^OK the approval body names this exact head") + LEG = re.compile(r"^(OK all \d+ required checks|INFO .*requires no status checks|" r"FAIL (could not (read|evaluate) .*required checks|required check\(s\) not satisfied|base branch unreadable))") @@ -270,7 +283,7 @@ def main(): open(p, "w").write(body) os.chmod(p, 0o755) failures = 0 - for i, (name, base, mut, ok, leg, env) in enumerate(S): + for i, (name, base, mut, ok, leg, env, extra) in enumerate(S): fx = base_fixture(base) mut(fx) fpath = os.path.join(tmp, f"fx{i}.json") @@ -292,6 +305,8 @@ def main(): got_leg = legs[0] if legs else "" exit_ok = (p.returncode == 0) == ok leg_ok = re.search(leg, got_leg) is not None + if extra is not None: + leg_ok = leg_ok and re.search(extra, out, re.M) is not None mark = "ok " if exit_ok and leg_ok else "FAIL" if not (exit_ok and leg_ok): failures += 1