From 7a43cc35146deec0dc882b25e974f943e2054fab Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 12 Nov 2026 00:07:04 +0000 Subject: [PATCH] ci: check_protocol.sh -- a commit carries passing protocol evidence for its tree (#135) scripts/check_protocol.sh []: refused (exit 1) unless carries a logs//PROTOCOL.md naming a commit whose protocol passed on a clean tree, and that commit differs from only in the evidence (logs/ and experiments/bare_metal/runs/, committed after the run). Any other change since makes the evidence stale: the protocol is run again. scripts/test_check_protocol.sh, written first and seen to fail (8 of 8, and tightened so a missing script is not taken for a refusal): no protocol, stale, failed, dirty, unknown commit -- refused; fresh, fresh among stale ones, other files in logs/ -- passes. 8 of 8 pass. Not yet wired into acceptance.yml: that comes with nightly.sh protocol. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_017RzZgrf7V7Zk2iczBXDRbm --- scripts/check_protocol.sh | 50 +++++++++++++++++++++++++ scripts/test_check_protocol.sh | 67 ++++++++++++++++++++++++++++++++++ 2 files changed, 117 insertions(+) create mode 100755 scripts/check_protocol.sh create mode 100755 scripts/test_check_protocol.sh diff --git a/scripts/check_protocol.sh b/scripts/check_protocol.sh new file mode 100755 index 00000000..54ed5d49 --- /dev/null +++ b/scripts/check_protocol.sh @@ -0,0 +1,50 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: LicenseRef-Starship-1.0 +# Copyright (c) 2023-2026 Robert A. James +# check_protocol.sh [] -- does (default HEAD) carry the developer protocol's evidence +# for its own tree? (#135; Captain Bob, 2026-11-07: "the protocol evidence can be submitted in +# the PR"). Run by .gitea/workflows/acceptance.yml on the pull request's head, in the repository +# the current directory is in. Tests: scripts/test_check_protocol.sh. +# +# The evidence is a logs//PROTOCOL.md, as `scripts/nightly.sh protocol` writes it, naming +# - Commit: +# - Tree: clean +# - Result: PASS +# It is for 's tree when that commit differs from only in the evidence itself -- +# logs/ and experiments/bare_metal/runs/, which a developer commits after the run. Any other +# change since is a change the protocol did not see: the evidence is stale, and the protocol is run +# again (a repair means rerunning from the top). +# +# Exit 0 with the protocol that counts; 1 "refused", with why; 2 on a usage error. +set -uo pipefail +rev=${1:-HEAD} +EVIDENCE=(':(exclude)logs' ':(exclude)experiments/bare_metal/runs') + +refuse() { echo "check_protocol: refused -- $*" >&2; exit 1; } +git rev-parse --verify -q "$rev^{commit}" >/dev/null || { echo "check_protocol: no commit $rev" >&2; exit 2; } +head=$(git rev-parse "$rev^{commit}") + +mapfile -t protocols < <(git ls-tree -r --name-only "$head" -- logs | grep -E '^logs/[^/]+/PROTOCOL\.md$' | sort -r) +[ ${#protocols[@]} -gt 0 ] || refuse "${head:0:8} carries no logs//PROTOCOL.md: run scripts/nightly.sh protocol and commit its logs" + +why=() +for p in "${protocols[@]}"; do + body=$(git show "$head:$p") + commit=$(sed -n 's/^- Commit: *\([0-9a-f]\{40\}\) *$/\1/p' <<<"$body" | head -1) + tree=$(sed -n 's/^- Tree: *\([a-z]*\) *$/\1/p' <<<"$body" | head -1) + result=$(sed -n 's/^- Result: *\([A-Z]*\) *$/\1/p' <<<"$body" | head -1) + if [ -z "$commit" ] || ! git cat-file -e "$commit^{commit}" 2>/dev/null; then + why+=("$p: names no commit this repository has (${commit:-none}).") + elif [ "$result" != PASS ]; then + why+=("$p: the protocol for ${commit:0:8} did not pass (${result:-no result}).") + elif [ "$tree" != clean ]; then + why+=("$p: the protocol for ${commit:0:8} ran on a tree that was ${tree:-not said to be clean}.") + elif ! git diff --quiet "$commit" "$head" -- . "${EVIDENCE[@]}"; then + why+=("$p: stale -- ${head:0:8} differs from ${commit:0:8} outside logs/ and experiments/bare_metal/runs/: $(git diff --name-only "$commit" "$head" -- . "${EVIDENCE[@]}" | head -3 | tr '\n' ' ')") + else + echo "check_protocol: ${head:0:8} carries $p -- the protocol passed on ${commit:0:8}, the same tree but for its evidence" + exit 0 + fi +done +printf ' %s\n' "${why[@]}" >&2 +refuse "no PROTOCOL.md in ${head:0:8} is a passing protocol for its tree" diff --git a/scripts/test_check_protocol.sh b/scripts/test_check_protocol.sh new file mode 100755 index 00000000..45478b5e --- /dev/null +++ b/scripts/test_check_protocol.sh @@ -0,0 +1,67 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: LicenseRef-Starship-1.0 +# Copyright (c) 2023-2026 Robert A. James +# test_check_protocol.sh -- the tests of scripts/check_protocol.sh (#135), in a scratch git +# repository: a commit is refused without a PROTOCOL.md for its tree, and passes with one. +# +# Prints PASS:/FAIL: per check and RESULT:; exits 0 only if every check passes. +set -uo pipefail +CHECK="$(cd "$(dirname "$0")" && pwd)/check_protocol.sh" +T=$(mktemp -d) +trap 'rm -rf "$T"' EXIT +ok=1 + +expect() { # expect "what" -- runs the check on HEAD of the scratch repository + local want="$1" what="$2" out rc + out=$(cd "$T" && bash "$CHECK" HEAD 2>&1); rc=$? + # a refusal is exit 1 and says so -- any other failure (no script, a crash) is not a refusal + if { [ "$want" = pass ] && [ $rc -eq 0 ]; } || + { [ "$want" = refuse ] && [ $rc -eq 1 ] && grep -q "refused" <<<"$out"; }; then + echo "PASS: $what" + else + echo "FAIL: $what (exit $rc)"; printf ' %s\n' "$out" | head -5; ok=0 + fi +} + +protocol() { # protocol -- a PROTOCOL.md as nightly.sh writes it + mkdir -p "$T/logs/$1" + printf '%s\n' "# Protocol for ${2:0:8}" "" "- Commit: $2" "- Tree: $3" "- Result: $4" > "$T/logs/$1/PROTOCOL.md" +} + +commit() { (cd "$T" && git add -A && git commit -q -m "$1"); } + +(cd "$T" && git init -q && git config user.email t@example.invalid && git config user.name test && + git config commit.gpgsign false) +echo "int main(void) { return 0; }" > "$T/main.c"; commit "the code" +tested=$(cd "$T" && git rev-parse HEAD) + +expect refuse "no PROTOCOL.md: refused" + +protocol 20261112-010000 "$tested" clean PASS +mkdir -p "$T/logs/20261112-010000/amd64" "$T/experiments/bare_metal/runs" +echo "serial" > "$T/logs/20261112-010000/amd64/qemu-amd64.log" +: > "$T/experiments/bare_metal/runs/doe-amd64-20261112-010000.csv" +commit "the evidence" +expect pass "the evidence for the code, committed after it (logs/ and runs/ only): passes" + +echo "/* a change */" >> "$T/main.c"; commit "a change after the run" +expect refuse "a change outside the evidence after the run: refused as stale" + +newer=$(cd "$T" && git rev-parse HEAD) +protocol 20261112-020000 "$newer" clean FAIL; commit "a failed protocol" +expect refuse "the protocol for the head failed: refused" + +protocol 20261112-030000 "$newer" dirty PASS; commit "a protocol run on a dirty tree" +expect refuse "the protocol ran on a dirty tree: refused" + +protocol 20261112-040000 0123456789abcdef0123456789abcdef01234567 clean PASS; commit "a protocol for no commit" +expect refuse "the protocol names a commit this repository does not have: refused" + +protocol 20261112-050000 "$newer" clean PASS; commit "the evidence for the change" +expect pass "a fresh protocol among stale and failed ones: passes" + +printf '%s\n' "# not a protocol" > "$T/logs/20261112-060000.md"; commit "an unrelated note in logs/" +expect pass "files in logs/ that are not PROTOCOL.md do not matter: passes" + +echo "RESULT: $([ $ok = 1 ] && echo PASS || echo FAIL)" +[ $ok = 1 ]