Skip to content

Keep the axe fields we already fetch and discard: tags, help, failureSummary #61

Description

@georgekaran

Context

playwright-axe-auditor.ts maps each axe violation to five fields — id, impact, description, helpUrl, nodes — and drops everything else on the floor. Three of the discarded fields are the ones downstream features keep wanting:

Field What it carries Currently
tags The rule's standard mapping — wcag2a, wcag2aa, wcag143, cat.color, best-practice Discarded
help axe's short imperative fix summary, distinct from description Discarded
nodes[].failureSummary axe's per-node "Fix any of the following…" text Discarded

We already fetch all three on every audit. Nothing needs to be computed, scraped, or inferred — they arrive in the same object we are already mapping and are thrown away one line later.

The cost of waiting is asymmetric. Every audit run before this ships has no tags, and there is no way to derive them afterwards from what we stored: ruleId alone cannot tell you which success criterion a rule maps to without shipping our own copy of axe's rule metadata, which is exactly the kind of second source of truth that goes stale. Back-filling means re-auditing.

Scope

In

  • Capture tags, help and nodes[].failureSummary in the auditor's result.
  • Carry them through AddViolationParams and ViolationModel.
  • Persist them.

Out

  • Interpreting any of it. Turning tags into success criteria is its own issue; this one only stops discarding the input.
  • Exposing them on the API. See the boundary note below — that is a deliberate second step.

Implementation notes

  • tags is string[]. Either a text[] column or jsonb. A column with a GIN index is the better shape if anything will ever filter by standard, which is the whole point of keeping it.
  • jsonb writes must be stringified. nodes already goes through JSON.stringify; anything new following that route must too. node-postgres serialises a JS array as a Postgres array literal, which the jsonb parser rejects.
  • failureSummary is per node, not per violation, so it belongs inside the nodes jsonb rather than beside description.
  • help vs description are genuinely different. description states the rule ("Elements must meet minimum contrast ratio thresholds"); help states the action. Storing only one of them is what makes remediation copy read like a lint rule.

The boundary this must not cross by accident

toAuditResultResponse in presentation/helpers/audit-view.ts is a security boundary, not a convenience mapper: AuditModel carries pageId, which links to a site and therefore an account, so every field is named explicitly and there is a spec asserting the forbidden keys are absent.

Adding fields to the model must not turn that mapper into a spread. And note the share page at /r/:uuid is fully public — anything added to the audit response becomes world-readable for every shared audit. Deciding which of these three fields is public is part of the follow-up issue, not this one; this one stops at the database.

Acceptance criteria

  • An audit stores tags, help, and per-node failureSummary.
  • A spec asserts a real axe run yields non-empty tags for a known rule, rather than asserting the plumbing in isolation.
  • toAuditResultResponse is unchanged, and its "forbidden keys" spec still passes.
  • The migration notes that existing audits cannot be back-filled and must be re-audited to gain the new fields.

Metadata

Metadata

Assignees

No one assigned

    Labels

    area:serverBackend API and domain code under server/area:workerAudit worker (Playwright + axe-core)enhancementNew feature or request

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions