from __future__ import annotations from dataclasses import dataclass, field from typing import Any from skylattice.core.decisions import active_decisions, get_decision from skylattice.core.intent import validate_intent_schema from skylattice.core.paths import blueprint_dir, load_yaml @dataclass class PolicyResult: errors: list[str] = field(default_factory=list) warnings: list[str] = field(default_factory=list) @property def ok(self) -> bool: return not self.errors def run_policies(customer_root, intent: dict[str, Any]) -> PolicyResult: result = PolicyResult() result.errors.extend(validate_intent_schema(intent)) rules = load_yaml(blueprint_dir() / "policies" / "rules.yaml").get("rules", []) checks = {r["check"]: r for r in rules} if "domains_non_empty" in checks and not intent.get("domains"): result.errors.append(f"[{checks['domains_non_empty']['id']}] At least one domain is required.") if "blueprint_pinned" in checks and not intent.get("blueprint"): result.errors.append(f"[{checks['blueprint_pinned']['id']}] intent.blueprint must be set.") if "overrides_have_decisions" in checks: for ov in intent.get("overrides") or []: did = ov.get("decision_id") if not did: result.errors.append( f"[override_requires_decision] Override {ov.get('path')} missing decision_id." ) continue dec = get_decision(customer_root, did) if not dec: result.errors.append( f"[override_requires_decision] Decision '{did}' not found for override {ov.get('path')}." ) elif dec.get("status") != "active": result.errors.append( f"[override_requires_decision] Decision '{did}' is not active." ) if "unmanaged_have_decisions" in checks: for um in intent.get("unmanaged") or []: if not um.get("decision_id"): result.warnings.append( f"[unmanaged_requires_decision] Unmanaged {um.get('kind')}:{um.get('name')} has no decision_id." ) if "restricted_prod_curated_guard" in checks: restricted = { d["name"] for d in intent.get("domains") or [] if d.get("access_profile") == "restricted" } for ov in intent.get("overrides") or []: path = ov.get("path") or "" if "prod_curated_read" in path: # path like domains.finance.prod_curated_read parts = path.split(".") domain = parts[1] if len(parts) > 1 else "" if domain in restricted and not ov.get("decision_id"): result.errors.append( f"[restricted_prod_curated_read] {path} requires a decision." ) # Ensure referenced active decisions exist for explainability coverage _ = active_decisions(customer_root) return result