fix(action): add default value type validation to ActionArgumentSchema
CI / quality (pull_request) Failing after 1s
CI / security (pull_request) Failing after 1s
CI / lint (pull_request) Failing after 1s
CI / typecheck (pull_request) Failing after 1s
CI / coverage (pull_request) Has been skipped
CI / integration_tests (pull_request) Failing after 1s
CI / e2e_tests (pull_request) Failing after 1s
CI / unit_tests (pull_request) Failing after 1s
CI / docker (pull_request) Has been skipped
CI / build (pull_request) Failing after 1s
CI / benchmark-publish (pull_request) Has been skipped
CI / benchmark-regression (pull_request) Failing after 0s
CI / helm (pull_request) Failing after 1s
CI / push-validation (pull_request) Failing after 0s
CI / status-check (pull_request) Failing after 0s

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
and CONTRIBUTORS entries. Moved model_config before validators per Pydantic
convention.

ISSUES CLOSED: #9105
This commit is contained in:
2026-04-23 19:40:03 +00:00
parent 7523a50db8
commit 458d1dfd0e
5 changed files with 309 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)
+1
View File
@@ -20,3 +20,4 @@ Below are some of the specific details of various contributions.
* This project was made possible thanks to considerable donation of time, money, and resources by CleverThis, Inc.
* HAL 9000 has contributed automated bug fixes, CLI output formatting improvements, and ongoing maintenance as part of the CleverAgents automation system.
* HAL 9000 has contributed the file edit encoding parameter fix (PR #8258 / issue #7559).
* HAL 9000 has contributed the action argument default value type validation fix (PR #9178 / issue #9105): added `@model_validator` to `ActionArgumentSchema` that validates default values match their declared types, with correct `bool`/`int` disambiguation and `float` widening from `int`.
@@ -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
+77
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,82 @@ 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
# ────────────────────────────────────────────────────────────