@plot-pm/board 0.9.1 → 0.11.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 +170 -164
- package/package.json +6 -1
- package/plot-agent-monitor.sh +508 -0
- package/plot-approve.sh +145 -29
- package/plot-budget.sh +439 -0
- package/plot-config.sh +9 -0
- package/plot-deliver.sh +210 -117
- package/plot-dispatch.sh +829 -185
- package/plot-fleet-scan.sh +577 -65
- package/plot-host.sh +1329 -55
- package/plot-plan-meta.sh +246 -36
- package/plot-reap.sh +675 -57
- package/plot-transcript-quiet.sh +142 -0
- package/plot-worker-monitor.sh +644 -0
- package/plot-worker-state.sh +81 -50
package/plot-deliver.sh
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
# run did the work or found it already done); 1 on a refusal or a
|
|
11
11
|
# failure, with the reason on stderr.
|
|
12
12
|
#
|
|
13
|
-
# WHY THIS EXISTS. The board computes `
|
|
13
|
+
# WHY THIS EXISTS. The board computes `allSlicesMerged` — exactly the condition
|
|
14
14
|
# that says a plan is ready to deliver — but the transition itself lives only in
|
|
15
15
|
# `/plot-deliver`'s prose. So `Delivered` in the board asked for a caller with
|
|
16
16
|
# nothing safe to call, and an implementer reaching that point would have
|
|
@@ -116,105 +116,64 @@ case "$phase" in
|
|
|
116
116
|
esac
|
|
117
117
|
|
|
118
118
|
# ---------------------------------------------------------------------------
|
|
119
|
-
# Step 2 —
|
|
119
|
+
# Step 2 — ask whether every non-deferred branch has merged
|
|
120
120
|
# ---------------------------------------------------------------------------
|
|
121
121
|
#
|
|
122
|
-
# This is one of Plot's four phase guardrails
|
|
123
|
-
# check the merge state of all branches. Any branch that is not MERGED and not
|
|
124
|
-
# deferred is a refusal.
|
|
125
|
-
|
|
126
|
-
# Parse branches from the plan file, respecting deferred annotations.
|
|
127
|
-
# Read the plan's branches section (from EITHER spelling — ## Branches or ## Waves).
|
|
128
|
-
plan_content=$(cat "$plan_file")
|
|
129
|
-
|
|
130
|
-
# Extract branch lines from ## Branches or ## Waves section
|
|
131
|
-
branches_section=$(printf '%s' "$plan_content" | sed -n '/^## *[Bb]ranches\|^## *[Ww]aves/,/^## /p')
|
|
132
|
-
|
|
133
|
-
# A BRANCH LINE CARRIES A BRANCH PREFIX, and without that test a changelog
|
|
134
|
-
# bullet is read as a branch. The section range above closes at the next `## `,
|
|
135
|
-
# but a plan whose `## Changelog` bullet mentions a backticked identifier —
|
|
136
|
-
# `impl`, `pr_ready`, `--migrate`, `/api/story` were the four measured on
|
|
137
|
-
# 2026-08-27 — hands one of those to the merge check, which then refuses
|
|
138
|
-
# delivery over a branch that does not exist and never will.
|
|
122
|
+
# This is one of Plot's four phase guardrails, and it is NO LONGER DECIDED HERE.
|
|
139
123
|
#
|
|
140
|
-
#
|
|
141
|
-
#
|
|
142
|
-
#
|
|
143
|
-
#
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
#
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
#
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
#
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
non_deferred_branches=""
|
|
169
|
-
|
|
170
|
-
for br in $all_branches; do
|
|
171
|
-
# Check both spellings: old style has branch on a `- \`branch\`` line,
|
|
172
|
-
# new style has branch in a `### ... (Branch: branch ...)` heading.
|
|
173
|
-
if printf '%s' "$branches_section" | grep -F "\`$br\`" | grep -q '<!-- *deferred' 2>/dev/null; then
|
|
174
|
-
deferred_branches="${deferred_branches}${br}
|
|
175
|
-
"
|
|
176
|
-
elif printf '%s' "$branches_section" | grep "Branch: $br" | grep -q '<!-- *deferred' 2>/dev/null; then
|
|
177
|
-
deferred_branches="${deferred_branches}${br}
|
|
178
|
-
"
|
|
179
|
-
else
|
|
180
|
-
non_deferred_branches="${non_deferred_branches}${br}
|
|
181
|
-
"
|
|
182
|
-
fi
|
|
183
|
-
done
|
|
124
|
+
# THE SCRIPT USED TO PARSE THE PLAN ITSELF. Ninety-odd lines of `sed` and `grep`
|
|
125
|
+
# re-derived the branches section, re-derived which lines named a branch, and
|
|
126
|
+
# re-derived which of those were deferred — a second implementation of a job
|
|
127
|
+
# `plot-plan-meta.sh` already owns, with its own bug history. The measured ones:
|
|
128
|
+
# a `## Changelog` bullet read as a branch (four fully-merged plans undeliverable,
|
|
129
|
+
# 2026-08-27), and a `## Slices` section that parsed to ZERO branches so the gate
|
|
130
|
+
# passed having checked nothing (two approved plans, 2026-08-30).
|
|
131
|
+
#
|
|
132
|
+
# A THIRD was measured while this block was being removed, and it is the one
|
|
133
|
+
# that argues hardest for the removal. On
|
|
134
|
+
# `docs/plans/2026-08-21-waves-name-themselves.md` the old range matched a
|
|
135
|
+
# `## Waves` heading that opens DESIGN PROSE — the plan argues for that spelling
|
|
136
|
+
# and illustrates it — and closed at the next `## `, never reaching the real
|
|
137
|
+
# `## Branches` section a hundred lines below. It read three branch names out of
|
|
138
|
+
# the illustration, one of them `bug/one` from inside a code fence, and reported
|
|
139
|
+
# none of the plan's actual work. `plot-plan-meta.sh` reads the same file
|
|
140
|
+
# correctly. That plan was delivered by hand.
|
|
141
|
+
#
|
|
142
|
+
# So the reading stays in the scripts and the DECISION moves inside:
|
|
143
|
+
# `plot-plan-meta.sh` says which branches the plan names and which it gave up,
|
|
144
|
+
# `plot-impl-status.sh` says which the host merged, and the domain's `deliver`
|
|
145
|
+
# rule says whether that makes the plan deliverable. Manifesto Principle 3 —
|
|
146
|
+
# scripts collect and report — with the line drawn where the design plan draws
|
|
147
|
+
# it: parsing a plan is adaptation, "these branches make the plan deliverable"
|
|
148
|
+
# is a decision.
|
|
149
|
+
#
|
|
150
|
+
# THE REFUSAL SENTENCE COMES BACK FROM THE DOMAIN, unchanged in wording, so the
|
|
151
|
+
# two cannot describe one rule differently.
|
|
184
152
|
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
# Get implementation status for all branches
|
|
190
|
-
impl_status=$(bash "$script_dir/plot-impl-status.sh" "$slug" 2>/dev/null) || impl_status='{"prs":[]}'
|
|
191
|
-
|
|
192
|
-
# Check each non-deferred branch is merged
|
|
193
|
-
unmerged_branches=""
|
|
194
|
-
for br in $non_deferred_branches; do
|
|
195
|
-
[ -z "$br" ] && continue
|
|
196
|
-
# Look for this branch in the impl status. A branch is merged if its PR state is MERGED.
|
|
197
|
-
pr_state=$(printf '%s' "$impl_status" | jq -r --arg br "$br" '.prs[] | select(.branch == $br) | .state' 2>/dev/null || true)
|
|
198
|
-
if [ "$pr_state" != "MERGED" ]; then
|
|
199
|
-
unmerged_branches="${unmerged_branches}${br}
|
|
200
|
-
"
|
|
201
|
-
fi
|
|
202
|
-
done
|
|
153
|
+
ask_mjs="$script_dir/board/plot-ask.mjs"
|
|
154
|
+
if [ ! -f "$ask_mjs" ]; then
|
|
155
|
+
die "cannot find $ask_mjs — run 'pnpm build:board' to build it."
|
|
156
|
+
fi
|
|
203
157
|
|
|
204
|
-
|
|
158
|
+
verdict=$(PLOT_REPO_ROOT="$repo_root" PLOT_SCRIPTS_DIR="$script_dir" \
|
|
159
|
+
node "$ask_mjs" deliverable "$slug" "$plan_file" 2>/dev/null) || verdict=""
|
|
160
|
+
[ -n "$verdict" ] || die "cannot determine deliverability of '$slug' — refusing rather than guessing."
|
|
205
161
|
|
|
206
|
-
|
|
207
|
-
unmerged_count=$(printf '%s\n' "$unmerged_branches" | wc -l | tr -d ' ')
|
|
208
|
-
unmerged_list=$(printf '%s' "$unmerged_branches" | tr '\n' ', ' | sed 's/, $//')
|
|
209
|
-
die "cannot deliver: $unmerged_count branch(es) not merged: $unmerged_list
|
|
210
|
-
Merge them first, or mark them deferred with \`<!-- deferred: <reason> -->\`."
|
|
211
|
-
fi
|
|
162
|
+
vfield() { printf '%s' "$verdict" | jq -r "$1" 2>/dev/null; }
|
|
212
163
|
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
[ -z "$non_deferred_branches" ] && merged_count=0
|
|
164
|
+
if [ "$(vfield '.deliverable')" != "true" ]; then
|
|
165
|
+
die "$(vfield '.refusal')"
|
|
166
|
+
fi
|
|
217
167
|
|
|
168
|
+
merged_count=$(vfield '.merged')
|
|
169
|
+
deferred_count=$(vfield '.deferred')
|
|
170
|
+
# Empty rather than `0`, so the suffix below stays absent where the old block
|
|
171
|
+
# left it absent — `${x:+...}` fires on any non-empty value, and "0 deferred"
|
|
172
|
+
# is a line no run has ever printed.
|
|
173
|
+
[ "$deferred_count" = "0" ] && deferred_count=""
|
|
174
|
+
if [ "$merged_count" = "0" ] && [ -z "$deferred_count" ]; then
|
|
175
|
+
echo "step: no branches found in plan — proceeding (nothing to verify)"
|
|
176
|
+
else
|
|
218
177
|
echo "step: verified $merged_count branch(es) merged${deferred_count:+, $deferred_count deferred}"
|
|
219
178
|
fi
|
|
220
179
|
|
|
@@ -268,8 +227,11 @@ rel=$(cd "$repo_root" && real_plan_path "$plan_file") || rel=""
|
|
|
268
227
|
plan_basename=$(basename "$rel")
|
|
269
228
|
|
|
270
229
|
# Flip `**Phase:** Approved` → `Delivered` in the `## Status` section only.
|
|
271
|
-
|
|
272
|
-
|
|
230
|
+
#
|
|
231
|
+
# READS ONE FILE AND WRITES ANOTHER, rather than editing in place. It edited in
|
|
232
|
+
# place until 2026-09-02, which is what let the phase land without its record —
|
|
233
|
+
# see write_transition() below.
|
|
234
|
+
flip_phase() { # $1=in $2=out → 0 if it changed the file, 1 if nothing to flip
|
|
273
235
|
awk '
|
|
274
236
|
BEGIN { section = ""; done = 0 }
|
|
275
237
|
/^## / { section = ($0 ~ /^## Status/) ? "status" : ""; print; next }
|
|
@@ -282,17 +244,35 @@ flip_phase() { # $1=file → 0 if it changed the file, 1 if nothing to flip
|
|
|
282
244
|
}
|
|
283
245
|
{ print }
|
|
284
246
|
END { exit (changed ? 0 : 1) }
|
|
285
|
-
' "$
|
|
286
|
-
local rc=$?
|
|
287
|
-
if [ "$rc" = 0 ]; then mv "$f.plot-tmp" "$f"; else rm -f "$f.plot-tmp"; fi
|
|
288
|
-
return "$rc"
|
|
247
|
+
' "$1" > "$2"
|
|
289
248
|
}
|
|
290
249
|
|
|
291
250
|
# Insert one `- **Delivered:** YYYY-MM-DD` line into the plan's `## Status` section.
|
|
292
251
|
# Fills the placeholder first; falls back to appending after the last list item.
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
252
|
+
#
|
|
253
|
+
# IT STOPS AT AN HTML COMMENT, and that is the whole of the fix made 2026-09-01.
|
|
254
|
+
# The plan template ends `## Status` with
|
|
255
|
+
#
|
|
256
|
+
# <!-- Transition records — written by the workflow commands, not by hand:
|
|
257
|
+
# - **Started:** <date>, <who>, <branch>
|
|
258
|
+
# -->
|
|
259
|
+
#
|
|
260
|
+
# and those `- **Started:**` lines ARE list items. The scan tracked the last one
|
|
261
|
+
# it saw, so on any plan whose `Started:` records had been filled in, the record
|
|
262
|
+
# was appended INSIDE the comment — inert, and invisible to
|
|
263
|
+
# `plot-plan-meta.sh`, which reported `delivered_raw: ""` for a plan that had
|
|
264
|
+
# just been delivered. Measured on `a-browser-test-serves-its-own-state`: phase
|
|
265
|
+
# flipped to Delivered, two `Delivered:` lines written into the comment (the
|
|
266
|
+
# script is idempotent on the PHASE, so a second run wrote a second line), and
|
|
267
|
+
# no readable record at all.
|
|
268
|
+
#
|
|
269
|
+
# A comment is where a plan keeps the shape of a record rather than a record, so
|
|
270
|
+
# the insertion point is the last list item BEFORE one — never inside.
|
|
271
|
+
#
|
|
272
|
+
# READS ONE FILE AND WRITES ANOTHER, for the reason flip_phase() gives.
|
|
273
|
+
append_delivered_line() { # $1=in $2=out $3=record
|
|
274
|
+
local line
|
|
275
|
+
line="- **Delivered:** $3"
|
|
296
276
|
awk -v line="$line" '
|
|
297
277
|
{ lines[++n] = $0 }
|
|
298
278
|
END {
|
|
@@ -304,8 +284,11 @@ append_delivered_line() { # $1=file $2=date
|
|
|
304
284
|
insert = start
|
|
305
285
|
for (i = start + 1; i <= n; i++) {
|
|
306
286
|
if (lines[i] ~ /^##[ \t]/) break
|
|
287
|
+
# An HTML comment ends the writable region. Checked BEFORE the
|
|
288
|
+
# placeholder arms so a commented-out `- **Delivered:**` template line
|
|
289
|
+
# is never mistaken for the slot to fill.
|
|
290
|
+
if (lines[i] ~ /<!--/) break
|
|
307
291
|
if (lines[i] ~ /^[ \t]*[-*][ \t]*\*\*Delivered:\*\*[ \t]*$/) { slot = i; break }
|
|
308
|
-
if (lines[i] ~ /^[ \t]*[-*][ \t]*\*\*Delivered:\*\*[ \t]*<!--/) { slot = i; break }
|
|
309
292
|
if (lines[i] ~ /^[ \t]*[-*][ \t]/) insert = i
|
|
310
293
|
}
|
|
311
294
|
|
|
@@ -315,8 +298,116 @@ append_delivered_line() { # $1=file $2=date
|
|
|
315
298
|
if (!slot && i == insert) print line
|
|
316
299
|
}
|
|
317
300
|
}
|
|
318
|
-
' "$
|
|
319
|
-
|
|
301
|
+
' "$1" > "$2"
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
# ---------------------------------------------------------------------------
|
|
305
|
+
# THE TRANSITION — one value, decided in the domain, written whole or not at all
|
|
306
|
+
# ---------------------------------------------------------------------------
|
|
307
|
+
#
|
|
308
|
+
# WHAT THIS CLOSES. The phase and the record were two independent steps until
|
|
309
|
+
# 2026-09-02: `flip_phase` edited the file, and `append_delivered_line` then
|
|
310
|
+
# edited it again. The second could fail — a plan with no `## Status` heading is
|
|
311
|
+
# its documented refusal — and the first had already landed. That is not
|
|
312
|
+
# theoretical: measured 2026-08-20, a plan carrying `Phase: Delivered` with no
|
|
313
|
+
# `Delivered:` line was filtered out of `plot-fleet-scan.sh` ENTIRELY, which
|
|
314
|
+
# reads its delivered window from `delivered_raw` — the record itself. The scan
|
|
315
|
+
# reported zero plans for it. A phase without its record does not make a plan
|
|
316
|
+
# half-delivered; it makes it invisible.
|
|
317
|
+
#
|
|
318
|
+
# SO THE TWO ARE ONE WRITE. The domain says so in a type: `transitions/plan.ts`
|
|
319
|
+
# gives `Decision` a required `phase` AND a required `record`, so a decision
|
|
320
|
+
# missing either does not compile. `plot-transition.mjs` carries that value out
|
|
321
|
+
# to here, and this function is the half that could still have broken it —
|
|
322
|
+
# both edits run against scratch copies, and the plan file is replaced only
|
|
323
|
+
# once both have succeeded.
|
|
324
|
+
#
|
|
325
|
+
# THE DOMAIN DECIDES, THIS PERFORMS. The phase word and the record's text come
|
|
326
|
+
# back from `plot-transition.mjs`; the awk that knows where a `## Status` line
|
|
327
|
+
# lives stays here, because that is adaptation and it carries bug history worth
|
|
328
|
+
# keeping (the HTML-comment fix above, 2026-09-01).
|
|
329
|
+
#
|
|
330
|
+
# IDEMPOTENCE IS UNCHANGED, and it is still the source that answers. The domain
|
|
331
|
+
# reads the phase and the record THIS SCRIPT PARSED FROM THE FILE IT IS ABOUT TO
|
|
332
|
+
# WRITE, and answers `already` when both are present — the same question the two
|
|
333
|
+
# steps asked separately, asked once. No progress file appears here, because a
|
|
334
|
+
# progress file is exactly what would disagree with the repository when somebody
|
|
335
|
+
# intervened by hand between two runs.
|
|
336
|
+
transition_mjs="$script_dir/board/plot-transition.mjs"
|
|
337
|
+
|
|
338
|
+
# Ask the domain for the transition, and refuse in its words.
|
|
339
|
+
#
|
|
340
|
+
# Called with the file's OWN parse rather than the caller's: on the booking-
|
|
341
|
+
# worktree flow those are different files, and the plan on the default branch is
|
|
342
|
+
# the one that counts.
|
|
343
|
+
decide_transition() { # $1=file → prints "<Phase>\t<record>\t<write|already>"
|
|
344
|
+
local f="$1" m answer rc
|
|
345
|
+
[ -f "$transition_mjs" ] \
|
|
346
|
+
|| { echo "plot-deliver: cannot find $transition_mjs — run 'pnpm build:board'." >&2; return 1; }
|
|
347
|
+
m=$(bash "$script_dir/plot-plan-meta.sh" "$f" 2>/dev/null) || m=""
|
|
348
|
+
[ -n "$m" ] || { echo "plot-deliver: cannot parse $f — refusing rather than guessing." >&2; return 1; }
|
|
349
|
+
answer=$(printf 'deliver\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t\t\t\n' \
|
|
350
|
+
"$slug" \
|
|
351
|
+
"$(printf '%s' "$m" | jq -r '.phase // ""')" \
|
|
352
|
+
"$(printf '%s' "$m" | jq -r '.review // ""')" \
|
|
353
|
+
"$(printf '%s' "$m" | jq -r '.approved_raw // ""')" \
|
|
354
|
+
"$(printf '%s' "$m" | jq -r '.delivered_raw // ""')" \
|
|
355
|
+
"$(printf '%s' "$m" | jq -r '.released_raw // ""')" \
|
|
356
|
+
"$today" \
|
|
357
|
+
| node "$transition_mjs" 2>&1)
|
|
358
|
+
rc=$?
|
|
359
|
+
# Exit 1 is the domain's refusal and its sentence, tab-separated after the
|
|
360
|
+
# rule that fired. Exit 2 is this script handing it something unreadable,
|
|
361
|
+
# which no operator can act on — so it reports as the bug it is.
|
|
362
|
+
if [ "$rc" != 0 ]; then
|
|
363
|
+
if [ "$rc" = 1 ]; then
|
|
364
|
+
echo "plot-deliver: $(printf '%s' "$answer" | cut -f2-)" >&2
|
|
365
|
+
else
|
|
366
|
+
echo "plot-deliver: $answer" >&2
|
|
367
|
+
fi
|
|
368
|
+
return 1
|
|
369
|
+
fi
|
|
370
|
+
printf '%s' "$answer"
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
# Apply the decided transition: the phase and the record, or neither.
|
|
374
|
+
#
|
|
375
|
+
# BOTH EDITS RUN AGAINST SCRATCH FILES and the plan is replaced by one `mv`.
|
|
376
|
+
# An `## Status` section that cannot take the record leaves the file exactly as
|
|
377
|
+
# it was found — INCLUDING ITS PHASE — so re-running is still the repair, and
|
|
378
|
+
# the half-state that made a plan invisible cannot be reached from here.
|
|
379
|
+
#
|
|
380
|
+
# `$3` says whether the file already carries the record. A plan can carry one
|
|
381
|
+
# while its phase lags — written by hand, or an earlier run cut between the two
|
|
382
|
+
# writes — and appending a second is how one plan came to hold two `Delivered:`
|
|
383
|
+
# lines (2026-09-01). The domain returns the written record unchanged in that
|
|
384
|
+
# case, so the phase still flips and nothing is inserted.
|
|
385
|
+
write_transition() { # $1=file $2=record $3=recorded(yes|no) → sets phase_report record_report
|
|
386
|
+
local f="$1" record="$2" recorded="$3" a="$1.plot-phase" b="$1.plot-record" flipped=0
|
|
387
|
+
|
|
388
|
+
if flip_phase "$f" "$a"; then flipped=1; else flipped=0; fi
|
|
389
|
+
# awk wrote `$a` either way; where nothing flipped it is a faithful copy, so
|
|
390
|
+
# the record still has a file to be inserted into.
|
|
391
|
+
[ -s "$a" ] || { rm -f "$a"; echo "plot-deliver: could not read $rel" >&2; return 1; }
|
|
392
|
+
|
|
393
|
+
if [ "$recorded" = "yes" ]; then
|
|
394
|
+
mv "$a" "$f" || { rm -f "$a"; return 1; }
|
|
395
|
+
record_report="already"
|
|
396
|
+
else
|
|
397
|
+
if ! append_delivered_line "$a" "$b" "$record"; then
|
|
398
|
+
rm -f "$a" "$b"
|
|
399
|
+
echo "plot-deliver: $rel has no '## Status' section — nowhere to record the delivery." >&2
|
|
400
|
+
echo " Nothing was written: the phase is not flipped either, because a phase" >&2
|
|
401
|
+
echo " with no record is invisible to the scan. Fix the section and re-run." >&2
|
|
402
|
+
return 1
|
|
403
|
+
fi
|
|
404
|
+
mv "$b" "$f" || { rm -f "$a" "$b"; return 1; }
|
|
405
|
+
rm -f "$a"
|
|
406
|
+
record_report="written"
|
|
407
|
+
fi
|
|
408
|
+
|
|
409
|
+
phase_report=$([ "$flipped" = 1 ] && echo flipped || echo already)
|
|
410
|
+
return 0
|
|
320
411
|
}
|
|
321
412
|
|
|
322
413
|
# Update the sprint item annotation for this plan.
|
|
@@ -387,21 +478,23 @@ apply_local_writes() { # $1=root → sets phase_report record_report index_repo
|
|
|
387
478
|
local root="$1" f="$1/$rel"
|
|
388
479
|
[ -f "$f" ] || { echo "plot-deliver: $rel is not present in $root" >&2; return 1; }
|
|
389
480
|
|
|
390
|
-
# Step 3 —
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
#
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
481
|
+
# Step 3 — THE TRANSITION: the phase and its record, together or not at all.
|
|
482
|
+
#
|
|
483
|
+
# ONE STEP WHERE THERE WERE TWO, which is the whole of this change. The domain
|
|
484
|
+
# decides what the two lines say and whether they are owed at all; this writes
|
|
485
|
+
# them as one replacement of the file. Already-done test: the domain answers
|
|
486
|
+
# `already` when the file it is about to write ALREADY carries both, which is
|
|
487
|
+
# the source asked directly — never a progress file.
|
|
488
|
+
local decided record action recorded
|
|
489
|
+
decided=$(decide_transition "$f") || return 1
|
|
490
|
+
record=$(printf '%s' "$decided" | cut -f2)
|
|
491
|
+
action=$(printf '%s' "$decided" | cut -f3)
|
|
492
|
+
recorded=$(printf '%s' "$decided" | cut -f4)
|
|
493
|
+
if [ "$action" = "already" ]; then
|
|
494
|
+
phase_report="already"
|
|
397
495
|
record_report="already"
|
|
398
496
|
else
|
|
399
|
-
|
|
400
|
-
record_report="written"
|
|
401
|
-
else
|
|
402
|
-
echo "plot-deliver: $rel has no '## Status' section — nowhere to record the delivery" >&2
|
|
403
|
-
return 1
|
|
404
|
-
fi
|
|
497
|
+
write_transition "$f" "$record" "$recorded" || return 1
|
|
405
498
|
fi
|
|
406
499
|
|
|
407
500
|
# Step 5 — move the index symlink (best effort).
|