Repository navigation
test(runtime,metadata-protocol,objectql): pay the lint root's first load at module top where a clocked case paid it - #29785
Conversation
…oad in a setup file, outside the clocked case A served view read and an object publish load @objectstack/lint lazily; under vitest that first load is a cold transform paid inside whichever case makes the file's first such read. A traced full run of each suite found it inside a case in 9 runtime, 39 metadata-protocol and 18 objectql test files. Each package now imports the root in a setup file, which vitest runs before collection and no timeout clocks. objectql gains the anchored ^@objectstack/lint$ source alias, since it does not depend on lint itself. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017ihL2gLghEqeDKTbDTC7Zd
…testkit at module top instead of a setup file A setup file made every test file pay the lint root's load, measured at about +200 s of worker time in metadata-protocol and runtime and +330 s in objectql. A testkit per package, imported at module top by only the files whose traced run showed the first load inside a case, moves exactly that load into collection. objectql declares @objectstack/lint as a devDependency so the import type-checks, and keeps the anchored source alias that check:test-source-alias requires for it. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017ihL2gLghEqeDKTbDTC7Zd
…rm-lint-cold-load Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017ihL2gLghEqeDKTbDTC7Zd
📓 Docs Drift Check
What this run could not see
Coarse fallback — 42 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): |
|
domain:devx#2 · marchtian · session_017ihL2gLghEqeDKTbDTC7Zd 席位核对 — ACCEPT ·
|
Requested by 田騏 · project thread
Fixes #29627
Clause-②: no
Metadata-protocol: none
维护者速读
lint-root-preload.testkit.ts),在模块顶部加载@objectstack/lint包根;实测会在用例里冷加载它的 66 个测试文件(runtime 9、metadata-protocol 39、objectql 18)在文件顶部各引入一行。objectql 另加 lint 的 devDependency 和锚定源码别名。package.json/pnpm-lock.yaml多一条 devDependency。packages/objectql/package.json与packages/objectql/vitest.config.ts(新依赖与别名),packages/metadata-protocol/src/lint-root-preload.testkit.ts(为何不用 setupFiles 的实测),任一个测试文件顶部新增的两行。pnpm --filter @objectstack/metadata-protocol exec vitest run src/protocol.lock-door-read-agree.test.ts --reporter=verbose,预期全绿,an overlay view declaring _lock=none那个用例约 45 毫秒(main 上约 0.8 秒)。What changes
packages/runtime/src/lint-root-preload.testkit.ts,packages/metadata-protocol/src/lint-root-preload.testkit.ts,packages/objectql/src/__fixtures__/lint-root-preload.testkit.ts. Each is a module-topimport '@objectstack/lint'with a header naming the cost it moves and why it is not a setup file.it,expect, timeout or helper changes.@objectstack/lintbecomes a devDependency (package.json, 3 lines inpnpm-lock.yaml), andvitest.config.tsgains the anchored^@objectstack/lint$alias to../lint/src/index.ts. Both are forced, see Acceptance notes.vitest.config.ts: one comment on the existing#29603alias now names the testkit. No resolution change.Why
A served view read wraps its body in
withFormLayoutWarnings, which awaitsloadFormLayoutLint()(import('@objectstack/lint'),packages/metadata-protocol/src/metadata-diagnostics.ts). An object publish's build probes load the same root the same way (packages/metadata-protocol/src/build-probes.ts). Under vitest the first such load in a file is a cold transform of the lint graph, inside whichever case gets there first. A module-top import is paid during collection, which no timeout clocks, and the protocol's laterimport()gets the same instance back. The lazy import itself is product design and stays as it is.How the population was measured
Not by grep. A scratch tracer (deleted) appended one counter increment to the lint root's transformed module and recorded, per test file, whether the counter moved during collection, in a hook, or inside a case. One run of each package's whole suite (both vitest projects) on
7dae3b3c0d:meta-write-org-scope.test.ts)beforeAllwith a 120000 ms budget (protocol.read-lock-flags-write-door.test.ts), droppedThe card's coarse grep (real protocol plus a view read, minus files already importing the root) names 149 files in the three packages. The trace keeps 43 of them and drops 106, and it finds 23 positives the grep misses, mostly object publishes reaching the build probes. Of the card's three named runtime candidates,
package-revert-stored-membersandmeta-save-preconditions-parityare kept;packages-seed-apply-org-scopeis dropped (the lint root is never evaluated in that file).Measurements
Each sample is one vitest process on one file (
--maxWorkers=1, json reporter), underos-verify-lock.shon a 4-vCPU shared box. "Idle" is 3 sequential runs. "4x" is the contention method from #29622: 4 identical copies of the same file run concurrently, 1 round, 4 samples. The number is the duration of the case the trace named. Before is7dae3b3c0d; after is4bab1b183f(the same base plus this change). 0 failed runs in 924 samples.In runtime the root already resolves to lint's source through #29622's alias, and that load is cheaper (roughly 100 to 300 ms) than the 1.5 MB
dist/bundle metadata-protocol and objectql read (roughly 650 to 1100 ms). It still lands in the case, so the 9 files are kept. After the change, a targeted re-trace of the 66 files shows the root evaluated once per file, at collection, and 0 in-case loads.Per file, ms p50 / max
packages/runtime (9 files), first lint-loading case, ms p50 / max
domains/meta-app-package-id-key.test.tsdomains/meta-package-all-fold.test.tsdomains/meta-published-runtime-publish.test.tsdomains/meta-save-preconditions-parity.test.tsdomains/packages-export-base-unchanged-per-type.integration.test.tsdomains/packages-publish-drafts-change-note.integration.test.tspackage-list-commits-org-scope.integration.test.tspackage-promote-runtime-owner-refusal.integration.test.tspackage-revert-stored-members.integration.test.tspackages/metadata-protocol (39 files), first lint-loading case, ms p50 / max
protocol-publish-drafts-closure.test.tsprotocol-publish-drafts-endpoint-gate.test.tsprotocol-publish-drafts-object-field-refs.test.tsprotocol-publish-drafts-org-scope.test.tsprotocol-publish-drafts-package-scope.test.tsprotocol.app-package-id-key.test.tsprotocol.draft-publish-verdict.test.tsprotocol.extension-field-suffix.test.tsprotocol.flow-by-name-shipped-name.test.tsprotocol.list-slot-prefer-local.test.tsprotocol.lock-door-read-agree.test.tsprotocol.lock-one-resolution.shard-1.test.tsprotocol.lock-one-resolution.shard-2.test.tsprotocol.lock-one-resolution.shard-3.test.tsprotocol.lock-one-resolution.shard-4.test.tsprotocol.lock-one-resolution.test.tsprotocol.lock-org-axis-agree.test.tsprotocol.named-base-overlay-write.test.tsprotocol.object-cascade-delete-objects.test.tsprotocol.object-disabled-actions.test.tsprotocol.object-registry-write-through-spelling.test.tsprotocol.org-scoped-write-refused.test.tsprotocol.package-publish-audit-rows.test.tsprotocol.packageless-save-address.test.tsprotocol.publish-commit-capture-read-failure.test.tsprotocol.publish-side-effects-canonical-type.test.tsprotocol.publish-stored-type-canonical.test.tsprotocol.reads-environment-only.test.tsprotocol.record-package-commit-durability.test.tsprotocol.save-union-issues.test.tsprotocol.scoped-list-fallback.test.tsprotocol.seed-external-id-refusal.test.tsprotocol.served-content-hash.test.tsprotocol.stored-conversions.test.tsprotocol.summary-rollup-publish-gate.test.tsprotocol.unknown-datasource-refusal.test.tsprotocol.unknown-grant-object-refusal.test.tsprotocol.view-container-reset-expansions.test.tsview-container-runtime-expansion.test.tspackages/objectql (18 files), first lint-loading case, ms p50 / max
build-probes.test.tsenvironment-overlay-five-types.test.tspackage-disable-enforcement.test.tsprotocol-commit-history.test.tsprotocol-custom-override-bare-slot-envelope.test.tsprotocol-custom-override-effective-read.test.tsprotocol-duplicate-package-names-in-step.test.tsprotocol-lock-artifact-package-axis.test.tsprotocol-meta.test.tsprotocol-packaged-view-base.test.tsprotocol-packageless-overlay-migration.test.tsprotocol-packageless-read-dependent-package.test.tsprotocol-publish-package-drafts.test.tsprotocol-publish-rollback.test.tsprotocol-revert-org-scope.test.tspublish-package-drafts-backfill.test.tspublish-package-drafts-response-conformance.test.tspublish-package-drafts-seed-receipt-counts.test.tsThe 106 grep candidates the trace dropped (0 lint-root evaluations inside a case)
packages/runtime/src/audit-meta-item-org-scope.integration.test.ts,packages/runtime/src/domains/meta-item-query-decision.test.ts,packages/runtime/src/domains/meta-save-actor.test.ts,packages/runtime/src/domains/meta-save-destructive-remedy.test.ts,packages/runtime/src/domains/packages-adopt-orphans-readonly.test.ts,packages/runtime/src/domains/packages-customizations.test.ts,packages/runtime/src/domains/packages-publish-drafts-request-conformance.test.ts,packages/runtime/src/domains/packages-seed-apply-org-scope.test.ts,packages/runtime/src/http-dispatcher.test.ts,packages/runtime/src/meta-compound-arity-mint-door.test.ts,packages/runtime/src/meta-field-overlay-lock.test.ts,packages/runtime/src/meta-managed-content-seal.test.ts,packages/runtime/src/package-dependencies-patch.integration.test.ts,packages/runtime/src/package-duplicate-adopt-org-scope.integration.test.ts,packages/runtime/src/package-revert-code-shipped-members.integration.test.ts,packages/runtime/src/package-uninstall-org-scope.integration.test.ts,packages/runtime/src/package-uninstall-store-refusal.integration.test.ts,packages/runtime/src/stored-metadata-reader-seam.test.ts,packages/metadata-protocol/src/meta-overlay-cache.test.ts,packages/metadata-protocol/src/mutation-listeners.test.ts,packages/metadata-protocol/src/package-unmanaged-install.test.ts,packages/metadata-protocol/src/protocol-14078-audit-invalid-date-total-arm.test.ts,packages/metadata-protocol/src/protocol-publish-drafts-outcome.test.ts,packages/metadata-protocol/src/protocol.adr0005-org-override-rollback.test.ts,packages/metadata-protocol/src/protocol.audit-org-scope.test.ts,packages/metadata-protocol/src/protocol.audit-read-failure-propagation.test.ts,packages/metadata-protocol/src/protocol.batch-verb-driver-code.test.ts,packages/metadata-protocol/src/protocol.batch-verb-driver-text.test.ts,packages/metadata-protocol/src/protocol.bracketed-refusal-opener-absence.test.ts,packages/metadata-protocol/src/protocol.code-only-types.test.ts,packages/metadata-protocol/src/protocol.data-door-stored-content-hash.test.ts,packages/metadata-protocol/src/protocol.data-door-stored-metadata-filter-reads.test.ts,packages/metadata-protocol/src/protocol.data-door-stored-metadata-redaction.test.ts,packages/metadata-protocol/src/protocol.declines-stored-row-published.test.ts,packages/metadata-protocol/src/protocol.delete-object-registry-unregister.test.ts,packages/metadata-protocol/src/protocol.delete-receipt-wording.test.ts,packages/metadata-protocol/src/protocol.destructive-409-face-inventory.test.ts,packages/metadata-protocol/src/protocol.diff-canonical-type-and-history-outage.test.ts,packages/metadata-protocol/src/protocol.diff-credential-redaction.test.ts,packages/metadata-protocol/src/protocol.diff-dead-history-read.test.ts,packages/metadata-protocol/src/protocol.driver-text-disclosure.test.ts,packages/metadata-protocol/src/protocol.flow-layered-shipped-name.test.ts,packages/metadata-protocol/src/protocol.flow-org-override-closed.test.ts,packages/metadata-protocol/src/protocol.flow-stored-row-shipped-name.test.ts,packages/metadata-protocol/src/protocol.graft-folded-form-sections.test.ts,packages/metadata-protocol/src/protocol.hydrate-overlay-canonical-type.test.ts,packages/metadata-protocol/src/protocol.invalid-metadata-422-face-inventory.test.ts,packages/metadata-protocol/src/protocol.item-name-every-door.test.ts,packages/metadata-protocol/src/protocol.item-name-grammar.test.ts,packages/metadata-protocol/src/protocol.layered-code-unshipped-name.test.ts,packages/metadata-protocol/src/protocol.legacy-organization-read-seam.test.ts,packages/metadata-protocol/src/protocol.lifecycle-audit-rows.test.ts,packages/metadata-protocol/src/protocol.lock-gate-canonical-type.test.ts,packages/metadata-protocol/src/protocol.lock-gate-fail-closed.test.ts,packages/metadata-protocol/src/protocol.lock-one-resolution.kit.test.ts,packages/metadata-protocol/src/protocol.meta-types-degenerate-derivation.test.ts,packages/metadata-protocol/src/protocol.meta-types-mint-door-agreement.test.ts,packages/metadata-protocol/src/protocol.metadata-redaction.test.ts,packages/metadata-protocol/src/protocol.package-delete-refusal.test.ts,packages/metadata-protocol/src/protocol.package-door-before-gates.test.ts,packages/metadata-protocol/src/protocol.packaged-base-refusal.test.ts,packages/metadata-protocol/src/protocol.platform-schedule-org-gate.test.ts,packages/metadata-protocol/src/protocol.platform-store-system-opt-in.test.ts,packages/metadata-protocol/src/protocol.publish-item-draft-org-scope.test.ts,packages/metadata-protocol/src/protocol.read-lock-flags-write-door.test.ts,packages/metadata-protocol/src/protocol.read-seam-empty-accumulator.test.ts,packages/metadata-protocol/src/protocol.read-verb-canonical-fold.test.ts,packages/metadata-protocol/src/protocol.recovery-doors-mutation-projector.test.ts,packages/metadata-protocol/src/protocol.reference-derivation.test.ts,packages/metadata-protocol/src/protocol.revert-stored-type-canonical.test.ts,packages/metadata-protocol/src/protocol.rollback-carries-item-failures.test.ts,packages/metadata-protocol/src/protocol.row-binding-column.test.ts,packages/metadata-protocol/src/protocol.save-flow-canonicalization.test.ts,packages/metadata-protocol/src/protocol.save-meta-missing-item.test.ts,packages/metadata-protocol/src/protocol.save-receipt-wording.test.ts,packages/metadata-protocol/src/protocol.sealed-remedy-kind.test.ts,packages/metadata-protocol/src/protocol.stored-migration.test.ts,packages/metadata-protocol/src/protocol.stored-residue-resave.test.ts,packages/metadata-protocol/src/protocol.tenant-authored-write.test.ts,packages/metadata-protocol/src/protocol.ui-view-hidden-columns.test.ts,packages/metadata-protocol/src/protocol.ui-view-response-conformance.test.ts,packages/metadata-protocol/src/protocol.unrecognised-meta-type.test.ts,packages/metadata-protocol/src/sys-metadata-repository-14938-list-drafts-updated-at.test.ts,packages/metadata-protocol/src/sys-metadata-repository.package-writability.test.ts,packages/objectql/src/delete-meta-response-conformance.test.ts,packages/objectql/src/environment-overlay-email-locale-restart.test.ts,packages/objectql/src/overlay-precedence.test.ts,packages/objectql/src/protocol-bundle-member-base-address.test.ts,packages/objectql/src/protocol-destructive.test.ts,packages/objectql/src/protocol-environment-kernel-registry-write-through.test.ts,packages/objectql/src/protocol-flow-custom-override.test.ts,packages/objectql/src/protocol-layered-get.test.ts,packages/objectql/src/protocol-lock-enforcement.test.ts,packages/objectql/src/protocol-meta-types-rich.test.ts,packages/objectql/src/protocol-object-overlay-layer.test.ts,packages/objectql/src/protocol-org-overlay-registry-gate.test.ts,packages/objectql/src/protocol-package-lifecycle.test.ts,packages/objectql/src/protocol-per-package-layer-provenance.test.ts,packages/objectql/src/protocol-publish-canonical-fold.test.ts,packages/objectql/src/protocol-recorded-by-null.test.ts,packages/objectql/src/protocol-references.test.ts,packages/objectql/src/protocol-save-meta-repo-path-real-engine.test.ts,packages/objectql/src/protocol-save-meta-repo-path.test.ts,packages/objectql/src/protocol-view-identity-overlay.test.ts,packages/objectql/src/publish-meta-response-conformance.test.ts,packages/objectql/src/save-meta-response-conformance.test.tsWhy a testkit per file and not one setup file
Measured first: a
setupFilesentry in each package (commita89a1ccea2, superseded) did warm every case, but it made every file in the package pay the lint load, including the many that never reach it. Whole-suite runs, setup plus import worker time and wall, against the traced baseline on the same base:So the shared piece is the one module per package, and only the files that reach the load import it. Its header tells the author of a new file when to add the import.
Acceptance notes
package.jsonandpnpm-lock.yaml. objectql does not depend on@objectstack/lint; metadata-protocol does, and loads it from itsdist/. A static import of it from objectql code failstscwith TS2882 (measured:src/__fixtures__/lint-root-preload.ts(1,8): error TS2882: Cannot find module or type declarations for side-effect import of '@objectstack/lint'), so the import needs the package declared. A devDependency is enough because the file sits under__fixtures__, whichcheck:undeclared-dep-importsreads as test code (a.testkit.tsbeside product source was red there).check:test-source-aliaswith@objectstack/objectql: NEW unaliased artifact import(s) ... @objectstack/lint. With it, the preload and metadata-protocol's ownimport()resolve to one source module.dist/cost, but it still sits in 9 runtime cases. PM assumption 2, refuted: metadata-protocol resolves the root todist/(no alias; itscheck:test-source-aliasrow lists it).check:test-source-aliasis static, and this load is reached at run time through another package's code: whether a case's read serves aviewrecord, or a publish includes an object, is data. A dependency-shaped arm (any package whose tests import the protocol must preload) would name about 40 packages beyond these three, most of them never reaching the load, and red untouched packages. The tracer above is the instrument that answers the question; it is a scratch tool and is not landed.Verification
Head
c1fdec3921(branch merged withorigin/maincba12d2218; delta vsmain: 73 files, +240 / -1).pnpm --filter @objectstack/metadata-protocol test:Test Files 310 passed | 4 skipped (314),Tests 22292 passed | 26 skipped, VERDICT command-exit 0.pnpm --filter @objectstack/objectql test:Test Files 544 passed (544),Tests 10760 passed;test:repo:1 passed,5 passed. Both exit 0.pnpm --filter @objectstack/runtime test:Test Files 533 passed (533),Tests 8540 passed | 19 skipped;test:repo:3 passed,1211 passed. Both exit 0.pnpm --filter PKG typecheckfor each of the three packages after a closure build with declarations: exit 0 each;tsc --listFilesincludes each testkit.--no-inline-configover the 71 changed.tsfiles: exit 0.node scripts/pm/dispatch-gates.mjs --commandson this head: 67 commands (73 paths, 241 changed lines). All 67 ran on this head. 66 exited 0 on the first pass;check:dual-build-cjs-loadsexited 3 (PREREQUISITE NOT MET, nodist/for 37 packages), then 0 after a fullpnpm build.--ranreconciliation:67 derived, 67 run, 0 NOT-MEASURED, 0 UNRUN. Also run, outside the derived set:check:authz-resolver,check:filter-alias-parity,check:route-ledger-census(rosters under touched directories) andcheck-nul-bytes, each exit 0.!, no(narrowing)arm).🤖 Generated with Claude Code