feat(metadata,objectql): a leaf /view-container subpath keeps objectql lean under ADR-0076 - no manager, chokidar, glob or js-yaml in the core closure - #15343
Conversation
…tql's lean ADR-0076 closure free of the manager, chokidar, glob and js-yaml (#14680) `engine.ts` reached `deriveViewContainerObject` through `@objectstack/metadata`'s root entry, so `@objectstack/objectql/core`'s module-init closure inherited `MetadataPlugin` -> `NodeMetadataManager` -> `chokidar`, plus `glob`, `js-yaml` and `readdirp` — for a six-line pure function. The same file already imports `isMissingTableError` from the leaf `/errors` subpath, whose comment states that the subpath exists precisely to avoid that; this is that pattern, taken again. Measured on the built artifacts: 190 -> 185 modules and -498,616 B for the ESM lean entry, 188 -> 183 and -513,204 B for CJS. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ARYe3yQTQCUFm5qPYNgKaJ
📓 Docs Drift Check1 anchor(s) derived from 2 changed package(s); no hand-written page names any of them. What this run could not see
Coarse fallback — 24 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 43b6b8dd91432da17c50c11e6752c75843cf3ff7 && git checkout 43b6b8dd91432da17c50c11e6752c75843cf3ff7
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin c4d1354e32539eb418e35757ccbafb3dea57ba93 9b06e078b2b3ac82a91a7a9c7df1bb0e74bfe0bb && git checkout -B drift-repro c4d1354e32539eb418e35757ccbafb3dea57ba93 && git merge --no-ff 9b06e078b2b3ac82a91a7a9c7df1bb0e74bfe0bb
node scripts/docs-audit/affected-docs.mjs --json c4d1354e32539eb418e35757ccbafb3dea57ba93 |
/view-container subpath keeps objectql lean under ADR-0076 - no manager, chokidar, glob or js-yaml in the core closure
Seat review — ⭐ the card's headline figure is HALF FALSIFIED, and you traced where the wrong half came from. ⛔ Not the contract review.
1. Your open question — A, and the ambiguity is MINEYou read A2.5's 「⛔ Merge
Your stated reasoning for A is partly stale in a way that does not change the answer: #15319 has since merged, so it is no longer in the queue ahead of you. A still holds — the diff is one import line, the queue rebuilds onto current 2. The falsification, which is the card's real productThe card's whole
⭐ And you did not stop at "the number is wrong" — you found where it came from: ⇒ The card's conclusion survives on the delta; its baseline does not. ⛔ A reviewer should not carry the 3.6 KB figure forward. 3. Three methodological things worth naming
4.
|
Landing — contract review PASS at
|
Fixes #14680
packages/objectql/src/engine.tsreachedderiveViewContainerObjectthrough@objectstack/metadata's ROOT entry.core.ts— the ADR-0076 lean entry — re-exportsengine.ts, so@objectstack/objectql/core's module-init closure inheritedMetadataPlugin->NodeMetadataManager->chokidar, plusglob,js-yamlandreaddirp, for a six-line pure function. This adds the leaf subpath@objectstack/metadata/view-containerand re-points that one import onto it.1. The measurement — A2.1, the card's actual deliverable
Method
The card's number had never been measured against a built artifact. This one is: for each state, build the workspace, then load
@objectstack/objectql/corein a fresh Node process and record every module file Node actually evaluates, via amodule.registerHooksloadhook (which sees ESM and CJS) unioned withrequire.cache, with byte sizes fromstatSync. Both published flavours of the entry are measured separately, because the two resolve different files:dist/core.mjsunder theimportcondition,dist/core.jsunderrequire. Harness:measure-closure.mjs/.cjs(~40 lines each, quoted in the report on the card).This measures loading, which is what the card claims about ("loads
dist/index.jsat module-init"), rather than bundle size — no tree-shaking is applied, because Node applies none.Result
@objectstack/objectql/coredist/core.mjs)dist/core.mjs)dist/core.js)dist/core.js)Exactly six modules stop being loaded, and one takes their place (ESM flavour):
Wall-clock: the marginal module-init cost of the metadata root entry, measured on an already-warm lean closure, is ~22 ms (median of 7 runs; 20.4–27.5 ms) against a lean-entry init of ~630 ms.
The card's figure: the delta survives, the baseline does not
The finding argues on "roughly 3.6 KB to roughly 450 KB at module-init". Measured:
@objectstack/objectql/core's closure was already ~11.5 MiB before this import existed, dominated by@objectstack/spec(9,587,914 B) andzod(567,918 B) — neither of which the metadata root entry contributes. The lean entry has never been a 3.6 KB closure.wc -c packages/metadata/src/errors.tsis 3,632 bytes. That is the raw SOURCE size of the/errorsentry file, which is roughly 95% comment. The same file contributes 143 bytes to this closure when built, and loaded standalone pulls 2,487,842 bytes / 83 modules (it re-exports from@objectstack/types, which reaches@objectstack/spec). So the figure is raw source bytes of one file, compared against a runtime closure of a different thing — the identical defectpackages/objectql/src/core-boundary.ratchet.test.tsdocuments at length for the old "268KB" claim: "the figure never had a stated unit".Plainly: the card's conclusion stands and its baseline number does not. The import was worth removing — 6 modules and ~487 KiB is a real, measurable cost that nothing in CI reports — but it was never a 3.6 KB closure, and a reader who took "3.6 KB to 450 KB" literally would think this entry got 125x heavier. It got ~4.2% heavier.
2. Zone 2, item by item
A2.2 — anchors, located by symbol. CONFIRMED.
The
/errorsprecedent at:113with its[#5979]rationale block at:108, and the root import at:192— both exactly where the card and the claim put them.A2.3 — what belongs in the leaf. Decided on measurement, not on the file it lives in.
One symbol,
deriveViewContainerObject. Its module siblingexpandRuntimeViewContaineris left off:packages/metadata/src/metadata-manager.tsis its only caller, the ROOT entry does not export it either, and an exported symbol nobody imports is a promise made for nothing — the same callsrc/errors.tsmade aboutisSchemaAlreadyExistsError.expandViewContainer/isAggregatedViewContainer, the pair the card names as the family, are@objectstack/specexports and were never candidates here.A re-export shim was written first, measured, and rejected.
/errors' header states the requirement — "this entry re-exports one leaf module and nothing else, so the cross-package edge stays a leaf edge" — andview-container-expansion.tsis not a leaf: its other export needs@objectstack/specand@objectstack/spec/shared. esbuild tree-shakes the unused FUNCTION but keeps bothimportstatements, because it cannot prove an external package is side-effect-free. Measured on the built shim:view-container-expansion.ts(rejected)So the derivation moves into
packages/metadata/src/view-container.ts, which imports nothing at all, andview-container-expansion.tsimports it back and re-exports it —index.ts's root export andplugin.tskeep their spelling, and the symbol stays on the root entry (an additional door, not a relocation).Resolution controls, run from
packages/objectql:The negative control matters:
ERR_PACKAGE_PATH_NOT_EXPORTEDproves theexportsmap is being enforced, so the working/view-containerimport is a real map entry rather than a directory-fallback resolution.A2.4 — gate families, per family, with exit codes. See section 3.
A2.5 —
engine.tsco-tenancy. CONFIRMED: no line shift, census untouched.Four lines changed, four lines replaced, zero net — the import line plus three lines of the comment block already above it, rewritten in place to name the leaf subpath without adding a line.
content/docs/permissions/system-context.mdxis untouched andcheck:system-context-censusis green on its own verdict line, not on a bare exit code:--fixwas therefore never run, and the table is byte-identical toorigin/main.Two notes for the reviewer, both deliberate:
origin/main. Reading the dispatch's "Mergeorigin/mainas you go" as a prohibition (it is written with the same prohibition marker as thecontent/docs/releases/**line beside it), and given that fix(objectql): the boot loop refuses a view container whosenamedisagrees with its derived object key (#14666) #15319 is in the merge queue for this same file, the branch stays on its base6e67b86c0so the diff remains one import line and the queue does the integration on its rebuilt generation. If that reading is backwards, say so and it is a one-command fix.content/docs/releases/**untouched.A2.6 — the changeset bump, derived.
@objectstack/metadata: minor. A new key in a publishedexportsmap is a new public entry point — surface added, nothing removed or renamed,deriveViewContainerObjectstill exported from the root. Additive public surface isminorand cannot bepatch; a consumer pinned to the previous patch range cannot resolve@objectstack/metadata/view-container, so this is exactly the case the minor bump exists to carry.@objectstack/objectql: patch. Its own exported surface is unchanged — the only change is which door it uses to reach a function it already imported. The dependency-shape change is real but invisible to its API, so it is a patch, and changesets updates its internal dependency range in the same release.Not
majoron either: nothing is removed, renamed or narrowed. No bump was copied from a sibling card.3. Gate families — derived on the actual surface, per family, with exit codes
Derived with
node scripts/pm/dispatch-gates.mjsat commit9b06e078b(the script reads the change set from the merge base itself; no hand-built path list was passed). It reports 49 runnable families — 42 by path, 1 by change KIND, 6 declared whole-tree. All 49 were run. Exit codes captured with the redirect-then-capture form (cmd > log 2>&1; EXIT=$?), never through a pipe.49 of 49 exit 0. One needed its prerequisite satisfied first and is called out below.
check-adr-0087-registration --self-test,check-changeset-fixed,check-changeset-no-major --self-test,check-ci-filter-parity,check-closing-keyword-parity(+--self-test),check-comment-mask-adoption(+--self-test),check-comment-mask-corpus,check-empty-changeset --self-test,check-engine-split-ratio --days 90(+--self-test),check-keyed-text-bounds(+--self-test),check-osv-exemptions(+--self-test),check-plugin-teardown-shape(+--self-test),check-system-context-census(+--self-test),check-undeclared-dep-imports(+--self-test),docs-audit/check-affected-docs,docs-audit/check-drift-comment,pm/release-rehearsal-clone --self-test,spec run check:llms-txt,check:changeset-gate-self-tests,check:cross-package-test-inputs,check:dispatcher-error-vocabulary,check:doc-authoring,check:durability-log-level,check:logger-receiver-detach,check:nul-bytes,check:objectui-changeset,check:override-consistency,check:page-declaration-shape,check:pm-half-states,check:published-files,check:refd-timer-probe,check:service-providers,check:slot-lookup,check:stack-collection-maps,check:swallow-census-controls,check:test-source-alias,check:turbo-task-graph,check:type-source-resolution,check:watch-hint-literal,check:workspace-manifest-cyclescheck:dual-build-cjs-loads— see belowcheck:dual-build-cjs-loadsfirst returned exit 3, which is NOT MEASURED and is not a pass: "PREREQUISITE NOT MET — this gate reads built output, and some package has no dist/ ... Runpnpm buildfirst. This is NOT a pass: nothing was measured." A fullpnpm buildwas run and the gate re-run to exit 0, which is the reading that counts here, since this is the gate that actually exercises the newrequireentry point:check:published-filesis the other one that reads theexportsmap, and states its verdict about it explicitly: "69 declare anexportsmap gating what of that is resolvable (census control: floor 50)".Convention-scoped, outside any path derivation. The tool's own convention block named
check:dispatcher-error-vocabulary(exit 0). Because this change's KIND is "a new published package entry point", three further entry-point gates that the derivation scores as silent artifact rosters were run by hand —check:published-readme-exports(0),check:publish-smoke-pin(0),check:published-readme-links(0). Two of the gates the dispatch guessed at,check:browser-reachable-entriesandcheck:entry-nameability, do not exist in this repo — the fullcheck:*script list was read rather than recalled.Build, typecheck, tests
Heavy runs went through
scripts/pm/os-verify-lock.sh; verdicts read from its VERDICT line, never a bare$?.turbo buildturbo typecheck --affectedturbo ls --affectednames against the merge baseerror TSlinespnpm --filter @objectstack/metadata testpnpm --filter @objectstack/objectql testpnpm --filter @objectstack/metadata-protocol testpnpm linteslint . --no-inline-configThe test run is narrower than the 45 affected packages, and that is declared rather than implied: the cross-package observable is
@objectstack/metadata's ROOT entry,packages/metadata/src/index.tsis untouched by this diff (git diff BASE -- packages/metadata/src/index.tsis empty), and the function reached through the root is byte-identical to the one reached through the leaf (toString()equality, above). The three suites run are the two changed packages plus the nearest downstream consumer; CI's Test Core covers the rest of the affected set on the merge queue's rebuilt generation.Control-byte scan of every changed file: zero hits, with a firing positive control — the same
grep -naP '[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]'pattern matches a deliberately planted\x01in a probe file, so the zero is a measurement and not a broken pattern.4. Scope
packages/specwas not touched. Shape 2 of the card — moving the helper down besideexpandViewContainer/isAggregatedViewContainerin@objectstack/spec— is thedomain:specseat's single-owner surface and was not taken from this lane. Nothing in this measurement argues for or against it; the leaf subpath is whatengine.tsitself had already decided, 79 lines above the import it replaces.needs:contract-reviewis on this PR and on #14680: a new key in a publishedexportsmap is public-surface widening. Draft, not enqueued, no auto-merge.Generated by Claude Code