orchestrata_ultimate.harness.scenario
Reference › orchestrata_ultimate › harness › scenario
Loading and validating scenarios, and running their setup hooks (SPEC-102 US-001). load_scenario(path) reads a YAML file (_read_scenario_mapping requires a non-empty mapping), checks it with _validate_scenario_fields and builds a Scenario. Validation requires the fields id, volk, mode and evaluation, a mode of cxp_inject or direct_call, and a non-empty evaluation list whose items each have a name; problems raise ValueError naming the scenario. load_scenarios_for_volk and list_all_scenarios discover scenario files under harness/scenarios by default. run_setup_hook(hook_path, context) imports a hook given as module.path:function_name, and requires an async function that accepts the HarnessContext and returns None; every failure (import, missing name, not async, no parameter, an exception inside it, a non-None return) is reported as a ValueError. Used by the runner and the cli; the models are in models.
At a glance
| Source | src/orchestrata_ultimate/harness/scenario.py |
| Lines of code | 240 |
| Classes | 0 |
| Functions | 6 |
| Imports (internal) | orchestrata_ultimate.core.base.traces, orchestrata_ultimate.harness.models |
| Imported by | orchestrata_ultimate.harness.cli, orchestrata_ultimate.harness.runner |
| Tests that import it | tests/harness/test_scenario.py |
Class diagram
No classes; see Functions.
Dependency diagram
API
Functions
| Function | Signature | Summary |
|---|---|---|
_read_scenario_mapping traces |
_read_scenario_mapping(path: Path) -> dict |
Load path as YAML and ensure it is a non-empty mapping. |
_validate_scenario_fields traces |
_validate_scenario_fields(raw: dict, name: str) -> None |
Validate required fields, mode, and the evaluation list of a scenario dict. |
load_scenario traces |
load_scenario(path: Path) -> Scenario |
Parse a scenario YAML file and validate it with the Pydantic model. |
load_scenarios_for_volk traces |
load_scenarios_for_volk(volk_name: str, base_dir: Path = Path('harness/scenarios')) -> list[Scenario] |
Discover and load all scenario YAML files for a given volk. |
list_all_scenarios traces |
list_all_scenarios(base_dir: Path = Path('harness/scenarios')) -> list[Scenario] |
Discover and load all scenarios across all volks. |
run_setup_hook traces |
async run_setup_hook(hook_path: str, context: HarnessContext) -> None |
Dynamically import and execute an async setup hook function. |
Source
"""Scenario loading, validation, and setup hook execution."""
from __future__ import annotations
import importlib
import inspect
import logging
from pathlib import Path
from typing import Any
import yaml
from pydantic import ValidationError
from orchestrata_ultimate.core.base.traces import traces
from orchestrata_ultimate.harness.models import HarnessContext, Scenario
_logger = logging.getLogger(__name__)
@traces(spec="102", story="US-001", reqs=["REQ-102-004"])
def _read_scenario_mapping(path: Path) -> dict:
"""Load *path* as YAML and ensure it is a non-empty mapping.
Raises:
ValueError: If the file is missing, invalid YAML, empty, or not a mapping.
"""
if not path.exists():
raise ValueError(f"Scenario file not found: {path}")
try:
raw: Any = yaml.safe_load(path.read_text(encoding="utf-8"))
except Exception as exc:
raise ValueError(
f"Scenario file contains invalid YAML: {path} ({exc})"
) from exc
if raw is None:
raise ValueError(f"Scenario file is empty: {path}")
if not isinstance(raw, dict):
raise ValueError(f"Scenario file must contain a YAML mapping: {path}")
return raw
@traces(spec="102", story="US-001", reqs=["REQ-102-002"])
def _validate_scenario_fields(raw: dict, name: str) -> None:
"""Validate required fields, mode, and the evaluation list of a scenario dict.
Raises:
ValueError: If a required field is missing, the mode is invalid, or the
evaluation list is empty/malformed.
"""
required = {"id", "volk", "mode", "evaluation"}
missing = required - raw.keys()
if missing:
raise ValueError(f"Scenario {name} missing required fields: {sorted(missing)}")
mode = raw.get("mode")
if mode not in {"cxp_inject", "direct_call"}:
raise ValueError(
f"Scenario {name} has invalid mode '{mode}'; "
"must be 'cxp_inject' or 'direct_call'"
)
evaluation = raw.get("evaluation")
if not isinstance(evaluation, list) or len(evaluation) == 0:
raise ValueError(f"Scenario {name} 'evaluation' must be a non-empty list")
for idx, ev in enumerate(evaluation):
if not isinstance(ev, dict) or "name" not in ev:
raise ValueError(
f"Scenario {name} evaluator at index {idx} "
"is missing required field 'name'"
)
@traces(spec="102", story="US-001", reqs=["REQ-102-004"])
def load_scenario(path: Path) -> Scenario:
"""Parse a scenario YAML file and validate it with the Pydantic model.
Args:
path: Path to the YAML scenario file.
Returns:
A validated :class:`Scenario` instance.
Raises:
ValueError: If the YAML is invalid or fails validation rules.
"""
raw = _read_scenario_mapping(path)
_validate_scenario_fields(raw, path.name)
try:
return Scenario.model_validate(raw)
except ValidationError as exc:
raise ValueError(f"Scenario {path.name} validation failed: {exc}") from exc
@traces(spec="102", story="US-001", reqs=["REQ-102-001"])
def load_scenarios_for_volk(
volk_name: str,
base_dir: Path = Path("harness/scenarios"),
) -> list[Scenario]:
"""Discover and load all scenario YAML files for a given volk.
Args:
volk_name: Name of the volk directory to scan.
base_dir: Root directory containing volk scenario folders.
Returns:
List of validated :class:`Scenario` instances.
Raises:
ValueError: If duplicate scenario ids are found within the volk.
"""
volk_dir = base_dir / volk_name
if not volk_dir.exists():
_logger.warning(
"Scenario directory not found for volk '%s': %s",
volk_name,
volk_dir,
)
return []
scenarios: list[Scenario] = []
seen_ids: set[str] = set()
for yaml_path in sorted(volk_dir.glob("*.yaml")):
scenario = load_scenario(yaml_path)
if scenario.id in seen_ids:
raise ValueError(
f"Duplicate scenario id '{scenario.id}' in volk '{volk_name}'"
)
seen_ids.add(scenario.id)
scenarios.append(scenario)
return scenarios
@traces(spec="102", story="US-001", reqs=["REQ-102-006"])
def list_all_scenarios(
base_dir: Path = Path("harness/scenarios"),
) -> list[Scenario]:
"""Discover and load all scenarios across all volks.
Args:
base_dir: Root directory containing volk scenario folders.
Returns:
List of all validated :class:`Scenario` instances.
Raises:
ValueError: If duplicate scenario ids are found across volks.
"""
if not base_dir.exists():
_logger.warning(
"Scenario base directory not found: %s",
base_dir,
)
return []
all_scenarios: list[Scenario] = []
global_ids: set[str] = set()
for volk_dir in sorted(base_dir.iterdir()):
if not volk_dir.is_dir():
continue
for yaml_path in sorted(volk_dir.glob("*.yaml")):
scenario = load_scenario(yaml_path)
if scenario.id in global_ids:
raise ValueError(
f"Duplicate scenario id '{scenario.id}' across volks "
f"(file: {yaml_path})"
)
global_ids.add(scenario.id)
all_scenarios.append(scenario)
return all_scenarios
@traces(spec="102", story="US-006", reqs=["REQ-102-087", "REQ-102-088"])
async def run_setup_hook(hook_path: str, context: HarnessContext) -> None:
"""Dynamically import and execute an async setup hook function.
Args:
hook_path: Module path and function name in the format
``"module.path:function_name"``.
context: Runtime harness context to pass to the hook.
Raises:
ValueError: If the module cannot be imported, the function does not
exist, is not an async function, does not accept
:class:`HarnessContext`, or returns a non-None value.
"""
if ":" not in hook_path:
raise ValueError(
f"Invalid hook path '{hook_path}'; "
"expected format 'module.path:function_name'"
)
module_path, func_name = hook_path.split(":", 1)
try:
module = importlib.import_module(module_path)
except Exception as exc:
raise ValueError(
f"Failed to import setup hook module '{module_path}': {exc}"
) from exc
func = getattr(module, func_name, None)
if func is None:
raise ValueError(
f"Setup hook function '{func_name}' not found in module '{module_path}'"
)
if not callable(func):
raise ValueError(f"Setup hook '{hook_path}' is not callable")
if not inspect.iscoroutinefunction(func):
raise ValueError(f"Setup hook '{hook_path}' must be an async function")
# Verify the function accepts at least one positional argument
sig = inspect.signature(func)
params = list(sig.parameters.values())
if len(params) == 0:
raise ValueError(
f"Setup hook '{hook_path}' must accept a HarnessContext argument"
)
try:
result = await func(context)
except Exception as exc:
raise ValueError(
f"Setup hook '{hook_path}' raised an exception: {exc}"
) from exc
if result is not None:
raise ValueError(
f"Setup hook '{hook_path}' must return None, "
f"got {type(result).__name__}"
)