Skip to main content

omena_semantic/
observation.rs

1//! Theory-observation harness contracts for semantic milestones.
2//!
3//! The observation surface records whether selector identity, source evidence,
4//! semantic graph readiness, and coupling boundaries are present before a gate
5//! declares a semantic milestone ready.
6
7use std::collections::BTreeMap;
8
9use serde::Serialize;
10
11use crate::StyleSemanticGraphSummaryV0;
12
13#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
14#[serde(rename_all = "camelCase")]
15pub struct TheoryObservationHarnessSummaryV0 {
16    pub schema_version: &'static str,
17    pub product: &'static str,
18    pub graph_product: &'static str,
19    pub selector_identity: SelectorIdentityObservationV0,
20    pub source_evidence: SourceEvidenceObservationV0,
21    pub downstream_readiness: SemanticGraphDownstreamReadinessV0,
22    pub coupling_boundary: SemanticCouplingBoundaryObservationV0,
23    pub blocking_gaps: Vec<&'static str>,
24    pub next_priorities: Vec<&'static str>,
25}
26
27#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
28#[serde(rename_all = "camelCase")]
29pub struct TheoryObservationContractV0 {
30    pub schema_version: &'static str,
31    pub product: &'static str,
32    pub observation_product: &'static str,
33    pub ready: bool,
34    pub publish_ready: bool,
35    pub selector_identity_status: &'static str,
36    pub source_evidence_status: &'static str,
37    pub downstream_readiness_status: &'static str,
38    pub generic_observation_count: usize,
39    pub cme_coupled_observation_count: usize,
40    pub blocking_gaps: Vec<&'static str>,
41    pub publish_blocking_gaps: Vec<&'static str>,
42    pub observation_gaps: Vec<&'static str>,
43    pub next_priorities: Vec<&'static str>,
44}
45
46#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
47#[serde(rename_all = "camelCase")]
48pub struct SelectorIdentityObservationV0 {
49    pub status: &'static str,
50    pub observed_selector_count: usize,
51    pub rename_safe_selector_count: usize,
52    pub rewrite_blocked_selector_count: usize,
53    pub precise_rename_span_ready: bool,
54    pub rename_safe: bool,
55    pub blockers: Vec<&'static str>,
56}
57
58#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
59#[serde(rename_all = "camelCase")]
60pub struct SourceEvidenceObservationV0 {
61    pub status: &'static str,
62    pub reference_site_count: usize,
63    pub editable_direct_site_count: usize,
64    pub expression_count: usize,
65    pub explainable_certainty_reason_count: usize,
66    pub missing_certainty_reason_count: usize,
67    pub certainty_reason_counts: BTreeMap<String, usize>,
68    pub cme_coupled: bool,
69}
70
71#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
72#[serde(rename_all = "camelCase")]
73pub struct SemanticGraphDownstreamReadinessV0 {
74    pub status: &'static str,
75    pub semantic_graph_ready: bool,
76    pub downstream_check_ready: bool,
77    pub precise_rename_ready: bool,
78    pub formatter_ready: bool,
79    pub recovery_diagnostics_observed: bool,
80    pub blocking_gap_count: usize,
81}
82
83#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
84#[serde(rename_all = "camelCase")]
85pub struct SemanticCouplingBoundaryObservationV0 {
86    pub status: &'static str,
87    pub generic_observation_count: usize,
88    pub cme_coupled_observation_count: usize,
89    pub generic_surfaces: Vec<&'static str>,
90    pub cme_coupled_surfaces: Vec<&'static str>,
91    pub split_recommendation: &'static str,
92}
93
94pub trait TheoryObservationHarnessInput {
95    fn summarize_theory_observation_harness(&self) -> TheoryObservationHarnessSummaryV0;
96
97    fn summarize_theory_observation_contract(&self) -> TheoryObservationContractV0 {
98        summarize_theory_observation_contract(self)
99    }
100}
101
102impl TheoryObservationHarnessInput for StyleSemanticGraphSummaryV0 {
103    fn summarize_theory_observation_harness(&self) -> TheoryObservationHarnessSummaryV0 {
104        summarize_style_semantic_graph_observation(self)
105    }
106}
107
108pub fn summarize_theory_observation_harness<T>(input: &T) -> TheoryObservationHarnessSummaryV0
109where
110    T: TheoryObservationHarnessInput + ?Sized,
111{
112    input.summarize_theory_observation_harness()
113}
114
115pub fn summarize_theory_observation_contract<T>(input: &T) -> TheoryObservationContractV0
116where
117    T: TheoryObservationHarnessInput + ?Sized,
118{
119    summarize_theory_observation_contract_from_summary(
120        &input.summarize_theory_observation_harness(),
121    )
122}
123
124fn summarize_style_semantic_graph_observation(
125    graph: &StyleSemanticGraphSummaryV0,
126) -> TheoryObservationHarnessSummaryV0 {
127    let selector_identity = observe_selector_identity(graph);
128    let source_evidence = observe_source_evidence(graph);
129    let downstream_readiness = observe_downstream_readiness(graph, &selector_identity);
130    let coupling_boundary = observe_coupling_boundary(graph);
131    let mut blocking_gaps = Vec::new();
132
133    if selector_identity.status != "ready" {
134        blocking_gaps.push("selectorRewriteSafety");
135    }
136    if source_evidence.status != "ready" {
137        blocking_gaps.push("sourceEvidence");
138    }
139    if downstream_readiness.status != "ready" {
140        blocking_gaps.push("downstreamReadiness");
141    }
142
143    let next_priorities = if blocking_gaps.is_empty() {
144        vec!["externalCorpus", "traitDogfooding"]
145    } else {
146        blocking_gaps.clone()
147    };
148
149    TheoryObservationHarnessSummaryV0 {
150        schema_version: "0",
151        product: "omena-semantic.theory-observation-harness",
152        graph_product: graph.product,
153        selector_identity,
154        source_evidence,
155        downstream_readiness,
156        coupling_boundary,
157        blocking_gaps,
158        next_priorities,
159    }
160}
161
162fn summarize_theory_observation_contract_from_summary(
163    observation: &TheoryObservationHarnessSummaryV0,
164) -> TheoryObservationContractV0 {
165    let publish_blocking_gaps = publish_blocking_gaps_for_observation(observation);
166    let observation_gaps = observation
167        .blocking_gaps
168        .iter()
169        .copied()
170        .filter(|gap| !publish_blocking_gaps.contains(gap))
171        .collect::<Vec<_>>();
172
173    TheoryObservationContractV0 {
174        schema_version: "0",
175        product: "omena-semantic.theory-observation-contract",
176        observation_product: observation.product,
177        ready: observation.blocking_gaps.is_empty(),
178        publish_ready: publish_blocking_gaps.is_empty(),
179        selector_identity_status: observation.selector_identity.status,
180        source_evidence_status: observation.source_evidence.status,
181        downstream_readiness_status: observation.downstream_readiness.status,
182        generic_observation_count: observation.coupling_boundary.generic_observation_count,
183        cme_coupled_observation_count: observation.coupling_boundary.cme_coupled_observation_count,
184        blocking_gaps: observation.blocking_gaps.clone(),
185        publish_blocking_gaps,
186        observation_gaps,
187        next_priorities: observation.next_priorities.clone(),
188    }
189}
190
191fn publish_blocking_gaps_for_observation(
192    observation: &TheoryObservationHarnessSummaryV0,
193) -> Vec<&'static str> {
194    let mut gaps = Vec::new();
195
196    if observation.selector_identity.status != "ready" {
197        gaps.push("selectorRewriteSafety");
198    }
199    if observation.coupling_boundary.generic_observation_count == 0 {
200        gaps.push("genericObservationBoundary");
201    }
202
203    gaps
204}
205
206fn observe_selector_identity(graph: &StyleSemanticGraphSummaryV0) -> SelectorIdentityObservationV0 {
207    let observed_selector_count = graph.selector_identity_engine.canonical_id_count;
208    let rewrite_blocked_selector_count = graph
209        .selector_identity_engine
210        .rewrite_safety
211        .blocked_canonical_ids
212        .len();
213    let rename_safe_selector_count = graph
214        .selector_identity_engine
215        .rewrite_safety
216        .safe_canonical_ids
217        .len();
218    let precise_rename_span_ready = graph
219        .lossless_cst_contract
220        .consumer_readiness
221        .precise_rename_base_ready;
222    let rename_safe = observed_selector_count > 0
223        && rewrite_blocked_selector_count == 0
224        && precise_rename_span_ready;
225
226    SelectorIdentityObservationV0 {
227        status: if observed_selector_count == 0 {
228            "gap"
229        } else if rename_safe {
230            "ready"
231        } else {
232            "partial"
233        },
234        observed_selector_count,
235        rename_safe_selector_count,
236        rewrite_blocked_selector_count,
237        precise_rename_span_ready,
238        rename_safe,
239        blockers: graph
240            .selector_identity_engine
241            .rewrite_safety
242            .blockers
243            .clone(),
244    }
245}
246
247fn observe_source_evidence(graph: &StyleSemanticGraphSummaryV0) -> SourceEvidenceObservationV0 {
248    let evidence = &graph.source_input_evidence;
249    let expression_count = evidence.certainty_reason.expression_count;
250    let missing_certainty_reason_count = evidence.certainty_reason.missing_reason_count;
251    let explainable_certainty_reason_count =
252        expression_count.saturating_sub(missing_certainty_reason_count);
253    let source_observed =
254        evidence.reference_site_identity.reference_site_count > 0 || expression_count > 0;
255    let source_ready = evidence.reference_site_identity.status == "ready"
256        && evidence.certainty_reason.status == "ready"
257        && evidence.binding_origin.status == "ready"
258        && evidence.style_module_edge.status == "ready"
259        && evidence.value_domain_explanation.status == "ready";
260
261    SourceEvidenceObservationV0 {
262        status: if source_ready {
263            "ready"
264        } else if source_observed {
265            "partial"
266        } else {
267            "gap"
268        },
269        reference_site_count: evidence.reference_site_identity.reference_site_count,
270        editable_direct_site_count: evidence.reference_site_identity.editable_direct_site_count,
271        expression_count,
272        explainable_certainty_reason_count,
273        missing_certainty_reason_count,
274        certainty_reason_counts: evidence.certainty_reason.reason_counts.clone(),
275        cme_coupled: true,
276    }
277}
278
279fn observe_downstream_readiness(
280    graph: &StyleSemanticGraphSummaryV0,
281    selector_identity: &SelectorIdentityObservationV0,
282) -> SemanticGraphDownstreamReadinessV0 {
283    let semantic_graph_ready = graph.product == "omena-semantic.style-semantic-graph"
284        && graph.promotion_evidence.blocking_gaps.is_empty()
285        && graph.selector_identity_engine.canonical_id_count > 0
286        && graph
287            .lossless_cst_contract
288            .span_invariants
289            .byte_span_contract_ready;
290    let downstream_check_ready = semantic_graph_ready && selector_identity.status != "gap";
291    let precise_rename_ready = downstream_check_ready && selector_identity.rename_safe;
292    let formatter_ready = graph
293        .lossless_cst_contract
294        .consumer_readiness
295        .formatter_base_ready;
296
297    SemanticGraphDownstreamReadinessV0 {
298        status: if downstream_check_ready && precise_rename_ready {
299            "ready"
300        } else if semantic_graph_ready {
301            "partial"
302        } else {
303            "gap"
304        },
305        semantic_graph_ready,
306        downstream_check_ready,
307        precise_rename_ready,
308        formatter_ready,
309        recovery_diagnostics_observed: graph
310            .lossless_cst_contract
311            .consumer_readiness
312            .recovery_diagnostics_observed,
313        blocking_gap_count: graph.promotion_evidence.blocking_gaps.len(),
314    }
315}
316
317fn observe_coupling_boundary(
318    graph: &StyleSemanticGraphSummaryV0,
319) -> SemanticCouplingBoundaryObservationV0 {
320    let generic_surfaces = vec![
321        "parserSemanticFacts",
322        "designTokenSemantics",
323        "selectorIdentity",
324        "losslessCstContract",
325    ];
326    let cme_coupled_surfaces = vec!["sourceInputEvidence", "promotionEvidenceWithSourceInput"];
327    let cme_coupled_observation_count = if graph.source_input_evidence.input_version.is_empty() {
328        0
329    } else {
330        cme_coupled_surfaces.len()
331    };
332    let split_recommendation = if cme_coupled_observation_count == 0 {
333        "keep-integrated-source-gap"
334    } else {
335        "keep-integrated-observe-boundary"
336    };
337
338    SemanticCouplingBoundaryObservationV0 {
339        status: "ready",
340        generic_observation_count: generic_surfaces.len(),
341        cme_coupled_observation_count,
342        generic_surfaces,
343        cme_coupled_surfaces,
344        split_recommendation,
345    }
346}