Repository navigation
XML safe subset: #2 carried forward with the review fixes (bar v2) - #4
Merged
Merged
Conversation
The commits that follow change Spec.lean, SPEC.md, the tests and the page, all of which the spec hash covers. Under bar v2 a person who wrote the specification cannot approve it, so the second writer is listed next to the original author. Status stays in-progress with no approval entry. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AovXVgJBHGiN44TxQM4dcd
Review 1 finding 6 (repeated in review 3): run_tool never read the exit status, and the generated, rejection and malformed parts paired answers with inputs through zip with no length check, so a tool that died after ten answers still gave "Failures: 0". Generated failures were also recorded only while fewer than ten had been seen, and the tool was rebuilt only when the binary was missing. Now run_tool records a failure for a non-zero exit status, a timeout, or a number of answers that differs from the number of requests, and pads missing answers with a value no check accepts. Every failing check is counted, and only the printed list is cut at 40. The script always runs lake build, which does nothing when the build is current. Checked by pointing the harness at a wrapper that answers ten requests and then dies with SIGSEGV: the run now reports 3248 failures and exits 1. A normal run is unchanged: 0 failures. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AovXVgJBHGiN44TxQM4dcd
Review 3 found bar items 5 and 6 open on format: tests/TEST-RESULTS.md had no "- Inputs:" line, and the page's status line read "Status: in-progress", which the bar checker does not take as "in progress". Review 1 finding 7 also noted that "subset mode" had nothing behind it: et_parse is the stock ElementTree parser. The record now gives the oracle with the Python and expat versions and the exact parser setup, the seed, and an Inputs line with the number of requests sent to the tool. The README status line reads "Status: in progress". The README and the harness describe the ElementTree setup as it is (stock XMLParser, namespace processing on, comments and processing instructions kept) instead of "subset mode". The harness keeps lake build output off stdout unless the build fails. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AovXVgJBHGiN44TxQM4dcd
Blocking item 1 of review 3 on 4875cec (first raised as review 1 finding 2): a default namespace declaration was accepted as an ordinary attribute. <a xmlns="urn:x"/> gave the tree a/xmlns=urn:x, while ElementTree reads {urn:x}a with no attributes, and the page said documents with namespaces are rejected. The colon ban on names does not catch it, because DefaultAttName is the plain name xmlns. - Xml/Value.lean: new xmlnsName; AttrsOk now also requires that no attribute is named xmlns, so the round trip still covers every tree the parser can give. - Xml/Parse.lean: parseAttrs refuses an attribute named xmlns. Prefixed declarations (xmlns:p) were already refused by the colon ban. Only the exact name is refused: xmlnsx, XMLNS and an element named xmlns declare nothing. - Spec.lean: Spec.rejects_xmlns, a general statement: an attribute named xmlns on the root element is refused after any well formed prolog and any well formed attributes before it, printed, whatever follows the name that does not make it longer. Proved in the new Proofs/Refusals.lean, re-exported from Proofs/SpecProofs.lean, and listed in comparator.json. bar.toml lists Xml.xmlnsName as code under proof. - Proofs/RoundTrip.lean: parseAttrs_print takes the new AttrsOk field. - tests: six named rejections (default declaration, empty, single quotes, after another attribute, on a child, prefixed) and a fixed case that accepts xmlnsx, XMLNS and xmln as plain attributes plus an element named xmlns. The generator never names an attribute xmlns. - SPEC.md and README.md state the rule and the new WF condition, and the new theorem's row says exactly what it covers. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AovXVgJBHGiN44TxQM4dcd
Review 1 finding 4, repeated in reviews 3 and 4: <a b="1"c="2"/> was accepted and printed as <a b="1" c="2"/>. XML 1.0 productions [40] and [44] read (S Attribute)*, so whitespace must stand before each attribute; expat, libxml2 and ElementTree reject the input, as do the W3C not-wf cases o-p40fail1 and o-p44fail4 that the tool accepted. - Xml/Parse.lean: new startsWithWs; parseAttrs reads a name only when the text before it starts with whitespace. After the element name the next character is never a name character, so the check only bites after a closing quote. - Proofs/RoundTrip.lean: the printer writes one space before each attribute, so parseAttrs_print needs one more fact. - Spec.lean: Spec.rejects_attribute_without_space, a general statement: on the root element, after any well formed prolog and at least one well formed attribute, printed, a name start character right after the closing quote is refused, whatever follows. Proved in Proofs/Refusals.lean and listed in comparator.json; bar.toml lists Xml.startsWithWs as code under proof. - tests: five named rejections (both quote kinds, both W3C forms, a child element) and a fixed case with tab and line feed separators. The generator now varies the whitespace before each attribute (never empty), before the end of a tag, in end tags, and at the top level, so the 3200 generated documents differ from the earlier run. - SPEC.md and README.md state the rule, and the new row says exactly what the theorem covers. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AovXVgJBHGiN44TxQM4dcd
Review 1 finding 5, repeated in review 3: a literal carriage return was kept in the tree. <a>x CR LF y</a> read as x CR LF y and printed as <a>x \ny</a>, which every conforming parser reads with a CR, while ElementTree reads the input as x LF y. Section 2.11 requires CR LF, and a CR not followed by LF, to become one LF before parsing. The plan does not choose otherwise, so the parser now does that. - Xml/Parse.lean: new normEol; parse normalizes the text, then reads it. parseAttrValue drops its CR LF branch, which normalization made unreachable; a CR there would also read as a space. - Xml/Value.lean: WF now requires that comment bodies and processing instruction data hold no CR. After normalization the parser can never give one there (they have no character references), and the printer could not write one back. - Proofs/RoundTrip.lean: the print of a WF document holds no CR (noCR_print), so normalization leaves it unchanged and parse_print still holds. - Spec.lean: Spec.crlf_reads_as_lf and Spec.cr_reads_as_lf, general statements: anywhere in a text, CR LF reads exactly as LF, and a CR not followed by LF reads exactly as LF, unless the text before ends with a CR. Proved in the new Proofs/LineEnds.lean and listed in comparator.json; bar.toml lists Xml.normEol as code under proof. Proofs/Refusals.lean now follows the parser through normalization. - tests: ten fixed cases (text, CDATA, comments, PI data, attribute values, tags, the top level, a CR kept by reference). The generator spells line feeds of the tree as LF, CR LF or a lone CR in text, comments, PI data, tag whitespace and the top level, and ElementTree now agrees on all of them. The named difference the harness used to describe is gone. - SPEC.md gets a Line ends section and the WF conditions; the boundary that said CR is kept is removed, from README.md too. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AovXVgJBHGiN44TxQM4dcd
lake comparator --paranoid failed on this branch, as the author reported on 4875cec: nanoda, one of the six kernels, overflowed its stack. Exporting each theorem and running nanoda on it alone showed the cause: a simp step that reduces a match over character literal patterns (for example parseAttrs on '>' :: rest) leaves a term that makes nanoda compare the function with its own matcher, and that check recurses deeper than nanoda's stack. A one-line example with a three-arm match on Char literals reproduces it; Nat and Bool literal patterns do not. The same reduction proved by rfl, or by split, is accepted. - Proofs/RoundTrip.lean: parseAttrs_print_open ends in rfl; the new parsePI_space states the processing instruction step after a space and is proved by split; parsePI_print uses it. - Proofs/Refusals.lean: parseAttrs_xmlns rewrites and ends in rfl. No statement changes. Each theorem in RoundTrip, Refusals, LineEnds and SpecProofs now passes nanoda on its own. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AovXVgJBHGiN44TxQM4dcd
Review 1 finding 3 (repeated in review 3), and the general statement that review 3's blocking item 2 prefers: the plan's main claim, "printing a parsed document gives a document that parses to the same tree", needs parse -> WF, and no theorem gave it. - Proofs/ParseWF.lean (new): Xml.Proofs.parse_wf, for every text cs, parse cs = some d -> WF d. It goes reader by reader: each gives back legal characters, valid names, comment bodies with no -- and no final -, PI data with no ?> that does not start with whitespace, attribute lists that meet AttrsOk (unique names, none xmlns), and a rest of the text made of characters of its input, so no comment or PI data can hold a carriage return after normalization. The one non-local fact, that no two text runs stand next to each other, is nodes_head_not_text: character data stops at the end or at a less sign that does not open CDATA, and the content reader never starts its answer with a text run there. Then print_parse follows from the round trip. Every theorem in the file passes nanoda on its own. - Spec.lean: Spec.parse_wf and Spec.print_parse, with a note that a parser refusing every text meets both and Spec.parse_print rules it out. Re-exported from Proofs/SpecProofs.lean and listed in comparator.json. - tests: a new part sends every canonical print the tool gave back to it and requires the same print (3248 of 3248). - README.md and SPEC.md: two rows, and "What is not proved" now says that neither statement ties the tree to the text it came from (a parser that read &e; as some character would meet both), which rests on the tests. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AovXVgJBHGiN44TxQM4dcd
… theorem says Blocking item 2 of review 3 on 4875cec: the "What is proved" tables in README.md and SPEC.md stated 17 general rules ("a reference to an entity the subset does not define is refused", "a DOCTYPE is refused") while each theorem pinned one input. A parseRef that expands &e; and © met all 18 statements. Each single-input refusal in Spec.lean is replaced by a statement over a family of texts, and every row now names that family and no more. Most families are: any well formed prolog, printed; for most, the root's start tag with any valid name and attributes meeting AttrsOk, printed; then the forbidden form; then anything. - Spec.rejects_undefined_entity: in the root's first run of character data (any text with no < and no & before it), &name; for any valid name other than lt, gt, amp, apos, quot is refused. The new Spec.rejects_undefined_entity_in_attribute says the same for an attribute value of the root. Both refuse the reviewer's mutant. - rejects_doctype and rejects_cdata_in_prolog: after any prolog. rejects_xml_declaration: <? and a target that is xml in any case. rejects_text_before_root, rejects_digit_name (the ten digits), rejects_colon_in_name: after any prolog. rejects_second_root: any WF document, printed, then < and a name start character. - rejects_mismatched_tags: any WF root element whose end tag names another valid name. rejects_unclosed_element: the root's start tag at the end of the text. - rejects_surrogate_ref (�), rejects_zero_ref (�), rejects_cdata_close_in_text (]]>): in the root's first run of character data. rejects_dashdash_in_comment: a comment that is the root's first content, with a body a WF comment may hold, then -- and any character but >. rejects_duplicate_attribute, rejects_lt_in_attribute, rejects_unquoted_attribute: on the root, after its printed attributes. All are proved in Proofs/Refusals.lean and listed in comparator.json (25 theorems). README "What is not proved" now says that each refusal covers only the positions its row names, so "no entity is ever expanded" is a theorem only in the root's first text run and in the root's attribute values. SPEC.md uses the same rows and says the rules are the specification, with the theorems listed separately. The nanoda kernel overflowed its stack on some of these proofs. It does so when it must compare a character literal with a different literal pattern while checking a matcher, and how deep it goes depends on what it checked before. The step lemmas therefore take the characters as variables, and the literal cases instantiate them. The whole export of the 25 theorems passes nanoda, and lake comparator --paranoid accepts all of them under all six kernels. tests: 18 named rejections for the same rules in other positions (after a prolog, after text, after other attributes, after a whole document). 72 of 72 rejected; 0 failures. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AovXVgJBHGiN44TxQM4dcd
- Xml/Parse.lean: list the whitespace rule for attributes, and say what is and is not proved about the fuel (enough for every canonical print; not proved for every accepted text), instead of "always enough". - Proofs/RoundTrip.lean: the header said a spec statement still had to be added by a reviewer; Spec.parse_print states it now. - tests/run_tests.py: the named rejections are no longer one input per rule. - README.md Re-check it: the test command also sends every canonical print back to the tool; lake build is described as building with no errors, since the original proof file carries linter warnings. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AovXVgJBHGiN44TxQM4dcd
Review 1 finding 7 (minor): the named rejections had no case for A (upper case X), , a DOCTYPE inside content, or <?xml after the root, and the generator wrote ASCII text only. All four are now named rejections (the tool refuses each), and the generator's text and attribute characters include e acute, a CJK character, U+FFFD and an emoji outside the Basic Multilingual Plane. Same seed; 76 of 76 rejections, 3200 of 3200 generated documents agree with ElementTree, 0 failures. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AovXVgJBHGiN44TxQM4dcd
The independent check found that SPEC.md, under "Document type declarations and other <! forms", said as a plain fact that the parser never expands an entity beyond the five predefined ones. The theorems prove this in two places only: the root's first run of character data (Spec.rejects_undefined_entity) and the root's attribute values (Spec.rejects_undefined_entity_in_attribute). A parser that expanded &e; only inside a nested element would meet every statement and still accept <a><b>&e;</b></a>. The sentence now says the parser must never expand such an entity, names the two theorems and the positions they cover, and says every other position rests on the tests. The README already said so under What is not proved. The tests gain seven named rejections in positions no theorem covers: inside a nested element, after a child element, after a comment, after a CDATA section, after a predefined reference, in a nested element's attribute, and in an attribute after a reference. The tool refuses all of them, and so does ElementTree. tests/TEST-RESULTS.md is rewritten from a fresh run: 6591 inputs, 83 of 83 named rejections, 0 failures. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AovXVgJBHGiN44TxQM4dcd
The independent check found one sentence under What is proved that named no theorem: "A carriage return written as in text or an attribute value reads as itself, and the printer writes it that way." Bar item 2 asks every claim on the page to name its theorem. Only canonical prints back it, through Spec.parse_print. Nothing proves how 
, or elsewhere, reads. The paragraph now says what is proved: line ends read as the rows for Spec.crlf_reads_as_lf and Spec.cr_reads_as_lf state, and a carriage return in a well formed tree prints as and survives the round trip by Spec.parse_print. It sends the rest to What is not proved. That section now says a parser that read 
, or outside a canonical print, as another legal character would meet every statement, and that the differential tests check the reading. Two fixed cases cover those spellings: 
 and in a single quoted attribute value, and 
 and 
 in a nested element's text and attribute. Both read as a carriage return, as ElementTree reads them. tests/TEST-RESULTS.md is rewritten from a fresh run: 6595 inputs, 50 of 50 fixed cases, 0 failures. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AovXVgJBHGiN44TxQM4dcd
The independent check listed the build's linter warnings. One was in Xml/Parse.lean: the arm of parseCharData that stops before a less sign binds the fuel as fuel but never uses it. The arm now binds it as _, so the linter is quiet. The match, its equation lemmas and what the function computes are unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AovXVgJBHGiN44TxQM4dcd
The independent check listed 57 linter warnings in Proofs/RoundTrip.lean, all from the original round trip proof. 53 were simp arguments the linter found unused: mostly List.append_assoc and hypotheses such as h1 to h4 that simp never rewrote with. They are removed, as the linter suggests. The other four used if_pos and if_neg, which Lean 4.35 deprecates in favour of ite_eq_left and ite_eq_right, with the same statements. The new names replace them. No statement changed. lake build now reports no warnings at all. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AovXVgJBHGiN44TxQM4dcd
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AovXVgJBHGiN44TxQM4dcd
The org's license policy (mission#9, mission#26) asks each Lake package to set license = "Apache-2.0". The LICENSE file came from main. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AovXVgJBHGiN44TxQM4dcd
orda-aska
approved these changes
Oct 29, 2026
orda-aska
left a comment
Contributor
There was a problem hiding this comment.
Approving head 671b4dd.
What I checked
- CI:
bar / baris green on this head. The log showslake build(18 jobs) andcheck.py --run-testsonline. Items 2 to 6 PASS, and item 3 sayslake comparator --paranoidaccepts all 25 theorems under every bundled kernel, so the nanoda overflow from #2 is gone. Item 1 (no spec approval recorded) and item 7 (main does not requirebar / bar) are OPEN. Both are expected while bar.toml saysin-progress. - Local build:
lake buildon Lean 4.35.0 succeeds with 18 jobs and no warnings.python3 tests/run_tests.pygives 0 failures and rewritestests/TEST-RESULTS.mdbyte for byte. - Escape hatches:
Xml/,Proofs/,Main.leanand the root modules contain nosorry,admit,axiom,native_decide,implemented_by,extern,unsafe,partialoropaque. The onlysorrys are the challenge statements inSpec.lean. - Merge: the branch merges cleanly with main, which already holds the LICENSE merge.
Findings from the earlier reviews on #2
I checked each one on the compiled binary.
- xmlns (blocking):
<a xmlns="urn:x"/>and<a><b xmlns=""/></a>are refused.<a xmlnsx="1"/>is still accepted.Spec.rejects_xmlnsstates the rule, andAttrsOkcarries the matching condition. - "What is proved" vs the theorems (blocking): the single-input refusals are now statements over families of texts.
Spec.parse_wfandSpec.print_parsehold for every input. README and SPEC rows name the positions each theorem covers. "What is not proved" says plainly that other positions rest on the tests. - Whitespace between attributes:
<a b="1"c="2"/>is refused, andSpec.rejects_attribute_without_spacestates the rule. - Line ends:
<a>x\r\ny</a>reads asx\ny, and a lone CR reads as LF.Spec.crlf_reads_as_lfandSpec.cr_reads_as_lfstate both. - Harness: it now fails closed (exit status, answer count, every failure counted) and always runs
lake build. - Missing inputs: the cases listed in review 1 finding 7 are named rejections now. An undefined entity in a nested element,
<a><b>&e;</b></a>, is refused.
Non-blocking
- The plan names Go encoding/xml and Rust quick-xml as oracles. They are still not wired in. The README says so under What you must trust, so this can be a follow-up.
- This approval is a code review of the slice. It does not record a spec approval in
bar.toml. Bar item 1 still needs its own approval entry, and main still needs thebar / barbranch rule for item 7.
Generated by Claude Code
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Carries #2 forward, as the note on #2 said. Refs #1.
This branch carries #2 forward. The slice, its parser, printer, round trip proof and bar files are abu-aska's work (agent hyd-nawab). The branch starts at that PR's head, 4875cec, and keeps its commits exactly as they are. The fixes come as 15 new commits on top, one per logical change, and each one names the review finding it answers. The first 11 answer the reviews. The last 4 answer the gaps that an independent check of the first 11 found.
What changed, finding by finding
Blocking 1:
xmlnswas accepted as an ordinary attribute (review 3 item 1, first raised as review 1 finding 2)<a xmlns="urn:x"/>gave a tree with an attributexmlns. ElementTree reads the same text as{urn:x}awith no attributes. The colon ban on names does not catch this case, because a default namespace declaration is the plain namexmlns.Xml/Value.lean:219addsxmlnsName.AttrsOk(:228) now also requires that no attribute is namedxmlns, so the round trip still covers every tree the parser gives.Xml/Parse.lean:305:parseAttrsrefuses the name. Only that exact name is refused:xmlnsx,XMLNSand an element namedxmlnsstay ordinary names. Prefixed declarations (xmlns:p) were already refused by the colon rule.Spec.rejects_xmlns(Spec.lean:288, proofProofs/Refusals.lean:154) states the rule for the root element after any well formed prolog and any well formed attributes.Spec.parse_wf(below) adds that no accepted tree holds anxmlnsattribute anywhere.xmlnsx,XMLNSandxmlnas attributes, and an element namedxmlns;xmlns.WFcondition.Blocking 2: "What is proved" stated general rules backed by one-input theorems (review 3 item 2)
A mutant
parseRefthat expanded&e;and©met all 18 statements. I took the preferred route and proved general statements. I then reworded every row to say exactly which texts its theorem covers.Spec.parse_wf(Spec.lean:50) states that for every text,parse cs = some d → WF d.Spec.print_parse(:55) is the plan's main claim as written: printing an accepted document gives a text that parses back to the same document.Proofs/ParseWF.lean(parse_wf:1095,print_parse:1101), which also answers review 1 finding 3. The proof goes reader by reader. The one non-local fact, that no two text runs stand next to each other, isnodes_head_not_text(:799).Proofs/Refusals.lean:rejects_undefined_entity(Spec.lean:168): in the root's first run of character data, after any prolog, any start tag, and any text with no<or&, a reference&name;to any valid name other than the five predefined ones is refused, whatever follows.rejects_undefined_entity_in_attribute(new,:182): the same in an attribute value of the root.rejects_doctype,rejects_cdata_in_prolog,rejects_text_before_root,rejects_digit_name(any of the ten digits) andrejects_colon_in_name.rejects_xml_declaration:<?and a target that isxmlin any letter case.rejects_second_root: any well formed document, printed, followed by<and a name start character.rejects_mismatched_tags: any well formed root element whose end tag names another valid name.rejects_unclosed_element: the root's start tag at the end of the text.rejects_surrogate_ref(�),rejects_zero_ref(�) andrejects_cdata_close_in_text(]]>).rejects_dashdash_in_comment: a comment that is the root's first content.rejects_duplicate_attribute,rejects_lt_in_attributeandrejects_unquoted_attribute.parseRefin a scratch copy, the kernel proves that the mutant accepts<a>&e;</a>and<a b="©"/>. Instances ofSpec.rejects_undefined_entityandSpec.rejects_undefined_entity_in_attributesay both texts are refused, so the mutant fails them, and its proofs also stop building.parse_wfnorprint_parseties the tree to the text it came from;Spec.parse_print.Should-fix: no whitespace between attributes (review 1 finding 4, reviews 3 and 4)
<a b="1"c="2"/>was accepted. XML 1.0 productions [40] and [44] require whitespace before each attribute.Xml/Parse.lean:282addsstartsWithWs, andparseAttrs(:302) reads a name only after whitespace.Spec.rejects_attribute_without_space(Spec.lean:274, proofProofs/Refusals.lean:226) states the rule for the root.Should-fix: carriage returns were kept (review 1 finding 5, review 3)
The plan does not choose otherwise, so the parser now normalizes line ends as XML 1.0 section 2.11 requires.
Xml/Parse.lean:437addsnormEol, andparse(:445) normalizes before reading.WFnow requires that comment bodies and PI data hold no carriage return (Xml/Value.lean:256,:261). The parser can never give one there any more, and the printer could not write one back.noCR_print(Proofs/RoundTrip.lean:1688) keepsparse_printintact.Spec.crlf_reads_as_lfandSpec.cr_reads_as_lf(Spec.lean:65,:73; proofsProofs/LineEnds.lean:114,:131): anywhere in a text, CR LF reads exactly as LF, and a lone CR reads exactly as LF, unless the text before it ends with a CR.Test harness (review 1 finding 6, review 3)
run_tool(tests/run_tests.py:215) now records a failure for:It pads missing answers with a value no check accepts.
Every failing check is counted. Before, generated failures stopped being recorded after ten.
build_exe(:742) always runslake build.Checked with a wrapper that answers ten requests and then crashes: the harness now reports 3,248 failures and exits 1, where before it reported
Failures: 0.tests/TEST-RESULTS.mdnow records the lines the bar checker reads:- Oracle:(with the Python and expat versions and the exact ElementTree setup, instead of "subset mode"),- Seed:,- Inputs:and- Failures:.The README status line reads
Status: in progress.A new part sends every canonical print back to the tool and requires the same print, which checks
print_parseon the compiled program.The named rejections cover the same rules in other positions: after a prolog, after text, after other attributes, and after a whole document.
They also add the inputs review 1 finding 7 listed as missing:
A,, a DOCTYPE in content, and<?xmlafter the root.The generated text includes non-ASCII and non-BMP characters.
The nanoda kernel (bar item 3)
As the author reported,
lake comparator --paranoidcrashed in nanoda on 4875cec. Running nanoda on each theorem alone found the cause:Charreproduces it, and how deep it goes depends on what was checked before.rfl,split, or a lemma over variable characters does that work:parsePI_space(Proofs/RoundTrip.lean:764), andparseMisc_stop_two,parseNodes_comment_none'andparseMisc_pi_none(Proofs/Refusals.lean).Process
zero-askaas a spec author next toabu-aska. Status staysin-progress, with no approval entry.xmlnsName,startsWithWsandnormEol.Gaps from the independent check
The independent check of the first 11 commits found every blocking finding fixed and every check passing. It listed three minor gaps, each closed by its own commit:
SPEC.md stated "the parser never expands an entity" as a fact (008de98).
&e;only inside a nested element meets every statement.A README sentence about
named no theorem (1b5846e).Spec.crlf_reads_as_lf,Spec.cr_reads_as_lfandSpec.parse_printfor what each backs.
or outside a canonical print as another character would meet every statement, and that the tests check the reading.58 linter warnings (491b0b6, 751dca9). Fixed:
Xml/Parse.lean:275;if_posandif_neguses.No statement changed, and
lake buildnow prints no warnings.Final numbers (clean clone of 751dca9)
lake build: succeeds, 18 jobs, no errors and no warnings.lake comparator --paranoid: "Your solution is okay!" for all 25 theorems in comparator.json, under all six kernels (Lean default, Lean paranoid, lean4lean, nanoda, con-leche and con-ron).python3 tests/run_tests.py: seed 20261026 (unchanged), 6,595 inputs, 0 failures, and a fresh run rewritestests/TEST-RESULTS.mdbyte for byte:error.check.py . --offline --run-tests: no FAIL.What is still open
If abu-aska would rather land #2 with these commits,
git pull https://github.com/Proven-Commons/xml claude/xml-first-sliceon that branch is a fast-forward, and I'll close this PR.Generated by Claude Code