@plot-pm/board 0.15.0 → 0.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/board-server.mjs +109 -107
- package/package.json +2 -1
- package/plot-agent-manifest.sh +80 -1
- package/plot-approve.sh +3 -1
- package/plot-budget.sh +378 -19
- package/plot-config.sh +52 -1
- package/plot-deliver.sh +3 -1
- package/plot-dispatch.sh +24 -30
- package/plot-fleet-scan.sh +51 -150
- package/plot-host.sh +227 -72
- package/plot-plan-meta.sh +58 -10
- package/plot-reap.sh +104 -8
- package/plot-resolve-artifact.sh +4 -2
- package/plot-tmp.sh +108 -0
- package/plot-transcript-quiet.sh +29 -1
- package/plot-worker-monitor.sh +66 -5
package/plot-dispatch.sh
CHANGED
|
@@ -178,6 +178,7 @@
|
|
|
178
178
|
set -uo pipefail
|
|
179
179
|
|
|
180
180
|
script_dir=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
|
|
181
|
+
. "$script_dir/plot-tmp.sh"
|
|
181
182
|
|
|
182
183
|
# The shared worker classifier. Sourced by both this script and
|
|
183
184
|
# plot-fleet-scan.sh so a worker has ONE state, not one per reader.
|
|
@@ -392,28 +393,9 @@ fi
|
|
|
392
393
|
# until this moved. Nothing here executes at definition time; only the position
|
|
393
394
|
# changed.
|
|
394
395
|
|
|
395
|
-
#
|
|
396
|
-
#
|
|
397
|
-
#
|
|
398
|
-
# — never to `$RANDOM` or a timestamp. Two workers launched in the same second by
|
|
399
|
-
# the same fan-out would collide on either, and a collision here silently merges
|
|
400
|
-
# two agents into one manifest.
|
|
401
|
-
#
|
|
402
|
-
# Lowercased because the runtime writes its transcript filename in lowercase and
|
|
403
|
-
# the board joins on exact string equality; `uuidgen` on macOS returns uppercase.
|
|
404
|
-
plot_session_id() {
|
|
405
|
-
local id=""
|
|
406
|
-
if command -v uuidgen >/dev/null 2>&1; then
|
|
407
|
-
id=$(uuidgen 2>/dev/null | tr 'A-Z' 'a-z')
|
|
408
|
-
fi
|
|
409
|
-
if [ -z "$id" ]; then
|
|
410
|
-
# 16 random bytes rendered as a v4-shaped id. The shape matters only for
|
|
411
|
-
# recognisability; nothing parses it.
|
|
412
|
-
id=$(od -An -tx1 -N16 /dev/urandom 2>/dev/null | tr -d ' \n' \
|
|
413
|
-
| sed -E 's/(.{8})(.{4})(.{4})(.{4})(.{12})/\1-\2-\3-\4-\5/')
|
|
414
|
-
fi
|
|
415
|
-
printf '%s' "$id"
|
|
416
|
-
}
|
|
396
|
+
# `plot_session_id` — the session id a launch records — is SOURCED from
|
|
397
|
+
# `plot-agent-manifest.sh` above, because the worker loop mints one on a hop to
|
|
398
|
+
# a new branch and two generators of one id drift.
|
|
417
399
|
|
|
418
400
|
# JSON-escape one string for a manifest value.
|
|
419
401
|
#
|
|
@@ -806,8 +788,20 @@ request_brief() { # $1 = branch, $2 = slug → 0 if a command was started
|
|
|
806
788
|
# this outlives the dispatch run by design, because the fan-out must not block
|
|
807
789
|
# on a `claude -p` session of unknown length. `setsid` is not used — it does
|
|
808
790
|
# not exist on macOS, where most of this fleet runs.
|
|
791
|
+
# THE SETTINGS FILE THIS PROJECT STARTS ITS AGENTS WITH. The `Brief command` is
|
|
792
|
+
# a `claude -p` session like any other agent and inherits every `SessionStart`
|
|
793
|
+
# hook the operator's plugins declare, so it carries the same variable the
|
|
794
|
+
# worker loop exports — and the configured command interpolates
|
|
795
|
+
# `${PLOT_AGENT_SETTINGS:+--settings "$PLOT_AGENT_SETTINGS"}` itself.
|
|
796
|
+
#
|
|
797
|
+
# SET INLINE rather than exported, because this spawn already names its
|
|
798
|
+
# environment on the command line. A refusal or an absent key leaves it EMPTY,
|
|
799
|
+
# which `${VAR:+…}` reads as nothing — so a broken config cannot stop a brief
|
|
800
|
+
# being written.
|
|
801
|
+
_brief_settings="$(bash "$(dirname "${BASH_SOURCE[0]}")/plot-agent-settings.sh" 2>/dev/null || echo "")"
|
|
809
802
|
( cd "$repo_root" \
|
|
810
803
|
&& PLOT_UNATTENDED=1 PLOT_PLAN_SLUG="$bslug" PLOT_BRIEF_BRANCH="$branch" \
|
|
804
|
+
PLOT_AGENT_SETTINGS="$_brief_settings" \
|
|
811
805
|
nohup sh -c "$cmd \"\$@\"" plot-brief \
|
|
812
806
|
"$(brief_prompt "$branch" "$bslug")" \
|
|
813
807
|
>"$log" 2>&1 </dev/null & ) 2>/dev/null
|
|
@@ -2659,18 +2653,18 @@ fi
|
|
|
2659
2653
|
# materialised into a temp file rather than parsed here — the parser stays the
|
|
2660
2654
|
# one place that knows what a plan file looks like.
|
|
2661
2655
|
#
|
|
2662
|
-
#
|
|
2663
|
-
# suffix after them, while GNU accepts it. The first version
|
|
2664
|
-
# `plot-gate-XXXXXX.md` and failed on macOS — and because the failure fell
|
|
2665
|
-
# to the working tree, the gate silently went back to reading the exact
|
|
2666
|
-
# this fix exists to stop reading. Hence also: NO working-tree fallback
|
|
2667
|
-
# If the shared blob cannot be materialised, the gate refuses.
|
|
2656
|
+
# `plot-tmp.sh` puts the template's X's last: BSD mktemp (macOS) rejects a
|
|
2657
|
+
# template with a suffix after them, while GNU accepts it. The first version
|
|
2658
|
+
# wrote `plot-gate-XXXXXX.md` and failed on macOS — and because the failure fell
|
|
2659
|
+
# back to the working tree, the gate silently went back to reading the exact
|
|
2660
|
+
# surface this fix exists to stop reading. Hence also: NO working-tree fallback
|
|
2661
|
+
# below. If the shared blob cannot be materialised, the gate refuses.
|
|
2668
2662
|
plan_file="$plan_path"
|
|
2669
2663
|
gate_blob=""
|
|
2670
2664
|
if [ -n "$gate_sha" ]; then
|
|
2671
|
-
gate_dir
|
|
2665
|
+
gate_dir=""
|
|
2666
|
+
plot_tmpdir gate_dir gate || gate_dir=""
|
|
2672
2667
|
if [ -n "$gate_dir" ]; then
|
|
2673
|
-
trap 'rm -rf "$gate_dir"' EXIT
|
|
2674
2668
|
gate_blob="$gate_dir/$(basename "$plan_path")"
|
|
2675
2669
|
git show "$gate_ref:$plan_path" >"$gate_blob" 2>/dev/null || gate_blob=""
|
|
2676
2670
|
fi
|
package/plot-fleet-scan.sh
CHANGED
|
@@ -133,9 +133,9 @@
|
|
|
133
133
|
# lifecycle state, verbatim from plot-plan-meta.sh, and the half of a
|
|
134
134
|
# row's phase git cannot answer. Which column a row reads is composed
|
|
135
135
|
# from it AND the branch state one layer up; this script decides nothing.
|
|
136
|
-
# The plan set also includes
|
|
137
|
-
#
|
|
138
|
-
#
|
|
136
|
+
# The plan set also includes every plan at `Delivered`, until it is
|
|
137
|
+
# `Released` (see "the release scope"), so DONE names what the next
|
|
138
|
+
# release ships.
|
|
139
139
|
# Plans are enumerated from REFS (`git ls-tree`/`git show`), NOT from
|
|
140
140
|
# the working tree — so the list describes committed state and does not
|
|
141
141
|
# change while rebases and worker commits rewrite the checkout
|
|
@@ -253,6 +253,8 @@ script_dir=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
|
|
|
253
253
|
# plot-dispatch.sh so a worker has ONE state, not one per reader.
|
|
254
254
|
# shellcheck source=plot-worker-state.sh
|
|
255
255
|
. "$script_dir/plot-worker-state.sh"
|
|
256
|
+
# Every temp path this scan creates, and its only EXIT/INT/TERM traps.
|
|
257
|
+
. "$script_dir/plot-tmp.sh"
|
|
256
258
|
cfg() { "$script_dir/plot-config.sh" get "$1" "${2:-}"; }
|
|
257
259
|
|
|
258
260
|
do_fetch=1
|
|
@@ -573,14 +575,15 @@ fi
|
|
|
573
575
|
# outlives the scan that fetched it — a stale `merged` read from a previous run
|
|
574
576
|
# is exactly the fabricated verdict the failure direction above forbids.
|
|
575
577
|
#
|
|
576
|
-
# Cleanup is
|
|
578
|
+
# Cleanup is registered rather than trailing: the script exits early in several
|
|
577
579
|
# places (--next with nothing to start, no active plans), and a temp directory
|
|
578
|
-
# left behind on those paths would accumulate one per poll.
|
|
580
|
+
# left behind on those paths would accumulate one per poll. It goes through
|
|
581
|
+
# `plot-tmp.sh`'s one registry: this cache had its own EXIT trap, and the
|
|
582
|
+
# `REF_TMP` trap below replaced it, so every scan left one `tmp.*` directory of
|
|
583
|
+
# ~955 files behind (measured 2026-09-30).
|
|
579
584
|
HOST_STATE_CACHE=""
|
|
580
585
|
if [ "$HOST_LOOKUP_OK" = 1 ]; then
|
|
581
|
-
HOST_STATE_CACHE
|
|
582
|
-
[ -n "$HOST_STATE_CACHE" ] \
|
|
583
|
-
&& trap 'rm -rf "$HOST_STATE_CACHE" 2>/dev/null || true' EXIT INT TERM
|
|
586
|
+
plot_tmpdir HOST_STATE_CACHE fleet-host-state 2>/dev/null || HOST_STATE_CACHE=""
|
|
584
587
|
fi
|
|
585
588
|
|
|
586
589
|
# The cache key. Shared by the join and by `host_pr_state`, because the two
|
|
@@ -2245,37 +2248,15 @@ pr_ready() {
|
|
|
2245
2248
|
}
|
|
2246
2249
|
|
|
2247
2250
|
# ---------------------------------------------------------------------------
|
|
2248
|
-
#
|
|
2251
|
+
# Delivered plans: the release scope
|
|
2249
2252
|
# ---------------------------------------------------------------------------
|
|
2250
2253
|
#
|
|
2251
|
-
#
|
|
2252
|
-
#
|
|
2253
|
-
#
|
|
2254
|
-
#
|
|
2255
|
-
#
|
|
2256
|
-
#
|
|
2257
|
-
#
|
|
2258
|
-
# A ROLLING 24 HOURS, not the calendar day. Literally "delivered today" is
|
|
2259
|
-
# easier to explain and wrong at exactly the wrong moment: a plan delivered at
|
|
2260
|
-
# 23:50 vanishes ten minutes later, mid-session, while the branches it names
|
|
2261
|
-
# are still on screen. 24 is also the one freshness bound this repo already
|
|
2262
|
-
# uses (`Claim stale after`), so it is one unit to learn rather than two.
|
|
2263
|
-
#
|
|
2264
|
-
# THE WINDOW FILTERS BEFORE THE PARSE. Measured: ~57 ms per plan through
|
|
2265
|
-
# plot-plan-meta.sh against a scan that already runs 500–1050 ms, so parsing
|
|
2266
|
-
# fourteen delivered plans to discard thirteen would roughly double the pulse —
|
|
2267
|
-
# and that cost grows with the archive, which only ever gets larger, while the
|
|
2268
|
-
# answer stays the size of a day's work. So the cheap signal comes first (the
|
|
2269
|
-
# delivered symlink's own mtime) and only the candidates it admits are parsed.
|
|
2270
|
-
#
|
|
2271
|
-
# The pre-filter may OVER-ADMIT AND PAY A PARSE; it may never exclude. A
|
|
2272
|
-
# checkout can freshen an old file, so the `Delivered:` record keeps the last
|
|
2273
|
-
# word — but nothing mtime rules out could have been delivered inside the
|
|
2274
|
-
# window. On a fresh clone or a CI worktree every file shares one checkout
|
|
2275
|
-
# timestamp and ALL of them are admitted: correct, merely slower, once. Reaching
|
|
2276
|
-
# for `git log` per plan to avoid that would spend a git call to save a parse.
|
|
2277
|
-
DELIVERED_WINDOW_HOURS=$(cfg "Claim stale after" "24")
|
|
2278
|
-
case "$DELIVERED_WINDOW_HOURS" in (*[!0-9]*|'') DELIVERED_WINDOW_HOURS=24 ;; esac
|
|
2254
|
+
# A plan at `Delivered` stays in the pulse until it is `Released`, whatever its
|
|
2255
|
+
# age. DONE holds the release scope (`done-holds-what-is-still-yours`): every
|
|
2256
|
+
# plan whose work has landed and whose version has not shipped. A 24-hour window
|
|
2257
|
+
# on the `Delivered:` record bounded this until 2026-10-01, when 25 plans awaited
|
|
2258
|
+
# 2.22.0 and DONE showed 10. Every plan file is parsed in one pass anyway, so the
|
|
2259
|
+
# window bought no parse time; it only hid work the next release ships.
|
|
2279
2260
|
|
|
2280
2261
|
|
|
2281
2262
|
# ---------------------------------------------------------------------------
|
|
@@ -2441,52 +2422,7 @@ changed_ago_of() { # $1=branch → "<seconds since>\t<epoch of>" for the newest
|
|
|
2441
2422
|
if [ "$newest" -gt "$now" ]; then printf '0\t%s' "$newest"; else printf '%s\t%s' "$((now - newest))" "$newest"; fi
|
|
2442
2423
|
}
|
|
2443
2424
|
|
|
2444
|
-
# A plan whose delivered symlink was touched inside the window. `find -newermt`
|
|
2445
|
-
# is not portable to every BSD find in the wild, so the cutoff is computed and
|
|
2446
|
-
# compared with `stat` — one stat per file, no parse.
|
|
2447
|
-
delivered_candidates() {
|
|
2448
|
-
local cutoff now link mtime
|
|
2449
|
-
now=$(date +%s)
|
|
2450
|
-
cutoff=$((now - DELIVERED_WINDOW_HOURS * 3600))
|
|
2451
|
-
for link in "$DELIVERED_DIR"*.md; do
|
|
2452
|
-
[ -e "$link" ] || continue
|
|
2453
|
-
# `stat` follows the symlink, which is what we want: the TARGET is the plan,
|
|
2454
|
-
# and a plan edited after delivery must still admit. An unreadable time
|
|
2455
|
-
# ADMITS rather than excludes — the pre-filter may only over-admit, and the
|
|
2456
|
-
# `Delivered:` record has the last word either way.
|
|
2457
|
-
mtime=$(file_mtime "$link") || { printf '%s\n' "$link"; continue; }
|
|
2458
|
-
[ "$mtime" -ge "$cutoff" ] && printf '%s\n' "$link"
|
|
2459
|
-
done
|
|
2460
|
-
}
|
|
2461
2425
|
|
|
2462
|
-
# Does this plan's `Delivered:` record fall inside the window? The RECORD
|
|
2463
|
-
# decides — mtime only chose who got asked.
|
|
2464
|
-
#
|
|
2465
|
-
# "No date, no row." A delivered plan with an empty record does not appear at
|
|
2466
|
-
# all: no date means no membership in any window, the same rule the waiting age
|
|
2467
|
-
# already follows. Showing it always would create the one row that can never
|
|
2468
|
-
# age out of DONE, and the missing record is a bookkeeping fault
|
|
2469
|
-
# plot-reconcile-scan.sh exists to report — a view that quietly compensates
|
|
2470
|
-
# for it makes the fault harder to see.
|
|
2471
|
-
#
|
|
2472
|
-
# A BARE DATE IS ANCHORED AT THE END OF ITS DAY, not at midnight, and this is
|
|
2473
|
-
# the one detail that makes "rolling, not the calendar day" true rather than
|
|
2474
|
-
# merely stated. Every `Delivered:` record in this repo is a bare date, which
|
|
2475
|
-
# names no time — so anchoring at 00:00 measures from up to a day BEFORE the
|
|
2476
|
-
# delivery, and the window collapses back into exactly the calendar boundary
|
|
2477
|
-
# the rolling window exists to avoid: a plan delivered at 23:50 would be an
|
|
2478
|
-
# hour from expiry the moment it was written, and gone ten minutes later
|
|
2479
|
-
# mid-session while the branches it names are still on screen.
|
|
2480
|
-
#
|
|
2481
|
-
# Anchoring at 23:59:59 over-admits by at most the length of the delivery day.
|
|
2482
|
-
# That is the same direction the mtime pre-filter is allowed to err in, and for
|
|
2483
|
-
# the same reason: showing a finished plan slightly too long costs a row, while
|
|
2484
|
-
# dropping one mid-session costs the reader the work they were looking at. A
|
|
2485
|
-
# record that DOES carry a time is honoured exactly, so the imprecision belongs
|
|
2486
|
-
# to the record rather than to the rule.
|
|
2487
|
-
# The rule itself now runs inside the ONE estate parse below (`in_window`),
|
|
2488
|
-
# because asking it per plan meant an interpreter per plan. What it decides is
|
|
2489
|
-
# unchanged; only the number of processes that decide it is.
|
|
2490
2426
|
|
|
2491
2427
|
# ---------------------------------------------------------------------------
|
|
2492
2428
|
# Plan enumeration: from the REF, not from the tree
|
|
@@ -2577,15 +2513,15 @@ ref_ls() { # $1=dir → newline-separated paths under it in origin/$MAIN
|
|
|
2577
2513
|
# `ref_plan_file` is called as `$(ref_plan_file ...)`, which runs it in a
|
|
2578
2514
|
# SUBSHELL. A lazy `[ -z "$REF_TMP" ] && REF_TMP=$(mktemp -d)` inside it
|
|
2579
2515
|
# assigns in the child and the parent never sees it — so every call made a
|
|
2580
|
-
# fresh directory, the parent's variable stayed empty, and the
|
|
2581
|
-
#
|
|
2516
|
+
# fresh directory, the parent's variable stayed empty, and the exit cleanup
|
|
2517
|
+
# removed nothing. Measured while writing this: three plans, three temp dirs,
|
|
2582
2518
|
# none removed. The lifetime is owned out here, where the trap can see it.
|
|
2583
2519
|
REF_TMP=""
|
|
2584
2520
|
if [ "$PLAN_SOURCE" = "ref" ]; then
|
|
2585
|
-
REF_TMP=$(mktemp -d "${TMPDIR:-/tmp}/plot-fleet-ref.XXXXXX") || REF_TMP=""
|
|
2586
2521
|
# The scan is read-only and short-lived, and the board polls it every 5 s —
|
|
2587
|
-
# a directory that outlives the run would accumulate one per poll
|
|
2588
|
-
|
|
2522
|
+
# a directory that outlives the run would accumulate one per poll, so the
|
|
2523
|
+
# helper removes it at exit.
|
|
2524
|
+
plot_tmpdir REF_TMP fleet-ref || REF_TMP=""
|
|
2589
2525
|
# No temp dir means no way to hand the parser a file, so the ref path cannot
|
|
2590
2526
|
# work. Falling back to the checkout is the honest answer, and it announces
|
|
2591
2527
|
# itself through `plan_source` exactly like an unreadable ref.
|
|
@@ -2890,7 +2826,7 @@ is_plan_phase() { # $1=normalized phase → 0 when this file is a plan
|
|
|
2890
2826
|
#
|
|
2891
2827
|
# `record` types, one per line, all tab-separated and all prefixed by the plan
|
|
2892
2828
|
# file they describe:
|
|
2893
|
-
# P <file> <phase>
|
|
2829
|
+
# P <file> <phase> one per parsed file
|
|
2894
2830
|
# W <file> <wave-idx> <branch> <deferred> <why> <wave-name> <claim>
|
|
2895
2831
|
#
|
|
2896
2832
|
# NO ASSOCIATIVE ARRAYS. `/bin/bash` on macOS is 3.2 and this script uses no
|
|
@@ -2908,7 +2844,6 @@ is_plan_phase() { # $1=normalized phase → 0 when this file is a plan
|
|
|
2908
2844
|
# gave when it failed.
|
|
2909
2845
|
plan_meta_files=()
|
|
2910
2846
|
plan_meta_phases=()
|
|
2911
|
-
plan_meta_inwindow=()
|
|
2912
2847
|
plan_meta_waves=()
|
|
2913
2848
|
|
|
2914
2849
|
# Parses every plan file given, filling the four arrays above. Called ONCE.
|
|
@@ -2919,33 +2854,6 @@ parse_plan_estate() { # $@=files to parse
|
|
|
2919
2854
|
| python3 -c '
|
|
2920
2855
|
import json, re, sys, time
|
|
2921
2856
|
|
|
2922
|
-
window = float(sys.argv[1]) * 3600
|
|
2923
|
-
now = time.time()
|
|
2924
|
-
|
|
2925
|
-
def in_window(raw):
|
|
2926
|
-
"""The `Delivered:` record against the rolling window — the same rule the
|
|
2927
|
-
per-plan test applied, moved into the one pass. A record whose date does
|
|
2928
|
-
not parse is dropped rather than coerced: Date-style leniency would turn a
|
|
2929
|
-
typo into a confident answer. A record with no time anchors at 23:59:59,
|
|
2930
|
-
so a plan delivered at 23:50 is not an hour from expiry the moment it is
|
|
2931
|
-
written. A FUTURE record is INSIDE (negative age), because hiding a live
|
|
2932
|
-
plan for a mistyped year costs more than showing one."""
|
|
2933
|
-
raw = (raw or "").strip()
|
|
2934
|
-
if not raw:
|
|
2935
|
-
return False
|
|
2936
|
-
m = re.match(r"(\d{4})-(\d{2})-(\d{2})(?:[T ](\d{2}):(\d{2}))?", raw)
|
|
2937
|
-
if not m:
|
|
2938
|
-
return False
|
|
2939
|
-
y, mo, dy, hh, mi = m.groups()
|
|
2940
|
-
timed = hh is not None
|
|
2941
|
-
try:
|
|
2942
|
-
at = time.mktime((int(y), int(mo), int(dy),
|
|
2943
|
-
int(hh) if timed else 23, int(mi) if timed else 59,
|
|
2944
|
-
0 if timed else 59, 0, 0, -1))
|
|
2945
|
-
except (ValueError, OverflowError):
|
|
2946
|
-
return False
|
|
2947
|
-
return (now - at) <= window
|
|
2948
|
-
|
|
2949
2857
|
def clean(s):
|
|
2950
2858
|
return str(s).replace("\t", " ").replace("\n", " ")
|
|
2951
2859
|
|
|
@@ -2963,8 +2871,7 @@ for line in sys.stdin:
|
|
|
2963
2871
|
f = d.get("file")
|
|
2964
2872
|
if not f:
|
|
2965
2873
|
continue
|
|
2966
|
-
print("\t".join(["P", clean(f), clean(d.get("phase", ""))
|
|
2967
|
-
"1" if in_window(d.get("delivered_raw")) else "0"]))
|
|
2874
|
+
print("\t".join(["P", clean(f), clean(d.get("phase", ""))]))
|
|
2968
2875
|
for i, w in enumerate(d.get("waves", []) or []):
|
|
2969
2876
|
name = w.get("name")
|
|
2970
2877
|
for b in w.get("branches", []) or []:
|
|
@@ -2991,7 +2898,7 @@ for line in sys.stdin:
|
|
|
2991
2898
|
(b.get("deferred_reason") or "-"),
|
|
2992
2899
|
(b.get("waits_on") or "-"),
|
|
2993
2900
|
name or "-", b.get("claimed") or "-"]))
|
|
2994
|
-
'
|
|
2901
|
+
' 2>/dev/null) || records=""
|
|
2995
2902
|
|
|
2996
2903
|
local kind file rest
|
|
2997
2904
|
while IFS=$'\t' read -r kind file rest; do
|
|
@@ -2999,9 +2906,8 @@ for line in sys.stdin:
|
|
|
2999
2906
|
case "$kind" in
|
|
3000
2907
|
P)
|
|
3001
2908
|
plan_meta_files+=("$file")
|
|
3002
|
-
# `rest` is "<phase
|
|
3003
|
-
plan_meta_phases+=("$
|
|
3004
|
-
plan_meta_inwindow+=("${rest##* }")
|
|
2909
|
+
# `rest` is "<phase>", a single token with no tabs.
|
|
2910
|
+
plan_meta_phases+=("$rest")
|
|
3005
2911
|
plan_meta_waves+=("")
|
|
3006
2912
|
;;
|
|
3007
2913
|
W)
|
|
@@ -3125,8 +3031,8 @@ else
|
|
|
3125
3031
|
# was buying less than it appeared to: it keyed off the `$DELIVERED_DIR`
|
|
3126
3032
|
# symlink's mtime, and a fresh checkout stamps every symlink at once — 56 of
|
|
3127
3033
|
# 56 delivered links admitted here, so the parse it was meant to avoid was
|
|
3128
|
-
# already being paid in full.
|
|
3129
|
-
#
|
|
3034
|
+
# already being paid in full. The `Delivered:` record's window was the filter
|
|
3035
|
+
# that actually decided until 2026-10-01, when the phase alone took over, and the pre-filter's own
|
|
3130
3036
|
# contract was that it may only ever OVER-admit. Removing it takes that
|
|
3131
3037
|
# contract to its limit — strictly more correct, and on this repo not even
|
|
3132
3038
|
# more expensive.
|
|
@@ -3374,7 +3280,7 @@ EOF
|
|
|
3374
3280
|
echo "$total $n"
|
|
3375
3281
|
}
|
|
3376
3282
|
|
|
3377
|
-
# WHAT WAS READ OF ONE BRANCH —
|
|
3283
|
+
# WHAT WAS READ OF ONE BRANCH — eleven tab-separated fields, and no decision.
|
|
3378
3284
|
#
|
|
3379
3285
|
# `branch_state()` UNTIL THIS SLICE, and every line of git archaeology below is
|
|
3380
3286
|
# its own, unchanged. What went is the `if` chain that merged these readings
|
|
@@ -3391,10 +3297,11 @@ EOF
|
|
|
3391
3297
|
# run of tabs collapses into one separator under `read`, so no field is ever
|
|
3392
3298
|
# empty. Nothing here is optional, so nothing can shift.
|
|
3393
3299
|
#
|
|
3394
|
-
# EIGHT FIELDS, NOT
|
|
3395
|
-
# what the host said about it — are appended by the caller, because reading
|
|
3396
|
-
# second costs a host round trip and the scan spends it only where it could
|
|
3300
|
+
# EIGHT FIELDS, NOT ELEVEN. The two the plan states — the prerequisite's name
|
|
3301
|
+
# and what the host said about it — are appended by the caller, because reading
|
|
3302
|
+
# the second costs a host round trip and the scan spends it only where it could
|
|
3397
3303
|
# change the answer. The rule reports which states those are; see the caller.
|
|
3304
|
+
# The caller appends the PR list's completeness last, once per run.
|
|
3398
3305
|
#
|
|
3399
3306
|
# THE DEFAULT BRANCH'S TIP IS READ ONCE PER RUN, not once per branch. It does
|
|
3400
3307
|
# not move while the scan runs — every fact below is derived from the ref batch
|
|
@@ -3743,26 +3650,14 @@ for plan in "${plans[@]}"; do
|
|
|
3743
3650
|
# is a judgment that belongs one layer up (Manifesto Principle 3).
|
|
3744
3651
|
plan_phase="${plan_meta_phases[$meta_i]}"
|
|
3745
3652
|
|
|
3746
|
-
#
|
|
3747
|
-
#
|
|
3748
|
-
#
|
|
3749
|
-
#
|
|
3750
|
-
#
|
|
3751
|
-
#
|
|
3752
|
-
|
|
3753
|
-
|
|
3754
|
-
# `Phase: Delivered` was drift the window must not hide; under the phase rule
|
|
3755
|
-
# that drift cannot be constructed, because there is no second place for the
|
|
3756
|
-
# answer to live. One source, so nothing to disagree.
|
|
3757
|
-
#
|
|
3758
|
-
# Two exits, and both matter:
|
|
3759
|
-
# * the record's date has aged out of the window — ordinary expiry;
|
|
3760
|
-
# * there is NO record — "no date, no row". `reconcile-scan-accuracy.md` is
|
|
3761
|
-
# the live example; showing it would create the one row that can never
|
|
3762
|
-
# age out of DONE.
|
|
3763
|
-
# Both leave before a single git call is spent on the plan's branches.
|
|
3764
|
-
if is_terminal_phase "$plan_phase"; then
|
|
3765
|
-
[ "${plan_meta_inwindow[$meta_i]}" = "1" ] || continue
|
|
3653
|
+
# THE RELEASE SCOPE, decided by the PHASE alone. A terminal plan stays in the
|
|
3654
|
+
# pulse only while it is `delivered`: its work has landed and its version has
|
|
3655
|
+
# not shipped, which is what DONE holds. `released`, `rejected` and
|
|
3656
|
+
# `superseded` leave, whatever their age, and leave before a single git call
|
|
3657
|
+
# is spent on their branches. No date bounds it, so a plan delivered weeks
|
|
3658
|
+
# before a release still names the release's contents.
|
|
3659
|
+
if is_terminal_phase "$plan_phase" && [ "$plan_phase" != "delivered" ]; then
|
|
3660
|
+
continue
|
|
3766
3661
|
fi
|
|
3767
3662
|
|
|
3768
3663
|
n_plans=$((n_plans + 1))
|
|
@@ -3860,12 +3755,17 @@ for plan in "${plans[@]}"; do
|
|
|
3860
3755
|
# deciding it — see pass 1c.
|
|
3861
3756
|
readings=""
|
|
3862
3757
|
order=""
|
|
3758
|
+
# THE ELEVENTH FIELD: whether the PR list held every PR (`.list-complete`,
|
|
3759
|
+
# written by `prefill_pr_states`). The rule reads a `NONE` for a ref behind
|
|
3760
|
+
# main as `open` only when it is true; a capped list may omit a merged PR.
|
|
3761
|
+
list_complete=false
|
|
3762
|
+
[ -n "$HOST_STATE_CACHE" ] && [ -f "$HOST_STATE_CACHE/.list-complete" ] && list_complete=true
|
|
3863
3763
|
while IFS=$'\t' read -r idx br deferred why waits wname claim; do
|
|
3864
3764
|
[ -n "$br" ] || continue
|
|
3865
3765
|
# "-" is the absent marker the shim writes, for the tab-collapse reason
|
|
3866
3766
|
# above. Normalized here so everything downstream tests emptiness.
|
|
3867
3767
|
[ "$waits" = "-" ] && waits=""
|
|
3868
|
-
readings+="$(branch_readings "$br" "$deferred") ${waits:--} ?"$'\n'
|
|
3768
|
+
readings+="$(branch_readings "$br" "$deferred") ${waits:--} ? $list_complete"$'\n'
|
|
3869
3769
|
order+="$idx $br $deferred $why ${waits:--} $wname $claim"$'\n'
|
|
3870
3770
|
done <<< "$wave_lines"
|
|
3871
3771
|
|
|
@@ -3920,7 +3820,8 @@ for plan in "${plans[@]}"; do
|
|
|
3920
3820
|
# list may legitimately omit: its plan may be delivered and its ref gone.
|
|
3921
3821
|
# `host_pr_state`'s run cache keeps this at one call per prerequisite per
|
|
3922
3822
|
# run, never one per pass.
|
|
3923
|
-
|
|
3823
|
+
# Field 10 is replaced; field 11 is carried.
|
|
3824
|
+
refill+="$(printf '%s' "$rd_line" | cut -f1-9) $(waits_pr_state "$waits_br") $(printf '%s' "$rd_line" | cut -f11)"$'\n'
|
|
3924
3825
|
else
|
|
3925
3826
|
refill+="$rd_line"$'\n'
|
|
3926
3827
|
fi
|