#2645·a2ui

[BUG]: Python v0.9/v0.9.1 catalogs reject the specified `FunctionCall.returnType`

Author: gspencergoogCreated Sep 14, 2026Updated Sep 16, 2026
Labelstype: bugpython

Describe the Bug

The Python SDK validates v0.9 and v0.9.1 function calls against the v1.0 shape.

FunctionCall is defined once, in python/a2ui_core/src/a2ui/core/schema/common_types.py, and every version package imports it. Its fields are call, args and catalogId, and it derives from StrictBaseModel, which sets extra="forbid". That field set is the v1.0 one:

v0.9 / v0.9.1 common_types.json v1.0 common_types.json Python model
call yes yes (via FunctionCommon) yes
returnType yes, default "boolean" removed no
catalogId no yes (via FunctionCommon) yes

Two consequences for v0.9 and v0.9.1:

  1. A payload carrying returnType is rejected, although the specification defines the field. extra="forbid" turns the unknown key into an error.
  2. A payload carrying catalogId is accepted, although the version does not define it.

returnType is load-bearing in v0.9, not decorative. DynamicString, DynamicNumber and DynamicBoolean each constrain their function branch with allOf: [FunctionCall, {properties: {returnType: {const: "string"}}}], so the field is how the document states the expected return type of a call in a typed slot.

Steps to Reproduce

python
from a2ui.core.schema.common_types import FunctionCall

FunctionCall.model_validate({"call": "f"})                          # ok
FunctionCall.model_validate({"call": "f", "catalogId": "c"})        # ok, but v1.0-only
FunctionCall.model_validate({"call": "f", "returnType": "string"})  # raises

The third call fails with:

1 validation error for FunctionCall
returnType
  Extra inputs are not permitted [type=extra_forbidden, ...]

The same gap appears at the schema layer. Validating a v0.9 DynamicString slot against the v0.9 BasicCatalog schema, versus against specification/v0_9/json/common_types.json:

payload                                    BasicCatalog   specification
{"call": "f"}                              accepts        accepts
{"call": "f", "returnType": "string"}      rejects        accepts
{"call": "f", "returnType": "number"}      rejects        rejects

Expected Behavior

A catalog built for v0.9 or v0.9.1 accepts returnType, and enforces it where the version's Dynamic* definitions require a particular value. Whether catalogId should be rejected on those versions or accepted as an extension is a call for the maintainers; today it is accepted silently.

Additional Context

  • Scope: this is one shared model, not the version-specific component models. The v0.9 models supply correct version-specific definitions elsewhere. For example the v0.9 AccessibilityAttributes correctly carries only label and description, without the hidden and live that v1.0 added.
  • Related but separate: the hardcoded fallback definitions in _get_dynamic_types_defs() (catalog.py) are also v1.0-shaped. Their reach is small, since definitions from the version's own models and from a caller-supplied common_types.json both take precedence. For the v0.9 BasicCatalog only ComponentId, DynamicBoolean, DynamicNumber, DynamicString and DynamicValue come from the fallback. The practical effect there is the lost returnType constraint on the three scalar wrappers, which is the same root cause as above.
  • Detection: python/a2ui_core/tests/test_specification_drift.py compares the properties each version's BasicCatalog accepts against that version's published common_types.json. The divergence is recorded there in KNOWN_PROPERTY_GAPS under v0_9 and v0_9_1. Fixing this issue means deleting those two entries; the test fails if a recorded gap closes.
  • Not verified: whether the TypeScript web_core v0.9 path has the same gap.

Environment Details

  • SDK/Package Name & Version: a2ui-core (Python), main
  • Protocol Version: v0.9 and v0.9.1