Lightweight Assurance 1: Define and enforce the Rust assurance baseline#2568
Lightweight Assurance 1: Define and enforce the Rust assurance baseline#2568yuvalkom-M wants to merge 6 commits intomainfrom
Conversation
There was a problem hiding this comment.
Pull request overview
This PR is part of “Lightweight Assurance 1” and focuses on defining a stricter Rust assurance baseline for the velvet election-tally pipeline crate (and related SQLite helpers), primarily by enforcing documentation and Clippy/Rust linting standards and refactoring code to be more explicit/safer.
Changes:
- Enforces a strict Rust/Clippy lint baseline in
packages/velvet(docs, unsafe, and many Clippy lint groups set todeny). - Adds extensive Rustdoc/module documentation across pipeline, CLI, config, fixtures, and tests, plus various refactors (saturating arithmetic, slice-based APIs, tracing usage).
- Converts several SQLite helper functions in
sequent-corefromasync fnto synchronous functions and updates call sites.
Reviewed changes
Copilot reviewed 57 out of 57 changed files in this pull request and generated 23 comments.
Show a summary per file
| File | Description |
|---|---|
| packages/velvet/tests/pipes/mod.rs | Adds module-level test documentation. |
| packages/velvet/tests/mod.rs | Adds crate-level test documentation and module docs. |
| packages/velvet/tests/instant_runoff/mod.rs | Adds module docs for IRV tests. |
| packages/velvet/tests/instant_runoff/irv_unit_tests.rs | Updates calls to find_first_active_choice to match signature change. |
| packages/velvet/src/utils.rs | Adds docs, updates error variant usage in parse_file, and documents helpers. |
| packages/velvet/src/pipes/pipes.rs | Documents Pipe/PipeManager and simplifies pipe dispatch match arms. |
| packages/velvet/src/pipes/pipe_name.rs | Adds docs and refactors serde visitor impl style. |
| packages/velvet/src/pipes/pipe_inputs.rs | Adds docs and refactors path building/parsing helpers and input reading. |
| packages/velvet/src/pipes/mod.rs | Adds module docs and allows module-inception for pipes. |
| packages/velvet/src/pipes/mark_winners/mod.rs | Adds module docs and allows module-inception. |
| packages/velvet/src/pipes/mark_winners/mark_winners.rs | Refactors winners generation and IO paths, adds docs, and updates error variant names. |
| packages/velvet/src/pipes/generate_reports/mod.rs | Adds module docs and allows module-inception. |
| packages/velvet/src/pipes/generate_reports/generate_reports.rs | Large refactor + extensive docs; changes reporting computations, serialization paths, and output writing. |
| packages/velvet/src/pipes/generate_db/mod.rs | Adds module docs and allows module-inception. |
| packages/velvet/src/pipes/generate_db/generate_db.rs | Refactors DB population flow, adds docs, and updates to sync SQLite helpers. |
| packages/velvet/src/pipes/error.rs | Documents pipe error types and renames UnexpectedError → Unexpected. |
| packages/velvet/src/pipes/do_tally/tally.rs | Adds docs and refactors aggregation/count arithmetic to be saturating. |
| packages/velvet/src/pipes/do_tally/mod.rs | Adds module docs and allows module-inception. |
| packages/velvet/src/pipes/do_tally/error.rs | Adds docs for tally error/result types. |
| packages/velvet/src/pipes/do_tally/do_tally.rs | Adds docs, refactors internal helpers, replaces stdout prints with tracing. |
| packages/velvet/src/pipes/do_tally/counting_algorithm/utils.rs | Refactors vote metric computations and signatures (slice params, saturating math) with added docs. |
| packages/velvet/src/pipes/do_tally/counting_algorithm/plurality_at_large.rs | Refactors imports and arithmetic safety; documents algorithm. |
| packages/velvet/src/pipes/do_tally/counting_algorithm/mod.rs | Adds module docs and allows module-inception. |
| packages/velvet/src/pipes/do_tally/counting_algorithm/instant_runoff.rs | Extensive refactor + docs; changes helper signatures and arithmetic safety. |
| packages/velvet/src/pipes/do_tally/counting_algorithm/error.rs | Documents counting algorithm errors and renames UnexpectedError → Unexpected. |
| packages/velvet/src/pipes/do_tally/counting_algorithm/counting_algorithm.rs | Documents CountingAlgorithm trait and error expectations. |
| packages/velvet/src/pipes/decode_ballots/mod.rs | Adds module docs and allows module-inception. |
| packages/velvet/src/pipes/decode_ballots/decode_mcballots.rs | Refactors error variant name and minor data-structure updates with added docs. |
| packages/velvet/src/pipes/decode_ballots/decode_ballots.rs | Adds docs and updates error variant name. |
| packages/velvet/src/pipes/ballot_images/mod.rs | Adds module docs and allows module-inception. |
| packages/velvet/src/pipes/ballot_images/mcballot_images.rs | Refactors APIs to slices, replaces stdout with tracing, adds docs, and simplifies logic. |
| packages/velvet/src/pipes/ballot_images/ballot_images.rs | Adds docs, replaces stdout with tracing, and refactors template computation. |
| packages/velvet/src/main.rs | Enables missing-doc warnings and adds top-level module docs. |
| packages/velvet/src/lib.rs | Adds crate docs and module-level docs. |
| packages/velvet/src/fixtures/mod.rs | Adds module docs and allows module-inception for fixtures module. |
| packages/velvet/src/fixtures/fixtures.rs | Adds docs, improves temp file handling, and improves cleanup logging. |
| packages/velvet/src/fixtures/elections.rs | Refactors fixture construction, adds docs, and adjusts TreeNodeArea annotation initialization. |
| packages/velvet/src/fixtures/contests.rs | Adds docs and refactors vote limits conversion. |
| packages/velvet/src/fixtures/candidates.rs | Adds docs and minor string construction refactors. |
| packages/velvet/src/fixtures/ballot_styles.rs | Adds docs and adjusts default option initialization. |
| packages/velvet/src/fixtures/areas.rs | Adds docs and refactors UUID parsing. |
| packages/velvet/src/config/mod.rs | Adds module docs and allows module-inception. |
| packages/velvet/src/config/generate_reports.rs | Adds docs and refines constant typing. |
| packages/velvet/src/config/config.rs | Adds docs for config types and structures. |
| packages/velvet/src/config/ballot_images_config.rs | Adds docs for ballot image configuration types/constants. |
| packages/velvet/src/cli/test_all.rs | Adds docs and refactors test ballot generation logic and formatting. |
| packages/velvet/src/cli/state.rs | Adds docs and refactors pipeline progression logic. |
| packages/velvet/src/cli/mod.rs | Adds module docs and allows module-inception. |
| packages/velvet/src/cli/error.rs | Adds docs for CLI error/result types. |
| packages/velvet/src/cli/cli.rs | Adds docs and refactors stage validation logic. |
| packages/velvet/Cargo.toml | Introduces strict Rustdoc/Rust/Clippy lint baselines (many set to deny). |
| packages/sequent-core/src/sqlite/results_event.rs | Converts create_results_event_sqlite from async to sync. |
| packages/sequent-core/src/sqlite/results_election.rs | Converts create_results_election_sqlite from async to sync. |
| packages/sequent-core/src/sqlite/results_contest.rs | Converts create_results_contest_sqlite from async to sync. |
| packages/sequent-core/src/sqlite/results_contest_candidate.rs | Converts create_results_contest_candidates_sqlite from async to sync. |
| packages/sequent-core/src/sqlite/results_area_contest.rs | Converts create_results_area_contests_sqlite from async to sync. |
| packages/sequent-core/src/sqlite/results_area_contest_candidate.rs | Converts create_results_area_contest_candidates_sqlite from async to sync. |
Comments suppressed due to low confidence (1)
packages/velvet/src/pipes/generate_db/generate_db.rs:200
populate_results_tablescommits the SQLite transaction unconditionally, even ifprocess_results_tables(...)returns an error. That can persist partial/inconsistent data. Onlycommit()on success; on error, return early and let the transaction rollback (drop).
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| @@ -60,15 +86,18 @@ impl PipeInputs { | |||
| let election_list = Self::read_input_dir_config(root_path_config.as_path())?; | |||
| Ok(Self { | |||
| cli, | |||
| root_path_config: root_path_config.to_path_buf(), | |||
| root_path_ballots: root_path_ballots.to_path_buf(), | |||
| root_path_tally_sheets: root_path_tally_sheets.to_path_buf(), | |||
| root_path_database: root_path_database.to_path_buf(), | |||
| root_path_config: root_path_config.clone(), | |||
| root_path_ballots: root_path_ballots.clone(), | |||
| root_path_tally_sheets: root_path_tally_sheets.clone(), | |||
| root_path_database: root_path_database.clone(), | |||
There was a problem hiding this comment.
PipeInputs::new stores root_path_* into the struct as PathBuf, but the locals are &PathBuf and .clone() here clones the reference, not the PathBuf. This won’t type-check (expected PathBuf, found &PathBuf). Make the locals owned PathBuf (no &) or use to_path_buf()/PathBuf::from when assigning to the struct fields.
| complexity = "deny" | ||
| style = "deny" | ||
| perf = "deny" | ||
| pedantic = "deny" No newline at end of file |
There was a problem hiding this comment.
These new lint settings (pedantic = "deny", unwrap_used = "deny", panic = "deny", etc.) will fail the build with existing unwrap()/expect() usage across the crate (including tests/benches). Either remove/replace all unwrap/expect sites (e.g. convert to Result and propagate) or relax/allow specific lints where panics are acceptable (tests/benches).
| pedantic = "deny" | |
| pedantic = "warn" |
| #[instrument(err, skip_all)] | ||
| /// Computes and formats reports for rendering. | ||
| /// | ||
| /// # Errors | ||
| /// | ||
| /// Returns an error if report computation fails. | ||
| /// | ||
| /// # Panics | ||
| /// | ||
| /// May panic if `contest_result` is None for a contest. | ||
| #[instrument(err, skip_all)] | ||
| #[allow(clippy::too_many_lines)] | ||
| pub fn compute_reports( |
There was a problem hiding this comment.
#[instrument(...)] is applied twice to compute_reports (once above the doc comment and again immediately before #[allow(...)]). This is redundant and may produce duplicated tracing spans/metadata. Remove the duplicate attribute and keep a single #[instrument(...)] on the function.
| let mut candidate_result = vec![]; | ||
|
|
||
| if (contest_result_opt.is_some()) { | ||
| let mut contest_result = contest_result_opt.clone().unwrap(); | ||
| let mut contest_result = contest_result_opt |
There was a problem hiding this comment.
The if (contest_result_opt.is_some()) / if (is_consolidated) style introduces needless parentheses that will be denied under the new Clippy style = "deny"/pedantic = "deny" settings (needless_parens). Please remove the extra parentheses to keep Clippy clean.
| if (contest_result_opt.is_some()) { | ||
| let mut contest_result = contest_result_opt.clone().unwrap(); | ||
| let mut contest_result = contest_result_opt | ||
| .clone() | ||
| .expect("contest_result should be present"); | ||
|
|
There was a problem hiding this comment.
New .expect(...) usage here will be rejected under the new Clippy pedantic = "deny" (via expect_used) / panic policy. Instead of panicking when contest_result_opt is None, return a proper Error (or restructure so contest_result_opt is required by type) and propagate it.
| continue; | ||
| } | ||
| let contest_result = contest.contest_result.clone().unwrap(); | ||
| let current_contest = contest.contest.clone().unwrap(); | ||
| let contest_result = contest | ||
| .contest_result | ||
| .clone() | ||
| .expect("contest_result should be present"); | ||
| let current_contest = contest.contest.clone().expect("contest should be present"); | ||
|
|
There was a problem hiding this comment.
New .expect(...) calls here (and similar ones below) will be denied under the new Clippy pedantic baseline (expect_used) and also turn malformed input data into a hard crash. Prefer returning an error (anyhow::Error/pipe Error) instead of panicking when required fields are missing.
| .insert(decoded_contest.contest_id.clone(), HashMap::new()); | ||
| } | ||
| let area_dvc_map = output_map | ||
| .get_mut(&decoded_contest.contest_id) | ||
| .expect("impossible"); | ||
|
|
||
| if !area_dvc_map.contains_key(&area_id) { | ||
| area_dvc_map.insert(area_id.clone(), vec![]); | ||
| } | ||
| area_dvc_map.entry(area_id).or_default(); | ||
| let values = area_dvc_map.get_mut(&area_id).expect("impossible"); |
There was a problem hiding this comment.
The expect("impossible") calls will be denied under the new Clippy expect_used baseline. Even if these invariants should hold, prefer debug_assert! plus a regular error return (or restructure to avoid the second lookup) so release builds don’t panic.
| .get(candidate_id) | ||
| .map(|o| o.wins) | ||
| .unwrap_or(0); | ||
| outcome.transference = outcome.wins as i64 - prev_wins as i64; | ||
| .map_or(0, |o| o.wins); | ||
| outcome.transference = | ||
| (outcome.wins.cast_signed()).saturating_sub(prev_wins.cast_signed()); | ||
| } |
There was a problem hiding this comment.
cast_signed() is called here, but there is no definition/import of such a trait/method in the repo (search only finds these call sites). This will not compile unless a casting extension trait is added and brought into scope. Use an explicit checked conversion (e.g. i64::try_from(...)) or introduce/import the intended cast helper trait.
| election_event_id: election_event_id.into(), | ||
| election_id: election.election_id.clone(), | ||
| results_event_id: results_event_id.into(), | ||
| name: None, | ||
| elegible_census: Some(election.census as i64), | ||
| total_voters: Some(election.total_votes as i64), | ||
| elegible_census: Some(election.census.cast_signed()), | ||
| total_voters: Some(election.total_votes.cast_signed()), |
There was a problem hiding this comment.
cast_signed() is used extensively here, but there’s no cast_signed implementation/import in the repository (search only finds these call sites). This will not compile as-is. Replace with explicit checked conversions (e.g. i64::try_from(...)) or add/import the intended casting extension trait.
| // Check if votes are within valid range | ||
| if actual_votes >= (contest.min_votes as u64) && actual_votes <= (contest.max_votes as u64) { | ||
| let min_votes_u64 = contest.min_votes.cast_unsigned(); | ||
| let max_votes_u64 = contest.max_votes.cast_unsigned(); | ||
| if actual_votes >= min_votes_u64 && actual_votes <= max_votes_u64 { |
There was a problem hiding this comment.
cast_unsigned() is called on contest.min_votes/contest.max_votes, but there’s no cast_unsigned implementation/import found in the repo. This is likely a compile error. Use explicit checked conversions (e.g. u64::try_from(...)) or add/import the intended casting helper trait.
parent issue: https://github.com/sequentech/meta/issues/11566