Skip to content

Commit f41ee93

Browse files
Merge branch 'main' into fix/idris-lang-chapel-not-c
2 parents 49203a8 + edf8d6e commit f41ee93

5 files changed

Lines changed: 134 additions & 160 deletions

File tree

.github/workflows/rust-ci.yml

Lines changed: 6 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -1,73 +1,17 @@
11
# SPDX-License-Identifier: MPL-2.0
2+
# Rust CI — thin wrapper calling the shared estate reusable in
3+
# hyperpolymath/standards. Configure once, propagate everywhere.
4+
# See: docs/CI-REUSABLE-WORKFLOWS.adoc in standards.
25
name: Rust CI
36

47
on:
58
push:
6-
branches: [ main ]
9+
branches: [main, master]
710
pull_request:
8-
branches: [ main ]
911

1012
permissions:
1113
contents: read
1214

1315
jobs:
14-
test:
15-
name: Test
16-
runs-on: ubuntu-latest
17-
steps:
18-
- name: Checkout code
19-
uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5
20-
21-
- name: Install Rust toolchain
22-
uses: dtolnay/rust-toolchain@4be9e76fd7c4901c61fb841f559994984270fce7 # stable
23-
with:
24-
toolchain: stable
25-
components: rustfmt, clippy
26-
27-
- name: Cache cargo registry
28-
uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2
29-
30-
- name: Check formatting
31-
run: cargo fmt -- --check
32-
33-
- name: Run clippy
34-
run: cargo clippy --all-targets --all-features -- -D warnings
35-
36-
- name: Build
37-
run: cargo build --verbose
38-
39-
- name: Run tests
40-
run: cargo test --verbose
41-
42-
- name: Build release
43-
run: cargo build --release --verbose
44-
45-
- name: Check for warnings
46-
run: |
47-
OUTPUT=$(cargo build --release 2>&1)
48-
if echo "$OUTPUT" | grep -i "warning"; then
49-
echo "❌ Build produced warnings:"
50-
echo "$OUTPUT" | grep -i "warning"
51-
exit 1
52-
fi
53-
echo "✅ Zero warnings"
54-
55-
msrv:
56-
name: Check MSRV (1.85.0)
57-
runs-on: ubuntu-latest
58-
steps:
59-
- name: Checkout code
60-
uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5
61-
62-
- name: Install Rust 1.85.0
63-
uses: dtolnay/rust-toolchain@4be9e76fd7c4901c61fb841f559994984270fce7 # stable
64-
with:
65-
toolchain: 1.85.0
66-
67-
- name: Cache cargo registry
68-
uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2
69-
70-
- name: Check build
71-
# NB: the `gui` feature pulls eframe/egui which raise MSRV above 1.85.
72-
# Verify the default + non-GUI optional features compile on MSRV.
73-
run: cargo check --features signing,http
16+
rust-ci:
17+
uses: hyperpolymath/standards/.github/workflows/rust-ci-reusable.yml@4fdf4314b4ab54269adbaff10e30e483b5e86845

src/main.rs

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -468,8 +468,11 @@ enum Commands {
468468
headless: bool,
469469
},
470470

471-
/// GUI review of a saved report (requires `--features gui` at build time)
472-
#[cfg(feature = "gui")]
471+
/// GUI review of a saved report.
472+
///
473+
/// `--headless` always works (text panel summaries to stdout). The
474+
/// windowed renderer requires `--features gui` at build time because
475+
/// eframe/egui raise MSRV above 1.85.0.
473476
Gui {
474477
/// Assault report JSON file
475478
#[arg(value_name = "REPORT")]
@@ -1701,14 +1704,22 @@ fn run_main() -> Result<()> {
17011704
}
17021705
}
17031706

1704-
#[cfg(feature = "gui")]
17051707
Commands::Gui { report, headless } => {
17061708
let content = read_report_bounded(&report)?;
17071709
let assault_report: AssaultReport = serde_json::from_str(&content)?;
17081710
if headless {
1709-
report::ReportGui::run_headless(assault_report)?;
1711+
report::gui_text::run_headless(assault_report)?;
17101712
} else {
1711-
report::ReportGui::run(assault_report)?;
1713+
#[cfg(feature = "gui")]
1714+
{
1715+
report::ReportGui::run(assault_report)?;
1716+
}
1717+
#[cfg(not(feature = "gui"))]
1718+
{
1719+
anyhow::bail!(
1720+
"windowed GUI requires the `gui` feature; rebuild with `cargo build --features gui`, or pass --headless"
1721+
);
1722+
}
17121723
}
17131724
}
17141725

src/report/gui.rs

Lines changed: 5 additions & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
// SPDX-License-Identifier: MPL-2.0
22

33
//! Minimal GUI for reviewing assault reports, system images, and temporal diffs.
4+
//!
5+
//! This module is compiled only when the `gui` feature is enabled because the
6+
//! `eframe`/`egui` dependency chain raises MSRV above the 1.85.0 baseline.
7+
//! The text-only `gui --headless` path lives in [`super::gui_text`] and is
8+
//! always compiled.
49
510
use crate::mass_panic::imaging::SystemImage;
611
use crate::mass_panic::temporal::TemporalDiff;
@@ -65,99 +70,6 @@ impl ReportGui {
6570
Ok(())
6671
}
6772

68-
/// Run without a display server: print a structured text summary of all panels.
69-
///
70-
/// Safe to call in CI or headless environments with no Wayland/X11 display.
71-
/// Promotes the gui subcommand from Grade E to Grade D.
72-
pub fn run_headless(report: AssaultReport) -> Result<()> {
73-
let assail = &report.assail_report;
74-
let formatter = ReportFormatter::new();
75-
76-
println!("PANIC-ATTACK GUI REPORT (headless)");
77-
println!();
78-
79-
println!("=== Summary ===");
80-
println!("Language: {:?}", assail.language);
81-
println!(
82-
"Score: {:.1}/100",
83-
report.overall_assessment.robustness_score
84-
);
85-
println!("Crashes: {}", report.total_crashes);
86-
println!("Signatures: {}", report.total_signatures);
87-
println!("Weak points: {}", assail.weak_points.len());
88-
println!("Frameworks: {:?}", assail.frameworks);
89-
println!();
90-
91-
println!("=== Assail ===");
92-
println!("Program: {}", assail.program_path.display());
93-
println!(
94-
"Stats: lines={} unsafe={} panics={} unwraps={}",
95-
assail.statistics.total_lines,
96-
assail.statistics.unsafe_blocks,
97-
assail.statistics.panic_sites,
98-
assail.statistics.unwrap_calls
99-
);
100-
for wp in &assail.weak_points {
101-
let loc = wp
102-
.location
103-
.as_deref()
104-
.or(wp.file.as_deref())
105-
.unwrap_or("<unknown>");
106-
println!(
107-
" [{:?}] {:?} @ {} — {}",
108-
wp.severity, wp.category, loc, wp.description
109-
);
110-
}
111-
println!();
112-
113-
println!("=== File Risk ===");
114-
for detail in formatter.file_risk_details(assail) {
115-
println!(" {}", detail);
116-
}
117-
println!();
118-
119-
println!("=== Matrix ===");
120-
println!(
121-
"Dependency edges: {} Taint rows: {}",
122-
assail.dependency_graph.edges.len(),
123-
assail.taint_matrix.rows.len()
124-
);
125-
for detail in formatter.taint_matrix_details(assail) {
126-
println!(" {}", detail);
127-
}
128-
println!();
129-
130-
println!("=== Attacks ===");
131-
for result in &report.attack_results {
132-
let status = if result.skipped {
133-
"skipped"
134-
} else if result.success {
135-
"passed"
136-
} else {
137-
"failed"
138-
};
139-
println!(
140-
" {:?}: {} crashes={} duration={:.2}s",
141-
result.axis,
142-
status,
143-
result.crashes.len(),
144-
result.duration.as_secs_f64()
145-
);
146-
}
147-
println!();
148-
149-
println!("=== Assessment ===");
150-
for issue in &report.overall_assessment.critical_issues {
151-
println!(" CRITICAL: {}", issue);
152-
}
153-
for rec in &report.overall_assessment.recommendations {
154-
println!(" REC: {}", rec);
155-
}
156-
println!();
157-
158-
Ok(())
159-
}
160-
16173
/// Attempt to load a JSON file as either an `AssaultReport`, `SystemImage`,
16274
/// or `TemporalDiff`. Detection is format-based: we try each in turn and
16375
/// keep the first successful parse.

src/report/gui_text.rs

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
// SPDX-License-Identifier: MPL-2.0
2+
3+
//! Headless GUI report renderer — text-only output, no display server.
4+
//!
5+
//! This is the `gui --headless` path. It is always compiled (independent of the
6+
//! `gui` feature flag) so the readiness test `readiness_d_gui_headless_runs`
7+
//! and CI integrations keep working in MSRV-clean default builds. The
8+
//! windowed renderer in [`crate::report::gui`] is feature-gated because eframe
9+
//! raises MSRV above 1.85.0.
10+
11+
use crate::report::formatter::ReportFormatter;
12+
use crate::types::AssaultReport;
13+
use anyhow::Result;
14+
15+
/// Print a structured text summary of every GUI panel.
16+
///
17+
/// Safe to call in CI or headless environments with no Wayland/X11 display.
18+
/// Promotes the `gui` subcommand from Grade E to Grade D.
19+
pub fn run_headless(report: AssaultReport) -> Result<()> {
20+
let assail = &report.assail_report;
21+
let formatter = ReportFormatter::new();
22+
23+
println!("PANIC-ATTACK GUI REPORT (headless)");
24+
println!();
25+
26+
println!("=== Summary ===");
27+
println!("Language: {:?}", assail.language);
28+
println!(
29+
"Score: {:.1}/100",
30+
report.overall_assessment.robustness_score
31+
);
32+
println!("Crashes: {}", report.total_crashes);
33+
println!("Signatures: {}", report.total_signatures);
34+
println!("Weak points: {}", assail.weak_points.len());
35+
println!("Frameworks: {:?}", assail.frameworks);
36+
println!();
37+
38+
println!("=== Assail ===");
39+
println!("Program: {}", assail.program_path.display());
40+
println!(
41+
"Stats: lines={} unsafe={} panics={} unwraps={}",
42+
assail.statistics.total_lines,
43+
assail.statistics.unsafe_blocks,
44+
assail.statistics.panic_sites,
45+
assail.statistics.unwrap_calls
46+
);
47+
for wp in &assail.weak_points {
48+
let loc = wp
49+
.location
50+
.as_deref()
51+
.or(wp.file.as_deref())
52+
.unwrap_or("<unknown>");
53+
println!(
54+
" [{:?}] {:?} @ {} — {}",
55+
wp.severity, wp.category, loc, wp.description
56+
);
57+
}
58+
println!();
59+
60+
println!("=== File Risk ===");
61+
for detail in formatter.file_risk_details(assail) {
62+
println!(" {}", detail);
63+
}
64+
println!();
65+
66+
println!("=== Matrix ===");
67+
println!(
68+
"Dependency edges: {} Taint rows: {}",
69+
assail.dependency_graph.edges.len(),
70+
assail.taint_matrix.rows.len()
71+
);
72+
for detail in formatter.taint_matrix_details(assail) {
73+
println!(" {}", detail);
74+
}
75+
println!();
76+
77+
println!("=== Attacks ===");
78+
for result in &report.attack_results {
79+
let status = if result.skipped {
80+
"skipped"
81+
} else if result.success {
82+
"passed"
83+
} else {
84+
"failed"
85+
};
86+
println!(
87+
" {:?}: {} crashes={} duration={:.2}s",
88+
result.axis,
89+
status,
90+
result.crashes.len(),
91+
result.duration.as_secs_f64()
92+
);
93+
}
94+
println!();
95+
96+
println!("=== Assessment ===");
97+
for issue in &report.overall_assessment.critical_issues {
98+
println!(" CRITICAL: {}", issue);
99+
}
100+
for rec in &report.overall_assessment.recommendations {
101+
println!(" REC: {}", rec);
102+
}
103+
println!();
104+
105+
Ok(())
106+
}

src/report/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ pub mod formatter;
77
pub mod generator;
88
#[cfg(feature = "gui")]
99
pub mod gui;
10+
pub mod gui_text;
1011
pub mod migration;
1112
pub mod output;
1213
pub mod sarif;

0 commit comments

Comments
 (0)