R-404: block the push that can create the golden debt, notify the one that cannot
push_scope.py classifies a push as code or documents from an ALLOW-LIST of document paths - everything else, including any new top-level directory, is code. Every uncertainty (first push, force-push, merge commit, empty range, unreadable stdin) answers code: guessing 'documents' would hand out the exemption by accident. repo_gates.py gains a fifth GATES field and --scope=code|docs. On a documents-only push a golden-currency CONVICTION prints as ADVISORY in its own block and does not refuse; every other gate still refuses every push, and golden-currency still refuses a push touching code. The gate itself is UNCHANGED - its verdict, exit codes and wording are byte-identical. What changed is who is refused. Measured on git 2.47.3: a pre-push hook receives <local ref> <local sha> <remote ref> <remote sha> on stdin, one line per ref; a first push carries an all-zero remote sha and a deletion an all-zero local sha. Both land on code.
This commit is contained in:
@@ -92,11 +92,77 @@ jobs:
|
||||
git checkout -q FETCH_HEAD
|
||||
echo "controller CHANGELOG at $(git rev-parse --short=12 HEAD): $(head -1 CHANGELOG.md)"
|
||||
|
||||
- name: Classify the push - code or documents (R-404)
|
||||
# ONE RULE, NOT TWO. The pre-push hook exempts a golden-currency CONVICTION on a
|
||||
# documents-only push; if CI did not do the same, a drill night would still produce red CI
|
||||
# runs indistinguishable from real ones, which is R-417 exactly and is half the reason this
|
||||
# change exists.
|
||||
#
|
||||
# CI CANNOT USE A COMMIT RANGE. The checkout above is `--depth 1` of a single SHA, so there
|
||||
# is no history here to diff against — `git diff before..after` would fail, and deepening
|
||||
# the fetch to make it work would slow every run to solve a problem the push event has
|
||||
# already answered. So the file list comes from the push event payload instead, and is fed
|
||||
# to the SAME classifier the hook uses (`--files-from`), so there is one implementation of
|
||||
# "what counts as a document" and not two.
|
||||
#
|
||||
# FAIL CLOSED, EVERY PATH. No payload, no `commits` array, an empty array, unreadable JSON,
|
||||
# a missing classifier — all write `code`, which is exactly today's behaviour. This step can
|
||||
# therefore only ever make CI as strict as it is now, never looser. That is also why it is
|
||||
# safe to ship before it has been observed on a real push: the untested direction is the
|
||||
# safe one.
|
||||
run: |
|
||||
set -u
|
||||
python3 - > /tmp/pushed-files.txt <<'PY' || : > /tmp/pushed-files.txt
|
||||
import json, os, sys
|
||||
path = os.environ.get("GITHUB_EVENT_PATH", "")
|
||||
if not path or not os.path.isfile(path):
|
||||
sys.stderr.write("no GITHUB_EVENT_PATH - the file list is unknown\n")
|
||||
raise SystemExit(0)
|
||||
try:
|
||||
ev = json.load(open(path))
|
||||
except Exception as e:
|
||||
sys.stderr.write("event payload unreadable: %s\n" % e)
|
||||
raise SystemExit(0)
|
||||
commits = ev.get("commits") or []
|
||||
if not commits:
|
||||
sys.stderr.write("the payload carries no commits array - unknown\n")
|
||||
raise SystemExit(0)
|
||||
seen = []
|
||||
for c in commits:
|
||||
for key in ("added", "modified", "removed"):
|
||||
for f in (c.get(key) or []):
|
||||
if f not in seen:
|
||||
seen.append(f)
|
||||
sys.stderr.write("%d commit(s), %d distinct path(s) in the payload\n"
|
||||
% (len(commits), len(seen)))
|
||||
for f in seen:
|
||||
print(f)
|
||||
PY
|
||||
echo "--- paths the push event reported ---"
|
||||
cat /tmp/pushed-files.txt
|
||||
echo "-------------------------------------"
|
||||
if [ -s /tmp/pushed-files.txt ] && [ -f scripts/push_scope.py ]; then
|
||||
SCOPE=$(python3 scripts/push_scope.py --files-from /tmp/pushed-files.txt) || SCOPE=code
|
||||
else
|
||||
echo "no usable file list - treating this push as CODE (fail-closed)"
|
||||
SCOPE=code
|
||||
fi
|
||||
[ "$SCOPE" = "docs" ] || SCOPE=code
|
||||
echo "PUSH_SCOPE=$SCOPE" >> "$GITHUB_ENV"
|
||||
echo "scope: $SCOPE"
|
||||
|
||||
- name: Run the gate entry point
|
||||
# The ONLY thing CI runs. No go build, no go test, no linting, no deploy — those are either
|
||||
# already reliably run by a person or none of CI's business. The exit code IS the result:
|
||||
# no `|| true`, no pipe that could swallow it.
|
||||
run: python3 scripts/repo_gates.py --fast
|
||||
#
|
||||
# A documents-only run that convicts ONLY on golden-currency prints the advisory and stays
|
||||
# green. THE DEBT IS NOT HIDDEN WHEN THAT HAPPENS — three things still carry it: the
|
||||
# advisory block in this run's own log, `STATUS.md`, and the controller repo's golden-notice,
|
||||
# which prints at the moment a release is committed, where someone can actually act on it.
|
||||
# Those are the compensating controls that make this green honest. Every other gate still
|
||||
# fails this job on any push, and golden-currency still fails it on a push touching code.
|
||||
run: python3 scripts/repo_gates.py --fast --scope="${PUSH_SCOPE:-code}"
|
||||
|
||||
- name: Alarm on failure
|
||||
# THE POINT OF THE WHOLE THING. Probe P5 measured that a failed run produces NO mail, NO
|
||||
|
||||
Reference in New Issue
Block a user