Measured while implementing #11350 (whose ruling scoped the repair to the three names measured there: FormFieldInput, NavigationItemInput, StateNodeConfig — this delta is deliberately NOT fixed in that PR, per the dispatch's instruction to report rather than silently widen a ruled surface change).
Measurement (worktree at 4ceae8ab0 + the #11350 re-exports, freshly built spec dist)
A minimal one-file program — the smallest consumer shape there is:
import{defineStack}from'@objectstack/spec';exportdefaultdefineStack({objects: []});compiled with declaration: true + noEmit, module/moduleResolution NodeNext, resolving @objectstack/spec through a real node_modules symlink (the same physical resolution a pnpm workspace consumer performs), produces — through the ordinary CHUNKED build — two TS2883 errors beyond the three #11350 repaired:
consumer.ts(3,1): error TS2883: The inferred type of 'default' cannot be named without a reference to 'BaseValidationRuleShape' from './node_modules/@objectstack/spec/dist/object.zod-OA2khMnR'. This is likely not portable. A type annotation is necessary.
consumer.ts(3,1): error TS2883: The inferred type of 'default' cannot be named without a reference to 'FilterCondition' from './node_modules/@objectstack/spec/dist/filter.zod-DVwOdic1'. This is likely not portable. A type annotation is necessary.
Both names are already public on @objectstack/spec/data (api-surface/data.json records BaseValidationRuleShape (interface), FilterCondition (type), FilterConditionSchema (const)).
Why #11350's measurements did not surface these two
So the class the #11350 ruling's invariant names — a type that appears structurally in an entry's public declarations must be nameable from that same entry — is still open at the root entry: any consumer whose program does not otherwise import @objectstack/spec/data and lets tsc infer through a root-entry function hits it. The trigger population is real (an objectstack.config.ts that defines only, say, apps/flows and no objects imports nothing from /data).
Repro harness
packages/spec/scripts/root-entry-type-nameability.pin.test.ts (landed with #11350's PR) is the exact harness: drop its context.ts fixture from the consumer program and the pin goes red with precisely the two errors above. Its docblock records this delta and points here.
Options
Recommendation: A′ now (the surface promise is already made one subpath away, and the pull is the same class the maintainer already accepted), C as the follow-up that ends the per-name whack-a-mole.
Generated by Claude Code
Measured while implementing #11350 (whose ruling scoped the repair to the three names measured there:
FormFieldInput,NavigationItemInput,StateNodeConfig— this delta is deliberately NOT fixed in that PR, per the dispatch's instruction to report rather than silently widen a ruled surface change).Measurement (worktree at
4ceae8ab0+ the #11350 re-exports, freshly built spec dist)A minimal one-file program — the smallest consumer shape there is:
compiled with
declaration: true+noEmit,module/moduleResolutionNodeNext, resolving@objectstack/specthrough a realnode_modulessymlink (the same physical resolution a pnpm workspace consumer performs), produces — through the ordinary CHUNKED build — two TS2883 errors beyond the three #11350 repaired:Both names are already public on
@objectstack/spec/data(api-surface/data.jsonrecordsBaseValidationRuleShape (interface),FilterCondition (type),FilterConditionSchema (const)).Why #11350's measurements did not surface these two
@objectstack/spec's root entry does not re-export three types its own public API's inferred types mention — every consumer inferring throughdefineStackhits TS2883 #11350's finding 2 sawBaseValidationRuleShapeleak only under an experimental single-entry dts build and recorded it as chunked-build-invisible. That reading was an artifact of the repro program, not of the build: the nine i18n-extract configs' programs all contain their object modules, whichimport { ObjectSchema, Field } from '@objectstack/spec/data'— and@objectstack/spec's root entry does not re-export three types its own public API's inferred types mention — every consumer inferring throughdefineStackhits TS2883 #11350's own control shows a program file importing a subpath entry makes that entry's names nameable program-wide. Strip the program to the single config file and both/datanames leak through the chunked build too.FilterConditionis new — it was masked the same way and never seen at all.So the class the #11350 ruling's invariant names — a type that appears structurally in an entry's public declarations must be nameable from that same entry — is still open at the root entry: any consumer whose program does not otherwise import
@objectstack/spec/dataand lets tsc infer through a root-entry function hits it. The trigger population is real (anobjectstack.config.tsthat defines only, say, apps/flows and no objects imports nothing from/data).Repro harness
packages/spec/scripts/root-entry-type-nameability.pin.test.ts(landed with #11350's PR) is the exact harness: drop itscontext.tsfixture from the consumer program and the pin goes red with precisely the two errors above. Its docblock records this delta and points here.Options
src/index.ts— same shape as the@objectstack/spec's root entry does not re-export three types its own public API's inferred types mention — every consumer inferring throughdefineStackhits TS2883 #11350 repair (api-surface/root.json+2 rows viagen:api-surface, export-origins regen, extend the pin's program-scoping note). Small, measured, but it is again an addition to the most-imported entry's public face, so it wants the same maintainer call@objectstack/spec's root entry does not re-export three types its own public API's inferred types mention — every consumer inferring throughdefineStackhits TS2883 #11350 got — which is why this is a card and not a rider./datakeep paying with a per-consumer annotation.@objectstack/spec's root entry does not re-export three types its own public API's inferred types mention — every consumer inferring throughdefineStackhits TS2883 #11350, restated). A gate holding the invariant closed: fail a public entry.d.tswhose declarations structurally mention a type not nameable from that entry. The minimal-program compile the pin test performs is exactly the needed probe generalized per entry; it would have caught both of these names and every future one. C needs A′ (or per-hit equivalents) to go green first.Recommendation: A′ now (the surface promise is already made one subpath away, and the pull is the same class the maintainer already accepted), C as the follow-up that ends the per-name whack-a-mole.
Generated by Claude Code