Source code for ralph.cli.commands.check_policy
"""check_policy command — validate the active policy and report results."""
from __future__ import annotations
import sys
from typing import TYPE_CHECKING
from ralph.cli.commands.explain import _resolve_policy_dir
from ralph.policy.loader import load_policy, load_policy_for_workspace_scope
from ralph.policy.validation import PolicyValidationError, validate_policy_completeness
from ralph.workspace.scope import resolve_workspace_scope
if TYPE_CHECKING:
from pathlib import Path
[docs]
def check_policy_command(
policy_dir: Path | None = None,
counter_overrides: dict[str, int] | None = None,
) -> int:
"""Validate the active policy and print a pass/fail summary to stdout.
Resolves the policy directory the same way as --explain-policy, loads and
validates the policy, then prints a summary of what was found or the
validation error. When counter_overrides are supplied, validates that every
key is declared in pipeline.budget_counters.
Args:
policy_dir: Directory containing policy TOML files. Defaults to the
workspace-local .agent directory (if it contains TOML files),
then the bundled defaults.
counter_overrides: Budget counter overrides from --counter flags. Any key
not declared in pipeline.budget_counters raises a PolicyValidationError.
Returns:
Exit code: 0 on success, 1 on general error, 2 on policy validation error.
"""
try:
if policy_dir is not None:
resolved_dir = policy_dir
if not resolved_dir.is_dir():
print(f"Policy directory not found: {resolved_dir}", file=sys.stderr)
return 1
bundle = load_policy(resolved_dir)
else:
resolved_dir, _ = _resolve_policy_dir()
bundle = load_policy_for_workspace_scope(resolve_workspace_scope())
if counter_overrides:
validate_policy_completeness(bundle, cli_counter_overrides=counter_overrides)
block_count = len(bundle.pipeline.blocks)
lifecycle_count = len(bundle.pipeline.lifecycle_phases)
phase_count = len(bundle.pipeline.phases)
drain_count = len(bundle.agents.agent_drains)
artifact_count = len(bundle.artifacts.artifacts)
loop_count = len(bundle.pipeline.loop_counters)
budget_count = len(bundle.pipeline.budget_counters)
workflow_fallback_count = sum(
1 for defn in bundle.pipeline.phases.values() if defn.workflow_fallback is not None
)
terminal_failure_phase = bundle.pipeline.recovery.terminal_failure_phase
print(f"Policy OK: {resolved_dir}")
if bundle.pipeline.entry_block is not None:
print(f" entry block: {bundle.pipeline.entry_block}")
print(f" blocks: {block_count}")
print(f" lifecycle completion phases: {lifecycle_count}")
print(f" phases: {phase_count}")
print(f" drains: {drain_count}")
print(f" artifact contracts: {artifact_count}")
print(f" loop counters: {loop_count}")
print(f" budget counters: {budget_count}")
print(f" workflow fallbacks: {workflow_fallback_count}")
if terminal_failure_phase is not None:
print(f" terminal failure phase: {terminal_failure_phase}")
if counter_overrides and bundle.pipeline.budget_counters:
print(" effective budget caps (after --counter overrides):")
for name, cfg in bundle.pipeline.budget_counters.items():
effective = counter_overrides.get(name, cfg.default_max)
print(f" {name}: {effective}")
return 0
except PolicyValidationError as exc:
print(f"Policy validation error: {exc}", file=sys.stderr)
return 2
except Exception as exc:
print(f"Error loading policy: {exc}", file=sys.stderr)
return 1