Skip to content

test(devx): gate the module-top load of a dist-resolved workspace dep (clocked-window rule) - #10135

Merged
os-zhuang merged 5 commits into
mainfrom
claude/issue-10126-test-static-import-gate
Aug 20, 2026
Merged

test(devx): gate the module-top load of a dist-resolved workspace dep (clocked-window rule)#10135
os-zhuang merged 5 commits into
mainfrom
claude/issue-10126-test-static-import-gate

Conversation

@os-zhuang

@os-zhuangos-zhuang commented Aug 20, 2026

Copy link
Copy Markdown
Contributor

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.mjs gains a clocked-window rule, in the registry-plus-gate
idiom it already uses. For every specifier a package really resolves through dist/ — the
measurement KNOWN_UNALIASED_TEST_IMPORTS mirrors, read at specifier granularity — a test
file that loads it through a dynamic import() / require()inside a function body
(an it()/test() body, a beforeAll/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:

packages/x/src/y.test.ts:41: `import('@objectstack/z')` is paid inside a function body — a CLOCKED window.
THE CONVENTION: clocked windows measure behaviour, never loading — a test that boots a real
plugin chain pays its first load at module top.
Add a module-top side-effect import so the transform is paid during COLLECTION, which vitest
clocks against nothing (it clocks hooks and test bodies only):
import '@objectstack/z';

Four boundaries, all argued in the header and each pinned by a self-test leg:

  • Compliance is a MODULE-SCOPE load, which is wider than "a static import." A static
    import / export … from is the spelling the remedy prints, but a dynamic import() at
    module scope — including one inside a top-level if block — is paid during collection by
    the 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, and
    never a compliance token either. That shape is real in this tree
    (sys-metadata-repository.history-counters.test.ts).
  • The population is the ledger's, not "every import anywhere." A dep already aliased to
    source still costs a transform; widening to it is a different card.
  • "Inside a function body" is decided by a brace scanner over the source with comments and
    string/template/regex CONTENT masked out, reusing scanSource from
    scripts/js-comment-mask.mjs rather than a second hand-rolled tokenizer.

Self-test: 47 → 57 assertions

Ten new legs, each observed failing and observed silent:

legfixturepinned
hook shapeclocked-loadbeforeAll(async () => { await import(…) }) is reported
test-body shapeclocked-loada load nested two functions deep inside it() is reported
exact countclocked-loadexactly the two loads that fixture pays in a clocked window
remedy textclocked-loadthe finding prints the module-top import and says where the cost moves TO
the conventionclocked-loadthe sentence itself is in the author-facing text, not only in a comment
both present is compliantclocked-load-paidmodule-top import + the same dynamic call ⇒ silent (this is the remediation, not the defect)
decoyclocked-decoya dynamic import of a specifier outside the ledger, a relative import and a node builtin ⇒ all silent
module scope ≠ static importclocked-top-level-awaittop-level await import(), and one in a top-level if, ⇒ silent
type queryclocked-type-querytypeof import('x') does not silence the finding it never paid for
typed signatureclocked-typed-signaturea helper ending }): 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/cli 7 · packages/runtime 10 · packages/objectql 3 · packages/rest 1 ·
packages/metadata-protocol 1 · packages/services/service-datasource 1 ·
packages/qa/dogfood 1 · examples/app-crm 1 · examples/embed-objectql 1.

One file got more than a new import: packages/cli/src/commands/dev-default-db.test.ts
carried a beforeAll warm-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 (static import { resolveStandaloneDatabase } from '@objectstack/runtime' above its warm-up hook) and
union-branch-policy.cross-package-parity.test.ts for 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 of
that 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.

- a dynamic import inside a `beforeAll` hook was not reported — the exact shape of the #10115 incident
- a dynamic import nested two functions deep inside an `it()` body went unseen — the brace scanner reads only `=> {`
- the clocked-window rule did not report exactly the two loads this fixture pays in a clocked window
- `typeof import('x')` was read as a real module-scope load, and silenced a finding it never paid for

B — the compliance leg disabled (moduleScope ignored). Predicted: the silent legs
invert. 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 if block would stop counting as module scope. Observed exactly one
red — 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.ts was being exempted
silently. 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 openOf map, => inside a function type is not counted as a generic closer, the
shape 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:

Clocked windows measure behaviour, never loading — a test that boots a real plugin chain
pays its first load at module top; pnpm check:test-source-alias gates it.

Getting it there took a ceiling raise, and scripts/pm/check-skill-line-ratchet.mjs requires
the authorising ruling to be quoted in the raising PR — so, verbatim and untranslated
(2026-08-20 live chat, recorded at
#10126 (comment)):

「A — 抬上限到 961 (Recommended)」

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-error
bullet 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.mjs against the actual changed paths
after the final commit — AGENTS.md and scripts/pm/check-skill-line-ratchet.mjs are in the
set, and the derivation returned the same 20 families (the ratchet script matches the
scripts/** and AGENTS.md/** sources already present). Exit codes captured before any pipe.

pm-skill-ratchet 0 (AGENTS.md 961, ceiling 961, headroom 0; self-test 19/19)
pm-governed-prose 0 · pm-governed-merges 0 · pm-skill-id-lint 0 · docs-audit-scope 0
required-contexts 0 · cross-package-test-inputs 0 · dispatcher-error-vocabulary 0
durability-log-level 0 · slot-lookup 0 · test-source-alias 0 (+ --self-test 57 assertions)
type-source-resolution 0 · nul-bytes 0 · query-options-erasure 0 · type-check-coverage 0
engine-double-contract 0 · where-matcher 0 · ratchet-remedy-authority 0
engine-split-ratio 0 · affected-docs 0
spec: empty-state 0 · liveness 0 · strictness-ledger 0 · variant-docs 0

One narrowing, declared: check:type-check-debt --re-measure was run green in the earlier
round (33 entries, 1924 raw errors, none above its recorded number) and not re-run for
the layer ④ commit, which touches only AGENTS.md and two scripts/*.mjs files — no .ts
file it measures changed, so it cannot move a count. CI runs it regardless.

check:ratchet-remedy-authority is run deliberately: it classifies this gate's refusal
shape 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):

cli 135 files, 1470 tests, all passed
runtime 177 files, 2649 tests, all passed
objectql 221 files, all passed
rest 131 files, 2134 tests, all passed
metadata-protocol 123 passed | 2 skipped (125)
service-datasource 22 files, all passed
qa/dogfood 121 passed | 1 skipped (122); 880 passed | 3 skipped
examples/app-crm 4 files, 42 tests, all passed
examples/embed-objectql 1 file, 2 tests, all passed
typecheck (all 9 packages) EXIT=0

Changeset

None, deliberately: scripts/, AGENTS.md and test files only — publishes nothing.
skip-changeset applied, same mechanism PR #10120 used — scripts/check-empty-changeset.mjs
rejects 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

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
@os-zhuangos-zhuang added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Aug 20, 2026 — with Claude
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

Nothing 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
@github-actionsgithub-actionsBot added the documentation Improvements or additions to documentation label Aug 20, 2026
@os-zhuangClaude

Copy link
Copy Markdown
ContributorAuthor

PM review — ACCEPT the content. ⛔ NOT arming: GOVERNED_HITS=['AGENTS.md'] — human-merge-only.

Reviewed at the layer-④ head by the spec seat (session session_01URCaKuNTuK3BKJvwqM74QU), diff re-verified from the branch, not from the report.

Layer ① (the gate) — accepted on the strength of the round-1 report: self-test 47→57 with every leg red/green both ways, the baseline sweep repairing all 32 violations (9 packages) the #10120 way with zero quarantines/exemptions/budget raises, and ablation leg C catching a real silent exemption (}): Promise<{…}> { multi-line signature) that the sweep alone could not have shown — the exact class of false green this whole incident was about.

Layer ④ (the ruling round) — verified against the maintainer's option-A ruling (recorded at #10126 (comment)):

  • check-skill-line-ratchet.mjs: ['AGENTS.md', 958]['AGENTS.md', 961], exactly +3, ruling quoted verbatim and untranslated in the map comment, scoped as one-time. ✓
  • AGENTS.md: +3/−0, lands at 961/961 (headroom 0 again by construction). ✓
  • One deviation, disclosed: the mandated sentence is present intact and in order (case-insensitive full-sentence grep = 1) but landed with a sentence-initial capital and an appended pointer — ; pnpm check:test-source-alias gates it. — inside the same 3-line budget. Nothing the maintainer specified was removed or reworded; the appendix names the enforcing gate. Judged acceptable and flagged rather than silently passed; if the maintainer wants the bare sentence, it is a one-line trim.

Why this seat is not merging: AGENTS.md is a governed surface — GOVERNED_SURFACES id agents-md in scripts/pm/check-governed-merges.mjs, per the maintainer's approved unified list quoted in that file's header. Human-merge-only, no exceptions from this seat. The PR stays draft, auto-merge stays off, and it hangs for the maintainer by design. PR-side CI at this head: all completed checks green, heavy jobs in progress, zero red.

@os-elon — this PR is ready for your merge decision once CI completes.


Generated by Claude Code

@os-zhuangClaude

Copy link
Copy Markdown
ContributorAuthor

Merging on the maintainer's explicit per-PR authorization (verbatim, untranslated: 「帮我审核并合并 #10135」, 2026-08-20 live chat with session session_01URCaKuNTuK3BKJvwqM74QU). This is a governed-surface PR (AGENTS.md, surface id agents-md) that was correctly left hanging as draft for a human; the instruction above is that human decision, quoted here so the merge does not read as an agent acting alone — same precedent as PR #10003.

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

@os-zhuang
os-zhuang marked this pull request as ready for review August 20, 2026 08:45
@os-zhuang
os-zhuang added this pull request to the merge queueAug 20, 2026
Merged via the queue into main with commit 2f15284Aug 20, 2026
30 checks passed
@os-zhuang
os-zhuang deleted the claude/issue-10126-test-static-import-gate branch August 20, 2026 09:03
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentationImprovements or additions to documentationsize/lskip-changesetPR has no user-facing published change; bypasses the changeset gatetests

Projects

None yet

2 participants

@os-zhuang@claude