@@ -1404,42 +1404,61 @@ function typeDeclRegions(code) {
14041404 * • `dottedRe` `(?<![\w$])` a DOTTED token, which must tolerate its own dots
14051405 * • `rulePatternFor`/`commandPatternFor` `(?<![\w$.-])` a doc-side PROSE span, where `-`
14061406 * glues tokens in English
1407- * `declLead`'s key is a BARE token, so `symbolRe`'s set is the analogue: `$` continues an
1408- * identifier (`$route`), and `.` makes the token a MEMBER ACCESS whose colon belongs to a
1409- * ternary and never to a key (`cond ? obj.route : x`). Both mint the same phantom, and a
1410- * character class closes the class rather than enumerating the escapees.
1407+ * ⛔ AND THAT TABLE IS WHY THIS IS NOW AN ALLOWLIST (#11717). Every idiom above names a class
1408+ * it EXCLUDES, so every one of them has a residue — and this key's residue was worked through
1409+ * one card at a time: #11494 the colon run, #11542 the word boundary (`subroute:`), #11630 `$`
1410+ * and `.` (`$route:`, `cond ? obj.route : x`), #11711 queued for Unicode because `\w` is
1411+ * ASCII-only. Each card was small, provably free and honestly priced, and each named the next
1412+ * residue as a pin for the next card to flip. That is a good discipline for an open-ended
1413+ * defect and the wrong one for a BOUNDED defect: over code points 0..0x2FFF the `\b` anchor
1414+ * admitted 12225 characters and `(?<![\w$.])` admitted 12223, so the whole family was arguing
1415+ * about a handful at the edge of a set of twelve thousand.
14111416 *
1412- * ⛔ `-` IS DELIBERATELY NOT IN THE SET, which is the one place this departs from
1413- * `rulePatternFor`. `a-route` is two tokens (`a - route`), so the `route` there IS the whole
1414- * token `route` — it is not a declaration for a DIFFERENT reason (expression position), and
1415- * that reason is shared with the bare `cond ? route : x` that NO lookbehind can exclude.
1416- * Excluding `-` would close one spelling of that class while leaving its plainest spelling
1417- * open, which is enumerating escapees wearing a character class.
1417+ * SO THE ANCHOR IS INVERTED rather than shrunk a fifth time. It no longer enumerates what may
1418+ * not PRECEDE the key; it names the positions where an object-literal property key may BEGIN —
1419+ * start of input, whitespace, `{`, `,` — and rejects everything else. Spelled as a negative
1420+ * lookbehind over the COMPLEMENT of that allowlist, `(?<![^\s{,])`, which also gives
1421+ * start-of-input for free rather than as a second alternative. The set is bounded at three
1422+ * characters where a blocklist is not bounded at all, and it closes `$`, `.`, `-`, Unicode and
1423+ * every future escapee of that shape in ONE move instead of one per card.
14181424 *
1419- * ⛔ THE RESIDUE THIS STILL DOES NOT CLOSE, named rather than left to be discovered. `\w` is
1420- * ASCII-only, so a Unicode identifier character still passes: `éroute:` is admitted by the
1421- * lookbehind exactly as it was by `\b` (measured — both admit it). Closing it means a
1422- * `\p{L}`-class lookbehind under the `u` flag, which changes the escape semantics of every
1423- * source these leads are COMPOSED with at the eight call sites. 0 occurrences across the
1424- * seven live ledgers, and pinned in `--self-test` as deliberately unmoved so the next card
1425- * of this shape MOVES a pin rather than finding none.
1425+ * ⛔ AND IT NEEDS NO `u` FLAG, which is the concrete cost the blocklist route was carrying:
1426+ * closing the Unicode residue as a class meant `\p{L}` under `u`, which changes the escape
1427+ * semantics of every source these leads are COMPOSED with at the eight call sites. An
1428+ * allowlist of ASCII positions needs none, so #11711 is subsumed at no cost at all.
14261429 *
1427- * ⛔ THIS IS THE SECOND POPULATION MOVE, and unlike #11542's it moves `declarationsIn` too —
1428- * the one scan #11542's before/after was priced to leave byte-identical. It is priced with
1429- * its OWN before/after against the header of `--bridge-coverage`, at ROW IDENTITY rather
1430- * than counter equality: `--bridge-coverage --json` carries all 177 `unreachableRows` by
1431- * `{file, route, client}` , and it hashes `d04a5cedfb613370e5b46ac4725db1d941e5dc88` on the
1432- * base tree, on the ablated tree and on the fixed tree alike. Counters agreeing is consistent
1433- * with two rows swapping places; row identity is not. Provably free for the direct reason:
1434- * across the seven live ledgers, all 499 `route:`/`client:` leads are preceded by a SPACE —
1435- * 0 preceded by `$`, 0 by `.`, 0 by any non-word character at all (positive control: the same
1436- * scan reports both classes when a fixture carries them) .
1430+ * ⛔ THE RESIDUE THIS LEAVES — and it is the end of what ANY left-anchor can reach, so it is a
1431+ * BOUNDARY rather than the next link in the chain. A key in EXPRESSION position preceded by
1432+ * whitespace, `cond ? route : 'GET /api/v1/x'`, is byte-for-byte what a property key looks
1433+ * like, so the allowlist admits it — correctly, by its own rule. #11630 named this same class
1434+ * as the one no lookbehind can reach , and that is exactly why it left `-` admitted: `a-route`
1435+ * really is the whole token `route`. The allowlist closes every spelling of the class that
1436+ * WEARS a character (`$route`, `.route`, `-route`, `éroute`) and leaves the plainest one.
1437+ * Closing THAT needs the colon's enclosing expression, not its left neighbour — a parser
1438+ * question, not an anchor question, and a different card if a puller ever appears. Pinned in
1439+ * `--self-test` as deliberately unmoved .
14371440 *
1438- * ⛔ AND IT CAN ONLY EVER REMOVE. The key alternation always opens with a word character, so
1439- * `\b` there fails exactly when the previous character is a word character — making
1440- * `(?<![\w$.])` a STRICT SUBSET of `\b`. Swept over code points 0..0x2FFF: `\b` admits 12225,
1441- * the lookbehind admits 12223, and the lookbehind admits 0 that `\b` does not. The move is
1442- * exactly the 2 characters named above.
1441+ * ⛔ THIS IS THE THIRD POPULATION MOVE, and like #11630's it moves BOTH the ledger rows and
1442+ * `declarationsIn`. Priced with its own before/after against the header of `--bridge-coverage`,
1443+ * at ROW IDENTITY rather than counter equality, and re-derived against THIS base rather than
1444+ * inherited from #11630: `--bridge-coverage --json` carries all 177 `unreachableRows` by
1445+ * `{file, route, client}` and still hashes `d04a5cedfb613370e5b46ac4725db1d941e5dc88`, and the
1446+ * `declarationsIn` population is byte-identical across the change. Counters agreeing is
1447+ * consistent with two rows swapping places; row identity is not. Provably free for the direct
1448+ * reason: across the seven live ledgers all 499 `route:`/`client:` leads are preceded by a
1449+ * SPACE — 0 by `$`, 0 by `.`, 0 by `-`, 0 by `{`, 0 by `,`, 0 by anything outside the
1450+ * allowlist at all (positive control: the same scan reports every one of those classes the
1451+ * moment a fixture carries them, so the zeros are readings and not a blind scan).
1452+ *
1453+ * ⛔ AND IT CAN ONLY EVER REMOVE, swept rather than argued — against the anchor it actually
1454+ * replaces, not against the one two cards ago. Over code points 0..0x2FFF `(?<![\w$.])` admits
1455+ * 12223 and this allowlist admits 25, and the allowlist admits 0 that `(?<![\w$.])` does not.
1456+ * Both numbers and the zero are pinned in `--self-test`.
1457+ *
1458+ * WHY THERE IS NO RIGHT-HAND ANCHOR, since every idiom above carries one: the `\s*:` that
1459+ * follows IS the right anchor, exactly. `routes:` cannot match — after `route` the `\s*` takes
1460+ * nothing and the `:` meets `s` — while `route :` legitimately does. A trailing class would be
1461+ * a second spelling of a constraint the colon already makes exact.
14431462 *
14441463 * @param {string } keys the key alternation ONLY — `(route|client)`, `route`,
14451464 * `(?:route|client)`. The capture groups are the call site's question; the ANCHOR, the
@@ -1449,7 +1468,7 @@ function typeDeclRegions(code) {
14491468 * no argument carries either form.
14501469 */
14511470function declLead ( keys ) {
1452- return String . raw `(?<![\w$. ])${ keys } \s*:\s*` ;
1471+ return String . raw `(?<![^\s{, ])${ keys } \s*:\s*` ;
14531472}
14541473
14551474/**
@@ -3092,33 +3111,59 @@ function selfTest() {
30923111 '1 route / 1 client / 0 declined' ,
30933112 `${ dottedKey . routesDeclared } route / ${ dottedKey . clientsDeclared } client / ${ dottedKey . declined . length } declined` ) ;
30943113
3095- // ⛔ THE BOUNDARY THIS CARD DOES NOT CROSS, in its turn — both halves pinned so the next
3096- // card of this shape MOVES a pin rather than finding none, exactly as this one did.
3114+ // ⛔ BOTH OF #11630'S BOUNDARY PINS ARE FLIPPED HERE (#11717), not deleted — the allowlist
3115+ // crosses both, and a green reached by removing an assertion is the one thing this gate
3116+ // cannot afford.
30973117 //
3098- // (a) `-` is NOT in the set. `a-route` is two tokens, so that `route` IS the whole token —
3099- // not a declaration for a DIFFERENT reason (expression position), one it shares with the
3100- // bare `cond ? route : x` no lookbehind can reach. Excluding `-` would close one spelling of
3101- // that class and leave its plainest spelling open.
3118+ // (a) `-`. #11630 left it admitted on the reasoning that `a-route` is two tokens, so that
3119+ // `route` IS the whole token and is a non-declaration for a DIFFERENT reason (expression
3120+ // position) — a reason it shares with the bare `cond ? route : x` no lookbehind can reach.
3121+ // The allowlist moves it, and for a reason that reads the same fact the other way round:
3122+ // whole token or not, `-` is not a place an object-literal KEY may begin. What #11630 called
3123+ // the shared class is now pinned directly, in its plainest spelling, at the end of this
3124+ // block — so the class is pinned rather than approximated by one of its spellings.
31023125 const minusKey = parseLedgerSource ( [
31033126 'export const L = [' ,
31043127 " { route: 'GET /api/v1/meta', family: 'metadata', disposition: 'sdk', client: 'meta.getTypes' }," ,
31053128 '];' ,
31063129 "const n = cond ? a-route : 'GET /api/v1/gone';" ,
31073130 ] . join ( '\n' ) ) ;
3108- check ( 'parseLedgerSource' , 'a `-`-prefixed lead still mints a row — deliberately outside the set (#11630)' ,
3109- 'row count' , 2 , minusKey . rows . length ) ;
3110- // (b) `\w` is ASCII-only, so a UNICODE identifier character still passes — `éroute:` is
3111- // admitted by the lookbehind exactly as it was by `\b`. Closing it means a `\p{L}` class
3112- // under the `u` flag, which changes escape semantics for every source these leads are
3113- // COMPOSED with at the eight call sites. 0 occurrences across the seven live ledgers.
3131+ check ( 'parseLedgerSource' , 'a `-`-prefixed lead mints NO row — the pin #11630 left is FLIPPED by the allowlist (#11717)' ,
3132+ 'row count' , 1 , minusKey . rows . length ) ;
3133+ // (b) UNICODE, which is #11711's whole card, subsumed. `\w` is ASCII-only, so `éroute:` was
3134+ // admitted by the lookbehind exactly as it was by `\b`. As a BLOCKLIST closing it meant a
3135+ // `\p{L}` class under the `u` flag, which changes escape semantics for every source these
3136+ // leads are COMPOSED with at the eight call sites — the concrete cost that kept it open. An
3137+ // allowlist of ASCII positions needs no flag, so it closes for free. 0 occurrences across
3138+ // the seven live ledgers either way.
31143139 const unicodeKey = parseLedgerSource ( [
31153140 'export const L = [' ,
31163141 " { éroute: 'GET /api/v1/gone', family: 'metadata', disposition: 'sdk' }," ,
31173142 " { route: 'GET /api/v1/meta', family: 'metadata', disposition: 'sdk', client: 'meta.getTypes' }," ,
31183143 '];' ,
31193144 ] . join ( '\n' ) ) ;
3120- check ( 'parseLedgerSource' , 'a UNICODE-prefixed lead still mints a phantom row — residue, deliberately unmoved' ,
3121- 'row count' , 2 , unicodeKey . rows . length ) ;
3145+ check ( 'parseLedgerSource' , 'a UNICODE-prefixed lead mints NO row — #11711 subsumed, and with NO `u` flag (#11717)' ,
3146+ 'row count' , 1 , unicodeKey . rows . length ) ;
3147+
3148+ // ⛔ (c) THE RESIDUE THIS LEAVES, and it is the END of what any left-anchor can reach — a
3149+ // boundary, not the next link in the chain. A key in EXPRESSION position preceded by
3150+ // WHITESPACE is byte-for-byte what a property key looks like, so the allowlist admits it,
3151+ // correctly by its own rule. This is the class #11630 named as unreachable by any
3152+ // lookbehind and used to justify leaving `-` open; the allowlist closes every spelling of it
3153+ // that WEARS a character and leaves this one. Closing it needs the colon's enclosing
3154+ // EXPRESSION, not its left neighbour — a parser question. Pinned deliberately unmoved so a
3155+ // card that ever takes it MOVES a pin rather than finding none.
3156+ const bareExprKey = parseLedgerSource ( [
3157+ 'export const L = [' ,
3158+ " { route: 'GET /api/v1/meta', family: 'metadata', disposition: 'sdk', client: 'meta.getTypes' }," ,
3159+ '];' ,
3160+ "const n = cond ? route : 'GET /api/v1/gone';" ,
3161+ ] . join ( '\n' ) ) ;
3162+ check ( 'parseLedgerSource' , 'a bare `? route :` in EXPRESSION position still mints a phantom — deliberately unmoved' ,
3163+ 'row count' , 2 , bareExprKey . rows . length ) ;
3164+ check ( 'parseLedgerSource' , 'and it is still SILENT — all eight scans agree on it, so both terms move together' ,
3165+ 'declared' , '2 route / 1 client / 0 declined' ,
3166+ `${ bareExprKey . routesDeclared } route / ${ bareExprKey . clientsDeclared } client / ${ bareExprKey . declined . length } declined` ) ;
31223167
31233168 // ⛔ REPORTED, NEVER A VERDICT. A comment explaining a retired row by quoting its old path
31243169 // is legitimate prose; reddening CI over it is the false red the #9747 family declines.
@@ -3754,7 +3799,7 @@ function selfTest() {
37543799 // copy is the same hole re-opening, and only a source pin can see it: every behavioural
37553800 // fixture above would keep passing while the new scan drifted on its own.
37563801 check ( 'declLead' , 'the run between a `route:`/`client:` colon and its value is spelled ONCE' , 'affected-docs.mjs' ,
3757- 1 , ( ownSource . match ( / S t r i n g \. r a w ` \( \? < ! \[ \\ w \$ \. \] \) \$ \{ k e y s \} \\ s \* : \\ s \* ` / g) || [ ] ) . length ) ;
3802+ 1 , ( ownSource . match ( / S t r i n g \. r a w ` \( \? < ! \[ \^ \\ s \{ , \] \) \$ \{ k e y s \} \\ s \* : \\ s \* ` / g) || [ ] ) . length ) ;
37583803 check ( 'declLead' , 'and all eight lead scans are built from it, none inline' , 'affected-docs.mjs' ,
37593804 8 , ( ownSource . match ( / n e w R e g E x p \( d e c l L e a d \( / g) || [ ] ) . length ) ;
37603805 // …AND THE KEY ANCHOR IS SPELLED ONCE TOO (#11542), which is the same pin one field over.
@@ -3773,15 +3818,19 @@ function selfTest() {
37733818 // come back anchored, from the one place that spells the anchor. This is what "all eight
37743819 // read the same anchored spelling" means when checked rather than asserted.
37753820 check ( 'declLead' , 'every key spelling a call site passes comes back ANCHORED' , 'declLead' ,
3776- String . raw `(?<![\w$. ])(route|client)\s*:\s* | (?<![\w$. ])route\s*:\s* | (?<![\w$. ])(?:route|client)\s*:\s*` ,
3821+ String . raw `(?<![^\s{, ])(route|client)\s*:\s* | (?<![^\s{, ])route\s*:\s* | (?<![^\s{, ])(?:route|client)\s*:\s*` ,
37773822 [ '(route|client)' , 'route' , '(?:route|client)' ] . map ( ( k ) => declLead ( k ) ) . join ( ' | ' ) ) ;
3778- // …and BEHAVIOURALLY the anchor is a strict TIGHTENING of the `\b` it replaced, which is the
3779- // invariant the population pricing rests on: the key alternation always opens with a word
3780- // character, so `\b` there fails exactly when the previous character is a word character,
3781- // making `(?<![\w$.])` a strict SUBSET. Swept rather than argued — over code points
3782- // 0..0x2FFF the lookbehind admits nothing `\b` does not, and exactly 2 characters move .
3823+ // …and BEHAVIOURALLY the allowlist is a strict TIGHTENING of BOTH anchors it has replaced,
3824+ // which is the invariant the population pricing rests on. Swept rather than argued, and
3825+ // swept against the anchor it ACTUALLY replaces (#11710's lookbehind) as well as against the
3826+ // `\b` two cards back: a sweep that only ever compares with the oldest spelling stops being
3827+ // evidence the moment two cards land in a row .
37833828 {
37843829 const bAnchor = new RegExp ( String . raw `\b(route|client)\s*:\s*` ) ;
3830+ // The lookbehind this card replaces, spelled here as a LITERAL rather than taken from
3831+ // `declLead` — it is the BEFORE state, so reading it from the function under test would
3832+ // make the comparison vacuous the moment the function changes.
3833+ const prevAnchor = new RegExp ( String . raw `(?<![\w$.])(route|client)\s*:\s*` ) ;
37853834 // Built through a NAMED intermediate on purpose: the pin above counts the eight
37863835 // production lead SCANS by the way each one compiles `declLead` directly, and this probe
37873836 // is a behavioural check rather than a ninth scan. Inflating that count to 9 would blunt
@@ -3791,19 +3840,28 @@ function selfTest() {
37913840 // quoted, for the same reason.
37923841 const leadSource = declLead ( '(route|client)' ) ;
37933842 const lead = new RegExp ( leadSource ) ;
3794- let admitsMore = 0 ;
3795- let moved = 0 ;
3843+ let admitsMoreThanB = 0 ;
3844+ let admitsMoreThanPrev = 0 ;
3845+ let admitsB = 0 ;
3846+ let admitsPrev = 0 ;
3847+ let admitsLead = 0 ;
37963848 for ( let c = 0 ; c < 0x3000 ; c ++ ) {
37973849 const s = String . fromCodePoint ( c ) + "route: 'x'" ;
37983850 const b = bAnchor . test ( s ) ;
3851+ const q = prevAnchor . test ( s ) ;
37993852 const l = lead . test ( s ) ;
3800- if ( l && ! b ) admitsMore ++ ;
3801- if ( b && ! l ) moved ++ ;
3853+ if ( b ) admitsB ++ ;
3854+ if ( q ) admitsPrev ++ ;
3855+ if ( l ) admitsLead ++ ;
3856+ if ( l && ! b ) admitsMoreThanB ++ ;
3857+ if ( l && ! q ) admitsMoreThanPrev ++ ;
38023858 }
38033859 check ( 'declLead' , 'the anchor only ever REMOVES — it admits nothing `\\b` did not' , 'code points 0..0x2FFF' ,
3804- 0 , admitsMore ) ;
3805- check ( 'declLead' , 'and the characters it moves are exactly `$` and `.`' , 'code points 0..0x2FFF' ,
3806- 2 , moved ) ;
3860+ 0 , admitsMoreThanB ) ;
3861+ check ( 'declLead' , 'and nothing the LOOKBEHIND it replaces did not — the invariant against the real before-state' ,
3862+ 'code points 0..0x2FFF' , 0 , admitsMoreThanPrev ) ;
3863+ check ( 'declLead' , 'and the allowlist is the far smaller set, by the margin the sweep measures' ,
3864+ 'code points 0..0x2FFF' , '12225 / 12223 / 25' , `${ admitsB } / ${ admitsPrev } / ${ admitsLead } ` ) ;
38073865 }
38083866
38093867
0 commit comments