Skip to content
Merged
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
18 changes: 15 additions & 3 deletions scripts/merge-preflight.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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; }
Expand Down
21 changes: 18 additions & 3 deletions scripts/test_merge_preflight_strict.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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))")

Expand All @@ -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")
Expand All @@ -292,6 +305,8 @@ def main():
got_leg = legs[0] if legs else "<no required-checks line>"
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
Expand Down
Loading