critical priority medium complexity backend pending backend specialist Tier 1

Acceptance Criteria

A `ContrastRatioValidator` class exists with a method `ValidationResult validatePair(Color foreground, Color background, {TextSize textSize = TextSize.normal})`
`ValidationResult` is an immutable data class with fields: `double ratio`, `bool passesAA`, `bool passesAAA`, `WcagLevel highestLevel`, `String summary`
AA threshold is 4.5:1 for normal text and 3.0:1 for large text (>=18pt normal or >=14pt bold)
AAA threshold is 7.0:1 for normal text and 4.5:1 for large text
`WcagLevel` enum has values: `fail`, `aa`, `aaa`
The validator can be instantiated with `const ContrastRatioValidator()` — no dependencies injected
A Dart CLI entry point `bin/validate_contrast.dart` exists that accepts two hex color arguments and prints the ValidationResult to stdout
The CLI exits with code 0 on AA pass and code 1 on AA fail — usable in CI shell scripts
The class is documented with dartdoc including an example usage snippet

Technical Requirements

frameworks
Dart
Flutter (dart:ui)
data models
ValidationResult
WcagLevel
TextSize
performance requirements
validatePair() is synchronous and completes in under 10 microseconds

Execution Context

Execution Tier
Tier 1

Tier 1 - 540 tasks

Can start after Tier 0 completes

Implementation Notes

Define `TextSize` as an enum with `normal` and `large` values. The `WcagLevel` highestLevel field should be computed as: ratio >= AAA_threshold(textSize) ? WcagLevel.aaa : ratio >= AA_threshold(textSize) ? WcagLevel.aa : WcagLevel.fail.

Keep `ContrastRatioValidator` as a stateless class (no fields) so it can be used as a `const` singleton or injected via Riverpod as a plain provider (`Provider((ref) => const ContrastRatioValidator())`). The CLI entry point in `bin/` should parse hex strings like `#FFFFFF` or `FFFFFF` (with or without `#`), convert them to `Color` objects using `Color(int.parse('FF' + hex.replaceAll('#',''), radix: 16))`, and call validatePair. Print output in a machine-readable format (JSON) for CI parsing: `{"ratio": 21.0, "level": "aaa", "pass": true}`.

Testing Requirements

Write flutter_test unit tests for ContrastRatioValidator covering: (1) pair known to pass AA normal — assert passesAA=true, passesAAA=false or true as appropriate, (2) pair known to fail AA — assert passesAA=false, highestLevel=fail, (3) large text pair at 3.5:1 — passesAA=true when textSize=TextSize.large, passesAA=false when textSize=TextSize.normal, (4) AAA threshold boundary at exactly 7.0:1 — assert passesAAA=true, (5) summary string contains the ratio formatted to 2 decimal places. Also write a shell integration test for the CLI: run `dart bin/validate_contrast.dart FFFFFF 000000` and assert exit code 0; run with a failing pair and assert exit code 1.

Component
Contrast Ratio Validator Service
service medium
Epic Risks (3)
high impact medium prob technical

The WCAG 2.2 relative luminance formula requires gamma-corrected sRGB calculations. Floating-point rounding differences between Dart and reference implementations could produce off-by-one classifications for near-threshold color pairs, resulting in pairs that just pass or just fail in CI but behave differently at runtime.

Mitigation & Contingency

Mitigation: Implement the algorithm directly from the WCAG 2.2 specification using the exact linearisation constants. Validate the Dart implementation against the W3C reference test vectors and against a known-good JavaScript implementation for at least 50 color pairs spanning the compliance boundaries.

Contingency: If discrepancies are found, add a configurable tolerance margin (e.g., ±0.005 on the ratio) and flag near-threshold pairs as warnings rather than hard failures, escalating to the design team for manual review.

medium impact high prob scope

The token manifest is a static data file. If developers add new color tokens to the design-token-provider without updating the manifest, the manifest becomes stale and the CI validator produces false negatives — passing builds that contain unvalidated color pairs.

Mitigation & Contingency

Mitigation: Add a CI step that cross-references every token constant exported by the design-token-provider against the manifest at build time, failing if any token is present in the provider but absent from the manifest. Document this requirement clearly in the contributing guide.

Contingency: If drift is detected post-merge, run a full manifest regeneration script and treat the resulting manifest diff as a blocking pull request with mandatory accessibility review.

medium impact medium prob dependency

The flutter_accessibility_lints package (or custom lint rules) may produce false positives on patterns the team deliberately uses — for example, decorative icon widgets that intentionally omit semantic labels. Excessive false positives will lead developers to add blanket ignore comments, undermining the entire lint strategy.

Mitigation & Contingency

Mitigation: Audit the full lint rule set against the existing codebase before enabling rules. Create a documented list of approved ignore-comment patterns with mandatory justification comments. Restrict ignore patterns to decorative-only contexts.

Contingency: If false positive rates exceed 10% of lint output, disable the highest-noise individual rules and replace them with targeted custom lint rules scoped to the specific patterns the team controls.