← Files NGS Analysis WorkbenchARCHIVED FILE

mcp/ngs_workbench_mcp/readiness/models.py

3.67 KB · Sep 30, 2026 · 23:20 UTC

↓ Download file

"""Typed results produced by execution-readiness evaluation."""

from __future__ import annotations

from datetime import datetime
from typing import Literal

from pydantic import BaseModel, Field, model_validator

from ..compute_targets import ComputeTargetRef
from ..runtime.models import (
    ControllerRuntimeCandidate,
    DockerRuntime,
    RuntimeCommand,
    RuntimePlatform,
)

WorkflowEngine = Literal["nextflow", "snakemake"]


class RuntimeRequirement(BaseModel):
    """One runtime-layer capability required by an exact execution binding."""

    id: str
    layer: Literal[
        "workflow_controller",
        "compute_executor",
        "task_environment",
        "task_software",
    ]
    capability: Literal["command", "command_group", "daemon"]
    value: str
    source: str
    detail: str | None = None
    probe_mode: Literal["presence", "version"] = "presence"

    def command_candidates(self) -> list[tuple[str, str]]:
        """Return stable observation names and executables for command capabilities."""
        if self.capability == "command":
            return [(self.id, self.value)]
        if self.capability == "command_group":
            values = [value.strip() for value in self.value.split("|") if value.strip()]
            return [(f"{self.id}:{value}", value) for value in values]
        return []


class ReadinessEvidence(BaseModel):
    """Provenance for one requirement or observation used by readiness."""

    kind: str
    source: str
    detail: str


class RequirementSet(BaseModel):
    """Binding-owned requirements for one exact proposed execution."""

    binding: WorkflowEngine
    pipeline: str
    requirements: list[RuntimeRequirement]
    evidence: list[ReadinessEvidence] = Field(default_factory=list)
    blockers: list[str] = Field(default_factory=list)
    unknowns: list[str] = Field(default_factory=list)
    warnings: list[str] = Field(default_factory=list)


class ReadinessAssessment(BaseModel):
    """Binding-specific readiness derived from a runtime snapshot."""

    ok: bool
    status: Literal["ready", "blocked", "unknown"]
    binding: WorkflowEngine | None = None
    pipeline: str | None = None
    target: ComputeTargetRef | None = None
    readiness_id: str | None = Field(
        default=None,
        pattern=r"^readiness-[0-9a-f]{32}$",
    )
    scope: str
    snapshot_id: str | None = Field(default=None, pattern=r"^runtime-[0-9a-f]{32}$")
    observed_at: datetime | None = None
    expires_at: datetime | None = None
    host: RuntimePlatform | None = None
    commands: list[RuntimeCommand]
    controller_candidates: list[ControllerRuntimeCandidate] = Field(default_factory=list)
    selected_controller: ControllerRuntimeCandidate | None = None
    docker: DockerRuntime | None = None
    requirements: list[RuntimeRequirement] = Field(default_factory=list)
    evidence: list[ReadinessEvidence] = Field(default_factory=list)
    blockers: list[str]
    unknowns: list[str] = Field(default_factory=list)
    warnings: list[str] = Field(default_factory=list)

    @model_validator(mode="after")
    def validate_status(self) -> ReadinessAssessment:
        if self.ok != (self.status == "ready"):
            raise ValueError("ok must be true exactly when readiness status is ready")
        if self.status == "ready" and (self.blockers or self.unknowns):
            raise ValueError("ready status cannot contain blockers or unknowns")
        if self.status == "blocked" and not self.blockers:
            raise ValueError("blocked status requires at least one blocker")
        if self.status == "unknown" and (self.blockers or not self.unknowns):
            raise ValueError("unknown status requires unknowns and no concrete blockers")
        return self

SHA-256: c5c60069531f81906aea166c76ddbf3eb9b37f65410fba376ba9688106773527