#!/bin/bash # orders-selftest.sh - prove the ORDER QUEUE end-to-end against a throwaway # mailbox (never touches ~/.claude/coord). Re-run after any edit to # coord-order-send.sh / coord-order-inbox.sh / coord-order-claim.sh / # coord-order-done.sh. ASCII only, bash 3.2 safe. # # The order queue is a SECOND channel beside inbox/, with the opposite # authorization class: mail is untrusted cross-repo data that may never # instruct a session, an order is operator-authorized work delivered by # dispatch. The two must never be able to become each other, so section 4 # pins the separation STRUCTURALLY (no write path exists) and not only # behaviourally (this one send did not cross over). set -u export LC_ALL=C DIR="$(cd "$(dirname "$0")" && pwd)" SEND="$DIR/coord-order-send.sh" READ="$DIR/coord-order-inbox.sh" CLAIM="$DIR/coord-order-claim.sh" ODONE="$DIR/coord-order-done.sh" MSEND="$DIR/coord-send.sh" MDONE="$DIR/coord-done.sh" BOARD="$DIR/board.sh" CLAUDE_COORD_DIR="$(mktemp -d)" export CLAUDE_COORD_DIR WORK="$(mktemp -d)" cleanup() { /bin/rm -rf "$CLAUDE_COORD_DIR" "$WORK" 2>/dev/null; } trap cleanup EXIT PASS=0; FAIL=0; SKIP=0 check() { if [ "$2" -eq 0 ]; then PASS=$((PASS+1)); echo " ok - $1"; else FAIL=$((FAIL+1)); echo " FAIL - $1"; fi; } # A skip is NOT a pass and is never silent: it prints, it is counted, and the # denominator at the bottom names it. Verifiseringsloven face 4 - an absent # measurement must not read as a positive one. skip() { SKIP=$((SKIP+1)); echo " SKIP - $1"; } echo "orders-selftest (mailbox: $CLAUDE_COORD_DIR)" # --- 1. Delivery ----------------------------------------------------------- out1="$("$SEND" --to fake-repo --from dispatcher --subject "order one" --message "do the thing" 2>&1)"; rc=$? [ "$rc" -eq 0 ]; check "order send exits 0" $? oid1="$(printf '%s\n' "$out1" | sed -n 's/^order-id=//p')" [ -n "$oid1" ]; check "order send prints order-id=" $? of1="$CLAUDE_COORD_DIR/fake-repo/orders/$oid1.md" [ -f "$of1" ]; check "order file lands in the recipient's orders/" $? grep -q "^from: dispatcher$" "$of1" 2>/dev/null; check "frontmatter carries from" $? grep -q "^to: fake-repo$" "$of1" 2>/dev/null; check "frontmatter carries to" $? grep -q "^order-id: $oid1$" "$of1" 2>/dev/null; check "frontmatter carries order-id" $? grep -q "^subject: order one$" "$of1" 2>/dev/null; check "frontmatter carries subject" $? grep -q "^date: " "$of1" 2>/dev/null; check "frontmatter carries date" $? grep -q "^do the thing$" "$of1" 2>/dev/null; check "body is the whole prompt" $? # The prompt normally arrives as a FILE (that is what dispatch writes), so the # file path must be a first-class input and not something the caller has to # shell out to cat. printf 'line A\nline B\n' > "$WORK/p.prompt" out1b="$("$SEND" --to fake-repo --from dispatcher --subject "from file" --prompt-file "$WORK/p.prompt" 2>&1)" oid1b="$(printf '%s\n' "$out1b" | sed -n 's/^order-id=//p')" grep -q "^line B$" "$CLAUDE_COORD_DIR/fake-repo/orders/$oid1b.md" 2>/dev/null check "--prompt-file carries the whole file as the body" $? # --- 2. Read side: injection ----------------------------------------------- r2="$("$READ" --repo fake-repo)"; rc=$? [ "$rc" -eq 0 ]; check "order read exits 0" $? printf '%s' "$r2" | grep -q "2 pending"; check "read reports the pending count" $? printf '%s' "$r2" | grep -q "order one"; check "read shows the subject" $? printf '%s' "$r2" | grep -q "dispatcher"; check "read shows the sender" $? printf '%s' "$r2" | grep -q "coord-order-claim $oid1"; check "read gives a per-order claim hint" $? # The order body is deliberately NOT injected: an order can be a full session # prompt, and the queue view has to stay readable at session start. The text # arrives at claim time, from the one place it lives. [ "$(printf '%s' "$r2" | grep -c 'do the thing')" -eq 0 ] check "read does NOT inject the order body (that arrives at claim)" $? # The authorization class is the whole point of the second channel, and it has # to be stated where a session reads it, not only in a doc. printf '%s' "$r2" | grep -q "OPERATOR-AUTHORIZED"; check "read states the order authorization class" $? printf '%s' "$r2" | grep -q "CONVENTION"; check "read states that the writer rule is convention, not enforcement" $? printf '%s' "$r2" | grep -q "NESTE"; check "read carries the D-check against STATE's NESTE" $? # Rule 7's shape, transposed: a pending order may be left, but never silently. printf '%s' "$r2" | grep -q "leaving it pending"; check "read states the procedural duty" $? r2b="$("$READ" --repo fake-repo)" printf '%s' "$r2b" | grep -q "order one" check "pending order re-injected on the next read (survives /clear)" $? [ -f "$of1" ]; check "reading an order does not move it" $? # Silence is reserved for a genuinely empty queue. r2c="$("$READ" --repo nobody)"; rc=$? [ -z "$r2c" ] && [ "$rc" -eq 0 ]; check "empty order queue is a silent no-op" $? # --- 3. Claim -------------------------------------------------------------- c3="$("$CLAIM" --repo fake-repo "$oid1" 2>&1)"; rc=$? [ "$rc" -eq 0 ]; check "claim exits 0" $? printf '%s' "$c3" | grep -q "do the thing"; check "claim prints the full order body" $? printf '%s' "$c3" | grep -q "NESTE"; check "claim instructs the D-check against STATE's NESTE" $? [ ! -e "$of1" ]; check "claimed order leaves the pending queue" $? [ -f "$CLAUDE_COORD_DIR/fake-repo/orders/claimed/$oid1.md" ]; check "claimed order lands in orders/claimed" $? # A second claim of the same order must lose, and must not be mistaken for a # usage error: exit 1 is "you did not get it", exit 2 stays "nothing was even # attempted". "$CLAIM" --repo fake-repo "$oid1" >/dev/null 2>&1; [ $? -eq 1 ] check "re-claiming an already claimed order exits 1" $? # Claimed but abandoned is the one way an order could still evaporate, so the # read side has to keep showing it - with its age - rather than let the queue # read as empty. r3="$("$READ" --repo fake-repo)" printf '%s' "$r3" | grep -q "1 claimed"; check "read reports the claimed count" $? printf '%s' "$r3" | grep -q "CLAIMED"; check "read shows a claimed order as in flight" $? printf '%s' "$r3" | grep -q "coord-order-done $oid1 --return"; check "read gives the return hint for a claimed order" $? # --next takes the oldest pending order, so a session never has to parse the # queue to obey it. c3b="$("$CLAIM" --repo fake-repo --next 2>&1)"; rc=$? [ "$rc" -eq 0 ]; check "claim --next takes the oldest pending order" $? printf '%s' "$c3b" | grep -q "line B"; check "claim --next printed that order's body" $? "$CLAIM" --repo fake-repo --next >/dev/null 2>&1; [ $? -eq 1 ] check "claim --next on an empty queue exits 1" $? # --- 4. Channel separation ------------------------------------------------- # STRUCTURAL first: the claim is "no write path from mail to orders exists", # and a behavioural test only samples one case. sep_hits="$(grep -l 'orders' "$MSEND" "$MDONE" "$DIR/coord-inbox.sh" "$DIR/coord-sweep.sh" "$DIR/coord-count.sh" 2>/dev/null | wc -l | tr -d ' ')" [ "$sep_hits" -eq 0 ]; check "no mail script mentions orders at all (no write path)" $? # Known-positive control for that grep: it must be able to find the string. grep -q 'orders' "$SEND" 2>/dev/null; check "control: the grep CAN find 'orders' (in the order engine)" $? # BEHAVIOURAL, both directions. "$MSEND" --to sep-repo --from someone --subject "just mail" --message "not an order" >/dev/null 2>&1 [ ! -d "$CLAUDE_COORD_DIR/sep-repo/orders" ]; check "a coord message never creates an orders queue" $? "$SEND" --to sep2-repo --from dispatcher --subject "just an order" --message "an order" >/dev/null 2>&1 [ ! -d "$CLAUDE_COORD_DIR/sep2-repo/inbox" ]; check "an order never creates an inbox" $? r4="$("$READ" --repo sep-repo)" [ -z "$r4" ]; check "the order read path shows nothing for a mail-only mailbox" $? # The two done-verbs must not reach across either. mb="$(basename "$(ls "$CLAUDE_COORD_DIR"/sep-repo/inbox/*.md 2>/dev/null | head -1)")" oid4="$(printf '%s\n' "$("$SEND" --to sep-repo --from dispatcher --subject "x" --message "y" 2>&1)" | sed -n 's/^order-id=//p')" "$MDONE" --repo sep-repo "$oid4.md" >/dev/null 2>&1 [ -f "$CLAUDE_COORD_DIR/sep-repo/orders/$oid4.md" ]; check "coord-done cannot archive an order" $? "$CLAIM" --repo sep-repo "$mb" >/dev/null 2>&1; [ $? -ne 0 ] check "coord-order-claim cannot claim a coord message" $? [ -f "$CLAUDE_COORD_DIR/sep-repo/inbox/$mb" ]; check "the coord message is untouched by the order engine" $? # --- 5. Atomic claim (antakelse 4 - the design marks this RISIKO) ---------- # A naive "two claimers, one winner" test does not race at all: the first # finishes before the second starts and the test goes green having proven # nothing. Every claimer is therefore barriered on a start flag, and the whole # harness is validated against a deliberately RACY claim that must produce # more than one winner. Without that control, "exactly one winner" is # indistinguishable from "the race never happened". race_one() { # $1 = label, $2 = claim command as a shell snippet operating on $SRC/$DST rc_dir="$WORK/race-$1" mkdir -p "$rc_dir/ready" "$rc_dir/won" rc_start="$rc_dir/start" rc_n=20 rc_i=1 while [ "$rc_i" -le "$rc_n" ]; do ( : > "$rc_dir/ready/$rc_i" while [ ! -e "$rc_start" ]; do :; done if eval "$2" >/dev/null 2>&1; then : > "$rc_dir/won/$rc_i"; fi ) & rc_i=$((rc_i + 1)) done rc_w=0 while [ "$(ls "$rc_dir/ready" 2>/dev/null | wc -l | tr -d ' ')" -lt "$rc_n" ] && [ "$rc_w" -lt 100 ]; do sleep 0.1; rc_w=$((rc_w + 1)) done : > "$rc_start" wait ls "$rc_dir/won" 2>/dev/null | wc -l | tr -d ' ' } oid5="$(printf '%s\n' "$("$SEND" --to race-repo --from dispatcher --subject "contended" --message "one winner only" 2>&1)" | sed -n 's/^order-id=//p')" [ -n "$oid5" ]; check "race fixture: order delivered" $? winners="$(race_one real "\"$CLAIM\" --repo race-repo $oid5")" [ "$winners" -eq 1 ]; check "20 concurrent claims produce EXACTLY ONE winner (got $winners)" $? [ -f "$CLAUDE_COORD_DIR/race-repo/orders/claimed/$oid5.md" ]; check "the contended order exists in exactly one place after the race" $? [ ! -e "$CLAUDE_COORD_DIR/race-repo/orders/$oid5.md" ]; check "the contended order is gone from pending after the race" $? # Known-negative control: the same harness against a check-then-act claim. # The sleep makes it deterministic rather than merely likely - every child # passes the existence test before any of them acts. SRC="$WORK/racy-src"; DST="$WORK/racy-dst" mkdir -p "$DST"; : > "$SRC" racy_winners="$(race_one control "[ -e \"$SRC\" ] && { sleep 0.3; cp \"$SRC\" \"$DST/\$\$\"; /bin/rm -f \"$SRC\"; }")" [ "$racy_winners" -gt 1 ]; check "control: a check-then-act claim DOES produce multiple winners (got $racy_winners)" $? # --- 6. Terminal states ---------------------------------------------------- # Executed: archived with a result pointer. The commit hash is the pointer, and # it is required - an order that finished with nothing to show for it is either # a --no-commit with a stated why, or a return. "$ODONE" --repo fake-repo "$oid1" --commit deadbee >/dev/null 2>&1; rc=$? [ "$rc" -eq 0 ]; check "order-done --commit exits 0" $? [ -f "$CLAUDE_COORD_DIR/fake-repo/orders/archive/$oid1.md" ]; check "executed order lands in orders/archive" $? [ ! -e "$CLAUDE_COORD_DIR/fake-repo/orders/claimed/$oid1.md" ]; check "executed order leaves orders/claimed" $? grep -q 'order-result: executed' "$CLAUDE_COORD_DIR/fake-repo/orders/archive/$oid1.md" 2>/dev/null check "archived order records the result" $? grep -q 'commit=deadbee' "$CLAUDE_COORD_DIR/fake-repo/orders/archive/$oid1.md" 2>/dev/null check "archived order records the commit pointer" $? [ ! -e "$CLAUDE_COORD_DIR/fake-repo/orders/claimed/$oid1.claim" ]; check "the claim marker is cleared on a terminal state" $? "$ODONE" --repo fake-repo "$oid1" --commit deadbee >/dev/null 2>&1; [ $? -eq 1 ] check "closing an order twice exits 1 (nothing left to close)" $? "$ODONE" --repo fake-repo "$oid1b" --commit x >/dev/null 2>&1 [ -f "$CLAUDE_COORD_DIR/fake-repo/orders/archive/$oid1b.md" ]; check "the --next-claimed order closes too" $? # --no-commit is the honest form of "executed, nothing to commit"; it costs a # stated reason so it cannot become the silent default. oid6="$(printf '%s\n' "$("$SEND" --to nc-repo --from dispatcher --subject "measure" --message "just measure" 2>&1)" | sed -n 's/^order-id=//p')" "$CLAIM" --repo nc-repo "$oid6" >/dev/null 2>&1 "$ODONE" --repo nc-repo "$oid6" --no-commit >/dev/null 2>&1; [ $? -eq 2 ] check "--no-commit without --reason is refused" $? "$ODONE" --repo nc-repo "$oid6" --no-commit --reason "measurement only" >/dev/null 2>&1; [ $? -eq 0 ] check "--no-commit with a reason closes the order" $? grep -q 'commit=none' "$CLAUDE_COORD_DIR/nc-repo/orders/archive/$oid6.md" 2>/dev/null check "a --no-commit close records commit=none" $? # Returned: back to pending, with the reason visible to whoever picks it up. oid7="$(printf '%s\n' "$("$SEND" --to ret-repo --from dispatcher --subject "stale" --message "premise is dead" 2>&1)" | sed -n 's/^order-id=//p')" "$CLAIM" --repo ret-repo "$oid7" >/dev/null 2>&1 "$ODONE" --repo ret-repo "$oid7" --return >/dev/null 2>&1; [ $? -eq 2 ] check "--return without --reason is refused" $? "$ODONE" --repo ret-repo "$oid7" --return --reason "forutsetningen er dod" >/dev/null 2>&1; [ $? -eq 0 ] check "--return with a reason exits 0" $? [ -f "$CLAUDE_COORD_DIR/ret-repo/orders/$oid7.md" ]; check "returned order is pending again" $? [ ! -e "$CLAUDE_COORD_DIR/ret-repo/orders/claimed/$oid7.md" ]; check "returned order left orders/claimed" $? grep -q 'order-returned' "$CLAUDE_COORD_DIR/ret-repo/orders/$oid7.md" 2>/dev/null check "returned order records the return" $? r7="$("$READ" --repo ret-repo)" printf '%s' "$r7" | grep -q "forutsetningen er dod" check "the return reason reaches the next session's injection" $? "$CLAIM" --repo ret-repo "$oid7" >/dev/null 2>&1; [ $? -eq 0 ] check "a returned order can be claimed again" $? # --- 7. Usage guards ------------------------------------------------------- "$SEND" --from x --subject s --message m >/dev/null 2>&1; [ $? -eq 2 ]; check "order send without --to is refused" $? "$SEND" --to x --from y --message m >/dev/null 2>&1; [ $? -eq 2 ]; check "order send without --subject is refused" $? "$SEND" --to x --from y --subject s --message "" >/dev/null 2>&1; [ $? -eq 2 ]; check "empty order body is refused" $? : > "$WORK/empty.prompt" "$SEND" --to x --from y --subject s --prompt-file "$WORK/empty.prompt" >/dev/null 2>&1; [ $? -eq 2 ] check "an empty --prompt-file is refused (a session told nothing)" $? "$SEND" --to x --from y --subject s --prompt-file "$WORK/missing.prompt" >/dev/null 2>&1; [ $? -eq 2 ] check "a missing --prompt-file is refused" $? "$SEND" --to "../evil" --from y --subject s --message m >/dev/null 2>&1; [ $? -eq 2 ] check "path-traversal --to is refused" $? "$SEND" --to _broadcast --from y --subject s --message m >/dev/null 2>&1; [ $? -eq 2 ] check "the reserved _ namespace is refused as an order target" $? "$SEND" --to ktg-plugin-marketplace --from y --subject s --message m >/dev/null 2>&1; [ $? -eq 2 ] check "the retired ktg-plugin-marketplace address is refused" $? "$SEND" --to x --from _engine --subject s --message m >/dev/null 2>&1; [ $? -eq 2 ] check "a reserved sender identity is refused" $? "$CLAIM" --repo x "../evil" >/dev/null 2>&1; [ $? -eq 2 ]; check "path-traversal order id is refused at claim" $? "$CLAIM" --repo _broadcast anything >/dev/null 2>&1; [ $? -eq 2 ]; check "the reserved namespace is refused at claim" $? "$ODONE" --repo x someid >/dev/null 2>&1; [ $? -eq 2 ]; check "order-done without a mode is refused" $? "$ODONE" --repo x someid --commit a --return --reason r >/dev/null 2>&1; [ $? -eq 2 ] check "order-done with two modes is refused" $? # A trailing value-flag with no value must exit, never hang (bash 3.2 shift 2). fast_exit() { "$@" >/dev/null 2>&1 & fe_pid=$! fe_i=0 while [ "$fe_i" -lt 30 ]; do if ! kill -0 "$fe_pid" 2>/dev/null; then wait "$fe_pid" 2>/dev/null; echo "rc=$?"; return 0; fi sleep 0.1; fe_i=$((fe_i + 1)) done kill -9 "$fe_pid" 2>/dev/null; echo "HUNG"; return 0 } [ "$(fast_exit "$SEND" --to)" = "rc=2" ]; check "order send --to with no value exits 2, never hangs" $? [ "$(fast_exit "$CLAIM" --repo)" = "rc=2" ]; check "claim --repo with no value exits 2, never hangs" $? [ "$(fast_exit "$ODONE" --repo)" = "rc=2" ]; check "order-done --repo with no value exits 2, never hangs" $? [ "$(fast_exit "$READ" --repo)" = "rc=2" ]; check "order read --repo with no value exits 2, never hangs" $? # The order id round-trips through argv and into shell-quoted hints, so it must # be shell-clean by construction even when the sender's name is not. oid8="$(printf '%s\n' "$("$SEND" --to odd-repo --from 'we ird/name' --subject s --message m 2>&1)" | sed -n 's/^order-id=//p')" case "$oid8" in *[!A-Za-z0-9._-]*) false ;; *) true ;; esac check "order id is shell-clean even for an odd sender name" $? # A newline in the subject would forge extra frontmatter lines. "$SEND" --to nl-repo --from y --subject "$(printf 'a\nsubject: b')" --message m >/dev/null 2>&1 nlf="$(ls "$CLAUDE_COORD_DIR"/nl-repo/orders/*.md 2>/dev/null | head -1)" [ "$(grep -c '^subject:' "$nlf" 2>/dev/null)" -eq 1 ] check "a newline in the subject cannot inject a second frontmatter line" $? # --- 8. Board integration -------------------------------------------------- # The ORDRE column is a repo-scan property like INN, counted the same way, and # the two are never summed: INN is "others are waiting on you", ORDRE is # "work is waiting on this repo". bt="$WORK/boardroot" mkdir -p "$bt/ordrepo/.git" cat > "$bt/ordrepo/STATE.md" <<'EOF' # STATE ## NESTE Do the planned thing. EOF boid="$("$SEND" --to ordrepo --from dispatcher --subject "board order" --message "b" 2>&1 | sed -n 's/^order-id=//p')" "$SEND" --to ordrepo --from dispatcher --subject "board order 2" --message "b" >/dev/null 2>&1 "$MSEND" --to ordrepo --from someone --subject "board mail" --message "m" >/dev/null 2>&1 bout="$(BOARD_ROOTS="$bt" bash "$BOARD" 2>/dev/null)" printf '%s' "$bout" | grep -q 'ORDRE'; check "board table has an ORDRE column" $? printf '%s' "$bout" | grep -q 'ordrepo'; check "board table lists the fixture repo" $? # One mail, two orders, and neither number absorbed the other. # Matched on the rendered row rather than by awk field number: KOST is # "Sonnet 5/high", which contains a space, so a field index would be counting # the wrong columns and would keep "passing" if the layout shifted. printf '%s' "$bout" | grep -qE '^ordrepo[[:space:]]+planned[[:space:]]+Sonnet 5/high[[:space:]]+1[[:space:]]+2[[:space:]]' check "board prints INN 1 and ORDRE 2 side by side, never summed" $? # --dispatch --order-id: the thin starter form. The order text lives in the # queue; the pasted line only points at it. dout="$(BOARD_ROOTS="$bt" bash "$BOARD" --dispatch --repo ordrepo --order-id "$boid" \ --target-pane yes --path known --verification strong --reversibility cheap --scope local --rationale "smoke" 2>&1)"; rc=$? [ "$rc" -eq 0 ]; check "--dispatch --order-id exits 0" $? printf '%s' "$dout" | grep -q '^paste='; check "--dispatch --order-id emits a paste line" $? printf '%s' "$dout" | grep -q 'coord-order-claim'; check "the starter tells the session to claim the order" $? printf '%s' "$dout" | grep -q 'NESTE'; check "the starter carries the D-check" $? BOARD_ROOTS="$bt" bash "$BOARD" --dispatch --repo ordrepo --order-id 'evil;id' \ --target-pane yes --path known --verification strong --reversibility cheap --scope local --rationale x >/dev/null 2>&1 [ $? -eq 2 ]; check "a non-shell-clean --order-id is refused" $? BOARD_ROOTS="$bt" bash "$BOARD" --dispatch --repo ordrepo --order-id 20990101T000000Z-0-from-nobody \ --target-pane yes --path known --verification strong --reversibility cheap --scope local --rationale x >/dev/null 2>&1 [ $? -eq 2 ]; check "an --order-id with no order in the queue is refused" $? BOARD_ROOTS="$bt" bash "$BOARD" --dispatch --repo ordrepo --order-id someid --prompt-file /etc/hosts \ --target-pane yes --path known --verification strong --reversibility cheap --scope local --rationale x >/dev/null 2>&1 [ $? -eq 2 ]; check "--order-id and --prompt-file together are refused" $? # --- 9. Plan-file starter through the real morning (antakelse 6) ----------- # The design leaves this UNMEASURED and calls it an acceptance test for this # order. It runs against the installed morning; if morning is absent it SKIPS # loudly rather than passing, because an unmeasured assumption that reads as # green is the failure this test exists to prevent. if command -v morning >/dev/null 2>&1; then poid="$("$SEND" --to ordrepo --from dispatcher --subject "plan starter" --message "p" 2>&1 | sed -n 's/^order-id=//p')" planf="$WORK/starter.plan" BOARD_ROOTS="$bt" bash "$BOARD" --dispatch --repo ordrepo --order-id "$poid" \ --target-pane no --path known --verification strong --reversibility cheap \ --scope local --rationale "antakelse 6" > "$planf" 2>/dev/null [ -s "$planf" ]; check "plan-file starter renders" $? mout="$(morning --plan-file "$planf" --dry-run 2>&1)" printf '%s' "$mout" | grep -q '1 of 1' check "morning --plan-file --dry-run reports 1 of 1 for the thin starter" $? else skip "morning not installed - antakelse 6 (plan-file starter) NOT measured" skip "morning not installed - plan-file starter render NOT measured" fi echo echo "orders-selftest: $PASS passed, $FAIL failed, $SKIP skipped (of $((PASS+FAIL+SKIP)) checks)" [ "$FAIL" -eq 0 ] || exit 1 exit 0