7Guarded read layer for the roadmap GeneratedView renderers (Plan 142 P1.6).
11Open the **verified** authored projection (P1.4 reader guard) and project the
12INCLUDED §3.2 rows the renderers need — the derived ``current`` pointer
13(AC §8.35), the milestone (m_phase) table, per-phase sub-items with ``Done N/M``
14(AC §8.50), non-phase item tables, and the era overview — every query routed
15through the §8.30 render guard so no runtime-excluded table/column is read.
19- ``open_verified(db_path)`` — open through ``open_roadmap_reader`` (the
20 ``REGEN_UNVERIFIED_PROJECTION`` guard).
21- ``projection_hashes(conn)`` — the header's ``source_hash`` /
22 ``logical_state_hash`` (from the verification stamp; deterministic fallback).
23- ``current_phase_code`` / ``selected_roadmap_version`` — the §3.2 derived pointer
24 and the rendered version label.
25- ``milestone_rows`` / ``sub_items`` / ``non_phase_rows`` / ``era_rows``.
32 L3 - selection summaries
33 L4 - per-row projection tracing
37- Read-only; never mutates the store.
38- ``current`` = the first phase in canonical ``sort_order`` whose parent
39 ``m_phase`` item is non-terminal (done/deferred/superseded all terminal).
43- oc_diagnostics (structured debug logging via _dbg)
44- oc_chat.registry.regenerate.verified_projection (the reader guard)
45- oc_chat.registry.source.hashing (logical_state_hash fallback)
46- oc_chat.registry.render.roadmap._common (the render guard + glyphs)
48from __future__
import annotations
51from dataclasses
import dataclass
52from pathlib
import Path
53from typing
import Optional
55from oc_diagnostics
import _dbg
61_dbg(
"OC_CHAT.MAIN",
"module",
"loaded", 4)
64ALL_COMPLETE_LINE =
"**Current:** _all phases complete_"
67@dataclass(frozen=True)
69 """A structural M-phase row (the parent ``m_phase`` ``roadmap_items_v3`` row)."""
75 priority: Optional[str]
78 target_version: Optional[str]
79 successor: Optional[str]
80 superseded_by: Optional[str]
83 depends_on: tuple[str, ...]
86@dataclass(frozen=True)
88 """A non-phase or sub-phase ``roadmap_items_v3`` row (INCLUDED columns only)."""
96 priority: Optional[str]
98 successor: Optional[str]
99 superseded_by: Optional[str]
100 blocked_by: tuple[str, ...] = ()
104 """Open ``db_path`` through the P1.4 verified-projection reader guard."""
105 return open_roadmap_reader(Path(db_path))
109 """Return ``(source_hash, logical_state_hash)`` for the generated header.
111 Read from the durable verification stamp (written at regenerate step-7). An
112 ``authored_projection`` store MUST carry a ``passed`` stamp — a missing or
113 non-``passed`` stamp fails closed with ``REGEN_UNVERIFIED_PROJECTION``
114 (Plan 142i R2(a), via ``stamp_hashes``). For any other store (a legacy live
115 ingest store — the F7 pass-through), fall back to the recomputed logical hash
116 for both — keeps ``render --check`` deterministic.
118 hashes = stamp_hashes(conn)
119 if hashes
is not None:
121 computed = logical_state_hash(conn)
122 return computed, computed
126 """Return the ``current`` roadmap version id, else the highest, else None."""
130 "SELECT version_id FROM roadmap_versions "
131 "ORDER BY status = 'current' DESC, version_id DESC LIMIT 1",
133 return None if row
is None else str(row[0])
137 """Return ``{phase_code: (done, total)}`` over each phase's child items."""
138 counts: dict[str, tuple[int, int]] = {}
139 for row
in guarded_query(
142 "SELECT parent_item_id AS parent, "
143 "SUM(CASE WHEN status = 'done' THEN 1 ELSE 0 END) AS done, COUNT(*) AS total "
144 "FROM roadmap_items_v3 WHERE parent_item_id IS NOT NULL "
145 "GROUP BY parent_item_id",
147 counts[str(row[
"parent"])] = (int(row[
"done"]), int(row[
"total"]))
153_BLOCKING_KINDS = (
"blocks",
"gates")
157 """Return ``{dependent_item_id: (predecessor_id, ...)}`` for ``blocks``/``gates``
158 edges only (§3.2 direction) — the predecessors a 🔒 blocker annotation names."""
159 deps: dict[str, list[str]] = {}
160 for row
in guarded_query(
162 "SELECT from_item_id, to_item_id, kind FROM roadmap_item_dependencies "
163 "ORDER BY to_item_id, from_item_id",
165 if str(row[
"kind"])
not in _BLOCKING_KINDS:
167 deps.setdefault(str(row[
"to_item_id"]), []).append(str(row[
"from_item_id"]))
168 return {key: tuple(value)
for key, value
in deps.items()}
172 """Return ``{item_id: title}`` for the 🔒 ``Blocked by <id> — <title>`` lookup."""
173 conn.row_factory = sqlite3.Row
175 str(row[
"item_id"]): str(row[
"title"])
176 for row
in guarded_query(conn,
"SELECT item_id, title FROM roadmap_items_v3").fetchall()
181 """Return ``{item_id: status}`` for the blocker filter — only an *incomplete*
182 (non-terminal) predecessor constitutes an active 🔒 block; a satisfied
183 (done/deferred/superseded) predecessor is not shown."""
184 conn.row_factory = sqlite3.Row
186 str(row[
"item_id"]): str(row[
"status"])
187 for row
in guarded_query(conn,
"SELECT item_id, status FROM roadmap_items_v3").fetchall()
192 """Return the structural M-phase rows in canonical ``sort_order`` (AC §8.50)."""
193 conn.row_factory = sqlite3.Row
196 rows = guarded_query(
199 "SELECT i.item_id, i.title, i.purpose, i.prose, i.priority, i.status, "
200 "i.sort_order, i.target_version, i.successor_item_id, i.superseded_by_item_id "
201 "FROM roadmap_items_v3 i "
202 "JOIN roadmap_phases p ON p.phase_code = i.item_id "
203 "WHERE i.track_kind = 'm_phase' AND i.parent_item_id IS NULL "
204 "ORDER BY i.sort_order, i.item_id",
206 milestones: list[Milestone] = []
208 done, total = counts.get(str(row[
"item_id"]), (0, 0))
211 phase_code=str(row[
"item_id"]),
212 title=str(row[
"title"]),
213 purpose=str(row[
"purpose"]),
215 priority=row[
"priority"],
216 status=str(row[
"status"]),
217 sort_order=int(row[
"sort_order"]),
218 target_version=row[
"target_version"],
219 successor=row[
"successor_item_id"],
220 superseded_by=row[
"superseded_by_item_id"],
223 depends_on=deps.get(str(row[
"item_id"]), ()),
230 """Derive the ``current`` pointer (§3.2 / AC §8.35).
232 The first phase, in canonical ``sort_order``, whose parent ``m_phase`` item is
233 non-terminal (``done``/``deferred``/``superseded`` are all terminal). Null
234 when every phase is terminal (all-complete behaviour).
236 func =
"current_phase_code"
238 if milestone.status
not in TERMINAL_STATUSES:
239 _dbg(
"OC_CHAT.MAIN", func, milestone.phase_code, 3)
240 return milestone.phase_code
244def sub_items(conn: sqlite3.Connection, phase_code: str) -> list[Item]:
245 """Return a phase's child items (sub-phases + promoted) in ``sort_order``,
246 each carrying its ``blocks``/``gates`` predecessors (for the 🔒 annotation)."""
248 conn.row_factory = sqlite3.Row
249 rows = guarded_query(
252 "SELECT item_id, track_kind, title, purpose, prose, status, priority, "
253 "sort_order, successor_item_id, superseded_by_item_id "
254 "FROM roadmap_items_v3 WHERE parent_item_id = ? "
255 "ORDER BY sort_order, item_id",
258 return [
_to_item(row, deps.get(str(row[
"item_id"]), ()))
for row
in rows]
261def non_phase_rows(conn: sqlite3.Connection, track_kinds: tuple[str, ...]) -> list[Item]:
262 """Return non-phase item rows for the given ``track_kinds`` in ``sort_order``."""
265 conn.row_factory = sqlite3.Row
266 placeholders =
",".join(
"?" for _
in track_kinds)
267 rows = guarded_query(
270 "SELECT item_id, track_kind, title, purpose, prose, status, priority, "
271 "sort_order, successor_item_id, superseded_by_item_id "
273 f
"FROM roadmap_items_v3 WHERE track_kind IN ({placeholders}) "
274 "AND phase_id IS NULL "
275 "ORDER BY track_kind, sort_order, item_id",
278 return [
_to_item(row)
for row
in rows]
281def era_rows(conn: sqlite3.Connection) -> list[sqlite3.Row]:
282 """Return era rows in ``sort_order`` for the era-overview section."""
283 conn.row_factory = sqlite3.Row
284 return guarded_query(
286 "SELECT era_id, era_label, major_bump_reason, started_at, ended_at, sort_order "
287 "FROM roadmap_eras ORDER BY sort_order, era_id",
291def _to_item(row: sqlite3.Row, blocked_by: tuple[str, ...] = ()) -> Item:
293 item_id=str(row[
"item_id"]),
294 track_kind=str(row[
"track_kind"]),
295 title=str(row[
"title"]),
296 purpose=str(row[
"purpose"]),
298 status=str(row[
"status"]),
299 priority=row[
"priority"],
300 sort_order=int(row[
"sort_order"]),
301 successor=row[
"successor_item_id"],
302 superseded_by=row[
"superseded_by_item_id"],
303 blocked_by=tuple(blocked_by),
313 "selected_roadmap_version",
315 "current_phase_code",
dict[str, tuple[int, int]] _child_counts(sqlite3.Connection conn)
dict[str, tuple[str,...]] _dependencies(sqlite3.Connection conn)
dict[str, str] item_statuses(sqlite3.Connection conn)
tuple[str, str] projection_hashes(sqlite3.Connection conn)
list[Item] sub_items(sqlite3.Connection conn, str phase_code)
Optional[str] selected_roadmap_version(sqlite3.Connection conn)
list[sqlite3.Row] era_rows(sqlite3.Connection conn)
sqlite3.Connection open_verified(Path db_path)
Optional[str] current_phase_code(sqlite3.Connection conn)
list[Milestone] milestone_rows(sqlite3.Connection conn)
list[Item] non_phase_rows(sqlite3.Connection conn, tuple[str,...] track_kinds)
dict[str, str] item_titles(sqlite3.Connection conn)
Item _to_item(sqlite3.Row row, tuple[str,...] blocked_by=())