From 02ff456fdf437232016972092bdc18b25748e255 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 21 Jul 2026 20:14:07 +0000 Subject: [PATCH 1/3] fix(selur): add missing tokio AsyncReadExt import so the crate compiles selur has never compiled: sign_image had a function-local `use tokio::io::AsyncReadExt;` but generate_keypair (a second call site added later) also calls `.take(..).read_to_string(..)` without the trait in scope, so `file.take(1024*1024)` resolved to Iterator::take on tokio::fs::File and failed with E0599/E0282. The GitLab cargo-* jobs never caught it because they were dormant until they were wired to run (#71); the NIF/service build in nif-build.yml doesn't cover services/. Hoist the import to module scope (covering both call sites, dropping the now-redundant local one) and drop the unnecessary `mut` on the two read-only File bindings. Verified: `cargo build --release` and `cargo test` both pass. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01Kq24sZCEohSrNFXSuEuz6C --- services/selur/src/main.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/services/selur/src/main.rs b/services/selur/src/main.rs index def4c00..8a8b6e4 100644 --- a/services/selur/src/main.rs +++ b/services/selur/src/main.rs @@ -16,6 +16,7 @@ use serde::{Deserialize, Serialize}; use std::path::PathBuf; use std::process::Stdio; use tokio::fs; +use tokio::io::AsyncReadExt; use tokio::process::Command; use tower_http::trace::TraceLayer; use tracing::{error, info, warn}; @@ -169,11 +170,10 @@ async fn sign_image( // Read public key (limit to 1MB) let pub_key_path = key_path.with_extension("pub"); - let mut file = fs::File::open(&pub_key_path) + let file = fs::File::open(&pub_key_path) .await .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, format!("Failed to open public key: {}", e)))?; let mut public_key = String::new(); - use tokio::io::AsyncReadExt; file.take(1024 * 1024).read_to_string(&mut public_key) .await .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, format!("Failed to read public key: {}", e)))?; @@ -312,7 +312,7 @@ async fn generate_keypair( } // Read public key (limit to 1MB) - let mut file = fs::File::open(&public_key_path) + let file = fs::File::open(&public_key_path) .await .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, format!("Failed to open public key: {}", e)))?; let mut public_key = String::new(); From 1d743b2c7813cf1ce5765b8633cbd6c7823f947f Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 21 Jul 2026 20:15:25 +0000 Subject: [PATCH 2/3] style(rust): cargo fmt all first-party crates The GitLab `rustfmt` job (not allow_failure) loops over every first-party crate running `cargo fmt -- --check`; all eight failed because the code had never been fmt-checked while the job was dormant. Pure `cargo fmt` output across the eight buildable crates (opsm-ui/tui, opsm_pq_nif, and the six services/*), no logic changes. quic_transport is left untouched (quarantined; does not build). Verified: every crate is now fmt-clean and still builds. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01Kq24sZCEohSrNFXSuEuz6C --- opsm-ui/tui/src/lib.rs | 242 +++++++++++++++++++----- opsm-ui/tui/src/main.rs | 218 ++++++++++++++------- opsm_ex/native/opsm_pq_nif/src/lib.rs | 4 +- services/checky-monkey/src/main.rs | 88 +++++---- services/oikos/src/main.rs | 16 +- services/palimpsest-license/src/main.rs | 106 ++++++++--- services/selur/src/main.rs | 87 +++++++-- services/svalinn/src/main.rs | 76 ++++---- services/vordr/src/main.rs | 37 ++-- 9 files changed, 616 insertions(+), 258 deletions(-) diff --git a/opsm-ui/tui/src/lib.rs b/opsm-ui/tui/src/lib.rs index 8e2219e..27de86f 100644 --- a/opsm-ui/tui/src/lib.rs +++ b/opsm-ui/tui/src/lib.rs @@ -207,7 +207,10 @@ impl AppState { // SPARK post-condition debug_assert!(state.running, "SPARK post: Initialize sets Running = True"); - debug_assert!(state.check_invariant(), "SPARK post: Initialize establishes invariant"); + debug_assert!( + state.check_invariant(), + "SPARK post: Initialize establishes invariant" + ); state } @@ -235,20 +238,48 @@ impl AppState { // Accessors // ========================================================================= - pub fn is_running(&self) -> bool { self.running } - pub fn width(&self) -> u16 { self.width } - pub fn height(&self) -> u16 { self.height } - pub fn cursor(&self) -> usize { self.cursor } - pub fn scroll_offset(&self) -> usize { self.scroll_offset } - pub fn view(&self) -> View { self.view } - pub fn filter(&self) -> Filter { self.filter } - pub fn input_mode(&self) -> InputMode { self.input_mode } - pub fn packages(&self) -> &[PackageEntry] { &self.packages } - pub fn history(&self) -> &[HistoryEntry] { &self.history } - pub fn search_query(&self) -> &str { &self.search_query } - pub fn status_message(&self) -> &str { &self.status_message } - pub fn confirm_action(&self) -> Option<&str> { self.confirm_action.as_deref() } - pub fn confirm_package(&self) -> Option<&str> { self.confirm_package.as_deref() } + pub fn is_running(&self) -> bool { + self.running + } + pub fn width(&self) -> u16 { + self.width + } + pub fn height(&self) -> u16 { + self.height + } + pub fn cursor(&self) -> usize { + self.cursor + } + pub fn scroll_offset(&self) -> usize { + self.scroll_offset + } + pub fn view(&self) -> View { + self.view + } + pub fn filter(&self) -> Filter { + self.filter + } + pub fn input_mode(&self) -> InputMode { + self.input_mode + } + pub fn packages(&self) -> &[PackageEntry] { + &self.packages + } + pub fn history(&self) -> &[HistoryEntry] { + &self.history + } + pub fn search_query(&self) -> &str { + &self.search_query + } + pub fn status_message(&self) -> &str { + &self.status_message + } + pub fn confirm_action(&self) -> Option<&str> { + self.confirm_action.as_deref() + } + pub fn confirm_package(&self) -> Option<&str> { + self.confirm_package.as_deref() + } /// Visible height for the package list (content area minus chrome). pub fn visible_height(&self) -> usize { @@ -270,7 +301,10 @@ impl AppState { self.width = w.clamp(1, MAX_WIDTH); self.height = h.clamp(1, MAX_HEIGHT); self.clamp_cursor(); - debug_assert!(self.check_invariant(), "SPARK post: resize maintains invariant"); + debug_assert!( + self.check_invariant(), + "SPARK post: resize maintains invariant" + ); } /// Move cursor up. @@ -281,12 +315,19 @@ impl AppState { self.scroll_offset = self.cursor; } } - debug_assert!(self.check_invariant(), "SPARK post: cursor_up maintains invariant"); + debug_assert!( + self.check_invariant(), + "SPARK post: cursor_up maintains invariant" + ); } /// Move cursor down. pub fn cursor_down(&mut self) { - let max = if self.packages.is_empty() { 0 } else { self.packages.len() - 1 }; + let max = if self.packages.is_empty() { + 0 + } else { + self.packages.len() - 1 + }; if self.cursor < max { self.cursor += 1; let vis = self.visible_height(); @@ -294,7 +335,10 @@ impl AppState { self.scroll_offset = self.cursor.saturating_sub(vis - 1); } } - debug_assert!(self.check_invariant(), "SPARK post: cursor_down maintains invariant"); + debug_assert!( + self.check_invariant(), + "SPARK post: cursor_down maintains invariant" + ); } /// Page up (move by visible_height). @@ -302,23 +346,37 @@ impl AppState { let vis = self.visible_height(); self.cursor = self.cursor.saturating_sub(vis); self.scroll_offset = self.scroll_offset.saturating_sub(vis); - debug_assert!(self.check_invariant(), "SPARK post: page_up maintains invariant"); + debug_assert!( + self.check_invariant(), + "SPARK post: page_up maintains invariant" + ); } /// Page down (move by visible_height). pub fn page_down(&mut self) { let vis = self.visible_height(); - let max = if self.packages.is_empty() { 0 } else { self.packages.len() - 1 }; + let max = if self.packages.is_empty() { + 0 + } else { + self.packages.len() - 1 + }; self.cursor = (self.cursor + vis).min(max); - self.scroll_offset = (self.scroll_offset + vis).min(self.packages.len().saturating_sub(vis)); - debug_assert!(self.check_invariant(), "SPARK post: page_down maintains invariant"); + self.scroll_offset = + (self.scroll_offset + vis).min(self.packages.len().saturating_sub(vis)); + debug_assert!( + self.check_invariant(), + "SPARK post: page_down maintains invariant" + ); } /// Jump to top. pub fn jump_top(&mut self) { self.cursor = 0; self.scroll_offset = 0; - debug_assert!(self.check_invariant(), "SPARK post: jump_top maintains invariant"); + debug_assert!( + self.check_invariant(), + "SPARK post: jump_top maintains invariant" + ); } /// Jump to bottom. @@ -328,7 +386,10 @@ impl AppState { let vis = self.visible_height(); self.scroll_offset = self.packages.len().saturating_sub(vis); } - debug_assert!(self.check_invariant(), "SPARK post: jump_bottom maintains invariant"); + debug_assert!( + self.check_invariant(), + "SPARK post: jump_bottom maintains invariant" + ); } /// Switch view. @@ -343,7 +404,10 @@ impl AppState { self.cursor = 0; self.scroll_offset = 0; self.status_message = format!("Filter: {}", filter); - debug_assert!(self.check_invariant(), "SPARK post: set_filter maintains invariant"); + debug_assert!( + self.check_invariant(), + "SPARK post: set_filter maintains invariant" + ); } /// Enter search mode. @@ -366,7 +430,10 @@ impl AppState { if self.search_query.len() < MAX_SEARCH_LEN { self.search_query.push(c); } - debug_assert!(self.check_invariant(), "SPARK post: search_push maintains invariant"); + debug_assert!( + self.check_invariant(), + "SPARK post: search_push maintains invariant" + ); } /// Delete last character from search query. @@ -414,7 +481,10 @@ impl AppState { self.status_message = format!("Error: {}", e); } } - debug_assert!(self.check_invariant(), "SPARK post: load_installed maintains invariant"); + debug_assert!( + self.check_invariant(), + "SPARK post: load_installed maintains invariant" + ); } /// Load history from `opsm history list --json`. @@ -433,7 +503,9 @@ impl AppState { /// Run search via `opsm search --json`. pub fn run_search(&mut self) { - if self.search_query.is_empty() { return; } + if self.search_query.is_empty() { + return; + } self.status_message = format!("Searching: {}...", self.search_query); match run_opsm(&["search", &self.search_query, "--json"]) { Ok(output) => { @@ -445,13 +517,20 @@ impl AppState { self.cursor = 0; self.scroll_offset = 0; self.filter = Filter::SearchResults; - self.status_message = format!("{} results for '{}'", self.packages.len(), self.search_query); + self.status_message = format!( + "{} results for '{}'", + self.packages.len(), + self.search_query + ); } Err(e) => { self.status_message = format!("Search error: {}", e); } } - debug_assert!(self.check_invariant(), "SPARK post: run_search maintains invariant"); + debug_assert!( + self.check_invariant(), + "SPARK post: run_search maintains invariant" + ); } /// Install selected package via `opsm install `. @@ -461,7 +540,11 @@ impl AppState { let args = if pkg.forth.is_empty() { vec!["install".to_string(), pkg.name.clone()] } else { - vec!["install".to_string(), format!("@{}", pkg.forth), pkg.name.clone()] + vec![ + "install".to_string(), + format!("@{}", pkg.forth), + pkg.name.clone(), + ] }; let ref_args: Vec<&str> = args.iter().map(|s| s.as_str()).collect(); match run_opsm(&ref_args) { @@ -529,7 +612,11 @@ impl AppState { self.status_message = String::from("Verifying..."); match run_opsm(&["check"]) { Ok(output) => { - self.status_message = output.lines().last().unwrap_or("Check complete").to_string(); + self.status_message = output + .lines() + .last() + .unwrap_or("Check complete") + .to_string(); } Err(e) => { self.status_message = format!("Check failed: {}", e); @@ -584,7 +671,12 @@ fn parse_list_output(output: &str) -> Vec { Some(PackageEntry { name: name_ver[0].to_string(), version: name_ver.get(1).unwrap_or(&"?").to_string(), - forth: parts.get(1).unwrap_or(&"").trim_start_matches('(').trim_end_matches(')').to_string(), + forth: parts + .get(1) + .unwrap_or(&"") + .trim_start_matches('(') + .trim_end_matches(')') + .to_string(), installed: true, description: parts.get(2).map(|s| s.to_string()), update_available: None, @@ -670,9 +762,33 @@ mod tests { fn test_cursor_with_packages() { let mut state = AppState::new(80, 24); state.packages = vec![ - PackageEntry { name: "a".into(), version: "1".into(), forth: "npm".into(), installed: true, description: None, update_available: None, checksum_status: None }, - PackageEntry { name: "b".into(), version: "2".into(), forth: "npm".into(), installed: true, description: None, update_available: None, checksum_status: None }, - PackageEntry { name: "c".into(), version: "3".into(), forth: "npm".into(), installed: true, description: None, update_available: None, checksum_status: None }, + PackageEntry { + name: "a".into(), + version: "1".into(), + forth: "npm".into(), + installed: true, + description: None, + update_available: None, + checksum_status: None, + }, + PackageEntry { + name: "b".into(), + version: "2".into(), + forth: "npm".into(), + installed: true, + description: None, + update_available: None, + checksum_status: None, + }, + PackageEntry { + name: "c".into(), + version: "3".into(), + forth: "npm".into(), + installed: true, + description: None, + update_available: None, + checksum_status: None, + }, ]; state.cursor_down(); assert_eq!(state.cursor(), 1); @@ -688,9 +804,15 @@ mod tests { #[test] fn test_resize_preserves_invariant() { let mut state = AppState::new(80, 24); - state.packages = vec![ - PackageEntry { name: "a".into(), version: "1".into(), forth: "npm".into(), installed: true, description: None, update_available: None, checksum_status: None }, - ]; + state.packages = vec![PackageEntry { + name: "a".into(), + version: "1".into(), + forth: "npm".into(), + installed: true, + description: None, + update_available: None, + checksum_status: None, + }]; state.cursor = 0; state.resize(40, 10); assert!(state.check_invariant()); @@ -723,8 +845,24 @@ mod tests { fn test_filter_resets_cursor() { let mut state = AppState::new(80, 24); state.packages = vec![ - PackageEntry { name: "a".into(), version: "1".into(), forth: "npm".into(), installed: true, description: None, update_available: None, checksum_status: None }, - PackageEntry { name: "b".into(), version: "2".into(), forth: "npm".into(), installed: true, description: None, update_available: None, checksum_status: None }, + PackageEntry { + name: "a".into(), + version: "1".into(), + forth: "npm".into(), + installed: true, + description: None, + update_available: None, + checksum_status: None, + }, + PackageEntry { + name: "b".into(), + version: "2".into(), + forth: "npm".into(), + installed: true, + description: None, + update_available: None, + checksum_status: None, + }, ]; state.cursor = 1; state.set_filter(Filter::Installed); @@ -735,15 +873,17 @@ mod tests { #[test] fn test_page_navigation() { let mut state = AppState::new(80, 30); // visible_height = 22 - state.packages = (0..50).map(|i| PackageEntry { - name: format!("pkg-{}", i), - version: "1.0.0".into(), - forth: "npm".into(), - installed: true, - description: None, - update_available: None, - checksum_status: None, - }).collect(); + state.packages = (0..50) + .map(|i| PackageEntry { + name: format!("pkg-{}", i), + version: "1.0.0".into(), + forth: "npm".into(), + installed: true, + description: None, + update_available: None, + checksum_status: None, + }) + .collect(); state.page_down(); assert!(state.cursor() > 0); assert!(state.check_invariant()); diff --git a/opsm-ui/tui/src/main.rs b/opsm-ui/tui/src/main.rs index 90f599d..13c5a0d 100644 --- a/opsm-ui/tui/src/main.rs +++ b/opsm-ui/tui/src/main.rs @@ -70,14 +70,17 @@ struct Cli { /// /// Layout: header(3) | content(min 5) | status(1) | footer(1) fn render(frame: &mut Frame, state: &AppState) { - debug_assert!(state.is_running(), "SPARK pre: Render requires State.Running"); + debug_assert!( + state.is_running(), + "SPARK pre: Render requires State.Running" + ); let area = frame.area(); let chunks = Layout::default() .direction(Direction::Vertical) .constraints([ - Constraint::Length(3), // Header + Constraint::Length(3), // Header Constraint::Min(5), // Content Constraint::Length(1), // Status bar Constraint::Length(2), // Footer / keybindings @@ -102,14 +105,20 @@ fn render_header(frame: &mut Frame, state: &AppState, area: Rect) { let mut spans: Vec = vec![ Span::styled( " OPSM ", - Style::default().fg(Color::White).bg(ACCENT).add_modifier(Modifier::BOLD), + Style::default() + .fg(Color::White) + .bg(ACCENT) + .add_modifier(Modifier::BOLD), ), Span::raw(" "), ]; for (label, filter) in &tabs { let style = if *filter == state.filter() { - Style::default().fg(BG).bg(ACCENT).add_modifier(Modifier::BOLD) + Style::default() + .fg(BG) + .bg(ACCENT) + .add_modifier(Modifier::BOLD) } else { Style::default().fg(SUBTEXT) }; @@ -122,8 +131,11 @@ fn render_header(frame: &mut Frame, state: &AppState, area: Rect) { Style::default().fg(YELLOW), )); - let header = Paragraph::new(Line::from(spans)) - .block(Block::default().borders(Borders::ALL).border_style(Style::default().fg(BORDER))); + let header = Paragraph::new(Line::from(spans)).block( + Block::default() + .borders(Borders::ALL) + .border_style(Style::default().fg(BORDER)), + ); frame.render_widget(header, area); } @@ -170,7 +182,10 @@ fn render_package_list(frame: &mut Frame, state: &AppState, area: Rect) { }; let name_style = if selected { - Style::default().fg(BG).bg(ACCENT).add_modifier(Modifier::BOLD) + Style::default() + .fg(BG) + .bg(ACCENT) + .add_modifier(Modifier::BOLD) } else { Style::default().fg(TEXT) }; @@ -189,7 +204,10 @@ fn render_package_list(frame: &mut Frame, state: &AppState, area: Rect) { } if let Some(ref update) = pkg.update_available { - spans.push(Span::styled(format!(" -> {}", update), Style::default().fg(GREEN))); + spans.push(Span::styled( + format!(" -> {}", update), + Style::default().fg(GREEN), + )); } ListItem::new(Line::from(spans)) @@ -197,13 +215,12 @@ fn render_package_list(frame: &mut Frame, state: &AppState, area: Rect) { .collect(); let title = format!(" Packages ({}) ", packages.len()); - let list = List::new(items) - .block( - Block::default() - .title(title) - .borders(Borders::ALL) - .border_style(Style::default().fg(BORDER)), - ); + let list = List::new(items).block( + Block::default() + .title(title) + .borders(Borders::ALL) + .border_style(Style::default().fg(BORDER)), + ); frame.render_widget(list, area); } @@ -212,40 +229,69 @@ fn render_package_list(frame: &mut Frame, state: &AppState, area: Rect) { fn render_detail_panel(frame: &mut Frame, state: &AppState, area: Rect) { let content = if let Some(pkg) = state.selected_package() { let mut lines = vec![ - Line::from(Span::styled(&pkg.name, Style::default().fg(ACCENT).add_modifier(Modifier::BOLD))), - Line::from(Span::styled(format!("v{}", pkg.version), Style::default().fg(TEXT))), - Line::from(Span::styled(format!("@{}", pkg.forth), Style::default().fg(YELLOW))), + Line::from(Span::styled( + &pkg.name, + Style::default().fg(ACCENT).add_modifier(Modifier::BOLD), + )), + Line::from(Span::styled( + format!("v{}", pkg.version), + Style::default().fg(TEXT), + )), + Line::from(Span::styled( + format!("@{}", pkg.forth), + Style::default().fg(YELLOW), + )), Line::from(""), Line::from(Span::styled( - if pkg.installed { "Status: Installed" } else { "Status: Available" }, + if pkg.installed { + "Status: Installed" + } else { + "Status: Available" + }, Style::default().fg(if pkg.installed { GREEN } else { SUBTEXT }), )), ]; if let Some(ref desc) = pkg.description { lines.push(Line::from("")); - lines.push(Line::from(Span::styled(desc.as_str(), Style::default().fg(SUBTEXT)))); + lines.push(Line::from(Span::styled( + desc.as_str(), + Style::default().fg(SUBTEXT), + ))); } if let Some(ref cs) = pkg.checksum_status { lines.push(Line::from("")); - lines.push(Line::from(Span::styled(format!("Checksum: {}", cs), Style::default().fg(SUBTEXT)))); + lines.push(Line::from(Span::styled( + format!("Checksum: {}", cs), + Style::default().fg(SUBTEXT), + ))); } lines.push(Line::from("")); lines.push(Line::from("")); if pkg.installed { - lines.push(Line::from(Span::styled(" [r] Remove [R] Reinstall", Style::default().fg(RED)))); + lines.push(Line::from(Span::styled( + " [r] Remove [R] Reinstall", + Style::default().fg(RED), + ))); } else { - lines.push(Line::from(Span::styled(" [i] Install", Style::default().fg(GREEN)))); + lines.push(Line::from(Span::styled( + " [i] Install", + Style::default().fg(GREEN), + ))); } - lines.push(Line::from(Span::styled(" [d] Deps [Enter] Full detail", Style::default().fg(SUBTEXT)))); + lines.push(Line::from(Span::styled( + " [d] Deps [Enter] Full detail", + Style::default().fg(SUBTEXT), + ))); lines } else { - vec![ - Line::from(Span::styled("No package selected", Style::default().fg(SUBTEXT))), - ] + vec![Line::from(Span::styled( + "No package selected", + Style::default().fg(SUBTEXT), + ))] }; let detail = Paragraph::new(content) @@ -262,30 +308,36 @@ fn render_detail_panel(frame: &mut Frame, state: &AppState, area: Rect) { /// History view. fn render_history_view(frame: &mut Frame, state: &AppState, area: Rect) { - let items: Vec = state.history().iter().map(|entry| { - let pkg_str = entry.package.as_deref().unwrap_or(""); - let style = match entry.operation.as_str() { - op if op.contains("install") => Style::default().fg(GREEN), - op if op.contains("remove") => Style::default().fg(RED), - op if op.contains("undo") => Style::default().fg(YELLOW), - _ => Style::default().fg(TEXT), - }; - ListItem::new(Line::from(vec![ - Span::styled(&entry.timestamp[..19.min(entry.timestamp.len())], Style::default().fg(SUBTEXT)), - Span::raw(" "), - Span::styled(&entry.operation, style), - Span::raw(" "), - Span::styled(pkg_str, Style::default().fg(ACCENT)), - ])) - }).collect(); - - let list = List::new(items) - .block( - Block::default() - .title(" History [u]ndo [r]edo ") - .borders(Borders::ALL) - .border_style(Style::default().fg(BORDER)), - ); + let items: Vec = state + .history() + .iter() + .map(|entry| { + let pkg_str = entry.package.as_deref().unwrap_or(""); + let style = match entry.operation.as_str() { + op if op.contains("install") => Style::default().fg(GREEN), + op if op.contains("remove") => Style::default().fg(RED), + op if op.contains("undo") => Style::default().fg(YELLOW), + _ => Style::default().fg(TEXT), + }; + ListItem::new(Line::from(vec![ + Span::styled( + &entry.timestamp[..19.min(entry.timestamp.len())], + Style::default().fg(SUBTEXT), + ), + Span::raw(" "), + Span::styled(&entry.operation, style), + Span::raw(" "), + Span::styled(pkg_str, Style::default().fg(ACCENT)), + ])) + }) + .collect(); + + let list = List::new(items).block( + Block::default() + .title(" History [u]ndo [r]edo ") + .borders(Borders::ALL) + .border_style(Style::default().fg(BORDER)), + ); frame.render_widget(list, area); } @@ -293,7 +345,10 @@ fn render_history_view(frame: &mut Frame, state: &AppState, area: Rect) { /// Dependency tree view (placeholder — calls opsm depends). fn render_deps_view(frame: &mut Frame, state: &AppState, area: Rect) { let content = if let Some(pkg) = state.selected_package() { - format!("Dependencies for {}@{} (@{})\n\nPress 'd' in packages view to load.", pkg.name, pkg.version, pkg.forth) + format!( + "Dependencies for {}@{} (@{})\n\nPress 'd' in packages view to load.", + pkg.name, pkg.version, pkg.forth + ) } else { "Select a package first".to_string() }; @@ -313,15 +368,16 @@ fn render_deps_view(frame: &mut Frame, state: &AppState, area: Rect) { /// Trust dashboard view. fn render_trust_view(frame: &mut Frame, _state: &AppState, area: Rect) { - let widget = Paragraph::new("Trust Pipeline Dashboard\n\nPress 'v' to run verification (opsm check)") - .style(Style::default().fg(TEXT)) - .block( - Block::default() - .title(" Trust ") - .borders(Borders::ALL) - .border_style(Style::default().fg(BORDER)), - ) - .wrap(Wrap { trim: true }); + let widget = + Paragraph::new("Trust Pipeline Dashboard\n\nPress 'v' to run verification (opsm check)") + .style(Style::default().fg(TEXT)) + .block( + Block::default() + .title(" Trust ") + .borders(Borders::ALL) + .border_style(Style::default().fg(BORDER)), + ) + .wrap(Wrap { trim: true }); frame.render_widget(widget, area); } @@ -361,7 +417,10 @@ fn render_status(frame: &mut Frame, state: &AppState, area: Rect) { // Search query display if state.input_mode() == InputMode::Search { spans.push(Span::styled(" /", Style::default().fg(ACCENT))); - spans.push(Span::styled(state.search_query(), Style::default().fg(TEXT))); + spans.push(Span::styled( + state.search_query(), + Style::default().fg(TEXT), + )); spans.push(Span::styled("█", Style::default().fg(ACCENT))); // cursor } else if state.input_mode() == InputMode::Confirm { spans.push(Span::styled( @@ -375,8 +434,7 @@ fn render_status(frame: &mut Frame, state: &AppState, area: Rect) { )); } - let status = Paragraph::new(Line::from(spans)) - .style(Style::default().bg(SURFACE)); + let status = Paragraph::new(Line::from(spans)).style(Style::default().bg(SURFACE)); frame.render_widget(status, area); } @@ -398,7 +456,8 @@ fn render_footer(frame: &mut Frame, state: &AppState, area: Rect) { let footer = Paragraph::new(Span::styled( format!(" {} ", keys), Style::default().fg(SUBTEXT), - )).style(Style::default().bg(SURFACE)); + )) + .style(Style::default().bg(SURFACE)); frame.render_widget(footer, area); } @@ -467,17 +526,34 @@ fn handle_normal_input(state: &mut AppState, key: KeyEvent) { KeyCode::Char('/') => state.enter_search(), // Filter tabs - KeyCode::Char('1') => { state.set_filter(Filter::Installed); state.load_installed(); } - KeyCode::Char('2') => { state.set_filter(Filter::Updates); state.load_installed(); } - KeyCode::Char('3') => { state.set_filter(Filter::SearchResults); } - KeyCode::Char('4') => { state.set_filter(Filter::All); state.load_installed(); } + KeyCode::Char('1') => { + state.set_filter(Filter::Installed); + state.load_installed(); + } + KeyCode::Char('2') => { + state.set_filter(Filter::Updates); + state.load_installed(); + } + KeyCode::Char('3') => { + state.set_filter(Filter::SearchResults); + } + KeyCode::Char('4') => { + state.set_filter(Filter::All); + state.load_installed(); + } // Views - KeyCode::Char('h') => { state.set_view(View::History); state.load_history(); } + KeyCode::Char('h') => { + state.set_view(View::History); + state.load_history(); + } KeyCode::Char('t') => state.set_view(View::Trust), KeyCode::Char('d') => state.set_view(View::DepsTree), KeyCode::Enter => state.set_view(View::Detail), - KeyCode::Esc => { state.set_view(View::Packages); state.exit_input_mode(); } + KeyCode::Esc => { + state.set_view(View::Packages); + state.exit_input_mode(); + } // Actions KeyCode::Char('i') => { diff --git a/opsm_ex/native/opsm_pq_nif/src/lib.rs b/opsm_ex/native/opsm_pq_nif/src/lib.rs index 80a9610..ce21125 100644 --- a/opsm_ex/native/opsm_pq_nif/src/lib.rs +++ b/opsm_ex/native/opsm_pq_nif/src/lib.rs @@ -15,9 +15,7 @@ use pqcrypto_sphincsplus::sphincsshake256fsimple as sphincs; use pqcrypto_traits::kem::{ Ciphertext as KemCiphertext, PublicKey as KemPk, SecretKey as KemSk, SharedSecret, }; -use pqcrypto_traits::sign::{ - PublicKey as SignPk, SecretKey as SignSk, SignedMessage, -}; +use pqcrypto_traits::sign::{PublicKey as SignPk, SecretKey as SignSk, SignedMessage}; use rustler::{Binary, Encoder, Env, NewBinary, Term}; mod atoms { diff --git a/services/checky-monkey/src/main.rs b/services/checky-monkey/src/main.rs index fa2e3a4..4c1ae49 100644 --- a/services/checky-monkey/src/main.rs +++ b/services/checky-monkey/src/main.rs @@ -137,8 +137,14 @@ struct GithubAttestationResponse { async fn health(State(state): State) -> Json { let jobs = state.jobs.read().await; - let active = jobs.values().filter(|j| matches!(j.status, JobStatus::Running)).count(); - let queued = jobs.values().filter(|j| matches!(j.status, JobStatus::Queued)).count(); + let active = jobs + .values() + .filter(|j| matches!(j.status, JobStatus::Running)) + .count(); + let queued = jobs + .values() + .filter(|j| matches!(j.status, JobStatus::Queued)) + .count(); Json(HealthResponse { status: "healthy".to_string(), @@ -210,7 +216,10 @@ async fn get_verification_status( .collect(); if matching.is_empty() { - Err((StatusCode::NOT_FOUND, "No jobs found for commit".to_string())) + Err(( + StatusCode::NOT_FOUND, + "No jobs found for commit".to_string(), + )) } else { Ok(Json(matching)) } @@ -375,36 +384,36 @@ async fn verify_github_attestation( // reap the child if the timeout below drops the future .kill_on_drop(true); - let output = match tokio::time::timeout(tokio::time::Duration::from_secs(120), command.output()).await { - Err(_elapsed) => { - return Err(( - StatusCode::GATEWAY_TIMEOUT, - "gh attestation verify timed out".to_string(), - )) - } - Ok(Err(e)) if e.kind() == std::io::ErrorKind::NotFound => { - return Err(( - StatusCode::SERVICE_UNAVAILABLE, - "gh CLI not available on this service".to_string(), - )) - } - Ok(Err(e)) => { - return Err(( - StatusCode::INTERNAL_SERVER_ERROR, - format!("Failed to run gh: {}", e), - )) - } - Ok(Ok(output)) => output, - }; + let output = + match tokio::time::timeout(tokio::time::Duration::from_secs(120), command.output()).await { + Err(_elapsed) => { + return Err(( + StatusCode::GATEWAY_TIMEOUT, + "gh attestation verify timed out".to_string(), + )) + } + Ok(Err(e)) if e.kind() == std::io::ErrorKind::NotFound => { + return Err(( + StatusCode::SERVICE_UNAVAILABLE, + "gh CLI not available on this service".to_string(), + )) + } + Ok(Err(e)) => { + return Err(( + StatusCode::INTERNAL_SERVER_ERROR, + format!("Failed to run gh: {}", e), + )) + } + Ok(Ok(output)) => output, + }; if output.status.success() { - let parsed: serde_json::Value = serde_json::from_slice(&output.stdout) - .map_err(|e| { - ( - StatusCode::BAD_GATEWAY, - format!("gh produced unparseable JSON: {}", e), - ) - })?; + let parsed: serde_json::Value = serde_json::from_slice(&output.stdout).map_err(|e| { + ( + StatusCode::BAD_GATEWAY, + format!("gh produced unparseable JSON: {}", e), + ) + })?; let statement = extract_statement(&parsed); let builder_id = statement @@ -498,7 +507,10 @@ fn oci_registry_allowed(oci_uri: &str) -> bool { .next() .unwrap_or("") .to_lowercase(); - !host.is_empty() && oci_registry_allowlist().iter().any(|allowed| allowed == &host) + !host.is_empty() + && oci_registry_allowlist() + .iter() + .any(|allowed| allowed == &host) } /// "owner/repo" with both segments limited to GitHub-legal name characters — @@ -633,7 +645,10 @@ async fn main() -> anyhow::Result<()> { let app = Router::new() .route("/health", get(health)) .route("/verify", post(submit_verification)) - .route("/verify/github-attestation", post(verify_github_attestation)) + .route( + "/verify/github-attestation", + post(verify_github_attestation), + ) .route("/verify/status/{sha}", get(get_verification_status)) .route("/verify/{request_id}", get(get_verification_detail)) .route("/verify/{request_id}/cancel", post(cancel_verification)) @@ -691,7 +706,8 @@ mod tests { #[test] fn statement_extraction_flat_shape_and_miss() { - let flat = serde_json::json!([{"statement": {"predicateType": "https://slsa.dev/provenance/v1"}}]); + let flat = + serde_json::json!([{"statement": {"predicateType": "https://slsa.dev/provenance/v1"}}]); assert!(extract_statement(&flat).is_some()); assert!(extract_statement(&serde_json::json!([])).is_none()); assert!(extract_statement(&serde_json::json!({"not": "an array"})).is_none()); @@ -699,7 +715,9 @@ mod tests { #[test] fn oci_registry_guard() { - assert!(oci_registry_allowed("oci://ghcr.io/hyperpolymath/proven:latest")); + assert!(oci_registry_allowed( + "oci://ghcr.io/hyperpolymath/proven:latest" + )); assert!(oci_registry_allowed("oci://GHCR.IO/x/y@sha256:abc")); assert!(!oci_registry_allowed("oci://evil.example.com/x/y")); assert!(!oci_registry_allowed("oci://ghcr.io.evil.com/x")); diff --git a/services/oikos/src/main.rs b/services/oikos/src/main.rs index 7023f35..d15ae5b 100644 --- a/services/oikos/src/main.rs +++ b/services/oikos/src/main.rs @@ -159,8 +159,7 @@ async fn analyze_repository( findings: vec![Finding { category: "meta".to_string(), severity: FindingSeverity::Info, - message: "Repository analysis is in stub mode — metrics not yet computed" - .to_string(), + message: "Repository analysis is in stub mode — metrics not yet computed".to_string(), file_path: None, }], recommendations: vec![ @@ -185,16 +184,13 @@ async fn get_repository_analysis( ) -> Result, (StatusCode, String)> { let analyses = state.analyses.read().await; - analyses - .get(&encoded_url) - .cloned() - .map(Json) - .ok_or((StatusCode::NOT_FOUND, "No cached analysis found".to_string())) + analyses.get(&encoded_url).cloned().map(Json).ok_or(( + StatusCode::NOT_FOUND, + "No cached analysis found".to_string(), + )) } -async fn analyze_diff( - Json(request): Json, -) -> Json { +async fn analyze_diff(Json(request): Json) -> Json { info!( "Analyzing diff for {} ({}..{})", request.repo_url, request.base_ref, request.head_ref diff --git a/services/palimpsest-license/src/main.rs b/services/palimpsest-license/src/main.rs index 3203789..f7a15eb 100644 --- a/services/palimpsest-license/src/main.rs +++ b/services/palimpsest-license/src/main.rs @@ -223,12 +223,10 @@ async fn get_license( Path(spdx_id): Path, ) -> Result, (StatusCode, String)> { let upper = spdx_id.to_uppercase(); - state - .licenses - .get(&upper) - .cloned() - .map(Json) - .ok_or((StatusCode::NOT_FOUND, format!("License {} not found", spdx_id))) + state.licenses.get(&upper).cloned().map(Json).ok_or(( + StatusCode::NOT_FOUND, + format!("License {} not found", spdx_id), + )) } async fn list_licenses(State(state): State) -> Json> { @@ -269,20 +267,86 @@ fn build_license_db() -> HashMap { let entries = vec![ ("MIT", "MIT License", true, true, CopyleftType::None), - ("Apache-2.0", "Apache License 2.0", true, true, CopyleftType::None), - ("BSD-2-Clause", "BSD 2-Clause", true, true, CopyleftType::None), - ("BSD-3-Clause", "BSD 3-Clause", true, true, CopyleftType::None), + ( + "Apache-2.0", + "Apache License 2.0", + true, + true, + CopyleftType::None, + ), + ( + "BSD-2-Clause", + "BSD 2-Clause", + true, + true, + CopyleftType::None, + ), + ( + "BSD-3-Clause", + "BSD 3-Clause", + true, + true, + CopyleftType::None, + ), ("ISC", "ISC License", true, true, CopyleftType::None), - ("MPL-2.0", "Mozilla Public License 2.0", true, true, CopyleftType::Weak), - ("LGPL-2.1-only", "GNU LGPL v2.1", true, true, CopyleftType::Weak), - ("LGPL-3.0-only", "GNU LGPL v3.0", true, true, CopyleftType::Weak), - ("GPL-2.0-only", "GNU GPL v2.0", true, true, CopyleftType::Strong), - ("GPL-3.0-only", "GNU GPL v3.0", true, true, CopyleftType::Strong), - ("MPL-2.0-only", "GNU AGPL v3.0", true, true, CopyleftType::Network), + ( + "MPL-2.0", + "Mozilla Public License 2.0", + true, + true, + CopyleftType::Weak, + ), + ( + "LGPL-2.1-only", + "GNU LGPL v2.1", + true, + true, + CopyleftType::Weak, + ), + ( + "LGPL-3.0-only", + "GNU LGPL v3.0", + true, + true, + CopyleftType::Weak, + ), + ( + "GPL-2.0-only", + "GNU GPL v2.0", + true, + true, + CopyleftType::Strong, + ), + ( + "GPL-3.0-only", + "GNU GPL v3.0", + true, + true, + CopyleftType::Strong, + ), + ( + "MPL-2.0-only", + "GNU AGPL v3.0", + true, + true, + CopyleftType::Network, + ), ("Unlicense", "The Unlicense", true, true, CopyleftType::None), ("0BSD", "Zero-Clause BSD", true, true, CopyleftType::None), - ("CC0-1.0", "CC0 1.0 Universal", false, false, CopyleftType::None), - ("MPL-2.0", "Palimpsest License", false, false, CopyleftType::Weak), + ( + "CC0-1.0", + "CC0 1.0 Universal", + false, + false, + CopyleftType::None, + ), + ( + "MPL-2.0", + "Palimpsest License", + false, + false, + CopyleftType::Weak, + ), ]; for (spdx, name, osi, fsf, copyleft) in entries { @@ -349,10 +413,7 @@ async fn main() -> anyhow::Result<()> { ) .init(); - info!( - "Starting Palimpsest License v{}", - env!("CARGO_PKG_VERSION") - ); + info!("Starting Palimpsest License v{}", env!("CARGO_PKG_VERSION")); let state = AppState { licenses: Arc::new(build_license_db()), @@ -372,8 +433,7 @@ async fn main() -> anyhow::Result<()> { .layer(TraceLayer::new_for_http()) .with_state(state); - let port = - std::env::var("PALIMPSEST_LICENSE_PORT").unwrap_or_else(|_| "8082".to_string()); + let port = std::env::var("PALIMPSEST_LICENSE_PORT").unwrap_or_else(|_| "8082".to_string()); let addr = format!("0.0.0.0:{}", port); info!("Listening on {}", addr); diff --git a/services/selur/src/main.rs b/services/selur/src/main.rs index 8a8b6e4..8e77a3a 100644 --- a/services/selur/src/main.rs +++ b/services/selur/src/main.rs @@ -157,7 +157,12 @@ async fn sign_image( .stderr(Stdio::piped()) .output() .await - .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, format!("Failed to execute cosign: {}", e)))?; + .map_err(|e| { + ( + StatusCode::INTERNAL_SERVER_ERROR, + format!("Failed to execute cosign: {}", e), + ) + })?; if !output.status.success() { let stderr = String::from_utf8_lossy(&output.stderr); @@ -170,16 +175,28 @@ async fn sign_image( // Read public key (limit to 1MB) let pub_key_path = key_path.with_extension("pub"); - let file = fs::File::open(&pub_key_path) - .await - .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, format!("Failed to open public key: {}", e)))?; + let file = fs::File::open(&pub_key_path).await.map_err(|e| { + ( + StatusCode::INTERNAL_SERVER_ERROR, + format!("Failed to open public key: {}", e), + ) + })?; let mut public_key = String::new(); - file.take(1024 * 1024).read_to_string(&mut public_key) + file.take(1024 * 1024) + .read_to_string(&mut public_key) .await - .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, format!("Failed to read public key: {}", e)))?; + .map_err(|e| { + ( + StatusCode::INTERNAL_SERVER_ERROR, + format!("Failed to read public key: {}", e), + ) + })?; // Generate signature digest (simplified - in production would extract from cosign output) - let signature_digest = format!("sha256:{}", hex::encode(&output.stdout[..32.min(output.stdout.len())])); + let signature_digest = format!( + "sha256:{}", + hex::encode(&output.stdout[..32.min(output.stdout.len())]) + ); let response = SignResponse { image: request.image, @@ -228,7 +245,12 @@ async fn verify_image( .stderr(Stdio::piped()) .output() .await - .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, format!("Failed to execute cosign: {}", e)))?; + .map_err(|e| { + ( + StatusCode::INTERNAL_SERVER_ERROR, + format!("Failed to execute cosign: {}", e), + ) + })?; let verified = output.status.success(); let stdout = String::from_utf8_lossy(&output.stdout); @@ -277,9 +299,12 @@ async fn generate_keypair( info!("Generating keypair: {}", key_name); // Ensure key directory exists - fs::create_dir_all(&state.key_dir) - .await - .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, format!("Failed to create key directory: {}", e)))?; + fs::create_dir_all(&state.key_dir).await.map_err(|e| { + ( + StatusCode::INTERNAL_SERVER_ERROR, + format!("Failed to create key directory: {}", e), + ) + })?; // Generate keypair with cosign let output = Command::new("cosign") @@ -290,7 +315,12 @@ async fn generate_keypair( .stderr(Stdio::piped()) .output() .await - .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, format!("Failed to execute cosign: {}", e)))?; + .map_err(|e| { + ( + StatusCode::INTERNAL_SERVER_ERROR, + format!("Failed to execute cosign: {}", e), + ) + })?; if !output.status.success() { let stderr = String::from_utf8_lossy(&output.stderr); @@ -305,20 +335,39 @@ async fn generate_keypair( if key_name != "cosign" { fs::rename(state.key_dir.join("cosign.key"), &private_key_path) .await - .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, format!("Failed to rename private key: {}", e)))?; + .map_err(|e| { + ( + StatusCode::INTERNAL_SERVER_ERROR, + format!("Failed to rename private key: {}", e), + ) + })?; fs::rename(state.key_dir.join("cosign.pub"), &public_key_path) .await - .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, format!("Failed to rename public key: {}", e)))?; + .map_err(|e| { + ( + StatusCode::INTERNAL_SERVER_ERROR, + format!("Failed to rename public key: {}", e), + ) + })?; } // Read public key (limit to 1MB) - let file = fs::File::open(&public_key_path) - .await - .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, format!("Failed to open public key: {}", e)))?; + let file = fs::File::open(&public_key_path).await.map_err(|e| { + ( + StatusCode::INTERNAL_SERVER_ERROR, + format!("Failed to open public key: {}", e), + ) + })?; let mut public_key = String::new(); - file.take(1024 * 1024).read_to_string(&mut public_key) + file.take(1024 * 1024) + .read_to_string(&mut public_key) .await - .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, format!("Failed to read public key: {}", e)))?; + .map_err(|e| { + ( + StatusCode::INTERNAL_SERVER_ERROR, + format!("Failed to read public key: {}", e), + ) + })?; let response = KeyGenResponse { private_key_path: private_key_path.display().to_string(), diff --git a/services/svalinn/src/main.rs b/services/svalinn/src/main.rs index 12ce58b..0023ee5 100644 --- a/services/svalinn/src/main.rs +++ b/services/svalinn/src/main.rs @@ -137,30 +137,26 @@ async fn scan_image( for scanner in scanners { match scanner { - Scanner::Trivy if state.trivy_enabled => { - match scan_with_trivy(&request.image).await { - Ok(vulns) => { - info!("Trivy found {} vulnerabilities", vulns.len()); - all_vulnerabilities.extend(vulns); - scanners_used.push("trivy".to_string()); - } - Err(e) => { - warn!("Trivy scan failed: {}", e); - } + Scanner::Trivy if state.trivy_enabled => match scan_with_trivy(&request.image).await { + Ok(vulns) => { + info!("Trivy found {} vulnerabilities", vulns.len()); + all_vulnerabilities.extend(vulns); + scanners_used.push("trivy".to_string()); } - } - Scanner::Grype if state.grype_enabled => { - match scan_with_grype(&request.image).await { - Ok(vulns) => { - info!("Grype found {} vulnerabilities", vulns.len()); - all_vulnerabilities.extend(vulns); - scanners_used.push("grype".to_string()); - } - Err(e) => { - warn!("Grype scan failed: {}", e); - } + Err(e) => { + warn!("Trivy scan failed: {}", e); } - } + }, + Scanner::Grype if state.grype_enabled => match scan_with_grype(&request.image).await { + Ok(vulns) => { + info!("Grype found {} vulnerabilities", vulns.len()); + all_vulnerabilities.extend(vulns); + scanners_used.push("grype".to_string()); + } + Err(e) => { + warn!("Grype scan failed: {}", e); + } + }, _ => { warn!("Scanner {:?} not available or not enabled", scanner); } @@ -244,32 +240,38 @@ fn parse_trivy_output(json: &str) -> anyhow::Result> { for result in results { if let Some(vulns) = result.get("Vulnerabilities").and_then(|v| v.as_array()) { for vuln in vulns { - let id = vuln.get("VulnerabilityID") + let id = vuln + .get("VulnerabilityID") .and_then(|v| v.as_str()) .unwrap_or("UNKNOWN") .to_string(); - let package = vuln.get("PkgName") + let package = vuln + .get("PkgName") .and_then(|v| v.as_str()) .unwrap_or("unknown") .to_string(); - let version = vuln.get("InstalledVersion") + let version = vuln + .get("InstalledVersion") .and_then(|v| v.as_str()) .unwrap_or("unknown") .to_string(); - let severity_str = vuln.get("Severity") + let severity_str = vuln + .get("Severity") .and_then(|v| v.as_str()) .unwrap_or("UNKNOWN"); let severity = parse_severity(severity_str); - let fixed_version = vuln.get("FixedVersion") + let fixed_version = vuln + .get("FixedVersion") .and_then(|v| v.as_str()) .map(|s| s.to_string()); - let description = vuln.get("Title") + let description = vuln + .get("Title") .or_else(|| vuln.get("Description")) .and_then(|v| v.as_str()) .unwrap_or("") @@ -302,35 +304,41 @@ fn parse_grype_output(json: &str) -> anyhow::Result> { let artifact = vuln_match.get("artifact"); if let (Some(vuln), Some(art)) = (vuln_data, artifact) { - let id = vuln.get("id") + let id = vuln + .get("id") .and_then(|v| v.as_str()) .unwrap_or("UNKNOWN") .to_string(); - let package = art.get("name") + let package = art + .get("name") .and_then(|v| v.as_str()) .unwrap_or("unknown") .to_string(); - let version = art.get("version") + let version = art + .get("version") .and_then(|v| v.as_str()) .unwrap_or("unknown") .to_string(); - let severity_str = vuln.get("severity") + let severity_str = vuln + .get("severity") .and_then(|v| v.as_str()) .unwrap_or("Unknown"); let severity = parse_severity(severity_str); - let fixed_version = vuln.get("fix") + let fixed_version = vuln + .get("fix") .and_then(|f| f.get("versions")) .and_then(|v| v.as_array()) .and_then(|arr| arr.first()) .and_then(|v| v.as_str()) .map(|s| s.to_string()); - let description = vuln.get("description") + let description = vuln + .get("description") .and_then(|v| v.as_str()) .unwrap_or("") .to_string(); diff --git a/services/vordr/src/main.rs b/services/vordr/src/main.rs index b25cbe7..02fe0e0 100644 --- a/services/vordr/src/main.rs +++ b/services/vordr/src/main.rs @@ -245,7 +245,12 @@ async fn verify_with_opa( .stdout(Stdio::piped()) .stderr(Stdio::piped()) .spawn() - .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, format!("Failed to spawn OPA: {}", e)))?; + .map_err(|e| { + ( + StatusCode::INTERNAL_SERVER_ERROR, + format!("Failed to spawn OPA: {}", e), + ) + })?; // Write input to stdin if let Some(mut stdin) = child.stdin.take() { @@ -253,14 +258,21 @@ async fn verify_with_opa( stdin .write_all(input_json.to_string().as_bytes()) .await - .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, format!("Failed to write input: {}", e)))?; + .map_err(|e| { + ( + StatusCode::INTERNAL_SERVER_ERROR, + format!("Failed to write input: {}", e), + ) + })?; drop(stdin); // Close stdin } - let result = child - .wait_with_output() - .await - .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, format!("OPA execution failed: {}", e)))?; + let result = child.wait_with_output().await.map_err(|e| { + ( + StatusCode::INTERNAL_SERVER_ERROR, + format!("OPA execution failed: {}", e), + ) + })?; if !result.status.success() { let stderr = String::from_utf8_lossy(&result.stderr); @@ -278,8 +290,12 @@ async fn verify_with_opa( } fn parse_opa_output(output: &[u8]) -> Result, (StatusCode, String)> { - let json: serde_json::Value = serde_json::from_slice(output) - .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, format!("Failed to parse OPA output: {}", e)))?; + let json: serde_json::Value = serde_json::from_slice(output).map_err(|e| { + ( + StatusCode::INTERNAL_SERVER_ERROR, + format!("Failed to parse OPA output: {}", e), + ) + })?; let mut violations = Vec::new(); @@ -394,10 +410,7 @@ fn verify_builtin( violations.push(PolicyViolation { rule: "no_writable_host_paths".to_string(), severity: Severity::High, - message: format!( - "Host path volume must be read-only: {}", - volume.source - ), + message: format!("Host path volume must be read-only: {}", volume.source), field: Some("volumes".to_string()), }); } From 89a4c1bb820b83e3544e68de59bcec0360096263 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 21 Jul 2026 20:15:25 +0000 Subject: [PATCH 3/3] fix(gitlab-ci): quarantine the non-building quic_transport NIF from cargo jobs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Wiring the cargo-* jobs to actually run (#71) exposed that opsm_ex/native/quic_transport does not compile: its pinned h3 0.0.6 / h3-quinn 0.0.7 predate quinn 0.11's private StreamId field, and with no committed Cargo.lock the fresh dependency resolve breaks. It is an OPTIONAL QUIC/HTTP3 NIF — Opsm.Transport.QuicNif swallows a failed :erlang.load_nif and falls back to HTTP/2 (nif_loaded? is hard-coded false), so the Elixir side runs without it and no CI builds it today. Remove it from $RUST_CRATE_DIRS (the build loop) and from every `rules: exists:` / artifacts list, with a QUARANTINED header note giving the un-quarantine condition: migrate the h3/h3-quinn/quinn triple to a mutually-compatible set and commit a lockfile. Tracked as a follow-up. The remaining eight crates all build, test, and fmt-clean locally. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01Kq24sZCEohSrNFXSuEuz6C --- .gitlab-ci.yml | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 5ebcc75..17ef49e 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -16,6 +16,16 @@ # $RUST_CRATE_DIRS. Keep that variable and each job's `rules: exists:` # list in sync when a crate is added or removed — `rules: exists:` needs # literal paths, it does not expand CI variables. +# +# QUARANTINED: opsm_ex/native/quic_transport is deliberately absent from +# $RUST_CRATE_DIRS. It is an OPTIONAL QUIC/HTTP3 NIF — Opsm.Transport.QuicNif +# swallows a failed :erlang.load_nif and falls back to HTTP/2 (nif_loaded? +# is hard-coded false), so the Elixir side runs fine without it, and no CI +# builds it today. It currently does not compile: its pinned h3 0.0.6 / +# h3-quinn 0.0.7 predate quinn 0.11's private StreamId, and with no committed +# Cargo.lock the fresh resolve breaks. Un-quarantine only after migrating the +# h3/h3-quinn/quinn triple to a mutually-compatible set and committing a lock. +# Tracked as a follow-up; do not add it back to the loop until it builds. stages: - security @@ -26,10 +36,10 @@ variables: CARGO_HOME: ${CI_PROJECT_DIR}/.cargo # First-party Rust crates only — opsm_ex/deps/** is vendored (Mix deps # cache), never linted/audited/built as our own code. + # quic_transport intentionally excluded — see QUARANTINED note in the header. RUST_CRATE_DIRS: >- opsm-ui/tui opsm_ex/native/opsm_pq_nif - opsm_ex/native/quic_transport services/checky-monkey services/oikos services/palimpsest-license @@ -73,7 +83,6 @@ cargo-audit: - exists: - opsm-ui/tui/Cargo.toml - opsm_ex/native/opsm_pq_nif/Cargo.toml - - opsm_ex/native/quic_transport/Cargo.toml - services/checky-monkey/Cargo.toml - services/oikos/Cargo.toml - services/palimpsest-license/Cargo.toml @@ -94,7 +103,6 @@ cargo-deny: - exists: - opsm-ui/tui/Cargo.toml - opsm_ex/native/opsm_pq_nif/Cargo.toml - - opsm_ex/native/quic_transport/Cargo.toml - services/checky-monkey/Cargo.toml - services/oikos/Cargo.toml - services/palimpsest-license/Cargo.toml @@ -132,7 +140,6 @@ rustfmt: - exists: - opsm-ui/tui/Cargo.toml - opsm_ex/native/opsm_pq_nif/Cargo.toml - - opsm_ex/native/quic_transport/Cargo.toml - services/checky-monkey/Cargo.toml - services/oikos/Cargo.toml - services/palimpsest-license/Cargo.toml @@ -153,7 +160,6 @@ clippy: - exists: - opsm-ui/tui/Cargo.toml - opsm_ex/native/opsm_pq_nif/Cargo.toml - - opsm_ex/native/quic_transport/Cargo.toml - services/checky-monkey/Cargo.toml - services/oikos/Cargo.toml - services/palimpsest-license/Cargo.toml @@ -198,7 +204,6 @@ cargo-test: - exists: - opsm-ui/tui/Cargo.toml - opsm_ex/native/opsm_pq_nif/Cargo.toml - - opsm_ex/native/quic_transport/Cargo.toml - services/checky-monkey/Cargo.toml - services/oikos/Cargo.toml - services/palimpsest-license/Cargo.toml @@ -232,7 +237,6 @@ cargo-build: paths: - opsm-ui/tui/target/release/ - opsm_ex/native/opsm_pq_nif/target/release/ - - opsm_ex/native/quic_transport/target/release/ - services/checky-monkey/target/release/ - services/oikos/target/release/ - services/palimpsest-license/target/release/ @@ -244,7 +248,6 @@ cargo-build: - exists: - opsm-ui/tui/Cargo.toml - opsm_ex/native/opsm_pq_nif/Cargo.toml - - opsm_ex/native/quic_transport/Cargo.toml - services/checky-monkey/Cargo.toml - services/oikos/Cargo.toml - services/palimpsest-license/Cargo.toml