Skip to main content

corpora_core/
model.rs

1//! The schema (schema-v0) as types: illegal states unrepresentable where cheap.
2//! Parsing produces a [`Record`]; a kind-specific [`Facet`] carries the rest.
3
4#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
5pub struct Id(pub String); // "D2", "A1", "F5"
6
7#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
8pub struct FindingId(pub String); // "r9", "t6", "am-3"
9
10#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
11pub struct DocPath(pub String);
12
13/// ISO-8601 `YYYY-MM-DD`. Stored as text in v0; structured parsing is Phase 2.
14#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
15pub struct Date(pub String);
16
17/// A VCS revision (git sha / ref). Resolution is behind `RevisionOracle` in the engine.
18#[derive(Clone, Debug, PartialEq, Eq, Hash)]
19pub struct Rev(pub String);
20
21#[derive(Clone, Copy, Debug, PartialEq, Eq)]
22pub enum Kind {
23    Decision,
24    Axiom,
25    Invariant,
26    Architecture,
27    Current,
28    Roadmap,
29    Milestone,
30    Evidence,
31    ReviewLog,
32    Evolution,
33    Handoff,
34    Explainer,
35    Index,
36}
37
38#[derive(Clone, Copy, Debug, PartialEq, Eq)]
39pub enum Lifecycle {
40    Draft,
41    Current,
42    Superseded,
43    Historical,
44}
45
46#[derive(Clone, Copy, Debug, PartialEq, Eq)]
47pub enum Authority {
48    Normative,
49    Axiomatic,
50    Descriptive,
51    Prospective,
52    Evidence,
53    Historical,
54    Operational,
55    Explanatory,
56    Navigational,
57}
58
59/// Adoption state — the decide-timeline. A fork is just `Open`.
60#[derive(Clone, Copy, Debug, PartialEq, Eq)]
61pub enum Status {
62    Open,
63    Proposed,
64    Accepted,
65    Superseded,
66    Deprecated,
67    Rejected,
68}
69
70#[derive(Clone, Copy, Debug, PartialEq, Eq)]
71pub enum Impl {
72    Absent,
73    Scaffold,
74    Partial,
75    Implemented,
76    Verified,
77}
78
79#[derive(Clone, Copy, Debug, PartialEq, Eq)]
80pub enum Door {
81    Reversible,
82    OneWay,
83}
84
85/// Kind-specific payload. Parsing must produce exactly one, validated against `kind`.
86#[derive(Clone, Debug)]
87pub enum Facet {
88    Decision(DecisionFacet),
89    /// Assumed external fact (A1–A4): cited, never preserved by code.
90    Axiom,
91    /// architecture / invariant — canon impl relaxed to *may-carry* (schema-v0 A7).
92    Canon {
93        implementation: Option<Impl>,
94        code_revision: Option<Rev>,
95    },
96    Current {
97        implementation: Impl,
98        code_revision: Rev,
99        source_revision: Option<Rev>,
100    },
101    /// roadmap / milestone.
102    Plan {
103        implementation: Impl,
104        code_revision: Rev,
105    },
106    Evidence {
107        measured: Option<(Impl, Rev)>,
108        source_revision: Option<Rev>,
109    },
110    /// review-log / evolution / handoff / explainer / index.
111    Narrative,
112}
113
114#[derive(Clone, Debug)]
115pub struct DecisionFacet {
116    pub status: Status,
117    pub date: Date,
118    /// Present ⇒ held/deferred/done are distinguishable (schema-v0 §3).
119    pub implementation: Option<Impl>,
120    /// `Some` iff `status == Open`.
121    pub fork: Option<Fork>,
122    /// roadmap/milestone links — a deferred decision is a *scheduled* one.
123    pub realized_by: Vec<Id>,
124}
125
126#[derive(Clone, Debug)]
127pub struct Fork {
128    pub lean: String,
129    pub decide_when: String,
130    pub door: Door,
131}
132
133impl Facet {
134    /// The pinned code revision this record carries, if any — what Gate B verifies.
135    pub fn code_revision(&self) -> Option<&Rev> {
136        match self {
137            Facet::Current { code_revision, .. } | Facet::Plan { code_revision, .. } => {
138                Some(code_revision)
139            }
140            Facet::Canon { code_revision, .. } => code_revision.as_ref(),
141            Facet::Evidence { measured, .. } => measured.as_ref().map(|(_, rev)| rev),
142            _ => None,
143        }
144    }
145}
146
147/// Typed edges between records.
148#[derive(Clone, Debug, Default)]
149pub struct Edges {
150    pub depends_on: Vec<Id>,
151    pub supersedes: Vec<Id>,
152    pub related: Vec<Id>,
153    pub supports: Vec<Id>,
154    pub driven_by: Vec<FindingId>,
155}
156
157/// Extracted markdown body signals (Phase 2 parser fills these from the prose).
158#[derive(Clone, Debug, Default)]
159pub struct Body {
160    /// Prose occurrences of ids with no typed edge — the E3 *warning* path. Excludes the
161    /// record's own id/aka and anything already cited via a structured edge.
162    pub bare_mentions: Vec<Id>,
163    /// Review-log finding anchors (`<a id="r9">`), in document order.
164    pub findings: Vec<Finding>,
165    /// `§`-prefixed section references (e.g. `9.6`), for the positional-ref rule.
166    pub section_refs: Vec<String>,
167    /// Raw link destinations found in the body.
168    pub links: Vec<String>,
169    /// Ids named by body links — a typed citation (see [`crate::Relation::Link`]), so a
170    /// link to a superseded atom is an E3 error like any other structured edge.
171    pub link_refs: Vec<Id>,
172}
173
174#[derive(Clone, Debug)]
175pub struct Finding {
176    pub id: FindingId,
177    /// Findings carry their own inline status vocabulary (open/refuted/…); text in v0.
178    pub status: String,
179}
180
181#[derive(Clone, Debug)]
182pub struct Record {
183    pub id: Option<Id>,
184    pub path: DocPath,
185    pub kind: Kind,
186    pub lifecycle: Lifecycle,
187    pub authority: Authority,
188    pub last_reviewed: Date,
189    /// id-aliases: `id="D2", aka=["F2"]` — resolves the F↔D identity (schema-v0 §5).
190    pub aka: Vec<Id>,
191    pub edges: Edges,
192    pub facet: Facet,
193    pub body: Body,
194}
195
196impl Record {
197    /// Construct a record with empty edges/body and a blank `last_reviewed`.
198    /// A convenience for tests and embedding — the real builder is the Phase 2 parser.
199    pub fn minimal(
200        id: Option<Id>,
201        path: DocPath,
202        kind: Kind,
203        lifecycle: Lifecycle,
204        authority: Authority,
205        facet: Facet,
206    ) -> Self {
207        Record {
208            id,
209            path,
210            kind,
211            lifecycle,
212            authority,
213            last_reviewed: Date(String::new()),
214            aka: Vec::new(),
215            edges: Edges::default(),
216            facet,
217            body: Body::default(),
218        }
219    }
220}