ops-mason/src/ops_mason/plan.py

57 lines
1.7 KiB
Python
Raw Normal View History

"""Parse a construction plan file's YAML frontmatter.
See docs/construction-plan-format.md. The frontmatter's status field is
the phase-4 hard gate: the build executor (executor.py) refuses to run
against anything but status: approved with both approved_by/approved_at
set.
"""
from __future__ import annotations
from dataclasses import dataclass
from pathlib import Path
from typing import Any
import yaml
class PlanError(RuntimeError):
pass
@dataclass
class ConstructionPlan:
id: str
status: str
approved_by: str | None
approved_at: str | None
demand_source: str
consumer_repo: str
credential_type: str
path: Path
@classmethod
def load(cls, path: str | Path) -> ConstructionPlan:
path = Path(path)
text = path.read_text()
parts = text.split("---", 2)
if len(parts) < 3 or not text.startswith("---"):
raise PlanError(f"{path}: missing YAML frontmatter")
data: dict[str, Any] = yaml.safe_load(parts[1]) or {}
missing = {"id", "status"} - set(data)
if missing:
raise PlanError(f"{path}: frontmatter missing field(s): {', '.join(sorted(missing))}")
return cls(
id=data["id"],
status=data["status"],
approved_by=data.get("approved_by"),
approved_at=data.get("approved_at"),
demand_source=data.get("demand_source", ""),
consumer_repo=data.get("consumer_repo", ""),
credential_type=data.get("credential_type", ""),
path=path,
)
def is_approved(self) -> bool:
return self.status == "approved" and bool(self.approved_by) and bool(self.approved_at)