Accessibility Fingerprint Profiles
This directory defines the first stable, interoperable accessibility fingerprint profiles and the machinery that keeps them stable. It is the normative implementation of the fingerprint concepts introduced conceptually in Accessibility Finding Tracking. Read that guide first for the terminology (finding, occurrence, pattern, tracker ID, display ID, and so on). This document defines the exact bytes that go into a fingerprint.
A published fingerprint profile is immutable. Any change that can alter its output requires a new profile version.
This directory does not define a complete machine-readable accessibility-finding schema. That is a separate, later change.
Files
| File | Purpose |
|---|---|
a11y-pattern-v1.json |
Normative, machine-readable definition of the a11y/pattern/v1 profile. |
a11y-occurrence-v1.json |
Normative, machine-readable definition of the a11y/occurrence/v1 profile, which depends on a11y/pattern/v1. |
test-vectors.json |
Golden test vectors. Every expected_fingerprint was computed by this profile’s own reference implementation and independently cross-checked with a second SHA-256 implementation, not hand-typed. |
check-fingerprint-vectors.mjs |
Executable checker. Recomputes every vector and fails if any computed value disagrees with the recorded expectation. |
package.json / package-lock.json |
Pins the canonicalize dependency used for RFC 8785 canonicalization. |
Run the checker with:
cd examples/fingerprints
npm ci
npm run check
Normative computation
The authoritative fingerprint for both profiles is:
lowercase-hex(
SHA-256(
UTF-8(
JCS-canonicalize(fingerprint-input)
)
)
)
- Hash algorithm: SHA-256, as defined in FIPS 180-4.
- Encoding: UTF-8.
- Canonicalization: RFC 8785, the JSON Canonicalization Scheme (JCS).
- Digest format: all 64 lowercase hexadecimal characters. This full value is authoritative.
The profile field (the exact string a11y/pattern/v1 or a11y/occurrence/v1) is part of the canonicalized input, not a label applied afterward. This is what keeps a pattern fingerprint and an occurrence fingerprint from ever colliding even if the rest of their JSON happened to match.
Do not:
- use a delimiter-joined string (such as
namespace|rule|locator) as the normative representation — see requirement 16’s pipe-collision test vectors for why; - use ordinary
JSON.stringify()in place of RFC 8785 canonicalization (it does not sort object keys or fix number formatting); - implement a partial canonicalizer and describe it as RFC 8785 compliant;
- hand-type an expected digest value.
This repository uses the maintained canonicalize npm package (pinned to 3.0.0 in package.json/package-lock.json) as its RFC 8785 implementation. A shortened value, such as a display ID, is a display alias only and is never authoritative.
Pattern fingerprint contract: a11y/pattern/v1
Canonical input shape (see a11y-pattern-v1.json for the full, normative field-by-field contract):
{
"profile": "a11y/pattern/v1",
"target": {
"scope_type": "repository",
"scope_id": "https://github.com/example/product"
},
"rule": {
"namespace": "axe-core",
"id": "color-contrast"
},
"locator": {
"type": "css",
"normalization_profile": "a11y/css-locator/v1",
"value": "#edit-submit"
},
"state_key": null
}
Field notes:
target.scope_typeidentifies the kind of product boundary.a11y/pattern/v1supportsrepository,product,site-origin,package,component-library, andproject-namespace.target.scope_idis a stable, normalized identifier for that scope, such as a repository URL or a package URL (purl). Do not put an individual page URL here unless that URL is the entire target scope.rule.namespaceis the rule system or producer, e.g.axe-core,htmlcs,qualweb,act,manual,project. It must be supplied explicitly, not inferred fromrule.id.rule.idis the stable rule identifier within that namespace. It must not be a WCAG success criterion; WCAG and ACT mappings are recorded separately unless a profile explicitly uses an ACT rule as its rule identity.locator.typedocuments the kind of locator:css,xpath,test-id,accessibility-tree,component,html-fragment,human-description, orunknown.locator.normalization_profilenames the exact normalization contract applied tolocator.value. One normalization process is not assumed to work for every locator type.locator.valueis the locator after that normalization has already been applied. Do not lowercase it unless the named normalization profile explicitly allows that.state_keyis an optional, stable, product-intrinsic state (for example"expanded"on a disclosure widget). Use JSONnull, not an omitted field, when no state applies. It must never carry scan date, severity, priority, theme, colour scheme, viewport, browser, orientation, direction, build, or tracker status.
All five fields are required. An omitted state_key is a validation error, not an implicit null — see test vector PV-12-missing-required-field.
Occurrence fingerprint contract: a11y/occurrence/v1
Canonical input shape (see a11y-occurrence-v1.json for the full contract):
{
"profile": "a11y/occurrence/v1",
"pattern_fingerprint": {
"profile": "a11y/pattern/v1",
"algorithm": "sha-256",
"value": "<64-lowercase-hexadecimal-characters>"
},
"location": {
"scope": "route-pattern",
"normalization_profile": "a11y/route/v1",
"key": "/node/[nid]"
},
"test_profile": null
}
Field notes:
pattern_fingerprintmust contain the complete authoritativea11y/pattern/v1fingerprint. Never embed a display ID here — test vectorOV-10-rejects-display-id-as-pattern-fingerprintdocuments this, and the checker rejects it explicitly.location.scopeisexact-resource,route-pattern,component-state, orunknown.location.normalization_profilenames the immutable normalization contract applied tolocation.key.location.keyis the normalized resource, route pattern, or component-state key. A route such as/node/[nid]can represent many concrete resources.test_profileisnullby default. A project may define a named test profile when it intentionally treats specific test configurations as distinct occurrences, but that profile must come from explicit configuration. It must never be inferred from viewport width or described as a physical device without evidence.
All three fields are required, including an explicit test_profile: null — see test vector OV-09-missing-required-field.
Fields excluded from fingerprint identity
Neither contract has a field for the following. They are evidence, conditions, mappings, provenance, or lifecycle metadata, and excluding them from identity does not make them unimportant:
- scan date, first-seen date, last-seen date
- build number, commit hash, scanner version
- browser version, operating-system version
- viewport dimensions, inferred device type
- colour scheme, theme, text direction, orientation
- severity, priority, review status, resolution status
- tracker ID
- WCAG mappings, ACT mappings
- evidence URLs
- concrete locations represented by a route pattern
Locator and location normalization profiles
Both contracts depend on named, versioned normalization profiles. This stage defines only the minimal set needed by the published test vectors:
a11y/css-locator/v1a11y/exact-resource/v1a11y/route/v1a11y/component-state/v1
Each normalization profile must eventually state its supported input type, Unicode handling, whitespace handling, case handling, URL handling, query and fragment handling, generated-value handling, error handling, whether raw input must be retained separately, examples, and limitations.
For version 1, where a normalization behaviour is not yet sufficiently understood, the conservative choice is an identity transformation (pass the value through unchanged) with its limitations documented, rather than an aggressive transformation that might merge unrelated findings. It is safer for a conservative profile to under-merge than for an aggressive one to over-merge: a separate fingerprint can still be reviewed and correlated by a human; a merged one has already discarded the distinction.
A full specification for each normalization profile, with its own test vectors, is follow-up work and is not completed in this change.
Display IDs
A11Y-PAT- + uppercase(first 12 hexadecimal characters of the full pattern fingerprint)
A11Y-OCC- + uppercase(first 12 hexadecimal characters of the full occurrence fingerprint)
Worked example, taken directly from test-vectors.json (PV-01-baseline / OV-01-baseline):
A11Y-PAT-E7C842E0E569
A11Y-OCC-BB20023FD962
These display IDs are produced by the a11y/pattern-display/v1 and a11y/occurrence-display/v1 profiles referenced from display_profile in each profile JSON file.
Rules:
- Display IDs are not authoritative. A lookup must confirm the full digest before treating two display IDs as the same fingerprint.
- Display IDs may collide; 12 hex characters is 48 bits, not the full 256-bit space.
- Pattern and occurrence aliases use distinct prefixes (
A11Y-PAT-vsA11Y-OCC-) so the two never look alike even if their first 12 hex characters matched. - Changing the alias length or derivation requires a new display-ID profile version.
- Legacy short IDs (see Accessibility Finding Tracking) retain their original formatting. They are not recomputed under
a11y/pattern-display/v1ora11y/occurrence-display/v1.
Stability requirements
- Published profile versions are immutable.
- Existing profile inputs must not be added, removed, reordered, renamed, reinterpreted, or normalized differently after publication.
- A new profile version is required for any change to: canonical serialization; character encoding; hash algorithm; field names; field types; required fields; input ordering where relevant; default values; null handling; omitted-value handling; target scoping; rule identity; locator normalization; route normalization; component-state normalization; case handling; whitespace handling; Unicode handling; display-ID derivation; condition or test-profile inclusion; or interpretation of an existing value.
- Fixing a mistake in an algorithm still requires a new profile version if the correction can change an existing fingerprint.
- Editorial corrections that cannot affect computation (typo fixes in descriptions, added examples) may be made without a new profile version.
- Dependency upgrades (including the pinned
canonicalizeversion) must not silently alter output. Re-run the checker before and after any upgrade. - Implementations must use the published test vectors in
test-vectors.jsonto verify compatibility. - Existing test-vector outputs must never be updated merely to make a failing implementation pass.
- A legitimate output change requires retaining the old profile, creating a new profile version, adding new test vectors, documenting the reason and affected fields, describing the expected migration relationship, dual-writing old and new fingerprints during migration, and retaining old fingerprints for historical lookup.
- A profile must not be called
"status": "stable"until its normative profile definition and test vectors are committed together.
Changing a fingerprint profile
Do not edit a stable profile file or its published vectors in place. Instead, open a proposal with the following fields:
Current profile:
Proposed profile:
Reason for change:
Fields affected:
Normalization affected:
Existing IDs affected:
Expected migration relationship:
Legacy retention plan:
Dual-write period:
Comparison-key transition:
New test vectors:
Implementation repositories affected:
A new version is required whenever output may change, for example:
a11y/pattern/v1 -> a11y/pattern/v2
The procedure:
- Leave
v1intact. - Create
v2alongside it (a new pair of profile/vector files, e.g.a11y-pattern-v2.json). - Publish separate
v2test vectors. - Document whether
v1->v2mappings areequivalent,split,merged, orunresolved, using the vocabulary in Accessibility Finding Tracking. - Require implementations to emit both versions during the transition.
- Require consumers to compare using the newest profile version they mutually support.
- Do not delete older fingerprints while historical consumers may still depend on them.
- Record affected repositories and migration status.
How stable-profile modification is detected
check-fingerprint-vectors.mjs recomputes every vector on every run; it cannot pass if a11y-pattern-v1.json, a11y-occurrence-v1.json, or test-vectors.json have drifted from what the pinned canonicalization and hashing steps actually produce.
That alone does not stop someone from editing a stable profile file and also updating its expected outputs to match, which would silently launder a breaking change through as if it were still v1. .github/workflows/fingerprint-contract.yml adds a second, independent check for that: it diffs the current status: "stable" profile files and their vectors against the pull request’s base revision and fails if their content differs at all. Adding new vectors, or adding an entirely new v2 profile pair, does not trip this check. Editing byte content of an existing stable profile or vector file does.
If your CI environment cannot resolve a reliable base revision (for example, a shallow clone or a non-PR trigger), run the same comparison locally before merging:
cd examples/fingerprints
node ../../.github/scripts/check-stable-fingerprint-profiles.mjs --base <base-revision>
References
- RFC 8785, JSON Canonicalization Scheme
- NIST FIPS 180-4, Secure Hash Standard
- SARIF 2.1.0 — cited only as an informative migration precedent for how another ecosystem versions and identifies findings. This profile is not SARIF and is not required by SARIF.
- GitHub: SARIF support for code scanning
- Accessibility Finding Tracking
Limitations
- This stage defines
a11y/pattern/v1anda11y/occurrence/v1only. It does not define a complete accessibility-finding JSON Schema; that is separate follow-up work. - The four normalization profiles named above are placeholders with conservative, identity-transformation behaviour. They are not yet fully specified with their own test vectors.
- No historical
drupal-coreoropen-scansdata is migrated by this change. See Accessibility Finding Tracking for the migration scenarios those projects would need to work through, none of which have been implemented yet. - This directory does not establish accessibility conformance. It establishes a way to tell whether two findings are computed as the same correlation candidate.