Engine v0.2: flow graph, datapoint states, gates, modes, overrides, deviations (RBT-WP-0007)
All checks were successful
CI Smoke / host-smoke (push) Successful in 1s
CI Smoke / container-smoke (push) Successful in 4s
Work Records / validate (push) Successful in 12s

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: sonnet
Assistant-Process: 295952@bnt-lap001
Assistant-Session: e93f64ad-516c-46eb-9666-aad8d300c477
This commit is contained in:
tegwick 2026-09-29 11:34:07 +02:00
parent 3fc279a6ed
commit 60d983769f
13 changed files with 1017 additions and 22 deletions

View file

@ -72,8 +72,11 @@ def _writable(run: Run) -> None:
def create(pack: Pack, operator: str, params: dict[str, str] | None = None) -> Run:
if not pack.ok or pack.data.get("engine") != "native":
raise RunError("only valid native packs can be run")
if pack.spec == "runbook-pack/v0.2":
from . import run2
return run2.create(pack, operator, params)
if pack.spec != "runbook-pack/v0.1":
raise RunError(f"{pack.spec} packs cannot be run yet (RBT-WP-0007)")
raise RunError(f"{pack.spec} packs cannot be run")
operator = _clean(operator.strip(), "operator")
if not re.fullmatch(r"\S+", operator):
raise RunError("operator must be one word (name or handle)")
@ -239,6 +242,10 @@ def exercise_update(run: Run, today: date | None = None) -> tuple[str, str]:
"""
if any(v["state"] != "done" for v in run.data["steps"].values()):
raise RunError("every step must be done (not skipped) to exercise the pack")
if run.data.get("deviations"):
raise RunError("a run with deviations (overrides, waivers, push-through) cannot exercise the pack")
if any(v.get("pushed_through") for v in run.data["steps"].values()):
raise RunError("a run with pushed-through steps cannot exercise the pack")
today = today or date.today()
old = run.pack.path.read_text()
new = STATUS_LINE.sub(f"exercise_status: exercised {today.isoformat()} by {run.data['operator']}",
@ -337,6 +344,8 @@ def finalize(run: Run, outcome: str, note: str = "") -> None:
states = [v["state"] for v in run.data["steps"].values()]
if outcome == "succeeded" and any(x != "done" for x in states):
raise RunError("a run succeeds only when every step is done (skipped steps do not count)")
if outcome == "succeeded" and any(g["state"] not in ("passed", "waived") for g in run.data.get("gates", {}).values()):
raise RunError("a run succeeds only when every gate has passed or been waived")
if outcome == "failed" and "failed" not in states:
raise RunError("a run can be finished as failed only when a step has failed; otherwise abandon it")
run.data["outcome"] = outcome