@@ -1391,24 +1391,65 @@ function typeDeclRegions(code) {
13911391 * explicitly for their own population moves, and it is made explicitly here for the same
13921392 * reason: the alternative is moving a reported number quietly.
13931393 *
1394- * ⛔ THE RESIDUE THIS DOES NOT CLOSE, named rather than left to be discovered. `\b` fails only
1395- * against a preceding WORD character, so `$route:` — a legal JS identifier — is still read as
1396- * a declaration. It is no longer a DIVERGENCE (all eight now agree on it), which is what this
1397- * card was about, but it is still a phantom, and tightening to `(?<![\w$])` would move
1398- * `declarationsIn` as well — the one scan this card's before/after was priced to leave
1399- * byte-identical — so it is a SECOND population move with its own before/after to price.
1400- * Filed as #11630 rather than folded in, for the reason this card was filed rather than
1401- * folded into #11494, and pinned in `--self-test` so that card MOVES a pin rather than
1402- * finding none.
1403- *
1404- * @param {string } keys the key alternation ONLY — `(route|client)`, `route`,
1394+ * ⛔ THE FAMILY THIS RETIRES (#11717). `\b` fails only against a preceding WORD character, so
1395+ * every character that is NOT one still admitted a phantom: `$route:` (a legal JS identifier),
1396+ * `cond ? defaults.route : 'X'` (a member access whose colon belongs to a TERNARY), `-route:`,
1397+ * and — because `\w` is ASCII-only — any Unicode identifier character, so `éroute:` too. Four
1398+ * cards (#11494, #11542, #11630, #11711) each shrank ONE more character class out of that set
1399+ * and named the next residue as a pin for the next card to flip. That is a widening CHAIN, and
1400+ * it is the wrong discipline for a BOUNDED defect: over code points 0..0x2FFF the `\b` anchor
1401+ * admits 12225 characters, and the whole family was arguing about a handful at the edge of it.
1402+ *
1403+ * SO THE ANCHOR IS INVERTED, and that is the point of this card rather than a fifth shrink. It
1404+ * no longer enumerates what may not PRECEDE the key; it names the positions where an
1405+ * object-literal property key may BEGIN — start of input, whitespace, `{`, `,` — and rejects
1406+ * everything else. Spelled as a negative lookbehind over the COMPLEMENT of that allowlist,
1407+ * `(?<![^\s{,])`, which also gives start-of-input for free rather than as a second alternative.
1408+ * ⚠️ This DEPARTS from the three blocklist idioms below (`symbolRe` `(?<![\w$.])`, `dottedRe`
1409+ * `(?<![\w$])`, `rulePatternFor` `(?<![\w$.-])`) deliberately: each of those names a class it
1410+ * excludes and each therefore has a residue, which is exactly the chain this retires. The
1411+ * allowlist is bounded at three characters where the blocklist was not bounded at all, and it
1412+ * closes `$`, `.`, `-`, Unicode and every future escapee in ONE move instead of one per card.
1413+ * ⛔ It is also why NO `u` flag is needed — closing the Unicode residue as a blocklist meant a
1414+ * `\p{L}` class under `u`, which would change escape semantics for every source these leads
1415+ * are composed with at the eight call sites. An allowlist of ASCII positions needs none.
1416+ *
1417+ * ⛔ IT ADMITS STRICTLY LESS, NEVER MORE — swept, not argued. Over 0..0x2FFF the `\b` anchor
1418+ * admits 12225 and this one admits 25, and the count admitted by this one but NOT by `\b` is
1419+ * ZERO. All three numbers are pinned in `--self-test`, so a future widening that admits
1420+ * something `\b` did not fails there rather than reading as a tightening.
1421+ *
1422+ * ⛔ THIS MOVED BOTH MEASURED POPULATIONS — the ledger rows AND `declarationsIn`, the scan
1423+ * #11542's before/after was priced to leave byte-identical — which the header of
1424+ * `--bridge-coverage` attaches a before/after standard to. Priced on the tree where the move is
1425+ * provably free: across the seven live ledgers all 499 `route:`/`client:` lead occurrences are
1426+ * preceded by a SPACE — 0 by `$`, 0 by `.`, 0 by `-`, 0 by any character outside the allowlist
1427+ * at all — so all 499 survive and no row enters or leaves either population.
1428+ *
1429+ * WHY THERE IS NO RIGHT-HAND ANCHOR, since the idioms below all carry one: the `\s*:` that
1430+ * follows IS the right anchor, exactly. `routes:` cannot match — after `route` the `\s*` takes
1431+ * nothing and the `:` meets `s` — while `route :` legitimately does. A trailing class would be
1432+ * a second spelling of a constraint the colon already makes exact.
1433+ *
1434+ * ⛔ THE RESIDUE THIS DOES NOT CLOSE, named rather than left to be discovered — and it is the
1435+ * end of what ANY left-anchor can reach. A key in EXPRESSION position preceded by whitespace,
1436+ * `cond ? route : 'GET /api/v1/x'`, is byte-for-byte indistinguishable from a property key by
1437+ * anything that only looks left: the allowlist admits it because a space is exactly where a key
1438+ * may begin. #11630 named this same class as the one "no lookbehind can reach" and it is why
1439+ * `-` was left admitted there; the allowlist closes every spelling of it that wears a character
1440+ * (`-route`, `.route`, `$route`) and leaves the plainest one. Closing THAT needs the colon's
1441+ * enclosing expression, not its left neighbour — a parser question, not an anchor question, and
1442+ * a different card if a puller ever appears. Pinned in `--self-test` as deliberately unmoved.
1443+ *
1444+ * @param {string } keys the key alternation ONLY — `(route|client)`, `route`, `client`,
14051445 * `(?:route|client)`. The capture groups are the call site's question; the ANCHOR, the
14061446 * colon and the run between the colon and the value are this function's. ⛔ A call site must
1407- * NOT restate the `\b`: a second copy is a second spelling, which is the entire defect this
1408- * closes, and `--self-test` pins that no argument carries one.
1447+ * NOT restate the anchor in ANY spelling — neither the old `\b` nor a lookbehind of its own:
1448+ * a second copy is a second spelling, which is the entire defect this closes, and
1449+ * `--self-test` pins that no argument carries either.
14091450 */
14101451function declLead ( keys ) {
1411- return String . raw `\b ${ keys } \s*:\s*` ;
1452+ return String . raw `(?<![^\s{,]) ${ keys } \s*:\s*` ;
14121453}
14131454
14141455/**
@@ -1439,8 +1480,9 @@ function declarationsIn(text) {
14391480 const code = codeOnly ( text ) ;
14401481 const skip = typeDeclRegions ( code ) ;
14411482 const out = [ ] ;
1442- // The `\b` this used to carry is now `declLead`'s (#11542) — it was the LAST spelling the
1443- // eight scans decided for themselves, and this was the one scan that decided it correctly.
1483+ // The `\b` this used to carry is now `declLead`'s (#11542), and #11717 replaced it there
1484+ // with the key-POSITION allowlist — it was the LAST spelling the eight scans decided for
1485+ // themselves, and this was the one scan that decided it correctly.
14441486 const re = new RegExp ( declLead ( '(route|client)' ) , 'g' ) ;
14451487 let m ;
14461488 while ( ( m = re . exec ( code ) ) !== null ) {
@@ -2913,26 +2955,84 @@ function selfTest() {
29132955 'declared' , '1 row / 1 route / 0 declined' ,
29142956 `${ keyUnreadable . rows . length } row / ${ keyUnreadable . routesDeclared } route / ${ keyUnreadable . declined . length } declined` ) ;
29152957
2916- // ⛔ THE BOUNDARY THIS CARD DOES NOT CROSS. `\b` fails only against a preceding WORD
2917- // character, so `$route:` — a legal JS identifier — is still read as a declaration. It is no
2918- // longer a DIVERGENCE, which is what this card was about: all eight scans agree on it now,
2919- // and they agree by reading the spelling `declarationsIn` already had. It is still a phantom
2920- // row, and closing it means tightening to `(?<![\w$])`, which moves `declarationsIn` as well
2921- // — a SECOND population move, with its own before/after to price against the header of
2922- // `--bridge-coverage`. Filed as #11630 rather than folded in, for the reason this card was
2923- // filed rather than folded into #11494, and pinned so that card MOVES this pin rather than
2924- // finding none.
2958+ // ⛔ THE FAMILY IS RETIRED HERE (#11717), and these are the pins it FLIPS. `\b` failed only
2959+ // against a preceding WORD character, so every non-word character was a phantom waiting to
2960+ // be filed one card at a time — #11630 had `$` and `.` queued, #11711 had Unicode. The
2961+ // allowlist admits only where an object-literal key may BEGIN, so all four classes close at
2962+ // once and each gets its own fixture: a class closed without a fixture is a class that
2963+ // re-opens silently.
29252964 const dollarKey = parseLedgerSource ( [
29262965 'export const L = [' ,
29272966 " { $route: 'GET /api/v1/gone', family: 'metadata', disposition: 'sdk' }," ,
29282967 " { route: 'GET /api/v1/meta', family: 'metadata', disposition: 'sdk', client: 'meta.getTypes' }," ,
29292968 '];' ,
29302969 ] . join ( '\n' ) ) ;
2931- check ( 'parseLedgerSource' , 'a `$route:` still mints a phantom row — #11630, deliberately unmoved' ,
2932- 'row count' , 2 , dollarKey . rows . length ) ;
2970+ check ( 'parseLedgerSource' , 'a `$route:` mints NO row — `$` is not a place a KEY may begin (#11717)' ,
2971+ 'row count' , 1 , dollarKey . rows . length ) ;
2972+ check ( 'parseLedgerSource' , 'and the row that survives is the REAL one, carrying its binding' ,
2973+ 'row' , 'GET /api/v1/meta → meta.getTypes' ,
2974+ `${ dollarKey . rows [ 0 ] ?. route } → ${ dollarKey . rows [ 0 ] ?. client } ` ) ;
2975+ check ( 'parseLedgerSource' , 'and the DENOMINATOR drops it too, so no phantom gap opens' , 'declared' ,
2976+ '1 route / 1 client / 0 declined' ,
2977+ `${ dollarKey . routesDeclared } route / ${ dollarKey . clientsDeclared } client / ${ dollarKey . declined . length } declined` ) ;
2978+ check ( 'bridgeCoverageFrom' , 'and the retirement carries no verdict on an accurate ledger' , 'brokenScan' ,
2979+ 0 , bridgeCoverageFrom ( [ { file : 'p-route-ledger.ts' , ...dollarKey } ] , [ '/api/v1/meta' ] ) . brokenScan . length ) ;
2980+
2981+ // …a MEMBER ACCESS, where the colon belongs to a TERNARY and never to a key (#11630's `.`).
2982+ const dottedKey = parseLedgerSource ( [
2983+ 'export const L = [' ,
2984+ " { path: cond ? defaults.route : 'GET /api/v1/gone', family: 'metadata' }," ,
2985+ " { route: 'GET /api/v1/meta', family: 'metadata', disposition: 'sdk', client: 'meta.getTypes' }," ,
2986+ '];' ,
2987+ ] . join ( '\n' ) ) ;
2988+ check ( 'parseLedgerSource' , 'a member-access `.route :` in a TERNARY mints NO row (#11717)' ,
2989+ 'row count' , 1 , dottedKey . rows . length ) ;
2990+ check ( 'parseLedgerSource' , 'and it leaves the denominator alone as well' , 'declared' ,
2991+ '1 route / 1 client / 0 declined' ,
2992+ `${ dottedKey . routesDeclared } route / ${ dottedKey . clientsDeclared } client / ${ dottedKey . declined . length } declined` ) ;
2993+
2994+ // …a `-`, which #11630 left ADMITTED deliberately because `a-route` really is the whole
2995+ // token `route`. The allowlist moves it for a different and better reason: whole token or
2996+ // not, a `-` before it means EXPRESSION position, where the colon is a ternary's.
2997+ const dashKey = parseLedgerSource ( [
2998+ 'export const L = [' ,
2999+ " { a-route: 'GET /api/v1/gone', family: 'metadata' }," ,
3000+ " { route: 'GET /api/v1/meta', family: 'metadata', disposition: 'sdk', client: 'meta.getTypes' }," ,
3001+ '];' ,
3002+ ] . join ( '\n' ) ) ;
3003+ check ( 'parseLedgerSource' , 'a `-`-prefixed lead mints NO row — the pin #11630 left is FLIPPED (#11717)' ,
3004+ 'row count' , 1 , dashKey . rows . length ) ;
3005+
3006+ // …and a UNICODE identifier character, which `\w` could never reach because `\w` is
3007+ // ASCII-only. This is #11711's whole card, subsumed — and closed WITHOUT the `u` flag a
3008+ // `\p{L}` blocklist would have forced on all eight call sites.
3009+ const unicodeKey = parseLedgerSource ( [
3010+ 'export const L = [' ,
3011+ " { \u00e9route: 'GET /api/v1/gone', family: 'metadata' }," ,
3012+ " { route: 'GET /api/v1/meta', family: 'metadata', disposition: 'sdk', client: 'meta.getTypes' }," ,
3013+ '];' ,
3014+ ] . join ( '\n' ) ) ;
3015+ check ( 'parseLedgerSource' , 'a UNICODE-prefixed lead mints NO row — #11711 subsumed, no `u` flag (#11717)' ,
3016+ 'row count' , 1 , unicodeKey . rows . length ) ;
3017+
3018+ // ⛔ THE RESIDUE THIS LEAVES, and it is the end of what any LEFT-ANCHOR can reach. A key in
3019+ // expression position preceded by WHITESPACE is byte-for-byte what a property key looks
3020+ // like, so the allowlist admits it — correctly, by its own rule. #11630 named this same
3021+ // class as the one "no lookbehind can reach", which is why it left `-` admitted; the
3022+ // allowlist closes every spelling of it that wears a character and leaves the plainest one.
3023+ // Closing THAT is a parser question, not an anchor question. Pinned as deliberately unmoved
3024+ // so a card that ever takes it MOVES a pin rather than finding none.
3025+ const bareExprKey = parseLedgerSource ( [
3026+ 'export const L = [' ,
3027+ " { path: cond ? route : 'GET /api/v1/gone', family: 'metadata' }," ,
3028+ " { route: 'GET /api/v1/meta', family: 'metadata', disposition: 'sdk', client: 'meta.getTypes' }," ,
3029+ '];' ,
3030+ ] . join ( '\n' ) ) ;
3031+ check ( 'parseLedgerSource' , 'a bare `? route :` in EXPRESSION position still mints a phantom — deliberately unmoved' ,
3032+ 'row count' , 2 , bareExprKey . rows . length ) ;
29333033 check ( 'parseLedgerSource' , 'and it is still SILENT — all eight scans agree on it, so both terms move together' ,
29343034 'declared' , '2 route / 0 declined' ,
2935- `${ dollarKey . routesDeclared } route / ${ dollarKey . declined . length } declined` ) ;
3035+ `${ bareExprKey . routesDeclared } route / ${ bareExprKey . declined . length } declined` ) ;
29363036
29373037 // ⛔ REPORTED, NEVER A VERDICT. A comment explaining a retired row by quoting its old path
29383038 // is legitimate prose; reddening CI over it is the false red the #9747 family declines.
@@ -3568,7 +3668,7 @@ function selfTest() {
35683668 // copy is the same hole re-opening, and only a source pin can see it: every behavioural
35693669 // fixture above would keep passing while the new scan drifted on its own.
35703670 check ( 'declLead' , 'the run between a `route:`/`client:` colon and its value is spelled ONCE' , 'affected-docs.mjs' ,
3571- 1 , ( ownSource . match ( / S t r i n g \. r a w ` \\ b \$ \{ k e y s \} \\ s \* : \\ s \* ` / g) || [ ] ) . length ) ;
3671+ 1 , ( ownSource . match ( / S t r i n g \. r a w ` \( \? < ! \[ \^ \\ s \{ , \] \) \$ \{ k e y s \} \\ s \* : \\ s \* ` / g) || [ ] ) . length ) ;
35723672 check ( 'declLead' , 'and all eight lead scans are built from it, none inline' , 'affected-docs.mjs' ,
35733673 8 , ( ownSource . match ( / n e w R e g E x p \( d e c l L e a d \( / g) || [ ] ) . length ) ;
35743674 // …AND THE KEY ANCHOR IS SPELLED ONCE TOO (#11542), which is the same pin one field over.
@@ -3579,14 +3679,39 @@ function selfTest() {
35793679 // side, and only a source pin can see it: every behavioural fixture above stays green while
35803680 // the argument drifts.
35813681 check ( 'declLead' , 'and the KEY anchor is spelled once too — no call site restates it' , 'affected-docs.mjs' ,
3582- 0 , ( ownSource . match ( / d e c l L e a d \( [ ^ \n ] * ?\\ b / g) || [ ] ) . length ) ;
3682+ 0 , ( ownSource . match ( / d e c l L e a d \( [ ^ \n ] * ?(?: \\ b | \( \? < ) / g) || [ ] ) . length ) ;
35833683 // BEHAVIOURAL, not merely textual: the three key spellings the eight call sites pass all
35843684 // come back anchored, from the one place that spells the anchor. This is what "all eight
35853685 // read the same anchored spelling" means when checked rather than asserted.
35863686 check ( 'declLead' , 'every key spelling a call site passes comes back ANCHORED' , 'declLead' ,
3587- String . raw `\b (route|client)\s*:\s* | \broute \s*:\s* | \b (?:route|client)\s*:\s*` ,
3687+ String . raw `(?<![^\s{,]) (route|client)\s*:\s* | (?<![^\s{,])route \s*:\s* | (?<![^\s{,]) (?:route|client)\s*:\s*` ,
35883688 [ '(route|client)' , 'route' , '(?:route|client)' ] . map ( ( k ) => declLead ( k ) ) . join ( ' | ' ) ) ;
35893689
3690+ // ⛔ AND IT ONLY EVER REMOVES (#11717) — swept over code points, not argued. The allowlist
3691+ // is a strict SUBSET of the `\b` it replaced, which is what makes "retire the family" a
3692+ // tightening rather than a re-shuffle. A future card that widens the allowlist — adding `(`
3693+ // to admit a call-position key, say — fails HERE the moment it admits one character `\b`
3694+ // rejected, instead of reading as one more honest shrink.
3695+ const sweepOld = String . raw `\b(route|client)\s*:\s*` ;
3696+ const sweepNew = declLead ( '(route|client)' ) ;
3697+ const admitsAfter = ( src , ch ) => {
3698+ for ( const m of ( ch + "route: 'x'" ) . matchAll ( new RegExp ( src , 'g' ) ) ) if ( m . index === ch . length ) return true ;
3699+ return false ;
3700+ } ;
3701+ let sweptOld = 0 , sweptNew = 0 , sweptNewNotOld = 0 ;
3702+ for ( let cp = 0 ; cp <= 0x2fff ; cp ++ ) {
3703+ const ch = String . fromCodePoint ( cp ) ;
3704+ const o = admitsAfter ( sweepOld , ch ) ;
3705+ const n = admitsAfter ( sweepNew , ch ) ;
3706+ if ( o ) sweptOld ++ ;
3707+ if ( n ) sweptNew ++ ;
3708+ if ( n && ! o ) sweptNewNotOld ++ ;
3709+ }
3710+ check ( 'declLead' , 'the anchor only ever REMOVES — it admits nothing the `\\b` it replaced rejected' ,
3711+ 'sweep 0..0x2FFF' , 0 , sweptNewNotOld ) ;
3712+ check ( 'declLead' , 'and the allowlist is the far SMALLER set, by the margin the sweep measures' ,
3713+ 'sweep 0..0x2FFF' , '12225 → 25' , `${ sweptOld } → ${ sweptNew } ` ) ;
3714+
35903715
35913716 if ( failed ) {
35923717 console . error ( `\n✗ affected-docs self-test failed (${ failed } case(s)).` ) ;
0 commit comments