diff --git a/.claude/skills/pm-dispatch/references/platform-readings.md b/.claude/skills/pm-dispatch/references/platform-readings.md index 3f6c5b3b73e..572c05192e3 100644 --- a/.claude/skills/pm-dispatch/references/platform-readings.md +++ b/.claude/skills/pm-dispatch/references/platform-readings.md @@ -385,9 +385,9 @@ - 强制档不因临缺授权降档:临缺非退役,退役唯维护者裁;不达档起隔离达档子代理或等档。 - 本车道强制多是过宽的回忆:`dispatch-gates.mjs --tier PATH` 逐路径现推,路径线是下限非放行。 - 该脚本只答自己那棵树:姊妹仓路径回 absent from this tree,姊妹仓的档位与条款②只能手推。 -- required checks 的名单是每仓事实,objectstack 十个: - `TypeScript Type Check` · `Lint & Repo Gates` · `Test Core` · `Build Core` · `Lint UI` · - `Governed Surface Queue Guard` · `Type Check UI` · `Test UI` · `Build & E2E UI` · `Live E2E UI`。 +- required checks 的名单是每仓事实,objectstack 十一个: + `TypeScript Type Check` · `Lint & Repo Gates` · `Test Core` · `Build Core` · `Lint UI` · `Test UI` · + `Governed Surface Queue Guard` · `Type Check UI` · `Build & E2E UI` · `Live E2E UI` · `Dogfood Regression Gate`。 - ⭐ 本表的 `mustName` 声明 `statesTheSet` ⇒ 必须逐字等于注册表,加注册行不跟改本表即红。 - ⇒ 门绿即名单逐字齐全的读数;⛔ 但数量词未钉:实测 七→六 照绿,只能手跟改。 - ⛔ 聚合命令同样不作判定:`check:type-check-debt` 可在包级 typecheck 绿时红。 diff --git a/.claude/skills/pm-dispatch/references/review-checklist.md b/.claude/skills/pm-dispatch/references/review-checklist.md index 3f67e56a3ee..c4781298950 100644 --- a/.claude/skills/pm-dispatch/references/review-checklist.md +++ b/.claude/skills/pm-dispatch/references/review-checklist.md @@ -46,7 +46,7 @@ - ⛔ 非当前 head 上的 `cancelled` 零动作、永不重跑:新推送自带全套 run。 - 席位核对门禁证据 = check-runs(名+结论);本地并集只证本地子集,⛔ 不替缺席者。 - `!` 或 `(narrowing)`:入队前核 `dogfood_battery` 钉 head、全量、红已修/已报;缺或收窄 ⇒ REWORK。 -- 把发送或写挪到应答之后的 PR:dogfood 门禁不跑于 PR 与队列,全绿照样红 `main` 的 push 全量。 +- 把发送或写挪到应答之后的 PR:dogfood 门禁不跑于 PR、跑于队列,全绿 PR 照样被队列弹出。 - ⇒ 入队前 grep `packages/qa/dogfood/test` 里应答后紧读该副作用的用例,同一 PR 改为有界等待。 ## 断言与收益 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3e0d01fbc65..3bc4ca7b024 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -122,9 +122,8 @@ concurrency: # github.event_name != 'pull_request' # # in its `if:`, so a dogfood red shows on the queue build of the entry that -# carries it rather than first on `main`'s push run after it. It is advisory -# there: the context is outside the required set (below), so the queue may -# merge the entry before or despite the shards. +# carries it rather than first on `main`'s push run after it, and the red +# ejects that entry: the context is in the required set again (below). # `main`'s 4-hourly `schedule` run, its `push` runs and an on-demand # `workflow_dispatch` carry the FULL BATTERY: all of that plus the three jobs # below that spell @@ -139,9 +138,9 @@ concurrency: # `scripts/ci/scheduled-full-run.mjs --release-gate`). ⛔ No affected-only # dogfood filter on the queue: the shards run on every entry, through the # `core` output, which is 'true' on `merge_group` (letter C was not ruled). -# The queue enforces only the ruleset's required set, and `Dogfood Regression -# Gate` is outside it since #22998 (check:required-contexts' tombstone); this -# file does not decide whether it rejoins. +# The queue enforces only the ruleset's required set. `Dogfood Regression +# Gate` left it by #22998 and rejoined it by the #30461 follow-up ruling +# (ruling record 6669982756); check:required-contexts pins its row. # # Spelled as a DENY-list of the per-PR events on purpose — the filter # contract's half 1 below, applied to events: a trigger added to `on:` later @@ -2724,9 +2723,9 @@ jobs: overwrite: true dogfood-gate: - # Stable check name for a SHARDED job (#3622 follow-up). Since #22998 the - # context is outside the required set (THE TWO SPEEDS above `jobs:`). On a - # PR both legs skip and this gate counts zero expected attestations; on a + # Stable check name for a SHARDED job (#3622 follow-up), a required + # context again since #30461 (THE TWO SPEEDS above `jobs:`). On a PR both + # legs skip and this gate counts zero expected attestations; on a # queue build (since #30461) the three dogfood shards run and are counted, # while `dogfood-verify` skips and is judged with zero expected; the full # battery runs and counts both. The history below is why the name and the @@ -2738,10 +2737,9 @@ jobs: # EVERY pull request in the repo sat permanently BLOCKED (mergeable, all # checks green, merge button dead). #3622's own comment called for updating # branch protection; keeping the contract HERE instead means a future - # shard-count change cannot deadlock the repo a second time. From #6865 - # until #22998 the bare name was pinned by `check:required-contexts`; it - # left that registry with the required set, so a rename now falsifies the - # prose that names it without reddening anything — treat it as contract. + # shard-count change cannot deadlock the repo a second time. The bare name + # is pinned by `check:required-contexts` (since #6865; out of that registry + # from #22998 until #30461) — treat it as contract. # # Also aggregates dogfood-verify (the CLI pass that used to ride shard 1), # so the one context still covers everything it covered before the split. diff --git a/AGENTS.md b/AGENTS.md index 856259d9a8b..da934f618bf 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -505,11 +505,11 @@ Even inside your own worktree, operate defensively: yet"; `in_progress` is not a pass, and arming a red PR hides it ([why](docs/agents/multi-agent-discipline.md#arming-a-red-pr-hides-it)). Always read *two* things: the queue branch **and** `origin/main`. And **the queue enforces only the required - set** — ten contexts block: `Lint & Repo Gates` (all `check:*` gates), `TypeScript Type Check`, + set** — eleven contexts block: `Lint & Repo Gates` (all `check:*` gates), `TypeScript Type Check`, `Test Core`, `Build Core`, `Governed Surface Queue Guard`, `Lint UI`, `Type Check UI`, `Test UI`, - `Build & E2E UI` and `Live E2E UI`; a check outside those ten is advisory + `Build & E2E UI`, `Live E2E UI` and `Dogfood Regression Gate`; a check outside those eleven is advisory ([why that matters](docs/agents/multi-agent-discipline.md#advisory-checks-ride-through)). A required context is - matched by check-run name, so a rename detaches its gate silently — treat those ten names as contract. + matched by check-run name, so a rename detaches its gate silently — treat those eleven names as contract. **Two speeds** — PR runs carry the fast set, queue runs add the dogfood shards; `main`'s scheduled and push runs carry the full battery ([detail](docs/agents/multi-agent-discipline.md#two-speeds-which-runs-carry-which-battery)). **Main-red:** a full-battery red is its lane's first work — triage files it under diff --git a/docs/agents/multi-agent-discipline.md b/docs/agents/multi-agent-discipline.md index ba77aab524b..0cb6e0e860b 100644 --- a/docs/agents/multi-agent-discipline.md +++ b/docs/agents/multi-agent-discipline.md @@ -123,7 +123,7 @@ Arming a red PR does not queue it, it hides it: ← [`AGENTS.md` › Multi-agent working discipline](../../AGENTS.md#multi-agent-working-discipline) -A check outside those ten is advisory and rides through, and an advisory red +A check outside those eleven is advisory and rides through, and an advisory red that lands rides `main`'s merge ref into every later PR until stanched. ## Two speeds: which runs carry which battery @@ -134,9 +134,9 @@ A check outside those ten is advisory and rides through, and an advisory red of wall per queue entry (median, 2026-11-05); `main`'s scheduled (4-hourly) and push runs carry the full battery — dogfood, e2e, `Temporal Conformance (live PG + MySQL)` — read by the publish lane before any approval. Queue runs also carry the three `Dogfood Regression Gate` shards (ruling record - 6663883327), so a dogfood red shows on the queue build, advisory only; `Dogfood Verify CLI`, + 6663883327), so a dogfood red shows on the queue build; `Dogfood Verify CLI`, Temporal Conformance and `UI Published Artifacts` stay full-battery only. The `Dogfood Regression - Gate` context is outside the required set, so the queue does not hold an entry on it. + Gate` context is in the required set again (ruling record 6669982756), so a dogfood red ejects the entry. ## Re-arm awareness diff --git a/scripts/check-required-contexts.mjs b/scripts/check-required-contexts.mjs index 43ced42092b..1d7c2e1b07a 100644 --- a/scripts/check-required-contexts.mjs +++ b/scripts/check-required-contexts.mjs @@ -371,27 +371,21 @@ const UNATTRIBUTED_BATTERY = '(no battery open)'; * is a property of the workflow, so the workflow is where it is read from: the * job's own step list, one grep away and never stale (#9103). * - * ⛔ TOMBSTONE — `Dogfood Regression Gate` (ci.yml:dogfood-gate) and `Temporal - * Conformance (live PG + MySQL)` (ci.yml:temporal-conformance), dropped by the - * #22998 ruling (two-speed CI; ruling record 6120820653 on #22991): their legs - * run in the FULL BATTERY only — `main`'s push, hourly and dispatch runs — so a - * PR or queue build carries the fast set and these two contexts leave the + * ⛔ TOMBSTONE — `Temporal Conformance (live PG + MySQL)` + * (ci.yml:temporal-conformance), dropped by the + * #22998 ruling (two-speed CI; ruling record 6120820653 on #22991): its leg + * runs in the FULL BATTERY only — `main`'s push, hourly and dispatch runs — so a + * PR or queue build carries the fast set and the context left the * required set. The order is the #9325 two-step: the registry and the - * instruction surfaces drop the rows in the PR, and the maintainer removes the - * two contexts from the ruleset in the same sitting as the merge; until then - * `--verify-required-set` reports them in direction B (required, pinned by no + * instruction surfaces drop the row in the PR, and the maintainer removes the + * context from the ruleset in the same sitting as the merge; until then + * `--verify-required-set` reports it in direction B (required, pinned by no * row), report-only, which is the sitting's own reminder. Prose and not a * RETIRED_CONTEXT_NAMES row, for the reason the `Build Docs` / `Console Pin - * Gate` tombstone above gives: neither name is dead — both jobs still publish - * these check-runs on every full-battery run, and the instruction files name - * where each runs. - * Since #30461 (ruling record 6663883327, letter B) the three dogfood shards - * also run on `merge_group`, so a queue build publishes `Dogfood Regression - * Gate` over their credentials; `Temporal Conformance` stays full-battery - * only. The context stays out of this registry and of the ruleset for now: the - * follow-up ruling on #30461 (A, in two steps, 6669982756) puts it back only - * after a clean 24-hour reading of the queue builds, the row here landing - * first and the ruleset edit after it. + * Gate` tombstone above gives: the name is not dead — the job still publishes + * this check-run on every full-battery run, and the instruction files name + * where it runs. + * Returned: `Dogfood Regression Gate` (ci.yml:dogfood-gate) left with it by #22998 and is back since #30461 (letter B 6663883327 put its shards on `merge_group`; letter A 6669982756 re-enrolled the row below). */ export const REQUIRED_CONTEXTS = [ { @@ -497,6 +491,18 @@ export const REQUIRED_CONTEXTS = [ 'in the same sitting as the merge (#9325)', carries: 'the Console against a real workspace backend: the allowlisted `ui/e2e/live` specs', }, + { + workflow: 'ci.yml', + job: 'dogfood-gate', + context: 'Dogfood Regression Gate', + authorized: + '#30461 follow-up ruling 6669982756 (letter A, in two steps), maintainer 「同意」 2026-11-05T21:25Z — after the ' + + '24-hour queue reading 6707277208 met the step-3 threshold (zero flakes); this row is the repo half and merges ' + + 'first, the ruleset entry is the maintainer\'s, the same day (#9325)', + carries: + 'the sharded dogfood matrix, `Dogfood Regression Gate (k/3)`, read through the aggregate by check:shard-attestation, ' + + 'and the `Dogfood Verify CLI` leg, which its `if:` skips on merge_group and pull_request', + }, ]; /** @@ -613,9 +619,10 @@ export const INSTRUCTION_SURFACES = [ // contexts moved to the full battery). Then 5→7 by #25853 (the `ui/` // import's `Lint UI` and `Build & E2E UI`), 7→8 with `Type Check UI` // once bill item 49 closed (#25856), 8→9 with the `Test UI` aggregate - // (#26460), and 9→10 with `Live E2E UI` (#27154). The full set is pinned - // here so the corrected sentence cannot rot back — a rename in ANY of the - // ten reddens this gate instead. + // (#26460), 9→10 with `Live E2E UI` (#27154), and 10→11 with `Dogfood + // Regression Gate` back (#30461). The full set is pinned here so the + // corrected sentence cannot rot back — a rename in ANY of the eleven + // reddens this gate instead. file: 'AGENTS.md', statesTheSet: true, mustName: [ @@ -629,6 +636,7 @@ export const INSTRUCTION_SURFACES = [ 'Type Check UI', 'Test UI', 'Live E2E UI', + 'Dogfood Regression Gate', ], }, { @@ -662,6 +670,7 @@ export const INSTRUCTION_SURFACES = [ 'Type Check UI', 'Test UI', 'Live E2E UI', + 'Dogfood Regression Gate', ], }, { @@ -683,7 +692,7 @@ export const INSTRUCTION_SURFACES = [ * of the six surfaces have one; `AGENTS.md` does not, because a repo-root FILE * has no separator to be found by — so an AGENTS.md card derived this gate not * at all, while that surface is the one carrying `mustName` for every - * required context (widened 2→6 by #9677, 6→7 by #17798, 7→5 by #22998, 5→7 by #25853, 7→8, 8→9 by #26460, 9→10 by #27154). Editing the merge-queue + * required context (widened 2→6 by #9677, 6→7 by #17798, 7→5 by #22998, 5→7 by #25853, 7→8, 8→9 by #26460, 9→10 by #27154, 10→11 by #30461). Editing the merge-queue * paragraph there is precisely how this gate goes red, and it was reachable * only by judgment. * @@ -2004,6 +2013,15 @@ async function selfTest() { renamedTestUi.problems.some((p) => p.includes("job 'test-ui-gate'") && p.includes("'Test UI'")), 'growing a suffix on the bare `Test UI` name ⇒ red, naming the aggregate job and the required context', ); + // The third (#30461): `Dogfood Regression Gate` is the bare name of + // `dogfood-gate`, back in the registry, and its shards are `… (k/3)`. + const renamedDogfood = fixture('rename Dogfood Regression Gate', 'ci.yml', (s) => + s.replace(' name: Dogfood Regression Gate\n', ' name: Dogfood Regression Gate (aggregate)\n'), + ); + assert( + renamedDogfood.problems.some((p) => p.includes("job 'dogfood-gate'") && p.includes("'Dogfood Regression Gate'")), + 'growing a suffix on the bare `Dogfood Regression Gate` name ⇒ red, naming the aggregate job and the required context', + ); // ── (2) the job disappearing entirely ───────────────────────────────────── // Re-pointed from `console-pin` to `temporal-conformance` when #9533 dropped @@ -2169,6 +2187,20 @@ async function selfTest() { sources['ci.yml'].includes(TEST_UI_SHARD_NAME), 'the real suffixed Test UI shard name coexists with the bare `Test UI` gate name', ); + // The third live specimen (#30461): the `dogfood` shards and their + // `Dogfood Regression Gate` aggregate, the matrix width read the same way. + const DOGFOOD_SHARD_NAME = `name: Dogfood Regression Gate (\${{ matrix.shard }}/${shardTotal(sources['ci.yml'], 'dogfood')})`; + const collidedDogfood = fixture('collide the dogfood shard name with its gate name', 'ci.yml', (s) => + s.replace(DOGFOOD_SHARD_NAME, 'name: Dogfood Regression Gate'), + ); + assert( + collidedDogfood.problems.some((p) => p.includes("job 'dogfood'") && p.includes('published by ci.yml:dogfood-gate')), + 'a dogfood shard wearing the bare `Dogfood Regression Gate` context name ⇒ red', + ); + assert( + sources['ci.yml'].includes(DOGFOOD_SHARD_NAME), + 'the real suffixed dogfood shard name coexists with the bare `Dogfood Regression Gate` gate name', + ); // ── (8) a registry that lists one context twice ────────────────────────── battery('(8) a registry that lists one context twice'); @@ -2798,11 +2830,12 @@ async function selfTest() { assert(snapshot.strict === false, 'strict_required_status_checks_policy is read, not assumed'); assert(snapshot.enforcing.length === 1 && !snapshot.noEnforcingRuleset, 'one active ruleset covers the default branch'); // Direction B against the checked-in registry, derived for the same reason - // as direction A below: the 2026-08-18 reading still carries the two - // contexts #22998 dropped from the registry (`Dogfood Regression Gate`, - // `Temporal Conformance (live PG + MySQL)`), so the snapshot reports them - // as required-and-unpinned — exactly the reading the maintainer's - // settings half of that two-step clears. + // as direction A below: the 2026-08-18 reading still carries + // `Temporal Conformance (live PG + MySQL)`, which #22998 dropped from the + // registry, so the snapshot reports it as required-and-unpinned — exactly + // the reading the maintainer's settings half of that two-step clears. + // (`Dogfood Regression Gate`, dropped with it, is pinned again since + // #30461 and so no longer reads as unpinned here.) const pinned = new Set(REQUIRED_CONTEXTS.map((e) => e.context)); const unpinnedNames = snapshot.unpinned.map((e) => e.context ?? e).sort(); assert(