Uh oh!
There was an error while loading. Please reload this page.
test(devx): gate the module-top load of a dist-resolved workspace dep (clocked-window rule) - #10135
Conversation
Extends `check:test-source-alias` with the clocked-window rule (#10126): a test file that loads one of its package's unaliased specifiers through a dynamic `import()` / `require()` inside a function body — an `it()` body, a hook, or any nested function — with no module-scope load of the same specifier is a finding. vitest clocks hooks and test bodies only; collection is clocked against nothing, so a module-top side-effect import moves a cold transform OUT of every budget rather than into a bigger one. #10115 / PR #10120 paid for that reading with 30 ejected merge-queue builds in one night. Baseline sweep: 31 violations across 21 files in 9 packages, all fixed here the PR #10120 way (module-top `import '<specifier>'`), none quarantined or registry-exempted. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01URCaKuNTuK3BKJvwqM74QU
`packages/clocked-load` is also a prefix of `packages/clocked-load-paid`, so the count assertion read the compliant fixture's findings as the violating one's. Found by ablating the compliance leg: the count moved to 3 for a reason that was in the assertion rather than in the gate. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01URCaKuNTuK3BKJvwqM74QU
The classifier's backward walk bailed on the braces, the semicolon and the newlines a multi-line signature puts between the parameter list and the body, so a module-level helper called from test bodies read as "not a function" and its file was exempted SILENTLY. Surfaced by ablating the classifier, not by the sweep: `serve-marketplace-offline-runtime-config.test.ts` sat green through it. Balanced groups are now jumped through the forward pass's `openOf` map, `=>` inside a function type is not counted as a generic closer, and the shape is pinned as its own self-test fixture. One more real violation follows, fixed the same way. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01URCaKuNTuK3BKJvwqM74QU
#10126's layer 4 asked for the sentence in AGENTS.md's testing conventions. That file sits exactly on its shrink-only line ceiling (958, headroom 0, `scripts/pm/check-skill-line-ratchet.mjs`); the sentence costs three lines and its own section has two lines of lossless rewrap headroom. Raising a ceiling takes a maintainer ruling, and truncating a maintainer-specified sentence to fit one is not this gate's call — so the doc placement stays open and is reported. Meanwhile the sentence is stated verbatim in the gate header AND in the failure text, pinned by its own self-test assertion so it cannot decay into a comment. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01URCaKuNTuK3BKJvwqM74QU
📓 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. |
…958 -> 961) Layer 4 of #10126, unblocked by a maintainer ruling. AGENTS.md sat exactly on its shrink-only line ceiling with the mandated sentence costing three lines and its section carrying two lines of lossless rewrap headroom, so it could not be paid for in place. Ruling 2026-08-20, verbatim and untranslated: 「A — 抬上限到 961 (Recommended)」 The raise is exactly +3 and nothing more; headroom is 0 again by construction, so the next author needing a line is back to compressing. The sentence lands beside the sibling test-gate conventions in § Build & Test, unedited, and the gate header's note about why it was NOT there is corrected. The ratchet's self-test pins coverage and shape, never a ceiling's numeric value, so it needed no extension; re-run green at 19 cases. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01URCaKuNTuK3BKJvwqM74QU
os-zhuang
commented
Aug 20, 2026
PM review — ACCEPT the content. ⛔ NOT arming: |
os-zhuang
commented
Aug 20, 2026
Merging on the maintainer's explicit per-PR authorization (verbatim, untranslated: 「帮我审核并合并 #10135」, 2026-08-20 live chat with session The ACCEPT review is one comment up; CI at this head is fully green (29/29 success or skipped, zero red). Un-drafting and enqueueing now. The one disclosed deviation (sentence-initial capital + the appended gate pointer inside the 3-line budget) was in front of the maintainer when they authorized. Generated by Claude Code |
Uh oh!
There was an error while loading. Please reload this page.
Fixes#10126
Both layers of the card land here: ① the new gate limb, ④ the convention sentence in
AGENTS.md § Build & Test.
The new limb
scripts/check-test-source-alias.mjsgains a clocked-window rule, in the registry-plus-gateidiom it already uses. For every specifier a package really resolves through
dist/— themeasurement
KNOWN_UNALIASED_TEST_IMPORTSmirrors, read at specifier granularity — a testfile that loads it through a dynamic
import()/require()inside a function body(an
it()/test()body, abeforeAll/beforeEach/other hook, or any nested function)with no module-scope load of the same specifier is a finding, reported against the test
file with the remedy PR #10120 landed:
Four boundaries, all argued in the header and each pinned by a self-test leg:
import/export … fromis the spelling the remedy prints, but a dynamicimport()atmodule scope — including one inside a top-level
ifblock — is paid during collection bythe same measurement PR test(plugin-dev): pay the plugin-security transform at module load, out of every clocked window #10120 made, so flagging it would invent a failure.
typeof import('x')is a type query, erased before anything loads: never a finding, andnever a compliance token either. That shape is real in this tree
(
sys-metadata-repository.history-counters.test.ts).source still costs a transform; widening to it is a different card.
string/template/regex CONTENT masked out, reusing
scanSourcefromscripts/js-comment-mask.mjsrather than a second hand-rolled tokenizer.Self-test: 47 → 57 assertions
Ten new legs, each observed failing and observed silent:
clocked-loadbeforeAll(async () => { await import(…) })is reportedclocked-loadit()is reportedclocked-loadclocked-loadclocked-loadclocked-load-paidclocked-decoyclocked-top-level-awaitawait import(), and one in a top-levelif, ⇒ silentclocked-type-querytypeof import('x')does not silence the finding it never paid forclocked-typed-signature}): Promise< { … } > {is a function body (see below)Baseline sweep
Run before wiring the limb into a green state. 32 violations across 22 files in 9
packages, every one repaired the way PR #10120 did — a module-top side-effect import with a
comment naming the incident. Nothing quarantined, nothing registry-exempted, no timeout
raised, no assertion touched.
packages/cli7 ·packages/runtime10 ·packages/objectql3 ·packages/rest1 ·packages/metadata-protocol1 ·packages/services/service-datasource1 ·packages/qa/dogfood1 ·examples/app-crm1 ·examples/embed-objectql1.One file got more than a new import:
packages/cli/src/commands/dev-default-db.test.tscarried a
beforeAllwarm-up with a 60s budget — the exact shape PR #10120 had to abandon.The hook is gone and the comment now records why a bigger budget is not the fix.
Two files were already compliant and correctly stayed silent, which is the compliance leg
working on the real corpus rather than only on fixtures:
unified-db-resolution.pin.test.ts(staticimport { resolveStandaloneDatabase } from '@objectstack/runtime'above its warm-up hook) andunion-branch-policy.cross-package-parity.test.tsfor the two subpaths it aliases.Ablation
Four legs, each predicted before running, each restored and verified byte-identical by
git hash-object— not by a matching--stat.A — the reporting limb removed. Predicted: the four assertions that expect a finding go
red; the four
length === 0/.every()legs pass vacuously (that is what assertions ofthat shape do when nothing is produced, and it is why the exact-count leg exists); the repo
scan stays green, because removing the limb stops it looking rather than repairing anything.
Observed exactly that.
B — the compliance leg disabled (
moduleScopeignored). Predicted: the silent legsinvert. Observed 3 red, and the repo scan went from 0 findings to 38 — the 32 this PR
repairs plus 6 files that were already compliant through a pre-existing module-scope load. So
the compliance leg is load-bearing on the real tree, not only on fixtures.
C — every brace reads as a function body. Predicted: only the top-level-await leg goes
red, since a top-level
ifblock would stop counting as module scope. Observed exactly onered — and one extra finding on the real tree, which turned out not to be a false
positive but a real miss (next paragraph).
D — the pre-repair angle walk restored. Predicted: the typed-signature leg goes red and the
repo scan stays green, because the file it was silently exempting is now repaired. Observed
exactly that, which is the whole reason that fixture exists.
What ablation C found
packages/cli/test/serve-marketplace-offline-runtime-config.test.tswas being exemptedsilently. Its helper's signature spans lines and ends
}): Promise<{ app: HonoShapedApp; wiring: ReturnType< typeof Serve.planMarketplaceWiring > }> {— and the backward walk bailed on the braces, the semicolon and the newlines between the
parameter list and the body, reading a real function body as "not a function." A silent
exemption is indistinguishable from compliance in everything the gate prints, so the sweep
above would have shipped one file short. Balanced groups are now jumped through the forward
pass's
openOfmap,=>inside a function type is not counted as a generic closer, theshape is pinned as fixture (25), and the file is repaired like the rest. Commit
94eb16cff.Layer ④ — the convention sentence, and the ceiling it needed
The sentence now sits in AGENTS.md § Build & Test, beside the sibling test-gate conventions
(
check:cross-package-test-inputs,check:type-check-coverage), unedited:Getting it there took a ceiling raise, and
scripts/pm/check-skill-line-ratchet.mjsrequiresthe authorising ruling to be quoted in the raising PR — so, verbatim and untranslated
(2026-08-20 live chat, recorded at
#10126 (comment)):
Why it was needed, measured rather than asserted: AGENTS.md sat at exactly 958 lines against
a ceiling of 958 — headroom 0. The sentence is 122 characters, so at the file's 90-column
fill it costs two prose lines plus a blank: +3. Lossless rewrap headroom in the whole
§ Build & Test section is 2 lines (the type-check paragraph 7→6, the
@ts-expect-errorbullet 8→7); every other paragraph there is already at fill. So it could not be paid for in
place, which is the one case the ratchet's header reserves a ruling for.
The raise is exactly +3 and nothing more: 958 → 961, the file lands at 961, and headroom
is 0 again by construction, so the next author needing a line is back to compressing. The
ratchet's self-test pins coverage and shape (which files are covered, that every ceiling is a
positive integer, the root-file watch-hint declaration) and never a ceiling's numeric value,
so it needed no extension — re-run green at 19 cases.
The sentence is also carried in this gate's failure text, pinned by its own self-test
assertion, so it cannot decay back into a comment no tripping author reads.
Gates
Union re-derived by
node scripts/pm/dispatch-gates.mjsagainst the actual changed pathsafter the final commit — AGENTS.md and
scripts/pm/check-skill-line-ratchet.mjsare in theset, and the derivation returned the same 20 families (the ratchet script matches the
scripts/**andAGENTS.md/**sources already present). Exit codes captured before any pipe.One narrowing, declared:
check:type-check-debt --re-measurewas run green in the earlierround (33 entries, 1924 raw errors, none above its recorded number) and not re-run for
the layer ④ commit, which touches only
AGENTS.mdand twoscripts/*.mjsfiles — no.tsfile it measures changed, so it cannot move a count. CI runs it regardless.
check:ratchet-remedy-authorityis run deliberately: it classifies this gate's refusalshape as
refused-by-predication, and the limb adds new author-facing text. Still green.Tests and typecheck (layer ① round; layer ④ touches no test or package source), full
workspace build first (71 tasks, 6m06s):
Changeset
None, deliberately:
scripts/,AGENTS.mdand test files only — publishes nothing.skip-changesetapplied, same mechanism PR #10120 used —scripts/check-empty-changeset.mjsrejects an empty changeset, so the label is the repo's answer rather than a placeholder file.
Related: #10115 is the root cause and #10112's re-scoped registry-shrink half is the
complementary card; neither is addressed here. Aliasing removes entries from this gate's
input, and this limb covers whatever remains.
Generated by Claude Code