1use omena_refinement_trait::{
4 RefinementVerdictV0, RefinementWitnessV0, refinement_provenance_v0, refinement_witness_v0,
5};
6
7use crate::{
8 CascadeDeclaration, CascadeLevel, LayerFlattenInputV0, ScopeFlattenInputV0,
9 StaticSupportsAssumptionV0, StaticSupportsEvalVerdictV0, evaluate_static_supports_condition,
10 prove_layer_flatten_candidate, prove_scope_flatten_candidate,
11};
12
13#[derive(Debug, Clone, PartialEq, Eq)]
14pub struct CascadeRefinementContextV0 {
15 pub supports_condition: Option<String>,
16 pub scope_root_selector: Option<String>,
17 pub layer_name: Option<String>,
18 pub closed_bundle: bool,
19}
20
21impl Default for CascadeRefinementContextV0 {
22 fn default() -> Self {
23 Self {
24 supports_condition: None,
25 scope_root_selector: None,
26 layer_name: None,
27 closed_bundle: true,
28 }
29 }
30}
31
32pub fn refine_declaration_in_context(
33 declaration: &CascadeDeclaration,
34 context: &CascadeRefinementContextV0,
35) -> RefinementWitnessV0 {
36 let mut provenances = Vec::new();
37 let mut verdicts = Vec::new();
38
39 if let Some(condition) = context.supports_condition.as_deref() {
40 let supports = evaluate_static_supports_condition(
41 condition,
42 StaticSupportsAssumptionV0::ModernBrowser,
43 );
44 provenances.push(refinement_provenance_v0(
45 "supports-predicate",
46 Some("evaluate_static_supports_condition"),
47 ));
48 verdicts.push(match supports.verdict {
49 StaticSupportsEvalVerdictV0::AlwaysTrue => RefinementVerdictV0::SatisfiedAll,
50 StaticSupportsEvalVerdictV0::AlwaysFalse => RefinementVerdictV0::Unsatisfiable,
51 StaticSupportsEvalVerdictV0::Unknown => RefinementVerdictV0::Unknown,
52 });
53 }
54
55 if let Some(root_selector) = context.scope_root_selector.as_deref() {
56 let scope = prove_scope_flatten_candidate(ScopeFlattenInputV0 {
57 root_selector: root_selector.to_string(),
58 limit_selector: None,
59 scoped_rule_count: 1,
60 peer_scope_count: 0,
61 competing_unscoped_rule_count: 0,
62 inside_layer: context.layer_name.is_some(),
63 });
64 provenances.push(refinement_provenance_v0(
65 "scope-predicate",
66 Some("prove_scope_flatten_candidate"),
67 ));
68 verdicts.push(if scope.accepted {
69 RefinementVerdictV0::SatisfiedAll
70 } else {
71 RefinementVerdictV0::Unknown
72 });
73 }
74
75 if context.layer_name.is_some() {
76 let layer = prove_layer_flatten_candidate(LayerFlattenInputV0 {
77 layer_name: context.layer_name.clone(),
78 layer_rule_count: 1,
79 peer_layer_count: 0,
80 unlayered_rule_count: 0,
81 important_declaration_count: usize::from(matches!(
82 declaration.key.level,
83 CascadeLevel::InlineImportant
84 | CascadeLevel::AuthorImportant
85 | CascadeLevel::UserImportant
86 | CascadeLevel::UserAgentImportant
87 )),
88 closed_bundle: context.closed_bundle,
89 });
90 provenances.push(refinement_provenance_v0(
91 "layer-predicate",
92 Some("prove_layer_flatten_candidate"),
93 ));
94 verdicts.push(if layer.accepted {
95 RefinementVerdictV0::SatisfiedAll
96 } else {
97 RefinementVerdictV0::Unknown
98 });
99 }
100
101 let verdict = combine_refinement_verdicts(&verdicts);
102 refinement_witness_v0("cascade-refinement-conjunction", verdict, provenances)
103}
104
105fn combine_refinement_verdicts(verdicts: &[RefinementVerdictV0]) -> RefinementVerdictV0 {
106 if verdicts.is_empty() {
107 return RefinementVerdictV0::SatisfiedAll;
108 }
109 if verdicts.contains(&RefinementVerdictV0::Unsatisfiable) {
110 return RefinementVerdictV0::Unsatisfiable;
111 }
112 if verdicts
113 .iter()
114 .all(|verdict| *verdict == RefinementVerdictV0::SatisfiedAll)
115 {
116 return RefinementVerdictV0::SatisfiedAll;
117 }
118 if verdicts.contains(&RefinementVerdictV0::SatisfiedAll) {
119 RefinementVerdictV0::SatisfiedSome
120 } else {
121 RefinementVerdictV0::Unknown
122 }
123}
124
125#[cfg(test)]
126mod tests {
127 use super::*;
128 use crate::{
129 CascadeKey, CascadeValue, LayerOrdinal, OpenWorldTieEvidence, Specificity,
130 SpecificityExactnessV0, normalized_layer_rank,
131 };
132
133 const EXPECTED_LEGACY_PROOFS_RS_SHA256: [u8; 32] = [
134 0x49, 0xeb, 0x0f, 0x3c, 0x85, 0x80, 0x45, 0x1d, 0x72, 0x9a, 0x57, 0x87, 0x88, 0x0f, 0xc1,
135 0xe1, 0x28, 0x04, 0xda, 0xf0, 0x46, 0x72, 0xd1, 0xcc, 0x4e, 0xd0, 0xab, 0x12, 0xdb, 0x6a,
136 0x7d, 0xb7,
137 ];
138
139 #[test]
140 fn legacy_proofs_rs_byte_untouched() {
141 let digest = sha256(include_bytes!("proofs.rs"));
142 assert_eq!(digest, EXPECTED_LEGACY_PROOFS_RS_SHA256);
143 }
144
145 #[test]
146 fn inline_important_declarations_block_layer_flattening() {
147 let Some(layer_ordinal) = LayerOrdinal::new(0) else {
148 unreachable!("zero must remain a sentinel-safe layer ordinal");
149 };
150 let declaration = CascadeDeclaration {
151 id: "inline-important".to_string(),
152 property: "color".to_string(),
153 value: CascadeValue::Literal("red".to_string()),
154 key: CascadeKey::new(
155 CascadeLevel::InlineImportant,
156 normalized_layer_rank(true, Some(layer_ordinal)),
157 0,
158 Specificity::ZERO,
159 1,
160 ),
161 open_world_tie_evidence: OpenWorldTieEvidence::NONE,
162 specificity_exactness: SpecificityExactnessV0::Exact,
163 };
164 let witness = refine_declaration_in_context(
165 &declaration,
166 &CascadeRefinementContextV0 {
167 layer_name: Some("theme".to_string()),
168 ..CascadeRefinementContextV0::default()
169 },
170 );
171
172 assert_eq!(witness.verdict, RefinementVerdictV0::Unknown);
175 }
176
177 fn sha256(input: &[u8]) -> [u8; 32] {
178 const H0: [u32; 8] = [
179 0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab,
180 0x5be0cd19,
181 ];
182 const K: [u32; 64] = [
183 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4,
184 0xab1c5ed5, 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe,
185 0x9bdc06a7, 0xc19bf174, 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f,
186 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7,
187 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967, 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc,
188 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, 0xa2bfe8a1, 0xa81a664b,
189 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, 0x19a4c116,
190 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
191 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7,
192 0xc67178f2,
193 ];
194
195 let mut bytes = input.to_vec();
196 let bit_len = (bytes.len() as u64) * 8;
197 bytes.push(0x80);
198 while bytes.len() % 64 != 56 {
199 bytes.push(0);
200 }
201 bytes.extend_from_slice(&bit_len.to_be_bytes());
202
203 let mut state = H0;
204 for chunk in bytes.chunks_exact(64) {
205 let mut w = [0u32; 64];
206 for (index, word) in chunk.chunks_exact(4).enumerate() {
207 w[index] = u32::from_be_bytes([word[0], word[1], word[2], word[3]]);
208 }
209 for index in 16..64 {
210 let s0 = w[index - 15].rotate_right(7)
211 ^ w[index - 15].rotate_right(18)
212 ^ (w[index - 15] >> 3);
213 let s1 = w[index - 2].rotate_right(17)
214 ^ w[index - 2].rotate_right(19)
215 ^ (w[index - 2] >> 10);
216 w[index] = w[index - 16]
217 .wrapping_add(s0)
218 .wrapping_add(w[index - 7])
219 .wrapping_add(s1);
220 }
221
222 let [mut a, mut b, mut c, mut d, mut e, mut f, mut g, mut h] = state;
223 for index in 0..64 {
224 let s1 = e.rotate_right(6) ^ e.rotate_right(11) ^ e.rotate_right(25);
225 let ch = (e & f) ^ ((!e) & g);
226 let temp1 = h
227 .wrapping_add(s1)
228 .wrapping_add(ch)
229 .wrapping_add(K[index])
230 .wrapping_add(w[index]);
231 let s0 = a.rotate_right(2) ^ a.rotate_right(13) ^ a.rotate_right(22);
232 let maj = (a & b) ^ (a & c) ^ (b & c);
233 let temp2 = s0.wrapping_add(maj);
234 h = g;
235 g = f;
236 f = e;
237 e = d.wrapping_add(temp1);
238 d = c;
239 c = b;
240 b = a;
241 a = temp1.wrapping_add(temp2);
242 }
243
244 for (slot, value) in state.iter_mut().zip([a, b, c, d, e, f, g, h]) {
245 *slot = slot.wrapping_add(value);
246 }
247 }
248
249 let mut digest = [0u8; 32];
250 for (chunk, word) in digest.chunks_exact_mut(4).zip(state) {
251 chunk.copy_from_slice(&word.to_be_bytes());
252 }
253 digest
254 }
255}