Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
461 changes: 371 additions & 90 deletions .fusa-hara.json

Large diffs are not rendered by default.

152 changes: 151 additions & 1 deletion .fusa-reqs.json
Original file line number Diff line number Diff line change
Expand Up @@ -990,6 +990,156 @@
"tags": [
"mock"
]
},
{
"id": "REQ-VIRT-013",
"title": "VirtualBus send_header applies back-pressure without blocking",
"description": "send_header broadcasts to subscribers per each queue's back-pressure policy; a full subscriber queue drops the frame rather than blocking the broadcaster",
"asil": "ASIL-B",
"rationale": "internal: send_header broadcasts to subscribers per each queue's back-",
"tags": [
"virt"
]
},
{
"id": "REQ-VIRT-014",
"title": "VirtualBus subscribe delivers to multiple subscribers independently",
"description": "Each subscriber receives its own copy of matching frames; one subscriber's queue state does not affect another's delivery",
"asil": "ASIL-B",
"rationale": "internal: Each subscriber receives its own copy of matching frames; on",
"tags": [
"virt"
]
},
{
"id": "REQ-VIRT-015",
"title": "VirtualBus close is idempotent",
"description": "Calling close() more than once does not return an error and does not panic",
"asil": "ASIL-B",
"rationale": "internal: Calling close() more than once does not return an error and ",
"tags": [
"virt"
]
},
{
"id": "REQ-VIRT-016",
"title": "VirtualBus remains consistently closed after repeated close",
"description": "After any number of close() calls, the bus reports closed and rejects further publish/send_header calls with Error::Closed",
"asil": "ASIL-B",
"rationale": "internal: After any number of close() calls, the bus reports closed an",
"tags": [
"virt"
]
},
{
"id": "REQ-VIRT-017",
"title": "VirtualBus send_header returns Closed after close",
"description": "send_header called after close() returns Error::Closed rather than attempting a frame exchange",
"asil": "ASIL-B",
"rationale": "internal: send_header called after close() returns Error::Closed rathe",
"tags": [
"virt"
]
},
{
"id": "REQ-VIRT-018",
"title": "VirtualBus concurrent access does not panic",
"description": "Concurrent publish/subscribe/send_header calls from multiple tasks complete without panicking or deadlocking",
"asil": "ASIL-B",
"rationale": "internal: Concurrent publish/subscribe/send_header calls from multiple",
"tags": [
"virt"
]
},
{
"id": "REQ-VIRT-019",
"title": "VirtualBus publish stores a defensive copy",
"description": "publish() copies the caller's data buffer; mutating the caller's buffer after the call does not affect the stored slave response",
"asil": "ASIL-B",
"rationale": "internal: publish() copies the caller's data buffer; mutating the call",
"tags": [
"virt"
]
},
{
"id": "REQ-MASTER-006",
"title": "MasterNode run invokes on_frame in schedule order",
"description": "run() invokes on_frame for each successful schedule slot, in the order the slots appear in the schedule table",
"asil": "ASIL-B",
"rationale": "internal: run() invokes on_frame for each successful schedule slot, in",
"tags": [
"master"
]
},
{
"id": "REQ-MASTER-007",
"title": "MasterNode run continues after on_error",
"description": "After invoking on_error for a failed slot, run() continues executing the remaining schedule slots rather than aborting",
"asil": "ASIL-B",
"rationale": "internal: After invoking on_error for a failed slot, run() continues e",
"tags": [
"master"
]
},
{
"id": "REQ-MASTER-008",
"title": "MasterNode run returns Timeout on context expiry",
"description": "When the supplied Context expires mid-schedule, run() returns Error::Timeout",
"asil": "ASIL-B",
"rationale": "internal: When the supplied Context expires mid-schedule, run() return",
"tags": [
"master"
]
},
{
"id": "REQ-MASTER-009",
"title": "MasterNode run rejects an empty schedule",
"description": "Calling run() before a schedule has been installed returns an error rather than looping forever",
"asil": "ASIL-B",
"rationale": "internal: Calling run() before a schedule has been installed returns a",
"tags": [
"master"
]
},
{
"id": "REQ-MASTER-010",
"title": "MasterNode set_schedule rejects an empty entries list",
"description": "set_schedule(vec![]) returns Error::Other rather than installing an empty schedule",
"asil": "ASIL-B",
"rationale": "internal: set_schedule(vec![]) returns Error::Other rather than instal",
"tags": [
"master"
]
},
{
"id": "REQ-MASTER-011",
"title": "MasterNode set_schedule rejects an out-of-range frame ID",
"description": "set_schedule rejects any entry whose id exceeds LIN_MAX_ID with Error::InvalidFrame",
"asil": "ASIL-B",
"rationale": "internal: set_schedule rejects any entry whose id exceeds LIN_MAX_ID w",
"tags": [
"master"
]
},
{
"id": "REQ-MASTER-012",
"title": "MasterNode set_schedule stores a defensive copy",
"description": "set_schedule copies the caller's entries; mutating the caller's Vec after the call does not affect the installed schedule",
"asil": "ASIL-B",
"rationale": "internal: set_schedule copies the caller's entries; mutating the calle",
"tags": [
"master"
]
},
{
"id": "REQ-MASTER-013",
"title": "MasterNode run invokes callbacks in schedule order",
"description": "run() invokes on_frame/on_error for each slot strictly in schedule order, matching the order entries were installed",
"asil": "ASIL-B",
"rationale": "internal: run() invokes on_frame/on_error for each slot strictly in sc",
"tags": [
"master"
]
}
]
}
}
13 changes: 9 additions & 4 deletions .fusa.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
{
"tool": "rsfusa",
"version": "0.5",
"project": "rust-LIN",
"configVersion": "1.0",
"project": {
"name": "rust-LIN",
"version": "0.4.1"
},
"standard": "iso26262",
"asil": "ASIL-B",
"protocol": "LIN"
"sourceDirs": ["src"],
"excludePatterns": ["target/**"],
"strict": true
}
95 changes: 71 additions & 24 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -53,9 +53,22 @@ jobs:
cargo install cargo-llvm-cov --locked
cargo llvm-cov --all-features --workspace --lcov --output-path lcov.info
cargo llvm-cov report --summary-only | tee coverage-summary.txt
# Gate: overall line coverage must be ≥ 90 %
pct=$(grep -oP 'Lines\s+\K[\d.]+' coverage-summary.txt || echo "0")
# Gate: overall line coverage must be >= 80% (current baseline ~85%;
# ratchet toward the 90% target as coverage improves — see #16).
# The TOTAL row looks like:
# TOTAL 3693 567 84.65% 330 43 86.97% 2485 369 85.15% 0 0 -
# i.e. region-cover%, function-cover%, then LINE-cover% as the third
# percentage column — pull that one, not the first.
pct=$(grep '^TOTAL' coverage-summary.txt | grep -oE '[0-9]+\.[0-9]+%' | sed -n '3p' | tr -d '%')
if [ -z "$pct" ]; then
echo "::error::could not extract line coverage from coverage-summary.txt"
exit 1
fi
echo "Line coverage: ${pct}%"
awk -v pct="$pct" 'BEGIN { if (pct + 0 < 80) { exit 1 } }' || {
echo "::error::line coverage ${pct}% is below the 80% gate"
exit 1
}

- name: Upload coverage
if: matrix.os == 'ubuntu-latest' && always()
Expand Down Expand Up @@ -133,7 +146,7 @@ jobs:
key: ubuntu-cargo-safety-${{ hashFiles('**/Cargo.lock') }}

- name: Install rsfusa
run: cargo install --git https://github.com/SoundMatt/rust-FuSa --tag v0.3.10 rsfusa --locked
run: cargo install --git https://github.com/SoundMatt/rust-FuSa --tag v0.3.14 rsfusa --locked

- name: Build release binary (needed for qualify)
run: cargo build --release --locked
Expand All @@ -142,57 +155,90 @@ jobs:
run: cargo test --locked

- name: Capture test evidence with rsfusa
run: rsfusa verify --dir . || true
run: |
# KNOWN UPSTREAM BUG (SoundMatt/rust-FuSa): `rsfusa verify` invokes
# `cargo test -- --test-output=immediate`, but that flag does not
# exist for cargo's built-in libtest harness ("error: Unrecognized
# option: 'test-output'"), so this step always fails regardless of
# whether the project's own tests pass — see the `test` step above
# in the build-test job for the real, authoritative test result.
# Non-blocking until fixed upstream, but loud rather than silent:
if ! rsfusa verify --dir .; then
echo "::warning title=rsfusa verify (known upstream bug)::rsfusa verify failed due to an invalid --test-output=immediate flag it passes to cargo test; this is a rust-FuSa bug, not a rust-LIN test failure (see build-test job's 'test' step for real results)."
fi

- name: Coding standard lint (ISO 26262 Part 6)
run: rsfusa lint --dir . --format json --output lint-report.json || true
run: rsfusa lint --dir . --format json --output lint-report.json

- name: Static analysis
run: rsfusa analyze --dir . --format json --output analyze-report.json || true
run: rsfusa analyze --dir . --format json --output analyze-report.json

- name: Safety check (ASIL-B strict — §20.1.2)
run: rsfusa check --dir . --strict --format json --output check-report.json || true
run: rsfusa check --dir . --strict --format json --output check-report.json

- name: Safety check SARIF (GitHub code scanning)
run: rsfusa check --dir . --format sarif --output results.sarif || true
run: rsfusa check --dir . --format sarif --output results.sarif

- name: Cyclomatic complexity V(G)
run: rsfusa comp --dir . --format json --output comp-report.json || true
run: rsfusa comp --dir . --format json --output comp-report.json

- name: Requirement traceability matrix (§20.1.2)
run: |
rsfusa trace --dir . --format json --output trace.json || true
rsfusa trace --dir . --format text || true
rsfusa trace --dir . --format json --output trace.json
rsfusa trace --dir . --format text

- name: FMEA
run: rsfusa fmea --dir . || true
run: rsfusa fmea --dir .

- name: Threat analysis (TARA)
run: rsfusa tara --dir . || true
run: rsfusa tara --dir .

- name: Cybersecurity analysis (CWE)
run: rsfusa cyber --dir . --format json --output cyber-report.json || true
run: rsfusa cyber --dir . --format json --output cyber-report.json

- name: Data coupling analysis (ISO 26262-6 §6.4.4)
run: rsfusa coupling --dir . --output coupling-report.json

- name: Boundary diagram
run: rsfusa boundary --dir . || true
run: rsfusa boundary --dir .

- name: HARA
run: rsfusa hara show --dir . || true

- name: Safety case assembly
run: rsfusa safety-case --dir . || true

- name: ISO 26262 Part 6 gap report
run: rsfusa iso26262 --dir . --format json --output iso26262-gap-report.json || true
run: rsfusa hara show --dir .

- name: Tool qualification
run: rsfusa qualify
# Runs before safety-case/iso26262 below: both of those check for
# qualify-report.json's presence as required evidence, so it must
# already exist by the time they run.
run: |
rsfusa qualify --format json \
--qualification-method self \
--qualifier "SoundMatt / rust-LIN maintainers" \
--record-uri "https://github.com/SoundMatt/rust-LIN/blob/main/tool-qualification/rsfusa-tql2.md" \
--output qualify-report.json
badge=$(python3 -c "import json; print(json.load(open('qualify-report.json'))['qualificationBadge'])")
echo "Tool qualification badge: ${badge}"
if [ "$badge" = "unqualified" ]; then
echo "::error::rsfusa qualify badge is 'unqualified' — see qualify-report.json"
exit 1
fi

- name: Release artifacts (SBOM, provenance, manifest)
# Also runs before safety-case/iso26262: both check for sbom.json.
run: rsfusa release --dir .

- name: Safety case assembly
run: |
rsfusa safety-case --dir . | tee safety-case-stdout.txt
if grep -q "required evidence item(s) missing" safety-case-stdout.txt; then
echo "::error::safety-case reports missing required evidence — see safety-case-stdout.txt"
exit 1
fi

- name: ISO 26262 Part 6 gap report
run: rsfusa iso26262 --dir . --format json --output iso26262-gap-report.json

- name: Audit pack
run: rsfusa audit-pack --output audit-pack.zip || true
run: rsfusa audit-pack --output audit-pack.zip

- name: Upload SARIF to GitHub Code Scanning
if: always() && github.event_name != 'pull_request'
Expand All @@ -212,6 +258,7 @@ jobs:
analyze-report.json
comp-report.json
cyber-report.json
coupling-report.json
trace.json
fmea.json
tara.json
Expand Down
53 changes: 53 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
name: Release assets

# Attaches supply-chain evidence (SBOM, provenance, artifact manifest) to a
# published GitHub Release so downstream consumers can fetch and verify them
# alongside the exact release they describe, instead of only inside an
# ephemeral CI workflow-run artifact bundle. See #17.
on:
release:
types: [published]

permissions:
contents: write

env:
CARGO_TERM_COLOR: always

jobs:
attach-evidence:
name: Attach SBOM / provenance / manifest to release
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.event.release.tag_name }}

- uses: dtolnay/rust-toolchain@stable

- name: Cache cargo
uses: actions/cache@v4
with:
path: |
~/.cargo/registry
~/.cargo/git
target
key: ubuntu-cargo-release-${{ hashFiles('**/Cargo.lock') }}

- name: Install rsfusa
run: cargo install --git https://github.com/SoundMatt/rust-FuSa --tag v0.3.14 rsfusa --locked

- name: Build release binary
run: cargo build --release --locked

- name: Generate SBOM, provenance, and artifact manifest for this tag
run: rsfusa release --dir .

- name: Attach evidence to the GitHub Release
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
gh release upload "${{ github.event.release.tag_name }}" \
sbom.json provenance.json artifact-manifest.json \
--repo "${{ github.repository }}" \
--clobber
Loading
Loading