Skip to content

feat(atomic-a11y): add OpenACR converter modules#7124

Open
y-lakhdar wants to merge 39 commits intofeat/a11y-merge-shardsfrom
feat/a11y-openacr
Open

feat(atomic-a11y): add OpenACR converter modules#7124
y-lakhdar wants to merge 39 commits intofeat/a11y-merge-shardsfrom
feat/a11y-openacr

Conversation

@y-lakhdar
Copy link
Copy Markdown
Contributor

@y-lakhdar y-lakhdar commented Feb 16, 2026

TL;DR

Converts the a11y-report.json (from the Vitest reporter) into OpenACR-compliant YAML — the machine-readable format behind VPAT 2.5 accessibility conformance reports.

What it does

After the reporter (#7123) captures axe-core results and the shard merger (#7137) consolidates parallel runs, we need to translate that data into an industry-standard format. OpenACR is the format the U.S. GSA uses for machine-readable VPATs.

a11y-report.json  +  manual-audit-*.json  +  a11y-overrides.json
      │                    │                        │
      └────────────────────┴────────────────────────┘
                           │
                 transformJsonToOpenAcr()
                           │
                           ▼
                  reports/openacr.yaml

Conformance priority: override → manual audit → existing report → automated (axe-core)

Design decisions

Schema-aligned output types

The OpenAcrReport and OpenAcrCriterion types match the official GSA OpenACR 0.1.0 JSON Schema exactly. No custom extensions are added to the YAML output — the @openacr/openacr CLI only reads official schema fields (num, components[].adherence.level, components[].adherence.notes), so anything extra would be dead data.

Runtime validation of the generated YAML against the official schema is handled in #7164.

Options objects for conformance functions

resolveConformance() and buildRemarks() use typed context objects (ConformanceContext, RemarksContext) instead of positional parameters. Both share the same base context (criterion, aggregate, manual aggregates, override), and buildRemarks extends it with the resolved conformance and component lists.

File organization

  • types.ts — Only type definitions and mapping constants. No functions.
  • manual-audit.ts — Parsing, loading, and counting logic for manual audit baselines.
  • conformance.ts — Conformance resolution priority chain and remarks generation.
  • report-builder.ts — Assembles the full OpenACR structure.
  • json-to-openacr.ts — Entry point that wires I/O together.

How to review

Order File What to look for
1 json-to-openacr.ts Entry point — reads inputs, writes YAML
2 conformance.ts resolveConformance() priority chain + buildRemarks()
3 report-builder.ts Assembles full OpenACR structure (chapters, criteria)
4 manual-audit.ts Parses manual-audit-*.json baselines, worst-wins resolution
5 overrides.ts Loads a11y-overrides.json per-criterion exceptions
6 types.ts OpenACR interfaces (schema-aligned) + conformance mapping constants

Try it

Minimal run (automated data only)

If reports/a11y-report.json exists (from the Vitest reporter), you can generate the OpenACR YAML immediately:

node scripts/json-to-openacr.mjs
# → writes reports/openacr.yaml

If the input report doesn't exist yet, the converter still runs — it produces a placeholder YAML with all 55 criteria set to not-evaluated.

Full loop (with manual audits + overrides)

1. Create a manual audit baseline in a11y/reports/:

mkdir -p a11y/reports
// a11y/reports/manual-audit-search.json
[
  {
    "name": "atomic-search-box",
    "category": "search",
    "manual": {
      "status": "complete",
      "wcag22Criteria": {
        "1.1.1-non-text-content": "pass",
        "2.1.1-keyboard": "pass",
        "2.4.7-focus-visible": "partial",
        "4.1.2-name-role-value": "fail"
      }
    }
  }
]

Files must match manual-audit-*.json (no -violations in the name). Only entries with "status": "complete" are processed. Status values: pass, fail, partial, not-applicable.

See docs/manual-audit-guide.md for the full QA reference on manual baselines.

2. (Optional) Add a conformance override in a11y/:

// a11y/a11y-overrides.json
{
  "overrides": [
    {
      "criterion": "1.4.2",
      "conformance": "not-applicable",
      "reason": "Atomic components do not produce audio output."
    }
  ]
}

3. Run the converter:

node scripts/json-to-openacr.mjs

You'll see output like:

[json-to-openacr] Loaded 1 conformance override(s) from a11y/a11y-overrides.json.
[json-to-openacr] Loaded {x} manual audit entries across {x} criteria from x baseline file(s): {filename}.

Tests

Extracted to #7163 to keep this PR focused on implementation.

PR Chain (5 of 8)

# PR Description
1 #7111 Package scaffolding
2 #7122 Shared types, constants, utilities
3 #7123 VitestA11yReporter + wiring
4 #7137 Shard merging
5 #7124 OpenACR generator ← this PR
6 #7163 OpenACR tests
7 #7164 Schema validation
8 #7125 CLI scripts
9 #7117 CI workflow

KIT-5470

- Add VitestA11yReporter class for collecting axe-core results during vitest runs
- Add reporter-utils.ts with shard resolution, component extraction, error processing
- Add axe-integration.ts with axe-core type guards and WCAG tag parsing
- Add merge-shards.ts for combining sharded a11y reports
- Add unit tests for reporter (9 tests) and merge-shards (3 tests)
- Update index.ts with reporter exports
- Add test and a11y:merge-shards scripts to package.json
- Add src/openacr/types.ts with OpenACR interfaces and conformance mappings
- Add src/openacr/overrides.ts for loading override configuration
- Add src/openacr/manual-audit.ts for manual audit baseline parsing and resolution
- Add src/openacr/conformance.ts for multi-source conformance calculation
- Add src/openacr/report-builder.ts for building OpenACR report structure
- Add src/openacr/yaml-serializer.ts with hand-rolled YAML serializer (no deps)
- Add src/reporter/json-to-openacr.ts as slim CLI orchestrator (137 lines)
- Add unit tests for json-to-openacr (7 tests)
- Update index.ts with OpenACR exports
- Add a11y:vpat script to package.json
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 94a34f0b09

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread packages/atomic-a11y/src/openacr/types.ts Outdated
Comment thread packages/atomic-a11y/src/openacr/json-to-openacr.ts Outdated
Comment thread packages/atomic-a11y/package.json Outdated
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: d26e8a39e0

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread packages/atomic-a11y/package.json Outdated
Comment thread packages/atomic-a11y/a11y/a11y-overrides.json
@svcsnykcoveo
Copy link
Copy Markdown

svcsnykcoveo commented Mar 30, 2026

Snyk checks have failed. 1 issues have been found so far.

Status Scan Engine Critical High Medium Low Total (1)
Open Source Security 0 0 0 0 0 issues
Licenses 0 0 0 0 0 issues
Code Security 0 0 1 0 1 issues

💻 Catch issues earlier using the plugins for VS Code, JetBrains IDEs, Visual Studio, and Eclipse.

Comment thread packages/atomic-a11y/src/openacr/types.ts
@@ -0,0 +1 @@
[]
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The file as is doesn't seem super useful 😅

Should we fill it with sample data? Or just remove it? Or is it just there so that the reports folder is kept? If so we can just add a .gitkeep file instead.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good point. this is something i forgot to fill. even tho it is explained in the readme. I will update the file and commit it. good catch

Comment thread packages/atomic-a11y/docs/manual-audit-guide.md Outdated
Comment thread packages/atomic-a11y/docs/manual-audit-guide.md Outdated
Comment thread packages/atomic-a11y/docs/manual-audit-guide.md Outdated
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The problem with this format is that it leaves no room for comments from the human auditor.

The VPAT report might ignore such remarks, but as engineers working on the project, we need to understand precisely why a criteria was given a status of fail, partial, or not-applicable by the human who did the manual audit, so that we can either challenge the conclusion or take remedial action.

If I put myself in the shoes of a QA working on a manual audit, I'd probably leave these kinds of remarks somewhere. If I can't leave them in the structured manual audit file, then I'd leave them in some kind of document somewhere. I personally think it would be much better to have the information live in the same place (in the repo), and be cleanly a consistently structured.


const DEFAULT_REPORT_TITLE = 'Coveo Accessibility Conformance Report';
const DEFAULT_REPORT_PRODUCT_NAME = 'Coveo Atomic';
const DEFAULT_REPORT_PRODUCT_VERSION = '3.x.x';
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we don't receive a specific, something is seriously wrong and we should probably not even bother producing the report.

I think that's how we should handle missing version; not with a default value.

},
success_criteria_level_aa: {
notes:
'Conformance is based on automated Storybook + axe-core output, interactive keyboard/screen-reader testing, and pending manual validation.',
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the exact same note as in success_criteria_level_a; consider storing it in a variable instead of duplicating it.

export const DEFAULT_A11Y_REPORT_FILENAME = 'a11y-report.json';
export const DEFAULT_WCAG_22_AA_CRITERIA_COUNT = 55;
export const BASELINE_FILE_PATTERN =
/^manual-audit-(?!example\.json$)(?!.*-violations)([\w-]+)\.json$/;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you decide to remove the example file and use a .gitkeep instead, you can update the regex accordingly.

"a11y:vpat": "pnpm build && node scripts/json-to-openacr.mjs && npx --yes @openacr/openacr output -f reports/openacr.yaml -o reports/vpat-2.5-coveo-atomic.md -t scripts/vpat-from-openacr.handlebars",
"a11y:merge-shards": "pnpm build && node scripts/merge-shards-cli.mjs"
},
"dependencies": {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we also have yaml as a dependency in the create-atomic package, so we could put it in the root package.json instead and use catalog:

});
```

```ts
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need this? We have pnpm a11y:merge-shards and pnpm a11y:vpat

If we don't then we could just remove mergeA11yShardReports and transformJsonToOpenAcr from index.ts.

Co-authored-by: Frederic Beaudoin <fbeaudoin@coveo.com>
@alexprudhomme
Copy link
Copy Markdown
Contributor

alexprudhomme commented Apr 8, 2026

Hey @y-lakhdar 👋 ✅ This PR does not need a changeset file — you don't have one currently, so it's perfect. This PR only affects @coveo/atomic-a11y, which is a private package and is not published to npm. For more info on when changesets are needed, see the Changesets README.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants