oc_chat
Loading...
Searching...
No Matches
promoted_item.py
Go to the documentation of this file.
1#!/usr/bin/env python3
2# -*- coding: utf-8 -*-
3# oc_chat/registry/source/promoted_item.py
4
5# OCT-LINT: disable=tests_exist reason="Covered by oc_chat/tests/registry/source/test_canonical.py"
6"""
7The promoted-item (``phases/<code>/<sub_id>.md``) source model (Plan 142 P1.4).
8
9Purpose
10-------
11Close the P1.1 model gap (umbrella §12.1): a promoted sub-item is an ``m_phase``
12item, but ``RoadmapItem.track_kind`` excludes ``m_phase`` (§3.3), so P1.3's
13loader cannot model it. ``PromotedItem`` reuses the P1.1 strict types + base so a
14promoted record parses, validates, canonicalises, hashes, and builds into one
15``roadmap_items_v3`` row with NO ``roadmap_phases`` row (AC §8.39). ``track_kind``
16is implied ``m_phase`` by the path and is set by the builder, not authored.
17
18Home & layering (Plan 142i [OP-5])
19----------------------------------
20The model lives under ``source/`` (not ``regenerate/``) so the ``source`` layer
21(``canonical``/``hashing``) can resolve it for full three-projection parity
22WITHOUT importing ``regenerate`` (``source`` must not depend on ``regenerate``).
23``regenerate/promoted.py`` is retained as a re-export shim for the pre-existing
24importers outside this plan's file scope (``tools/seed_142_ingest.py``).
25
26Responsibilities
27----------------
28- Define the promoted-item frontmatter schema (item fields minus ``track_kind``/
29 ``era``) with the shared lifecycle cross-field rule.
30
31Diagnostics
32-----------
33Domain: OC_CHAT.MAIN
34Levels:
35 L2 - model lifecycle
36 L3 - field validation
37 L4 - lifecycle cross-field checks
38
39Contracts
40---------
41- ``frozen``/``extra=forbid``/``strict`` (inherited from ``SourceRecord``).
42- ``track_kind`` is NOT a field — the builder sets it to ``m_phase``.
43
44Dependencies
45------------
46- oc_diagnostics (structured debug logging via _dbg)
47- option_c_registry_contracts (P1.1 base + strict types + lifecycle helper)
48- pydantic (Field and model_validator - list and map defaults and the lifecycle cross-field check)
49"""
50from __future__ import annotations
51
52from typing import Optional
53
54from pydantic import Field, model_validator
55
56from oc_diagnostics import _dbg
57
58from option_c_registry_contracts import (
59 DependsOn,
60 EntityId,
61 ExtensionsMap,
62 Line300,
63 Priority,
64 RequiredLine,
65 SourceRecord,
66 Status,
67 VersionId,
68 check_item_lifecycle,
69)
70
71_dbg("OC_CHAT.MAIN", "module", "loaded", 4)
72
73
74class PromotedItem(SourceRecord):
75 """A promoted sub-item authored as ``phases/<phase_code>/<sub_id>.md`` (§5.6).
76
77 Field order mirrors ``RoadmapItem`` minus ``track_kind`` (implied ``m_phase``
78 from the path) and ``era`` (phases carry the era). The builder sets
79 ``track_kind='m_phase'``, ``phase_id`` from the parent ``phases/<code>.md``,
80 and ``parent_item_id`` to the parent ``m_phase`` item.
81 """
82
83 id: EntityId
84 title: RequiredLine
85 purpose: Line300
86 status: Status
87 priority: Optional[Priority] = None
88 depends_on: DependsOn = Field(default_factory=list)
89 track_codes: list[EntityId] = Field(default_factory=list)
90 order: int
91 target_version: Optional[VersionId] = None
92 introduced_in_version: Optional[VersionId] = None
93 closed_in_version: Optional[VersionId] = None
94 successor: Optional[EntityId] = None
95 superseded_by: Optional[EntityId] = None
96 extensions: ExtensionsMap = Field(default_factory=dict)
97
98 @model_validator(mode="after")
99 def _validate_lifecycle(self) -> "PromotedItem":
100 return check_item_lifecycle(self)