Public repositories' commit messages state the engineering reason

This commit is contained in:
2026-09-29 22:08:48 -07:00
parent 50e93e3138
commit 72ae7a720a
4 changed files with 177 additions and 1 deletions
+35 -1
View File
@@ -409,7 +409,7 @@ jobs:
# Naming the list here also makes the release order self-announcing: a
# pipeline that has grown a tool refuses at step one, and says which ref
# it read and what was missing from it.
need="certclaims conflictmarkers gocache gover ignoretracked imgplat imgtags imgver modsize publishable site vendormark"
need="certclaims conflictmarkers gocache gover ignoretracked imgplat imgtags imgver modsize narrative publishable site vendormark"
miss='<no host answered>'
# A sha cannot be a --branch, so it arrives by fetch rather than clone:
# the caller's pin as well as this file's own commit. Both are wrapped
@@ -532,6 +532,40 @@ jobs:
echo "**Submodules: not fetched** — \`${miss}\`" >> "${GITHUB_STEP_SUMMARY:-/dev/null}"; }
fi
- name: Commit messages state the engineering reason (public repositories)
# A public commit message is published when it is pushed and stays in
# history, so it says what a change does and why; severity labels and
# incident accounts belong in the private record. bin/narrative flags
# both. A pull request can still be reworded, so a severity label fails
# it; a push is already public and history is never rewritten, so a push
# only annotates and never holds a release. Private repositories skip it.
# One leg is enough: the commits are the same on both architectures.
if: >-
env.HANZO_CI_ROLE == 'test' && matrix.runner == 'linux-amd64' &&
github.event.repository.private == false
env:
EVENT: ${{ github.event_name }}
BASE: ${{ github.event.pull_request.base.sha }}
HEAD: ${{ github.event.pull_request.head.sha }}
BEFORE: ${{ github.event.before }}
AFTER: ${{ github.event.after || github.sha }}
run: |
set -uo pipefail
# The checkout is shallow, so the range is fetched first; a range that
# cannot be read is reported by the tool and checks nothing.
case "$EVENT" in
pull_request*)
git fetch -q --no-tags --depth=200 origin "$BASE" "$HEAD" 2>/dev/null || true
"$RUNNER_TEMP/ci/bin/narrative" --strict "$BASE..$HEAD" ;;
*)
git fetch -q --no-tags --depth=200 origin "$AFTER" 2>/dev/null || true
if [ -n "$BEFORE" ] && ! [[ $BEFORE =~ ^0+$ ]] && git cat-file -e "$BEFORE^{commit}" 2>/dev/null; then
"$RUNNER_TEMP/ci/bin/narrative" "$BEFORE..$AFTER"
else
"$RUNNER_TEMP/ci/bin/narrative" -1 "$AFTER"
fi ;;
esac
- name: Provision parse toolchain (jq + PyYAML)
# This reusable parses the caller's hanzo.yml with python3 + PyYAML and
# slices JSON with jq. A minimal runner image
Executable
+89
View File
@@ -0,0 +1,89 @@
#!/usr/bin/env bash
# narrative — flag commit messages in a public repository that label a security
# severity or narrate an incident. One implementation, every caller.
#
# narrative [--strict] <git-rev-range | -1 <sha>>
#
# WHY. A public commit message is published the moment it is pushed and stays in
# history for good. The house rule is to state the engineering reason a change
# is right — "Scope identity deletes to the caller's org" — and to keep severity
# labels and incident accounts in the private record, where they are useful and
# are not a map of what to try.
#
# WHAT IT FLAGS, two classes, case-insensitive, on word boundaries:
#
# label a severity label: RED|BLUE + CRITICAL|HIGH|MEDIUM|LOW, a
# "(CRITICAL)" or "[HIGH]" tag, "severity:", SEV0-4, a CVE id,
# CVSS.
# narrative an incident account: outage, incident, post-mortem, leaked,
# exploit(ed), bypass(ed), account takeover, cross-tenant,
# privilege escalation, vulnerability, "fail(s|ed) open",
# "was down", "broke prod".
#
# The lists are short on purpose. A word that is also ordinary engineering
# vocabulary ("secret", "token", "rotate", "harden") is not here: "Rotate the
# signing key into KMS" is exactly the kind of message the rule asks for.
#
# WHAT IT DOES WITH ONE. Every hit is an annotation naming the short sha and the
# phrase. --strict (a pull request, which can still be reworded before it lands)
# fails on a label; everything else is a warning. On a push the commit is already
# public and history is never rewritten, so a push only ever warns: the next
# message is where the fix goes, and a release is never held for this.
set -euo pipefail
strict=0
if [ "${1:-}" = "--strict" ]; then strict=1; shift; fi
[ $# -gt 0 ] || { echo "usage: narrative [--strict] <rev-range | -1 <sha>>" >&2; exit 2; }
git rev-parse --is-inside-work-tree >/dev/null 2>&1 || {
echo "narrative: not a git work tree ($(pwd)) — skipping"; exit 0; }
label='\b(red|blue)[[:space:]:/-]+(critical|high|medium|low)\b|[([](critical|high|medium|low)[])]|\bseverity[[:space:]]*:|\bsev[[:space:]-]?[0-4]\b|\bcve-[0-9]{4}-[0-9]+\b|\bcvss\b'
narr='\boutages?\b|\bincidents?\b|\bpost-?mortem\b|\bleak(ed|s)?\b|\bexploit(ed|s|able)?\b|\bbypass(ed|es)?\b|\baccount[[:space:]-]takeover\b|\bcross[[:space:]-]tenant\b|\bprivilege[[:space:]-]escalation\b|\bvulnerab(le|ility|ilities)\b|\bfail(s|ed)?[[:space:]-]open\b|\bwas[[:space:]]+down\b|\bbroke[[:space:]]+prod(uction)?\b'
# %x1e separates commits and %x1f the sha from the message, so a message may
# carry any text at all without splitting wrong.
log=$(git log --no-merges --format='%h%x1f%B%x1e' "$@" 2>/dev/null) || {
echo "::warning::narrative: could not read commits for '$*' — nothing checked"; exit 0; }
n=0; labels=0; tales=0
summary=""
while IFS=$'\x1f' read -r -d $'\x1e' sha msg; do
sha=${sha//$'\n'/}
[ -n "$sha" ] || continue
n=$((n + 1))
subject=$(printf '%s\n' "$msg" | sed -n '1p')
hit=$(printf '%s\n' "$msg" | grep -ioE "$label" | head -1 || true)
if [ -n "$hit" ]; then
labels=$((labels + 1))
kind=error; [ $strict = 1 ] || kind=warning
echo "::${kind} title=Commit message carries a severity label::${sha} \"${hit}\" — ${subject}"
summary="${summary}| ${sha} | label | ${hit} | ${subject//|/\\|} |"$'\n'
continue
fi
hit=$(printf '%s\n' "$msg" | grep -ioE "$narr" | head -1 || true)
if [ -n "$hit" ]; then
tales=$((tales + 1))
echo "::warning title=Commit message narrates an incident::${sha} \"${hit}\" — ${subject}"
summary="${summary}| ${sha} | narrative | ${hit} | ${subject//|/\\|} |"$'\n'
fi
done <<< "$log"
if [ -n "$summary" ] && [ -n "${GITHUB_STEP_SUMMARY:-}" ]; then
{
echo "### Commit messages in a public repository"
echo
echo "State the engineering reason; keep severity and incident detail in the private record."
echo
echo "| commit | class | phrase | subject |"
echo "|---|---|---|---|"
printf '%s' "$summary"
} >> "$GITHUB_STEP_SUMMARY"
fi
echo "narrative: ${n} commit(s), ${labels} with a severity label, ${tales} narrating an incident"
if [ $strict = 1 ] && [ $labels -gt 0 ]; then
echo "Reword the flagged commit(s) before merging: say what the change does and why."
exit 1
fi
exit 0
+46
View File
@@ -0,0 +1,46 @@
#!/usr/bin/env bash
# Tests for bin/narrative. Offline and deterministic: every case is a git repo
# written into a temp dir. Run: bash bin/narrative_test.sh
set -uo pipefail
cd "$(dirname "$0")/.."
N="$PWD/bin/narrative"
tmp=$(mktemp -d); trap 'rm -rf "$tmp"' EXIT
fail=0
# t <name> <want-rc> <want-in-output> <mode> <message>
t() {
local name=$1 want=$2 grepfor=$3 mode=$4 msg=$5
local d="$tmp/$RANDOM$RANDOM"; mkdir -p "$d"
local g=(git -c user.email=t@t -c user.name=t -c core.hooksPath=/dev/null)
( cd "$d" && git init -q . && echo base > f && "${g[@]}" add -A \
&& "${g[@]}" commit -qm "Start" && echo next >> f \
&& "${g[@]}" commit -qam "$msg" ) >/dev/null 2>&1
out=$( cd "$d" && bash "$N" $mode HEAD~1..HEAD 2>&1 ); rc=$?
if [ "$rc" = "$want" ] && { [ -z "$grepfor" ] || grep -q -- "$grepfor" <<<"$out"; }; then
printf 'ok %-64s rc=%s\n' "$name" "$rc"
else
printf 'FAIL %-64s rc=%s (want %s, output to carry %q)\n%s\n' "$name" "$rc" "$want" "$grepfor" "$out"; fail=1
fi
}
echo "--- a pull request fails on a severity label ---"
t "RED HIGH label" 1 "::error" --strict "fix RED HIGH cross-tenant read"
t "parenthesised severity" 1 "::error" --strict "Close the org read (CRITICAL)"
t "severity: field" 1 "::error" --strict "$(printf 'Scope reads\n\nseverity: high')"
t "a CVE id" 1 "::error" --strict "Bump x for CVE-2026-1234"
echo "--- a pull request only warns on a narrative ---"
t "outage account" 0 "::warning" --strict "The gate answered 503 during the outage"
t "bypass account" 0 "::warning" --strict "Close the auth bypass"
t "fails open" 0 "::warning" --strict "The gate fails open when commerce is down"
echo "--- a push never fails ---"
t "label on a push" 0 "::warning" "" "fix RED HIGH cross-tenant read"
echo "--- the engineering reason passes clean ---"
t "rotate a key" 0 "0 with a severity label, 0 narrating" --strict "Rotate the signing key into KMS"
t "scope deletes" 0 "0 with a severity label, 0 narrating" --strict "Scope identity deletes to the caller's org"
t "high as ordinary english" 0 "0 with a severity label, 0 narrating" --strict "Raise the high-water mark for the queue"
t "tokens as text" 0 "0 with a severity label, 0 narrating" --strict "Count input tokens before the model call"
exit $fail
+7
View File
@@ -515,6 +515,13 @@ test:
# hanzoai/app's same-line fixture and an eight-bracket heredoc still pass.
# Offline and deterministic: git repos in temp dirs, no network.
run: bash bin/conflictmarkers_test.sh
- name: narrative
# bin/narrative reads every public repository's commit messages, so the suite
# pins both directions: a severity label fails a pull request and only warns on
# a push, a narrative only ever warns, and a message that states the
# engineering reason ("Rotate the signing key into KMS") passes clean.
# Offline and deterministic: git repos in temp dirs, no network.
run: bash bin/narrative_test.sh
# No `deploy:` key exists any more — for any repo. A build ends at a published
# image; what RUNS is a reviewed pin in hanzo/universe, which cd.hanzo.ai reads.