Files
narOS/tests/parity-sweep.sh
T

80 lines
2.9 KiB
Bash
Executable File

#!/usr/bin/env bash
# Tool-inventory parity sweep (NAROS.md §10.3): nothing agents rely on may silently
# vanish when the default sandbox image moves from nucleic-sandbox:v7 to naros-agent.
#
# parity-sweep.sh <candidate-image> <reference-image> [--allow FILE]
#
# Compares (a) the set of executables on PATH and (b) the npm global package set, in
# each image. Anything present in the REFERENCE but missing from the CANDIDATE fails
# the sweep unless listed in the allowlist (deliberate, understood removals — one name
# per line, `#` comments). Additions are reported informationally. Python module parity
# is not swept: neither image bakes site-packages beyond pip's own.
#
# Exit: 0 parity holds, 1 unexplained removals, 2 usage/docker errors.
set -euo pipefail
CAND="${1:?usage: parity-sweep.sh <candidate-image> <reference-image> [--allow FILE]}"
REF="${2:?missing reference image}"
shift 2
ALLOW="$(cd "$(dirname "$0")" && pwd)/parity-allowlist.txt"
while [ $# -gt 0 ]; do
case "$1" in
--allow) ALLOW="$2"; shift 2 ;;
*) echo "unknown arg: $1" >&2; exit 2 ;;
esac
done
# Every executable name reachable on the image's default PATH. --entrypoint /bin/sh is
# nash in naros images and dash in the v7 base — the script is portable to both.
path_inventory() {
docker run --rm --entrypoint /bin/sh "$1" -c \
'for d in $(echo "$PATH" | tr : " "); do ls -1 "$d" 2>/dev/null; done | sort -u'
}
npm_inventory() {
docker run --rm --entrypoint /bin/sh "$1" -c \
'npm ls -g --depth=0 --parseable 2>/dev/null | tail -n +2' \
| awk -F/ 'NF { if ($(NF-1) ~ /^@/) print $(NF-1)"/"$NF; else print $NF }' | sort -u
}
tmp="$(mktemp -d)"
trap 'rm -rf "$tmp"' EXIT
echo "inventorying candidate $CAND …"
path_inventory "$CAND" > "$tmp/cand.path"
npm_inventory "$CAND" > "$tmp/cand.npm"
echo "inventorying reference $REF …"
path_inventory "$REF" > "$tmp/ref.path"
npm_inventory "$REF" > "$tmp/ref.npm"
allow_filter() {
if [ -f "$ALLOW" ]; then
grep -vE '^\s*(#|$)' "$ALLOW" | grep -vxF -f /dev/stdin "$1" || true
else
cat "$1"
fi
}
comm -23 "$tmp/ref.path" "$tmp/cand.path" > "$tmp/removed.path"
comm -23 "$tmp/ref.npm" "$tmp/cand.npm" > "$tmp/removed.npm"
added_path=$(comm -13 "$tmp/ref.path" "$tmp/cand.path" | wc -l)
cat "$tmp/removed.path" "$tmp/removed.npm" | sort -u > "$tmp/removed.all"
allow_filter "$tmp/removed.all" > "$tmp/unexplained" || true
echo "PATH: $(wc -l < "$tmp/ref.path") reference, $(wc -l < "$tmp/cand.path") candidate," \
"$(wc -l < "$tmp/removed.path") removed, $added_path added"
echo "npm globals removed: $(wc -l < "$tmp/removed.npm")"
if [ -s "$tmp/removed.all" ]; then
echo "--- removed (before allowlist) ---"
cat "$tmp/removed.all"
fi
if [ -s "$tmp/unexplained" ]; then
echo "PARITY FAILURE — present in $REF, missing from $CAND, not allowlisted:" >&2
cat "$tmp/unexplained" >&2
echo "(fix the image, or add to $ALLOW with a comment explaining the removal)" >&2
exit 1
fi
echo "parity OK"