Skip to content

[Decision] Should check:platform-checklist move from manual cadence to a path-filtered CI gate? #9306

Closed as not planned

Description

@os-zhuang

Filed at the maintainer's request while scoping the QA wave #9296. This revises a recorded maintainer decision, so it is not auto-adjudicable: the confidence gate bars a triage ruling that narrows or overturns an existing one, even when the change would strengthen a gate rather than weaken it.

What triggered it

Maintainer, 2026-08-17, verbatim:

check-platform-checklist.mjs 如果 是 CI,会导致每一个pr都增加大量时间吧?有必要吗

The cost half of that premise is measurably false, so the question deserves a straight answer rather than an inherited assumption.

Measured facts (re-check commands included — a premise with no re-check command ages into folklore)

fact reading re-check
validator runtime ~80 ms time node scripts/check-platform-checklist.mjs
full pnpm script (selector self-test + validator) 0.165 s time pnpm check:platform-checklist
dependencies none — node:fs, node:path only, so no pnpm install and no build needed grep -n '^import' scripts/check-platform-checklist.mjs
current posture not in CI, by maintainer decision grep -n 'platform-checklist' .github/workflows/lint.yml (see the note at L426)
drift has reached main at least once #7347 — "check:platform-checklist is red on main: the new qa liveness ledger is neither mapped nor waived" gh issue view 7347

(Cost-row correction, 2026-08-17: these figures were measured on an idle container; under load the pnpm wrapper dominates (~2.9 s wall). What survives any machine state: zero-dependency, direct-node sub-second. See the correction comment — cost was never the deciding axis.)

So the real trade is not seconds. Appended to the existing lint job (which already has a checkout) the marginal cost is ~0.2 s; only giving it a dedicated job would cost anything, and that cost is runner spin-up, not the script.

Options

  • A — status quo. Manual/periodic cadence (before a release, after a large surface lands, alongside a checklist-author/checklist-test). Drift is caught at the next manual run, by whoever remembers.
  • B — path-filtered CI (recommended). Append the one line to the existing lint job, guarded to PRs touching packages/spec/liveness/**, packages/spec/src/**, or docs/qa/platform-checklist/**. Unrelated PRs are untouched; the PRs that can actually turn it red pay 0.2 s.
  • C — every-PR CI. Cheap in seconds, but couples every PR in the repo to QA-ledger bookkeeping.
  • D — scheduled run (e.g. nightly) that opens/refreshes one issue when red. Catches drift without touching PR flow at all; detection lands hours after the merge, on nobody's desk in particular.

B and D are not exclusive.

Four-corner card face

  • ① platform long-term coherence — the coverage ratchet encodes a declared invariant ("凡是有的能力, 都要测试"). Under A it is enforced by memory, which is the declared≠enforced shape ADR-0049 exists to eliminate. B closes that gap without adding a special case: the check already exists, only its trigger changes. C closes it by making every author pay for a ledger they did not touch — coherence bought with coupling.
  • ② measured business pull — one measured instance of drift reaching main (finding: check:platform-checklist is red on main — the new qa liveness ledger is neither mapped nor waived in coverage.json #7347, the qa liveness ledger neither mapped nor waived), found by a sweep rather than a gate. Low frequency, not zero. A second data point is inbound: QA wave R4: coverage sweep (checklist-author) — re-audit the ledger against the 727 commits since the 2026-08-08 sweep #9299 will report whether the ledger drifted again across the 727 commits since the 2026-08-08 sweep — that reading is worth waiting for before deciding, and is the honest answer to "有必要吗".
  • ③ AI-agent error-resistance — the gate would fire on the PR whose author still holds the context to map the new kind (usually an AI agent). Detected days later, a different agent has to reconstruct why the kind exists and what should cover it — the expensive direction. Loud refusal at authoring time over silent drift.
  • ④ startup scope discipline — B is one line plus a path guard on an existing job: no new workflow, no new ledger, no new declared surface to maintain. D would add a schedule and an issue-updating step — a second mechanism, and a heavier obligation than the problem currently justifies.

Recommendation

B, and only after #9299 reports. If the sweep finds the ledger did not drift across 727 commits, the measured pull for any gate is weak and A is the honest answer — the manual cadence would have been proven sufficient. If it did drift, B is the smallest change that makes the invariant real, and it costs 0.2 s on exactly the PRs that can break it.

⛔ No agent implements any of this until the maintainer rules. Related: #9296 (QA wave), #9299 (which corrects SWEEP.md's factual claim that this check already runs in CI — a text fix only, explicitly not a posture change).


Ruled 2026-08-17 (final): A — status quo; card closed. The premised ruling (B if #9299 found drift, A if zero drift) resolved through the third branch: #9299 found the ledger did drift (8 gaps), but with zero new kinds and zero new enum members — every gap was new behavior inside already-mapped kinds, invisible to both ratchets by construction. The gate would have caught none of it; the sweep is the mechanism that works. check:platform-checklist stays out of CI; the .github/workflows/lint.yml L426 posture note remains authoritative. Re-open condition: gate-visible drift (a new kind or new pinned-enum member reaching main unmapped) — file a fresh card citing this one. See the resolution comment for the full record.

Activity

  1. added
    needs-user-decisionBlocked on a maintainer decision — do not dispatch; decision cards only, never on a PR
    domain:devxDomain lane — seat card indexed by label:pm:seat
    on Aug 17, 2026
  2. os-zhuang commented on Aug 17, 2026

    @os-zhuang
    ContributorAuthor

    Maintainer ruling recorded (premised) — maintainer (zhuangjianguo@steedos.com), 2026-08-17, verbatim:

    帮我决策 #9306

    The maintainer delegated this decision to the PM seat. Recorded under that delegation, with the maintainer able to veto in one line. Provenance: PM session session_01NYgmGheCzM6NrHZN436Cxf.

    Ruling: B — path-filtered CI, premised on #9299 finding drift

    Append pnpm check:platform-checklist to the existing lint job (no new workflow, no dedicated job), guarded to PRs touching:

    • packages/spec/liveness/**
    • packages/spec/src/**
    • docs/qa/platform-checklist/**

    Named premise (falsifiable): #9299's sweep finds the ledger did drift across the 727 commits since the 2026-08-08 sweep. That sweep is in flight and has not reported.

    If the premise fails — #9299 measures zero drift — the outcome flips to A (status quo): the manual cadence would have been proven sufficient over 727 commits, and the measured pull for any gate is too weak to justify even a one-line coupling. That is the honest answer to the maintainer's original 「有必要吗」.

    ⛔ Fork prohibition: no agent silently implements either branch. When #9299 reports, the PM reads the drift finding on that thread, states on this card which branch of the premise held, and only then does this card become dispatchable. If #9299 comes back ambiguous (e.g. drift found but only in prose, not in the coverage ledger itself), that is a report-back to the maintainer, not a judgment call.

    Why B over C/D (four-corner summary)

    State

    needs-user-decision → pm:blocked, body carries Blocked-by: #9299. The unlock scan re-surfaces this card when #9299 closes; the PM then resolves the premise as above.


    Generated by Claude Code

  3. added
    pm:blockedBlocked by another issue/PR — body carries Blocked-by: #N
    and removed
    needs-user-decisionBlocked on a maintainer decision — do not dispatch; decision cards only, never on a PR
    on Aug 17, 2026
  4. os-zhuang commented on Aug 17, 2026

    @os-zhuang
    ContributorAuthor

    Correction to the cost row in this card's premise table — recorded before the maintainer rules on it, because the ruling partly rests on it.

    The figures in the table (~80 ms validator, 0.165 s for the full pnpm script) were measured on an idle container. The #9299 sweep re-measured on the same container while a cold monorepo install was running in parallel and got ~0.12–0.18 s for the validator and ~2.9 s wall for pnpm check:platform-checklist — the pnpm wrapper's own startup dominating under load.

    Both readings are honest measurements of different machine states, which is the useful conclusion: a precise millisecond figure does not survive a different machine, so no ruling should rest on one. What survives:

    • the check is zero-dependency (node:fs, node:path) — no install, no build, nothing to cache;
    • direct-node cost is sub-second in every state measured (~0.25 s for self-test + validator together);
    • timing it through pnpm measures the wrapper, not the check.

    The decision framing is unchanged, and if anything sharpened: cost was never the real axis. Even the loaded-container figure is noise next to a CI job's runner spin-up, and a path-filtered trigger (option B) would charge it only to PRs that can actually turn it red. The axis that decides this is PR independence versus enforcing a declared invariant — options A vs B in the body above.

    The evidence I said this card should wait for has since landed: #9299 / PR #9309 reports the ledger DID drift across the window — 8 items authored, and both surviving coverage waivers (book, doc) turned out factually stale, making it 6 of 6 waivers ever written that aged into falsehood.

    But read what that drift was before letting it decide this card: the window added ~156 non-test source files and zero new metadata kinds, so every one of those 8 gaps was new behavior inside an already-mapped kind — invisible to the coverage ratchet (no new kind) and to the enumSource ratchet (no new enum member). None of it would have been caught by check:platform-checklist however often it ran. That cuts directly against option B's value: the drift that actually happened is not the drift a gate detects. The measured pull for putting this in CI is weaker after the sweep than it looked before it, not stronger — the honest reading now leans toward A (status quo), with the sweep, not the gate, as the mechanism that works.

    Still a maintainer call; ⛔ no agent acts on this either way.


    Generated by Claude Code

  5. os-zhuang commented on Aug 17, 2026

    @os-zhuang
    ContributorAuthor

    Premise resolved — final ruling: A (status quo). Maintainer, 2026-08-17, forwarding the QA-wave seat's revised recommendation into the PM decision channel, verbatim:

    转 agent:#9306(CI 姿态)—— 我的推荐已从 B 改到 A(维持现状),因为 sweep 证明这次真实漂移是门禁按构造就看不见的(零个新 kind、零个新枚举成员,全是已映射 kind 内部的新行为)。

    Recorded under the maintainer's standing delegation for this card (「帮我决策 #9306」, same day) plus this explicit forward. Provenance: PM session session_01NYgmGheCzM6NrHZN436Cxf. Verified against the primary source, not the relay: #9299's report and its review both state the window added ~156 non-test source files with zero new metadata kinds and zero new enum members — all 8 authored gaps are new behavior inside already-mapped kinds.

    How the premise resolved

    The premised ruling above named the fork: drift ⇒ B, zero drift ⇒ A, ambiguous ⇒ report back. What actually happened is the instructive third case: drift occurred, but of a class the gate cannot see by construction. The premise's letter ("did drift") is satisfied; its reason — that a CI trigger would have caught the drift — is falsified. A premise inherits with its reason, and the reason is dead: check:platform-checklist would have stayed green through every one of the 8 real gaps however often it ran. Wiring it into CI would buy coupling and a false sense of enforcement — declared≠enforced in a subtler costume, exactly what ADR-0049 exists to refuse.

    Final ruling

    Closing as not planned: the decision is made and its outcome is "change nothing".


    Generated by Claude Code

  6. removed
    pm:blockedBlocked by another issue/PR — body carries Blocked-by: #N
    on Aug 17, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    domain:devxDomain lane — seat card indexed by label:pm:seattests

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions