Repository navigation
Commit cb0c616
fix(pm): stop T1 reading a typed parameter, and a re-spelled closed set, as a new key (#17760)
Fixes #17618
## What was wrong
`check-widening-tells`' **T1** tell reads an added line shaped
`identifier: z.Something` on the contract source surface as *"a new key
on a Zod object schema — the accept set gains a spelling an author may
now write"*. Two live pairs raised it against diffs that spell nothing
new. Both were reproduced from the PRs' own pushed bytes **before**
anything was written.
**Instance 1 — PR #17616
(`packages/spec/src/ui/dashboard.zod.ts:470`).** The row fires on `ctx:
z.RefinementCtx,`, the second **parameter** of an exported object-level
refinement. `z.RefinementCtx` is a type; nothing constructs a shape
there, and the diff it appears on *refuses* metadata that parses today.
That signature is this repo's own prescribed one (the `#16489`
convention — `checkListViewPageMount`, `checkPageSourceCompleteness`,
`checkGlobalFilterDateDefaultValue`), so **every** diff adding a
cross-field refusal raised a widening tell for the refusal itself.
**Instance 2 — PR #17638
(`packages/spec/src/system/cache.zod.ts:197`).** The row fires on
`strategy: z.enum(['eager', 'lazy'], {`, an in-shape key the same change
block removed as `strategy: z.enum(['eager', 'lazy',
'scheduled']).default('lazy')`. Same key, one member **fewer**. That
pair declares `Clause-②: no`, so it exited **4** and held a reviewed,
green retirement PR out of the queue, where the only sanctioned clear is
the false `yes` the file already refuses to ask an author for.
## The measurement that picked the fix
Instance 2 is **not** the #16943 net-delta budget being too thin. The
budget was *earned* — the removed `strategy:` line is itself T1-shaped
and bought one T1 unit — and then refused at the **spend**, by
`!CLOSED_SET_OPENER.test(text)`: "a line that DECLARES a closed set is
never spent against the budget". That clause was written about an
**opener**, and an opener never reaches it: `memberTellKind` already
answers `null` for an opener-only line. So the only lines it ever caught
were **keys** whose value opens `z.enum(` / `z.union(` /
`z.discriminatedUnion(` / `z.literal(`. Measured on this tree with the
pre-change reader:
```
field: z.string() -> field: z.string().optional() 0 row(s) (declines)
kind: z.enum(['a']) -> kind: z.enum(['a']).optional() 1 row (fires)
```
The asymmetry was accidental.
## What changed (`scripts/pm/check-widening-tells.mjs`, one file)
**The spend.** What the blanket refusal was protecting is real and is
kept: an **inline** set has no per-member line for T2 to read, so a set
widened in place is visible on the T1 row and nowhere else. A
closed-set-valued key may now spend the budget only on three facts the
**block** carries — a removed line naming the **same key**, both member
lists readable on their own line, and the added list a **subset** of the
removed one. `z.enum(['a', 'b'])` to `z.enum(['a', 'b', 'c'])` still
fires; a list that opens on a later line is unreadable and still fires;
another key's removal pays nothing.
**The parameter.** The claim is deliberately smaller than the
depth-aware `z.object({ … })` reader T1's own comment refuses — the one
whose cheap version fails GREEN by truncating. A Zod shape body is
`{`-delimited **by construction**, so the question is never "which shape
is this line in" but "which bracket is innermost", read over the line's
**own hunk**, plus a declaration head left of that paren. Every state
the reader cannot carry honestly answers `null` — *keep the tell
firing*: a closer arriving on an empty stack (the hunk began inside
something it was never shown), a string literal that does not close on
its line, a declaration head it does not recognise (a method shorthand,
for one). Nothing it returns ever means "no longer inside a shape",
which is why it has no truncating failure mode. The same reading is
applied on the **old** side too, the way #16822's fragment rule is: a
deleted parameter must not buy an added key the right to go unreported.
## Price of the change, measured
Over the **233 commits** touching these surfaces in this tree's history,
A/B'd row-for-row between the pre-change reader (`origin/main`
`e9efc403`) and this one:
| | rows |
| --- | --- |
| tell rows the previous reading raises | 20,193 |
| the new reading raises | 20,170 |
| **now decline** | **23** — all T1; no T2, T3 or T4 row moves |
Of the 23: **15 are parameters** (twelve `ctx: z.RefinementCtx` /
`z.core.$RefinementCtx`, three `input: z.input[typeof SomeSchema]`) and
**8 are existing keys re-spelled** to carry `.meta({ title })` or a
rewritten `.describe()` around an identical enum (e.g. `d2badf723`,
`f502898a4`). Not one is a key or a member its diff added. On the tree
itself, **16 of 8,974** T1-shaped lines under `packages/spec/src/**` sit
inside a parameter list, 10 of them annotated `z.RefinementCtx`.
The file's header prices this in the register it already uses, and
states both quiet directions rather than leaving them to be discovered:
1. A parameter **added to an already-exported** function is a signature
widening and now goes unreported here; nothing else in the file catches
it (T3's listing records that an export EXISTS, and
`api-surface-signatures.json` carries 27 `define*` helpers, none of them
one of these checks). What is not lost is the function itself — a newly
exported check adds its own T3 row, which is why #17616 still reports
one.
2. A widening carried by the **chained methods** rather than the member
list (`.optional()` first among them) now declines on a
closed-set-valued key. Not a new class: #16943 already declines it for
every key whose value is not a closed set; this removes the accidental
exception rather than adding one. In the measured population all eight
re-spellings are `.meta` / `.describe` rewrites and none adds
`.optional()`.
## Acceptance (grading comment 5642818369)
| item | before | after |
| --- | --- | --- |
| the parameter-list line reads no tell | T1 at `dashboard.zod.ts:470` |
no row |
| the in-shape re-spelling with a shrinking set reads no tell | T1 at
`cache.zod.ts:197` | no row |
| a genuinely new key on a shape STILL tells (control) |
`manifest.zod.ts:44` | `manifest.zod.ts:44` |
| the `#16489` signature as a named fixture | — |
`FILE_REFINEMENT_SIGNATURE` in `--self-test` |
| `--pair 17638` with its `Clause-②: no` unchanged | **exit 4**, one T1
row | **exit 0**, no tell |
```
$ node scripts/pm/check-clause2-carriers.mjs --pair 17638
check-clause2-carriers: PR #17638 / card #17157 — the clause-② declaration is readable in the fixed
spelling and both carriers agree, and its diff carries no widening tell. (exit 0)
```
1 parent 46dac81 commit cb0c616
1 file changed
Lines changed: 431 additions & 3 deletions
0 commit comments