7Preflight checks for round open and resume.
11Validate that a round-close can safely begin (or resume) before any
12git or operation-log side effect happens.
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.
24 L2 - preflight pass/fail
25 L3 - per-check details
30- Preflight performs reads only; it never mutates the registry or git.
31- A failing preflight returns reasons; the caller raises PreflightFailedError.
35- oc_diagnostics (structured debug logging via _dbg)
36- oc_chat.registry.api (Registry facade)
37- oc_chat.registry.round.git_shim (GitShim seam)
39from __future__
import annotations
41from dataclasses
import dataclass, field
42from typing
import Sequence
44from oc_diagnostics
import _dbg
50_dbg(
"OC_CHAT.MAIN",
"module",
"loaded", 4)
52MERGE_READY_STATUSES = frozenset({
"ready_for_merge",
"integrating"})
55@dataclass(frozen=True)
57 """Outcome of a preflight check sweep."""
60 reasons: tuple[str, ...] = field(default_factory=tuple)
67 plan_ids: Sequence[str],
71 """Validate a fresh round-open request."""
73 func =
"preflight_open"
74 reasons: list[str] = []
75 plan_ids = list(plan_ids)
77 reasons.append(
"plan set is empty")
79 for plan_id
in plan_ids:
80 status = statuses.get(plan_id)
82 reasons.append(f
"plan {plan_id!r} not found in registry")
83 elif status
not in MERGE_READY_STATUSES:
85 f
"plan {plan_id!r} status {status!r} is not merge-ready"
87 existing = registry.backend.get_round(round_no)
88 if existing
is None and git.branch_exists(staging_branch):
90 f
"orphan staging branch {staging_branch!r} with no round row"
93 _dbg(
"OC_CHAT.MAIN", func, f
"round={round_no} ok={ok}", 2)
103 """Validate that a resume target job and round exist."""
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")
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")
114 _dbg(
"OC_CHAT.MAIN", func, f
"round={round_no} ok={ok}", 2)
120 plan_ids: Sequence[str],
122 func =
"_plan_statuses"
125 placeholders =
", ".join(
"?" for _
in plan_ids)
126 connect_func = getattr(registry.backend,
"connect")
127 with connect_func()
as conn:
129 f
"SELECT plan_id, status FROM plans WHERE plan_id IN ({placeholders})",
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)
PreflightResult preflight_resume(Registry registry, *, int round_no, str|None job_id)
dict[str, str] _plan_statuses(Registry registry, Sequence[str] plan_ids)
PreflightResult preflight_open(Registry registry, *, int round_no, Sequence[str] plan_ids, GitShim git, str staging_branch)