fix(action): add default value type validation to ActionArgumentSchema
CI / lint (pull_request) Failing after 1m11s
CI / quality (pull_request) Successful in 1m29s
CI / helm (pull_request) Successful in 26s
CI / build (pull_request) Successful in 1m5s
CI / typecheck (pull_request) Successful in 1m49s
CI / security (pull_request) Successful in 1m54s
CI / push-validation (pull_request) Successful in 26s
CI / coverage (pull_request) Has been skipped
CI / benchmark-publish (pull_request) Has been skipped
CI / integration_tests (pull_request) Successful in 3m59s
CI / unit_tests (pull_request) Successful in 4m34s
CI / docker (pull_request) Has been skipped
CI / e2e_tests (pull_request) Failing after 4m19s
CI / status-check (pull_request) Failing after 3s
CI / benchmark-regression (pull_request) Successful in 1h4m18s

Added @model_validator(mode="after") to ActionArgumentSchema that validates default value type against declared type field. Supports all argument types: string, integer, float, boolean, list. Handles edge cases: None defaults (always valid), bool vs int disambiguation (bool checked before int since bool is a subclass of int), float widening from int.

Added @a2a @domain @action tags to the BDD feature file. Reordered step definitions so specific patterns match before generic ones. Added CHANGELOG entry. Moved model_config before validators per Pydantic convention.

Rebased on latest master to resolve merge conflicts and fix CI failures caused by the branch being 171 commits behind master.

ISSUES CLOSED: #9105
This commit is contained in:
2026-04-23 04:16:24 +00:00
parent 7523a50db8
commit 01cfd88076
4 changed files with 304 additions and 4 deletions
+7
View File
@@ -7,6 +7,13 @@ The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
### Fixed
- **Action Argument Default Type Validation** (#9105): `ActionArgumentSchema` now
validates that default values match their declared types at parse time via a
`@model_validator`. Supports all argument types (string, integer, float, boolean,
list) with correct `bool`/`int` disambiguation and `float` widening from `int`.
Invalid configurations that previously passed silently now raise clear, actionable
`ValueError` messages indicating the argument name, declared type, and actual type.
- **UKO Runtime Layer 2 (Paradigm) Indexing** (#9351): Added missing `rdf:type
uko-oo:Class` triple emission in `PythonAnalyzer._extract_class()` so that
Python class definitions are now correctly classified at layer 2 (paradigm/OO)
@@ -0,0 +1,163 @@
@a2a @domain @action
Feature: Action Schema Default Value Type Validation
Validates that ActionArgumentSchema enforces type matching between
the declared 'type' field and the 'default' value.
Background:
Given an action YAML string with only required fields
# ============================================================
# String Type Tests
# ============================================================
Scenario: String argument with string default passes validation
Given an action YAML string with an argument of type "string" and default "hello"
When I validate the action schema
Then the action schema validation should succeed
Scenario: String argument with integer default fails validation
Given an action YAML string with an argument of type "string" and default 42
When I validate the action schema expecting failure
Then the action schema validation should fail
And the action schema error should mention "has type 'string' but default value"
Scenario: String argument with boolean default fails validation
Given an action YAML string with an argument of type "string" and default true
When I validate the action schema expecting failure
Then the action schema validation should fail
And the action schema error should mention "has type 'string' but default value"
# ============================================================
# Integer Type Tests
# ============================================================
Scenario: Integer argument with integer default passes validation
Given an action YAML string with an argument of type "integer" and default 42
When I validate the action schema
Then the action schema validation should succeed
Scenario: Integer argument with string default fails validation
Given an action YAML string with an argument of type "integer" and default "42"
When I validate the action schema expecting failure
Then the action schema validation should fail
And the action schema error should mention "has type 'integer' but default value"
Scenario: Integer argument with boolean default fails validation
Given an action YAML string with an argument of type "integer" and default true
When I validate the action schema expecting failure
Then the action schema validation should fail
And the action schema error should mention "has type 'integer' but default value"
And the action schema error should mention "is bool, not int"
Scenario: Integer argument with float default fails validation
Given an action YAML string with an argument of type "integer" and default 3.14
When I validate the action schema expecting failure
Then the action schema validation should fail
And the action schema error should mention "has type 'integer' but default value"
# ============================================================
# Float Type Tests
# ============================================================
Scenario: Float argument with float default passes validation
Given an action YAML string with an argument of type "float" and default 3.14
When I validate the action schema
Then the action schema validation should succeed
Scenario: Float argument with integer default passes validation
Given an action YAML string with an argument of type "float" and default 42
When I validate the action schema
Then the action schema validation should succeed
Scenario: Float argument with string default fails validation
Given an action YAML string with an argument of type "float" and default "3.14"
When I validate the action schema expecting failure
Then the action schema validation should fail
And the action schema error should mention "has type 'float' but default value"
Scenario: Float argument with boolean default fails validation
Given an action YAML string with an argument of type "float" and default true
When I validate the action schema expecting failure
Then the action schema validation should fail
And the action schema error should mention "has type 'float' but default value"
# ============================================================
# Boolean Type Tests
# ============================================================
Scenario: Boolean argument with boolean true default passes validation
Given an action YAML string with an argument of type "boolean" and default true
When I validate the action schema
Then the action schema validation should succeed
Scenario: Boolean argument with boolean false default passes validation
Given an action YAML string with an argument of type "boolean" and default false
When I validate the action schema
Then the action schema validation should succeed
Scenario: Boolean argument with integer default fails validation
Given an action YAML string with an argument of type "boolean" and default 1
When I validate the action schema expecting failure
Then the action schema validation should fail
And the action schema error should mention "has type 'boolean' but default value"
Scenario: Boolean argument with string default fails validation
Given an action YAML string with an argument of type "boolean" and default "true"
When I validate the action schema expecting failure
Then the action schema validation should fail
And the action schema error should mention "has type 'boolean' but default value"
# ============================================================
# List Type Tests
# ============================================================
Scenario: List argument with list default passes validation
Given an action YAML string with an argument of type "list" and default ["item1", "item2"]
When I validate the action schema
Then the action schema validation should succeed
Scenario: List argument with empty list default passes validation
Given an action YAML string with an argument of type "list" and default []
When I validate the action schema
Then the action schema validation should succeed
Scenario: List argument with string default fails validation
Given an action YAML string with an argument of type "list" and default "item1"
When I validate the action schema expecting failure
Then the action schema validation should fail
And the action schema error should mention "has type 'list' but default value"
Scenario: List argument with integer default fails validation
Given an action YAML string with an argument of type "list" and default 42
When I validate the action schema expecting failure
Then the action schema validation should fail
And the action schema error should mention "has type 'list' but default value"
# ============================================================
# None Default Tests (Always Valid)
# ============================================================
Scenario: String argument with None default passes validation
Given an action YAML string with an argument of type "string" and no default
When I validate the action schema
Then the action schema validation should succeed
Scenario: Integer argument with None default passes validation
Given an action YAML string with an argument of type "integer" and no default
When I validate the action schema
Then the action schema validation should succeed
Scenario: Float argument with None default passes validation
Given an action YAML string with an argument of type "float" and no default
When I validate the action schema
Then the action schema validation should succeed
Scenario: Boolean argument with None default passes validation
Given an action YAML string with an argument of type "boolean" and no default
When I validate the action schema
Then the action schema validation should succeed
Scenario: List argument with None default passes validation
Given an action YAML string with an argument of type "list" and no default
When I validate the action schema
Then the action schema validation should succeed
+73
View File
@@ -7,6 +7,7 @@ environment variable interpolation, and error messages.
from __future__ import annotations
import json
from pathlib import Path
from typing import Any
@@ -131,6 +132,78 @@ def step_given_yaml_with_name(context: Context, name: str) -> None:
context.action_yaml_string = _MINIMAL_YAML.replace("local/simple-action", name)
# NOTE: More specific steps with "and default" / "and no default" MUST be
# defined BEFORE the shorter pattern 'with an argument of type "{arg_type}"'
# so that Behave's parse matcher selects the longer match first.
@given('an action YAML string with an argument of type "{arg_type}" and default {default_value}')
def step_given_yaml_with_arg_and_default(context: Context, arg_type: str, default_value: str) -> None:
"""Provide YAML with an argument that has a specific type and default value."""
# Parse the default value from the string representation
# Handle special cases: true/false for booleans, numbers, strings, lists
if default_value.lower() == "true":
parsed_default: str | int | float | bool | list[str] = True
elif default_value.lower() == "false":
parsed_default = False
elif default_value.startswith("[") and default_value.endswith("]"):
# Parse list
parsed_default = json.loads(default_value)
elif default_value.startswith('"') and default_value.endswith('"'):
# String value
parsed_default = default_value[1:-1]
else:
# Try to parse as number
try:
if "." in default_value:
parsed_default = float(default_value)
else:
parsed_default = int(default_value)
except ValueError:
# Treat as string if not a number
parsed_default = default_value
# Format the default value for YAML
if isinstance(parsed_default, bool):
yaml_default = "true" if parsed_default else "false"
elif isinstance(parsed_default, list):
yaml_default = json.dumps(parsed_default)
elif isinstance(parsed_default, str):
yaml_default = f'"{parsed_default}"'
else:
yaml_default = str(parsed_default)
# Build YAML with the argument
context.action_yaml_string = f"""\
name: local/test-default-type
description: Test argument with default value
strategy_actor: openai/gpt-4
execution_actor: openai/gpt-4
definition_of_done: Done
arguments:
- name: test_arg
type: {arg_type}
required: false
default: {yaml_default}
"""
@given('an action YAML string with an argument of type "{arg_type}" and no default')
def step_given_yaml_with_arg_no_default(context: Context, arg_type: str) -> None:
"""Provide YAML with an argument that has no default value."""
context.action_yaml_string = f"""\
name: local/test-no-default
description: Test argument without default value
strategy_actor: openai/gpt-4
execution_actor: openai/gpt-4
definition_of_done: Done
arguments:
- name: test_arg
type: {arg_type}
required: false
"""
@given('an action YAML string with an argument of type "{arg_type}"')
def step_given_yaml_with_bad_arg_type(context: Context, arg_type: str) -> None:
"""Provide YAML with an invalid argument type."""
+61 -4
View File
@@ -119,6 +119,11 @@ class ActionArgumentSchema(BaseModel):
description="Maximum value for numeric arguments.",
)
model_config = ConfigDict(
str_strip_whitespace=True,
extra="forbid",
)
@field_validator("name")
@classmethod
def validate_name(cls, v: str) -> str:
@@ -140,10 +145,62 @@ class ActionArgumentSchema(BaseModel):
raise ValueError(f"Invalid argument type '{v}'. Allowed types: {valid}.")
return v_lower
model_config = ConfigDict(
str_strip_whitespace=True,
extra="forbid",
)
@model_validator(mode="after")
def validate_default_type(self) -> ActionArgumentSchema:
"""Validate that the default value's type matches the declared type field.
Raises:
ValueError: If the default value's type does not match the declared type.
"""
# None default is always valid (means no default provided)
if self.default is None:
return self
arg_type = self.type.lower()
default_value = self.default
# Check type compatibility
if arg_type == "string":
if not isinstance(default_value, str):
raise ValueError(
f"Argument '{self.name}' has type 'string' but default value "
f"'{default_value}' is {type(default_value).__name__}, not str."
)
elif arg_type == "integer":
# Explicitly check for bool first, since bool is a subclass of int
if isinstance(default_value, bool):
raise ValueError(
f"Argument '{self.name}' has type 'integer' but default value "
f"'{default_value}' is bool, not int."
)
if not isinstance(default_value, int):
raise ValueError(
f"Argument '{self.name}' has type 'integer' but default value "
f"'{default_value}' is {type(default_value).__name__}, not int."
)
elif arg_type == "float":
# float or int are acceptable for float type
is_bool = isinstance(default_value, bool)
is_numeric = isinstance(default_value, (float, int))
if not is_numeric or is_bool:
raise ValueError(
f"Argument '{self.name}' has type 'float' but default value "
f"'{default_value}' is {type(default_value).__name__}, "
"not float or int."
)
elif arg_type == "boolean":
if not isinstance(default_value, bool):
raise ValueError(
f"Argument '{self.name}' has type 'boolean' but default value "
f"'{default_value}' is {type(default_value).__name__}, not bool."
)
elif arg_type == "list" and not isinstance(default_value, list):
raise ValueError(
f"Argument '{self.name}' has type 'list' but default value "
f"'{default_value}' is {type(default_value).__name__}, not list."
)
return self
# ────────────────────────────────────────────────────────────