corpus: freeze the damage, patch one class at a time, and say where one rule stops - #2161
corpus: freeze the damage, patch one class at a time, and say where one rule stops#2161gHashTag wants to merge 2 commits into
Conversation
Closes #2158) Two measurement tools were lost and every number they had produced became unreproducible with them. cost.py and diffbin.py were written, quoted in #2151, and never committed; the working copy was later re-cloned. Six recovery routes came back empty -- dangling objects held only a git stash WIP with triage.py, the reflog records the clone rather than the content, shell history is absent, CI artifacts hold only FPGA outputs, no PR or issue comment carries the source, and the session snapshot preserved prose about the scripts instead of the scripts. So these are reimplementations from a written contract. Recalling what the old ones roughly did would have reproduced the old one's defect. That defect was the specification for the new one. It reported "0 regressions" over 634 specs while files were losing declared struct fields, because a per-file judgement had relabelled the loss as an acceptable trade and the aggregate then printed the judgement as if it were a measurement. No differential result may be called "0 regressions" unless the metric actually checks the claimed class of loss. diffbin now assigns five ordered categories -- unchanged, field-loss, strict-improvement, malformed-input-tradeoff, unknown -- with field-loss tested before strict-improvement, so removing a phantom while dropping a declared field is a loss and not an improvement. Phantom and declared are told apart by a stated rule: a removed field is a phantom only if its base type text was empty. Only an ExprIdentifier whose parent is a StructDecl counts, so identifiers in function bodies stay out of the totals. Re-measured on the same 634 specs and the same two binaries: 616 unchanged, 13 field-loss, 1 strict-improvement, 4 malformed-input-tradeoff, 0 unknown. handoff.t27 goes from 35 parsed fields to 12. All 17 files that moved are inside the damaged set and no well-formed spec changed at all, which is what 0 unknown is carrying. cost reports per stratum with n, median, p95, min-max ms/KB and coefficient of variation, alpha only at n >= 8 with its r2 and KB range, and no cross-family alpha at all: that number is a metric of corpus composition rather than of the parser (#2133), and a printed number gets quoted while its caveat does not travel with it. damage classifies the corrupt annotations by shape rather than repairing them (#2154): 125 lines, 65 files, 15 shapes, one fixture each. The first draft reported 429, of which 230 were the legitimate bound `target : < 5000ns`, so the fix was deleting two bad signals rather than tuning a threshold. loop-tools-tracked.sh fails when a loop tool is missing, untracked, or unrouted, and was verified to fail in exactly the pre-loss state. The dispatcher no longer looks for a built compiler before running helpers that never use one.
…ne rule stops (Closes #2160) The mechanism is one character: the opening quote of the type string was replaced by '['. Substituting it back and asking whether the result is a closed string is a decision procedure, and it splits the 125 lines exactly on class boundaries -- 107 in 12 classes restorable, 18 in 3 classes truncated beyond recovery and held for a language decision rather than guessed at. Two validations per candidate, because deleting a line also makes a file parse: the field must return with a non-empty type and nothing previously present may vanish. Two measurement units, because our own first reading of six still-malformed classes (co-located destroyed lines) was checked and found false -- the cause was co-located damage from other restorable classes. Repairing those exposed a third defect that had been hidden behind them: ten files now fail on 'pub const Name(T) = struct', a parser gap on generic const struct declarations, not corpus damage. Re-run differential: 623 unchanged, 8 field-loss, 1 strict-improvement, 2 malformed-input-tradeoff, 0 unknown (was 616/13/1/4/0). All eight remaining field-loss files contain a destroyed line. field-loss is not zero, so #2151 stays undecided. Negative fixtures were tested for discriminating power, not assumed to have it: the reconstructed first signal set fires on 6 of 6, the current one on 0. No spec under specs/ is modified by anything here.
Disclosure: this stacked PR is NOT gate-equivalent to a master-based one
Cause, read from the workflow files rather than inferred:
Base here is Ran the two content gates by hand instead, so the claim is measured rather than assumed:
Not verified by hand: whatever |
#2630 established that the boundary column's denominator holds two populations -- comparisons that decide a verdict, and loop bounds and display cutoffs that cannot -- and that no line-local filter separates them, because a kill is the only proof of verdict-reachability and the filter removed proven kills. That leaves the column unreadable rather than wrong. `SURVIVED at boundary lines 45, 91, 214, 223, 226, 409` gives a reader nothing to act on. Two changes, neither touching the measurement. Print the source beside the line number, for this operator only: 91 `if len(out) > 6:` a display cutoff 214 `while len(v) < N:` a loop bound 226 `while j < len(src):` a scan bound 223 `if b < 0:` the only one worth reading Five of six classify themselves at a glance. The reader does the separation the tool cannot, in seconds rather than by opening the file. And say what the denominator is, in the summary, only when this operator ran -- so killed/total is not read as a rate. The rule: when a metric mixes populations you cannot separate, do not report a ratio; report the members. A rate over a mixed denominator invites exactly the conclusion this command exists to prevent, and invites it most from whoever computed it. The killed count keeps its meaning: a LOWER BOUND, established after the fact, on how many comparisons reach a verdict. Smaller than a percentage, and true. 194 tests pass. Refs #2161
* gft encoder: pin the smallest normal binade `if off < 0: return 0` survived the boundary operator. Printing the source beside the line number (#2634, yesterday) showed it is not a loop bound. `off = e + 40` is ZERO in the smallest normal binade, [2^-40, 2^-39). With `<=`, every value there encodes as 0 -- which is the ZERO SENTINEL. A non-zero magnitude silently reported as zero, in the encoder every trained weight passes through. The exact power 2^-40 is NOT a witness: its mantissa is 0, so it already encodes as 0 and the mutant changes nothing there. A witness needs a non-zero mantissa in that binade, so the pin is 1.5*2^-40, which encodes 256 and would become 0. Three assertions: the magnitude, the sign, and the value one binade below which IS legitimately zero. Negative control: with `<=` planted the tool exits 1 on the new assertion naming it; restored, 0. Nothing else in this file reaches that binade -- every value the training self-tests encode sits many binades higher, which is why the mutant lived. This also corrects a generalization I made yesterday. Of the four `if x < 0:` sites across the tools, only the `find()` idiom in verify_igla_race is equivalent (find returns -1 or an index, and every caller passes a match start whose first character is a keyword). The other three -- this one, `d < 0` in _magsub, and `end < 0` after rfind in diffbin -- are real boundaries where 0 is a valid value. "Boundary survivors are loop bounds" was too broad. Refs #2161 * skill: a surviving boundary was real, and the boundary value was not its witness (Refs #2161)
* gft encoder: pin round-half-to-even on exact ties
`if t > hf: mant += 1` in _magadd survived the boundary operator. It is
the round-half-to-even decision:
if t > hf: mant += 1 strictly above half
elif t == hf and (s & 1): mant += 1 exactly half -> to even
With `>=` the pair becomes round-half-UP and the parity test on the
next line is dead code. Measured:
_magadd(25600, 20480) clean 25600 mutated 25601
_magadd(20992, 20481) clean 21248 mutated 21249
_magadd(21504, 20482) clean 21632 mutated 21633
256 such witnesses exist. The self-tests did not notice ANY of them,
because they train a net and check ACCURACY: an optimiser absorbs a
last-bit error in every addition without changing whether XOR reaches
4/4. An outcome test cannot see an arithmetic defect that outcomes
tolerate.
Three assertions pin the decision itself, one per branch: a tie with
even `s` must NOT round up, a tie with odd `s` must, and strictly above
half must. Negative controls: weakening `>` to `>=` fails the first,
disabling the parity test fails the second.
A correction, too. I planned this tick around line 203, `if d < 0` in
_magsub, listed yesterday among four boundary sites -- without checking
it was a survivor. It is killed: with `<=` the XOR self-test reports
1/4. A finding recorded as a line number expires on the next edit, and
that rule is one I wrote.
Refs #2161
* skill: an outcome test cannot see a defect the outcome tolerates (Refs #2161)
… (#2644) * gft multiplier: pin round-half-to-even on both carry paths `_magmul` rounds with the identical pair `_magadd` uses, over a product instead of a sum, and its `if r > half` survived the boundary operator for the same reason: the self-tests train a net and check ACCURACY, and an optimiser absorbs a last-bit error in every multiply as readily as in every add. Two paths with different `half`, so four cases rather than three. Only the even-q rows distinguish `>=`; only the odd-q rows distinguish a dead parity branch: case clean r >= half parity off carry=0, q even 20610 20611 20610 carry=0, q odd 20738 20738 20737 carry=1, q even 20998 20999 20998 carry=1, q odd 21016 21016 21015 Measured by planting each mutant, not derived. AND MY FIRST NEGATIVE CONTROL DID NOT APPLY. It reported the `>=` mutant as surviving the new assertions. It had never been planted: the replacement string I built lost the leading `if `, so `str.replace` matched nothing and rewrote the file unchanged. A silent no-op substitution reads as the strongest possible evidence for the wrong conclusion -- "your assertion does not catch this" -- when nothing was tested at all. Both controls now assert the anchor exists before replacing. With that, `>=` is caught by the even-q assertion and disabling parity by the odd-q one, each by the assertion aimed at it. Refs #2161 * skill: the same defect one function over, and a control that did not apply (Refs #2161)
…#2161) The gate asks for docs/now/<date>-<slug>.md, which is where 184 entries live. Root NOW.md has not changed on master since 2026-08-09; editing it satisfied nothing and was the wrong file.
… (Refs #2161) Section 116. The mechanism (512 == 1 << 9 is the low bit of the exponent field, so a stuck mantissa renormalises for free on even exponents), the three probe failures that hid it, and the two controls that localised the fault to the input space rather than the harness.
) (#2648) * gft interpreter: pin renormalisation carry at all four sites (Refs #2161) `enc`, `_magmul`, `_magadd` and `_magsub` all end in if mant >= 512: mant = 0; <exponent> += 1 and all four survived the `>=` -> `>` boundary mutant, for one shared reason: the mutant leaves mant == 512, and `(off << 9) | 512` is IDENTICAL to `((off + 1) << 9)` whenever `off` is EVEN, because 512 == 1 << 9 is the low bit of the exponent field. The stuck mantissa renormalises the value by accident across half the exponent space. Two further reasons a sweep misses this, both measured: * mant == 512 is reached only by rounding UP from 511, so a sweep of exactly-representable inputs never enters the branch; * in `_magmul` the carry path cannot reach it in principle -- the largest product 1023 * 1023 == 1046529 gives q == 1022, mant == 510. Four assertions, each checked both ways: holds clean, fails when its own site is mutated, catches no other site's mutant. Boundary column for this file, measured after the change: killed 8/31 -> 12/31. * docs/now: renormalisation carry entry; drop the root NOW.md edit (Refs #2161) The gate asks for docs/now/<date>-<slug>.md, which is where 184 entries live. Root NOW.md has not changed on master since 2026-08-09; editing it satisfied nothing and was the wrong file. * ci-gates skill: three sweeps, three ways of never reaching the branch (Refs #2161) Section 116. The mechanism (512 == 1 << 9 is the low bit of the exponent field, so a stuck mantissa renormalises for free on even exponents), the three probe failures that hid it, and the two controls that localised the fault to the input space rather than the harness.
…Refs #2161) (#2653) `_magsub` resolves a rounding tie three ways and only two arms are live. Deleting `if sticky: mant += 1` changes 0 of 525_918 outputs; forcing the sticky DETECTOR to always fire changes 9851. So the arm is wired -- it is the value `sticky` takes that never reaches the tie. An exhaustive search over every (hm, lm, d) for ho in {2, 3, 40, 79} finds no input where `rem == half` and `sticky == 1` hold together. That is what keeps the function correct. The arm is the ADDITION rule in the SUBTRACTION path: discarded bits of the subtrahend make the true difference smaller, so a tie with lost bits sits strictly below half and must round DOWN. Measured with exact rationals -- the code's diff is 13303794 against a true 13303793.734375. Reported as #2652, not changed: this mirrors board/bpseq.v and the RTL has not been measured. Three assertions, each catching a different operator class -- boundary, dead-branch, and deletion -- verified by planting all six mutants. `if d >= 26` -> `> 26` is recorded as a proven equivalent mutant so the next tick does not re-chase it.
…em (Refs #2161) (#2656) * tri gates mutate: refute the equivalence claims instead of quoting them (Refs #2161) `# mutant-equivalent: <why>` marks a survivor as unkillable by construction, and this command printed it as "claims equivalent: ...". The word `claims` was carrying real weight: nothing had ever checked one. Six sit in tools/. The run best placed to notice a stale claim is this one -- it already built the mutant and already knows the verdict. A claimed line whose mutant DIES is now reported as contradicted. Measured against the six that already existed: none contradicted. Counts, not membership: a line can hold two mutable sites (`if a < 1 or b < 1:`), and a claimed line with one of two dead has been contradicted once. Keying on "did the line leave the survivor list" would call that claim intact. Claims name no operator, and every one in the tree argues about a comparison. A line equivalent under `boundary` can die under `invert`, so the report names the direction rather than pretending to judge. `contradicted_claims()` is separate from the run loop and has four tests: survived, died, half-died, and claimed-but-not-mutated-in-this-direction. A checker nobody can test is the same failure one level up. * tri gates mutate: refute the equivalence claims instead of quoting them (Refs #2161) `# mutant-equivalent: <why>` marks a survivor as unkillable by construction, and this command printed it as "claims equivalent: ...". The word `claims` was carrying real weight: nothing had ever checked one. Six sit in tools/. The run best placed to notice a stale claim is this one -- it already built the mutant and already knows the verdict. A claimed line whose mutant DIES is now reported as contradicted. Measured against all six that already existed: none contradicted. Counts, not membership: a line can hold two mutable sites (`if a < 1 or b < 1:`), and a claimed line with one of two dead has been contradicted once. Keying on "did the line leave the survivor list" would call that claim intact. The marker must now OPEN the comment rather than appear anywhere in it. Prose describing the mechanism -- "that reasoning sits on the line as a `# mutant-equivalent:` claim" -- registered as a claim of its own, bound to whatever code line followed, which the new check would have reported as contradicted the day that unrelated line's mutant died. Caught because the count printed 2 where one claim had been written. Every real claim in the tree already opens its comment this way. `contradicted_claims()` is separate from the run loop; six tests cover survived, died, half-died, claimed-but-not-mutated-in-this-direction, mention-is-not-a-claim, and marker-opens-the-comment. First claim written under the check: gft_backprop_microcode.py's `if d >= 26`, proven equivalent over 525_918 points.
…2161) (#2659) Closing the sticky finding I cited `board/bpseq.v` as the reason not to change a rounding rule I had just shown to be wrong. `git log --all` for that path is empty -- it has never existed in this repository. The pointer came out of the file's own docstring and I repeated it without checking it resolved. The counterpart is `specs/ternary/gft_sadd.t27`. Measured: it carries the rule line for line, 30 specs carry a copy, the arm is dead there too (exhaustive over every (hm, lm, d) for six ho), and the spec's barrel shift agrees with this file's 12-step loop bit-for-bit over 2_193_075 points. The generated Verilog, C and Rust all carry the same dormant rule, and the bit-exactness gates cannot see it: they prove the compiler faithful to the spec, never the spec correct. verify_multitarget's operands were `uniform(-4, 4)` -- off 35..41, six of 81 exponents. Widened to 16 of 81 across the full range by APPENDING pairs, so the existing 600 stay byte-identical: 1744 pairs, still green; negative control gives 12 mismatches on a mutated model. New `tri pointers`: prose pointers ("see X", "cf. X") that do not resolve. Every path-shaped string was measured first -- 873 mentions, 409 unresolved, mostly paths a program creates -- and discarded as noise. The narrow form gives 193 and 16. Its first run flagged its own docstring; the docstring was reworded rather than the tool special-cased.
…2161) (#2661) `tri pointers` shipped, and its first run on master flagged §120 -- the section documenting it -- because that section quoted the dead path after a cue word while explaining the bug. 16 -> 17, and the row named the skill file. Third instance in one session, after the equivalence marker and the pointer tool's own docstring. The trigger lives in the vocabulary, so every document that teaches the vocabulary carries it; the durable defence is to run the checker after writing about the checker and read the count. Fixed the writer, not the tool, as the rule chosen one commit earlier requires: a special case for skill files would blind the checker to exactly the documents most likely to discuss paths.
…n (Refs #2161) (#2663) * tri gates mutate: the boundary scanner never left the control function (Refs #2161) `--boundary` reported 31 sites for tools/gft_backprop_microcode.py, and an independent tokeniser counted 31 comparisons before `if __name__`. The numbers agreed and the story wrote itself: the operator declines to mutate a file's own self-test. Both numbers were real and the agreement was a coincidence. Asking for the site LINES instead of the count showed the scan ending at 371 -- nine lines before `__main__`, at `def self_check():`. `is_control_fn` sets `in_control` and `boundary_sites` never reset it, so everything from 380 to end of file had silently never been mutated. 31 -> 62 sites, which is the tokeniser's count for the whole file. Three of four site finders already carried this fix (T125). They are line-oriented; this one is byte-oriented, so it had no line to attach to when `--boundary` was added. The motivating file cannot test the other half: `self_check` there holds zero comparisons. A synthetic helper/control/`__main__` source covers all three answers, and reverting the fix makes it fail with `[2]`. Also in this commit: * `_magsub`: assert `sticky == 0` at every tie. The arm below it is the addition rule in the subtraction path and is correct only because it never runs; this makes waking up loud without deciding what the rule should be (that is 30 specs, #2652). Positive control: waking the detector fires it. * `tri pointers`: `(?![\w])` restored -- without it `.v` matched inside `docker/Dockerfile.vivado` and the tool's own first run invented a dead pointer. The guard was in the exploratory version and lost in the rewrite. * `tri pointers`: dead pointers are now split by history into NEVER (no commit ever -- the pointer was wrong when written) and REMOVED (stale, and the history says where it went). 12 and 2. Both REMOVED ones were moved by one docs restructure and are fixed here, taking that class to zero. * docs/now: the count was right for the wrong reason (Refs #2161)
…2666) `if d > 26` on master should be `if d >= 26`. A full `tri gates mutate` sweep was running in the background when I staged with `git add -A`, and the mutant it was holding at that instant went into the commit and through the merge. Behaviour is unaffected -- this is the mutant proved equivalent over 525_918 points, and the line's own `# mutant-equivalent:` comment says so. That is what makes it worth fixing rather than shrugging at: the code stopped matching the comment directly above it while every test stayed green. Skill section 122: the dirty-tree guard protects the run, not the operator. `git add -A` cannot tell a mutant from an edit, and `target/.tri-mutating` was sitting right there unread.
…rol (Refs #2161) (#2687) A five-way fan-out audit raised 19 findings; 11 survived adversarial refutation, 8 were killed. SCANNER (cli/tri/src/gates.rs), three defects, each with a test that fails when its fix is removed: * The three LINE-oriented site finders had no string state, so a flush-left line inside a control function's docstring cleared `in_control` and handed the operators the control's own `assert` -- a site nobody can kill, because neutering a control makes it pass. Two files differing by four spaces of indentation on ONE docstring line scored 3 assert sites versus 0. * The same blindness scored word-wrapped prose beginning a line with `assert` as an assertion to neuter. * `assert_sites`' message splitter advanced one byte on a backslash instead of two, so an escaped quote either produced a mutant that does not PARSE (python exits 1, the site scores KILLED -- a false green in the column this command exists to make trustworthy) or stranded the quote state and silently dropped the message. `boundary_sites` had `i += 2` all along. CONTROL (tools/gft_backprop_microcode.py): the second plant did not test what it says. `input [31:0] x0i` spelled out occurs exactly once in the file -- at the assertion that checks for it -- because the emitter builds ports with an f-string. The plant rewrote the assertion's own expected string and the emitted Verilog was untouched. It now targets the emitter's format string. STALE NUMBERS of mine, corrected: * boundary denominators of the form k/31 came from the scanner bug; the file has 62 sites. Killed count is unchanged at 14. Errata added. * `tri pointers`' docstring froze "193 mentions and 16 unresolved"; the tool printed 191 and 12 within a day. The counts are removed rather than updated -- a number in a docstring cannot learn it went stale. * `--boundary`'s help advertised "the worst kill rate of the five (26 of 77 across the tree)", whose denominator this same fix doubled. Also: external/README.md and external/OWNERS.md pointed at a canon path that moved. Owner issues filed: #2685 (a live PRL draft claims zero admitted lemmas; proofs/ has 32, and the cited audit trail never existed) and #2686 (Gate Topology audits 15 of 45 workflows and misses two live violators; coq-proofs.yml has never been green in 50 runs).
…16/34 (Refs #2161) (#2702) The column was the number of planted faults, not a verdict on 32 assertions (§123). The model was tested by predicting before measuring: 2 plants -> 2/34, +7 -> 9/34, +7 more -> 16/34. Three for three. Cost was never the obstacle and nobody had checked. Each new plant fires in the arithmetic block at the top of `__main__`, before any training: 0.06s apiece. The control went 11.8s -> 12.5s for eight times the coverage. The old assumption that more plants meant more whole-program runs was generalised from a sample of two, both of which are full runs. Within a family the fault has to be surgical -- adjacent assertions test adjacent cases, so the obvious fault breaks them all and only the first is seen. `elif t == hf and (s & 1)` -> `... and False` kills the ODD tie only, leaving the even one true so it passes and the odd one speaks. One resisted and is left alone: disabling `_magsub`'s `if rem > half` also breaks the renormalisation-carry case, which is checked earlier, so the plant fires that instead. Separating them would mean encoding the exact remainders, and then the control is a second copy of the subject. Also: `spawned()` now asserts the plant edited the SUBJECT. All subject code sits above the control and every assertion under test below it, so the first changed byte must precede `def self_check(`. T124 caught a plant that edited the control's own source; T211 one that edited the assertion checking the result. Both went green. Neither was caught by reading; both would have been caught by this line.
Closes #2160
Stacked on
w699-restore-loop-tools(#2159), not onmaster—damage_repair.pyimports the field extractor fromdiffbin.py, which lives on that branch. Base it on master and the import breaks. Review #2159 first.No spec under
specs/is modified by anything here. Repairs are written to a scratch tree;specs/is read-only to both new tools.What the tools do
tri damage-freeze— writes the snapshot, 125 rows with class id, shape, file, line, field, verbatim rhs, ±2 lines of context, and the file digest.docs/corpus/damage_snapshot_2026-08-15.json,corpus_sha256 = 1b5a37b7a89efb782db7efd2ca7af728d4ed48b1c13ac2a35088c7926b16afd9.tri damage-repair— one candidate per class, reversible diff, effect measured. Refuses to run if any file digest has moved since the freeze, because a patch applied to a changed file is applied to a different file than the one surveyed.The mechanism
Intact:
name : "TypeText",. Damaged: the opening quote became[.children : "[4]?QuadNode",→children : [[4]?QuadNode",. One fact, both signals, one-character patch, self-inverse.The split, which is the result
[[]Const [,→"[]Const [,is not closed.[]Const []Const u8and[]Const [N]u8are both plausible; the file has no evidence. Held asneeds-human-language-decision, owner: language owner. Criterion: state what an unclosed element type in slice position means and whether a placeholder is permitted.Double validation
Deleting the line also makes a file parse. So: (1)
t27c parseexits 0; (2) the field is back with a non-empty type and nothing previously present vanished.Two units, because a hypothesis of mine was wrong
Per-class: 6
parse-restored, 6still-malformed, 0ambiguous, 3needs-human-language-decision.I read those six as co-located unrestorable damage. Checked: false — 0 destroyed lines across all seven files. Real cause: co-located damage from other restorable classes, untouched by a single-class run. Hence
--combined:A third defect, visible only after the first two were fixed
10 of 11 remaining files fail at
pub const Name(T) = struct {—Unexpected token in expression: KwStruct. Parser gap on generic const struct declarations, not corpus damage.bitset.t27fails atExpected LParen, got KwTest— separate, unclassified. Both need their own issues; neither is in scope here.Re-run differential
623 unchanged, 8 field-loss, 1 strict-improvement, 2 malformed-input-tradeoff, 0 unknown(was616 / 13 / 1 / 4 / 0).8 of 8 remaining field-loss files contain a destroyed line — exact correlation.
field-loss ≠ 0, so the gate on #2151 is not met. #2151 stays undecided and must not be merged.
Fixtures, tested rather than assumed
15 positive (one per class, carrying shape/origin/candidate/expected effect) + 6 negative pinning the false signals:
target : < 5000ns, match arms, multi-line arrays, function signatures, raw strings, intact convention.Discriminating power: reconstructed first signal set fires on 6 of 6 negatives, current on 0. On the real corpus naive = 1378 lines, current = 125.
loop-tools-trackedextended to both new tools and verified to FAIL (exit 1) while untracked and PASS after commit.Not claimed
cost, full-run hang undiagnosedfpga-formal/fpga-synthesisred is the master baseline (#2153).check-first-party-doc-language.shhas 8 pre-existing errors on master; none is a file from this branch.