Source code for httk.workflow.backends
"""Runner-backend contracts used by the workflow manager."""
from collections.abc import Mapping, Sequence
from dataclasses import dataclass
from pathlib import Path
from typing import TYPE_CHECKING, Any, Protocol
from .errors import FormatError
from .models import JobDefinition, Marker
if TYPE_CHECKING:
from .workspace import Workspace
__all__ = [
"AttemptLaunch",
"OutcomeCommit",
"PathRunnerBackend",
"RunnerBackend",
]
@dataclass(frozen=True)
[docs]
class AttemptLaunch:
"""Paths and context needed to construct an attempt command.
``runner`` is the executable the manager resolved for this attempt. A
payload runner is the file inside ``payload``; a shared runner is the
verified copy the manager staged below ``control``, never the original.
"""
[docs]
context_path: Path
[docs]
context: Mapping[str, Any]
[docs]
runner: Path | None = None
@property
[docs]
def runner_command(self) -> Path:
"""Return the executable to run, defaulting to the payload runner."""
if self.runner is not None:
return self.runner
return self.payload.joinpath(*self.job.runner_path.parts)
@dataclass(frozen=True)
[docs]
class OutcomeCommit:
"""A published outcome being committed by the authoritative manager."""
[docs]
outcome: Mapping[str, Any]
[docs]
class RunnerBackend(Protocol):
"""Execution behavior selected by ``runner.backend`` in ``job.json``."""
[docs]
def validate(self, job: JobDefinition, payload: Path) -> None:
"""Validate backend-specific immutable payload requirements."""
[docs]
def command(self, launch: AttemptLaunch) -> Sequence[str]:
"""Return the command argument vector for one attempt."""
raise NotImplementedError
[docs]
def commit_outcome(self, commit: OutcomeCommit) -> None:
"""Complete backend-specific idempotent work before the marker advances."""
[docs]
def reconcile(self, workspace: "Workspace") -> None:
"""Repair backend-specific derived views; never alter authoritative state."""
[docs]
def marker_changed(self, workspace: "Workspace", marker: Marker) -> None:
"""Refresh derived views after an authoritative marker transition."""
[docs]
class PathRunnerBackend:
"""The normal backend which directly executes ``runner.path``."""
[docs]
def validate(self, job: JobDefinition, payload: Path) -> None:
if job.runner_source != "payload":
# A shared runner lives outside the immutable payload, so it is
# resolved, staged, and digest-verified per attempt instead.
return
runner = payload.joinpath(*job.runner_path.parts)
if not runner.is_file():
raise FormatError(f"runner does not exist or is not a regular file: {job.runner_path}")
[docs]
def command(self, launch: AttemptLaunch) -> Sequence[str]:
return [str(launch.runner_command), *launch.job.runner_arguments]
[docs]
def commit_outcome(self, commit: OutcomeCommit) -> None:
return
[docs]
def reconcile(self, workspace: "Workspace") -> None:
return
[docs]
def marker_changed(self, workspace: "Workspace", marker: Marker) -> None:
return