fix(coord-send): reply mode claimed "marked handled" without checking

coord-send.sh ran coord-done under `>/dev/null 2>&1` and then printed the
handled claim unconditionally. Measured with a stub coord-done exiting 1:
the original stayed in the inbox, no archive/ was created, and coord-send
still exited 0 saying "marked handled" - a false success in the message
transport itself, which is why every reply had to be verified by hand.

The predicate is deliberately wider than the exit code: coord-done exits 0
when it archives nothing (an unknown name is idempotently fine by its own
contract), so an exit-code-only fix still certifies a message that never
moved. The check is exit 0 AND the original no longer being at
$COORD/$FROM/inbox/$REPLYTO - recomputed rather than reusing $REPLY_ORIG,
which resolves to the inbox OR the archive, so replying to an already
archived original moves nothing and must not warn.

Failure is exit 1, a new status: the reply WAS delivered and re-sending it
would duplicate it, so 2 stays the nothing-was-written status.

Selftest section 34 (14 checks, red first) pins all four cases: coord-done
fails outright, coord-done exits 0 without moving, the real happy path, and
an archive-path reply. coord-selftest 206 -> 220.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ett8uHMDLir6trFaMzrYRu
This commit is contained in:
Kjell Tore Guttormsen 2026-08-16 15:42:13 +02:00
commit 21e2873e21
3 changed files with 130 additions and 4 deletions

View file

@ -16,7 +16,7 @@ marketplace plugin. Three components, one boundary:
`coord-done.sh` archives, `coord-count.sh` counts without delivering,
`coord-sweep.sh` closes the aged FYI backlog machine-wide.
Everything is pinned by `coord-selftest.sh`
(206 checks, throwaway mailbox via `CLAUDE_COORD_DIR`).
(220 checks, throwaway mailbox via `CLAUDE_COORD_DIR`).
**`ktg-plugin-marketplace` is a RETIRED `--to` address (operator decision
2026-08-15), rejected rather than redirected.** It is a polyrepo directory,
@ -30,6 +30,25 @@ marketplace plugin. Three components, one boundary:
mistake is made. Only `--to` is retired, not `--from` - the defect was mail
*arriving* there, never mail claiming to *originate* there.
**`coord-send --reply-to` asserts "marked handled" against GROUND TRUTH, and
the exit code alone is NOT that ground truth.** The line used to print
unconditionally with `coord-done`'s output discarded (`>/dev/null 2>&1`),
so the one line a session relies on to close a reply debt was false at the
moment it was printed — measured with a stub `coord-done` exiting 1: original
still in the inbox, no `archive/`, exit 0, "marked handled". Every reply this
repo sent had to be verified by hand afterwards, which is what a false
success in the TRANSPORT costs. The check is `exit 0` **and** the original no
longer being at `$COORD/$FROM/inbox/$REPLYTO`, because `coord-done` exits 0
when it archives nothing (an unknown name is idempotently fine by its own
contract), so a nonzero-exit test still certifies a message that never moved.
The path is recomputed rather than reusing `$REPLY_ORIG`, which resolves to
the inbox OR the archive — replying to an already-archived original moves
nothing and must not warn. Failure is exit **1**, a new status: the reply WAS
delivered and re-sending would duplicate it, so 2 stays the
nothing-was-written status it has always been. Selftest section 34 pins all
four cases (fails outright / exits 0 without moving / real happy path /
archive-path reply).
**`coord-sweep.sh` is the only path that closes a message with no human in
the loop, and every constraint on it follows from that.** It may close exactly
one mechanically decidable class - `reply-expected: no`, older than the grace
@ -460,7 +479,7 @@ obligations in another repo.
- Zero dependencies everywhere: bash + coreutils in the engine, `node:`
builtins only in hook and tests.
- TDD: no behavior change without a failing selftest check first.
`bash scripts/coord-selftest.sh` must exit 0 (206/206),
`bash scripts/coord-selftest.sh` must exit 0 (220/220),
`bash scripts/board-selftest.sh` must exit 0 (183/183),
`bash scripts/route-selftest.sh` must exit 0 (69/69) and
`bash scripts/state-line-guard-selftest.sh` must exit 0 (23/23).