Skip to content

ci: run a touched component's own accessibility contracts beside the tests, and block the merge on them - #9707

Merged
cixzhang merged 9 commits into
mainfrom
ci/pr-component-a11y-contracts
Nov 4, 2026
Merged

cixzhang merged 9 commits into
mainfrom
ci/pr-component-a11y-contracts

Conversation

@cixzhang

@cixzhang cixzhang commented Nov 3, 2026 •

Copy link
Copy Markdown
Contributor

Maintainer impact

A component's own browser accessibility contracts do not run on that component's pull request. About 50 spec files across about 30 components are release-only, so the first check that runs them is main's release check. Example: #9636 merged green while Switch's aborted-press contract (WCAG 2.5.2) failed. The release check found it, and the release waited for a fix-forward. #9611 (Selector) and #9169 (RadioList) skipped their contracts the same way.

Intended invariant

spec:AST-030 FR11, FR12, FR13, DEC-7 and DEC-22, as amended by #9693:

  • One contract job runs the touched components' contracts, in parallel with the unit-test jobs. It needs only the Storybook build and must finish before the slowest unit-test job, so time to green does not grow.
  • It runs the changed browser accessibility specs (at most four), then each touched component's own scoped and release-only specs. It runs no spec of another component or a reverse dependent, no path-token match on a shared module, and no unchanged audit or always spec. A wide change runs only its changed specs.
  • It stops after eight minutes, and it starts a file only when that file's recorded time fits. A file it does not run is "not run" in the check's summary and annotations, with the label that runs it. That file passes the join and is never split.
  • A failing contract fails the existing required test join. No new required context is added.
  • The labelled ci:run-a11y run also gets the release-only specs that a changed component owns directly, but never through a scope token or an unknown scope.

Change

.github/scripts/a11y-pr-contracts.mjs

  • --owned-by <components> and --changed-files <file>, used with --changed-only, add ownedSpecs(). It is the labelled run's own selectSpecs() with two limits: only scoped and release-only specs, and only changes inside a component folder (packages/<pkg>/src/<Name>/). So it uses the same mapping. A spec is owned when:

    • it is named for a touched component or sits in that component's folder;
    • its component folder changed;
    • it is scoped and a scope token names a touched component, or matches a change inside a component folder (for example, the slider pattern contract for a Slider/Slider.tsx edit).

    A change to a shared module, the contract harness or a story owns nothing. --changed-files gives every changed path, and the workflow passes the scope step's changed_files_path.

  • planDefaultCheck() runs the changed specs first (at most four), then the owned specs in path order. projectedMs() projects the run on the job's workers from .github/a11y-contract-durations.json, with a 1.25× margin and 30 s for start-up. Every file, changed or owned, starts only if the projected run still ends inside 8 minutes. The other files are not run. An edited TokenizerEndLane spec, for example, is not run, so the stop never cuts it.

  • summaryMarkdown() writes the job summary. Each not-run file also gets a ::notice, and selection.json records the plan.

  • selectSpecs(): on a pull request, a release-only spec is selected only by direct ownership (the spec changed, its folder changed, or its named component is in scope). Never by a scope token, and never on an unknown scope.

.github/workflows/ci.yml

  • New job pr-component-a11y-contracts ("Component accessibility contracts"). It needs [room, admission, heavy-ci, build-storybook], not pr-visual or the touched check. It runs on ubuntu-latest with 4 Playwright workers and a 15-minute limit. It gets the touched components from the same touched-components.mjs scope call the touched check makes. Outside a pull request, on a specialized lane (spec, docsite, tooling or Lab only), or with nothing in packages/*/src/ or internal/a11y-spec/ changed, it skips its work and reports success.
  • If the scope step applies but cannot list the changed files, the job fails ("Require the changed-file list"). The runner also refuses --owned-by without --changed-files. A contract that was not chosen therefore never reads as passed.
  • test needs it, and ci-test-join.mjs requires its success.
  • The $comment in .github/a11y-contract-durations.json now says the numbers also plan the pull-request job.
  • pr-touched-components no longer runs the changed specs. That run moved into the new job, so the touched check stays advisory: visual, axe and RTL.

Required-check mechanism

The existing required test join waits on the new job (option ii). Branch protection does not change, no new required name is added, and the merge guard needs no change. This adds nothing to time to green only because the job ends before test's slowest current need. The evidence below shows that.

Evidence

Times are minutes after the run's admission job ends, so CI-slot queueing is left out. "Slowest need" is the latest of test's current needs (test-ui and test-node legs, test-build, pr-interaction, pr-interaction-webkit). In every run it was test-node (4/4).

Component PR Run Storybook built Contract job ends Slowest need ends test (time to green) Contract job ends this much earlier
Switch, before #9636 run 3.5 — 15.0 15.5 —
Switch, before #9686 run 4.0 — 15.7 16.4 —
Switch, after proof head run 3.5 5.7 15.3 15.9 9.6
Selector, before #9611 run 2.9 — 17.6 18.3 —
Selector, after proof head run 4.1 7.6 18.1 18.7 10.5
RadioList, before #9042 run 3.7 — 17.7 18.1 —
RadioList, after proof head run 3.4 6.1 15.7 16.2 9.6
  • Time to green does not change. The contract job ends 9.6 to 10.5 minutes before the slowest existing need, so test starts when it did before. The after-minus-before differences in test are run-to-run variation in test-node (4/4), which this PR does not touch. The fix(RadioList, Switch): required inputs take their invalid state from status #9042 run predates the current admission jobs.
  • Each proof run actually ran the owned contracts:
    • Switch: Switch.a11y.chromium.spec.ts (release-only), 12 tests passed in 58 s on 1 worker.
    • Selector: 5 owned files (Listbox and Selector are release-only) plus the combobox pattern, through its core/Selector scope token. 60 tests passed in 2.6 min on 4 workers.
    • RadioList: RadioGroup.a11y.chromium.spec.ts (release-only), 28 tests passed in 1.7 min.
    • Before this change, none of these files ran on a PR.
  • Wider check: across 17 recent component PRs, Storybook finished about 4 minutes in, and the slowest need finished 9.9 to 25 minutes after Storybook. The job's worst case is about 1.5 minutes of setup plus the 8-minute stop, and an owned file starts only if its projected time fits. Two files never fit and are reported as not run: TokenizerEndLane (552 s recorded) and TextInput (479 s recorded, too long with the margin).
  • At the final head, a Selector change also owns FieldDisabledForcedColorsFocus (its path token names the Selector folder). That file has one test and no recorded time, so it is planned at the median of 17 s. Listbox (325 s) still sets the end of the Selector run on 4 workers. Switch and RadioList select the same files as in their proof runs.
  • Concurrency: at most 11 jobs were in progress at once in each proof run. Recent component PRs before this change peaked at 10 to 11. Admission's pull-request peak of 16 still covers this, so it is unchanged.
  • The proof heads touched Switch.tsx, Selector.tsx and RadioList.tsx with a one-line comment, one at a time. The final head drops it and carries only the CI change, so on this PR the new job skips as tooling-only. The workflow is the same as in the three runs above. On the proof heads, Lint's Changeset coverage check failed because of the temporary component touch. The final head has no such touch.

CI impact:

  • Runner class: the new pr-component-a11y-contracts job runs on standard ubuntu-latest. pr-touched-components stays on standard ubuntu-24.04-arm, and one step moves out of it.
  • Job-minutes per affected update: a component PR goes from about 93–99 to about 95–102 (+2.1 Switch, +3.4 Selector, +2.6 RadioList; with Listbox, Selector is the largest common case). A PR that edits a browser accessibility spec saves that spec's run time in pr-touched-components, and the new job spends about the same. A PR outside the job's scope gets a scope-only run of about 0.1–0.2 min. Specialized lanes skip.
  • Runner-pool use: about 759 CI pull-request runs a day, most of them skipped drafts. If about 140 a day are component updates, that adds about 380 runner-minutes a day. Other updates add about 30. That is under 2% of the pool, and average concurrency rises by about 0.3 jobs.
  • Time to green: unchanged (table above).

Scope

  • No intended public API, product behavior, visual direction, or policy change.
  • Product changes discovered during the work were removed or split.
  • Public text and artifacts contain no internal Meta context.

Testing

  • vitest run .github/: 107 files and 2,473 tests pass.
  • a11y-pr-contracts.test.mjs:
    • ownership: folder or name, folder change, component token, a path token naming a touched component's folder (slider pattern with Slider/Slider.tsx). Not owned: a path token matching a shared module or the harness, a shared pattern change, a reverse dependent, an audit or always spec. Nothing is owned on a wide change. Against the repository's real specs, a Chat/ChatLayout.tsx change owns useChatStreamScroll and ChatLayoutScrollButtonSurface, and a Slider/Slider.tsx change owns the slider pattern contract.
    • budget: one worker against four; the changed spec counted first; no file run twice; a changed 552 s spec and TextInput are reported as not run and never started; the median for an unrecorded file.
    • projection, per-file seconds, summary text, labelled release-only routing.
  • The runner refuses owners without the changed-file list (spawned script, both a missing and an empty --changed-files). A workflow test checks the fail step's condition and that it comes before the run step.
  • A local dry run with a changed TokenizerEndLane spec and a Chat/ChatLayout.tsx change, owner core/ChatLayout, selects the 6 Chat folder specs. It reports the Tokenizer spec as not run.
  • touched-components.test.mjs: the new job's needs and gate, the test join wiring, skip-with-success, the run command and env (including the full changed-file list), the step limit under the job limit. The touched check runs no contract.
  • ci-test-join.test.mjs: failure, skipped, cancelled or missing contract result fails the join.
  • ci-admission, ci-test-routing, ci-tooling-routing: the new job carries the admission guard and asks for the component-audit outputs. It is part of test's needs.
  • actionlint (with shellcheck) reports no new finding, and bash -n passes on every changed run: script.

@vercel

vercel Bot commented Nov 3, 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 Nov 4, 2026 8:45am UTC

Request Review

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

astracat-bot Bot commented Nov 3, 2026 •

Copy link
Copy Markdown
Contributor

AI review status for this pull request.

Review status Updated
🟢 Reviewed (for maintainers only) Nov 4, 2026, 8:50 AM UTC

@github-actions

github-actions Bot commented Nov 3, 2026 •

Copy link
Copy Markdown
Contributor

PR Analysis Report

🔎 Preview · d462084

Storybook · Sandbox · built from this PR's head

Package size: no published package changed; no new dependencies.

API changes: none in this pull request

Committed API report behind this build: cli 8 rows, theme-butter 1 row, theme-chocolate 1 row, theme-gothic 1 row, theme-matcha 1 row, theme-neutral 1 row, theme-stone 1 row, theme-y2k 1 row; run pnpm api:report.

Not run on this PR: add ci:run-visual, ci:run-a11y or ci:run-rtl to run them.

CI impact: −0.8 job-min per update · pool −0.8% · standard runners · 9 CI action or script files changed (not measured)
Before After Change
Job-minutes per PR update 92.7 91.9 −0.8
Runner pool, runner-minutes a day 32,717 32,440 −277 (−0.8%)
Average concurrent jobs 22.7 22.5 −0.2
Time to green (median → this run) — 16.7 min —
Runs a day 353 353 0

Changed jobs: Component accessibility contracts added: standard · Touched components changed: standard · test changed: standard

Not measured (the jobs that use these keep their recent minutes above): .github/scripts/a11y-pr-contracts.mjs, .github/scripts/a11y-pr-contracts.test.mjs, .github/scripts/ci-admission.test.mjs, .github/scripts/ci-test-join.mjs, .github/scripts/ci-test-join.test.mjs, .github/scripts/ci-test-routing.test.mjs, .github/scripts/ci-tooling-routing.test.mjs, .github/scripts/interaction-legs.test.mjs, .github/scripts/touched-components.test.mjs.

Assumptions: before is each job's average over the last 20 pull-request runs; after keeps that for unchanged jobs and uses this run's minutes for added or changed ones, weighted by how often each changed job ran in those runs; runs a day averages the seven whole days before today; time to green compares this run with the median of those runs that ran the same jobs.


Generated by PR Enrichment workflow | View full report

@astracat-bot astracat-bot Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This needs changes before it can merge. The contract job and its test wiring follow AST-030 FR11, FR12 and DEC-22; these points remain:

  • A changed spec skips the recorded-time check in planDefaultCheck, so editing TokenizerEndLane.a11y.chromium.spec.ts (551.8 s) or TextInput.a11y.chromium.spec.ts starts a file the 8-minute stop cuts and fails test, where AST-030 FR12 says such a file is reported as not run.
  • ownedSpecs gets only the changed-spec list in CI, so its folder-change branch never fires, and edits in Chat, Layer or NavMenu skip owned contracts the labelled run selects, such as ChatLayoutScrollButtonSurface and useChatStreamScroll.
  • Remove the proof-run comment at packages/core/src/Switch/Switch.tsx:1152 before review completes.
  • Add the FR19 CI impact: line (runner class, job-minutes per update before and after, runner-pool usage, time to green) from the proof runs.
  • The description says every file starts only when its recorded time fits and that a folder change owns its specs; fixing the two code issues makes that true, or else narrow the wording.

The six changed test files pass; a test with a changed over-budget spec, and a workflow test that passes the full changed-file list to the owned-spec step, would cover both fixes.

[Automated review]

Comment thread .github/scripts/a11y-pr-contracts.mjs Outdated
workers,
budgetMs = DEFAULT_CHECK_BUDGET_MS,
}) {
const {run, notRun} = withinBudget(changed);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Changed specs go straight into run here with no recorded-time check, so a changed TokenizerEndLane.a11y.chromium.spec.ts (551.8 s, about 720 s with the margin) starts and is cut by --global-timeout, failing the required test join. Running each changed file through the same STARTUP_MS + projectedMs(...) <= budgetMs test as the owned files keeps an overflow as "not run" (AST-030 FR12).

Comment thread .github/scripts/a11y-pr-contracts.mjs Outdated
? withinBudget(changedOnly(routed, changedPaths))
? planDefaultCheck({
changed: changedOnly(routed, changedPaths),
owned: ownedSpecs({specs, components: ownedBy, changedPaths}),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Under --changed-only, changedPaths is the changed-spec list, so the folder branch in ownedSpecs never sees a component file. An edit to packages/core/src/Chat/ChatLayout.tsx then skips ChatLayoutScrollButtonSurface and useChatStreamScroll (release-only), which the labelled run selects by folder. Passing the scope's full changed-file list here would fix it.

Comment thread packages/core/src/Switch/Switch.tsx Outdated
}

Switch.displayName = 'Switch';
// CI proof run only: removed before merge.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Proof-run line: please drop this commit before review completes.

@cixzhang
cixzhang marked this pull request as ready for review November 3, 2026 05:33
@cixzhang
cixzhang force-pushed the ci/pr-component-a11y-contracts branch from 630ad35 to e8d8dd5 Compare November 3, 2026 07:09
@cixzhang
cixzhang marked this pull request as draft November 3, 2026 07:16

@astracat-bot astracat-bot Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This still needs changes before it can merge. The contract job and its test wiring follow AST-030 FR11, FR12 and DEC-22, and the six changed test files pass; these points remain:

  • A changed spec skips the recorded-time check in planDefaultCheck, so editing TokenizerEndLane.a11y.chromium.spec.ts (551.8 s) or TextInput.a11y.chromium.spec.ts (478.7 s) starts a file the 8-minute stop cuts and fails test, where AST-030 FR12 says such a file is reported as not run.
  • ownedSpecs gets only the changed-spec list in CI, so its folder-change branch never fires, and an edit to Chat/ChatLayout.tsx runs none of the Chat folder's contracts that the labelled run selects, three of them release-only.
  • The proof-run comment at packages/core/src/Selector/Selector.tsx:2211 turns the required lint check red (no Changeset for @astryxdesign/core), so drop that commit as planned.
  • Add the FR19 CI impact: line (runner class, job-minutes per update before and after, runner-pool usage, time to green) to the description.
  • The description says every file starts only when its recorded time fits and that a folder change owns its specs; fixing the first two points makes that true, or else narrow the wording.

A test with a changed over-budget spec, and a workflow test that passes the full changed-file list to the owned-spec step, would cover both code fixes.

[Automated review]

Comment thread packages/core/src/Selector/Selector.tsx Outdated
}

Selector.displayName = 'Selector';
// CI proof run only: removed before merge.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Proof-run line: the required lint check fails on it (no Changeset for @astryxdesign/core). Please drop this commit before merge.

@cixzhang
cixzhang marked this pull request as ready for review November 3, 2026 07:42
@cixzhang
cixzhang marked this pull request as draft November 3, 2026 08:04
@cixzhang
cixzhang force-pushed the ci/pr-component-a11y-contracts branch from e8d8dd5 to 07119da Compare November 3, 2026 08:04
@cixzhang
cixzhang marked this pull request as ready for review November 3, 2026 08:08

@astracat-bot astracat-bot Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This needs changes before it can merge. The contract job and its test wiring follow AST-030 FR11, FR12 and DEC-22, and the six changed test files pass; these points remain:

  • A changed spec skips the recorded-time check in planDefaultCheck (.github/scripts/a11y-pr-contracts.mjs:445), so a pull request that edits TokenizerEndLane.a11y.chromium.spec.ts (551.8 s recorded) or TextInput.a11y.chromium.spec.ts (478.7 s) starts a file the 8-minute stop cuts and fails test, where AST-030 FR12 reports such a file as not run.
  • ownedSpecs gets only the changed-spec list in CI (:754), so its folder-change branch never sees a component file, and an edit to Chat/ChatLayout.tsx skips ChatLayoutScrollButtonSurface, useChatStreamScroll and two scoped Chat specs that the labelled run's mapping selects.
  • The proof-run comment at packages/core/src/RadioList/RadioList.tsx:449 turns the required lint check red (no Changeset for @astryxdesign/core), so drop that commit as planned.
  • Add the FR19 CI impact: line (runner class, job-minutes per update before and after, runner-pool usage, time to green) to the description, from the Switch and Selector proof runs as well as this head's RadioList run.
  • The description says every file starts only when its recorded time fits and that a folder change owns its specs; fixing the first two points makes that true, or else narrow the wording.

A test with a changed over-budget spec, and a workflow test that passes the full changed-file list to the owned-spec step, would cover both code fixes.

[Automated review]

Comment thread .github/scripts/a11y-pr-contracts.mjs Outdated
workers,
budgetMs = DEFAULT_CHECK_BUDGET_MS,
}) {
const {run, notRun} = withinBudget(changed);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Changed specs go straight into run here with no recorded-time check, so a changed TokenizerEndLane.a11y.chromium.spec.ts (551.8 s, about 720 s with the margin) starts and is cut by --global-timeout, failing the required test join. Running each changed file through the same STARTUP_MS + projectedMs(...) <= budgetMs test as the owned files keeps an overflow as "not run" (AST-030 FR12).

Comment thread .github/scripts/a11y-pr-contracts.mjs Outdated
? withinBudget(changedOnly(routed, changedPaths))
? planDefaultCheck({
changed: changedOnly(routed, changedPaths),
owned: ownedSpecs({specs, components: ownedBy, changedPaths}),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Under --changed-only, changedPaths is the changed-spec list, so the folder branch in ownedSpecs never sees a component file. An edit to packages/core/src/Chat/ChatLayout.tsx then skips ChatLayoutScrollButtonSurface and useChatStreamScroll (release-only), which the labelled run selects by folder. Passing the scope's full changed-file list here would fix it.

}

RadioList.displayName = 'RadioList';
// CI proof run only: removed before merge.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Proof-run line: the required lint check fails on it (no Changeset for @astryxdesign/core). Please drop this commit before merge.

@cixzhang
cixzhang marked this pull request as draft November 3, 2026 08:31
@cixzhang
cixzhang force-pushed the ci/pr-component-a11y-contracts branch from 07119da to feb9405 Compare November 3, 2026 08:31
@cixzhang
cixzhang marked this pull request as ready for review November 3, 2026 08:41

@astracat-bot astracat-bot Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This needs changes before it can merge. The earlier points are fixed: changed specs now go through the 8-minute fit, folder ownership sees every changed path, the proof-run lines are gone, and the CI impact: line is in place. One issue remains, in three parts:

  • ownedSpecs ignores every path scope token (.github/scripts/a11y-pr-contracts.mjs:378-380), so a Slider.tsx edit never runs internal/a11y-spec/src/patterns/slider.chromium.spec.ts, whose routing line names packages/core/src/Slider/, and the same holds for the pattern contracts of 15 other components (Table, DropdownMenu, Tooltip and more), which contradicts AST-030 FR12's "same @pr-a11y scope and spec-location mapping the labelled run uses".
  • ownedSpecs copies the owner loop of selectSpecs instead of reusing it, which is where the two mappings drifted apart; deriving the owned set from selectSpecs with only FR13's exclusions keeps one mapping.
  • The title and description say the job runs each touched component's own scoped contracts and skips path-token matches only on shared modules, which becomes true once a path token that names a touched component's folder counts, or else the wording should be narrowed.

An ownership test with the slider pattern and a Slider/Slider.tsx change would cover the fix.

[Automated review]

Comment thread .github/scripts/a11y-pr-contracts.mjs Outdated
Comment on lines +378 to +380
const token = routing.scope.find(
t => !t.startsWith('^') && componentMatches(t, components),
);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This drops every path token, but most name a component's own folder: internal/a11y-spec/src/patterns/slider.chromium.spec.ts is scoped to packages/core/src/Slider/, so a Slider.tsx edit never runs it, and 14 more pattern contracts are bound the same way. AST-030 FR12 asks for the same mapping the labelled run uses; counting a path token that matches a changed file in a touched component's folder keeps shared modules out.

@cixzhang
cixzhang marked this pull request as draft November 3, 2026 09:15
@cixzhang
cixzhang marked this pull request as ready for review November 3, 2026 09:16

@astracat-bot astracat-bot Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This needs one more change before it can merge. The earlier points are fixed: owned specs now come from selectSpecs, so a pattern contract bound by a path token (the slider pattern for a Slider.tsx edit) runs, and the description matches what the job selects.

  • When the scope step cannot list the changed files it sets no changed_files_path, so the readLines(changedFilesFile) ?? changedPaths fallback (.github/scripts/a11y-pr-contracts.mjs:759) leaves only contracts named for a touched component (a Chat/ChatLayout.tsx change drops from 6 owned specs to 0), reports none of the dropped ones as not run, and lets test pass, against AST-030 FR12's "a skip never reads as a pass"; failing the job when the scope applies and the list is empty would close it.

Separately, the $comment in .github/a11y-contract-durations.json still says only shard balance depends on those numbers, which stops being true once they decide what the required job starts.

[Automated review]

owned: ownedSpecs({
specs,
components: ownedBy,
changedPaths: readLines(changedFilesFile) ?? changedPaths,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

When the scope step cannot list the changed files, FILES_PATH is empty, readLines('') returns null, and this falls back to the changed-spec list. Folder- and path-token-owned contracts then drop without a not-run notice and the job still passes test (a Chat/ChatLayout.tsx change goes from 6 owned specs to 0). Failing when --changed-files is given empty, or when the scope applies with no changed_files_path, keeps a skip from reading as a pass (AST-030 FR12).

@cixzhang
cixzhang marked this pull request as draft November 3, 2026 09:39
@cixzhang
cixzhang marked this pull request as ready for review November 3, 2026 09:44

@astracat-bot astracat-bot Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This looks good to merge once the CI run on this head finishes green. The contract job and its test wiring follow AST-030 FR4, FR11, FR12 and DEC-22, and the earlier point is fixed: the job now fails when the scope step cannot list the changed files, and the runner refuses owners without that list.

One optional follow-up: the guard's "Re-run the job" message could name the scope step's warning, since a pull request whose file list GitHub truncates at 3,000 (or returns empty) fails on every re-run.

[Automated review]

Comment thread .github/workflows/ci.yml
- name: Require the changed-file list
if: steps.scope.outputs.applies == 'true' && steps.scope.outputs.changed_files_path == ''
run: |
echo "::error::The scope step could not list the changed files, so the contracts to run are unknown. Re-run the job."

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Optional: when GitHub truncates the file list at 3,000 or returns none, the scope step gives up for good (.github/actions/test-scope/action.yml:195-196), so a re-run fails the same way. Pointing at the scope step's warning here would name the real cause.

@astracat-bot astracat-bot Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This looks ready to merge once test passes on this head. This push deferred the heavy suites, so test has not run yet. The new contract job and its test wiring follow AST-030 FR4, FR11, FR12 and DEC-22, and the changed tests pass.

Two optional wording fixes, neither blocking:

  • The guard at .github/workflows/ci.yml:3746 says "Re-run the job", but a list of 3,000 or more files, or an empty list, fails the scope step on every run. Pointing to the scope step's warning would name the real cause.
  • On a wide change that also edits a spec, the job summary says "No touched component." (.github/scripts/a11y-pr-contracts.mjs:471). Saying the change was wide, with its component count, would be accurate.

[Automated review]

Comment thread .github/workflows/ci.yml
- name: Require the changed-file list
if: steps.scope.outputs.applies == 'true' && steps.scope.outputs.changed_files_path == ''
run: |
echo "::error::The scope step could not list the changed files, so the contracts to run are unknown. Re-run the job."

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Optional: when GitHub lists 3,000 or more files, or none, the scope step gives up on every attempt (.github/actions/test-scope/action.yml:195-196), so "Re-run the job" cannot help there. Pointing to the scope step's warning would name the real cause.

lines.push(
components.length
? `Touched components: ${components.map(c => `\`${c}\``).join(', ')}.`
: 'No touched component.',

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Optional: on a wide change the workflow passes an empty --owned-by, so this prints "No touched component." even when the change touched more than five components. Passing the wide flag and count would let the summary say why no owned contract ran.

@humbertovirtudes humbertovirtudes left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Re-reviewed exact head f84d638144656e916c10a43cc31c56ab49028e98. The contract runner, its regression tests, test join, scope action and Playwright configuration are byte-identical to the previously reviewed implementation; the current own merge-base patch remains confined to the ten CI/tooling files. The fail-closed changed-file guard, canonical scope selection, duration-budget checks and required-test wiring address the prior findings. No blocking code finding remains.

This is a code approval, not a passing-CI certificate. The current build, test and docsite-test jobs stopped at the explicit Heavy CI deferred step under AST-030 FR20. Required execution must still finish green before merge.

@astracat-bot astracat-bot Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This needs one change before it can merge. The new contract job and its test wiring follow AST-030 FR4, FR11, FR12 and DEC-22.

  • Regression: merging main brought in .github/scripts/interaction-legs.test.mjs, whose join case (lines 247-267) runs ci-test-join.mjs without PR_COMPONENT_A11Y_CONTRACTS_RESULT, so the join now exits 1 and test-node fails; add PR_COMPONENT_A11Y_CONTRACTS_RESULT: 'success' to its passing env.

The two optional wording fixes from the last review still apply: the guard's "Re-run the job" (.github/workflows/ci.yml:3830) and "No touched component." on a wide change (.github/scripts/a11y-pr-contracts.mjs:471). CI has not run the heavy suites on this head yet.

[Automated review]

Comment on lines +23 to +26
[
'pr-component-a11y-contracts',
process.env.PR_COMPONENT_A11Y_CONTRACTS_RESULT,
],

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Merging main brought in .github/scripts/interaction-legs.test.mjs, whose join case builds its passing env without this result (lines 247-267). The join now exits 1 with pr-component-a11y-contracts: missing, so that test fails and test-node goes red. Adding PR_COMPONENT_A11Y_CONTRACTS_RESULT: 'success' to that object fixes it.

@cixzhang
cixzhang marked this pull request as draft November 4, 2026 06:08
The interaction-legs contract hard-coded the lanes the test join reads, so
adding the component accessibility contracts lane to the join left the
all-passing fixture without that lane and the join returned 1. The fixture
now reads every needs.<lane>.result and scope output from the join step,
checks each lane is in the job needs, and proves a failed, cancelled,
skipped or empty result of every lane fails the join.

@astracat-bot astracat-bot Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Approved pending CI: CI has not run on this head yet, because the pull request is still a draft. Marking it ready for review starts CI, and the merge still needs test to pass.

The new contract job and its test wiring follow AST-030 FR4, FR11, FR12 and DEC-22, and the join fixture in .github/scripts/interaction-legs.test.mjs now covers every lane the join reads, so the earlier test failure is fixed.

Two optional wording fixes from earlier rounds still apply: the guard's "Re-run the job" (.github/workflows/ci.yml:3830) cannot help when the file list is truncated or empty, and the summary's "No touched component." (.github/scripts/a11y-pr-contracts.mjs:471) hides a wide change.

[Automated review]

@cixzhang
cixzhang marked this pull request as ready for review November 4, 2026 08:22
@cixzhang
cixzhang marked this pull request as draft November 4, 2026 08:28

@astracat-bot astracat-bot Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Approved pending CI: CI has not run on this head yet because the pull request is still a draft; marking it ready for review starts CI, and the merge still needs test to pass.

The contract job and its test wiring follow AST-030 FR4, FR11, FR12 and DEC-22, the latest merge from main leaves them unchanged, and the changed tests pass on this head.

Two optional wording fixes still apply: the guard's "Re-run the job" (.github/workflows/ci.yml:3832) cannot help when GitHub lists 3,000 or more files or none, and the job summary's "No touched component." (.github/scripts/a11y-pr-contracts.mjs:471) hides a wide change.

[Automated review]

@cixzhang
cixzhang marked this pull request as ready for review November 4, 2026 10:06
@cixzhang
cixzhang merged commit 8a20186 into main Nov 4, 2026
101 checks passed
@github-actions
github-actions Bot deleted the ci/pr-component-a11y-contracts branch November 5, 2026 06:03

This branch was successfully deployed

1 active deployment
Preview — d4620840 Deployed Nov 4, 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.

2 participants