Uh oh!
There was an error while loading. Please reload this page.
test(lint): pay the startup-verdict corpus sweep once, under a stated hook budget - #10911
Merged
Merged
Conversation
… hook budget The corpus read + scan (1880 files / 28.15 MB) ran twice, once inside each of the two corpus cases, sharing nothing — and both cases sat under vitest's default 5000ms per-test timeout. On a loaded merge-queue shard the first case measured 9144ms and ejected PR #10733, which never touched packages/lint, from the queue; every entry behind it rebuilt. Hoist the sweep into one beforeAll and give it an explicit, commented budget: - Work removed: file `tests` total 1441ms -> 816ms locally; the largest thing measured against the per-test budget drops from 972ms to 2ms. - The budget is a hook timeout, deliberately loose (60s ~= 6.5x the worst wall-clock ever observed for this sweep). It is a liveness backstop, not a performance tripwire — a snug budget is what ejected an unrelated PR, and the work grows with the repo (1872 files at 12:23Z, 1880 four hours later) while the wall-clock varies with shard load. Sharing is only sound because neither case mutates what the other reads, so the findings array and every finding in it are frozen, and the shared value starts `undefined` rather than `[]` behind a `corpusFindings()` accessor that throws. That last part is a third false green the file now refuses: a case reading a sweep that never ran would otherwise print as a clean audit over nothing. Both gate cases still fire individually — proven by ablation, see the PR body. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DdCnBGcHeufjrq7drTD3wt
Contributor
📓 Docs Drift CheckNothing in this diff resolved to a documentable surface (no symbol, route or SDK anchor derived from 0 changed package(s)), so this run has no opinion about the docs. |
This was referenced Aug 21, 2026
os-zhuang
marked this pull request as ready for review
August 22, 2026 03:54
Uh oh!
There was an error while loading. Please reload this page.
Contributor
⛔ merge queue 构建失败 — 先分诊,再决定要不要重排队列构建 32550265418 红了。队列跑的是全量套件(PR 侧 CI 只跑 affected 子集), 失败的 job(日志抽取,best effort):
跨 PR 相同签名(24h,按失败测试文件聚合):
历史信号:
分诊清单:
Generated by Claude Code · merge-queue-triage workflow (#4859) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for freeto join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes#10838
What was removed vs what was widened
Removed: one of the two full corpus sweeps. The read + scan of every non-test
.tsunderpackages/(1,880 files / 28.15 MB as of this branch) ran once inside each of the two corpus cases, sharing nothing. It is now paid once in abeforeAlland both cases read the same findings.Widened: nothing per-test. The sweep's budget moved from vitest's implicit default 5000ms per-test timeout to an explicit, commented 60s hook timeout.
Measurements I took on this branch
Corpus, re-derived (the card's numbers were from 12:23Z):
The corpus grew by 8 files in about four hours. That is the trend the card is about, and it is why the fix is not a bigger number.
Sweep cost, isolated (
tsx, this worktree):The test file, before and after (
vitest --reporter=verbose, same machine):teststotalSo: 43% of the work is gone, and the largest single thing measured against the 5000ms per-test budget drops from 972ms to 2ms.
Why the timeout is still needed, and why it is 60s
Be honest about which half does the work. Sharing removes one warm sweep (~465ms locally). The ejecting run's 9144ms was the cold leg — the lazy ~9 MB
typescriptload plus a cold parse of the corpus on a shard that reportedimport 106.30s. That leg still has to happen once, and it is ~9.2x its local cost on a loaded queue shard.Project the ejecting run onto the new shape: the hook does what its case 1 did, ~9144ms, and clears vitest's default hook timeout of 10000ms by 856ms. That is still a near-threshold budget on a shard whose load is the variable — which is exactly the failure mode this card is about. So the number is stated rather than defaulted.
60s is ~6.5x the worst wall-clock this sweep has ever been observed to take and ~60x the local one. The comment in the file says plainly that this is a liveness backstop — a wedged sweep must not pin a worker forever — and explicitly not a performance tripwire, because a snug budget over work that grows with the repo is guaranteed to red on somebody else's PR eventually. The cost stays legible without a tripwire: it is one hook now, and hook time lands in the file's own
teststotal.The gate's meaning is unchanged, and that is proven, not asserted
Both of the file's original false-green refusals are untouched (
reads a non-empty corpus;the sweep can still fire). Sharing introduces a third hazard, so the file now refuses that too:filter,map). A comment cannot hold that open, so the findings array and every finding in it are frozen.undefined, not[], behind acorpusFindings()accessor that throws. A case reading a sweep that never ran would otherwise print as a clean audit over nothing — false green 1's shape, one level up.Ablation — predictions written down before any leg ran
No build/dist step is involved: the test imports
./lint-startup-registry-verdict.js, which vitest resolves to the sibling.tsinsrc, and the corpus is read from the filesystem at run time. Each leg was mutate → prove the mutation on disk by marker count → run → restore → prove absent on disk.start()drawing a verdict fromgetRegisteredNodeTypes()and warning "will fail at execution time" (the #4771 shape) into the corpus× no package records a verdict…, both findings emitted with paths/lines;✓ no ledger entry is stale; 1 failed / 3 passedLEDGERkey no live finding produces× no ledger entry is stale — the site is fixed, delete the line: packages/ablation/src/gone.ts:1::startup-open-vocabulary-verdict;✓ no package records a verdict…; 1 failed / 3 passedsweepResult = …publish inbeforeAll(sweep still runs, result never published)Error: the corpus sweep did not run — this case would otherwise report a clean audit over a corpus it never swept (#10838); 2 failed / 2 passedfindings[0].messagein the first caseTypeError: Cannot assign to read only property 'message'(vitest names the frozen object after it)A and B are the load-bearing pair: each case reds individually off the shared array, so neither has quietly stopped testing anything. C proves the new refusal is not decorative. D is worth noting for a reviewer: TypeScript did not catch the mutation statically —
corpusFindings()returnsreadonly Finding[], which freezes the array shape but not the elements' properties — so the runtime freeze is the thing that actually holds the invariant.Restore was proven byte-identical by regenerating the file deterministically from the pristine
HEADcopy and comparing hashes:git status --porcelainshowed the one intended file and no untracked ablation residue.Verification
Final green, at the commit this PR carries (
b97a70c9a7):Whole package:
pnpm --filter @objectstack/lint test→Test Files 80 passed (80),Tests 2246 passed | 5 skipped (2251), exit 0.pnpm --filter @objectstack/lint typecheck→ exit 0, noerror TS. Both filters echoed@objectstack/lint@17.1.0, and--filter @objectstack/definitely-not-a-packagewas run once as a negative control: it printedNo projects matched the filtersand exited 0 (#10853), so the reading above is confirmed rather than assumed.Gate union re-derived at
b97a70c9a7withnode scripts/pm/dispatch-gates.mjs(no hand-built path list), all green, each quoting the gate's own verdict line:check:cross-package-test-inputs—OK: 13 package(s) read outside themselves, all declared…(+ 104 self-test cases)check:slot-lookup—✓ slot-lookup ratchet holds: 107 unswept site(s)… none newcheck:test-source-alias—OK — 72 packages with tests scanned…check:type-source-resolution—OK — 76 packages with a tsconfig.json scanned…check-ci-filter-parity.mjs—OK: all 82 declared cross-package glob(s)…check-plugin-teardown-shape.mjs—✓ … 57 Plugin implementation(s) across 4408 source(s)check-affected-docs.mjs—✓ affected-docs self-test: 323 cases pass.check:query-options-erasure—✓ ratchet holds… baseline key set verified against 409077e: no files addedcheck:type-check-coverage—OK — 64/77 workspace packages type-checked…check:engine-double-contract—OK — 376 pinned, 133 in the DEBT ledger, 2 exemptcheck:where-matcher—✓ conformance holds: 275 matcher(s)… none newcheck-nul-bytes.mjs—OK (scanned 6301 text file(s)… no raw ASCII control bytes)One declared narrowing:
check:type-check-debt --re-measurewas not run locally. It refuses to measure on an unbuilt worktree and needsturbo run buildacross the whole workspace, which would hold this container's shared verify lock for a long time while three other agents are queued on it. The reason it is convention-triggered here is "a new test file that does not typecheck cleanly" — this PR adds no new test file, edits one that already typechecks, andpnpm --filter @objectstack/lint typecheckis green at this commit. CI runs it regardless.Release declaration
Test-only: the change is confined to
packages/lint/src/lint-startup-registry-verdict.corpus.test.ts, whichtsupnever builds intodistandpackage.json#filesnever publishes. Nothing releases, so this PR carriesskip-changesetrather than a changeset — derived frompr-automation.yml'schangeset-check(its two exemptions both mean "this PR declares no release of its own"), not assumed.Generated by Claude Code