*** Settings ***
Documentation     Integration tests for retry policy wiring into services
...               Tests the ServiceRetryWiring, RetryPolicyConfig, CircuitBreakerConfig,
...               and per-service retry policies with circuit breaker protection.

Library           OperatingSystem
Library           Process
Library           String
Library           Collections
Library           BuiltIn
Resource          ${CURDIR}/common.resource

Default Tags      retry    wiring    integration

Test Setup        Setup Retry Policy Test Environment
Test Teardown     Cleanup Retry Policy Test Environment

*** Variables ***
${PYTHON}         python
${WORKSPACE_ROOT}     ${CURDIR}/..
${TEST_FILE}      ${EMPTY}
${TEST_OUTPUT}    ${EMPTY}
${TIMEOUT}        30s

*** Test Cases ***
Test RetryPolicyConfig Default Values
    [Documentation]    Verify RetryPolicyConfig default construction
    ${output} =    Run Retry Test Script    retry_policy_defaults
    Should Match Regexp    ${output}    \\bmax_attempts=3\\b
    Should Contain    ${output}    base_delay=1.0
    Should Contain    ${output}    max_delay=60.0
    Should Contain    ${output}    jitter=True
    Should Contain    ${output}    backoff_strategy=exponential

Test CircuitBreakerConfig Default Values
    [Documentation]    Verify CircuitBreakerConfig default construction
    ${output} =    Run Retry Test Script    cb_config_defaults
    Should Match Regexp    ${output}    \\bfailure_threshold=5\\b
    Should Contain    ${output}    recovery_timeout=60.0
    Should Contain    ${output}    half_open_max_successes=2
    Should Contain    ${output}    enabled=True

Test ServiceRetryPolicyRegistry Known Service
    [Documentation]    Verify registry returns policy for known service
    ${output} =    Run Retry Test Script    registry_known
    Should Contain    ${output}    service_name=plan_service
    Should Contain    ${output}    has_description=True

Test ServiceRetryPolicyRegistry Override
    [Documentation]    Verify registry applies config overrides
    ${output} =    Run Retry Test Script    registry_override
    Should Match Regexp    ${output}    \\bmax_attempts=7\\b

Test ServiceRetryWiring Idempotent Retry
    [Documentation]    Verify wiring retries idempotent operations
    ${output} =    Run Retry Test Script    wiring_idempotent
    Should Contain    ${output}    result=success
    Should Contain    ${output}    call_count=2

Test ServiceRetryWiring Non-Idempotent Skips Retry
    [Documentation]    Verify wiring does not retry non-idempotent operations
    ${output} =    Run Retry Test Script    wiring_non_idempotent
    Should Contain    ${output}    call_count=1
    Should Contain    ${output}    failed=True

Test ServiceRetryWiring Circuit Breaker Open
    [Documentation]    Verify wiring raises CircuitBreakerOpen when circuit is open
    ${output} =    Run Retry Test Script    wiring_cb_open
    Should Contain    ${output}    CircuitBreakerOpen raised

Test Retry Policy Validation
    [Documentation]    Verify max_delay >= base_delay validation
    ${output} =    Run Retry Test Script    validation
    Should Contain    ${output}    ValidationError raised

Test Settings Retry Config Keys
    [Documentation]    Verify Settings has retry configuration fields
    ${output} =    Run Retry Test Script    settings_keys
    Should Match Regexp    ${output}    \\bretry_max_attempts=3\\b
    Should Match Regexp    ${output}    \\bcircuit_breaker_failure_threshold=5\\b

Test Category Defaults Coverage
    [Documentation]    Verify CATEGORY_DEFAULTS covers all categories
    ${output} =    Run Retry Test Script    category_defaults
    Should Contain    ${output}    all_categories_covered=True

Test Services Package Exports
    [Documentation]    Verify star-import from application.services works and
    ...               ServiceRetryWiring is importable via the public package API
    ${output} =    Run Retry Test Script    services_package_exports
    Should Contain    ${output}    star_import_ok=True
    Should Contain    ${output}    ServiceRetryWiring_importable=True
    Should Contain    ${output}    no_ContextFragment=True

Test CircuitBreakerState Enum Importable And Usable
    [Documentation]    Verify CircuitBreakerState StrEnum is importable, has expected
    ...               members, and string comparisons still work
    ${output} =    Run Retry Test Script    circuit_breaker_state_enum
    Should Contain    ${output}    CLOSED=closed
    Should Contain    ${output}    OPEN=open
    Should Contain    ${output}    HALF_OPEN=half-open
    Should Contain    ${output}    is_strenum=True
    Should Contain    ${output}    str_compare_ok=True

Test Structured Log Emission On Retry
    [Documentation]    Verify that retry attempts emit structured log output
    ...               including service name, operation, attempt number, and error
    ${output} =    Run Retry Test Script    structured_log_emission
    Should Contain    ${output}    has_retry_log=True
    Should Contain    ${output}    has_attempt_field=True
    Should Contain    ${output}    has_service_field=True

Test Unicode Control Char Rejection In ServiceRetryPolicy
    [Documentation]    Verify ServiceRetryPolicy.service_name rejects Unicode control
    ...               characters while accepting valid service names
    ${output} =    Run Retry Test Script    unicode_control_char_rejection
    Should Contain    ${output}    valid_name_ok=True
    Should Contain    ${output}    null_byte_rejected=True
    Should Contain    ${output}    bel_char_rejected=True
    Should Contain    ${output}    zero_width_rejected=True

Test Async Wiring Execute With Retry
    [Documentation]    Verify ServiceRetryWiring.async_execute retries idempotent
    ...               async operations correctly
    ${output} =    Run Retry Test Script    async_wiring_execute
    Should Contain    ${output}    async_result=success
    Should Contain    ${output}    async_call_count=2

Test Circuit Breaker Half Open Cooldown
    [Documentation]    Verify circuit breaker cooldown prevents premature half-open
    ...               transition and allows it after cooldown expires
    ${output} =    Run Retry Test Script    cb_half_open_cooldown
    Should Contain    ${output}    cooldown_blocks=True
    Should Contain    ${output}    after_cooldown_allows=True

*** Keywords ***
Setup Retry Policy Test Environment
    [Documentation]    Create temporary directory for test files
    ${temp_dir}=    Evaluate    tempfile.mkdtemp()    modules=tempfile
    ${python_exec}=    Evaluate    sys.executable    modules=sys
    Set Test Variable    ${PYTHON}    ${python_exec}
    Set Test Variable    ${TEMP_DIR}    ${temp_dir}
    Set Test Variable    ${TEST_FILE}    ${temp_dir}/retry_policy_test.py
    Set Test Variable    ${TEST_OUTPUT}    ${temp_dir}/retry_policy_output.txt

Cleanup Retry Policy Test Environment
    [Documentation]    Clean up temporary test files
    Run Keyword And Ignore Error    Remove File    ${TEST_FILE}
    Run Keyword And Ignore Error    Remove File    ${TEST_OUTPUT}
    Run Keyword And Ignore Error    Remove Directory    ${TEMP_DIR}    recursive=True

Run Retry Test Script
    [Arguments]    ${test_type}
    [Documentation]    Create and run a Python test script, returning its output
    Create Test Script    ${test_type}
    ${result} =    Run Process    ${PYTHON}    ${TEST_FILE}
    ...    stdout=${TEST_OUTPUT}    stderr=STDOUT    timeout=${TIMEOUT}    on_timeout=kill
    Should Be Equal As Integers    ${result.rc}    0    msg=Script ${test_type} failed:\n${result.stdout}
    ${output} =    Get File    ${TEST_OUTPUT}
    RETURN    ${output}

Create Test Script
    [Arguments]    ${test_type}
    [Documentation]    Create a Python test script for the given test type
    ${script} =    Run Keyword    Get ${test_type} Script
    Create File    ${TEST_FILE}    ${script}

Get retry_policy_defaults Script
    ${script} =    Catenate    SEPARATOR=\n
    ...    import sys
    ...    sys.path.insert(0, '${WORKSPACE_ROOT}/src')
    ...    from cleveragents.domain.models.core.retry_policy import RetryPolicyConfig
    ...    p = RetryPolicyConfig()
    ...    print(f"max_attempts={p.max_attempts}", flush=True)
    ...    print(f"base_delay={p.base_delay}", flush=True)
    ...    print(f"max_delay={p.max_delay}", flush=True)
    ...    print(f"jitter={p.jitter}", flush=True)
    ...    print(f"backoff_strategy={p.backoff_strategy.value}", flush=True)
    ...    sys.exit(0)
    RETURN    ${script}

Get cb_config_defaults Script
    ${script} =    Catenate    SEPARATOR=\n
    ...    import sys
    ...    sys.path.insert(0, '${WORKSPACE_ROOT}/src')
    ...    from cleveragents.domain.models.core.retry_policy import CircuitBreakerConfig
    ...    c = CircuitBreakerConfig()
    ...    print(f"failure_threshold={c.failure_threshold}", flush=True)
    ...    print(f"recovery_timeout={c.recovery_timeout}", flush=True)
    ...    print(f"half_open_max_successes={c.half_open_max_successes}", flush=True)
    ...    print(f"enabled={c.enabled}", flush=True)
    ...    sys.exit(0)
    RETURN    ${script}

Get registry_known Script
    ${script} =    Catenate    SEPARATOR=\n
    ...    import sys
    ...    sys.path.insert(0, '${WORKSPACE_ROOT}/src')
    ...    from cleveragents.domain.models.core.retry_policy import ServiceRetryPolicyRegistry
    ...    reg = ServiceRetryPolicyRegistry()
    ...    p = reg.get("plan_service")
    ...    print(f"service_name={p.service_name}", flush=True)
    ...    print(f"has_description={bool(p.description)}", flush=True)
    ...    sys.exit(0)
    RETURN    ${script}

Get registry_override Script
    ${script} =    Catenate    SEPARATOR=\n
    ...    import sys
    ...    sys.path.insert(0, '${WORKSPACE_ROOT}/src')
    ...    from cleveragents.domain.models.core.retry_policy import ServiceRetryPolicyRegistry
    ...    reg = ServiceRetryPolicyRegistry()
    ...    reg.apply_overrides({"plan_service": {"retry": {"max_attempts": 7}}})
    ...    p = reg.get("plan_service")
    ...    print(f"max_attempts={p.retry.max_attempts}", flush=True)
    ...    sys.exit(0)
    RETURN    ${script}

Get wiring_idempotent Script
    ${script} =    Catenate    SEPARATOR=\n
    ...    import sys
    ...    import os
    ...    sys.path.insert(0, '${WORKSPACE_ROOT}/src')
    ...    import logging
    ...    logging.getLogger().setLevel(logging.ERROR)
    ...    os.environ['PYTHONUNBUFFERED'] = '1'
    ...    from cleveragents.config.settings import Settings
    ...    from cleveragents.application.services.service_retry_wiring import ServiceRetryWiring
    ...    settings = Settings(database_url="sqlite:///${TEMP_DIR}/test.db", mock_providers=True, env="test")
    ...    wiring = ServiceRetryWiring(settings)
    ...    call_count = 0
    ...    def fail_once():
    ...    ${SPACE}${SPACE}${SPACE}${SPACE}global call_count
    ...    ${SPACE}${SPACE}${SPACE}${SPACE}call_count += 1
    ...    ${SPACE}${SPACE}${SPACE}${SPACE}if call_count <= 1:
    ...    ${SPACE}${SPACE}${SPACE}${SPACE}${SPACE}${SPACE}${SPACE}${SPACE}raise RuntimeError("transient")
    ...    ${SPACE}${SPACE}${SPACE}${SPACE}return "success"
    ...    result = wiring.execute("plan_service", "test_op", fail_once, idempotent=True)
    ...    print(f"result={result}", flush=True)
    ...    print(f"call_count={call_count}", flush=True)
    ...    sys.exit(0)
    RETURN    ${script}

Get wiring_non_idempotent Script
    ${script} =    Catenate    SEPARATOR=\n
    ...    import sys
    ...    import os
    ...    sys.path.insert(0, '${WORKSPACE_ROOT}/src')
    ...    import logging
    ...    logging.getLogger().setLevel(logging.ERROR)
    ...    os.environ['PYTHONUNBUFFERED'] = '1'
    ...    from cleveragents.config.settings import Settings
    ...    from cleveragents.application.services.service_retry_wiring import ServiceRetryWiring
    ...    settings = Settings(database_url="sqlite:///${TEMP_DIR}/test.db", mock_providers=True, env="test")
    ...    wiring = ServiceRetryWiring(settings)
    ...    call_count = 0
    ...    def always_fail():
    ...    ${SPACE}${SPACE}${SPACE}${SPACE}global call_count
    ...    ${SPACE}${SPACE}${SPACE}${SPACE}call_count += 1
    ...    ${SPACE}${SPACE}${SPACE}${SPACE}raise RuntimeError("fail")
    ...    failed = False
    ...    try:
    ...    ${SPACE}${SPACE}${SPACE}${SPACE}wiring.execute("plan_service", "test_write", always_fail, idempotent=False)
    ...    except RuntimeError:
    ...    ${SPACE}${SPACE}${SPACE}${SPACE}failed = True
    ...    print(f"call_count={call_count}", flush=True)
    ...    print(f"failed={failed}", flush=True)
    ...    sys.exit(0)
    RETURN    ${script}

Get wiring_cb_open Script
    ${script} =    Catenate    SEPARATOR=\n
    ...    import sys
    ...    import os
    ...    sys.path.insert(0, '${WORKSPACE_ROOT}/src')
    ...    import logging
    ...    logging.getLogger().setLevel(logging.ERROR)
    ...    os.environ['PYTHONUNBUFFERED'] = '1'
    ...    from cleveragents.config.settings import Settings
    ...    from cleveragents.application.services.service_retry_wiring import ServiceRetryWiring
    ...    from cleveragents.core.retry_patterns import CircuitBreakerOpen
    ...    from cleveragents.core.circuit_breaker import CircuitBreakerState
    ...    settings = Settings(database_url="sqlite:///${TEMP_DIR}/test.db", mock_providers=True, env="test")
    ...    wiring = ServiceRetryWiring(settings)
    ...    cb = wiring.get_circuit_breaker("plan_service")
    ...    # Drive the circuit breaker open through repeated failures
    ...    for i in range(cb.failure_threshold):
    ...    ${SPACE}${SPACE}${SPACE}${SPACE}try:
    ...    ${SPACE}${SPACE}${SPACE}${SPACE}${SPACE}${SPACE}${SPACE}${SPACE}cb.call(lambda: (_ for _ in ()).throw(Exception("fail")))
    ...    ${SPACE}${SPACE}${SPACE}${SPACE}except Exception:
    ...    ${SPACE}${SPACE}${SPACE}${SPACE}${SPACE}${SPACE}${SPACE}${SPACE}pass
    ...    assert cb.state == CircuitBreakerState.OPEN, f"Expected OPEN, got {cb.state}"
    ...    try:
    ...    ${SPACE}${SPACE}${SPACE}${SPACE}wiring.execute("plan_service", "test", lambda: "ok", idempotent=True)
    ...    ${SPACE}${SPACE}${SPACE}${SPACE}print("No exception raised", flush=True)
    ...    except CircuitBreakerOpen:
    ...    ${SPACE}${SPACE}${SPACE}${SPACE}print("CircuitBreakerOpen raised", flush=True)
    ...    sys.exit(0)
    RETURN    ${script}

Get validation Script
    ${script} =    Catenate    SEPARATOR=\n
    ...    import sys
    ...    sys.path.insert(0, '${WORKSPACE_ROOT}/src')
    ...    from pydantic import ValidationError
    ...    from cleveragents.domain.models.core.retry_policy import RetryPolicyConfig
    ...    try:
    ...    ${SPACE}${SPACE}${SPACE}${SPACE}RetryPolicyConfig(base_delay=10.0, max_delay=5.0)
    ...    ${SPACE}${SPACE}${SPACE}${SPACE}print("No error", flush=True)
    ...    except ValidationError:
    ...    ${SPACE}${SPACE}${SPACE}${SPACE}print("ValidationError raised", flush=True)
    ...    sys.exit(0)
    RETURN    ${script}

Get settings_keys Script
    ${script} =    Catenate    SEPARATOR=\n
    ...    import sys
    ...    sys.path.insert(0, '${WORKSPACE_ROOT}/src')
    ...    from cleveragents.config.settings import Settings
    ...    s = Settings(database_url="sqlite:///${TEMP_DIR}/test.db", mock_providers=True, env="test")
    ...    print(f"retry_max_attempts={s.retry_max_attempts}", flush=True)
    ...    print(f"circuit_breaker_failure_threshold={s.circuit_breaker_failure_threshold}", flush=True)
    ...    sys.exit(0)
    RETURN    ${script}

Get category_defaults Script
    ${script} =    Catenate    SEPARATOR=\n
    ...    import sys
    ...    sys.path.insert(0, '${WORKSPACE_ROOT}/src')
    ...    from cleveragents.domain.models.core.retry_policy import CATEGORY_DEFAULTS, RetryCategory
    ...    all_covered = all(cat in CATEGORY_DEFAULTS for cat in RetryCategory)
    ...    print(f"all_categories_covered={all_covered}", flush=True)
    ...    sys.exit(0)
    RETURN    ${script}

Get services_package_exports Script
    ${script} =    Catenate    SEPARATOR=\n
    ...    import sys
    ...    sys.path.insert(0, '${WORKSPACE_ROOT}/src')
    ...    # Test 1: star-import must not raise ImportError (U1 removed phantom ContextFragment)
    ...    try:
    ...    ${SPACE}${SPACE}${SPACE}${SPACE}exec("from cleveragents.application.services import *")
    ...    ${SPACE}${SPACE}${SPACE}${SPACE}print("star_import_ok=True", flush=True)
    ...    except ImportError as e:
    ...    ${SPACE}${SPACE}${SPACE}${SPACE}print(f"star_import_ok=False error={e}", flush=True)
    ...    # Test 2: ServiceRetryWiring is importable via the public package API (U3)
    ...    try:
    ...    ${SPACE}${SPACE}${SPACE}${SPACE}from cleveragents.application.services import ServiceRetryWiring
    ...    ${SPACE}${SPACE}${SPACE}${SPACE}print(f"ServiceRetryWiring_importable={ServiceRetryWiring is not None}", flush=True)
    ...    except ImportError as e:
    ...    ${SPACE}${SPACE}${SPACE}${SPACE}print(f"ServiceRetryWiring_importable=False error={e}", flush=True)
    ...    # Test 3: ContextFragment must NOT be in __all__ (U1)
    ...    from cleveragents.application.services import __all__ as exports
    ...    print(f"no_ContextFragment={'ContextFragment' not in exports}", flush=True)
    ...    sys.exit(0)
    RETURN    ${script}

Get circuit_breaker_state_enum Script
    ${script} =    Catenate    SEPARATOR=\n
    ...    import sys
    ...    import enum
    ...    sys.path.insert(0, '${WORKSPACE_ROOT}/src')
    ...    from cleveragents.core.circuit_breaker import CircuitBreakerState
    ...    # Verify enum members
    ...    print(f"CLOSED={CircuitBreakerState.CLOSED.value}", flush=True)
    ...    print(f"OPEN={CircuitBreakerState.OPEN.value}", flush=True)
    ...    print(f"HALF_OPEN={CircuitBreakerState.HALF_OPEN.value}", flush=True)
    ...    # Verify it is a StrEnum
    ...    print(f"is_strenum={issubclass(CircuitBreakerState, (str, enum.Enum))}", flush=True)
    ...    # Verify string comparison still works
    ...    ok = (CircuitBreakerState.CLOSED == "closed"
    ...    ${SPACE}${SPACE}${SPACE}${SPACE}and CircuitBreakerState.OPEN == "open"
    ...    ${SPACE}${SPACE}${SPACE}${SPACE}and CircuitBreakerState.HALF_OPEN == "half-open")
    ...    print(f"str_compare_ok={ok}", flush=True)
    ...    sys.exit(0)
    RETURN    ${script}

Get structured_log_emission Script
    ${script} =    Catenate    SEPARATOR=\n
    ...    import sys
    ...    import os
    ...    import json
    ...    sys.path.insert(0, '${WORKSPACE_ROOT}/src')
    ...    import logging
    ...    import structlog
    ...    os.environ['PYTHONUNBUFFERED'] = '1'
    ...    # Install a structlog capture processor to intercept log events
    ...    captured = []
    ...    def capture_processor(logger, method_name, event_dict):
    ...    ${SPACE}${SPACE}${SPACE}${SPACE}captured.append(dict(event_dict))
    ...    ${SPACE}${SPACE}${SPACE}${SPACE}raise structlog.DropEvent
    ...    structlog.configure(
    ...    ${SPACE}${SPACE}${SPACE}${SPACE}processors=[capture_processor],
    ...    ${SPACE}${SPACE}${SPACE}${SPACE}wrapper_class=structlog.make_filtering_bound_logger(logging.DEBUG),
    ...    ${SPACE}${SPACE}${SPACE}${SPACE}logger_factory=structlog.PrintLoggerFactory(),
    ...    )
    ...    from cleveragents.config.settings import Settings
    ...    from cleveragents.application.services.service_retry_wiring import ServiceRetryWiring
    ...    settings = Settings(database_url="sqlite:///${TEMP_DIR}/test.db", mock_providers=True, env="test")
    ...    wiring = ServiceRetryWiring(settings)
    ...    call_count = 0
    ...    def fail_once():
    ...    ${SPACE}${SPACE}${SPACE}${SPACE}global call_count
    ...    ${SPACE}${SPACE}${SPACE}${SPACE}call_count += 1
    ...    ${SPACE}${SPACE}${SPACE}${SPACE}if call_count <= 1:
    ...    ${SPACE}${SPACE}${SPACE}${SPACE}${SPACE}${SPACE}${SPACE}${SPACE}raise RuntimeError("transient")
    ...    ${SPACE}${SPACE}${SPACE}${SPACE}return "ok"
    ...    wiring.execute("plan_service", "read_plan", fail_once, idempotent=True)
    ...    # Inspect captured logs for retry-related events
    ...    retry_logs = [e for e in captured if "retry" in str(e.get("event", "")).lower()
    ...    ${SPACE}${SPACE}${SPACE}${SPACE}or "attempt" in str(e).lower()]
    ...    has_retry = len(retry_logs) > 0
    ...    has_attempt = any("attempt" in str(e) for e in retry_logs)
    ...    has_service = any("service" in str(e) for e in retry_logs)
    ...    print(f"has_retry_log={has_retry}", flush=True)
    ...    print(f"has_attempt_field={has_attempt}", flush=True)
    ...    print(f"has_service_field={has_service}", flush=True)
    ...    sys.exit(0)
    RETURN    ${script}

Get unicode_control_char_rejection Script
    ${script} =    Catenate    SEPARATOR=\n
    ...    import sys
    ...    sys.path.insert(0, '${WORKSPACE_ROOT}/src')
    ...    from pydantic import ValidationError
    ...    from cleveragents.domain.models.core.retry_policy import ServiceRetryPolicy
    ...    # Valid name should succeed
    ...    try:
    ...    ${SPACE}${SPACE}${SPACE}${SPACE}ServiceRetryPolicy(service_name="plan_service")
    ...    ${SPACE}${SPACE}${SPACE}${SPACE}print("valid_name_ok=True", flush=True)
    ...    except ValidationError:
    ...    ${SPACE}${SPACE}${SPACE}${SPACE}print("valid_name_ok=False", flush=True)
    ...    # Null byte should be rejected
    ...    try:
    ...    ${SPACE}${SPACE}${SPACE}${SPACE}ServiceRetryPolicy(service_name="plan\\x00service")
    ...    ${SPACE}${SPACE}${SPACE}${SPACE}print("null_byte_rejected=False", flush=True)
    ...    except ValidationError:
    ...    ${SPACE}${SPACE}${SPACE}${SPACE}print("null_byte_rejected=True", flush=True)
    ...    # BEL character should be rejected
    ...    try:
    ...    ${SPACE}${SPACE}${SPACE}${SPACE}ServiceRetryPolicy(service_name="plan\\x07service")
    ...    ${SPACE}${SPACE}${SPACE}${SPACE}print("bel_char_rejected=False", flush=True)
    ...    except ValidationError:
    ...    ${SPACE}${SPACE}${SPACE}${SPACE}print("bel_char_rejected=True", flush=True)
    ...    # Zero-width space (U+200B) should be rejected
    ...    try:
    ...    ${SPACE}${SPACE}${SPACE}${SPACE}ServiceRetryPolicy(service_name="plan\\u200bservice")
    ...    ${SPACE}${SPACE}${SPACE}${SPACE}print("zero_width_rejected=False", flush=True)
    ...    except ValidationError:
    ...    ${SPACE}${SPACE}${SPACE}${SPACE}print("zero_width_rejected=True", flush=True)
    ...    sys.exit(0)
    RETURN    ${script}

Get async_wiring_execute Script
    ${script} =    Catenate    SEPARATOR=\n
    ...    import sys
    ...    import os
    ...    import asyncio
    ...    sys.path.insert(0, '${WORKSPACE_ROOT}/src')
    ...    import logging
    ...    logging.getLogger().setLevel(logging.ERROR)
    ...    os.environ['PYTHONUNBUFFERED'] = '1'
    ...    from cleveragents.config.settings import Settings
    ...    from cleveragents.application.services.service_retry_wiring import ServiceRetryWiring
    ...    settings = Settings(database_url="sqlite:///${TEMP_DIR}/test.db", mock_providers=True, env="test")
    ...    wiring = ServiceRetryWiring(settings)
    ...    call_count = 0
    ...    async def async_fail_once():
    ...    ${SPACE}${SPACE}${SPACE}${SPACE}global call_count
    ...    ${SPACE}${SPACE}${SPACE}${SPACE}call_count += 1
    ...    ${SPACE}${SPACE}${SPACE}${SPACE}if call_count <= 1:
    ...    ${SPACE}${SPACE}${SPACE}${SPACE}${SPACE}${SPACE}${SPACE}${SPACE}raise RuntimeError("transient async")
    ...    ${SPACE}${SPACE}${SPACE}${SPACE}return "success"
    ...    async def main():
    ...    ${SPACE}${SPACE}${SPACE}${SPACE}result = await wiring.async_execute(
    ...    ${SPACE}${SPACE}${SPACE}${SPACE}${SPACE}${SPACE}${SPACE}${SPACE}"plan_service", "async_read", async_fail_once, idempotent=True)
    ...    ${SPACE}${SPACE}${SPACE}${SPACE}return result
    ...    result = asyncio.run(main())
    ...    print(f"async_result={result}", flush=True)
    ...    print(f"async_call_count={call_count}", flush=True)
    ...    sys.exit(0)
    RETURN    ${script}

Get cb_half_open_cooldown Script
    ${script} =    Catenate    SEPARATOR=\n
    ...    import sys
    ...    import os
    ...    import time
    ...    sys.path.insert(0, '${WORKSPACE_ROOT}/src')
    ...    import logging
    ...    import structlog
    ...    logging.getLogger().setLevel(logging.ERROR)
    ...    structlog.configure(wrapper_class=structlog.make_filtering_bound_logger(logging.ERROR))
    ...    os.environ['PYTHONUNBUFFERED'] = '1'
    ...    from cleveragents.core.retry_patterns import CircuitBreaker, CircuitBreakerOpen
    ...    from cleveragents.core.circuit_breaker import CircuitBreakerState
    ...    # Create CB with short recovery and moderate cooldown
    ...    cb = CircuitBreaker(failure_threshold=1, recovery_timeout=0.1, cooldown_seconds=0.5)
    ...    # Phase 1: Open the circuit
    ...    try:
    ...    ${SPACE}${SPACE}${SPACE}${SPACE}cb.call(lambda: (_ for _ in ()).throw(Exception("fail")))
    ...    except Exception:
    ...    ${SPACE}${SPACE}${SPACE}${SPACE}pass
    ...    assert cb.state == CircuitBreakerState.OPEN
    ...    # Phase 2: Wait past recovery_timeout, enter half-open, then fail again to re-open
    ...    time.sleep(0.15)
    ...    try:
    ...    ${SPACE}${SPACE}${SPACE}${SPACE}cb.call(lambda: (_ for _ in ()).throw(Exception("fail again")))
    ...    except (Exception, CircuitBreakerOpen):
    ...    ${SPACE}${SPACE}${SPACE}${SPACE}pass
    ...    # Now the CB is open again and _last_half_open_time is set
    ...    assert cb.state == CircuitBreakerState.OPEN
    ...    # Phase 3: Wait past recovery_timeout but NOT past cooldown
    ...    time.sleep(0.15)
    ...    try:
    ...    ${SPACE}${SPACE}${SPACE}${SPACE}cb.call(lambda: "probe")
    ...    ${SPACE}${SPACE}${SPACE}${SPACE}blocks = False
    ...    except CircuitBreakerOpen:
    ...    ${SPACE}${SPACE}${SPACE}${SPACE}blocks = True
    ...    print(f"cooldown_blocks={blocks}", flush=True)
    ...    # Phase 4: Wait past cooldown
    ...    time.sleep(0.5)
    ...    try:
    ...    ${SPACE}${SPACE}${SPACE}${SPACE}cb.call(lambda: "probe")
    ...    ${SPACE}${SPACE}${SPACE}${SPACE}allows = True
    ...    except CircuitBreakerOpen:
    ...    ${SPACE}${SPACE}${SPACE}${SPACE}allows = False
    ...    print(f"after_cooldown_allows={allows}", flush=True)
    ...    sys.exit(0)
    RETURN    ${script}
