From 44b030592347688bbc04921838f597cf409a037a Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 19 Aug 2026 00:06:53 +0000 Subject: [PATCH] docs(lint): correct the ./runtime subpath's "light entry" claim with measurements MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `@objectstack/lint`'s `./runtime` subpath was documented as the kernel-safe entry that "never names the module graph that reaches the react/jsx source parsers". Measured on this tree, that is false in the direction that costs a reader a console build to discover: . 72 modules dist/index.js 552,936 B 263 exported names ./runtime 71 modules dist/runtime.js 518,583 B 5 exported names (70 shared) (93.8%) 93.8% of the bytes, 1.9% of the surface. The single non-barrel module the root reaches and `./runtime` does not is `lint-startup-registry-verdict.ts`; both entries reach `validate-react-pages.ts` and `validate-react-page-props.ts`. `validateCapabilityReferences` is compiled INTO `dist/runtime.js` and merely not exported from it, so a consumer pays its bytes and cannot call it. What the entry actually buys is an export-surface pin, not weight: the kernel gate must import it rather than the root barrel, and `authoring-rule-wiring.test.ts` fails if it ever does otherwise. The lazy loading of typescript/sucrase/ajv is the PACKAGE's property, pinned by `lazy-deps.test.ts` over all of `src/` — the root entry has it too. Prose only; nothing that executes changes. The corrected copies are `src/runtime.ts`, `src/index.ts`, `tsup.config.ts` and the stale assertion message in `authoring-rule-wiring.test.ts` (message text only, the assertion is untouched). `runtime-lazy-deps.test.ts` already stated the truth and is the witness the other four contradicted. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01XqDQYVU5smx29ts9pAErja --- .../lint/src/authoring-rule-wiring.test.ts | 12 ++-- packages/lint/src/index.ts | 7 +- packages/lint/src/runtime.ts | 66 +++++++++++++++---- packages/lint/tsup.config.ts | 9 ++- 4 files changed, 72 insertions(+), 22 deletions(-) diff --git a/packages/lint/src/authoring-rule-wiring.test.ts b/packages/lint/src/authoring-rule-wiring.test.ts index bb267ac439..d8ff2ec2bb 100644 --- a/packages/lint/src/authoring-rule-wiring.test.ts +++ b/packages/lint/src/authoring-rule-wiring.test.ts @@ -411,13 +411,17 @@ describe('authoring-rule registry wiring (#4409)', () => { `here rebuilds, on a fourth surface, the exact drift #3583 → #4409 took five repairs to end.`, ).toEqual([]); - // And it must not reach past the kernel-safe entry: `@objectstack/lint`'s - // root barrel pulls the react/jsx rules' module graph, which is the one - // thing the boot path may not name (`lazy-deps.test.ts`). + // And it must not reach past the narrow entry. What that pins is the + // EXPORT surface, not the module graph: measured, `./runtime` reaches 70 + // of the root's 72 modules, so BOTH entries name the react/jsx rules' + // modules (`runtime-lazy-deps.test.ts` says so from the other side). What + // the root barrel adds is the 258 further NAMES, every CLI-only rule + // among them — and a kernel-path consumer that can name one can + // hand-call one, which is the drift #4463 closed. expect( source, `${RUNTIME_GATE_FILE} must import from '@objectstack/lint/runtime', not the root barrel — ` + - `the root entry reaches the typescript/sucrase rules the kernel boot path must not name.`, + `the root entry exports the CLI-only rules the kernel gate must never call directly.`, ).not.toMatch(/from\s*['"]@objectstack\/lint['"]/); }); diff --git a/packages/lint/src/index.ts b/packages/lint/src/index.ts index 0b40167a2b..e79e0c6c13 100644 --- a/packages/lint/src/index.ts +++ b/packages/lint/src/index.ts @@ -658,8 +658,11 @@ export type { } from './authoring-rules.js'; // The runtime publish gate over that registry. Also published as the -// `@objectstack/lint/runtime` subpath — the entry the kernel boot path imports, -// so a consumer there never names the graph that reaches the source parsers. +// `@objectstack/lint/runtime` subpath — the entry the kernel boot path imports. +// That subpath narrows the EXPORT surface to these five names, not the module +// graph: measured, it reaches 70 of this entry's 72 modules and 93.8% of its +// bundled bytes, and it does name the modules that reach the source parsers. +// `runtime.ts`'s header carries the measurement and what the narrowing buys. export { buildRuntimeWriteSnapshots, runRuntimeAuthoringRules, diff --git a/packages/lint/src/runtime.ts b/packages/lint/src/runtime.ts index 42c47a8a03..c5af8b3ec0 100644 --- a/packages/lint/src/runtime.ts +++ b/packages/lint/src/runtime.ts @@ -1,19 +1,59 @@ // Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. /** - * `@objectstack/lint/runtime` — the KERNEL-SAFE entry (#4463). - * - * The metadata write path (`@objectstack/metadata-protocol`) sits on the kernel - * boot path and must reach the shared rule core without dragging the lint - * package's gate-only dependencies (`typescript` ~9 MB, `sucrase`) into it. - * That constraint is real and it is guarded from both ends: - * - * - `lazy-deps.test.ts` pins that no `src/` file eagerly imports either dep, so - * importing this entry loads neither; - * - `runtime-lazy-deps.test.ts` pins the stronger claim this entry needs — that - * RUNNING the gate on a real, gated body loads neither either, because the - * rules #4463 wired to `runtime-publish` (flow / approval / expression / - * reference) never parse authored source. + * `@objectstack/lint/runtime` — the NARROW entry. Not the light one (#4463). + * + * ## What it is, measured + * + * This entry narrows the EXPORT SURFACE. It does not narrow the module graph, + * and reading it as a weight boundary is a trap that costs a console build to + * disprove. Measured by walking the static import graph of `src/` from each + * entry, and by `stat` on this package's own `tsup` output: + * + * | | modules reached | ESM bundle | exported names | + * |------------|------------------------|------------------|----------------| + * | `.` | 72 | 552,936 B | 263 | + * | `./runtime`| 71, 70 shared with `.` | 518,583 B (93.8%)| 5 | + * + * So: **93.8% of the bytes, 1.9% of the surface.** The one non-barrel module + * `.` reaches and this entry does not is `lint-startup-registry-verdict.ts`; + * that single module IS the whole graph delta, and it is not the reason the + * entry exists. (Re-derive rather than trust these: the numbers move with the + * rule set, the ratio has not.) + * + * The published `.d.ts` is the one place the narrowing shows as bytes — + * `dist/runtime.d.ts` is a 278 B re-export line against `dist/index.d.ts`'s + * ~158 KB — because types are erased, and code is not. + * + * ## What it does NOT carry + * + * `validateCapabilityReferences` is not exported here, and switching to this + * entry to get a cheaper capability check gets neither half: the rule's code is + * compiled INTO `dist/runtime.js` (it is a member of the one shared registry), + * it is only not named on the way out. You pay its bytes and cannot call it. + * The root entry is where that rule, and the other 257 names, are reachable. + * + * ## Why it exists anyway + * + * So the kernel boot path can name only the five gate functions below, and so a + * test can prove it named nothing else. `@objectstack/metadata-protocol`'s + * runtime gate must reach this package through this entry rather than the root + * barrel, and `authoring-rule-wiring.test.ts` fails if it ever does otherwise. + * That is an import-discipline boundary, machine-checked: it stops a + * kernel-path consumer from hand-calling a CLI-only rule, which is the drift + * #4463 closed. The value is the pin, not the payload. + * + * ## What is the PACKAGE's doing, not this entry's + * + * Lazy dependency loading. `lazy-deps.test.ts` pins that no `src/` file eagerly + * imports `typescript` (~9 MB), `sucrase` or `ajv` — so importing `.` loads + * none of them either, and this entry is not what buys that. + * `runtime-lazy-deps.test.ts` adds the claim this consumer actually needs: that + * RUNNING the gate on a real, gated body loads none of them, because the rules + * #4463 wired to `runtime-publish` (flow / approval / expression / reference) + * never parse authored source. Both are properties of which rules RUN. Neither + * is a property of which entry you import — the react/jsx rules' modules are + * present in this entry's graph, exactly as `runtime-lazy-deps.test.ts` states. * * The deliberate NON-goal: this is not a second, lighter rule set. It re-exports * a filtered view of the ONE registry in `authoring-rules.ts`. If the two ever diff --git a/packages/lint/tsup.config.ts b/packages/lint/tsup.config.ts index cfbcbdb9eb..54940a82a5 100644 --- a/packages/lint/tsup.config.ts +++ b/packages/lint/tsup.config.ts @@ -5,9 +5,12 @@ import { defineConfig } from 'tsup'; * cannot use the repo-root `tsup.config.ts` (single `src/index.ts`). * * - `index` — the full authoring surface, used by the CLI. - * - `runtime` — the kernel-safe subset the metadata write path imports. Kept a - * separate entry so a consumer on the boot path never even names the module - * graph that reaches the react/jsx source parsers. + * - `runtime` — the narrowed subset the metadata write path imports. It is a + * separate entry so that surface can be PINNED (`authoring-rule-wiring.test.ts` + * fails if the kernel gate imports the root barrel instead), not because it is + * lighter. `splitting: false` emits each entry self-contained, and measured + * `dist/runtime.js` is 93.8% of `dist/index.js` and does name the react/jsx + * rules' modules. `src/runtime.ts`'s header carries the measurement. */ export default defineConfig({ entry: ['src/index.ts', 'src/runtime.ts'],