fix(orders): a pending order's age comes from the filename, not the mtime

`coord-order-done --return` rewrites the order file's mtime, and both age
surfaces read mtime, so putting an order back reset the very reading that
says how long it has waited. An order returned three times could never look
old - on the one surface that exists precisely so a repo nobody opens still
shows something.

Found by reading the board right after this repo returned an order of its
own, not by looking for it: a file whose name says 2026-09-02 rendered
`ORDRE 1:0d` and `pending, 0d old` minutes later. Verified live after the
fix: the same order now reads 1d.

Two questions, two sources, and only one of them moved. A PENDING order's
age is "how long has this sat with no owner" = now - delivery time, which
only the filename carries and nothing rewrites. A CLAIMED order's age is
"how long has it been in flight", which is the claim's own mtime and was
already right. So oldest_pending_age() sits BESIDE oldest_order_age(), and
pending_age_of() beside age_of() - switching FLY to the filename would
answer the delivery question in the column that asks the flight question.
An unparseable filename yields "?" for the whole reading, never a
fabricated 0, because an unmeasured order could be the oldest one.

TDD, red first: orders-selftest section 11 (110 -> 116) and board-selftest
section 30 (360 -> 368), each asserting its own ground truth before
anything depends on it, with controls that a freshly delivered order still
reads 0d and that FLY did not move. Mutation-verified in both files:
restoring the mtime read turns exactly the defect checks red and leaves
every control green.

Section 28's fixtures were rewritten as part of this rather than
incidentally: they encoded their ages in `touch -t` while their filenames
held fixed 2026-01/2026-08 dates, which a filename-based reading makes both
wrong and time-dependent. They now compute their stems from `date -v` and
the section asserts two ground truths, the filename for ORDRE and the mtime
for FLY.

Order 20260903T185736Z-1290610855 (.claude). Version 0.32.1 across all
seven files; no catalog change in this session.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Kjell Tore Guttormsen 2026-09-03 22:17:26 +02:00
commit 5316688844
13 changed files with 332 additions and 24 deletions

View file

@ -1,6 +1,6 @@
{
"name": "repo-mailbox",
"version": "0.32.0",
"version": "0.32.1",
"description": "Local mailbox for coordination between Claude Code sessions in different repositories. Directed messages and broadcasts as plain Markdown files on your own disk, injected as context at session start. Local, private, no network.",
"author": {
"name": "Kjell Tore Guttormsen"

View file

@ -5,6 +5,45 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [0.32.1] - 2026-09-03
### Fixed
- **A pending order's age is read from the FILENAME, never the mtime.**
`coord-order-done --return` rewrites the order file's mtime, and both age
surfaces read mtime, so returning an order reset the very reading that says
how long it has waited. An order returned three times could never look old.
Found by reading the board immediately after this repo returned an order of
its own: a file whose name says 2026-09-02 rendered `ORDRE 1:0d` and
`pending, 0d old` minutes later. The filename is written once, at delivery,
and nothing rewrites it - which is exactly the fact "how long has this sat
with no owner" is asking about.
Two questions, two sources, and the second one does not move: a CLAIMED
order's age is "how long has it been in flight", which is the claim's own
mtime and was already right. `board.sh` gains `oldest_pending_age()` beside
`oldest_order_age()`; `coord-order-inbox.sh` gains `pending_age_of()` beside
`age_of()`. Switching FLY to the filename would answer the delivery question
in the column that asks the flight question.
An unparseable filename yields `?` for the whole reading, never a fabricated
`0` - the same fail-safe the mtime path already carried, and for the same
reason: an unmeasured order could be the oldest one.
Pinned by `orders-selftest.sh` section 11 (116 checks, up from 110) and
`board-selftest.sh` section 30 (368, up from 360), each with its ground truth
asserted before anything depends on it and with known-positive controls that
a freshly delivered order still reads `0d` and that FLY did not move.
Mutation-verified in both files: restoring the mtime read turns exactly the
defect checks red and leaves every control green.
Section 28's fixtures were rewritten as part of this, not incidentally: they
encoded their intended ages in `touch -t` with fixed 2026-01/2026-08
filenames, which a filename-based reading makes wrong and time-dependent.
They now compute their stems from `date -v`, and the section asserts two
ground truths - the filename for ORDRE, the mtime for FLY - because the two
columns no longer read the same source.
## [0.32.0] - 2026-09-01
Two commits, and the first of them was already written when this release

View file

@ -146,7 +146,7 @@ marketplace plugin. Three components, one boundary:
mailbox, `~/.claude/coord/<repo>/orders/`, with four one-verb scripts —
`coord-order-send.sh` (write), `coord-order-inbox.sh` (read for injection),
`coord-order-claim.sh` (claim), `coord-order-done.sh` (terminal state).
Pinned by `orders-selftest.sh` (110 checks).
Pinned by `orders-selftest.sh` (116 checks).
**It is a separate CHANNEL, not more mail, and the axis is authorization.**
Inbox content is untrusted cross-repo data that may never instruct a session
@ -411,7 +411,7 @@ marketplace plugin. Three components, one boundary:
- **Board (`scripts/board.sh`):** cross-repo attention board. Reads STATE.md
next-step blocks + board lines, `git status`, and mailbox pending counts, and
prints one line per repo. Read-only by construction: it writes to no repo, no
STATE.md and no mailbox. Pinned by `board-selftest.sh` (360 checks).
STATE.md and no mailbox. Pinned by `board-selftest.sh` (368 checks).
**It lives here because the mailbox is one of its three inputs, and it carries
the same axis distinction the mailbox does.** A pending count means *others
@ -1080,6 +1080,40 @@ marketplace plugin. Three components, one boundary:
broken `touch -t` fails there rather than turning the section into a test of
nothing.
**Order 20260903T185736Z-1290610855 (2026-09-03): the PENDING age read the
mtime, and `coord-order-done --return` rewrites it - so putting an order back
reset the very reading that says how long it has waited.** An order returned
three times could never look old, on the one surface that exists precisely so
a repo nobody opens still shows something. Found by reading the board right
after this repo returned an order of its own, not by looking for it: a file
whose name says 2026-09-02 rendered `ORDRE 1:0d` minutes later. It is the
same class as every other reading hardened here - a failed or reset
measurement rendering as a reassuring value - with the twist that the reset
was performed by the engine itself.
**Two questions, two sources, and only one of them moved.** A PENDING order's
age is "how long has this sat with no owner" = now - DELIVERY time, and only
the filename carries that; it is written once and nothing rewrites it. A
CLAIMED order's age is "how long has it been in flight", which is the claim's
own mtime and was already right - so `oldest_pending_age()` sits BESIDE
`oldest_order_age()` rather than replacing it, and `coord-order-inbox.sh`
keeps `age_of` for the claim marker next to a new `pending_age_of`. Switching
FLY to the filename would answer the delivery question in the column that
asks the flight question. An unparseable filename yields `?` for the whole
reading, never a fabricated `0`, because an unmeasured order could be the
oldest one - the same rule the mtime path already carried.
**Section 28's fixtures had to be rewritten, and that is the finding worth
recording.** They encoded their intended ages in `touch -t` while their
filenames held fixed 2026-01/2026-08 dates, which a filename-based reading
makes both wrong and time-dependent - the checks would have started failing
on their own as the wall clock moved past them. They now compute their stems
from `date -v`, and the section asserts TWO ground truths, the filename for
ORDRE and the mtime for FLY, because a fixture check guarding only one source
leaves the other assertion resting on nothing. Mutation-verified in both
files: restoring the mtime read turns exactly the defect checks red with
every control green.
**There is deliberately NO process inspection, and the selftest asserts it
structurally** (no live `pgrep`/`pkill`/`lsof` in `board.sh`, with a
known-positive control proving the grep can find a planted call). Four
@ -1261,9 +1295,9 @@ obligations in another repo.
builtins only in hook and tests.
- TDD: no behavior change without a failing selftest check first.
`bash scripts/coord-selftest.sh` must exit 0 (242/242),
`bash scripts/board-selftest.sh` must exit 0 (360/360),
`bash scripts/board-selftest.sh` must exit 0 (368/368),
`bash scripts/route-selftest.sh` must exit 0 (73/73),
`bash scripts/orders-selftest.sh` must exit 0 (110/110) and
`bash scripts/orders-selftest.sh` must exit 0 (116/116) and
`bash scripts/state-line-guard-selftest.sh` must exit 0 (54/54).
- English for all code, docs, and commit messages (public repo). Norwegian
trigger aliases in the skill description are deliberate.

View file

@ -10,7 +10,7 @@ Session A in repo X leaves a message for repo Y; the next session in repo Y gets
*AI-generated: all code produced by Claude Code through dialog-driven development.*
![Version](https://img.shields.io/badge/version-0.32.0-blue)
![Version](https://img.shields.io/badge/version-0.32.1-blue)
![Hooks](https://img.shields.io/badge/hooks-1-green)
![Skills](https://img.shields.io/badge/skills-4-orange)
![CLI scripts](https://img.shields.io/badge/CLI_scripts-8-blue)

View file

@ -1,6 +1,6 @@
{
"name": "repo-mailbox",
"version": "0.32.0",
"version": "0.32.1",
"private": true,
"type": "module",
"engines": {

View file

@ -1920,9 +1920,14 @@ for i in 1 2 3; do
printf -- '---\nfrom: x\nto: ord-both\nsubject: m%s\n---\nbody\n' "$i" \
> "$CLAUDE_COORD_DIR/ord-both/inbox/2026010${i}T000000Z-${i}-from-x.md"
done
# The PENDING filenames carry TODAY's stamp: since ORDRE 20260903T185736Z the
# ORDRE age is read from the name, so a fixed 2026-01 name would render its real
# delivery age here and this row asserts 2:0d. Claimed fixtures keep their fixed
# names - FLY still reads the mtime.
ORD_TODAY="$(date -u +%Y%m%dT%H%M%SZ)"
for i in 1 2; do
printf -- '---\nfrom: d\nto: ord-both\norder-id: o%s\nsubject: order %s\n---\nbody\n' "$i" "$i" \
> "$CLAUDE_COORD_DIR/ord-both/orders/2026010${i}T000000Z-${i}-from-d.md"
> "$CLAUDE_COORD_DIR/ord-both/orders/${ORD_TODAY}-${i}-from-d.md"
done
# A CLAIMED order is deliberately NOT counted: the column answers "what can a
# session pick up here", and one already in flight cannot be. Counting it would
@ -2192,8 +2197,9 @@ mkfly fly-inflight planned; mkorder fly-inflight claimed 20260101T000000Z-1-fro
mkfly fly-empty planned
# Both states at once, so the two counts are provably read from different dirs.
mkfly fly-mixed planned
mkorder fly-mixed pending 20260102T000000Z-2-from-d
mkorder fly-mixed pending 20260103T000000Z-3-from-d
FLY_TODAY="$(date -u +%Y%m%dT%H%M%SZ)"
mkorder fly-mixed pending "${FLY_TODAY}-2-from-d"
mkorder fly-mixed pending "${FLY_TODAY}-3-from-d"
mkorder fly-mixed claimed 20260104T000000Z-4-from-d
FLY_OUT="$("$BOARD" --roots "$FLY_ROOT" 2>/dev/null)"
@ -2221,7 +2227,7 @@ check "the legend denies that FLY proves a session is alive" $?
# deferred repos were NOT free (2 held a pending order, 1 owed a reply, 1 had
# an uncommitted tree). All four conditions are required.
mkfly fly-free done
mkfly fly-busy-order done; mkorder fly-busy-order pending 20260105T000000Z-5-from-d
mkfly fly-busy-order done; mkorder fly-busy-order pending "${FLY_TODAY}-5-from-d"
mkfly fly-busy-fly done; mkorder fly-busy-fly claimed 20260106T000000Z-6-from-d
mkfly fly-busy-mail done
mkdir -p "$CLAUDE_COORD_DIR/fly-busy-mail/inbox"
@ -2576,9 +2582,17 @@ check "exit3: a nonzero that is not 3 is caught too, and names its own status" $
# problem, and the newest reading is precisely what would hide it. This is not
# a new mechanism in the file - ALDER (STATE.md mtime) and SISTE (last commit)
# already read a clock the same way.
#
# Since ORDRE 20260903T185736Z the PENDING age is read from the FILENAME, so
# these fixtures carry their intended age in the NAME rather than only in a
# `touch -t`. The stems are computed once - the timestamp embeds HHMMSS, so
# recomputing one a second later names a different file. FLY still reads the
# mtime, which is why the claimed fixture keeps its touch.
AGROOT="$(mktemp -d)"
AGCOORD="$(mktemp -d)"
AG_OLD="$(date -v-10d +%Y%m%d%H%M)"
AG_OLD_TS="$(date -u -v-10d +%Y%m%dT%H%M%SZ)"
AG_NEW_TS="$(date -u +%Y%m%dT%H%M%SZ)"
mk_ag_repo() {
mkrepo "$AGROOT/$1"
@ -2599,15 +2613,15 @@ mk_ag_order() { # $1=repo $2=subdir ("orders" or "orders/claimed") $3=file $4=t
# ag-old: one fresh order AND one ten days old. The whole point of the section.
mk_ag_repo ag-old
mk_ag_order ag-old orders 20260829T000000Z-fresh -
mk_ag_order ag-old orders 20260818T000000Z-stale "$AG_OLD"
mk_ag_order ag-old orders "${AG_NEW_TS}-fresh" -
mk_ag_order ag-old orders "${AG_OLD_TS}-stale" "$AG_OLD"
# ag-fresh: KNOWN-POSITIVE CONTROL. Two orders, both fresh. If the assertion
# below could not tell these two repos apart it would pass on anything - this
# is the tree "where the age is wrong" that proves the check can fail.
mk_ag_repo ag-fresh
mk_ag_order ag-fresh orders 20260829T000001Z-a -
mk_ag_order ag-fresh orders 20260829T000002Z-b -
mk_ag_order ag-fresh orders "${AG_NEW_TS}-a" -
mk_ag_order ag-fresh orders "${AG_NEW_TS}-b" -
# ag-none: no orders at all. "0 days old" and "no orders" are two different
# facts and must not render the same - the same reason FLY exists next to ORDRE.
@ -2617,7 +2631,7 @@ mk_ag_repo ag-none
# that most needs this: nothing un-claims an order when the session dies, so a
# claim with no age is the 117-hour claim with its only counter-evidence removed.
mk_ag_repo ag-fly
mk_ag_order ag-fly orders/claimed 20260818T000001Z-inflight "$AG_OLD"
mk_ag_order ag-fly orders/claimed "${AG_OLD_TS}-inflight" "$AG_OLD"
AGOUT="$(CLAUDE_COORD_DIR="$AGCOORD" "$BOARD" --roots "$AGROOT" 2>/dev/null)"
ag_row() { printf '%s' "$AGOUT" | grep "^$1 "; }
@ -2625,9 +2639,15 @@ ag_row() { printf '%s' "$AGOUT" | grep "^$1 "; }
# Ground truth for the fixture itself: the stale file really is ~10 days old.
# Asserted before anything depends on it, so a broken `touch -t` fails here
# rather than silently turning the checks below into a test of nothing.
ag_gt="$(( ( $(date +%s) - $(stat -f %m "$AGCOORD/ag-old/orders/20260818T000000Z-stale.md") ) / 86400 ))"
# Two ground truths, because the two columns now read two different sources.
# ORDRE depends on the FILENAME, FLY on the mtime, and a fixture check that
# guarded only one of them would leave the other assertion resting on nothing.
ag_gt_name="$(( ( $(date -u +%s) - $(date -u -j -f %Y%m%d%H%M%S "$(printf '%s' "$AG_OLD_TS" | tr -dc '0-9')" +%s) ) / 86400 ))"
[ "$ag_gt_name" -eq 10 ]
check "orderage: ground truth - the stale fixture's FILENAME says 10 days" $?
ag_gt="$(( ( $(date +%s) - $(stat -f %m "$AGCOORD/ag-fly/orders/claimed/${AG_OLD_TS}-inflight.md") ) / 86400 ))"
[ "$ag_gt" -eq 10 ]
check "orderage: ground truth - the fixture's stale order really is 10 days old" $?
check "orderage: ground truth - the in-flight fixture's MTIME really is 10 days old" $?
ag_cells() { ag_row "$1" | cut -c89- ; }
[ "$(ag_cells ag-old | awk '{print $1}')" = "2:10d" ]
@ -2940,6 +2960,90 @@ vsum_after="$(find "$VROOT" -type f | sort | wc -l)"
check "voyage: board wrote nothing to the scanned tree (read-only preserved)" $?
/bin/rm -rf "$VROOT" "$VCOORD" 2>/dev/null
# --- 30. ORDRE's pending age comes from the FILENAME, never the mtime -------
# ORDRE 20260903T185736Z-1290610855 (.claude, 2026-09-03). Found by reading the
# board right after this repo returned an order of its own: `--return` rewrites
# the order file's mtime, and ORDRE read mtime, so a 1-day-old order rendered
# `1:0d`. An order returned three times could never look old on the one surface
# that exists so a repo nobody opens still shows something.
#
# PM decision - two questions, two sources. A PENDING order's age is "how long
# has this sat with no owner" = now - DELIVERY time, which only the filename
# carries and nothing rewrites. A CLAIMED order's age is "how long has it been
# in flight", which is the claim's own mtime and is already right.
PAROOT="$(mktemp -d)"
PACOORD="$(mktemp -d)"
pa_ts() { date -u -v-"$1"d +%Y%m%dT%H%M%SZ; }
mk_pa_repo() {
mkrepo "$PAROOT/$1"
{
echo "# STATE - $1"
echo ""
printf '## %s NESTE %s START HER\n' "$HAND" "$EMDASH"
echo "<!-- board: status=in-progress; blocked-on=-; next-cost=Sonnet 5/high -->"
echo "Et steg."
} > "$PAROOT/$1/STATE.md"
}
mk_pa_order() { # $1=repo $2=subdir $3=full filename stem (carries the timestamp)
mkdir -p "$PACOORD/$1/$2"
printf -- '---\nfrom: x\nto: %s\n---\nDo a thing.\n' "$1" > "$PACOORD/$1/$2/$3.md"
}
# The stems are computed ONCE and reused: pa_ts embeds HHMMSS, so recomputing
# it a second later names a different file than the one that was created.
pa_ret_stem="$(pa_ts 3)-1111111111-from-x"
pa_fresh_stem="$(pa_ts 0)-2222222222-from-x"
pa_fly_stem="$(pa_ts 9)-3333333333-from-x"
# pa-returned: filename says 3 days ago, mtime is NOW - exactly what a return
# leaves behind. This is the defect, reproduced in a fixture.
mk_pa_repo pa-returned
mk_pa_order pa-returned orders "$pa_ret_stem"
# pa-fresh: KNOWN-POSITIVE CONTROL. Delivered today, so it must still read 0d -
# without this, an assertion of "3d" would pass against a function that always
# says 3.
mk_pa_repo pa-fresh
mk_pa_order pa-fresh orders "$pa_fresh_stem"
# pa-fly: an OLD filename claimed just now. FLY must stay on the mtime and read
# 0d, or the fix has quietly changed the other question's answer too.
mk_pa_repo pa-fly
mk_pa_order pa-fly orders/claimed "$pa_fly_stem"
PAOUT="$(CLAUDE_COORD_DIR="$PACOORD" "$BOARD" --roots "$PAROOT" 2>/dev/null)"
pa_cells() { printf '%s' "$PAOUT" | grep "^$1 " | cut -c89- ; }
# Ground truth for the fixture first: the mtime really is fresh, so a green
# 30b cannot come from the file secretly being 3 days old on disk.
pa_gt="$(( ( $(date +%s) - $(stat -f %m "$PACOORD/pa-returned/orders/$pa_ret_stem.md") ) / 86400 ))"
[ "$pa_gt" -eq 0 ]
check "pendingage: ground truth - the returned fixture's mtime is TODAY" $?
[ "$(pa_cells pa-returned | awk '{print $1}')" = "1:3d" ]
check "pendingage: ORDRE reads the DELIVERY age from the filename, not the mtime" $?
[ "$(pa_cells pa-returned | awk '{print $1}')" = "1:0d" ]; [ $? -ne 0 ]
check "pendingage: a rewritten mtime can no longer reset a pending order's age" $?
[ "$(pa_cells pa-fresh | awk '{print $1}')" = "1:0d" ]
check "pendingage: control - an order delivered today still reads 0d" $?
[ "$(pa_cells pa-fly | awk '{print $2}')" = "1:0d" ]
check "pendingage: FLY still reads the claim's mtime - the other question is unchanged" $?
[ "$(pa_cells pa-fly | awk '{print $2}')" = "1:9d" ]; [ $? -ne 0 ]
check "pendingage: FLY did NOT switch to the filename with ORDRE" $?
# A name the grammar does not produce has no readable delivery time. "?" is the
# whole reading, never a skipped file: an unmeasured order could be the oldest,
# which is the same rule the mtime path already carried.
mk_pa_repo pa-bad
mk_pa_order pa-bad orders "not-a-timestamp"
PAOUT2="$(CLAUDE_COORD_DIR="$PACOORD" "$BOARD" --roots "$PAROOT" 2>/dev/null)"
[ "$(printf '%s' "$PAOUT2" | grep "^pa-bad " | cut -c89- | awk '{print $1}')" = "1:?" ]
check "pendingage: an unparseable filename reads ?, never a fabricated 0" $?
/bin/rm -rf "$PAROOT" "$PACOORD" 2>/dev/null
echo ""
echo "board-selftest: $PASS passed, $FAIL failed"
[ "$FAIL" -eq 0 ] || exit 1

View file

@ -406,6 +406,49 @@ oldest_order_age() {
}
# The PENDING age is read from the FILENAME's timestamp, never the mtime.
# ORDRE 20260903T185736Z-1290610855: `coord-order-done --return` rewrites the
# order file's mtime, and this column read mtime, so an order returned three
# times could never look old - on the one surface that exists precisely so a
# repo nobody opens still shows something. Measured on the live queue the day
# the order was written: a file whose name said 2026-09-02 rendered `1:0d`.
#
# The filename is written once, at delivery, and nothing rewrites it, which is
# exactly the fact "how long has this sat with no owner" is asking about.
#
# FLY keeps oldest_order_age above, and that is not an oversight: a claimed
# order's age is "how long has it been in flight", which is when the CLAIM
# happened - the mtime - and switching it to the filename would answer the
# delivery question in a column that asks the flight question.
#
# Same three outcomes as oldest_order_age, for the same reasons: "-" empty,
# "?" unmeasured (a name the grammar does not produce has no readable delivery
# time, and an unmeasured file could be the oldest one), "Nd" measured.
oldest_pending_age() {
opa_oldest=""; opa_seen=0; opa_unmeasured=0
for opa_f in "$1"/*.md; do
[ -f "$opa_f" ] || continue
opa_seen=1
opa_ts="$(basename "$opa_f")"; opa_ts="${opa_ts%%-*}"
case "$opa_ts" in
[0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9]T[0-9][0-9][0-9][0-9][0-9][0-9]Z)
opa_n="$(printf '%s' "$opa_ts" | tr -dc '0-9')"
opa_e="$(date -u -j -f %Y%m%d%H%M%S "$opa_n" +%s 2>/dev/null)"
case "$opa_e" in
''|*[!0-9]*) opa_unmeasured=1 ;;
*) if [ -z "$opa_oldest" ] || [ "$opa_e" -lt "$opa_oldest" ]; then
opa_oldest="$opa_e"
fi ;;
esac ;;
*) opa_unmeasured=1 ;;
esac
done
if [ "$opa_seen" -eq 0 ]; then printf -- '-'; return 0; fi
if [ "$opa_unmeasured" -eq 1 ] || [ -z "$opa_oldest" ]; then printf '?'; return 0; fi
printf '%dd' $(( (NOW - opa_oldest) / 86400 ))
}
# --- Voyage project detection (ORDRE 20260831T135934Z-696228720) ------------
# board read STATE lines and knew nothing about Voyage briefs, so a programme
# running Voyage across several repos had no shared surface: nobody could
@ -653,7 +696,7 @@ printf '%s\n' "$REPOS" | while IFS= read -r d; do
# measured 2026-08-23), so a claim without an age is that non-claim with its
# only counter-evidence removed.
ordersage="-"
[ "$orders" -eq 0 ] || ordersage="$(oldest_order_age "$COORD/$name/orders")"
[ "$orders" -eq 0 ] || ordersage="$(oldest_pending_age "$COORD/$name/orders")"
claimedage="-"
[ "$claimed" -eq 0 ] || claimedage="$(oldest_order_age "$COORD/$name/orders/claimed")"

View file

@ -73,6 +73,31 @@ age_of() {
if [ -n "$ao_m" ]; then echo $(( (NOW - ao_m) / 86400 )); else echo "?"; fi
}
# Delivery age in whole days, read from the FILENAME's timestamp and never from
# the mtime. ORDRE 20260903T185736Z-1290610855: `--return` rewrites the order
# file's mtime, so an order returned three times reported as brand new - the
# reading that exists to say "this has sat here a long time" was reset by the
# act of putting it back. The filename is written once, at delivery, and nothing
# rewrites it, which is exactly the fact a PENDING age is asking about.
#
# This is NOT the claimed case. A claim's age is "how long has it been in
# flight", which is the claim marker's mtime - a different question with a
# different right answer, so age_of stays and stays used there.
#
# A name the grammar does not produce has no readable delivery time and yields
# "?" - the same fail-safe age_of already used for an unreadable mtime, never a
# fabricated 0, which would make an unmeasured order look new.
pending_age_of() {
pao_ts="$(basename "$1")"; pao_ts="${pao_ts%%-*}"
case "$pao_ts" in
[0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9]T[0-9][0-9][0-9][0-9][0-9][0-9]Z) ;;
*) echo "?"; return 0 ;;
esac
pao_n="$(printf '%s' "$pao_ts" | tr -dc '0-9')"
pao_e="$(date -u -j -f %Y%m%d%H%M%S "$pao_n" +%s 2>/dev/null)"
if [ -n "$pao_e" ]; then echo $(( (NOW - pao_e) / 86400 )); else echo "?"; fi
}
field_of() {
# Bounded to the frontmatter block: a body line must never be able to forge a
# header field the reader is told to trust.
@ -92,7 +117,7 @@ for f in "$ORDERS"/*.md; do
# next session the same dead premise with no warning that it is dead.
ret="$(grep -m1 '^<!-- order-returned:' "$f" 2>/dev/null | sed -e 's/^<!-- order-returned:[[:space:]]*//' -e 's/[[:space:]]*-->$//')"
OUT="${OUT}
--- order: ${id} (from ${from}, pending, $(age_of "$f")d old) ---
--- order: ${id} (from ${from}, pending, $(pending_age_of "$f")d old) ---
subject: ${subj}"
[ -n "$ret" ] && OUT="${OUT}
returned earlier: ${ret}"

View file

@ -408,6 +408,69 @@ CLAUDE_COORD_DIR="$O10DIR" "$SEND" --to .profile --from tester --subject s --mes
check "10d: control - a dot-prefixed target name still receives its order" $?
/bin/rm -rf "$O10DIR" 2>/dev/null
# --- 11. Pending age comes from the FILENAME, never the mtime ---------------
# ORDRE 20260903T185736Z-1290610855: `--return` rewrites the order file's
# mtime, and both age surfaces read mtime, so an order returned three times can
# never read as old. Measured on the live queue before the order was written: a
# file whose name says 2026-09-02 reported "0d old" minutes after a return.
#
# PM decision, and it is two questions with two answers: a PENDING order's age
# is "how long has this sat with no owner" = now - the DELIVERY time, which only
# the filename carries; a CLAIMED order's age is "how long has it been in
# flight" = the claim marker's mtime, which is already right and stays.
O11DIR="$WORK/o11"; mkdir -p "$O11DIR"
o11_old_ts="$(date -u -v-2d +%Y%m%dT%H%M%SZ 2>/dev/null)"
CLAUDE_COORD_DIR="$O11DIR" "$SEND" --to o11repo --from dispatcher \
--subject "aged order" --message "body" >/dev/null 2>&1
o11_q="$O11DIR/o11repo/orders"
o11_orig="$(ls -1 "$o11_q"/*.md 2>/dev/null | head -1)"
o11_id="$(basename "$o11_orig" .md)"
o11_aged_id="${o11_old_ts}-${o11_id#*-}"
mv "$o11_orig" "$o11_q/$o11_aged_id.md" 2>/dev/null
# Ground truth FIRST, so a broken `date -v` fails here instead of turning the
# whole section into a test of nothing (the F13 section-11 lesson).
[ -n "$o11_old_ts" ] && [ -f "$o11_q/$o11_aged_id.md" ]
check "11a: ground truth - the fixture order's filename timestamp is 2 days old" $?
# Drive the REAL defect: claim it, then return it. The return is what rewrites
# the mtime, so this is the path that produced the live 0d reading.
CLAUDE_COORD_DIR="$O11DIR" "$CLAIM" --repo o11repo "$o11_aged_id" >/dev/null 2>&1
CLAUDE_COORD_DIR="$O11DIR" "$ODONE" --repo o11repo "$o11_aged_id" --return --reason "test" >/dev/null 2>&1
o11_mtime="$(stat -f %m "$o11_q/$o11_aged_id.md" 2>/dev/null)"
o11_now="$(date +%s)"
[ -n "$o11_mtime" ] && [ $(( o11_now - o11_mtime )) -lt 300 ]
check "11b: ground truth - the return really did rewrite the file's mtime to now" $?
o11_out="$(CLAUDE_COORD_DIR="$O11DIR" "$READ" --repo o11repo 2>/dev/null)"
printf '%s' "$o11_out" | grep -q 'pending, 2d old'
check "11c: a returned order reports its DELIVERY age (2d), not 0d" $?
# Known-positive control: the reading must still be able to say 0d, or 11c
# would pass just as well against a function that always prints 2.
CLAUDE_COORD_DIR="$O11DIR" "$SEND" --to o11fresh --from dispatcher \
--subject "fresh order" --message "body" >/dev/null 2>&1
o11_fresh="$(CLAUDE_COORD_DIR="$O11DIR" "$READ" --repo o11fresh 2>/dev/null)"
printf '%s' "$o11_fresh" | grep -q 'pending, 0d old'
check "11d: control - a freshly delivered order still reports 0d" $?
# FLY is the OTHER question and must not move: the claim marker's mtime is when
# the claim happened, and an order with an ancient filename claimed just now has
# been in flight for 0 days.
CLAUDE_COORD_DIR="$O11DIR" "$CLAIM" --repo o11repo "$o11_aged_id" >/dev/null 2>&1
o11_fly="$(CLAUDE_COORD_DIR="$O11DIR" "$READ" --repo o11repo 2>/dev/null)"
printf '%s' "$o11_fly" | grep -q 'CLAIMED 0d ago'
check "11e: FLY age still comes from the claim marker's mtime, not the filename" $?
# A name the grammar does not produce has no readable delivery time. It must
# read "?" - the same fail-safe the mtime path already used, never a fabricated
# 0, which would make an unmeasured order look brand new.
mkdir -p "$O11DIR/o11bad/orders"
printf -- '---\nfrom: x\nsubject: s\n---\nbody\n' > "$O11DIR/o11bad/orders/not-a-timestamp.md"
o11_bad="$(CLAUDE_COORD_DIR="$O11DIR" "$READ" --repo o11bad 2>/dev/null)"
printf '%s' "$o11_bad" | grep -q 'pending, ?d old'
check "11f: an unparseable filename timestamp reads ?, never 0" $?
echo
echo "orders-selftest: $PASS passed, $FAIL failed, $SKIP skipped (of $((PASS+FAIL+SKIP)) checks)"
[ "$FAIL" -eq 0 ] || exit 1

View file

@ -23,7 +23,7 @@ description: >-
user names no repo and no tool — choosing *between* repos is this skill. Not for
"where were we" inside the current repo: that is this repo's own STATE.md,
already injected at session start.
version: "0.32.0"
version: "0.32.1"
---
# board — which repo deserves the next session

View file

@ -15,7 +15,7 @@ description: >-
covers retiring a broadcast that has become wrong or obsolete: "retract that
broadcast", "that announcement is outdated, pull it", "trekk tilbake kringkastingen",
"den broadcasten er utdatert".
version: "0.32.0"
version: "0.32.1"
---
# coord-send — natural-language front door for inter-repo messages

View file

@ -17,7 +17,7 @@ description: >-
skill. Not for choosing WHICH repo deserves the next session (that is `board`),
not for scoring model and effort alone (that is `route`), and not for sending a
message to another repo (that is `coord-send`).
version: "0.32.0"
version: "0.32.1"
---
# dispatch — hand a session a task it can actually start on

View file

@ -14,7 +14,7 @@ description: >-
the operator names no model and no tool — choosing the model for the next
session IS this skill. Not for choosing which REPO gets the next session:
that is the `board` skill.
version: "0.32.0"
version: "0.32.1"
---
# route — what the next session should run with