Uh oh!
There was an error while loading. Please reload this page.
fix(spec): re-export the three types the root entry's own inferred types mention (#11350) - #11716
Merged
Merged
Conversation
…pes mention (#11350) An un-annotated `export default defineStack(...)` is emitted as the structural expansion of `ObjectStackDefinition` (declared `z.input<...>`, which the declaration emitter does not preserve as an alias). That expansion mentions `FormFieldInput` / `NavigationItemInput` / `StateNodeConfig`, which were public on their domain subpaths but not on the root entry — so tsc could only name them through a hash-named internal dist chunk (TS2883 in every consumer inferring through a root-entry function). Invariant recorded (maintainer ruling 2026-08-23): a type that appears structurally in an entry's public declarations must be nameable from that same entry. Measured: the plugin-audit i18n-extract repro goes 3 errors -> 0. api-surface/root.json regenerated via gen:api-surface (+3 rows). Pin test compiles the consumer shape against the built root dts, with a declaration-emit canary guarding the harness axis. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Rxnd8cyFnoU8V5y21PaTsy
… add changeset (#11350) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Rxnd8cyFnoU8V5y21PaTsy
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Rxnd8cyFnoU8V5y21PaTsy
Contributor
📓 Docs Drift CheckNothing in this diff resolved to a documentable surface (no symbol, route or SDK anchor derived from 2 changed package(s)), so this run has no opinion about the docs. What this run could not see
Coarse fallback — 127 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): |
…11350) Test Core deliberately never builds spec's own dist (turbo's test task depends on ^build, dependencies only), so the pin's beforeAll freshness throw redded a whole CI shard for a measurement that lane cannot make. The pin now follows the live-dialect-cell discipline — reported, never omitted, no third outcome: dist fresh = both programs run; dist absent by default = a NAMED skip carrying the refusal reason; dist absent under OS_EXPECT_ROOT_NAMEABILITY=1 = a failure quoting the refusal. The flag is set in lint.yml's 'Type Check - consumer gates' lane right after its full packages-closure builds, beside check:api-surface and check:skill-examples — the other consumer-shaped gates reading the built dist — so the pin still runs for real in CI and cannot quietly degrade to never-measured. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Rxnd8cyFnoU8V5y21PaTsy
…to the /ui entry (#11350) protocol.ts imports both the spec root (applyConversionsToStoredItem) and /ui, and its inferred public declarations structurally mention FormFieldInput. Once the root entry exported that name, tsc's declaration emitter switched its synthesized reference from /ui to the root — both portable, but the root specifier drags spec's entire root module graph into every downstream tsc program reading this package's dts (measured on the debt-ledger re-measure of @objectstack/http-conformance: +2 program files, +190k types, +805k instantiations, +~560MB — past a 4GB child heap, which is what redded Type Check - debt ledger on PR #11716). A local type-only import binding makes the emitter reuse it, keeping the reference on the narrow /ui entry. Erased at runtime: emitted JS byte-identical; the curated index.ts entry is unchanged. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Rxnd8cyFnoU8V5y21PaTsy
This was referenced Aug 24, 2026
os-warren
marked this pull request as ready for review
August 24, 2026 21:39
Uh oh!
There was an error while loading. Please reload this page.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for freeto join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes#11350
Ruling implemented
Maintainer 2026-08-23 (recorded in issue comment 5386669705, verbatim 「10950 不考虑存量,其他接受你的建议」): add the three type re-exports (
FormFieldInput,NavigationItemInput,StateNodeConfig) to the spec root entry — the measured fix (3 errors → 0).Invariant recorded (and carried as a comment on the new export block in
src/index.ts): a type that appears structurally in an entry's public declarations must be nameable from that same entry.defineStackreturnsObjectStackDefinition, declared as thez.inputinstantiation ofObjectStackDefinitionSchema— a generic instantiation the declaration emitter does not preserve as an alias — so an un-annotatedexport default defineStack(...)is emitted as the structural expansion, which mentions these three types. All three were already public on/uiand/automation; the root entry simply could not name them, so tsc reached for the hash-named dist chunk (TS2883, unaddressable through theexportsmap).Changes
packages/spec/src/index.ts— the threeexport typere-exports, from the modules that declare them, with the invariant + card reference in the comment.packages/spec/api-surface/root.json— regenerated viagen:api-surface; grows by exactly 3 rows (FormFieldInput (type),NavigationItemInput (type),StateNodeConfig (type)).packages/spec/export-origins/root.json— regenerated viagen:export-origins(check:generatedproved it stale; +3 origin rows).packages/spec/scripts/root-entry-type-nameability.pin.test.ts— pin test compiling the consumer shape verbatim (un-annotatedexport default defineStack(...)) against the built root dts, resolved through a realnode_modulessymlink + the package's ownexportsmap, withdeclaration: true. A second program is a hermetic declaration-emit canary (TS4094; measured red withdeclaration: true, green without), so dropping the declaration axis from the harness cannot silently turn the pin into a phantom check. Dist staleness is refused via the sharedinspectDistFreshnessrule..changeset/root-entry-type-reexports-11350.md— minor for@objectstack/spec(public face +3, additive; not breaking, so no ADR-0087 disposition marker is owed —check:adr-0087-registrationgreen).Measurements
4ceae8ab0, annotation from the earlier consumer-side repair reverted onpackages/plugins/plugin-audit/scripts/i18n-extract.config.ts, dependency closure freshly built):npx tsc --noEmit -p tsconfig.repro.json→ exactly 3 × TS2883 naming the three types fromdist/view.zod-DlqDxILi.js/dist/app.zod-CeA4DPnB.js/dist/state-machine.zod-BTAjKYaW.js; exit 2.dist/index.d.ts): same command → 0 diagnostics, exit 0. The consumer file was then restored to its committed annotated form (untouched by this PR).dist/index.d.tsfinal export statement (mutation proven on disk: replacement count 3, final-line grep 0, file differed from backup), the pin test went red with exactly the 3 TS2883s naming the three types, and the dist was restored byte-identically (cmpclean) with the pin green again.Premise delta — reported, not silently fixed
The pin's minimal one-file program surfaced two more names leaking through the chunked build that the fixed root entry still cannot name:
BaseValidationRuleShapeandFilterCondition(both public on/data). The nine i18n-extract programs never see them because their object modules import@objectstack/spec/data— the card's own control effect. #11709 is not addressed here and remains open, filed with the measurement and options (including the card's Option C gate, which its dispatch reserved for a proposal rather than this PR). The pin test's consumer program deliberately mirrors the real consumers (acontext.tsimporting/data), scoping the pin to the ruled three; its docblock records the delta and points at #11709.Verification at final head
ddf0566b2pnpm --filter @objectstack/spec exec vitest run— 421 files / 11224 tests passed (ran atd62f9cc42; the only later commit changes one docblock line inside the pin test, which was rerun green atddf0566b2).pnpm --filter @objectstack/spec typecheck— exit 0 (tsc --noEmit+ scripts + test-layer; ledger line: "55 file(s) / 263 error(s) held", unchanged).pnpm --filter @objectstack/spec check:generated— exit 0, all 14 artifacts current (check:api-surface✓).node scripts/pm/dispatch-gates.mjs, no hand-built path list; derivation stampedobjectstack-ai/objectstack): all 22 matched + test-kind convention gates green atddf0566b2, exceptcheck-dev-prereqs.mjsexit 1 solely on its environmental precondition ("The workspace is not built — 1 unmet precondition": 52/67 package dists absent in this fresh worktree; spec's own dist is built and stamped; CI runs it afterpnpm build).pnpm lint(eslint . --no-inline-config, one flat config); files linted from--format json= 2 (the only lintable files in this diff — the md/json artifacts report "File ignored because no matching configuration was supplied"); invariance = the config's own text states type-aware linting is never enabled for any file, so this diff cannot move untouched files' verdicts. Result: 0 errors / 0 warnings, exit 0.check:type-check-debt --re-measurenot run locally (needs the full workspace closure built), declared: the only ledger entries this diff can reach are spec's, re-measured green insidepnpm --filter @objectstack/spec typecheck; the change is additive on dist exports, which cannot remove or narrow anything a consumer package's program reads.Generated by Claude Code