b41f536da6
CI / helm (push) Successful in 47s
CI / build (push) Successful in 1m11s
CI / push-validation (push) Successful in 40s
CI / benchmark-publish (push) Has started running
CI / lint (push) Successful in 1m26s
CI / typecheck (push) Successful in 1m55s
CI / e2e_tests (push) Successful in 1m12s
CI / quality (push) Successful in 2m6s
CI / security (push) Successful in 2m9s
CI / benchmark-regression (push) Failing after 1m28s
CI / integration_tests (push) Successful in 3m48s
CI / unit_tests (push) Successful in 7m31s
CI / coverage (push) Has started running
CI / docker (push) Successful in 1m35s
CI / helm (pull_request) Successful in 39s
CI / push-validation (pull_request) Successful in 39s
CI / build (pull_request) Successful in 1m13s
CI / lint (pull_request) Successful in 1m32s
CI / quality (pull_request) Successful in 1m49s
CI / typecheck (pull_request) Successful in 1m48s
CI / security (pull_request) Successful in 1m58s
CI / status-check (push) Has been cancelled
CI / integration_tests (pull_request) Successful in 4m48s
CI / unit_tests (pull_request) Successful in 5m11s
CI / coverage (pull_request) Has started running
CI / docker (pull_request) Successful in 1m44s
CI / status-check (pull_request) Has been cancelled
Add BDD regression tests for issue #4328 automation profile gates Tests verify that complete_strategize() and complete_execute() respect automation profile thresholds and do NOT unconditionally call auto_progress(). Covers 8 built-in profiles: manual, full-auto, supervised, auto, review_before_apply, ci, trusted ISSUES CLOSED: #4328
603 lines
23 KiB
Python
603 lines
23 KiB
Python
"""Helper script for wf14_server_mode_integration.robot tests.
|
|
|
|
Each subcommand is a self-contained check that prints a sentinel on success.
|
|
Validates the server mode team collaboration workflow (Specification
|
|
Example 14) with mocked LLM providers and in-memory domain services.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import logging
|
|
import os
|
|
import sys
|
|
import tempfile
|
|
from collections.abc import Callable
|
|
from pathlib import Path
|
|
|
|
# Keep helper stderr focused on assertion failures and tracebacks.
|
|
logging.disable(logging.CRITICAL)
|
|
|
|
# Ensure local source tree is importable
|
|
_SRC = str(Path(__file__).resolve().parents[1] / "src")
|
|
if _SRC not in sys.path:
|
|
sys.path.insert(0, _SRC)
|
|
|
|
from cleveragents.application.services.config_service import ( # noqa: E402
|
|
_REGISTRY,
|
|
ConfigLevel,
|
|
ConfigService,
|
|
)
|
|
from cleveragents.application.services.plan_lifecycle_service import ( # noqa: E402
|
|
PlanLifecycleService,
|
|
)
|
|
from cleveragents.config.settings import Settings # noqa: E402
|
|
from cleveragents.domain.models.core.action import ( # noqa: E402
|
|
ActionArgument,
|
|
ActionState,
|
|
)
|
|
from cleveragents.domain.models.core.plan import ( # noqa: E402
|
|
PlanPhase,
|
|
ProcessingState,
|
|
ProjectLink,
|
|
)
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Helpers
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
def _make_config_service(tmpdir: str) -> ConfigService:
|
|
"""Create a ConfigService backed by a temporary directory."""
|
|
config_dir = Path(tmpdir)
|
|
config_path = config_dir / "config.toml"
|
|
return ConfigService(config_dir=config_dir, config_path=config_path)
|
|
|
|
|
|
_WF14_CONFIG_ENV_OVERRIDES: tuple[str, ...] = (
|
|
"CLEVERAGENTS_SERVER_URL",
|
|
"CLEVERAGENTS_SERVER_TOKEN",
|
|
"CLEVERAGENTS_SERVER_SYNC_AUTO",
|
|
"CLEVERAGENTS_SERVER_SYNC_INTERVAL",
|
|
"CLEVERAGENTS_SERVER_TLS_VERIFY",
|
|
"CLEVERAGENTS_SERVER_NAMESPACE",
|
|
"CLEVERAGENTS_CORE_NAMESPACE",
|
|
"CLEVERAGENTS_NAMESPACE",
|
|
"CLEVERAGENTS_CORE_AUTOMATION_PROFILE",
|
|
"CLEVERAGENTS_AUTOMATION_PROFILE",
|
|
)
|
|
|
|
|
|
def _clear_config_env_overrides() -> None:
|
|
"""Remove env vars that can override config resolution in tests."""
|
|
for env_name in _WF14_CONFIG_ENV_OVERRIDES:
|
|
os.environ.pop(env_name, None)
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Subcommands
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
def config_server_mode() -> None:
|
|
"""Set and verify server config values using ConfigService."""
|
|
_clear_config_env_overrides()
|
|
|
|
with tempfile.TemporaryDirectory() as tmpdir:
|
|
svc = _make_config_service(tmpdir)
|
|
|
|
# Set server mode config values
|
|
svc.set_value("server.url", "https://agents.example.com")
|
|
svc.set_value("server.token", "tok_wf14_test_abc123")
|
|
svc.set_value("core.namespace", "team-alpha")
|
|
|
|
# Read back and verify
|
|
data = svc.read_config()
|
|
assert data["server.url"] == "https://agents.example.com", (
|
|
"Expected server.url='https://agents.example.com', "
|
|
f"got {data.get('server.url')}"
|
|
)
|
|
assert data["server.token"] == "tok_wf14_test_abc123", (
|
|
"Expected server.token='tok_wf14_test_abc123', "
|
|
f"got {data.get('server.token')}"
|
|
)
|
|
assert data["core.namespace"] == "team-alpha", (
|
|
f"Expected core.namespace='team-alpha', got {data.get('core.namespace')}"
|
|
)
|
|
|
|
# Verify resolution chain returns correct values
|
|
resolved_url = svc.resolve("server.url")
|
|
assert resolved_url.value == "https://agents.example.com", (
|
|
"Expected resolve(server.url) to return configured URL, "
|
|
f"got {resolved_url.value!r}"
|
|
)
|
|
assert resolved_url.source == ConfigLevel.GLOBAL, (
|
|
"Expected resolve(server.url) source=global, "
|
|
f"got {resolved_url.source.value}"
|
|
)
|
|
|
|
resolved_token = svc.resolve("server.token")
|
|
assert resolved_token.value == "tok_wf14_test_abc123", (
|
|
"Expected resolve(server.token) to return configured token, "
|
|
f"got {resolved_token.value!r}"
|
|
)
|
|
assert resolved_token.source == ConfigLevel.GLOBAL, (
|
|
"Expected resolve(server.token) source=global, "
|
|
f"got {resolved_token.source.value}"
|
|
)
|
|
|
|
resolved_ns = svc.resolve("core.namespace")
|
|
assert resolved_ns.value == "team-alpha", (
|
|
"Expected resolve(core.namespace) to return configured namespace, "
|
|
f"got {resolved_ns.value!r}"
|
|
)
|
|
assert resolved_ns.source == ConfigLevel.GLOBAL, (
|
|
"Expected resolve(core.namespace) source=global, "
|
|
f"got {resolved_ns.source.value}"
|
|
)
|
|
|
|
print("wf14-config-server-mode-ok")
|
|
|
|
|
|
def diagnostics() -> None:
|
|
"""Verify config registry contains required server-mode keys and defaults.
|
|
|
|
Checks that all server-mode configuration keys are registered and that
|
|
their default values match the specification. Server connectivity and
|
|
database checks are not exercised here because no real server or
|
|
database is available in the integration test environment.
|
|
"""
|
|
_clear_config_env_overrides()
|
|
|
|
# Verify config registry has required server keys
|
|
required_keys = [
|
|
"server.url",
|
|
"server.token",
|
|
"server.sync.auto",
|
|
"server.sync.interval",
|
|
"server.tls-verify",
|
|
"server.namespace",
|
|
"core.namespace",
|
|
"core.automation-profile",
|
|
]
|
|
for key in required_keys:
|
|
assert key in _REGISTRY, f"Missing config key: {key}"
|
|
|
|
# Verify config service can be instantiated with temp directory
|
|
with tempfile.TemporaryDirectory() as tmpdir:
|
|
svc = _make_config_service(tmpdir)
|
|
|
|
# Verify basic config reads succeed (empty config initially)
|
|
data = svc.read_config()
|
|
assert len(data) == 0, (
|
|
"Expected empty initial config in temporary directory, "
|
|
f"got keys: {sorted(data.keys())}"
|
|
)
|
|
|
|
# Verify default values are accessible via resolution
|
|
resolved = svc.resolve("server.sync.auto")
|
|
assert resolved.value is True, f"Expected True, got {resolved.value}"
|
|
assert resolved.source == ConfigLevel.DEFAULT, (
|
|
f"Expected server.sync.auto source=default, got {resolved.source.value}"
|
|
)
|
|
|
|
resolved_interval = svc.resolve("server.sync.interval")
|
|
assert resolved_interval.value == 300, (
|
|
f"Expected 300, got {resolved_interval.value}"
|
|
)
|
|
assert resolved_interval.source == ConfigLevel.DEFAULT, (
|
|
"Expected server.sync.interval source=default, "
|
|
f"got {resolved_interval.source.value}"
|
|
)
|
|
|
|
resolved_tls = svc.resolve("server.tls-verify")
|
|
assert resolved_tls.value is True, f"Expected True, got {resolved_tls.value}"
|
|
assert resolved_tls.source == ConfigLevel.DEFAULT, (
|
|
"Expected server.tls-verify source=default, "
|
|
f"got {resolved_tls.source.value}"
|
|
)
|
|
|
|
# Verify core.namespace default is 'local'
|
|
resolved_ns = svc.resolve("core.namespace")
|
|
assert resolved_ns.value == "local", (
|
|
f"Expected 'local', got {resolved_ns.value}"
|
|
)
|
|
assert resolved_ns.source == ConfigLevel.DEFAULT, (
|
|
f"Expected core.namespace source=default, got {resolved_ns.source.value}"
|
|
)
|
|
|
|
# Verify automation-profile default is 'supervised'
|
|
resolved_profile = svc.resolve("core.automation-profile")
|
|
assert resolved_profile.value == "supervised", (
|
|
f"Expected 'supervised', got {resolved_profile.value}"
|
|
)
|
|
assert resolved_profile.source == ConfigLevel.DEFAULT, (
|
|
"Expected core.automation-profile source=default, "
|
|
f"got {resolved_profile.source.value}"
|
|
)
|
|
|
|
# Server connectivity cannot be tested (no real server) -- verify
|
|
# that the server.url default is None (graceful handling)
|
|
resolved_url = svc.resolve("server.url")
|
|
assert resolved_url.value is None, (
|
|
f"Expected None default for server.url, got {resolved_url.value}"
|
|
)
|
|
assert resolved_url.source == ConfigLevel.DEFAULT, (
|
|
f"Expected server.url source=default, got {resolved_url.source.value}"
|
|
)
|
|
|
|
print("wf14-diagnostics-ok")
|
|
|
|
|
|
def action_namespace() -> None:
|
|
"""Create and list namespaced actions using PlanLifecycleService."""
|
|
settings = Settings()
|
|
lifecycle = PlanLifecycleService(settings=settings)
|
|
definition_of_done = "Service is running and health checks pass"
|
|
|
|
# Create an action with a namespace prefix
|
|
action = lifecycle.create_action(
|
|
name="team-alpha/deploy-service",
|
|
description="Deploy a microservice to staging",
|
|
definition_of_done=definition_of_done,
|
|
strategy_actor="openai/gpt-4",
|
|
execution_actor="openai/gpt-4",
|
|
automation_profile="supervised",
|
|
tags=["deploy", "team-alpha"],
|
|
)
|
|
|
|
assert str(action.namespaced_name) == "team-alpha/deploy-service", (
|
|
f"Expected 'team-alpha/deploy-service', got '{action.namespaced_name}'"
|
|
)
|
|
assert action.state == ActionState.AVAILABLE, "Expected action state AVAILABLE"
|
|
assert action.reusable is True, "Expected action.reusable to default to True"
|
|
assert action.read_only is False, "Expected action.read_only to default to False"
|
|
assert action.automation_profile == "supervised", (
|
|
"Expected action automation_profile='supervised', "
|
|
f"got {action.automation_profile!r}"
|
|
)
|
|
assert action.definition_of_done == definition_of_done, (
|
|
"Expected definition_of_done to match the configured value"
|
|
)
|
|
assert action.tags == ["deploy", "team-alpha"], (
|
|
f"Expected action tags ['deploy', 'team-alpha'], got {action.tags!r}"
|
|
)
|
|
|
|
# List actions and verify the namespaced action appears
|
|
actions = lifecycle.list_actions()
|
|
assert len(actions) == 1, f"Expected 1 action, got {len(actions)}"
|
|
action_names = [str(a.namespaced_name) for a in actions]
|
|
assert "team-alpha/deploy-service" in action_names, (
|
|
f"'team-alpha/deploy-service' not in {action_names}"
|
|
)
|
|
|
|
print("wf14-action-namespace-ok")
|
|
|
|
|
|
def action_actor_refs() -> None:
|
|
"""Create an action with namespaced actor references and verify fields.
|
|
|
|
Validates that actions can reference actors from different namespaces
|
|
(strategy, execution, review) and that those references are preserved
|
|
when the action is retrieved.
|
|
"""
|
|
settings = Settings()
|
|
lifecycle = PlanLifecycleService(settings=settings)
|
|
|
|
# Create an action that references namespaced actors
|
|
action = lifecycle.create_action(
|
|
name="team-alpha/analyze-code",
|
|
description="Analyze codebase for improvements",
|
|
definition_of_done="Analysis report generated",
|
|
strategy_actor="openai/gpt-4",
|
|
execution_actor="anthropic/claude-sonnet",
|
|
review_actor="openai/gpt-4",
|
|
automation_profile="supervised",
|
|
)
|
|
|
|
assert action.strategy_actor == "openai/gpt-4", (
|
|
f"Expected strategy_actor 'openai/gpt-4', got {action.strategy_actor!r}"
|
|
)
|
|
assert action.execution_actor == "anthropic/claude-sonnet", (
|
|
"Expected execution_actor 'anthropic/claude-sonnet', "
|
|
f"got {action.execution_actor!r}"
|
|
)
|
|
assert action.review_actor == "openai/gpt-4", (
|
|
f"Expected review_actor 'openai/gpt-4', got {action.review_actor!r}"
|
|
)
|
|
assert action.automation_profile == "supervised", (
|
|
"Expected action automation_profile='supervised', "
|
|
f"got {action.automation_profile!r}"
|
|
)
|
|
|
|
# Verify the action is accessible
|
|
fetched = lifecycle.get_action("team-alpha/analyze-code")
|
|
assert str(fetched.namespaced_name) == "team-alpha/analyze-code", (
|
|
"Expected fetched action name 'team-alpha/analyze-code', "
|
|
f"got {fetched.namespaced_name!s}"
|
|
)
|
|
assert fetched.strategy_actor == "openai/gpt-4", (
|
|
"Expected fetched strategy_actor 'openai/gpt-4', "
|
|
f"got {fetched.strategy_actor!r}"
|
|
)
|
|
assert fetched.execution_actor == "anthropic/claude-sonnet", (
|
|
"Expected fetched execution_actor 'anthropic/claude-sonnet', "
|
|
f"got {fetched.execution_actor!r}"
|
|
)
|
|
assert fetched.review_actor == "openai/gpt-4", (
|
|
f"Expected fetched review_actor 'openai/gpt-4', got {fetched.review_actor!r}"
|
|
)
|
|
|
|
print("wf14-action-actor-refs-ok")
|
|
|
|
|
|
def shared_actions() -> None:
|
|
"""List actions from a specific namespace."""
|
|
settings = Settings()
|
|
lifecycle = PlanLifecycleService(settings=settings)
|
|
|
|
# Create actions in different namespaces
|
|
lifecycle.create_action(
|
|
name="team-alpha/shared-lint",
|
|
description="Lint code with team standards",
|
|
definition_of_done="All lint checks pass",
|
|
strategy_actor="openai/gpt-4",
|
|
execution_actor="openai/gpt-4",
|
|
automation_profile="supervised",
|
|
)
|
|
lifecycle.create_action(
|
|
name="team-beta/shared-test",
|
|
description="Run integration test suite",
|
|
definition_of_done="All tests pass",
|
|
strategy_actor="openai/gpt-4",
|
|
execution_actor="openai/gpt-4",
|
|
automation_profile="supervised",
|
|
)
|
|
lifecycle.create_action(
|
|
name="team-alpha/shared-review",
|
|
description="Code review with team conventions",
|
|
definition_of_done="Review complete with no blockers",
|
|
strategy_actor="openai/gpt-4",
|
|
execution_actor="openai/gpt-4",
|
|
automation_profile="supervised",
|
|
)
|
|
|
|
# Verify total unfiltered count
|
|
all_actions = lifecycle.list_actions()
|
|
assert len(all_actions) == 3, f"Expected 3 total actions, got {len(all_actions)}"
|
|
assert all(a.state == ActionState.AVAILABLE for a in all_actions), (
|
|
"Expected all actions to be AVAILABLE"
|
|
)
|
|
|
|
# List only team-alpha namespace
|
|
alpha_actions = lifecycle.list_actions(namespace="team-alpha")
|
|
assert len(alpha_actions) == 2, (
|
|
f"Expected 2 team-alpha actions, got {len(alpha_actions)}"
|
|
)
|
|
alpha_names = [str(a.namespaced_name) for a in alpha_actions]
|
|
assert "team-alpha/shared-lint" in alpha_names, (
|
|
f"'team-alpha/shared-lint' not in {alpha_names}"
|
|
)
|
|
assert "team-alpha/shared-review" in alpha_names, (
|
|
f"'team-alpha/shared-review' not in {alpha_names}"
|
|
)
|
|
# Ensure only team-alpha namespace values are returned.
|
|
assert all(n.startswith("team-alpha/") for n in alpha_names), (
|
|
f"Expected only team-alpha names, got {alpha_names}"
|
|
)
|
|
|
|
# List team-beta namespace
|
|
beta_actions = lifecycle.list_actions(namespace="team-beta")
|
|
beta_names = [str(a.namespaced_name) for a in beta_actions]
|
|
assert "team-beta/shared-test" in beta_names, (
|
|
f"'team-beta/shared-test' not in {beta_names}"
|
|
)
|
|
assert len(beta_actions) == 1, f"Expected 1 beta action, got {len(beta_actions)}"
|
|
|
|
print("wf14-shared-actions-ok")
|
|
|
|
|
|
def use_shared_action() -> None:
|
|
"""Use a shared action to create a plan and verify plan attributes.
|
|
|
|
Covers Specification Example 14, Step 3: a developer uses a shared
|
|
action published by another team member. Verifies that the resulting
|
|
plan inherits the action's namespace and enters the expected initial
|
|
phase and processing state.
|
|
"""
|
|
settings = Settings()
|
|
lifecycle = PlanLifecycleService(settings=settings)
|
|
|
|
# Publish an action (simulating another team member)
|
|
target_module_arg = ActionArgument.parse(
|
|
"target_module:string:required:Module path to generate tests"
|
|
)
|
|
published = lifecycle.create_action(
|
|
name="team-alpha/generate-tests",
|
|
description="Generate tests for a target module",
|
|
definition_of_done="All target modules have test coverage above 80%",
|
|
strategy_actor="openai/gpt-4",
|
|
execution_actor="openai/gpt-4",
|
|
automation_profile="supervised",
|
|
arguments=[target_module_arg],
|
|
tags=["wf14", "shared"],
|
|
)
|
|
assert published.automation_profile == "supervised", (
|
|
"Expected published action automation_profile='supervised', "
|
|
f"got {published.automation_profile!r}"
|
|
)
|
|
|
|
# Use the shared action (simulating a second developer)
|
|
plan = lifecycle.use_action(
|
|
action_name="team-alpha/generate-tests",
|
|
project_links=[ProjectLink(project_name="local/payment-service")],
|
|
arguments={"target_module": "src/payments"},
|
|
)
|
|
|
|
# Verify plan attributes
|
|
assert plan.namespaced_name.namespace == "team-alpha", (
|
|
f"Expected namespace 'team-alpha', got '{plan.namespaced_name.namespace}'"
|
|
)
|
|
assert plan.phase == PlanPhase.STRATEGIZE, (
|
|
f"Expected phase STRATEGIZE, got {plan.phase}"
|
|
)
|
|
assert plan.processing_state == ProcessingState.COMPLETE, (
|
|
f"Expected state COMPLETE, got {plan.processing_state}"
|
|
)
|
|
assert plan.action_name == "team-alpha/generate-tests", (
|
|
f"Expected action_name 'team-alpha/generate-tests', got '{plan.action_name}'"
|
|
)
|
|
assert plan.strategy_actor == "openai/gpt-4", (
|
|
f"Expected strategy_actor 'openai/gpt-4', got {plan.strategy_actor!r}"
|
|
)
|
|
assert plan.execution_actor == "openai/gpt-4", (
|
|
f"Expected execution_actor 'openai/gpt-4', got {plan.execution_actor!r}"
|
|
)
|
|
assert plan.arguments == {"target_module": "src/payments"}, (
|
|
"Expected plan arguments {'target_module': 'src/payments'}, "
|
|
f"got {plan.arguments!r}"
|
|
)
|
|
assert plan.arguments_order == ["target_module"], (
|
|
f"Expected arguments_order ['target_module'], got {plan.arguments_order!r}"
|
|
)
|
|
assert len(plan.project_links) == 1, (
|
|
f"Expected 1 project link, got {len(plan.project_links)}"
|
|
)
|
|
assert plan.project_links[0].project_name == "local/payment-service", (
|
|
"Expected project link to local/payment-service, "
|
|
f"got {plan.project_links[0].project_name!r}"
|
|
)
|
|
assert plan.definition_of_done == published.definition_of_done, (
|
|
"Expected plan definition_of_done to match published action"
|
|
)
|
|
assert plan.reusable is True, "Expected plan.reusable=True from action default"
|
|
assert plan.read_only is False, "Expected plan.read_only=False from action default"
|
|
assert plan.tags == ["wf14", "shared"], (
|
|
f"Expected plan tags ['wf14', 'shared'], got {plan.tags!r}"
|
|
)
|
|
|
|
print("wf14-use-shared-action-ok")
|
|
|
|
|
|
def plan_namespace() -> None:
|
|
"""Create plans and verify namespace-filtered listing.
|
|
|
|
Covers Specification Example 14, Step 4: monitoring plans across the
|
|
team by listing plans filtered by namespace. Creates actions in two
|
|
namespaces, uses them to instantiate plans, and verifies that namespace
|
|
filtering returns only the expected plans.
|
|
"""
|
|
settings = Settings()
|
|
lifecycle = PlanLifecycleService(settings=settings)
|
|
|
|
# Create and use actions in team-alpha namespace
|
|
lifecycle.create_action(
|
|
name="team-alpha/monitor-lint",
|
|
description="Lint code with team standards",
|
|
definition_of_done="All lint checks pass",
|
|
strategy_actor="openai/gpt-4",
|
|
execution_actor="openai/gpt-4",
|
|
automation_profile="supervised",
|
|
)
|
|
lifecycle.create_action(
|
|
name="team-beta/monitor-test",
|
|
description="Run integration test suite",
|
|
definition_of_done="All tests pass",
|
|
strategy_actor="openai/gpt-4",
|
|
execution_actor="openai/gpt-4",
|
|
automation_profile="supervised",
|
|
)
|
|
|
|
alpha_plan = lifecycle.use_action(action_name="team-alpha/monitor-lint")
|
|
beta_plan = lifecycle.use_action(action_name="team-beta/monitor-test")
|
|
|
|
# Both plans auto-completed strategize (decompose_task=0.0 in supervised profile)
|
|
# Move beta to Execute — already at STRATEGIZE/COMPLETE, skip to execute_plan
|
|
beta_plan = lifecycle.execute_plan(beta_plan.identity.plan_id)
|
|
|
|
# List all plans
|
|
all_plans = lifecycle.list_plans()
|
|
assert len(all_plans) == 2, f"Expected 2 plans total, got {len(all_plans)}"
|
|
|
|
# List plans filtered to team-alpha namespace
|
|
alpha_plans = lifecycle.list_plans(namespace="team-alpha")
|
|
assert len(alpha_plans) == 1, f"Expected 1 team-alpha plan, got {len(alpha_plans)}"
|
|
assert alpha_plans[0].identity.plan_id == alpha_plan.identity.plan_id, (
|
|
"team-alpha plan ID mismatch"
|
|
)
|
|
assert alpha_plans[0].phase == PlanPhase.STRATEGIZE, (
|
|
f"Expected team-alpha phase STRATEGIZE, got {alpha_plans[0].phase}"
|
|
)
|
|
assert alpha_plans[0].processing_state == ProcessingState.COMPLETE, (
|
|
"Expected team-alpha processing state COMPLETE, "
|
|
f"got {alpha_plans[0].processing_state}"
|
|
)
|
|
assert alpha_plans[0].action_name == "team-alpha/monitor-lint", (
|
|
"Expected team-alpha action_name 'team-alpha/monitor-lint', "
|
|
f"got {alpha_plans[0].action_name!r}"
|
|
)
|
|
|
|
# List plans filtered to team-beta namespace
|
|
beta_plans = lifecycle.list_plans(namespace="team-beta")
|
|
assert len(beta_plans) == 1, f"Expected 1 team-beta plan, got {len(beta_plans)}"
|
|
assert beta_plans[0].identity.plan_id == beta_plan.identity.plan_id, (
|
|
"team-beta plan ID mismatch"
|
|
)
|
|
assert beta_plans[0].phase == PlanPhase.EXECUTE, (
|
|
f"Expected team-beta phase EXECUTE, got {beta_plans[0].phase}"
|
|
)
|
|
assert beta_plans[0].processing_state == ProcessingState.QUEUED, (
|
|
"Expected team-beta processing state QUEUED after execute transition, "
|
|
f"got {beta_plans[0].processing_state}"
|
|
)
|
|
assert beta_plans[0].action_name == "team-beta/monitor-test", (
|
|
"Expected team-beta action_name 'team-beta/monitor-test', "
|
|
f"got {beta_plans[0].action_name!r}"
|
|
)
|
|
|
|
# Verify phase filtering within namespaces.
|
|
alpha_strategize = lifecycle.list_plans(
|
|
namespace="team-alpha", phase=PlanPhase.STRATEGIZE
|
|
)
|
|
assert len(alpha_strategize) == 1, (
|
|
f"Expected 1 team-alpha strategize plan, got {len(alpha_strategize)}"
|
|
)
|
|
assert alpha_strategize[0].identity.plan_id == alpha_plan.identity.plan_id, (
|
|
"team-alpha strategize phase filter returned wrong plan"
|
|
)
|
|
|
|
beta_execute = lifecycle.list_plans(namespace="team-beta", phase=PlanPhase.EXECUTE)
|
|
assert len(beta_execute) == 1, (
|
|
f"Expected 1 team-beta execute plan, got {len(beta_execute)}"
|
|
)
|
|
assert beta_execute[0].identity.plan_id == beta_plan.identity.plan_id, (
|
|
"team-beta execute phase filter returned wrong plan"
|
|
)
|
|
|
|
# Verify cross-namespace isolation
|
|
gamma_plans = lifecycle.list_plans(namespace="team-gamma")
|
|
assert len(gamma_plans) == 0, f"Expected 0 team-gamma plans, got {len(gamma_plans)}"
|
|
|
|
print("wf14-plan-namespace-ok")
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Dispatcher
|
|
# ---------------------------------------------------------------------------
|
|
|
|
_COMMANDS: dict[str, Callable[[], None]] = {
|
|
"config-server-mode": config_server_mode,
|
|
"diagnostics": diagnostics,
|
|
"action-namespace": action_namespace,
|
|
"action-actor-refs": action_actor_refs,
|
|
"shared-actions": shared_actions,
|
|
"use-shared-action": use_shared_action,
|
|
"plan-namespace": plan_namespace,
|
|
}
|
|
|
|
if __name__ == "__main__":
|
|
if len(sys.argv) < 2 or sys.argv[1] not in _COMMANDS:
|
|
print(f"Usage: {sys.argv[0]} <{'|'.join(_COMMANDS)}>", file=sys.stderr)
|
|
sys.exit(2)
|
|
fn = _COMMANDS[sys.argv[1]]
|
|
fn()
|