Skip to content

Commit a7bafc2

Browse files
os-justinclaude
andauthored
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

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)