Compare commits

...
Author SHA1 Message Date
Frank a3c2465638 feat(scripts): deterministic .AGENTTRASH purger with bats seam
Portable POSIX-sh purge of exactly the fleet's deletion-policy trash: a
root's own .AGENTTRASH is emptied in place (the convention survives),
deeper .AGENTTRASH trees are removed with their nested entries accounted
once, symlinked/foreign-owned candidates are refused, / is refused.
Dry-run by default; --execute performs the plan and appends one dated log
line; every printed line is fixed at plan time in the on-disk plan table,
so dry-run and execute output can never drift.

Documented in Operations/Payload and Disk Reclaim.md section 6 (one fact
one owner - no new vault page). Tests: 14 green, ./tests/run.sh 576 green.
2026-10-28 15:54:23 -07:00
dank 58dc7a64c1 Merge pull request 'feat(qwen38): score the inherited corpus at the window the container really has' (#78) from feat/qwen38-w5nl64 into main 2026-10-28 11:04:59 -07:00
dank fca47d25d8 Merge pull request 'fix(qwen38): the rig's card split is inherited from frank-archon.env, not invented' (#77) from feat/qwen38-w5nl64 into main 2026-10-27 23:53:12 -07:00
dank 8270524edd Merge pull request 'feat(qwen38): the w5nl64 heavier-quant campaign toolchain (fetch, BF16 reference, arms, FP8-anchored gate)' (#76) from feat/qwen38-w5nl64 into main 2026-10-27 19:59:37 -07:00
3 changed files with 518 additions and 0 deletions

No files matched your search

+335
View File
@@ -0,0 +1,335 @@
#!/bin/sh
# Deterministically empty every .AGENTTRASH directory under the given roots.
#
# PURPOSE
# The fleet's deletion policy parks deleted files in .AGENTTRASH/ — in-repo
# beside the item, elsewhere in $HOME as .AGENTTRASH/YYYYMMDD-topic — and
# waits for approval to purge. This script is that purge: given a machine,
# it clears .AGENTTRASH directories and nothing else.
#
# CONTRACT
# Default dry-run: the plan prints and nothing is deleted; --execute deletes.
# Every line the script can ever print for a candidate is decided at plan time
# and stored in the plan table, so a dry run prints exactly what --execute
# will print. A .AGENTTRASH that is a root's own trash (a repo's
# .AGENTTRASH, a machine's ~/.AGENTTRASH) prints `plan empty <path>`: the
# directory is the convention and survives, its staged contents are cleared.
# A .AGENTTRASH deeper than that is content and prints `plan remove <path>`
# plus, once executed, `removed <path>`; its own nested .AGENTTRASH entries
# belong to that tree and are never planned again. Candidates are found with
# `find -xdev` per root, so a mounted tree can never be walked by accident;
# ordering is lexicographic (LC_ALL=C). `/`, symlinked, and foreign-owned
# candidates are refused and reported (exit 1), never touched.
# A summary line `removed=<candidates> deleted=<removed> refused=<n>` closes
# every run and one dated line is appended to the log.
# Exit: 0 success, 1 operational failure, 2 usage error. A second run over
# the same tree plans nothing further and exits 0.
#
# PORTABILITY
# POSIX sh, clean under `shellcheck -S style`. Externals: find (must support
# -delete), stat, rm, date, mkdir. No bashisms, no GNU-only flags, no arrays,
# no `local`. Paths containing whitespace are rejected at parse time — the
# same rule applied to roots and candidates — so the plan can be walked with
# plain word splitting and nothing can be silently mis-split.
#
# USAGE
# purge-agenttrash [--execute] [--quiet] [--log PATH] [PATH...]
# --execute delete; without it the run is a plan and deletes nothing
# --quiet suppress per-line output; errors and the summary still print
# --log PATH log destination (default: $HOME/.AGENTTRASH-cleanup.log)
# PATH... roots to search (default: $HOME)
#
# Everything else is fixed by design: the root default, the ordering, the
# guards. There is no configuration file and no environment override.
set -eu
usage() {
sed -n '3,44p' "$0" | sed 's/^# \{0,1\}//'
}
die() {
printf '%s: %s\n' 'purge-agenttrash' "$1" >&2
exit "${2:-1}"
}
# ------------------------------------------------------------------ arg parser
# Roots accumulate as one space-separated string: a root containing whitespace is
# rejected at parse time rather than silently split later. That leaves the plan
# iteratable with plain `$@`-free word splitting, the only portable construct
# that cannot corrupt a path it has already vetted.
MODE=plan
QUIET=0
LOGPATH=
ROOTS=
while [ "$#" -gt 0 ]; do
case "$1" in
--help|-h)
usage
exit 0
;;
--execute)
MODE=execute
;;
--quiet)
QUIET=1
;;
--log)
[ "$#" -ge 2 ] || die '--log needs a value' 2
LOGPATH=$2
shift
;;
--log=*)
LOGPATH=${1#--log=}
;;
-*)
die "unknown option: $1" 2
;;
*)
case $1 in
*' '*) die "root contains whitespace: $1" 2 ;;
esac
if [ -z "$ROOTS" ]; then
ROOTS=$1
else
ROOTS="$ROOTS $1"
fi
;;
esac
shift
done
[ -n "${HOME:-}" ] || die 'HOME is not set; pass roots explicitly or set HOME' 2
[ -n "$LOGPATH" ] || LOGPATH=$HOME/.AGENTTRASH-cleanup.log
# ------------------------------------------------------------ tool availability
require() {
command -v "$1" >/dev/null 2>&1 || die "required tool not found: $1" 1
}
require find
require stat
require rm
require date
require mkdir
# `find -delete` backs the in-place emptying. Probe it rather than trusting
# `find --version`: a shell function named `find` must not be able to fake it.
if ! find . -maxdepth 0 -name '.purge-probe-does-not-exist' -delete >/dev/null 2>&1; then
die 'find does not support -delete; refusing to guess at deletion' 1
fi
UID_SELF=$(id -u)
# ------------------------------------------------------------------- locking
# The lock dir also hosts this run's plan table, so both live and die together:
# the exit trap removes the whole directory and only the log survives the run.
LOCKDIR=$HOME/.agenttrash-purge.lock
PLANFILE=$LOCKDIR/plan
acquire_lock() {
if ! mkdir "$LOCKDIR" 2>/dev/null; then
held_pid=$(cat "$LOCKDIR/pid" 2>/dev/null || printf '%s\n' '')
if [ -n "$held_pid" ] && kill -0 "$held_pid" 2>/dev/null; then
die "another purge holds $LOCKDIR (pid $held_pid)" 1
fi
# Owner died mid-run: drop its pid, then retry exactly once. An
# unremovable/nonempty lock dir is not ours to reclaim.
rm -f "$LOCKDIR/pid" 2>/dev/null || true
rmdir "$LOCKDIR" 2>/dev/null || die "$LOCKDIR exists and is not ours to reclaim" 1
mkdir "$LOCKDIR" 2>/dev/null || die "cannot reacquire $LOCKDIR" 1
fi
printf '%s\n' "$$" >"$LOCKDIR/pid" || die "cannot stamp $LOCKDIR/pid" 1
: >"$PLANFILE" || die "cannot write $PLANFILE" 1
trap 'rm -rf "$LOCKDIR"' EXIT
trap 'exit 130' INT
trap 'exit 143' TERM
}
# ------------------------------------------------------------------ predicates
is_dir() { # true only for a real directory, never a symlink to one
[ -d "$1" ] && [ ! -L "$1" ]
}
is_ours() { # true only when the invoking user owns the path
owner=$(stat -c '%u' "$1" 2>/dev/null) || return 1
[ "$owner" = "$UID_SELF" ]
}
refuse() { # refuse <path> <reason> — reported, never deleted
printf '%s: refused %s (%s)\n' 'purge-agenttrash' "$1" "$2" >&2
REFUSED=$((REFUSED + 1))
}
emit() { # emit <line> — silenced by --quiet
[ "$QUIET" -eq 1 ] || printf '%s\n' "$1"
}
# --------------------------------------------------------------- the plan table
# One row per candidate: `<action> <path> <line>`. The first two fields tell
# `act_on` what to do; the rest of the row is EXACTLY what the run prints for
# that candidate — in plan mode verbatim, in execute mode as the first line of
# its report. The table is a file, so no shell quoting rule (quoted-for,
# heredoc, IFS games) can corrupt an approved plan, and the two modes can
# never drift apart. A removal's second line (`removed <path>`) is derived from
# the row's action, so it is also fixed at plan time.
CANDIDATES=0
REFUSED=0
record() { # record <action> <path> <line> — append one row to the plan table
printf '%s %s %s\n' "$1" "$2" "$3" >>"$PLANFILE"
CANDIDATES=$((CANDIDATES + 1))
}
# ------------------------------------------------------------- candidate rule
# The whole contract in one place:
# `/` is refused outright (never a candidate, never walked);
# a root that is a symlink or a plain file is refused, never followed;
# a root (or any path) that IS a real .AGENTTRASH prints `plan empty <path>`:
# the convention persists, its staged contents are what gets cleared;
# otherwise the root's DIRECT .AGENTTRASH child is also `empty` — it is that
# level's own trash ($HOME/.AGENTTRASH, x1/.AGENTTRASH, repo-a/.AGENTTRASH);
# every .AGENTTRASH deeper than that is content: `remove`, printed as
# `plan remove <path>` (+ `removed <path>` when executed), unless it lives
# inside a tree already claimed (then it dies with that tree, never planned
# twice) or is foreign-owned/symlinked (then refused).
collect_root() { # collect_root <path> -> 0 ok, 1 refused
target=$1
if [ "$target" = / ]; then
refuse / 'root refusal'
return 1
fi
if [ -L "$target" ] || ! [ -d "$target" ]; then
refuse "$target" 'not a real directory'
return 1
fi
if ! is_ours "$target"; then
refuse "$target" "not owned by uid $UID_SELF"
return 1
fi
if [ "${target##*/}" = .AGENTTRASH ]; then
record empty "$target" "plan empty $target"
return 0
fi
# `own` is the root's own .AGENTTRASH once handled; hits below it belong to
# that tree and must never be planned a second time.
own=
if [ -e "$target/.AGENTTRASH" ]; then
if ! is_ours "$target/.AGENTTRASH"; then
refuse "$target/.AGENTTRASH" "not owned by uid $UID_SELF"
elif ! is_dir "$target/.AGENTTRASH"; then
refuse "$target/.AGENTTRASH" 'not a real directory'
else
record empty "$target/.AGENTTRASH" "plan empty $target/.AGENTTRASH"
own=$target/.AGENTTRASH
fi
fi
# No `-type d`: implementations disagree on whether that matches a
# symlink-to-directory (GNU find says no, others yes), so every entry named
# .AGENTTRASH is collected and classified here instead of by `find`.
if ! found=$(find "$target" -xdev -name .AGENTTRASH 2>/dev/null); then
refuse "$target" 'find failed'
return 1
fi
previous=
for entry in $found; do
[ -n "$entry" ] || continue
if [ -n "$own" ]; then
case $entry in
"$own"|"$own"/*) continue ;; # already emptied in place
esac
fi
if [ -n "$previous" ]; then
case $entry in
"$previous"/*) continue ;; # nested: dies with its parent
esac
fi
previous=$entry
if ! is_ours "$entry"; then
refuse "$entry" "not owned by uid $UID_SELF"
continue
fi
if ! is_dir "$entry"; then
refuse "$entry" 'not a real directory'
continue
fi
record remove "$entry" "plan remove $entry"
done
return 0
}
# ------------------------------------------------------------------ execution
remove_tree() { # rm -rf a real directory; symlinks inside die as entries
is_dir "$1" || return 1
rm -rf -- "$1"
}
empty_dir() { # empty a real directory in place, never following symlinks
is_dir "$1" || return 1
find "$1" -mindepth 1 -xdev -delete
}
# act_on <action> <path> <line> -> 0 when the action succeeded.
# remove: prints the stored line (`plan remove <path>`) then `removed <path>`.
# empty: prints the stored line (`plan empty <path>`) and nothing else — the
# directory survives, so the plan line IS the outcome.
act_on() { # act_on <action> <path> <line> -> 0 when the action succeeded
if [ "$1" = remove ]; then
if remove_tree "$2" 2>/dev/null; then
emit "$3"
emit "removed $2"
return 0
fi
printf '%s: failed to remove %s\n' 'purge-agenttrash' "$2" >&2
return 1
fi
if empty_dir "$2" 2>/dev/null; then
emit "$3"
return 0
fi
printf '%s: failed to empty %s\n' 'purge-agenttrash' "$2" >&2
return 1
}
# Walk the plan table once: plan mode prints every row's line verbatim, execute
# mode acts on each row and counts failures separately from plan-time refusals.
walk_plan() {
while read -r action path line; do
[ -n "$action" ] || continue
if [ "$MODE" = plan ]; then
emit "$line"
elif act_on "$action" "$path" "$line"; then
REMOVED=$((REMOVED + 1))
else
EXEC_REFUSED=$((EXEC_REFUSED + 1))
fi
done <"$PLANFILE"
}
# ----------------------------------------------------------------------- main
[ -n "$ROOTS" ] || ROOTS=$HOME
acquire_lock
for root in $ROOTS; do
collect_root "$root" || true
done
REMOVED=0
EXEC_REFUSED=0
walk_plan
STATUS=0
[ "$REFUSED" -eq 0 ] || STATUS=1
[ "$EXEC_REFUSED" -eq 0 ] || STATUS=1
printf 'removed=%s deleted=%s refused=%s\n' \
"$CANDIDATES" "$REMOVED" "$((REFUSED + EXEC_REFUSED))"
if ! printf '%s\tplan\tcandidates=%s\tremoved=%s\trefused=%s\n' \
"$(date -u '+%Y-%m-%dT%H:%M:%SZ')" "$CANDIDATES" "$REMOVED" \
"$((REFUSED + EXEC_REFUSED))" >>"$LOGPATH" 2>/dev/null; then
printf '%s: cannot write log %s\n' 'purge-agenttrash' "$LOGPATH" >&2
STATUS=1
fi
exit "$STATUS"
@@ -107,6 +107,23 @@ curl -s -o /dev/null -w '%{http_code}\n' http://127.0.0.1:7770/health
Report: staged items and sizes, what was kept and why, what is **still not
freed**, remaining candidates untouched. Purge only on explicit approval.
## 6. The approved purge — `scripts/purge-agenttrash.sh`
Approval turns into one command. The script is POSIX sh, dry-run by default,
and touches nothing but `.AGENTTRASH` directories: pass it the machine's roots
(default `$HOME`) and it prints the plan; `--execute` performs it and appends
one dated line to `$HOME/.AGENTTRASH-cleanup.log`. A repo's (or the home's own)
`.AGENTTRASH` is **emptied in place** — the directory is the convention and
survives — while `.AGENTTRASH` trees that are themselves inside staged content
are removed with their parent. Foreign-owned and symlinked candidates are
refused, never followed; `rm -rf` deletes symlink entries but never traverses
them, so a planted world-writable trash cannot reach outside itself.
```sh
scripts/purge-agenttrash.sh /home/dank # plan
scripts/purge-agenttrash.sh --execute /home/dank # purge
```
## Repo-flow gotcha: do NOT push before you deploy
`scripts/deploy.sh` sets `BASE_REVISION=$(git rev-parse origin/main)`. If you
+166
View File
@@ -0,0 +1,166 @@
#!/usr/bin/env bats
# Seam: the agent-trash purger.
# Behavioural contract only — what an operator sees (stdout plan lines, summary
# line, exit code, the log line it leaves behind) and what survives on disk.
# The script's own choice of implementation is not asserted.
load helpers
PURGE_SH="$REPO_ROOT/scripts/purge-agenttrash.sh"
# fixture_home <dir> — a fake $HOME holding every shape the purger must handle:
# the home's own .AGENTTRASH (the dated-staging convention, emptied in place)
# and one populated plus one flat per-repo .AGENTTRASH (removed outright), plus
# a file that is not trash and must survive.
fixture_home() {
mkdir -p "$1/repo-a/.AGENTTRASH/bucket" "$1/repo-b/.AGENTTRASH" \
"$1/.AGENTTRASH/20260101-topic/.AGENTTRASH/inner"
: > "$1/repo-a/.AGENTTRASH/bucket/staged.txt"
: > "$1/repo-b/.AGENTTRASH/loose.md"
: > "$1/.AGENTTRASH/20260101-topic/staged.txt"
: > "$1/.AGENTTRASH/20260101-topic/.AGENTTRASH/inner/deep.txt"
: > "$1/repo-a/keep.txt"
}
@test "script exists and is executable" {
[ -f "$PURGE_SH" ]
[ -x "$PURGE_SH" ]
}
@test "usage lists every documented option" {
run "$PURGE_SH" --help
[ "$status" -eq 0 ]
printf '%s\n' "$output" | grep -q -- '--execute'
printf '%s\n' "$output" | grep -q -- '--quiet'
printf '%s\n' "$output" | grep -q -- '--log'
}
@test "unknown option is a usage error" {
run bash -c 'export HOME="${1:?}"; mkdir -p "$HOME"; exec "$2" --bogus' \
_ "$BATS_TEST_TMPDIR" "$PURGE_SH"
[ "$status" -eq 2 ]
}
@test "every positional root is honoured, not just the first" {
home="$BATS_TEST_TMPDIR/home"
mkdir -p "$home"
mkdir -p "$home/x1/.AGENTTRASH" "$home/x2/.AGENTTRASH"
: > "$home/x1/.AGENTTRASH/a"
: > "$home/x2/.AGENTTRASH/b"
run "$PURGE_SH" --log "$home/purge.log" "$home/x1" "$home/x2"
[ "$status" -eq 0 ]
printf '%s\n' "$output" | grep -qxF "plan empty $home/x1/.AGENTTRASH"
printf '%s\n' "$output" | grep -qxF "plan empty $home/x2/.AGENTTRASH"
printf '%s\n' "$output" | grep -qxF 'removed=2 deleted=0 refused=0'
}
@test "--log=FORM accepts its value in one word" {
home="$BATS_TEST_TMPDIR/home"
mkdir -p "$home"
run "$PURGE_SH" "--log=$home/purge.log" "$home"
[ "$status" -eq 0 ]
[ -f "$home/purge.log" ]
}
@test "dry run plans the removals and deletes nothing" {
home="$BATS_TEST_TMPDIR/home"
fixture_home "$home"
run "$PURGE_SH" --log "$home/purge.log" "$home"
[ "$status" -eq 0 ]
printf '%s\n' "$output" | grep -qxF "plan empty $home/.AGENTTRASH"
printf '%s\n' "$output" | grep -qxF "plan remove $home/repo-a/.AGENTTRASH"
printf '%s\n' "$output" | grep -qxF "plan remove $home/repo-b/.AGENTTRASH"
[ "$(printf '%s\n' "$output" | grep -c '^plan ')" -eq 3 ]
printf '%s\n' "$output" | grep -qxF 'removed=3 deleted=0 refused=0'
[ -f "$home/repo-a/.AGENTTRASH/bucket/staged.txt" ]
[ -f "$home/repo-b/.AGENTTRASH/loose.md" ]
[ -f "$home/.AGENTTRASH/20260101-topic/staged.txt" ]
[ -f "$home/purge.log" ]
}
@test "execute empties the home trash and removes the repo trees" {
home="$BATS_TEST_TMPDIR/home"
fixture_home "$home"
run "$PURGE_SH" --execute --log "$home/purge.log" "$home"
[ "$status" -eq 0 ]
printf '%s\n' "$output" | grep -qxF 'removed=3 deleted=3 refused=0'
# the home trash is emptied in place: it survives, empty — including a
# .AGENTTRASH nested inside it, which dies with its parent.
printf '%s\n' "$output" | grep -qxF "plan empty $home/.AGENTTRASH"
[ -d "$home/.AGENTTRASH" ]
[ -z "$(ls -A "$home/.AGENTTRASH")" ]
# the per-repo trees are removed outright, their content is what was staged.
printf '%s\n' "$output" | grep -qxF "plan remove $home/repo-a/.AGENTTRASH"
printf '%s\n' "$output" | grep -qxF "removed $home/repo-a/.AGENTTRASH"
printf '%s\n' "$output" | grep -qxF "removed $home/repo-b/.AGENTTRASH"
[ ! -e "$home/repo-a/.AGENTTRASH" ]
[ ! -e "$home/repo-b/.AGENTTRASH" ]
[ -f "$home/repo-a/keep.txt" ]
[ -f "$home/purge.log" ]
}
@test "execute leaves no lock behind" {
home="$BATS_TEST_TMPDIR/home"
fixture_home "$home"
"$PURGE_SH" --quiet --execute --log "$home/purge.log" "$home"
[ ! -e "$home/.agenttrash-purge.lock" ]
}
@test "the second execute is a no-op with an empty plan" {
home="$BATS_TEST_TMPDIR/home"
fixture_home "$home"
"$PURGE_SH" --quiet --execute --log "$home/purge.log" "$home"
run "$PURGE_SH" --execute --log "$home/purge.log" "$home"
[ "$status" -eq 0 ]
}
@test "a symlinked trash dir is refused, its target survives" {
home="$BATS_TEST_TMPDIR/home"
mkdir -p "$home/live"
: > "$home/live/keep.txt"
ln -s "$home/live" "$home/.AGENTTRASH"
run "$PURGE_SH" --execute --log "$home/purge.log" "$home"
[ "$status" -eq 1 ]
printf '%s\n' "$output" | grep -q 'refused'
[ -L "$home/.AGENTTRASH" ]
[ -f "$home/live/keep.txt" ]
}
@test "/ is refused as a root without touching anything else" {
home="$BATS_TEST_TMPDIR/home"
fixture_home "$home"
run "$PURGE_SH" --execute --log "$home/purge.log" / "$home"
[ "$status" -eq 1 ]
printf '%s\n' "$output" | grep -qxF 'purge-agenttrash: refused / (root refusal)'
printf '%s\n' "$output" | grep -qxF 'removed=3 deleted=3 refused=1'
}
@test "a root that is a plain file is refused and exits nonzero" {
home="$BATS_TEST_TMPDIR/home"
mkdir -p "$home"
: > "$home/stray.txt"
run "$PURGE_SH" --execute --log "$home/purge.log" "$home/stray.txt"
[ "$status" -eq 1 ]
printf '%s\n' "$output" | grep -qxF "purge-agenttrash: refused $home/stray.txt (not a real directory)"
printf '%s\n' "$output" | grep -qxF 'removed=0 deleted=0 refused=1'
[ -f "$home/stray.txt" ]
}
@test "a root path containing whitespace is a usage error" {
home="$BATS_TEST_TMPDIR/home"
mkdir -p "$home"
run "$PURGE_SH" --log "$home/purge.log" "my project"
[ "$status" -eq 2 ]
printf '%s\n' "$output" | grep -q 'root contains whitespace'
}
@test "an empty trash dir yields an empty plan and an empty log line" {
home="$BATS_TEST_TMPDIR/home"
mkdir -p "$home"
run "$PURGE_SH" --log "$home/purge.log" "$home"
[ "$status" -eq 0 ]
printf '%s\n' "$output" | grep -qxF 'removed=0 deleted=0 refused=0'
printf '%s\n' "$output" >"$BATS_TEST_TMPDIR/out.txt"
[ "$(grep -c . "$BATS_TEST_TMPDIR/out.txt")" -eq 1 ]
grep -q 'plan' "$home/purge.log"
}