Skip to content

[finding] packages/lint has no tsc program that compiles its tests — the 2,700-line rule pin file, receiver scan included, is type-checked by nobody #14173

Description

@claude

Found while implementing #13935 (packages/lint field-rule root vocabulary). Out of that card's scope — recorded, not fixed.

Same class as #14062 (packages/plugins/**), #13676 (packages/objectql) and #12542 (packages/rest, closed). This instance is packages/lint, which no card names yet — and it is the package whose job is hosting the platform's static-analysis rules.

Measurement

packages/lint/tsconfig.json:

{
  "extends": "../../tsconfig.json",
  "compilerOptions": { "outDir": "./dist", "rootDir": "./src", "types": ["node"] },
  "include": ["src/**/*"],
  "exclude": ["node_modules", "dist", "**/*.spec.ts", "**/*.test.ts"]
}

The package's typecheck script is a bare tsc --noEmit, there is no sibling tsconfig.test.json, and packages/lint carries no test-typecheck-debt.json — the four packages wired to scripts/check-test-typecheck.mts are client, objectql, rest, spec.

Verified with --listFiles on this checkout rather than inferred from the config:

npx tsc --noEmit --listFiles | grep -c 'validate-expressions.test.ts'   -> 0
npx tsc --noEmit --listFiles | grep -c 'validate-expressions.ts'        -> 1

So pnpm --filter @objectstack/lint typecheck renders a green verdict having read the implementation and none of its tests.

Why this instance is worth naming separately

The count is not the point; what is sitting in the untyped zone is. packages/lint/src/validate-expressions.test.ts alone is roughly 2,700 lines and is built almost entirely out of compile-time and meta pins — the #5017 receiver scan that reads the rule's own source text, the TRACKED_UNDECLARED_READS shrink-only list, the residual-root table generated from the real baseline, the FieldSchema.shape key cross-checks. AGENTS.md names this exact shape as how a pin becomes a phantom check: an assertion in a file no tsc program compiles asserts nothing about types, and weakening it is invisible to every gate.

It also has a second-order effect the sibling cards do not: this package's rules are consumed by scripts/check-doc-formula-expressions.mjs and by os validate, so a pin that quietly stops meaning what it says takes a published diagnostic surface with it.

⚠️ To be precise about what is and is not broken, in the same spirit as #12511: vitest still RUNS these tests and they still pass. What is absent is type checking of the test sources — so a wrong key, a stale schema shape, or a signature drift in a pin's own scaffolding is caught by nobody.

Not proposing the remedy

Two routes exist in-repo and choosing between them is a maintainer/triage call, not this finding's: the packages/spec pattern (a sibling tsconfig.test.json named in the typecheck script) or onboarding to scripts/check-test-typecheck.mts with a measured shrink-only ledger. Onboarding by wiring, never by copying, per AGENTS.md. ⛔ I am not proposing a severity.

Provenance

Measured in /home/user/objectstack-issue-13935 at origin/main base 2bff79ae3, during #13935.


Generated by Claude Code

Metadata

Metadata

Assignees

Type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions