Files
temp/features/steps/plan_lifecycle_transitions_r2_steps.py
CoreRasurae 007af498b8 refactor(autonomy): rename automation profile task flags to spec names
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
2026-03-30 13:18:07 +01:00

209 lines
7.8 KiB
Python

"""Step definitions for plan_lifecycle_transitions_r2.feature.
Targets lifecycle transition branches in ``plan_lifecycle_service.py``:
* Non-reusable ``use_action`` archiving (line 576-577).
* ``execute_plan`` persisted mode (line 216 True via ``_commit_plan``).
* ``apply_plan`` persisted mode (line 216 True via ``_commit_plan``).
* ``cancel_plan`` persisted mode (line 216 True via ``_commit_plan``).
* ``pause_plan`` / ``resume_plan`` persisted mode.
All step text uses the ``r2plc-`` prefix to avoid collisions with
existing step definitions.
Shared steps (Background, common Given/Then) live in
``plan_lifecycle_error_r2_steps.py`` and are discovered globally by
Behave from the ``steps/`` directory.
"""
from __future__ import annotations
from typing import Any
from behave import given, then, when
from behave.runner import Context
from cleveragents.domain.models.core.action import ActionState
from cleveragents.domain.models.core.plan import (
PlanPhase,
ProjectLink,
)
# -------------------------------------------------------------------
# Helpers
# -------------------------------------------------------------------
def _create_action(context: Context, name: str, **kwargs: Any) -> Any:
"""Create an action through the service with sensible defaults."""
defaults: dict[str, Any] = {
"name": name,
"description": f"R2 test action {name}",
"definition_of_done": "Tests pass",
"strategy_actor": "openai/gpt-4",
"execution_actor": "openai/gpt-4",
}
defaults.update(kwargs)
return context.r2_service.create_action(**defaults)
# ===================================================================
# use_action with non-reusable action (line 576-577)
# ===================================================================
@given('r2plc-a non-reusable action "{name}" exists')
def step_r2_non_reusable_action(context: Context, name: str) -> None:
context.r2_ctx.reset_mock()
_create_action(context, name, reusable=False)
context.r2_ctx.reset_mock()
@when("r2plc-I use the non-reusable action to create a plan")
def step_r2_use_non_reusable(context: Context) -> None:
context.r2_plan = context.r2_service.use_action(
action_name="local/r2-oneshot",
project_links=[ProjectLink(project_name="proj-r2-oneshot")],
)
@then('r2plc-the action "{name}" should be archived')
def step_r2_check_non_reusable_archived(context: Context, name: str) -> None:
action = context.r2_service.get_action(name)
assert action.state == ActionState.ARCHIVED, (
f"Expected action '{name}' to be ARCHIVED, got {action.state}"
)
@then("r2plc-a plan should have been created from the action")
def step_r2_check_plan_created(context: Context) -> None:
assert context.r2_plan is not None, "Expected a plan to be created"
assert context.r2_plan.phase == PlanPhase.STRATEGIZE
# ===================================================================
# execute_plan persisted mode (line 216 True via _commit_plan)
# ===================================================================
@given("r2plc-a plan in STRATEGIZE COMPLETE state")
def step_r2_plan_strategize_complete(context: Context) -> None:
"""Create a plan and advance to STRATEGIZE/COMPLETE."""
context.r2_ctx.reset_mock()
action = _create_action(context, f"local/r2-exec-{id(context)}")
plan = context.r2_service.use_action(
action_name=str(action.namespaced_name),
project_links=[ProjectLink(project_name="proj-r2-exec")],
)
context.r2_service.start_strategize(plan.identity.plan_id)
context.r2_service.complete_strategize(plan.identity.plan_id)
# complete_strategize calls auto_progress which may call execute_plan
# Re-fetch the plan to see its actual state
context.r2_plan = context.r2_service.get_plan(plan.identity.plan_id)
# If auto_progress already executed, the plan may be in EXECUTE.
# For testing execute_plan explicitly, we need the plan in STRATEGIZE/COMPLETE.
# The manual profile (default) has create_tool=1.0 so auto_progress won't fire.
context.r2_ctx.reset_mock()
@when("r2plc-I call execute_plan in persisted mode")
def step_r2_execute_plan_persisted(context: Context) -> None:
context.r2_plan = context.r2_service.execute_plan(context.r2_plan.identity.plan_id)
@then("r2plc-the plan should be in EXECUTE phase")
def step_r2_check_execute_phase(context: Context) -> None:
assert context.r2_plan.phase == PlanPhase.EXECUTE, (
f"Expected EXECUTE, got {context.r2_plan.phase}"
)
# ===================================================================
# apply_plan persisted mode (line 216 True via _commit_plan)
# ===================================================================
@given("r2plc-a plan in EXECUTE COMPLETE state")
def step_r2_plan_execute_complete(context: Context) -> None:
"""Create a plan and advance to EXECUTE/COMPLETE."""
context.r2_ctx.reset_mock()
action = _create_action(context, f"local/r2-apply-{id(context)}")
plan = context.r2_service.use_action(
action_name=str(action.namespaced_name),
project_links=[ProjectLink(project_name="proj-r2-apply")],
)
pid = plan.identity.plan_id
context.r2_service.start_strategize(pid)
context.r2_service.complete_strategize(pid)
context.r2_service.execute_plan(pid)
context.r2_service.start_execute(pid)
context.r2_service.complete_execute(pid)
context.r2_plan = context.r2_service.get_plan(pid)
context.r2_ctx.reset_mock()
@when("r2plc-I call apply_plan in persisted mode")
def step_r2_apply_plan_persisted(context: Context) -> None:
context.r2_plan = context.r2_service.apply_plan(context.r2_plan.identity.plan_id)
@then("r2plc-the plan should be in APPLY phase")
def step_r2_check_apply_phase(context: Context) -> None:
assert context.r2_plan.phase == PlanPhase.APPLY, (
f"Expected APPLY, got {context.r2_plan.phase}"
)
# ===================================================================
# cancel_plan persisted mode (line 216 True via _commit_plan)
# ===================================================================
@when('r2plc-I cancel the plan with reason "{reason}"')
def step_r2_cancel_plan(context: Context, reason: str) -> None:
context.r2_ctx.reset_mock()
context.r2_plan = context.r2_service.cancel_plan(
context.r2_plan.identity.plan_id, reason=reason
)
# ===================================================================
# pause_plan persisted mode
# ===================================================================
@when("r2plc-I pause the plan")
def step_r2_pause_plan(context: Context) -> None:
context.r2_ctx.reset_mock()
context.r2_plan = context.r2_service.pause_plan(context.r2_plan.identity.plan_id)
@then("r2plc-the plan automation profile should be manual")
def step_r2_check_manual_profile(context: Context) -> None:
assert context.r2_plan.automation_profile is not None
assert context.r2_plan.automation_profile.profile_name == "manual", (
f"Expected 'manual', got '{context.r2_plan.automation_profile.profile_name}'"
)
# ===================================================================
# resume_plan persisted mode
# ===================================================================
@when('r2plc-I resume the plan with profile "{profile}"')
def step_r2_resume_plan(context: Context, profile: str) -> None:
context.r2_ctx.reset_mock()
context.r2_plan = context.r2_service.resume_plan(
context.r2_plan.identity.plan_id,
automation_profile=profile,
)
@then('r2plc-the plan automation profile should be "{profile}"')
def step_r2_check_profile(context: Context, profile: str) -> None:
assert context.r2_plan.automation_profile is not None
assert context.r2_plan.automation_profile.profile_name == profile, (
f"Expected '{profile}', got '{context.r2_plan.automation_profile.profile_name}'"
)