Repository navigation
Conversation
MaxGhenis
force-pushed
the
max/abstract-guard-ties-20261106
branch
from
November 7, 2026 02:15
b5995b5 to
0fd4223
Compare
This was referenced Nov 7, 2026
The guard checked the 2020–2025 retrospective errors (2.29, 1.65, 0.51 and 0.90 percent) and the 2025 evaluation errors (1.17, 2.58 and 0.75 percent) against the whole document. The body restates 1.17, 2.58 and 0.75, so the guard passed when one of them changed in the abstract alone. The guard now also reads each of these figures from the abstract alone, beside the rule it scores, so a swap between rules fails too. It also ties the abstract's count of four projection rules to the retrospective artifact. The abstract slice now comes from section(): when "abstract: |" or "# Introduction" goes missing or repeats, the slice comes back empty and every abstract tie fails instead of reading the whole paper. New tests change each figure in the abstract alone, swap two pairs, and rerun every case with the original wording restated in the body. Another test renames, repeats and restyles the slice's markers. No paper text or number changes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…d rate to match
The abstract ties read section("abstract: |", "# Introduction"), which also
took in the fields after the abstract, the closing delimiter and any body
text before the introduction, so a copy there could rescue a drifted figure.
scripts/paper_text.py now finds the one top-level `abstract: |` key in the
front matter and returns only its indented block. A missing, repeated or
restyled key leaves the slice empty and fails every abstract tie.
Each tie now collects every rate the abstract gives in its rule's wording
and requires them all to equal the registered value, so a correct copy
elsewhere in the abstract cannot hide a changed one, and a sign flip fails.
The mutation tests place decoys in another metadata field, in body text
before the introduction, inside the abstract and under the discussion
heading, and check that renaming, demoting, repeating or moving the
introduction heading changes nothing. tests/test_paper_text.py adds unit
and seeded property tests for the parser and the slot reader.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
MaxGhenis
force-pushed
the
max/abstract-guard-ties-20261106
branch
from
November 7, 2026 17:34
0fd4223 to
f95d8d7
Compare
block_scalar_span now follows pandoc's reading of the front matter: - a comment indented less than the block ends the block; - the block takes its indentation from its first line with text; - a quoted key or an explicit "? abstract" also names the key; - a line after the front matter that sets the key, at its start or in flow style, fails closed, since pandoc lets a later metadata block, even inside a list item or block quote, replace the abstract; - a tab in a block line's indentation fails closed, since Quarto rejects it, and so does a lone carriage return in the front matter, which pandoc drops; - lines end only at line feeds, and front matter needs text on the line after its opening dashes. The guard's label for a missing or ambiguous block becomes "paper must set abstract only in one abstract: | block of its front matter". A new guard test shows that a comment after the block cannot satisfy a tie, and the fails-closed test adds a quoted repeated key and a body metadata block. tests/test_paper_text.py grows to 40 tests, among them a differential test that compares the parser with pandoc wherever pandoc is installed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Quarto 1.9.36 renders a tab that follows the block's indentation as text, as pandoc does, so the parser now reads such a line instead of failing closed. A tab within the indentation still fails closed: there Quarto rejects the line, or ends the block, where pandoc reads text. The parser counts only leading spaces as the block's indentation. The parser tests now separate the tabs that fail closed from the tabs that read as text, and the edge cases add both kinds. A comparison with Quarto's rendered abstract over 333 documents (47 edge cases, 86 shared cases, 40 random documents and 160 random documents edited so Quarto renders them) found 207 agreements, 41 documents where the parser fails closed, 36 that both refuse and 48 that Quarto cannot render. One document diverges: an aliased key in a body metadata block, which the pull request lists as out of scope. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Quarto renders the paper, and it reads the front matter with its own YAML parser before pandoc runs, so the two can disagree. Quarto rejects a `...` terminator, a line holding only a tab and a repeated key, and it reads a tab-led `#` line after the block as a comment where pandoc reads text. Wherever Quarto is installed, three new tests render the fixture, the 47 edge cases and 12 random documents with decoys, and require Quarto's abstract to show the parser's words, or Quarto to reject the document. The random documents close with `---`, empty their tab-only lines and rename the `notes` list, all of which Quarto would reject. CI installs no Quarto, so it skips them, as it skips the pandoc tests. The guard's and parser's docstrings now say they read the abstract as Quarto and pandoc read it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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.
WIP. This PR builds on #12, so merge #12 first. Once #12 merges and we delete its branch, GitHub retargets this PR to
master.What changes
scripts/check_paper.pychecked the abstract's seven error rates against the whole document:tables/current_projection.md, and no other line ofpaper/index.qmdstates them.The whole-document ties read
paper/index.qmdwithout its included tables. Because §6.2 restates the evaluation figures, the guard passed when 1.17, 2.58 or 0.75 changed in the abstract alone (the known gap #12 lists). It caught a lone change to the four retrospective figures only because no other line of the file states them.The guard keeps its whole-document ties and now also checks each figure inside the abstract, beside the rule it scores.
scripts/paper_text.pyfinds the front matter's one top-levelabstract: |key and returns the lines under it. Like Quarto and pandoc, it ends that block at the next line with fewer leading spaces than the block's first line of text. Other metadata fields, a comment after the block and the body fall outside it. Each tie collects every number the abstract gives in its rule's wording and passes only if each one equals the registered value. For example, every number the abstract puts before "percent with the Consumer Price Index" must read 2.29. The guard now fails if someone changes a figure, flips its sign or swaps two figures, and if someone adds a different figure in a rule's wording, even beside a correct copy.Where Quarto could render other text as the abstract, or where Quarto and pandoc could disagree, the parser returns nothing. The guard then reports "paper must set abstract only in one abstract: | block of its front matter" and fails every abstract tie. The parser returns nothing when:
abstract: |, as inabstract: >,"abstract": |or? abstract;{abstract: …}, since a later metadata block, even in a list item or block quote, replaces the front matter's abstract;The guard also ties "compare four projection rules" to the retrospective artifact, whose four rules must match the four the abstract names. #12's guard took the abstract as the text before the first "# Introduction", so renaming that heading widened its slice to the whole paper. The parser reads no headings, and #12's four in-kind ties that read the abstract now read the block and still pass.
The PR changes no paper text or number.
Tests
tests/test_paper_guard.pygainsABSTRACT_PROSE_MUTATIONS: eleven cases that edit only the abstract through #12'smutate_article, which now finds the abstract with the guard's own parser. Seven move one figure by 0.01, one flips the sign of 2.29, one changes "four projection rules" to "five", and two swap 2.29 with 1.65 and 1.17 with 0.75.The PR adds five tests to that file:
test_abstract_figures_drift_aloneruns each case and asserts that the guard fails and names the case's ties.test_abstract_pins_reject_a_copy_restated_elsewherereruns each case after restating the original wording in four places: under the Discussion heading, in anote:field after the abstract, in body text before the introduction, and on an extra line inside the abstract. Each time, it asserts that only the case's ties fail.test_abstract_ties_ignore_the_introduction_headingrenames "# Introduction", demotes it, repeats it, and moves it after a sentence that restates 1.17. Each time, the guard passes the paper, and changing the abstract's 1.17 to 1.18 fails the 1.17 tie alone.test_abstract_ties_skip_a_comment_after_the_blockrewords the CPI-U rule, then Add a section on imputed in-kind benefits #12's "less than one point" bound, and each time keeps the old wording in a comment line after the block, which Quarto leaves out of the abstract. The guard fails that wording's tie and no other.test_abstract_block_fails_closed_unless_one_key_sets_the_abstractfolds the key toabstract: >, repeats it, repeats it as"abstract": |, renames it tosummary: |with anabstract: |copy in the body, and sets the abstract again from a metadata block in the body. Each time, it asserts that the guard reports "paper must set abstract only in one abstract: | block of its front matter" and fails all eight abstract ties and the in-kind lift-range tie.Across these tests, the guard at 9ba8f61, the #12 commit this PR builds on, passes 62 of the 66 papers it should reject. Its whole-document ties catch only a lone change to 2.29, 1.65, 0.51 or 0.90. This PR's guard rejects all 66 and passes the four papers that only move the heading.
tests/test_paper_text.pyadds 44 tests of the parser and the slot reader: 31 unit cases, 7 property tests that each draw 300 seeded random documents or statements, and 6 differential tests. The property tests check that:The differential tests render the fixture, 47 edge cases and random documents with decoys: 100 through pandoc and 12 through Quarto. Wherever the parser reads an abstract, each tool must show the same words or reject the document. Quarto reads the front matter with its own YAML parser before pandoc runs, and it rejects a
...terminator, a line holding only a tab in the abstract's block and a list undernotes. We edit the Quarto tests' random documents to avoid all three. CI installs neither tool, so it skips these tests. When we breakpaper_text.pyin any of 31 ways, one at a time, at least one test in that file fails, even without either tool.Beyond the suite, we rendered 333 documents with Quarto 1.9.36, adding 86 cases and 200 more random documents to the edge cases. Quarto showed the parser's words in 207 of them. The parser failed closed on 41 that Quarto renders, returned text for 48 that Quarto rejects and returned nothing for 36 that Quarto rejects or renders without an abstract. The last document sets the abstract through an alias in a body metadata block, a spelling the parser does not resolve.
Like the rest of the suite, the tests use only the standard library.
Invariants
These hold for every paper the guard reads:
abstract: |key, which ends at the next line with fewer leading spaces than its first line of text. No text in another field, in a comment after the block or in the body can satisfy a tie.abstract: |with text under it. The ties also fail if a later line names the key at its start or in a flow mapping.The parser recognizes the key spelled plainly, quoted or as
? abstract, but it resolves no YAML tags, anchors, aliases, escapes in quoted keys or flow keys without values. Quarto 1.9.36 rejects every such spelling in our comparison but one: in a body metadata block,k: &k abstractfollowed by*k : …sets the abstract while the ties still read the front matter's block.Follow-up
§6.2 restates 1.17, 2.58 and 0.75, and the guard still checks those restatements against the whole document, where the abstract's copies satisfy them. If someone changes one of them in §6.2 alone, the guard at this head still passes; it fails on 2.02, which only §6.2 states. In all, 39 call sites in the guard read the whole document, some of them in loops over several figures. #14 ties each of those figures to the section that states it.
Plan
axiom: n/a: guard and tests only; no policy rule changes
🤖 Generated with Claude Code