Filed unassigned by the #13013 dev (PR pending), session session_01CPrUz21stTFhJRUirdc4yw. Observation class — nothing behaves wrongly. Not graded, not routed. Severity not judged.
Found while pruning dist/testing.cjs in #13013. It is the same defect class — bytes packed into the npm tarball that no exports condition can reach — but it is a different remedy with a different open question, so it was deliberately not fixed as a rider on that PR.
Measured
On a full pnpm build at 2fde235c84, comparing every emitted dist/**/*.d.cts against the union of declaration paths each manifest actually declares (types, typings, and every types condition at any depth inside exports):
publishable packages shipping UNDECLARED .d.cts: 28 · 36 file(s) · 5,497,247 B (5.2 MiB)
1,144,571 B 1 file(s) @objectstack/service-messaging
959,102 B 1 file(s) @objectstack/metadata-core
559,196 B 1 file(s) @objectstack/service-automation
483,141 B 1 file(s) @objectstack/service-storage
462,256 B 1 file(s) @objectstack/metadata-protocol
234,012 B 2 file(s) @objectstack/plugin-webhooks
223,477 B 1 file(s) @objectstack/runtime
218,614 B 3 file(s) @objectstack/lint
For scale: #13013 removed ~113 KB across two packages. This is ~48x that, and @objectstack/metadata-core alone carries more of it (959 KB) than #13013 removed in total.
Why they are unreachable
The dual-build manifests spell one types condition as a sibling of import/require rather than one inside each branch:
"exports": {
".": {
"types": "./dist/index.d.ts",
"import": "./dist/index.js",
"require": "./dist/index.cjs"
}
}
So both conditions resolve types to dist/index.d.ts, and the dist/index.d.cts twin that format: ['esm','cjs'] makes tsup emit is named by nothing. files: ["dist"] packs it anyway. check:published-files cannot object — it asks that the whitelist covers declared entry points, and it never reads dist/ at all (SKIP_DIRS includes it), so an extra shipped file is outside the question it asks.
Not established — and why this is not a mechanical prune
⛔ Do not read the measurement as "delete them". Two things need deciding first, and neither is settled by the numbers above:
- Whether any consumer toolchain reaches
.d.cts by file adjacency rather than through the types condition. Unreachable-through-the-manifest is proven; unreachable-in-practice is not the same claim, and a require-side consumer resolving index.cjs is exactly where an adjacent index.d.cts would be looked for. That is a real question about published surface, not a build-config tidy-up. - Whether the remedy is dropping the emission or declaring the file. The other direction is equally available and arguably more correct: give each condition its own nested
types ("require": { "types": "./dist/index.d.cts", "default": "./dist/index.cjs" }), which makes the shipped file reachable instead of deleting it, and gives CJS consumers declarations built for CJS. Choosing wrongly here is a published-surface change in 28 packages at once.
Note the asymmetry with #13013: there the dead file had no possible reader, because the subpath had no require condition at all after #13001. Here there is a plausible reader and the question is which of two shapes is right.
The converse-gate question this sharpens
#13013 left open whether a gate should assert the converse of check:published-files — that every published entry-point-shaped dist file is reachable through exports — and warned the naive form would be noisy, because chunks, maps and .d.ts files are legitimately published and are not entry points.
This measurement suggests the noise objection is answerable, because the query above is already the precise form: compare emitted declaration files against the declared declaration set, which scripts/check-dts-emitted.mjs already computes (declaredDeclarationPaths) for the opposite direction. That gate asks "is every declared declaration present"; the converse asks "is every present declaration declared", over the same population, with the same function. Chunks and maps never enter it. Whether that gate should exist, and whether it reds or merely reports, is still open — but it would not be the noisy shape #13013 was worried about.
Dedup
One targeted search_issues (repo-scoped REST is 403 on this seat), 23 matches. Nothing open on undeclared .d.cts or on the converse reachability gate. Nearest neighbours, all distinct: #13013 (open, this card's parent class, testing.cjs only), #12985 (closed, created the ESM-only ./testing shape), #8133 (open, @objectstack/spec chunk sharing across entry bundles — a different property of a different package). Control: the same query returned #13013 itself, so the empty result is a reading rather than a broken instrument.
Re-check
git ls-files 'packages/**/package.json'
then, per manifest, diff dist/**/*.d.cts against the union of types, typings and every types condition inside exports. Requires a full pnpm build first — with no dist/ the population is empty and the query answers zero for the wrong reason.
Filed unassigned by the #13013 dev (PR pending), session
session_01CPrUz21stTFhJRUirdc4yw. Observation class — nothing behaves wrongly. Not graded, not routed. Severity not judged.Found while pruning
dist/testing.cjsin #13013. It is the same defect class — bytes packed into the npm tarball that noexportscondition can reach — but it is a different remedy with a different open question, so it was deliberately not fixed as a rider on that PR.Measured
On a full
pnpm buildat2fde235c84, comparing every emitteddist/**/*.d.ctsagainst the union of declaration paths each manifest actually declares (types,typings, and everytypescondition at any depth insideexports):For scale: #13013 removed ~113 KB across two packages. This is ~48x that, and
@objectstack/metadata-corealone carries more of it (959 KB) than #13013 removed in total.Why they are unreachable
The dual-build manifests spell one
typescondition as a sibling ofimport/requirerather than one inside each branch:So both conditions resolve types to
dist/index.d.ts, and thedist/index.d.ctstwin thatformat: ['esm','cjs']makes tsup emit is named by nothing.files: ["dist"]packs it anyway.check:published-filescannot object — it asks that the whitelist covers declared entry points, and it never readsdist/at all (SKIP_DIRSincludes it), so an extra shipped file is outside the question it asks.Not established — and why this is not a mechanical prune
⛔ Do not read the measurement as "delete them". Two things need deciding first, and neither is settled by the numbers above:
.d.ctsby file adjacency rather than through thetypescondition. Unreachable-through-the-manifest is proven; unreachable-in-practice is not the same claim, and arequire-side consumer resolvingindex.cjsis exactly where an adjacentindex.d.ctswould be looked for. That is a real question about published surface, not a build-config tidy-up.types("require": { "types": "./dist/index.d.cts", "default": "./dist/index.cjs" }), which makes the shipped file reachable instead of deleting it, and gives CJS consumers declarations built for CJS. Choosing wrongly here is a published-surface change in 28 packages at once.Note the asymmetry with #13013: there the dead file had no possible reader, because the subpath had no
requirecondition at all after #13001. Here there is a plausible reader and the question is which of two shapes is right.The converse-gate question this sharpens
#13013 left open whether a gate should assert the converse of
check:published-files— that every published entry-point-shapeddistfile is reachable throughexports— and warned the naive form would be noisy, because chunks, maps and.d.tsfiles are legitimately published and are not entry points.This measurement suggests the noise objection is answerable, because the query above is already the precise form: compare emitted declaration files against the declared declaration set, which
scripts/check-dts-emitted.mjsalready computes (declaredDeclarationPaths) for the opposite direction. That gate asks "is every declared declaration present"; the converse asks "is every present declaration declared", over the same population, with the same function. Chunks and maps never enter it. Whether that gate should exist, and whether it reds or merely reports, is still open — but it would not be the noisy shape #13013 was worried about.Dedup
One targeted
search_issues(repo-scoped REST is 403 on this seat), 23 matches. Nothing open on undeclared.d.ctsor on the converse reachability gate. Nearest neighbours, all distinct: #13013 (open, this card's parent class,testing.cjsonly), #12985 (closed, created the ESM-only./testingshape), #8133 (open,@objectstack/specchunk sharing across entry bundles — a different property of a different package). Control: the same query returned #13013 itself, so the empty result is a reading rather than a broken instrument.Re-check
then, per manifest, diff
dist/**/*.d.ctsagainst the union oftypes,typingsand everytypescondition insideexports. Requires a fullpnpm buildfirst — with nodist/the population is empty and the query answers zero for the wrong reason.