Skip to content

Commit 7dd97e2

Browse files
committed
fix(pm): check-widening-tells' parameter decline survives a hunk's leading context
`enclosingDelimiter` abandoned its walk — answering `null`, which every caller reads as "keep the tell firing" — the first time a closer arrived with an empty stack. A real hunk opens on CONTEXT lines, and on this repo's spec files that context is the tail of the previous declaration (` });`), so #17618's parameter decline was over before the hunk reached the `export function …(` head it went on to show. Every PR adding an object-level refinement earned a false T1 on `ctx: z.RefinementCtx,` and a C5 / exit 4 on `--pair`, whose cheapest repair — re-declaring `yes` — is the wrong one. An underflow now DROPS the closer and the walk continues. The openers a hunk shows are strictly inside the ones it did not, so the shown stack is a suffix of the real one and its top IS the innermost open delimiter; an empty shown stack still answers `null`, so the reading stays positive-evidence-only. Claude-Session: https://claude.ai/code/session_01Gqi43smmqjJ5sUrhfoPeKu Co-authored-by: Claude <noreply@anthropic.com>
1 parent 7572329 commit 7dd97e2

1 file changed

Lines changed: 175 additions & 5 deletions

File tree

‎scripts/pm/check-widening-tells.mjs‎

Lines changed: 175 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -295,6 +295,9 @@
295295
* EVERY diff that adds a cross-field refusal raised a widening tell for the
296296
* refusal itself: the instrument read the tightening direction as the
297297
* widening one, which is the inverse of what clause ② exists to catch.
298+
* ⚠️ …and the decline written here did NOT reach a real diff of that shape
299+
* until #18721 below: it was abandoned on the hunk's LEADING CONTEXT, and
300+
* the synthetic hunk that pins it carries none.
298301
* - PR #17638 — `+ strategy: z.enum(['eager', 'lazy'], {`, an in-shape key
299302
* the same change block removed as `- strategy: z.enum(['eager', 'lazy',
300303
* 'scheduled']).default('lazy')`. The same key, one member FEWER. This one
@@ -857,6 +860,55 @@
857860
* fact about where it is DECLARED; whether it belongs to EITHER is a fact about
858861
* what it RETURNS. Two questions, measured separately, ⛔ never one heuristic.
859862
*
863+
* ## The eleventh accidental variable #18721 removed — a hunk's LEADING
864+
* CONTEXT
865+
*
866+
* #17618 taught T1 that a typed PARAMETER is not a key on a shape, and this
867+
* file's header names PR #17616's `+ ctx: z.RefinementCtx,` — the SECOND
868+
* PARAMETER of an exported object-level refinement, this repo's own prescribed
869+
* `#16489` signature — as the measured case that decline was written for. The
870+
* decline was real and it was pinned. It still never fired on a real diff.
871+
*
872+
* ⭐ The variable is WHERE THE HUNK STARTS. `enclosingDelimiter` walks from the
873+
* first line of the line's own hunk, and it abandoned the walk — answering
874+
* `null`, which every caller reads as "keep the tell firing" — the first time a
875+
* closer arrived with an empty stack. A real hunk opens on CONTEXT lines, and
876+
* on this repo's spec files that context is the tail of the previous
877+
* declaration: ` });`. Two closers, no opener above them, and the reading was
878+
* over before the hunk reached the `export function …(` head it went on to show
879+
* 108 lines later. The three-line synthetic the pin drives
880+
* (`+export const refine = (\n+ ctx: z.RefinementCtx,\n+) => ctx;`) has no
881+
* context line at all, so the pin stayed green through every diff it was
882+
* written to protect.
883+
*
884+
* ⭐ RE-DERIVED here rather than inherited from the card: `git diff
885+
* 72dd95fa5a..09e16a5745 -- packages/spec/src/ui/dashboard.zod.ts` (PR #18720's
886+
* own hunk, 202 lines, ONE hunk) with `--declaration no` exited 4 on a T1 row
887+
* against the `ctx` parameter of
888+
* `packages/spec/src/ui/dashboard.zod.ts#checkDashboardWidgetMetricMeasureArity`
889+
* — new-file line 628 — and the same file's true-positive control,
890+
* `+ brandNewAuthorableKey: z.string().optional(),` added to
891+
* `#DashboardWidgetSchema` as a real `git diff`, fired on line 701 of the same
892+
* run of the same matcher. ⇒ a FALSE POSITIVE, ⛔ not a dead instrument. The
893+
* proving line is the hunk's own first line, ` });`, a CONTEXT line.
894+
*
895+
* ⛔ The repair is at that branch and nowhere else — ⛔ NOT a `z.RefinementCtx`
896+
* type-name exception, which one differently-named parameter type walks past.
897+
* An underflow DROPS the closer and the walk continues. The argument is a stack
898+
* one: everything the hunk opens is strictly INSIDE everything it did not show,
899+
* so the shown stack is a SUFFIX of the real one and its top — whenever it has
900+
* one — IS the innermost open delimiter, whatever sits below. An empty shown
901+
* stack still answers `null`, so the reading remains positive evidence only.
902+
*
903+
* ⭐ The direction is provable both ways, and BOTH are pinned. On the added
904+
* side the decline reaches diffs it never reached, which is the false positive
905+
* this round removes. On the REMOVED side — where #17618 reads the same decline
906+
* so a deleted parameter cannot buy an added key the right to go unreported —
907+
* it makes a phantom #16943 budget disappear: a block that removes a parameter
908+
* behind leading context and adds a genuine key now FIRES on that key, where it
909+
* was silent before. One repair, one false positive closed and one false
910+
* negative with it.
911+
*
860912
* ## The remedy with no reader — #17848, and a pin the shape never had
861913
*
862914
* #17848 filed two halves against this family. Re-measuring both on the tree
@@ -1138,6 +1190,7 @@ const SELF_TEST_BATTERIES = Object.freeze({
11381190
'#18560 — the declaring vocabulary is a NAMED list, every form pinned by a counterfactual fixture': 32,
11391191
'#18640 — an inline closed set RE-SPELLED at the same binding is not a set that gained a value': 20,
11401192
'#18702 — a declaring factory PRIVATE to one file, resolved through its own DEFINITION': 54,
1193+
"#18721 — a hunk's LEADING CONTEXT is not a reason to abandon the parameter reading": 14,
11411194
});
11421195

11431196
// DELETING an entry silences that battery's floor exactly as effectively as
@@ -1942,10 +1995,23 @@ function topLevelMembers(s, open, close) {
19421995
*
19431996
* ⭐ Positive evidence only, and `null` is the whole safety property. The scan
19441997
* starts at the first line of the line's OWN hunk, so a construct opened before
1945-
* the hunk is never guessed at: a closer arriving with an empty stack means the
1946-
* hunk began inside something it was never shown, and a string literal that
1947-
* does not close on its line means the state cannot be carried across it —
1948-
* both answer `null`, and both callers read `null` as "keep the tell firing".
1998+
* the hunk is never guessed at, and a string literal that does not close on its
1999+
* line means the state cannot be carried across it — that answers `null`, and
2000+
* both callers read `null` as "keep the tell firing". The answer is ALWAYS an
2001+
* opener this hunk showed, never one inferred from a closer.
2002+
*
2003+
* ⭐ #18721 — a closer arriving with an EMPTY stack closes an opener the hunk
2004+
* never showed, and that is NOT a reason to abandon the reading. The openers
2005+
* the hunk DOES show are strictly INSIDE the ones it did not, so the shown
2006+
* stack is a SUFFIX of the real one: whenever it is non-empty its top IS the
2007+
* innermost open delimiter, whatever sits below it. So an underflow drops the
2008+
* closer and the walk continues, and the answer is still `null` for exactly the
2009+
* state that has no positive evidence — a shown stack that is empty where the
2010+
* line sits. ⛔ The reading this replaces abandoned the walk at the FIRST
2011+
* underflow, which a real hunk reaches on its LEADING CONTEXT LINES: a hunk
2012+
* whose context opens on the tail of the previous declaration (` });`) said
2013+
* `null` for every line after it, however plainly the hunk went on to show the
2014+
* `(` the line sits in.
19492015
*
19502016
* ⛔ This is NOT the depth-aware `z.object({ … })` reader T1's own comment
19512017
* refuses, and ⛔ it must never be grown into one. It answers exactly one
@@ -1982,7 +2048,12 @@ export function enclosingDelimiter(side, index) {
19822048
}
19832049
if (BRACKET_CLOSERS[ch] !== undefined) { stack.push({ opener: ch, head: s.slice(0, k) }); continue; }
19842050
if (ch === ')' || ch === ']' || ch === '}') {
1985-
if (stack.length === 0) return null;
2051+
// #18721 — UNDERFLOW: this closes an opener the hunk never showed. Drop
2052+
// it and keep walking. The shown stack is a suffix of the real one, so
2053+
// nothing below it can ever be the innermost open delimiter; an empty
2054+
// shown stack still answers `null` at the end, which is the same "no
2055+
// positive evidence" this reader has always reported.
2056+
if (stack.length === 0) continue;
19862057
stack.pop();
19872058
}
19882059
}
@@ -5213,6 +5284,104 @@ export function selfTest() {
52135284
t('⛔ …a COMMENT carrying a local-factory key line is still not a key line', localRun(BLUEPRINT, "+ // snapshotObject: strictIdent('x'),", FACTORY_FIXTURES.strictIdent.definition).rows.length === 0);
52145285
t('⛔ …and a file OFF the contract source surface reads no blob at all, whatever its lines say', localRun('README.md', FACTORY_FIXTURES.strictIdent.line, FACTORY_FIXTURES.strictIdent.definition).unresolved.length === 0);
52155286

5287+
// -- #18721: a hunk's LEADING CONTEXT is not a reason to abandon the walk ---
5288+
//
5289+
// The live pair is PR #18720 (card #17779): `git diff 72dd95fa5a..09e16a5745
5290+
// -- packages/spec/src/ui/dashboard.zod.ts` — 202 lines, ONE hunk — exited 4
5291+
// on `+ ctx: z.RefinementCtx,` at `dashboard.zod.ts:628`, the SECOND
5292+
// PARAMETER of an exported object-level refinement and the very line #17618's
5293+
// decline was written for. The decline did not fire because
5294+
// `enclosingDelimiter` abandoned its walk at the hunk's FIRST LINE: a real
5295+
// hunk opens on CONTEXT, and this one's context is the tail of the previous
5296+
// declaration — ` });` — whose closers underflow a stack that has seen no
5297+
// opener. The three-line synthetic the #18560 battery drives shows no context
5298+
// at all, so the pin held while every real diff of this shape told.
5299+
//
5300+
// ⭐ Read the FIRING half beside the decline, the way every battery above is
5301+
// ordered: the card's own TRUE-POSITIVE control on the SAME file, and the two
5302+
// shapes that prove the drop cannot silence a real key — a genuine new key
5303+
// behind the same underflowing context, and one added after the parameter
5304+
// list closes. ⚠️ The filing card's first control read 0 and was its own
5305+
// mis-build (a synthetic path off the declared surface is judged by nothing);
5306+
// both fixtures here sit on the real path the probe was taken from.
5307+
battery("#18721 — a hunk's LEADING CONTEXT is not a reason to abandon the parameter reading");
5308+
const DASHBOARD = 'packages/spec/src/ui/dashboard.zod.ts';
5309+
// PR #18720's own hunk, reduced to exactly what the failing branch needs: the
5310+
// leading CONTEXT that closes the previous declaration, the function head,
5311+
// the object-literal-typed FIRST parameter, and the `ctx` line — at the line
5312+
// the card reported. ⛔ Not the three-line synthetic: the context is the case.
5313+
const PROBE_18720 = {
5314+
filename: DASHBOARD,
5315+
status: 'modified',
5316+
patch: [
5317+
'@@ -623,3 +623,7 @@ export function checkDashboardWidgetStageOrder(',
5318+
' });',
5319+
' }',
5320+
' ',
5321+
'+export function checkDashboardWidgetMetricMeasureArity(',
5322+
'+ widget: { id?: unknown; type?: unknown; values?: unknown },',
5323+
'+ ctx: z.RefinementCtx,',
5324+
'+): void {',
5325+
].join('\n'),
5326+
};
5327+
t('⭐ THE FINDING — PR #18720\'s real hunk: `ctx: z.RefinementCtx,` behind three leading context lines reads NO tell', tells(PROBE_18720).length === 0);
5328+
t('…at the line the card reported, which is the line that told — the fixture is the probe, not a shape like it', patchLines(PROBE_18720.patch).find((r) => r.kind === 'added' && r.text.includes('z.RefinementCtx'))?.line === 628);
5329+
t('…and the whole verdict is CLEAN, which is the exit code the live pair could not reach', wideningRefusal({ declaration: 'no', files: [PROBE_18720] }).state === 'clean');
5330+
t('⛔ …and an object-literal TYPE on the first parameter is not what confused it: the `{` closes on its own line', enclosingDelimiter([{ text: 'export function check(', hunk: 0 }, { text: ' widget: { id?: unknown },', hunk: 0 }, { text: ' ctx: z.RefinementCtx,', hunk: 0 }], 2)?.opener === '(');
5331+
// ⭐ THE TRUE-POSITIVE CONTROL, on the SAME file the probe was taken from —
5332+
// the card's own, re-derived here as a real `git diff` in a worktree.
5333+
const NEW_KEY_ON_DASHBOARD = {
5334+
filename: DASHBOARD,
5335+
status: 'modified',
5336+
patch: [
5337+
'@@ -698,3 +698,4 @@ export const DashboardWidgetSchema = lazySchema(() => strictObject({',
5338+
' ',
5339+
' /** Widget Description (displayed below the title) */',
5340+
" description: I18nLabelSchema.optional().describe('Widget description text below the header').meta({ title: 'Description' }),",
5341+
'+ brandNewAuthorableKey: z.string().optional(),',
5342+
].join('\n'),
5343+
};
5344+
t('⛔ CONTROL — a genuinely new key on the SAME file still FIRES: the matcher was never dead, the reading was false', tells(NEW_KEY_ON_DASHBOARD)[0]?.tell === 'T1');
5345+
t('…with its own file:line, the one an author can open', at(NEW_KEY_ON_DASHBOARD)[0] === 'packages/spec/src/ui/dashboard.zod.ts:701');
5346+
t('⛔ CONTROL — a new key behind the SAME underflowing context still tells: the hunk shows no opener, so there is no positive evidence to read', tells({ filename: DASHBOARD, status: 'modified', patch: ['@@ -30,1 +30,2 @@', ' });', '+ brandNewAuthorableKey: z.string().optional(),'].join('\n') }).length === 1);
5347+
t('⛔ CONTROL — a real key added AFTER the parameter list closes still tells, underflowing context and all', tells({ filename: DASHBOARD, status: 'modified', patch: ['@@ -30,1 +30,7 @@', ' });', '+export function check(', '+ ctx: z.RefinementCtx,', '+): void {}', '+export const S = z.object({', '+ extra: z.string(),', '+});'].join('\n') }).map((r) => r.text).join('|') === 'extra: z.string(),');
5348+
// ⭐ The reading itself, at the branch: an underflow DROPS the closer and the
5349+
// walk goes on, because the openers a hunk shows are strictly inside the ones
5350+
// it did not — so a non-empty shown stack is the innermost open delimiter
5351+
// whatever sits below it, and an empty one is still `null`.
5352+
const AFTER_UNDERFLOW = [
5353+
{ text: ' });', hunk: 0 },
5354+
{ text: 'export function checkThing(', hunk: 0 },
5355+
{ text: ' ctx: z.RefinementCtx,', hunk: 0 },
5356+
];
5357+
t('⭐ an opener the hunk shows AFTER an underflow is the answer — the shown stack is a suffix of the real one', enclosingDelimiter(AFTER_UNDERFLOW, 2)?.opener === '(' && inParameterList(AFTER_UNDERFLOW, 2) === true);
5358+
t('⛔ …while an underflow with NO opener after it still answers `null` — positive evidence only, never a guess', enclosingDelimiter([{ text: ' });', hunk: 0 }, { text: ' extra: z.string(),', hunk: 0 }], 1) === null);
5359+
t('⛔ …and the drop does not leak past the parameter list\'s own close: the body\'s `{` is innermost there', inParameterList([{ text: ' });', hunk: 0 }, { text: 'export function check(', hunk: 0 }, { text: ' ctx: z.RefinementCtx,', hunk: 0 }, { text: '): void {', hunk: 0 }, { text: ' extra: z.string(),', hunk: 0 }], 4) === false);
5360+
t('⛔ …and no reading crosses a HUNK boundary, underflow or not', inParameterList([{ text: ' });', hunk: 0 }, { text: 'export function checkThing(', hunk: 0 }, { text: ' ctx: z.RefinementCtx,', hunk: 1 }], 2) === false);
5361+
// ⭐ The OLD side moves with it, and that direction is LOUD: #17618 reads the
5362+
// decline on the removed side too, so a removed parameter behind leading
5363+
// context now buys no #16943 budget — and the key added in the same block,
5364+
// which that phantom budget used to pay for, fires.
5365+
const REMOVED_PARAM_PAYS_NOTHING = {
5366+
filename: DASHBOARD,
5367+
status: 'modified',
5368+
patch: [
5369+
'@@ -40,7 +40,7 @@',
5370+
' });',
5371+
' }',
5372+
' export function check(',
5373+
'- ctx: z.RefinementCtx,',
5374+
'-): void {}',
5375+
'-const S = z.object({',
5376+
'+): void {}',
5377+
'+const S = z.object({',
5378+
'+ extra: z.string(),',
5379+
' });',
5380+
].join('\n'),
5381+
};
5382+
t('⭐ the OLD side moves too — a REMOVED parameter behind leading context is still not a key, so it buys no budget', inParameterList(AFTER_UNDERFLOW, 2) === true && tells(REMOVED_PARAM_PAYS_NOTHING).length === 1);
5383+
t('…and the row that fires is the genuine new key the phantom budget used to pay for', tells(REMOVED_PARAM_PAYS_NOTHING)[0]?.text === 'extra: z.string(),');
5384+
52165385
// -- the floor -------------------------------------------------------------
52175386
const floorFailures = [];
52185387
const floorFailure = (text) => {
@@ -5268,6 +5437,7 @@ export function selfTest() {
52685437
"#18234's key narrowed out of a universal acceptor — certified by the REMOVED value's own semantics rather than by the added value's spelling, with the dark, different-key, never-universal, narrowing-step and surplus controls that still fire, " +
52695438
"#18640's inline closed set re-spelled at the same binding — bounded by the control set that IS the finding, the same edit spelled one member per line and at a keyed property, with the added-arm, different-binding, brand-new, widened-enum and new-key controls that still fire, " +
52705439
"#18702's FILE-LOCAL declaring factory, resolved through its own definition at the head BLOB and classified by what its body returns — every factory the filing card names pinned against its own arm, the refusal arm read off a `z.never` definition rather than a name with its chained-arm control, the counterfactual bracketed by the same fixture with the resolver blind, and both boundaries (an imported factory, an unclassifiable body) pinned as a STATED silence the reader prints, " +
5440+
"#18721's hunk LEADING CONTEXT — an underflowing closer drops and the walk goes on, so #17618's parameter decline reaches a real diff: PR #18720's own hunk silent at its reported line, bracketed by the same file's true-positive control that fires, by a new key behind the same underflowing context, by a key added after the parameter list closes, and by the removed side where a phantom budget disappearing makes a genuine key fire, " +
52715441
"#16448's four positive controls each with its file:line, its negative controls — " +
52725442
'the same diffs with `yes`, and a removal-only diff with `no` — the local path composed end ' +
52735443
'to end so a binary change to a tell surface cannot read as clean, #17112\'s split count with ' +

0 commit comments

Comments
 (0)