Surfaced while sweeping @object-ui/components for objectui#6892 (slice 6). Deliberately not fixed there — that PR sweeps one specifier and adds one line to COVERED_SPECIFIERS; changing what the gate's scope RESOLVER accepts is a different decision.
What was measured
scripts/check-vi-mock-inherit.mjs decides scope by EXACT string equality against COVERED_SPECIFIERS:
coveredSet.has(specifier)
? 'covered'
: specifier.startsWith('@object-ui/')
? 'workspace' // counted, never judged
: 'external'
So a SUBPATH of an already-swept package is not covered. Measured on 214d5d5a6 (the slice-6 head, eleven members in the constant), by running the gate's own exported scan():
COVERED_SPECIFIERS members : 11
vi.mock call sites naming a SUBPATH of one of them : 2
@object-ui/components/ui/sonner
packages/plugin-form/src/MasterDetailForm.outcomeToastSupersede.test.tsx:99
packages/plugin-form/src/WizardForm.outcomeToastSupersede.test.tsx:96
judged with those two subpaths added to `covered` : 2 inherit, 0 frozen
Both already inherit, so tightening this today is free — the same "the tree is at zero, so the gate starts as a ratchet" argument that scoped objectui#6849.
Why it matters
A package joins COVERED_SPECIFIERS because its frozen factories were swept to zero and the gate now holds it there. That guarantee silently does not extend to its own subpaths: a factory written tomorrow as a frozen double of @object-ui/components/ui/sonner (or any future subpath of any of the eleven) is counted in the "other workspace" bucket and never judged, while the maintainer's reading of the verdict line is that the package is covered. The defect the gate exists to catch — a module-scope read of an export the frozen stand-in never listed, killing the file during COLLECTION with zero failed assertions — does not care whether the specifier was the package root or a subpath of it.
This is not an exemption and not a recogniser bug: the resolver is doing exactly what it says. It is a scope boundary that was drawn when the covered set had one member and no one had a subpath.
Options, not a recommendation
- Prefix match — treat
spec === c || spec.startsWith(c + '/') as covered. Smallest change; makes every future subpath of a swept package covered by construction. Needs the population re-measured first, because a subpath can have a genuinely different export surface.
- Enumerate subpaths in the constant — keep exact matching, add
@object-ui/components/ui/sonner as its own member. Honest and free today, but the constant then has to grow once per subpath and the gap reopens for every new one.
- Leave it and say so in the header — record the boundary explicitly so the verdict line is not read as a claim about subpaths.
Option 1 is the only one that closes the class rather than the two instances; option 2 is the only one that keeps the "a member joins only by sweep" invariant literally true. Both are free at today's numbers.
Reproduce
node -e "import('./scripts/check-vi-mock-inherit.mjs').then(m => { const r = m.scan(process.cwd(), { covered: [], floors: {} }); console.log(r.sites.filter(s => s.scope === 'workspace' && m.COVERED_SPECIFIERS.some(c => s.specifier.startsWith(c + '/')))); })"
Related: objectui#6849 (the gate), objectui#6892 (the per-specifier worklist), objectui#8117 (a different pin-versus-prose issue in the same file).
Generated by Claude Code
Surfaced while sweeping
@object-ui/componentsfor objectui#6892 (slice 6). Deliberately not fixed there — that PR sweeps one specifier and adds one line toCOVERED_SPECIFIERS; changing what the gate's scope RESOLVER accepts is a different decision.What was measured
scripts/check-vi-mock-inherit.mjsdecides scope by EXACT string equality againstCOVERED_SPECIFIERS:So a SUBPATH of an already-swept package is not covered. Measured on
214d5d5a6(the slice-6 head, eleven members in the constant), by running the gate's own exportedscan():Both already inherit, so tightening this today is free — the same "the tree is at zero, so the gate starts as a ratchet" argument that scoped objectui#6849.
Why it matters
A package joins
COVERED_SPECIFIERSbecause its frozen factories were swept to zero and the gate now holds it there. That guarantee silently does not extend to its own subpaths: a factory written tomorrow as a frozen double of@object-ui/components/ui/sonner(or any future subpath of any of the eleven) is counted in the "other workspace" bucket and never judged, while the maintainer's reading of the verdict line is that the package is covered. The defect the gate exists to catch — a module-scope read of an export the frozen stand-in never listed, killing the file during COLLECTION with zero failed assertions — does not care whether the specifier was the package root or a subpath of it.This is not an exemption and not a recogniser bug: the resolver is doing exactly what it says. It is a scope boundary that was drawn when the covered set had one member and no one had a subpath.
Options, not a recommendation
spec === c || spec.startsWith(c + '/')as covered. Smallest change; makes every future subpath of a swept package covered by construction. Needs the population re-measured first, because a subpath can have a genuinely different export surface.@object-ui/components/ui/sonneras its own member. Honest and free today, but the constant then has to grow once per subpath and the gap reopens for every new one.Option 1 is the only one that closes the class rather than the two instances; option 2 is the only one that keeps the "a member joins only by sweep" invariant literally true. Both are free at today's numbers.
Reproduce
Related: objectui#6849 (the gate), objectui#6892 (the per-specifier worklist), objectui#8117 (a different pin-versus-prose issue in the same file).
Generated by Claude Code