---
title: "Results and exports"
url: https://recursiveai.org/onehtml/docs/results/
description: "A CheckReport (see checker.md) holds RuleResults. This page defines the result model and the two JSON exports. Both exports are deterministic: the same…"
version: "1.0.0"
source: https://github.com/recursive-ai-oss/onehtml-csharp/blob/cecf589eb9acfc9e1fbc529486dfef519a32c19c/docs/results.md
---

# Results and exports

A `CheckReport` (see [checker.md](https://recursiveai.org/onehtml/docs/checker/)) holds `RuleResult`s. This page defines the result model and the two JSON
exports. Both exports are deterministic: the same report gives the same bytes on every platform. Both are written with
`System.Text.Json` writers; nothing uses reflection.

## The result model

| Member | Meaning |
|---|---|
| `Rule`, `RuleId` | the rule that produced it, with its descriptor |
| `Outcome` | `Failed`, `Passed`, `NeedsReview` (static facts cannot decide), `NeedsRender` (rendered facts are needed), `Inapplicable` |
| `Target` | the element (or text node); the null node for `Inapplicable` |
| `Locator` | a selector unique in the document (generated on first use; null when none exists) |
| `SourceBytes` | the byte range of the target's start tag in the input, or none |
| `Message`, `Findings` | one sentence; and the attributes and values found, in attribute order |
| `TaskImpact`, `TaskImpactReason` | `BlocksTask` with `interactive`, `form` or `dialog`, or `Cosmetic` |
| `CssOnly` | the style context had interaction state, so the result reflects the CSS alone |
| `FactsUsed`, `FactsMissing` | rendered facts a result used (for example `layout text`, `text boxes`); for `NeedsRender`, the facts to supply |
| `SuggestedEdits` | edits, only where the rule names exactly one judgement-free fix |

Labels live on the rule: `Rule.Descriptor` has the sources (ACT id, axe id, failure technique, or ours), the criteria
(number, name, level, version introduced), `BestPractice`, `Needs`, the fix or the reason there is none, and tags; the rule
also has a description, a help sentence and an impact. Descriptions and help texts are written for this library.

A suggested edit is a normal `Edit` against the checked snapshot, carrying `meta` with the rule id, the criteria and
`"suggested": true`. Applying one is the caller's choice; `Document.Apply` and replay both accept them. `aria-valid-attr-value`, `aria-prohibited-attr` and `listitem` have no public fix: the correct edit depends on what the author meant (a mistyped value, a name meant for another element, a layout choice), so they report the problem and offer no suggestion.

## Native JSON: `onehtml-results` version 1

Top level, in this order:

| Field | Value |
|---|---|
| `format` | `"onehtml-results"` |
| `version` | `1`. A change that alters the meaning of a field raises it; new fields do not. Readers ignore unknown fields. |
| `document` | `{ "sha256": hex of the input bytes, "url": as given in the export options (omitted when none) }` |
| `styleContext` | `viewportWidth`, `viewportHeight`, `media` (`screen`/`print`), `colorScheme`, `reducedMotion`, `contrast`, `forcedColors`, `cssOnly`, and `focus`, `hover`, `active`, `target` (locators) when set |
| `profile` | the profile name (`wcag-2.1-aa`, `all`, …) or `null` |
| `limitReached` | a rule dropped results at `MaxResultsPerRule` |
| `rules` | the rules that ran, in id order: `id`, `description`, `help`, `impact`, `bestPractice`, `needs` (`static`/`rendered-facts`), `sources` (`kind`: `act`/`axe`/`failure-technique`/`own`, `id`), `criteria` (`number`, `handle`, `level`, `version`), `tags`, `fix`, `noFixReason` |
| `results` | see below |
| `suggestedEdits` | one edit-format envelope (`docs/edit-format.md`) holding every distinct suggested edit, so replay needs one document |

Each result: `rule`, `outcome` (`failed`, `passed`, `needs-review`, `needs-render`, `inapplicable`), `message`, `target`
(`null`, or `tag`, `locator`, `source` with byte `start` and `end`), `findings` (`attribute`, `value`, `message`),
`taskImpact` (`blocksTask`, `reason`), `cssOnly`, `factsUsed`, `factsMissing`, and `suggested`: a list of
`{ "description", "edit": n }` where `n` indexes `suggestedEdits.edits`. Results keep the report order: rule id, document
order of the target, outcome.

Strings are UTF-8 JSON with the characters `<`, `>`, `&` and `'` unescaped (the output is not meant to be embedded in
HTML). Numbers are integers except the viewport size.

## Axe-compatible JSON

The shape follows the results object that axe-core's public API documentation describes: `violations`, `passes`,
`incomplete` and `inapplicable`, each a list of rule results with `id`, `impact`, `tags`, `description`, `help`,
`helpUrl` and `nodes`; each node with `html`, `impact`, `target`, `any`, `all`, `none` and, for failures,
`failureSummary`. Top-level `testEngine`, `testRunner`, `testEnvironment` and `toolOptions` are present; `url` is present
when given.

The mapping is lossy, and these are its rules:

| Ours | Axe-compatible |
|---|---|
| failed | `violations` |
| passed | `passes` |
| needs-review, needs-render | `incomplete`; the node's `onehtml.outcome` says which |
| inapplicable | `inapplicable` (a rule with no target) |
| rule id | the axe id where an axe rule is a counterpart; ours otherwise, with our tags |
| rule impact | the rule's impact on violation and incomplete nodes; `null` on passes and inapplicable |
| `target` | `[locator]` (one selector; no frames or shadow roots) |
| `html` | the target's start tag as written in the source, cut to `MaxHtmlLength` (300) characters; axe gives the serialised element |
| `any`/`all`/`none` | a failure or incomplete node has one check in `any` (id = rule id, `data` = findings, `message`); `all` and `none` are empty. Passed nodes have empty arrays. Our rules are not built from axe's checks, so nothing finer is available. |
| `failureSummary` | the result message and the findings' messages on separate lines (our wording, not axe's) |
| `helpUrl` | the Deque University page of the axe version for rules with an axe source; empty otherwise |
| `description`, `help` | our own sentences |
| not exported | `timestamp` (it would make the output differ between runs), `relatedNodes` (always empty), frame and shadow paths |
| added | per node, `onehtml`: `outcome`, `blocksTask`, `cssOnly`, `factsMissing`, `suggestedEdits` (count) |

A rule appears once per group it has results in; a rule with failures and passes is in both `violations` and `passes`.
Within a group rules are in id order and nodes in document order.

## SARIF

`ToSarifJson` writes SARIF 2.1.0; the mapping is in `sarif.md`.

## Not provided yet

An EARL export. There is no reader for the native JSON; consumers read it with any JSON parser, and the edit
envelopes inside it are read with `EditJson.Parse`.
