status.py
python
sha256:4671b7f787ddbe63ced31c895b688c77ab495653b65a730b423329f26b3c1439
feat: K1-P1 complete — agent provenance, build-verification…
Sonnet 4.6
patch
53 days ago
| 1 | """``overseer status`` command (§K4.4).""" |
| 2 | |
| 3 | from __future__ import annotations |
| 4 | |
| 5 | from argparse import Namespace |
| 6 | from pathlib import Path |
| 7 | |
| 8 | from adapters.config import load_config |
| 9 | from adapters.errors import ConfigError, ReadError |
| 10 | from cli.context import CliContext |
| 11 | from cli.digest import sha256_hex |
| 12 | from cli.drift import compute_drift |
| 13 | from cli.footprint import resolve_footprint |
| 14 | from cli.kit_root import kit_version |
| 15 | from cli.output import CommandReport |
| 16 | from cli.paths import is_within_repo, resolve_config_path, resolve_repo_root |
| 17 | from cli.sanitize import format_config_error, sanitize_text |
| 18 | from cli.vcs_status import read_vcs_status, vcs_report |
| 19 | from cli.version_lock import LockError, lock_path, read_version_lock |
| 20 | from tools.governance_gates import scan_governance_gates |
| 21 | from tools.governance_gates.format import format_pending_gate_lines, pending_gates_payload |
| 22 | from tools.substrate_health import check_substrate |
| 23 | |
| 24 | |
| 25 | GOVERNANCE_SYNC_MARKER = "last_governance_sync" |
| 26 | |
| 27 | |
| 28 | def _read_governance_sync_marker(repo_root: Path) -> str | None: |
| 29 | marker = repo_root / ".overseer" / GOVERNANCE_SYNC_MARKER |
| 30 | if not marker.is_file(): |
| 31 | return None |
| 32 | text = marker.read_text(encoding="utf-8").strip() |
| 33 | return text or None |
| 34 | |
| 35 | |
| 36 | def _compute_footprint_integrity( |
| 37 | repo_root: Path, |
| 38 | lock, |
| 39 | rendered, |
| 40 | ) -> tuple[str, list[str]]: |
| 41 | """Recompute kit-only digest; report preserved-living paths separately (§K6.4).""" |
| 42 | from cli.version_lock import ORIGIN_PRESERVED, compute_lock_digest, entry_origin |
| 43 | |
| 44 | prior = {entry.path: entry for entry in lock.footprint} |
| 45 | preserved_living: list[str] = [] |
| 46 | kit_entries = [] |
| 47 | for item in rendered: |
| 48 | entry = prior.get(item.destination) |
| 49 | origin = entry_origin(entry) if entry is not None else "kit" |
| 50 | dest = repo_root / item.destination |
| 51 | if dest.is_file(): |
| 52 | content = dest.read_bytes() |
| 53 | else: |
| 54 | content = b"" |
| 55 | digest_hex = sha256_hex(content) |
| 56 | if origin == ORIGIN_PRESERVED: |
| 57 | preserved_living.append(item.destination) |
| 58 | continue |
| 59 | from cli.version_lock import FootprintEntry, ORIGIN_KIT |
| 60 | |
| 61 | kit_entries.append( |
| 62 | FootprintEntry( |
| 63 | path=item.destination, |
| 64 | source=item.source, |
| 65 | sha256=digest_hex, |
| 66 | origin=ORIGIN_KIT, |
| 67 | ) |
| 68 | ) |
| 69 | computed = compute_lock_digest(kit_entries) |
| 70 | integrity = "ok" if computed == lock.footprint_digest else "mismatch" |
| 71 | return integrity, preserved_living |
| 72 | |
| 73 | |
| 74 | def _lock_summary(lock) -> dict: |
| 75 | return { |
| 76 | "lock_version": lock.lock_version, |
| 77 | "kit_version": lock.kit_version, |
| 78 | "config_version": lock.config_version, |
| 79 | "footprint_digest": lock.footprint_digest, |
| 80 | "installed_at": lock.installed_at, |
| 81 | "synced_at": lock.synced_at, |
| 82 | } |
| 83 | |
| 84 | |
| 85 | def _exit_code_from_conditions( |
| 86 | *, |
| 87 | config_error: bool, |
| 88 | integrity: str | None, |
| 89 | drift_status: str | None, |
| 90 | use_exit_code: bool, |
| 91 | substrate_ok: bool = True, |
| 92 | ) -> int: |
| 93 | """Apply frozen precedence: 2 > 6 > 3 > 0.""" |
| 94 | if not use_exit_code: |
| 95 | return 0 |
| 96 | if config_error or not substrate_ok: |
| 97 | return 2 |
| 98 | if integrity == "mismatch": |
| 99 | return 6 |
| 100 | if drift_status in {"behind", "ahead"}: |
| 101 | return 3 |
| 102 | return 0 |
| 103 | |
| 104 | |
| 105 | def run_status(args: Namespace, ctx: CliContext) -> int: |
| 106 | """Execute ``overseer status``.""" |
| 107 | report = CommandReport() |
| 108 | repo_root = resolve_repo_root(cwd=ctx.cwd, repo_arg=args.repo, command="status") |
| 109 | overseer_dir = repo_root / ".overseer" |
| 110 | |
| 111 | if not overseer_dir.is_dir(): |
| 112 | payload = { |
| 113 | "initialized": False, |
| 114 | "warnings": [], |
| 115 | } |
| 116 | if ctx.output.json_mode: |
| 117 | ctx.output.emit_json(payload) |
| 118 | else: |
| 119 | ctx.output.emit("not initialized") |
| 120 | return 0 |
| 121 | |
| 122 | config_path = resolve_config_path(repo_root, args.config) |
| 123 | if not is_within_repo(repo_root, config_path): |
| 124 | ctx.output.error("refused: config path outside repo root") |
| 125 | return 4 |
| 126 | |
| 127 | config_error = False |
| 128 | lock_error = False |
| 129 | |
| 130 | try: |
| 131 | config = load_config(config_path) |
| 132 | except ConfigError as exc: |
| 133 | config_error = True |
| 134 | ctx.output.error(format_config_error(exc, repo_root)) |
| 135 | payload = { |
| 136 | "initialized": True, |
| 137 | "error": str(exc), |
| 138 | "warnings": report.warnings, |
| 139 | } |
| 140 | if ctx.output.json_mode: |
| 141 | ctx.output.emit_json(payload) |
| 142 | return 2 |
| 143 | |
| 144 | substrate = check_substrate(config, repo_root) |
| 145 | if not substrate.ok: |
| 146 | report.add_warning(f"substrate: {substrate.state} — {substrate.message}") |
| 147 | if substrate.remediation: |
| 148 | report.add_warning(f"substrate-remediation: {substrate.remediation}") |
| 149 | |
| 150 | gate_scan = None |
| 151 | if config.governance_gates.remind and "status" in config.governance_gates.surfaces: |
| 152 | gate_scan = scan_governance_gates(config, repo_root) |
| 153 | if gate_scan.pending: |
| 154 | for line in format_pending_gate_lines(gate_scan): |
| 155 | report.add_warning(line) |
| 156 | |
| 157 | lock_file = lock_path(repo_root) |
| 158 | lock = None |
| 159 | try: |
| 160 | lock = read_version_lock(lock_file) |
| 161 | except LockError as exc: |
| 162 | lock_error = True |
| 163 | report.add_warning(str(exc)) |
| 164 | |
| 165 | rendered: list = [] |
| 166 | integrity: str | None = None |
| 167 | preserved_living: list[str] = [] |
| 168 | if lock is not None: |
| 169 | try: |
| 170 | rendered = resolve_footprint(config, kit=ctx.kit) |
| 171 | except ConfigError as exc: |
| 172 | config_error = True |
| 173 | ctx.output.error(format_config_error(exc, repo_root)) |
| 174 | return 2 |
| 175 | |
| 176 | if args.check_footprint: |
| 177 | integrity, preserved_living = _compute_footprint_integrity(repo_root, lock, rendered) |
| 178 | if integrity == "mismatch": |
| 179 | report.add_warning("footprint_integrity: mismatch") |
| 180 | for path in preserved_living: |
| 181 | report.add_warning(f"preserved-living: {path}") |
| 182 | |
| 183 | drift = compute_drift( |
| 184 | cli_version=kit_version(), |
| 185 | lock=lock, |
| 186 | rendered=rendered, |
| 187 | repo_root=repo_root, |
| 188 | ) |
| 189 | if drift["status"] in {"behind", "ahead"}: |
| 190 | report.add_warning(f"drift: {drift['status']}") |
| 191 | |
| 192 | vcs_result = read_vcs_status(config, repo_root, ctx.runner) |
| 193 | if isinstance(vcs_result, ReadError): |
| 194 | ctx.output.error(sanitize_text(str(vcs_result), repo_root)) |
| 195 | payload = { |
| 196 | "initialized": True, |
| 197 | "substrate": _substrate_payload(substrate), |
| 198 | "vcs": { |
| 199 | "error": sanitize_text(str(vcs_result), repo_root), |
| 200 | "command": sanitize_text(vcs_result.command, repo_root), |
| 201 | }, |
| 202 | "warnings": report.warnings, |
| 203 | } |
| 204 | if ctx.output.json_mode: |
| 205 | ctx.output.emit_json(payload) |
| 206 | return 2 |
| 207 | |
| 208 | payload = { |
| 209 | "initialized": True, |
| 210 | "kit_version": kit_version(), |
| 211 | "substrate": _substrate_payload(substrate), |
| 212 | "lock": _lock_summary(lock) if lock else None, |
| 213 | "drift": drift, |
| 214 | "footprint_integrity": integrity, |
| 215 | "preserved_living": preserved_living if args.check_footprint else [], |
| 216 | "vcs": vcs_report(vcs_result, config), |
| 217 | "last_governance_sync": _read_governance_sync_marker(repo_root), |
| 218 | "governance_gates": pending_gates_payload(gate_scan) |
| 219 | if gate_scan is not None |
| 220 | else {"enabled": False, "suppressed": False, "active_phases": [], "pending": []}, |
| 221 | "warnings": report.warnings, |
| 222 | } |
| 223 | if lock_error: |
| 224 | payload["lock_error"] = True |
| 225 | |
| 226 | exit_code = _exit_code_from_conditions( |
| 227 | config_error=config_error, |
| 228 | integrity=integrity if args.check_footprint else None, |
| 229 | drift_status=drift["status"], |
| 230 | use_exit_code=args.exit_code, |
| 231 | substrate_ok=substrate.ok, |
| 232 | ) |
| 233 | if lock_error and args.exit_code: |
| 234 | exit_code = 6 if exit_code == 0 else max(exit_code, 6) |
| 235 | |
| 236 | if ctx.output.json_mode: |
| 237 | ctx.output.emit_json(payload) |
| 238 | else: |
| 239 | ctx.output.emit(f"kit_version: {payload['kit_version']}") |
| 240 | if not substrate.ok: |
| 241 | ctx.output.emit(f"substrate: {substrate.state} — {substrate.message}") |
| 242 | if substrate.remediation: |
| 243 | ctx.output.emit(f"substrate-remediation: {substrate.remediation}") |
| 244 | if lock: |
| 245 | ctx.output.emit(f"lock kit_version: {lock.kit_version}") |
| 246 | ctx.output.emit(f"drift: {drift['status']}") |
| 247 | if integrity: |
| 248 | ctx.output.emit(f"footprint_integrity: {integrity}") |
| 249 | if gate_scan is not None and gate_scan.pending: |
| 250 | ctx.output.emit("") |
| 251 | for line in format_pending_gate_lines(gate_scan): |
| 252 | ctx.output.emit(line) |
| 253 | ctx.output.emit(f"vcs.regime: {vcs_result.regime}") |
| 254 | ctx.output.emit(f"vcs.branch: {vcs_result.branch}") |
| 255 | ctx.output.emit(f"vcs.dirty: {vcs_result.dirty}") |
| 256 | |
| 257 | return exit_code |
| 258 | |
| 259 | |
| 260 | def _substrate_payload(substrate) -> dict: |
| 261 | return { |
| 262 | "state": substrate.state, |
| 263 | "ok": substrate.ok, |
| 264 | "missing": list(substrate.missing), |
| 265 | "remediation": substrate.remediation, |
| 266 | "message": substrate.message, |
| 267 | } |
File History
1 commit
sha256:4671b7f787ddbe63ced31c895b688c77ab495653b65a730b423329f26b3c1439
feat: K1-P1 complete — agent provenance, build-verification…
Sonnet 4.6
patch
53 days ago