mirror of
https://github.com/hanzoai/ci.git
synced 2026-10-20 15:28:05 +00:00
Public repositories' commit messages state the engineering reason
This commit is contained in:
@@ -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
@@ -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
|
||||
Executable
+46
@@ -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
|
||||
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user