fix: CI trustworthiness + cpp-FuSa/RELAY version currency #20
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: CI | |
| on: | |
| push: | |
| branches: [main] | |
| pull_request: | |
| branches: [main] | |
| concurrency: | |
| group: ${{ github.workflow }}-${{ github.ref }} | |
| cancel-in-progress: true | |
| jobs: | |
| build-and-test: | |
| name: ${{ matrix.os }} / ${{ matrix.compiler }} / C++${{ matrix.std }} | |
| runs-on: ${{ matrix.os }} | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| - os: ubuntu-22.04 | |
| compiler: clang-14 | |
| std: 17 | |
| cc: clang-14 | |
| cxx: clang++-14 | |
| - os: ubuntu-22.04 | |
| compiler: gcc-12 | |
| std: 17 | |
| cc: gcc-12 | |
| cxx: g++-12 | |
| - os: ubuntu-22.04 | |
| compiler: gcc-12 | |
| std: 20 | |
| cc: gcc-12 | |
| cxx: g++-12 | |
| - os: macos-14 | |
| compiler: clang | |
| std: 17 | |
| cc: clang | |
| cxx: clang++ | |
| - os: windows-2022 | |
| compiler: msvc | |
| std: 17 | |
| cc: cl | |
| cxx: cl | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Install tools (Ubuntu) | |
| if: runner.os == 'Linux' | |
| run: | | |
| sudo apt-get update -qq | |
| sudo apt-get install -y cmake ninja-build | |
| - name: Set up MSVC environment (Windows) | |
| if: runner.os == 'Windows' | |
| uses: ilammy/msvc-dev-cmd@v1 | |
| - name: Configure | |
| shell: bash | |
| env: | |
| CC: ${{ matrix.cc }} | |
| CXX: ${{ matrix.cxx }} | |
| run: | | |
| cmake -B build \ | |
| -DCMAKE_BUILD_TYPE=Release \ | |
| -DCMAKE_CXX_STANDARD=${{ matrix.std }} \ | |
| -DCMAKE_EXPORT_COMPILE_COMMANDS=ON \ | |
| -G Ninja | |
| - name: Build | |
| run: cmake --build build --parallel | |
| - name: Test (single-threaded to avoid TempDir races) | |
| run: ctest --test-dir build --output-on-failure -j1 | |
| coverage: | |
| name: Coverage (LCOV) | |
| runs-on: ubuntu-22.04 | |
| needs: build-and-test | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Install tools | |
| run: sudo apt-get update -qq && sudo apt-get install -y cmake ninja-build lcov | |
| - name: Configure (coverage build) | |
| run: | | |
| cmake -B build-cov \ | |
| -DCMAKE_BUILD_TYPE=Debug \ | |
| -DCMAKE_CXX_FLAGS="--coverage -O0" \ | |
| -DCMAKE_EXE_LINKER_FLAGS="--coverage" \ | |
| -G Ninja | |
| - name: Build | |
| run: cmake --build build-cov --parallel | |
| - name: Test | |
| run: ctest --test-dir build-cov --output-on-failure -j1 | |
| - name: Collect LCOV data | |
| run: | | |
| lcov --capture \ | |
| --directory build-cov \ | |
| --output-file coverage.info | |
| lcov --remove coverage.info \ | |
| '*/tests/*' '*/catch2/*' '*/FetchContent/*' '*/_deps/*' \ | |
| '/usr/include/*' '/usr/lib/*' \ | |
| --output-file coverage.info | |
| - name: Enforce 70% line coverage gate | |
| run: | | |
| COVERAGE=$(lcov --summary coverage.info 2>&1 | awk '/lines/ {gsub(/%/,""); print $2}') | |
| echo "Line coverage: ${COVERAGE}%" | |
| if ! awk "BEGIN {exit (${COVERAGE} + 0 >= 70) ? 0 : 1}"; then | |
| echo "::error::Line coverage ${COVERAGE}% is below the required 70%" | |
| exit 1 | |
| fi | |
| - name: Upload coverage report | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: coverage-report | |
| path: coverage.info | |
| relay-conform: | |
| name: RELAY conformance (relay conform) | |
| runs-on: ubuntu-22.04 | |
| needs: build-and-test | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Install tools | |
| run: sudo apt-get update -qq && sudo apt-get install -y cmake ninja-build | |
| - name: Build CLI | |
| run: | | |
| cmake -B build \ | |
| -DCMAKE_BUILD_TYPE=Release \ | |
| -DCMAKE_CXX_STANDARD=17 \ | |
| -G Ninja | |
| cmake --build build --parallel --target cpp-lin-cli | |
| - name: Install Go | |
| uses: actions/setup-go@v5 | |
| with: | |
| go-version: 'stable' | |
| - name: Install relay tool | |
| # Pinned, not @latest: an unpinned install means the spec version | |
| # actually being conformance-checked against can silently change | |
| # between CI runs with no diff to review (see cpp-LIN#22). v2.0.0 is | |
| # RELAY's current tagged release but is not installable via | |
| # `go install ...@v2.0.0` — its go.mod doesn't declare the | |
| # `/v2` module path suffix Go's semantic import versioning requires | |
| # for a v2+ major version, so the install fails outright (filed | |
| # upstream as SoundMatt/RELAY#68). v1.14.0 is therefore the current | |
| # actually-installable latest; keep this in sync with README.md and | |
| # requirements/requirements.json's declared spec version. | |
| run: go install github.com/SoundMatt/RELAY/cmd/relay@v1.14.0 | |
| - name: RELAY conformance gate | |
| run: relay conform --strict ./build/cli/cpp-lin-cli | |
| - name: RELAY interop gate (§20 Continuous Conformance) | |
| run: relay interop --protocol LIN ./build/cli/cpp-lin-cli | |
| sanitizers: | |
| name: ASan + UBSan (IEC 61508 SIL-2 dynamic analysis) | |
| runs-on: ubuntu-22.04 | |
| needs: build-and-test | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Install tools | |
| run: | | |
| sudo apt-get update -qq | |
| sudo apt-get install -y cmake ninja-build gcc-12 g++-12 | |
| - name: Configure (ASan + UBSan) | |
| env: | |
| CC: gcc-12 | |
| CXX: g++-12 | |
| run: | | |
| cmake -B build-san \ | |
| -DCMAKE_BUILD_TYPE=Debug \ | |
| -DCMAKE_CXX_FLAGS="-fsanitize=address,undefined -fno-omit-frame-pointer -O1" \ | |
| -DCMAKE_EXE_LINKER_FLAGS="-fsanitize=address,undefined" \ | |
| -G Ninja | |
| - name: Build | |
| run: cmake --build build-san --parallel | |
| - name: Test with sanitizers | |
| env: | |
| ASAN_OPTIONS: "halt_on_error=1:detect_stack_use_after_return=1" | |
| UBSAN_OPTIONS: "halt_on_error=1:print_stacktrace=1" | |
| run: ctest --test-dir build-san --output-on-failure -j1 | |
| tsan: | |
| name: ThreadSanitizer (REQ-VIRT-018 concurrent access) | |
| runs-on: ubuntu-22.04 | |
| needs: build-and-test | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Install tools | |
| run: | | |
| sudo apt-get update -qq | |
| sudo apt-get install -y cmake ninja-build gcc-12 g++-12 | |
| - name: Configure (ThreadSanitizer) | |
| env: | |
| CC: gcc-12 | |
| CXX: g++-12 | |
| run: | | |
| cmake -B build-tsan \ | |
| -DCMAKE_BUILD_TYPE=Debug \ | |
| -DCMAKE_CXX_FLAGS="-fsanitize=thread -fno-omit-frame-pointer -O1" \ | |
| -DCMAKE_EXE_LINKER_FLAGS="-fsanitize=thread" \ | |
| -G Ninja | |
| - name: Build | |
| run: cmake --build build-tsan --parallel | |
| - name: Test with ThreadSanitizer | |
| env: | |
| TSAN_OPTIONS: "halt_on_error=1:second_deadlock_stack=1" | |
| run: ctest --test-dir build-tsan --output-on-failure -j1 | |
| fusa-asil-b: | |
| name: cpp-FuSa ASIL-B qualification | |
| runs-on: ubuntu-22.04 | |
| needs: build-and-test | |
| steps: | |
| - name: Check out cpp-LIN | |
| uses: actions/checkout@v4 | |
| with: | |
| path: cpp-LIN | |
| - name: Check out cpp-FuSa | |
| uses: actions/checkout@v4 | |
| with: | |
| repository: SoundMatt/cpp-FuSa | |
| path: cpp-FuSa | |
| ref: v0.17.1 | |
| - name: Install tools | |
| run: sudo apt-get update -qq && sudo apt-get install -y cmake ninja-build zip | |
| - name: Build cpfusa | |
| run: | | |
| cmake -B cpp-FuSa/build \ | |
| -S cpp-FuSa \ | |
| -DCMAKE_BUILD_TYPE=Release \ | |
| -G Ninja | |
| cmake --build cpp-FuSa/build --parallel | |
| - name: cpfusa init | |
| working-directory: cpp-LIN | |
| # No --force: .fusa.json/.fusa-reqs.json are checked into the repo | |
| # and already populated (hundreds of real REQ-* entries). --force | |
| # would silently overwrite them with an empty scaffold right before | |
| # the traceability step reads them, making the traceability gate a | |
| # permanent no-op (see cpp-LIN#19). Without --force, `init` just | |
| # prints "already exists" and exits 0 when the files are present, so | |
| # no `|| true` is needed either. | |
| run: ../cpp-FuSa/build/cpfusa init --name cpp-LIN --standard iso26262 --asil ASIL-B --project-version 0.4.0 | |
| - name: cpfusa check | |
| working-directory: cpp-LIN | |
| run: ../cpp-FuSa/build/cpfusa check --dir . | |
| - name: cpfusa lint | |
| working-directory: cpp-LIN | |
| run: ../cpp-FuSa/build/cpfusa lint --dir . | |
| - name: cpfusa trace (requirements traceability) | |
| working-directory: cpp-LIN | |
| run: ../cpp-FuSa/build/cpfusa trace --dir . | |
| - name: cpfusa cyber | |
| working-directory: cpp-LIN | |
| run: ../cpp-FuSa/build/cpfusa cyber --write --dir . | |
| - name: cpfusa qualify (ASIL-B gate) | |
| working-directory: cpp-LIN | |
| run: ../cpp-FuSa/build/cpfusa qualify --dir . | |
| - name: cpfusa hara init | |
| working-directory: cpp-LIN | |
| run: ../cpp-FuSa/build/cpfusa hara init --project cpp-LIN --dir . | |
| - name: cpfusa boundary | |
| working-directory: cpp-LIN | |
| run: ../cpp-FuSa/build/cpfusa boundary --dir . | |
| - name: cpfusa tara | |
| working-directory: cpp-LIN | |
| run: ../cpp-FuSa/build/cpfusa tara --dir . | |
| - name: cpfusa fmea | |
| working-directory: cpp-LIN | |
| run: ../cpp-FuSa/build/cpfusa fmea --dir . | |
| - name: cpfusa safety-case | |
| working-directory: cpp-LIN | |
| run: ../cpp-FuSa/build/cpfusa safety-case --dir . | |
| - name: cpfusa sas | |
| working-directory: cpp-LIN | |
| run: ../cpp-FuSa/build/cpfusa sas --dir . | |
| - name: cpfusa sci | |
| working-directory: cpp-LIN | |
| run: ../cpp-FuSa/build/cpfusa sci --dir . | |
| - name: cpfusa release (SBOM + provenance + artifact manifest) | |
| working-directory: cpp-LIN | |
| # Was never called even though the evidence-upload step below has | |
| # always listed sbom.json/provenance.json/artifact-manifest.json — | |
| # they never actually existed at upload time. | |
| run: ../cpp-FuSa/build/cpfusa release --dir . | |
| - name: cpfusa iso26262 (ASIL-B gap gate) | |
| working-directory: cpp-LIN | |
| # Run after every other evidence-generating step above so the gap | |
| # analysis sees the real, complete evidence set (previously this ran | |
| # right after `hara init`, before boundary/tara/fmea/safety-case/ | |
| # sas/sci/release had produced anything, so it under-reported). | |
| # | |
| # `cpfusa iso26262` exits 1 whenever any objective is in the "gap" | |
| # state, which is real behavior, not a bug: with the full evidence | |
| # set in place, 11 of the 20 ASIL-B objectives still report "gap" | |
| # (6-5.2, 6-6.1, 6-6.2, 6-6.3, 6-7.1, 6-7.2, 6-8.1, 6-9.1, 8-6.1, | |
| # 8-6.2, 9-1.1) purely because cpfusa v0.17.1's own | |
| # iso26262::detect_status() has no evidence-detection case at all for | |
| # those clauses (it falls through to Status::Gap unconditionally, | |
| # regardless of what's actually in the repo — e.g. 8-6.2 "Safety | |
| # manual" reports gap even though SAFETY_MANUAL.md exists and is | |
| # substantial). Filed upstream as SoundMatt/cpp-FuSa#57. Until that's | |
| # fixed, blanket `|| true` here would hide real regressions just as | |
| # much as it hides this known floor, so instead this gate fails only | |
| # if the gap count regresses past that documented floor. | |
| run: | | |
| ../cpp-FuSa/build/cpfusa iso26262 \ | |
| --asil ASIL-B \ | |
| --output iso26262-gap-report.json \ | |
| --dir . || true | |
| GAPS=$(jq '.summary.gaps' iso26262-gap-report.json) | |
| echo "ISO 26262 ASIL-B: $(jq -c '.summary' iso26262-gap-report.json)" | |
| MAX_GAPS=11 | |
| if [ "$GAPS" -gt "$MAX_GAPS" ]; then | |
| echo "::error::ISO 26262 ASIL-B gap count ($GAPS) exceeds the known ${MAX_GAPS}-gap floor (see step comment / SoundMatt/cpp-FuSa#57) — this is a real regression, not the known tool limitation. Investigate before merging." | |
| exit 1 | |
| fi | |
| - name: cpfusa iec61508 (SIL-2 gap gate) | |
| working-directory: cpp-LIN | |
| # Same reasoning as the ISO 26262 step above: 10 of the 18 required | |
| # SIL-2 objectives (1-7.1, 1-8.1, 1-8.2, 3-7.2, 3-7.4, 3-7.5, 3-7.6, | |
| # 3-7.7, 3-7.8, 2-7.1) have no detect_status() case in cpfusa v0.17.1 | |
| # and can never leave "gap" via this tool version regardless of real | |
| # project evidence. Same upstream issue (SoundMatt/cpp-FuSa#57). | |
| run: | | |
| ../cpp-FuSa/build/cpfusa iec61508 \ | |
| --sil SIL-2 \ | |
| --output iec61508-gap-report.json \ | |
| --dir . || true | |
| GAPS=$(jq '.summary.gaps' iec61508-gap-report.json) | |
| echo "IEC 61508 SIL-2: $(jq -c '.summary' iec61508-gap-report.json)" | |
| MAX_GAPS=10 | |
| if [ "$GAPS" -gt "$MAX_GAPS" ]; then | |
| echo "::error::IEC 61508 SIL-2 gap count ($GAPS) exceeds the known ${MAX_GAPS}-gap floor (see step comment / SoundMatt/cpp-FuSa#57) — this is a real regression, not the known tool limitation. Investigate before merging." | |
| exit 1 | |
| fi | |
| - name: cpfusa badge | |
| working-directory: cpp-LIN | |
| run: ../cpp-FuSa/build/cpfusa badge --dir . | |
| - name: cpfusa vuln | |
| working-directory: cpp-LIN | |
| run: ../cpp-FuSa/build/cpfusa vuln --dir . | |
| - name: cpfusa metrics record | |
| working-directory: cpp-LIN | |
| run: ../cpp-FuSa/build/cpfusa metrics record --dir . | |
| - name: cpfusa report (JSON) | |
| working-directory: cpp-LIN | |
| run: | | |
| ../cpp-FuSa/build/cpfusa report \ | |
| --format json \ | |
| --output check-report.json \ | |
| --dir . | |
| - name: Upload ASIL-B evidence artifacts | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: asil-b-evidence | |
| path: | | |
| cpp-LIN/check-report.json | |
| cpp-LIN/qualify-report.json | |
| cpp-LIN/cyber-report.json | |
| cpp-LIN/tara.json | |
| cpp-LIN/tara.md | |
| cpp-LIN/fmea.json | |
| cpp-LIN/fmea.csv | |
| cpp-LIN/safety-case.json | |
| cpp-LIN/safety-case.mermaid | |
| cpp-LIN/safety-case.md | |
| cpp-LIN/sbom.json | |
| cpp-LIN/provenance.json | |
| cpp-LIN/artifact-manifest.json | |
| cpp-LIN/fusa-badge.svg | |
| cpp-LIN/iso26262-gap-report.json | |
| cpp-LIN/iec61508-gap-report.json | |
| cpp-LIN/sas.json | |
| cpp-LIN/sas.md | |
| cpp-LIN/sci.json | |
| cpp-LIN/vuln.json | |
| cpp-LIN/boundary.mermaid | |
| cpp-LIN/boundary.dot | |
| cpp-LIN/.fusa-hara.json | |
| cpp-LIN/.fusa-metrics.json | |
| cpp-LIN/TARA.md | |
| cpp-LIN/SAFETY_MANUAL.md | |
| cpp-LIN/INCIDENT-RESPONSE.md | |
| cpp-LIN/SECURITY.md | |
| static-analysis: | |
| name: Static analysis (clang-tidy) | |
| runs-on: ubuntu-22.04 | |
| needs: build-and-test | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Install tools | |
| run: | | |
| sudo apt-get update -qq | |
| sudo apt-get install -y cmake ninja-build clang-14 clang-tidy-14 | |
| - name: Configure (generate compile_commands.json) | |
| env: | |
| CC: clang-14 | |
| CXX: clang++-14 | |
| run: | | |
| cmake -B build \ | |
| -DCMAKE_BUILD_TYPE=Release \ | |
| -DCMAKE_CXX_STANDARD=17 \ | |
| -DCMAKE_EXPORT_COMPILE_COMMANDS=ON \ | |
| -G Ninja | |
| - name: clang-tidy | |
| run: | | |
| find src include -name '*.cpp' -o -name '*.hpp' | \ | |
| xargs clang-tidy-14 \ | |
| -p build \ | |
| --checks='-*,clang-analyzer-*,bugprone-*,modernize-use-override,modernize-use-nullptr,cppcoreguidelines-no-malloc' \ | |
| 2>&1 | tee clang-tidy.log || true | |
| if grep -q "error:" clang-tidy.log; then | |
| echo "::error::clang-tidy reported errors" | |
| grep "error:" clang-tidy.log | |
| exit 1 | |
| fi | |
| docker-build: | |
| name: Docker build (smoke test) | |
| runs-on: ubuntu-22.04 | |
| needs: build-and-test | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Build Docker image | |
| run: docker build -f docker/Dockerfile --target test -t cpp-lin-test . | |
| - name: Smoke test - run all tests in container | |
| run: docker run --rm cpp-lin-test | |
| sarif: | |
| name: SARIF upload | |
| runs-on: ubuntu-22.04 | |
| needs: build-and-test | |
| permissions: | |
| security-events: write | |
| steps: | |
| - name: Check out cpp-LIN | |
| uses: actions/checkout@v4 | |
| with: | |
| path: cpp-LIN | |
| - name: Check out cpp-FuSa | |
| uses: actions/checkout@v4 | |
| with: | |
| repository: SoundMatt/cpp-FuSa | |
| path: cpp-FuSa | |
| ref: v0.17.1 | |
| - name: Install tools | |
| run: sudo apt-get update -qq && sudo apt-get install -y cmake ninja-build | |
| - name: Build cpfusa | |
| run: | | |
| cmake -B cpp-FuSa/build \ | |
| -S cpp-FuSa \ | |
| -DCMAKE_BUILD_TYPE=Release \ | |
| -G Ninja | |
| cmake --build cpp-FuSa/build --parallel | |
| - name: cpfusa init | |
| working-directory: cpp-LIN | |
| run: ../cpp-FuSa/build/cpfusa init --name cpp-LIN --standard iso26262 --asil ASIL-B --project-version 0.4.0 --force || true | |
| - name: Generate SARIF report | |
| working-directory: cpp-LIN | |
| run: | | |
| ../cpp-FuSa/build/cpfusa check \ | |
| --format sarif \ | |
| --output cpfusa.sarif \ | |
| --dir . || true | |
| - name: Upload SARIF to GitHub Security tab | |
| if: hashFiles('cpp-LIN/cpfusa.sarif') != '' | |
| uses: github/codeql-action/upload-sarif@v4 | |
| with: | |
| sarif_file: cpp-LIN/cpfusa.sarif |