// WHAT THE ADJUDICATOR IS SHOWN — the subjects, and the content classes they collapse to. // // A criterion the engine cannot decide is handed to an agent with the evidence it must rule // against. Two properties decide whether that verdict is worth anything, and this module owns // both. // // 1. AIM. Evidence used to be the union of the harvesters of a criterion's mapped success // criteria, taken in `pc.wcag` order and then cut to a fixed cap. Measured on a real audit: // RGAA 11.1 (are form fields labelled?) maps [1.3.1, 2.4.6, 3.3.2, 4.1.2], the 1.3.1 // harvest filled every slot with headings and lists, and the control harvest — the actual // SUBJECT of 11.1 — never appeared. 9.2 (landmarks) and 5.8 (layout tables) received the // very same thirty headings. Those three are exactly the criteria a real run had refused // for a "fabricated" citation: the agent had opened the right file and cited the right // element, and the evidence set simply did not contain the subject. So a criterion names // its SUBJECTS, and a pack criterion may name its own (`PACK_SUBJECTS`) rather than // inherit a union that is aimed elsewhere. // // 2. COMPLETENESS. A cap over raw anchors is a sample, and a `C` over a sample is a claim // about a population nobody looked at — measured, 30 of 2652 anchors for 11.1, and not one // of them from a rendered page. But a population is only large when counted as // occurrences: 887 links across 38 captured pages are 97 distinct (text, href) pairs, and // 47 images are 8 distinct (alt, src) pairs. So every subject declares the CONTENT CLASS // an anchor belongs to, the harvest collapses to one representative per class, and the // occurrence count travels with it. What the agent reads is then the whole population, // expressed once per distinct thing. import { type Doc, type El, ancestors, attr, elementsByTag, snippet as elSnippet, textContent } from "./parse/html.js"; import { parseInlineStyle } from "./color.js"; import type { Evidence } from "./adjudicate.js"; /** Mask the parts of a text that change between runs without the code changing. * * Only the class IDENTITY is masked — the note and the snippet keep the real text, so the * adjudicator still reads what the page says. What this decides is whether two anchors are * "the same thing": a row rendered at 13:04 and the same row rendered at 14:12 are one * decision about accessibility, not two. * * It matters beyond tidiness. The verdict ledger fingerprints the evidence a criterion was * ruled against, and a fingerprint that moves every run makes a stored verdict stale on * arrival — the criterion returns to « to assess » and the ledger stops paying for itself. * Measured on a real capture set: four criteria carried a run timestamp in their evidence. * * Deliberately narrow. Generated ids that are stable for a given tree (React `useId`) and * content hashes that are stable for given source are NOT masked: when those move, the code * moved, and a verdict about it SHOULD be re-examined. */ const VOLATILE: [RegExp, string][] = [ [/\b\d{1,2}[/-]\d{1,2}[/-]\d{2,4}\b/g, ""], [/\b\d{4}-\d{2}-\d{2}\b/g, ""], [/\b\d{1,2}:\d{2}(?::\d{2})?\b/g, "