qa 0.1.1

Requirements traceability for safety-critical Rust software
Documentation
# 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

```toml
# Cargo.toml
[dependencies]
qa = "0.1"
```

```rust
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

```rust
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

```rust
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`:

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

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

### Accessing metadata

```rust
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

```rust
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:

```rust
#[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

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

### Analysis

```rust
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

```rust
// 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:

```rust
// 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:

```rust
// 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:

```toml
[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

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

### no_std verification

```sh
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