fix(pm): check-widening-tells' parameter decline survives a hunk's leading context - #18804
Merged
os-justin merged 1 commit intoSep 17, 2026
Merged
Conversation
…ading 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>
This was referenced Sep 17, 2026
os-justin
marked this pull request as ready for review
September 17, 2026 22:13
os-justin
deleted the
claude/issue-18721-refinement-ctx-parameter-false-t1
branch
September 17, 2026 22:39
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #18721
Clause-②: no
The defect
scripts/pm/check-widening-tells.mjsread+ ctx: z.RefinementCtx,— the second parameter of an exported object-level refinement, this repo's own prescribed#16489signature — 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-declaringClause-②: 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/main7572329069(the card measured94b3f37be; the defect is unchanged on the newer tip).FALSE POSITIVE — the card's own probe, rebuilt from git:
TRUE-POSITIVE CONTROL — same matcher, same run, same real file path, built as a real
git diffin the worktree and then restored withgit checkout HEAD -- packages/spec/src/ui/dashboard.zod.ts: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
enclosingDelimiterwalks from the first line of the line's own hunk and abandoned the walk, answeringnull, the first time a closer arrived with an empty stack.nullis 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:
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 theexport 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 underflowreturn 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:
The argument is a stack one, and it is why this does not loosen the
nocriterion: 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 answersnull, 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.RefinementCtxtype-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_FORMSis 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:DashboardWidgetSchema.valuesfor the metric/kpi/gauge/solid-gauge/bullet family (objectui#8894 ruling D) #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, thectxline) at the line the card reported — T1 silent, and the whole verdict CLEAN.+ brandNewAuthorableKey: z.string().optional(),FIRES, atpackages/spec/src/ui/dashboard.zod.ts:701.null; the drop does not leak past the parameter list's own close; no reading crosses a hunk boundary.check-clause2-carriersT1 reads a function PARAMETER annotatedctx: z.RefinementCtxas a new authorable key — so every PR that adds an object-level refusal raises a widening tell for the refusal itself #17618 reads the same decline on the removed side, so a removed parameter behind leading context now buys no [finding] check-clause2-carriers T2 fires on a replaced string property value as "a new member of a closed set", and the C5 row it raises cannot be cleared except by declaring Clause-② yes on a change that does not widen #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
#18560battery (#17618's parameter decline is untouched by the wider vocabulary) and its own battery's underflow pin are byte-unchanged and green.Self-test
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 (
continueback toreturn null), proved the mutation landed by blob hash and by anchor counts, ran the suite, restored under atrapand verified the restore by hash. There is no build step and nodist/for ascripts/pm/*.mjsfile, so the on-disk proof is the hash plus the anchor counts.All 6 failures are in the new
#18721battery and nothing pre-existing reds:An earlier ablation attempt was a NO-OP (
perlwith a double-escaped pattern, anchor counts1 -> 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 setscripts/pm/check-widening-tells.mjs, 1 path vs merge base757232906) — 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).check-scripts-symbol-anchorswas the one red in the first sweep — the header section citedpath:628, and a line number is not an anchor form. Rewritten to the symbol anchorpackages/spec/src/ui/dashboard.zod.ts#checkDashboardWidgetMetricMeasureArity; green on re-run, and the line numbers that carry evidence stayed, in prose.pnpm lintis 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