@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 +8 -0
- package/package.json +1 -1
- package/validator.js +50 -17
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.
|
|
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:
|
|
18
|
-
allTypesRegistered:
|
|
19
|
-
noOrphanedChildren:
|
|
20
|
-
cardStructure:
|
|
21
|
-
flatAdjacency:
|
|
22
|
-
noBareDivs:
|
|
23
|
-
noHardcodedColors:
|
|
24
|
-
noInlineLayout:
|
|
25
|
-
textContentSet:
|
|
26
|
-
idUniqueness:
|
|
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:
|
|
29
|
-
headingHierarchy:
|
|
28
|
+
imagesHaveAlt: 2,
|
|
29
|
+
headingHierarchy: 2,
|
|
30
30
|
tabStructure: 2,
|
|
31
|
-
gridVsColumn:
|
|
32
|
-
landmarkStructure:
|
|
33
|
-
intentAlignment:
|
|
34
|
-
|
|
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
|
|