Skip to content

feat(core): gate pre-1.0 component metadata and dual-read v1.0 @-prefixed envelope mixins - #4037

Merged
gspencergoog merged 9 commits into
a2ui-project:mainfrom
gspencergoog:feat/v1-envelope-dual-read
Nov 11, 2026
Merged

gspencergoog merged 9 commits into
a2ui-project:mainfrom
gspencergoog:feat/v1-envelope-dual-read

Conversation

@gspencergoog

@gspencergoog gspencergoog commented Nov 6, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

This change gates component metadata and catalogId envelope handling to v1.0 and later surfaces. Pre-1.0 surfaces now treat metadata and catalogId as 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

  • Inspect 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, and swift/core/Sources/A2UICore/Validation/ComponentEnvelope.swift. These helpers distinguish standard ComponentCommon and Checkable shapes from custom properties with the same bare name. They also reject v1.0 component schemas that declare custom @metadata, @catalogId, @accessibility, or @checks properties.
  • Inspect typescript/core/web_core/src/validation/payload-validator.ts and typescript/core/web_core/src/processing/message-processor.ts, alongside their Python, Dart, and Swift counterparts. On pre-1.0 surfaces, metadata and catalogId remain in ComponentModel.properties without running v1.0 metadata rules. On v1.0 surfaces, they accept @-prefixed keys, fall back to undeclared bare keys, and reject duplicate keys or null @catalogId values. They also check that @metadata.extensions keys match <vendor_namespace>:<field> where both segments are UAX #31 identifiers.
  • Inspect 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, and swift/core/Sources/A2UICore/Resolution/NodeResolver.swift. When a component schema does not declare a custom accessibility property, node resolution copies @accessibility onto accessibility. This lets existing component implementations continue reading props.accessibility.
  • Inspect typescript/renderers/angular/src/core/component-binder.service.ts and dart/a2ui_flutter/lib/src/node_view.dart. Angular's ComponentBinder.bind selects @checks or bare checks based on the surface protocol version. Flutter's NodeView reads @accessibility with a fallback to accessibility.

Changes

  • Added shared cross-SDK conformance suites in conformance/core/message_processor_v0_9.yaml, conformance/core/message_processor_v1_0.yaml, conformance/core/validator_v1_0.yaml, and conformance/core/reserved_keys.yaml. These cases test pre-1.0 custom metadata and catalogId preservation and v1.0 @-prefixed mixin ingestion. They also test custom-property coexistence, duplicate key rejection, @metadata.extensions key rules, and nested @ directive rejection.
  • Updated @a2ui/web_core and @a2ui/angular to gate pre-1.0 envelope extraction and dual-read v1.0 @-prefixed mixins. Node resolution normalizes @accessibility, ComponentModel.toJSON() serializes @catalogId and @metadata on v1.0, and Angular ComponentBinder gates check keys by version.
  • Updated python/a2ui_core (catalog.py, payload_validator.py, message_processor.py, generic_binder.py, node_resolver.py, component_model.py, and surface_components_model.py). It now gates pre-1.0 extraction, resolves v1.0 @-prefixed mixins, and serializes @catalogId and @metadata in ComponentModel.to_json().
  • Updated dart/a2ui_core and dart/a2ui_flutter (catalog.dart, component_model.dart, reference_schema.dart, processor.dart, generic_binder.dart, validator.dart, and node_view.dart). It gates pre-1.0 metadata rules, dual-reads v1.0 @-prefixed mixins, and serializes @catalogId and @metadata in ComponentModel.toJson().
  • Updated swift/core and swift/swiftui (ComponentEnvelope.swift, PayloadValidator.swift, MessageProcessor.swift, GraphTopologyValidator.swift, V10VersionAdapter.swift, NodeResolver.swift, Node.swift, and ComponentModel.swift). It gates pre-1.0 envelope extraction and dual-reads v1.0 @-prefixed mixins.

Impact & Risks

Testing

  • Run the Python and conformance test suites: uv run pytest python/a2ui_core conformance/tests and uv run --all-packages mypy ..
  • Run the TypeScript tests: npm --prefix typescript/core/web_core test and npm --prefix typescript/renderers/angular test -- --watch=false.
  • Run the Dart and Flutter analyzers and tests: dart analyze --fatal-infos and dart test in dart/a2ui_core, plus flutter analyze and flutter test in dart/a2ui_flutter.
  • Run the Swift test suite and formatter lint: swift test in swift/core and swift-format lint --strict -r swift/core.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@gspencergoog
gspencergoog force-pushed the feat/v1-envelope-dual-read branch 2 times, most recently from 2ce903a to 8e5fb98 Compare November 6, 2026 22:29
@gspencergoog
gspencergoog force-pushed the feat/v1-envelope-dual-read branch from 8e5fb98 to cb5713c Compare November 6, 2026 22:42
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.
Comment thread typescript/core/web_core/CHANGELOG.md Resolved
- 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 josemontespg left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM! Left a few minor, non-blocking inline comments on the Angular fallback binder and changelog entry ordering.

Comment thread typescript/core/web_core/CHANGELOG.md Resolved
Comment thread python/a2ui_core/CHANGELOG.md Resolved
Comment thread dart/a2ui_core/CHANGELOG.md Resolved
Comment thread dart/a2ui_flutter/CHANGELOG.md Resolved
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
@gspencergoog
gspencergoog merged commit 8c3bd40 into a2ui-project:main Nov 11, 2026
55 checks passed
@github-project-automation github-project-automation Bot moved this from Todo to Done in A2UI Nov 11, 2026
gspencergoog added a commit that referenced this pull request Nov 11, 2026
…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.
@gspencergoog
gspencergoog deleted the feat/v1-envelope-dual-read branch November 11, 2026 23:25
pinieb added a commit that referenced this pull request Nov 13, 2026
# 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]>
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.

Pre-v1.0 custom catalog component properties named 'metadata' are stripped and fail validation (TypeScript, Dart, Swift)

3 participants