#!/usr/bin/env bash
# brain-kit maintainer pre-push gate: refuse to push anything that looks like a
# secret or like household data (the maintainer's company, colleagues, clients).
#
# Why a pre-push and not CI: CI runs after the push, when the content is already
# in the public history of the repository. This hook runs before anything
# leaves the machine.
#
# Personal patterns live OUTSIDE the repository, one extended regex per line,
# because the list of names to protect is itself the data to protect:
#   default file: ~/.config/brain-kit/leak-patterns.txt
#   override:     BRAIN_KIT_LEAK_PATTERNS=/path/to/file
# The hook fails closed: no patterns file, no push. That check, and every
# other check this file used to make about the patterns file's own shape
# (missing, empty, a directory, unreadable), now lives in Node (see the
# sixth round below) and is enforced the moment `brain-kit scan-blobs` starts,
# before it reads anything from standard input.
#
# Portability: bash 3.2 and a POSIX userland. The project's own CI runs this on
# macOS, whose /bin/bash is 3.2.57 and whose grep and sort are BSD, so nothing
# here may need bash 4 (no associative arrays, no ${var,,}) or a GNU-only
# flag. The one flag relied on is `sort -z`, present in both userlands, and
# every empty-array expansion uses the ${arr[@]+"${arr[@]}"} form, which old
# bash needs under `set -u`.
#
# Activate once per clone:  .githooks/install-gate
#
# Since 22/09/2026 the half of this gate that asks git what a push contains
# lives in src/push/records.sh, run by `brain-kit push-gate` out of the
# installed snapshot (see the note above the push-gate call at the end of
# this file). The history below, (a) to (r), is that enumeration's history:
# where it says "this loop" or "this file" about the range, the diff or the
# reference lines, it now means records.sh.
#
# NOT `git config core.hooksPath .githooks`. This file is the SOURCE of the
# gate, not the thing git runs. `.githooks/install-gate` copies this file
# and a snapshot of the engine it needs into a directory under the git
# directory and points core.hooksPath there; see the eighth round below,
# and that script's own header, for why the copy has to live outside the
# working tree. Pointing core.hooksPath at this directory instead leaves
# the half of the gate that decides WHAT to scan inside the tree, where a
# checkout replaces it and an uncommitted edit weakens it, so the hook
# refuses to run from here and says so.
set -u

# 18/09/2026, fourth lesson: three gaps, all the same shape. A scanner that
# could not see something shrugged instead of refusing.
#   (a) Merge commits were never scanned. `git diff-tree` on a commit with
#       more than one parent prints nothing at all by default, so a merge
#       whose conflict resolution typed in a secret that exists in neither
#       parent went out unexamined, with no force and no adversarial setup.
#       Fixed with `-m`, which emits one diff per parent: a path whose blob
#       differs from ANY parent is listed, which is a superset of everything
#       the merge itself introduced. A path identical to every parent is not
#       new in the merge, and that content was already covered when those
#       parents were scanned, or is already public on the remote. Paths repeat
#       across the per-parent diffs, so they are deduped per commit.
#   (b) Binary-flagged blobs were skipped. This is now moot: the Node reader
#       (src/leak.mjs, via `brain-kit scan-blobs`) reads a blob's bytes
#       directly and never asks git or grep whether it looks like text, so
#       there is no binary/text distinction left to get wrong. See the sixth
#       round below.
#   (c) The patterns file was accepted when it was useless, and grep's errors
#       were swallowed. Also moot: there is no grep left in this hook. See
#       the sixth round below.
#
# 18/09/2026, fifth lesson: four more, the last two about the machine the hook
# runs on rather than about git.
#   (d) Typechanges were dropped. `--diff-filter=AM` names what to KEEP, and
#       git has more letters than A and M: replacing a symlink with a regular
#       file carrying a secret is a `T`, which was excluded, so it reached the
#       remote unscanned with exit 0 and no warning. An allow-list of change
#       kinds is a blind spot by construction. The filter is inverted now:
#       `--diff-filter=d`, lower case, excludes deletions and keeps everything
#       else, so additions, modifications, typechanges, copies and renames are
#       all scanned. Deletions are the only kind with nothing to read.
#   (e) Only the last stage's status was read, in the old `git show | tr |
#       grep` pipeline. There is no pipeline left in this hook at all: every
#       command whose result is used runs as a statement of its own, with
#       its own status read, rather than as a stage of a pipe whose earlier
#       stages report nothing.
#   (f) The hook required bash 4. The dedupe used an associative array and
#       `"${remote_exclusions[@]}"` aborts on an empty array under `set -u` in
#       old bash, so on macOS (bash 3.2.57) the dedupe silently switched off
#       and the very first push died on an unbound variable. A gate that
#       behaves differently, or not at all, on the machine its users have is
#       not a gate. The dedupe is `sort -zu` now, which keeps the NUL
#       discipline and needs no shell features, the exclusions use the
#       ${arr[@]+"${arr[@]}"} form, and the bash-version detection is gone:
#       there is no version-dependent behaviour left to detect.
#   (g) The locale could abort the scan. Also moot: the Node reader decodes
#       every blob as raw bytes (`latin1`, one code point per byte), which has
#       no locale to depend on in the first place. See the sixth round below.
#
# 19/09/2026, sixth round. THE SEQUEL TO THE FIFTH LESSON. The five rounds
# above were all fixes to a shell scanning pipeline: `git show "$sha:$path" |
# tr -d '\000' | grep -n -a -i -E -m "$MAX_HITS" -f "$PATTERNS_FILE" -e
# "$GENERIC_PATTERNS"`. That pipeline is gone. Every behaviour those rounds
# established (scan every commit in the range, diff merges with `-m`, keep
# typechanges with `--diff-filter=d`, distrust the local tracking ref, fall
# back to a full scan when a range cannot be computed or the remote cannot be
# reached) still holds and is still this file's job, because it is all about
# asking GIT what a push contains, which is exactly what shell remains good
# at. Reading a blob's bytes, deciding whether they look like a secret, and
# deciding whether a result is safe to print move to `brain-kit scan-blobs`
# (src/commands/scan-blobs.mjs), which calls src/leak.mjs, a module with its
# own three review rounds and three fix rounds. There, an unread status is no
# longer even possible: loadPatterns and scanText raise rather than return
# something a caller could fail to check, which is the fifth round's own
# lesson applied one layer up. `brain-kit scan-blobs` is called exactly ONCE
# per push, not once per blob: every record this file's own git plumbing
# finds is written, NUL-separated, to one scratch file, and read back by that
# single Node process, so the whole push shares one process start instead of
# paying it once per file.
#
# 19/09/2026, seventh round, from the sixth round's own review. Four more,
# and the first two were leaks nobody had to make a mistake to reach.
#   (h) Only blob CONTENT was scanned. A file NAME, a commit message, an
#       annotated tag's message and an author or committer identity all
#       reached the remote unexamined, with a correct patterns file and an
#       untouched gate. In a knowledge vault a client's name is in the
#       FILENAMES at least as often as in the prose, because that is how a
#       vault is organised. This file now hands `scan-blobs` three kinds of
#       record instead of one path list, and that command scans every one of
#       those channels through the same patterns, the same scanner and the
#       same never-print contract:
#         blob   NUL <commit> NUL <mode> NUL <path> NUL
#         commit NUL <commit> NUL
#         tip    NUL <sha> NUL
#       The MODE travels with the path because deciding what a tree entry is
#       belongs with the code that decides what to do about it: a gitlink
#       (160000) has no content in this repository to read, and an entry this
#       file mis-parses does not resolve to 160000, so a parsing mistake
#       makes the gate scan MORE, never less.
#   (i) The scanner ran from the WORKING TREE, so an uncommitted edit to any
#       module it loads weakened the gate that was supposed to judge the
#       push, which a reviewer demonstrated by editing one neighbouring file
#       and walking a leak straight through. The fix that round shipped,
#       extracting the toolchain from the commit being pushed, was worse
#       than the hole; see the eighth round below, which is where this is
#       actually fixed.
#   (j) A repository containing a submodule could not be pushed at all: the
#       gitlink made `git show` exit 128 and the gate refused every push
#       permanently, leaving --no-verify as the only way out, which is not a
#       safe state. Gitlinks are skipped now, explicitly and out loud. The
#       rule that decides: refuse when something that should have been
#       readable could not be read; skip when there was never anything here
#       to read.
#   (k) Statuses again, in the half that stayed in bash. The work list was
#       built with writes nobody checked, and a work list that loses records
#       is a push whose remaining blobs are never examined and which then
#       exits 0. Every write to it is checked, and a failed one refuses the
#       push immediately rather than scanning a prefix of it.
#
# 19/09/2026, eighth round. ONE FINDING, AND IT IS THE SEVENTH ROUND'S OWN
# CENTRAL FIX. Running the scanner out of the commit being pushed is
# arbitrary code execution: the push chose the code that judged it. A
# branch carrying its own bin/brain-kit.mjs was accepted with the hook
# intact, a correct pattern list and no --no-verify, and the code it
# carried read BRAIN_KIT_LEAK_PATTERNS and copied the private pattern list
# out. That list lives outside the repository precisely because, in this
# file's own words, the list of names to protect is itself the data to
# protect, and the seventh round handed it to whatever the push carried.
#
# Both that hole and the one it was built to close are the same mistake in
# opposite directions: the code that judges a push was read from somewhere
# the push itself can reach. The working tree can be edited without
# committing; the pushed tip can be written outright. Neither is a place to
# read a judge from.
#
#   (l) BOTH HALVES OF THE GATE NOW LIVE OUTSIDE THE WORKING TREE, under
#       the git directory, installed there by `.githooks/install-gate`
#       (read its header for the location argument and for what this still
#       does not close). This file is only the SOURCE of the hook; the copy
#       git runs is <git common dir>/brain-kit-gate/pre-push, and the
#       engine it runs is <git common dir>/brain-kit-gate/engine. A
#       checkout cannot remove them, an orphan branch cannot replace them,
#       an uncommitted edit cannot weaken them, and nothing in any pushed
#       object is read except the objects being scanned.
#
#       That one change dissolves a whole class at once, and the deletions
#       are the evidence: there is no `git archive` of the tip any more, no
#       tar, no scratch toolchain directory, and no record-protocol token
#       grep, because the hook and the engine are installed by one command
#       and cannot be different ages. With them go three refusals the
#       extraction design had to invent, each of which had --no-verify as
#       its only escape: a tip carrying no package, a tip whose bin/ has no
#       entry point, and a tip whose scanner predates the record protocol.
#       An orphan branch and a pre-package tag are ordinary pushes again,
#       and they are SCANNED rather than waved through.
#
#   (m) An install can be half done, so the gate checks its own. Running
#       from anywhere but the installed directory refuses, which is what
#       keeps `git config core.hooksPath .githooks` from quietly putting
#       the deciding half back in the tree; a missing engine snapshot or a
#       missing stamp refuses too. Every one of those messages names the
#       exact command that fixes it, because a gate that fails without
#       saying how to make it work is a gate people learn to skip.
#
#   (n) The snapshot is deliberately NOT refreshed automatically: doing so
#       would read the gate back out of the working tree on every push and
#       hand (i) straight back. So it goes stale by design, and the hook
#       says which snapshot it ran. See (o) for when.
#
# 19/09/2026, ninth round, from the eighth round's own re-review.
#   (o) THE ANNOUNCEMENT WAS UNCONDITIONAL, WHICH IS HOW IT WENT UNREAD.
#       (n) printed two lines on every push, clean or refused, on stderr,
#       the same stream the findings use. A message that appears on every
#       successful push is one people stop parsing within a week, and it
#       was competing for attention with the messages that matter, which
#       weakened the only signal a stale snapshot has. The honesty (n)
#       argues for is kept and the noise is not: on a REFUSAL both lines
#       print, always, whatever went wrong and wherever the refusal came
#       from (an EXIT trap, so no future exit path can forget); on a clean
#       push they print only when the snapshot is actually stale, decided
#       mechanically and never by age. Stale means the stamp records a
#       dirty source tree, or this repository is the brain-kit checkout
#       the gate came from and its HEAD is not the commit the stamp names
#       or its .githooks/pre-push no longer matches the installed copy.
#       Nothing in that reads the engine out of the tree, so none of it
#       hands (i) back.
#   (p) A PUSHED REFERENCE THAT DOES NOT PEEL TO A COMMIT WAS NEVER
#       SCANNED. This loop assumed every ref resolves to a commit. Git
#       does not require that: a ref can name a blob or a tree outright,
#       and `git push origin <blob>:refs/leaks/one` is a one-line command.
#       `git rev-list` on a non-commit exits ZERO with empty output, which
#       this loop read as "nothing to scan", and the tip handler in
#       scan-blobs broke out of its loop on any type it did not recognise,
#       so nothing scanned it and nothing said so: exit 0, and the content
#       readable straight off the bare remote. A reviewer proved three
#       shapes of it, a raw blob and an annotated tag naming a blob and
#       one naming a tree.
#
#       This is the THIRD time on this gate that a command succeeding with
#       empty output was read as nothing to do, after the two the comments
#       at full_history and at the range computation already name. Both of
#       those guard a command that FAILS. Neither guarded a command that
#       succeeds and returns nothing because its argument was never the
#       kind of thing it was asked about. So the question is asked before
#       the command instead of inferred from its silence: this loop peels
#       the ref and refuses to let rev-list stand in for the answer, and
#       scan-blobs handles every terminal type by name and refuses one it
#       does not know.
#
# 19/09/2026, tenth round. THE SIXTH AND LAST UNSCANNED CHANNEL.
#   (q) A REFERENCE NAME REACHED THE REMOTE UNEXAMINED. Five channels were
#       scanned (blob content, file names, commit messages, annotated tag
#       messages, author and committer identities) and the name of the
#       reference itself was not one of them. Reproduced on the previous
#       tree: a branch named after an active pattern pushed with exit 0 and
#       the name sat on the bare remote, readable by anyone who can list
#       references. It is the same shape as the file-name channel and it
#       matters for the same reason: a vault is full of people and
#       organisations, and naming a branch after one of them is what a
#       person does without thinking.
#
#       The DESTINATION name is scanned, because that is the field that
#       decides where the reference lands and the only one that crosses
#       the wire; the source name never leaves the machine (see the
#       protocol note in scan-blobs for the rest of that argument, and for
#       why scanning the source would take away the remedy).
#
#       Deletions are scanned too. A deletion carries no objects, but it
#       still transmits its destination name, and git does NOT require
#       that name to exist on the remote first: pushing a deletion of a
#       reference that was never there is accepted, with a warning, and
#       the name reaches the receiving end all the same. Measured, not
#       assumed.
#
#       This file also stopped printing reference names of its own accord.
#       A name that matched is withheld by the scanner, so a message here
#       that echoed the same name back would hand the finding to whatever
#       reads this output; every message below names a reference by its
#       NUMBER in the push instead.
#   (r) THE FIFTH "A COMMAND SUCCEEDED AND SAID NOTHING", found by going
#       looking rather than by being told, and this one is in the
#       reference loop's own condition rather than in any command. A
#       `while read` loop drops a final line that has no newline after
#       it, having already assigned its fields, so the last reference of
#       such an input was skipped entirely: all six channels of it, exit
#       0, no output. Git terminates its lines, so it was latent, and it
#       is guarded now anyway. The same pass made a push with no
#       references at all say so instead of being silent, for the reason
#       every other skip in this gate says so.
# THE GATE ASKS GIT WHAT THE PUSH CONTAINS, AND GIT CAN BE TOLD TO LIE.
#
# `git replace <a commit> <another commit>` installs a ref under
# refs/replace/ and from then on every ordinary git read, rev-list,
# diff-tree, cat-file, show, reports the REPLACEMENT wherever the original
# was asked about. `git push` does not: it sends the object that is really
# there. So the gate scanned one commit and the remote received a different
# one. Measured on 19/09/2026 against a throwaway bare remote with the real
# installed gate, no --no-verify and no edit to the gate at all: a commit
# whose file matched an active pattern was pushed with exit 0, no output,
# and the pattern in plaintext on the remote.
#
# This is the third time this component has been defeated by editing
# something the gate TRUSTED rather than the gate itself, and it is the
# cheapest of the three: one ordinary command, no configuration the
# maintainer would notice, nothing left in the working tree.
#
# Exported here, so it reaches every git this hook runs AND the git
# processes the scanner runs as its child. The scanner passes
# --no-replace-objects on its own calls as well rather than relying on
# inheriting this, because the two halves are separately installable and a
# guarantee that only holds when both are current is not a guarantee.
GIT_NO_REPLACE_OBJECTS=1
export GIT_NO_REPLACE_OBJECTS

PATTERNS_FILE_LABEL="${BRAIN_KIT_LEAK_PATTERNS:-$HOME/.config/brain-kit/leak-patterns.txt}"

if ! command -v node >/dev/null 2>&1; then
  echo "pre-push: node is required to run brain-kit scan-blobs; refusing to push." >&2
  exit 1
fi
REPO_ROOT="$(git rev-parse --show-toplevel 2>/dev/null)"
if [ -z "$REPO_ROOT" ]; then
  echo "pre-push: could not determine the repository root (git rev-parse --show-toplevel failed); refusing to push." >&2
  exit 1
fi

# --- the gate's own installation (eighth round, (l), (m) and (n)) --------
#
# Where the installed gate lives, computed the same way `.githooks/install-gate`
# computes it, from git rather than from this file's own location: the two
# have to agree, and asking git is the only way to agree without one of them
# guessing. --git-common-dir rather than --git-dir so a linked worktree
# finds the one gate the repository has.
GIT_COMMON_DIR="$(git rev-parse --git-common-dir 2>/dev/null)"
if [ -z "$GIT_COMMON_DIR" ]; then
  echo "pre-push: could not locate the git directory (git rev-parse --git-common-dir failed), so this gate cannot find its own installation; refusing to push." >&2
  exit 1
fi
case "$GIT_COMMON_DIR" in
  /*) ;;
  *) GIT_COMMON_DIR="$REPO_ROOT/$GIT_COMMON_DIR" ;;
esac
if ! GIT_COMMON_DIR="$(cd "$GIT_COMMON_DIR" 2>/dev/null && pwd -P)"; then
  echo "pre-push: the git directory reported by git does not exist, so this gate cannot find its own installation; refusing to push." >&2
  exit 1
fi
GATE_DIR="$GIT_COMMON_DIR/brain-kit-gate"

# (m), first half. THIS half of the gate decides what gets scanned at all:
# the commit range, the diff filter, merge handling, the exclusions, and
# whether the engine is called once or not at all. Left in the working tree
# it is a file a checkout replaces and an uncommitted edit empties, which a
# reviewer demonstrated by emptying it and pushing a matching file with
# exit 0 and no output. So it refuses to run from anywhere but the
# installed copy. This is also the check that keeps the old activation line
# (`git config core.hooksPath .githooks`) from silently working: it would
# run this file from the tree, and it now stops instead.
HOOK_DIR="$(cd "$(dirname "$0")" 2>/dev/null && pwd -P)"
if [ "$HOOK_DIR" != "$GATE_DIR" ]; then
  echo "pre-push: this gate is running from $HOOK_DIR, not from its installed copy at $GATE_DIR. The half of the gate that decides what to scan must not live where a checkout can replace it or an uncommitted edit can empty it. Install it and point core.hooksPath at the installed copy by running .githooks/install-gate from your brain-kit checkout." >&2
  exit 1
fi

# (m), second half. An install with a hook but no engine is the shape a
# person half-does without noticing: a copied hook, a moved git directory,
# a partly deleted gate. The stamp is written LAST by the installer, so its
# presence means the rest arrived; the engine files this hook reaches are
# checked by name anyway, because a message naming what is missing beats a
# module error naming a path nobody recognises. src/push/records.sh is one
# of them: it is run by bash rather than imported, so its absence would
# otherwise surface as a shell error about a file nobody knows is needed.
ENGINE_DIR="$GATE_DIR/engine"
BRAIN_KIT_BIN="$ENGINE_DIR/bin/brain-kit.mjs"
SNAPSHOT_FILE="$GATE_DIR/SNAPSHOT"
INSTALLER_FILE="$GATE_DIR/INSTALLER"
for required in "$BRAIN_KIT_BIN" "$ENGINE_DIR/src/commands/scan-blobs.mjs" "$ENGINE_DIR/src/commands/push-gate.mjs" "$ENGINE_DIR/src/push/records.sh" "$INSTALLER_FILE" "$SNAPSHOT_FILE"; do
  if [ ! -f "$required" ]; then
    echo "pre-push: the gate's engine snapshot is incomplete ($required is missing), so there is nothing here to scan this push with; refusing to push. Re-install it by running .githooks/install-gate from your brain-kit checkout." >&2
    exit 1
  fi
done
# The installer recorded its own path, so every message below names the
# exact command rather than a reconstruction of it: the checkout the gate
# was installed from is not necessarily the repository being pushed.
if ! INSTALL_CMD="$(cat "$INSTALLER_FILE")" || [ -z "$INSTALL_CMD" ]; then
  echo "pre-push: the gate cannot read $INSTALLER_FILE, so it cannot say which command re-installs it; refusing to push. Re-install it by running .githooks/install-gate from your brain-kit checkout." >&2
  exit 1
fi
if ! SNAPSHOT="$(cat "$SNAPSHOT_FILE")" || [ -z "$SNAPSHOT" ]; then
  echo "pre-push: the gate's snapshot stamp at $SNAPSHOT_FILE could not be read, so this gate cannot say which engine it is about to run; refusing to push. Re-install it with: $INSTALL_CMD" >&2
  exit 1
fi

# (n) and (o). The two lines, printed at most once, from wherever the
# decision to print them is taken.
gate_lines_printed=0
print_gate_lines() {
  [ "$gate_lines_printed" -eq 1 ] && return 0
  gate_lines_printed=1
  echo "pre-push: gate engine snapshot: $SNAPSHOT" >&2
  echo "pre-push: refresh it with: $INSTALL_CMD" >&2
  return 0
}

# (o). Is the snapshot stale? Mechanical, and never age: a snapshot taken
# an hour ago from a tree that has since changed is stale, and one taken a
# month ago from a gate nobody has touched is not, so a threshold in days
# would be wrong in both directions.
#
# Three signals, any one of which is enough, and every one of them errs
# toward printing. The stamp's own record of a dirty source tree is the
# first: a snapshot taken from a dirty tree does not correspond to the
# commit it names, and what made it dirty may be the gate itself. The
# other two apply only when the repository being pushed IS the brain-kit
# checkout the gate is maintained in, which is the only case where this
# hook can tell what the gate ought to be: a HEAD that is not the commit
# the stamp names, and a .githooks/pre-push or src/push/records.sh that no
# longer matches its installed copy byte for byte. That second one catches
# the case the first cannot, an edit made and not yet committed, which is
# the ordinary way a gate change is tested.
#
# Reading those two files here does not hand (i) back. They are COMPARED,
# never executed and never sourced; the hook that runs is still only the
# installed copy, and the comparison can make this gate noisier, never
# quieter or weaker.
snapshot_stale=0
case "$SNAPSHOT" in
  *"working tree dirty"*) snapshot_stale=1 ;;
esac
if [ -f "$REPO_ROOT/.githooks/pre-push" ] && [ -f "$REPO_ROOT/.githooks/install-gate" ]; then
  # The stamp carries the FULL object name for exactly this comparison, so
  # neither side has to decide how many characters of the other to look at.
  snapshot_rest="${SNAPSHOT#*from commit }"
  snapshot_commit="${snapshot_rest%% *}"
  head_commit="$(git rev-parse HEAD 2>/dev/null || true)"
  if [ -z "$head_commit" ] || [ "$snapshot_commit" != "$head_commit" ]; then
    snapshot_stale=1
  fi
  if ! cmp -s "$REPO_ROOT/.githooks/pre-push" "$GATE_DIR/pre-push"; then
    snapshot_stale=1
  fi
  # The enumeration used to be part of this file, so the comparison above
  # covered it. Since 22/09/2026 it lives in src/push/records.sh and is
  # installed into the engine snapshot, so it is compared on its own, the
  # same way: compared, never run.
  if ! cmp -s "$REPO_ROOT/src/push/records.sh" "$ENGINE_DIR/src/push/records.sh"; then
    snapshot_stale=1
  fi
fi
if [ "$snapshot_stale" -eq 1 ]; then
  print_gate_lines
fi

failed=0

# (o). The EXIT trap carries the refusal half of the announcement, because
# a refusal can leave from several places in this file and a rule that
# every one of them has to remember is a rule the next one added will not.
# Reading $? in the trap makes "refused" mean what the shell means by it,
# so nothing has to be kept in step by hand.
gate_exit() {
  if [ "$1" -ne 0 ]; then
    print_gate_lines
  fi
}
trap 'gate_exit $?' EXIT

# THE ENUMERATION AND THE SCAN, IN ONE CALL (22/09/2026).
#
# Everything this file used to do from here on, asking git what the push
# contains (the range, `-m`, `--diff-filter=d`, the remote query, the
# reference numbers, the destination names, every status it reads), now
# lives in src/push/records.sh, moved unchanged, and `brain-kit push-gate`
# runs it and scans what it lists. The move exists because the gate brain-kit
# ships to other people needs the same enumeration, and a second copy of the
# most-reviewed code in this repository is the copy somebody forgets.
#
# It does not move the enumeration back within reach of the push. push-gate
# runs the records.sh beside its own module, and the module that runs here is
# the SNAPSHOT's, so the enumeration that judges this push is
# $ENGINE_DIR/src/push/records.sh, under the git directory, where no commit
# and no checkout can reach it: the eighth round's (l), unchanged.
#
# push-gate reads the enumeration's status before its stream and refuses on
# any failure, loads the personal patterns exactly as scan-blobs did (and
# fails closed the same way, even for a push with nothing to scan), and
# exits non-zero when anything matched or could not be read. That status is
# read here and is the verdict. "${1:-origin}" is the default the
# enumeration always had; git passes both arguments. The second, the url
# git is actually pushing to, is what the enumeration asks about what the
# remote already holds (see the note at remote_url in records.sh).
if ! node "$BRAIN_KIT_BIN" push-gate "${1:-origin}" "${2:-}" --patterns personal; then
  failed=1
fi

if [ "$failed" -ne 0 ]; then
  echo "pre-push: push refused. Remove the flagged content (and rewrite the commits that contain it) before pushing." >&2
  echo "pre-push: personal patterns are read from: $PATTERNS_FILE_LABEL" >&2
  exit 1
fi
exit 0
