Skip to content

Commit 5cc8a3b

Browse files
os-steveclaude
andauthored
docs(lint): correct the ./runtime subpath's "light entry" claim with measurements (#9802)
`@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. Claude-Session: https://claude.ai/code/session_01XqDQYVU5smx29ts9pAErja Co-authored-by: Claude <noreply@anthropic.com>
1 parent 2a29caa commit 5cc8a3b

4 files changed

Lines changed: 72 additions & 22 deletions

File tree

packages/lint/src/authoring-rule-wiring.test.ts

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -411,13 +411,17 @@ describe('authoring-rule registry wiring (#4409)', () => {
411411
`here rebuilds, on a fourth surface, the exact drift #3583 → #4409 took five repairs to end.`,
412412
).toEqual([]);
413413

414-
// And it must not reach past the kernel-safe entry: `@objectstack/lint`'s
415-
// root barrel pulls the react/jsx rules' module graph, which is the one
416-
// thing the boot path may not name (`lazy-deps.test.ts`).
414+
// And it must not reach past the narrow entry. What that pins is the
415+
// EXPORT surface, not the module graph: measured, `./runtime` reaches 70
416+
// of the root's 72 modules, so BOTH entries name the react/jsx rules'
417+
// modules (`runtime-lazy-deps.test.ts` says so from the other side). What
418+
// the root barrel adds is the 258 further NAMES, every CLI-only rule
419+
// among them — and a kernel-path consumer that can name one can
420+
// hand-call one, which is the drift #4463 closed.
417421
expect(
418422
source,
419423
`${RUNTIME_GATE_FILE} must import from '@objectstack/lint/runtime', not the root barrel — ` +
420-
`the root entry reaches the typescript/sucrase rules the kernel boot path must not name.`,
424+
`the root entry exports the CLI-only rules the kernel gate must never call directly.`,
421425
).not.toMatch(/from\s*['"]@objectstack\/lint['"]/);
422426
});
423427

packages/lint/src/index.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -658,8 +658,11 @@ export type {
658658
} from './authoring-rules.js';
659659

660660
// The runtime publish gate over that registry. Also published as the
661-
// `@objectstack/lint/runtime` subpath — the entry the kernel boot path imports,
662-
// so a consumer there never names the graph that reaches the source parsers.
661+
// `@objectstack/lint/runtime` subpath — the entry the kernel boot path imports.
662+
// That subpath narrows the EXPORT surface to these five names, not the module
663+
// graph: measured, it reaches 70 of this entry's 72 modules and 93.8% of its
664+
// bundled bytes, and it does name the modules that reach the source parsers.
665+
// `runtime.ts`'s header carries the measurement and what the narrowing buys.
663666
export {
664667
buildRuntimeWriteSnapshots,
665668
runRuntimeAuthoringRules,

packages/lint/src/runtime.ts

Lines changed: 53 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,59 @@
11
// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license.
22

33
/**
4-
* `@objectstack/lint/runtime` — the KERNEL-SAFE entry (#4463).
5-
*
6-
* The metadata write path (`@objectstack/metadata-protocol`) sits on the kernel
7-
* boot path and must reach the shared rule core without dragging the lint
8-
* package's gate-only dependencies (`typescript` ~9 MB, `sucrase`) into it.
9-
* That constraint is real and it is guarded from both ends:
10-
*
11-
* - `lazy-deps.test.ts` pins that no `src/` file eagerly imports either dep, so
12-
* importing this entry loads neither;
13-
* - `runtime-lazy-deps.test.ts` pins the stronger claim this entry needs — that
14-
* RUNNING the gate on a real, gated body loads neither either, because the
15-
* rules #4463 wired to `runtime-publish` (flow / approval / expression /
16-
* reference) never parse authored source.
4+
* `@objectstack/lint/runtime` — the NARROW entry. Not the light one (#4463).
5+
*
6+
* ## What it is, measured
7+
*
8+
* This entry narrows the EXPORT SURFACE. It does not narrow the module graph,
9+
* and reading it as a weight boundary is a trap that costs a console build to
10+
* disprove. Measured by walking the static import graph of `src/` from each
11+
* entry, and by `stat` on this package's own `tsup` output:
12+
*
13+
* | | modules reached | ESM bundle | exported names |
14+
* |------------|------------------------|------------------|----------------|
15+
* | `.` | 72 | 552,936 B | 263 |
16+
* | `./runtime`| 71, 70 shared with `.` | 518,583 B (93.8%)| 5 |
17+
*
18+
* So: **93.8% of the bytes, 1.9% of the surface.** The one non-barrel module
19+
* `.` reaches and this entry does not is `lint-startup-registry-verdict.ts`;
20+
* that single module IS the whole graph delta, and it is not the reason the
21+
* entry exists. (Re-derive rather than trust these: the numbers move with the
22+
* rule set, the ratio has not.)
23+
*
24+
* The published `.d.ts` is the one place the narrowing shows as bytes —
25+
* `dist/runtime.d.ts` is a 278 B re-export line against `dist/index.d.ts`'s
26+
* ~158 KB — because types are erased, and code is not.
27+
*
28+
* ## What it does NOT carry
29+
*
30+
* `validateCapabilityReferences` is not exported here, and switching to this
31+
* entry to get a cheaper capability check gets neither half: the rule's code is
32+
* compiled INTO `dist/runtime.js` (it is a member of the one shared registry),
33+
* it is only not named on the way out. You pay its bytes and cannot call it.
34+
* The root entry is where that rule, and the other 257 names, are reachable.
35+
*
36+
* ## Why it exists anyway
37+
*
38+
* So the kernel boot path can name only the five gate functions below, and so a
39+
* test can prove it named nothing else. `@objectstack/metadata-protocol`'s
40+
* runtime gate must reach this package through this entry rather than the root
41+
* barrel, and `authoring-rule-wiring.test.ts` fails if it ever does otherwise.
42+
* That is an import-discipline boundary, machine-checked: it stops a
43+
* kernel-path consumer from hand-calling a CLI-only rule, which is the drift
44+
* #4463 closed. The value is the pin, not the payload.
45+
*
46+
* ## What is the PACKAGE's doing, not this entry's
47+
*
48+
* Lazy dependency loading. `lazy-deps.test.ts` pins that no `src/` file eagerly
49+
* imports `typescript` (~9 MB), `sucrase` or `ajv` — so importing `.` loads
50+
* none of them either, and this entry is not what buys that.
51+
* `runtime-lazy-deps.test.ts` adds the claim this consumer actually needs: that
52+
* RUNNING the gate on a real, gated body loads none of them, because the rules
53+
* #4463 wired to `runtime-publish` (flow / approval / expression / reference)
54+
* never parse authored source. Both are properties of which rules RUN. Neither
55+
* is a property of which entry you import — the react/jsx rules' modules are
56+
* present in this entry's graph, exactly as `runtime-lazy-deps.test.ts` states.
1757
*
1858
* The deliberate NON-goal: this is not a second, lighter rule set. It re-exports
1959
* a filtered view of the ONE registry in `authoring-rules.ts`. If the two ever

packages/lint/tsup.config.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,12 @@ import { defineConfig } from 'tsup';
55
* cannot use the repo-root `tsup.config.ts` (single `src/index.ts`).
66
*
77
* - `index` — the full authoring surface, used by the CLI.
8-
* - `runtime` — the kernel-safe subset the metadata write path imports. Kept a
9-
* separate entry so a consumer on the boot path never even names the module
10-
* graph that reaches the react/jsx source parsers.
8+
* - `runtime` — the narrowed subset the metadata write path imports. It is a
9+
* separate entry so that surface can be PINNED (`authoring-rule-wiring.test.ts`
10+
* fails if the kernel gate imports the root barrel instead), not because it is
11+
* lighter. `splitting: false` emits each entry self-contained, and measured
12+
* `dist/runtime.js` is 93.8% of `dist/index.js` and does name the react/jsx
13+
* rules' modules. `src/runtime.ts`'s header carries the measurement.
1114
*/
1215
export default defineConfig({
1316
entry: ['src/index.ts', 'src/runtime.ts'],

0 commit comments

Comments
 (0)