refactor(layering): R83 forbids package exports that publish test support

Merged
TH
2h ago
+875-309

What

R83 package-export-surface measures its offenders against the merge-base and keeps no list: 99 test-only exports at head against 99 at the merge-base (daemon 80, capture-kit 6, and 13 across eight other packages). R11's devDependency support for test-file imports (what lets the daemon's @agent-device/proxy dependency flip to devDependencies) is carried by #3659, the next layer in this stack.

No workspace package except @agent-device/test-kit may publish test support in exports. A package's tests live beside the module they test; fixtures one package uses stay inside it; fixtures shared across packages go to test-kit.

  • Merge-base ratchet, no list. The offender set (<package> <export key>) is measured at the merge-base through mergeBaseRatchets and at head by the same function. An offender at head that the merge-base did not publish fails; a cleanup needs no edit, and nothing sits above what main holds. The check's output reports 99 test-only export(s) remain against 99 at the merge-base.
  • Classification judges the export key and every file the entry resolves to under every condition (types, import, default, nested), expanding * patterns against the tracked package files. It flags fixtures (*.fixtures.ts, *.fixture.ts, *-fixture*.ts, fixtures.ts), __tests__/, testing/, test/, *.test.*/*.spec.*, and keys or paths with fixture(s), fake(s), mock(s), stub(s), harness, test-support, test-host. Production names that merely mention test (./test-command, ./session-test-*, ./android-clipboard-support) are pinned as accepted.
  • One production predicate. isProductionSourceFile in tracked-sources.ts owns "is this production source": TypeScript under root src/ or a package src/, minus __tests__/ and test/ trees, *.test.*/*.spec.*, *.fixtures.* and test-kit. The layering scans, the eager-closure reader, R11's consumer ban and R13 all call it; isTestSupportFile, isTestConsumerFile and R13's isProductionSource/isNonProductionConsumer are deleted. It has its own table test. Side effect: fixtures and test-kit leave the layering scans (1880 โ†’ 1800 files), so test-kit leaves UNRANKED_ZONES and the gateway fixtures row leaves ROOT_MODULE_ZONES.
  • Consumer ban moved into the R11 import walk (same sites, same vi.mock scan) and checks all condition targets: production source may not import a test-only export or @agent-device/test-kit. test/ and scripts/ are not production sources.
  • Removed: TEST_ONLY_FIXTURE_SUBPATHS, the ./testing/ namespace check and R13's ANDROID_TEST_ONLY_SUBPATHS (R13 leaves test-only subpaths to R83 and filters them from its exact-export comparison).

Seed at head (99, re-measured at the merge-base after the daemon test relocations)

packageoffenders
daemon80 (./testing/*, incl. ./testing/server/daemon-runtime and ./testing/daemon-command-registry)
capture-kit6
daemon-contracts2
host-kit2
platform-android2
platform-apple1
replay-port2
selectors2
contracts1
device-selection1

#3647 and #3650 drop only production replay-port/session-journal exports (session-test-*, session-event-*), none of them R83 offenders, so the seed is unaffected.

Evidence

  • package-export-surface.test.ts plants 22 test-support shapes and 7 look-alike production shapes; fixtures in types only, in default only and in a nested condition; string-valued and missing exports; wildcards with and without a matching fixture file; ratchet growth, cleanup, moved file, renamed key and a brand-new package; and the consumer ban at root and package sites (including .spec, fixtures files, scripts/).
  • Planted a catch-all ./* โ†’ ./src/*.ts plus packages/kernel/src/planted.fixtures.ts; check:layering named it under R83. Reverted.
  • Gates: pnpm check:quick, check:layering (361 node tests), check:fallow, check:gate-manifest, check:affected --run, repo-wide pnpm format.
  • Workers: tests that pin a package's exact export list (e.g. selectors in package-boundaries.test.ts) edit in the same PR that drops the export.

๐Ÿค– Generated with Claude Code