@adia-ai/a2ui-validator 0.5.18 → 0.5.20

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -6,6 +6,14 @@ Follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and
6
6
 
7
7
  _No pending changes._
8
8
 
9
+ ## [0.5.20] - 2026-05-18
10
+
11
+ _Lockstep ride-along (no source change in this package; companion to web-components v0.5.20 — see root CHANGELOG)._
12
+
13
+ ## [0.5.19] - 2026-05-17
14
+
15
+ _Lockstep ride-along (no source change in this package; companion to web-components v0.5.19 — see root CHANGELOG)._
16
+
9
17
  ## [0.5.18] - 2026-05-16
10
18
 
11
19
  _Lockstep ride-along (no source change in this package; companion to web-components v0.5.18 — see root CHANGELOG)._
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adia-ai/a2ui-validator",
3
- "version": "0.5.18",
3
+ "version": "0.5.20",
4
4
  "description": "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 without pulling the whole generator graph.",
5
5
  "type": "module",
6
6
  "main": "./index.js",
package/validator.js CHANGED
@@ -14,24 +14,25 @@ import { registry, wiringRegistry } from '@adia-ai/a2ui-runtime';
14
14
 
15
15
  const WEIGHTS = {
16
16
  validMessageFormat: 8,
17
- hasRootComponent: 7,
18
- allTypesRegistered: 9,
19
- noOrphanedChildren: 9,
20
- cardStructure: 6,
21
- flatAdjacency: 5,
22
- noBareDivs: 7,
23
- noHardcodedColors: 3,
24
- noInlineLayout: 5,
25
- textContentSet: 5,
26
- idUniqueness: 5,
17
+ hasRootComponent: 6,
18
+ allTypesRegistered: 8,
19
+ noOrphanedChildren: 8,
20
+ cardStructure: 5,
21
+ flatAdjacency: 4,
22
+ noBareDivs: 6,
23
+ noHardcodedColors: 2,
24
+ noInlineLayout: 4,
25
+ textContentSet: 4,
26
+ idUniqueness: 4,
27
27
  interactiveHasLabel: 4,
28
- imagesHaveAlt: 3,
29
- headingHierarchy: 3,
28
+ imagesHaveAlt: 2,
29
+ headingHierarchy: 2,
30
30
  tabStructure: 2,
31
- gridVsColumn: 3,
32
- landmarkStructure: 3,
33
- intentAlignment: 13,
34
- componentsHaveRenderableContent: 6, // §165 (v0.5.4) — the 3rd safety layer
31
+ gridVsColumn: 2,
32
+ landmarkStructure: 2,
33
+ intentAlignment: 7,
34
+ ontologyAlignment: 15,
35
+ componentsHaveRenderableContent: 5, // §165 (v0.5.4) — the 3rd safety layer
35
36
  };
36
37
 
37
38
  /**
@@ -107,6 +108,7 @@ export function validateSchema(messages, options = {}) {
107
108
  checkGridVsColumn(allComponents),
108
109
  checkLandmarkStructure(allComponents),
109
110
  checkIntentAlignment(allComponents, options.intent),
111
+ checkOntologyAlignment(allComponents, options.context),
110
112
  ];
111
113
 
112
114
  // Wiring checks (only scored when wireComponents present)
@@ -128,7 +130,7 @@ export function validateSchema(messages, options = {}) {
128
130
  score = Math.round(score * 100);
129
131
 
130
132
  // Hard-fail: bare HTML elements or unregistered types mean the output is fundamentally wrong
131
- const hardFails = ['noBareDivs', 'allTypesRegistered', 'hasRootComponent', 'tabStructure'];
133
+ const hardFails = ['noBareDivs', 'allTypesRegistered', 'hasRootComponent', 'tabStructure', 'ontologyAlignment'];
132
134
  const hasHardFail = checks.some(c => hardFails.includes(c.name) && !c.passed);
133
135
 
134
136
  // Severe intent mismatch: if intentAlignment score < 0.3 and intent was provided,
@@ -1097,6 +1099,37 @@ function checkIntentAlignment(components, intent) {
1097
1099
  * @param {Set<string>} componentIds — IDs from updateComponents
1098
1100
  * @returns {object[]} — Array of check results
1099
1101
  */
1102
+ function checkOntologyAlignment(components, context) {
1103
+ if (!context || !context.domain || !context.domain.entities) {
1104
+ return { name: 'ontologyAlignment', passed: true, score: 1, detail: 'No ontology context provided' };
1105
+ }
1106
+
1107
+ // Check if at least one entity or task from the context appears in the components
1108
+ const entities = context.domain.entities.map(e => e.toLowerCase());
1109
+ const tasks = (context.tasks?.primary || []).map(t => t.toLowerCase());
1110
+ const terms = [...entities, ...tasks];
1111
+
1112
+ if (terms.length === 0) {
1113
+ return { name: 'ontologyAlignment', passed: true, score: 1, detail: 'Empty ontology context' };
1114
+ }
1115
+
1116
+ const jsonStr = JSON.stringify(components).toLowerCase();
1117
+
1118
+ const foundTerms = terms.filter(t => jsonStr.includes(t));
1119
+
1120
+ // Need at least one term match. If 0, it hallucinates completely different data.
1121
+ const score = foundTerms.length > 0 ? 1 : 0;
1122
+
1123
+ return {
1124
+ name: 'ontologyAlignment',
1125
+ passed: score > 0,
1126
+ score: score,
1127
+ detail: score > 0
1128
+ ? `Ontology respected (Found terms: ${foundTerms.join(', ')})`
1129
+ : `Ontology violation: None of the planned domain entities or tasks were found in the UI tree.`,
1130
+ };
1131
+ }
1132
+
1100
1133
  function checkWiring(wire, componentIds) {
1101
1134
  const checks = [];
1102
1135