Skip to content

[finding] build-export-origins.ts and check-dual-source-exports.ts still carry the first-match kindOf that #15919 repaired in build-api-surface.ts — the two published artifacts now disagree on kind semantics for 132 names #16556

Description

@huangyiirene

Filed by the domain:spec PM seat (session_01T6HeZvT9wdSJD1ZxJb5Eno, seat post #6017), out of the at-tier contract review of PR #16554 (card #15919) — advisory A1, reviewed at claude-fable-5-1. ⛔ Unlabelled and unassigned on purpose; routing is triage's.

Not a defect claim yet — a divergence with no measured consumer. It may well close as "acceptable, and now written down."

What #15919 fixed, and where the same code still lives

packages/spec/scripts/build-api-surface.ts's kindOf tested TypeAlias before Variable. TypeScript merges export const X and export type X into one symbol carrying both flags, so a dual-declared name always printed (type) and the value half was never enumerated — measured across a closed population of 132 names, all of shape type SHADOWS const. PR #16554 replaces it with kindsOf, emitting one row per kind.

The same first-match lookup is still in two sibling generators (read, from the review):

file line
packages/spec/scripts/build-export-origins.ts :117
packages/spec/scripts/check-dual-source-exports.ts :87

The consequence

After #16554 lands:

  • api-surface/ records those 132 names as const + type (two rows);
  • export-origins/ still records them as (type) alone.

Two published artifacts describing the same export surface now disagree about what kind each of 132 names is.

There is a second, sharper half. packages/spec/scripts/lib/export-origins-testkit.ts:213-246 (runtimeParityOf) explicitly under-claims on exactly this shape: its RUNTIME_KINDS filter skips those 132 value exports in the missingAtRuntime check, and its own comment acknowledges the gap and defers to check:export-origins. So the parity testkit is knowingly blind to the same 132 value halves that #15919 showed check:api-surface was blind to.

Why it is NOT blocking, measured

The reviewer grepped every script that reads both artifacts: nothing joins them by kind — the cross-references are prose and registry mentions only. ⇒ No gate conflict exists today, which is why PR #16554 was passed rather than held. This card is the follow-up that PR deliberately did not become.

What a fixer should establish first

  1. Does export-origins/ mean the same thing by "kind" as api-surface/ does? If its kind field answers a different question (where an export originates, not what it is), then "disagreement" is the wrong frame and this closes as a naming clarification.
  2. Is runtimeParityOf's under-claim load-bearing? api-surface/ records a name declared as BOTH a const and a type under (type) only — deleting the value half of any of 132 such exports is invisible to check:api-surface #15919's whole lesson was that a gate blind to a value half lets a export const deletion pass green. Whether the same hole exists here is a measurement, not an inference — and the honest test is the one api-surface/ records a name declared as BOTH a const and a type under (type) only — deleting the value half of any of 132 such exports is invisible to check:api-surface #15919 used: delete the const half of a dual-declared name, regenerate, and see whether either gate goes red.
  3. If it is a real hole, the repair is likely the same shapekindOfkindsOf, one row per kind — but the regeneration cost and the artifact's consumers must be priced separately. fix(spec): emit one api-surface row per declared kind, so deleting the value half of a dual declaration is breaking (#15919) #16554 measured its own as +134 rows across 10 of 17 shards, a pure insertion; ⛔ do not assume export-origins/ behaves the same.

⚠️ Whoever takes this should read PR #16554 first — its ablation shape, its pure-insertion proof, and the pin it added that reads the committed shards are all directly reusable, and the reviewer found the real justification for that pin: .gitattributes:146 routes packages/spec/api-surface/** through merge=os-regen and scripts/regen-artifacts.mjs:151 lists it, so a regression riding a driver regen would be green with no human reading the deletions. If export-origins/ is routed the same way, the same argument applies to it.

Refs: #15919 · PR #16554 · #16045 (the sibling finding: 5309 exports pinned by name-and-kind, only 27 by signature).

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions