#!/usr/bin/env bash# Generated by scripts/setup/githooks.sh — do not edit directly.# Source of truth: scripts/setup/hooks/pre-push.template## Native pre-push hook. Runs once per push:# 1. Encryption-at-rest audit: every file under solutions/private that this# push would add to the remote is stored as gitfilter ciphertext, read# straight from the object store so a plaintext leak can never be pushed.# Inlined below (not sourced from a helper script) so the push gate stays# self-contained.# 2. No compiled binaries (ELF blobs) anywhere in the tracked tree.# 3. flake8 on packages + solutions.# 4. mypy on packages + solutions.# 5. shellcheck on every tracked shell script (scripts + hook templates).# 6. the unit tests — only when the push changes packages/, scripts/ or tests/.# 7. audit/ (sensitive run logs) is gitignored whenever it exists.## Output contract: every check announces itself, prints its own detail on# failure, and the hook always ends with a single PASS/FAIL verdict that# names exactly which checks failed — so a buried error can't be masked by a# later tool's "Success" line.set-uopipefail# Resolve the working tree at runtime so the hook operates on the checkout# actually being pushed — including linked worktrees. VENV stays absolute: the# virtualenv lives only in the main checkout.REPO_ROOT="$(gitrev-parse--show-toplevel)"VENV="__VENV__"cd"$REPO_ROOT"||exit1# ── Reporting helpers ────────────────────────────────────────────────────FAILURES=()# human-readable names of checks that failedsection(){printf'\n── %s ──\n'"$1">&2;}note(){printf' %s\n'"$1">&2;}fail(){FAILURES+=("$1");printf' ✗ %s\n'"$1">&2;}pass(){printf' ✓ %s\n'"$1">&2;}skip(){printf' – %s\n'"$1">&2;}print_verdict(){printf'\n'>&2if((${#FAILURES[@]}==0));then# "pushing" only when there is something to push. Git runs this hook with an# EMPTY stdin when it has already settled the push itself — a non-fast-forward# is decided before the hook runs — so claiming a push is about to happen# printed a cheerful verdict directly above git's rejection.if((${#PUSH_REFS[@]}==0));thenprintf'✓ pre-push: checks passed, but git offered no refs — this push delivers nothing.\n'>&2elseprintf'✓ pre-push: all checks passed — pushing.\n'>&2fireturn0fiprintf'════════════════════════════════════════════════════════════\n'>&2printf'✗ pre-push BLOCKED — %d check(s) failed:\n'"${#FAILURES[@]}">&2localfforfin"${FAILURES[@]}";doprintf' • %s\n'"$f">&2doneprintf' (scroll up to the matching "── ... ──" section for details)\n'>&2printf'════════════════════════════════════════════════════════════\n'>&2return1}# ── The refs being pushed ────────────────────────────────────────────────# Git passes one line per ref on stdin:# <local ref> <local sha> <remote ref> <remote sha># Read it up front so the audit below can scope itself to the blobs this push# would actually add to the remote. An all-zero local sha is a ref deletion# (nothing to audit); an all-zero remote sha is a new remote ref, for which# "new" means every commit not already reachable from some other ref on this# remote.## NO lines at all is its own case: git offers none when it has already settled the# push without the hook's help — a non-fast-forward, which git decides up front and# rejects on its own. Nothing then reaches the remote, so the blob audits below have# nothing to read and say so; reporting '0 blobs checked ✓' invited the reader to# think a push had been audited and cleared when it had not been audited at all.REMOTE="${1:-origin}"PUSH_REFS=()whileread-r_local_reflocal_sha_remote_refremote_sha;do[[-z"${local_sha}"]]&&continue[["${local_sha}"=~^0+$]]&&continuePUSH_REFS+=("${local_sha}${remote_sha}")done# Emit the blobs added or modified by this push under the given pathspec (all# paths when none is given), as a NUL-separated stream of alternating# "<raw meta>" and "<path>" records (the `git diff-tree -z` raw format). `-m`# diffs merges against each parent so an evil merge cannot smuggle a blob past# the audit; duplicates across refs and parents are deduplicated by the caller.pushed_blobs(){localentrylocal_sharemote_shaforentryin"${PUSH_REFS[@]}";doread-rlocal_sharemote_sha<<<"${entry}"if[["${remote_sha}"=~^0+$]];thengitrev-list"${local_sha}"--not--remotes="${REMOTE}"\|gitdiff-tree-r-m--stdin--root--no-commit-id-z\--diff-filter=AM--"$@"elsegitdiff-tree-r-m--no-commit-id-z--diff-filter=AM\"${remote_sha}""${local_sha}"--"$@"fidone}# Emit the paths this push changes under the given pathspec, one per line.## Sibling of pushed_blobs, and deliberately not a --name-only mode of it: this asks a# different question. pushed_blobs audits blob CONTENT, so it wants added/modified# blobs only (--diff-filter=AM); this asks whether a push touches an area at all, and# a deletion touches it — a module removed out from under an import is exactly the# change whose tests must run.pushed_paths(){localentrylocal_sharemote_shaforentryin"${PUSH_REFS[@]}";doread-rlocal_sharemote_sha<<<"${entry}"if[["${remote_sha}"=~^0+$]];thengitrev-list"${local_sha}"--not--remotes="${REMOTE}"\|gitdiff-tree-r-m--stdin--root--no-commit-id--name-only--"$@"elsegitdiff-tree-r-m--no-commit-id--name-only\"${remote_sha}""${local_sha}"--"$@"fidone}# Populate the associative array named by $1 with "<blob sha>" → "<path>" for# every blob this push adds or modifies under the pathspec in the remaining# args. Keying by sha deduplicates the same object arriving via several refs or# merge parents. Gitlinks (mode 160000) are skipped: a submodule entry is a# commit, not a blob, and its object need not even be present here.collect_pushed_blobs(){local-n_cpb_out="$1";shiftlocalmetapathmodeshawhileIFS=read-r-d''meta&&IFS=read-r-d''path;do# 'git diff-tree -r -z' raw meta: ":<src mode> <dst mode> <src sha> <dst sha> <status>"read-r_mode_sha_<<<"${meta}"[["${mode}"==160000]]&&continue[["${sha}"=~^0+$]]&&continue_cpb_out["${sha}"]="${path}"done<<(pushed_blobs"$@")}# Print the first $2 bytes of blob $1 as hex, read straight from the object# store so no smudge filter runs. head closes the pipe early (SIGPIPE on# cat-file); that's expected and harmless here — the hook runs without `set -e`.blob_head_hex(){gitcat-file-p"$1"|head-c"$2"|xxd-p}# ── 1. Encryption-at-rest audit (solutions/private) ──────────────────────# Inlined (rather than sourced from a helper script) so the push gate is# self-contained and survives that helper being changed or removed. Reads each# blob this push adds under solutions/private straight from the object store (so# NO smudge filter runs) and checks it begins with the gitfilter MAGIC. The magic# is read from config.gitfilter_magic, keeping it in lockstep with# packages/solver/solver/crypto/gitfilter.py; a blob lacking it is plaintext that slipped past# the clean filter — block the push.section"Encryption-at-rest audit (solutions/private, blobs in this push)"magic_hex=$("$VENV/bin/python"-c'from solver.crypto import wire; print(wire.MAGIC.hex())')if((${#PUSH_REFS[@]}==0));thenskip"no refs offered by git — this push adds no blobs to audit"elif[[-z"$magic_hex"]];thenfail"could not read config.gitfilter_magic (is the venv intact?)"elsemlen=$((${#magic_hex}/2))total=0encrypted=0plaintext_files=()declare-Aprivate_blobs=()collect_pushed_blobsprivate_blobssolutions/privateforshain"${!private_blobs[@]}";dohead_hex=$(blob_head_hex"$sha""$mlen")total=$((total+1))if[["$head_hex"=="$magic_hex"]];thenencrypted=$((encrypted+1))elseplaintext_files+=("${private_blobs[$sha]}")fidoneif((${#plaintext_files[@]}==0));thenpass"$encrypted encrypted, 0 plaintext, $total total (magic=0x$magic_hex)"elsenote"plaintext blobs under solutions/private in this push:"forpin"${plaintext_files[@]}";donote" !! $p";donenote"the encryption filter was not working when these were committed. Fix and re-commit:"note" solver shell: git-filter install (verifies key access first)"note" then: git add --renormalize -- solutions/private && git commit"fail"files under solutions/private in this push are not all encrypted at rest"fifi# ── 2. Compiled binaries in this push ────────────────────────────────────# Backstop to the pre-commit check: read every blob this push adds — any path,# not just solutions/private — from the object store and flag any that begins# with the ELF magic. Build artifacts (e.g. the p<NNNN>_s<K>_c runner# executables) are gitignored and must never reach the remote.section"compiled binaries (blobs in this push)"ELF_MAGIC_HEX='7f454c46'pushed_bins=()declare-Aall_blobs=()if((${#PUSH_REFS[@]}==0));thenskip"no refs offered by git — this push adds no blobs to audit"elsecollect_pushed_blobsall_blobsforshain"${!all_blobs[@]}";doif[["$(blob_head_hex"$sha"4)"=="$ELF_MAGIC_HEX"]];thenpushed_bins+=("${all_blobs[$sha]}")fidoneif((${#pushed_bins[@]}==0));thenpass"no compiled binaries in this push (${#all_blobs[@]} blob(s) checked)"elsenote"compiled binaries (ELF) in this push:"forbin"${pushed_bins[@]}";donote" !! $b";donefail"compiled binaries are in this push — run 'git rm --cached' on them before pushing"fifi# ── 3. flake8 on packages + solutions ──────────────────────────────────────section"flake8 (solutions, packages)"if"$VENV/bin/flake8"solutionspackages;thenpass"flake8 clean"elsefail"flake8 reported issues"fi# ── 4. mypy on packages + solutions + scripts + tests ──────────────────────# `scripts` holds tracked Python that ships and runs (scripts/setup/euler-smtp.py is# installed to /usr/local/bin and run as a service), so it is checked with the rest; so is# `tests`, whose calls into the packages go stale when a signature changes.section"mypy (solutions, packages, scripts, tests)"if"$VENV/bin/mypy"solutionspackagesscriptstests;thenpass"mypy clean"elsefail"mypy reported type errors"fi# ── 5. shellcheck on every tracked shell script ──────────────────────────# The whole-tree backstop to pre-commit's staged-only lane: that one only sees the# scripts you touched, so a script edited around the hook (--no-verify, a rebase, a# merge) reaches the remote unlinted otherwise. Whole-tree like the flake8/mypy lanes# above, and about as quick as a single git command — every script is parsed# independently, nothing is resolved across files.## The hook templates are in scope: they are shell, and only become .git/hooks/* once# githooks.sh substitutes __VENV__ — which means a broken template is discovered at# `githooks.sh install` time, i.e. as a broken hook, unless it is linted here.section"shellcheck (tracked shell scripts)"mapfile-t-d''tracked_sh<<(gitls-files-z--'*.sh''*.template')if((${#tracked_sh[@]}==0));thenskip"no shell scripts tracked"elif!command-vshellcheck>/dev/null2>&1;thennote"install it with: make install-system"fail"shellcheck is not installed"elifshellcheck--"${tracked_sh[@]}";thenpass"shellcheck clean (${#tracked_sh[@]} script(s))"elsefail"shellcheck reported issues"fi# ── 6. unit tests, when this push touches the code they cover ────────────# The suite takes ~10s and covers packages/ + scripts/. A solutions-only push cannot# break it, and the collaborator pushing their problem should not wait on it — so the# trigger is scoped to what this push changes, like the audits above.## tests/ is in the trigger with the code it covers: a push editing only a test is the# case most certain to want the suite run, and skipping it would let a test be pushed# without ever having been run.## This is the gate that was missing: nothing in the commit or push path ran the tests,# so `PullRequestTest` sat red from 18f5da8b until a human happened to run them. flake8# and mypy could not have caught it — the code type-checked perfectly and simply did# something the tests said it should not.section"unit tests (only when anything changes under packages/, scripts/, or tests/)"if((${#PUSH_REFS[@]}==0));thenskip"no refs offered by git — nothing to test"elif[[-z"$(pushed_pathspackagesscriptstests)"]];thenskip"this push changes nothing under packages/, scripts/ or tests/"# `-t .` imports the tests package first, so its pinned identity is in place before any# command registers (tests/__init__.py): the result must not depend on this terminal's groups.elif"$VENV/bin/python"-munittestdiscover-stests-t.-q;thenpass"unit tests pass"elsefail"unit tests failed"fi# ── 7. audit/ is gitignored whenever it exists ───────────────────────────# The push-side twin of pre-commit's check: audit/ holds run logs that name# collaborators and the host's layout, safe only while .gitignore covers it.section"audit/ folder (gitignored)"if[[!-eaudit]];thenpass"no audit/ folder in this checkout"elif!gitcheck-ignore--no-index-qaudit/;thenfail"audit/ exists but is not gitignored — restore '/audit/' in .gitignore"elif[[-n"$(gitls-files--audit)"]];thenfail"files under audit/ are tracked — 'git rm -r --cached audit' before pushing"elsepass"audit/ is gitignored"fi# ── Verdict ──────────────────────────────────────────────────────────────print_verdictexit$?