Description
Comparing two property schemas, or checking whether one is castable to the other, silently mutates a schema that combines a type list with anyOf: the entries of type are appended to the schema's own anyOf list. The schema keeps growing on every comparison.
Reproduction
from pyagentspec.property import json_schemas_have_same_type
schema_a = {"type": ["integer", "null"], "anyOf": [{"type": "string"}]}
schema_b = {"anyOf": [{"type": "string"}, {"type": "integer"}, {"type": "null"}]}
json_schemas_have_same_type(schema_a, schema_b)
print(schema_a)
# {'type': ['integer', 'null'], 'anyOf': [{'type': 'string'}, {'type': 'integer'}, {'type': 'null'}]}
# ^ two entries were appended to the caller's schema
json_schemas_have_same_type(schema_a, schema_b)
print(len(schema_a["anyOf"])) # 5
The same happens through json_schema_is_castable_to and value_is_of_compatible_type, which are called during Flow / DataFlowEdge validation, so a Property.json_schema can change as a side effect of building a flow.
Expected behavior
Type comparison is read-only; the input schemas are left untouched.
Root cause
_normalize_json_schema_union_types in pyagentspec/src/pyagentspec/property.py uses all_types = schema.get("anyOf", []) as the accumulator of normalized types and then appends to it, so when the schema already has an anyOf list the appends go into the caller's list.
Environment
- pyagentspec
main (26.4.0.dev0), Python 3.12
Description
Comparing two property schemas, or checking whether one is castable to the other, silently mutates a schema that combines a
typelist withanyOf: the entries oftypeare appended to the schema's ownanyOflist. The schema keeps growing on every comparison.Reproduction
The same happens through
json_schema_is_castable_toandvalue_is_of_compatible_type, which are called duringFlow/DataFlowEdgevalidation, so aProperty.json_schemacan change as a side effect of building a flow.Expected behavior
Type comparison is read-only; the input schemas are left untouched.
Root cause
_normalize_json_schema_union_typesinpyagentspec/src/pyagentspec/property.pyusesall_types = schema.get("anyOf", [])as the accumulator of normalized types and then appends to it, so when the schema already has ananyOflist the appends go into the caller's list.Environment
main(26.4.0.dev0), Python 3.12