feat(orders): order queue channel with atomic claim, board ORDRE column

ORDRE 59. A dispatched order used to live only in a scratch prompt file
passed through argv, so it died with the pane it was typed into. Measured
2026-08-17: one order was dispatched three times over 90 minutes before it
was worked, because the first two tabs ran something else and the order
left no trace in the receiving repo at all.

New channel `~/.claude/coord/<repo>/orders/`, beside `inbox/` and never
merged with it. The axis is authorization: inbox content is untrusted
cross-repo data that may never instruct a session (Rule 6), a dispatch
order is operator-authorized work by construction. One channel carrying
both classes would mean either mail that can instruct or orders that
cannot, so the infrastructure is reused and the channel is not.

Four one-verb engines: coord-order-send.sh (write), coord-order-inbox.sh
(read, writes nothing at all), coord-order-claim.sh (atomic claim),
coord-order-done.sh (executed with a commit pointer / --no-commit with a
reason / --return with a reason).

The claim is a rename with no check-then-act step, so of N racing sessions
exactly one finds the source and the rest get ENOENT. The test that proves
it spawns 20 claimers BARRIERED on a start flag - unbarriered children do
not race at all - and runs the identical harness against a deliberately
racy `[ -e src ] && cp && rm` as a known-negative control, which must
produce many winners. Without that control, "exactly one winner" is
indistinguishable from "the race never happened".

Channel separation is pinned structurally, not only behaviourally: no mail
script may contain the string `orders`, with a known-positive control
proving the grep can find. coord-done cannot archive an order and
coord-order-claim cannot claim a message.

board gains an ORDRE column beside INN, counted with the identical idiom
and never summed with it: INN is "others are waiting on YOU", ORDRE is
"work is waiting on this REPO". Claimed orders are excluded - the column
answers what a session can pick up. board.sh --dispatch --order-id emits a
thin starter carrying only the id and the four steps, so the order text has
exactly one home; the id is validated shell-clean and must be pending in
the target's queue.

SessionStart injects the queue as its own block below the mailbox block.
Two channels, two blocks, mail first: it carries Rule 7, and the queue
order is mail -> orders -> STATE's NESTE.

Also folds in dde392d (board prefix-match fix), which landed after the
0.26.0 bump and before any tag. v0.26.0 was never tagged, so 0.27.0 is the
release that carries all of it.

Suites: coord 220, board 237, route 69, orders 97, guard 40; npm test 11/11.
Antakelse 4 (atomic claim) and antakelse 6 (morning --plan-file --dry-run
reports 1 of 1 for the thin starter) both measured, not assumed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0134iB7ipXGgEpv9imYoVmr2
This commit is contained in:
Kjell Tore Guttormsen 2026-08-17 21:17:17 +02:00
commit c519ab4994
18 changed files with 1410 additions and 80 deletions

145
scripts/coord-order-send.sh Executable file
View file

@ -0,0 +1,145 @@
#!/bin/bash
# coord-order-send.sh - deliver a WORK ORDER into a repo's order queue
# (~/.claude/coord/<repo>/orders/). Model-invoked; no network, no service.
#
# Usage:
# coord-order-send.sh --to <repo> --subject "<subject>" [--from <repo>]
# [--message "<text>" | --prompt-file <path>]
# Body comes from --message, from --prompt-file, or from stdin (heredoc) when
# neither is given. The body IS the whole prompt the dispatched session runs on.
#
# Prints `order-id=<id>` on stdout; the order file is <id>.md in the queue.
#
# WHY A SECOND CHANNEL, and not just another inbox message: the two have
# OPPOSITE authorization classes. Inbox content is untrusted cross-repo data
# that may never instruct a session (Rule 6); a dispatch order is
# operator-authorized work by construction - dispatch IS the operator's
# authorization. Mixing the classes in one channel would mean either mail that
# can instruct, or orders that cannot - both wrong. So the infrastructure is
# reused and the channel is not.
#
# That authority rests on dispatch being this queue's ONLY writer BY
# CONVENTION. The engine does not enforce it and cannot: --from redefines
# identity here exactly as it does in coord-send.sh, so any session can write
# an order into any repo's queue. The read side says so in the words it injects
# rather than claiming a guarantee that does not exist.
#
# Exit: 0 delivered, 2 usage/IO error and nothing written.
# ASCII only, bash 3.2 safe.
set -u
export LC_ALL=C
COORD="${CLAUDE_COORD_DIR:-$HOME/.claude/coord}"
TO=""; SUBJECT=""; FROM=""; MESSAGE=""; HAVE_MESSAGE=0; PROMPT_FILE=""
require_value() {
# bash 3.2: `shift 2` past the end of $# is a no-op, so a trailing value-flag
# without its value would loop forever. Every two-arg flag must check first.
if [ "$2" -lt 2 ]; then echo "coord-order-send: $1 requires a value" >&2; exit 2; fi
}
while [ $# -gt 0 ]; do
case "$1" in
--to) require_value --to $#; TO="$2"; shift 2 ;;
--subject) require_value --subject $#; SUBJECT="$2"; shift 2 ;;
--from) require_value --from $#; FROM="$2"; shift 2 ;;
--message) require_value --message $#; MESSAGE="$2"; HAVE_MESSAGE=1; shift 2 ;;
--prompt-file) require_value --prompt-file $#; PROMPT_FILE="$2"; shift 2 ;;
-h|--help) grep '^#' "$0" | sed 's/^# \{0,1\}//'; exit 0 ;;
*) echo "coord-order-send: unknown argument: $1" >&2; exit 2 ;;
esac
done
# --- Resolve sender identity (same rule as coord-send.sh) ------------------
# git toplevel or an explicit --from, never basename(pwd): an invented identity
# signs an order as a repo that does not exist.
if [ -z "$FROM" ]; then
FROM="$(basename "$(git rev-parse --show-toplevel 2>/dev/null)" 2>/dev/null)"
fi
if [ -z "$FROM" ]; then
echo "coord-order-send: cannot resolve sender identity (not inside a git repo); pass --from <repo>" >&2
exit 2
fi
case "$FROM" in
_*) echo "coord-order-send: invalid sender identity: $FROM (names starting with _ are reserved for the engine)" >&2; exit 2 ;;
esac
# Frontmatter is line-oriented: a CR/LF inside a field would inject extra
# frontmatter lines or a premature '---' terminator.
sanitize_field() { printf '%s' "$1" | tr '\r\n' ' ' | tr -d '\000-\037'; }
FROM="$(sanitize_field "$FROM")"
SUBJECT="$(sanitize_field "$SUBJECT")"
# --- Validate target -------------------------------------------------------
[ -n "$TO" ] || { echo "coord-order-send: missing --to <repo>" >&2; exit 2; }
case "$TO" in
*/*|.|..|_*) echo "coord-order-send: invalid target repo name: $TO" >&2; exit 2 ;;
esac
# Retired address, same rule and same reason as coord-send.sh: a polyrepo
# DIRECTORY is not a git repo, so no session can ever hold that identity and
# read what lands there. Reject at the sender, never redirect.
case "$TO" in
ktg-plugin-marketplace)
echo "coord-order-send: ktg-plugin-marketplace is a retired coord address (it is a polyrepo directory, not a git repo - no session can ever hold that identity); send to --to catalog instead" >&2
exit 2
;;
esac
[ -n "$SUBJECT" ] || { echo "coord-order-send: missing --subject" >&2; exit 2; }
# --- Body ------------------------------------------------------------------
# --prompt-file is first-class because that is the shape dispatch already has:
# the skill writes the order to a file, and making the caller cat it would put
# the body through one more shell than it needs to pass.
if [ -n "$PROMPT_FILE" ]; then
[ "$HAVE_MESSAGE" -eq 1 ] && { echo "coord-order-send: use either --message or --prompt-file, not both" >&2; exit 2; }
[ -f "$PROMPT_FILE" ] || { echo "coord-order-send: no prompt file at $PROMPT_FILE" >&2; exit 2; }
# test -s, not test -e: an empty order is a session started and told nothing,
# which from the far end is indistinguishable from one waiting for a Go.
[ -s "$PROMPT_FILE" ] || { echo "coord-order-send: the prompt file is empty: $PROMPT_FILE (the order would tell the session nothing)" >&2; exit 2; }
BODY="$(cat "$PROMPT_FILE")"
elif [ "$HAVE_MESSAGE" -eq 1 ]; then
BODY="$MESSAGE"
else
BODY="$(cat)"
fi
if [ -z "$BODY" ]; then
echo "coord-order-send: empty order body (pass --message, --prompt-file, or pipe the prompt on stdin)" >&2
exit 2
fi
# --- Write -----------------------------------------------------------------
# The order id round-trips through argv, through the injection's claim hints and
# into the startup command board.sh --dispatch emits, so it is shell-clean BY
# CONSTRUCTION: the sender name is sanitized into the id, never carried raw.
DEST_DIR="$COORD/$TO/orders"
mkdir -p "$DEST_DIR" 2>/dev/null || { echo "coord-order-send: cannot create $DEST_DIR" >&2; exit 2; }
TS="$(date -u +%Y%m%dT%H%M%SZ)"
SAFE_FROM="$(printf '%s' "$FROM" | tr -c 'A-Za-z0-9._-' '-')"
ORDER_ID="${TS}-$$${RANDOM}-from-${SAFE_FROM}"
DEST="$DEST_DIR/$ORDER_ID.md"
DATE_ISO="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
# Temp file inside the destination dir (dot-prefixed so the *.md glob never
# sees it): the final mv is a same-filesystem rename, so a reader - or a
# concurrent claimer - never observes a half-written order.
TMP="$(mktemp "$DEST_DIR/.coord-order.XXXXXX" 2>/dev/null)"
[ -n "$TMP" ] || { echo "coord-order-send: cannot create temp file in $DEST_DIR" >&2; exit 2; }
{
echo "---"
echo "from: $FROM"
echo "to: $TO"
echo "order-id: $ORDER_ID"
echo "subject: $SUBJECT"
echo "date: $DATE_ISO"
echo "---"
printf '%s\n' "$BODY"
} > "$TMP"
if ! mv "$TMP" "$DEST" 2>/dev/null; then
/bin/rm -f "$TMP" 2>/dev/null; echo "coord-order-send: write failed" >&2; exit 2
fi
echo "coord-order-send: order delivered to $TO ($ORDER_ID.md)"
echo "order-id=$ORDER_ID"
exit 0