# The wpt.fyi vote table
A per-subtest vote table built from wpt.fyi's published results, joined to
browser-compat-data through WPT's `WEB_FEATURES.yml` files and the `web-features` package,
plus the fixed held-out WPT partition that `roadmap.md` → "WPT changes" reserves for
scoring. Motivation: `docs/oracle-coverage.md` → "wpt.fyi as a ready-made vote table".
Crate: `crates/wpt-votes`.
```mermaid
flowchart LR
runs["wpt.fyi run listing
8 runs, pinned by revision"] --> raw["raw results
~300 MB each, streamed"]
raw --> table["table.tsv
one row per test/subtest"]
wf["WEB_FEATURES.yml
at the run's revision"] --> roll["compat-rollup.tsv"]
pkg["web-features 3.39.0
data.json"] --> roll
table --> roll
table --> area["per-area table"]
```
## Sources and pinned versions
- **Runs**: the latest aligned stable and experimental runs for Chrome, Firefox and Safari
(`wpt_votes::runs::aligned`), and Servo and Ladybird runs at either revision
(`wpt_votes::runs::at_revision`). Pinned by WPT revision, browser name and version.
- **Raw results**: each run's `raw_results_url`, cached under `corpus/wpt-votes/downloads/`
(gitignored) and streamed one test at a time (`wpt_votes::report`), never loaded whole.
- **`WEB_FEATURES.yml`** at each run's exact revision, read from a blobless partial clone
at `corpus/wpt-votes/wpt-repo` via `git cat-file --batch`.
- **`web-features` `data.json`** pinned to **3.39.0** (`WEB_FEATURES_PACKAGE_VERSION` in
`crates/wpt-votes/src/bin/join_features.rs`); each feature's `compat_features` lists
compat keys directly. It does not record which compat-data snapshot it was built from.
The run set of 2026-09-21:
| Column | Browser | Version | WPT revision |
|---|---|---|---|
| `chrome_stable` | Chrome | 153.0.8010.52 | `bc93a431dd` |
| `firefox_stable` | Firefox | 156.0 | `bc93a431dd` |
| `safari_stable` | Safari | 27.0 (22625.1.29.11.27) | `bc93a431dd` |
| `chrome_exp` | Chrome | 156.0.8066.0 | `26f417a8c0` |
| `firefox_exp` | Firefox | 158.0a1 | `26f417a8c0` |
| `safari_exp` | Safari | 252 preview | `26f417a8c0` |
| `servo` | Servo | 0.6.0-b5a1f5e6e | `bc93a431dd` |
| `ladybird` | Ladybird | 1.0-4e806b61a1 | `bc93a431dd` |
## Table schema
`corpus/wpt-votes//table.tsv`: tab-separated, one row per `(test path,
subtest name)`; `subtest` is empty for tests without subtests. `test` and `subtest` are
escaped (`wpt_votes::tsv`), since subtest names do contain tabs and newlines.
| Column | Meaning |
|---|---|
| `test`, `subtest` | The WPT key, escaped. |
| `held_out` | `1` if this test path falls in the held-out partition (below). |
| `chrome_stable`, `firefox_stable`, `safari_stable`, `chrome_exp`, `firefox_exp`, `safari_exp`, `servo`, `ladybird` | One status code per run, `-` if that engine reported nothing for this key. |
| `pass_count`, `ran_count` | Out of the three *stable* engines only. |
| `majority` | `1` if at least half of the stable engines that ran agree on pass. |
| `outlier` | The one stable engine that disagrees on a 2–1 split, `-` otherwise. |
| `frontier` | `1` if exactly one of the three stable engines passes **and all three ran** — a missing engine is not evidence it would have failed, so it does not count toward or against frontier status. |
| `frontier_engine` | Which engine, when `frontier` is `1`. |
| `rescued` | `1` if a *different* stable engine's experimental channel passes. |
Status codes: `P` pass/OK, `F` fail, `T` timeout, `N` notrun, `E` error, `C` crash, `R`
precondition-failed, `S` skip, `O` anything else, `-` no result.
The raw JSON is never resident; the cross-engine join is (one entry per key, a few hundred
MB for 2.28 million keys). A streaming merge over sorted per-engine files is the fallback
if WPT outgrows that.
## Held-out partition
`wpt_votes::partition::is_held_out(test_path)`: FNV-1a 64-bit hash of the test path,
`hash % 100 < 20`. By test path, so every subtest of a test lands on the same side. FNV-1a
because Rust's `DefaultHasher` is not promised stable across versions, and a partition
that moved would silently invalidate every past score. The hash, the modulus and the 20%
are the definition; changing any invalidates every score computed under the old one.
## Summary: the 2026-09-21 run (revision `bc93a431dd` stable, `26f417a8c0` experimental)
`cargo run --release -p wpt-votes --bin build_table`, over **2,282,301** distinct keys:
- **Stable majority: 2,162,676 (94.8%)** — across all of WPT, mostly well-established
HTML/DOM, so it answers a different question from `docs/oracle-coverage.md`'s 57% over
hard areas.
- **Single-engine frontier, all three stable engines having run:** 54,421 (chrome-only
31,515, firefox-only 15,951, safari-only 6,955).
- **Rescued by experimental:** 9,309 of that frontier (17.1%).
### Reconciling the frontier count with `docs/oracle-coverage.md`
Dropping the "all three ran" requirement reproduces `docs/oracle-coverage.md`'s 72,014
(41,132 / 20,834 / 10,048) exactly. About 10.7% of keys have fewer than three stable
engines reporting; the looser rule counts a missing run as a fail. This crate's stricter
54,421 (`wpt_votes::vote::StableVote::frontier`) means "single-engine, with all three
actually tested" — not a correction of the other figure.
### Compat-key rollup
`cargo run --release -p wpt-votes --bin join_features corpus/wpt-votes/bc93a431dd bc93a431ddd2e301dece2c6346eb23858aa487a8`
writes `corpus/wpt-votes/bc93a431dd/compat-rollup.tsv` (per compat key: subtests,
majority, frontier). **1,765,824 rows (77.4%) land under at least one `web-features`
identifier**; the rest have no covering `WEB_FEATURES.yml` (1,017 such files at this
revision, mostly CSS, canvas and web-audio). The join touches **844 of 1,210
`web-features` identifiers** and **12,217 compat keys**.
### Per-area rollup, against `docs/oracle-coverage.md`'s wpt.fyi table
`wpt_votes::domain` rebuilds `docs/oracle-coverage.md`'s six hand-drawn areas from a fixed
list of WPT directories per area (`html/canvas` special-cased).
`cargo run --release -p wpt-votes --bin domain_table corpus/wpt-votes/bc93a431dd`:
| Area | Subtests | Pass in two or more engines |
|---|---|---|
| Media | 13,380 | 66% |
| Timing | 4,053 | 85% |
| WebRTC, capture, WebTransport | 6,375 | 72% |
| Security and permission UI | 2,388 | 43% |
| GPU and canvas | 25,340 | 39% |
| Devices and sensors | 2,558 | 25% |
| **Total** | **54,094** | **53%** |
The total matches the hand estimate within 0.7% (54,094 against 53,725); individual areas
diverge more (Security 43% against 66%) because the directory lists are close but not
identical. The total is the load-bearing check.
## Negative labels — skipped
Marking subtests Gecko's `testing/web-platform/meta` lists as expected failures is not
implemented; see Next steps.
## Next steps
1. **Negative labels from Gecko's WPT metadata** (`testing/web-platform/meta/**/*.ini`),
to tell a known Firefox bug from fresh signal.
2. **A streaming merge-join**, if the in-memory index stops being cheap.
3. **Feed the held-out partition and the rollup into the harness**; nothing here is wired
into `crates/harness` yet.
## How to verify
- `cargo test --workspace` covers the partition rule (stable, within 1% of 20% over
200,000 paths), vote derivation, the streaming reader and the `WEB_FEATURES.yml`
inheritance rules.
- `cargo run --release -p wpt-votes --bin list_runs` prints the current run set without
downloading.
- `build_table`, `join_features` and `domain_table` reproduce the numbers above (they
drift as WPT moves; the reconciliation should hold in kind). The first run at a new
revision clones WPT blobless and prefetches every `WEB_FEATURES.yml` blob in one request.
- Spot-check rows of `table.tsv` against wpt.fyi's viewer for the run ids `list_runs`
prints.