VAI Verification kit

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

opethon.com at 1280 px: automated WCAG 2.2 AA violations over the 21 rules that found something to test. R1 R2

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.

Automated WCAG 2.2 AA rules at 1280 px
0 / 21 violations / applicable rules
R1R2
The same rules at 320 px, the width WCAG reflow is written for
0 / 21 violations / applicable rules
R3R4
Items the rules could not decide, left for a person
0 and 0 at 1280 and 320 px
R5R6
Target size, WCAG 2.5.8, by axe-core's target-size rule
pass and pass at 1280 and 320 px
R7R8
Animations still running after the browser asks for reduced motion
0 and 0 at 1280 and 320 px
R9R10
Design-contract engine score, as the engine reports it
99.1 (A) 27 of 40 checks scored
R11R12R13R14
Flags the contract engine raised, and what re-measurement made of them
3 raised: 2 rejected, 1 stands as raised
R15R16R17
Misfire rate
null a website has no input method of its own
R18

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

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

  1. 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.
  2. 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.
  3. Flags and findings stay apart. Engines raise flags. A flag becomes a finding only after re-measurement in the rendered page confirms it.
  4. What the rules cannot decide goes to a person. Items needing review are listed on their own and never counted as passes.
  5. 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.

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.

The instrument's reference session, run for this page
null false activations per armed hour
R24
Exposure in that session
26.9 s observed
R25R26
Exposure floor before a rate prints
15 min
R27

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

Exit codes: 0 no automated violations, 1 violations found, 2 a WCAG number was withheld and the report says why, 3 the run could not start. The method, every parameter and every known engine weakness are in METHOD.md; setup steps are in README.md.

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, scanned by this kit at 1280 px
0 / 21 violations / applicable rules
R29R30
This page at 320 px
0 / 21 violations / applicable rules
R31R32
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

  1. 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
  2. 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).
  3. 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
  4. 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

R1 opethon.com, automated WCAG 2.2 AA violations at 1280 px
0
run B, /wcag/viewports/0/violations
R2 opethon.com, applicable rules at 1280 px (the denominator for R1)
21
run B, /wcag/viewports/0/applicable_rules
R3 opethon.com, violations at 320 px
0
run B, /wcag/viewports/1/violations
R4 opethon.com, applicable rules at 320 px (the denominator for R3)
21
run B, /wcag/viewports/1/applicable_rules
R5 opethon.com, items left for a person at 1280 px
0
run B, /wcag/viewports/0/needs_review
R6 opethon.com, items left for a person at 320 px
0
run B, /wcag/viewports/1/needs_review
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
R11 opethon.com, design-contract engine score
99.1
run B, /contract/score
R12 opethon.com, design-contract engine grade
A
run B, /contract/grade
R13 opethon.com, contract checks the engine scored
27
run B, /contract/checks_scored
R14 contract checks in the engine
40
run B, /contract/checks_total
R15 opethon.com, flags the contract engine raised
3
run B, /contract/flags_raised
R16 opethon.com, flags rejected on re-measurement
2
run B, /contract/flag_outcomes/rejected
R17 opethon.com, flags the kit could not re-measure
1
run B, /contract/flag_outcomes/not_re_measured
R18 opethon.com, misfire rate
null
run B, /misfire_rate/value
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
R24 reference session, false activations per armed hour
null
run C, /measurement/armed/falsePerHour
R25 reference session, exposure in milliseconds
26.9 s
run C, /measurement/observedMs
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
R29 this page, violations at 1280 px
0
run D, /wcag/viewports/0/violations
R30 this page, applicable rules at 1280 px
21
run D, /wcag/viewports/0/applicable_rules
R31 this page, violations at 320 px
0
run D, /wcag/viewports/1/violations
R32 this page, applicable rules at 320 px
21
run D, /wcag/viewports/1/applicable_rules
R33 this page, design-contract engine score
null
run D, /contract/score