decisions/REGION_GATE_HARDENING_FINDINGS.md
Produced by the bali pass-2 landing, where check_region_page.py went red and the repair ran
through three implement-then-adversarially-attack rounds. Everything CLOSED is in the gate; this
file is the record of what was found, what was deliberately ACCEPTED, and what is still OPEN.
DOC_MAP row: §12 Harness & gates. Consumer: the next person who touches check_region_page.py,
and the self-test arming work in OPEN-1.
The gate reported 42 use_class errors on bali. All 42 were false positives. Underneath them sat
THREE REAL zone-catalog errors that nobody could see, because the scorecard truncates at 20 errors
per page. The false positives were not merely annoying — they were concealing the findings that
mattered. Treat a rule that fires in bulk as a defect in the rule until proven otherwise, and clear
the noise before trusting anything below it.
Every real bug found here was one of two shapes:
use_class rule scanned every Section-16 line containing SRC_, punishing the inline citation
that cited-or-cut requires. The zone rule set its scan body to the Zone Catalog header through
END OF PAGE, so every markdown table below it was read as zone rows.
error. A one-space indent on | zone_id | disabled the entire zone rule. An H3 or indented
## Zone Catalog heading disabled the contiguity assert. Both fail OPEN and SILENT, which is the
worst failure mode a gate has.
When adding a rule to this gate, state its scope explicitly and write a fixture proving the rule
still fires when its anchor is written in every legal shape.
--self-test)declared in Section 16 on a line carrying a use_class token. Strictly stronger — it gains the
never-declared-at-all class the per-line form structurally could not reach, because that form
only ever scanned inside Section 16.
[SRC: ...] bracket form. Bare mentions no longer declare, closinga hole where a prose negation ("None of these are FRINGE-SKIP: SRC_00157, SRC_00251") made a page
with ZERO real use_class assignments pass.
NON-SUBSTRATE, REFERENCES and CROSS-REFERENCE no longer count as declarations.
dropping every row below the break. This closes a regression the bounding fix itself introduced —
found by the adversarial pass, not by the implementer.
also accepts H3).
of the anchor-then-overscan class in this one file, and the first that had already caused real
damage. Its first repair (pure nearest-neighbour) then UNDER-scanned and was widened to
anchor-plus-coordinated-list the same day. See the section below.
^\s*\| is WRONG for these anchors: \s matches NEWLINES, so the match starts on the blank lineABOVE the header, the contiguous run terminates immediately, the bounded body comes back empty
and all four pages go red. Horizontal whitespace only: ^[ \t]*\|. An inline ANCHOR NOTE guards
this in the source; do not "simplify" it back.
^\s*\|. The implementer ran it, watched it red-line thecorpus, and corrected it. Critics outrank authors on findings, but a critic's prescribed REMEDY
is still just a proposal — run it before adopting it.
This one is worth reading in full, because the false positive did not merely annoy — it propagated
into canon. The gate reported:
link_type: LINK_0185 is 'knowledge-transmission' but the line names ['parallel-independent']
That was read as "the page mischaracterizes LINK_0185". IT DID NOT. The page already said
knowledge-transmission. The error came from a DIFFERENT physical line — a bookkeeping sentence that
co-mentioned LINK_0185 beside the token parallel-independent, which described LINK_0186 and
LINK_0187. The old rule pooled every LINK id and every hyphenated type token on one physical line
and demanded each id's registry type appear somewhere in that pool, so an id merely NAMED next to
another link's type went red. Acting on that false premise, a fixer rewrote a correct,
critic-verified sentence on sumatra_java.md and in doing so deleted the registry-endorsed
guardrail that Angkor is "an author, not a passive recipient" — a §17.1 care defect in the
OVER-CREDITING direction (shifting authorship of a Khmer achievement toward the Indic source).
That prose was reverted before this fix; grep "because it received that frame" over the pages
dir now returns nothing, and sumatra_java.md:3851-3859 carries the correct both-things-are-true
wording again. Recording it here because the lesson generalizes: a false gate error is not a
cosmetic problem, it is an instruction to break working canon.
THE FIX, ROUND 1 — the unit of judgement is the CLAIM, not the LINE. Each hyphenated type-token
occurrence binds to the nearest LINK id on its line, measured edge to edge, ties resolving to the
id that PRECEDES the token (the corpus's dominant idiom is "the <type> LINK_x"). An id is judged
only against the tokens actually bound to it. Why this scoping and not the alternatives: "only
check lines with exactly one id" would blind the rule on every real bookkeeping line and is
trivially evaded by adding a second id; "require strict adjacency" breaks the equally live
LINK_0185 (link_type knowledge-transmission) and the parallel-independent LINK_0186 shapes,
which put the token on opposite sides at different distances. Nearest-neighbour keeps both.
Round 1's stated price ("a plural token is checked against the NEARER id only") was not an
acceptable price, it was a regression: in the coordinated shape
LINK_0185 and LINK_0187 are both parallel-independent
the single token bound only to LINK_0187, so a mistyped LINK_0185 went UNJUDGED — and the OLD
per-line pooling had caught exactly that. The rule's whole purpose is the §17.1 case where a page
asserts independent invention over a registry-recorded transmission, so losing the far id of a
coordinated list loses the defect class itself. The idiom is live: sumatra_java.md Section 15
writes "The flying-viscera-witch LINK_0186 (…) and the hydraulic-achievement LINK_0187 (…) are both
parallel-independent." Both ARE parallel-independent, so the page is correct — but round 1 would
not have noticed if one of them were not.
Binding is now ANCHOR + COORDINATED-LIST EXPANSION (bind_types_on_line). The token still anchors
to its nearest id, then grows outward in BOTH directions across every adjacent id whose intervening
text is list GLUE, and is claimed about every id in that list. Glue is deliberately narrow — the
gap must contain a coordinator (,, and, or, &), must contain NO type token, must contain no
sentence break (., ;, :), and, after parenthetical/bracketed asides are stripped, every
remaining word must be a bare connective (and or the a an plus both) or a hyphenated descriptor.
That single whitelist is what separates the three behaviours that must coexist:
| shape | binding | why |
|---|---|---|
A (aside) and the hydraulic-achievement B (aside) are both <type> | A and B | gap is glue: aside stripped, only and/the/a hyphenated descriptor remain |
A, B and C are <type> | A, B and C | , and and are glue |
the knowledge-transmission A and the parallel-independent B | A→kt, B→pi, separately | the gap CONTAINS a type token, which ends the list |
A (link_type <type>) / the <type> A | A only | no coordinated neighbour |
B originate on the excursion page and A is received there | nothing bound | originate/on/excursion/page/received are not connectives — the walk stops, so a binding cannot run away down a sentence |
WHAT IT CAN AND CANNOT SEE (the honest account):
parenthetical asides between the members.
live sumatra_java Section 15 sentence wraps — its two ids sit on one line and its "are both
parallel-independent" predicate on the next — so neither line judges it today. This is the one
residual that costs LIVE coverage. Joining continuation lines was rejected as too blunt a
remedy, but the reason first recorded here was FALSE and is corrected: it claimed that joining
the next sentence's lines would bind parallel-independent to LINK_0186 across "originate on
the excursion page and". An adversarial probe joined those exact lines and it binds NOTHING to
LINK_0186 — the glue whitelist stops at "originate". The honest reason to defer line-joining is
that it widens the binding surface without a proven need, not that it demonstrably breaks. If
wrapped claims are to be judged, that is the follow-up to build, with its own teeth proof.
version of this entry claimed that "removes nothing real"; that is FALSE and is corrected. A
real live case exists: angkor_khmer.md:537 writes an inline SET LABEL — "the
weaving-progression set: the architecture-proportion weaving LINK_0121 ... is deepened by ...
and the guardian-at-the-gate weaving LINK_0129 ..." — where the original per-line pooling judged
LINK_0129 and the current binding does not. Measured across all four pages the loss is exactly
one id (angkor_khmer 4 bound vs 5 pooled; bali, flores and sumatra_java deltas empty). Recorded
as OPEN-5 rather than papered over.
knowledge-transmission and parallel-independent respectively" binds each token to the wrong id.
The first version claimed this "fails LOUD, never silent" — FALSE, corrected: coordination
expansion gives every id in the list every token, so the actual not in named check can never
fire and the wrong pairing passes SILENTLY. It was equally silent under the original pooling, so
this is not a regression — but it is a silent hole, which is the worst kind, and it is OPEN-6.
lexical, and still the critic's.
_is_coordination guards [.;:] for theexpansion walk, but the nearest-id anchor does not, so a type token in a DIFFERENT sentence on
the same physical line binds to an id it does not describe and fires a FALSE RED. Inherited from
round 1, not introduced by round 2, and no live page triggers it — but this file's own headline
lesson is that a false gate error is an instruction to break working canon, so it is OPEN-7.
TEETH, proven not asserted:
--self-test carries LINK_TYPE_GOOD_FIXTURE (a correctinline characterization + the bare co-mention shape + a two-token bookkeeping line + the live
coordinated are both shape + a three-id comma list) and LINK_TYPE_BAD_FIXTURE (LINK_0185, a
knowledge-transmission link, typed parallel-independent). The self-test asserts the BAD fixture
flags ONLY LINK_0185 — flagging a co-mentioned LINK_0186/LINK_0187 would prove the rule had slid
back to judging the line.
LINK_TYPE_BAD_FAR_FIXTURE: the live coordinated shape with the FAR id mistyped(LINK_0188, knowledge-transmission, swept into an "are both parallel-independent" predicate).
The self-test requires the error to NAME LINK_0188, and separately requires that the correctly
typed near id LINK_0187 is NOT flagged — so the expansion cannot pass by over-binding.
characterization teeth are missing".
reproducing the ORIGINAL false error verbatim: `LINK_0185 is 'knowledge-transmission' but the
text binds ['parallel-independent'] to it`. This is the proof the GOOD fixture captures the real
defect and not a strawman — an earlier draft of the fixture used only the two-token line and
survived mutation B, i.e. it was a vacuous fixture. If you weaken this rule, that fixture is what
catches you.
forcing _is_coordination to return False) → self-test RED, exit 1, naming the far-id tooth:
"the FAR id of a coordinated list … was NOT caught". Restored and verified BYTE-IDENTICAL by
sha256 (fd8615d4afc1e2168a1522d78b238a013d903e9826d82950e34cd5be01981753; the round-1 hash
recorded above no longer applies — the file changed).
(angkor_khmer 6, bali 10, flores_island 1, sumatra_java 7), binding them to 22 distinct ids —
unchanged by round 2, because every live coordinated list is a WRAPPED one. The expansion loses
no live coverage and adds the far-id tooth for every unwrapped list. All four pages pass.
[SRC: ...]bracket declares, including an ordinary inline prose citation. The adjacency alternative was
tested and re-breaks the legal grouped-declaration shape flores_island uses (four sources bound
to one SUBSTRATE token in a single bracket). No live page reaches this.
angkor_khmer.md contains ZERO SRC_ ids, so the use_class rule is VACUOUSLY satisfied there. Itsgreen is not evidence the rule works.
contiguity assert. It fails LOUD, and no page has one.
This is the one residual that currently costs live coverage (sumatra_java Section 15 wraps its
coordinated are both parallel-independent sentence). Rejected remedy: joining continuation
lines, which re-opens run-away binding. The critic covers it.
were unarmed; a rigorous per-rule mutation sweep (mutate each decisive condition to a no-op, run
--self-test, record green/red) found the count was UNDERSTATED — the "header checks" are three
independent string checks and the "machine-blocks check" is two, and it omitted region_id:resolves
and region_id:snake_case entirely. The definitive unarmed set was ELEVEN conditions. All that
CAN be armed now ARE (each with a minimal single-rule BAD fixture in --self-test, each
mutation-proven RED and naming its own tooth):
header: Region Identity block missingheader: Character Quick Reference block missingheader: the HEADER END separator missingregion_id: not found (region_id present)region_id: does not resolve in T0_Region_Index (armed by injecting a populated region set; every prior self-test call passed region_ids=set(), so this elif branch was structurally
dead in the self-test — the reason its mutation stayed green)
chapter_set: no CH_NN references foundmachine blocks: Zone Catalog missingmachine blocks: Derived Indexes missingsections: missing Section(s) (1-20 completeness)derived index: BE_ not in T0_Boss_Encounter (armed by injecting a populated boss set)FK: CHAR_ not in T0_Character_Index) — the LINK branch was already armed by FK_BAD_FIXTURE, but the char_ids branch had never been
exercised; now it is.
link_type was already ARMED before this pass (see the section above); confirmed still red
under mutation.
**The ONE residual — region_id:snake_case — is UNREACHABLE DEAD CODE and cannot be armed by any
fixture.** REGION_ID_RE's capture group ([a-z0-9_]+) guarantees the captured id already
satisfies re.fullmatch(r"[a-z0-9_]+", rid), so if not re.fullmatch(...) can never be True. An
uppercase value makes the regex fail to match and trips region_id: not found instead; a hyphen
truncates to a passing prefix. This is not a missing fixture — it is a branch no input can reach.
The honest fix is a LOGIC change (widen REGION_ID_RE to capture a looser token so a malformed
slug reaches the snake_case check, or delete the dead branch), out of scope for a fixture-only
arming pass and flagged here for the next editor. A NOTE in self_test records the same.
With that one exception every rule the gate enforces is now armed and mutation-verified.
| zone_id | (say it is renamed | zone id |), theENTIRE zone rule vanishes silently — the same shape the indent fix closed, but for the column
name.
validation if its zone_id carries any uppercase, if the cited site token is capitalized, if the
source cell names one VALID site alongside an invented one (the rule is "at least one resolves",
not "all resolve"), if the cell mentions region-ambient anywhere (a by-design exemption that
also exempts an invented site named on the same line), or if the invented token is four
characters or shorter. Verified non-vacuous on real data first: all 27 zone rows across the four
pages are single-site and fully resolve, and no live row uses the region-ambient exemption, so
the four green pages are genuinely validated rather than passing through these holes.
ZONE_ROW_RE is defined and was updated by this session's indent fix, but is referencednowhere. Dead constant that invites a reader to think the zone scan flows through it.
angkor_khmer.md:537 names LINK_0129inside a "weaving-progression set" label whose type token binds elsewhere, so the original
per-line pooling judged it and the current binding does not. Exactly one id across four pages
(angkor_khmer 4 bound vs 5 pooled). Either bind a set label to its whole list, or accept it in
writing — do not leave it undocumented, which is what the first draft of this file did.
every id in a list every token, so a wrong pairing cannot fire actual not in named. Equally
silent under the original pooling, so not a regression — but a silent hole is the worst kind and
it should either be caught or explicitly declared out of scope.
_is_coordination guards [.;:] for theexpansion walk; the nearest-id anchor does not, so a type token in a different sentence on the
same physical line binds to an id it does not describe and fires a FALSE RED. No live page
triggers it. This is the same false-positive class that already cost canon prose once today.
coverage today (sumatra_java Section 15). See the CANNOT-see block above for why line-joining
was deferred rather than rejected on evidence.