@adia-ai/a2ui-validator
v0.5.4
Published
AdiaUI A2UI validator — JSON Schema structural validation plus catalog-aware semantic validation (component exists, props match YAML). Split out from the compose engine so non-compose tooling (tests, MCP validator tools, CI gates) can depend on validation
Downloads
3,344
Readme
@adia-ai/a2ui-validator
JSON Schema structural validation + catalog-aware semantic validation
for A2UI (Agent-to-UI) protocol messages. Extracted from
@adia-ai/a2ui-compose so non-compose tooling (tests,
MCP validator tools, CI gates) can depend on validation without
pulling the full generator graph.
Install
npm install @adia-ai/a2ui-validatorUsage
import { validateSchema } from '@adia-ai/a2ui-validator';
const messages = [/* A2UI protocol messages */];
const result = validateSchema(messages);
if (!result.valid) {
console.error(result.errors);
}Catalog-aware validation (component exists + props match YAML):
import { validateAgainstCatalog } from '@adia-ai/a2ui-validator/catalog';
const result = validateAgainstCatalog(messages, catalog);What's here
- Structural validator — JSON Schema validation against the A2UI protocol schema (message shape, required fields, enum constraints).
- Catalog validator — semantic checks: does this component exist in the catalog? Do its props match the YAML contract? Are references resolvable?
- Semantic validator (optional, shadow-mode) — LLM-judged output quality against a rubric; cached on disk by content hash.
The 18 weighted checks
validator.js runs 18 weighted checks that sum to 100. Default pass
threshold is valid: score ≥ 70. The compose eval harness uses
combined: 0.6 × validation + 0.4 × semantic at threshold 80, so a
high structural score with low semantic still gets rejected at
compose time.
| Check | Weight | What it catches |
|---|---:|---|
| intentAlignment | 13 | Output addresses the intent (LLM-free heuristic) |
| allTypesRegistered | 9 | Every component value exists in the runtime registry |
| noOrphanedChildren | 9 | Every children ID resolves to a real node |
| validMessageFormat | 8 | Top-level shape is valid A2UI |
| hasRootComponent | 7 | Exactly one root |
| noBareDivs | 7 | Use semantic primitives (<card-ui>, <col-ui>) not bare <div> |
| cardStructure | 6 | Card pattern: header / body / footer hierarchy |
| flatAdjacency | 5 | Component IDs are flat (no nested arrays) |
| noInlineLayout | 5 | Use <col-ui> / <row-ui> / <grid-ui> not inline style="display:flex" |
| textContentSet | 5 | Text components have actual content |
| idUniqueness | 5 | No duplicate IDs |
| interactiveHasLabel | 4 | Buttons / inputs have accessible labels |
| imagesHaveAlt | 3 | <img> has alt attribute |
| headingHierarchy | 3 | h1 → h2 → h3 monotonic |
| gridVsColumn | 3 | Right primitive for the layout |
| landmarkStructure | 3 | Page-level main / nav / footer |
| noHardcodedColors | 3 | Token contract — --a-chrome-* only |
| tabStructure | 2 | <tabs-ui> has <tab-ui> children |
| Total | 100 | |
Wiring checks are tracked separately (don't roll into the component
score) and sum to 13: wiringControllersExist (3), wiringHostsExist
(3), wiringHandlersExist (3), wiringSourcesExist (2),
wiringDataPathsValid (2).
Status
- Structural + catalog validation — shipped, default-on. Single source of truth for component-shape contracts in A2UI messages.
- Semantic validator (Phases 1 + 2) — shipped. LLM-judge with
dominantPattern(0.5 weight) +requiredCapabilities(0.35) +forbiddenNoise(0.15). Combined-gating opt-in via--gate-mode combinedto the eval harness. - Phases 3–5 — planned per
semantic-validator.md: rubric expansion, persisted per-intent thresholds, and judge ensembling.
Runtime
ajv+ajv-formatsfor structural validation.@adia-ai/a2ui-runtimefor the registry shape.
Related
- Spec:
docs/specs/semantic-validator.md— canonical design narrative - Audit:
docs/reports/audit-validator-2026-05-06.md— 226-line deep-dive with check-by-check rationale + recalibration notes - Repo:
adiahealth/gen-ui-kit - CHANGELOG:
CHANGELOG.md
