1use std::{
2 collections::{BTreeMap, BTreeSet, VecDeque},
3 ffi::OsString,
4 fs,
5 path::{Component, Path, PathBuf},
6 sync::{Mutex, OnceLock},
7};
8
9use crate::bundler_config_alias::load_omena_bridge_workspace_bundler_path_alias_mappings;
10use crate::external_sif_signature::verify_omena_external_sif_keyless_bundle;
11use omena_resolver::{
12 OmenaResolverBundlerPathAliasMappingV0, OmenaResolverStyleModuleConfirmationOptionsV0,
13 OmenaResolverStyleModuleDiskCandidateIdentityV0, OmenaResolverStylePackageManifestV0,
14 OmenaResolverTsconfigPathMappingV0,
15 collect_omena_resolver_style_module_source_candidates_with_path_mappings,
16 confirm_omena_resolver_style_module_candidate_with_options,
17 is_omena_resolver_indexable_style_module_path,
18 normalize_omena_resolver_style_module_source_for_routing,
19};
20use omena_sif::{
21 OMENA_SIF_PUBLISHED_ATTESTATION_SUBJECT_PRODUCT_V1,
22 OMENA_SIF_PUBLISHED_ATTESTATION_SUBJECT_SCHEMA_VERSION_V1,
23 OMENA_SIF_SHARD_TRUST_ENVELOPE_PRODUCT_V1, OMENA_SIF_SHARD_TRUST_ENVELOPE_SCHEMA_VERSION_V1,
24 OMENA_SIF_SHARD_VERDICT_DIR_V1, OmenaLifExportsV1, OmenaSifPublishedAttestationSubjectV1,
25 OmenaSifShardLockBindingV1, OmenaSifShardRecordedVerdictV1, OmenaSifShardTrustEnvelopeV1,
26 OmenaSifSourceSyntaxV1, OmenaSifStaticGeneratorInputV1, OmenaSifTrustTierV1, OmenaSifV1,
27 compute_omena_sif_artifact_hash_v1, compute_omena_sif_leaf_hash_v1,
28 compute_omena_sif_shard_recorded_verdict_address_v1, generate_static_omena_lif_exports_v1,
29 generate_static_omena_sif_v1, read_omena_sif_json_v1,
30 read_omena_sif_shard_recorded_verdict_json_v1, read_omena_sif_shard_trust_envelope_json_v1,
31 validate_omena_sif_published_attestation_subject_v1, write_omena_canonical_json_bytes_v1,
32 write_omena_sif_json_v1, write_omena_sif_published_attestation_subject_json_v1,
33};
34use serde::Serialize;
35use serde_json::{Value, json};
36
37const WORKSPACE_PACKAGE_MANIFEST_SCAN_LIMIT: usize = 1024;
38const EXTERNAL_SIF_CACHE_SCHEMA_VERSION: &str = "1";
39const EXTERNAL_SIF_CACHE_LEGACY_SCHEMA_VERSION: &str = "0";
40const EXTERNAL_SIF_CACHE_KEY_SCHEMA_VERSION: &str = "0";
41const EXTERNAL_SIF_CACHE_PRODUCT: &str = "omena-bridge.external-sif-cache-shard";
42const EXTERNAL_SIF_CACHE_DIR: &str = "external-sif-v0";
43const EXTERNAL_SIF_CACHE_ENV_KILL_SWITCH: &str = "OMENA_BRIDGE_EXTERNAL_SIF_CACHE";
44const EXTERNAL_SIF_CACHE_MAX_MEMORY_ENTRIES: usize = 256;
45const EXTERNAL_SIF_CACHE_MAX_SHARDS: usize = 2048;
46const EXTERNAL_SIF_CACHE_MAX_TOTAL_BYTES: u64 = 256 * 1024 * 1024;
47const EXTERNAL_SIF_CACHE_MAX_SHARD_BYTES: u64 = 8 * 1024 * 1024;
48const EXTERNAL_SIF_RECORDED_BUNDLE_DIR_V1: &str = "bundles-v1";
49const EXTERNAL_SIF_RECORDED_BUNDLE_SUFFIX_V1: &str = ".sigstore.json";
50const EXTERNAL_SIF_RECORDED_BUNDLE_MAX_BYTES: u64 = 4 * 1024 * 1024;
51const EXTERNAL_SIF_RECORDED_VERDICT_SCAN_LIMIT: usize = 4096;
52const WORKSPACE_STYLE_PATH_IDENTITY_SCAN_LIMIT: usize = 4096;
53const WORKSPACE_STYLE_PATH_IDENTITY_MAX_DEPTH: usize = 8;
54
55static EXTERNAL_SIF_MEMORY_CACHE: OnceLock<
56 Mutex<BTreeMap<String, OmenaBridgeExternalSifWithTrustV1>>,
57> = OnceLock::new();
58
59#[cfg(test)]
60static EXTERNAL_SIF_LOCAL_REGENERATION_COUNTS: OnceLock<Mutex<BTreeMap<String, usize>>> =
61 OnceLock::new();
62
63#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
64#[serde(rename_all = "camelCase")]
65pub struct OmenaBridgeStyleResolutionSummaryV0 {
66 pub schema_version: &'static str,
67 pub product: &'static str,
68 pub owner_crate: &'static str,
69 pub resolver_name: &'static str,
70 pub supported_specifier_kinds: Vec<&'static str>,
71 pub candidate_extensions: Vec<&'static str>,
72 pub request_path_policy: Vec<&'static str>,
73}
74
75#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize)]
76#[serde(rename_all = "camelCase")]
77pub struct OmenaBridgeStyleResolutionInputsV0 {
78 pub package_manifests: Vec<OmenaResolverStylePackageManifestV0>,
79 pub tsconfig_path_mappings: Vec<OmenaResolverTsconfigPathMappingV0>,
80 pub bundler_path_mappings: Vec<OmenaResolverBundlerPathAliasMappingV0>,
81 #[serde(skip_serializing_if = "Vec::is_empty")]
82 pub disk_style_path_identities: Vec<OmenaResolverStyleModuleDiskCandidateIdentityV0>,
83}
84
85#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize)]
86#[serde(rename_all = "camelCase")]
87pub struct OmenaBridgeExternalSifCacheContextV0 {
88 #[serde(skip_serializing_if = "Option::is_none")]
89 pub freshness_fingerprint: Option<String>,
90}
91
92#[derive(Debug, Clone, PartialEq, Eq)]
93pub struct OmenaBridgeExternalSifStorageV0 {
94 workspace_cache_root: PathBuf,
95 workspace_identity: Option<String>,
96 recorded_verdict_dir: Option<PathBuf>,
97}
98
99impl OmenaBridgeExternalSifStorageV0 {
100 pub fn from_workspace_cache_root(workspace_cache_root: PathBuf) -> Self {
101 Self {
102 recorded_verdict_dir: Some(workspace_cache_root.join(OMENA_SIF_SHARD_VERDICT_DIR_V1)),
103 workspace_cache_root,
104 workspace_identity: None,
105 }
106 }
107
108 pub fn from_workspace_cache_root_and_identity(
109 workspace_cache_root: PathBuf,
110 workspace_identity: impl Into<String>,
111 ) -> Self {
112 Self {
113 recorded_verdict_dir: Some(workspace_cache_root.join(OMENA_SIF_SHARD_VERDICT_DIR_V1)),
114 workspace_cache_root,
115 workspace_identity: Some(workspace_identity.into()),
116 }
117 }
118
119 pub fn workspace_cache_root(&self) -> &Path {
120 self.workspace_cache_root.as_path()
121 }
122
123 pub fn with_recorded_verdict_dir(mut self, recorded_verdict_dir: PathBuf) -> Self {
124 self.recorded_verdict_dir = Some(recorded_verdict_dir);
125 self
126 }
127
128 pub fn recorded_verdict_dir(&self) -> Option<&Path> {
129 self.recorded_verdict_dir.as_deref()
130 }
131}
132
133#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
134#[serde(rename_all = "camelCase")]
135pub enum OmenaBridgeExternalSifTrustSourceV1 {
136 RecordedVerdict,
137 UnsignedLegacy,
138}
139
140#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
141#[serde(rename_all = "camelCase")]
142pub struct OmenaBridgeExternalSifWithTrustV1 {
143 pub sif: OmenaSifV1,
144 pub trust_envelope: OmenaSifShardTrustEnvelopeV1,
145 pub trust_source: OmenaBridgeExternalSifTrustSourceV1,
146}
147
148#[derive(Debug, Clone, Copy, PartialEq, Eq)]
149pub enum OmenaBridgeExternalSifShardRefusalV1 {
150 ShardIdentityMismatch,
151 CanonicalUrlMismatch,
152 LocalRegenerationMismatch,
153 PayloadDigestMismatch,
154 MalformedSif,
155 MissingTrustEnvelope,
156 InvalidTrustEnvelope,
157 LockBindingMismatch,
158 MissingRecordedVerdict,
159 RecordedVerdictMismatch,
160 TierAboveRecordedVerdict,
161 RecordedVerdictDowngrade,
162 RecordedVerdictSignatureVerificationFailed,
163}
164
165impl OmenaBridgeExternalSifShardRefusalV1 {
166 pub fn code(self) -> &'static str {
167 match self {
168 Self::ShardIdentityMismatch => "shardIdentityMismatch",
169 Self::CanonicalUrlMismatch => "canonicalUrlMismatch",
170 Self::LocalRegenerationMismatch => "localRegenerationMismatch",
171 Self::PayloadDigestMismatch => "payloadDigestMismatch",
172 Self::MalformedSif => "malformedSif",
173 Self::MissingTrustEnvelope => "missingTrustEnvelope",
174 Self::InvalidTrustEnvelope => "invalidTrustEnvelope",
175 Self::LockBindingMismatch => "lockBindingMismatch",
176 Self::MissingRecordedVerdict => "missingRecordedVerdict",
177 Self::RecordedVerdictMismatch => "recordedVerdictMismatch",
178 Self::TierAboveRecordedVerdict => "tierAboveRecordedVerdict",
179 Self::RecordedVerdictDowngrade => "recordedVerdictDowngrade",
180 Self::RecordedVerdictSignatureVerificationFailed => {
181 "recordedVerdictSignatureVerificationFailed"
182 }
183 }
184 }
185}
186
187pub fn summarize_omena_bridge_style_resolution_boundary() -> OmenaBridgeStyleResolutionSummaryV0 {
188 OmenaBridgeStyleResolutionSummaryV0 {
189 schema_version: "0",
190 product: "omena-bridge.style-resolution",
191 owner_crate: "omena-bridge",
192 resolver_name: "style-import-specifier-resolver",
193 supported_specifier_kinds: vec![
194 "relative",
195 "tsconfigPaths",
196 "jsconfigPaths",
197 "bundlerAliases",
198 "npmPackages",
199 "packageImports",
200 ],
201 candidate_extensions: vec!["scss", "sass", "css", "less"],
202 request_path_policy: vec![
203 "resolverConsumesSourceUriWorkspaceUriAndRawSpecifier",
204 "relativeSpecifierExpandsStyleModuleCandidates",
205 "pathAliasResolutionUsesNearestWorkspaceTsconfigOrJsconfig",
206 "pathAliasResolutionFollowsRelativeTsconfigExtends",
207 "bundlerAliasResolutionUsesLiteralViteWebpackConfig",
208 "packageSpecifierResolutionUsesOmenaResolver",
209 "fileUriOutputIsPercentEncoded",
210 "lspServerOwnsOnlyDocumentRoutingAndUriRangeMapping",
211 ],
212 }
213}
214
215pub fn resolve_omena_bridge_style_uri_for_specifier(
216 source_uri: &str,
217 workspace_folder_uri: Option<&str>,
218 specifier: &str,
219) -> Option<String> {
220 resolve_omena_bridge_style_uri_for_specifier_with_package_manifests(
221 source_uri,
222 workspace_folder_uri,
223 specifier,
224 &[],
225 )
226}
227
228pub fn resolve_omena_bridge_style_uri_for_specifier_with_package_manifests(
229 source_uri: &str,
230 workspace_folder_uri: Option<&str>,
231 specifier: &str,
232 configured_package_manifests: &[OmenaResolverStylePackageManifestV0],
233) -> Option<String> {
234 let source_path = normalize_path(file_uri_to_path(source_uri)?);
235 let workspace_path = workspace_folder_uri
236 .and_then(file_uri_to_path)
237 .map(normalize_path);
238 let package_manifests = merged_package_manifests_for_request(
239 source_path.parent(),
240 workspace_path.as_deref(),
241 specifier,
242 configured_package_manifests,
243 );
244 let inputs = OmenaBridgeStyleResolutionInputsV0 {
245 package_manifests,
246 tsconfig_path_mappings: tsconfig_path_mappings_for_workspace(workspace_path.as_deref())
247 .unwrap_or_default(),
248 bundler_path_mappings: load_omena_bridge_workspace_bundler_path_alias_mappings(
249 workspace_path.as_deref(),
250 ),
251 disk_style_path_identities: workspace_path
252 .as_deref()
253 .map(workspace_style_path_identities)
254 .unwrap_or_default(),
255 };
256 resolve_omena_bridge_style_uri_for_specifier_with_resolution_inputs(
257 source_uri,
258 workspace_folder_uri,
259 specifier,
260 &inputs,
261 )
262}
263
264pub fn resolve_omena_bridge_style_uri_for_specifier_with_resolution_inputs(
265 source_uri: &str,
266 _workspace_folder_uri: Option<&str>,
267 specifier: &str,
268 resolution_inputs: &OmenaBridgeStyleResolutionInputsV0,
269) -> Option<String> {
270 let source_path = normalize_path(file_uri_to_path(source_uri)?);
271 let source_path_text = source_path.to_string_lossy().to_string();
272 let routing_specifier = normalize_omena_resolver_style_module_source_for_routing(specifier);
273 let requires_existing_candidate = (package_name_from_specifier(routing_specifier).is_some()
274 || is_package_import_specifier(routing_specifier))
275 && !resolution_inputs
276 .tsconfig_path_mappings
277 .iter()
278 .any(|mapping| {
279 tsconfig_path_pattern_matches(mapping.pattern.as_str(), routing_specifier)
280 })
281 && !resolution_inputs
282 .bundler_path_mappings
283 .iter()
284 .any(|mapping| {
285 bundler_path_alias_pattern_matches(mapping.pattern.as_str(), routing_specifier)
286 });
287 let candidates = collect_omena_resolver_style_module_source_candidates_with_path_mappings(
288 source_path_text.as_str(),
289 specifier,
290 resolution_inputs.package_manifests.as_slice(),
291 resolution_inputs.bundler_path_mappings.as_slice(),
292 resolution_inputs.tsconfig_path_mappings.as_slice(),
293 );
294
295 style_uri_for_resolver_candidates(
296 candidates.as_slice(),
297 resolution_inputs.disk_style_path_identities.as_slice(),
298 requires_existing_candidate,
299 )
300}
301
302pub fn generate_omena_bridge_sif_for_resolved_style_path(
315 resolved_path: &str,
316) -> Result<OmenaSifV1, String> {
317 generate_omena_bridge_sif_for_resolved_style_path_with_cache_context(
318 resolved_path,
319 &OmenaBridgeExternalSifCacheContextV0::default(),
320 )
321}
322
323pub fn generate_omena_bridge_lif_exports_for_resolved_style_path(
324 resolved_path: &str,
325) -> Result<OmenaLifExportsV1, String> {
326 let raw_path = raw_resolved_style_entry_path(resolved_path)
327 .ok_or_else(|| format!("unresolvable style module entry path: {resolved_path}"))?;
328 let path = normalize_path(raw_path);
329 let source = fs::read_to_string(path.as_path()).map_err(|error| {
330 format!(
331 "failed to read resolved style module {}: {error}",
332 path.to_string_lossy()
333 )
334 })?;
335 let syntax = infer_omena_bridge_sif_source_syntax(path.as_path());
336 Ok(generate_static_omena_lif_exports_v1(
337 OmenaSifStaticGeneratorInputV1 {
338 canonical_url: path_to_file_uri(path.as_path()).as_str(),
339 source: source.as_str(),
340 syntax,
341 },
342 ))
343}
344
345pub fn generate_omena_bridge_sif_for_resolved_style_path_with_cache_context(
346 resolved_path: &str,
347 cache_context: &OmenaBridgeExternalSifCacheContextV0,
348) -> Result<OmenaSifV1, String> {
349 generate_omena_bridge_sif_for_resolved_style_path_with_cache_context_and_storage(
350 resolved_path,
351 cache_context,
352 None,
353 )
354}
355
356pub fn generate_omena_bridge_sif_for_resolved_style_path_with_cache_context_and_storage(
357 resolved_path: &str,
358 cache_context: &OmenaBridgeExternalSifCacheContextV0,
359 cache_storage: Option<&OmenaBridgeExternalSifStorageV0>,
360) -> Result<OmenaSifV1, String> {
361 generate_omena_bridge_sif_for_resolved_style_path_with_cache_context_storage_and_trust(
362 resolved_path,
363 cache_context,
364 cache_storage,
365 )
366 .map(|result| result.sif)
367}
368
369pub fn generate_omena_bridge_sif_for_resolved_style_path_with_cache_context_storage_and_trust(
370 resolved_path: &str,
371 cache_context: &OmenaBridgeExternalSifCacheContextV0,
372 cache_storage: Option<&OmenaBridgeExternalSifStorageV0>,
373) -> Result<OmenaBridgeExternalSifWithTrustV1, String> {
374 generate_omena_bridge_sif_for_resolved_style_path_with_canonical_url_impl(
375 resolved_path,
376 None,
377 cache_context,
378 cache_storage,
379 )
380}
381
382pub fn generate_omena_bridge_sif_for_resolved_style_path_with_canonical_url_cache_context_storage_and_trust(
383 resolved_path: &str,
384 canonical_url: &str,
385 cache_context: &OmenaBridgeExternalSifCacheContextV0,
386 cache_storage: Option<&OmenaBridgeExternalSifStorageV0>,
387) -> Result<OmenaBridgeExternalSifWithTrustV1, String> {
388 if canonical_url.trim().is_empty() {
389 return Err("external SIF canonical URL must not be empty".to_string());
390 }
391 generate_omena_bridge_sif_for_resolved_style_path_with_canonical_url_impl(
392 resolved_path,
393 Some(canonical_url),
394 cache_context,
395 cache_storage,
396 )
397}
398
399fn generate_omena_bridge_sif_for_resolved_style_path_with_canonical_url_impl(
400 resolved_path: &str,
401 canonical_url_override: Option<&str>,
402 cache_context: &OmenaBridgeExternalSifCacheContextV0,
403 cache_storage: Option<&OmenaBridgeExternalSifStorageV0>,
404) -> Result<OmenaBridgeExternalSifWithTrustV1, String> {
405 let raw_path = raw_resolved_style_entry_path(resolved_path)
406 .ok_or_else(|| format!("unresolvable style module entry path: {resolved_path}"))?;
407 let path = normalize_path(raw_path.clone());
408 let canonical_url = canonical_url_override
409 .map(ToOwned::to_owned)
410 .unwrap_or_else(|| path_to_file_uri(path.as_path()));
411 let source_bytes = fs::read(path.as_path()).map_err(|error| {
412 format!(
413 "failed to read resolved style module {}: {error}",
414 path.to_string_lossy()
415 )
416 })?;
417 let source_hash = compute_omena_sif_leaf_hash_v1(source_bytes.as_slice())
418 .as_str()
419 .to_string();
420 let resolved_base_dir = path
421 .parent()
422 .map(|base_dir| base_dir.to_string_lossy().to_string())
423 .unwrap_or_default();
424 let cache_key = external_sif_cache_key(
425 source_hash.as_str(),
426 resolved_base_dir.as_str(),
427 canonical_url.as_str(),
428 cache_context.freshness_fingerprint.as_deref(),
429 );
430 let cache_enabled = !external_sif_cache_kill_switch_engaged();
431 let cache_dir = cache_enabled
432 .then(|| external_sif_cache_dir_for_path(raw_path.as_path(), cache_storage))
433 .flatten();
434 let cache_workspace_identity = cache_storage
435 .and_then(|storage| storage.workspace_identity.clone())
436 .or_else(|| {
437 crate::cache_root::external_sif_workspace_root(raw_path.as_path())
438 .map(|root| root.to_string_lossy().into_owned())
439 });
440 let recorded_verdict_dir =
441 external_sif_recorded_verdict_dir_for_path(raw_path.as_path(), cache_storage);
442 let memory_cache_key = external_sif_memory_cache_key(cache_key.as_str(), cache_dir.as_deref());
443 if cache_enabled
444 && let Some(result) = load_external_sif_from_memory_cache(memory_cache_key.as_str())
445 {
446 if result.sif.canonical_url == canonical_url
447 && result.sif.fingerprints.leaf_hash.as_str() == source_hash
448 {
449 let result = external_sif_result_with_recorded_verdict(
450 result.sif,
451 recorded_verdict_dir.as_deref(),
452 )?;
453 store_external_sif_in_memory_cache(memory_cache_key.clone(), result.clone());
454 return Ok(result);
455 }
456 remove_external_sif_from_memory_cache(memory_cache_key.as_str());
457 }
458 let source = String::from_utf8(source_bytes).map_err(|error| {
459 format!(
460 "failed to decode resolved style module {} as utf-8: {error}",
461 path.to_string_lossy()
462 )
463 })?;
464 let syntax = infer_omena_bridge_sif_source_syntax(path.as_path());
465 let locally_regenerated_sif =
466 generate_local_external_sif(canonical_url.as_str(), source.as_str(), syntax)?;
467 if cache_enabled
468 && let Some(cache_dir) = cache_dir.as_deref()
469 && let Some(result) = load_external_sif_cache_shard(
470 cache_dir,
471 cache_key.as_str(),
472 canonical_url.as_str(),
473 source_hash.as_str(),
474 resolved_base_dir.as_str(),
475 recorded_verdict_dir.as_deref(),
476 &locally_regenerated_sif,
477 )
478 {
479 store_external_sif_in_memory_cache(memory_cache_key.clone(), result.clone());
480 return Ok(result);
481 }
482 let result = external_sif_result_with_recorded_verdict(
483 locally_regenerated_sif,
484 recorded_verdict_dir.as_deref(),
485 )?;
486 if cache_enabled {
487 store_external_sif_in_memory_cache(memory_cache_key, result.clone());
488 if let Some(cache_dir) = cache_dir.as_deref() {
489 store_external_sif_cache_shard(
490 cache_dir,
491 cache_key.as_str(),
492 canonical_url.as_str(),
493 source_hash.as_str(),
494 resolved_base_dir.as_str(),
495 &result,
496 cache_workspace_identity.as_deref(),
497 );
498 }
499 }
500 Ok(result)
501}
502
503fn generate_local_external_sif(
504 canonical_url: &str,
505 source: &str,
506 syntax: OmenaSifSourceSyntaxV1,
507) -> Result<OmenaSifV1, String> {
508 #[cfg(test)]
509 if let Ok(mut counts) = EXTERNAL_SIF_LOCAL_REGENERATION_COUNTS
510 .get_or_init(|| Mutex::new(BTreeMap::new()))
511 .lock()
512 {
513 let count = counts.entry(canonical_url.to_string()).or_default();
514 *count = count.saturating_add(1);
515 }
516 generate_static_omena_sif_v1(OmenaSifStaticGeneratorInputV1 {
517 canonical_url,
518 source,
519 syntax,
520 })
521 .map_err(|error| format!("failed to generate SIF for {canonical_url}: {error}"))
522}
523
524fn raw_resolved_style_entry_path(resolved_path: &str) -> Option<PathBuf> {
525 let path = if resolved_path.starts_with("file://") {
526 file_uri_to_path(resolved_path)?
527 } else if resolved_path.is_empty() {
528 return None;
529 } else {
530 PathBuf::from(resolved_path)
531 };
532 Some(normalize_path_lexical(path))
533}
534
535fn external_sif_cache_key(
536 source_hash: &str,
537 resolved_base_dir: &str,
538 canonical_url: &str,
539 freshness_fingerprint: Option<&str>,
540) -> String {
541 external_sif_cache_key_with_crate_version(
542 source_hash,
543 resolved_base_dir,
544 canonical_url,
545 freshness_fingerprint,
546 env!("CARGO_PKG_VERSION"),
547 )
548}
549
550fn external_sif_cache_key_with_crate_version(
551 source_hash: &str,
552 resolved_base_dir: &str,
553 canonical_url: &str,
554 freshness_fingerprint: Option<&str>,
555 crate_version: &str,
556) -> String {
557 let input = json!({
558 "schemaVersion": EXTERNAL_SIF_CACHE_KEY_SCHEMA_VERSION,
559 "product": "omena-bridge.external-sif-cache-key",
560 "crateVersion": crate_version,
561 "sourceHash": source_hash,
562 "resolvedBaseDir": resolved_base_dir,
563 "canonicalUrl": canonical_url,
564 "freshnessFingerprint": freshness_fingerprint,
565 });
566 write_omena_canonical_json_bytes_v1(&input)
567 .map(|bytes| {
568 compute_omena_sif_leaf_hash_v1(bytes.as_slice())
569 .as_str()
570 .to_string()
571 })
572 .unwrap_or_else(|_| {
573 compute_omena_sif_leaf_hash_v1(
574 format!(
575 "{crate_version}\0{source_hash}\0{resolved_base_dir}\0{canonical_url}\0{}",
576 freshness_fingerprint.unwrap_or("")
577 )
578 .as_bytes(),
579 )
580 .as_str()
581 .to_string()
582 })
583}
584
585fn load_external_sif_from_memory_cache(key: &str) -> Option<OmenaBridgeExternalSifWithTrustV1> {
586 EXTERNAL_SIF_MEMORY_CACHE
587 .get_or_init(|| Mutex::new(BTreeMap::new()))
588 .lock()
589 .ok()?
590 .get(key)
591 .cloned()
592}
593
594fn store_external_sif_in_memory_cache(key: String, result: OmenaBridgeExternalSifWithTrustV1) {
595 let Ok(mut cache) = EXTERNAL_SIF_MEMORY_CACHE
596 .get_or_init(|| Mutex::new(BTreeMap::new()))
597 .lock()
598 else {
599 return;
600 };
601 cache.insert(key, result);
602 while cache.len() > EXTERNAL_SIF_CACHE_MAX_MEMORY_ENTRIES {
603 let Some(first_key) = cache.keys().next().cloned() else {
604 break;
605 };
606 cache.remove(first_key.as_str());
607 }
608}
609
610fn remove_external_sif_from_memory_cache(key: &str) {
611 let Ok(mut cache) = EXTERNAL_SIF_MEMORY_CACHE
612 .get_or_init(|| Mutex::new(BTreeMap::new()))
613 .lock()
614 else {
615 return;
616 };
617 cache.remove(key);
618}
619
620fn external_sif_memory_cache_key(key: &str, cache_dir: Option<&Path>) -> String {
621 cache_dir
622 .map(|cache_dir| format!("{}\0{key}", cache_dir.to_string_lossy()))
623 .unwrap_or_else(|| key.to_string())
624}
625
626fn external_sif_cache_dir_for_path(
627 path: &Path,
628 cache_storage: Option<&OmenaBridgeExternalSifStorageV0>,
629) -> Option<PathBuf> {
630 if let Some(cache_storage) = cache_storage {
631 return Some(
632 cache_storage
633 .workspace_cache_root()
634 .join(EXTERNAL_SIF_CACHE_DIR),
635 );
636 }
637 crate::cache_root::process_external_sif_cache_root(path)?
638 .workspace
639 .map(|root| root.join(EXTERNAL_SIF_CACHE_DIR))
640}
641
642fn external_sif_recorded_verdict_dir_for_path(
643 path: &Path,
644 cache_storage: Option<&OmenaBridgeExternalSifStorageV0>,
645) -> Option<PathBuf> {
646 if let Some(dir) = cache_storage.and_then(|storage| storage.recorded_verdict_dir()) {
647 return Some(dir.to_path_buf());
648 }
649 crate::cache_root::external_sif_workspace_root(path).map(|root| {
650 root.join(".cache")
651 .join("omena")
652 .join(OMENA_SIF_SHARD_VERDICT_DIR_V1)
653 })
654}
655
656fn external_sif_cache_shard_file_path(dir: &Path, key: &str) -> Option<PathBuf> {
657 let hex = key.strip_prefix("blake3:")?;
658 if hex.is_empty() || !hex.bytes().all(|byte| byte.is_ascii_hexdigit()) {
659 return None;
660 }
661 Some(dir.join(format!("{hex}.json")))
662}
663
664fn load_external_sif_cache_shard(
665 dir: &Path,
666 key: &str,
667 canonical_url: &str,
668 source_hash: &str,
669 resolved_base_dir: &str,
670 recorded_verdict_dir: Option<&Path>,
671 locally_regenerated_sif: &OmenaSifV1,
672) -> Option<OmenaBridgeExternalSifWithTrustV1> {
673 let shard_path = external_sif_cache_shard_file_path(dir, key)?;
674 let metadata = fs::metadata(shard_path.as_path()).ok()?;
675 if !metadata.is_file() || metadata.len() > EXTERNAL_SIF_CACHE_MAX_SHARD_BYTES {
676 let _ = fs::remove_file(shard_path.as_path());
677 return None;
678 }
679 let bytes = fs::read(shard_path.as_path()).ok()?;
680 let shard = serde_json::from_slice::<Value>(bytes.as_slice()).ok()?;
681 match validate_external_sif_cache_shard(
682 &shard,
683 key,
684 canonical_url,
685 source_hash,
686 resolved_base_dir,
687 recorded_verdict_dir,
688 locally_regenerated_sif,
689 ) {
690 Ok(result) => Some(result),
691 Err(_) => {
692 let _ = fs::remove_file(shard_path.as_path());
693 None
694 }
695 }
696}
697
698fn validate_external_sif_cache_shard(
699 shard: &Value,
700 key: &str,
701 canonical_url: &str,
702 source_hash: &str,
703 resolved_base_dir: &str,
704 recorded_verdict_dir: Option<&Path>,
705 locally_regenerated_sif: &OmenaSifV1,
706) -> Result<OmenaBridgeExternalSifWithTrustV1, OmenaBridgeExternalSifShardRefusalV1> {
707 let schema_version = shard.get("schemaVersion").and_then(Value::as_str);
708 if !matches!(
709 schema_version,
710 Some(EXTERNAL_SIF_CACHE_SCHEMA_VERSION | EXTERNAL_SIF_CACHE_LEGACY_SCHEMA_VERSION)
711 ) || shard.get("product").and_then(Value::as_str) != Some(EXTERNAL_SIF_CACHE_PRODUCT)
712 || shard.get("key").and_then(Value::as_str) != Some(key)
713 || shard.get("canonicalUrl").and_then(Value::as_str) != Some(canonical_url)
714 || shard.get("sourceHash").and_then(Value::as_str) != Some(source_hash)
715 || shard.get("resolvedBaseDir").and_then(Value::as_str) != Some(resolved_base_dir)
716 {
717 return Err(OmenaBridgeExternalSifShardRefusalV1::ShardIdentityMismatch);
718 }
719 let sif_json = shard
720 .get("sifJson")
721 .and_then(Value::as_str)
722 .ok_or(OmenaBridgeExternalSifShardRefusalV1::MalformedSif)?;
723 let payload_digest = compute_omena_sif_leaf_hash_v1(sif_json.as_bytes());
724 if shard.get("payloadDigest").and_then(Value::as_str) != Some(payload_digest.as_str()) {
725 return Err(OmenaBridgeExternalSifShardRefusalV1::PayloadDigestMismatch);
726 }
727 let sif = read_omena_sif_json_v1(sif_json)
728 .map_err(|_| OmenaBridgeExternalSifShardRefusalV1::MalformedSif)?;
729 if sif.canonical_url != canonical_url {
730 return Err(OmenaBridgeExternalSifShardRefusalV1::CanonicalUrlMismatch);
731 }
732 if sif != *locally_regenerated_sif {
733 return Err(OmenaBridgeExternalSifShardRefusalV1::LocalRegenerationMismatch);
734 }
735 let sif_hash = compute_omena_sif_artifact_hash_v1(&sif)
736 .map_err(|_| OmenaBridgeExternalSifShardRefusalV1::MalformedSif)?;
737 if schema_version == Some(EXTERNAL_SIF_CACHE_LEGACY_SCHEMA_VERSION) {
738 if has_recorded_shard_verdict_for_canonical_url(recorded_verdict_dir, canonical_url) {
739 return Err(OmenaBridgeExternalSifShardRefusalV1::RecordedVerdictDowngrade);
740 }
741 return Ok(unsigned_external_sif_result(sif, payload_digest, sif_hash));
742 }
743 let envelope_value = shard
744 .get("trustEnvelope")
745 .ok_or(OmenaBridgeExternalSifShardRefusalV1::MissingTrustEnvelope)?;
746 let envelope_source = serde_json::to_string(envelope_value)
747 .map_err(|_| OmenaBridgeExternalSifShardRefusalV1::InvalidTrustEnvelope)?;
748 let envelope = read_omena_sif_shard_trust_envelope_json_v1(envelope_source.as_str())
749 .map_err(|_| OmenaBridgeExternalSifShardRefusalV1::InvalidTrustEnvelope)?;
750 if envelope.payload_digest != payload_digest {
751 return Err(OmenaBridgeExternalSifShardRefusalV1::PayloadDigestMismatch);
752 }
753 if envelope.lock_binding.canonical_url != canonical_url
754 || envelope.lock_binding.sif_hash != sif_hash
755 {
756 return Err(OmenaBridgeExternalSifShardRefusalV1::LockBindingMismatch);
757 }
758 if envelope.trust_tier < OmenaSifTrustTierV1::T2 {
759 if has_recorded_shard_verdict_for_canonical_url(recorded_verdict_dir, canonical_url) {
760 return Err(OmenaBridgeExternalSifShardRefusalV1::RecordedVerdictDowngrade);
761 }
762 return Ok(OmenaBridgeExternalSifWithTrustV1 {
763 sif,
764 trust_envelope: envelope,
765 trust_source: OmenaBridgeExternalSifTrustSourceV1::UnsignedLegacy,
766 });
767 }
768 let verdict = load_recorded_shard_verdict(recorded_verdict_dir, canonical_url, &sif_hash)
769 .ok_or(OmenaBridgeExternalSifShardRefusalV1::MissingRecordedVerdict)?;
770 if envelope.trust_tier > verdict.trust_tier {
771 return Err(OmenaBridgeExternalSifShardRefusalV1::TierAboveRecordedVerdict);
772 }
773 if envelope.lock_binding.canonical_url != verdict.canonical_url
774 || envelope.lock_binding.sif_hash != verdict.sif_hash
775 || envelope.signature.as_ref() != Some(&verdict.signature)
776 {
777 return Err(OmenaBridgeExternalSifShardRefusalV1::RecordedVerdictMismatch);
778 }
779 verify_recorded_shard_verdict(recorded_verdict_dir, &verdict)?;
780 Ok(OmenaBridgeExternalSifWithTrustV1 {
781 sif,
782 trust_envelope: envelope,
783 trust_source: OmenaBridgeExternalSifTrustSourceV1::RecordedVerdict,
784 })
785}
786
787fn store_external_sif_cache_shard(
788 dir: &Path,
789 key: &str,
790 canonical_url: &str,
791 source_hash: &str,
792 resolved_base_dir: &str,
793 result: &OmenaBridgeExternalSifWithTrustV1,
794 workspace_identity: Option<&str>,
795) {
796 let Ok(sif_json) = write_omena_sif_json_v1(&result.sif) else {
797 return;
798 };
799 let payload_digest = compute_omena_sif_leaf_hash_v1(sif_json.as_bytes())
800 .as_str()
801 .to_string();
802 let shard = json!({
803 "schemaVersion": EXTERNAL_SIF_CACHE_SCHEMA_VERSION,
804 "product": EXTERNAL_SIF_CACHE_PRODUCT,
805 "key": key,
806 "canonicalUrl": canonical_url,
807 "sourceHash": source_hash,
808 "resolvedBaseDir": resolved_base_dir,
809 "payloadDigest": payload_digest,
810 "trustEnvelope": result.trust_envelope,
811 "sifJson": sif_json,
812 });
813 let Ok(bytes) = write_omena_canonical_json_bytes_v1(&shard) else {
814 return;
815 };
816 if bytes.len() as u64 > EXTERNAL_SIF_CACHE_MAX_SHARD_BYTES {
817 return;
818 }
819 if write_external_sif_cache_shard_atomically(dir, key, bytes.as_slice()).is_ok() {
820 if let Some(workspace_identity) = workspace_identity {
821 crate::cache_root::ensure_omena_cache_root_attribution(dir, workspace_identity);
822 }
823 enforce_external_sif_cache_caps(dir);
824 }
825}
826
827fn external_sif_result_with_recorded_verdict(
828 sif: OmenaSifV1,
829 recorded_verdict_dir: Option<&Path>,
830) -> Result<OmenaBridgeExternalSifWithTrustV1, String> {
831 let sif_json = write_omena_sif_json_v1(&sif)
832 .map_err(|error| format!("failed to serialize generated SIF trust payload: {error}"))?;
833 let payload_digest = compute_omena_sif_leaf_hash_v1(sif_json.as_bytes());
834 let sif_hash = compute_omena_sif_artifact_hash_v1(&sif)
835 .map_err(|error| format!("failed to hash generated SIF trust payload: {error}"))?;
836 let verdict =
837 load_recorded_shard_verdict(recorded_verdict_dir, sif.canonical_url.as_str(), &sif_hash);
838 let verified_verdict = verdict
839 .filter(|verdict| verify_recorded_shard_verdict(recorded_verdict_dir, verdict).is_ok());
840 let (trust_tier, signature, trust_source) = match verified_verdict {
841 Some(verdict) => (
842 verdict.trust_tier,
843 Some(verdict.signature),
844 OmenaBridgeExternalSifTrustSourceV1::RecordedVerdict,
845 ),
846 None => (
847 OmenaSifTrustTierV1::T1,
848 None,
849 OmenaBridgeExternalSifTrustSourceV1::UnsignedLegacy,
850 ),
851 };
852 let trust_envelope = OmenaSifShardTrustEnvelopeV1 {
853 schema_version: OMENA_SIF_SHARD_TRUST_ENVELOPE_SCHEMA_VERSION_V1.to_string(),
854 product: OMENA_SIF_SHARD_TRUST_ENVELOPE_PRODUCT_V1.to_string(),
855 trust_tier,
856 payload_digest,
857 signature,
858 lock_binding: OmenaSifShardLockBindingV1 {
859 canonical_url: sif.canonical_url.clone(),
860 sif_hash,
861 },
862 };
863 omena_sif::validate_omena_sif_shard_trust_envelope_v1(&trust_envelope)
864 .map_err(|error| format!("generated SIF trust envelope is invalid: {error}"))?;
865 Ok(OmenaBridgeExternalSifWithTrustV1 {
866 sif,
867 trust_envelope,
868 trust_source,
869 })
870}
871
872fn unsigned_external_sif_result(
873 sif: OmenaSifV1,
874 payload_digest: omena_sif::OmenaSifDigestV1,
875 sif_hash: omena_sif::OmenaSifDigestV1,
876) -> OmenaBridgeExternalSifWithTrustV1 {
877 OmenaBridgeExternalSifWithTrustV1 {
878 trust_envelope: OmenaSifShardTrustEnvelopeV1 {
879 schema_version: OMENA_SIF_SHARD_TRUST_ENVELOPE_SCHEMA_VERSION_V1.to_string(),
880 product: OMENA_SIF_SHARD_TRUST_ENVELOPE_PRODUCT_V1.to_string(),
881 trust_tier: OmenaSifTrustTierV1::T1,
882 payload_digest,
883 signature: None,
884 lock_binding: OmenaSifShardLockBindingV1 {
885 canonical_url: sif.canonical_url.clone(),
886 sif_hash,
887 },
888 },
889 sif,
890 trust_source: OmenaBridgeExternalSifTrustSourceV1::UnsignedLegacy,
891 }
892}
893
894fn load_recorded_shard_verdict(
895 recorded_verdict_dir: Option<&Path>,
896 canonical_url: &str,
897 sif_hash: &omena_sif::OmenaSifDigestV1,
898) -> Option<OmenaSifShardRecordedVerdictV1> {
899 let verdict_dir = recorded_verdict_dir?;
900 let address =
901 compute_omena_sif_shard_recorded_verdict_address_v1(canonical_url, sif_hash).ok()?;
902 let hex = address.as_str().strip_prefix("blake3:")?;
903 let verdict_path = verdict_dir.join(format!("{hex}.json"));
904 let metadata = fs::metadata(verdict_path.as_path()).ok()?;
905 if !metadata.is_file() || metadata.len() > 1024 * 1024 {
906 return None;
907 }
908 let source = fs::read_to_string(verdict_path.as_path()).ok()?;
909 let verdict = read_omena_sif_shard_recorded_verdict_json_v1(source.as_str()).ok()?;
910 (verdict.canonical_url == canonical_url && verdict.sif_hash == *sif_hash).then_some(verdict)
911}
912
913fn has_recorded_shard_verdict_for_canonical_url(
914 recorded_verdict_dir: Option<&Path>,
915 canonical_url: &str,
916) -> bool {
917 let Some(verdict_dir) = recorded_verdict_dir else {
918 return false;
919 };
920 let entries = match fs::read_dir(verdict_dir) {
921 Ok(entries) => entries,
922 Err(error) if error.kind() == std::io::ErrorKind::NotFound => return false,
923 Err(_) => return true,
924 };
925 let mut verdict_file_count = 0usize;
926 for entry in entries {
927 let Ok(entry) = entry else {
928 return true;
929 };
930 let path = entry.path();
931 if path.extension().and_then(|extension| extension.to_str()) != Some("json") {
932 continue;
933 }
934 verdict_file_count += 1;
935 if verdict_file_count > EXTERNAL_SIF_RECORDED_VERDICT_SCAN_LIMIT {
936 return true;
937 }
938 let Ok(metadata) = entry.metadata() else {
939 return true;
940 };
941 if !metadata.is_file() || metadata.len() > 1024 * 1024 {
942 return true;
943 }
944 let Ok(source) = fs::read_to_string(path) else {
945 return true;
946 };
947 let Ok(verdict) = read_omena_sif_shard_recorded_verdict_json_v1(source.as_str()) else {
948 return true;
949 };
950 if verdict.canonical_url == canonical_url {
951 return true;
952 }
953 }
954 false
955}
956
957fn verify_recorded_shard_verdict(
958 recorded_verdict_dir: Option<&Path>,
959 verdict: &OmenaSifShardRecordedVerdictV1,
960) -> Result<(), OmenaBridgeExternalSifShardRefusalV1> {
961 let verdict_dir = recorded_verdict_dir
962 .ok_or(OmenaBridgeExternalSifShardRefusalV1::RecordedVerdictSignatureVerificationFailed)?;
963 let reference = verdict.signature.reference.as_str();
964 let relative_bundle_name = reference
965 .strip_prefix(&format!("{EXTERNAL_SIF_RECORDED_BUNDLE_DIR_V1}/"))
966 .and_then(|name| name.strip_suffix(EXTERNAL_SIF_RECORDED_BUNDLE_SUFFIX_V1))
967 .filter(|digest| {
968 digest.len() == 64
969 && digest
970 .bytes()
971 .all(|byte| byte.is_ascii_hexdigit() && !byte.is_ascii_uppercase())
972 })
973 .ok_or(OmenaBridgeExternalSifShardRefusalV1::RecordedVerdictSignatureVerificationFailed)?;
974 let bundle_path = verdict_dir
975 .join(EXTERNAL_SIF_RECORDED_BUNDLE_DIR_V1)
976 .join(format!(
977 "{relative_bundle_name}{EXTERNAL_SIF_RECORDED_BUNDLE_SUFFIX_V1}"
978 ));
979 let metadata = fs::metadata(bundle_path.as_path()).map_err(|_| {
980 OmenaBridgeExternalSifShardRefusalV1::RecordedVerdictSignatureVerificationFailed
981 })?;
982 if !metadata.is_file() || metadata.len() > EXTERNAL_SIF_RECORDED_BUNDLE_MAX_BYTES {
983 return Err(
984 OmenaBridgeExternalSifShardRefusalV1::RecordedVerdictSignatureVerificationFailed,
985 );
986 }
987 let bundle_bytes = fs::read(bundle_path.as_path()).map_err(|_| {
988 OmenaBridgeExternalSifShardRefusalV1::RecordedVerdictSignatureVerificationFailed
989 })?;
990 let attested_subject = OmenaSifPublishedAttestationSubjectV1 {
991 schema_version: OMENA_SIF_PUBLISHED_ATTESTATION_SUBJECT_SCHEMA_VERSION_V1.to_string(),
992 product: OMENA_SIF_PUBLISHED_ATTESTATION_SUBJECT_PRODUCT_V1.to_string(),
993 canonical_url: verdict.canonical_url.clone(),
994 trust_tier: verdict.trust_tier,
995 sif_hash: verdict.sif_hash.clone(),
996 };
997 validate_omena_sif_published_attestation_subject_v1(&attested_subject).map_err(|_| {
998 OmenaBridgeExternalSifShardRefusalV1::RecordedVerdictSignatureVerificationFailed
999 })?;
1000 let attested_subject_json =
1001 write_omena_sif_published_attestation_subject_json_v1(&attested_subject).map_err(|_| {
1002 OmenaBridgeExternalSifShardRefusalV1::RecordedVerdictSignatureVerificationFailed
1003 })?;
1004 verify_omena_external_sif_keyless_bundle(
1005 attested_subject_json.as_bytes(),
1006 bundle_bytes.as_slice(),
1007 relative_bundle_name,
1008 )
1009 .map_err(|_| OmenaBridgeExternalSifShardRefusalV1::RecordedVerdictSignatureVerificationFailed)
1010}
1011
1012fn write_external_sif_cache_shard_atomically(
1013 dir: &Path,
1014 key: &str,
1015 bytes: &[u8],
1016) -> std::io::Result<()> {
1017 fs::create_dir_all(dir)?;
1018 crate::cache_root::ensure_omena_cache_root_markers(dir);
1019 let final_path = external_sif_cache_shard_file_path(dir, key).ok_or_else(|| {
1020 std::io::Error::new(
1021 std::io::ErrorKind::InvalidInput,
1022 "invalid external SIF cache key",
1023 )
1024 })?;
1025 let temporary_path = final_path.with_extension(format!("tmp-{}", std::process::id()));
1026 fs::write(temporary_path.as_path(), bytes)?;
1027 let renamed = fs::rename(temporary_path.as_path(), final_path.as_path());
1028 if renamed.is_err() {
1029 let _ = fs::remove_file(temporary_path.as_path());
1030 if final_path.is_file() {
1031 return Ok(());
1032 }
1033 }
1034 renamed
1035}
1036
1037fn enforce_external_sif_cache_caps(dir: &Path) {
1038 let Ok(entries) = fs::read_dir(dir) else {
1039 return;
1040 };
1041 let mut shards = entries
1042 .flatten()
1043 .filter_map(|entry| {
1044 let path = entry.path();
1045 if path.extension().and_then(|extension| extension.to_str()) != Some("json") {
1046 return None;
1047 }
1048 let metadata = entry.metadata().ok()?;
1049 if !metadata.is_file() {
1050 return None;
1051 }
1052 let modified = metadata.modified().ok()?;
1053 Some((modified, metadata.len(), path))
1054 })
1055 .collect::<Vec<_>>();
1056 shards.sort_by(|left, right| left.0.cmp(&right.0).then_with(|| left.2.cmp(&right.2)));
1057 let mut total_bytes = shards.iter().map(|(_, bytes, _)| *bytes).sum::<u64>();
1058 let mut shard_count = shards.len();
1059 for (_, bytes, path) in shards {
1060 if shard_count <= 1
1061 || (shard_count <= EXTERNAL_SIF_CACHE_MAX_SHARDS
1062 && total_bytes <= EXTERNAL_SIF_CACHE_MAX_TOTAL_BYTES)
1063 {
1064 break;
1065 }
1066 if fs::remove_file(path.as_path()).is_ok() {
1067 shard_count -= 1;
1068 total_bytes = total_bytes.saturating_sub(bytes);
1069 }
1070 }
1071}
1072
1073fn external_sif_cache_kill_switch_engaged() -> bool {
1074 std::env::var(EXTERNAL_SIF_CACHE_ENV_KILL_SWITCH)
1075 .is_ok_and(|value| value.eq_ignore_ascii_case("off") || value == "0" || value == "false")
1076}
1077
1078#[cfg(test)]
1079fn clear_external_sif_memory_cache_for_storage_for_test(storage: &OmenaBridgeExternalSifStorageV0) {
1080 let namespace = format!(
1081 "{}\0",
1082 storage
1083 .workspace_cache_root()
1084 .join(EXTERNAL_SIF_CACHE_DIR)
1085 .to_string_lossy()
1086 );
1087 if let Some(cache) = EXTERNAL_SIF_MEMORY_CACHE.get()
1088 && let Ok(mut cache) = cache.lock()
1089 {
1090 cache.retain(|key, _| !key.starts_with(namespace.as_str()));
1091 }
1092}
1093
1094#[cfg(test)]
1095fn local_external_sif_regeneration_count_for_test(canonical_url: &str) -> usize {
1096 EXTERNAL_SIF_LOCAL_REGENERATION_COUNTS
1097 .get_or_init(|| Mutex::new(BTreeMap::new()))
1098 .lock()
1099 .ok()
1100 .and_then(|counts| counts.get(canonical_url).copied())
1101 .unwrap_or_default()
1102}
1103
1104fn infer_omena_bridge_sif_source_syntax(path: &Path) -> OmenaSifSourceSyntaxV1 {
1105 match path
1106 .extension()
1107 .and_then(|extension| extension.to_str())
1108 .map(str::to_ascii_lowercase)
1109 .as_deref()
1110 {
1111 Some("css") => OmenaSifSourceSyntaxV1::Css,
1112 Some("sass") => OmenaSifSourceSyntaxV1::Sass,
1113 Some("less") => OmenaSifSourceSyntaxV1::Less,
1114 _ => OmenaSifSourceSyntaxV1::Scss,
1115 }
1116}
1117
1118pub fn load_omena_bridge_workspace_style_resolution_inputs(
1119 workspace_folder_uri: Option<&str>,
1120 configured_package_manifests: &[OmenaResolverStylePackageManifestV0],
1121) -> OmenaBridgeStyleResolutionInputsV0 {
1122 let workspace_path = workspace_folder_uri
1123 .and_then(file_uri_to_path)
1124 .map(normalize_path);
1125 load_omena_bridge_workspace_style_resolution_inputs_from_path(
1126 workspace_path.as_deref(),
1127 configured_package_manifests,
1128 )
1129}
1130
1131fn load_omena_bridge_workspace_style_resolution_inputs_from_path(
1132 workspace_path: Option<&Path>,
1133 configured_package_manifests: &[OmenaResolverStylePackageManifestV0],
1134) -> OmenaBridgeStyleResolutionInputsV0 {
1135 OmenaBridgeStyleResolutionInputsV0 {
1136 package_manifests: merge_package_manifest_lists(
1137 configured_package_manifests,
1138 workspace_package_manifests(workspace_path).as_slice(),
1139 ),
1140 tsconfig_path_mappings: tsconfig_path_mappings_for_workspace(workspace_path)
1141 .unwrap_or_default(),
1142 bundler_path_mappings: load_omena_bridge_workspace_bundler_path_alias_mappings(
1143 workspace_path,
1144 ),
1145 disk_style_path_identities: workspace_path
1146 .map(workspace_style_path_identities)
1147 .unwrap_or_default(),
1148 }
1149}
1150
1151fn workspace_style_path_identities(
1152 workspace_path: &Path,
1153) -> Vec<OmenaResolverStyleModuleDiskCandidateIdentityV0> {
1154 let mut identities = Vec::new();
1155 let mut queue = VecDeque::from([workspace_path.to_path_buf()]);
1156 while let Some(dir) = queue.pop_front() {
1157 if identities.len() >= WORKSPACE_STYLE_PATH_IDENTITY_SCAN_LIMIT {
1158 break;
1159 }
1160 let Ok(entries) = fs::read_dir(dir.as_path()) else {
1161 continue;
1162 };
1163 for entry in entries.flatten() {
1164 if identities.len() >= WORKSPACE_STYLE_PATH_IDENTITY_SCAN_LIMIT {
1165 break;
1166 }
1167 let path = entry.path();
1168 let Some(file_name) = path.file_name().and_then(|name| name.to_str()) else {
1169 continue;
1170 };
1171 if path.is_dir() {
1172 let relative_depth = path
1173 .strip_prefix(workspace_path)
1174 .ok()
1175 .map(|relative| relative.components().count())
1176 .unwrap_or(usize::MAX);
1177 if relative_depth > WORKSPACE_STYLE_PATH_IDENTITY_MAX_DEPTH {
1178 continue;
1179 }
1180 if should_skip_style_identity_scan_dir(file_name) {
1181 continue;
1182 }
1183 queue.push_back(path);
1184 continue;
1185 }
1186 if !is_indexable_style_path(path.as_path()) {
1187 continue;
1188 }
1189 let Some(metadata_identity) = file_metadata_identity(path.as_path()) else {
1190 continue;
1191 };
1192 identities.push(OmenaResolverStyleModuleDiskCandidateIdentityV0 {
1193 style_path: normalize_path(path).to_string_lossy().to_string(),
1194 metadata_identity,
1195 });
1196 }
1197 }
1198 identities.sort_by(|left, right| left.style_path.cmp(&right.style_path));
1199 identities.dedup_by(|left, right| left.style_path == right.style_path);
1200 identities
1201}
1202
1203fn should_skip_style_identity_scan_dir(name: &str) -> bool {
1204 matches!(
1205 name,
1206 ".git" | ".next" | ".nuxt" | ".svelte-kit" | "coverage" | "target"
1207 )
1208}
1209
1210fn file_metadata_identity(path: &Path) -> Option<String> {
1211 let metadata = fs::symlink_metadata(path).ok()?;
1212 let modified = metadata
1213 .modified()
1214 .ok()
1215 .and_then(|time| time.duration_since(std::time::UNIX_EPOCH).ok())
1216 .map(|duration| format!("{}.{:09}", duration.as_secs(), duration.subsec_nanos()))
1217 .unwrap_or_else(|| "unknownMtime".to_string());
1218 let file_type = if metadata.file_type().is_symlink() {
1219 "symlink"
1220 } else if metadata.is_file() {
1221 "file"
1222 } else {
1223 "other"
1224 };
1225 Some(format!("{file_type}|len{}|mtime{modified}", metadata.len()))
1226}
1227
1228fn merge_package_manifest_lists(
1229 primary: &[OmenaResolverStylePackageManifestV0],
1230 secondary: &[OmenaResolverStylePackageManifestV0],
1231) -> Vec<OmenaResolverStylePackageManifestV0> {
1232 let mut manifests = primary.to_vec();
1233 let mut seen = manifests
1234 .iter()
1235 .map(|manifest| manifest.package_json_path.clone())
1236 .collect::<BTreeSet<_>>();
1237 for manifest in secondary {
1238 if seen.insert(manifest.package_json_path.clone()) {
1239 manifests.push(manifest.clone());
1240 }
1241 }
1242 manifests
1243}
1244
1245fn merged_package_manifests_for_specifier(
1246 source_dir: Option<&Path>,
1247 specifier: &str,
1248 configured_package_manifests: &[OmenaResolverStylePackageManifestV0],
1249) -> Vec<OmenaResolverStylePackageManifestV0> {
1250 merge_package_manifest_lists(
1251 configured_package_manifests,
1252 package_manifests_for_specifier(source_dir, specifier)
1253 .unwrap_or_default()
1254 .as_slice(),
1255 )
1256}
1257
1258fn merged_package_manifests_for_request(
1259 source_dir: Option<&Path>,
1260 workspace_path: Option<&Path>,
1261 specifier: &str,
1262 configured_package_manifests: &[OmenaResolverStylePackageManifestV0],
1263) -> Vec<OmenaResolverStylePackageManifestV0> {
1264 let source_manifests =
1265 merged_package_manifests_for_specifier(source_dir, specifier, configured_package_manifests);
1266 merge_package_manifest_lists(
1267 source_manifests.as_slice(),
1268 workspace_package_manifests(workspace_path).as_slice(),
1269 )
1270}
1271
1272fn tsconfig_path_mappings_for_workspace(
1273 workspace_path: Option<&Path>,
1274) -> Option<Vec<OmenaResolverTsconfigPathMappingV0>> {
1275 let workspace_path = workspace_path?;
1276 let mut mappings = Vec::new();
1277 for config_path in [
1278 workspace_path.join("tsconfig.json"),
1279 workspace_path.join("jsconfig.json"),
1280 ] {
1281 mappings.extend(tsconfig_path_mappings_for_config(config_path.as_path()));
1282 }
1283 Some(mappings)
1284}
1285
1286fn tsconfig_path_mappings_for_config(
1287 config_path: &Path,
1288) -> Vec<OmenaResolverTsconfigPathMappingV0> {
1289 tsconfig_path_mappings_for_config_with_seen(config_path, &mut BTreeSet::new())
1290}
1291
1292fn tsconfig_path_mappings_for_config_with_seen(
1293 config_path: &Path,
1294 seen: &mut BTreeSet<PathBuf>,
1295) -> Vec<OmenaResolverTsconfigPathMappingV0> {
1296 let normalized_config_path = normalize_path(config_path.to_path_buf());
1297 if !seen.insert(normalized_config_path.clone()) {
1298 return Vec::new();
1299 }
1300 let Some(config_text) = fs::read_to_string(config_path).ok() else {
1301 return Vec::new();
1302 };
1303 let Some(config) = serde_json::from_str::<Value>(config_text.as_str()).ok() else {
1304 return Vec::new();
1305 };
1306 let own_mappings = tsconfig_path_mappings_from_value(config_path, &config).unwrap_or_default();
1307 if !own_mappings.is_empty() {
1308 return own_mappings;
1309 }
1310 resolve_tsconfig_extends_path(config_path, &config)
1311 .map(|extends_path| {
1312 tsconfig_path_mappings_for_config_with_seen(extends_path.as_path(), seen)
1313 })
1314 .unwrap_or_default()
1315}
1316
1317fn tsconfig_path_mappings_from_value(
1318 config_path: &Path,
1319 config: &Value,
1320) -> Option<Vec<OmenaResolverTsconfigPathMappingV0>> {
1321 let compiler_options = config.get("compilerOptions")?;
1322 let paths = compiler_options.get("paths")?.as_object()?;
1323 let config_dir = config_path.parent()?;
1324 let base_url = compiler_options
1325 .get("baseUrl")
1326 .and_then(Value::as_str)
1327 .unwrap_or(".");
1328 let base_path = normalize_path(config_dir.join(base_url));
1329 let mut mappings = Vec::new();
1330 for (pattern, targets) in paths {
1331 let Some(targets) = targets.as_array() else {
1332 continue;
1333 };
1334 let target_patterns = targets
1335 .iter()
1336 .filter_map(Value::as_str)
1337 .map(ToString::to_string)
1338 .collect::<Vec<_>>();
1339 if target_patterns.is_empty() {
1340 continue;
1341 }
1342 mappings.push(OmenaResolverTsconfigPathMappingV0 {
1343 base_path: base_path.to_string_lossy().to_string(),
1344 pattern: pattern.to_string(),
1345 target_patterns,
1346 });
1347 }
1348 Some(mappings)
1349}
1350
1351fn resolve_tsconfig_extends_path(config_path: &Path, config: &Value) -> Option<PathBuf> {
1352 let extends = config.get("extends")?.as_str()?;
1353 if !extends.starts_with('.') {
1354 return None;
1355 }
1356 let config_dir = config_path.parent()?;
1357 let raw_path = config_dir.join(extends);
1358 tsconfig_extends_candidates(raw_path)
1359 .into_iter()
1360 .find(|candidate| candidate.exists())
1361}
1362
1363fn tsconfig_extends_candidates(path: PathBuf) -> Vec<PathBuf> {
1364 if path.extension().is_some() {
1365 return vec![path];
1366 }
1367 vec![path.with_extension("json"), path.join("tsconfig.json")]
1368}
1369
1370fn package_manifests_for_specifier(
1371 source_dir: Option<&Path>,
1372 specifier: &str,
1373) -> Option<Vec<OmenaResolverStylePackageManifestV0>> {
1374 if is_package_import_specifier(specifier) {
1375 return Some(package_scope_manifests_for_source_dir(source_dir));
1376 }
1377 let package_name = package_name_from_specifier(specifier)?;
1378 let mut manifests = Vec::new();
1379 let mut seen = BTreeSet::new();
1380 let mut current_dir = source_dir;
1381 while let Some(dir) = current_dir {
1382 let package_json_path = dir
1383 .join("node_modules")
1384 .join(package_name)
1385 .join("package.json");
1386 if seen.insert(package_json_path.clone())
1387 && let Ok(package_json_source) = fs::read_to_string(package_json_path.as_path())
1388 {
1389 manifests.push(OmenaResolverStylePackageManifestV0 {
1390 package_json_path: normalize_path(package_json_path)
1391 .to_string_lossy()
1392 .to_string(),
1393 package_json_source,
1394 });
1395 }
1396 current_dir = dir.parent();
1397 }
1398 Some(manifests)
1399}
1400
1401fn package_scope_manifests_for_source_dir(
1402 source_dir: Option<&Path>,
1403) -> Vec<OmenaResolverStylePackageManifestV0> {
1404 let mut manifests = Vec::new();
1405 let mut current_dir = source_dir;
1406 while let Some(dir) = current_dir {
1407 push_workspace_package_manifest(dir.join("package.json"), &mut manifests);
1408 current_dir = dir.parent();
1409 }
1410 manifests
1411}
1412
1413fn workspace_package_manifests(
1414 workspace_path: Option<&Path>,
1415) -> Vec<OmenaResolverStylePackageManifestV0> {
1416 let Some(workspace_path) = workspace_path else {
1417 return Vec::new();
1418 };
1419 let mut manifests = Vec::new();
1420 push_workspace_package_manifest(workspace_path.join("package.json"), &mut manifests);
1421
1422 let node_modules = workspace_path.join("node_modules");
1423 let Ok(entries) = fs::read_dir(node_modules.as_path()) else {
1424 return manifests;
1425 };
1426 for entry in entries.flatten() {
1427 if manifests.len() >= WORKSPACE_PACKAGE_MANIFEST_SCAN_LIMIT {
1428 break;
1429 }
1430 let path = entry.path();
1431 let Some(file_name) = path.file_name().and_then(|value| value.to_str()) else {
1432 continue;
1433 };
1434 if file_name.starts_with('@') {
1435 push_scoped_workspace_package_manifests(path.as_path(), &mut manifests);
1436 } else {
1437 push_workspace_package_manifest(path.join("package.json"), &mut manifests);
1438 }
1439 }
1440 manifests.sort_by(|left, right| left.package_json_path.cmp(&right.package_json_path));
1441 manifests.dedup_by(|left, right| left.package_json_path == right.package_json_path);
1442 manifests
1443}
1444
1445fn push_scoped_workspace_package_manifests(
1446 scope_path: &Path,
1447 manifests: &mut Vec<OmenaResolverStylePackageManifestV0>,
1448) {
1449 let Ok(entries) = fs::read_dir(scope_path) else {
1450 return;
1451 };
1452 for entry in entries.flatten() {
1453 if manifests.len() >= WORKSPACE_PACKAGE_MANIFEST_SCAN_LIMIT {
1454 return;
1455 }
1456 push_workspace_package_manifest(entry.path().join("package.json"), manifests);
1457 }
1458}
1459
1460fn push_workspace_package_manifest(
1461 package_json_path: PathBuf,
1462 manifests: &mut Vec<OmenaResolverStylePackageManifestV0>,
1463) {
1464 if manifests.len() >= WORKSPACE_PACKAGE_MANIFEST_SCAN_LIMIT {
1465 return;
1466 }
1467 let normalized_package_json_path = normalize_path(package_json_path);
1468 let package_json_path_text = normalized_package_json_path.to_string_lossy().to_string();
1469 if manifests
1470 .iter()
1471 .any(|manifest| manifest.package_json_path == package_json_path_text)
1472 {
1473 return;
1474 }
1475 let Ok(package_json_source) = fs::read_to_string(normalized_package_json_path.as_path()) else {
1476 return;
1477 };
1478 manifests.push(OmenaResolverStylePackageManifestV0 {
1479 package_json_path: package_json_path_text,
1480 package_json_source,
1481 });
1482}
1483
1484fn package_name_from_specifier(specifier: &str) -> Option<&str> {
1485 let specifier = specifier.strip_prefix("pkg:").unwrap_or(specifier);
1486 if specifier.starts_with('.')
1487 || specifier.starts_with('/')
1488 || is_package_import_specifier(specifier)
1489 || is_external_style_specifier(specifier)
1490 {
1491 return None;
1492 }
1493 if specifier.starts_with('@') {
1494 let mut segments = specifier.splitn(3, '/');
1495 let scope = segments.next()?;
1496 let package = segments.next()?;
1497 if scope.len() <= 1 || package.is_empty() {
1498 return None;
1499 }
1500 return specifier.get(..scope.len() + 1 + package.len());
1501 }
1502 specifier.split('/').next().filter(|name| !name.is_empty())
1503}
1504
1505fn is_package_import_specifier(specifier: &str) -> bool {
1506 specifier
1507 .strip_prefix("pkg:")
1508 .unwrap_or(specifier)
1509 .starts_with('#')
1510}
1511
1512fn tsconfig_path_pattern_matches(pattern: &str, specifier: &str) -> bool {
1513 if let Some((prefix, suffix)) = pattern.split_once('*') {
1514 return !suffix.contains('*')
1515 && specifier.starts_with(prefix)
1516 && specifier.ends_with(suffix)
1517 && specifier.len() >= prefix.len() + suffix.len();
1518 }
1519 pattern == specifier
1520}
1521
1522fn bundler_path_alias_pattern_matches(pattern: &str, specifier: &str) -> bool {
1523 if pattern.is_empty() {
1524 return false;
1525 }
1526 if let Some(exact_pattern) = pattern.strip_suffix('$') {
1527 return specifier == exact_pattern;
1528 }
1529 if pattern == specifier {
1530 return true;
1531 }
1532 let prefix = if pattern.ends_with('/') {
1533 pattern.to_string()
1534 } else {
1535 format!("{pattern}/")
1536 };
1537 specifier.starts_with(prefix.as_str())
1538}
1539
1540fn is_external_style_specifier(specifier: &str) -> bool {
1541 specifier.starts_with("sass:")
1542 || specifier.starts_with("http://")
1543 || specifier.starts_with("https://")
1544}
1545
1546fn style_uri_for_resolver_candidates(
1547 candidates: &[String],
1548 disk_style_path_identities: &[OmenaResolverStyleModuleDiskCandidateIdentityV0],
1549 requires_existing_candidate: bool,
1550) -> Option<String> {
1551 let empty_available = BTreeSet::new();
1552 let confirmation = confirm_omena_resolver_style_module_candidate_with_options(
1553 candidates,
1554 &empty_available,
1555 disk_style_path_identities,
1556 OmenaResolverStyleModuleConfirmationOptionsV0 {
1557 allow_disk_confirmation: true,
1558 allow_live_disk_confirmation: true,
1559 allow_unconfirmed_indexable_candidate: !requires_existing_candidate,
1560 ..OmenaResolverStyleModuleConfirmationOptionsV0::default()
1561 },
1562 );
1563 confirmation
1564 .resolved_style_path
1565 .map(PathBuf::from)
1566 .map(|path| path_to_file_uri(normalize_path(path).as_path()))
1567}
1568
1569fn is_indexable_style_path(path: &Path) -> bool {
1570 is_omena_resolver_indexable_style_module_path(path.to_string_lossy().as_ref())
1571}
1572
1573fn file_uri_to_path(uri: &str) -> Option<PathBuf> {
1574 let raw_path = uri.strip_prefix("file://")?;
1575 Some(PathBuf::from(percent_decode_uri_path(raw_path)?))
1576}
1577
1578fn percent_decode_uri_path(raw_path: &str) -> Option<String> {
1579 let bytes = raw_path.as_bytes();
1580 let mut decoded = Vec::with_capacity(bytes.len());
1581 let mut index = 0usize;
1582 while index < bytes.len() {
1583 if bytes[index] == b'%' {
1584 let high = bytes.get(index + 1).and_then(|byte| hex_value(*byte))?;
1585 let low = bytes.get(index + 2).and_then(|byte| hex_value(*byte))?;
1586 decoded.push((high << 4) | low);
1587 index += 3;
1588 } else {
1589 decoded.push(bytes[index]);
1590 index += 1;
1591 }
1592 }
1593 String::from_utf8(decoded).ok()
1594}
1595
1596fn hex_value(byte: u8) -> Option<u8> {
1597 match byte {
1598 b'0'..=b'9' => Some(byte - b'0'),
1599 b'a'..=b'f' => Some(byte - b'a' + 10),
1600 b'A'..=b'F' => Some(byte - b'A' + 10),
1601 _ => None,
1602 }
1603}
1604
1605fn path_to_file_uri(path: &Path) -> String {
1606 let path = normalize_path(path.to_path_buf());
1607 format!(
1608 "file://{}",
1609 percent_encode_uri_path(path.to_string_lossy().as_ref())
1610 )
1611}
1612
1613fn percent_encode_uri_path(path: &str) -> String {
1614 let mut encoded = String::with_capacity(path.len());
1615 for byte in path.as_bytes() {
1616 match *byte {
1617 b'A'..=b'Z'
1618 | b'a'..=b'z'
1619 | b'0'..=b'9'
1620 | b'-'
1621 | b'.'
1622 | b'_'
1623 | b'~'
1624 | b'/'
1625 | b'@'
1626 | b':'
1627 | b'!'
1628 | b'$'
1629 | b'&'
1630 | b'\''
1631 | b'*'
1632 | b'+'
1633 | b','
1634 | b';'
1635 | b'=' => encoded.push(*byte as char),
1636 _ => encoded.push_str(format!("%{byte:02X}").as_str()),
1637 }
1638 }
1639 encoded
1640}
1641
1642fn normalize_path(path: PathBuf) -> PathBuf {
1643 if let Some(canonical) = canonicalize_existing_path_or_parent(path.as_path()) {
1644 return normalize_path_lexical(canonical);
1645 }
1646 normalize_path_lexical(path)
1647}
1648
1649fn canonicalize_existing_path_or_parent(path: &Path) -> Option<PathBuf> {
1650 if let Ok(canonical) = fs::canonicalize(path) {
1651 return Some(canonical);
1652 }
1653
1654 let mut current = path.to_path_buf();
1655 let mut suffix = Vec::<OsString>::new();
1656 while let Some(parent) = current.parent() {
1657 if let Some(file_name) = current.file_name() {
1658 suffix.push(file_name.to_os_string());
1659 }
1660 if let Ok(mut canonical_parent) = fs::canonicalize(parent) {
1661 for segment in suffix.iter().rev() {
1662 canonical_parent.push(segment);
1663 }
1664 return Some(canonical_parent);
1665 }
1666 current = parent.to_path_buf();
1667 }
1668 None
1669}
1670
1671fn normalize_path_lexical(path: PathBuf) -> PathBuf {
1672 let mut normalized = PathBuf::new();
1673 for component in path.components() {
1674 match component {
1675 Component::CurDir => {}
1676 Component::ParentDir => {
1677 normalized.pop();
1678 }
1679 Component::Normal(_) | Component::RootDir | Component::Prefix(_) => {
1680 normalized.push(component.as_os_str());
1681 }
1682 }
1683 }
1684 normalized
1685}
1686
1687#[cfg(test)]
1688mod tests {
1689 use std::{fs, time::SystemTime};
1690
1691 use super::*;
1692
1693 #[test]
1694 fn resolves_relative_style_candidates() -> Result<(), Box<dyn std::error::Error>> {
1695 let root = temp_dir("omena_bridge_style_relative")?;
1696 let source = root.join("src/App.tsx");
1697 let style = root.join("src/Button.module.scss");
1698 fs::create_dir_all(
1699 source
1700 .parent()
1701 .ok_or_else(|| std::io::Error::other("parent"))?,
1702 )?;
1703 fs::write(&source, "")?;
1704 fs::write(&style, ".root {}")?;
1705
1706 let uri = resolve_omena_bridge_style_uri_for_specifier(
1707 path_to_file_uri(source.as_path()).as_str(),
1708 Some(path_to_file_uri(root.as_path()).as_str()),
1709 "./Button.module.scss",
1710 );
1711
1712 assert_eq!(
1713 uri.as_deref(),
1714 Some(path_to_file_uri(style.as_path()).as_str())
1715 );
1716 let _ = fs::remove_dir_all(root);
1717 Ok(())
1718 }
1719
1720 #[test]
1721 fn generates_sif_for_resolved_relative_style_module() -> Result<(), Box<dyn std::error::Error>>
1722 {
1723 let root = temp_dir("omena_bridge_sif_resolved")?;
1724 let source = root.join("src/App.tsx");
1725 let style = root.join("src/theme.scss");
1726 fs::create_dir_all(
1727 style
1728 .parent()
1729 .ok_or_else(|| std::io::Error::other("parent"))?,
1730 )?;
1731 fs::write(&source, "")?;
1732 fs::write(&style, "$brand: #0af;\n@mixin focus-ring {}\n")?;
1733
1734 let resolved = resolve_omena_bridge_style_uri_for_specifier(
1735 path_to_file_uri(source.as_path()).as_str(),
1736 Some(path_to_file_uri(root.as_path()).as_str()),
1737 "./theme.scss",
1738 )
1739 .ok_or_else(|| std::io::Error::other("resolution failed"))?;
1740
1741 let sif = generate_fixture_sif_for_resolved_style_path(style.as_path(), resolved.as_str())?;
1742
1743 assert_eq!(sif.canonical_url, resolved);
1744 assert_eq!(sif.source.syntax, OmenaSifSourceSyntaxV1::Scss);
1745 assert!(
1746 sif.exports
1747 .variables
1748 .iter()
1749 .any(|variable| variable.name == "$brand"),
1750 "expected $brand variable export, got {:?}",
1751 sif.exports.variables
1752 );
1753 assert!(
1754 sif.exports
1755 .mixins
1756 .iter()
1757 .any(|mixin| mixin.name == "focus-ring"),
1758 "expected focus-ring mixin export, got {:?}",
1759 sif.exports.mixins
1760 );
1761 let json = omena_sif::write_omena_sif_json_v1(&sif)?;
1764 let parsed = omena_sif::read_omena_sif_json_v1(json.as_str())?;
1765 assert_eq!(parsed, sif);
1766 let _ = fs::remove_dir_all(root);
1767 Ok(())
1768 }
1769
1770 #[test]
1771 fn generates_lif_exports_for_resolved_less_specifier() -> Result<(), Box<dyn std::error::Error>>
1772 {
1773 let root = temp_dir("omena_bridge_lif_resolved_less")?;
1774 let source = root.join("src/App.tsx");
1775 let style = root.join("src/tokens.less");
1776 fs::create_dir_all(
1777 style
1778 .parent()
1779 .ok_or_else(|| std::io::Error::other("parent"))?,
1780 )?;
1781 fs::write(&source, "")?;
1782 fs::write(
1783 &style,
1784 "@brand: #fff;\n@tokens: { primary: @brand; @gap: 2px; };\n.button(@gap: 1rem) when (@gap > 0) { color: @brand; }\n",
1785 )?;
1786
1787 let resolved = resolve_omena_bridge_style_uri_for_specifier(
1788 path_to_file_uri(source.as_path()).as_str(),
1789 Some(path_to_file_uri(root.as_path()).as_str()),
1790 "./tokens.less",
1791 )
1792 .ok_or_else(|| std::io::Error::other("Less resolution failed"))?;
1793
1794 let exports = generate_omena_bridge_lif_exports_for_resolved_style_path(resolved.as_str())?;
1795
1796 assert_eq!(exports.less_variables[0].name, "@brand");
1797 assert_eq!(
1798 exports.less_variables[0].value_repr.as_deref(),
1799 Some("#fff")
1800 );
1801 assert_eq!(exports.less_mixins[0].name, ".button");
1802 assert!(exports.less_mixins[0].guarded);
1803 assert_eq!(exports.less_detached_rulesets[0].name, "@tokens");
1804 assert_eq!(
1805 exports.less_detached_rulesets[0].member_names,
1806 vec!["@gap", "primary"]
1807 );
1808 let _ = fs::remove_dir_all(root);
1809 Ok(())
1810 }
1811
1812 #[test]
1813 fn generates_sif_from_plain_resolved_path() -> Result<(), Box<dyn std::error::Error>> {
1814 let root = temp_dir("omena_bridge_sif_plain")?;
1815 let style = root.join("tokens.sass");
1816 fs::write(&style, "$gap: 8px\n")?;
1817
1818 let sif = generate_fixture_sif_for_resolved_style_path(
1819 style.as_path(),
1820 style.to_string_lossy().as_ref(),
1821 )?;
1822
1823 assert_eq!(sif.source.syntax, OmenaSifSourceSyntaxV1::Sass);
1824 let _ = fs::remove_dir_all(root);
1825 Ok(())
1826 }
1827
1828 #[test]
1829 fn generates_sif_with_less_source_syntax() -> Result<(), Box<dyn std::error::Error>> {
1830 let root = temp_dir("omena_bridge_sif_less")?;
1831 let style = root.join("tokens.less");
1832 fs::write(&style, "@gap: 8px;\n.button { margin: @gap; }\n")?;
1833
1834 let sif = generate_fixture_sif_for_resolved_style_path(
1835 style.as_path(),
1836 style.to_string_lossy().as_ref(),
1837 )?;
1838
1839 assert_eq!(sif.source.syntax, OmenaSifSourceSyntaxV1::Less);
1840 let json = omena_sif::write_omena_sif_json_v1(&sif)?;
1841 assert!(json.contains(r#""syntax":"less""#));
1842 let _ = fs::remove_dir_all(root);
1843 Ok(())
1844 }
1845
1846 #[test]
1847 fn generates_lif_exports_for_resolved_less_path() -> Result<(), Box<dyn std::error::Error>> {
1848 let root = temp_dir("omena_bridge_lif_less")?;
1849 let style = root.join("tokens.less");
1850 fs::write(
1851 &style,
1852 "@brand: red;\n@tokens: { primary: @brand; };\n.button(@gap: 1rem) { color: @brand; }\n",
1853 )?;
1854
1855 let exports = generate_omena_bridge_lif_exports_for_resolved_style_path(
1856 style.to_string_lossy().as_ref(),
1857 )?;
1858
1859 assert_eq!(exports.less_variables[0].name, "@brand");
1860 assert_eq!(exports.less_mixins[0].name, ".button");
1861 assert_eq!(exports.less_detached_rulesets[0].name, "@tokens");
1862 assert_eq!(
1863 exports.less_detached_rulesets[0].member_names,
1864 vec!["primary"]
1865 );
1866 let _ = fs::remove_dir_all(root);
1867 Ok(())
1868 }
1869
1870 #[test]
1871 fn external_sif_cache_key_is_base_dir_sensitive_and_serves_fresh_sif()
1872 -> Result<(), Box<dyn std::error::Error>> {
1873 let root = temp_dir("omena_bridge_external_sif_cache")?;
1874 let first_dir = root.join("node_modules/design-a");
1875 let second_dir = root.join("node_modules/design-b");
1876 fs::create_dir_all(first_dir.as_path())?;
1877 fs::create_dir_all(second_dir.as_path())?;
1878 fs::write(root.join("package.json"), r#"{"name":"workspace"}"#)?;
1879 let first_style = first_dir.join("tokens.scss");
1880 let second_style = second_dir.join("tokens.scss");
1881 let source = "$brand: #0af;\n";
1882 fs::write(first_style.as_path(), source)?;
1883 fs::write(second_style.as_path(), source)?;
1884
1885 let first_path = normalize_path(first_style.clone());
1886 let second_path = normalize_path(second_style.clone());
1887 let source_hash = compute_omena_sif_leaf_hash_v1(source.as_bytes())
1888 .as_str()
1889 .to_string();
1890 let first_base_dir = first_path
1891 .parent()
1892 .ok_or_else(|| std::io::Error::other("first parent"))?
1893 .to_string_lossy()
1894 .to_string();
1895 let second_base_dir = second_path
1896 .parent()
1897 .ok_or_else(|| std::io::Error::other("second parent"))?
1898 .to_string_lossy()
1899 .to_string();
1900 let first_key = external_sif_cache_key(
1901 source_hash.as_str(),
1902 first_base_dir.as_str(),
1903 path_to_file_uri(first_path.as_path()).as_str(),
1904 None,
1905 );
1906 let second_key = external_sif_cache_key(
1907 source_hash.as_str(),
1908 second_base_dir.as_str(),
1909 path_to_file_uri(second_path.as_path()).as_str(),
1910 None,
1911 );
1912 assert_ne!(
1913 first_key, second_key,
1914 "same bytes under different resolved bases must not share an external SIF cache key"
1915 );
1916 let old_fingerprint_key = external_sif_cache_key(
1917 source_hash.as_str(),
1918 first_base_dir.as_str(),
1919 path_to_file_uri(first_path.as_path()).as_str(),
1920 Some("lockfile:old"),
1921 );
1922 let new_fingerprint_key = external_sif_cache_key(
1923 source_hash.as_str(),
1924 first_base_dir.as_str(),
1925 path_to_file_uri(first_path.as_path()).as_str(),
1926 Some("lockfile:new"),
1927 );
1928 assert_ne!(
1929 old_fingerprint_key, new_fingerprint_key,
1930 "lockfile or package-manager freshness changes must invalidate external SIF cache keys"
1931 );
1932 let old_crate_version_key = external_sif_cache_key_with_crate_version(
1933 source_hash.as_str(),
1934 first_base_dir.as_str(),
1935 path_to_file_uri(first_path.as_path()).as_str(),
1936 None,
1937 "0.2.0",
1938 );
1939 let current_crate_version_key = external_sif_cache_key_with_crate_version(
1940 source_hash.as_str(),
1941 first_base_dir.as_str(),
1942 path_to_file_uri(first_path.as_path()).as_str(),
1943 None,
1944 env!("CARGO_PKG_VERSION"),
1945 );
1946 assert_ne!(
1947 old_crate_version_key, current_crate_version_key,
1948 "a shard address from another crate version must never be served"
1949 );
1950
1951 let first_uri = path_to_file_uri(first_style.as_path());
1952 let cache_storage = fixture_cache_storage(first_style.as_path());
1953 let fresh =
1954 generate_omena_bridge_sif_for_resolved_style_path_with_cache_context_and_storage(
1955 first_uri.as_str(),
1956 &OmenaBridgeExternalSifCacheContextV0::default(),
1957 Some(&cache_storage),
1958 )?;
1959 clear_external_sif_memory_cache_for_storage_for_test(&cache_storage);
1960 let cached =
1961 generate_omena_bridge_sif_for_resolved_style_path_with_cache_context_and_storage(
1962 first_uri.as_str(),
1963 &OmenaBridgeExternalSifCacheContextV0::default(),
1964 Some(&cache_storage),
1965 )?;
1966 assert_eq!(cached, fresh);
1967 let cache_dir =
1968 external_sif_cache_dir_for_path(first_style.as_path(), Some(&cache_storage))
1969 .ok_or_else(|| std::io::Error::other("cache dir"))?;
1970 assert!(
1971 cache_dir.read_dir()?.flatten().any(|entry| entry
1972 .path()
1973 .extension()
1974 .and_then(|ext| ext.to_str())
1975 == Some("json")),
1976 "expected a disk external SIF cache shard in {}",
1977 cache_dir.display()
1978 );
1979 let _ = fs::remove_dir_all(root);
1980 Ok(())
1981 }
1982
1983 #[test]
1984 fn memory_cache_hit_uses_fresh_source_hash_without_local_regeneration()
1985 -> Result<(), Box<dyn std::error::Error>> {
1986 let root = temp_dir("omena_bridge_external_sif_memory_fast_path")?;
1987 fs::write(root.join("package.json"), r#"{"name":"workspace"}"#)?;
1988 let style = root.join("tokens.scss");
1989 fs::write(style.as_path(), "$brand: #0af;\n")?;
1990 let resolved = path_to_file_uri(style.as_path());
1991 let storage = fixture_cache_storage(style.as_path());
1992 clear_external_sif_memory_cache_for_storage_for_test(&storage);
1993
1994 let before = local_external_sif_regeneration_count_for_test(resolved.as_str());
1995 let first =
1996 generate_omena_bridge_sif_for_resolved_style_path_with_cache_context_and_storage(
1997 resolved.as_str(),
1998 &OmenaBridgeExternalSifCacheContextV0::default(),
1999 Some(&storage),
2000 )?;
2001 let after_first = local_external_sif_regeneration_count_for_test(resolved.as_str());
2002 assert_eq!(after_first, before + 1);
2003
2004 let second =
2005 generate_omena_bridge_sif_for_resolved_style_path_with_cache_context_and_storage(
2006 resolved.as_str(),
2007 &OmenaBridgeExternalSifCacheContextV0::default(),
2008 Some(&storage),
2009 )?;
2010 assert_eq!(second, first);
2011 assert_eq!(
2012 local_external_sif_regeneration_count_for_test(resolved.as_str()),
2013 after_first,
2014 "a memory hit bound to the freshly read source hash must skip static SIF regeneration"
2015 );
2016
2017 fs::write(style.as_path(), "$brand: #f50;\n")?;
2018 let changed =
2019 generate_omena_bridge_sif_for_resolved_style_path_with_cache_context_and_storage(
2020 resolved.as_str(),
2021 &OmenaBridgeExternalSifCacheContextV0::default(),
2022 Some(&storage),
2023 )?;
2024 assert_ne!(changed, first);
2025 assert_eq!(
2026 local_external_sif_regeneration_count_for_test(resolved.as_str()),
2027 after_first + 1,
2028 "changed source bytes must miss the source-hash-addressed memory entry"
2029 );
2030
2031 let _ = fs::remove_dir_all(root);
2032 Ok(())
2033 }
2034
2035 #[test]
2036 #[ignore = "release-only external SIF cache timing probe"]
2037 fn release_external_sif_source_hash_cache_probe() -> Result<(), Box<dyn std::error::Error>> {
2038 assert!(
2039 !std::hint::black_box(cfg!(debug_assertions)),
2040 "run this ignored probe with cargo test --release"
2041 );
2042 let root = temp_dir("omena_bridge_external_sif_release_probe")?;
2043 fs::write(root.join("package.json"), r#"{"name":"workspace"}"#)?;
2044 let style = root.join("tokens.scss");
2045 let source = (0..400)
2046 .map(|index| format!("$token-{index}: #{:06x};\n", index * 97))
2047 .collect::<String>();
2048 fs::write(style.as_path(), source.as_bytes())?;
2049 let resolved = path_to_file_uri(style.as_path());
2050 let storage = fixture_cache_storage(style.as_path());
2051 clear_external_sif_memory_cache_for_storage_for_test(&storage);
2052 let cache_context = OmenaBridgeExternalSifCacheContextV0::default();
2053 let _ = generate_omena_bridge_sif_for_resolved_style_path_with_cache_context_and_storage(
2054 resolved.as_str(),
2055 &cache_context,
2056 Some(&storage),
2057 )?;
2058
2059 const ITERATIONS: usize = 200;
2060 let cached_start = std::time::Instant::now();
2061 for _ in 0..ITERATIONS {
2062 std::hint::black_box(
2063 generate_omena_bridge_sif_for_resolved_style_path_with_cache_context_and_storage(
2064 resolved.as_str(),
2065 &cache_context,
2066 Some(&storage),
2067 )?,
2068 );
2069 }
2070 let cached_elapsed = cached_start.elapsed();
2071
2072 let regenerated_start = std::time::Instant::now();
2073 for _ in 0..ITERATIONS {
2074 let bytes = fs::read(style.as_path())?;
2075 std::hint::black_box(compute_omena_sif_leaf_hash_v1(bytes.as_slice()));
2076 let text = String::from_utf8(bytes)?;
2077 std::hint::black_box(generate_static_omena_sif_v1(
2078 OmenaSifStaticGeneratorInputV1 {
2079 canonical_url: resolved.as_str(),
2080 source: text.as_str(),
2081 syntax: OmenaSifSourceSyntaxV1::Scss,
2082 },
2083 )?);
2084 }
2085 let regenerated_elapsed = regenerated_start.elapsed();
2086 eprintln!(
2087 "externalSifCacheReleaseProbe rules=400 iterations={ITERATIONS} cachedNs={} regeneratedNs={} speedup={:.3}",
2088 cached_elapsed.as_nanos(),
2089 regenerated_elapsed.as_nanos(),
2090 regenerated_elapsed.as_secs_f64() / cached_elapsed.as_secs_f64()
2091 );
2092 assert!(
2093 cached_elapsed < regenerated_elapsed,
2094 "source-hash memory validation must be cheaper than repeated local regeneration"
2095 );
2096
2097 let _ = fs::remove_dir_all(root);
2098 Ok(())
2099 }
2100
2101 #[test]
2102 fn global_external_sif_storage_never_cross_serves_workspace_partitions()
2103 -> Result<(), Box<dyn std::error::Error>> {
2104 let root = temp_dir("omena_bridge_workspace_partitioned_global_sif")?;
2105 let workspace_a = root.join("workspace-a");
2106 let workspace_b = root.join("workspace-b");
2107 fs::create_dir_all(workspace_a.as_path())?;
2108 fs::create_dir_all(workspace_b.as_path())?;
2109 fs::write(
2110 workspace_a.join("package.json"),
2111 r#"{"name":"workspace-a"}"#,
2112 )?;
2113 fs::write(
2114 workspace_b.join("package.json"),
2115 r#"{"name":"workspace-b"}"#,
2116 )?;
2117 let shared_package = root.join("node_modules").join("design-system");
2118 fs::create_dir_all(shared_package.as_path())?;
2119 let style = shared_package.join("tokens.scss");
2120 fs::write(style.as_path(), "$brand: #0af;\n")?;
2121
2122 let platform_cache_home = root.join("global");
2123 let workspace_identity_a = workspace_a.to_string_lossy().into_owned();
2124 let workspace_identity_b = workspace_b.to_string_lossy().into_owned();
2125 let derived_workspace_root = |workspace_root: &Path, workspace_identity: &str| {
2126 crate::cache_root::resolve_omena_cache_roots(
2127 crate::cache_root::CacheRootResolverInputsV0 {
2128 platform_cache_home: Some(platform_cache_home.as_path()),
2129 workspace_root: Some(workspace_root),
2130 workspace_identity: Some(workspace_identity),
2131 ..crate::cache_root::CacheRootResolverInputsV0::default()
2132 },
2133 )
2134 .workspace
2135 };
2136 let workspace_cache_root_a =
2137 derived_workspace_root(workspace_a.as_path(), workspace_identity_a.as_str())
2138 .ok_or_else(|| std::io::Error::other("workspace A cache root"))?;
2139 let workspace_cache_root_b =
2140 derived_workspace_root(workspace_b.as_path(), workspace_identity_b.as_str())
2141 .ok_or_else(|| std::io::Error::other("workspace B cache root"))?;
2142 assert_ne!(workspace_cache_root_a, workspace_cache_root_b);
2143 let storage_a = OmenaBridgeExternalSifStorageV0::from_workspace_cache_root_and_identity(
2144 workspace_cache_root_a,
2145 workspace_identity_a,
2146 );
2147 let storage_b = OmenaBridgeExternalSifStorageV0::from_workspace_cache_root_and_identity(
2148 workspace_cache_root_b,
2149 workspace_identity_b,
2150 );
2151 let cache_context = OmenaBridgeExternalSifCacheContextV0::default();
2152 clear_external_sif_memory_cache_for_storage_for_test(&storage_a);
2153 clear_external_sif_memory_cache_for_storage_for_test(&storage_b);
2154 let sif_a =
2155 generate_omena_bridge_sif_for_resolved_style_path_with_cache_context_and_storage(
2156 style.to_string_lossy().as_ref(),
2157 &cache_context,
2158 Some(&storage_a),
2159 )?;
2160 let sif_b =
2161 generate_omena_bridge_sif_for_resolved_style_path_with_cache_context_and_storage(
2162 style.to_string_lossy().as_ref(),
2163 &cache_context,
2164 Some(&storage_b),
2165 )?;
2166 assert_eq!(sif_a, sif_b);
2167
2168 let cache_dir_a = storage_a
2169 .workspace_cache_root()
2170 .join(EXTERNAL_SIF_CACHE_DIR);
2171 let cache_dir_b = storage_b
2172 .workspace_cache_root()
2173 .join(EXTERNAL_SIF_CACHE_DIR);
2174 let shard_files = |dir: &Path| -> Vec<PathBuf> {
2175 let Ok(entries) = fs::read_dir(dir) else {
2176 return Vec::new();
2177 };
2178 let mut files = entries
2179 .flatten()
2180 .map(|entry| entry.path())
2181 .filter(|path| {
2182 path.extension()
2183 .is_some_and(|extension| extension == "json")
2184 })
2185 .collect::<Vec<_>>();
2186 files.sort();
2187 files
2188 };
2189 let shard_a = shard_files(cache_dir_a.as_path());
2190 let shard_b = shard_files(cache_dir_b.as_path());
2191 assert_eq!(shard_a.len(), 1);
2192 assert_eq!(shard_b.len(), 1);
2193 assert_ne!(shard_a, shard_b);
2194 let shard_a_bytes = fs::read(shard_a[0].as_path())?;
2195 let mut poisoned_shard = serde_json::from_slice::<Value>(shard_a_bytes.as_slice())?;
2196 let mut poisoned_sif = sif_a.clone();
2197 poisoned_sif.exports.variables.clear();
2198 let poisoned_sif_json = write_omena_sif_json_v1(&poisoned_sif)?;
2199 poisoned_shard["sifJson"] = Value::String(poisoned_sif_json.clone());
2200 poisoned_shard["payloadDigest"] = Value::String(
2201 compute_omena_sif_leaf_hash_v1(poisoned_sif_json.as_bytes())
2202 .as_str()
2203 .to_string(),
2204 );
2205 fs::write(
2206 shard_a[0].as_path(),
2207 write_omena_canonical_json_bytes_v1(&poisoned_shard)?,
2208 )?;
2209 clear_external_sif_memory_cache_for_storage_for_test(&storage_a);
2210 let poisoned_a =
2211 generate_omena_bridge_sif_for_resolved_style_path_with_cache_context_and_storage(
2212 style.to_string_lossy().as_ref(),
2213 &cache_context,
2214 Some(&storage_a),
2215 )?;
2216 assert_eq!(poisoned_a, sif_a);
2217 assert_ne!(poisoned_a, poisoned_sif);
2218 let isolated_b =
2219 generate_omena_bridge_sif_for_resolved_style_path_with_cache_context_and_storage(
2220 style.to_string_lossy().as_ref(),
2221 &cache_context,
2222 Some(&storage_b),
2223 )?;
2224 assert_eq!(isolated_b, sif_b);
2225 assert_eq!(isolated_b, poisoned_a);
2226 assert_ne!(isolated_b, poisoned_sif);
2227 assert_eq!(shard_files(cache_dir_a.as_path()).len(), 1);
2228 assert_eq!(shard_files(cache_dir_b.as_path()).len(), 1);
2229 eprintln!(
2230 "externalSifStorage globalBase={} workspaceA={} workspaceB={} shardsA=1 shardsB=1 crossWorkspaceServe=false",
2231 platform_cache_home
2232 .join("omena")
2233 .join("workspaces")
2234 .display(),
2235 cache_dir_a.display(),
2236 cache_dir_b.display(),
2237 );
2238
2239 let _ = fs::remove_dir_all(root);
2240 Ok(())
2241 }
2242
2243 #[test]
2244 fn cache_payload_without_recorded_verdict_is_replaced_by_local_regeneration()
2245 -> Result<(), Box<dyn std::error::Error>> {
2246 let fixture = recorded_verdict_attack_fixture("cache-payload-without-verdict")?;
2247 write_poisoned_low_tier_fixture_shard(&fixture)?;
2248 clear_external_sif_memory_cache_for_storage_for_test(&fixture.storage);
2249
2250 let loaded =
2251 generate_omena_bridge_sif_for_resolved_style_path_with_cache_context_storage_and_trust(
2252 fixture.resolved.as_str(),
2253 &OmenaBridgeExternalSifCacheContextV0::default(),
2254 Some(&fixture.storage),
2255 )?;
2256
2257 assert_eq!(loaded.sif, fixture.original_sif);
2258 assert_ne!(loaded.sif, fixture.poisoned_sif);
2259 fs::remove_dir_all(fixture.root)?;
2260 Ok(())
2261 }
2262
2263 #[test]
2264 fn deleting_recorded_verdict_does_not_enable_cached_payload_substitution()
2265 -> Result<(), Box<dyn std::error::Error>> {
2266 let fixture = recorded_verdict_attack_fixture("deleted-verdict-cache-payload")?;
2267 write_fixture_recorded_shard_verdict(
2268 &fixture.storage,
2269 &fixture.original_sif,
2270 OmenaSifTrustTierV1::T2,
2271 )?;
2272 fs::remove_dir_all(fixture.verdict_dir.as_path())?;
2273 write_poisoned_low_tier_fixture_shard(&fixture)?;
2274 clear_external_sif_memory_cache_for_storage_for_test(&fixture.storage);
2275
2276 let loaded =
2277 generate_omena_bridge_sif_for_resolved_style_path_with_cache_context_storage_and_trust(
2278 fixture.resolved.as_str(),
2279 &OmenaBridgeExternalSifCacheContextV0::default(),
2280 Some(&fixture.storage),
2281 )?;
2282
2283 assert_eq!(loaded.sif, fixture.original_sif);
2284 assert_ne!(loaded.sif, fixture.poisoned_sif);
2285 fs::remove_dir_all(fixture.root)?;
2286 Ok(())
2287 }
2288
2289 #[test]
2290 fn parsed_sif_canonical_url_must_match_requested_resource()
2291 -> Result<(), Box<dyn std::error::Error>> {
2292 let fixture = recorded_verdict_attack_fixture("canonical-url-confused-deputy")?;
2293 let mut substituted_sif = fixture.poisoned_sif.clone();
2294 substituted_sif.canonical_url = "pkg:untrusted/substituted.scss".to_string();
2295 let substituted_sif_json = write_omena_sif_json_v1(&substituted_sif)?;
2296 let substituted_payload_digest =
2297 compute_omena_sif_leaf_hash_v1(substituted_sif_json.as_bytes());
2298 let substituted_sif_hash = compute_omena_sif_artifact_hash_v1(&substituted_sif)?;
2299 let mut shard = fixture.original_shard.clone();
2300 shard["sifJson"] = Value::String(substituted_sif_json);
2301 shard["payloadDigest"] = Value::String(substituted_payload_digest.as_str().to_string());
2302 shard["trustEnvelope"] = serde_json::to_value(OmenaSifShardTrustEnvelopeV1 {
2303 schema_version: OMENA_SIF_SHARD_TRUST_ENVELOPE_SCHEMA_VERSION_V1.to_string(),
2304 product: OMENA_SIF_SHARD_TRUST_ENVELOPE_PRODUCT_V1.to_string(),
2305 trust_tier: OmenaSifTrustTierV1::T1,
2306 payload_digest: substituted_payload_digest,
2307 signature: None,
2308 lock_binding: OmenaSifShardLockBindingV1 {
2309 canonical_url: fixture.resolved.clone(),
2310 sif_hash: substituted_sif_hash,
2311 },
2312 })?;
2313
2314 let loaded = validate_external_sif_cache_shard(
2315 &shard,
2316 fixture.key.as_str(),
2317 fixture.resolved.as_str(),
2318 fixture.source_hash.as_str(),
2319 fixture.resolved_base_dir.as_str(),
2320 None,
2321 &fixture.original_sif,
2322 );
2323 assert_eq!(
2324 loaded,
2325 Err(OmenaBridgeExternalSifShardRefusalV1::CanonicalUrlMismatch),
2326 "a parsed SIF for another canonical URL must reach the named confused-deputy refusal"
2327 );
2328 fs::remove_dir_all(fixture.root)?;
2329 Ok(())
2330 }
2331
2332 #[test]
2333 fn unverified_recorded_verdict_never_elevates_memory_or_disk_shards()
2334 -> Result<(), Box<dyn std::error::Error>> {
2335 let root = temp_dir("omena_bridge_recorded_shard_verdict")?;
2336 fs::write(root.join("package.json"), r#"{"name":"workspace"}"#)?;
2337 let style = root.join("tokens.scss");
2338 fs::write(style.as_path(), "$brand: #0af;\n")?;
2339 let resolved = path_to_file_uri(style.as_path());
2340 let storage = fixture_cache_storage(style.as_path());
2341 let cache_context = OmenaBridgeExternalSifCacheContextV0::default();
2342
2343 clear_external_sif_memory_cache_for_storage_for_test(&storage);
2344 let unsigned =
2345 generate_omena_bridge_sif_for_resolved_style_path_with_cache_context_and_storage(
2346 resolved.as_str(),
2347 &cache_context,
2348 Some(&storage),
2349 )?;
2350 let cache_dir = external_sif_cache_dir_for_path(style.as_path(), Some(&storage))
2351 .ok_or_else(|| std::io::Error::other("cache dir"))?;
2352 write_fixture_recorded_shard_verdict(&storage, &unsigned, OmenaSifTrustTierV1::T2)?;
2353 let verdict_dir = storage
2354 .recorded_verdict_dir()
2355 .ok_or_else(|| std::io::Error::other("verdict dir"))?;
2356
2357 let memory_hit =
2358 generate_omena_bridge_sif_for_resolved_style_path_with_cache_context_storage_and_trust(
2359 resolved.as_str(),
2360 &cache_context,
2361 Some(&storage),
2362 )?;
2363 assert_eq!(
2364 memory_hit.trust_envelope.trust_tier,
2365 OmenaSifTrustTierV1::T1
2366 );
2367 assert_eq!(
2368 memory_hit.trust_source,
2369 OmenaBridgeExternalSifTrustSourceV1::UnsignedLegacy
2370 );
2371 clear_external_sif_memory_cache_for_storage_for_test(&storage);
2372 let disk_hit =
2373 generate_omena_bridge_sif_for_resolved_style_path_with_cache_context_storage_and_trust(
2374 resolved.as_str(),
2375 &cache_context,
2376 Some(&storage),
2377 )?;
2378 assert_eq!(disk_hit.sif, unsigned);
2379 assert_eq!(disk_hit.trust_envelope.trust_tier, OmenaSifTrustTierV1::T1);
2380 assert_eq!(
2381 disk_hit.trust_source,
2382 OmenaBridgeExternalSifTrustSourceV1::UnsignedLegacy
2383 );
2384
2385 let shard_path = only_fixture_cache_shard_path(cache_dir.as_path())?;
2386 let mut elevated_shard = serde_json::from_slice::<Value>(&fs::read(shard_path)?)?;
2387 let key = elevated_shard
2388 .get("key")
2389 .and_then(Value::as_str)
2390 .ok_or_else(|| std::io::Error::other("shard key"))?
2391 .to_string();
2392 let source_hash = elevated_shard
2393 .get("sourceHash")
2394 .and_then(Value::as_str)
2395 .ok_or_else(|| std::io::Error::other("shard source hash"))?
2396 .to_string();
2397 let resolved_base_dir = elevated_shard
2398 .get("resolvedBaseDir")
2399 .and_then(Value::as_str)
2400 .ok_or_else(|| std::io::Error::other("shard resolved base"))?
2401 .to_string();
2402 let sif_hash = compute_omena_sif_artifact_hash_v1(&unsigned)?;
2403 elevated_shard["trustEnvelope"]["trustTier"] = Value::String("t2".to_string());
2404 elevated_shard["trustEnvelope"]["signature"] =
2405 serde_json::to_value(omena_sif::OmenaSifShardSignatureV1 {
2406 algorithm_version: omena_sif::OMENA_SIF_SHARD_SIGNATURE_ALGORITHM_VERSION_V1
2407 .to_string(),
2408 reference: "fixture:keyless-attestation".to_string(),
2409 signed_payload_digest: sif_hash,
2410 })?;
2411 assert_eq!(
2412 validate_external_sif_cache_shard(
2413 &elevated_shard,
2414 key.as_str(),
2415 resolved.as_str(),
2416 source_hash.as_str(),
2417 resolved_base_dir.as_str(),
2418 Some(verdict_dir),
2419 &unsigned,
2420 ),
2421 Err(OmenaBridgeExternalSifShardRefusalV1::RecordedVerdictSignatureVerificationFailed)
2422 );
2423
2424 let mut over_tier = elevated_shard;
2425 over_tier["trustEnvelope"]["trustTier"] = Value::String("t3".to_string());
2426 assert_eq!(
2427 validate_external_sif_cache_shard(
2428 &over_tier,
2429 key.as_str(),
2430 resolved.as_str(),
2431 source_hash.as_str(),
2432 resolved_base_dir.as_str(),
2433 Some(verdict_dir),
2434 &unsigned,
2435 ),
2436 Err(OmenaBridgeExternalSifShardRefusalV1::TierAboveRecordedVerdict)
2437 );
2438
2439 fs::remove_dir_all(root)?;
2440 Ok(())
2441 }
2442
2443 #[test]
2444 fn forged_recorded_verdict_cannot_mint_elevated_trust_for_poisoned_payload()
2445 -> Result<(), Box<dyn std::error::Error>> {
2446 let fixture = recorded_verdict_attack_fixture("forged-sidecar")?;
2447 write_fixture_recorded_shard_verdict(
2448 &fixture.storage,
2449 &fixture.poisoned_sif,
2450 OmenaSifTrustTierV1::T3,
2451 )?;
2452 let mut forged = fixture.original_shard.clone();
2453 forged["sifJson"] = Value::String(fixture.poisoned_sif_json.clone());
2454 forged["payloadDigest"] =
2455 Value::String(fixture.poisoned_payload_digest.as_str().to_string());
2456 forged["trustEnvelope"] = serde_json::to_value(OmenaSifShardTrustEnvelopeV1 {
2457 schema_version: OMENA_SIF_SHARD_TRUST_ENVELOPE_SCHEMA_VERSION_V1.to_string(),
2458 product: OMENA_SIF_SHARD_TRUST_ENVELOPE_PRODUCT_V1.to_string(),
2459 trust_tier: OmenaSifTrustTierV1::T3,
2460 payload_digest: fixture.poisoned_payload_digest.clone(),
2461 signature: Some(omena_sif::OmenaSifShardSignatureV1 {
2462 algorithm_version: omena_sif::OMENA_SIF_SHARD_SIGNATURE_ALGORITHM_VERSION_V1
2463 .to_string(),
2464 reference: "fixture:keyless-attestation".to_string(),
2465 signed_payload_digest: fixture.poisoned_sif_hash.clone(),
2466 }),
2467 lock_binding: OmenaSifShardLockBindingV1 {
2468 canonical_url: fixture.resolved.clone(),
2469 sif_hash: fixture.poisoned_sif_hash.clone(),
2470 },
2471 })?;
2472
2473 let attack = validate_external_sif_cache_shard(
2474 &forged,
2475 fixture.key.as_str(),
2476 fixture.resolved.as_str(),
2477 fixture.source_hash.as_str(),
2478 fixture.resolved_base_dir.as_str(),
2479 Some(fixture.verdict_dir.as_path()),
2480 &fixture.poisoned_sif,
2481 );
2482 assert_eq!(
2483 attack,
2484 Err(OmenaBridgeExternalSifShardRefusalV1::RecordedVerdictSignatureVerificationFailed),
2485 "forged local sidecar did not reach the typed signature refusal"
2486 );
2487 fs::remove_dir_all(fixture.root)?;
2488 Ok(())
2489 }
2490
2491 #[test]
2492 fn recorded_verdict_blocks_legacy_schema_downgrade_for_canonical_url()
2493 -> Result<(), Box<dyn std::error::Error>> {
2494 let fixture = recorded_verdict_attack_fixture("schema-downgrade")?;
2495 write_fixture_recorded_shard_verdict(
2496 &fixture.storage,
2497 &fixture.original_sif,
2498 OmenaSifTrustTierV1::T2,
2499 )?;
2500 let mut downgraded = fixture.original_shard.clone();
2501 downgraded["schemaVersion"] =
2502 Value::String(EXTERNAL_SIF_CACHE_LEGACY_SCHEMA_VERSION.to_string());
2503 downgraded["sifJson"] = Value::String(fixture.poisoned_sif_json.clone());
2504 downgraded["payloadDigest"] =
2505 Value::String(fixture.poisoned_payload_digest.as_str().to_string());
2506 downgraded
2507 .as_object_mut()
2508 .ok_or_else(|| std::io::Error::other("fixture shard object"))?
2509 .remove("trustEnvelope");
2510
2511 let attack = validate_external_sif_cache_shard(
2512 &downgraded,
2513 fixture.key.as_str(),
2514 fixture.resolved.as_str(),
2515 fixture.source_hash.as_str(),
2516 fixture.resolved_base_dir.as_str(),
2517 Some(fixture.verdict_dir.as_path()),
2518 &fixture.poisoned_sif,
2519 );
2520 assert_eq!(
2521 attack,
2522 Err(OmenaBridgeExternalSifShardRefusalV1::RecordedVerdictDowngrade),
2523 "schema downgrade did not reach the typed downgrade refusal"
2524 );
2525 fs::remove_dir_all(fixture.root)?;
2526 Ok(())
2527 }
2528
2529 #[test]
2530 fn recorded_verdict_blocks_tier_downgrade_for_canonical_url()
2531 -> Result<(), Box<dyn std::error::Error>> {
2532 let fixture = recorded_verdict_attack_fixture("tier-downgrade")?;
2533 write_fixture_recorded_shard_verdict(
2534 &fixture.storage,
2535 &fixture.original_sif,
2536 OmenaSifTrustTierV1::T2,
2537 )?;
2538 let mut downgraded = fixture.original_shard.clone();
2539 downgraded["sifJson"] = Value::String(fixture.poisoned_sif_json.clone());
2540 downgraded["payloadDigest"] =
2541 Value::String(fixture.poisoned_payload_digest.as_str().to_string());
2542 downgraded["trustEnvelope"] = serde_json::to_value(OmenaSifShardTrustEnvelopeV1 {
2543 schema_version: OMENA_SIF_SHARD_TRUST_ENVELOPE_SCHEMA_VERSION_V1.to_string(),
2544 product: OMENA_SIF_SHARD_TRUST_ENVELOPE_PRODUCT_V1.to_string(),
2545 trust_tier: OmenaSifTrustTierV1::T1,
2546 payload_digest: fixture.poisoned_payload_digest.clone(),
2547 signature: None,
2548 lock_binding: OmenaSifShardLockBindingV1 {
2549 canonical_url: fixture.resolved.clone(),
2550 sif_hash: fixture.poisoned_sif_hash.clone(),
2551 },
2552 })?;
2553
2554 let attack = validate_external_sif_cache_shard(
2555 &downgraded,
2556 fixture.key.as_str(),
2557 fixture.resolved.as_str(),
2558 fixture.source_hash.as_str(),
2559 fixture.resolved_base_dir.as_str(),
2560 Some(fixture.verdict_dir.as_path()),
2561 &fixture.poisoned_sif,
2562 );
2563 assert_eq!(
2564 attack,
2565 Err(OmenaBridgeExternalSifShardRefusalV1::RecordedVerdictDowngrade),
2566 "tier downgrade did not reach the typed downgrade refusal"
2567 );
2568 fs::remove_dir_all(fixture.root)?;
2569 Ok(())
2570 }
2571
2572 #[test]
2573 fn omena_published_bundle_is_the_only_fixture_path_to_elevated_advisory_tier()
2574 -> Result<(), Box<dyn std::error::Error>> {
2575 const BUNDLE_SHA256: &str =
2576 "0c99e37ac1b1d3cbfd677416a74218c9a1ca8e28c3aac95c7614549f3b3b0ce1";
2577 let root = temp_dir("verified-keyless-shard")?;
2578 let storage = OmenaBridgeExternalSifStorageV0::from_workspace_cache_root(root.clone());
2579 let verdict_dir = storage
2580 .recorded_verdict_dir()
2581 .ok_or_else(|| std::io::Error::other("verdict dir"))?;
2582 let bundle_dir = verdict_dir.join(EXTERNAL_SIF_RECORDED_BUNDLE_DIR_V1);
2583 fs::create_dir_all(bundle_dir.as_path())?;
2584 let bundle_reference = format!(
2585 "{EXTERNAL_SIF_RECORDED_BUNDLE_DIR_V1}/{BUNDLE_SHA256}{EXTERNAL_SIF_RECORDED_BUNDLE_SUFFIX_V1}"
2586 );
2587 let bundle_path = verdict_dir.join(bundle_reference.as_str());
2588 let bundle_bytes =
2589 include_bytes!("../tests/fixtures/published-sif-attestation.sigstore.json");
2590 fs::write(bundle_path.as_path(), bundle_bytes)?;
2591 let sif_source =
2592 include_str!("../tests/fixtures/published-sif-attestation.sif.json").trim_end();
2593 let sif = read_omena_sif_json_v1(sif_source)?;
2594 write_fixture_recorded_shard_verdict_with_reference(
2595 &storage,
2596 &sif,
2597 OmenaSifTrustTierV1::T3,
2598 bundle_reference.as_str(),
2599 )?;
2600
2601 let elevated = external_sif_result_with_recorded_verdict(sif.clone(), Some(verdict_dir))?;
2602 assert_eq!(elevated.trust_envelope.trust_tier, OmenaSifTrustTierV1::T3);
2603 assert_eq!(
2604 elevated.trust_source,
2605 OmenaBridgeExternalSifTrustSourceV1::RecordedVerdict
2606 );
2607 let payload_digest = compute_omena_sif_leaf_hash_v1(sif_source.as_bytes());
2608 let shard = json!({
2609 "schemaVersion": EXTERNAL_SIF_CACHE_SCHEMA_VERSION,
2610 "product": EXTERNAL_SIF_CACHE_PRODUCT,
2611 "key": "fixture-key",
2612 "canonicalUrl": sif.canonical_url,
2613 "sourceHash": "fixture-source-hash",
2614 "resolvedBaseDir": "fixture-base",
2615 "payloadDigest": payload_digest,
2616 "trustEnvelope": elevated.trust_envelope,
2617 "sifJson": sif_source,
2618 });
2619 let validated = validate_external_sif_cache_shard(
2620 &shard,
2621 "fixture-key",
2622 sif.canonical_url.as_str(),
2623 "fixture-source-hash",
2624 "fixture-base",
2625 Some(verdict_dir),
2626 &sif,
2627 )
2628 .map_err(|refusal| {
2629 std::io::Error::other(format!("verified fixture shard was refused: {refusal:?}"))
2630 })?;
2631 assert_eq!(validated.trust_envelope.trust_tier, OmenaSifTrustTierV1::T3);
2632
2633 fs::write(bundle_path.as_path(), b"{}")?;
2634 assert_eq!(
2635 validate_external_sif_cache_shard(
2636 &shard,
2637 "fixture-key",
2638 sif.canonical_url.as_str(),
2639 "fixture-source-hash",
2640 "fixture-base",
2641 Some(verdict_dir),
2642 &sif,
2643 ),
2644 Err(OmenaBridgeExternalSifShardRefusalV1::RecordedVerdictSignatureVerificationFailed)
2645 );
2646 let downgraded = external_sif_result_with_recorded_verdict(sif, Some(verdict_dir))?;
2647 assert_eq!(
2648 downgraded.trust_envelope.trust_tier,
2649 OmenaSifTrustTierV1::T1
2650 );
2651 assert_eq!(
2652 downgraded.trust_source,
2653 OmenaBridgeExternalSifTrustSourceV1::UnsignedLegacy
2654 );
2655
2656 fs::remove_dir_all(root)?;
2657 Ok(())
2658 }
2659
2660 #[test]
2661 fn published_bundle_cannot_authorize_a_different_recorded_tier()
2662 -> Result<(), Box<dyn std::error::Error>> {
2663 const BUNDLE_SHA256: &str =
2664 "0c99e37ac1b1d3cbfd677416a74218c9a1ca8e28c3aac95c7614549f3b3b0ce1";
2665 let root = temp_dir("published-bundle-tier-binding")?;
2666 let storage = OmenaBridgeExternalSifStorageV0::from_workspace_cache_root(root.clone());
2667 let verdict_dir = storage
2668 .recorded_verdict_dir()
2669 .ok_or_else(|| std::io::Error::other("verdict dir"))?;
2670 let bundle_reference = format!(
2671 "{EXTERNAL_SIF_RECORDED_BUNDLE_DIR_V1}/{BUNDLE_SHA256}{EXTERNAL_SIF_RECORDED_BUNDLE_SUFFIX_V1}"
2672 );
2673 let bundle_path = verdict_dir.join(bundle_reference.as_str());
2674 fs::create_dir_all(
2675 bundle_path
2676 .parent()
2677 .ok_or_else(|| std::io::Error::other("bundle parent"))?,
2678 )?;
2679 fs::write(
2680 bundle_path,
2681 include_bytes!("../tests/fixtures/published-sif-attestation.sigstore.json"),
2682 )?;
2683 let sif = read_omena_sif_json_v1(
2684 include_str!("../tests/fixtures/published-sif-attestation.sif.json").trim_end(),
2685 )?;
2686 write_fixture_recorded_shard_verdict_with_reference(
2687 &storage,
2688 &sif,
2689 OmenaSifTrustTierV1::T2,
2690 bundle_reference.as_str(),
2691 )?;
2692
2693 let result = external_sif_result_with_recorded_verdict(sif, Some(verdict_dir))?;
2694 assert_eq!(result.trust_envelope.trust_tier, OmenaSifTrustTierV1::T1);
2695 assert_eq!(
2696 result.trust_source,
2697 OmenaBridgeExternalSifTrustSourceV1::UnsignedLegacy
2698 );
2699
2700 fs::remove_dir_all(root)?;
2701 Ok(())
2702 }
2703
2704 #[test]
2705 fn published_bundle_cannot_elevate_a_different_sif_artifact()
2706 -> Result<(), Box<dyn std::error::Error>> {
2707 const BUNDLE_SHA256: &str =
2708 "0c99e37ac1b1d3cbfd677416a74218c9a1ca8e28c3aac95c7614549f3b3b0ce1";
2709 let root = temp_dir("published-bundle-artifact-substitution")?;
2710 let storage = OmenaBridgeExternalSifStorageV0::from_workspace_cache_root(root.clone());
2711 let verdict_dir = storage
2712 .recorded_verdict_dir()
2713 .ok_or_else(|| std::io::Error::other("verdict dir"))?;
2714 let bundle_dir = verdict_dir.join(EXTERNAL_SIF_RECORDED_BUNDLE_DIR_V1);
2715 fs::create_dir_all(bundle_dir.as_path())?;
2716 let bundle_reference = format!(
2717 "{EXTERNAL_SIF_RECORDED_BUNDLE_DIR_V1}/{BUNDLE_SHA256}{EXTERNAL_SIF_RECORDED_BUNDLE_SUFFIX_V1}"
2718 );
2719 fs::write(
2720 verdict_dir.join(bundle_reference.as_str()),
2721 include_bytes!("../tests/fixtures/published-sif-attestation.sigstore.json"),
2722 )?;
2723 let mut poisoned_sif = read_omena_sif_json_v1(
2724 include_str!("../tests/fixtures/published-sif-attestation.sif.json").trim_end(),
2725 )?;
2726 poisoned_sif.generator.name = "untrusted-generator".to_string();
2727 write_fixture_recorded_shard_verdict_with_reference(
2728 &storage,
2729 &poisoned_sif,
2730 OmenaSifTrustTierV1::T3,
2731 bundle_reference.as_str(),
2732 )?;
2733 let poisoned_hash = compute_omena_sif_artifact_hash_v1(&poisoned_sif)?;
2734 let verdict = load_recorded_shard_verdict(
2735 Some(verdict_dir),
2736 poisoned_sif.canonical_url.as_str(),
2737 &poisoned_hash,
2738 )
2739 .ok_or_else(|| std::io::Error::other("poisoned verdict"))?;
2740
2741 assert_eq!(
2742 verify_recorded_shard_verdict(Some(verdict_dir), &verdict),
2743 Err(OmenaBridgeExternalSifShardRefusalV1::RecordedVerdictSignatureVerificationFailed)
2744 );
2745 let result = external_sif_result_with_recorded_verdict(poisoned_sif, Some(verdict_dir))?;
2746 assert_eq!(result.trust_envelope.trust_tier, OmenaSifTrustTierV1::T1);
2747 assert_eq!(
2748 result.trust_source,
2749 OmenaBridgeExternalSifTrustSourceV1::UnsignedLegacy
2750 );
2751
2752 fs::remove_dir_all(root)?;
2753 Ok(())
2754 }
2755
2756 #[test]
2757 fn same_version_legacy_shard_without_trust_fields_remains_workspace_local()
2758 -> Result<(), Box<dyn std::error::Error>> {
2759 let root = temp_dir("omena_bridge_legacy_shard")?;
2760 fs::write(root.join("package.json"), r#"{"name":"workspace"}"#)?;
2761 let style = root.join("tokens.scss");
2762 fs::write(style.as_path(), "$brand: #0af;\n")?;
2763 let resolved = path_to_file_uri(style.as_path());
2764 let storage = fixture_cache_storage(style.as_path());
2765 let cache_context = OmenaBridgeExternalSifCacheContextV0::default();
2766 clear_external_sif_memory_cache_for_storage_for_test(&storage);
2767 let fresh =
2768 generate_omena_bridge_sif_for_resolved_style_path_with_cache_context_storage_and_trust(
2769 resolved.as_str(),
2770 &cache_context,
2771 Some(&storage),
2772 )?;
2773 let cache_dir = external_sif_cache_dir_for_path(style.as_path(), Some(&storage))
2774 .ok_or_else(|| std::io::Error::other("cache dir"))?;
2775 let shard_path = only_fixture_cache_shard_path(cache_dir.as_path())?;
2776 let mut legacy = serde_json::from_slice::<Value>(&fs::read(shard_path.as_path())?)?;
2777 legacy["schemaVersion"] =
2778 Value::String(EXTERNAL_SIF_CACHE_LEGACY_SCHEMA_VERSION.to_string());
2779 legacy
2780 .as_object_mut()
2781 .ok_or_else(|| std::io::Error::other("legacy shard object"))?
2782 .remove("trustEnvelope");
2783 fs::write(
2784 shard_path.as_path(),
2785 write_omena_canonical_json_bytes_v1(&legacy)?,
2786 )?;
2787 clear_external_sif_memory_cache_for_storage_for_test(&storage);
2788 let loaded =
2789 generate_omena_bridge_sif_for_resolved_style_path_with_cache_context_storage_and_trust(
2790 resolved.as_str(),
2791 &cache_context,
2792 Some(&storage),
2793 )?;
2794 assert_eq!(loaded.sif, fresh.sif);
2795 assert_eq!(loaded.trust_envelope.trust_tier, OmenaSifTrustTierV1::T1);
2796 assert_eq!(
2797 loaded.trust_source,
2798 OmenaBridgeExternalSifTrustSourceV1::UnsignedLegacy
2799 );
2800 let _ = fs::remove_dir_all(root);
2801 Ok(())
2802 }
2803
2804 #[test]
2805 fn external_sif_disk_cache_root_carries_self_ignore_markers()
2806 -> Result<(), Box<dyn std::error::Error>> {
2807 let root = temp_dir("omena_bridge_external_sif_cache_markers")?;
2808 fs::write(root.join("package.json"), r#"{"name":"workspace"}"#)?;
2809 let style = root.join("tokens.scss");
2810 fs::write(style.as_path(), "$brand: #0af;\n")?;
2811
2812 let cache_storage = fixture_cache_storage(style.as_path());
2813 generate_omena_bridge_sif_for_resolved_style_path_with_cache_context_and_storage(
2814 style.to_string_lossy().as_ref(),
2815 &OmenaBridgeExternalSifCacheContextV0::default(),
2816 Some(&cache_storage),
2817 )?;
2818
2819 let cache_dir = external_sif_cache_dir_for_path(style.as_path(), Some(&cache_storage))
2820 .ok_or_else(|| std::io::Error::other("cache dir"))?;
2821 let cache_root = cache_dir
2822 .parent()
2823 .ok_or_else(|| std::io::Error::other("cache root"))?;
2824 assert_eq!(
2825 fs::read(cache_root.join(".gitignore"))?,
2826 b"# machine-generated omena cache - safe to delete\n*\n",
2827 "external SIF cache root {} must self-ignore generated files",
2828 cache_root.display()
2829 );
2830 assert_eq!(
2831 fs::read(cache_root.join("CACHEDIR.TAG"))?,
2832 b"Signature: 8a477f597d28d172789f06886806bc55\n# This directory is an omena cache; contents are regenerable.\n",
2833 "external SIF cache root {} must carry the standard cache tag",
2834 cache_root.display()
2835 );
2836 let attribution = serde_json::from_slice::<Value>(&fs::read(
2837 cache_root.join(".omena-cache-owner.json"),
2838 )?)?;
2839 assert_eq!(
2840 attribution.get("product").and_then(Value::as_str),
2841 Some("omena.cache-root-attribution")
2842 );
2843 assert!(
2844 attribution
2845 .get("workspaceIdentity")
2846 .and_then(Value::as_str)
2847 .is_some_and(|identity| !identity.is_empty())
2848 );
2849 let _ = fs::remove_dir_all(root);
2850 Ok(())
2851 }
2852
2853 #[test]
2854 fn errors_gracefully_for_missing_resolved_style_module() {
2855 let missing = std::env::temp_dir().join("omena_bridge_sif_missing/does-not-exist.scss");
2856 let result =
2857 generate_omena_bridge_sif_for_resolved_style_path(missing.to_string_lossy().as_ref());
2858 assert!(result.is_err(), "expected error for missing entry");
2859 }
2860
2861 #[test]
2862 fn errors_gracefully_for_empty_resolved_path() {
2863 let result = generate_omena_bridge_sif_for_resolved_style_path("");
2864 assert!(result.is_err(), "expected error for empty path");
2865 }
2866
2867 #[test]
2868 fn resolves_tsconfig_path_alias_style_candidates() -> Result<(), Box<dyn std::error::Error>> {
2869 let root = temp_dir("omena_bridge_style_alias")?;
2870 let source = root.join("src/App.tsx");
2871 let style = root.join("src/styles/Button.module.scss");
2872 fs::create_dir_all(
2873 style
2874 .parent()
2875 .ok_or_else(|| std::io::Error::other("parent"))?,
2876 )?;
2877 fs::write(&source, "")?;
2878 fs::write(&style, ".root {}")?;
2879 fs::write(
2880 root.join("tsconfig.json"),
2881 r#"{"compilerOptions":{"baseUrl":".","paths":{"@styles/*":["src/styles/*"]}}}"#,
2882 )?;
2883
2884 let uri = resolve_omena_bridge_style_uri_for_specifier(
2885 path_to_file_uri(source.as_path()).as_str(),
2886 Some(path_to_file_uri(root.as_path()).as_str()),
2887 "@styles/Button.module.scss",
2888 );
2889
2890 assert_eq!(
2891 uri.as_deref(),
2892 Some(path_to_file_uri(style.as_path()).as_str())
2893 );
2894 let _ = fs::remove_dir_all(root);
2895 Ok(())
2896 }
2897
2898 #[test]
2899 fn resolves_tsconfig_extends_path_alias_style_candidates()
2900 -> Result<(), Box<dyn std::error::Error>> {
2901 let root = temp_dir("omena_bridge_style_alias_extends")?;
2902 let source = root.join("src/App.tsx");
2903 let style = root.join("src/shared/Button.module.scss");
2904 let config_dir = root.join("config");
2905 fs::create_dir_all(
2906 style
2907 .parent()
2908 .ok_or_else(|| std::io::Error::other("parent"))?,
2909 )?;
2910 fs::create_dir_all(config_dir.as_path())?;
2911 fs::write(&source, "")?;
2912 fs::write(&style, ".root {}")?;
2913 fs::write(
2914 config_dir.join("base.json"),
2915 r#"{"compilerOptions":{"baseUrl":"..","paths":{"$shared/*":["src/shared/*"]}}}"#,
2916 )?;
2917 fs::write(root.join("tsconfig.json"), r#"{"extends":"./config/base"}"#)?;
2918
2919 let uri = resolve_omena_bridge_style_uri_for_specifier(
2920 path_to_file_uri(source.as_path()).as_str(),
2921 Some(path_to_file_uri(root.as_path()).as_str()),
2922 "$shared/Button.module.scss",
2923 );
2924
2925 assert_eq!(
2926 uri.as_deref(),
2927 Some(path_to_file_uri(style.as_path()).as_str())
2928 );
2929 let _ = fs::remove_dir_all(root);
2930 Ok(())
2931 }
2932
2933 #[test]
2934 fn tsconfig_extends_child_paths_override_parent_paths() -> Result<(), Box<dyn std::error::Error>>
2935 {
2936 let root = temp_dir("omena_bridge_style_alias_extends_override")?;
2937 let source = root.join("src/App.tsx");
2938 let parent_style = root.join("src/parent/Button.module.scss");
2939 let child_style = root.join("src/child/Button.module.scss");
2940 fs::create_dir_all(
2941 parent_style
2942 .parent()
2943 .ok_or_else(|| std::io::Error::other("parent"))?,
2944 )?;
2945 fs::create_dir_all(
2946 child_style
2947 .parent()
2948 .ok_or_else(|| std::io::Error::other("child"))?,
2949 )?;
2950 fs::write(&source, "")?;
2951 fs::write(&parent_style, ".root { color: red; }")?;
2952 fs::write(&child_style, ".root { color: green; }")?;
2953 fs::write(
2954 root.join("base.json"),
2955 r#"{"compilerOptions":{"baseUrl":".","paths":{"$shared/*":["src/parent/*"]}}}"#,
2956 )?;
2957 fs::write(
2958 root.join("tsconfig.json"),
2959 r#"{"extends":"./base.json","compilerOptions":{"baseUrl":".","paths":{"$shared/*":["src/child/*"]}}}"#,
2960 )?;
2961
2962 let uri = resolve_omena_bridge_style_uri_for_specifier(
2963 path_to_file_uri(source.as_path()).as_str(),
2964 Some(path_to_file_uri(root.as_path()).as_str()),
2965 "$shared/Button.module.scss",
2966 );
2967
2968 assert_eq!(
2969 uri.as_deref(),
2970 Some(path_to_file_uri(child_style.as_path()).as_str())
2971 );
2972 let _ = fs::remove_dir_all(root);
2973 Ok(())
2974 }
2975
2976 #[test]
2977 fn resolves_vite_bundler_alias_style_candidates() -> Result<(), Box<dyn std::error::Error>> {
2978 let root = temp_dir("omena_bridge_style_bundler_alias")?;
2979 let source = root.join("src/App.tsx");
2980 let style = root.join("src/styles/Button.module.scss");
2981 fs::create_dir_all(
2982 style
2983 .parent()
2984 .ok_or_else(|| std::io::Error::other("parent"))?,
2985 )?;
2986 fs::write(&source, "")?;
2987 fs::write(&style, ".root {}")?;
2988 fs::write(
2989 root.join("vite.config.ts"),
2990 r#"export default { resolve: { alias: { "@styles": "./src/styles" } } };"#,
2991 )?;
2992
2993 let uri = resolve_omena_bridge_style_uri_for_specifier(
2994 path_to_file_uri(source.as_path()).as_str(),
2995 Some(path_to_file_uri(root.as_path()).as_str()),
2996 "@styles/Button.module.scss",
2997 );
2998
2999 assert_eq!(
3000 uri.as_deref(),
3001 Some(path_to_file_uri(style.as_path()).as_str())
3002 );
3003 let _ = fs::remove_dir_all(root);
3004 Ok(())
3005 }
3006
3007 #[test]
3008 fn resolves_webpack_exact_bundler_alias_style_candidates()
3009 -> Result<(), Box<dyn std::error::Error>> {
3010 let root = temp_dir("omena_bridge_style_bundler_exact_alias")?;
3011 let source = root.join("src/App.tsx");
3012 let style = root.join("src/styles/index.module.scss");
3013 fs::create_dir_all(
3014 style
3015 .parent()
3016 .ok_or_else(|| std::io::Error::other("parent"))?,
3017 )?;
3018 fs::write(&source, "")?;
3019 fs::write(&style, ".root {}")?;
3020 fs::write(
3021 root.join("webpack.config.js"),
3022 r#"module.exports = { resolve: { alias: [{ find: "@theme$", replacement: "./src/styles/index.module.scss" }] } };"#,
3023 )?;
3024
3025 let exact_uri = resolve_omena_bridge_style_uri_for_specifier(
3026 path_to_file_uri(source.as_path()).as_str(),
3027 Some(path_to_file_uri(root.as_path()).as_str()),
3028 "@theme",
3029 );
3030 let prefix_uri = resolve_omena_bridge_style_uri_for_specifier(
3031 path_to_file_uri(source.as_path()).as_str(),
3032 Some(path_to_file_uri(root.as_path()).as_str()),
3033 "@theme/Button.module.scss",
3034 );
3035
3036 assert_eq!(
3037 exact_uri.as_deref(),
3038 Some(path_to_file_uri(style.as_path()).as_str())
3039 );
3040 assert!(prefix_uri.is_none());
3041 let _ = fs::remove_dir_all(root);
3042 Ok(())
3043 }
3044
3045 #[test]
3046 fn resolves_sass_style_candidates_without_legacy_language_filter()
3047 -> Result<(), Box<dyn std::error::Error>> {
3048 let root = temp_dir("omena_bridge_style_sass")?;
3049 let source = root.join("src/App.tsx");
3050 let style = root.join("src/Button.module.sass");
3051 fs::create_dir_all(
3052 source
3053 .parent()
3054 .ok_or_else(|| std::io::Error::other("parent"))?,
3055 )?;
3056 fs::write(&source, "")?;
3057 fs::write(&style, ".root\n color: red\n")?;
3058
3059 let uri = resolve_omena_bridge_style_uri_for_specifier(
3060 path_to_file_uri(source.as_path()).as_str(),
3061 Some(path_to_file_uri(root.as_path()).as_str()),
3062 "./Button.module.sass",
3063 );
3064
3065 assert_eq!(
3066 uri.as_deref(),
3067 Some(path_to_file_uri(style.as_path()).as_str())
3068 );
3069 let _ = fs::remove_dir_all(root);
3070 Ok(())
3071 }
3072
3073 #[test]
3074 fn resolves_package_style_candidates_through_omena_resolver()
3075 -> Result<(), Box<dyn std::error::Error>> {
3076 let root = temp_dir("omena_bridge_style_package")?;
3077 let source = root.join("src/App.module.scss");
3078 let package_root = root.join("node_modules/@design/tokens");
3079 let style = package_root.join("src/index.scss");
3080 fs::create_dir_all(
3081 style
3082 .parent()
3083 .ok_or_else(|| std::io::Error::other("parent"))?,
3084 )?;
3085 fs::create_dir_all(
3086 source
3087 .parent()
3088 .ok_or_else(|| std::io::Error::other("source parent"))?,
3089 )?;
3090 fs::write(&source, "@use \"@design/tokens\";")?;
3091 fs::write(
3092 package_root.join("package.json"),
3093 r#"{"sass":"src/index.scss"}"#,
3094 )?;
3095 fs::write(&style, "$gap: 1rem;")?;
3096
3097 let uri = resolve_omena_bridge_style_uri_for_specifier(
3098 path_to_file_uri(source.as_path()).as_str(),
3099 Some(path_to_file_uri(root.as_path()).as_str()),
3100 "@design/tokens",
3101 );
3102
3103 assert_eq!(
3104 uri.as_deref(),
3105 Some(path_to_file_uri(style.as_path()).as_str())
3106 );
3107 let _ = fs::remove_dir_all(root);
3108 Ok(())
3109 }
3110
3111 #[test]
3112 fn resolves_sass_pkg_style_candidates_through_manifest_discovery()
3113 -> Result<(), Box<dyn std::error::Error>> {
3114 let root = temp_dir("omena_bridge_style_pkg_manifest")?;
3115 let source = root.join("src/App.module.scss");
3116 let package_root = root.join("node_modules/@design/tokens");
3117 let style = package_root.join("dist/theme.scss");
3118 fs::create_dir_all(
3119 style
3120 .parent()
3121 .ok_or_else(|| std::io::Error::other("style parent"))?,
3122 )?;
3123 fs::create_dir_all(
3124 source
3125 .parent()
3126 .ok_or_else(|| std::io::Error::other("source parent"))?,
3127 )?;
3128 fs::write(&source, "@use \"pkg:@design/tokens/theme\";")?;
3129 fs::write(
3130 package_root.join("package.json"),
3131 r#"{"exports":{"./theme":{"sass":"./dist/theme.scss"}}}"#,
3132 )?;
3133 fs::write(&style, "$gap: 1rem;")?;
3134
3135 let uri = resolve_omena_bridge_style_uri_for_specifier(
3136 path_to_file_uri(source.as_path()).as_str(),
3137 Some(path_to_file_uri(root.as_path()).as_str()),
3138 "pkg:@design/tokens/theme",
3139 );
3140
3141 assert_eq!(
3142 uri.as_deref(),
3143 Some(path_to_file_uri(style.as_path()).as_str())
3144 );
3145 let _ = fs::remove_dir_all(root);
3146 Ok(())
3147 }
3148
3149 #[test]
3150 fn resolves_package_import_style_candidates_through_workspace_manifests()
3151 -> Result<(), Box<dyn std::error::Error>> {
3152 let root = temp_dir("omena_bridge_style_package_import_manifest")?;
3153 let source = root.join("src/App.module.scss");
3154 let package_root = root.join("node_modules/@design/tokens");
3155 let style = package_root.join("dist/theme.scss");
3156 fs::create_dir_all(
3157 style
3158 .parent()
3159 .ok_or_else(|| std::io::Error::other("style parent"))?,
3160 )?;
3161 fs::create_dir_all(
3162 source
3163 .parent()
3164 .ok_or_else(|| std::io::Error::other("source parent"))?,
3165 )?;
3166 fs::write(&source, "@use \"#theme\" as tokens;")?;
3167 fs::write(
3168 root.join("package.json"),
3169 r##"{"imports":{"#theme":"@design/tokens/theme"}}"##,
3170 )?;
3171 fs::write(
3172 package_root.join("package.json"),
3173 r#"{"exports":{"./theme":{"sass":"./dist/theme.scss"}}}"#,
3174 )?;
3175 fs::write(&style, "$gap: 1rem;")?;
3176
3177 let uri = resolve_omena_bridge_style_uri_for_specifier(
3178 path_to_file_uri(source.as_path()).as_str(),
3179 Some(path_to_file_uri(root.as_path()).as_str()),
3180 "#theme",
3181 );
3182
3183 assert_eq!(
3184 uri.as_deref(),
3185 Some(path_to_file_uri(style.as_path()).as_str())
3186 );
3187 let _ = fs::remove_dir_all(root);
3188 Ok(())
3189 }
3190
3191 #[cfg(unix)]
3192 #[test]
3193 fn resolves_symlinked_package_style_candidates_to_canonical_uri()
3194 -> Result<(), Box<dyn std::error::Error>> {
3195 let root = temp_dir("omena_bridge_style_symlinked_package")?;
3196 let source = root.join("src/App.module.scss");
3197 let real_package = root.join(".pnpm/@design+tokens@1.0.0/node_modules/@design/tokens");
3198 let linked_scope = root.join("node_modules/@design");
3199 let linked_package = linked_scope.join("tokens");
3200 let style = real_package.join("src/index.scss");
3201 fs::create_dir_all(
3202 style
3203 .parent()
3204 .ok_or_else(|| std::io::Error::other("style parent"))?,
3205 )?;
3206 fs::create_dir_all(
3207 source
3208 .parent()
3209 .ok_or_else(|| std::io::Error::other("source parent"))?,
3210 )?;
3211 fs::create_dir_all(linked_scope.as_path())?;
3212 fs::write(&source, "@use \"@design/tokens\";")?;
3213 fs::write(
3214 real_package.join("package.json"),
3215 r#"{"sass":"src/index.scss"}"#,
3216 )?;
3217 fs::write(&style, "$gap: 1rem;")?;
3218 std::os::unix::fs::symlink(real_package.as_path(), linked_package.as_path())?;
3219
3220 let uri = resolve_omena_bridge_style_uri_for_specifier(
3221 path_to_file_uri(source.as_path()).as_str(),
3222 Some(path_to_file_uri(root.as_path()).as_str()),
3223 "@design/tokens",
3224 );
3225 let expected_uri = path_to_file_uri(fs::canonicalize(style)?.as_path());
3226
3227 assert_eq!(uri.as_deref(), Some(expected_uri.as_str()));
3228 let _ = fs::remove_dir_all(root);
3229 Ok(())
3230 }
3231
3232 #[test]
3233 fn does_not_fabricate_missing_package_style_candidates()
3234 -> Result<(), Box<dyn std::error::Error>> {
3235 let root = temp_dir("omena_bridge_style_missing_package")?;
3236 let source = root.join("src/App.tsx");
3237 fs::create_dir_all(
3238 source
3239 .parent()
3240 .ok_or_else(|| std::io::Error::other("parent"))?,
3241 )?;
3242 fs::write(&source, "")?;
3243
3244 let uri = resolve_omena_bridge_style_uri_for_specifier(
3245 path_to_file_uri(source.as_path()).as_str(),
3246 Some(path_to_file_uri(root.as_path()).as_str()),
3247 "@design/tokens",
3248 );
3249
3250 assert!(uri.is_none(), "{uri:?}");
3251 let _ = fs::remove_dir_all(root);
3252 Ok(())
3253 }
3254
3255 #[test]
3256 fn emits_percent_encoded_file_uris_for_route_group_paths()
3257 -> Result<(), Box<dyn std::error::Error>> {
3258 let root = temp_dir("omena_bridge_style_route_group")?;
3259 let source = root.join("app/(marketing)/page.tsx");
3260 let style = root.join("app/(marketing)/Card.module.scss");
3261 fs::create_dir_all(
3262 source
3263 .parent()
3264 .ok_or_else(|| std::io::Error::other("parent"))?,
3265 )?;
3266 fs::write(&source, "")?;
3267 fs::write(&style, ".card {}")?;
3268
3269 let uri = resolve_omena_bridge_style_uri_for_specifier(
3270 path_to_file_uri(source.as_path()).as_str(),
3271 Some(path_to_file_uri(root.as_path()).as_str()),
3272 "./Card.module.scss",
3273 )
3274 .ok_or_else(|| std::io::Error::other("route group style should resolve"))?;
3275
3276 assert!(uri.contains("%28marketing%29"), "{uri}");
3277 assert_eq!(uri, path_to_file_uri(style.as_path()));
3278 let _ = fs::remove_dir_all(root);
3279 Ok(())
3280 }
3281
3282 #[test]
3283 fn declares_bridge_owned_style_resolution_boundary() {
3284 let summary = summarize_omena_bridge_style_resolution_boundary();
3285
3286 assert_eq!(summary.product, "omena-bridge.style-resolution");
3287 assert_eq!(summary.owner_crate, "omena-bridge");
3288 assert!(summary.supported_specifier_kinds.contains(&"tsconfigPaths"));
3289 assert!(
3290 summary
3291 .supported_specifier_kinds
3292 .contains(&"bundlerAliases")
3293 );
3294 assert!(summary.supported_specifier_kinds.contains(&"npmPackages"));
3295 assert!(
3296 summary
3297 .request_path_policy
3298 .contains(&"pathAliasResolutionFollowsRelativeTsconfigExtends")
3299 );
3300 assert!(
3301 summary
3302 .request_path_policy
3303 .contains(&"bundlerAliasResolutionUsesLiteralViteWebpackConfig")
3304 );
3305 assert!(
3306 summary
3307 .request_path_policy
3308 .contains(&"lspServerOwnsOnlyDocumentRoutingAndUriRangeMapping")
3309 );
3310 }
3311
3312 #[test]
3313 fn resolves_nested_next_config_alias_style_candidates() -> Result<(), Box<dyn std::error::Error>>
3314 {
3315 let root = temp_dir("omena_bridge_style_next_nested")?;
3316 let app_dir = root.join("apps/web");
3317 let source = app_dir.join("src/App.tsx");
3318 let style = app_dir.join("src/styles/Button.module.scss");
3319 fs::create_dir_all(
3320 style
3321 .parent()
3322 .ok_or_else(|| std::io::Error::other("style parent"))?,
3323 )?;
3324 fs::write(&source, "")?;
3325 fs::write(&style, ".root {}")?;
3326 fs::write(
3327 app_dir.join("next.config.mjs"),
3328 r#"export default { resolve: { alias: { "@styles": "./src/styles" } } };"#,
3329 )?;
3330
3331 let uri = resolve_omena_bridge_style_uri_for_specifier(
3332 path_to_file_uri(source.as_path()).as_str(),
3333 Some(path_to_file_uri(root.as_path()).as_str()),
3334 "@styles/Button.module.scss",
3335 );
3336
3337 assert_eq!(
3338 uri.as_deref(),
3339 Some(path_to_file_uri(style.as_path()).as_str())
3340 );
3341 let _ = fs::remove_dir_all(root);
3342 Ok(())
3343 }
3344
3345 #[test]
3346 fn resolves_tilde_package_style_candidates() -> Result<(), Box<dyn std::error::Error>> {
3347 let root = temp_dir("omena_bridge_style_tilde_package")?;
3348 let source = root.join("src/App.module.scss");
3349 let package_root = root.join("node_modules/@scope/theme");
3350 let style = package_root.join("index.scss");
3351 fs::create_dir_all(
3352 source
3353 .parent()
3354 .ok_or_else(|| std::io::Error::other("source parent"))?,
3355 )?;
3356 fs::create_dir_all(package_root.as_path())?;
3357 fs::write(&source, "@use \"~@scope/theme\";")?;
3358 fs::write(
3359 package_root.join("package.json"),
3360 r#"{"sass":"./index.scss"}"#,
3361 )?;
3362 fs::write(&style, "$brand: red;")?;
3363
3364 let uri = resolve_omena_bridge_style_uri_for_specifier(
3365 path_to_file_uri(source.as_path()).as_str(),
3366 Some(path_to_file_uri(root.as_path()).as_str()),
3367 "~@scope/theme",
3368 );
3369
3370 assert_eq!(
3371 uri.as_deref(),
3372 Some(path_to_file_uri(style.as_path()).as_str())
3373 );
3374 let _ = fs::remove_dir_all(root);
3375 Ok(())
3376 }
3377
3378 fn temp_dir(prefix: &str) -> Result<PathBuf, Box<dyn std::error::Error>> {
3379 let suffix = SystemTime::now()
3380 .duration_since(SystemTime::UNIX_EPOCH)?
3381 .as_nanos();
3382 let path = std::env::temp_dir().join(format!("{prefix}_{suffix}"));
3383 fs::create_dir_all(path.as_path())?;
3384 Ok(path)
3385 }
3386
3387 fn fixture_cache_storage(path: &Path) -> OmenaBridgeExternalSifStorageV0 {
3388 OmenaBridgeExternalSifStorageV0::from_workspace_cache_root(
3389 path.parent()
3390 .unwrap_or_else(|| Path::new("."))
3391 .join(".cache")
3392 .join("omena"),
3393 )
3394 }
3395
3396 fn only_fixture_cache_shard_path(
3397 cache_dir: &Path,
3398 ) -> Result<PathBuf, Box<dyn std::error::Error>> {
3399 let mut shards = fs::read_dir(cache_dir)?
3400 .flatten()
3401 .map(|entry| entry.path())
3402 .filter(|path| {
3403 path.extension()
3404 .is_some_and(|extension| extension == "json")
3405 })
3406 .collect::<Vec<_>>();
3407 shards.sort();
3408 if shards.len() != 1 {
3409 return Err(std::io::Error::other(format!(
3410 "expected one fixture cache shard, found {}",
3411 shards.len()
3412 ))
3413 .into());
3414 }
3415 Ok(shards.remove(0))
3416 }
3417
3418 fn write_fixture_recorded_shard_verdict(
3419 storage: &OmenaBridgeExternalSifStorageV0,
3420 sif: &OmenaSifV1,
3421 trust_tier: OmenaSifTrustTierV1,
3422 ) -> Result<(), Box<dyn std::error::Error>> {
3423 write_fixture_recorded_shard_verdict_with_reference(
3424 storage,
3425 sif,
3426 trust_tier,
3427 "fixture:keyless-attestation",
3428 )
3429 }
3430
3431 fn write_fixture_recorded_shard_verdict_with_reference(
3432 storage: &OmenaBridgeExternalSifStorageV0,
3433 sif: &OmenaSifV1,
3434 trust_tier: OmenaSifTrustTierV1,
3435 signature_reference: &str,
3436 ) -> Result<(), Box<dyn std::error::Error>> {
3437 let sif_hash = compute_omena_sif_artifact_hash_v1(sif)?;
3438 let verdict = OmenaSifShardRecordedVerdictV1 {
3439 schema_version: omena_sif::OMENA_SIF_SHARD_RECORDED_VERDICT_SCHEMA_VERSION_V1
3440 .to_string(),
3441 product: omena_sif::OMENA_SIF_SHARD_RECORDED_VERDICT_PRODUCT_V1.to_string(),
3442 verification_owner: omena_sif::OMENA_SIF_SHARD_VERIFICATION_OWNER_V1.to_string(),
3443 canonical_url: sif.canonical_url.clone(),
3444 sif_hash: sif_hash.clone(),
3445 trust_tier,
3446 signature: omena_sif::OmenaSifShardSignatureV1 {
3447 algorithm_version: omena_sif::OMENA_SIF_SHARD_SIGNATURE_ALGORITHM_VERSION_V1
3448 .to_string(),
3449 reference: signature_reference.to_string(),
3450 signed_payload_digest: sif_hash.clone(),
3451 },
3452 };
3453 omena_sif::validate_omena_sif_shard_recorded_verdict_v1(&verdict)?;
3454 let verdict_dir = storage
3455 .recorded_verdict_dir()
3456 .ok_or_else(|| std::io::Error::other("verdict dir"))?;
3457 fs::create_dir_all(verdict_dir)?;
3458 let address = compute_omena_sif_shard_recorded_verdict_address_v1(
3459 sif.canonical_url.as_str(),
3460 &sif_hash,
3461 )?;
3462 let hex = address
3463 .as_str()
3464 .strip_prefix("blake3:")
3465 .ok_or_else(|| std::io::Error::other("verdict address"))?;
3466 let source = omena_sif::write_omena_sif_shard_recorded_verdict_json_v1(&verdict)?;
3467 fs::write(verdict_dir.join(format!("{hex}.json")), source)?;
3468 Ok(())
3469 }
3470
3471 struct RecordedVerdictAttackFixture {
3472 root: PathBuf,
3473 resolved: String,
3474 storage: OmenaBridgeExternalSifStorageV0,
3475 verdict_dir: PathBuf,
3476 key: String,
3477 source_hash: String,
3478 resolved_base_dir: String,
3479 original_shard: Value,
3480 original_sif: OmenaSifV1,
3481 poisoned_sif: OmenaSifV1,
3482 poisoned_sif_json: String,
3483 poisoned_payload_digest: omena_sif::OmenaSifDigestV1,
3484 poisoned_sif_hash: omena_sif::OmenaSifDigestV1,
3485 }
3486
3487 fn recorded_verdict_attack_fixture(
3488 label: &str,
3489 ) -> Result<RecordedVerdictAttackFixture, Box<dyn std::error::Error>> {
3490 let root = temp_dir(label)?;
3491 fs::write(root.join("package.json"), r#"{"name":"workspace"}"#)?;
3492 let style = root.join("tokens.scss");
3493 fs::write(style.as_path(), "$brand: #0af;\n")?;
3494 let resolved = path_to_file_uri(style.as_path());
3495 let storage = fixture_cache_storage(style.as_path());
3496 clear_external_sif_memory_cache_for_storage_for_test(&storage);
3497 let original_sif =
3498 generate_omena_bridge_sif_for_resolved_style_path_with_cache_context_and_storage(
3499 resolved.as_str(),
3500 &OmenaBridgeExternalSifCacheContextV0::default(),
3501 Some(&storage),
3502 )?;
3503 let cache_dir = external_sif_cache_dir_for_path(style.as_path(), Some(&storage))
3504 .ok_or_else(|| std::io::Error::other("cache dir"))?;
3505 let original_shard = serde_json::from_slice::<Value>(&fs::read(
3506 only_fixture_cache_shard_path(cache_dir.as_path())?,
3507 )?)?;
3508 let key = original_shard
3509 .get("key")
3510 .and_then(Value::as_str)
3511 .ok_or_else(|| std::io::Error::other("shard key"))?
3512 .to_string();
3513 let source_hash = original_shard
3514 .get("sourceHash")
3515 .and_then(Value::as_str)
3516 .ok_or_else(|| std::io::Error::other("source hash"))?
3517 .to_string();
3518 let resolved_base_dir = original_shard
3519 .get("resolvedBaseDir")
3520 .and_then(Value::as_str)
3521 .ok_or_else(|| std::io::Error::other("resolved base"))?
3522 .to_string();
3523 let mut poisoned_sif = original_sif.clone();
3524 poisoned_sif.exports.variables.clear();
3525 let poisoned_sif_json = write_omena_sif_json_v1(&poisoned_sif)?;
3526 let poisoned_payload_digest = compute_omena_sif_leaf_hash_v1(poisoned_sif_json.as_bytes());
3527 let poisoned_sif_hash = compute_omena_sif_artifact_hash_v1(&poisoned_sif)?;
3528 let verdict_dir = storage
3529 .recorded_verdict_dir()
3530 .ok_or_else(|| std::io::Error::other("verdict dir"))?
3531 .to_path_buf();
3532 Ok(RecordedVerdictAttackFixture {
3533 root,
3534 resolved,
3535 storage,
3536 verdict_dir,
3537 key,
3538 source_hash,
3539 resolved_base_dir,
3540 original_shard,
3541 original_sif,
3542 poisoned_sif,
3543 poisoned_sif_json,
3544 poisoned_payload_digest,
3545 poisoned_sif_hash,
3546 })
3547 }
3548
3549 fn write_poisoned_low_tier_fixture_shard(
3550 fixture: &RecordedVerdictAttackFixture,
3551 ) -> Result<(), Box<dyn std::error::Error>> {
3552 let mut poisoned = fixture.original_shard.clone();
3553 poisoned["sifJson"] = Value::String(fixture.poisoned_sif_json.clone());
3554 poisoned["payloadDigest"] =
3555 Value::String(fixture.poisoned_payload_digest.as_str().to_string());
3556 poisoned["trustEnvelope"] = serde_json::to_value(OmenaSifShardTrustEnvelopeV1 {
3557 schema_version: OMENA_SIF_SHARD_TRUST_ENVELOPE_SCHEMA_VERSION_V1.to_string(),
3558 product: OMENA_SIF_SHARD_TRUST_ENVELOPE_PRODUCT_V1.to_string(),
3559 trust_tier: OmenaSifTrustTierV1::T1,
3560 payload_digest: fixture.poisoned_payload_digest.clone(),
3561 signature: None,
3562 lock_binding: OmenaSifShardLockBindingV1 {
3563 canonical_url: fixture.resolved.clone(),
3564 sif_hash: fixture.poisoned_sif_hash.clone(),
3565 },
3566 })?;
3567 let cache_dir = fixture
3568 .storage
3569 .workspace_cache_root()
3570 .join(EXTERNAL_SIF_CACHE_DIR);
3571 let shard_path = only_fixture_cache_shard_path(cache_dir.as_path())?;
3572 fs::write(shard_path, write_omena_canonical_json_bytes_v1(&poisoned)?)?;
3573 Ok(())
3574 }
3575
3576 fn generate_fixture_sif_for_resolved_style_path(
3577 path: &Path,
3578 resolved_path: &str,
3579 ) -> Result<OmenaSifV1, String> {
3580 let cache_storage = fixture_cache_storage(path);
3581 generate_omena_bridge_sif_for_resolved_style_path_with_cache_context_and_storage(
3582 resolved_path,
3583 &OmenaBridgeExternalSifCacheContextV0::default(),
3584 Some(&cache_storage),
3585 )
3586 }
3587}