Skip to content

Commit 7078fc0

Browse files
committed
tooling(pm): mechanize the closed-card residue strip as a patrol step
A card GitHub closes on a merged `Fixes` pull request keeps every label, so the pm-loop state labels stay behind as claims that work is in flight. The seat has been removing them by hand, once per landing. `scripts/pm/sweep-closed-cards.mjs` takes that stroke: it lists the closed cards carrying H22's residue set (imported, never restated), establishes that the close really was a merged delivery by one of two routes -- a closing commit contained in the default branch, or a pull request whose body closes the card and merged into it -- and removes exactly the residue labels, leaving ownership, priority and outcome untouched. Each stripped card gets one comment naming the readings taken. The action set is bounded to the fresh edge by a window, because the 2026-08-31 maintainer ruling refused a backfill over the archived stock while keeping close-time hygiene as the convention. `--all-time` exists so a seat can measure that stock in a dry run; the workflow never passes it. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019RfFHiRCSs3JXLK4cwcfox
1 parent f7db8f4 commit 7078fc0

3 files changed

Lines changed: 1346 additions & 0 deletions

File tree

.github/workflows/half-state-patrol.yml

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,10 @@ on:
149149
# place that has to name it. (The adopted objectui copy carries this row
150150
# already; upstream is catching up to its own port.)
151151
- 'scripts/invoked-as.mjs'
152+
# The closed-card sweep this workflow also calls (#16005). Same reasoning
153+
# as the row above, one file along: a step whose script can change without
154+
# this trigger firing is a step whose PR-time proof is a coincidence.
155+
- 'scripts/pm/sweep-closed-cards.mjs'
152156
- '.github/workflows/half-state-patrol.yml'
153157

154158
# Least privilege: this job reads the repo and writes exactly one issue BODY.
@@ -248,6 +252,74 @@ jobs:
248252
echo "check-half-states exited $code"
249253
cat "$RUNNER_TEMP/report.err" >&2 || true
250254
255+
- name: Sweep the closed cards
256+
id: closed-cards
257+
# #16005 — the pm-loop state labels are CLAIMS that work is in flight,
258+
# and GitHub leaves every label in place when a merged `Fixes` pull
259+
# request closes a card. The seat was paying a hand round trip per
260+
# landing to remove them (eighteen identical ones in one measured
261+
# shift). This step is that stroke, mechanized; the script's header
262+
# carries the ruling it obeys and the window that keeps it to
263+
# close-time hygiene rather than the backfill the 2026-08-31 maintainer
264+
# ruling refused.
265+
#
266+
# ⛔ Gated on the repository NAME, unlike every other step in this file:
267+
# this one is objectstack-only until a sibling has both its patrol
268+
# anchor (objectui#5986) and a copy of the script. A verbatim copy of
269+
# this workflow in a sibling repo therefore SKIPS this step rather than
270+
# failing on a missing file.
271+
#
272+
# ⛔ This step never fails the job, whatever the sweep returns. The
273+
# anchor write below is the patrol's product and must not be starved by
274+
# a step that runs before it; the alarm rides an annotation and the run
275+
# summary instead. Findings are not a failure condition here either —
276+
# the same posture the sweep step above takes.
277+
if: github.repository == 'objectstack-ai/objectstack'
278+
env:
279+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
280+
# Same wiring, and the same reason, as the live sweep above: the board
281+
# this run acts on is the repo this workflow is installed in.
282+
PM_SWEEP_REPO: ${{ github.repository }}
283+
PROVENANCE: >-
284+
posted by half-state-patrol [run ${{ github.run_id }}](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }})
285+
· trigger `${{ github.event_name }}`
286+
# A pull_request run PROVES the step — the transport, the flags and
287+
# the rendering on a real runner — and writes nothing, exactly as the
288+
# anchor write below is skipped for it. That convention is this file's
289+
# and it is load-bearing: a PR must never write to the board.
290+
SWEEP_MODE: ${{ github.event_name == 'pull_request' && '--dry-run' || '--write' }}
291+
run: |
292+
set +e
293+
node scripts/pm/sweep-closed-cards.mjs "$SWEEP_MODE" --provenance="$PROVENANCE" \
294+
> "$RUNNER_TEMP/closed-cards.md" 2> "$RUNNER_TEMP/closed-cards.err"
295+
code=$?
296+
set -e
297+
# Captured with NO pipe in between, for the reason the step above
298+
# states at length: piped, `$?` is the pipe's status and a red run and
299+
# a green one read the same.
300+
echo "exit_code=$code" >> "$GITHUB_OUTPUT"
301+
{
302+
echo "### Closed-card sweep — exit $code (\`$SWEEP_MODE\`)"
303+
echo
304+
echo '```'
305+
cat "$RUNNER_TEMP/closed-cards.md" 2>/dev/null || echo '(no report produced)'
306+
echo '```'
307+
echo
308+
echo '<details><summary>stderr</summary>'
309+
echo
310+
echo '```'
311+
cat "$RUNNER_TEMP/closed-cards.err" 2>/dev/null || true
312+
echo '```'
313+
echo
314+
echo '</details>'
315+
} >> "$GITHUB_STEP_SUMMARY"
316+
cat "$RUNNER_TEMP/closed-cards.err" >&2 || true
317+
if [ "$code" = "3" ]; then
318+
echo "::error::closed-card sweep exited 3 — it could NOT read the board, so it says nothing about whether residue is accumulating. See this run's summary."
319+
elif [ "$code" != "0" ]; then
320+
echo "::warning::closed-card sweep exited $code — at least one card was left UNJUDGED. An unjudged card is not a clean card; see this run's summary."
321+
fi
322+
251323
- name: Resolve the anchor issue
252324
# An install with no anchor configured has nowhere to land its report,
253325
# and the ONLY safe behaviour is to say so loudly (#11217). The two

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@
7575
"check:declared-population-live": "node scripts/check-declared-population-live.mjs --self-test && node scripts/check-declared-population-live.mjs",
7676
"check:pm-half-states": "node scripts/pm/check-half-states.mjs --self-test",
7777
"check:pm-clause2-carriers": "node scripts/pm/check-clause2-carriers.mjs --self-test",
78+
"check:pm-closed-card-sweep": "node scripts/pm/sweep-closed-cards.mjs --self-test",
7879
"check:pm-governed-merges": "node scripts/pm/check-governed-merges.mjs --self-test",
7980
"check:pm-governed-prose": "node scripts/pm/check-governed-prose.mjs --self-test && node scripts/pm/check-governed-prose.mjs",
8081
"check:publish-smoke-pin": "node scripts/publish-smoke-pack.mjs --self-test",

0 commit comments

Comments
 (0)