oc_chat
Loading...
Searching...
No Matches
preflight.py
Go to the documentation of this file.
1#!/usr/bin/env python3
2# -*- coding: utf-8 -*-
3# oc_chat/registry/round/preflight.py
4
5# OCT-LINT: disable=tests_exist reason="Covered by oc_chat/tests/registry/test_round_open_executor.py"
6"""
7Preflight checks for round open and resume.
8
9Purpose
10-------
11Validate that a round-close can safely begin (or resume) before any
12git or operation-log side effect happens.
13
14Responsibilities
15----------------
16- Confirm the plan set is non-empty and every plan is merge-ready.
17- Confirm there is no orphan staging branch for a fresh open.
18- Confirm a resume target job and round both exist.
19
20Diagnostics
21-----------
22Domain: OC_CHAT.MAIN
23Levels:
24 L2 - preflight pass/fail
25 L3 - per-check details
26 L4 - deep tracing
27
28Contracts
29---------
30- Preflight performs reads only; it never mutates the registry or git.
31- A failing preflight returns reasons; the caller raises PreflightFailedError.
32
33Dependencies
34------------
35- oc_diagnostics (structured debug logging via _dbg)
36- oc_chat.registry.api (Registry facade)
37- oc_chat.registry.round.git_shim (GitShim seam)
38"""
39from __future__ import annotations
40
41from dataclasses import dataclass, field
42from typing import Sequence
43
44from oc_diagnostics import _dbg
45
46from oc_chat.registry.api import Registry
47from oc_chat.registry.round.git_shim import GitShim
48
49
50_dbg("OC_CHAT.MAIN", "module", "loaded", 4)
51
52MERGE_READY_STATUSES = frozenset({"ready_for_merge", "integrating"})
53
54
55@dataclass(frozen=True)
57 """Outcome of a preflight check sweep."""
58
59 ok: bool
60 reasons: tuple[str, ...] = field(default_factory=tuple)
61
62
64 registry: Registry,
65 *,
66 round_no: int,
67 plan_ids: Sequence[str],
68 git: GitShim,
69 staging_branch: str,
70) -> PreflightResult:
71 """Validate a fresh round-open request."""
72
73 func = "preflight_open"
74 reasons: list[str] = []
75 plan_ids = list(plan_ids)
76 if not plan_ids:
77 reasons.append("plan set is empty")
78 statuses = _plan_statuses(registry, plan_ids)
79 for plan_id in plan_ids:
80 status = statuses.get(plan_id)
81 if status is None:
82 reasons.append(f"plan {plan_id!r} not found in registry")
83 elif status not in MERGE_READY_STATUSES:
84 reasons.append(
85 f"plan {plan_id!r} status {status!r} is not merge-ready"
86 )
87 existing = registry.backend.get_round(round_no)
88 if existing is None and git.branch_exists(staging_branch):
89 reasons.append(
90 f"orphan staging branch {staging_branch!r} with no round row"
91 )
92 ok = not reasons
93 _dbg("OC_CHAT.MAIN", func, f"round={round_no} ok={ok}", 2)
94 return PreflightResult(ok=ok, reasons=tuple(reasons))
95
96
98 registry: Registry,
99 *,
100 round_no: int,
101 job_id: str | None,
102) -> PreflightResult:
103 """Validate that a resume target job and round exist."""
104
105 func = "preflight_resume"
106 reasons: list[str] = []
107 if registry.backend.get_round(round_no) is None:
108 reasons.append(f"round {round_no} not found")
109 if job_id is None:
110 reasons.append(f"no resumable round-job for round {round_no}")
111 elif registry.backend.get_round_job(job_id) is None:
112 reasons.append(f"round-job {job_id!r} not found")
113 ok = not reasons
114 _dbg("OC_CHAT.MAIN", func, f"round={round_no} ok={ok}", 2)
115 return PreflightResult(ok=ok, reasons=tuple(reasons))
116
117
119 registry: Registry,
120 plan_ids: Sequence[str],
121) -> dict[str, str]:
122 func = "_plan_statuses"
123 if not plan_ids:
124 return {}
125 placeholders = ", ".join("?" for _ in plan_ids)
126 connect_func = getattr(registry.backend, "connect")
127 with connect_func() as conn:
128 rows = conn.execute(
129 f"SELECT plan_id, status FROM plans WHERE plan_id IN ({placeholders})",
130 tuple(plan_ids),
131 ).fetchall()
132 result = {str(row["plan_id"]): str(row["status"]) for row in rows}
133 _dbg("OC_CHAT.MAIN", func, f"{len(result)} plan statuses", 4)
134 return result
PreflightResult preflight_resume(Registry registry, *, int round_no, str|None job_id)
Definition preflight.py:102
dict[str, str] _plan_statuses(Registry registry, Sequence[str] plan_ids)
Definition preflight.py:121
PreflightResult preflight_open(Registry registry, *, int round_no, Sequence[str] plan_ids, GitShim git, str staging_branch)
Definition preflight.py:70