Skip to content

ci: each test job decides its own scope; fold and drop unit-level jobs - #8888

Closed
cixzhang wants to merge 6 commits into
lane2-wip/plan1-preview-basefrom
ci/test-scope-consolidation
Closed

cixzhang wants to merge 6 commits into
lane2-wip/plan1-preview-basefrom
ci/test-scope-consolidation

Conversation

@cixzhang

@cixzhang cixzhang commented Oct 29, 2026 •

Copy link
Copy Markdown
Contributor

Superseded. This change is now split into four atomic PRs that land in order:

  1. ci: fold fixture-contrast into test-build #8916: fold fixture-contrast into test-build
  2. ci: interaction and native Swift decide their own scope #8917: the test-scope action, used by interaction and native Swift
  3. ci: the docsite, test and build owners decide their own scope #8918: the docsite, test and build owners
  4. ci: the heavy lanes decide their component scope; drop the scope jobs #8919: the heavy lanes, and removal of the scope jobs

Together they carry the same result. This PR is closed in their favor.

Who this helps: everyone opening a pull request. Each CI run starts 4 fewer jobs, and no test waits about a minute for a scope job before it can start.

Stacked: on #8826, #8881, #8880, #8877 and #8884, which land first. The base moves to main and the branch is rebased once they merge. The off-PR and informational checks (registry contract, ci-duration, the weekly sweeps, test-deps) are split into a follow-up PR stacked on this one.

What changes

  • No scope jobs. check-scope, interaction-scope, native-swift-scope and check-components are removed.
    • Each job that owns tests runs .github/actions/test-scope right after a sparse checkout. The action reads the PR's file list and the base commit's classifier (change-scope and its two helpers).
    • When nothing in the job's scope changed, every later step skips and the job reports success.
    • The step puts everything in scope when it can't decide: no PR, an API error, an empty or truncated file list, or a classifier missing at the base (spec:AST-030 FR3, FR4).
    • A classifier that rejects the change still fails the job (a spec-only change with a Changeset).
  • One pre-job stays. native-swift-runner exists because only it chooses a runner (macOS when Swift changed), and a runner has to be picked before the job starts. It runs on a free slim runner for a few seconds.
  • Heavy lanes keep their scope. On a ci:heavy-lanes PR the accessibility, visual and RTL audits still cover only the affected components (spec:AST-030 FR13).
    • With component-audit: true, the same action also runs the base's component-audit-scope.cjs and lists the changed files.
    • Each audit job skips its work, downloads included, when nothing in its component scope changed. A spec-, docsite- or tooling-only labelled PR therefore never waits for an analysis artifact that was never built.
    • The RTL jobs pass the decision to rtl-shard-scope.mjs and rtl-join.mjs. The join accepts shards that decided their scope and did nothing.
    • The visual jobs run visual-scope.mjs on the file list.
    • Release-check (outside a PR) stays full scope.
  • A release bump applies no audit. On a release-branch PR the action reports no component audit, and the visual jobs report no stable visual work on any specialized lane.
  • An undecided scope never skips validation. The action reports decided. docsite-test validates spec records when its own scope is spec-only or undecided, so a failed lookup can't pass the required check unvalidated (FR4).
  • Required contexts are unchanged. test, build and docsite-test still report on every PR. The test join also requires release-bump to pass on a release-branch PR, which check-scope used to enforce.
  • Folded: fixture-contrast's two steps run in test-build, which already installs Playwright on the same runner (one job fewer per run).
  • Self-triggering: a change to the scope action itself runs the interaction proofs and the native Swift job.

Line count

+1,724 / −1,587, net +137.

  • This PR is net positive by 137 lines. The per-job audit gating, the decided path and the routing tests that pin them are all review findings.
  • The rest of item 9 (ci: drop off-PR and informational checks #8894) is net −1,058, so the pair is about −900.

Why the scope-job removal is one PR

  • check-scope feeds 19 jobs and the required joins, and check-components feeds the audit jobs.
  • Migrating job groups one at a time would keep both classifiers, plus a second scope mechanism, alive across several PRs. Each step would need its own CI restart, and every intermediate state would need its own fail-safe review.
  • The fixture fold is two steps moved into test-build's existing Playwright runner. Separating it would first mean giving fixture-contrast its own scope step, only to delete it in the next PR.
  • The main-heavy-lanes.yml registry entry keeps this stack's tests green until the slimmed ci: every workflow declares when it runs, as an allowlist (AST-030 FR14) #8884 removes that registry entirely. At that rebase it's dropped, not kept.

Testing

  • vitest run --project node .github/scripts internal/shadcn-registry internal/lab-readiness: all pass (1,580).
  • Routing tests (FR9):
    • a labelled out-of-scope change leaves the RTL shards as no-ops and the join green;
    • the audit and visual steps are gated on their component scope;
    • docsite-test validates when its scope is undecided (the step's own condition);
    • each heavy lane's analysis download is gated;
    • a release bump gets no component audit.
  • New test-scope-action.test.mjs runs the action's real script against a stubbed GitHub API serving this repo's classifier files. It covers:
    • every fail-safe path, renames, a specialized lane, the classifier rejection and the head-file notice;
    • component-audit scope, outside a PR, on a component change, on a specialized lane, and with the audit classifier missing.
  • pnpm lab:readiness:check passes.

Removes the scope classifier jobs (check-scope, interaction-scope,
native-swift-scope, check-components). Each job that owns tests now runs
.github/actions/test-scope right after a sparse checkout: it reads the pull
request's file list and the base commit's trusted classifier, and skips the
job's remaining steps with success when nothing in its scope changed.
Anything it cannot decide puts everything in scope (spec:AST-030 FR3, FR4).
native-swift-runner is the one pre-job left, because only a job's runner has
to be chosen before it starts.

Also:
- fixture-contrast's steps run in test-build (same Playwright runner);
- registry-contract becomes test-registry-e2e and runs for release-check
  only; its network-free production gate runs with the unit tests;
- ci-duration and the weekly a11y and RTL workflows are removed (the full
  audits run on main);
- the Internal Registry job becomes test-deps, scoped by its trigger paths;
- the test join requires release-bump on a release-branch pull request.
@vercel

vercel Bot commented Oct 29, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
astryx Ready Ready Preview Oct 29, 2026 8:52am UTC

Request Review

@meta-cla meta-cla Bot added the CLA Signed This label is managed by the Meta Open Source bot. label Oct 29, 2026
@astracat-bot

astracat-bot Bot commented Oct 29, 2026 •

Copy link
Copy Markdown

AI review status for this pull request.

Review status Updated
🟡 Reviewed (for maintainers only) Oct 29, 2026, 9:19 AM UTC

The main-tier release-check dispatcher runs on every landed commit, like the
Main workflow, so it is a landed-commit workflow.

@astracat-bot astracat-bot Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approval needs an engineering-owner decision first: moving the full-catalog ShadCN registry check off pull requests (draft spec:AST-026 FR10 says required pull-request CI runs it) and deleting the ci-duration job are not settled by any current spec, so an engineering owner records that decision by commenting /approve-spec <full head SHA> on this pull request, and the author may do so if they are an engineering owner. Whatever that decision, these need changes:

  • A ci:heavy-lanes pull request now runs the whole visual plan and the full RTL roster (ci.yml:2079, :2366), but AST-030 FR13 and rtl-audit-evidence INV7 limit pull-request visual and RTL audits to the affected components.
  • Deleting rtl-weekly.yml removes the scheduled full-roster audit that INV7 still requires, so keep it or amend INV7 and its verification row with owner approval.
  • Add .github/actions/test-scope/ to the paths of pr-interaction and native-swift-runner, so a change to the scope action runs the jobs it gates.
  • When docsite-test's own scope step falls back on a spec-only pull request, it skips Validate spec records while the docsite owners skipped as spec-only, so make that job validate the records, or fail, when its scope is undecided.
  • Please split the registry, ci-duration, weekly-sweep and Internal Registry changes out of this 3,967-line pull request.

[Automated review]

Comment thread .github/workflows/ci.yml Outdated
if: github.event_name == 'pull_request'
env:
BROAD_STABLE_VISUAL: ${{ needs.check-components.outputs.broad_stable_visual }}
BROAD_STABLE_VISUAL: 'true'

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hard-coding BROAD_STABLE_VISUAL: 'true' sends every opted-in pull request to the whole stable plan (COMMAND=release), so the focused tiers below can no longer run. AST-030 FR13 has a pull request's visual audit cover the changed components and their reverse dependents; derive this flag from the analysis artifact or visual-scope.mjs, as the deleted check-components output did.

Comment thread .github/workflows/ci.yml Outdated
--force-full "${{ needs.check-components.outputs.force_full_component_audits }}" \
--has-components "${{ needs.check-components.outputs.has_rtl_components }}" \
--has-harness "${{ needs.check-components.outputs.has_rtl_harness }}" \
--force-full true \

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

--force-full true makes every opted-in pull request audit the full RTL roster. AST-030 FR13 and rtl-audit-evidence INV7 scope a pull request's RTL audit to the affected components; pass the analysis artifact's forceFullComponentAudits here instead, and the matching value in the pr-rtl join (line 2574).

Comment thread .github/workflows/ci.yml Outdated
id: scope
uses: ./.test-scope/.github/actions/test-scope
with:
paths: '^(internal/interaction-harness/|internal/a11y-spec/|playwright\.interaction\.config\.ts$|packages/[^/]+/src/|packages/themes/[^/]+/src/|apps/storybook/|\.github/workflows/ci\.yml$|package\.json$|pnpm-lock\.yaml$)'

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The code that decides applies moved from ci.yml, which this pattern still lists, to .github/actions/test-scope/, which it does not. A pull request that changes only the action gets applies=false here, so the proofs never run on it. Add |\.github/actions/test-scope/ (and the same to native-swift-runner's paths, line 760), with a touched case in ci-interaction.test.mjs.

Comment thread .github/workflows/ci.yml
if: github.event_name == 'workflow_dispatch' && inputs.operation == 'release-check'
# The preview and hidden production registries are generated and verified
# independently, so each gets its own leg (~9 minutes in one job). `test`
# joins them: the job result is success only when both passed.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

test no longer joins test-registry-e2e; release-check does. This comment now points at the wrong join.

Comment thread docs/architecture/rtl-audit-evidence.md Outdated
report completeness and exclusion visibility remain requirements, not authority
to suppress findings.
- `.github/workflows/rtl-weekly.yml` — full-roster scheduled audit.
- `.github/workflows/ci.yml` release-check — the full-roster audit on every push

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

INV7 still says "The scheduled unfiltered audit checks the full live roster and shared regressions. Neither silently replaces the other.", and its verification row still names the weekly-summary tests this pull request deletes. Keep rtl-weekly.yml, or amend INV7 and that row with owner approval; per-push release-check is the stacked DEC-7, which main's AST-030 FR12 does not allow ("Release checks MUST NOT run on every push").

…per job

Review follow-ups:
- The registry contract, ci-duration, the weekly sweeps and the Internal
  Registry rename move to their own pull request, so this one only removes
  the scope classifier jobs and folds fixture-contrast into test-build.
  registry-contract stays a pull-request job here, deciding its own scope.
- A ci:heavy-lanes pull request keeps its visual and RTL audits on the
  affected components (spec:AST-030 FR13): the scope action can also run the
  base's component-audit-scope.cjs and list the changed files, and the RTL
  and visual jobs read those instead of a classification job.
- The interaction and native Swift scopes include the scope action, so a
  change to it runs the jobs it gates.
@cixzhang

Copy link
Copy Markdown
Contributor Author

Posted by Cindy's agent on her behalf.

/approve-spec 910c16e

@astracat-bot astracat-bot Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changes are needed before this can merge, although the per-job scope step follows AST-030's trusted-base and fail-closed rules and the earlier registry, ci-duration and weekly-sweep concerns are resolved. Please fix the two routing defects below with routing tests for each (AST-030 FR9), and split the change along its job groups or show why it cannot land in parts.

  • On a ci:heavy-lanes pull request outside RTL scope, such as an internal/a11y-spec/ or CLI-only change, pr-rtl-shard now always runs while rtl-join.mjs still accepts an out-of-scope result only from skipped shards, so pr-rtl fails (and on a spec-, docsite- or tooling-only pull request with the label, the heavy jobs fail downloading a pr-analysis artifact that build-storybook never uploads).
  • If docsite-test's own scope lookup fails on a spec-only pull request, it falls back to everything in scope, sees the docsite owners' skipped success and skips Validate spec records, so the required check passes with no record validated.
  • At 3,080 changed lines the migration still decomposes by job group, and the fixture-contrast fold and the main-heavy-lanes.yml registry entry are separable.

[Automated review]

Comment thread .github/workflows/ci.yml
(needs.check-components.outputs.has_rtl_components == 'true' ||
needs.check-components.outputs.has_rtl_harness == 'true' ||
needs.check-components.outputs.force_full_component_audits == 'true')
(github.event_name == 'workflow_dispatch' && inputs.operation == 'release-check'))

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Without its scope gate this job now runs on every ci:heavy-lanes pull request and reports success, but rtl-join.mjs still accepts --should-run false only when the shards were skipped (rtl-join.mjs:161). An opted-in change outside RTL scope, such as internal/a11y-spec/ or packages/cli/, therefore turns pr-rtl red, after the full-suite case has audited every package's full roster. Gate this job's work on the scope outputs, as pr-rtl-merge does, and let the join accept a successful no-op shard result, with a routing test for that case.

Comment thread .github/workflows/ci.yml Outdated

- name: Validate spec records
if: needs.check-scope.outputs.spec_only == 'true'
if: steps.scope.outputs.spec_only == 'true'

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this job's own scope lookup falls back (a 403 is not retried) while docsite-browser and docsite-data decided spec-only, spec_only is false here, both result checks above see success, and this step skips: the required docsite-test passes with no spec record validated, and test-ui's knowledge check skipped too. Report whether the scope was decided and validate, or fail, when it was not (AST-030 FR4).

Comment thread .github/workflows/ci.yml
needs: [build-storybook, check-components]
if: ((github.event_name == 'pull_request' && contains(github.event.pull_request.labels.*.name, 'ci:heavy-lanes')) || (github.event_name == 'workflow_dispatch' && inputs.operation == 'release-check')) && needs.check-components.outputs.has_components == 'true'
needs: [build-storybook]
if: ((github.event_name == 'pull_request' && contains(github.event.pull_request.labels.*.name, 'ci:heavy-lanes')) || (github.event_name == 'workflow_dispatch' && inputs.operation == 'release-check'))

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With check-components gone, nothing gates this job on component scope: on a spec-, docsite- or tooling-only pull request with ci:heavy-lanes, build-storybook uploads no pr-analysis, so the download below fails and pr-a11y turns red where the base skipped it. The same holds for pr-a11y-specs, pr-visual and the RTL shards. Add the scope step and gate the work, downloads included, on its audit outputs.

@cixzhang

Copy link
Copy Markdown
Contributor Author

/approve-spec 910c16e

Posted by Cindy's agent on her behalf.

…ocsite-test validates

Review follow-ups:
- On a ci:heavy-lanes pull request, the accessibility, visual and RTL jobs
  decide their own component scope and skip their work, downloads included,
  when it does not apply, so a spec-, docsite- or tooling-only change no
  longer fails on a missing analysis artifact. The RTL join accepts shards
  that decided their scope and did nothing.
- The scope action reports whether it decided. docsite-test validates spec
  records when its own scope is spec-only or undecided, so a failed lookup
  can never pass the required check unvalidated (spec:AST-030 FR4).
cixzhang added a commit that referenced this pull request Oct 29, 2026
cixzhang added a commit that referenced this pull request Oct 29, 2026
@cixzhang

Copy link
Copy Markdown
Contributor Author

/approve-spec 215480c

Posted by Cindy's agent on her behalf.

cixzhang added a commit that referenced this pull request Oct 29, 2026
# Conflicts:
#	.github/scripts/spec-owner-workflow.test.mjs
#	.github/workflows/ci.yml
@cixzhang

Copy link
Copy Markdown
Contributor Author

/approve-spec 215480c

@astracat-bot astracat-bot Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changes are needed before this can merge: the per-job scope step follows AST-030's trusted-base and fail-closed rules, and the earlier docsite-test fallback and out-of-scope heavy-lane failures are fixed, but three points remain.

  • Split the 3,235-line change by job group, or show which current AST-030 claim an intermediate state would break; the fixture-contrast fold can land first on its own.
  • Add tests that fail when the decided clause is dropped from Validate spec records or when the heavy-lane pr-analysis download gates are removed (AST-030 FR9); today only the setup step's condition is pinned.
  • On a release-branch pull request the scope action reports every audit in full, so a release bump labelled ci:heavy-lanes still fails pr-a11y, Stable visual regression and pr-rtl; report no audits there.

[Automated review]

Comment thread .github/workflows/ci.yml

- name: Validate spec records
if: needs.check-scope.outputs.spec_only == 'true'
if: (steps.scope.outputs.spec_only == 'true' || steps.scope.outputs.decided != 'true')

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No test pins this decided clause: spec-owner-workflow.test.mjs checks the condition only on the setup step above, so dropping it here leaves every routing test green while an undecided spec-only pull request passes docsite-test unvalidated (AST-030 FR9). Assert this step's own if directly, as ci-docsite-browser.test.mjs does for the docsite owners' steps.

Comment thread .github/actions/test-scope/action.yml Outdated
const pr = context.payload.pull_request;
if (pr.base.ref.startsWith('release/v')) {
core.info('Release-branch pull request: the generated version bump.');
report({...everything, spec_only: 'true', tooling_only: 'true', release_bump: 'true', touches: 'false', applies: 'false'});

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For component-audit jobs, everything already holds every audit in full (line 143) and this return sets no changed_files, so a release bump labelled ci:heavy-lanes sends pr-a11y-guards, pr-a11y-specs, pr-rtl-shard and pr-visual after a pr-analysis artifact that build-storybook never uploads on this lane. Spread auditNone here when audit is set, as the comment at line 129 intends, and have the visual scope step report no stable visual work on a specialized lane.

Review follow-ups:
- On a release-branch pull request the scope action reports no component
  audit, and the visual jobs report no stable visual work on any
  specialized lane, so a labelled release bump never waits for an analysis
  artifact that was never built.
- Routing tests pin docsite-test's own validation condition and every heavy
  lane's analysis-download gate (spec:AST-030 FR9).
cixzhang added a commit that referenced this pull request Oct 29, 2026
cixzhang added a commit that referenced this pull request Oct 29, 2026
@cixzhang

Copy link
Copy Markdown
Contributor Author

/approve-spec 50f50e0

@astracat-bot astracat-bot Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changes are needed before this can merge: the per-job scope step follows AST-030's trusted-base and fail-closed rules and the release-bump heavy-lane fix works, but two points remain.

  • Split the 3,311-line change by job group, or show which current AST-030 claim an intermediate state would break; the fixture-contrast fold can land first on its own, since at the base it and test-build share the same scope condition and runner.
  • The test "reports no stable visual work on a specialized lane" (ci-test-routing.test.mjs:884) still passes when the short circuit is deleted, because indexOf returns -1; assert that the index exists, or run the step with SPECIALIZED=true and expect has_stable_visual=false (AST-030 FR9).

[Automated review]

Comment on lines +884 to +886
expect(decide.run.indexOf('"$SPECIALIZED" = true'), name).toBeLessThan(
decide.run.indexOf('!= pull_request'),
);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This still passes when the short circuit is deleted: indexOf returns -1, which is less than the index of != pull_request. With the block removed from both Decide stable visual scope steps, every .github/scripts test passes, so a labelled release bump could again go after artifacts that are never built with CI green (AST-030 FR9). Assert toBeGreaterThanOrEqual(0) on the index first, or run the step script with SPECIALIZED=true and no files and expect has_stable_visual=false.

cixzhang added a commit that referenced this pull request Oct 29, 2026
cixzhang added a commit that referenced this pull request Oct 29, 2026
@cixzhang

Copy link
Copy Markdown
Contributor Author

/approve-spec 1f3c699

@astracat-bot astracat-bot Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changes are needed before this can merge: the per-job scope step follows AST-030's trusted-base and fail-closed rules, and the specialized-lane visual test now fails when the short circuit it pins is deleted, but at 3,313 changed lines the change still needs to be split or shown to be inseparable. Please split it along its job groups (for example the scope action with the docsite and test owners, then the build owners, then the heavy lanes with check-components, then the interaction, native Swift and helper removal), or name the current AST-030 claim an intermediate state would break; the fixture-contrast fold can land first on its own, since at the base it and test-build share the same scope condition and runner.

[Automated review]

@cixzhang cixzhang closed this Oct 30, 2026
cixzhang added a commit that referenced this pull request Oct 30, 2026
#8888 is superseded by four atomic PRs; this change now sits on the last of
them. The relevance-registry edit drops out: that registry is not on main.
cixzhang added a commit that referenced this pull request Oct 30, 2026
#8888 is superseded by four atomic PRs; this change now sits on the last of
them. The relevance-registry edits drop out: that registry is not on main.
cixzhang added a commit that referenced this pull request Oct 30, 2026
…csite-test

#8888 was split into #8916-#8919 on main. This carries the one-job
docsite-test change onto #8919 as a 3-way merge against its v8: the
docsite jobs are identical there, so the delta applies unchanged.
cixzhang added a commit that referenced this pull request Oct 30, 2026
…t's base

#8888 was split into four pull requests; this one now stacks on #8919.
The tree is #8919 plus this pull request's own changes, re-applied to
its job layout: the per-author heavy caps (FR16), the draft and
outside-contributor gate (FR17), CONTRIBUTING and the tests.
cixzhang added a commit that referenced this pull request Oct 30, 2026
This pull request now stands alone on main. The tree is main plus its own
change, re-applied to main's job layout:

- the draft and outside-contributor gate on every pull_request workflow,
  with the required checks failing for an unlabelled fork (FR17);
- the per-author heavy-suite caps as a standalone heavy-caps step that
  pr-interaction, test-build and fixture-contrast run right after their
  checkout, each limit one entry in an ordered list (FR16);
- CONTRIBUTING, the spec and the tests.
@github-actions
github-actions Bot deleted the ci/test-scope-consolidation branch October 31, 2026 06:09

This branch was successfully deployed

1 active deployment
Preview — 1f3c6997 Deployed Oct 29, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CLA Signed This label is managed by the Meta Open Source bot.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant