Skip to content

rewrite(phase2): port watchdog + build allocation fault-injection seam, ported from c-RCP #165

rewrite(phase2): port watchdog + build allocation fault-injection seam, ported from c-RCP

rewrite(phase2): port watchdog + build allocation fault-injection seam, ported from c-RCP #165

Workflow file for this run

name: CI
on:
push:
branches: [main, rewrite/v3-from-c-rcp]
pull_request:
branches: [main, rewrite/v3-from-c-rcp]
workflow_dispatch:
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
# ── Build and test (cross-platform matrix) ───────────────────────────────────
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@0b201ec74fa43914dc39ae48a89fd1d8cb592756 # 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 \
-DRELAY_BUILD_CLI=ON \
-G Ninja
- name: Build
run: cmake --build build --parallel
- name: Test
run: ctest --test-dir build --output-on-failure -j1
- name: Upload compile_commands.json (Linux clang-14 C++17 only)
if: matrix.os == 'ubuntu-22.04' && matrix.compiler == 'clang-14' && matrix.std == 17
uses: actions/upload-artifact@v4
with:
name: compile-commands
path: build/compile_commands.json
# ── rcp/l2.hpp real-socket round trip (Linux, needs CAP_NET_RAW/root) ────────
# Exercises rcp::l2::Server/Client over a real veth pair — real AF_PACKET
# sockets, a real Linux network interface pair, real Ethernet frames. This
# is deliberately separate from build-and-test above: it needs elevated
# privileges (CAP_NET_RAW, satisfied here via `sudo`) that job intentionally
# does not run with, and tests/test_l2.cpp's own header comment documents
# why its pure codec tests must not require them either.
l2-veth:
name: l2 veth round trip (rcp::l2::Server/Client, real frames)
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v4
- name: Install tools
run: |
sudo apt-get update -qq
sudo apt-get install -y cmake ninja-build
- name: Configure
run: |
cmake -B build \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_CXX_STANDARD=17 \
-G Ninja
- name: Build l2_veth_roundtrip
run: cmake --build build --target l2_veth_roundtrip --parallel
- name: Create veth pair
run: |
sudo ip link add veth0 type veth peer name veth1
sudo ip link set veth0 up
sudo ip link set veth1 up
- name: Run real Server/Client round trip over the veth pair
run: |
SERVER_MAC=$(cat /sys/class/net/veth1/address)
echo "veth1 MAC: ${SERVER_MAC}"
sudo ./build/tests/l2_veth_roundtrip veth1 veth0 "${SERVER_MAC}"
# ── RELAY conformance gate (spec §12) ────────────────────────────────────────
relay-conform:
name: relay conform (RELAY §12 conformance)
runs-on: ubuntu-22.04
steps:
- name: Checkout cpp-RCP
uses: actions/checkout@v4
# Pinned to the latest tagged RELAY release rather than floating on the
# default branch (issue #83 / cpp-RCP-15): an unpinned checkout means
# this job's pass/fail can change with no cpp-RCP-side change at all
# whenever RELAY's main branch moves, which is not reproducible CI.
# Bump this SHA/tag deliberately (in its own commit) when cpp-RCP is
# ready to validate against a newer RELAY conformance ruleset.
- name: Checkout RELAY (authoritative conformance validator)
uses: actions/checkout@v4
with:
repository: SoundMatt/RELAY
ref: 810d1877362145a407b865426bb007657e42d00f # v2.0.4
path: RELAY
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: '1.25'
- name: Build relay conformance tool
working-directory: RELAY
run: go build -o "$RUNNER_TEMP/relay" ./cmd/relay
- name: Install build tools
run: |
sudo apt-get update -qq
sudo apt-get install -y cmake ninja-build
- name: Build cpp-rcp CLI
run: |
cmake -B build -DCMAKE_BUILD_TYPE=Release -DRELAY_BUILD_CLI=ON -G Ninja
cmake --build build --target cpp-rcp --parallel
- name: Run relay conform (strict)
run: '"$RUNNER_TEMP/relay" conform --strict build/cpp-rcp'
# ── clang-tidy ───────────────────────────────────────────────────────────────
clang-tidy:
name: 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-tidy
- name: Configure
env:
CC: clang-14
CXX: clang++-14
run: |
cmake -B build \
-DCMAKE_BUILD_TYPE=Debug \
-DCMAKE_CXX_STANDARD=17 \
-DCMAKE_EXPORT_COMPILE_COMMANDS=ON \
-G Ninja
- name: Build (generate compile_commands.json)
run: cmake --build build --parallel
- name: Run clang-tidy
# Only pass tests/*.cpp as translation units: this project's
# include/rcp/*.hpp headers are header-only (no standalone compile
# command in compile_commands.json for a .hpp on its own), so
# invoking clang-tidy directly on a header fails with a
# clang-diagnostic-error "file not found" on its own #includes
# (no -I flags to resolve them) rather than a real lint finding.
# .clang-tidy's HeaderFilterRegex ('include/rcp/') still surfaces
# findings from those headers as they're transitively included by
# the .cpp translation units below.
run: |
find tests -name '*.cpp' | \
xargs clang-tidy -p build --warnings-as-errors='*'
# ── ASan/UBSan (regmap + faultinject) ─────────────────────────────────────────
# Scoped to the two suites whose headers hand out raw references into
# containers they also mutate:
#
# - rcp/regmap.hpp's Ep0 does raw std::vector<T> indexing across
# per-endpoint access-control checks and the config vectors those checks
# gate (cpp-RCP-N2-01/N2-02, issues #64/#65).
# - rcp/faultinject.hpp's Interceptor picks a rule out of a std::vector it
# erases from in the same pass (cpp-RCP-D6). That defect was live and
# the *existing* test_faultinject cases already reproduced it under
# ASan — this job simply wasn't running them, which is why CI stayed
# green. Keeping it in scope makes that class of defect gated rather
# than fixed once.
#
# This is additive to build-and-test above, not a replacement for it.
asan-ubsan-regmap:
name: ASan/UBSan (regmap + faultinject)
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: Configure (ASan + UBSan)
env:
CC: clang-14
CXX: clang++-14
run: |
cmake -B build-asan \
-DCMAKE_BUILD_TYPE=Debug \
-DCMAKE_CXX_STANDARD=17 \
-DCMAKE_CXX_FLAGS="-fsanitize=address,undefined -fno-omit-frame-pointer -g" \
-DCMAKE_EXE_LINKER_FLAGS="-fsanitize=address,undefined" \
-G Ninja
- name: Build test_regmap and test_faultinject
run: cmake --build build-asan --target test_regmap test_faultinject --parallel
- name: Run test_regmap under ASan/UBSan
env:
ASAN_OPTIONS: halt_on_error=1
UBSAN_OPTIONS: halt_on_error=1,print_stacktrace=1
run: ./build-asan/tests/test_regmap
- name: Run test_faultinject under ASan/UBSan
env:
ASAN_OPTIONS: halt_on_error=1
UBSAN_OPTIONS: halt_on_error=1,print_stacktrace=1
run: ./build-asan/tests/test_faultinject
# ── LCOV coverage ─────────────────────────────────────────────────────────────
coverage:
name: Coverage (LCOV)
runs-on: ubuntu-22.04
needs: build-and-test
outputs:
coverage-info: coverage.info
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)
env:
CC: gcc-12
CXX: g++-12
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: |
# --rc lcov_branch_coverage=1 must be passed to BOTH the --capture
# and --remove invocations below (lcov re-reads/rewrites the .info
# file's branch records on every invocation, so omitting it on
# either step silently drops branch data even if the other step
# collected it). Without it, totalBranches/hitBranches downstream
# in coverage-report.json are always 0/0 -- a vacuous "nothing
# failed" rather than a genuine 100%-branch-coverage result against
# this project's declared DAL-B target (see issues #67/#68).
lcov --capture \
--directory build-cov \
--gcov-tool gcov-12 \
--rc lcov_branch_coverage=1 \
--output-file coverage.info
# /usr/include/* excludes STL/toolchain system headers, which
# otherwise dominate the file-entry count (confirmed: 101/153 file
# entries and ~5979/9034 lines were system headers, dragging
# blended line coverage down to ~68.8% vs. ~91.5% for actual
# project files alone -- see issue #67/#68).
lcov --remove coverage.info \
'*/tests/*' '*/catch2/*' '*/FetchContent/*' '*/cmake-build*/*' \
'/usr/include/*' \
--rc lcov_branch_coverage=1 \
--output-file coverage.info
- name: Upload coverage report
uses: actions/upload-artifact@v4
with:
name: coverage-info
path: coverage.info
# ── Build cpp-FuSa binary (shared by all cpfusa-* jobs) ──────────────────────
cpfusa-build:
name: Build cpp-FuSa
runs-on: ubuntu-22.04
steps:
- name: Install tools
run: sudo apt-get update -qq && sudo apt-get install -y cmake ninja-build
- name: Build cpp-FuSa from source
run: |
git clone --depth=1 --branch v0.18.0 https://github.com/SoundMatt/cpp-FuSa /tmp/cpfusa
cmake -B /tmp/cpfusa/build -S /tmp/cpfusa \
-DCMAKE_BUILD_TYPE=Release -G Ninja
cmake --build /tmp/cpfusa/build --parallel
- name: Upload cpfusa binary
uses: actions/upload-artifact@v4
with:
name: cpfusa-binary
path: /tmp/cpfusa/build/cpfusa
# ── Helper: download cpfusa binary ───────────────────────────────────────────
# (Not a real job — these steps are inlined in each cpfusa-* job below.)
# ── cpp-FuSa: built-in safety check (FUSA001–005) ────────────────────────────
cpfusa-check:
name: cpfusa check --strict
runs-on: ubuntu-22.04
needs: [build-and-test, cpfusa-build]
steps:
- uses: actions/checkout@v4
- uses: actions/download-artifact@v4
with:
name: cpfusa-binary
path: /tmp/cpfusa/build
- run: chmod +x /tmp/cpfusa/build/cpfusa
- name: Safety check (0 errors required)
run: /tmp/cpfusa/build/cpfusa check
- name: Verify required safety artifacts are present
run: |
missing=0
for f in \
.fusa.json \
.fusa-reqs.json \
.fusa-hara.json \
.fusa-problems.json \
.fusa-iec62443.json \
SECURITY.md \
INCIDENT-RESPONSE.md \
SAFETY_PLAN.md \
.github/CODEOWNERS; do
if [ ! -f "$f" ]; then
echo "::error::Required safety artifact missing: $f"
missing=$((missing + 1))
fi
done
[ "$missing" -eq 0 ] || exit 1
# ── cpp-FuSa: MISRA/AUTOSAR/JSF++ lint (LINT001–010) ─────────────────────────
cpfusa-lint:
name: cpfusa lint (MISRA/AUTOSAR)
runs-on: ubuntu-22.04
needs: [build-and-test, cpfusa-build]
steps:
- uses: actions/checkout@v4
- uses: actions/download-artifact@v4
with:
name: cpfusa-binary
path: /tmp/cpfusa/build
- run: chmod +x /tmp/cpfusa/build/cpfusa
- name: MISRA/AUTOSAR/JSF++ lint
run: /tmp/cpfusa/build/cpfusa lint
# ── cpp-FuSa: static analysis (clang-tidy + cppcheck + own passes) ───────────
cpfusa-analyze:
name: cpfusa analyze
runs-on: ubuntu-22.04
needs: [build-and-test, cpfusa-build]
steps:
- uses: actions/checkout@v4
- name: Install analysis tools
run: |
sudo apt-get update -qq
sudo apt-get install -y cmake ninja-build clang-tidy cppcheck
- uses: actions/download-artifact@v4
with:
name: cpfusa-binary
path: /tmp/cpfusa/build
- run: chmod +x /tmp/cpfusa/build/cpfusa
- name: Configure (generate compile_commands.json)
env:
CC: clang-14
CXX: clang++-14
run: |
cmake -B build \
-DCMAKE_BUILD_TYPE=Debug \
-DCMAKE_CXX_STANDARD=17 \
-DCMAKE_EXPORT_COMPILE_COMMANDS=ON \
-G Ninja
- name: Build
run: cmake --build build --parallel
- name: Static analysis
run: /tmp/cpfusa/build/cpfusa analyze || true
# ── cpp-FuSa: 20 CWE-mapped cybersecurity rules (ISO 21434) ─────────────────
cpfusa-cyber:
name: cpfusa cyber --strict
runs-on: ubuntu-22.04
needs: [build-and-test, cpfusa-build]
steps:
- uses: actions/checkout@v4
- uses: actions/download-artifact@v4
with:
name: cpfusa-binary
path: /tmp/cpfusa/build
- run: chmod +x /tmp/cpfusa/build/cpfusa
- name: Cybersecurity rules (ISO 21434)
run: /tmp/cpfusa/build/cpfusa cyber
# ── cpp-FuSa: requirements traceability (≥80% coverage gate) ─────────────────
cpfusa-trace:
name: cpfusa trace (100% coverage)
runs-on: ubuntu-22.04
needs: [build-and-test, cpfusa-build]
steps:
- uses: actions/checkout@v4
- uses: actions/download-artifact@v4
with:
name: cpfusa-binary
path: /tmp/cpfusa/build
- run: chmod +x /tmp/cpfusa/build/cpfusa
- name: Traceability check (all requirements traced)
run: /tmp/cpfusa/build/cpfusa trace
# Informational only, not a gate: this dumps the gap detail for human
# review. The actual gate for this job is the "Enforce 100% requirement
# coverage" step below, which runs without `|| true` and fails the job
# for real.
- name: Traceability gaps report (informational, non-gating)
run: /tmp/cpfusa/build/cpfusa trace --gaps || true
# RELAY §20.1: every requirement MUST be traced (100%). NOTE: despite
# this job's own prior "traced AND tested" comment, --req-coverage
# only gates cpfusa's annotation_coverage (traced) metric -- confirmed
# by direct read of cpp-FuSa v0.18.0's trace.cpp, which wires
# --req-coverage to annotation_coverage alone, never to `tested`. The
# "AND tested" half was previously unenforced (this repo's own
# `tested`/`total` has happened to be 100% regardless, so nothing was
# ever observed to regress, but nothing was actually checking it).
- name: Enforce 100% requirement coverage (traced)
run: /tmp/cpfusa/build/cpfusa trace --req-coverage 100
# RELAY §20.1's other half: every requirement MUST also be tested
# (100%). cpp-FuSa v0.18.0 has no CLI flag for this metric alone (its
# --sec-tested flag is a differently-named, differently-scoped gate --
# see the next step's own header -- and is NOT a substitute here: it
# currently has a bug, filed as SoundMatt/cpp-FuSa#64, that makes it
# silently check general test coverage instead of security-test
# coverage, so relying on it for "tested" would work today by
# coincidence and break the moment that bug is fixed). `cpfusa trace
# --format json`'s `coverage` object exposes `testedRequirements`/
# `totalRequirements` directly and correctly -- parsed here via jq,
# matching the same JSON-over-text-scraping choice SoundMatt/c-RCP#577
# made for the identical problem in that sibling repo's own CI.
- name: Enforce 100% requirement coverage (tested)
run: |
cov="$(/tmp/cpfusa/build/cpfusa trace --format json | jq '.coverage')"
echo "$cov"
total=$(echo "$cov" | jq '.totalRequirements')
tested=$(echo "$cov" | jq '.testedRequirements')
if [ "$tested" != "$total" ]; then
echo "::error::'tested' metric gate failed: tested=$tested total=$total (must be equal for a 100% gate)"
exit 1
fi
echo "OK: $tested/$total tested"
# Genuine security-test coverage: CYBERSECURITY.md's own six security
# layers cite exactly 64 requirement ids as security-relevant
# (REQ-TLS-001..010, REQ-AUTH-001..008, REQ-E2E-001..014,
# REQ-RL-001..008, REQ-DISC-001..009, REQ-REGMAP-001..015), each now
# marked `"severity": "cybersecurity"` in `.fusa-reqs.json` -- the
# field cpp-FuSa's own trace.cpp already reads to compute
# `secTestedRequirements` (a test annotation on a cybersecurity-marked
# requirement), previously never populated so this metric was always
# zero. Deliberately NOT using cpfusa's own `--sec-tested` CLI flag:
# confirmed by direct read of v0.18.0's trace.cpp/cli.cpp that it
# gates on the wrong variable (general test_coverage, not
# sec_tested/total) -- the exact same bug class SoundMatt/c-FuSa#147
# fixed in c-FuSa, filed upstream as SoundMatt/cpp-FuSa#64, not yet
# fixed. Parses `--format json`'s already-correct
# `secTestedRequirements` field directly instead, at a 17% floor
# (64/376 = 17.02%) -- NOT a claim that only 17% of this codebase is
# security-relevant, a regression floor against the exact,
# CYBERSECURITY.md-enumerated subset. Once cpp-FuSa#64 is fixed
# upstream, this step can switch to `cpfusa trace --sec-tested 17`
# directly.
- name: Enforce security-test coverage (17% floor, real CYBERSECURITY.md subset)
run: |
cov="$(/tmp/cpfusa/build/cpfusa trace --format json | jq '.coverage')"
echo "$cov"
total=$(echo "$cov" | jq '.totalRequirements')
sec=$(echo "$cov" | jq '.secTestedRequirements')
pct=$(( sec * 100 / total ))
if [ "$pct" -lt 17 ]; then
echo "::error::sec-tested gate failed: ${pct}% ($sec/$total) < required 17%"
exit 1
fi
echo "OK: ${pct}% ($sec/$total) security-tested"
# ── cpp-FuSa: ctest integration → .fusa-evidence.json ────────────────────────
cpfusa-verify:
name: cpfusa verify (ctest evidence)
runs-on: ubuntu-22.04
needs: [build-and-test, cpfusa-build]
steps:
- uses: actions/checkout@v4
- name: Install tools
run: sudo apt-get update -qq && sudo apt-get install -y cmake ninja-build
- uses: actions/download-artifact@v4
with:
name: cpfusa-binary
path: /tmp/cpfusa/build
- run: chmod +x /tmp/cpfusa/build/cpfusa
- name: Configure
env:
CC: gcc-12
CXX: g++-12
run: |
cmake -B build \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_CXX_STANDARD=17 \
-G Ninja
- name: Build
run: cmake --build build --parallel
- name: Run tests + generate evidence
run: /tmp/cpfusa/build/cpfusa verify
- name: Upload test evidence
uses: actions/upload-artifact@v4
with:
name: fusa-evidence
path: .fusa-evidence.json
# ── cpp-FuSa: tool qualification suite ───────────────────────────────────────
cpfusa-qualify:
name: cpfusa qualify
runs-on: ubuntu-22.04
needs: cpfusa-build
steps:
- uses: actions/checkout@v4
- uses: actions/download-artifact@v4
with:
name: cpfusa-binary
path: /tmp/cpfusa/build
- run: chmod +x /tmp/cpfusa/build/cpfusa
- name: Tool qualification (8 positive/negative test cases)
run: /tmp/cpfusa/build/cpfusa qualify
- name: Upload qualification report
uses: actions/upload-artifact@v4
with:
name: qualify-report
path: qualify-report.json
# ── cpp-FuSa: CMake dependency CVE scan ───────────────────────────────────────
cpfusa-vuln:
name: cpfusa vuln (CVE scan)
runs-on: ubuntu-22.04
needs: cpfusa-build
steps:
- uses: actions/checkout@v4
- uses: actions/download-artifact@v4
with:
name: cpfusa-binary
path: /tmp/cpfusa/build
- run: chmod +x /tmp/cpfusa/build/cpfusa
- name: Dependency vulnerability scan
run: /tmp/cpfusa/build/cpfusa vuln
- name: Upload vulnerability report
uses: actions/upload-artifact@v4
with:
name: vuln-report
path: vuln.json
# ── cpp-FuSa: impact analysis on PRs ─────────────────────────────────────────
cpfusa-impact:
name: cpfusa impact (change analysis)
runs-on: ubuntu-22.04
if: github.event_name == 'pull_request'
needs: [cpfusa-build, cpfusa-trace]
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: actions/download-artifact@v4
with:
name: cpfusa-binary
path: /tmp/cpfusa/build
- run: chmod +x /tmp/cpfusa/build/cpfusa
- name: Change impact analysis
run: /tmp/cpfusa/build/cpfusa impact
# ── cpp-FuSa: full compliance report (aggregated) ────────────────────────────
cpfusa-report:
name: cpfusa report (compliance)
runs-on: ubuntu-22.04
needs:
- build-and-test
- coverage
- cpfusa-check
- cpfusa-lint
- cpfusa-analyze
- cpfusa-cyber
- cpfusa-trace
- cpfusa-verify
steps:
- uses: actions/checkout@v4
- uses: actions/download-artifact@v4
with:
name: cpfusa-binary
path: /tmp/cpfusa/build
- run: chmod +x /tmp/cpfusa/build/cpfusa
- name: Download coverage info
uses: actions/download-artifact@v4
with:
name: coverage-info
path: .
# Artifact generation only, not a gate: this project's stub bridge
# implementations cannot reach the 100%-branch DAL-B threshold this
# command grades against, so `cpfusa coverage` exits non-zero by
# design today (same reasoning as release.yml's gap-report steps).
# `|| true` is intentional here so the job can still produce and
# upload coverage-report.json for inspection; it does not mean "DAL-B
# coverage is enforced". The step immediately below is what actually
# gates on this job's data (issues #67/#68).
- name: LCOV structural coverage report (DO-178C, artifact only — non-gating)
run: /tmp/cpfusa/build/cpfusa coverage --profile coverage.info --dal DAL-B --output coverage-report.json || true
# Real gate (issues #67/#68): a coverage report with totalBranches==0
# is not "100% branch coverage met", it's "no branch data was
# collected at all" -- a 0/0 ratio that would otherwise vacuously
# satisfy any branch-coverage threshold check. Fail hard if that
# happens, independent of whether the DAL-B step above is gating.
- name: Assert branch coverage data is non-vacuous
run: |
total_branches=$(jq '.summary.totalBranches' coverage-report.json)
echo "coverage-report.json summary.totalBranches=${total_branches}"
if [ "$total_branches" -le 0 ]; then
echo "::error::coverage-report.json reports totalBranches=${total_branches} — no branch coverage data was collected, so any branch-coverage threshold is being met vacuously (0/0), not genuinely. See issues #67/#68."
exit 1
fi
# Artifact generation only, not a gate: these convert the aggregated
# findings (already gated individually by the cpfusa-check / -lint /
# -cyber / -trace / -verify jobs this job `needs:`) into downloadable
# JSON/SARIF report formats. `|| true` reflects that intent, not a
# bug — there is nothing pass/fail-shaped about a format conversion.
- name: Full compliance report (JSON, artifact only — non-gating)
run: /tmp/cpfusa/build/cpfusa report --format json --output report.json || true
- name: Full compliance report (SARIF, artifact only — non-gating)
run: /tmp/cpfusa/build/cpfusa report --format sarif --output results.sarif || true
# Artifact generation only, not a gate: records a point-in-time safety
# metrics snapshot for trend tracking. There is no pass/fail threshold
# associated with recording a snapshot.
- name: Safety metrics snapshot (artifact only — non-gating)
run: /tmp/cpfusa/build/cpfusa metrics record || true
- name: Upload compliance report
uses: actions/upload-artifact@v4
with:
name: compliance-report
path: |
report.json
results.sarif
coverage-report.json