Repository navigation
Commit 08a6c7a
fix(scripts): make check-test-typecheck signatures path-independent (#18173)
Fixes #17739
## The defect
`normalizeMessage` in `scripts/check-test-typecheck.mts` keeps a quoted
span verbatim when it is short and free of the structural punctuation
that makes tsc print a whole shape. A **TS6059** message reads `File
'ABS' is not under 'rootDir' 'ABS'`, and an absolute filesystem path
carries none of that punctuation — so **its LENGTH was the only thing
deciding whether it survived**, and the identical diagnostic produced
one signature in a short-path checkout and a different one in a
long-path checkout.
The ledgers were recorded from a long path, and CI's checkout path is
long too, which is why this survived: it reds **only locally and in
short-path agent worktrees**.
## The two readings — pristine `main`, same commit, same command
Both worktrees at `origin/main` `7e58d177e`, both after `pnpm install`
and the same `pnpm --workspace-concurrency=2 --filter
'@objectstack/lint^...' build` dependency-closure build. Nothing but the
checkout path differs.
| worktree | `packages/lint/src` | `pnpm --filter @objectstack/lint
typecheck` |
|:---|---:|:---|
| `/home/user/objectstack-issue-17739` | 52 chars | **exit 0** — `OK, 2
file(s) / 6 error(s) / 2 pinned signature(s)` |
| `/tmp/w9` | 25 chars | **exit 1** — `4 problem(s)` |
The short-path run reports the **same six already-ledgered TS6059
errors** as both ARRIVED (`... 'rootDir' '/tmp/w9/packages/lint/src'` —
a signature the ledger does not record) and VANISHED (`... 'rootDir'
'...'` — the recorded one absent), and its ARRIVED text instructs the
reader to go repair errors that are not new. The card's headline is
therefore **confirmed**, and confirmed on today's `main`, not only on
the `431c757120` it was filed against.
It also contradicted the function's own docblock: an absolute checkout
path is not "a name, a dotted path, or a small literal type", and it
changes when the **checkout** moves rather than when the error does.
## The repair
A quoted span that is a filesystem-absolute path (POSIX, Windows drive
letter with either separator, UNC) now collapses **at every length** —
which is exactly what a long-path checkout already did. The change is
one added condition plus its detector, `isAbsolutePathSpan`, exported so
the self-test can prove it still reaches its subject.
**Rejected alternative: relativising the path against the repo root.**
It buys a sharper signature, but it re-spells the signature every
existing ledger already holds in its collapsed form — churning a
maintainer-gated shrink-only ratchet for discrimination no ledger has
ever recorded. Measured across all 19 `test-typecheck-debt.json` ledgers
**before** choosing: quoted spans beginning with `/` = **0**,
drive-letter spans = **0**, against **89** capital-initial spans as the
firing control.
**Effect on the existing ledger: none, measured two ways.**
1. By construction, from the counts above: no recorded signature
contains an absolute-path span, so none can be re-spelled.
2. Empirically, at the final head with the fix in place: `pnpm -r run
check:test-typecheck` over **every** package that wires this gate — **36
packages, 36 `OK` lines, 0 problems**, and `git status --porcelain`
empty afterwards (no ledger rewritten). Same lint verdict before and
after the fix on the long path: `2 file(s) / 6 error(s) / 2 pinned
signature(s)`.
This does **not** weaken the gate: it collapses only what a long path
already collapsed, so no discrimination any ledger or CI has ever relied
on is given up. An over-masking control in the self-test pins that short
**non**-path spans (a relative specifier, a named type) still survive
verbatim.
## Tests
A sixth self-test battery, `Path-independence: a signature must not move
with the CHECKOUT (#17739)`, floor 7, registered in
`SELF_TEST_BATTERIES` with `SELF_TEST_BATTERY_FLOOR` raised 5 to 6. It
pins the property (one signature from two checkout paths), a control
that the fixture really straddles the 32-character threshold the defect
lived on, a zero-churn pin against the spelling the ledgers hold today,
the Windows spellings, an over-masking control, and a
detector-discrimination control.
**Ablation** (one-shot, on the throwaway short-path worktree whose copy
was proved byte-identical to this branch's blob
`76c65e7e41f1230139d7cbdbdf48ce09a3f0b462` before the mutation, and
restored to that same hash after):
| leg | on-disk proof | result |
|:---|:---|:---|
| remove ` && !isAbsolutePathSpan(inner)` | marker count 1 to 0, blob
hash `76c65e7e` to `d568fd90` | self-test **exit 1**, 3 named `#17739`
failures — the property, the zero-churn pin, the Windows pin; `pnpm
--filter @objectstack/lint typecheck` **exit 1** |
| restore from the branch | `git hash-object` back to
`76c65e7e41f1230139d7cbdbdf48ce09a3f0b462` | self-test exit 0 |
The ablated property failure prints the defect verbatim: `got "TS6059:
File '...' is not under 'rootDir' '/tmp/w9/packages/lint/src'. ..." vs
"TS6059: File '...' is not under 'rootDir' '...'. ..."`.
## Gates
`node scripts/pm/dispatch-gates.mjs --commands` derived **27** families
for this one-path diff; all 27 run at the final head `f7c116ae2`, all
**exit 0**, reconciled with `--ran` carrying exit codes: *27 derived, 27
run, 0 NOT-MEASURED (a DERIVED zero)*. `check:type-check-debt` first
returned **exit 3 (PREREQUISITE NOT MET)** twice — first for an unbuilt
workspace closure, then for an OOM under the caller's own
`--max-old-space-size=4096`, which is tighter than the CI-shaped 6144
ceiling that gate pins. Neither exit 3 is recorded as a measurement; the
closure was built (`turbo run build`, 72/72) and the gate re-run at 8192
to a real **exit 0**.
Also run: the self-test through `tsx` directly, `eslint` on the changed
file (0 errors / 0 warnings over 1 file, read from `--format json`;
`eslint.config.mjs` enables no type-aware linting, so a one-file diff in
`scripts/` cannot move any untouched file's verdict), and a manual
control-character sweep over the changed file (0 hits) beside
`check:nul-bytes`.
## Changeset
`skip-changeset`. Measured on the publish surface rather than asserted:
all **70** non-private packages declare `files[]`, the distinct entries
are `CHANGELOG.md README.md api-surface dist json-schema liveness
llms.txt prompts spec-changes.json src/**/*.zod.ts`, **0** of them
escape their own package directory, and grepping the 69 built `dist`
trees for the changed symbols gives `isAbsolutePathSpan` **0** and
`STABLE_QUOTED_SPAN` **0** against a positive control of `ObjectKernel`
**106**. The repo-root `scripts/` directory publishes nothing.
## Acceptance notes
- `noted, not filed:` the card's declared scope limit still stands —
only TS6059 was measured to carry an absolute path in a quoted span, and
no sweep of other diagnostic codes was done. The repair does not need
one (it is keyed on the span's shape, not on the TS code), so this is an
observation rather than a finding. Carrier: whoever next reads
`normalizeMessage`.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
https://claude.ai/code/session_017ef78bLdybu3AffehKkhfk
---
_Generated by [Claude
Code](https://claude.ai/code/session_017ef78bLdybu3AffehKkhfk)_
Co-authored-by: Claude <noreply@anthropic.com>1 parent fffe3e5 commit 08a6c7a
1 file changed
Lines changed: 143 additions & 5 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
315 | 315 | | |
316 | 316 | | |
317 | 317 | | |
| 318 | + | |
| 319 | + | |
| 320 | + | |
| 321 | + | |
| 322 | + | |
| 323 | + | |
| 324 | + | |
| 325 | + | |
| 326 | + | |
| 327 | + | |
| 328 | + | |
| 329 | + | |
| 330 | + | |
| 331 | + | |
| 332 | + | |
| 333 | + | |
| 334 | + | |
| 335 | + | |
| 336 | + | |
| 337 | + | |
| 338 | + | |
| 339 | + | |
| 340 | + | |
| 341 | + | |
| 342 | + | |
| 343 | + | |
| 344 | + | |
| 345 | + | |
| 346 | + | |
| 347 | + | |
| 348 | + | |
| 349 | + | |
| 350 | + | |
| 351 | + | |
| 352 | + | |
| 353 | + | |
| 354 | + | |
| 355 | + | |
| 356 | + | |
| 357 | + | |
| 358 | + | |
| 359 | + | |
| 360 | + | |
| 361 | + | |
| 362 | + | |
| 363 | + | |
| 364 | + | |
| 365 | + | |
| 366 | + | |
| 367 | + | |
| 368 | + | |
| 369 | + | |
318 | 370 | | |
319 | 371 | | |
320 | 372 | | |
321 | | - | |
322 | | - | |
| 373 | + | |
| 374 | + | |
| 375 | + | |
323 | 376 | | |
324 | 377 | | |
325 | 378 | | |
326 | 379 | | |
327 | | - | |
| 380 | + | |
| 381 | + | |
| 382 | + | |
328 | 383 | | |
329 | 384 | | |
330 | 385 | | |
| |||
538 | 593 | | |
539 | 594 | | |
540 | 595 | | |
| 596 | + | |
541 | 597 | | |
542 | 598 | | |
543 | 599 | | |
544 | 600 | | |
545 | | - | |
| 601 | + | |
546 | 602 | | |
547 | 603 | | |
548 | 604 | | |
| |||
759 | 815 | | |
760 | 816 | | |
761 | 817 | | |
| 818 | + | |
| 819 | + | |
| 820 | + | |
| 821 | + | |
| 822 | + | |
| 823 | + | |
| 824 | + | |
| 825 | + | |
| 826 | + | |
| 827 | + | |
| 828 | + | |
| 829 | + | |
| 830 | + | |
| 831 | + | |
| 832 | + | |
| 833 | + | |
| 834 | + | |
| 835 | + | |
| 836 | + | |
| 837 | + | |
| 838 | + | |
| 839 | + | |
| 840 | + | |
| 841 | + | |
| 842 | + | |
| 843 | + | |
| 844 | + | |
| 845 | + | |
| 846 | + | |
| 847 | + | |
| 848 | + | |
| 849 | + | |
| 850 | + | |
| 851 | + | |
| 852 | + | |
| 853 | + | |
| 854 | + | |
| 855 | + | |
| 856 | + | |
| 857 | + | |
| 858 | + | |
| 859 | + | |
| 860 | + | |
| 861 | + | |
| 862 | + | |
| 863 | + | |
| 864 | + | |
| 865 | + | |
| 866 | + | |
| 867 | + | |
| 868 | + | |
| 869 | + | |
| 870 | + | |
| 871 | + | |
| 872 | + | |
| 873 | + | |
| 874 | + | |
| 875 | + | |
| 876 | + | |
| 877 | + | |
| 878 | + | |
| 879 | + | |
| 880 | + | |
| 881 | + | |
| 882 | + | |
| 883 | + | |
| 884 | + | |
| 885 | + | |
| 886 | + | |
| 887 | + | |
| 888 | + | |
| 889 | + | |
| 890 | + | |
| 891 | + | |
| 892 | + | |
| 893 | + | |
| 894 | + | |
| 895 | + | |
| 896 | + | |
| 897 | + | |
762 | 898 | | |
763 | 899 | | |
764 | 900 | | |
| |||
1068 | 1204 | | |
1069 | 1205 | | |
1070 | 1206 | | |
1071 | | - | |
| 1207 | + | |
| 1208 | + | |
| 1209 | + | |
1072 | 1210 | | |
1073 | 1211 | | |
1074 | 1212 | | |
| |||
0 commit comments