Skip to content

ci(test-core): build declarations on one CLI slice only — pin the CLI's declaration readers into slice 1/n - #27018

Merged
os-zhuang merged 4 commits into
mainfrom
claude/issue-25870-cli-reader-sequencer
Oct 28, 2026
Merged

os-zhuang merged 4 commits into
mainfrom
claude/issue-25870-cli-reader-sequencer

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Part of #25870

Ruling-ref: 6439314592
Clause-②: no
Metadata-protocol: none

维护者速读

  • 改了什么: CLI 测试里只有 5 个文件要读构建出的 .d.ts。现在 vitest 把它们固定排进第 1 片(sequencer 在 packages/cli/vitest.config.ts)。Test Core 只在带 cli 1/4 的那个分片构建声明,metadata 也被排到同一分片;其余分片都设 OS_SKIP_DTS=1。
  • 为什么改: 以前这 5 个文件按 sha1 散落在 4 个切片里,导致 5 个分片各自多花约 4.5 分钟构建声明。改后只剩 1 个分片构建声明。按模型推算,最慢分片从约 22 分钟降到约 19 分钟,CI 实际预计 20–21 分钟。
  • 风险与回滚: 另外 515 个 cli 文件里有 9 个换了切片,对负载敏感的邻居可能跟着变(ci(test-shards): place Test Core bins by predicted wall, and cut the CLI into 4 slices on the refreshed dataset #25968 那一类)。以后有人新增读声明的测试却没登记,它会在不构建声明的分片上以 TS7016 报红,失败是响亮的。回滚办法是 revert 本 PR,只涉及 3 个文件,不动数据也不动 schema。
  • 先看: .github/workflows/ci.yml 里 "Choose this shard's declaration build" 一步的 case 判断;packages/cli/vitest.config.ts 的 DeclarationReaderSequencer.shard();scripts/partition-test-shards.mjs 的 partition() / refineToCap() 对 rideWith 的处理。
  • 两分钟验证: 运行 node scripts/partition-test-shards.mjs --self-test,应看到 self-test OK 和 whole legs 143/1597/1597/925/924/923/1599/1597s,其中第 1 分片只放 cli 1/4 和 metadata。
  • 需要你做的: 以 GOVERNED_APPROVERS 身份审阅并批准这个 Tier H PR(.github/workflows/ci.yml)。

What changes

Ruling A (6439314592), as specified in 6436991767, built on seat 1's commit 7e64f7c1d3 (cherry-picked onto current main, every hunk re-read and re-verified here) plus one fix:

  • packages/cli/vitest.config.ts. DECLARATION_READER_FILES lists the 5 files that compile against built declarations. DeclarationReaderSequencer overrides only shard(). It puts the readers first and the remaining files in vitest's own sha1 order (key / + root-relative path, the same as BaseSequencer), then cuts that sequence with vitest's own equal-count ranges. The readers land in slice 1/n, and every other file moves at most 5 places along vitest's order. The config refuses to load when a listed reader is missing from the queue population. It also refuses when the readers do not fit in slice 1/n. With no shard set, or in a nightly-tier run (no readers in the population), the order is vitest's own.
  • .github/workflows/ci.yml, the Test Core "Choose this shard's declaration build" step and its comment only. DTS_READER_SLICE='1': a shard keeps declarations when it carries a whole reader package or slice 1/* of one. Every other shard sets OS_SKIP_DTS=1. The comment now lists create-in-repo-scaffold-typechecks, which the earlier list missed.
  • scripts/partition-test-shards.mjs.
    • The reader slice is charged the declaration closure as packing weight: 270 s × 2.9 = 783 s. This is never test weight, so the slice-count derivation, the drift step and serialFloorOf() are unchanged.
    • Every whole package in DECLARATION_READER_PACKAGES (@objectstack/metadata) rides into the reader slice's bin, so one shard builds declarations, not two.
    • A run with no reader slice (cli whole, cli absent, or a nightly document) is charged nothing and splits exactly as before.
    • The self-test reads the slice index back from all three places it is spelled (ci.yml, the cli config, this map). Battery 3e: 16 checks.
  • Fix on top of the inherited commit: refineToCap() could move the riding whole reader off the reader slice's shard. That would put declarations back on a second shard. Riders are now fixed in place like slices. The self-test gains the fixture where only that rule keeps the rider in place.

⛔ Unchanged: no test is skipped or quarantined, no timeout is raised, no test leaves Test Core, the shard count stays 8, and no other ci.yml job is touched.

Tests

All readings are local, on a shared 4-core box, so wall-clock figures are shared-box seconds.

Reader set re-derived on current main (merge base 912076f)
  • OS_SKIP_DTS=1 turbo run build --filter=@objectstack/cli --filter=@objectstack/metadata: 61/61 tasks, and 0 .d.ts files under packages/spec/dist and packages/cli/dist.
  • The whole cli suite on that closure (vitest run --maxWorkers=3, under the verify lock) gave VERDICT command-exit 1, with Test Files 5 failed | 514 passed | 1 skipped (520) and Tests 25 failed | 6432 passed | 9 skipped (6466). The red files are exactly the 5 listed in DECLARATION_READER_FILES, and every one fails for missing declarations:
    • create-in-repo-scaffold-typechecks: TS7016
    • published-subpath-console.pin: TS7016, ENOENT on dist/console.d.ts
    • published-subpath-hook-body.pin: TS7016, ENOENT on dist/hook-body.d.ts
    • scaffold-emission-typechecks: TS7016 / TS2307
    • scaffold-test-script-passes: TS7016
  • No new reader was found.
  • Control leg, the same tree rebuilt with declarations (243 .d.ts), running OS_TEST_SHARD=1/4: VERDICT command-exit 0, Test Files 129 passed | 1 skipped (130), Tests 1750 passed | 2 skipped. The 5 readers ran there green, 56/56 tests.
Slice map, from vitest's own context on the real config

createVitest + globTestSpecifications() + config.sequence.sequencer against BaseSequencer, for OS_TEST_SHARD=k/4:

files per slice readers per slice
before (BaseSequencer) 130/130/130/130 2/1/1/1
after (DeclarationReaderSequencer) 130/130/130/130 5/0/0/0
  • 520 files in the union, 0 duplicated, 9 files changed slice.
  • The 1/4 run above executed exactly the after-map's slice 1 (130/130 files match).
  • Summed per-file seconds per slice (from the runs above): before 301/602/833/401, after 313/595/827/401.
  • The readers sum to 47 s in the DTS run, and they land on the lightest slice.
Placement and wall model, full `turbo ls` list (129 packages, dogfood excluded)
shard before (main) after
1 1502 s whole cli 1/4 + metadata (143 s whole), declarations
2 1502 s whole 1598 s whole
3 818 s whole + cli 1/4, declarations 1598 s whole
4 828 s whole + cli 2/4, declarations 925 s whole + cli 2/4
5 826 s whole + cli 3/4, declarations 926 s whole + cli 3/4
6 827 s whole + cli 4/4, declarations 924 s whole + cli 4/4
7 1502 s whole incl. metadata, declarations 1591 s whole
8 1501 s whole 1600 s whole
  • Shards building declarations: 5 before, 1 after.
  • Wall model: step = whole/2.9 + slice, plus the closure (270 s with declarations, about 45–90 s without), plus about 90 s of setup.
    • Before, the slowest shard is about 22.0 min. CI read 23.9–25.4 min, so the model reads about 2 min low.
    • After, the slowest shard is about 18.8 min (shards 4–6), and shard 1 is about 18.1 min.
  • Expected in CI: about 20–21 min.
  • Every whole leg stays within the 1868 s cap. objectql and plugin-security stay apart.
Partitioner self-test and ablations
  • node scripts/partition-test-shards.mjs --self-test: self-test OK (75 measured packages -> 78 shard items, 8 shards, max/mean 1.00x ..., whole legs 143/1597/1597/925/924/923/1599/1597s within 1868s, file-level slices: @objectstack/cli x4).
  • Ablation of the rider fix, through scripts/ablation-replace.mjs:
    • The anchor went from 1 hit to 0 and the blob from 9e282f42e9d3 to 042fd9beb508.
    • The self-test then exited 1 with the cap repair moved the whole reader off the reader slice's shard.
    • Restore: the blob equals HEAD and git diff HEAD is empty.
  • Inherited assertions, re-run here at c5a59dfe5f. Each went through ablation-replace.mjs with the anchor going 1 to 0, and each was restored to blob == HEAD with an empty git diff HEAD:
    • closure wall 270 to 0: exit 1, cli 1/4's shard carries 826s of whole packages against [826, 827, 826]s ... not 300s less;
    • rider placed in the next bin: exit 1, @objectstack/metadata is on shard 2, not with @objectstack/cli 1/4 on shard 1;
    • ci.yml DTS_READER_SLICE='2': exit 1, DECLARATION_READER_SLICES says slice 1, ci.yml DTS_READER_SLICE says 2;
    • sequencer removed from the cli config: exit 1, its vitest config does not install DeclarationReaderSequencer;
    • reader dropped from DECLARATION_READER_FILES (slice map re-read through vitest's context): scaffold-test-script-passes moves to slice 2/4, a declaration-less slice, where the run above shows it red with TS7016. My first attempt at this one was refused by the tool, because the replacement text was not unique. It was restored, and the second attempt used a unique marker.

Acceptance notes

  • Acceptance is NOT MEASURED here: the card asks for the slowest Test Core shard ≤ ~20 min on 3 consecutive spec-touching pull_request runs. That can only be read on this PR in CI. After landing, the ruling repeats the 10-full-queue-build reading of 6430866969 before the card closes, so this PR says Part of, not Fixes.

  • Real slices are not equal-weight. The summed per-file seconds above make cli 3/4 the heaviest slice, which is the same slice as the slowest shard on main (5/8). This PR removes that shard's closure. It does not rebalance the slices (option C, not ruled).

  • The partitioner's per-shard "Ns predicted" bin totals now include the reader slice's 783 s packing charge (closure, never test weight). driftReport() reads per-package timings, not bin totals, so the drift verdict is unaffected.

  • Changeset: skip-changeset. @objectstack/cli ships files: ["dist", "README.md", "CHANGELOG.md"]. DeclarationReaderSequencer / DECLARATION_READER_FILES have 0 hits under those paths, and the positive control (hook-body under dist/) hits. scripts/ and .github/ are not published.

  • Contract review (seat, before this PR opened): card comment 6458259422, CONTRACT_REVIEW_TIER, PASS on c5a59dfe5f, no must-fix. Its two should-fix notes:

    • S2, the one hole this design cannot see: a NEW declaration-reading cli test that nobody adds to DECLARATION_READER_FILES passes as long as vitest's sha1 cut happens to put it in slice 1 (about one chance in four). A later population change that moves the cut then reds an unrelated PR. That red means "add the file to DECLARATION_READER_FILES", not a flake. A listed reader that is renamed or deleted already refuses the config at load.
    • S1: the shard log's Ns predicted (all bins: ...) line and balanceOf() now include the reader slice's 783 s packing charge without saying so. driftReport() is untouched, so no verdict changes. The wording fix rides a follow-up.

Verification

Final head c5a59dfe5f. It is a clean merge of origin/main that includes #26854 and the #25452 cache steps, in regions disjoint from this diff. packages/cli is unchanged across both merges. The reader and slice readings above were taken at 9d1cfdbf63, which is merge base 912076f885 plus this diff.

Derived gates at c5a59df
  • node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack derived 86 families. All 86 exited 0, each exit captured before any pipe.
  • --ran reported: 86 derived, 86 run, 0 NOT-MEASURED, 0 UNRUN (a derived zero: all 86 recorded an exit code).
  • On the earlier head 849642eb8c, check:dts-closure and check:dual-build-cjs-loads first exited 1 and 3. That run overlapped my own rebuild of the closure, which had tsup cleaning dists. Re-run on the settled tree, both exited 0: 233/233 declared declaration file(s) present and 112 published require entry point(s) across 69 package(s) load. Both are 0 again on the final head.
  • pnpm --filter @objectstack/cli typecheck, which compiles vitest.config.ts through tsconfig.test.json, gave VERDICT command-exit 0 and check:test-typecheck: OK. A first run went red in ../objectql/src/picklist-resolution.ts because metadata-core's dist was stale after the merge. It went green after rebuilding the closure.
  • The ci.yml step's shell was exercised on 6 shard-file fixtures:
    • cli 1/4 + metadata, cli whole, and metadata alone keep declarations;
    • cli 2/4 + metadata-core, client-react + cli 4/4, and cli 11/12 set OS_SKIP_DTS=1.
  • Not measured:
    • actionlint and shellcheck: no binary in this container;
    • CI check-runs: no PR yet, because the contract review comes first;
    • the card's acceptance (≤ ~20 min on 3 spec-touching runs): CI-only;
    • slices 2–4/4 were not re-run as slices (declared narrowing). The whole-suite no-declarations run covers every one of their files green, and the slice map shows they hold no reader.

Generated by Claude Code

…'s declaration readers into slice 1/n

The five CLI test files that compile against built .d.ts sat in all four
CLI slices by vitest's sha1 split, so five Test Core shards built the
declaration closure (~4.5 min) for one or two files each. A sequencer in
packages/cli/vitest.config.ts now puts them first in vitest's own order,
so they land in slice 1/n; ci.yml keeps declarations for a whole reader
or slice 1/* only; the partitioner charges that slice the closure
(270 s x 2.9 of packing weight) and seats @objectstack/metadata beside it,
so one shard builds declarations instead of five.

Claude-Session: https://claude.ai/code/session_01JQwxbaFpnMwgpCgneKe2qS
Co-authored-by: Claude <noreply@anthropic.com>
…th the reader slice out of the cap repair

refineToCap() moved any whole package off the densest bin, the rider
included, which would put declarations on a second shard. The rider now
stays with its slice like the slice itself; the self-test gains the
fixture where only that rule keeps it there.

Claude-Session: https://claude.ai/code/session_01QSeKYmTYfaPbLQD4BJfomW
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

5 anchor(s) derived from 1 changed package(s); no hand-written page names any of them, so this run has nothing to list — not a clean bill of health. This check sees only pages that NAME a derived anchor: one that documents this change in prose, or enumerates it in an authoring dialect, names none and stays invisible to it on every run.

What this run could not see
  • 3 name(s) were too generic to anchor anything (single lowercase words)
  • the SDK route bridge reached 55 of 207 client-bound route-ledger rows — the other 152 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 152: 0 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 55 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 97 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.
  • a key NAME is not a key, so the hand re-read the line above prescribes can land on the wrong schema. The same spelling is authorable on one governed type and a [REMOVED] tombstone on another for each of active, aria, joins, objects, template, tools and version (censused on [finding] tools is a key on BOTH AgentSchema (tombstoned, dead) and SkillSchema (live, cloud-attested), so a name-based search attributes skill examples to the agent key — it produced a false stop-the-line alarm on PR #19059 #19093 over the liveness ledger's governed types, top-level keys); nothing in a search result distinguishes the two, so a grep hit on a LIVE example reads as evidence about the DEAD key. Measured on fix(spec): the agent.tools liveness row says dead — it claimed live on a key the schema tombstoned #19059: content/docs/ai/agents.mdx was reported as contradicting the agent.tools tombstone over its tools: example at :161, which is inside the defineSkill({ block opened at :155 — the page was already correct. Settle ownership by PARSING the value against both schemas, never by the name: that literal PASSES SkillSchema, and as an AgentSchema it FAILS at tools with the tombstone prescription. ⛔ These names are not the whole class — a key retired through a .strict() guidance map leaves no tombstone in the walked shape and none of them here (tool.category, live as AIToolDefinition.category).

Coarse fallback — 32 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json 7eb613c39fb85cc1e4d0b658c9ecea4e45949c99 → packageMentionDocs.

Which tree this was computed on

This run read content/docs from d7d6b5547a5caa1e0eed0254ecd6ce653502bfcd — the merge of head c5a59dfe5fc24489c4caaa78d87c8f7a81959a78 into base 7eb613c39fb85cc1e4d0b658c9ecea4e45949c99, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin d7d6b5547a5caa1e0eed0254ecd6ce653502bfcd && git checkout d7d6b5547a5caa1e0eed0254ecd6ce653502bfcd
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 7eb613c39fb85cc1e4d0b658c9ecea4e45949c99 c5a59dfe5fc24489c4caaa78d87c8f7a81959a78 && git checkout -B drift-repro 7eb613c39fb85cc1e4d0b658c9ecea4e45949c99 && git merge --no-ff c5a59dfe5fc24489c4caaa78d87c8f7a81959a78

node scripts/docs-audit/affected-docs.mjs --json 7eb613c39fb85cc1e4d0b658c9ecea4e45949c99

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

@os-zhuang
os-zhuang added this pull request to the merge queue Oct 28, 2026
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

席位核对 — ACCEPT · domain:devx#2 · head c5a59dfe5f · 2026-10-28T01:49Z

维护者速读

  • 改了什么:Test Core 里只让「cli 第 1 片 + metadata」这一个分片构建 .d.ts 声明,其余分片都跳过声明构建(OS_SKIP_DTS=1)。cli 的 vitest 配置加了一个排序器,把 5 个读取声明的测试文件固定排进第 1 片;分片打包脚本按这一片多出的约 4.5 分钟来配重。
  • 为什么改:你裁的 A(6439314592)。以前 5 个分片都要各自构建声明,最慢的分片把每个改核心包的 PR 拖到约 28 分钟。
  • 风险与回滚:只改 CI 分片方式,不跳过、不隔离任何测试,不加超时,分片数不变;回滚即 revert 本 PR。已知盲区:以后新增一个读取声明的测试却没登记进清单,可能过一阵才在无关 PR 上报红(PR 正文 Acceptance notes 已写明怎么读)。
  • 先看:.github/workflows/ci.yml 中 "Choose this shard's declaration build" 一步,packages/cli/vitest.config.ts 的 DECLARATION_READER_FILES。
  • 两分钟验证:本 PR 的 Test Core 日志里只有 1 个分片构建声明;cli 配置漏列或改名一个读取文件时会直接拒绝加载。
  • 需要你做的:这是受管面(ci.yml),合并需要 GOVERNED_APPROVERS 的批准。本记录写下时,PR 上还没有批准(APPROVED)记录;os-zhuang 已把它排进合并队列,队列里的 Governed Surface Queue Guard 会核对批准。

席位意见:合入。达档契约复核 PASS(卡上 6458259422),无 must-fix;两条 should-fix 已写进 PR 正文 Acceptance notes。卡上的验收(连续 3 次改到 spec 的 PR 运行,最慢分片 ≤ 约 20 分钟)要在合入后读,所以正文写的是 Part of。

Shape: ready (os-zhuang), → main. Part of #25870. 3 files, +509/−24: ci.yml, the Test Core declaration step and its comment only (L913–960); packages/cli/vitest.config.ts; scripts/partition-test-shards.mjs. Tier H. Assignee os-justin, label skip-changeset (cli files[] = dist, README, CHANGELOG; the config never ships, confirmed by the review).

Seat's own read: the diff matches ruling A and its ⛔ list: no test skipped or moved, no timeout raised, SHARD_COUNT unchanged, no other job touched. The case "$SLICE" pattern keeps declarations for '' (a whole package) and 1/*, and skips the rest.

Gates: at head c5a59dfe5f, 67 check-runs: 56 success, 11 skipped, 0 failure. Test Core 8/8 success. Slowest shard 5/8: 12 min 08 s from job start (01:36:30–01:48:38). This run touched no packages/spec, so it is not one of the card's acceptance readings. Data only.

Acceptance (owed after landing, on the card):


Generated by Claude Code

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: c5a59dfe5fc24489c4caaa78d87c8f7a81959a78
Local-runs: none
Scope: .github/workflows/ci.yml + packages/cli/vitest.config.ts, scripts/partition-test-shards.mjs (its self-test is the same file), scripts/ci/plan-test-core-shards.mjs, turbo.json (globalEnv, @objectstack/cli#test / @objectstack/metadata#test), packages/cli/package.json (build under OS_SKIP_DTS), scripts/nightly-tiers.mjs (readTierMode), packages/spec/scripts/root-entry-type-nameability.pin.test.ts and .github/workflows/lint.yml L6023-6050 (the one Test Core test that self-skips without declarations, and where it runs instead)

Reviewed on PR #27018, adversarially against the dev report 6458117958, the seat's earlier record 6458259422 and the seat's ACCEPT 6458499735. Inputs: the card body and all 23 comments; the PR body, file list and the net diff origin/main...c5a59dfe (3 files, +509/-24; confirmed on the repo ref prr-27018, merge base 5e99bd936c); the 67 check-runs on the head and the step records of their own workflow run 40615848616 (gh api GET of the run's jobs; no log, no re-run). Nothing was built, run or re-run locally.

① Derived judgments

Gate verdicts on the head: 56 success, 11 skipped (opt-in and path-filtered jobs), 0 failure, 0 pending. Test Core 8/8 success, Plan Test Core shards success, Test completeness guard success on every shard, Build Core, Check Changeset, Governed Surface Queue Guard, PR-body guards, Lint (all nine), Type Check (all five) success.

Accept-set and surface changes the diff implies, each judged on the tree:

  • The only thing ci.yml changes is WHEN a shard exports OS_SKIP_DTS=1. Two hunks, L913-961, inside the test: job's one step ("Choose this shard's declaration build") and its comment; the third hunk a two-dot diff shows (L3418) is main's own later commit, not this PR's. The step still writes nothing but OS_SKIP_DTS=1 to $GITHUB_ENV or a log line. No --filter, no package list, no matrix, no if:, no timeout, no continue-on-error moves. Right.
  • OS_SKIP_DTS skips no test. Every consumer of the variable at the head is a build config (tsup.config.ts dts: !process.env.OS_SKIP_DTS in 20 packages, packages/cli and packages/spec build scripts) or spec's dist-freshness tooling; no vitest.config.ts, skipIf, runIf or describe.skip reads it anywhere in the tree. Within the cli, the conditional skips present (jsx-gate-manifest-notice.e2e, migrate-meta-default-range, uncreatable-directory-doors) gate on an oclif manifest, a semantic-version hop and /proc, never on a .d.ts; the nine cli test files that mention .d.ts are the five readers plus four that only filter .d.ts names out of src/ walks or set getDefaultLibFileName. So on a slice shard that now skips declarations, every file still runs; the only possible effect of a missing declaration is a RED (TS7016 / ENOENT), never a pass that measured nothing. Right — the coverage question is answered in the loud direction.
  • The one Test Core test that self-skips without built declarations is unchanged by this PR: spec's root-entry-type-nameability.pin gates on OS_EXPECT_ROOT_NAMEABILITY === '1', which only lint.yml's built-dist step sets (L6023-6028); in Test Core it skipped before this PR on every shard, declarations or not. Not narrowed. Right.
  • The readers are in slice 1/n and nowhere else. DeclarationReaderSequencer.shard() places the five listed files first, then every other file in sha1 order of / + root-relative path, and cuts that one ordered sequence with shardRange(); every slice k/n of the same population cuts the same sequence, so the union is the whole population and the slices are disjoint by the range arithmetic itself (remainder slices of base+1, the rest of base; slice k's end is slice k+1's start; slice n ends at total). The pin's correctness therefore does not depend on matching vitest's private math — a vitest upgrade that changes calculateShardRange changes nothing here. A reader count past slice 1's own range refuses at shard time; a listed reader absent from the queue population refuses at config load, naming it. Right.
  • A reader cannot land on a declaration-less closure by any path the tree can emit: the ci.yml case keeps declarations for '' (a whole reader package) and 1/* (slice 1 of any count: 1/4, 1/16; 11/12 does not match, the glob needs 1/ as prefix); cli slices k≠1 skip; metadata whole keeps. The nightly (OS_TEST_TIERS=nightly) holds none of the five in its population, so readers is empty there and the sequencer reproduces a plain sha1 cut; test-nightly-tiers.yml sets no OS_SKIP_DTS, and planRun() hands a self-slicing document NO_DECLARATION_READERS, so nothing is charged or ridden there. Right.
  • The partitioner still places every item exactly once. partition() places non-riders by LPT and each rider straight after its anchor; a rider whose anchor is not in the run is placed normally; refineToCap() now pins riders like slices; assertSlicesSpread() is unchanged; placeItems() / planShards() / planRun() thread readers through and scripts/ci/plan-test-core-shards.mjs reads bins via planRun(), so the plan digest and each shard's own bin agree. The slice-count derivation (sliceCountProblems, serialFloorOf) and driftReport() read weight, never closure; only partition()'s bin totals, balanceOf() and the --shard log line carry the 783 s packing charge. Right (and the charge's densest <= cap reading cannot flip the CLI whole: the reader bin is ~1600 s against a dataset-derived cap ≥ 2697 s).
  • The three spellings of the index are read back against each other in judgeDeclarationReaders() (ci.yml DTS_READERS = DECLARATION_READER_PACKAGES; DTS_READER_SLICE = every DECLARATION_READER_SLICES entry; each reader package's config pins the same index in code position and installs the sequencer; a sliced reader with no reader slice is refused). Battery 3e declares 16 and the section holds 16 check() calls; SELF_TEST_BATTERY_FLOOR 16 -> 17 with one roster entry. The lint gates that run the self-test are green on the head. Right.
  • Empirical reading of the head's own run (step records of run 40615848616, data only): "Build this shard's dependency closure" took 242 s on Test Core 1/8 and 0-58 s on 2/8-8/8, so exactly ONE shard built declarations; "Build the sliced package's dependency closure" ran on 1/8, 4/8, 5/8, 6/8 (the four cli slices), and all four, including the three that ran on a declaration-less closure, are green with the completeness guard green — i.e. slices 2-4/4 held no reader in CI, as the design says. This run touched no packages/spec, so it is not an acceptance reading (slowest shard 5/8 at 12.1 min, closure mostly cached).
  • Public surface: none. packages/cli files[] = dist, README.md, CHANGELOG.md; tsconfig.build.json builds src only, so vitest.config.ts and its two exports never ship. scripts/ and .github/ are not published.
  • Ruling 6439314592 (A as specified in 6436991767): sequencer in the cli config pinning the five measured readers (re-measured at 4373103 and 9d1cfdb, none added); ci.yml keeps the closure for cli 1/n and metadata only; partitioner charges the slice ~4.5 min (270 s x 2.9) and seats metadata on its shard; the comment lists create-in-repo-scaffold-typechecks. All four parts are in the tree. ⛔ list: no test skipped, quarantined or moved out of Test Core (no .skip(/.only(/.todo(/skipIf added in the diff, no tier change); no timeout raised (the diff carries no timeout token); SHARD_COUNT and FILE_SHARDED_PACKAGES (cli: 4) untouched, Test Core matrix still 8; no other ci.yml job touched. Inside the ruling.

② Semver level

skip-changeset is right and Check Changeset is green: the diff publishes nothing from any released package (a workflow step, a script under scripts/, a vitest config outside the cli build's include). The PR body's Clause-②: no and Metadata-protocol: none hold with the claim 6456145005 and the ruling: no spec key, value, type, default or meaning changes; no published entry's export changes. No changeset owed, no BREAKING banner, no ADR-0087 disposition. Ruling-ref: 6439314592 and Part of #25870 are in the body.

③ Boundary flags

  • Dev open question (6458117958: Fixes vs Part of) — answered by the seat at 6458149471 and the body carries Part of #25870. Right: the ruling closes the card only after the 10-full-queue-build reading is repeated post-landing, and the card's own acceptance (slowest Test Core shard ≤ ~20 min on three consecutive spec-touching pull_request runs) is read after this PR exists. Not measured on this head and not owed by this record.
  • Tier H approval is absent at read time. GET pulls/27018/reviews returns 0 reviews; GOVERNED_APPROVERS (hotlong, os-zhuang) are requested. The seat's ACCEPT 6458499735 says the same. A landing precondition for the queue guard, not a contract defect: escalated to the maintainer as the one action this PR waits on.
  • The head is 10 commits behind origin/main (merge base 5e99bd936c; main 7eb613c39f), and one of those, e6c4409000 (ci(ui-merge): port objectui's lockfile-integrity gate into Type Check UI and retire lockfile-dedupe and the shadcn-check report #26995), edits ci.yml at L3418-3449 inside the type-check-ui job. The regions are disjoint from L913-961 and the merge queue builds the merge, so nothing is owed; stated so a two-dot diff of main..head is not misread as this PR touching a second job.
  • Dev out-of-scope note 1 (real slices 313/595/827/401 s, cli 3/4 heaviest): option C, not ruled; this PR does not worsen it (sha1 split was 301/602/833/401). Stays on the card as the next lever if the CI reading misses ~20 min.
  • Dev out-of-scope note 2 / earlier S1 (the --shard log's Ns predicted (all bins: ...) and balanceOf() carry the 783 s charge unlabelled): driftReport() is untouched, so no verdict changes; a wording follow-up, not owed here.
  • Earlier S2 (an UNLISTED new reader passes while sha1 keeps it in slice 1 and reds an unrelated PR when the cut moves): the ruling's accepted facet; the PR body's Acceptance notes now say how to read that red. No code owed.
  • Declared narrowing (slices 2-4/4 not re-run as slices locally): CI ran them for real on this head, green on declaration-less closures (step record above). Closed.
  • actionlint / shellcheck NOT MEASURED by the dev; the "Lint · CI, release & engine gates" check-run on the head is green, which is the gate that owns them.
  • Nothing else to escalate.

No must-fix.

Implemented-by: claude/issue-25870-cli-reader-sequencer
Reviewed-by: session_01AavokzJ5DndAwitDXvKy4U

VERDICT: PASS


Generated by Claude Code

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

Labels

ci/cd size/l skip-changeset PR has no user-facing published change; bypasses the changeset gate tests

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants