Skip to content

Commit f8ca2fd

Browse files
committed
fix(pm): the widening refusal names the two doors that move its exit code, and pins the re-declared key
`REFUSAL_SENTENCE` offered "re-declare `yes` or explain in the claim why this addition does not widen". The second branch has no reader: `c5WideningTell()` compares the declaration against the diff's tells and stops, so an author who followed the instruction got the identical exit 4 and no way to learn the remedy was never implemented. That left one working door, `Clause-②: no` -> `yes`, which on a FALSE tell is the widening-that-did-not-happen this family exists against. The sentence now names the two real doors -- re-declare when the diff widens, repair the MATCHER when the tell is false -- and says outright that an explanation moves no exit code. Giving the explanation a reader was refused rather than overlooked: an author-written sentence that clears the author's own gate is self-clearance, and it needs the claim-line syntax #16448 forbids. The other half of the filing did not reproduce. Re-run against all three PR diffs it measured, the matcher reports no tell -- and neither does it at the card's own filing commit (758ac40), because #16943's replacement budget had landed three days earlier. No matcher change was made for it; what it did leave was a missing case, so the `{ error: ... }` re-declaration is now pinned with its dark control (same added lines, nothing removed -> still fires) and a surplus control (a real new key beside it -> still fires). No exit code moves. The self-test grew from 298 to 309 cases -- 11 added in both directions, none removed. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KB5PFtxuy1x3dcR5gxudx6
1 parent 1e496f9 commit f8ca2fd

1 file changed

Lines changed: 152 additions & 9 deletions

File tree

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

Lines changed: 152 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -480,6 +480,58 @@
480480
* which `surfaceFlags` puts off the contract source surface. It is a different
481481
* reading's card on the day that population is not zero.
482482
*
483+
* ## The remedy with no reader — #17848, and a pin the shape never had
484+
*
485+
* #17848 filed two halves against this family. Re-measuring both on the tree
486+
* that answers today is the whole of what this round changed.
487+
*
488+
* ⭐ HALF ONE DID NOT REPRODUCE. The card measured three parked PRs and read
489+
* nine T1 tells on keys that had existed for releases — a key re-declared
490+
* because the diff hung a zod `{ error: … }` param on it (#17846, seven doors),
491+
* or rewrote its `.describe()` (#17796), or moved beside a retirement (#17638).
492+
* Re-run against all three PR diffs, this matcher reports NO tell on any of
493+
* them — and neither does it AS IT STOOD AT THE CARD'S OWN FILING COMMIT
494+
* (`758ac409`, seventeen minutes before the card was written). The repair had
495+
* landed three days EARLIER, in #16943's replacement budget above: a key
496+
* re-declared in place removes a T1 line and adds one, and the removal pays.
497+
*
498+
* ⛔ So no matcher change was made for half one, and the reason is not that the
499+
* change would have been small. The shapes the card floated — pairing across a
500+
* HUNK, or diffing the file's key SET instead of the block's lines — are the
501+
* silence `changeBlocks`'s own docblock refuses, and buying them would have
502+
* traded a loud failure for a quiet one to repair a defect that was not there.
503+
*
504+
* What half one did leave is a gap in the INSTRUMENT rather than in the reader:
505+
* the `{ error: … }` shape had no case of its own, and it is arithmetically
506+
* distinct from the `.describe()` pair #16943 pinned — one removed line against
507+
* THREE added, of which exactly one is a key. A budget counting LINES instead
508+
* of KINDS comes up short precisely there. Its battery is below, carrying the
509+
* dark control that shows the silence is bought by the removal and never by the
510+
* shape, and the surplus control that shows a real new key riding along still
511+
* fires.
512+
*
513+
* ⭐ HALF TWO REPRODUCED EXACTLY, and it is what this round repairs. The
514+
* refusal sentence offered two doors and only one was real: "explain in the
515+
* claim why this addition does not widen" has NO READER — `c5WideningTell()`
516+
* compares the declaration against the diff's tells and stops there. The
517+
* sections above had recorded that twice, at #16822 and again at #16943,
518+
* without ever changing the string a refused author actually reads; so this
519+
* file knew, and the author could not. ⇒ the only door that moved the exit code
520+
* was `Clause-②: no` → `yes`, which on a FALSE tell is the one thing the
521+
* standing rule forbids outright: 「⛔ 永不把 `no` 翻成 `yes` 去过门」.
522+
*
523+
* A declared-but-unenforced remedy is a shape this repo removes rather than
524+
* documents, so the sentence now names the two doors that work — re-declare
525+
* when the diff really widens, and repair the MATCHER when the tell is false —
526+
* and states the explanation's uselessness outright, so nobody spends a round
527+
* rediscovering it. ⛔ Giving the explanation a READER was refused rather than
528+
* overlooked: an author-written sentence that clears the author's own gate is
529+
* 自查放行, and it would need exactly the new claim-line syntax #16448 forbids.
530+
*
531+
* ⛔ No exit code moves for either half. The register below is unchanged, every
532+
* tell fires where it fired, and `--self-test` GREW cases in both directions
533+
* rather than losing any.
534+
*
483535
* ## Where the surfaces come from — imported, never hand-copied
484536
*
485537
* The contract SOURCE surface is `SUSPECT_TIER_GLOBS`, imported from
@@ -694,11 +746,12 @@ const SELF_TEST_BATTERIES = Object.freeze({
694746
'#17618 — a PARAMETER is not a key, and a closed set RE-SPELLED around fewer values is not a new one': 24,
695747
'#17300 — the retirement ledger is a record of REMOVALS, not a set that gained a value': 27,
696748
'#17955 — a `retiredKey()` tombstone declares a key UNWRITABLE, and never adds a spelling': 29,
749+
'#17848 — a key RE-DECLARED with a zod `error` param is not a key ADDED': 8,
697750
'T3 — a new row in a published entry point': 8,
698751
'T4 — a new registration in a registry': 10,
699752
'#16448 acceptance: the four positive controls, each with its file:line': 8,
700753
'#16448 acceptance: the negative controls a widening gate must let through': 10,
701-
'the refusal sentence, and the two prohibitions it must keep': 8,
754+
'the refusal sentence, and the two prohibitions it must keep': 11,
702755
'the exit register is distinct in every direction it must be': 6,
703756
'the declared registry rows still exist in this tree': 4,
704757
'#17112 — the count is split: examined is not examinable': 23,
@@ -721,15 +774,36 @@ export const EXIT_INCOMPLETE = 2;
721774
export const EXIT_REFUSED = 4;
722775

723776
/**
724-
* The sentence #16448 fixes, quoted from the card and NOT paraphrased.
725-
*
726-
* It is a constant because the whole point of the refusal is that the author
727-
* knows the two ways out of it — re-declare, or explain — without reading this
728-
* file. A row renders it once; ⛔ never a second wording per tell.
777+
* The sentence a refused author reads — the two doors that MOVE THE EXIT CODE,
778+
* and the one that never did (#17848).
779+
*
780+
* ⚠️ It is no longer #16448's wording, and the change is measured rather than
781+
* editorial. That wording read "re-declare `yes` or explain in the claim why
782+
* this addition does not widen", and the header above has twice recorded that
783+
* its second branch has NO READER: `c5WideningTell()` compares the declaration
784+
* against the diff's tells and nothing else, so an author who followed the
785+
* instruction got the identical exit 4 with no way to learn that the remedy was
786+
* never implemented. What that left standing was the branch the standing rule
787+
* forbids outright — 「⛔ 永不把 `no` 翻成 `yes` 去过门」 — a widening written
788+
* into a governance ledger that did not happen and afterwards indistinguishable
789+
* from one that did. ⛔ A gate whose only working door is a lie teaches the lie.
790+
*
791+
* ⛔ The repair is NOT to give the explanation a reader. An author-written
792+
* sentence that clears the author's own gate is 自查放行, and it would need the
793+
* new claim-line syntax #16448 forbids. The repair is to name the door this
794+
* file has said was the right one since #16822: a DEMONSTRATED false positive
795+
* is repaired HERE, in the matcher, with a `--self-test` case pinning the
796+
* shape — and to say plainly that the claim is not where it gets repaired.
797+
*
798+
* It stays a constant for #16448's reason: the author knows the ways out
799+
* without opening this file. A row renders it once; ⛔ never a second wording
800+
* per tell.
729801
*/
730802
export const REFUSAL_SENTENCE =
731-
'a widening tell with `Clause-②: no` — re-declare `yes` or explain in the claim why this ' +
732-
'addition does not widen';
803+
'a widening tell with `Clause-②: no` — re-declare `yes` if the diff really widens; if the tell ' +
804+
'is FALSE, repair it here in the matcher (`scripts/pm/check-widening-tells.mjs`, with a ' +
805+
'`--self-test` case pinning the shape), or file that repair as its own card when it is out of ' +
806+
"this PR's scope. ⛔ An explanation in the claim moves no exit code — nothing reads one";
733807

734808
// ---------------------------------------------------------------------------
735809
// The surfaces
@@ -2823,6 +2897,71 @@ export function selfTest() {
28232897
// that reds when it does.
28242898
t('⚠️ QUIET — a multi-line tombstone whose CLOSING line chains a live arm is not reported; population 0, and the header carries the overturn condition', tells({ filename: TOMBSTONE_FILE, status: 'modified', patch: '@@ -30,0 +30,3 @@\n+ legacy: retiredKey(\n+ LEGACY_PRESCRIPTION,\n+ ).or(z.string()),' }).length === 0);
28252899

2900+
// -- #17848 -----------------------------------------------------------------
2901+
//
2902+
// The card behind this battery read nine T1 tells across three PRs on keys
2903+
// that had existed for releases. Re-measured, none of the three fires — and
2904+
// none fired at the card's own filing commit either, because #16943's
2905+
// replacement budget above had already landed. ⭐ These cases therefore pin a
2906+
// repair that was ALREADY HERE rather than one this round made, which is
2907+
// exactly why they earn their lines: the shape had no case of its own, so
2908+
// nothing would have reported the day it stopped being paid for.
2909+
//
2910+
// ⚠️ It is arithmetically distinct from the `.describe()` pair #16943 pinned.
2911+
// The block removes ONE line and adds THREE, of which exactly one is a key —
2912+
// a budget counting LINES instead of KINDS comes up short right here. The
2913+
// specimen is #17846's, written seven times in one diff.
2914+
battery("#17848 — a key RE-DECLARED with a zod `error` param is not a key ADDED");
2915+
const REDECLARED_WITH_ERROR_PARAM = {
2916+
filename: 'packages/spec/src/ui/component.zod.ts',
2917+
status: 'modified',
2918+
patch: [
2919+
'@@ -2491,7 +2502,12 @@ export const ObjectGridPropsSchema = lazySchema(() => strictObject({',
2920+
' dataSource: ElementDataSourceSchema.optional(),',
2921+
' ',
2922+
'- filter: z.array(ViewFilterRuleSchema).optional()',
2923+
'+ filter: z.array(ViewFilterRuleSchema, {',
2924+
"+ error: ruleArrayFilterError({ surface: 'object_grid', migration: 'rule-array' }),",
2925+
'+ }).optional()',
2926+
" .describe('Filter rules'),",
2927+
].join('\n'),
2928+
};
2929+
// The SAME added lines with nothing removed to pay for them. ⛔ Without this
2930+
// control the case above proves only that something declined, never that the
2931+
// REPLACEMENT is what declined it.
2932+
const ERROR_PARAM_UNPAID = {
2933+
...REDECLARED_WITH_ERROR_PARAM,
2934+
patch: REDECLARED_WITH_ERROR_PARAM.patch
2935+
.split('\n')
2936+
.filter((l) => !l.startsWith('-'))
2937+
.join('\n'),
2938+
};
2939+
// One genuine new key riding along with the re-declaration: the removal pays
2940+
// for the key it replaced and has nothing left for this one.
2941+
const ERROR_PARAM_PLUS_NEW_KEY = {
2942+
...REDECLARED_WITH_ERROR_PARAM,
2943+
patch: [
2944+
'@@ -2491,7 +2502,13 @@ export const ObjectGridPropsSchema = lazySchema(() => strictObject({',
2945+
' dataSource: ElementDataSourceSchema.optional(),',
2946+
' ',
2947+
'- filter: z.array(ViewFilterRuleSchema).optional()',
2948+
'+ filter: z.array(ViewFilterRuleSchema, {',
2949+
"+ error: ruleArrayFilterError({ surface: 'object_grid', migration: 'rule-array' }),",
2950+
'+ }).optional()',
2951+
"+ .describe('Filter rules'),",
2952+
'+ filterLogic: z.string().optional(),',
2953+
' ',
2954+
].join('\n'),
2955+
};
2956+
t('⭐ the card’s specimen — key, optionality and element schema byte-identical, an `error` param added — is not a new key', tells(REDECLARED_WITH_ERROR_PARAM).length === 0);
2957+
t('…and the pair reads CLEAN end to end, which is the exit code the card reported as unreachable', wideningRefusal({ declaration: 'no', files: [REDECLARED_WITH_ERROR_PARAM] }).state === 'clean');
2958+
t('⛔ DARK CONTROL — the same three added lines with NO removal still fire: the silence is bought by the replacement, never by the shape', tells(ERROR_PARAM_UNPAID).length === 1 && tells(ERROR_PARAM_UNPAID)[0]?.tell === 'T1');
2959+
t('…at the line that declares the key, not at the param that chooses a refusal message', at(ERROR_PARAM_UNPAID)[0] === 'packages/spec/src/ui/component.zod.ts:2504');
2960+
t('⛔ an `error:` param is not a key on a shape — its value is not schema-shaped, so it neither fires nor SPENDS the budget', memberTellKind(" error: ruleArrayFilterError({ surface: 'object_grid' }),", { onContractSource: true }) === null);
2961+
t('⭐ SURPLUS CONTROL — a genuinely new key added in the SAME block still fires: one removal pays for one key', tells(ERROR_PARAM_PLUS_NEW_KEY).length === 1 && tells(ERROR_PARAM_PLUS_NEW_KEY)[0]?.tell === 'T1');
2962+
t('…and the row it reports is the new key, never the re-declared one', at(ERROR_PARAM_PLUS_NEW_KEY)[0] === 'packages/spec/src/ui/component.zod.ts:2508');
2963+
t('⛔ and the re-declaration does not license the block: a THIRD key with no removal behind it is reported too', tells({ ...REDECLARED_WITH_ERROR_PARAM, patch: `${REDECLARED_WITH_ERROR_PARAM.patch}\n+ extra: z.string(),` }).length === 1);
2964+
28262965
// -- T3 --------------------------------------------------------------------
28272966
battery('T3 — a new row in a published entry point');
28282967
t('a new export row is a tell', tells(FILE_API_SURFACE)[0]?.tell === 'T3');
@@ -2882,7 +3021,10 @@ export function selfTest() {
28823021

28833022
// -- the sentence and the prohibitions ------------------------------------
28843023
battery('the refusal sentence, and the two prohibitions it must keep');
2885-
t('the sentence names both ways out', says(REFUSAL_SENTENCE, 're-declare `yes`') && says(REFUSAL_SENTENCE, 'explain in the claim'));
3024+
t('the sentence names both ways out', says(REFUSAL_SENTENCE, 're-declare `yes`') && says(REFUSAL_SENTENCE, 'repair it here in the matcher'));
3025+
t('⛔ #17848 — and BOTH are doors this file can open: the one with no reader is gone', !says(REFUSAL_SENTENCE, 'explain in the claim'));
3026+
t('…and its uselessness is stated outright, so no author spends a round rediscovering it', says(REFUSAL_SENTENCE, 'moves no exit code'));
3027+
t('…while the matcher door names the file to open and the case that must come with it', says(REFUSAL_SENTENCE, 'scripts/pm/check-widening-tells.mjs') && says(REFUSAL_SENTENCE, '`--self-test` case pinning the shape'));
28863028
t('…and quotes the declaration in the spelling the reader uses', says(REFUSAL_SENTENCE, '`Clause-②: no`'));
28873029
t('⛔ no label name appears anywhere in this file\'s outputs — a checker that hung one would be issuing the verdict', !says(REFUSAL_SENTENCE, 'needs:') && refusalLines(refusedAll).every((l) => !l.includes('needs:')));
28883030
t('⛔ no new claim-line syntax is invented: the two values are the sibling\'s two', wideningRefusal({ declaration: 'maybe', files: positives }).state === 'not-applicable');
@@ -3093,6 +3235,7 @@ export function selfTest() {
30933235
'each bracketed by the control that still fires, ' +
30943236
'#17300\'s retirement-ledger licence with the firing controls that bracket it on every side, ' +
30953237
'#17955\'s tombstone decline — read before the budget so a rename is still paid for, and requiring the value to BE the call — with the un-retiring control, the two chained-arm controls that fire, and the multi-line chained close pinned as the residual quiet direction, ' +
3238+
"#17848's re-declared key with a zod `error` param — declined by #16943's budget, bracketed by the dark control that fires when nothing paid and the surplus control that fires on a real new key beside it, " +
30963239
"#16448's four positive controls each with its file:line, its negative controls — " +
30973240
'the same diffs with `yes`, and a removal-only diff with `no` — the local path composed end ' +
30983241
'to end so a binary change to a tell surface cannot read as clean, #17112\'s split count with ' +

0 commit comments

Comments
 (0)