From 7704c549137437b48cb2954b7a8f3c90544c6d0d Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 19 Oct 2026 17:50:46 +0000 Subject: [PATCH 1/7] refactor(bin): use the library's modules instead of compiling them twice main.rs re-declared every module with `mod x;`, so the binary compiled its own private copy of the whole tree. Items used only through the library API (proven_tests, jit_context, comment_marker, ffi_kind) were then dead code inside the binary, which clippy -D warnings rejects. The binary now imports those modules from panic_attack; only the bin-only modules (diagnostics, groove, kin) stay as `mod`. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_019a95kk88TZ1iegsBK3kox3 --- src/main.rs | 53 ++++++++++++++++++++++++----------------------------- 1 file changed, 24 insertions(+), 29 deletions(-) diff --git a/src/main.rs b/src/main.rs index 2929e0a..78bd1e3 100644 --- a/src/main.rs +++ b/src/main.rs @@ -7,39 +7,34 @@ //! concurrency) and detecting bug signatures using logic programming techniques inspired by //! Mozart/Oz and Datalog. -mod a2ml; -mod abduct; -mod adjudicate; -mod aggregate; -mod ambush; -mod amuck; -mod assail; -mod assay; -mod assemblyline; -mod attack; -mod attestation; -mod axial; +use panic_attack::a2ml; +use panic_attack::abduct; +use panic_attack::adjudicate; +use panic_attack::aggregate; +use panic_attack::ambush; +use panic_attack::amuck; +use panic_attack::assail; +use panic_attack::assay; +use panic_attack::assemblyline; +use panic_attack::attack; +use panic_attack::attestation; +use panic_attack::axial; #[cfg(feature = "http")] -mod bridge; -mod campaign; -mod comment_marker; +use panic_attack::bridge; +use panic_attack::campaign; mod diagnostics; -mod ffi_kind; mod groove; -mod i18n; -mod jit_context; -mod kanren; +use panic_attack::i18n; mod kin; -mod mass_panic; -mod notify; -mod panll; -mod query; -mod report; -mod signatures; -mod storage; -mod sweep_tracker; -mod test_context; -mod types; +use panic_attack::mass_panic; +use panic_attack::notify; +use panic_attack::panll; +use panic_attack::query; +use panic_attack::report; +use panic_attack::signatures; +use panic_attack::storage; +use panic_attack::sweep_tracker; +use panic_attack::types; extern crate walkdir; From 5cdd1dfa8d4e8e03572f8ac4b0d28e0c5ecb416e Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 19 Oct 2026 17:50:46 +0000 Subject: [PATCH 2/7] fix(lint): clear the clippy 1.99 -D warnings errors - drop redundant borrows in format! args (storage) - split on a char array instead of a closure (safe_path) - rfind / find / and_then instead of filter+last, filter+next, map+flatten - egui Panel/CentralPanel::show_inside -> show (a pure rename in 0.36) - is_safe_char / all_chars_safe are proptest-only helpers: #[cfg(test)] - ProvenTestsReport::from_str becomes a real FromStr impl - qualify anyhow! inside the sarif-gated parser so the import is not unused without the feature Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_019a95kk88TZ1iegsBK3kox3 --- src/aggregate/proven_tests.rs | 9 +++++++-- src/assail/analyzer.rs | 3 +-- src/report/gui.rs | 6 +++--- src/safe_path.rs | 6 ++++-- src/scan/cargodeny.rs | 25 ++++++++++--------------- src/storage/mod.rs | 12 ++++++------ tests/readiness.rs | 3 +-- 7 files changed, 32 insertions(+), 32 deletions(-) diff --git a/src/aggregate/proven_tests.rs b/src/aggregate/proven_tests.rs index e12d807..21e6856 100644 --- a/src/aggregate/proven_tests.rs +++ b/src/aggregate/proven_tests.rs @@ -27,6 +27,7 @@ use anyhow::{Context, Result}; use serde::{Deserialize, Serialize}; use std::collections::BTreeSet; use std::path::Path; +use std::str::FromStr; /// A cited proof step (only Actually-Proven entries carry these). #[derive(Debug, Clone, Serialize, Deserialize)] @@ -115,9 +116,11 @@ impl ReportTier { } } -impl ProvenTestsReport { +impl FromStr for ProvenTestsReport { + type Err = anyhow::Error; + /// Parse a run report from JSON text. - pub fn from_str(text: &str) -> Result { + fn from_str(text: &str) -> Result { let report: ProvenTestsReport = serde_json::from_str(text).context("parsing proven-tests run report")?; if report.schema_version != 1 { @@ -128,7 +131,9 @@ impl ProvenTestsReport { } Ok(report) } +} +impl ProvenTestsReport { /// Parse a run report from a file path. pub fn from_path(path: &Path) -> Result { let text = diff --git a/src/assail/analyzer.rs b/src/assail/analyzer.rs index 7aab8fb..1fccd24 100644 --- a/src/assail/analyzer.rs +++ b/src/assail/analyzer.rs @@ -285,8 +285,7 @@ fn c_alloc_result_is_null_checked(lines: &[&str], idx: usize) -> bool { .next() .and_then(|lhs| { lhs.split(|c: char| !c.is_alphanumeric() && c != '_') - .filter(|s| !s.is_empty()) - .last() + .rfind(|s| !s.is_empty()) }) .map(str::to_string) } else { diff --git a/src/report/gui.rs b/src/report/gui.rs index 014c3fa..9502c4c 100644 --- a/src/report/gui.rs +++ b/src/report/gui.rs @@ -121,7 +121,7 @@ impl ReportGui { impl App for ReportGui { fn ui(&mut self, ui: &mut egui::Ui, _frame: &mut Frame) { - egui::Panel::top("header").show_inside(ui, |ui| { + egui::Panel::top("header").show(ui, |ui| { ui.horizontal(|ui| { ui.heading("panic-attack report"); ui.separator(); @@ -138,7 +138,7 @@ impl App for ReportGui { }); }); - egui::Panel::left("nav").show_inside(ui, |ui| { + egui::Panel::left("nav").show(ui, |ui| { ui.selectable_value(&mut self.tab, ReportTab::Summary, "Summary"); ui.selectable_value(&mut self.tab, ReportTab::Assail, "Assail"); ui.selectable_value(&mut self.tab, ReportTab::Matrix, "Matrix"); @@ -159,7 +159,7 @@ impl App for ReportGui { ui.selectable_value(&mut self.tab, ReportTab::Temporal, temporal_label); }); - egui::CentralPanel::default().show_inside(ui, |ui| match self.tab { + egui::CentralPanel::default().show(ui, |ui| match self.tab { ReportTab::Summary => self.render_summary(ui), ReportTab::Assail => self.render_assail(ui), ReportTab::Matrix => self.render_matrix(ui), diff --git a/src/safe_path.rs b/src/safe_path.rs index e79b525..ec06273 100644 --- a/src/safe_path.rs +++ b/src/safe_path.rs @@ -62,7 +62,7 @@ const SAFE_PUNCT: &[char] = &['-', '_', '.', '~']; /// assert!(!has_traversal("foo..bar")); // .. inside a name is not a segment /// ``` pub fn has_traversal(path: &str) -> bool { - for segment in path.split(|c| c == '/' || c == '\\') { + for segment in path.split(['/', '\\']) { if segment == ".." { return true; } @@ -100,11 +100,13 @@ pub fn sanitize_filename(filename: &str) -> String { } } +#[cfg(test)] /// Returns true if `c` is a character `sanitize_filename` would keep. fn is_safe_char(c: char) -> bool { c.is_ascii_alphanumeric() || SAFE_PUNCT.contains(&c) } +#[cfg(test)] /// Returns true if every character in `s` is safe. Used by the /// idempotence + non-empty proptest invariants. fn all_chars_safe(s: &str) -> bool { @@ -288,7 +290,7 @@ mod tests { #[test] fn prop_has_traversal_operational(s in ".*") { let split_says: bool = s - .split(|c| c == '/' || c == '\\') + .split(['/', '\\']) .any(|seg| seg == ".."); prop_assert_eq!( has_traversal(&s), diff --git a/src/scan/cargodeny.rs b/src/scan/cargodeny.rs index 5ebcb0b..7e9a89b 100644 --- a/src/scan/cargodeny.rs +++ b/src/scan/cargodeny.rs @@ -8,7 +8,7 @@ // feedback_no_automated_licence_edits. use crate::types::{Severity, WeakPoint, WeakPointCategory}; -use anyhow::{anyhow, Context, Result}; +use anyhow::{Context, Result}; use serde::{Deserialize, Serialize}; use std::path::{Path, PathBuf}; use std::process::Command; @@ -173,18 +173,13 @@ pub fn run_cargo_deny(config: &CargoDenyConfig) -> Result> { .map(|c| code_to_category(&c)) .unwrap_or(WeakPointCategory::Advisory); - let location = diagnostic - .location - .map(|loc| { - if let (Some(ref file), Some(line)) = (&loc.file, loc.line) { - Some(format!("{}:{}", file, line)) - } else if let Some(ref file) = loc.file { - Some(file.clone()) - } else { - None - } - }) - .flatten(); + let location = diagnostic.location.and_then(|loc| { + if let (Some(ref file), Some(line)) = (&loc.file, loc.line) { + Some(format!("{}:{}", file, line)) + } else { + loc.file.clone() + } + }); // Parse file and line from location string let (file, line) = if let Some(ref loc) = location { @@ -272,13 +267,13 @@ pub fn parse_sarif_output(sarif_json: &str) -> Result> { // SARIF structure: { runs: [{ results: [{ ... }] }] } let runs = v["runs"] .as_array() - .ok_or_else(|| anyhow!("no runs in SARIF"))?; + .ok_or_else(|| anyhow::anyhow!("no runs in SARIF"))?; let mut weak_points = Vec::new(); for run in runs { let results = run["results"] .as_array() - .ok_or_else(|| anyhow!("no results in SARIF run"))?; + .ok_or_else(|| anyhow::anyhow!("no results in SARIF run"))?; for result in results { let message = result["message"]["text"] .as_str() diff --git a/src/storage/mod.rs b/src/storage/mod.rs index 2c4fbd1..1c551ba 100644 --- a/src/storage/mod.rs +++ b/src/storage/mod.rs @@ -235,7 +235,7 @@ fn build_hexad(report: &AssaultReport) -> Result { let id = format!( "pa-{}-{}", now.format("%Y%m%d%H%M%S"), - &uuid_from_timestamp(now.timestamp_millis()) + uuid_from_timestamp(now.timestamp_millis()) ); let critical_count = report @@ -382,7 +382,7 @@ fn build_assemblyline_hexad( let id = format!( "pa-asmline-{}-{}", now.format("%Y%m%d%H%M%S"), - &uuid_from_timestamp(now.timestamp_millis()) + uuid_from_timestamp(now.timestamp_millis()) ); let document = serde_json::to_value(report)?; @@ -510,7 +510,7 @@ pub fn build_finding_hexads( let run_id = format!( "pa-asmline-{}-{}", now.format("%Y%m%d%H%M%S"), - &uuid_from_timestamp(now.timestamp_millis()) + uuid_from_timestamp(now.timestamp_millis()) ); let mut hexads = Vec::new(); @@ -540,7 +540,7 @@ pub fn build_finding_hexads( now.format("%Y%m%d%H%M%S"), repo_idx, wp_idx, - &uuid_from_timestamp(now.timestamp_millis()), + uuid_from_timestamp(now.timestamp_millis()), ); let document = serde_json::json!({ @@ -691,7 +691,7 @@ pub fn build_crosslang_hexads( run_ts, repo_idx, int_idx, - &uuid_from_timestamp(now.timestamp_millis()), + uuid_from_timestamp(now.timestamp_millis()), ); let document = serde_json::json!({ @@ -780,7 +780,7 @@ pub fn build_campaign_hexad(semantic: CampaignSemantic) -> PanicAttackHexad { let hexad_id = format!( "pa-campaign-{}-{}", now.format("%Y%m%d%H%M%S"), - &uuid_from_timestamp(now.timestamp_millis()) + uuid_from_timestamp(now.timestamp_millis()) ); PanicAttackHexad { diff --git a/tests/readiness.rs b/tests/readiness.rs index a9b9afd..727bc4e 100644 --- a/tests/readiness.rs +++ b/tests/readiness.rs @@ -319,8 +319,7 @@ fn cfg_test_panic() { clean_report .weak_points .iter() - .filter(|wp| wp.category == WeakPointCategory::PanicPath && !wp.suppressed) - .next() + .find(|wp| wp.category == WeakPointCategory::PanicPath && !wp.suppressed) .is_none(), "excluded panic contexts must produce zero active findings" ); From 3bfc1fc3809ed1942daa4363a0583cb6586ec30d Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 19 Oct 2026 22:14:04 +0000 Subject: [PATCH 3/7] fix(tests): restore the panic-surface control assertion and drop the re-added unused import after the 0f68c62 merge The text merge of 0f68c62 combined CodeRabbit's leading negation with this branch's .find(..).is_none(), inverting the readiness control; it is now the single !any(..) form. main.rs had its removed comment_marker/ffi_kind import re-added by the same merge. Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_019a95kk88TZ1iegsBK3kox3 --- src/main.rs | 1 - tests/readiness.rs | 3 +-- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/src/main.rs b/src/main.rs index 23f1459..adede31 100644 --- a/src/main.rs +++ b/src/main.rs @@ -55,7 +55,6 @@ use anyhow::{anyhow, Context, Result}; use clap::{CommandFactory, Parser, Subcommand}; use clap_complete::{generate, Shell}; use clap_complete_nushell::Nushell; -use panic_attack::{comment_marker, ffi_kind}; use std::collections::HashMap; use std::fs::{self, File}; use std::io::{self, Read, Write}; diff --git a/tests/readiness.rs b/tests/readiness.rs index 2b65ed8..9de3355 100644 --- a/tests/readiness.rs +++ b/tests/readiness.rs @@ -319,8 +319,7 @@ fn cfg_test_panic() { !clean_report .weak_points .iter() - .find(|wp| wp.category == WeakPointCategory::PanicPath && !wp.suppressed) - .is_none(), + .any(|wp| wp.category == WeakPointCategory::PanicPath && !wp.suppressed), "excluded panic contexts must produce zero active findings" ); Ok(()) From 7c3d0542f42810d6044f83263a762882fdebfdb6 Mon Sep 17 00:00:00 2001 From: "Jonathan D.A. Jewell" <6759885+hyperpolymath@users.noreply.github.com> Date: Mon, 19 Oct 2026 23:24:43 +0100 Subject: [PATCH 4/7] Update .github/workflows/scorecard.yml Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> Signed-off-by: Jonathan D.A. Jewell <6759885+hyperpolymath@users.noreply.github.com> --- .github/workflows/scorecard.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml index a389a51..d1ca432 100644 --- a/.github/workflows/scorecard.yml +++ b/.github/workflows/scorecard.yml @@ -17,7 +17,7 @@ jobs: scorecard: uses: hyperpolymath/standards/.github/workflows/scorecard-reusable.yml@333b06a01db1874ffedb2de1bfabb195640927c7 permissions: - actions: read + actions: read # required by scorecard-reusable.yml to read workflow metadata contents: read security-events: write id-token: write From 0ba37a53932f8c7ddac438c1f6ad166c08ffeba5 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 19 Oct 2026 22:23:31 +0000 Subject: [PATCH 5/7] fix(tests): restore the stack's safe_path test helpers; the 0f68c62 merge left two unused CodeRabbit's hunk added an all_chars_safe helper beside this branch's is_safe_char, and the merge kept both with neither caller, which cargo clippy --all-targets -- -D warnings rejects as dead code in the lib test target. The file is the version CI passed at 5db5d90. Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_019a95kk88TZ1iegsBK3kox3 --- src/safe_path.rs | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/safe_path.rs b/src/safe_path.rs index f60b699..ec06273 100644 --- a/src/safe_path.rs +++ b/src/safe_path.rs @@ -118,12 +118,6 @@ mod tests { use super::*; use proptest::prelude::*; - /// Property-test predicate kept separate from the sanitizer implementation. - fn all_chars_safe(s: &str) -> bool { - s.chars() - .all(|c| c.is_ascii_alphanumeric() || SAFE_PUNCT.contains(&c)) - } - // ───────────────────────────────────────────────────────────────── // has_traversal — concrete cases // ───────────────────────────────────────────────────────────────── From e7a6e452ea2a796c3218b674d46dc9e86150cde4 Mon Sep 17 00:00:00 2001 From: "coderabbitai[bot]" <136622811+coderabbitai[bot]@users.noreply.github.com> Date: Mon, 19 Oct 2026 22:27:44 +0000 Subject: [PATCH 6/7] chore: pin Chapel CI actions to commit SHAs and clarify JIT context documentation --- .github/workflows/actions.lock | 15 ++++++++++----- .github/workflows/chapel-ci.yml | 34 ++++++++++++++++----------------- src/assail/mod.rs | 3 ++- 3 files changed, 29 insertions(+), 23 deletions(-) diff --git a/.github/workflows/actions.lock b/.github/workflows/actions.lock index 8f31bb1..681da1c 100644 --- a/.github/workflows/actions.lock +++ b/.github/workflows/actions.lock @@ -18,11 +18,11 @@ workflows: - 'actions/upload-pages-artifact@v5.0.0' - 'haskell-actions/setup@v2.12.1' '.github/workflows/chapel-ci.yml': - - 'actions/cache@v6.1.0' - - 'actions/checkout@v7.0.1' - - 'actions/download-artifact@v8.0.2' - - 'actions/upload-artifact@v7.0.2' - - 'dtolnay/rust-toolchain@v1' + - 'actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9' + - 'actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1' + - 'actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333' + - 'actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9' + - 'dtolnay/rust-toolchain@683b588c487a69d01f6c6ddf40f036ff682cc2c8' '.github/workflows/codeql.yml': - 'actions/checkout@v7.0.1' - 'github/codeql-action@v4.39.0' @@ -114,6 +114,11 @@ dependencies: commit: 'sha1-368f82528645a54fb793d4d04e342629a3f51346' owner_id: 44036562 repo_id: 438112499 + 'actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333': + ref: 'v8.0.2' + commit: 'sha1-9000827ccba6bdab643e8b6fd33ac0654aef8333' + owner_id: 44036562 + repo_id: 192626254 'actions/download-artifact@v8.0.2': ref: 'v8.0.2' commit: 'sha1-9000827ccba6bdab643e8b6fd33ac0654aef8333' diff --git a/.github/workflows/chapel-ci.yml b/.github/workflows/chapel-ci.yml index 2a6eb88..3ae7720 100644 --- a/.github/workflows/chapel-ci.yml +++ b/.github/workflows/chapel-ci.yml @@ -72,7 +72,7 @@ jobs: outputs: relevant: ${{ steps.f.outputs.relevant }} steps: - - uses: actions/checkout@v7.0.1 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: fetch-depth: 2 - id: f @@ -105,7 +105,7 @@ jobs: runs-on: ubuntu-22.04 timeout-minutes: 45 steps: - - uses: actions/checkout@v7.0.1 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - name: Install Chapel ${{ env.CHAPEL_VERSION }} run: | set -euo pipefail @@ -128,7 +128,7 @@ jobs: runs-on: ubuntu-22.04 timeout-minutes: 45 steps: - - uses: actions/checkout@v7.0.1 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - name: Install Chapel ${{ env.CHAPEL_VERSION }} run: | set -euo pipefail @@ -144,7 +144,7 @@ jobs: chpl src/MassPanic.chpl src/Protocol.chpl src/Imaging.chpl src/Temporal.chpl -o mass-panic chpl smoke/two_repo_smoke.chpl src/Protocol.chpl src/Imaging.chpl -o smoke/two_repo_smoke - name: Upload Chapel artefacts - uses: actions/upload-artifact@v7.0.2 + uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2 with: name: chapel-binaries path: | @@ -159,7 +159,7 @@ jobs: runs-on: ubuntu-22.04 timeout-minutes: 45 steps: - - uses: actions/checkout@v7.0.1 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - name: Install Chapel ${{ env.CHAPEL_VERSION }} run: | set -euo pipefail @@ -168,7 +168,7 @@ jobs: curl -fsSL --retry 3 -o /tmp/chapel.deb "${{ env.CHAPEL_DEB_URL }}" sudo apt-get install -y /tmp/chapel.deb - name: Download Chapel artefacts - uses: actions/download-artifact@v8.0.2 + uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2 with: name: chapel-binaries path: chapel/ @@ -184,7 +184,7 @@ jobs: runs-on: ubuntu-22.04 timeout-minutes: 45 steps: - - uses: actions/checkout@v7.0.1 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - name: Install Chapel ${{ env.CHAPEL_VERSION }} run: | set -euo pipefail @@ -193,7 +193,7 @@ jobs: curl -fsSL --retry 3 -o /tmp/chapel.deb "${{ env.CHAPEL_DEB_URL }}" sudo apt-get install -y /tmp/chapel.deb - name: Download Chapel artefacts - uses: actions/download-artifact@v8.0.2 + uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2 with: name: chapel-binaries path: chapel/ @@ -221,11 +221,11 @@ jobs: runs-on: ubuntu-22.04 timeout-minutes: 45 steps: - - uses: actions/checkout@v7.0.1 - - uses: dtolnay/rust-toolchain@v1 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + - uses: dtolnay/rust-toolchain@683b588c487a69d01f6c6ddf40f036ff682cc2c8 # v1 with: toolchain: stable - - uses: actions/cache@v6.1.0 + - uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 with: path: | ~/.cargo/registry @@ -244,11 +244,11 @@ jobs: runs-on: ubuntu-22.04 timeout-minutes: 45 steps: - - uses: actions/checkout@v7.0.1 - - uses: dtolnay/rust-toolchain@v1 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + - uses: dtolnay/rust-toolchain@683b588c487a69d01f6c6ddf40f036ff682cc2c8 # v1 with: toolchain: stable - - uses: actions/cache@v6.1.0 + - uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 with: path: | ~/.cargo/registry @@ -263,7 +263,7 @@ jobs: curl -fsSL --retry 3 -o /tmp/chapel.deb "${{ env.CHAPEL_DEB_URL }}" sudo apt-get install -y /tmp/chapel.deb - name: Download Chapel artefacts - uses: actions/download-artifact@v8.0.2 + uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2 with: name: chapel-binaries path: chapel/ @@ -283,7 +283,7 @@ jobs: env: CHPL_HOME: /opt/chapel-multilocale steps: - - uses: actions/checkout@v7.0.1 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 # Cache the entire built-from-source Chapel tree. Key is stable across # PRs as long as the version, conduit, launcher and cache-gen marker @@ -291,7 +291,7 @@ jobs: # restore is ~30s. - name: Cache multilocale Chapel ($CHPL_HOME) id: chapel-cache - uses: actions/cache@v6.1.0 + uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 with: path: ${{ env.CHAPEL_MULTILOCALE_HOME }} key: ${{ runner.os }}-chapel-multilocale-${{ env.CHAPEL_VERSION }}-gasnet-smp-${{ env.CHAPEL_MULTILOCALE_CACHE_GEN }} diff --git a/src/assail/mod.rs b/src/assail/mod.rs index 7ec19ad..aaf369e 100644 --- a/src/assail/mod.rs +++ b/src/assail/mod.rs @@ -411,7 +411,8 @@ pub fn apply_suppression(report: &mut AssailReport) { /// 3. UnsafeFFI finding where the file's [[ffi_kind]] is /// `BuildSystem` or `TestMock` (audit-accepted by default). /// -/// JIT context handling remains inline in analyzer.rs:1117..1129. +/// JIT context handling remains in the transmute classification block of +/// `Analyzer::analyze_rust` in `analyzer.rs`. /// /// Fills missing `wp.test_context` for findings with a known file path so /// downstream audit consumers can render it. Returns the number of From 74a4654339b48d3b4365d75c1d181fc8a2fed3b1 Mon Sep 17 00:00:00 2001 From: "coderabbitai[bot]" <136622811+coderabbitai[bot]@users.noreply.github.com> Date: Mon, 19 Oct 2026 22:29:36 +0000 Subject: [PATCH 7/7] docs: clarify Rust function behavior, error conditions, and hexad ID guarantees --- src/aggregate/proven_tests.rs | 11 +++++++++-- src/assail/analyzer.rs | 18 ++++++++---------- src/diagnostics.rs | 6 ++++++ src/report/gui.rs | 3 +++ src/storage/mod.rs | 33 ++++++++++++++++++++------------- 5 files changed, 46 insertions(+), 25 deletions(-) diff --git a/src/aggregate/proven_tests.rs b/src/aggregate/proven_tests.rs index 60c0819..9799cad 100644 --- a/src/aggregate/proven_tests.rs +++ b/src/aggregate/proven_tests.rs @@ -119,7 +119,10 @@ impl ReportTier { impl FromStr for ProvenTestsReport { type Err = anyhow::Error; - /// Parse a run report from JSON text. + /// Parse a run report from JSON text, requiring schema version 1. + /// + /// # Errors + /// Returns JSON deserialisation errors or an error if `schema_version` is not 1. fn from_str(text: &str) -> Result { let report: ProvenTestsReport = serde_json::from_str(text).context("parsing proven-tests run report")?; @@ -134,7 +137,11 @@ impl FromStr for ProvenTestsReport { } impl ProvenTestsReport { - /// Parse a run report from a file path. + /// Read a UTF-8 JSON run report from `path` and parse it as schema version 1. + /// + /// # Errors + /// Returns file read errors (including invalid UTF-8), JSON deserialisation + /// errors or an error if `schema_version` is not 1. pub fn from_path(path: &Path) -> Result { let text = std::fs::read_to_string(path).with_context(|| format!("reading {}", path.display()))?; diff --git a/src/assail/analyzer.rs b/src/assail/analyzer.rs index 1b75185..4b86ad1 100644 --- a/src/assail/analyzer.rs +++ b/src/assail/analyzer.rs @@ -263,18 +263,16 @@ static RE_HARDCODED_SECRET: OnceLock = OnceLock::new(); /// Does not handle OCaml `(* *)` or Forth `\` — edge cases for later. static RE_TODO_COMMENT: OnceLock = OnceLock::new(); -/// Heuristic: is the `malloc` on `lines[idx]` NULL-checked within a short -/// window of following lines? +/// Heuristically recognise a null check for the allocation on `lines[idx]`. /// -/// An "UncheckedAllocation" finding should only fire when the result is *not* -/// guarded. This recognises the common C idioms (`if (p == NULL)`, -/// `if (!p)`, `if (p == nullptr)`) on the lvalue assigned from `malloc`. +/// Searches the allocation line and up to six following lines for `NULL` or +/// `nullptr` alongside the assigned identifier, or an `if` line containing `!` +/// immediately followed by that identifier. If no identifier can be extracted, +/// any `NULL` or `nullptr` substring in the window suffices. Substring matches +/// do not establish that the allocation is guarded. /// -/// Deliberately conservative to avoid false negatives in a security scanner: -/// a site is treated as checked only on a clear NULL-guard signal that -/// references the allocated variable (or, when no lvalue can be parsed, any -/// NULL/nullptr token in the window). Genuinely-unchecked allocations — the -/// real finding — are still reported. +/// # Panics +/// Panics if the zero-based `idx` is outside `lines`. fn c_alloc_result_is_null_checked(lines: &[&str], idx: usize) -> bool { let line = lines[idx]; // Parse the lvalue assigned from malloc: the last identifier left of '='. diff --git a/src/diagnostics.rs b/src/diagnostics.rs index f89ec6b..5890014 100644 --- a/src/diagnostics.rs +++ b/src/diagnostics.rs @@ -11,6 +11,12 @@ const HYPATIA_ENV: &str = "HYPATIA_API_KEY"; const GITBOT_FLEET_ENV: &str = "GITBOT_FLEET_ENDPOINT"; const PANICBOT_DIRECTIVE_PATH: &str = ".machine_readable/bot_directives/panicbot.scm"; +/// Print readiness checks using `manifest`, paths relative to the current +/// working directory and the configured integration environment variables. +/// +/// # Errors +/// Returns an error after printing all results if any check has error severity. +/// Warnings alone do not cause failure. pub fn run_self_diagnostics(manifest: &Manifest) -> Result<()> { println!("panic-attack self-diagnostics"); diff --git a/src/report/gui.rs b/src/report/gui.rs index 9502c4c..549560b 100644 --- a/src/report/gui.rs +++ b/src/report/gui.rs @@ -120,6 +120,9 @@ impl ReportGui { } impl App for ReportGui { + /// Render the file loader, navigation and selected report tab. + /// A load-button click or Enter after the path field loses focus attempts + /// to read the specified JSON file, updating the viewer or its status message. fn ui(&mut self, ui: &mut egui::Ui, _frame: &mut Frame) { egui::Panel::top("header").show(ui, |ui| { ui.horizontal(|ui| { diff --git a/src/storage/mod.rs b/src/storage/mod.rs index 1c551ba..3680f52 100644 --- a/src/storage/mod.rs +++ b/src/storage/mod.rs @@ -229,7 +229,10 @@ pub struct MigrationSemantic { pub snapshot_label: Option, } -/// Build a VerisimDB hexad from an assault report +/// Build a VerisimDB hexad from an assault report. +/// +/// # Errors +/// Propagates errors from serialising the report into its JSON document. fn build_hexad(report: &AssaultReport) -> Result { let now = Utc::now(); let id = format!( @@ -375,6 +378,9 @@ pub fn persist_report( /// /// Unlike single-repo hexads which wrap an AssaultReport, assemblyline /// hexads capture the batch scan results across many repos. +/// +/// # Errors +/// Propagates errors from serialising the report into its JSON document. fn build_assemblyline_hexad( report: &crate::assemblyline::AssemblylineReport, ) -> Result { @@ -492,13 +498,13 @@ fn severity_label(severity: &crate::types::Severity) -> &'static str { } } -/// Build one hexad per `WeakPoint` across all repo results in an -/// assemblyline report (issue #33 S1). +/// Build one hexad per unsuppressed `WeakPoint` across repo results with an +/// AssailReport in an assemblyline report (issue #33 S1). /// /// Subject identity lives in `semantic.finding.finding_id`; each emitted -/// hexad's top-level `id` remains per-run-unique so two runs of the same -/// finding produce two distinct hexad files (the join key is the -/// `finding_id`, not the hexad id). +/// hexad's top-level `id` includes a millisecond timestamp and repo/finding +/// indices. IDs are distinct within a call but may repeat across calls in +/// the same millisecond; use `finding_id` as the join key. /// /// `run_id` is shared across every finding-hexad in this run and stamped /// into both `first_seen_run` and `last_seen_run` (S1 has no prior-run @@ -662,10 +668,10 @@ fn derive_crosslang_interactions_for_report( /// repo in an assemblyline report (issue #33 kanren-crosslang follow-up). /// /// Subject identity lives in `semantic.crosslang.interaction_id`; the -/// top-level hexad `id` is per-run-unique so two runs of the same -/// interaction produce two distinct hexad files (the join key is the -/// `interaction_id`, not the hexad id). Returns an empty `Vec` when the -/// report has no AssailReport-bearing results or no derivable interactions. +/// top-level hexad `id` includes a millisecond timestamp and repo/interaction +/// indices. IDs are distinct within a call but may repeat across calls in +/// the same millisecond; use `interaction_id` as the join key. Returns an empty +/// `Vec` when the report has no AssailReport-bearing results or no derivable interactions. pub fn build_crosslang_hexads( report: &crate::assemblyline::AssemblylineReport, ) -> Result> { @@ -772,9 +778,10 @@ const HEXAD_FILE_READ_LIMIT: u64 = 16 * 1024 * 1024; /// Build a campaign-state hexad for one lifecycle event (issue #33 S2). /// -/// Append-only: each call produces a fresh hexad with a unique id. The -/// `finding_id` is carried as the semantic subject so the newest hexad -/// per finding is the current state. +/// The id is derived from the current millisecond timestamp, so calls in the +/// same millisecond can produce the same id. This only constructs a hexad; +/// it does not write to storage. The `finding_id` is carried as the semantic +/// subject so stored events can be grouped by finding. pub fn build_campaign_hexad(semantic: CampaignSemantic) -> PanicAttackHexad { let now = Utc::now(); let hexad_id = format!(