1use 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}