You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
SEP-1907: Neutralize the dependency-typing artifacts in ty's warning output (#1434)
`ty check` reported 4,072 diagnostics with nothing in the output
separating a genuine typing defect in this repository from an artifact
of how a third-party dependency is typed. This splits the two and
neutralizes only the artifact half — 446 diagnostics — leaving a 3,626
first-party remainder that SEP-1908 can be sized against. **No rule
severity changes**: no severity in `[tool.ty.rules]` moved, and
`[tool.ty.src].include` and the `Makefile` `typecheck` target are
byte-identical to `main`. The only edit inside the `[tool.ty.rules]`
span is the comment block introducing the override entries that follow
it.
**`scripts/classify_ty_diagnostics.py` (new).** Holds the classification
as executable predicates rather than prose: eleven `Group` entries, each
pairing a rule set with a message regex and recording the discriminant
it keys on. The discriminant is load-bearing because most groups share a
rule with genuine defects — under `unknown-argument`, the
pydantic-settings `_secrets_dir` kwarg and a first-party `PMM` kwarg
differ only in the symbol the message names, and
`tests/app/sep/test_config.py` carries both. One group is additionally
confined to a set of paths: `Cannot resolve imported module` reads
identically for a golden-app module scaffolded at test time and for a
first-party import someone mistyped, so `absent-modules` matches only
under `tests/app/sep/apps/framework/golden/` and at
`app/sep/sync/syncers/system_facts/payload.py`. Three modes: `report`
prints the group table and the per-rule artifact/residual split,
`baseline` emits a fingerprint manifest, `check` reconciles a run
against one.
`check` is the gate, and it compares fingerprints rather than counts. A
count comparison cannot establish the ticket's central invariant: once a
suppression hides a first-party warning that row is simply absent, so a
change removing one artifact *and* one first-party diagnostic while an
unrelated new one appears reconciles to the expected total. `check`
instead takes the multiset difference over `(path, rule, message)` and
fails unless every diagnostic that stopped reporting is one the
classification marks as an artifact, naming any that is not. The
fingerprint is exactly what `classify()` reads, so two diagnostics
sharing one always share a verdict; folding the line and column away
costs the gate no soundness and lets it survive the reformatting the
suppression comments provoke, which would otherwise report every
diagnostic below an edited line as newly suppressed.
**Two neutralization mechanisms, chosen per `(file, rule)` pair.**
Eleven `[[tool.ty.overrides]]` entries in `pyproject.toml` cover the 59
pairs whose every hit of that rule in that file is an artifact — 264
hits. Each entry names explicit file paths rather than a directory
wildcard, softens exactly one rule, and carries the reason. The 27 pairs
that mix take 182 per-site `# ty: ignore[rule]` comments instead,
because an override cannot discriminate within a file and would suppress
the genuine defects alongside. No pair gets both: an override makes a
same-rule comment unused, and `unused-ignore-comment` is unlisted in
`[tool.ty.rules]` and so inherits `all = "error"` — which is also what
makes the comments self-cleaning as the tree drifts.
**`docs/development/ty-policy.md`** gains a *Neutralized
dependency-typing artifacts* section recording the group table with each
discriminant and mechanism, the per-rule residual next to the commit it
was measured at, and the commands that reproduce the split. The existing
per-rule tables and sampling record are untouched; the recorded-baseline
section gains a pointer, since its 3,926 figure now describes a
configuration that has moved.
**Reproducing the claim** (the manifest is a build artifact, not a
committed file):
```bash
# Capture the base run first: the classifier does not exist at the merge base.
git switch --detach $(git merge-base HEAD origin/main)
ty check --output-format concise > /tmp/ty-base.txt
git switch -
python3 scripts/classify_ty_diagnostics.py baseline --from /tmp/ty-base.txt \
--out /tmp/ty-baseline.json
python3 scripts/classify_ty_diagnostics.py check --baseline /tmp/ty-baseline.json
```
Measured at merge-base 8ab1800 with ty 0.0.49: 4,072 → 3,626, all 446
removed rows are warnings, all 366 `error`-severity diagnostics survive
untouched, and `check` exits 0 with an empty `RETAINED` list.
**What is *not* neutralized.** The sqlmodel-vs-sqlalchemy `AsyncSession`
mismatch — 201 `invalid-argument-type` hits expecting
`sqlmodel.ext.asyncio.session.AsyncSession` and finding the `sqlalchemy`
one — is first-party and stays reportable. The two classes are not
parallel declarations by two libraries: the sqlmodel class subclasses
the sqlalchemy one, adding `exec`, and `app/core/db/crud.py:40` imports
the subclass so `BaseManager` can call `session.exec(...)` at
`app/core/db/crud.py:227`. A value typed as the supertype cannot satisfy
a parameter requiring the subtype, so ty is right; every hit arises
where a test file or helper annotates its own `session` parameter with
the sqlalchemy import.
0 commit comments