Files
cleveragents-core/robot/helper_decision_di.py
CoreRasurae 837ff4217b
CI / lint (pull_request) Successful in 15s
CI / benchmark-publish (pull_request) Has been skipped
CI / quality (pull_request) Successful in 18s
CI / build (pull_request) Successful in 14s
CI / typecheck (pull_request) Successful in 38s
CI / security (pull_request) Successful in 46s
CI / integration_tests (pull_request) Successful in 3m3s
CI / unit_tests (pull_request) Successful in 4m39s
CI / coverage (pull_request) Successful in 5m8s
CI / docker (pull_request) Successful in 55s
CI / lint (push) Successful in 12s
CI / quality (push) Successful in 16s
CI / security (push) Successful in 30s
CI / typecheck (push) Successful in 39s
CI / build (push) Successful in 15s
CI / benchmark-regression (push) Has been skipped
CI / unit_tests (push) Successful in 2m20s
CI / docker (push) Successful in 39s
CI / integration_tests (push) Successful in 3m2s
CI / coverage (push) Successful in 4m37s
CI / benchmark-regression (pull_request) Failing after 16m37s
CI / benchmark-publish (push) Failing after 9m16s
feat(async): add async command execution and workers
- Add AsyncJob domain model with status state machine and Pydantic validation
- Add AsyncWorker service with configurable concurrency and job store
- Add CancellationToken, WorkerHealthReport, InMemoryJobStore
- Add AsyncJobModel SQLAlchemy model and Alembic migration (m6_003)
- Add 5 async config keys to Settings (worker_id, concurrency, poll_interval, max_retries, timeout)
- Add _check_async_worker_health diagnostic check in system.py
- Add comprehensive Behave BDD tests (~60 scenarios) with full step definitions
- Add Robot Framework integration tests (6 smoke tests)
- Add ASV benchmark suite for async execution
- Add architecture documentation
- Update vulture_whitelist with new public API symbols
- All quality gates pass: lint, typecheck, unit_tests, integration_tests, coverage_report (97%)

1. Wire async job creation into PlanLifecycleService:
   - Add optional job_store parameter to __init__
   - Add _maybe_enqueue_async_job() helper that checks settings.async_enabled
     and job store presence before creating and enqueuing an AsyncJob
   - Call helper from execute_plan() (phase="execute") and apply_plan()
     (phase="apply") after phase transitions
   - When async is disabled or no job store is configured, behaviour is
     unchanged (silent no-op)

2. Redact secrets in failed job error messages:
   - Apply shared.redaction.redact_value() to the error string before
     persisting to AsyncJob.error_message, preventing accidental secret
     leakage (e.g. API keys in exception text) into the audit trail

Documentation:
- S1: Added specification reconciliation note (ADR-style) to
  async_architecture.md addressing tension between "No Plan Queuing"
  clause and the async subsystem authorised by issue #312

ISSUES CLOSED: #312
2026-03-04 23:47:20 +00:00

151 lines
4.7 KiB
Python

"""Helper script for Robot Framework decision DI wiring smoke tests.
Usage:
python robot/helper_decision_di.py <subcommand>
Subcommands:
resolve-service Verify DecisionService resolves from DI container
record-integration Verify decision recording during lifecycle transitions
"""
from __future__ import annotations
import os
import sys
from pathlib import Path
from unittest.mock import create_autospec
# Ensure src is importable when run from workspace root
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "src"))
from cleveragents.application.services.decision_service import DecisionService # noqa: I001
from cleveragents.application.services.plan_lifecycle_service import (
PlanLifecycleService,
)
from cleveragents.infrastructure.database.unit_of_work import UnitOfWork
# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------
def _make_uow() -> UnitOfWork:
"""Create a UoW backed by an in-memory SQLite database."""
uow = UnitOfWork("sqlite:///:memory:")
uow.init_database()
return uow
# ---------------------------------------------------------------------------
# Subcommands
# ---------------------------------------------------------------------------
def _resolve_service() -> None:
"""Verify DecisionService can be instantiated from the container."""
os.environ["CLEVERAGENTS_DATABASE_URL"] = "sqlite:///:memory:"
try:
from cleveragents.application.container import get_container, reset_container
reset_container()
container = get_container()
svc = container.decision_service()
assert isinstance(svc, DecisionService), (
f"Expected DecisionService, got {type(svc).__name__}"
)
# Verify it's a Factory (different instances per call)
svc2 = container.decision_service()
assert svc is not svc2, "Factory should return different instances"
reset_container()
finally:
os.environ.pop("CLEVERAGENTS_DATABASE_URL", None)
print("resolve-service-ok")
def _record_integration() -> None:
"""Verify decision recording during plan lifecycle transitions."""
uow = _make_uow()
from cleveragents.config.settings import Settings
mock_settings = create_autospec(Settings, instance=True)
mock_settings.database_url = "sqlite:///:memory:"
mock_settings.async_enabled = False
decision_svc = DecisionService(settings=mock_settings, unit_of_work=uow)
lifecycle_svc = PlanLifecycleService(
settings=mock_settings,
unit_of_work=uow,
decision_service=decision_svc,
)
# Create action + plan
lifecycle_svc.create_action(
name="local/robot-di-test",
description="Robot DI integration test",
definition_of_done="Pass",
strategy_actor="local/s",
execution_actor="local/e",
)
plan = lifecycle_svc.use_action("local/robot-di-test")
plan_id = plan.identity.plan_id
# Start strategize -> should record a strategy_choice decision
lifecycle_svc.start_strategize(plan_id)
decisions = decision_svc.list_decisions(plan_id)
strategy_decisions = [
d for d in decisions if str(d.decision_type) == "strategy_choice"
]
assert len(strategy_decisions) >= 1, (
f"Expected strategy_choice decision, got {len(strategy_decisions)}"
)
# Complete strategize and progress to execute
lifecycle_svc.complete_strategize(plan_id)
plan = lifecycle_svc.get_plan(plan_id)
from cleveragents.domain.models.core.plan import PlanPhase
if plan.phase == PlanPhase.STRATEGIZE:
lifecycle_svc.execute_plan(plan_id)
# Start execute -> should record an implementation_choice decision
lifecycle_svc.start_execute(plan_id)
decisions = decision_svc.list_decisions(plan_id)
impl_decisions = [
d for d in decisions if str(d.decision_type) == "implementation_choice"
]
assert len(impl_decisions) >= 1, (
f"Expected implementation_choice decision, got {len(impl_decisions)}"
)
print("record-integration-ok")
# ---------------------------------------------------------------------------
# Dispatcher
# ---------------------------------------------------------------------------
_COMMANDS = {
"resolve-service": _resolve_service,
"record-integration": _record_integration,
}
def main() -> None:
if len(sys.argv) < 2:
raise SystemExit(f"Usage: {sys.argv[0]} <{'|'.join(_COMMANDS)}>")
command = sys.argv[1]
handler = _COMMANDS.get(command)
if handler is None:
raise SystemExit(f"Unknown command: {command}")
handler()
if __name__ == "__main__":
main()