Skip to content

ci(pm): retire the half-state patrol's schedule — on demand only (#19497) - #19516

Merged
os-project-manager merged 3 commits into
mainfrom
claude/issue-19497-patrol-schedule-retired
Sep 21, 2026
Merged

os-project-manager merged 3 commits into
mainfrom
claude/issue-19497-patrol-schedule-retired

Conversation

@os-project-manager

Copy link
Copy Markdown
Collaborator

Fixes #19497

Clause-②: no

Retires the half-state patrol's schedule under ruling #208 on #19491 (director seat, summon #25), direct-dispatch channel, on the maintainer's verbatim instruction:

「19491 接受你的建议,并立刻派发处理相关任务。」

R3 of that ruling, verbatim:

R3 the half-state patrol: ⛔ not frozen, ⛔ not trimmed; its schedule is retired (workflow_dispatch only, on demand for the triage seat's daily reconciliation; #19497) and the charter's 「锚行未处置 ⛔ 不开新派发」 precondition is deleted (#19496). No line of the 36,717 is edited.

and the maintainer's question it answers: 「巡查仪留着却不继续开发,会不会反而不停的报错误的信息」

What changed — two files, +89 / −45

1. .github/workflows/half-state-patrol.yml — the card's change:

  1. on: drops the schedule: block (37 1,7,13,19 * * *) and its cadence comment. workflow_dispatch: {} and the whole pull_request: paths trigger are kept byte-identical, so a PR editing the patrol still proves it on a real runner.
  2. The header prose that promised the cadence is rewritten, in seven places, to describe on-demand use and to carry the ⛔ against re-scheduling it on the old argument: the 「why a workflow」 section (kept as history, marked half-superseded), the check-half-states --probe reports PREREQUISITE NOT MET in a container that reaches the API — Node's fetch bypasses the agent proxy, and the refusal reads as the container's verdict #13544 correction paragraph, the Swept heartbeat paragraph, the new on: block prose, the concurrency note, the anchor-issue fallback note, and the citation census's HOW OFTEN / WHAT IT COSTS note.
  3. ⛔ Not touched, as ruled: .github/actions/half-state-patrol/action.yml and scripts/pm/check-half-states.mjs — 0 lines of its 36,717.

2. scripts/pm/dispatch-gates.mjs — a forced repair, outside the card's named surface, reported rather than smuggled:

Two cases of that tool's self-test read the live .github/workflows tree and assert that the specimen workflow declares schedule. Retiring the schedule makes both false. Measured before touching them, on the workflow-only commit: ✗ dispatch-gates self-test: 2 of 1883 case(s) failed, and the two are exactly those. pnpm check:pm-dispatch-gates is a required-context gate (Lint & Repo Gates) and scripts/ci/select-gate-families.sh runs the pm_dispatch_gates family for a modified path of class workflow (its *) arm returns 0), so the red is this PR's, on this PR.

The repair is the minimum that makes each case true again, and each keeps the fact it was always about: 'schedule' drops out of the trigger list one case asserts (it still pins that the specimen is not "a workflow no PR runs"), and && isScheduled('half-state-patrol.yml') drops out of the other (it still pins that the live sweep is withheld by the value-bearing class and nothing else). ⛔ Nothing is added: pinning the ABSENCE of a schedule would be a new ratchet, which ruling #208's R6 does not let this file grow without the maintainer's sentence. A comment above the pair records the ruling so the next reader does not restore the pin.

If the director seat would rather this file were not touched at all, the alternative is to land the sibling card first or to leave the PR red by design — flagged in the report's open_questions, not decided here.

No step, permission, job, timeout or input of the workflow changed. Parsed after the edit:

top keys: ['name', True, 'permissions', 'concurrency', 'jobs']
on: {'workflow_dispatch': {}, 'pull_request': {'paths': ['scripts/pm/check-half-states.mjs', 'scripts/invoked-as.mjs', 'scripts/pm/sweep-closed-cards.mjs', 'scripts/check-issue-citations.mjs', '.github/actions/half-state-patrol/**', '.github/workflows/half-state-patrol.yml']}}
jobs: ['patrol']
steps: ['Checkout repository', 'Setup Node.js', 'Sweep the board and update the anchor', 'Sweep the closed cards', "Census the repo's issue citations"]

Census — every other path into the patrol, file by file

Population: git grep -ni 'half.state' -- .github = 15 files at 2cac363. Dropping comment lines leaves four non-comment references outside the patrol's own two files:

.github/workflows/board-snapshot.yml:176:      - 'scripts/pm/check-half-states.mjs'
.github/workflows/lint.yml:1189:      - name: PM half-state sweeper self-test
.github/workflows/lint.yml:1190:        run: pnpm check:pm-half-states
.github/workflows/test-nightly-tiers.yml:442:            // scripts/pm/check-half-states.mjs. ⛔ Do not re-derive 65,536 from

and exactly one uses: of the action anywhere in the tree (half-state-patrol.yml:236, the local path).

file runs the patrol? scheduled? verdict
.github/workflows/half-state-patrol.yml yesuses: ./.github/actions/half-state-patrol was 37 1,7,13,19 * * * schedule removed here; the call stays, workflow_dispatch + pull_request stay
.github/actions/half-state-patrol/action.yml it is the patrol body composite action — declares no trigger of its own untouched by ruling
.github/workflows/board-snapshot.yml no — it runs scripts/pm/board-snapshot.mjs; its one non-comment reference is a pull_request: paths: entry, because the archiver imports the sweeper's proxy-re-exec plan. Its other three hits are prose (the request budget it shares, and the cron offset it chose against the patrol). yes, 7 2,8,14,20 * * * — its own archiver cadence keeps its schedule and its call: it is not a path into the patrol
.github/workflows/lint.yml nopnpm check:pm-half-states is the sweeper's OFFLINE --self-test (no network, no token, no board read, no anchor write), which lint.yml's own comment separates from the live sweep at length yes (hourly full run) keeps both; the self-test's PR-time scoping is the sibling card's, not this one's
.github/workflows/test-nightly-tiers.yml no — one // comment citing where a constant comes from yes untouched
.github/workflows/checklist-status.yml, .github/workflows/merged-branch-reaper.yml no — 0 hits for half.state; they were hits on a wider grep only for their own :37 cron minute yes (37 3 * * 1, 37 4 * * 1) untouched
closing-target-claim-guard.yml, cross-repo-issue-closer.yml, cut-rc.yml, merge-queue-triage.yml, platform-checklist-watchdog.yml, release-coverage-patrol.yml, required-set-patrol.yml, scheduled-full-run-card.yml, shard-timings-refresh.yml, stale.yml no — comment text only (cut-rc.yml is a false positive: "the doc half states below") varies untouched

So: one scheduled invocation of the patrol existed, and it is the one this PR retires. No other workflow loses a call or a schedule.

H57 — the card's expectation, measured and corrected

The card asks this PR to record H57 (「the workflow's latest scheduled run is green and fresh」) as reading the retired schedule as stale on every on-demand run. Measured against scripts/pm/check-half-states.mjs at 2cac363, that is not what happens:

  • h57Population() holds a workflow out of H57's population when it declares any of H57_PR_GATING_EVENTS = pull_request / pull_request_target / merge_group. half-state-patrol.yml declares pull_request and already did before this PR, so H57 has never judged it (kind: 'pr-gating') and could not report it stale.
  • After this PR the same workflow is held out one branch earlier, as kind: 'not-scheduled'. The only H57-visible change is in its unconditional population clause: one fewer workflow "declares a schedule", one fewer held out as PR-gating. No row is filed either way.

The reading that IS stale, and is deliberately left unrepaired here: renderCadenceExpectation() stamps expected every 6h (...) · next by … beside Swept in the anchor body on every run, computed from the sweeper's own PATROL_CRON / PATROL_CADENCE_HOURS constants — which this PR may not touch (「No line of the 36,717 is edited」). So the next on-demand run publishes a deadline against a schedule nobody fires. The workflow header now says so in place, and the one-line repair is reported as an out-of-scope finding rather than done here.

Acceptance

$ git grep -n 'cron' .github/workflows/half-state-patrol.yml ; echo "exit=$?"
exit=1                      # 0 hits (was 1 line on 2cac3636ca)
$ git grep -c 'workflow_dispatch' .github/workflows/half-state-patrol.yml
3                           # was 2 on 2cac3636ca

The card states the control as = 1; measured on origin/main the same grep answers 2 (the trigger key plus the citation census's cadence note), and this PR takes it to 3 because two rewritten comments name the dispatch path in prose. The load-bearing readings are the ones that cannot be moved by prose: the trigger key appears exactly once (line 174), and the parsed on: mapping above contains workflow_dispatch and no schedule.

Gates

Every family node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack derives for this diff was run, each exit code captured before any pipe (cmd > log 2>&1; code=$?), then reconciled against a fresh derivation:

$ node scripts/pm/dispatch-gates.mjs --ran RANFILE --repo objectstack-ai/objectstack
Run reconciliation — 48 derived, 48 run, 0 NOT-MEASURED, 0 UNRUN.
✓ dispatch-gates --ran: 48 derived famil(ies) accounted for — 48 run, 0 NOT-MEASURED
  (a DERIVED zero — all 48 recorded an exit code and none of them is 3).

47 of the 48 exited 0 in one detached battery. The 48th is pnpm check:pm-dispatch-gates — the one its own header says to run detached — and it is also the reverse-verification pair for the repair above, both legs measured from committed states:

before (51a65a3628, the workflow-only commit):
  ✗ dispatch-gates self-test: 2 of 1883 case(s) failed.
  ✗ ⭐ the card's own specimen declares a pull_request trigger beside its schedule — …
  ✗ the card's specimen is still discovered … — not withheld for being scheduled
after (dbeb16ddbb, with the two cases re-pointed):
  ✓ dispatch-gates self-test: 1883 cases pass.
  check:pm-dispatch-gates: the battery took 1031.6s on this box.      :: exit 0

Also run, as the dispatch asks:

$ bash scripts/ci/select-gate-families.selftest.sh                     :: exit 0
all 40 cases passed (192 checks)

actionlint is not wired in this repo (0 hits over .github/workflows/*.yml and package.json); the workflow lint here is the check:workflow-status-functions / check:workflow-step-name-quoting / check:node-version / check:required-contexts / check:ci-filter-parity / check:self-test-wired / check:stall-guard-* families, all inside the 48 and all green.

Stated rather than implied: the 54 artifact-roster families, the 11 wide-population families, the 14 pending-changeset families and the always-runs tail that the same tool prints OUTSIDE the derived 48 were not run here — CI owns those. The branch carries a merge of main at 48c39e0 and CI judges the merged generation.

Tier

Tier S in the card's words — measured on the FINAL file list, the register does not hold either path:

$ node scripts/pm/check-governed-merges.mjs --branch claude/issue-19497-patrol-schedule-retired
derived from `git diff --name-only --no-renames 48c39e0023 dbeb16ddbb` (three-dot): 2 path(s).
governed-surface predicate: 0 of 2 path(s) hit the register (6 surfaces, repo-agnostic).
  ✅  NOT governed — ordinary queue landing applies to a PR with exactly this file list.
  size: 134 changed line(s) (+89 / −45) ≤ 5000 — under the human-merge threshold

skip-changeset: one .github/workflows/** file and one scripts/pm/** file. Nothing under packages/**, nothing in any package's files[], nothing published.

Acceptance notes

Noted, not filed — stale cadence prose this PR's change makes inaccurate in files it does not own:

  • .github/workflows/lint.yml:4742 ("runs REPORT-ONLY in half-state-patrol.yml, four times a day"). Successor: the sibling card's dev, already in flight in that file.
  • .github/workflows/board-snapshot.yml:38,140 and .github/workflows/release-coverage-patrol.yml:102,113 — both cite the patrol's retired cron as the reason their own minute/hour was chosen. The offsets they bought stay correct; only the reason is now historical. Successor: none scheduled.
  • scripts/pm/check-half-states.mjs header prose ("a scheduled caller", four runs a day) — under the same ⛔ as its constants; not touched.

Generated by Claude Code

The patrol is report-only, and the dispatch charter made its anchor rows a
precondition for opening a dispatch, so a row misreading a now-legal state
cost every executive seat a handling step four times a day until a dev
repaired the row. Ruling 208 (batch letter 2, part R3) retires the schedule
and keeps the instrument: no line of the sweeper is edited, the workflow
stays runnable, and the triage seat dispatches it for its reconciliation.

- `on:` drops the `schedule:` block; `workflow_dispatch: {}` and the
  `pull_request:` paths trigger stay exactly as they were.
- The header prose that promised a four-times-a-day cadence, the `:37`
  offset, the `Swept` heartbeat reading, the concurrency note, the anchor
  fallback note and the citation census's cost note are rewritten for
  on-demand use, and carry the ⛔ against re-scheduling it silently.
- The generator's own cadence constants are untouched by ruling, so the
  anchor body will keep stamping a deadline against a schedule nobody
  fires; the header records that as a known, unread reading.

Claude-Session: https://claude.ai/code/session_012GcsUbuqFGBibkEDMRC1eE
Co-authored-by: Claude <noreply@anthropic.com>
…hedule

Removing the half-state patrol's schedule makes two cases of the
dispatch-gates self-test false: both read the live `.github/workflows` tree
and assert that the specimen workflow declares `schedule`. The gate runs on
this diff's class (the selector's workflow arm falls through to run), and it
is a required context, so the pin is repaired in the same change that makes
it false — measured red first: 2 of 1883 cases, and no others.

Each case keeps the fact it was always about: one pins that the specimen is
not "a workflow no PR runs" (the PR-time trigger), the other that the live
sweep is withheld by the value-bearing class. Nothing is added — pinning the
absence of a schedule would be a new ratchet, which the same ruling forbids
this file without the maintainer's word.

Claude-Session: https://claude.ai/code/session_012GcsUbuqFGBibkEDMRC1eE
Co-authored-by: Claude <noreply@anthropic.com>

Copy link
Copy Markdown
Collaborator Author

Contract review

Reviewed 2026-09-21T05:21Z · PR #19516 · card #19497 · shape per ruling #208 R5 (diff read + check-runs read; no derived gate family re-run locally, ⛔ not check:pm-dispatch-gates) · adversarial, independent of the dev.

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: dbeb16ddbb1032dfd15d69445969ff61fdc5e69a

Read first: card #19497 (body, claim 5755284628, os-dev-report 5755630773), the ruling record on #19491 (comment 5755284987, R3 and R6), the PR body, and the diff origin/main...dbeb16dd in a fresh worktree at d114d4c40b (merge-base 48c39e0023; the merge of main inside dbeb16dd brings 0 lines to either file). Files: .github/workflows/half-state-patrol.yml +80/−41, scripts/pm/dispatch-gates.mjs +9/−4 — exactly the two the PR lists.

① Derived judgments

1. Scope — RIGHT. git diff origin/main...dbeb16dd --name-status = exactly the two files. The dispatch-gates.mjs hunk is a pin repair and nothing else: case A drops 'schedule' from the event array it asserts against half-state-patrol.yml (now ['workflow_dispatch', 'pull_request'], same eventsOfWorkflow map read from the live tree); case B drops the one conjunct && isScheduled('half-state-patrol.yml') and keeps its other five (single-workflow family, reachesPRTime, notRunnable, !ciOnly). Two case titles reworded to match, one six-line comment recording ruling #208. Counted, not recalled: t( cases 807 → 807 on both sides (no case added or removed, so the battery total moves by 0); the bodies of declaredTriggerEvents, isScheduled and reachesPRTime are untouched, and isScheduled keeps five other callers (no dead binding for lint). No derivation logic changed, no new assertion, no roster edit. Both cases read the live .github/workflows tree, so the workflow edit alone reds them — the dev measured that on the workflow-only commit 51a65a3 and green on dbeb16d, a proper reverse-verification pair from committed states. Inside R6's freeze exception (a workflow it feeds broke it), as the director read it.

2. The workflow — RIGHT. YAML-parsed at the head: top keys name / on / permissions / concurrency / jobs; on: = {workflow_dispatch: {}, pull_request: {paths: [six entries]}}, the six paths byte-equal to origin/main's, no schedule key (on origin/main the same parse shows schedule: [{cron: '37 1,7,13,19 * * *'}]). git grep -n cron on the file = 0 hits (exit 1); control git grep -n workflow_dispatch = 3 — the key exactly once at line 176 (the PR body says 174 — a prose slip in the body, not in the diff) plus two prose mentions; the card's = 1 was already 2 on origin/main, so the reading that carries weight is the parsed mapping. One job, five steps, the same three uses: (actions/checkout@v7, actions/setup-node@v7, ./.github/actions/half-state-patrol), permissions and concurrency values unchanged. The seven rewritten prose blocks, read one by one: (i) the header 「Why a workflow」 — marked HALF SUPERSEDED, cites ruling #208 on objectstack#19491 executed by objectstack#19497, keeps the #9844 argument as history; (ii) the #13544 correction paragraph — 「Since ruling #208 that on-demand read is the only read there is」; (iii) the Swept paragraph — the stamp now 「dates the last time a seat ASKED」, ⛔ not a heartbeat, plus a KNOWN STALE READING block for the sweeper's cadence constants (item 4a); (iv) the on: block — ⛔ ON DEMAND ONLY, carries the maintainer's sentence 「19491 接受你的建议,并立刻派发处理相关任务。」 and a ⛔ against re-adding schedule: on the old argument; the 37 1,7,13,19 / :37 / four-runs reasoning survives only under 「WHAT THE RETIRED SCHEDULE WAS」, past tense, framed as the thing that went; (v) the concurrency note — two dispatches racing, no scheduled run; (vi) the anchor-fallback note — 「silently, on every run it is given」; (vii) the citation census HOW OFTEN / WHAT IT COSTS — once per workflow_dispatch plus the pull_request runs, ~159 requests per run, no standing daily draw. No block still promises a cadence; nothing left reads false. Two nits, neither a finding: line 3's headline still calls this file 「The standing caller」, the term the new prose (lines 27, 71, 167) now reserves for the retired clock-driven caller; and both Chinese quotes render the maintainer's full-width comma as ASCII — the scripts/pm convention on this tree (28 ASCII vs 2 full-width inside 「…」 quotes), so not a rewrite of the ruling, noted only because AGENTS.md asks for verbatim.

3. Census — RIGHT. git grep -n 'half-state-patrol' .github scripts at the head, outside the workflow and its action directory: every hit is comment prose except the dispatch-gates.mjs self-test lines 20664–20665 and 20720 (the two cases in item 1). The only uses: of the action anywhere in the tree is half-state-patrol.yml:274 (line 112 is a commented-out sibling example). File by file: board-snapshot.ymlschedule: 7 2,8,14,20 * * * + workflow_dispatch + pull_request paths; it runs scripts/pm/board-snapshot.mjs, and its one non-comment reference to the sweeper is a pull_request: paths: entry (line 176) — it does NOT run the patrol action, and keeps its schedule. checklist-status.yml37 3 * * 1, 0 hits for the patrol; not a caller (a :37 grep hit only). merged-branch-reaper.yml37 4 * * 1, 0 hits; not a caller. lint.yml — push / pull_request / merge_group / hourly schedule / workflow_dispatch; its hits at 196, 3458 and 4742 are comments, and line 1190 runs pnpm check:pm-half-states = node scripts/pm/check-half-states.mjs --self-test (package.json:82), the offline self-test — not the patrol action. So this workflow was the patrol action's only scheduled invocation, as the dev says, and no other file loses a call or a schedule. .github/actions/half-state-patrol/action.yml (blob c4f5abfcaf) and scripts/pm/check-half-states.mjs (blob e322a97c31, 36,717 lines) are byte-identical to origin/main.

4. The dev's two findings — both confirmed from the code. (a) renderCadenceExpectation(sweptAt, cron = PATROL_CRON) (sweeper line 19263) renders 「expected every ${PATROL_CADENCE_HOURS}h (cron ${cron} UTC) · next by …」 from PATROL_CADENCE_HOURS = 6 (line 21399) and PATROL_CRON = '37 1,7,13,19 * * *' (line 21416); the one production caller (line 20070) passes no cron, so every on-demand run stamps a six-hourly deadline nothing fires, and the same body (line 20074) tells its reader that a Swept sitting past its next by means the standing caller died — so every anchor read more than 6h after a dispatch will describe itself as stalled. A grep over scripts and .github finds no mechanical reader of that stamp outside the sweeper; the misread is a human one, and the workflow header now says so in place. Does it matter: enough to file (the dev lists it as to-file with a one-line shape — render the clause only when a schedule is passed in), not enough to block; the PATROL_CRON docblock's own 「change one and change the other in the same edit」 obligation is overridden here by R3's 「No line of the 36,717 is edited」. (b) h57Population() (line 13849) returns not-scheduled before it consults H57_PR_GATING_EVENTS = ['pull_request', 'pull_request_target', 'merge_group'] (line 13470), and pr-gating when any of those is declared; half-state-patrol.yml declared pull_request before this PR, so H57 held it out as pr-gating and never judged it — it could not have read the retired schedule as stale. After this PR the same workflow is held out one branch earlier (not-scheduled); the only visible change is the population summary (one fewer scheduledDeclared, one fewer scheduledGating; caller at line 22852). The card's item-3 expectation was simply wrong, and the dev measured it rather than obeying it.

5. Stale cadence prose in files this PR does not own — carriers for other cards, not this PR's FAIL. Confirmed at the head: .github/workflows/lint.yml:4742 (「four times a day」 — PR #19511's dev is in that file); board-snapshot.yml:38 (「4/day at :37 on hours 1,7,13,19」) and :140 (「first shipped at 37 1,7,13,19 — the half-state patrol's cron」); release-coverage-patrol.yml:102 (「Offset from half-state-patrol.yml's 37 1,7,13,19」) — :113, which the dev also lists, is about the pull_request posture and is still true. Carriers the dev's list does not name: scripts/pm/check-clause2-carriers.mjs:84 (「a standing caller … four times a day, last green」 with a dated stamp); scripts/check-issue-citations.mjs:142 (「report-only and scheduled」); scripts/render-release-coverage-anchor.mjs:63–65 (「fires every six hours … wrong by 4×」); scripts/check-partof-closing-keyword.mjs:169 (「between two six-hourly sweeps」); .github/actions/half-state-patrol/action.yml:143 (「four times a day」, a file this card forbids); and one that is behaviour rather than prose — scripts/pm/sweep-closed-cards.mjs:173–181: DEFAULT_WINDOW_HOURS = 72 is sized as 「Six-hourly patrol, twelve runs of slack」, so with the clock gone, a gap longer than three days between dispatches leaves the cards closed inside it counted-and-reported, never written; the ruling's 「on demand for the triage seat's daily reconciliation」 keeps that inside the window only as long as someone dispatches daily. That one belongs on the follow-up card beside 4(a).

7. Collision — RIGHT. PR #19511 (claude/issue-19498-self-tests-off-pr-path, head 9a1ca2b) changes .github/workflows/lint.yml, scripts/ci/select-gate-families.sh and scripts/ci/select-gate-families.selftest.sh — disjoint from this PR's two files. git merge-tree --write-tree 9a1ca2ba dbeb16dd → tree 7eedd9ac36, exit 0. Neither of this PR's paths carries merge=os-regen (git check-attr merge: unspecified), so the local probe answers the same question GitHub's mergeability does.

② Semver level

Carriers: none hung — skip-changeset is on the PR (labels: ci/cd, size/m, skip-changeset), nothing under packages/**, nothing in any published files[]. Clause-②: no. Not a semver surface.

③ Boundary flags

6. CI on the head, read from the check-runs REST endpoint (36 check-runs, polled 2026-09-21T05:21Z) — the seven required contexts:

  • TypeScript Type Check — completed, success (its four sub-jobs Type Check · workspace / source gates / consumer gates / debt ledger all success).
  • Test Core — completed, success (all six shards success).
  • Dogfood Regression Gate — completed, success (the required rollup; its three shards skipped by the paths filter).
  • Build Core — completed, skipped (paths filter — no package source in the diff).
  • Temporal Conformance (live PG + MySQL) — completed, skipped (same filter).
  • Governed Surface Queue Guard — completed, success.
  • Lint & Repo Gatesin_progress at the poll = NOT MEASURED, named here (37 of 188 steps completed, 0 non-success among them). Inside it, the one step this PR's second file bears on — PM dispatch-gates self-test, step 34 — is completed, success, 12 minutes of wall clock ending 2026-09-21T05:20Z, read from the jobs endpoint and ⛔ not re-run here; PM half-state sweeper self-test (step 44) was still pending. The landing waits for this context's own conclusion.

Check Changeset success with skip-changeset on the PR. Advisory but relevant: this PR's own Live half-state sweep run on the pull_request trigger reached 「Sweep the board and update the anchor」 success and 「Sweep the closed cards」 success at the poll — the edited workflow parses and runs on a real runner.

Tier: node scripts/pm/check-governed-merges.mjs --branch claude/issue-19497-patrol-schedule-retired prints, verbatim: 「derived from git diff --name-only --no-renames 48c39e0023 dbeb16ddbb (three-dot): 2 path(s).」 · 「governed-surface predicate: 0 of 2 path(s) hit the register (6 surfaces, repo-agnostic).」 · 「✅ NOT governed — ordinary queue landing applies to a PR with exactly this file list.」 · 「size: 134 changed line(s) (+89 / −45) ≤ 5000 — under the human-merge threshold」. Tier S in the card's words (the director seat lands it after this record and green contexts); the register holds neither scripts/pm/** nor .github/**.

Governed surfaces touched: none. Release act: none. Review's own writes: REST only, no MCP GitHub tool; one write, this comment; no label, ready flip, auto-merge, body edit or issue.

Implemented-by: claude/issue-19497-patrol-schedule-retired
Reviewed-by: session_012GcsUbuqFGBibkEDMRC1eE

VERDICT: PASS


Generated by Claude Code

Copy link
Copy Markdown
Collaborator Author

Provenance — director seat, summon #25 (session_012GcsUbuqFGBibkEDMRC1eE), 2026-09-21T05:23Z

  • Contract review of record: 5755780744, Served-tier: CONTRACT_REVIEW_TIER, judged head dbeb16ddbb1032dfd15d69445969ff61fdc5e69a (the current head), VERDICT: PASS. The reviewer's transcript re-read by this seat: every request stamped at the tier constant, zero fallbacks. Independence pair Implemented-by: claude/issue-19497-patrol-schedule-retired (the dev was this seat's mode:subagent, direct dispatch under ruling 🔗 Broken links detected in documentation #208 R3 on [Decision] 仪器与吞吐的取舍:哪些闸门该硬、达档复核保留到什么程度 #19491) / Reviewed-by: session_012GcsUbuqFGBibkEDMRC1eE (an isolated reviewer subagent whose verdict this seat adopts verbatim).
  • What the reviewer re-derived first-hand (R5 shape: diff + the head's check-runs; the 17-minute check:pm-dispatch-gates battery was NOT re-run locally — its CI step on this head read completed success): the workflow's on: at the head parses to workflow_dispatch: {} plus the pre-existing pull_request paths trigger and no schedule; the seven rewritten prose blocks describe on-demand use and cite the ruling; the census — this workflow was the only scheduled invocation of the patrol action in the tree, and .github/actions/half-state-patrol/action.yml and scripts/pm/check-half-states.mjs (36,717 lines) are byte-identical to origin/main; the scripts/pm/dispatch-gates.mjs hunk is pin-only (two self-test cases that read the live workflow tree and asserted the specimen declares schedule; case count 807 → 807, no derivation logic changed, no assertion added) — inside R6's freeze exception 「touched only when a workflow it feeds breaks」, and this seat adopts the dev's option A (keep the pin repair in the PR that falsified the pin).
  • Two readings the review corrected on the card: H57 never judged this workflow (it declares pull_request, so h57Population() held it out before the schedule check) — the card's item 3 expectation was wrong, and nothing changes there; renderCadenceExpectation() (check-half-states.mjs:19263, PATROL_CRON / PATROL_CADENCE_HOURS = 6) will keep stamping 「expected every 6h · next by …」 in the anchor body on every on-demand run — no mechanical reader outside the sweeper, so it is a misleading line to a human, not a defect that moves a decision; under ruling 🔗 Broken links detected in documentation #208 R2 (report-only instruments earn no dev) it is recorded here, not filed. Sibling readings for the triage seat: scripts/pm/sweep-closed-cards.mjs DEFAULT_WINDOW_HOURS = 72 is sized for a six-hourly patrol, so the on-demand run should be dispatched at least every three days or given a wider --window — the daily full reconciliation covers it; stale cadence prose survives in lint.yml:4742, board-snapshot.yml:38,140, release-coverage-patrol.yml:102, check-clause2-carriers.mjs:84, check-issue-citations.mjs:142, render-release-coverage-anchor.mjs:63-65, check-partof-closing-keyword.mjs:169, action.yml:143 — carriers for whoever next touches those files, ⛔ not cards.
  • Carriers: none hung (Clause-②: no); skip-changeset on the PR. Tier S: check-governed-merges --branch → 0 of 2 paths on the register, 134 lines. Merge-tree against PR ci(lint): tooling self-tests run on a PR only when their own inputs change (#19498) #19511's head 9a1ca2ba exit 0.
  • Landing: Tier S — this seat lands it (ready + auto-merge squash) once 「Lint & Repo Gates」 on dbeb16dd reads success (in progress at this write; PM dispatch-gates self-test inside it already success) and --pair 19516 exits 0. After merge: landing record on ci(pm): retire the half-state patrol's schedule — on demand only; every scheduled invocation of the patrol action stops (ruling #208 on #19491) #19497 and close; the charter half (the anchor is no longer a dispatch precondition) lands with PR docs(pm-dispatch): instrument discipline — hard gates answer definite questions only; C5 and the patrol anchor stop blocking; at-tier review scoped to what ships and read from CI (#19496) #19513.

Generated by Claude Code

@os-project-manager
os-project-manager marked this pull request as ready for review September 21, 2026 05:31
@os-project-manager
os-project-manager added this pull request to the merge queue Sep 21, 2026
Merged via the queue into main with commit d76facf Sep 21, 2026
38 checks passed
@os-project-manager
os-project-manager deleted the claude/issue-19497-patrol-schedule-retired branch September 21, 2026 06:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ci/cd size/m skip-changeset PR has no user-facing published change; bypasses the changeset gate

Projects

None yet

Development

Successfully merging this pull request may close these issues.

ci(pm): retire the half-state patrol's schedule — on demand only; every scheduled invocation of the patrol action stops (ruling #208 on #19491)

2 participants