007af498b8
CI / benchmark-publish (pull_request) Has been skipped
CI / build (pull_request) Successful in 17s
CI / helm (pull_request) Successful in 33s
CI / lint (pull_request) Successful in 3m42s
CI / security (pull_request) Successful in 4m8s
CI / quality (pull_request) Successful in 4m9s
CI / typecheck (pull_request) Successful in 4m20s
CI / integration_tests (pull_request) Successful in 7m2s
CI / unit_tests (pull_request) Successful in 7m55s
CI / docker (pull_request) Successful in 1m19s
CI / coverage (pull_request) Successful in 8m46s
CI / e2e_tests (pull_request) Successful in 16m1s
CI / status-check (pull_request) Successful in 1s
CI / build (push) Successful in 17s
CI / helm (push) Successful in 22s
CI / quality (push) Successful in 31s
CI / lint (push) Successful in 3m28s
CI / typecheck (push) Successful in 3m54s
CI / benchmark-regression (push) Has been skipped
CI / security (push) Successful in 4m5s
CI / integration_tests (push) Successful in 6m13s
CI / unit_tests (push) Successful in 6m28s
CI / docker (push) Successful in 1m34s
CI / coverage (push) Successful in 12m5s
CI / e2e_tests (push) Successful in 18m47s
CI / status-check (push) Successful in 1s
CI / benchmark-publish (push) Successful in 28m0s
CI / benchmark-regression (pull_request) Successful in 59m48s
Renamed all 11 task-type confidence threshold fields in AutomationProfile from phase-transition semantics to spec-defined task-type semantics. Updated all 8 built-in profiles, CLI formatting, YAML schema, services, and all Behave/Robot tests referencing the old field names. Post-review fixes: - Fixed 24 stale old field names in M6 fixture files (automation_profiles.json, autonomy_guardrails.json) - Added model_validator(mode='before') to detect legacy field names and raise actionable ValueError with rename mapping - Added semantic bridge comments in PlanLifecycleService mapping task-type thresholds to phase-transition gates - Added threshold_field to structured log messages for observability - Restored categorised CLI automation-profile show output to match spec (Phase Transitions / Decision Automation / Self-Repair / Execution Controls) instead of flat list - Added missing access_network field to spec show output examples (Rich, Plain, JSON, YAML variants) - Aligned ADR-017 profile fields table to all 11 fields with descriptions matching spec Automatable Tasks table - Aligned automation_profiles.md threshold descriptions with spec - Added spec section references in phase_reversion.md, error_recovery.md, and plan_execute.md for field naming context - Extended repository roundtrip test to assert all 11 threshold fields - Fixed benchmark _make_profile() passing safety fields as top-level kwargs instead of via SafetyProfile sub-model (incompatible with extra="forbid") - Aligned CLI JSON/YAML output structure for automation-profile show with the specification grouped format (phase_transitions, decision_automation, self_repair, execution_controls) - Moved safety boolean fields into the Execution Controls section of Rich output per spec examples - Reverted auto profile description to "Fully automatic except apply" per specification (line 16703, line 28406) - Improved bridge comments in test steps with semantic context for threshold-to-gate mappings ISSUES CLOSED: #902
529 lines
19 KiB
Python
529 lines
19 KiB
Python
"""Step definitions for Automation Profile domain model tests."""
|
|
|
|
from typing import Any
|
|
|
|
from behave import then, when
|
|
from behave.runner import Context
|
|
from pydantic import ValidationError
|
|
|
|
from cleveragents.domain.models.core.automation_profile import (
|
|
BUILTIN_PROFILES,
|
|
AutomationProfile,
|
|
get_builtin_profile,
|
|
)
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Helpers
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
def _make_profile(**overrides: Any) -> AutomationProfile:
|
|
"""Create a profile with sensible defaults."""
|
|
defaults: dict[str, Any] = {
|
|
"name": "test/default",
|
|
}
|
|
defaults.update(overrides)
|
|
return AutomationProfile(**defaults)
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Profile creation with specific thresholds
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
@when("I create a profile with decompose_task {value:g}")
|
|
def step_create_profile_decompose_task(context: Context, value: float) -> None:
|
|
"""Create a profile with a specific decompose_task."""
|
|
context.profile_model = _make_profile(decompose_task=value)
|
|
context.profile_error = None
|
|
|
|
|
|
@when("I try to create a profile with decompose_task {value:g}")
|
|
def step_try_create_profile_decompose_task(context: Context, value: float) -> None:
|
|
"""Try creating a profile with invalid decompose_task."""
|
|
context.profile_error = None
|
|
context.profile_model = None
|
|
try:
|
|
context.profile_model = _make_profile(decompose_task=value)
|
|
except ValidationError as e:
|
|
context.profile_error = e
|
|
|
|
|
|
@when("I try to create a profile with create_tool {value:g}")
|
|
def step_try_create_profile_create_tool(context: Context, value: float) -> None:
|
|
"""Try creating a profile with invalid create_tool."""
|
|
context.profile_error = None
|
|
context.profile_model = None
|
|
try:
|
|
context.profile_model = _make_profile(create_tool=value)
|
|
except ValidationError as e:
|
|
context.profile_error = e
|
|
|
|
|
|
@when("I try to create a profile with select_tool {value:g}")
|
|
def step_try_create_profile_select_tool(context: Context, value: float) -> None:
|
|
"""Try creating with invalid select_tool."""
|
|
context.profile_error = None
|
|
context.profile_model = None
|
|
try:
|
|
context.profile_model = _make_profile(select_tool=value)
|
|
except ValidationError as e:
|
|
context.profile_error = e
|
|
|
|
|
|
@when("I try to create a profile with edit_code {value:g}")
|
|
def step_try_create_profile_auto_dec_strat(context: Context, value: float) -> None:
|
|
"""Try creating with invalid edit_code."""
|
|
context.profile_error = None
|
|
context.profile_model = None
|
|
try:
|
|
context.profile_model = _make_profile(edit_code=value)
|
|
except ValidationError as e:
|
|
context.profile_error = e
|
|
|
|
|
|
@when("I try to create a profile with execute_command {value:g}")
|
|
def step_try_create_profile_auto_dec_exec(context: Context, value: float) -> None:
|
|
"""Try creating with invalid execute_command."""
|
|
context.profile_error = None
|
|
context.profile_model = None
|
|
try:
|
|
context.profile_model = _make_profile(execute_command=value)
|
|
except ValidationError as e:
|
|
context.profile_error = e
|
|
|
|
|
|
@when("I try to create a profile with create_file {value:g}")
|
|
def step_try_create_profile_auto_val_fix(context: Context, value: float) -> None:
|
|
"""Try creating with invalid create_file."""
|
|
context.profile_error = None
|
|
context.profile_model = None
|
|
try:
|
|
context.profile_model = _make_profile(create_file=value)
|
|
except ValidationError as e:
|
|
context.profile_error = e
|
|
|
|
|
|
@when("I try to create a profile with delete_content {value:g}")
|
|
def step_try_create_profile_auto_strat_rev(context: Context, value: float) -> None:
|
|
"""Try creating with invalid delete_content."""
|
|
context.profile_error = None
|
|
context.profile_model = None
|
|
try:
|
|
context.profile_model = _make_profile(delete_content=value)
|
|
except ValidationError as e:
|
|
context.profile_error = e
|
|
|
|
|
|
@when("I try to create a profile with access_network {value:g}")
|
|
def step_try_create_profile_auto_rev_apply(context: Context, value: float) -> None:
|
|
"""Try creating with invalid access_network."""
|
|
context.profile_error = None
|
|
context.profile_model = None
|
|
try:
|
|
context.profile_model = _make_profile(access_network=value)
|
|
except ValidationError as e:
|
|
context.profile_error = e
|
|
|
|
|
|
@when("I try to create a profile with install_dependency {value:g}")
|
|
def step_try_create_profile_auto_child(context: Context, value: float) -> None:
|
|
"""Try creating with invalid install_dependency."""
|
|
context.profile_error = None
|
|
context.profile_model = None
|
|
try:
|
|
context.profile_model = _make_profile(install_dependency=value)
|
|
except ValidationError as e:
|
|
context.profile_error = e
|
|
|
|
|
|
@when("I try to create a profile with modify_config {value:g}")
|
|
def step_try_create_profile_auto_retry(context: Context, value: float) -> None:
|
|
"""Try creating with invalid modify_config."""
|
|
context.profile_error = None
|
|
context.profile_model = None
|
|
try:
|
|
context.profile_model = _make_profile(modify_config=value)
|
|
except ValidationError as e:
|
|
context.profile_error = e
|
|
|
|
|
|
@when("I try to create a profile with approve_plan {value:g}")
|
|
def step_try_create_profile_auto_ckpt(context: Context, value: float) -> None:
|
|
"""Try creating with invalid approve_plan."""
|
|
context.profile_error = None
|
|
context.profile_model = None
|
|
try:
|
|
context.profile_model = _make_profile(approve_plan=value)
|
|
except ValidationError as e:
|
|
context.profile_error = e
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Profile assertions
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
@then("the profile model should be created")
|
|
def step_check_profile_created(context: Context) -> None:
|
|
"""Verify the profile was created."""
|
|
assert context.profile_model is not None, "Profile should be created"
|
|
|
|
|
|
@then("the profile decompose_task should be {expected:g}")
|
|
def step_check_decompose_task(context: Context, expected: float) -> None:
|
|
"""Check decompose_task value."""
|
|
actual = context.profile_model.decompose_task
|
|
assert actual == expected, f"Expected decompose_task {expected}, got {actual}"
|
|
|
|
|
|
@then("the profile create_tool should be {expected:g}")
|
|
def step_check_create_tool(context: Context, expected: float) -> None:
|
|
"""Check create_tool value."""
|
|
actual = context.profile_model.create_tool
|
|
assert actual == expected, f"Expected create_tool {expected}, got {actual}"
|
|
|
|
|
|
@then("the profile select_tool should be {expected:g}")
|
|
def step_check_select_tool(context: Context, expected: float) -> None:
|
|
"""Check select_tool value."""
|
|
actual = context.profile_model.select_tool
|
|
assert actual == expected, f"Expected select_tool {expected}, got {actual}"
|
|
|
|
|
|
@then("the profile edit_code should be {expected:g}")
|
|
def step_check_auto_dec_strat(context: Context, expected: float) -> None:
|
|
"""Check edit_code value."""
|
|
actual = context.profile_model.edit_code
|
|
assert actual == expected, f"Expected {expected}, got {actual}"
|
|
|
|
|
|
@then("the profile execute_command should be {expected:g}")
|
|
def step_check_auto_dec_exec(context: Context, expected: float) -> None:
|
|
"""Check execute_command value."""
|
|
actual = context.profile_model.execute_command
|
|
assert actual == expected, f"Expected {expected}, got {actual}"
|
|
|
|
|
|
@then("the profile create_file should be {expected:g}")
|
|
def step_check_auto_val_fix(context: Context, expected: float) -> None:
|
|
"""Check create_file value."""
|
|
actual = context.profile_model.create_file
|
|
assert actual == expected, f"Expected {expected}, got {actual}"
|
|
|
|
|
|
@then("the profile delete_content should be {expected:g}")
|
|
def step_check_auto_strat_rev(context: Context, expected: float) -> None:
|
|
"""Check delete_content value."""
|
|
actual = context.profile_model.delete_content
|
|
assert actual == expected, f"Expected {expected}, got {actual}"
|
|
|
|
|
|
@then("the profile access_network should be {expected:g}")
|
|
def step_check_auto_rev_apply(context: Context, expected: float) -> None:
|
|
"""Check access_network value."""
|
|
actual = context.profile_model.access_network
|
|
assert actual == expected, f"Expected {expected}, got {actual}"
|
|
|
|
|
|
@then("the profile install_dependency should be {expected:g}")
|
|
def step_check_auto_child(context: Context, expected: float) -> None:
|
|
"""Check install_dependency value."""
|
|
actual = context.profile_model.install_dependency
|
|
assert actual == expected, f"Expected {expected}, got {actual}"
|
|
|
|
|
|
@then("the profile modify_config should be {expected:g}")
|
|
def step_check_auto_retry(context: Context, expected: float) -> None:
|
|
"""Check modify_config value."""
|
|
actual = context.profile_model.modify_config
|
|
assert actual == expected, f"Expected {expected}, got {actual}"
|
|
|
|
|
|
@then("the profile approve_plan should be {expected:g}")
|
|
def step_check_auto_ckpt(context: Context, expected: float) -> None:
|
|
"""Check approve_plan value."""
|
|
actual = context.profile_model.approve_plan
|
|
assert actual == expected, f"Expected {expected}, got {actual}"
|
|
|
|
|
|
@then("the profile safety require_sandbox should be {expected}")
|
|
def step_check_safety_require_sandbox(context: Context, expected: str) -> None:
|
|
"""Check safety.require_sandbox value via composed SafetyProfile."""
|
|
exp_bool = expected.lower() == "true"
|
|
actual = context.profile_model.safety.require_sandbox
|
|
assert actual is exp_bool, (
|
|
f"Expected safety.require_sandbox {exp_bool}, got {actual}"
|
|
)
|
|
|
|
|
|
@then("the profile safety require_checkpoints should be {expected}")
|
|
def step_check_safety_require_checkpoints(context: Context, expected: str) -> None:
|
|
"""Check safety.require_checkpoints value via composed SafetyProfile."""
|
|
exp_bool = expected.lower() == "true"
|
|
actual = context.profile_model.safety.require_checkpoints
|
|
assert actual is exp_bool, (
|
|
f"Expected safety.require_checkpoints {exp_bool}, got {actual}"
|
|
)
|
|
|
|
|
|
@then("the profile safety allow_unsafe_tools should be {expected}")
|
|
def step_check_safety_allow_unsafe(context: Context, expected: str) -> None:
|
|
"""Check safety.allow_unsafe_tools value via composed SafetyProfile."""
|
|
exp_bool = expected.lower() == "true"
|
|
actual = context.profile_model.safety.allow_unsafe_tools
|
|
assert actual is exp_bool, (
|
|
f"Expected safety.allow_unsafe_tools {exp_bool}, got {actual}"
|
|
)
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Validation errors
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
@then("a profile validation error should be raised")
|
|
def step_check_profile_validation_error(
|
|
context: Context,
|
|
) -> None:
|
|
"""Verify that a validation error was raised."""
|
|
assert context.profile_error is not None, "Expected a validation error to be raised"
|
|
|
|
|
|
@then('the profile error should mention "{text}"')
|
|
def step_check_profile_error_message(context: Context, text: str) -> None:
|
|
"""Check the error message contains expected text."""
|
|
error_str = str(context.profile_error)
|
|
assert text in error_str, f"Expected error to mention '{text}', got: {error_str}"
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Built-in profiles
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
@when('I load the built-in profile "{name}"')
|
|
def step_load_builtin_profile(context: Context, name: str) -> None:
|
|
"""Load a built-in profile by name."""
|
|
context.profile_model = get_builtin_profile(name)
|
|
context.profile_error = None
|
|
|
|
|
|
@then("there should be {count:d} built-in profiles")
|
|
def step_check_builtin_count(context: Context, count: int) -> None:
|
|
"""Check the number of built-in profiles."""
|
|
actual = len(BUILTIN_PROFILES)
|
|
assert actual == count, f"Expected {count} built-in profiles, got {actual}"
|
|
|
|
|
|
@then('built-in profile "{name}" should exist')
|
|
def step_check_builtin_exists(context: Context, name: str) -> None:
|
|
"""Check a built-in profile exists."""
|
|
assert name in BUILTIN_PROFILES, f"Expected built-in profile '{name}' to exist"
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Custom profile from config
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
@when('I load a profile from config with name "{name}" and select_tool {value:g}')
|
|
def step_load_profile_from_config(context: Context, name: str, value: float) -> None:
|
|
"""Load a profile from config dict."""
|
|
config = {
|
|
"name": name,
|
|
"description": "Custom profile",
|
|
"select_tool": value,
|
|
}
|
|
context.profile_model = AutomationProfile.from_config(config)
|
|
context.profile_error = None
|
|
|
|
|
|
@when("I try to load a profile from config missing name")
|
|
def step_try_load_profile_config_missing_name(
|
|
context: Context,
|
|
) -> None:
|
|
"""Try loading profile config without name."""
|
|
context.profile_config_error = None
|
|
try:
|
|
AutomationProfile.from_config({"description": "No name"})
|
|
except ValueError as e:
|
|
context.profile_config_error = e
|
|
|
|
|
|
@then('a profile config error should be raised with "{field}"')
|
|
def step_check_profile_config_error(context: Context, field: str) -> None:
|
|
"""Check config error mentions field."""
|
|
assert context.profile_config_error is not None, (
|
|
f"Expected ValueError for missing '{field}'"
|
|
)
|
|
assert field in str(context.profile_config_error), (
|
|
f"Expected error to mention '{field}', got: {context.profile_config_error}"
|
|
)
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Name validation
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
@when('I create a profile with name "{name}"')
|
|
def step_create_profile_by_name(context: Context, name: str) -> None:
|
|
"""Create a profile with specific name."""
|
|
context.profile_model = AutomationProfile(name=name)
|
|
context.profile_error = None
|
|
|
|
|
|
@when('I try to create a profile with invalid name "{name}"')
|
|
def step_try_create_profile_invalid_name(context: Context, name: str) -> None:
|
|
"""Attempt to create a profile with invalid name."""
|
|
context.profile_error = None
|
|
context.profile_model = None
|
|
try:
|
|
context.profile_model = AutomationProfile(name=name)
|
|
except ValidationError as e:
|
|
context.profile_error = e
|
|
|
|
|
|
@when("I try to create a profile with empty name")
|
|
def step_try_create_profile_empty_name(
|
|
context: Context,
|
|
) -> None:
|
|
"""Attempt to create a profile with empty name."""
|
|
context.profile_error = None
|
|
context.profile_model = None
|
|
try:
|
|
context.profile_model = AutomationProfile(name="")
|
|
except ValidationError as e:
|
|
context.profile_error = e
|
|
|
|
|
|
@then('the profile name should be "{expected}"')
|
|
def step_check_profile_name(context: Context, expected: str) -> None:
|
|
"""Check profile name."""
|
|
assert context.profile_model.name == expected, (
|
|
f"Expected name '{expected}', got '{context.profile_model.name}'"
|
|
)
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# get_builtin_profile helper
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
@when('I call get_builtin_profile with "{name}"')
|
|
def step_call_get_builtin(context: Context, name: str) -> None:
|
|
"""Call get_builtin_profile."""
|
|
context.profile_model = get_builtin_profile(name)
|
|
context.profile_error = None
|
|
|
|
|
|
@when('I try to call get_builtin_profile with "{name}"')
|
|
def step_try_call_get_builtin(context: Context, name: str) -> None:
|
|
"""Try calling get_builtin_profile with bad name."""
|
|
context.profile_key_error = None
|
|
try:
|
|
get_builtin_profile(name)
|
|
except KeyError as e:
|
|
context.profile_key_error = e
|
|
|
|
|
|
@then("a profile key error should be raised")
|
|
def step_check_profile_key_error(
|
|
context: Context,
|
|
) -> None:
|
|
"""Verify KeyError was raised."""
|
|
assert context.profile_key_error is not None, "Expected a KeyError to be raised"
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Schema version
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
@then('the profile schema_version should be "{expected}"')
|
|
def step_check_schema_version(context: Context, expected: str) -> None:
|
|
"""Check profile schema_version."""
|
|
actual = context.profile_model.schema_version
|
|
assert actual == expected, f"Expected schema_version '{expected}', got '{actual}'"
|
|
|
|
|
|
@when('I create a profile with schema_version "{version}"')
|
|
def step_create_profile_with_version(context: Context, version: str) -> None:
|
|
"""Create a profile with custom schema_version."""
|
|
context.profile_model = AutomationProfile(
|
|
name="test-profile", schema_version=version
|
|
)
|
|
context.profile_error = None
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Description
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
@then('the profile description should be "{expected}"')
|
|
def step_check_profile_description(context: Context, expected: str) -> None:
|
|
"""Check profile description."""
|
|
actual = context.profile_model.description
|
|
assert actual == expected, f"Expected description '{expected}', got '{actual}'"
|
|
|
|
|
|
@then("the profile description should be empty")
|
|
def step_check_profile_description_empty(
|
|
context: Context,
|
|
) -> None:
|
|
"""Check profile description is empty."""
|
|
actual = context.profile_model.description
|
|
assert actual == "", f"Expected empty description, got '{actual}'"
|
|
|
|
|
|
@when('I create a profile with description "{desc}"')
|
|
def step_create_profile_with_desc(context: Context, desc: str) -> None:
|
|
"""Create a profile with custom description."""
|
|
context.profile_model = AutomationProfile(name="desc-test", description=desc)
|
|
context.profile_error = None
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# validate_assignment
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
@when("I try to assign decompose_task {value:g} on the profile")
|
|
def step_try_assign_threshold(context: Context, value: float) -> None:
|
|
"""Try to assign an invalid threshold on existing profile."""
|
|
context.profile_error = None
|
|
try:
|
|
context.profile_model.decompose_task = value
|
|
except ValidationError as e:
|
|
context.profile_error = e
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# model_dump
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
@when("I create a profile and dump it with select_tool {value:g}")
|
|
def step_create_profile_with_select_tool(context: Context, value: float) -> None:
|
|
"""Create a profile and store model dump."""
|
|
context.profile_model = AutomationProfile(name="dump-test", select_tool=value)
|
|
context.profile_dump = context.profile_model.model_dump()
|
|
context.profile_error = None
|
|
|
|
|
|
@then('the profile model dump should have key "{key}"')
|
|
def step_check_dump_key(context: Context, key: str) -> None:
|
|
"""Check profile model dump has key."""
|
|
assert key in context.profile_dump, (
|
|
f"Expected key '{key}' in model dump, keys: {list(context.profile_dump)}"
|
|
)
|
|
|
|
|
|
@then("the profile model dump select_tool should be {expected:g}")
|
|
def step_check_dump_select_tool(context: Context, expected: float) -> None:
|
|
"""Check select_tool in model dump."""
|
|
actual = context.profile_dump["select_tool"]
|
|
assert actual == expected, f"Expected select_tool {expected}, got {actual}"
|