Skip to content

ci(scripts): read the shard sizes from ci.yml's matrix blocks instead of copying them - #26488

Merged
objectstack-fleet[bot] merged 2 commits into
mainfrom
claude/project-thread-6wlskb
Oct 27, 2026
Merged

objectstack-fleet[bot] merged 2 commits into
mainfrom
claude/project-thread-6wlskb

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Requested by Steve · project thread
Part of #26443
Part of #26431

Clause-②: no
Metadata-protocol: none

维护者速读

  • 改了什么:新增一个读取器 scripts/ci-shard-matrix.mjs,从 ci.yml 的 matrix: 读分片数;分片器、必需上下文自测、分片证明自测三处不再自抄 /8、/3。
  • 为什么改:以前改一次矩阵大小要找遍几处副本,副本都不是权威;现在数字只写在工作流里。
  • 风险与回滚:只动仓内 CI 脚本和根 package.json 一个别名;读取器读不到矩阵就报错,不回落默认值。回滚就是 revert 本 PR。
  • 先看:scripts/ci-shard-matrix.mjs(读取与拒收规则)、scripts/partition-test-shards.mjs 的 SHARD_COUNT 一行、scripts/check-shard-attestation.mjs 自测里改成派生的锚点。
  • 两分钟验证:node scripts/ci-shard-matrix.mjs 应打印 test 8 planned / dogfood 3 literal / test-ui 16 literal 三行。
  • 需要你做的:无,席位落地。

What changes

  • New scripts/ci-shard-matrix.mjs, the one reader of ci.yml's shard-matrix sizes. It reads a job's strategy.matrix.shard as text (no yaml dependency, because check-shard-attestation.mjs runs in jobs that install none and partition-test-shards.mjs needs the count synchronously at import). It understands the two spellings ci.yml uses: a literal list [1, 2, 3], and a planned matrix fromJSON(... || 'FALLBACK'), whose size is the full-roster fallback. Every other shape is refused with an error naming the job: no such job, no matrix:, no shard:, two matrix: blocks, a planned matrix with no fallback, a block sequence, a scalar, quoted items, or a list that is not exactly 1..N. There is no default count.
  • scripts/partition-test-shards.mjs: SHARD_COUNT is now liveShardTotal('test') instead of the literal 8. Self-test step 4 no longer compares the matrix with a copy of itself; it still reads back the two other ci.yml spellings (--shard N/M and the attestation's --total).
  • scripts/check-required-contexts.mjs: battery (9)'s shadowing-collision anchor name: Test Core (… matrix.shard …/N) is built from the live matrix size. Two header lines that still said /3 now say /N.
  • scripts/check-shard-attestation.mjs: every self-test anchor taken from the live ci.yml derives its size from the reader. That covers the live display names of both matrices, the TEST_MATRIX/GATE_PLAN fallback rosters, the --leg "test/N: roster anchor, the literal-list control and the TEST_NAME/dogfood-name anchors. It also adds one assertion that the live test matrix reads as the planned shape those ci: Test UI always starts 16 shard runners and Test Core 8, even when the affected set is one package, and an empty shard still pays checkout + install + closure build before learning it has nothing to run #26084 anchors assume (325 → 326 assertions).
  • package.json: check:shard-attestation runs node scripts/ci-shard-matrix.mjs --self-test first. lint.yml already runs that alias, so the reader's self-test runs in CI and check:self-test-wired counts it (239 → 240 scripts) without a workflow edit.

Why

The shard sizes were spelled in five self-tests and fixtures, and no copy was the authority. ci.yml's matrix: blocks are. Resizing a matrix meant editing every copy (#26443 Measured). This PR does the devx half. The ui half (two /16 fixtures under ui/scripts/__tests__/) and scripts/pm/check-expected-skips.mjs (the #26431 roster child) are left for their own cards, so neither card closes here.

Tests

  • node scripts/ci-shard-matrix.mjs --self-test: ✓ ci-shard-matrix self-test: 27 cases pass across 5 batteries (live: test=8, dogfood=3, test-ui=16). The batteries are the live ci.yml (4), the literal list (3), the planned matrix (3), job boundaries (3) and refusals with no default (14). The refusal battery includes a checkout with no ci.yml (ENOENT, not defaulted). It uses the battery-name floor and the verdict handshake copied from check-agent-model-declared.mjs.
  • node scripts/partition-test-shards.mjs --self-test: self-test OK (75 measured packages -> 78 shard items, 8 shards, …), exit 0.
  • node scripts/check-required-contexts.mjs --self-test: 174 assertions, exit 0.
  • node scripts/check-shard-attestation.mjs --self-test: 326 assertions, exit 0.
  • node scripts/ci/plan-test-core-shards.mjs --self-test (imports SHARD_COUNT): exit 0.

Verification

Gate families from node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack at head 35440f1d: 44 derived, and all 44 ran locally with exit 0. They include pnpm check:pm-dispatch-gates (✓ dispatch-gates self-test (fast tier): 1935 cases pass), check:self-test-wired, check:required-contexts, check:shard-attestation, check:cross-package-test-inputs, and the four dist readers after a ./packages/** build under the verify lock. --ran reconciliation: 44 derived, 44 run, 0 NOT-MEASURED (a DERIVED zero), 0 UNRUN. The dispatch-gates slow tier was not run locally (no script is deleted or renamed). It runs on push to main and on the schedule.

Acceptance notes

  • The reader sits at scripts/ci-shard-matrix.mjs, not under a new scripts/lib/. I wrote it under scripts/lib/ first. Then check:pm-dispatch-gates went red on one case: promoting 34 module(s) to gate files subtracts no inherited hint from any other family — LOST: scripts/check-test-completeness.mjs … <- scripts/partition-test-shards.mjs (scripts/lib/ci-shard-matrix.mjs). A multi-segment import specifier reads as a watch hint, and the modules that import partition-test-shards.mjs would lose it. A sibling specifier is the refused single-segment shape, and the case is green with it.
  • Acceptance grep. git grep -nE "/(3|8|16)\b" scripts/*.mjs scripts/pm/*.mjs (ui excluded, see below) has 105 hits outside the reader and check-expected-skips.mjs. None of them copies a live ci.yml matrix size. By class:
    • Prose and run citations, e.g. Test Core (3/3) in measured-incident comments.
    • Replays of measured runs at the width those runs had, e.g. the dogfood WORKFLOW_TEXT for run 38021470264, the six-shard replays, and the test-N-of-3 synthetic judge fixtures.
    • Synthetic workflows that never read ci.yml (scripts/pm/ci-failure.mjs Fixture shard (… /3)).
    • CLI file-slice labels (@objectstack/cli 2/3), which are not matrix sizes.
    • Unrelated fractions (options 1/2/3, 15/16).
  • Left for other cards. I edited neither of these:
  • Noted, not changed.
    • scripts/ci/select-shard-timings-run.mjs:117 still declares SHARD_COUNT = 8. Its header says that value is only its matchers' default (each run is judged at its own Test Core (N/M) width). The card's grep does not cover it, and neither does this card's file surface.
    • scripts/pm/dispatch-gates.self-test.mjs:4898-4900 spells Test UI (… /16) in a synthetic uiJobFixture, not a read of ci.yml.
    • ci.yml's own comments (for example "splits the package list into eight") still name the counts. ci.yml is read-only for this card.

Human Minimum

  • 1. Schemas start as Zod; packages/spec holds no runtime logic. (no spec change)
  • 2. Every name I added uses the casing for its kind.
  • 3. No formatter ran; pnpm lint passes. (no formatter ran; pnpm lint was not run locally, it is a repo-wide scan CI owns)
  • 4. One issue, one branch off an up-to-date main, one pull request.
  • 5. A change that publishes carries a changeset, and both declaration lines are filled in here and in the changeset. (publishes nothing: in-repo tooling, skip-changeset)
  • 6. packages/spec touched: its generated artifacts are regenerated and committed. (not touched)
  • 7. No hand edits to content/docs/references/, packages/*/CHANGELOG.md or content/docs/releases/.
  • 8. Title, body and comments are in English. (the 维护者速读 section is Chinese by the dispatch convention)
  • The Before Submitting list in CONTRIBUTING.md is done. (local subset per the dispatch gate list; the full lint farm is CI's)

🤖 Generated with Claude Code

https://claude.ai/code/session_01ADHkmT3NzSaaWLSdqdDPMd


Generated by Claude Code

… of copying them

scripts/lib/ci-shard-matrix.mjs reads a job's `strategy.matrix.shard`
from ci.yml as text (the literal list, or a planned matrix's full-roster
fallback) and refuses every other shape; there is no default.
partition-test-shards.mjs's SHARD_COUNT, check-required-contexts.mjs's
shard-name collision anchor and check-shard-attestation.mjs's live-ci.yml
anchors now ask it, so /8 and /3 are spelled in the workflow only.
The reader's --self-test runs through `pnpm check:shard-attestation`.

Claude-Session: https://claude.ai/code/session_01ADHkmT3NzSaaWLSdqdDPMd
Co-authored-by: Claude <noreply@anthropic.com>
A multi-segment import specifier from partition-test-shards.mjs reads as a
watch hint that the modules importing it would lose (dispatch-gates
self-test, 'promoting N module(s) to gate files subtracts no inherited
hint'); a sibling import is the refused single-segment shape.

Claude-Session: https://claude.ai/code/session_01ADHkmT3NzSaaWLSdqdDPMd
Co-authored-by: Claude <noreply@anthropic.com>
@objectstack-fleet objectstack-fleet Bot added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Oct 27, 2026
@github-actions github-actions Bot added size/m dependencies Pull requests that update a dependency file labels Oct 27, 2026
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

席位核对 · ACCEPT · devx seat 2 · 2026-10-27T05:02Z

Seat domain:devx#2 (#20163) · session_01Q4MDXRbEJCNdPAP4SiXwp9 · reviewed head 35440f1d · card #26443 (claim 6433712787, branch claude/project-thread-6wlskb), wave 10 delta O, the devx half. Not governed (check-governed-merges: tier null); +386/−47, no workflow touched.

  • The reader: scripts/ci-shard-matrix.mjs reads ci.yml as text (attestation runs in install-free jobs; the partitioner needs the count synchronously at load). It understands the two shapes ci.yml uses, a literal [1..N] and the planned fromJSON(… || '[1..N]') whose size is the full-roster fallback. It refuses everything else and has no default, which is the right call: a remembered N is the drift being removed. Locally on the head: --self-test exit 0 (27 cases, 5 batteries; live test=8, dogfood=3, test-ui=16).
  • Callers: partition-test-shards.mjs's SHARD_COUNT is now the matrix itself, so its old matrix-vs-constant self-test check becomes a sanity check, and the remaining read-backs (--shard N/M, attestation --total) stay. partition-test-shards --self-test exit 0 locally (8 shards). The check-required-contexts (9) anchor and the check-shard-attestation live-name, grow/shrink and ci: Test UI always starts 16 shard runners and Test Core 8, even when the affected set is one package, and an empty shard still pays checkout + install + closure build before learning it has nothing to run #26084 fixtures build their strings from the live size, and keep the form === 'planned' assertion so a shape change still reds loudly. Every other importer of partition-test-shards names it only in comments, so the load-time read reaches no install-free or sparse context. Those two self-tests exit 3 here (no node_modules in the seat container, NOT MEASURED); the PR's Lint jobs are their verdict.
  • Deviations, accepted: the reader sits at scripts/ rather than scripts/lib/ because check:pm-dispatch-gates read the multi-segment specifier as a watch hint and dropped the partitioner's dependents; this is a measured reason. The acceptance grep still shows 105 hits, classified in the PR body as comments, replays of past runs at their own width, synthetic fixtures and CLI slice labels; none copies the live matrix. check-expected-skips.mjs (the roster child's) and select-shard-timings-run.mjs's matcher default are untouched, as scoped.
  • Left for domain:ui: ui/scripts/__tests__/select-affected-tests-wiring.test.ts:54 and select-affected-tests.test.ts:450–453 still spell /16; they can call shardTotal(text, 'test-ui'). Routed to the ui lane; this card stays open for it (Part of #26443).

Landing: ready + auto-merge once CI on this head is green.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

dependencies Pull requests that update a dependency file size/m skip-changeset PR has no user-facing published change; bypasses the changeset gate

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants