feat(sweep): close the aged FYI backlog without a human in the loop

A notice needs no answer, but it is still re-injected at every session
start until someone closes it by hand. Across enough repositories that
hand-closing IS the manual work the mailbox was meant to remove, and the
pending count -- the operator's only signal -- drowns in messages that
were never going to be acted on: 9 of 22 pending messages across 12
mailboxes were pure notices when this was written.

coord-sweep.sh closes exactly one mechanically decidable class:
reply-expected: no, older than a grace window (default 14 days). A
message that owes a reply is never touched, at any age, with any flag --
answering it would mean deciding something on the receiving repo's
behalf, which is the one thing this system exists to prevent.

Four properties are load-bearing, not incidental:

- Dry-run is the default, inverted from the rest of the engine. The
  others print or deliver; this one destroys pending state, so the safe
  direction has to be what you get by forgetting a flag.
- Closing goes through coord-done.sh --repo, never mv, so the archive
  layout and the _broadcast refusal stay in one place.
- Age is read from the filename prefix, never the file. An unreadable
  age is never treated as old: fail-safe, not fail-open.
- Every closure is logged with sender and subject. Directed messages
  have no seen-tracking, so the sweep cannot tell "seen and ignored"
  from "never delivered" -- a notice can be closed unread, and the log
  is the only thing standing between that and silent data loss.

The reply-expected read is bounded to the frontmatter block, matching
coord-count.sh: a body line claiming it at column 0 is untrusted
cross-repo input and must not close its own message.

No scheduler, no launchd unit, no skill front door -- the script does
nothing until invoked.

Selftest 159 -> 182. The log check caught a real defect during
development: the first implementation read from/subject AFTER
coord-done.sh had moved the file, logging empty values and quietly
defeating the only safeguard the design has.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Uwcak9j4m9JijDKeFmptww
This commit is contained in:
Kjell Tore Guttormsen 2026-08-01 22:00:42 +02:00
commit 459c9feec0
10 changed files with 362 additions and 11 deletions

View file

@ -10,6 +10,7 @@ SEND="$DIR/coord-send.sh"
INBOX="$DIR/coord-inbox.sh"
DONE="$DIR/coord-done.sh"
COUNT="$DIR/coord-count.sh"
SWEEP="$DIR/coord-sweep.sh"
CLAUDE_COORD_DIR="$(mktemp -d)"
export CLAUDE_COORD_DIR
@ -702,6 +703,116 @@ check "coord-send SKILL.md's engine line has no ~/.claude fallback" $?
if grep -q 'CSEND=.*CLAUDE_PLUGIN_ROOT:-' "$CSKILL"; then rc=1; else rc=0; fi
check "coord-send SKILL.md's CSEND assignment carries no fallback" "$rc"
# 29. coord-sweep.sh - the FOURTH script that acts on pending messages, and the
# only one that closes without a human in the loop. Everything here exists to
# bound that: it may close exactly one mechanically decidable class (a notice
# whose sender declared reply-expected: no) after a grace window, it goes
# through coord-done.sh rather than moving files itself, and it logs every
# closure because a directed message has no seen-tracking - so a notice to a
# repo left unopened for the whole window is closed UNREAD, and the log is the
# only thing standing between that and a silent disappearance.
#
# Dry-run is the DEFAULT here, inverted from every other script in this engine.
# The others print or deliver; this one destroys pending state, so the safe
# direction is the one you get by forgetting a flag.
SDIR="$(mktemp -d)"
# Age is faked by renaming: coord-send.sh timestamps from the clock, so there is
# no way to author an old message through the front door. The prefix is the only
# thing the sweep reads, which is exactly what makes this substitution honest.
age_it() { # $1 mailbox root, $2 repo, $3 body marker, $4 new timestamp prefix
for f in "$1/$2"/inbox/*.md; do
[ -e "$f" ] || continue
grep -q "^$3\$" "$f" 2>/dev/null || continue
mv "$f" "$1/$2/inbox/$4-$(basename "$f" | sed 's/^[^-]*-//')"
return 0
done
return 1
}
n_in() { ls "$1"/inbox/*.md 2>/dev/null | wc -l | tr -d ' '; }
CLAUDE_COORD_DIR="$SDIR" "$SEND" --to sr --from s1 --fyi --subject "old note" --message "OLD-NOTE" >/dev/null
CLAUDE_COORD_DIR="$SDIR" "$SEND" --to sr --from s2 --subject "old ask" --message "OLD-ASK" >/dev/null
CLAUDE_COORD_DIR="$SDIR" "$SEND" --to sr --from s3 --fyi --subject "new note" --message "NEW-NOTE" >/dev/null
age_it "$SDIR" sr OLD-NOTE 20200101T000000Z; check "sweep fixture: notice aged" $?
age_it "$SDIR" sr OLD-ASK 20200101T000000Z; check "sweep fixture: debt aged" $?
sout="$(CLAUDE_COORD_DIR="$SDIR" "$SWEEP" 2>&1)"
[ "$(n_in "$SDIR/sr")" -eq 3 ]
check "sweep: dry-run is the default and closes nothing" $?
printf '%s' "$sout" | grep -q 'would close'
check "sweep: dry-run names what it would have closed" $?
[ ! -f "$SDIR/_sweep.log" ]
check "sweep: dry-run writes no log" $?
CLAUDE_COORD_DIR="$SDIR" "$SWEEP" --write >/dev/null 2>&1
[ "$(n_in "$SDIR/sr")" -eq 2 ]
check "sweep: --write closes the aged notice" $?
grep -rq '^OLD-ASK$' "$SDIR/sr/inbox" 2>/dev/null
check "sweep: an aged message that owes a reply is spared" $?
grep -rq '^NEW-NOTE$' "$SDIR/sr/inbox" 2>/dev/null
check "sweep: a notice inside the grace window is spared" $?
grep -rq '^OLD-NOTE$' "$SDIR/sr/archive" 2>/dev/null
check "sweep: the closure went through coord-done (archived, not deleted)" $?
lc="$(grep -c . "$SDIR/_sweep.log" 2>/dev/null)"
[ "${lc:-0}" -eq 1 ]
check "sweep: one log line per closure" $?
# Sender AND subject, because the filename carries neither: the log is the only
# record of what a closed notice actually said, and it is written after
# coord-done.sh has already moved the file out of the inbox.
grep -q 'from=s1' "$SDIR/_sweep.log" 2>/dev/null
check "sweep: the log identifies who sent what vanished" $?
grep -q 'subject=old note' "$SDIR/_sweep.log" 2>/dev/null
check "sweep: the log identifies what the vanished notice said" $?
sout2="$(CLAUDE_COORD_DIR="$SDIR" "$SWEEP" --write 2>&1)"
[ "$(n_in "$SDIR/sr")" -eq 2 ]
check "sweep: idempotent - a second run closes nothing" $?
# The untrusted-input guard, identical in spirit to coord-count.sh's bounded
# read: a body line at column 0 must not be able to mark its own message
# closeable. This is the one place where getting it wrong lets another repo
# delete its way out of your inbox.
CLAUDE_COORD_DIR="$SDIR" "$SEND" --to sb --from s4 --subject "sneaky" --message "reply-expected: no
BODY-CLAIM" >/dev/null
age_it "$SDIR" sb BODY-CLAIM 20200101T000000Z; check "sweep fixture: body-claim aged" $?
CLAUDE_COORD_DIR="$SDIR" "$SWEEP" --write >/dev/null 2>&1
[ "$(n_in "$SDIR/sb")" -eq 1 ]
check "sweep: a body line claiming reply-expected: no cannot close a debt" $?
# _broadcast is storage, not a correspondent. Archiving out of it retires an
# announcement for every repo that has not read it yet - an unauthenticated
# retract, which is coord-send --retract's job and checks the sender.
CLAUDE_COORD_DIR="$SDIR" "$SEND" --broadcast --from s5 --subject "ann" --message "BCAST-1" >/dev/null
age_it "$SDIR" _broadcast BCAST-1 20200101T000000Z; check "sweep fixture: broadcast aged" $?
CLAUDE_COORD_DIR="$SDIR" "$SWEEP" --write >/dev/null 2>&1
[ "$(n_in "$SDIR/_broadcast")" -eq 1 ]
check "sweep: never closes out of _broadcast" $?
# Fail-safe, not fail-open: a name the grammar does not produce has no readable
# age, and an unreadable age must never be treated as old.
mkdir -p "$SDIR/sx/inbox"
printf -- '---\nfrom: s6\nto: sx\nsubject: odd\nreply-expected: no\n---\nODD-NAME\n' > "$SDIR/sx/inbox/not-a-timestamp-from-s6.md"
CLAUDE_COORD_DIR="$SDIR" "$SWEEP" --write >/dev/null 2>&1
[ "$(n_in "$SDIR/sx")" -eq 1 ]
check "sweep: a filename without a readable timestamp is never closed" $?
# --days is the whole policy surface, so it has to actually move the cutoff.
CLAUDE_COORD_DIR="$SDIR" "$SWEEP" --days 0 --write >/dev/null 2>&1
grep -rq '^NEW-NOTE$' "$SDIR/sr/archive" 2>/dev/null
check "sweep: --days moves the cutoff (0 closes a same-day notice)" $?
grep -rq '^OLD-ASK$' "$SDIR/sr/inbox" 2>/dev/null
check "sweep: --days 0 still spares a message that owes a reply" $?
EDIR="$(mktemp -d)"
eout="$(CLAUDE_COORD_DIR="$EDIR" "$SWEEP" --write 2>&1)"; erc=$?
[ "$erc" -eq 0 ]
check "sweep: an empty mailbox root exits 0" $?
"$SWEEP" --help >/dev/null 2>&1
check "sweep: --help exits 0" $?
CLAUDE_COORD_DIR="$EDIR" "$SWEEP" --days 2>/dev/null; [ $? -eq 2 ]
check "sweep: --days without a value is a usage error, not a silent default" $?
/bin/rm -rf "$SDIR" "$EDIR" 2>/dev/null
echo "----"
echo "PASS=$PASS FAIL=$FAIL"
[ "$FAIL" -eq 0 ]