The state
44 occurrences of as unknown as across 22 files — 42 in tests, 2 in production. Each one is a place where the compiler was told to stop checking.
They accumulated because the guardrail only watches half the repository: scripts/check-cli-layering.mjs scans cli/src and nothing else. Tests have never been held to the rule.
Four causes, four different fixes
| Cause |
Count |
What the fix actually is |
| A — a partial double of a port or use-case |
31 |
Not a cast to replace. Either use the real in-memory doubles that already exist in cli/tests/helpers/ports/, or narrow the port. |
| C — a branded type or a typed Node API return |
9 |
A test-only constructor for FileHash, and properly typed execSync mocks. Mechanical. |
| D — a deliberately invalid value, to test rejection |
2 |
@ts-expect-error, which turns the cast into an assertion that fails the day the type stops forbidding it. |
| E — a real hole in production code |
2 |
framework-build-use-case.ts:88 and marketplace-build-strategy.ts:132, the two entries check-cli-layering.mjs currently grandfathers. |
Group A is a design signal, not laziness
{ two methods } as unknown as FileReader usually means the use-case genuinely needs two methods of a port that exposes twelve. The cast is hiding an interface-segregation problem, and "fixing" it has two honest answers — implement ten unused methods in a fake, or split the port. The second changes production interfaces.
A generic stub<T>(partial: Partial<T>): T is the same hole with a nicer name. It should not be the answer here.
Group D is already solved once
cli/tests/application/use-cases/telemetry/read-local-cost-use-case.unit.test.ts shows the pattern: the cast became a @ts-expect-error on a typed literal. Removing tool from the omitted set then failed the build in four places — the assertion itself plus all three readers, which are forced to name their tool. That is a stronger guarantee than the runtime check it replaced, and it cost two lines.
Done when
Sequencing
After #687. The telemetry work is the v1 path; 22 files of unrelated churn on top of it would make both diffs unreadable.
The state
44 occurrences of
as unknown asacross 22 files — 42 in tests, 2 in production. Each one is a place where the compiler was told to stop checking.They accumulated because the guardrail only watches half the repository:
scripts/check-cli-layering.mjsscanscli/srcand nothing else. Tests have never been held to the rule.Four causes, four different fixes
cli/tests/helpers/ports/, or narrow the port.FileHash, and properly typedexecSyncmocks. Mechanical.@ts-expect-error, which turns the cast into an assertion that fails the day the type stops forbidding it.framework-build-use-case.ts:88andmarketplace-build-strategy.ts:132, the two entriescheck-cli-layering.mjscurrently grandfathers.Group A is a design signal, not laziness
{ two methods } as unknown as FileReaderusually means the use-case genuinely needs two methods of a port that exposes twelve. The cast is hiding an interface-segregation problem, and "fixing" it has two honest answers — implement ten unused methods in a fake, or split the port. The second changes production interfaces.A generic
stub<T>(partial: Partial<T>): Tis the same hole with a nicer name. It should not be the answer here.Group D is already solved once
cli/tests/application/use-cases/telemetry/read-local-cost-use-case.unit.test.tsshows the pattern: the cast became a@ts-expect-erroron a typed literal. Removingtoolfrom the omitted set then failed the build in four places — the assertion itself plus all three readers, which are forced to name their tool. That is a stronger guarantee than the runtime check it replaced, and it cost two lines.Done when
as unknown asremains incli/, or each survivor is listed with the reason it cannot be removed — the way the layering script already lists its two.check-cli-layering.mjsscanscli/testsas well, so this cannot silently grow back.FileHashand any other branded type has a test-only constructor, so no test invents one through a cast.Sequencing
After #687. The telemetry work is the v1 path; 22 files of unrelated churn on top of it would make both diffs unreadable.