Opethon, Accessibility & Inclusion track
Most accessibility work ships without a number. A number beats a claim.
One command gives every entry the same automated WCAG 2.2 AA check and the same design-contract score. Each figure carries its denominator and the command that produced it. When the kit cannot stand behind a number, it prints null and names the reason.
Run it on your entry
./run-verification.sh https://your-entry.example
On Windows, .\run-verification.ps1 https://your-entry.example. Needs Node 22 or newer and Chrome, Edge, Chromium or Brave. Nothing to install.
Independent of Opethon's organizers. Built by VAI and offered to the track.
0 / 21
Worked example: opethon.com
The track's own site went first. It passes, which makes it the right first subject: the example shows what a clean result looks like when every figure can be traced to a run.
- The same rules at 320 px, the width WCAG reflow is written for
- 0 / 21 violations / applicable rules
- R3R4
The first attempt was refused, and the kit said so
A fresh automated browser does not reach this page. The host answers it with a browser check, HTTP 403. The kit read that response as text, never ran it, and printed null for both widths. R19 R20
The figures above come from the same kit running inside an ordinary browser session, which the host serves (--attach). Anyone can repeat them by hand with the console check further down.
Engine flags, settled by measurement
-
v24 touch targets
- The engine said
- Targets under 44 px, labeled WCAG 2.5.8. That criterion is Level AA and its figure is 24 px; 44 px belongs to 2.5.5, Level AAA.
- Measured in the page
- axe-core's target-size rule, which applies 2.5.8 with its spacing and inline exceptions, passed at 1280 and 320 px.
- Outcome
- rejected on re-measurement R21
-
v34 AI disclosure
- The engine said
- The page mentions AI and carries no disclosure marker for a chatbot.
- Measured in the page
- No text entry and no embedded frame at either width, so the page has nowhere to hold a chat.
- Outcome
- rejected: not applicable to this page R22
-
v14 typography rules
- The engine said
- Three optional typography declarations are missing.
- Measured in the page
- A contract preference outside WCAG. The kit has no measurement for it.
- Outcome
- not re-measured: reported as the engine raised it R23
The engine's passes on reduced motion, input size and reading measure were measured as well, and all three agree with the page.
Five rules every number follows
- The denominator travels with the number. Every figure says whether its denominator was observed in the run or declared by someone. The kit reports observed figures only.
- A number the kit cannot stand behind prints as
null. The reason prints beside it: a refused page, an HTTP error, a page that never settled, an engine file that fails its hash. - Flags and findings stay apart. Engines raise flags. A flag becomes a finding only after re-measurement in the rendered page confirms it.
- What the rules cannot decide goes to a person. Items needing review are listed on their own and never counted as passes.
- Every report lists what it did not look at. The list below is printed at the end of every run.
What the kit cannot see
Automated rules cover part of WCAG. W3C's own guidance is that no tool alone can determine whether a site meets accessibility standards. The kit tells an entrant which automated checks their page passes, with receipts, and shows a judge where testing with people has to take over.
- Whether people using assistive technology can finish the task the entry exists for
- Whether alt text, headings, labels and error messages mean what they say
- Anything behind a click: menus, dialogs, forms, signed-in states
- Keyboard use across a whole task: focus order, traps, visible focus
- What a screen reader announces
- Cognitive load, plain language, caption quality
- Pages other than the one scanned
- Speed, which needs field data
The misfire slot
Entries that ship an input method, such as dwell clicking, switch scanning, gaze or EMG, get one more figure: the misfire rate, the activations a person did not intend per hour the input was armed. It comes from access-conformance, an open-source instrument, and it prints only after 15 minutes of observed exposure.
That session is the instrument's own script, so it describes no device. It is here to show the rule working. No entrant's input method has been measured yet, and the slot stays empty until one is.
Run it on your entry
The kit is one folder: a runner, the pinned rules engine, this page and its receipts. It needs Node 22 or newer and a Chromium-family browser. Each run prints a summary and writes report.json with every rule, every measurement and the reason for any null.
A deployed entry
Scans the page at two widths in a fresh headless browser.
./run-verification.sh https://your-entry.example
A local build
Serves the folder on 127.0.0.1 for the length of the run. The contract engine takes domain names only, so its slot prints null here.
./run-verification.sh ./dist/index.html
A page behind a browser check
Open the page yourself in a browser started with a DevTools port, then attach. The report records that a session was used.
./run-verification.sh https://your-entry.example --attach 9222
Check us instead of trusting us
- The rules engine is axe-core 4.13.0 exactly as npm publishes it. The runner refuses to report if the file differs from this SHA-256.
c24f097bd2f451d4f933e8bc7d8d539f8672a2ebcb5cc9f9f3eec8ca9470a0c1- R28
- This page's design-contract score
- null the engine takes domain names only, and this page was scanned from a local folder
- R33
By hand, on any page. Open the page in your own browser, paste console-check.js into the developer console and press Enter. It loads the same engine file from jsDelivr with an integrity hash, so the browser refuses any other file, and prints violations over applicable rules for the width you are viewing.
The receipts. Every figure on this page has an entry in RECEIPTS.json naming its run, its command and the time it ran. node check-page.mjs confirms each number here matches its receipt, and each receipt matches the raw output it cites.
Receipts
Four runs produced every figure on this page. Each figure below names its run and the place in that run's raw output where its value sits, written as a JSON pointer.
Runs
-
Run A: opethon.com in a fresh headless browser
- Command
node verify.mjs https://opethon.com/ --out receipts- Ran at
- 2026-09-28T03:12:45-04:00
- Raw output
- receipts/opethon.com-20260928-031245-fresh/report.json
-
Run B: opethon.com inside an ordinary browser session
- Command
node verify.mjs https://opethon.com/ --attach 9222 --out receipts- Ran at
- 2026-09-28T03:12:52-04:00
- Raw output
- receipts/opethon.com-20260928-031252-attached/report.json
- Note
- Attached to a browser the operator already had open; its cookies applied. A fresh browser is refused by the host (run A).
-
Run C: access-conformance reference session
- Command
npx --yes access-conformance@0.1.2 --json- Ran at
- 2026-09-28T03:13:03-04:00 (recorded by the shell when the run started; the instrument's output carries no timestamp)
- Raw output
- receipts/access-conformance-reference.json
-
Run D: this page, served from its own folder
- Command
node verify.mjs index.html --out receipts- Ran at
- 2026-09-28T03:20:03-04:00
- Raw output
- receipts/self-scan-20260928-032003/report.json
- Note
- A scan cannot contain its own result: this run saw the page with the previous self-scan's entry in this list. Every figure it produced is unchanged.
Figures
- R2 opethon.com, applicable rules at 1280 px (the denominator for R1)
- 21
- run B,
/wcag/viewports/0/applicable_rules - R4 opethon.com, applicable rules at 320 px (the denominator for R3)
- 21
- run B,
/wcag/viewports/1/applicable_rules - R7 opethon.com, axe-core target-size rule at 1280 px
- pass
- run B,
/contract/cross_checks/0/evidence/0/axe_target_size - R8 opethon.com, axe-core target-size rule at 320 px
- pass
- run B,
/contract/cross_checks/0/evidence/1/axe_target_size - R9 opethon.com, animations running with reduced motion requested, 1280 px
- 0
- run B,
/wcag/viewports/0/motion/reduced_motion_requested/running - R10 opethon.com, animations running with reduced motion requested, 320 px
- 0
- run B,
/wcag/viewports/1/motion/reduced_motion_requested/running - R17 opethon.com, flags the kit could not re-measure
- 1
- run B,
/contract/flag_outcomes/not_re_measured - R19 opethon.com in a fresh browser, HTTP status of the page
- 403
- run A,
/wcag/viewports/0/http/status - R20 opethon.com in a fresh browser, violations at the worst width
- null
- run A,
/wcag/headline/violations_at_worst_width - R21 flag v24 (touch targets), outcome
- rejected on re-measurement
- run B,
/contract/flags/1/disposition - R22 flag v34 (AI disclosure), outcome
- rejected: not applicable to this page
- run B,
/contract/flags/2/disposition - R23 flag v14 (typography rules), outcome
- not re-measured: reported as the engine raised it
- run B,
/contract/flags/0/disposition - R26 reference session, where the exposure figure came from
- observed
- run C,
/measurement/exposureSource - R27 exposure floor in milliseconds before a rate prints
- 15 min
- run C,
/measurement/armed/minExposureMs - R28 SHA-256 of the rules engine file the runs used
- c24f097bd2f451d4f933e8bc7d8d539f8672a2ebcb5cc9f9f3eec8ca9470a0c1
- run B,
/engines/axe/sha256