Skip to content

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

Merged
thymikee merged 3 commits into
mainfrom
refactor/no-test-exports
Oct 15, 2026
Merged

thymikee merged 3 commits into
mainfrom
refactor/no-test-exports

Conversation

@thymikee

@thymikee thymikee commented Oct 15, 2026 •

Copy link
Copy Markdown
Member

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)

package offenders
daemon 80 (./testing/*, incl. ./testing/server/daemon-runtime and ./testing/daemon-command-registry)
capture-kit 6
daemon-contracts 2
host-kit 2
platform-android 2
platform-apple 1
replay-port 2
selectors 2
contracts 1
device-selection 1

#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

@thymikee thymikee changed the title refactor/no test exports refactor(layering): R83 forbids package exports that publish test support Oct 15, 2026
@thymikee
thymikee marked this pull request as ready for review October 15, 2026 11:14

@cubic-dev-ai cubic-dev-ai Bot left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All reported issues were addressed across 9 files

Reply with feedback, questions, or to request a fix.

View guided diff | Turn on auto-fix | Re-trigger cubic

Comment thread scripts/layering/package-export-surface.ts
Comment thread scripts/layering/package-export-surface-burn-down.ts Outdated
Comment thread scripts/layering/package-export-surface.test.ts
Comment thread scripts/layering/package-boundaries.ts Outdated
Comment thread scripts/layering/package-export-surface.ts Outdated
Comment thread scripts/layering/platform-package-policy.test.ts Outdated
Comment thread scripts/layering/package-export-surface.test.ts Outdated
@github-actions

github-actions Bot commented Oct 15, 2026 •

Copy link
Copy Markdown
Contributor

Size Report

Metric Base Current Diff
Installed (including dependencies) 5.24 MB 5.24 MB 0 B
Package (unpacked) 5.24 MB 5.24 MB 0 B
Package (download) 1.57 MB 1.57 MB 0 B

Startup median (7 runs, lower is better):

Scenario Base Current Diff
CLI --version 27.6 ms 27.9 ms +0.3 ms
CLI --help 77.9 ms 76.7 ms -1.3 ms

@thymikee

Copy link
Copy Markdown
Member Author

At 4fffafe, five open P2 review threads still apply and should be fixed before merge; they are listed below. The Repo Guards, Coverage, Typecheck and Lint checks were still queued when I looked, so CI has not shown an outcome yet. The next step is to fix those threads and answer the ownership question below.

Not blocking, and you can take or leave these: isTestSupportFile in package-export-surface.ts#L89 adds a third "is this production code" check next to isProductionSourceFile in tracked-sources.ts#L79 and isProductionSource/isNonProductionConsumer in platform-package-policy.ts#L140-L146, and the three disagree on package-local test trees, *.fixtures.*, test-kit and .spec. One predicate in tracked-sources.ts could own this, with the R13 copies deleted. The burn-down list in package-export-surface-burn-down.ts#L5 is hand-kept, and checkPackageExportSurface skips every listed entry. AGENTS.md says not to add an allowlist to get a pass.

Could R83 measure its offender set at the merge-base through mergeBaseRatchets in ratchet-reference.ts, as R6/R9/R10/R75 do, and fail on any entry not present there? That would drop the 122-entry list, the stale-entry check and the list-count text, and "shrink-only" would then be enforced. Who should own the ratchet: this list, or the merge-base measurement?

On the open threads, the six cubic-dev-ai threads still apply: five at P2 and one at P3. The P2 ones cover wildcard exports (#3645 (comment)), the skipped listed entries (#3645 (comment)), the types-only test fixture (#3645 (comment)), the single resolved target (#3645 (comment)) and the .spec mismatch (#3645 (comment)). The P3 ones are the message filter (#3645 (comment)) and the production-target test (#3645 (comment)). The wildcard thread is latent today, since the only wildcards are daemon ./testing/**/__tests__/* entries that their keys already flag. The .spec thread is latent too, since no .spec files exist at head, and the shared predicate would fix it.

I did not run check:layering or the new tests, and I did not confirm that the list matches the tracked manifests. I also did not check whether the new consumer ban fires on current production code, or reproduce the planted ./planted export from the PR body.

@thymikee
thymikee force-pushed the refactor/no-test-exports branch from 4fffafe to b962458 Compare October 15, 2026 12:30
@thymikee

Copy link
Copy Markdown
Member Author

Addressed in b962458.

@thymikee

Copy link
Copy Markdown
Member Author

The PR is ready. The earlier gaps are fixed at b962458: export targets are now judged across every condition and expanded * pattern, the burn-down file is gone, and the new tests cover the cases from the first round. The code has no remaining problems that block merge, and the author's own check:layering run showed the 121-versus-121 counts and a passing gate. I did not run check:layering, the node tests or the planted catch-all export, and I did not run the new tests against the old code to see them fail.

Not blocking: you can take or leave two cleanups. First, scripts/layering/package-export-surface.ts line 77 judges export targets with isTestOnlyExportTarget, a second path vocabulary beside isProductionSourceFile used for importers, and the two disagree on __mocks__/, testing/ and *-fixture-*.ts. Judging the path half as !isProductionSourceFile(<packageDir>/<target>) would fix that, keeping only the key tokens (fake, mock, stub, harness, testing) local to R83. Second, in scripts/layering/package-boundaries.ts line 55, exportConditionTargets is always set but typed optional, so checkTestOnlyImportSite carries a fallback, and measureRatchets defaults exportSurface to empty, which silently measures zero. Making both required would remove that.

Of the earlier inline threads, all seven are fixed at this head, so the author can resolve them. The P2 threads cover wildcard expansion (#3645 (comment)), the deleted burn-down file (#3645 (comment)), every export condition being judged (#3645 (comment)), the import-site check (#3645 (comment)) and .spec coverage (#3645 (comment)). The P3 threads cover the unfiltered assertion in the platform policy test (#3645 (comment)) and the production subpath test (#3645 (comment)).

Both Smoke Tests jobs are still queued or running, and none has failed. The diff only touches scripts/layering and scripts/__tests__, which the smoke tests do not exercise. Nothing blocks merge except waiting for those jobs to finish. There are no conflicts.

@thymikee thymikee added the ready-for-human Valid work that needs human implementation, judgment, or maintainer merge label Oct 15, 2026
@thymikee
thymikee force-pushed the refactor/no-test-exports branch from b962458 to d9bdf9d Compare October 15, 2026 16:09
@thymikee

Copy link
Copy Markdown
Member Author

The PR is ready at d9bdf9d. Since the earlier review at b962458, the delta only touches scripts/layering/platform-package-policy.ts, and it looks right: *.fixtures.ts, *.spec.ts and package-local test/ trees now count as non-production for the test-host rule. I read this from the predicate source and did not plant an import to exercise it. I also did not run check:layering or the scripts/layering node tests at head. All 17 checks are still queued or pending and none has failed. The Repo Guards and Lint & Format (check:layering) jobs cover this delta. No conflicts. Before merge, the queued checks (Repo Guards, Typecheck & Package, Lint & Format) need to pass at d9bdf9d.

Not blocking: the narrowed isProductionSourceFile now rejects *.fixtures.ts, so fixture export targets such as packages/platform-android/src/snapshot-capture.fixtures.ts and ui-hierarchy.fixtures.ts leave the eager-closure entry set from facadeEntryFiles (package-boundaries.ts#L458). That also takes them out of facade-surface.test.ts. Their lines in PLATFORM_MECHANICS_ENTRY_PREFIXES (https://github.com/callstack/agent-device/blob/d9bdf9d/scripts/__tests__/eager-closure-budgets.ts#L154) now match nothing, and the PR body only mentions the 1880 to 1800 layering-scan shrink. You can delete those two lines and their comment and note the wider effect in the PR body, or leave it as is.

thymikee and others added 3 commits October 15, 2026 19:02
Every exports target of every workspace package except test-kit must be a production module: not a
fixture, fake, mock, harness, __tests__ or testing/ entry, judged by key and by target. Today's 122
offenders are a shrink-only burn-down list; a new offender and a stale entry both fail. The
consumer-side ban (production source importing a test-only export or test-kit) moves into the R11
import walk and replaces TEST_ONLY_FIXTURE_SUBPATHS, the ./testing/ namespace check and R13's
ANDROID_TEST_ONLY_SUBPATHS.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…production predicate

R83's offender set is now a merge-base ratchet measured through mergeBaseRatchets (no hand-kept list
to append to), wildcard exports are expanded against the tracked package files, and the consumer ban
judges every export condition. isProductionSourceFile in tracked-sources owns production versus
test support for the layering scans, R11's test-only import ban and R13.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…r test-host rule

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@thymikee
thymikee force-pushed the refactor/no-test-exports branch from d9bdf9d to 0cb9ab0 Compare October 15, 2026 17:09
@thymikee
thymikee merged commit 87f3563 into main Oct 15, 2026
16 of 19 checks passed
@thymikee
thymikee deleted the refactor/no-test-exports branch October 15, 2026 17:20
@github-actions

Copy link
Copy Markdown
Contributor
PR Preview Action v1.8.1
Preview removed because the pull request was closed.
2026-10-15 17:30 UTC

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

Labels

ready-for-human Valid work that needs human implementation, judgment, or maintainer merge

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant