"""Pinned compliance spec for the gitea-workflow skill + drift detection. The auto-generated spec from skill-comply drifted between runs (the dry run marked four steps required; the full run marked only initialization). The pinned spec here is the source of truth: the critical merge workflow steps are always required, and check_spec_drift() flags any generated spec that omits or downgrades them. """ import json from pathlib import Path CRITICAL_MERGE_STEPS = ( "initialize_tools_and_context", "inspect_pr_state", "perform_independent_review", "obtain_explicit_merge_approval", "refuse_competing_skip_review", "avoid_blind_merge", "execute_merge_after_gates", "cleanup_only_when_permitted", ) DEFAULT_SPEC_PATH = Path(__file__).parent / "specs" / "gitea-workflow.json" class SpecValidationError(Exception): """Raised when a spec omits or downgrades a critical merge step.""" def load_pinned_spec(path=None): """Load and validate the pinned spec. Fails closed on any drift.""" spec_path = Path(path) if path else DEFAULT_SPEC_PATH spec = json.loads(spec_path.read_text()) steps = {s["id"]: s for s in spec.get("steps", [])} for step_id in CRITICAL_MERGE_STEPS: if step_id not in steps: raise SpecValidationError( f"pinned spec is missing critical step '{step_id}'") if not steps[step_id].get("required"): raise SpecValidationError( f"critical step '{step_id}' must be required, not optional") return spec def check_spec_drift(generated_steps, critical_steps=CRITICAL_MERGE_STEPS): """Compare a generated spec's steps against the critical step list. *generated_steps* is a list of {"id": str, "required": bool} dicts (the shape skill-comply emits). Returns a list of human-readable drift findings; empty means no drift. """ by_id = {s["id"]: s for s in generated_steps} drift = [] for step_id in critical_steps: if step_id not in by_id: drift.append( f"critical step '{step_id}' is missing from the generated spec") elif not by_id[step_id].get("required"): drift.append( f"critical step '{step_id}' was generated as optional but " "must be required") return drift