Repository navigation
ci(ui): start the Test UI shards when a package ui consumes changes - #28501
Conversation
The shards read the `ui` filter output alone, so a diff confined to one of the seven packages ui consumes (packages/lint/**, spec, ...) never ran a ui test, though the affected-test selector already answers mode=full for it. They now start on `ui || ui_consumers`, the shape Build UI Deps, Type Check UI and Build & E2E UI read; the Test UI aggregate explains a skip only when both outputs said false. A select-affected-tests pin holds the shards' if: to every outer package the selector turns full for. Claude-Session: https://claude.ai/code/session_01QUs3BsPpUdAGmm7wLdoKaK Co-authored-by: Claude <noreply@anthropic.com>
The select-affected-tests pin reconciled the ui_consumers list against the package graph, a second check the ruling did not take; the Test UI aggregate, which now explains a skip only when ui and ui_consumers both said false, already reds on the first diff an if: regression would skip. Claude-Session: https://claude.ai/code/session_01QUs3BsPpUdAGmm7wLdoKaK Co-authored-by: Claude <noreply@anthropic.com>
|
domain:devx#1 · os-zhuang · session_01QUs3BvP5Aq9USg5GtmgAAR Contract reviewServed-tier: Rendered by an isolated subagent at ① Derived judgments
② Semver levelCorrect. The PR carries ③ Boundary flags
④ Design judgment
Check-runs on the head at review time: Implemented-by: VERDICT: PASS |
|
domain:devx#1 · os-zhuang · session_01QUs3BvP5Aq9USg5GtmgAAR 席位核对 — ACCEPTReviewed head
Correction the seat carries (dev's should-fix, not patched into the body): the per-merge cost in the body ("~376 runner-min", PR head + queue) omits the Noted, not filed (seat is carrier):
Verdict: ACCEPT. Landing waits on the maintainer's merge (Tier H). |
Requested by Sam · project thread
domain:devx#1 · os-zhuang · session_01QUs3BvP5Aq9USg5GtmgAAR
Fixes #28220
Ruling-ref: 6545651687 (letter A, maintainer 「同意」 2026-10-31T15:50Z)
Clause-②: no
Metadata-protocol: none
维护者速读
ci.yml。.github/workflows/ci.yml的test-ui的if:一行;其次test-ui-gate判「跳过是否合理」的那段 shell。packages/spec/**或packages/lint/**的 PR,检查列表里Test UI (k/16)显示 success 而不是 skipped。.github/workflows/**(Tier H),需要你的批准,然后席位落地。What changes
.github/workflows/ci.ymlonly.test-ui(Test UI (n/16)) — the job-levelif:now readsneeds.filter.outputs.ui != 'false' || needs.filter.outputs.ui_consumers != 'false'(inside the existingui_treeconjunct), the exact shapeui-deps,type-check-uiandbuild-e2e-uialready use. Which files run is unchanged:ui/scripts/select-affected-tests.mjsalready answersmode=fullfor any of the seven packages (outsideTreeReasons), so aui_consumers-only diff runs the whole suite across the 16 shards.test-ui-gate(the requiredTest UIcontext) — itsfilterjob 一旦失败,Test Core / Build Core / Dogfood 会全部 skipped 而分支保护判为通过 —— 隐式 success() 今天已第三次咬人 #4928 guard explains askippedrollup only whenfiltersucceeded and saidui_tree=false, orui=falseandui_consumers=false(it readuialone). This follows from the sameif:: a skip is "expected" only on the diffs the shards' condition excludes. It also makes the gate the regression check for this card: if theif:ever dropsui_consumersagain, the firstui_consumers-only diff redsTest UIinstead of passing a silent skip.lint-ui— not changed. Measured below: ui's eslint reads none of the seven packages.filteroutputs block, theui_consumers:list and a new "when the shards start" paragraph abovetest-uisay what starts the shards and why.scripts/pm/check-expected-skips.mjsandscripts/check-ci-filter-parity.mjs— no edit needed, measured.check-expected-skipsderives its roster from each job'sif:(deriveGatedSkips), so the row moves by itself:--rosterprintedTest UI (${{ matrix.shard }}/16) [ci.yml › test-ui; gate: job-gate filter ui_tree + filter ui; derived]onorigin/mainea74ebf93and prints… gate: job-gate filter ui_tree + filter ui|ui_consumers; derivedon this head; its--self-testpins no live job name by design and passes unchanged.check-ci-filter-parityhas no ui subject (it holdscore/crosspkgto the cross-package declarations, Build Core's inputs and Test Core's turbo inputs); its scan and--self-testpass on this head.Why
A diff confined to
packages/lint/**(or spec, client, core, formula, sdui-parser, types) compiled and built the Console against the new shape (Type Check UI, Build & E2E UI) but never started the ui tests that pin that shape. The red surfaced on the next unrelated PR that did start the shards, and that bystander was ejected from the queue: #28192 → #28197 (#28219), and PR #28467 → p0 #28491, the fourth recurrence.Tests
Reproduce first: the skip on origin/main ea74ebf
Measured run — the queue build of #28192 (
3efccfd5,packages/lint/**+packages/metadata-protocol/**), run 41091107812,merge_group, read from the Actions jobs endpoint:Filter evaluation — the live
filterjob'sdorny/paths-filterblock read out ofci.yml, matched with picomatch 4.0.7 (the matcherdorny/paths-filteruses), then each ui job's job-levelif:evaluated with those outputs on apull_requestevent (a one-off scratch script, not committed):packages/spec/src/ui/action.zod.tsreads the same as lint on both sides;ui/packages/core/src/index.tsstarts every ui job on both;scripts/foo.mjsstarts none on both.The selector already answers full for such a diff:
outsideTreeReasons({ outside: ['packages/lint/src/index.ts'] })returnspackages/lint/src/index.ts: @objectstack/lint changed, and this tree's tests read its built output (through @object-ui/console), and over every trackedpackages/*/package.jsonit turns full for exactly the sevenui_consumerspackages (client, core, formula, lint, sdui-parser, spec, types).The test-ui-gate verdict, every case
The gate's
run:block read out of this head'sci.ymland executed with each input set (OS_TEST_UI_RESULT OS_FILTER_RESULT OS_UI_TREE OS_UI OS_UI_CONSUMERS -> exit):Acceptance notes
ui/eslint.config.jssets noparserOptions.project/projectService(no type-aware rules, so no rule resolves a type from an@objectstack/*dist), and the two local rules that name@objectstack/*(ui/eslint-rules/no-inline-spec-config.js,no-unprefixed-query-params.js) match AST shapes and read no file. The ruling's condition ("only if ui's eslint reads those packages") is not met. Worth the seat's eye, not acted on here: other Lint UI steps do read the seven packages —pnpm --filter @objectstack/spec check:livenessruns over spec's sources, and the skill-example / doc-snippet / doc-example / readme-export type checks compile against built@object-ui/*closures that include the@objectstack/*dists (check:skill-examples --build-filterderives 12 filters,@object-ui/data-objectstack...among them). So a spec change can still red Lint UI first on a bystander's ui PR. carrier: domain:devx seat (a card if the seat wants Lint UI onui_consumerstoo, by the same one-line shape).patches/**; 21 such root files are tracked (.npmrc,eslint.config.mjs,vitest.preset.mts,paseo.json,.gitattributes, …) and several are not in theui:list, nor ispatches/**, so a diff confined to one of them still skips the shards. Not the ruling's scope; dedupe words:ui filter patches npmrc Test UI skipped root file. carrier: domain:devx seat.ui_consumerslist against the selector's package graph was drafted and dropped: it is the second, reconciling check the ruling declined with option B. Thetest-ui-gatechange above is the in-surface regression check instead.Runner-minute cost (against #25869 and #25881)
pushruns ofmain(41258652854, 41261634108, 41261929545, 41262951436; 16/16 shards each): 185.2 to 192.1 runner-min summed, mean shard 11.6 to 12.0 min, slowest shard 13.6 to 14.4 min wall. Aui_consumers-only diff is always a full battery (the selector'soutsideTreeReasons).ui:path (first-parentorigin/main, the live filter globs, picomatch); 22 of 138 in the last 24 h. At 22 to 31 a day that is ~8,300 to ~11,700 runner-min a day, lower bound.Verification
Gate list derived with
node scripts/pm/dispatch-gates.mjs --commandson this head (1 path vs merge baseea74ebf93): 53 derived, 53 run, 53 exit 0;--ranreconciles53 derived, 53 run, 0 NOT-MEASURED, 0 UNRUN(a derived zero: every row records its exit code). Run at head33b9f44f31, after a fullpnpm buildunderscripts/pm/os-verify-lock.sh(exit 0, 13m42s) for the fourdist/-reading Build Core guards;check:pm-dispatch-gatesran its fast tier (1937 cases pass, 7 slow sections deferred). Beyond the derived list, the ui tests that read the rootci.yml's ui jobs:pnpm --dir ui exec vitest runonselect-affected-tests.test.ts,select-affected-tests-wiring.test.ts,turbo-cache-save-bound.test.ts,check-action-forward-parity.test.ts,lint-workflow.test.ts— 5 files, 148 tests pass.node scripts/pm/check-expected-skips.mjs --self-test: 206 cases pass. Every CI family is the PR's own check-runs.Every derived command and its exit code
Human Minimum
packages/specholds no runtime logic. (not touched)OS_UI_CONSUMERS, a CI-only step env)pnpm lintpasses. (no lintable file changed; CI)main, one pull request.skip-changeset)packages/spectouched: its generated artifacts are regenerated and committed. (not touched)content/docs/references/,packages/*/CHANGELOG.mdorcontent/docs/releases/.Generated by Claude Code