Skip to content

feat(web_core): add PayloadValidator, UAX 31 identifier checks, and full JSON Schema constraint parity - #2716

Open
gspencergoog wants to merge 6 commits into
v1_0_ts_multicatalog_parityfrom
v1_0_ts_payload_validator
Open

gspencergoog wants to merge 6 commits into
v1_0_ts_multicatalog_parityfrom
v1_0_ts_payload_validator

Conversation

@gspencergoog

@gspencergoog gspencergoog commented Sep 21, 2026

Copy link
Copy Markdown
Collaborator

Summary

Adds catalog-scoped PayloadValidator and shared UAX #31 identifier validation in @a2ui/web_core, and closes JSON Schema validation disparities with Python's Draft202012Validator directly in schema_loader.ts without an Ajv dependency.

Part of Stack #2720 (stacked on #2715).

Reviewer Focus

  • src/validation/payload-validator.ts: Verify validateFunction's @index fallback to IndexApi when un-registered in catalog.functions, argument validation returning parsed records to eliminate duplicate parsing in RpcHandler, and recursive validateNestedFunctions traversal with catalogId overrides.
  • src/catalog/schema_loader.ts: Scrutinize convertRefToZod (z.lazy cycle handling with defCache caching), convertUnionToZod (oneOf exclusivity vs anyOf), and numeric/string/array constraint conversion.
  • Files to skim: Conformance test updates in conformance/core/validator_v1_0.yaml and schema type registrations in src/types/ can be safely skimmed.

Changes

  • UAX #31 identifier validation (src/common/uax31.ts):
    • Extracted isValidUax31Identifier and assertUax31Identifier from schema_loader.ts.
    • Switched from ID_Start/ID_Continue to the specification's XID_Start/XID_Continue Unicode property classes.
    • Raised A2uiCatalogError on invalid identifiers at catalog construction time.
  • PayloadValidator (src/validation/payload-validator.ts):
    • Added validateComponent, validateFunction, and validateTheme scoped to a single Catalog.
    • Enforced v1.0 UAX #31 identifier checks on component instance IDs, function names, and argument keys.
    • Added @index system function fallback (IndexApi) so custom v1.0 catalogs validate @index calls under strict validation.
    • Returned parsed argument records from validateFunction to avoid redundant second-pass parsing.
    • Recursively validated nested {call, args} objects within component properties (validateNestedFunctions), resolving catalogId overrides against availableCatalogs.
    • Extracted identifier validation into assertFunctionIdentifiers to maintain cognitive complexity $\le 11$.
  • JSON Schema to Zod parity (src/catalog/schema_loader.ts):
    • Added z.lazy recursion and per-branch visitedPointers sets so self-recursive #/$defs/... references (such as TreeNode) validate at arbitrary depth, caching compiled closures in defCache.
    • Replaced silent z.unknown() fallbacks on unresolvable #/$defs/... references with a failing superRefine validator.
    • Added DataBinding and FunctionCall to COMMON_TYPE_SCHEMAS, preserving union branches when combined with enum in oneOf/anyOf.
    • Enforced bounds, counts, and item uniqueness constraints (minimum, maximum, minLength, maxLength, minItems, maxItems, uniqueItems, const, not).
    • Enforced .strict() on inline nested type: object schemas when additionalProperties: false is set.
    • Enforced oneOf single-branch exclusivity (matchCount === 1) distinctly from anyOf.
    • Extracted $ref and union conversion into helper functions (convertRefToZod, convertUnionToZod).
  • Processor and RPC wiring (message-processor.ts, rpc-handler.ts):
    • Routed component and theme validation in MessageProcessor and argument validation in RpcHandler.parseArguments through PayloadValidator.
  • Conformance suite (validator_v1_0.yaml, conformance_test.mjs):
    • Strengthened test_v10_uax31_invalid_identifier_error with a valid root component on createSurface and an expectError.message check.

Impact & Risks

  • Strict v1.0 identifier validation: Component instance IDs, function names, and argument keys in v1.0 surfaces are now checked against UAX #31. Payloads targeting v0.9 catalogs remain unaffected.
  • Stricter JSON-loaded catalog validation: Catalogs loaded via Catalog.fromSchema now enforce bounds, recursive $defs, nested additionalProperties: false, and oneOf exclusivity at runtime.

Testing

  1. Run unit tests in web_core (including uax31.test.ts and payload-validator.test.ts):
    yarn workspace @a2ui/web_core test
  2. Run conformance tests:
    node typescript/web_core/tests/conformance/conformance_test.mjs
  3. Run builds and linters across all workspaces:
    yarn workspaces foreach -A -t run build
    yarn workspaces foreach -A -p -j 8 run lint

gemini-code-assist[bot]

This comment was marked as resolved.

@gspencergoog
gspencergoog force-pushed the v1_0_ts_payload_validator branch 2 times, most recently from 646cc38 to b85abbd Compare September 21, 2026 23:51
@gspencergoog
gspencergoog added this pull request to stack #2719 September 21, 2026 23:51
@gspencergoog
gspencergoog removed this pull request from stack #2719 September 21, 2026 23:52
@gspencergoog
gspencergoog added this pull request to stack #2720 September 21, 2026 23:52
@gspencergoog
gspencergoog force-pushed the v1_0_ts_payload_validator branch from b85abbd to 64ca2b9 Compare September 22, 2026 16:38
@gspencergoog gspencergoog changed the title feat(web_core): add PayloadValidator, UAX #31 identifier checks, and full JSON Schema constraint parity feat(web_core): add PayloadValidator, UAX 31 identifier checks, and full JSON Schema constraint parity Sep 22, 2026
@gspencergoog
gspencergoog force-pushed the v1_0_ts_payload_validator branch 3 times, most recently from 1712e85 to f9d617c Compare September 22, 2026 18:42
@gspencergoog
gspencergoog requested a review from nan-yu September 22, 2026 19:46
Comment thread typescript/web_core/src/validation/payload-validator.ts Outdated
Comment thread typescript/web_core/src/validation/payload-validator.ts
@gspencergoog
gspencergoog force-pushed the v1_0_ts_payload_validator branch from f9d617c to 22a2e63 Compare September 22, 2026 21:56
…full JSON Schema constraint parity

Introduces `PayloadValidator` in `typescript/web_core/src/validation/payload-validator.ts`,
scoped to a single `Catalog`, and extracts UAX #31 identifier validation into
`typescript/web_core/src/common/uax31.ts` using the normative `XID_Start` /
`XID_Continue` Unicode property classes.

Closes all seven validation disparities between TypeScript's Zod-based validator
and Python's `Draft202012Validator` / `PayloadValidator` without introducing an
Ajv runtime dependency:

1. Recursively walks component property trees (`validateNestedFunctions`) to
   validate nested `{call, args}` function calls against their target catalog,
   resolving `catalogId` overrides via `availableCatalogs`.
2. Supports self-recursive `#/$defs/...` references (e.g. `TreeNode`) at
   arbitrary depth via `z.lazy` and per-branch `visitedPointers` sets.
3. Rejects unresolvable `#/$defs/...` pointers during validation instead of
   silently falling back to `z.unknown()`.
4. Preserves `DataBinding` and `FunctionCall` union branches when combined with
   `enum` in `oneOf` / `anyOf` schemas.
5. Translates JSON Schema bounds and literal constraints (`minimum`, `maximum`,
   `exclusiveMinimum`, `exclusiveMaximum`, `multipleOf`, `minLength`,
   `maxLength`, `minItems`, `maxItems`, `uniqueItems`, `const`, and `not`) into
   Zod refinements.
6. Enforces `additionalProperties: false` and `unevaluatedProperties: false` on
   inline nested `type: object` schemas via `.strict()`.
7. Enforces single-branch exclusivity (`matchCount === 1`) for `oneOf` schemas
   distinctly from `anyOf`.

Also delegates component and theme validation in `MessageProcessor` and incoming
function argument validation in `RpcHandler` to `PayloadValidator`, and
strengthens `conformance/core/validator_v1_0.yaml` (`test_v10_uax31_invalid_identifier_error`)
along with `expectError.message` assertions and `catalogPaths` basic-catalog ID
aliasing in `conformance_test.mjs`.
…ema loader

- Pipe raw input through superRefine before baseUnion in oneOf to check exclusivity on un-transformed values
- Preserve raw args when defined in validateNestedFunctions to catch non-object payloads
- Memoize local $defs compilation in convertPropertyToZod to avoid re-compilation on every z.lazy validation
- Serialize array items with recursively sorted keys in canonicalJsonStringify for uniqueItems checking
- Add regression tests for oneOf, non-object function args, and uniqueItems key ordering
@gspencergoog
gspencergoog force-pushed the v1_0_ts_payload_validator branch from 22a2e63 to 625a937 Compare September 22, 2026 23:53
- Remove availableCatalogs parameter from PayloadValidator constructor and from MessageProcessor.validateComponentUpdate
- In validateNestedFunctions, check UAX #31 identifier syntax on foreign-catalog calls but skip catalog schema validation, delegating function validation to runtime resolution in DataContext
- Update unit tests in payload-validator.test.ts to assert single-catalog behavior and foreign-call identifier checks
@gspencergoog

Copy link
Copy Markdown
Collaborator Author

@nan-yu PTAL

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants