1 unstable release

Uses new Rust 2024

0.1.1 Feb 26, 2026

#1848 in Rust patterns

MIT license

26KB
438 lines

qa

Requirements traceability for safety-critical Rust software. Define requirements as code, link them to tests with an attribute macro, and auto-generate a traceability matrix.

Quick Start

# Cargo.toml
[dependencies]
qa = "0.1"
use qa::RequirementType::*;

// Define a requirement
qa::requirement! {
    pub REQ_ENERGY_CONSERVATION: Safety {
        description: "Total energy must be conserved across transport events",
        source: "NRC Reg Guide 1.190 §3.1",
    }
}

// Link a test to it
#[test]
#[qa::traces(REQ_ENERGY_CONSERVATION)]
fn test_energy_is_conserved() {
    // ...
}

// Generate the matrix
#[test]
fn traceability_matrix() {
    let matrix = qa::Matrix::collect();
    matrix.print();
    matrix.write("traceability.md", qa::Format::Markdown).unwrap();
    matrix.assert_complete(); // panics if any requirement has no tests
}

Defining Requirements

Single requirement

qa::requirement! {
    pub REQ_VOID_TRACKING: Safety {
        description: "Particles must be tracked through void regions without energy loss",
        source: "NRC Reg Guide 1.190 §4.2",
        priority: "critical",
    }
}

Batch definition

qa::requirements! {
    pub REQ_VOID_TRACKING: Safety {
        description: "Particles must be tracked through void regions without energy loss",
        source: "NRC Reg Guide 1.190 §4.2",
    }

    pub REQ_ENERGY_CONSERVATION: Safety {
        description: "Total energy must be conserved across transport events",
        source: "NRC Reg Guide 1.190 §3.1",
    }

    pub REQ_XS_INTERPOLATION: Functional {
        description: "Cross-section lookup must interpolate between tabulated energy points",
        source: "ENDF/B-VIII.0 format manual",
    }
}

Requirement fields

  • description (required, must be first): human-readable text
  • type (required, after :): a RequirementType variant
  • All other key-value pairs become freeform metadata

RequirementType

Built-in variants: Functional, NonFunctional, Performance, Safety, Security, Interface.

Extend with Custom:

const REGULATORY: qa::RequirementType = qa::RequirementType::Custom("regulatory");

qa::requirement! {
    pub REQ_NRC_COMPLIANCE: REGULATORY {
        description: "System must comply with NRC regulations",
    }
}

Accessing metadata

let source = REQ_VOID_TRACKING.get("source"); // Some("NRC Reg Guide 1.190 §4.2")
let missing = REQ_VOID_TRACKING.get("nonexistent"); // None

Tracing Tests

use qa::traces;

#[test]
#[traces(REQ_VOID_TRACKING)]
fn test_void_traversal() { /* ... */ }

// Multiple requirements per test
#[test]
#[traces(REQ_VOID_TRACKING, REQ_ENERGY_CONSERVATION)]
fn test_energy_through_void() { /* ... */ }

Composability

#[traces] emits the test function unchanged and appends registration as a separate item. It composes with any attribute macro regardless of ordering:

#[rstest]
#[traces(REQ_FIXTURES)]
fn test_with_fixture(fixture: MyFixture) { /* ... */ }

#[test]
#[should_panic(expected = "negative energy")]
#[traces(REQ_ERROR_HANDLING)]
fn test_negative_energy_panics() { /* ... */ }

Generating the Matrix

let matrix = qa::Matrix::collect();

Analysis

matrix.requirements()   // all registered requirements
matrix.traces()         // all registered traces
matrix.traces_for("REQ_VOID_TRACKING") // tests for a specific requirement
matrix.untraced()       // requirements with no tests
matrix.orphan_traces()  // traces referencing nonexistent requirement IDs
matrix.coverage()       // fraction of requirements with ≥1 test (0.0..=1.0)
matrix.assert_complete() // panics if any requirement is untraced

Output

// Terminal table (via tabled)
matrix.print();

// Markdown file
matrix.write("traceability.md", qa::Format::Markdown).unwrap();

// JSON file (requires `json` feature)
matrix.write("traceability.json", qa::Format::Json).unwrap();

Organizing Requirements

Place requirements at the top of the files they govern:

// src/transport.rs
qa::requirements! {
    pub REQ_VOID_TRACKING: Safety {
        description: "Particles must be tracked through void regions without energy loss",
        source: "NRC Reg Guide 1.190 §4.2",
    }

    pub REQ_ENERGY_CONSERVATION: Safety {
        description: "Total energy must be conserved across transport events",
        source: "NRC Reg Guide 1.190 §3.1",
    }
}

// ... implementation code ...

Requirements register globally via linkme distributed slices regardless of which file or crate they're defined in. No central manifest to maintain.

Filtering and Grouping

Operate on freeform metadata for custom views:

// Only safety requirements
let safety = matrix.filter_by("priority", "critical");
safety.print();

// Group by source document
let by_source = matrix.group_by("source");
for (source, sub_matrix) in &by_source {
    println!("{}: {} requirements", source, sub_matrix.requirements().len());
}

no_std Support

The core types, macros, and linkme slices work without std. Disable default features:

[dependencies]
qa = { version = "0.1", default-features = false }

This gives you Requirement, RequirementType, TestTrace, requirement!, requirements!, and #[traces] on bare-metal targets. Matrix and all rendering require std.

Typical workflow: define requirements in your no_std library crate, then collect and render the matrix from a host-side test binary that enables std.

Feature Flags

Feature Default Description
std yes Matrix, terminal tables, file output
json no JSON rendering via serde (implies std)

Workspace

Crate Description
qa Core library — types, macros, matrix generation
qa-macros Proc-macro crate — #[traces] attribute

Development

cargo test --workspace                # all tests
cargo test --workspace --features json # with JSON output
cargo clippy --workspace --all-targets -- -D warnings

no_std verification

rustup target add thumbv7em-none-eabihf
cargo build -p qa --no-default-features --target thumbv7em-none-eabihf

Releasing

Releases are triggered manually from GitLab CI. Go to CI/CD → Pipelines → Run pipeline, set the BUMP variable to patch, minor, or major, then run the release job. This bumps versions across the workspace, publishes to crates.io, and pushes a version commit + tag.

License

MIT

Dependencies

~0.1–1.1MB
~23K SLoC