Skip to content

Lightweight Assurance 1: Define and enforce the Rust assurance baseline#2568

Open
yuvalkom-M wants to merge 6 commits intomainfrom
feat/meta-11566-2/main
Open

Lightweight Assurance 1: Define and enforce the Rust assurance baseline#2568
yuvalkom-M wants to merge 6 commits intomainfrom
feat/meta-11566-2/main

Conversation

@yuvalkom-M
Copy link
Copy Markdown
Contributor

@yuvalkom-M yuvalkom-M marked this pull request as ready for review March 31, 2026 06:53
Copilot AI review requested due to automatic review settings March 31, 2026 06:53
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 to deny).
  • 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-core from async fn to 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 UnexpectedErrorUnexpected.
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 UnexpectedErrorUnexpected.
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_tables commits the SQLite transaction unconditionally, even if process_results_tables(...) returns an error. That can persist partial/inconsistent data. Only commit() 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.

Comment on lines 81 to +92
@@ -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(),
Copy link

Copilot AI Mar 31, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
complexity = "deny"
style = "deny"
perf = "deny"
pedantic = "deny" No newline at end of file
Copy link

Copilot AI Mar 31, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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).

Suggested change
pedantic = "deny"
pedantic = "warn"

Copilot uses AI. Check for mistakes.
Comment on lines 134 to 146
#[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(
Copy link

Copilot AI Mar 31, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

#[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.

Copilot uses AI. Check for mistakes.
Comment on lines 184 to +187
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
Copy link

Copilot AI Mar 31, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
Comment on lines 186 to 190
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");

Copy link

Copilot AI Mar 31, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
Comment on lines 448 to 455
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");

Copy link

Copilot AI Mar 31, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
Comment on lines 179 to 186
.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");
Copy link

Copilot AI Mar 31, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
Comment on lines 347 to 351
.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());
}
Copy link

Copilot AI Mar 31, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
Comment on lines 432 to +437
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()),
Copy link

Copilot AI Mar 31, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
Comment on lines 43 to +46
// 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 {
Copy link

Copilot AI Mar 31, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants