Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .claude/skills/pm-dispatch/references/platform-readings.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 绿时红。
Expand Down
2 changes: 1 addition & 1 deletion .claude/skills/pm-dispatch/references/review-checklist.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 改为有界等待。

## 断言与收益
Expand Down
24 changes: 11 additions & 13 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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.
Expand Down
6 changes: 3 additions & 3 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
6 changes: 3 additions & 3 deletions docs/agents/multi-agent-discipline.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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

Expand Down
87 changes: 60 additions & 27 deletions scripts/check-required-contexts.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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 = [
{
Expand Down Expand Up @@ -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',
},
];

/**
Expand Down Expand Up @@ -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: [
Expand All @@ -629,6 +636,7 @@ export const INSTRUCTION_SURFACES = [
'Type Check UI',
'Test UI',
'Live E2E UI',
'Dogfood Regression Gate',
],
},
{
Expand Down Expand Up @@ -662,6 +670,7 @@ export const INSTRUCTION_SURFACES = [
'Type Check UI',
'Test UI',
'Live E2E UI',
'Dogfood Regression Gate',
],
},
{
Expand All @@ -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.
*
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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');
Expand Down Expand Up @@ -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(
Expand Down
Loading