route.py
python
sha256:a78e7e5a8740e03315f325d19edeb3aa1b306b3337d04abbaa9a9e0f3bbeb7a1
docs: MuseHub-first before ISR #74 — staging solidify NEXT
Human
9 hours ago
| 1 | """``overseer route`` command (§PR.6).""" |
| 2 | |
| 3 | from __future__ import annotations |
| 4 | |
| 5 | from argparse import Namespace |
| 6 | from pathlib import Path |
| 7 | |
| 8 | from adapters.config import OverseerConfig, load_config |
| 9 | from adapters.errors import ConfigError |
| 10 | from cli.context import CliContext |
| 11 | from cli.paths import PathEscapeError, confine_path, is_within_repo, resolve_config_path, resolve_repo_root |
| 12 | from cli.sanitize import config_exit_code, format_config_error |
| 13 | from tools.cost_awareness.derive import derive_cost_view |
| 14 | from tools.model_routing.labels import RoutingPolicyError, load_model_tier_cost_bands |
| 15 | from tools.model_routing.policy import load_routing_policy, validate_routing_policy |
| 16 | from tools.model_routing.resolve import resolve_route |
| 17 | from tools.model_routing.types import RouteSelector |
| 18 | |
| 19 | |
| 20 | def _resolve_policy_path(config: OverseerConfig, repo_root: Path) -> Path: |
| 21 | """Resolve configured policy path confined to repo root.""" |
| 22 | return confine_path(repo_root, config.model_routing.policy) |
| 23 | |
| 24 | |
| 25 | def run_route_command(args: Namespace, ctx: CliContext) -> int: |
| 26 | """Execute ``overseer route`` (read-only; no model call, no network).""" |
| 27 | repo_root = resolve_repo_root(cwd=ctx.cwd, repo_arg=args.repo, command="route") |
| 28 | overseer_dir = repo_root / ".overseer" |
| 29 | if not overseer_dir.is_dir(): |
| 30 | ctx.output.error("not initialized — run ok init first") |
| 31 | return 1 |
| 32 | |
| 33 | config_path = resolve_config_path(repo_root, args.config) |
| 34 | if not is_within_repo(repo_root, config_path): |
| 35 | ctx.output.error("refused: config path outside repo root") |
| 36 | return 4 |
| 37 | |
| 38 | try: |
| 39 | config = load_config(config_path) |
| 40 | except ConfigError as exc: |
| 41 | ctx.output.error(format_config_error(exc, repo_root)) |
| 42 | return config_exit_code(exc) |
| 43 | |
| 44 | for warning in config.extension_warnings: |
| 45 | ctx.output.warn(warning) |
| 46 | |
| 47 | try: |
| 48 | policy_path = _resolve_policy_path(config, repo_root) |
| 49 | except PathEscapeError: |
| 50 | ctx.output.error("refused: model_routing.policy path outside repo root") |
| 51 | return 4 |
| 52 | |
| 53 | if args.validate: |
| 54 | return _run_validate(config=config, policy_path=policy_path, kit_root=ctx.kit, ctx=ctx) |
| 55 | |
| 56 | query = RouteSelector( |
| 57 | position=args.position, |
| 58 | phase_tier=args.phase_tier, |
| 59 | gate=args.gate, |
| 60 | ) |
| 61 | return _run_resolve( |
| 62 | config=config, |
| 63 | policy_path=policy_path, |
| 64 | kit_root=ctx.kit, |
| 65 | query=query, |
| 66 | ctx=ctx, |
| 67 | ) |
| 68 | |
| 69 | |
| 70 | def _run_validate( |
| 71 | *, |
| 72 | config: OverseerConfig, |
| 73 | policy_path: Path, |
| 74 | kit_root: Path, |
| 75 | ctx: CliContext, |
| 76 | ) -> int: |
| 77 | result = validate_routing_policy(policy_path, kit_root=kit_root) |
| 78 | payload = { |
| 79 | "valid": result.valid, |
| 80 | "policy": config.model_routing.policy, |
| 81 | "violation": result.violation, |
| 82 | } |
| 83 | if ctx.output.json_mode: |
| 84 | ctx.output.emit_json(payload) |
| 85 | elif result.valid: |
| 86 | ctx.output.emit("routing policy: valid") |
| 87 | else: |
| 88 | ctx.output.error(result.violation or "routing policy invalid") |
| 89 | if result.valid: |
| 90 | return 0 |
| 91 | if result.violation and "missing or unreadable" in result.violation: |
| 92 | return 31 |
| 93 | return 30 |
| 94 | |
| 95 | |
| 96 | def _run_resolve( |
| 97 | *, |
| 98 | config: OverseerConfig, |
| 99 | policy_path: Path, |
| 100 | kit_root: Path, |
| 101 | query: RouteSelector, |
| 102 | ctx: CliContext, |
| 103 | ) -> int: |
| 104 | try: |
| 105 | policy = load_routing_policy(policy_path, kit_root=kit_root) |
| 106 | except RoutingPolicyError as exc: |
| 107 | ctx.output.error(exc.message) |
| 108 | return exc.exit_code |
| 109 | |
| 110 | try: |
| 111 | cost_bands = load_model_tier_cost_bands(kit_root, fail_closed=True) |
| 112 | except RoutingPolicyError as exc: |
| 113 | ctx.output.error(exc.message) |
| 114 | return exc.exit_code |
| 115 | |
| 116 | decision = resolve_route(policy, query) |
| 117 | cost_class, paid_step_before_spend = derive_cost_view(decision.model_tier, cost_bands) |
| 118 | payload = { |
| 119 | "route_id": decision.route_id, |
| 120 | "model_tier": decision.model_tier, |
| 121 | "fallback": list(decision.fallback), |
| 122 | "query": { |
| 123 | "position": query.position, |
| 124 | "phase_tier": query.phase_tier, |
| 125 | "gate": query.gate, |
| 126 | }, |
| 127 | "policy": config.model_routing.policy, |
| 128 | "cost_class": cost_class, |
| 129 | "paid_step_before_spend": paid_step_before_spend, |
| 130 | } |
| 131 | if ctx.output.json_mode: |
| 132 | ctx.output.emit_json(payload) |
| 133 | else: |
| 134 | ctx.output.emit(f"route_id: {decision.route_id}") |
| 135 | ctx.output.emit(f"model_tier: {decision.model_tier}") |
| 136 | ctx.output.emit(f"fallback: {', '.join(decision.fallback)}") |
| 137 | ctx.output.emit(f"cost_class: {cost_class}") |
| 138 | ctx.output.emit(f"paid_step_before_spend: {paid_step_before_spend}") |
| 139 | return 0 |
| 140 | |
| 141 | |
| 142 | def routing_policy_status(config: OverseerConfig, repo_root: Path, *, kit_root: Path) -> dict: |
| 143 | """Read-only routing-policy validity for ``overseer status`` when enabled.""" |
| 144 | if not config.model_routing.enabled: |
| 145 | return {"enabled": False} |
| 146 | try: |
| 147 | policy_path = _resolve_policy_path(config, repo_root) |
| 148 | except PathEscapeError: |
| 149 | return { |
| 150 | "enabled": True, |
| 151 | "policy": config.model_routing.policy, |
| 152 | "valid": False, |
| 153 | "violation": "model_routing.policy path escapes repo root", |
| 154 | } |
| 155 | result = validate_routing_policy(policy_path, kit_root=kit_root) |
| 156 | return { |
| 157 | "enabled": True, |
| 158 | "policy": config.model_routing.policy, |
| 159 | "valid": result.valid, |
| 160 | "violation": result.violation, |
| 161 | } |
File History
1 commit
sha256:a78e7e5a8740e03315f325d19edeb3aa1b306b3337d04abbaa9a9e0f3bbeb7a1
docs: MuseHub-first before ISR #74 — staging solidify NEXT
Human
9 hours ago