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:
parent
9cb405c2cd
commit
459c9feec0
10 changed files with 362 additions and 11 deletions
|
|
@ -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 ]
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue