Skip to content

ci: fold the four daily patrols into one fleet-patrols.yml workflow (1 of 3) - #26512

Merged
os-zhuang merged 1 commit into
mainfrom
claude/project-thread-31axox
Oct 27, 2026
Merged

os-zhuang merged 1 commit into
mainfrom
claude/project-thread-31axox

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Requested by Warren · project thread
Part of #26071
Clause-②: no
Metadata-protocol: none
Ruling-ref: Ruling: batch #397 item 2 · letter A · maintainer 「同意」 — comment 6416984681 on #26071 (director seat, 2026-10-26T13:18Z). This PR is (ii) of letter A, the first of three serial PRs from one card thread: (ii) the four daily patrols → one workflow (this), then (iii) the four PR-body guards → one workflow, then (i) the lint.yml schedule skip. The card closes with the last one. Head branch claude/project-thread-31axox is the card thread's harness push branch (the measured platform exception to claude/issue-N-slug; the next two PRs use -2 and -3).

维护者速读

  • 改了什么:四个日巡逻 workflow(required-set-patrol、platform-checklist-watchdog、release-coverage-patrol、board-snapshot)合为一个 .github/workflows/fleet-patrols.yml,四个 job 各守自己原来的 cron;每个 job 的 steps、permissions、concurrency、env 与原文件解析后逐项相同(脚本比对,见 Tests)。读这些文件名的两个门禁(check-required-contexts 的 PATROL_WORKFLOW、watchdog 静态 pin 的文件名与 pull_request 路径白名单)、锚点渲染器发布的文案、归档器的 adopt 清单、两份 checklist 文档改指新文件。
  • 为什么改:裁决 A(批 Define GitHub organization architecture for 60+ repository microkernel ecosystem #397 第 2 项,「同意」)的 (ii):四个文件各自 checkout + setup、各占一个 workflow;合为一文件四 job 省文件不省信号 —— 失败信号、最小权限、并发组都留在 job 级,没有一项上提到 workflow 级。
  • 风险与回滚:纯搬运,不改任何判定逻辑。风险两处:① 每个 job 的 if: 必须命中自己的 cron —— github.event.schedule 与 on.schedule 里的字符串是同一事实写两遍,文件头注已标 ⛔ 同改;② pull_request 触发是四个原过滤器的并集,所以改 scripts/pm/board-snapshot.mjs 之类的 PR 现在也跑 watchdog job(不阻塞:它不是 required context,checklist 红也不红 job)。回滚 = revert 本 PR,四个原文件原样回来。
  • 先看:① fleet-patrols.yml 四个 job 的 if:(各 job name: 下方三行)与 on.schedule 的四条 cron;② scripts/check-platform-checklist-watchdog.mjs 的 PR_TRIGGER_ALLOWED_PATHS(冻结的并集)与 clause 4 文案;③ scripts/check-required-contexts.mjs 的 PATROL_WORKFLOW。
  • 两分钟验证:node scripts/check-platform-checklist-watchdog.mjs --self-test && node scripts/check-platform-checklist-watchdog.mjs && pnpm check:required-contexts —— 三条 OK 行;落地后在 Actions 页对「Fleet Patrols」点 Run workflow,patrol 选 required-set-patrol,应只跑那一个 job,其余三个 skipped。
  • 需要你做的:Approve(Tier H,.github/workflows/**);席位随后落地。

What changes

File by file, against the retired workflow(s).

.github/workflows/fleet-patrols.yml (new) ← required-set-patrol.yml, platform-checklist-watchdog.yml, release-coverage-patrol.yml, board-snapshot.yml (deleted)

  • on.schedule — the four crons, each under the retired file's own cron comment, re-read on this tree and equal to the card's measurement: 23 4,16 * * * (required-set), 51 2 * * * (watchdog), 19 4 * * * (release-coverage), 7 2,8,14,20 * * * (board-snapshot).
  • on.workflow_dispatch — one patrol choice input (all by default, or a job id) so a manual fire or smoke test can target one patrol, as each retired file allowed, without spending the archiver's 800-request budget to smoke-test the watchdog.
  • on.pull_request.paths — the union of the four retired filters, each retired file's own path replaced by the new file: .github/workflows/fleet-patrols.yml, scripts/check-required-contexts.mjs, scripts/check-release-section-coverage.mjs, scripts/pm/board-snapshot.mjs, scripts/pm/check-half-states.mjs, scripts/invoked-as.mjs. A matching pull request runs all four jobs; none writes anything on a pull_request event (every board write keeps its github.event_name != 'pull_request' guard, which the watchdog pin judges across every job of the file).
  • No workflow-level permissions:, concurrency: or env:. Each job carries the retired file's blocks at job level, unchanged:
    • required-set-patrol (display name Live required-set sweep, was job sweep): permissions: {contents: read}, concurrency group required-set-patrol-${{ github.event.pull_request.number || github.ref }}, timeout-minutes: 10, 5 steps.
    • platform-checklist-watchdog (Platform checklist gate sweep, was watchdog): {contents: read, issues: write}, group platform-checklist-watchdog, env ANCHOR_IDENTITY_LABEL / ANCHOR_TITLE / ANCHOR_MARKER / ANCHOR_ROUTING_LABELS / PATROL_SCHEDULE, timeout-minutes: 15, 5 steps.
    • release-coverage-patrol (Release section-coverage sweep, was patrol): {contents: read, issues: write}, group release-coverage-patrol, env.ANCHOR_ISSUE (the folded scalar, same relative indentation), timeout-minutes: 10, 8 steps.
    • board-snapshot (Snapshot the board to the archive branch, was archive): {contents: write, issues: read, pull-requests: read}, group board-snapshot, timeout-minutes: 15, 9 steps.
  • One addition per job, a job-level if: — (github.event_name != 'schedule' || github.event.schedule == 'ITS CRON') && (github.event_name != 'workflow_dispatch' || inputs.patrol == 'all' || inputs.patrol == 'ITS JOB ID'). A scheduled run executes exactly one job (the other three read as skipped); a dispatch runs the one the input names; a pull_request run exercises every job. Job ids are the retired file stems; the job display names — the check-run names — are unchanged, so the Checks tab reads as before.
  • Comments — each retired file's header is carried whole as the comment block above its job (headed formerly .github/workflows/FILE), and a new file header explains the four clocks, the dispatch input, the union filter and where the retired runs' history is. Four comment edits beyond transport, each because the sentence described the retired file-level shape: (watchdog) "this file carries … paths-filtered to THIS FILE AND NOTHING ELSE" → the union, frozen in the pin; (watchdog) "fires only on a pull request editing this workflow" → "… or a sibling patrol's own script", with why that blocks nothing; (board-snapshot) the adopt list's "this file" → "the board-snapshot job of it; a repo that wants only the archiver deletes the three sibling jobs and their crons"; (release-coverage) one blank comment line between the carried "Nightly…" and "Two readers, one fact" paragraphs.
  • History of the retired files (hypothesis 4): GitHub keeps a deleted workflow's runs — the Actions sidebar keeps listing each retired workflow by name while runs of it exist, and GET /actions/workflows/FILE.yml answers with state: deleted. New history lands under "Fleet Patrols", one job per patrol; open a run and read its job list, or filter the run list by job name, to follow one patrol.

scripts/check-required-contexts.mjs

  • PATROL_WORKFLOW: 'required-set-patrol.yml' → 'fleet-patrols.yml'; the header sentence that names the standing caller names the job. The wiring battery of --self-test now reads the new file: no other workflow wires --verify-required-set, the file declares no merge_group, no REQUIRED_CONTEXTS row names it, and the drift annotation keys on the ✅ clean mark — all green. REQUIRED_CONTEXTS itself is untouched (ci.yml, lint.yml, governed-surface-guard.yml only).

scripts/check-platform-checklist-watchdog.mjs

  • WORKFLOW_REL → .github/workflows/fleet-patrols.yml.
  • Clause 4's allowed list PR_TRIGGER_ALLOWED_PATHS is the six-entry frozen union above, still compared as a SET, so widening the workflow's filter by one path without the same reviewed edit here reds exactly as before. The refusal text, the --list / OK lines and the header say so, and a new header section ("Why the allowed list is more than one entry") records the reason and what the clause keeps. The GOOD fixture and PR_BLOCK carry the six entries; the widened-by-one and swapped-for-another-file cases still fire, and one case is added — the list minus one sibling input fires on the missing half (clause 4 battery: floor 10, 11 cases; 43 assertions across 8 batteries).
  • One stale header sentence ("The watchdog carries no pull_request: trigger, by clause 3") corrected to clause 4's reality — it contradicted the clause it sat under since the 2026-09-06 ruling.

scripts/render-release-coverage-anchor.mjs

  • The anchor body's published preamble named .github/workflows/release-coverage-patrol.yml as the writer; it names the release-coverage-patrol job of fleet-patrols.yml. Two comments likewise. Declared: a file the order's surface did not name. The string is published to the anchor issue every night, and this rename made it false; os-dev's rule that a change must fix the published text it falsifies applies. Its --self-test pins no file name (17 assertions, green).

scripts/pm/board-snapshot.mjs

  • The adopt list's workflow entry: .github/workflows/board-snapshot.yml → fleet-patrols.yml (its board-snapshot job). Declared on the same ground: a comment-only line, but it is the adopt instruction whose own text forbids shortening it from memory. --self-test green (169 cases).

docs/qa/platform-checklist/README.md, docs/qa/platform-checklist/RUNNER.md

  • The three lines that name the retired watchdog file name the job of the new file; nothing else.

Not touched, by the order's surface: half-state-patrol.yml and governed-surface-guard.yml (not named by the ruling); REQUIRED_CONTEXTS; lint.yml; scripts/pm/check-expected-skips.mjs — its roster names only pr-automation.yml, pack-smoke-optin.yml, upgrade-harness-optin.yml and ci.yml rows, no patrol job, so there is nothing to re-point (hypothesis 2 measured).

Why

The ruling (letter A, (ii)): one file per concern, four jobs on their existing crons, each keeping its own failure signal and minimal permissions. This PR is the transport and nothing else — no judgement logic, no cadence, no grant changes.

Tests

Transport proof — a scratchpad script parsed fleet-patrols.yml and each retired file at git show d0a7e748:.github/workflows/FILE with the yaml package and deep-compared them: every job's steps, permissions, concurrency, env, name, runs-on and timeout-minutes equal the retired file's; on.pull_request.paths equals the union of the four retired filters with each file's own path renamed; four crons, 4 of 4; workflow-level permissions absent; triggers schedule, workflow_dispatch, pull_request; jobs required-set-patrol, platform-checklist-watchdog, release-coverage-patrol, board-snapshot. Verdict line: TRANSPORT CHECK OK.

  • node scripts/check-platform-checklist-watchdog.mjs --self-test → exit 0 — OK check-platform-checklist-watchdog self-test: 43 assertions across 8 floored batteries, every clause driven by a fixture that makes it fire.
  • node scripts/check-platform-checklist-watchdog.mjs → exit 0 — OK .github/workflows/fleet-patrols.yml: schedule + workflow_dispatch present, pull_request_target / merge_group absent, any pull_request trigger paths-filtered to this file and the sibling patrol jobs' own inputs alone, no board write reachable from a pull_request run, invoked through pnpm check:platform-checklist.
  • Reverse verification of the pin against the live file (fix committed first; restore by git checkout HEAD -- FILE, proven by git hash-object equality and an empty git diff HEAD): with docs/qa/platform-checklist/** injected after scripts/invoked-as.mjs in on.pull_request.paths (on disk: 1 injected line; a parse shows 7 entries) the gate exits 1 — … whose paths: filter also names docs/qa/platform-checklist/**.; restored, exit 0. A first attempt appended the line at the end of the file, which YAML read as a seventh step of the last job rather than a path, so the gate stayed 0: that reading was a no-op and is discarded; the anchored injection is the measurement.
  • pnpm check:required-contexts → exit 0 — the self-test's wiring battery reads fleet-patrols.yml: the only wirer of --verify-required-set, no merge_group, no REQUIRED_CONTEXTS row names it, the drift annotation keys on ✅.
  • node scripts/render-release-coverage-anchor.mjs --self-test → exit 0 (17 assertions pass); node scripts/pm/board-snapshot.mjs --self-test → exit 0 (169 cases pass across 13 batteries).
  • Not owed: no package test / typecheck (the diff touches no package source, so ① and ② are empty); test:repo, pnpm lint and the whole-tree scans are CI's. Dogfood battery: n/a — no ! in the title and no (narrowing) arm. No ablation: no tested behaviour changes; the workflow and the two gate scripts are proven by their self-tests and the reverse verification above.

Acceptance notes

  • The one behaviour change of the transport, stated: a workflow has one pull_request: trigger, so the watchdog job now also runs on pull requests that edit a sibling patrol's script (scripts/check-required-contexts.mjs, scripts/check-release-section-coverage.mjs, scripts/pm/board-snapshot.mjs, scripts/pm/check-half-states.mjs, scripts/invoked-as.mjs), where before its filter named its own file alone. Those are patrol-tooling pull requests, a few a month; the job publishes no required context and never reds on checklist drift (it reds only when the gate recorded no exit code), so the standing decision's purpose — no unrelated PR blocked by checklist drift — holds. The letter is kept by the pin: the path list is frozen as a set in PR_TRIGGER_ALLOWED_PATHS. The alternative — a fifth routing job (dorny/paths-filter) with needs: + always() on every patrol job — was not taken: the ruling says four jobs, and the if: subtlety it adds is the transport risk the card names.
  • Stale mentions left in place (comment-only, outside the order's surface; carrier: none — a docs-only sweep if the seat wants one): .github/workflows/lint.yml:2505 (the checklist NOTE names platform-checklist-watchdog.yml as the channel), merged-branch-reaper.yml:238, shard-timings-refresh.yml:217, test-nightly-tiers.yml:44, scripts/check-platform-checklist.mjs:88,488, scripts/check-stall-guard-budget.mjs:457 (a measured job-id-reuse reading: patrol was reused across half-state-patrol.yml and release-coverage-patrol.yml; after this PR it is not), scripts/ci/board-snapshot-r2-upload.sh:4, scripts/import-prerequisite.mjs:235, scripts/sync-release-index-currency.mjs:50, scripts/pm/dispatch-gates.mjs:329,1840-1842 and dispatch-gates.self-test.mjs:672,1044 (measurement narratives naming the files as measured). docs/audits/gate-census-2026-09.md names the retired files with line numbers as a dated census and is left as the reading it was. ui/content/docs/guide/ci-cd-pipeline.md and ui/scripts/dependabot-merge-gate.mjs describe objectui's own board-snapshot.yml, not this repo's.
  • actionlint is not wired in this repository (no reference under .github/, package.json or scripts/); the workflow was parsed with the yaml package by the transport check and by the two pins.
  • Seat: claim amendment — the file surface widens by scripts/render-release-coverage-anchor.mjs and scripts/pm/board-snapshot.mjs (one line each), both declared above.

Verification

Derived with node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack on head b33a799 (11 paths vs merge base d0a7e74; 2,905 changed lines, under the 3,000 human-merge threshold): 62 families. Each ran in this worktree with the exit code captured before any pipe (bash -c "$cmd" > log 2>&1; st=$?), then reconciled:

  • node scripts/pm/dispatch-gates.mjs --ran ran.list --repo objectstack-ai/objectstack → exit 0 — ✓ dispatch-gates --ran: 62 derived famil(ies) accounted for — 62 run, 0 NOT-MEASURED (a DERIVED zero — all 62 recorded an exit code and none of them is 3).
  • pnpm check:pm-dispatch-gates → exit 0 — fast tier, 1935 cases, 352 s on this box (check:pm-dispatch-gates: the battery (the fast tier) took 352.0s on this box.).
  • pnpm --filter @objectstack/lint run check:doc-formula-expressions first refused (exit 3: @objectstack/formula and @objectstack/lint not built); after pnpm exec turbo run build --filter=@objectstack/formula --filter=@objectstack/lint under scripts/pm/os-verify-lock.sh (slot issue-26071: VERDICT command-exit 0 · held the lock 97s) it exits 0, and the ran list records that re-run.
  • actionlint: not wired in this repository.
  • REQUIRED_CONTEXTS untouched: scripts/check-required-contexts.mjs still names only ci.yml, lint.yml and governed-surface-guard.yml.
  • CI on the PR is not waited for (the order forbids it); the seat reads the check-runs. Every reading above was taken on head b33a799; no commit followed.
All 62 derived families, exit 0 each (wall seconds shown where ≥ 10 s)
  • node scripts/check-adr-links.mjs → exit 0
  • node scripts/check-adr-links.mjs --self-test → exit 0
  • node scripts/check-aggregator-roster.mjs → exit 0
  • node scripts/check-aggregator-roster.mjs --self-test → exit 0
  • node scripts/check-ci-filter-parity.mjs → exit 0
  • node scripts/check-closing-keyword-parity.mjs → exit 0
  • node scripts/check-closing-keyword-parity.mjs --self-test → exit 0
  • node scripts/check-comment-mask-corpus.mjs → exit 0 (154 s)
  • node scripts/check-declaration-mirrors.mjs → exit 0
  • node scripts/check-declaration-mirrors.mjs --self-test → exit 0
  • node scripts/check-platform-checklist-watchdog.mjs → exit 0
  • node scripts/check-platform-checklist-watchdog.mjs --self-test → exit 0
  • node scripts/check-release-section-coverage.mjs → exit 0
  • node scripts/check-release-section-coverage.mjs --strict → exit 0
  • node scripts/check-scripts-symbol-anchors.mjs → exit 0
  • node scripts/check-scripts-symbol-anchors.mjs --self-test → exit 0
  • node scripts/check-self-test-wired.mjs → exit 0
  • node scripts/check-self-test-wired.mjs --self-test → exit 0
  • node scripts/check-self-test-workflow-commands.mjs → exit 0 (106 s)
  • node scripts/check-self-test-workflow-commands.mjs --self-test → exit 0
  • node scripts/check-step-collectors.mjs → exit 0
  • node scripts/check-step-collectors.mjs --self-test → exit 0
  • node scripts/check-whole-set-label-write.mjs → exit 0
  • node scripts/check-whole-set-label-write.mjs --self-test → exit 0
  • node scripts/ci/plan-test-core-shards.mjs --self-test → exit 0
  • node scripts/docs-audit/check-drift-comment.mjs → exit 0
  • node scripts/pm/bare-root-worklist.mjs --self-test → exit 0 (28 s)
  • node scripts/pm/board-snapshot.mjs --self-test → exit 0
  • node scripts/pm/ci-failure.mjs --self-test → exit 0
  • node scripts/render-release-coverage-anchor.mjs --self-test → exit 0
  • pnpm --filter @objectstack/lint run check:doc-formula-expressions → exit 0 (re-run after the formula/lint build; first run exit 3)
  • pnpm check:agent-test-spelling → exit 0
  • pnpm check:bash32-floor → exit 0
  • pnpm check:cli-command-ids → exit 0 (16 s)
  • pnpm check:cross-package-test-inputs → exit 0 (35 s)
  • pnpm check:declared-population-live → exit 0 (34 s)
  • pnpm check:doc-authoring → exit 0
  • pnpm check:driver-memory-census → exit 0
  • pnpm check:entry-guard → exit 0 (39 s)
  • pnpm check:gitlink-declared → exit 0
  • pnpm check:node-version → exit 0
  • pnpm check:nul-bytes → exit 0
  • pnpm check:parse-guard → exit 0
  • pnpm check:platform-checklist → exit 0 (28 s)
  • pnpm check:pm-board-snapshot → exit 0
  • pnpm check:pm-dispatch-gates → exit 0 (354 s)
  • pnpm check:pm-expected-skips → exit 0
  • pnpm check:pm-governed-merges → exit 0
  • pnpm check:pm-post-stamped → exit 0
  • pnpm check:pm-record-recognisers → exit 0
  • pnpm check:pnpm-acquisition → exit 0
  • pnpm check:pnpm-filter-targets → exit 0
  • pnpm check:pnpm-flag-spellings → exit 0
  • pnpm check:ratchet-remedy-authority → exit 0
  • pnpm check:refd-timer-probe → exit 0 (21 s)
  • pnpm check:required-contexts → exit 0
  • pnpm check:shard-attestation → exit 0
  • pnpm check:stall-guard-budget → exit 0
  • pnpm check:stall-guard-headroom → exit 0
  • pnpm check:watch-hint-literal → exit 0
  • pnpm check:workflow-status-functions → exit 0
  • pnpm check:workflow-step-name-quoting → exit 0

Generated by Claude Code

required-set-patrol, platform-checklist-watchdog, release-coverage-patrol
and board-snapshot become four jobs of .github/workflows/fleet-patrols.yml.
Each job keeps the retired file's steps, permissions, concurrency group
and env byte-for-byte; a job-level `if:` gates it to its own cron
(github.event.schedule), workflow_dispatch takes a `patrol` input to fire
one job by hand, and the pull_request trigger is the union of the four
retired filters.

The two gates that read those files by name follow the rename:
check-required-contexts' PATROL_WORKFLOW and the platform-checklist
watchdog pin, whose allowed pull_request path list is now the frozen
union (its self-test drives the widened and the narrowed shapes). The
anchor renderer's published preamble, the archiver's adopt list and the
two platform-checklist docs name the new file.

Claude-Session: https://claude.ai/code/session_01JQwxbW8nFvKeVjgKv8RTwH
Co-authored-by: Claude <noreply@anthropic.com>
@objectstack-fleet objectstack-fleet Bot added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Oct 27, 2026
@github-actions github-actions Bot added size/xl ci/cd documentation Improvements or additions to documentation labels Oct 27, 2026
@os-zhuang
os-zhuang marked this pull request as ready for review October 27, 2026 06:10
@os-zhuang
os-zhuang enabled auto-merge October 27, 2026 06:10
@os-zhuang
os-zhuang added this pull request to the merge queue Oct 27, 2026
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Reviewed at 2026-10-27T06:22Z · PR #26512 · card #26071, ruling A step (ii) · independent contract-review subagent, adversarial by design (not the dispatch order's, the seat's or the dev's conclusions).

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: b33a799608cad76990ba945fc6c437dd22abe5f8
Local-runs: probe — one YAML deep-compare of each retired file's job (plus that file's workflow-level permissions / concurrency / env) against the matching fleet-patrols.yml job, over git show blobs of d0a7e74 and b33a799; nothing built, no gate re-run, the head's check-runs are the gate verdicts
Scope: .github/workflows/board-snapshot.yml, .github/workflows/fleet-patrols.yml, .github/workflows/platform-checklist-watchdog.yml, .github/workflows/release-coverage-patrol.yml, .github/workflows/required-set-patrol.yml + scripts/check-platform-checklist-watchdog.mjs, scripts/check-required-contexts.mjs, scripts/render-release-coverage-anchor.mjs, scripts/pm/board-snapshot.mjs, docs/qa/platform-checklist/README.md, docs/qa/platform-checklist/RUNNER.md, scripts/pm/check-expected-skips.mjs (roster), scripts/pm/dispatch-gates.mjs (DERIVATION_SURFACE, L329 comment), .github/workflows/lint.yml (the lanes that run each gate; L2505), scripts/check-partof-closing-keyword.mjs and .claude/agents/os-dev.md L273/L323 (body spelling), main's e5131cf (#26488, the seat addendum)

① Derived judgments

(a) Transport, per job — every difference named. The PR's base in the object is 73683ee; its merge-base with main is d0a7e74, and the four retired files are byte-identical at d0a7e74 and at origin/main 14f3099, so the compare is against what main holds. For all four pairs (sweep → required-set-patrol, watchdog → platform-checklist-watchdog, patrol → release-coverage-patrol, archive → board-snapshot) the fleet job equals the retired job plus the retired file's workflow-level blocks, with ONE added key: permissions (read / read+issues:write / read+issues:write / contents:write+issues:read+pull-requests:read), concurrency (groups and cancel-in-progress: false), env (watchdog's five keys incl. PATROL_SCHEDULE: '51 2 * * *'; release-coverage's ANCHOR_ISSUE), runs-on, timeout-minutes (10/15/10/15), name and every step in order with its own if: are equal — steps 6 / 6 / 8 / 9. The retired files declared permissions / concurrency / env at WORKFLOW level; the fleet file has no workflow-level permissions, concurrency or env and carries each block at JOB level — the narrowing direction, equivalent for a one-job file, nothing widened (hypothesis 1 holds). The differences:

  1. The job-level if: (two conjuncts): github.event.schedule == 'CRON' where each CRON matches its on.schedule entry byte-for-byte (23 4,16 * * *, 51 2 * * *, 19 4 * * *, 7 2,8,14,20 * * *), and inputs.patrol == 'all' || inputs.patrol == 'JOB-ID' behind github.event_name != 'workflow_dispatch'. github.event.schedule is the cron that fired a schedule run; the inputs context is legal in a job-level if:; on pull_request both conjuncts short-circuit true. So a cron fires exactly its own job, a dispatch fires the one named (default all), a matching PR runs all four — correct. ⚠️ The twin strings are two spellings of one fact and nothing pins them: a cron edited on one side only is silent (the file header says so). Precision to-do for the seat, below.
  2. Job ids become the retired file stems; the display names (= check-run names) are unchanged, so the Checks tab and any reader keyed on them read as before (hypothesis 3 holds at the job level). Reading, not a defect: the RUN-level signal (Actions list, failure-notification subject) now says Fleet Patrols for all four; a reader opens the run to see which patrol failed.
  3. on.workflow_dispatch gains the patrol choice input (all | one job id). Beyond the ruling's letter ("four jobs on their existing crons"), declared as deviation 6 and offered for striking; it is harmless — the default reproduces each retired file's manual fire, and it is what keeps a hand-fired watchdog from spending the archiver's budget. Accepted as is.
  4. on.pull_request.paths is the exact element-wise union of the four retired filters with each file's own path replaced by the new one (six entries). Per job the pull_request leg runs MORE, never less: each job used to fire on its own 1–4 paths and now fires on all six, so a PR editing scripts/pm/board-snapshot.mjs also runs the live required-set sweep, the release-coverage sweep and the watchdog gate, and a PR editing scripts/check-required-contexts.mjs also runs the capped board-snapshot dry-run. No pull_request run can write: the guard github.event_name != 'pull_request' is on every writing step by my per-step read (watchdog step 5; release-coverage steps 4 and 7; board-snapshot steps 4, 6 and 7), unchanged from the retired files. Live evidence: workflow run 40459028676 (Fleet Patrols, event pull_request) on this head is green with all four jobs (Live required-set sweep, Platform checklist gate sweep, Release section-coverage sweep, Snapshot the board to the archive branch).
  5. Hypothesis 4 (history): the file header documents that GitHub keeps a deleted workflow's runs and where new history lands; no script fetches runs by the retired file names (every remaining mention is a comment or a dated census — ③).

(b) scripts/check-platform-checklist-watchdog.mjs (+62/−25) — why larger than a re-point, and did its reading change meaning. WORKFLOW_REL re-pointed; PR_TRIGGER_ALLOWED_PATHS goes from one entry to the six-entry union, still compared as a SET in both directions (extra and missing); the GOOD fixture and PR_BLOCK carry the six; one case added (the list minus one sibling input fires on the missing half); the refusal / --list / OK texts and the header say so; the stale header sentence ("carries no pull_request: trigger, by clause 3", contradicting clause 4) corrected. Clauses 1–3, 5 and 6 are untouched; clause 4 is widened exactly to the union and frozen; clause 4b (judgeBoardWrites) already iterated every job and now covers four. One over-claim in the PR body: its markers are only issues.create( / update( / createComment( / addLabels( in run: / with.script, so the board-snapshot job's git push and R2 upload steps are NOT judged by the pin (they carry the guard by my read, not by the gate). The dev's "report step restates the cron" is PATROL_SCHEDULE, carried unchanged and equal to the job's cron (watchdog 51 2 * * *, release-coverage 19 4 * * * at the Render step). Gate verdict: Lint · docs & code guards (runs the pin's --self-test and the gate) green on the head.

(c) scripts/check-required-contexts.mjs and the seat addendum. Against the merge-base the hunk is +3/−3: PATROL_WORKFLOW 'required-set-patrol.yml' → 'fleet-patrols.yml' and one docblock sentence. REQUIRED_CONTEXTS (86 lines: 5 ci.yml, 2 lint.yml, 1 governed-surface-guard.yml rows) is byte-identical at d0a7e74, b33a799 and origin/main 14f3099 — the ruling's ⛔ holds. The import { shardTotal } removal and the (1/N) → (1/3) / /8 rewrites the addendum saw in git diff origin/main b33a7996 are a two-dot artefact, not the PR's: e5131cf (#26488, "read the shard sizes from ci.yml's matrix blocks instead of copying them") added that import and the shardTotal(sources['ci.yml'], 'test') derivation in battery (9) on main AFTER this branch forked; the PR does not touch those lines, so the three-way merge keeps main's derivation (GitHub reads mergeable: true). No live derivation was replaced by a literal and no required-context name is hard-coded by this PR — not the #26439 class. Gate verdict: Lint · CI, release & engine gates (runs pnpm check:required-contexts and pnpm check:ci-filter-parity) green on the head; on the merged tree the wiring battery will read fleet-patrols.yml through the re-pointed constant.

(d) The rest of the diff. README.md (2 lines), RUNNER.md (1), scripts/pm/board-snapshot.mjs (1, the adopt list), scripts/render-release-coverage-anchor.mjs (3: the published anchor preamble string and two comments — a published text the rename would have falsified, rightly in) — name changes only; the renderer's self-test pins no file name.

(e) Gates that read workflow files by name — lane and verdict on the head. check:required-contexts and check:ci-filter-parity (the latter reads only ci.yml): Lint · CI, release & engine gates ✅. check:pm-expected-skips: its roster names only pr-automation.yml, pack-smoke-optin.yml, upgrade-harness-optin.yml and ci.yml rows — no patrol row to re-point (hypothesis 2 holds): Lint · repo & tooling gates ✅. check:pm-dispatch-gates (DERIVATION_SURFACE holds .github/workflows): Lint · PM dispatch-gates ✅. The watchdog pin: Lint · docs & code guards ✅. check:stall-guard-budget: Type Check · source gates ✅. actionlint: zero hits under .github/, package.json and scripts/ on origin/main — not wired, the dev's claim confirmed. Governed Surface Queue Guard ✅ twice.

(f) The PR body against the files. Line 1 the harness attribution comment, line 2 the Requested-by line, line 3 Part of #26071: os-dev.md L273 prescribes Part of #N for a PR that must not close its card and L323 keeps the harness comment on line 1; the guard's recogniser (check-partof-closing-keyword.mjs) reads Part of — the order's Part-of is the guard's name, not a body spelling — and both guards are green on the head (Part-of PR must not also close its card, The card this PR closes must claim this branch). Clause-②: no and Metadata-protocol: none line-initial (lines 4–5); Ruling-ref: names comment 6416984681; the six 速读 labels in order, 需要你做的 = Approve (one action, Tier H); file-by-file narrative against each retired file; What changes / Why / Tests / Acceptance notes / Verification. 2,905 changed lines by the PR object (1,499 + 1,406, 11 files), under the 3,000 threshold; labels skip-changeset, ci/cd, documentation, size/xl; assignee os-warren. Deviations between the body and the files (none load-bearing): the body says "5 steps" for required-set-patrol and for platform-checklist-watchdog — both have 6 (the fail-the-run step is the sixth); "the retired file's blocks at job level, unchanged" — the retired files carried them at workflow level (the move is the right direction); "every board write … which the watchdog pin judges across every job" — see (b).

(g) Report 6435073406 and the claim. status: done, branch: claude/project-thread-31axox = the claim's (6434226733) branch, head b33a799, mcp_calls: 0, api_writes: 2, deviations (8), out_of_scope_findings (2), open_questions: []; the key set matches os-dev.md's template (it has no files_changed / rules_retired key). The claim amendment the dev asked for (the two one-line scripts) is declared in the body's Acceptance notes.

Check-runs on b33a799 read twice: at 2026-10-27T06:19Z 66 check-runs, none failed, two still in progress (Lint UI, Test Core (1/8)); at 2026-10-27T06:21Z 67 check-runs, none in progress, none failed — every gate named above is a completed success. Approval: os-zhuang, APPROVED on b33a799 at 2026-10-27T06:10Z; the PR is no longer a draft; mergeable_state: clean at my last read.

② Semver level

No published package is touched (.github/**, scripts/**, docs/** only); skip-changeset is right and Check Changeset is green; Clause-②: no / Metadata-protocol: none line-initial, matching the claim. Correct.

③ Boundary flags

  1. Card candidate — stale prose naming the retired files (comment-only, not a defect; the dev's out-of-scope note 1, confirmed by git grep on the head): .github/workflows/lint.yml:2505 (governed; the checklist NOTE also still says the watchdog's filter names its own file ALONE — now false), merged-branch-reaper.yml:238, shard-timings-refresh.yml:217, test-nightly-tiers.yml:44, scripts/check-platform-checklist.mjs:88,488, scripts/check-stall-guard-budget.mjs:457 (the measured job-id-reuse reading this PR makes stale), scripts/ci/board-snapshot-r2-upload.sh:4, scripts/import-prerequisite.mjs:235, scripts/sync-release-index-currency.mjs:50, scripts/pm/dispatch-gates.mjs:329,1840-1842, scripts/pm/dispatch-gates.self-test.mjs:672,1044. docs/audits/gate-census-2026-09.md is a dated census and stays; ui/content/docs/guide/ci-cd-pipeline.md and ui/scripts/dependabot-merge-gate.mjs describe objectui's own board-snapshot.yml. PR 3's surface is lint.yml's schedule leg only, so the governed line is a sweep card, not PR 3. Dedupe words: fleet-patrols stale mention retired workflow name.
  2. Card candidate — pin the cron twins. Nothing asserts that each job's if: names a cron that on.schedule declares and vice versa; a one-sided edit is silent by design of Actions. A small battery in the watchdog pin (it already parses the file) or a sibling self-test closes it. Dedupe words: fleet-patrols cron if twin pin.
  3. 收班简报 notes: the run-level failure signal now reads Fleet Patrols (①(a)2); the pull_request union runs all four jobs on any of six paths, so patrol-tooling PRs now also spend the capped board-snapshot dry-run and a live required-set sweep (①(a)4); the patrol dispatch input is accepted as a benign addition beyond the ruling's letter.
  4. Base vs main: base 73683ee, origin/main 14f3099 (two commits ahead, fix(scripts): ADR-0087 gate reads an any index signature (D8 p4) and a src/index.tsx entry (D9) #26496 and refactor(plugin-grid): move ObjectGrid's query inputs and grouping decisions into sibling hooks (slice 4) #26499, neither touching the PR's paths), merge-base d0a7e74; main's ci(scripts): read the shard sizes from ci.yml's matrix blocks instead of copying them #26488 edit to scripts/check-required-contexts.mjs merges cleanly with this PR's +3/−3 (①(c)). No rebase owed.

Implemented-by: claude/project-thread-31axox
Reviewed-by: session_01LqKsGyMNFwwnbF5FTAqyPn

VERDICT: PASS

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

维护者速读

改了什么:按你对 #26071 的裁决 A 第 (ii) 步,四个日巡逻 workflow(required-set-patrol、platform-checklist-watchdog、release-coverage-patrol、board-snapshot)合成一个 .github/workflows/fleet-patrols.yml,四个 job 各守原来的 cron(23 4,16、51 2、19 4、7 2,8,14,20),每个 job 带自己的 permissions:/concurrency:/env:(从原文件的 workflow 级搬到 job 级,只收不放)和一个按 cron 分流的 if:,步骤逐条相同;七个文件里点名旧文件名的句子改名(check-required-contexts.mjs 的 REQUIRED_CONTEXTS 集合逐字节不变);watchdog 自检的第 4 条按冻结的六路径集放宽。
为什么改:四个 workflow 各自 checkout + setup,一天约 36 次定时触发里占四份;合并后同样的四个信号、一份文件、少三次环境搭建。
风险与回滚:只动 workflow 与点名它们的脚本/文档,无运行时改动;回滚 = revert 本 PR(一个提交)。复核用 YAML 深比对核过:job 体与原文件相同,无权限放宽;变化点是 pull_request 腿现在按六条路径的并集触发四个 job(写步骤仍有 event_name != 'pull_request' 守卫),以及 run 级名字统一为 Fleet Patrols(job 级信号不变)。
先看:① fleet-patrols.yml 四个 job 的 if: 与 on.schedule 的 cron 对子;② scripts/check-required-contexts.mjs 三行(只改 PATROL_WORKFLOW 常量与一句注释);③ scripts/check-platform-checklist-watchdog.mjs 第 4 条自检。
两分钟验证:PR 上的 Fleet Patrols run(pull_request 事件)四个 job 全绿;node scripts/check-required-contexts.mjs 与 check:platform-checklist-watchdog 自检绿。
需要你做的:无 —— os-zhuang 已于 06:10Z 批准;本记录落后席位布自动合并。
席位意见:同意落地。两张候选卡归席位:(a) 还有十余处散文点名已退役的 workflow 文件名(lint.yml:2505 等),单独立清扫卡,不混进第 3 步的 lint 定时 PR;(b) 没有自检钉住「每个 job 的 if: cron ⇔ on.schedule 条目」,单边改动会静默。第 (iii) 步(四个 PR 正文守卫合一)待本 PR 合并后同线程开。

席位核对

ACCEPT · domain:skills#2 · session_01LqKsGyMNFwwnbF5FTAqyPn · 2026-10-27T06:24Z — seat review of record for #26071 step (ii) on head b33a7996, against GitHub, not the report's own account. Governed surface .github/workflows/** ⇒ Tier H (2,905 changed lines, under the 3,000 human-merge threshold): the record of this tier is the independent ## Contract review at comment 6435281545 (Served-tier: CONTRACT_REVIEW_TIER, head b33a7996, VERDICT: PASS, Local-runs: probe — one YAML deep-compare, declared; drafted by a reviewer other than the implementer, the card thread's dev). This seat adds the landing read.

  1. Shape. Not draft, base main, mergeable: true (clean), one commit; body lines 1–2 the harness attribution block, line 3 Part of #26071 (the card closes on step (iii)+(i)'s last PR; the spelling os-dev.md L273/L323 and the closing-keyword guards accept, both green), Clause-②: no, Metadata-protocol: none line-initial, Ruling-ref: naming 6416984681; ## 维护者速读 six labels in order, then the file-by-file diff narrative / Tests / Acceptance notes / Verification. Files: four workflows removed, fleet-patrols.yml added (1,427 lines), check-required-contexts.mjs +3/−3 (PATROL_WORKFLOW + one docblock sentence against the merge-base d0a7e748; the two-dot diff against main's tip shows ci(scripts): read the shard sizes from ci.yml's matrix blocks instead of copying them #26488's shardTotal lines, which the PR does not touch and the three-way merge keeps), check-platform-checklist-watchdog.mjs +62/−25 (clause 4 of its pin widened to the frozen six-path set, clauses 1–3, 5, 6 unchanged in meaning), docs/qa/platform-checklist/README.md + RUNNER.md, scripts/pm/board-snapshot.mjs, scripts/render-release-coverage-anchor.mjs (name changes, one line each — the claim amendment the dev reported). Labels documentation, ci/cd, size/xl, skip-changeset. Branch claude/project-thread-31axox, named by the claim 6434226733.
  2. Record, measured. The reviewer deep-compared each fleet job against its retired file: steps 6/6/8/9 identical including per-step if: guards; workflow-level permissions: / concurrency: / env: moved to job level (narrowing direction, no workflow-level block remains); each job's if: cron string byte-equal to its on.schedule entry; display names unchanged; REQUIRED_CONTEXTS byte-identical at d0a7e748, b33a7996 and main 14f30991; the check-expected-skips roster carries no patrol row (hypothesis 2 holds); no script fetches runs by the retired names (hypothesis 4). Measured change beyond the letter: the pull_request leg now runs every job on the union of six paths (more, never less; write steps keep github.event_name != 'pull_request'); a patrol workflow_dispatch input added (benign, dev deviation 6). The seat re-read the diff and the body and reads the same.
  3. Gates, pinned to head b33a7996. 67 check-runs, none in progress, none failed (reviewer's read 2026-10-27T06:21Z); Fleet Patrols run 40459028676 (event pull_request) green with all four jobs; check:ci-filter-parity, check-required-contexts, check:pm-expected-skips, check:pm-dispatch-gates, the watchdog pin all success; actionlint not wired (confirmed).
  4. Accepted deviations. (a) Part of on line 3 behind the harness's two attribution lines. (b) Branch is the thread's harness branch. (c) pr_create through the relay, 0 MCP calls. (d) Body precision: 「5 steps」 for two jobs that have 6; 「blocks at job level, unchanged」 where they were workflow-level before; 「the watchdog pin judges every board write」 where only issues.*( calls are markers (board-snapshot's git push / R2 steps are guarded but not judged by the pin). (e) Tier H sequence: os-zhuang APPROVED on b33a7996 at 2026-10-27T06:10Z before the contract review (06:22Z) and this record.
  5. Carrier. Card candidates, seat-filed: stale prose naming the retired workflow files across lint.yml:2505 (governed; also claims the watchdog filter names its file alone — now false), merged-branch-reaper.yml, shard-timings-refresh.yml, test-nightly-tiers.yml, check-platform-checklist.mjs, check-stall-guard-budget.mjs, scripts/ci/board-snapshot-r2-upload.sh, import-prerequisite.mjs, sync-release-index-currency.mjs, dispatch-gates.mjs + its self-test (dedupe fleet-patrols stale mention retired workflow name); and a pin for the cron twins — each job's if: cron ⇔ an on.schedule entry (dedupe fleet-patrols cron if twin pin). 收班简报: run-level failure signal now reads 「Fleet Patrols」 (job-level kept); patrol-tooling PRs on any of six paths now run all four jobs.
  6. Landing. Tier H ⇒ this record and the contract review are on the head; os-zhuang's APPROVED review on b33a7996 is the authorised hand, so the seat arms auto-merge through the relay (automerge_enable) if it is not armed, follows the PR to MERGED, then tells the card thread to open step (iii) on a fresh origin/main. The card stays open (Part of) until step (i) lands.

Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ci/cd documentation Improvements or additions to documentation size/xl 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.

3 participants