Skip to content

downstream-contract's OTHER half — its typecheck — still resolves @objectstack/spec types through dist, so a narrowed export type reads green (measured) #8021

Description

@hotlong

Filing unassigned; found while implementing #7991, and deliberately not fixed there (that card's scope is the vitest resolution path, and its ruling is to report adjacent exposure rather than widen).

Context

packages/qa/downstream-contract is the repo's backward-compatibility gate for @objectstack/spec. Its README states the gate has two halves:

  • pnpm --filter @objectstack/downstream-contract typecheck — the fixtures are typed with real spec author-state types (Action, Report, Page, ...). A removed or narrowed export fails here (the fix(examples): typecheck example apps clean + gate in CI #2023 class of break).
  • pnpm --filter @objectstack/downstream-contract test — runs each bare-literal fixture through its schema's .parse() ...

#7991 fixed the second half: it shipped no vitest.config.*, so the suite resolved @objectstack/spec through exports to dist/ and reported 14/14 green against a source-only break of its own fixture. The first half has the same exposure and is untouched by that fix.

Measured

packages/qa/downstream-contract/tsconfig.json sets moduleResolution: NodeNext and declares no paths, so tsc resolves @objectstack/spec through the workspace link and the exports map — i.e. dist/index.d.mts, a build artifact.

Injected into source only, no rebuild (packages/spec/src/integration/connector.zod.ts), a narrowing that the frozen DcConnector fixture cannot satisfy:

-label: z.string().describe('Display label'),+label: z.number().describe('TEMPORARY TYPE PROBE — was z.string()'),

DcConnector is declared const DcConnector: Connector and assigns label: 'DC HubSpot', so with source types this is a type error. Result:

> @objectstack/downstream-contract@0.0.38-rc.5 typecheck
> tsc --noEmit
(clean, exit 0)

Green — the exact shape of #7991, one layer over: the verdict is about the last pnpm build, not about the spec in the checkout. (Probe reverted; the source file was verified byte-identical afterwards.)

Ordering does not reach it, same as #7991

turbo.json orders typecheck after ^build, so CI's pnpm typecheck measures a fresh dist and is not the failing path. What breaks is every path turbo does not mediate: pnpm --filter ... typecheck in the package, an editor's TS server, or an agent in a tree built at an older commit — the paths this gate is re-read on while someone is changing the spec.

The judgment this needs (why it is a card, not a one-liner)

A paths mapping to ../../spec/src fixes the resolution, but it changes what the fixture type-checks against: generated .d.ts versus source types are not always identical, and a real third party does consume the published types. That argument was already weighed and rejected for the runtime half — the fixture's purpose is to answer "would the spec in this checkout break a published-spec consumer" — so consistency points the same way here, but it is a deliberate decision about the gate's meaning, plus whatever type errors the switch surfaces on first run.

Prior art (searched before filing)

Nothing open covers the type half of this package.


Generated by Claude Code

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions