Commit a7bafc2
fix(pm): check-widening-tells' parameter decline survives a hunk's leading context (#18804)
Fixes #18721
Clause-②: no
## The defect
`scripts/pm/check-widening-tells.mjs` read `+ ctx: z.RefinementCtx,` —
the second parameter of an exported object-level refinement, this repo's
own prescribed `#16489` signature — as "a new key on a Zod object
schema", although #17618's parameter decline for exactly that line
already existed and was pinned. Every PR that adds an object-level
refusal therefore earned a free T1 and a C5 / exit 4 on `--pair`, and
the cheapest remedy that clears it — re-declaring `Clause-②: yes` — is
the wrong one: such a diff tightens the accept set, it does not widen
it. The instrument read the tightening direction as the widening one,
which is the inverse of what clause 2 exists to catch.
## The two before-readings, re-derived rather than inherited
Both taken on this worktree's base, `origin/main` `7572329069` (the card
measured `94b3f37be`; the defect is unchanged on the newer tip).
FALSE POSITIVE — the card's own probe, rebuilt from git:
```
git diff 72dd95f..09e16a5 -- packages/spec/src/ui/dashboard.zod.ts > d.patch # 202 lines, ONE hunk
node scripts/pm/check-widening-tells.mjs --declaration no --diff d.patch
-> exit 4
T1 packages/spec/src/ui/dashboard.zod.ts:628 - a new key on a Zod object schema
+ ctx: z.RefinementCtx,
```
TRUE-POSITIVE CONTROL — same matcher, same run, same real file path,
built as a real `git diff` in the worktree and then restored with `git
checkout HEAD -- packages/spec/src/ui/dashboard.zod.ts`:
```
+ brandNewAuthorableKey: z.string().optional(), # added to DashboardWidgetSchema
node scripts/pm/check-widening-tells.mjs --declaration no --diff control.patch
-> exit 4
T1 packages/spec/src/ui/dashboard.zod.ts:701 - a new key on a Zod object schema
```
The matcher fires on a real new key. So the first reading is a FALSE
POSITIVE, not a dead instrument. The filing card's own first control
read 0 and was its own mis-build (a synthetic path off the declared
surface is judged by nothing); this one sits on the path the probe was
taken from.
## The WHY, measured — and the line that proves it
`enclosingDelimiter` walks from the first line of the line's own hunk
and abandoned the walk, answering `null`, the first time a closer
arrived with an empty stack. `null` is what both callers read as "keep
the tell firing".
A real hunk opens on CONTEXT lines. This one's are the tail of the
previous declaration. Instrumented over the real patch, the new-side
reading is:
```
lines index of ctx: 110 newFile index: 110 new-file line: 628 hunk: 0
inParameterList -> false
enclosingDelimiter -> null
hunk start newFile index: 0
newFile[0] kind=context text=" });" # THE PROVING LINE: two closers, no opener above them
newFile[1] kind=context text="}"
newFile[2] kind=context text=""
function head newFile index: 108 "export function checkDashboardWidgetMetricMeasureArity("
enclosingDelimiter from a window starting at the head -> {"opener":"(","head":"export function checkDashboardWidgetMetricMeasureArity"}
```
The `)` on patch line 6 — the hunk's FIRST line, a context line —
underflows a stack that has seen no opener, and the reading was over 108
lines before the hunk reached the `export function ...(` head it went on
to show. Neither the 202-line hunk length, nor the object-literal type
on the first parameter (`widget: { id?: unknown; ... },`, whose braces
close on their own line), nor the distance to the head is the cause: the
single branch is the underflow `return null`. The three-line synthetic
the existing pin drives carries no context line at all, so that pin
stayed green through every real diff it was written to protect.
## The repair — route A's shape, at that branch
An underflow now DROPS the closer and the walk continues:
```js
if (ch === ')' || ch === ']' || ch === '}') {
// #18721 - UNDERFLOW: this closes an opener the hunk never showed. Drop it
// and keep walking. ...
if (stack.length === 0) continue;
stack.pop();
}
```
The argument is a stack one, and it is why this does not loosen the `no`
criterion: everything a hunk opens is strictly INSIDE everything it did
not show, so the shown stack is a SUFFIX of the real one and its top —
whenever it has one — IS the innermost open delimiter, whatever sits
below. An empty shown stack still answers `null`, so the reading stays
positive-evidence-only: the answer is always an opener this hunk showed,
never one inferred from a closer.
Not route B. A `z.RefinementCtx` type-name exception is walked past by
one differently-named parameter type, and it would leave the same branch
broken for every other parameter shape.
`SCHEMA_PROPERTY_FORMS` is untouched and still 10 rows. #18560 / PR
#18700 and #18702 / PR #18750 are the false-NEGATIVE direction on this
same matcher; their rows, fixtures and batteries are untouched, and
their headers' words are the ones this round's header section uses.
## The pins — one battery, both directions
New battery `#18721 - a hunk's LEADING CONTEXT is not a reason to
abandon the parameter reading`, 14 cases, registered in the roster at
14:
- THE FINDING: PR #18720's own hunk, reduced only as far as the failing
branch requires (the leading context that closes the previous
declaration, the function head, the object-literal-typed first
parameter, the `ctx` line) at the line the card reported — T1 silent,
and the whole verdict CLEAN.
- the line number is asserted from the fixture itself, so the fixture is
the probe and not merely a shape like it.
- the object-literal type on the first parameter is pinned as NOT the
confusing element.
- TRUE-POSITIVE CONTROL on the same file: `+ brandNewAuthorableKey:
z.string().optional(),` FIRES, at
`packages/spec/src/ui/dashboard.zod.ts:701`.
- a new key behind the SAME underflowing context still tells (no opener
shown, so no positive evidence).
- a real key added AFTER the parameter list closes still tells,
underflowing context and all, and the row reported is the shape member.
- the branch itself: an opener shown after an underflow is the answer;
an underflow with no opener after it is still `null`; the drop does not
leak past the parameter list's own close; no reading crosses a hunk
boundary.
- the OLD side: #17618 reads the same decline on the removed side, so a
removed parameter behind leading context now buys no #16943 budget — and
a genuine key added in the same block, which that phantom budget used to
pay for, FIRES. One repair, one false positive closed and one false
negative with it.
#17618's existing pin in the `#18560` battery (`#17618's parameter
decline is untouched by the wider vocabulary`) and its own battery's
underflow pin are byte-unchanged and green.
## Self-test
```
node scripts/pm/check-widening-tells.mjs --self-test
-> exit 0 :: 473 cases pass (459 before this round, + the 14 new)
```
Every case that fires today keeps firing: the whole suite was run, no
pre-existing case changed its verdict, and the header section records
the direction in both halves.
## Ablation, from the committed fix
Reverted the branch on disk (`continue` back to `return null`), proved
the mutation landed by blob hash and by anchor counts, ran the suite,
restored under a `trap` and verified the restore by hash. There is no
build step and no `dist/` for a `scripts/pm/*.mjs` file, so the on-disk
proof is the hash plus the anchor counts.
```
HEAD blob : 025f8e5
anchor counts : removed-text 1 -> 0 ; injected-text 0 -> 1
mutated blob : 9459620c63e6590b7e2c81a0c3a2a9a0cbb7020c
VERDICT --self-test under the ablation: exit 1 -> 6 of 473 cases failed
VERDICT probe under the ablation: exit 4 -> T1 back at dashboard.zod.ts:628
VERDICT true-positive control under the ablation: exit 4
restored blob : 025f8e5 (== HEAD blob)
git diff HEAD : 0 line(s)
```
All 6 failures are in the new `#18721` battery and nothing pre-existing
reds:
```
THE FINDING - PR #18720's real hunk ... reads NO tell
...and the whole verdict is CLEAN ...
CONTROL - a real key added AFTER the parameter list closes still tells, underflowing context and all
an opener the hunk shows AFTER an underflow is the answer ...
the OLD side moves too - a REMOVED parameter behind leading context ... buys no budget
...and the row that fires is the genuine new key the phantom budget used to pay for
```
An earlier ablation attempt was a NO-OP (`perl` with a double-escaped
pattern, anchor counts `1 -> 1`, blob unchanged): the script's own guard
refused it and exited non-zero rather than reporting a reading. The run
above is the one that landed.
## Gates
Derived from this worktree with `node scripts/pm/dispatch-gates.mjs
--commands --repo objectstack-ai/objectstack` (no hand-fed path list;
change set `scripts/pm/check-widening-tells.mjs`, 1 path vs merge base
`757232906`) — 29 families, all run, all exit 0. Reconciled with
`--ran`: `29 derived, 29 run, 0 NOT-MEASURED, 0 UNRUN` (a DERIVED zero —
every row carries its exit code).
```
node scripts/check-ci-filter-parity.mjs :: exit 0
node scripts/check-closing-keyword-parity.mjs :: exit 0
node scripts/check-closing-keyword-parity.mjs --self-test :: exit 0
node scripts/check-comment-mask-corpus.mjs :: exit 0
node scripts/check-declaration-mirrors.mjs :: exit 0
node scripts/check-declaration-mirrors.mjs --self-test :: exit 0
node scripts/check-scripts-symbol-anchors.mjs :: exit 0
node scripts/check-scripts-symbol-anchors.mjs --self-test :: exit 0
node scripts/check-self-test-wired.mjs :: exit 0
node scripts/check-self-test-wired.mjs --self-test :: exit 0
node scripts/check-self-test-workflow-commands.mjs :: exit 0
node scripts/check-self-test-workflow-commands.mjs --self-test :: exit 0
node scripts/check-whole-set-label-write.mjs :: exit 0
node scripts/check-whole-set-label-write.mjs --self-test :: exit 0
node scripts/pm/bare-root-worklist.mjs --self-test :: exit 0
pnpm check:agent-test-spelling :: exit 0
pnpm check:bash32-floor :: exit 0
pnpm check:cli-command-ids :: exit 0
pnpm check:cross-package-test-inputs :: exit 0
pnpm check:driver-memory-census :: exit 0
pnpm check:entry-guard :: exit 0
pnpm check:nul-bytes :: exit 0
pnpm check:parse-guard :: exit 0
pnpm check:pm-dispatch-gates :: exit 0 (detached, 742.1s, 1809 cases)
pnpm check:pm-widening-tells :: exit 0
pnpm check:pnpm-filter-targets :: exit 0
pnpm check:ratchet-remedy-authority :: exit 0
pnpm check:refd-timer-probe :: exit 0
pnpm check:watch-hint-literal :: exit 0
pnpm check:pm-clause2-carriers (consumes this file's verdict) :: exit 0 (838 cases)
pnpm lint (repo-wide, eslint . --no-inline-config) :: exit 0
```
`check-scripts-symbol-anchors` was the one red in the first sweep — the
header section cited `path:628`, and a line number is not an anchor
form. Rewritten to the symbol anchor
`packages/spec/src/ui/dashboard.zod.ts#checkDashboardWidgetMetricMeasureArity`;
green on re-run, and the line numbers that carry evidence stayed, in
prose.
`pnpm lint` is the repo-wide run at this PR's final commit, not a
narrowing.
No changeset: `scripts/pm/**` publishes nothing from any released
package — `skip-changeset`.
---
_Generated by [Claude
Code](https://claude.ai/code/session_01Gqi43smmqjJ5sUrhfoPeKu)_
Co-authored-by: Claude <noreply@anthropic.com>1 parent e7eb4e9 commit a7bafc2
1 file changed
Lines changed: 175 additions & 5 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
295 | 295 | | |
296 | 296 | | |
297 | 297 | | |
| 298 | + | |
| 299 | + | |
| 300 | + | |
298 | 301 | | |
299 | 302 | | |
300 | 303 | | |
| |||
857 | 860 | | |
858 | 861 | | |
859 | 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 | + | |
| 898 | + | |
| 899 | + | |
| 900 | + | |
| 901 | + | |
| 902 | + | |
| 903 | + | |
| 904 | + | |
| 905 | + | |
| 906 | + | |
| 907 | + | |
| 908 | + | |
| 909 | + | |
| 910 | + | |
| 911 | + | |
860 | 912 | | |
861 | 913 | | |
862 | 914 | | |
| |||
1138 | 1190 | | |
1139 | 1191 | | |
1140 | 1192 | | |
| 1193 | + | |
1141 | 1194 | | |
1142 | 1195 | | |
1143 | 1196 | | |
| |||
1942 | 1995 | | |
1943 | 1996 | | |
1944 | 1997 | | |
1945 | | - | |
1946 | | - | |
1947 | | - | |
1948 | | - | |
| 1998 | + | |
| 1999 | + | |
| 2000 | + | |
| 2001 | + | |
| 2002 | + | |
| 2003 | + | |
| 2004 | + | |
| 2005 | + | |
| 2006 | + | |
| 2007 | + | |
| 2008 | + | |
| 2009 | + | |
| 2010 | + | |
| 2011 | + | |
| 2012 | + | |
| 2013 | + | |
| 2014 | + | |
1949 | 2015 | | |
1950 | 2016 | | |
1951 | 2017 | | |
| |||
1982 | 2048 | | |
1983 | 2049 | | |
1984 | 2050 | | |
1985 | | - | |
| 2051 | + | |
| 2052 | + | |
| 2053 | + | |
| 2054 | + | |
| 2055 | + | |
| 2056 | + | |
1986 | 2057 | | |
1987 | 2058 | | |
1988 | 2059 | | |
| |||
5213 | 5284 | | |
5214 | 5285 | | |
5215 | 5286 | | |
| 5287 | + | |
| 5288 | + | |
| 5289 | + | |
| 5290 | + | |
| 5291 | + | |
| 5292 | + | |
| 5293 | + | |
| 5294 | + | |
| 5295 | + | |
| 5296 | + | |
| 5297 | + | |
| 5298 | + | |
| 5299 | + | |
| 5300 | + | |
| 5301 | + | |
| 5302 | + | |
| 5303 | + | |
| 5304 | + | |
| 5305 | + | |
| 5306 | + | |
| 5307 | + | |
| 5308 | + | |
| 5309 | + | |
| 5310 | + | |
| 5311 | + | |
| 5312 | + | |
| 5313 | + | |
| 5314 | + | |
| 5315 | + | |
| 5316 | + | |
| 5317 | + | |
| 5318 | + | |
| 5319 | + | |
| 5320 | + | |
| 5321 | + | |
| 5322 | + | |
| 5323 | + | |
| 5324 | + | |
| 5325 | + | |
| 5326 | + | |
| 5327 | + | |
| 5328 | + | |
| 5329 | + | |
| 5330 | + | |
| 5331 | + | |
| 5332 | + | |
| 5333 | + | |
| 5334 | + | |
| 5335 | + | |
| 5336 | + | |
| 5337 | + | |
| 5338 | + | |
| 5339 | + | |
| 5340 | + | |
| 5341 | + | |
| 5342 | + | |
| 5343 | + | |
| 5344 | + | |
| 5345 | + | |
| 5346 | + | |
| 5347 | + | |
| 5348 | + | |
| 5349 | + | |
| 5350 | + | |
| 5351 | + | |
| 5352 | + | |
| 5353 | + | |
| 5354 | + | |
| 5355 | + | |
| 5356 | + | |
| 5357 | + | |
| 5358 | + | |
| 5359 | + | |
| 5360 | + | |
| 5361 | + | |
| 5362 | + | |
| 5363 | + | |
| 5364 | + | |
| 5365 | + | |
| 5366 | + | |
| 5367 | + | |
| 5368 | + | |
| 5369 | + | |
| 5370 | + | |
| 5371 | + | |
| 5372 | + | |
| 5373 | + | |
| 5374 | + | |
| 5375 | + | |
| 5376 | + | |
| 5377 | + | |
| 5378 | + | |
| 5379 | + | |
| 5380 | + | |
| 5381 | + | |
| 5382 | + | |
| 5383 | + | |
| 5384 | + | |
5216 | 5385 | | |
5217 | 5386 | | |
5218 | 5387 | | |
| |||
5268 | 5437 | | |
5269 | 5438 | | |
5270 | 5439 | | |
| 5440 | + | |
5271 | 5441 | | |
5272 | 5442 | | |
5273 | 5443 | | |
| |||
0 commit comments