Skip to content

Commit 8852099

Browse files
ci(test-core): start only the Test Core shards whose bin holds work (#26391)
<!-- claude-projects-attribution: {"github_login":"os-steve"} --> _Requested by **Steve** · [project thread](https://claude.ai/code/project/chan_01FdL1XeUyvVNxLreh3F4Qi9?thread=cmsg_01FdL1XeUyvVNxLreh3F4Qi9MGLtJ9JRr4ZgCPbNRAmMxS)_ Part of #26084 Clause-②: no Metadata-protocol: none ## 维护者速读 - 改了什么:Test Core 不再固定起 8 个分片。新加一个 `Plan Test Core shards` job,先把受影响包算一遍、按原规则切成 8 份,只为有包的那几份起 runner;没包的分片不再创建。分片数(8)、分片名、每片测的包都不变。 - 为什么改:最近 25 次 PR 运行里,Test Core 的 168 个分片有 72 个是空的(43%),每个空片照样花约 97 秒做 checkout 和安装,合计 7020 runner 秒。只改脚本、文档或工作流的 PR 通常只需要 1 到 3 片。 - 风险与回滚:代价是 Test Core 的分片要等规划 job 跑完再开始,大约晚 65 到 95 秒。规划 job 挂了就退回 8 片全跑(不会少测)。每个分片会自己重算一遍计划,和规划 job 对不上就直接变红。回滚就是 revert 这个 PR。 - 先看:`.github/workflows/ci.yml` 里的 `test-plan` job、`test` 的矩阵和 `test-gate` 的 verify 步骤;`scripts/check-shard-attestation.mjs` 的 `--planned` 计数;`scripts/ci/plan-test-core-shards.mjs`。 - 两分钟验证:打开本 PR 的 CI 运行,看 `Plan Test Core shards` 的日志列出哪些分片启动,再看 Test Core 实际只出现那几个 `Test Core (k/8)`,并且必需检查 `Test Core` 是绿的。 - 需要你做的:这个 PR 改了 `.github/workflows/ci.yml`,属于 Tier H 受管面,需要 os-zhuang 或 hotlong 批准后才能落地。 ## What changes **Test Core starts only the shards whose bin holds work.** A new `test-plan` job (`Plan Test Core shards`) runs the shards' own selection (`scripts/ci/select-shard-packages.sh`, the same event fields) and the same partition (`planRun()` from `scripts/partition-test-shards.mjs` at `SHARD_COUNT` = 8, dogfood excluded) once, and publishes two outputs: `shards`, the 1-based indices of the non-empty bins as a JSON array, and `digest`, a sha256 of the whole partition. - `test` builds its matrix from that list: `shard: ${{ fromJSON(needs.test-plan.outputs.shards || '[1, 2, 3, 4, 5, 6, 7, 8]') }}`. Its `if:` gains `needs.test-plan.outputs.shards != '[]'`, so an empty plan starts no shard runner. - **The count does not change.** A started shard still runs `--shard k/8`, keeps its name `Test Core (k/8)`, its cache key and its attestation `test-k-of-8`. Only empty bins are never created. No package moves to another shard, and no shard carries more than before. - **Each shard checks the plan.** "Compute this shard's package set" now ends with `plan-test-core-shards.mjs --verify`. The shard recomputes the plan from its own package set and reds, naming both digests, if its digest differs from the one the matrix was built from. Equal digests mean equal bins, so the shards that were not started are empty by the shard's own computation too. - **It fails open.** If the plan job dies, it publishes nothing. The matrix then falls back to the literal 1..8, `--verify` gets an empty digest and says so, and the gate counts 1..8. If the planner's own self-test fails, the job stops before it plans and the same fallback applies. A broken planner costs runners, never coverage. - **`test-gate` counts the planned roster.** It gets `--planned "test=$OS_TEST_PLAN"` and `--plan-result "$OS_TEST_PLAN_RESULT"`. `OS_TEST_PLAN` is byte-for-byte the expression the matrix reads, fallback included. In `check-shard-attestation.mjs`: - `rosterFor()` and `shardsNeedingCancelCause()` take the planned list, so a cancel-cause read asks only about shards that actually existed. - A skipped leg passes on `[]` only when the plan job concluded `success`. Every other skip goes through the unchanged #4928 guard. - An unreadable roster is refused with a reason (`parsePlannedRoster`). - The static guard (`plannedMatrix()`) requires the planned matrix to carry the full-roster fallback. It requires the gate's `--planned` value to equal the matrix expression exactly, and `--plan-result` to read `needs.test-plan.result`. Both jobs must list `test-plan` in `needs:`. - Pins re-anchored onto the new matrix spelling: - `partition-test-shards.mjs --self-test` check 4 now reads the fallback literal. - `check-shard-attestation.mjs`'s "grow the matrix" fixture now grows the fallback literal. - `check-aggregator-roster.mjs`'s two `test-gate` `needs:` anchors now include `test-plan`. `test-plan` is declared a non-member (`OS_AGGREGATOR_NON_MEMBERS: filter test-plan`): it supplies the roster and carries no verdict. **Required contexts: unchanged.** The reading of `scripts/check-required-contexts.mjs` (`REQUIRED_CONTEXTS`, L388-470) at this head is below. `Test Core` is the bare-named aggregate `test-gate`, and its `name:`, `if: always()` and job id are untouched. The matrix members `Test Core (k/8)` are not required contexts: rule (4) of that script refuses a matrix on any required job. The new job `Plan Test Core shards` carries no registered name. `Test UI (k/16)` is not in the required set at all. ``` check-required-contexts: 8 required context name(s) pinned across 3 workflow(s) lint.yml:lint → 'Lint & Repo Gates' lint.yml:typecheck → 'TypeScript Type Check' ci.yml:test-gate → 'Test Core' ci.yml:build-core → 'Build Core' governed-surface-guard.yml:governed-surface-guard → 'Governed Surface Queue Guard' ci.yml:lint-ui → 'Lint UI' ci.yml:build-e2e-ui → 'Build & E2E UI' ci.yml:type-check-ui → 'Type Check UI' ``` **What this PR does not do, and why it says `Part of`:** - **Test UI keeps 16 shards.** No Test UI shard is empty. Over the same 25 runs, 0 of 336 Test UI shard jobs ran an empty test step. In affected mode the selector always adds the always-on population, 556 test files (~25.8 file-minutes on `ui/scripts/test-shard-timings.json`). `ui/scripts/test-shard-partition.mjs` gives every leg at least one file once there are 16 or more. So skipping empty shards saves nothing there. Running fewer, fuller shards would need the vitest denominator (`--shard=k/16`) to change. `ui/scripts/__tests__/select-affected-tests-wiring.test.ts` pins that denominator: the step header, `--shard=1/16`, and a refusal of any new expression in the step. That file is under `ui/`, which this card's claim excludes. Measured on 6390fe5 with one `ui/packages/app-shell` test file touched: 2552 of 5861 test files selected through 559 filters, ~220 file-minutes. - **A diff touching one core package still starts all 8 shards.** That is the right answer, not a miss. Every package diff on main reaches `@objectstack/cli`, `spec`, `core`, `objectql` and `runtime` through the dependency graph and the cross-package union. Ten of ten single-package probes on 6390fe5 filled all eight bins, and the smallest was about 5600 s of predicted test window. Packing such a run into two bins would roughly double its Test Core wall, so the plan removes only runners with nothing to do. ## Tests - `node scripts/ci/plan-test-core-shards.mjs --self-test`: 23 cases, 5 batteries. Covered: empty plan, single package, the bins-with-work rule, digest movement, `--verify` matching, drift and the empty-digest fallback, the output format, and the ci.yml wiring read from the live file. - `node scripts/check-shard-attestation.mjs --self-test`: 325 assertions, 24 batteries. Two batteries are new: - `#26084 a planned roster` (16 cases): planned shards counted, unplanned never named, an unexpected credential red, a failure veto, an empty plan from a successful vs failed plan job, the filter-skip pass, #3668 over a planned roster, the cancel-cause read limited to planned shards, and `parsePlannedRoster` refusals. - `#26084 the static guard` (10 cases): live-ci.yml fixtures that diverge the gate's expression by one byte, drop `--planned`, `--plan-result` or either `needs:` entry, break the fallback literal, or add `--planned` beside a literal matrix. Every one goes red. - `node scripts/partition-test-shards.mjs --self-test` and `node scripts/check-aggregator-roster.mjs --self-test`: green after re-anchoring. - The 63 test files under `ui/scripts/__tests__/` that read the root `ci.yml` are green against this `ci.yml` (1967 tests, `pnpm --dir ui exec vitest run`, no `ui/` file changed). `select-affected-tests-wiring.test.ts` and `ci-setup-pnpm-wiring.test.ts` are among them. ## Acceptance notes - **Card acceptance, Test Core leg.** "Three runs touching one core package show at most 2 shard jobs" cannot hold on today's main, for the reason above. What holds is narrower: a diff that reaches no package starts 1 to 3 shards. Measured on 6390fe5 with this planner: | One file changed | Shards started | | --- | --- | | `scripts/check-agent-model-declared.mjs` | `[1,2]` (spec, client) | | `.github/workflows/ci.yml` | `[1]` | | `content/docs/index.mdx` | `[1,2,3]` | | `connector-imap`, `plugin-approvals` or `packages/spec` source | all 8 | "A run touching `packages/spec` still shows the full count" holds. - **The real-failure leg** ("test-gate verdict unchanged for a run with a real failure") is shown with fixtures, not with a throwaway CI run: the planned roster's red paths in `judge()` and the `--verify` CLI. A missing planned shard and a `failure` result both red. No throwaway branch run was made, so the gap is written here. - **Test UI leg:** not done. It needs a `ui/` change to the wiring test, and its premise of empty shards does not hold (0 of 336). A ui-lane card would own it. - **Cost:** one serial hop on every run that starts Test Core, merge-queue entries included. Shards start after the plan job, ~65-95 s later, the same checkout and install a shard pays before its own selection (run 40426272873). The plan job runs once per run in place of 0 to 7 empty shards. ## Verification <details><summary>Derived gate families (dispatch-gates --commands, 67 commands), all run locally at the head after merging main</summary> Every derived command exited 0, plus the planner's self-test. `check:dts-closure`, `check:dual-build-cjs-loads`, `check:lean-entry-closure` and `check:sourcemap-no-sources-content` ran after `turbo run build --filter='./packages/**'` under the verify lock (exit 0). `dispatch-gates --ran` over the recorded exits: 67 derived, 67 run, 0 NOT-MEASURED. `check:self-test-wired` first went red: the planner's self-test was not run by CI. The fix wires it into the plan job. </details> <details><summary>The empty-shard reading</summary> The 25 most recent completed, non-cancelled `pull_request` CI runs, read 2026-10-27 (runs 40415024961 to 40426272873). 21 of them started Test Core, and 13 of those had at least one empty shard. An empty shard is one whose "Run this shard's tests" step took 3 s or less. 72 of 168 shard jobs were empty, 7020 runner-seconds in total, 97.5 s each. Test UI: 0 of 336 shard jobs had an empty "Run the ui tests" step. </details> --- _Generated by [Claude Code](https://claude.ai/code/session_01ADHkmNt4JnHedgMzAG9Xsy)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 80f267d commit 8852099

5 files changed

Lines changed: 744 additions & 28 deletions

File tree

‎.github/workflows/ci.yml‎

Lines changed: 137 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -500,6 +500,99 @@ jobs:
500500
- 'packages/sdui-parser/**'
501501
- 'packages/types/**'
502502
503+
# ── WHICH TEST CORE SHARDS START (#26084) ─────────────────────────────
504+
# The `test` matrix used to be the literal 1..8, so every Test Core run
505+
# started eight runners, and a shard whose bin was empty paid checkout,
506+
# install and its package-set selection before printing "No packages on this
507+
# shard". Measured over the 25 most recent completed pull_request runs on
508+
# 2026-10-27 (40415024961 to 40426272873): 72 of 168 Test Core shard jobs
509+
# were empty, 7020 runner-seconds, ~97 s each. A diff that reaches no
510+
# package (a root script, a doc, a workflow) fills one to three bins of
511+
# eight.
512+
#
513+
# This job runs the shards' own selection and partition ONCE and publishes
514+
# the indices of the bins that hold work; `test` builds its matrix from
515+
# them. The plan never re-sizes: the count stays SHARD_COUNT, so a started
516+
# shard carries exactly the slice it carried before, and only the empty ones
517+
# are not created. Every shard re-derives the plan from its own package set
518+
# and reds when its digest differs (`--verify` in "Compute this shard's
519+
# package set"), so the plan cannot silently disagree with the shards it
520+
# leaves out. scripts/ci/plan-test-core-shards.mjs carries the reasoning.
521+
#
522+
# The price is one serial hop: Test Core's shards start after this job, ~65-95
523+
# s later (the same checkout + install a shard pays before its selection,
524+
# measured on run 40426272873's shards), on every run that starts Test Core.
525+
#
526+
# Same `if:` as `test`, so the plan exists exactly when the matrix could.
527+
# Fail-open both ways (THE FILTER CONTRACT): a plan job that dies publishes
528+
# no outputs, the matrix falls back to its full literal, and the gate counts
529+
# that same fallback.
530+
test-plan:
531+
name: Plan Test Core shards
532+
needs: filter
533+
if: ${{ !cancelled() && (needs.filter.outputs.core != 'false' || needs.filter.outputs.crosspkg != 'false') }}
534+
runs-on: ubuntu-latest
535+
timeout-minutes: 10
536+
permissions:
537+
contents: read
538+
outputs:
539+
shards: ${{ steps.plan.outputs.shards }}
540+
digest: ${{ steps.plan.outputs.digest }}
541+
steps:
542+
- name: Checkout repository
543+
uses: actions/checkout@v7
544+
with:
545+
# The same history the shards check out: the selection diffs against
546+
# the PR's merge base, the queue's base_sha or the push's before sha.
547+
fetch-depth: 0
548+
549+
- name: Setup Node.js
550+
uses: actions/setup-node@v7
551+
with:
552+
node-version: '22'
553+
554+
- name: Setup pnpm
555+
uses: ./.github/actions/setup-pnpm
556+
557+
- name: Get pnpm store directory
558+
shell: bash
559+
run: |
560+
echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV
561+
562+
# Restore only: the `test` shards save this key.
563+
- name: Restore pnpm cache
564+
uses: actions/cache/restore@v6
565+
with:
566+
path: ${{ env.STORE_PATH }}
567+
key: ${{ runner.os }}-pnpm-store-v3-${{ hashFiles('**/pnpm-lock.yaml') }}
568+
restore-keys: |
569+
${{ runner.os }}-pnpm-store-v3-
570+
571+
- name: Install dependencies
572+
run: pnpm install --frozen-lockfile
573+
574+
# Its own self-test, where it runs (check:self-test-wired). Milliseconds.
575+
# A red here fails this job before it publishes a plan, so the matrix
576+
# falls back to all eight shards: a broken planner costs runners, never
577+
# coverage.
578+
- name: Test Core shard planner self-test
579+
run: node scripts/ci/plan-test-core-shards.mjs --self-test
580+
581+
# The event fields are the `test` job's "Compute this shard's package
582+
# set", verbatim: the plan is that step's answer, computed once.
583+
- name: Plan this run's Test Core shards
584+
id: plan
585+
env:
586+
OS_SHARD_EVENT_NAME: ${{ github.event_name }}
587+
OS_SHARD_PR_BASE_REF: ${{ github.event.pull_request.base.ref }}
588+
OS_SHARD_PR_PINNED_BASE_SHA: ${{ github.event.pull_request.base.sha }}
589+
OS_SHARD_MERGE_GROUP_BASE_SHA: ${{ github.event.merge_group.base_sha }}
590+
OS_SHARD_PUSH_BEFORE_SHA: ${{ github.event.before }}
591+
run: |
592+
bash scripts/ci/select-shard-packages.sh
593+
node scripts/ci/plan-test-core-shards.mjs "$RUNNER_TEMP/turbo-ls.json" \
594+
--exclude @objectstack/dogfood --github-output "$GITHUB_OUTPUT"
595+
503596
test:
504597
# Sharded 8-way BY PACKAGE (6-way until #25376): a core-touching PR ran
505598
# the affected suite ~11½ min on one 4-vCPU runner — the longest pole in
@@ -597,13 +690,18 @@ jobs:
597690
# can never publish again — the test-gate job below carries that name
598691
# (the #3622 lesson; see dogfood-gate).
599692
name: Test Core (${{ matrix.shard }}/8)
600-
needs: filter
693+
needs: [filter, test-plan]
601694
# "Skip only when the filter EXPLICITLY said no core paths changed" — see
602695
# THE FILTER CONTRACT on the filter job's outputs (#4928). `scripts` is ORed
603696
# in, never ANDed: two independent reasons to run this job, and it skips only
604697
# when BOTH filters explicitly said false, so the contract's "when in doubt,
605698
# RUN EVERYTHING" posture is unchanged (#9829).
606-
if: ${{ !cancelled() && (needs.filter.outputs.core != 'false' || needs.filter.outputs.crosspkg != 'false') }}
699+
#
700+
# The third clause is the plan's (#26084): `[]` is the test-plan job's
701+
# positive statement that the package set is empty, and only that literal
702+
# skips. A plan job that died publishes nothing, `'' != '[]'` holds, and the
703+
# matrix below falls back to all eight shards: half 1 of the contract again.
704+
if: ${{ !cancelled() && (needs.filter.outputs.core != 'false' || needs.filter.outputs.crosspkg != 'false') && needs.test-plan.outputs.shards != '[]' }}
607705
runs-on: ubuntu-latest
608706
# Backstop only — the stall guard on the test steps is the primary
609707
# detector for a #4250-style hang and fires well before this. 30 min was
@@ -676,10 +774,18 @@ jobs:
676774
timeout-minutes: 60
677775
permissions:
678776
contents: read
777+
# ⭐ ONLY THE SHARDS WITH WORK START (#26084). The list is the test-plan
778+
# job's: the indices of the bins `partition-test-shards.mjs` fills at
779+
# SHARD_COUNT (8) for this run's package set. The count stays 8, so a
780+
# started shard runs exactly the `--shard k/8` slice it ran before, under
781+
# the same name, cache key and attestation; a shard whose bin is empty is
782+
# simply never created. The literal after `||` is the full roster, read
783+
# when the plan job published nothing (it died or timed out), and the gate
784+
# reads this same expression, so the two cannot count different rosters.
679785
strategy:
680786
fail-fast: false
681787
matrix:
682-
shard: [1, 2, 3, 4, 5, 6, 7, 8]
788+
shard: ${{ fromJSON(needs.test-plan.outputs.shards || '[1, 2, 3, 4, 5, 6, 7, 8]') }}
683789

684790
steps:
685791
- name: Checkout repository
@@ -824,13 +930,20 @@ jobs:
824930
OS_SHARD_PR_PINNED_BASE_SHA: ${{ github.event.pull_request.base.sha }}
825931
OS_SHARD_MERGE_GROUP_BASE_SHA: ${{ github.event.merge_group.base_sha }}
826932
OS_SHARD_PUSH_BEFORE_SHA: ${{ github.event.before }}
933+
# No fallback on purpose: empty means the plan job published nothing
934+
# and the matrix is the full roster, which `--verify` says and passes.
935+
OS_SHARD_PLAN_DIGEST: ${{ needs.test-plan.outputs.digest }}
827936
run: |
828937
bash scripts/ci/select-shard-packages.sh
829938
node scripts/partition-test-shards.mjs "$RUNNER_TEMP/turbo-ls.json" \
830939
--shard ${{ matrix.shard }}/8 --exclude @objectstack/dogfood \
831940
> "$RUNNER_TEMP/shard-packages.txt"
832941
echo 'Items on this shard (a package name, or a package plus a k/n file-level slice):'
833942
cat "$RUNNER_TEMP/shard-packages.txt"
943+
# The plan this matrix was built from must be this shard's own plan,
944+
# or a bin the plan called empty could hold a package no shard tests.
945+
node scripts/ci/plan-test-core-shards.mjs --verify "$RUNNER_TEMP/turbo-ls.json" \
946+
--exclude @objectstack/dogfood --shard ${{ matrix.shard }} --digest "$OS_SHARD_PLAN_DIGEST"
834947
835948
# ⭐ A SHARD WHOSE TESTS READ NO `.d.ts` BUILDS ITS CLOSURE WITHOUT THEM
836949
# (#25375). vitest loads JavaScript, so on most shards every declaration
@@ -1656,8 +1769,14 @@ jobs:
16561769
# that was never scheduled publishes nothing and cannot be counted; a shard
16571770
# that failed publishes nothing either. `cancelled` (#3668) and
16581771
# filter-`skipped` (#4928) keep their existing meanings — see the script.
1772+
#
1773+
# The DECLARED roster is the PLANNED one since #26084: the `test-plan` job
1774+
# starts only the shards whose bin holds a package, so the gate counts
1775+
# those, read from the same expression the matrix reads (`OS_TEST_PLAN`
1776+
# below; check:shard-attestation holds the two byte-identical). A plan of
1777+
# `[]` skips the matrix and expects zero credentials, as a filter skip does.
16591778
name: Test Core
1660-
needs: [test, filter]
1779+
needs: [test, filter, test-plan]
16611780
# ── ROSTER DECLARATION (#10490) ───────────────────────────────────────
16621781
# What this required aggregate stands in for, stated rather than inferred.
16631782
# `node scripts/check-aggregator-roster.mjs` asserts MEMBERS + NON_MEMBERS
@@ -1667,12 +1786,14 @@ jobs:
16671786
# keep being claimed. `filter` is a non-member on purpose: it is consulted
16681787
# for the #4928 skipped-only-when-filter-succeeded guard, not aggregated —
16691788
# and having somewhere to SAY that is what stops the roster from being
1670-
# widened to launder a red. The members are cross-checked against the
1671-
# `--leg` tokens below, so this declaration cannot drift into a second
1672-
# source of truth for what check:shard-attestation counts.
1789+
# widened to launder a red. `test-plan` is a non-member for the same kind
1790+
# of reason: it supplies the roster (#26084), it carries no verdict. The
1791+
# members are cross-checked against the `--leg` tokens below, so this
1792+
# declaration cannot drift into a second source of truth for what
1793+
# check:shard-attestation counts.
16731794
env:
16741795
OS_AGGREGATOR_MEMBERS: test
1675-
OS_AGGREGATOR_NON_MEMBERS: filter
1796+
OS_AGGREGATOR_NON_MEMBERS: filter test-plan
16761797
if: always()
16771798
runs-on: ubuntu-latest
16781799
timeout-minutes: 10
@@ -1714,6 +1835,11 @@ jobs:
17141835
OS_TEST_RESULT: ${{ needs.test.result }}
17151836
OS_FILTER_RESULT: ${{ needs.filter.result }}
17161837
OS_DOWNLOAD_OUTCOME: ${{ steps.attestations.outcome }}
1838+
# The planned roster, spelled exactly as the `test` job's matrix
1839+
# spells it, fallback included: a plan job that died leaves the
1840+
# matrix on 1..8, and the gate then counts 1..8 too (#26084).
1841+
OS_TEST_PLAN: ${{ needs.test-plan.outputs.shards || '[1, 2, 3, 4, 5, 6, 7, 8]' }}
1842+
OS_TEST_PLAN_RESULT: ${{ needs.test-plan.result }}
17171843
# Read ONLY on the partially-attested-cancelled path above, never
17181844
# otherwise; without it that path fails closed (red, naming the
17191845
# missing token).
@@ -1724,7 +1850,9 @@ jobs:
17241850
--dir "$OS_ATTEST_DIR" \
17251851
--filter-result "$OS_FILTER_RESULT" \
17261852
--download-outcome "$OS_DOWNLOAD_OUTCOME" \
1727-
--leg "test/8:$OS_TEST_RESULT"
1853+
--leg "test/8:$OS_TEST_RESULT" \
1854+
--planned "test=$OS_TEST_PLAN" \
1855+
--plan-result "$OS_TEST_PLAN_RESULT"
17281856
17291857
# ── The timing table this gate publishes (#16454) ─────────────────────
17301858
#

‎scripts/check-aggregator-roster.mjs‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -499,7 +499,7 @@ async function selfTest() {
499499
const dropped = [
500500
{ label: 'typecheck loses a lane from needs:', file: 'lint.yml', member: 'typecheck-consumers', from: ' - typecheck-consumers\n', to: '' },
501501
{ label: 'lint loses a lane from needs:', file: 'lint.yml', member: 'lint-pm-dispatch-gates', from: ' - lint-pm-dispatch-gates\n', to: '' },
502-
{ label: 'test-gate loses its matrix from needs:', file: 'ci.yml', member: 'test', from: ' needs: [test, filter]\n', to: ' needs: [filter]\n' },
502+
{ label: 'test-gate loses its matrix from needs:', file: 'ci.yml', member: 'test', from: ' needs: [test, filter, test-plan]\n', to: ' needs: [filter, test-plan]\n' },
503503
{
504504
label: 'dogfood-gate loses the CLI pass from needs:',
505505
file: 'ci.yml',
@@ -528,7 +528,7 @@ async function selfTest() {
528528
const phantom = [
529529
{ label: 'typecheck needs a job that was deleted', file: 'lint.yml', from: ' - typecheck-consumers\n', to: ' - typecheck-consumers\n - typecheck-ghost\n', ghost: 'typecheck-ghost' },
530530
{ label: 'lint needs a job that was deleted', file: 'lint.yml', from: ' - lint-pm-dispatch-gates\n', to: ' - lint-pm-dispatch-gates\n - lint-ghost\n', ghost: 'lint-ghost' },
531-
{ label: 'test-gate needs a job that was deleted', file: 'ci.yml', from: ' needs: [test, filter]\n', to: ' needs: [test, filter, test-ghost]\n', ghost: 'test-ghost' },
531+
{ label: 'test-gate needs a job that was deleted', file: 'ci.yml', from: ' needs: [test, filter, test-plan]\n', to: ' needs: [test, filter, test-plan, test-ghost]\n', ghost: 'test-ghost' },
532532
{
533533
label: 'dogfood-gate needs a job that was deleted',
534534
file: 'ci.yml',

0 commit comments

Comments
 (0)