Skip to content

test(pagebreak): a texted marker's clean verdict can be a finding the gate drops - #201

Merged
bbertucc merged 2 commits into
mainfrom
test/148-pagebreak-demoted
Aug 26, 2026
Merged

test(pagebreak): a texted marker's clean verdict can be a finding the gate drops#201
bbertucc merged 2 commits into
mainfrom
test/148-pagebreak-demoted

Conversation

@bbertucc

Copy link
Copy Markdown
Member

Addressing the review of PR #148 (closed by @bbertucc because it would have undone #145's fix — the shape it prescribed put role="doc-pagebreak" on a <p>, where the role is dropped or its children are presentational either way). The shape argument is settled and main has it right. Its review left one note that still applies to main, and this is it:

test/lint-pagebreak.test.ts:61 — "the same attribute beside text is clean" is true of Iris's gate, not of axe: aria-prohibited-attr returns undefined for a non-empty element, so it lands in incomplete, and src/pipeline/lint.ts promotes only duplicate-id-aria out of incomplete.

That file never merged, but test/pagebreak-marker.test.ts (which did, via #160) carries the same claim and explains the silence as axe "declining to resolve the role at all". Measured on this branch, through axe directly on that one rule:

marker violations incomplete
<p role="doc-pagebreak" aria-label="Page 5">5</p> 0 1
<p role="doc-pagebreak" id="page-5">5</p> 0 0
<p role="doc-pagebreak" aria-label="Page 5"></p> 1 0
<hr role="doc-pagebreak" aria-label="Page 5" id="page-5"> 0 0

aria-prohibited-attr returns undefined rather than false when the element has text of its own — the attribute is prohibited either way, but whether the reader loses anything depends on what that text says, which axe will not decide — so the finding lands in incomplete, and runAxe promotes only duplicate-id-aria out of it.

So the six markers that passed in #145's document were findings the gate dropped, and the seventh had no text to demote it. That is a sharper answer to "why was it intermittent" than "six of them were clean", and it is the one a future reader of that file needs: a [] from the gate is not the same claim as "axe found nothing".

What changed

  • A new test measures the four rows above, so the distinction is checkable rather than described. If the promotion list ever grows to include this rule, that test fails and names the promotion list — instead of the failure surfacing as an apparent axe regression somewhere else in the suite.
  • The measurement runs runOnly by rule name rather than copying runAxe's tag filter and rule tuning. A second copy of that configuration in a test would drift from the gate it exists to reason about; one rule's own verdict needs none of it.
  • The existing test's title and comment are corrected to say the gate's silence, and the file header points at the difference. Its assertions are unchanged.

Nothing about the prescribed marker changes, and no src/ behaviour changes. The <hr> row is pinned too, so "clean" there means clean by the rule's own reckoning and not merely unreported.

Gates

  • npx tsc --noEmit clean
  • npm test — 776 tests, 776 pass, 0 fail (1 new)
  • bash test/e2e.sh — ALL ENDPOINTS PASSED ✅

Co-authored-by: bbertucc 46652+bbertucc@users.noreply.github.com

🤖 Generated with Claude Code

…, not clean

`test/pagebreak-marker.test.ts` recorded a reason for #145's intermittency that
is not the measured one. It asserts that a labelled page-break marker WITH text
passes, and explains the silence as axe "declining to resolve the role at all".
Measured here, through axe directly on the one rule:

  <p role="doc-pagebreak" aria-label="Page 5">5</p>   1 incomplete, 0 violations
  <p role="doc-pagebreak" id="page-5">5</p>           0 incomplete, 0 violations
  <p role="doc-pagebreak" aria-label="Page 5"></p>    0 incomplete, 1 violation

`aria-prohibited-attr` returns `undefined` rather than `false` when the element
has text of its own — the attribute is prohibited either way, but whether the
reader loses anything depends on what that text says, which axe will not decide —
so the finding lands in `incomplete`, and `runAxe` promotes only
`duplicate-id-aria` out of `incomplete`. The six markers that passed were
findings the GATE dropped, and the seventh had no text to demote it. That is a
sharper answer to "why was it intermittent" than "six were clean", and it is the
one a future reader of this file needs: a `[]` from the gate is not the same
claim as "axe found nothing".

The distinction is now measured rather than described, so the file cannot go
stale on it in the other direction either: if the promotion list ever grows to
include this rule, the new test fails and names the promotion list, instead of
the failure reading as an axe regression somewhere else in the suite.

The measurement runs `runOnly` by rule name rather than copying `runAxe`'s tag
filter and rule tuning — a second copy of that configuration in a test would
drift from the gate it exists to reason about.

Nothing about the prescribed marker changes: `<hr role="doc-pagebreak"
aria-label="Page 5" id="page-5">` is clean by this rule's own reckoning too, not
merely unreported, and that row is pinned as well.

Found while addressing the review of PR #148 (closed), whose third non-blocking
note is this one: "'the same attribute beside text is clean' is true of Iris's
gate, not of axe."

Co-authored-by: bbertucc <46652+bbertucc@users.noreply.github.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All six checks pass. One test file changed, no src/ behaviour, no workflow or agent-prompt changes. The four measured rows are correct and the point they support — a [] from the gate is not the claim "axe found nothing" — is a real sharpening. Two of the narrative claims wrapped around them are not.

Non-blocking notes

1. The "six markers were demoted" framing contradicts this file's own record of #145 — and its own middle row.

test/pagebreak-marker.test.ts:26-28:

the six markers that passed in #145's document were findings axe DEMOTED to incomplete, not findings it never made.

and :190-192: "six markers were DEMOTED, and the seventh had no text to demote it."

The file's opening paragraph, unchanged by this PR (:1-4), records what those six were:

Six other markers in that document carried their number as text instead of a label and passed

A marker with no naming attribute is row 2 of the new measurement — <p role="doc-pagebreak" id="page-5">5</p>{violations: 0, incomplete: 0} (:682) — and the new comment for that row says so itself: "there is nothing for this rule to judge and the silence really is silence."

So on this file's stated facts, #145's six markers produced no finding at all, not a demoted one. Only the label-and-text shape (:676) demotes, and that is not the shape the incident had. The correction is sound as a statement about aria-prohibited-attr; it is the attribution to those six markers (title included) that does not follow. Fix whichever of the two accounts of #145 is wrong.

Latent: comment text in a test file — no assertion is affected, nothing delivered changes.

2. The stated tripwire does not exist: this test cannot fail on a promotion-list change.

:190-193:

if the promotion list grows to include this rule, this test fails and says so, instead of the failure reading as an axe regression somewhere else in the suite

plus the assertion message at :678: "lint.ts's promotion list, or this test's argument, is out of date".

prohibitedAttr() (:543-566) calls w.axe.run directly. The promotion lives inside runAxesrc/pipeline/lint.ts:373, results.incomplete.filter((v) => v.id === "duplicate-id-aria") — which this helper never calls. Add aria-prohibited-attr to that filter and this test still measures {violations: 0, incomplete: 1} and still passes.

The test that would actually fail on that change is the existing gate test at :637, which asserts [] from rules() for the texted marker — and its failure message speaks about the prompt catching up, not about the promotion list. The intended signal is one test to the left of where the comment points, with the wrong explanation attached.

Latent: only misleads whoever reads the next failure.

Accessibility impact: none — test-only change; no assertion, prompt, or delivered document behaviour moves.

…s six

Both notes from the review of #201 are right, and both are about what the
comments claim rather than what the four rows measure.

The attribution was wrong. #145's six passing markers carried their number as
text INSTEAD of a label — this file's own opening paragraph says so — which is
the middle row of the new measurement: no naming attribute, nothing for
`aria-prohibited-attr` to judge, `{violations: 0, incomplete: 0}`. Their clean
verdict was not a demoted finding. What the demotion explains is the
labelled-AND-texted shape, which is the trap the test above it is about and the
shape a model reaches for when told to keep the label and show the number. The
correction to "axe has nothing to say about a texted marker" stands — it is true
of one of the two shapes and false of the other — so the rows stay and the
sentence around them says which is which.

The stated tripwire did not exist either. `prohibitedAttr()` calls `axe.run`
directly, so adding this rule to `runAxe`'s promotion list would leave every row
here unchanged. The test that sees that change is the gate test above, whose `[]`
would become a reported violation, and its message now names both ways it can
start failing instead of only the prompt catching up. This test's own message
speaks about axe's verdict, which is what it measures.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@bbertucc bbertucc changed the title test(pagebreak): the six markers that passed #145's gate were demoted, not clean test(pagebreak): a texted marker's clean verdict can be a finding the gate drops Aug 26, 2026
@bbertucc

Copy link
Copy Markdown
Member Author

Both notes fixed in 24f1b1c — you were right on both, and the second one changes what the test claims about itself.

1. The attribution. #145's six passing markers carried their number as text instead of a label, which is the middle row: no naming attribute, nothing for the rule to judge, {violations: 0, incomplete: 0}. Their clean verdict was not a demoted finding, and the title said it was. Retitled, and the two comments now say that the demotion explains the labelled-and-texted shape — the trap the test above it is about, and the shape a model reaches for when told to keep the label and show the number. The correction the PR exists for is unchanged: "axe has nothing to say about a texted marker" is true of one of the two shapes and false of the other, and the rows are what show which.

2. The tripwire. Correct — prohibitedAttr() goes to axe.run directly, so adding aria-prohibited-attr to runAxe's promotion filter leaves all four rows exactly as they are. The comment claiming otherwise is gone; the test that actually sees that change is the gate test above, and its message now names both ways its [] can start failing (axe catching up, or the promotion list growing) with a pointer to src/pipeline/lint.ts. This test's own assertion message speaks about axe's verdict, which is the only thing it measures.

One note on your line numbers: they point past the end of the file (219 lines), so if you were reading a different revision the two findings still hold as stated — I checked both against the file at 7a5213d.

tsc --noEmit clean, node --test test/pagebreak-marker.test.ts 6/6.

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Both notes from my review of 7a5213d are addressed in 24f1b1c, and the change is comment- and message-only — no assertions moved.

  1. The "six markers were demoted" attribution is fixed. test/pagebreak-marker.test.ts:28-30 and :191-195 now put #145's six markers in the middle row (no naming attribute → nothing for the rule to judge, silence really is silence) and reserve the demotion for the labelled-and-texted shape. That matches the file's own opening record at :2-4 and the measured row at :212, and the commit title says the same thing.

  2. The mis-pointed tripwire is fixed, and correctly. :198-201 now states outright that this test is not the tripwire on runAxe's promotion list, and the gate test's failure message at :171-173 names both reasons a [] could break. I verified the second reason actually holds: aria-prohibited-attr does reach the gate through runAxe's tag filter (src/pipeline/lint.ts:359 runs wcag2a, and :90-97 of this file asserts the empty marker surfaces as aria-prohibited-attr[serious]), so adding that id to the incomplete promotion at src/pipeline/lint.ts:373 would indeed turn that [] into a reported violation and fail the gate test. The signal now points at the test that sees it.

No new findings. All six checks pass, and the only file this branch changes is the test.

For the record: the config.example.yaml, prd.md, src/pipeline/assembly.ts, src/pipeline/lint.ts, test/config-example.test.ts and test/document-lang.test.ts hunks in my review context are main's already-merged #198/#199 work — the diff was taken from the merge ref against an older merge base (9c499f0), not from this branch. git diff origin/main 24f1b1c confirms the branch does not carry them, so I have not re-reviewed them here.

Accessibility impact: none — a test-only change; no prompt, gate configuration or delivered document behaviour moves.

@bbertucc
bbertucc merged commit e462ce5 into main Aug 26, 2026
1 check passed
@bbertucc
bbertucc deleted the test/148-pagebreak-demoted branch August 26, 2026 17:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant