Repository navigation
feat(core): gate pre-1.0 component metadata and dual-read v1.0 @-prefixed envelope mixins - #4037
Conversation
There was a problem hiding this comment.
Code Review
This pull request gates component-level metadata and catalogId envelope handling on v1.0+ surfaces across the Dart, Python, Swift, and TypeScript SDKs, ensuring pre-1.0 surfaces treat them as ordinary properties while v1.0 surfaces support both @-prefixed mixins and legacy bare forms. The review feedback identifies a few key issues: first, the Python MessageProcessor fails to map legacy bare checks to @checks for checkable components, which requires importing and using is_component_checkable. Second, the Swift ComponentEnvelope incorrectly treats standard metadata mixins referencing ComponentCommonMetadata as custom properties, which can be resolved by explicitly checking the referenced definition name.
2ce903a to
8e5fb98
Compare
…ixed envelope mixins
8e5fb98 to
cb5713c
Compare
Python now stores a legacy bare `checks` under `@checks` on a checkable v1.0 component that declares no `checks` of its own, matching Dart and TypeScript. Before this, the check rules stayed under `checks` and were not processed as the Checkable mixin. Swift's `ComponentEnvelope.declaresOwnProperty` now treats a `metadata` schema that references `ComponentCommonMetadata` as the standard mixin, matching Python. Before this, such a schema counted as a custom property, so a legacy bare `metadata` was not read as the mixin. Adds regression tests for both.
…al-read # Conflicts: # python/a2ui_agent/tests/conformance/test_conformance.py
…etadata in serialization A v1.0 component's legacy bare `catalogId` is chosen as its catalog by its schema in the surface default catalog. Treat that same key as the mixin when splitting the component's properties, even if the target catalog's schema declares a `catalogId` property of its own, so routing and storage agree (TypeScript, Python, Dart, Swift; new conformance case). `ComponentModel.componentTree` (TypeScript) and `toJson` (Dart) now write the envelope metadata and catalog as `@metadata` and `@catalogId` when the component has a property of that name, instead of overwriting it. Mark the pre-1.0 `metadata`/`catalogId` gating changelog entries as breaking, with a migration note for per-component catalog selection below v1.0.
- Always serialize v1.0 envelope metadata and catalog as `@metadata` and `@catalogId` (Python `component_tree` and `_validate_component`, TypeScript `componentTree`, Dart `toJson`), so an optional custom `metadata` or `catalogId` that a payload omits no longer makes re-validation reject a valid component. - Use one rule in all four SDKs for an inline bare `catalogId` or `metadata`: it is the standard mixin only beside an inline `id` and with the standard shape (`catalogId` a string; `metadata` an extensions-only object). Python and TypeScript also find custom envelope-named properties declared in `oneOf`/`anyOf` branches. - Reject a null `@catalogId` or legacy `catalogId` on v1.0 in Python and Dart, as TypeScript and Swift already did. - Python: attach the `AccessibilityAttributes` behavior to `@accessibility` on every v1.0 component, keep undeclared nested `checks` keys, drop a bare `checks` in the node resolver fallback only below v1.0, and check every combiner in `PayloadValidator._schema_has_property`. - Swift: validate and store a component routed by a legacy bare `catalogId` through `@catalogId`, and fix `ComponentEnvelope.isCheckable` for a `Checkable` component that also declares a custom `checks`. - TypeScript: report mixin conflicts at the `@` key, decide the `accessibility` mirror from the schema, keep the mirror in `NodeResolver.pruneOmittedProps`, and revert the Basic Catalog components to read `props.accessibility`. - Conformance: v1.0 envelope expectations use `@metadata`, and every harness decides envelope metadata from the expectation key alone. New cases cover the fixes above. - Add `TODO(a2ui-project#4082)` to each SDK's legacy fallback, and drop the breaking label from the pre-1.0 gating changelog entries.
…al-read # Conflicts: # dart/a2ui_core/CHANGELOG.md
josemontespg
left a comment
There was a problem hiding this comment.
LGTM! Left a few minor, non-blocking inline comments on the Angular fallback binder and changelog entry ordering.
Pick '@Checks' on v1.0+ surfaces and 'checks' on pre-1.0 surfaces in ComponentBinder.bind(context) so custom bare 'checks' component properties on v1.0 surfaces are not passed to bindChecks, and fold the PR a2ui-project#4037 changelog bullets below breaking entries.
…al-read # Conflicts: # dart/a2ui_flutter/CHANGELOG.md
…DKs (#4044) ## Summary This change updates the v1.0 specification, schemas, catalog examples, documentation, and Agent SDKs to emit `@catalogId`, `@metadata`, `@accessibility`, and `@checks`. Using `@`-prefixed keys prevents envelope fields from colliding with custom component properties. It builds on #4037, which updated all Core and Renderer SDKs to read both forms. Resolves #3466 Addresses #1376 Addresses #4012 ## Changes - Updated `specification/v1_0/json/common_types.json`, `specification/v1_0/json/agent_to_renderer.json`, v1.0 specification docs, `specification/v1_0/test/cases/*`, `catalogs/basic/v1/*`, and `catalogs/mcp/v1/*` to declare and use `@catalogId`, `@metadata`, `@accessibility`, and `@checks`. - Regenerated the v1.0 schema definitions across TypeScript (`@a2ui/web_core`), Python (`a2ui_core`), Dart (`a2ui_core`), and Swift (`A2UISchemaGenerator`). The bare dual-read fallback from #4037 remains active in the validators and processors. - Updated `to_pascal_case` in `.agents/skills/a2ui-generate-pydantic-models/scripts/utils.py` to split on non-alphanumeric characters so `@metadata` generates `ComponentCommonMetadata`. - Updated the Python, TypeScript, and Dart Agent SDKs (`Express`, `DirectJson`, `Atom`, `Elemental`, macro expander, `MessageReader`, and `RendererState`). They now emit `@`-prefixed keys on v1.0 and dual-read undeclared bare keys in the decompilers. - Updated `python/a2ui_agent/src/a2ui/builder/v1_0/models.py` and `dart/a2ui_cli` (`CatalogAnalyzer` and `PythonEmitter`). Python v1.0 builders keep `catalog_id`, `accessibility`, `extensions`, and `checks` arguments, serialize to `@`-prefixed keys, and accept both forms. - Updated conformance suites (`conformance/core/*`, `conformance/agent/*`, `conformance/e2e/newsletter_signup.yaml`, and `conformance/test_data/*`), `docs/public`, `docs/site`, and `blueprints/*` to use `@`-prefixed keys on v1.0.
# Description Backfills missing `## Unreleased` entries in [`swift/CHANGELOG.md`](https://github.com/a2ui-project/a2ui/blob/b52b2cda6/swift/CHANGELOG.md?plain=1#L5-L58) for Swift pull requests that merged since [`v0.10.0+swift`](https://github.com/a2ui-project/a2ui/releases/tag/v0.10.0%2Bswift) (`ae2b6baae`) while [#4133](#4133) was open or shortly after it landed: 1. [#4140](#4140), [#4037](#4037), and [#4044](#4044) (**BREAKING CHANGE**): Gate component `metadata` and `catalogId` envelope handling to v1.0 and later surfaces, and support `@catalogId`, `@metadata`, `@accessibility`, and `@checks` on v1.0 components with dual-read fallback for undeclared bare keys. 2. [#4022](#4022) (**BREAKING CHANGE**): Migrate `A2UIAudioPlayerModel` and `A2UIVideoPlayerModel` from `ObservableObject` to `@Observable`, take direct model instances in `A2UIAudioPlayer.init(node:model:)` and `A2UIVideo.init(node:model:)`, skip redundant `AVPlayer` seeks during `A2UIAudioPlayer` playback, and adopt `ContentUnavailableView` and a poster play button accessibility label in `A2UIVideo`. 3. [#4030](#4030): Inherit the enclosing function's resolved `catalogId` for catalogless calls inside v1.0 `FormatStringFunction` `${...}` template expressions, add `DataContext.activeFunctionCatalogID` and `FunctionHandler.resolvedFunctionCatalogID(forCallCatalogID:)`, and scope `RPCHandler` `callRendererFunction` evaluation to the target catalog. 4. [#4092](#4092): Include `$defs.anyComponent` and `$defs.anyFunction` unions in v1.0 or later inline catalogs emitted by `MessageProcessor.getRendererCapabilities(options:)`. 5. [#4097](#4097): Map `A2UIImage` `fit` values (`contain`, `cover`, `fill`, `none`, `scaleDown`) to CSS `object-fit` semantics and fix variant sizing in `BasicCatalogSwiftUI`. 6. [#4099](#4099): Drop `theme` from v1.0 `createSurface` payloads when `ValidationConfig.allowUnknownElements` is `true` and add `allowUnknownElements` overloads to `VersionAdapter` and `BaseVersionAdapter`. ## Pre-launch Checklist One time: - [x] I signed the [CLA]. - [x] I read the [Contributors Guide]. - [x] I read the [Style Guide]. - [x] I have built and run at least one [sample]. For this PR: - [x] I have updated the relevant CHANGELOG.md file. - [x] I updated/added relevant documentation. - [x] My code changes (if any) have tests. - [x] If my branch is on a fork, I have verified that [scripts/e2e_test.sh](https://github.com/a2ui-project/a2ui/blob/main/scripts/e2e_test.sh) passes. If you need help, consider asking for advice on the [discussion board]. <!-- Links --> [CLA]: https://cla.developers.google.com/ [Contributors Guide]: https://github.com/a2ui-project/a2ui/blob/main/CONTRIBUTING.md [discussion board]: https://github.com/a2ui-project/a2ui/discussions [Style Guide]: https://github.com/a2ui-project/a2ui/blob/main/CONTRIBUTING.md#coding-style [sample]: https://github.com/a2ui-project/a2ui/blob/main/samples/README.md#samples --------- Signed-off-by: Pete Biencourt <[email protected]>
Summary
This change gates component
metadataandcatalogIdenvelope handling to v1.0 and later surfaces. Pre-1.0 surfaces now treatmetadataandcatalogIdas ordinary component properties. On v1.0 surfaces, the TypeScript, Python, Dart, and Swift Core and Renderer SDKs accept@metadata,@catalogId,@accessibility, and@checks. They also continue to read bare keys during the transition when the component schema does not declare them.Resolves #4012
Addresses #3466
Addresses #1376
Reviewer Focus
typescript/core/web_core/src/catalog/component_envelope.ts,python/a2ui_core/src/a2ui/core/catalog/catalog.py,dart/a2ui_core/lib/src/core/catalog.dart, andswift/core/Sources/A2UICore/Validation/ComponentEnvelope.swift. These helpers distinguish standardComponentCommonandCheckableshapes from custom properties with the same bare name. They also reject v1.0 component schemas that declare custom@metadata,@catalogId,@accessibility, or@checksproperties.typescript/core/web_core/src/validation/payload-validator.tsandtypescript/core/web_core/src/processing/message-processor.ts, alongside their Python, Dart, and Swift counterparts. On pre-1.0 surfaces,metadataandcatalogIdremain inComponentModel.propertieswithout running v1.0 metadata rules. On v1.0 surfaces, they accept@-prefixed keys, fall back to undeclared bare keys, and reject duplicate keys ornull@catalogIdvalues. They also check that@metadata.extensionskeys match<vendor_namespace>:<field>where both segments are UAX#31identifiers.typescript/core/web_core/src/resolution/node-resolver.ts,python/a2ui_core/src/a2ui/core/resolution/node_resolver.py,dart/a2ui_core/lib/src/resolution/generic_binder.dart, andswift/core/Sources/A2UICore/Resolution/NodeResolver.swift. When a component schema does not declare a customaccessibilityproperty, node resolution copies@accessibilityontoaccessibility. This lets existing component implementations continue readingprops.accessibility.typescript/renderers/angular/src/core/component-binder.service.tsanddart/a2ui_flutter/lib/src/node_view.dart. Angular'sComponentBinder.bindselects@checksor barechecksbased on the surface protocol version. Flutter'sNodeViewreads@accessibilitywith a fallback toaccessibility.Changes
conformance/core/message_processor_v0_9.yaml,conformance/core/message_processor_v1_0.yaml,conformance/core/validator_v1_0.yaml, andconformance/core/reserved_keys.yaml. These cases test pre-1.0 custommetadataandcatalogIdpreservation and v1.0@-prefixed mixin ingestion. They also test custom-property coexistence, duplicate key rejection,@metadata.extensionskey rules, and nested@directive rejection.@a2ui/web_coreand@a2ui/angularto gate pre-1.0 envelope extraction and dual-read v1.0@-prefixed mixins. Node resolution normalizes@accessibility,ComponentModel.toJSON()serializes@catalogIdand@metadataon v1.0, and AngularComponentBindergates check keys by version.python/a2ui_core(catalog.py,payload_validator.py,message_processor.py,generic_binder.py,node_resolver.py,component_model.py, andsurface_components_model.py). It now gates pre-1.0 extraction, resolves v1.0@-prefixed mixins, and serializes@catalogIdand@metadatainComponentModel.to_json().dart/a2ui_coreanddart/a2ui_flutter(catalog.dart,component_model.dart,reference_schema.dart,processor.dart,generic_binder.dart,validator.dart, andnode_view.dart). It gates pre-1.0 metadata rules, dual-reads v1.0@-prefixed mixins, and serializes@catalogIdand@metadatainComponentModel.toJson().swift/coreandswift/swiftui(ComponentEnvelope.swift,PayloadValidator.swift,MessageProcessor.swift,GraphTopologyValidator.swift,V10VersionAdapter.swift,NodeResolver.swift,Node.swift, andComponentModel.swift). It gates pre-1.0 envelope extraction and dual-reads v1.0@-prefixed mixins.Impact & Risks
metadataorcatalogIdnow keep those values inComponentModel.properties. Previously, v1.0 envelope handling stripped or rejected them.@-prefixed and bare keys when the component schema does not declare that bare name.@-prefixed keys. Issue Remove legacy bare-key fallback for v1.0 component envelope fields #4082 tracks removing the bare v1.0 fallback after downstream consumers migrate.Testing
uv run pytest python/a2ui_core conformance/testsanduv run --all-packages mypy ..npm --prefix typescript/core/web_core testandnpm --prefix typescript/renderers/angular test -- --watch=false.dart analyze --fatal-infosanddart testindart/a2ui_core, plusflutter analyzeandflutter testindart/a2ui_flutter.swift testinswift/coreandswift-format lint --strict -r swift/core.