feat(board): FLY column and free-capacity lines, no process inspection
ORDRE counted pending orders only, so a repo with one order in flight and a repo with no orders at all both printed 0 - the same digit for two opposite facts. Measured 2026-08-23: two panes stood open and idle for 45 hours holding finished orders, with full quota authorised, and no column on the board reported it. FLY counts orders/claimed/ - the same queue in its other state, never summed with ORDRE and never a fourth axis. It does NOT mean a session is alive: nothing un-claims an order when the claiming session dies, and one order on the live mailbox had been claimed for 117 hours. The legend denies the liveness reading in those words, pinned as a check on the legend text. --plan now names free capacity as ledig_antall=N plus one ledig=<repo> (<status>) line per repo with nothing owed, no pending order, nothing in flight, a clean tree, at done or deferred. All four conditions are required: measured on the real tree, 4 of 17 done/deferred repos were not free. Lines, never blocks - the plan's second consumer discards a block with no tab=, so a block would be visible to the operator and invisible to the driver. Process inspection was considered and refused; the selftest asserts the absence of pgrep/pkill/lsof structurally with a known-positive control. The full argument, and the gap left open, are in docs/2026-08-23-free-capacity-investigation.md. board-selftest: 259 -> 281 checks, all green. npm test 11/11. Order 20260823T162951Z-941745020-from-.claude Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Hfbfr8ERC63kuAfWeHYHQb
This commit is contained in:
parent
2377735554
commit
3beef2a603
12 changed files with 532 additions and 25 deletions
128
scripts/board.sh
128
scripts/board.sh
|
|
@ -9,6 +9,21 @@
|
|||
# STATE.md board line - optional machine-readable field (see below)
|
||||
# git status --porcelain - uncommitted risk
|
||||
# git log -1 --format=%ct - when anything last landed (the SISTE column)
|
||||
# ~/.claude/coord/<repo>/orders - PENDING orders (the ORDRE column)
|
||||
# ~/.claude/coord/<repo>/orders/claimed - orders IN FLIGHT (the FLY column).
|
||||
# Same queue, other state - not a fourth axis.
|
||||
# It exists because ORDRE alone printed the same
|
||||
# digit for two opposite facts: one order in
|
||||
# flight and no orders at all both read 0. FLY
|
||||
# never claims a session is ALIVE: nothing
|
||||
# un-claims an order when the session that took
|
||||
# it dies (measured 2026-08-23: one order held
|
||||
# claimed for 117 hours). There is deliberately
|
||||
# NO process inspection here - pgrep/ps/lsof
|
||||
# would measure the operator's machine at one
|
||||
# instant, could not be pinned in a fixture tree,
|
||||
# and would make this script decide a session is
|
||||
# dead, which it cannot know.
|
||||
# ~/.claude/coord/<repo>/inbox - UNHANDLED INBOUND: others addressed this
|
||||
# repo and it has not processed them. This is an
|
||||
# obligation the repo owes outward - NOT evidence
|
||||
|
|
@ -81,6 +96,19 @@
|
|||
# Within a group: the quantity that group is about (released, then owed), then a
|
||||
# Sonnet next-cost (cheap to move under quota pressure), then oldest plan first.
|
||||
#
|
||||
# --plan also NAMES free capacity, as `ledig_antall=N` plus one `ledig=<repo>
|
||||
# (<status>)` line per repo, and never as a block. A repo with nothing owed,
|
||||
# nothing queued, nothing in flight and a clean tree, at status done or
|
||||
# deferred, has no next step to open a tab for - it used to just fall out of
|
||||
# the plan, which is where new capacity went unseen. All four conditions are
|
||||
# required: status=done alone is not one of them (measured 2026-08-23, 4 of 17
|
||||
# done/deferred repos were not free). It is a LINE and not a block on purpose:
|
||||
# plan_parse discards a block with no `tab=`, so a block would be visible to
|
||||
# the operator and invisible to the driver, while a line is visible to both and
|
||||
# can never be opened as a tab. The count prints even at 0, so "none found" and
|
||||
# "not computed" cannot render the same. A tab block whose repo holds a claimed
|
||||
# order additionally carries `fly=N` - the driver types into live panes.
|
||||
#
|
||||
# 0.19.0 shipped a weighted score here instead, and 0.20.0 replaced it. The score
|
||||
# expressed one thing this cannot - "owes one message AND releases two others" as
|
||||
# a single quantity - and the operator accepted losing that. What a score could
|
||||
|
|
@ -309,7 +337,7 @@ done
|
|||
[ -n "$(printf '%s' "$REPOS" | tr -d '[:space:]')" ] || exit 0
|
||||
|
||||
# --- Collect one record per repo -------------------------------------------
|
||||
# Record: bucket|sortkey|name|status|cost|inbox|dirty|age|last|dir|blockedon|orders|neste
|
||||
# Record: bucket|sortkey|name|status|cost|inbox|dirty|age|last|dir|blockedon|orders|claimed|neste
|
||||
RECORDS=""
|
||||
MALFORMED=""
|
||||
|
||||
|
|
@ -344,6 +372,30 @@ printf '%s\n' "$REPOS" | while IFS= read -r d; do
|
|||
[ -n "$orders" ] || orders=0
|
||||
fi
|
||||
|
||||
# CLAIMED orders, counted separately and never added to the one above. Same
|
||||
# queue, other state - not a fourth axis, which is why it is a second reading
|
||||
# of orders/ rather than a new source. It exists because ORDRE alone printed
|
||||
# the SAME DIGIT for two opposite facts: a repo with one order in flight and a
|
||||
# repo with no orders at all both read 0, so "work is happening here" and
|
||||
# "nothing is waiting here" were indistinguishable (order 20260823T162951Z,
|
||||
# measured on the live tree: two tabs idle 45 hours holding finished orders,
|
||||
# and no column reported it).
|
||||
#
|
||||
# What this count does NOT mean, and must never be reworded into: that a
|
||||
# session is alive. A claim is a `mv` a session performed once; nothing
|
||||
# un-claims it when that session dies, which is exactly why the order queue's
|
||||
# read path keeps showing claimed orders with their in-flight age. Measured
|
||||
# on the live mailbox the same day: wiki-advise held an order claimed for 117
|
||||
# hours. FLY is evidence that someone took the order, never proof anyone is
|
||||
# still working it - the legend says so in those words, because a column that
|
||||
# read as "a session is running here" would be the process axis smuggled in
|
||||
# as a file count.
|
||||
claimed=0
|
||||
if [ -d "$COORD/$name/orders/claimed" ]; then
|
||||
claimed="$(ls "$COORD/$name/orders/claimed"/*.md 2>/dev/null | wc -l | tr -d ' ')"
|
||||
[ -n "$claimed" ] || claimed=0
|
||||
fi
|
||||
|
||||
# Read for EVERY repo, not just the STATE-less ones: a repo whose plan file
|
||||
# is fresh can still have been silent for a year, and that is precisely the
|
||||
# repo no other column reports. A repo with no commits at all has no reading
|
||||
|
|
@ -359,8 +411,8 @@ printf '%s\n' "$REPOS" | while IFS= read -r d; do
|
|||
# No plan file, so no plan age: ALDER is blank rather than quietly showing
|
||||
# the commit age under a heading that means something else everywhere else
|
||||
# in the table. The sort key keeps using it - order is unchanged.
|
||||
printf '5|%06d|%s|-|-|%s|%s|-|%s|%s|-|%s|(ingen STATE.md)\n' \
|
||||
"$lastd" "$name" "$inbox" "$dirty" "$lastcol" "$d" "$orders"
|
||||
printf '5|%06d|%s|-|-|%s|%s|-|%s|%s|-|%s|%s|(ingen STATE.md)\n' \
|
||||
"$lastd" "$name" "$inbox" "$dirty" "$lastcol" "$d" "$orders" "$claimed"
|
||||
continue
|
||||
fi
|
||||
|
||||
|
|
@ -449,9 +501,9 @@ printf '%s\n' "$REPOS" | while IFS= read -r d; do
|
|||
# `orders` sits between blockedon and neste, never after it: `neste` is free
|
||||
# prose lifted out of a STATE.md and may contain a literal '|', so it has to
|
||||
# stay the last field on the line.
|
||||
printf '%s|%06d|%s|%s|%s|%s|%s|%sd|%s|%s|%s|%s|%s\n' \
|
||||
printf '%s|%06d|%s|%s|%s|%s|%s|%sd|%s|%s|%s|%s|%s|%s\n' \
|
||||
"$bucket" "$age" "$name" "$disp" "$cost" "$inbox" "$dirty" "$age" "$lastcol" "$d" \
|
||||
"${blockedon:--}" "$orders" "$neste"
|
||||
"${blockedon:--}" "$orders" "$claimed" "$neste"
|
||||
done > "${TMPDIR:-/tmp}/board.$$"
|
||||
|
||||
RECORDS="${TMPDIR:-/tmp}/board.$$"
|
||||
|
|
@ -558,15 +610,15 @@ awk -F'|' '$4 ~ /^blocked/ && $11 != "-" && $11 != "" {print $11}' "$RECORDS" \
|
|||
|
||||
hdr() {
|
||||
printf '\n%s\n' "$1"
|
||||
printf '%-32s %-34s %-14s %4s %6s %4s %6s %6s %s\n' \
|
||||
"REPO" "STATUS" "KOST" "INN" "ORDRE" "DRT" "ALDER" "SISTE" "NESTE"
|
||||
printf '%-32s %-34s %-14s %4s %6s %4s %4s %6s %6s %s\n' \
|
||||
"REPO" "STATUS" "KOST" "INN" "ORDRE" "FLY" "DRT" "ALDER" "SISTE" "NESTE"
|
||||
}
|
||||
|
||||
rows() {
|
||||
awk -F'|' -v b="$1" '$1==b' "$RECORDS" | sort -t'|' -k2,2n | \
|
||||
while IFS='|' read -r bucket sortkey name status cost inbox dirty age last dir blockedon orders neste; do
|
||||
printf '%-32s %-34s %-14s %4s %6s %4s %6s %6s %s\n' \
|
||||
"$name" "$status" "$cost" "$inbox" "$orders" "$dirty" "$age" "$last" "$(trunc "$neste" "$NESTE_WIDTH")"
|
||||
while IFS='|' read -r bucket sortkey name status cost inbox dirty age last dir blockedon orders claimed neste; do
|
||||
printf '%-32s %-34s %-14s %4s %6s %4s %4s %6s %6s %s\n' \
|
||||
"$name" "$status" "$cost" "$inbox" "$orders" "$claimed" "$dirty" "$age" "$last" "$(trunc "$neste" "$NESTE_WIDTH")"
|
||||
done
|
||||
}
|
||||
|
||||
|
|
@ -757,7 +809,7 @@ brief() {
|
|||
FILENAME==OWF { ow[$1] = $2 + 0; next }
|
||||
{ name = $3; owed = (name in ow) ? ow[name] : 0; if (owed > 0) print owed, $0 }
|
||||
' "$OWED" "$RECORDS" | sort -t'|' -k1,1nr | \
|
||||
while IFS='|' read -r owed bucket sortkey name status cost inbox dirty age last dir blockedon orders neste; do
|
||||
while IFS='|' read -r owed bucket sortkey name status cost inbox dirty age last dir blockedon orders claimed neste; do
|
||||
printf ' %-32s INN %-4s %s\n' "$name" "$owed" "$status"
|
||||
# Wrapped, not cut - the whole line is the point, but a 500-character one
|
||||
# is unreadable in a file nobody watched being written. Locale is set for
|
||||
|
|
@ -877,7 +929,7 @@ dispatch() {
|
|||
[ -n "$d_rec" ] || { echo "board: no repo named '$D_REPO' in the scanned roots ($ROOTS)" >&2; exit 2; }
|
||||
d_status="$(printf '%s' "$d_rec" | cut -d'|' -f4)"
|
||||
d_dir="$(printf '%s' "$d_rec" | cut -d'|' -f10)"
|
||||
d_neste="$(printf '%s' "$d_rec" | cut -d'|' -f13-)"
|
||||
d_neste="$(printf '%s' "$d_rec" | cut -d'|' -f14-)"
|
||||
[ -d "$d_dir" ] || { echo "board: the directory for $D_REPO does not exist: $d_dir" >&2; exit 2; }
|
||||
|
||||
# Rejected traits REFUSE. Degrading to a command without them is the worst
|
||||
|
|
@ -1287,6 +1339,40 @@ plan() {
|
|||
# decided has a second consumer elsewhere), but the driver must be able to
|
||||
# see that the omission happened rather than read a shorter list as complete.
|
||||
[ "$HAVE_COUNT" -eq 1 ] || printf 'advarsel=coord-count.sh mangler - gjeldsvekting (gruppe 2, inbox:N) er ikke beregnet\n'
|
||||
|
||||
# --- Free capacity: NAMED, never turned into a tab ------------------------
|
||||
# The question the plan could not answer until now, in the operator's own
|
||||
# words: "which repos are finished and can take more work". Until 0.29.0 such
|
||||
# a repo simply fell OUT of the plan - correctly, since it has no next step to
|
||||
# open a tab for, but silently, which is where new capacity was going unseen.
|
||||
#
|
||||
# It is a LOOKUP over fields the scan already read, exactly like every group
|
||||
# term above - no new source, no new axis. All four conditions are required,
|
||||
# and status=done alone is not one of them: measured on the real tree
|
||||
# 2026-08-23, 4 of 17 done/deferred repos were NOT free (two held a pending
|
||||
# order, one owed a reply, one had an uncommitted tree). Debt is OWED, not raw
|
||||
# pending, for the same reason group 2 is - a notice is not an obligation.
|
||||
#
|
||||
# Emitted as single key=value LINES, never as blocks, and that is the whole
|
||||
# design: `plan_parse` discards a block with no `tab=`, so a free repo written
|
||||
# as a block would be visible to the operator and invisible to the driver.
|
||||
# A line is visible to both and can never be opened as a tab by accident.
|
||||
# The count is printed even when it is 0, so "none found" and "not computed"
|
||||
# cannot render as the same output.
|
||||
lf="${TMPDIR:-/tmp}/board-ledig.$$"
|
||||
awk -F'|' -v OWF="$OWED" '
|
||||
FILENAME==OWF { ow[$1] = $2 + 0; next }
|
||||
{
|
||||
name = $3; status = $4; dirty = $7 + 0; orders = $12 + 0; claimed = $13 + 0
|
||||
owed = (name in ow) ? ow[name] : 0
|
||||
if (status != "done" && status != "deferred") next
|
||||
if (owed > 0 || orders > 0 || claimed > 0 || dirty > 0) next
|
||||
print name " (" status ")"
|
||||
}
|
||||
' "$OWED" "$RECORDS" | sort > "$lf"
|
||||
printf 'ledig_antall=%s\n' "$(awk 'END{print NR+0}' "$lf")"
|
||||
sed 's/^/ledig=/' "$lf"
|
||||
/bin/rm -f "$lf" 2>/dev/null
|
||||
if [ -n "$FOCUS" ]; then
|
||||
fp_state="$(awk -F'|' '{print $10}' "$RECORDS" | while read -r fp_sd; do
|
||||
[ -n "$fp_sd" ] && [ -f "$fp_sd/STATE.md" ] && echo x
|
||||
|
|
@ -1322,7 +1408,7 @@ plan() {
|
|||
echo ""
|
||||
|
||||
pn=0
|
||||
while IFS='|' read -r why bucket sortkey name status cost inbox dirty age last dir blockedon orders neste; do
|
||||
while IFS='|' read -r why bucket sortkey name status cost inbox dirty age last dir blockedon orders claimed neste; do
|
||||
[ -n "$name" ] || continue
|
||||
pn=$((pn + 1))
|
||||
printf 'tab=%s\n' "$pn"
|
||||
|
|
@ -1336,6 +1422,12 @@ plan() {
|
|||
fp_ev="$(focus_evidence "$dir")" && printf 'fokus_treff=%s\n' "$fp_ev"
|
||||
fi
|
||||
printf 'status=%s\n' "$status"
|
||||
# Emitted only when there IS one, like fokus_treff= above: a `fly=0` on
|
||||
# every block would be noise, and its absence already says nothing is in
|
||||
# flight. A tab whose repo holds a claimed order may already have a session
|
||||
# in it, and the driver types into live panes - it has to be able to see
|
||||
# this before it does.
|
||||
[ "${claimed:-0}" -gt 0 ] 2>/dev/null && printf 'fly=%s\n' "$claimed"
|
||||
printf 'neste=%s\n' "$neste"
|
||||
plan_cmd "$dir" "$cost"
|
||||
echo ""
|
||||
|
|
@ -1376,7 +1468,7 @@ inbox_plan() {
|
|||
FILENAME==RCF {
|
||||
name = $3
|
||||
rc_bucket[name] = $1; rc_status[name] = $4; rc_cost[name] = $5
|
||||
rc_dir[name] = $10; rc_neste[name] = $13
|
||||
rc_dir[name] = $10; rc_neste[name] = $14
|
||||
next
|
||||
}
|
||||
{
|
||||
|
|
@ -1451,6 +1543,14 @@ echo "INN = uhaandtert innboks (andre venter paa DEG). DRT = ukommiterte filer."
|
|||
# legend states the distinction where they are read - they measure opposite
|
||||
# directions and are never summed.
|
||||
echo "ORDRE = ventende ordrer (arbeid venter paa REPOET). INN og ORDRE summeres ALDRI."
|
||||
# FLY is the SAME queue as ORDRE in its other state, never a fourth axis and
|
||||
# never a liveness reading. The denial is part of the legend, not a footnote:
|
||||
# nothing un-claims an order when the session that claimed it dies (measured
|
||||
# 2026-08-23, wiki-advise: one order claimed 117 hours), so "an order is
|
||||
# claimed here" is the only claim this number can carry.
|
||||
echo "FLY = ordrer i arbeid (claimed). Beviser aldri at en okt lever eller er aktiv."
|
||||
echo " Beviser bare at noen TOK ordren; ingenting frigir den om okten dor."
|
||||
echo " Summeres ALDRI med ORDRE - samme ko, annen tilstand."
|
||||
echo "ALDER = dager siden STATE.md endret. SISTE = dager siden siste commit."
|
||||
|
||||
[ "$(count 1)" -gt 0 ] && { hdr "BLOKKERT (venter paa ekstern avhengighet)"; rows 1; }
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue