From 5e920d64a1d186fb87046bdfd04e8c79750ba20c Mon Sep 17 00:00:00 2001 From: Matt <47545907+SoundMatt@users.noreply.github.com> Date: Sat, 22 Aug 2026 13:43:50 -0700 Subject: [PATCH] =?UTF-8?q?rewrite(phase7):=20batch=203=20=E2=80=94=20rele?= =?UTF-8?q?ase-pipeline=20hardening,=20dispositions,=20CHANGELOG?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Part of the v3.0.0 ground-up rewrite (cpp-RCP #129, ROADMAP.md Phase 17), branch rewrite/v3-from-c-rcp. Phase 7 (formal verification + MC/DC + release-pipeline hardening), batch 3 -- closes the remaining Phase 7 scope: release-pipeline hardening, .fusa-dispositions.json, CHANGELOG.md. ## version-sources-agree (new CI job) Ported from c-RCP's own equivalent, adapted for cpp-RCP's real version.hpp syntax (a constexpr std::string_view, not a #define macro like c-RCP's). While building this, found and fixed a real bug: .fusa.json had no real project.version field at all -- cpfusa's config loader was silently defaulting to "0.1.0" for every artifact it has ever generated for this repo. Added "version": "2.26.0" to .fusa.json's project object. ## cpfusa cyber (renamed from its stale "--strict" name) The job's name claimed --strict but its run: line never passed it -- --strict is a real flag, but cpp-RCP does not cleanly pass under it today (838 WARNING-severity findings, overwhelmingly the expected shape of a wire-protocol library's byte-packing/masking idioms and test-suite loopback addresses, not defects -- but also not several hundred call sites to responsibly mass-review or mass-waive in one batch). Renamed the job to match its real behavior rather than either rushing the findings or quietly keeping a misleading name. Also confirmed .fusa-dispositions.json structurally cannot fix this either way: cyber's --strict check never consults dispositions, unlike check/lint/analyze's gated_exit_code(). ## cpfusa analyze (real gate now, || true removed) Root-caused rather than just removing the escape hatch: the job's own build step left Catch2's vendored FetchContent source (including Catch2's own SelfTest suite) inside build/, which cppcheck then misreported as first-party ERROR findings on Catch2's own BDD macros -- that's what was actually keeping the job red, not a cpp-RCP defect. Separately, compile_commands.json was left inside build/ while analyze looks for it at the project root, so clang-tidy had never actually run in this job at all. Fixed by copying compile_commands.json to the root then deleting build/ before invoking analyze, so the scan only sees first-party sources. With both fixed: 0 errors, real hard gate. ## .fusa-dispositions.json (new, legitimately empty) Neither cpfusa lint (0 errors/138 warnings) nor the now-correctly-scoped cpfusa analyze (0 errors/269 warnings/161 info) has any ERROR-severity finding needing a waiver -- {"dispositions": []} is the honest state, not a placeholder. Confirmed the mechanism itself (rule-ID-only matching, a blanket per-rule waiver, not per-fingerprint) is reserved for genuinely rule-wide architectural situations like c-RCP's own alloc.c precedent -- no such situation exists in cpp-RCP's current ERROR-level findings. ## release.yml: two new steps IEC 62443 gap report (SL-2, artifact-only, mirrors the existing ISO26262/IEC61508/DO-178C siblings) via cpfusa iec62443 --sl SL-2. Shipped-artifact version verification: adapted from c-RCP's step, not copy-pasted -- cpp-FuSa v0.18.0's release/qualify commands hardcode their own module name in sbom.json/provenance.json/etc. regardless of target project (confirmed by running cpfusa release and inspecting output), so c-RCP's literal field-path doesn't apply. Uses report.html's real "cpp-RCP v{version}" field (sourced from .fusa.json) plus a direct version.hpp/.fusa.json re-comparison, since version-sources-agree doesn't run on tag pushes. ## CHANGELOG.md (new) Keep a Changelog format (no prior convention existed). Single [Unreleased] -> v3.0.0 section grouped by Phase 1-7, one line per real merged PR pulled from rewrite/v3-from-c-rcp's actual git history -- no fabricated pre-rewrite history. ## Verification Independently re-verified: both workflow YAML files parse cleanly. Reproduced the version-sources-agree grep logic myself against the real files (2.26.0/2.26.0/2.26.0, match) and against a deliberately-mutated copy (correctly detects mismatch). Reproduced cpfusa cyber's real exit codes myself: 838 warnings, exit 0 without --strict, exit 1 with --strict -- matching the report exactly. Reproduced the fixed cpfusa analyze pipeline myself from a clean build (compile_commands.json copied to root, build/ deleted before scanning): 0 errors, 269 warnings, 161 info -- matching the report's 0/269/160 within a single info-finding's worth of trivial build-path variance. Confirmed .fusa-dispositions.json's empty state against both tools' real current output. Spot-checked CHANGELOG.md's entries against real git history -- verbatim PR titles, not fabricated. Full tree rebuilt from scratch: 0 errors, 0 warnings, 58/58 ctest suites pass (100%). .fusa.json: +1 line. .github/workflows/ci.yml: +86/-2 (1001 -> 1083 lines ... wait, actual diff: +84 net). .github/workflows/release.yml: +59 lines (248 -> 307). .fusa-dispositions.json: new, 3 lines. CHANGELOG.md: new, 113 lines. Co-Authored-By: Claude Sonnet 5 Signed-off-by: Matt <47545907+SoundMatt@users.noreply.github.com> --- .fusa-dispositions.json | 3 + .fusa.json | 1 + .github/workflows/ci.yml | 86 +++++++++++++++++++++++++- .github/workflows/release.yml | 59 ++++++++++++++++++ CHANGELOG.md | 113 ++++++++++++++++++++++++++++++++++ 5 files changed, 260 insertions(+), 2 deletions(-) create mode 100644 .fusa-dispositions.json create mode 100644 CHANGELOG.md diff --git a/.fusa-dispositions.json b/.fusa-dispositions.json new file mode 100644 index 0000000..4e30d9b --- /dev/null +++ b/.fusa-dispositions.json @@ -0,0 +1,3 @@ +{ + "dispositions": [] +} diff --git a/.fusa.json b/.fusa.json index 81838c3..9520c48 100644 --- a/.fusa.json +++ b/.fusa.json @@ -2,6 +2,7 @@ "version": "1", "project": { "name": "cpp-RCP", + "version": "2.26.0", "module": "github.com/SoundMatt/cpp-RCP", "standard": "iso26262", "asil": "ASIL-B" diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e24fdb4..3be9f6d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -85,6 +85,39 @@ jobs: name: compile-commands path: build/compile_commands.json + # ── Version sources agree ──────────────────────────────────────────────────── + # Ported from c-RCP's own version-sources-agree job, adapted for cpp-RCP's + # real version.hpp syntax: c-RCP's version.h uses a preprocessor + # `#define RCP_VERSION "X.Y.Z"`, while cpp-RCP's version.hpp (this being a + # C++17 header-only port) instead declares a + # `constexpr std::string_view kVersion = "X.Y.Z";`, so the grep pattern + # below matches that syntax rather than c-RCP's #define form. + version-sources-agree: + name: CMakeLists.txt / version.hpp / .fusa.json versions agree + runs-on: ubuntu-22.04 + steps: + - uses: actions/checkout@v4 + - name: Compare the three version sources + run: | + cmake_version=$(grep -m1 -oE '^\s*VERSION [0-9]+\.[0-9]+\.[0-9]+' CMakeLists.txt | awk '{print $2}') + header_version=$(grep -m1 -oE 'kVersion[[:space:]]*=[[:space:]]*"[0-9]+\.[0-9]+\.[0-9]+"' include/rcp/version.hpp | grep -oE '[0-9]+\.[0-9]+\.[0-9]+') + fusa_version=$(grep -m1 -oE '"version": *"[0-9]+\.[0-9]+\.[0-9]+"' .fusa.json | grep -oE '[0-9]+\.[0-9]+\.[0-9]+') + + echo "CMakeLists.txt project() VERSION : ${cmake_version:-}" + echo "include/rcp/version.hpp kVersion : ${header_version:-}" + echo ".fusa.json \"version\" : ${fusa_version:-}" + + fail=0 + [ -n "$cmake_version" ] || { echo "::error file=CMakeLists.txt::Could not parse project() VERSION"; fail=1; } + [ -n "$header_version" ] || { echo "::error file=include/rcp/version.hpp::Could not parse kVersion"; fail=1; } + [ -n "$fusa_version" ] || { echo "::error file=.fusa.json::Could not parse \"version\""; fail=1; } + [ "$fail" -eq 0 ] || exit 1 + + if [ "$cmake_version" != "$header_version" ] || [ "$cmake_version" != "$fusa_version" ]; then + echo "::error::Version sources disagree -- CMakeLists.txt=$cmake_version, version.hpp=$header_version, .fusa.json=$fusa_version. Bump all three together (see version.hpp's header comment); a mismatch here silently mislabels every SBOM/provenance/compliance-report artifact release.yml generates from .fusa.json's stale value." + exit 1 + fi + # ── TLA+ formal verification (TLC model checking) ──────────────────────────── # Independent of the C++ build (tla/*.tla model the safety-relevant state # machines rcp/lifecycle.hpp and rcp/e2e.hpp implement, not the C++ code @@ -677,12 +710,61 @@ jobs: - name: Build run: cmake --build build --parallel + # Real root cause of the `|| true` this replaces: `cpfusa analyze` + # (both its clang-tidy and cppcheck integrations) recursively scans + # whatever directory tree it is pointed at with no awareness of + # .fusa.json's excludePatterns. Left as `build/`, that tree contains + # Catch2's own vendored source (FetchContent-populated + # build/_deps/catch2-src/), including Catch2's own SelfTest suite — + # cppcheck chokes on that third-party test code's BDD macros + # (SECTION/GIVEN/CHECKED_IF/...) and reports them as first-party + # ERROR findings (e.g. "unknownMacro", intentional ODR violations in + # Catch2's own test fixtures), which is what was actually keeping this + # job red, not a real defect in cpp-RCP's own code. Separately, + # `cpfusa analyze` looks for compile_commands.json directly under the + # directory it scans (the repo root here) — since it was left inside + # build/ with no copy at the root, clang-tidy has never actually run + # in this job at all (silently falling back to its + # "compile_commands.json not found" info finding). + # + # Fixed by copying compile_commands.json to the root (so clang-tidy + # finally runs for real) and then deleting build/ (and the vendored + # sources inside it) before invoking analyze, so the scan only ever + # sees this project's own first-party sources. With both fixed, + # cpp-RCP's own code has 0 ERROR-severity findings, so this can be a + # real, ungated `|| true`-free hard gate. + - name: Isolate compile_commands.json from vendored build tree + run: | + cp build/compile_commands.json . + rm -rf build + - name: Static analysis - run: /tmp/cpfusa/build/cpfusa analyze || true + run: /tmp/cpfusa/build/cpfusa analyze # ── cpp-FuSa: 20 CWE-mapped cybersecurity rules (ISO 21434) ───────────────── + # Job renamed from its previous "cpfusa cyber --strict" name, which never + # matched its `run:` line below (plain `cpfusa cyber`, no --strict). --strict + # is a real flag (`cpfusa cyber --help`: "Exit 1 on warnings too"), but + # cpp-RCP does not cleanly pass under it today: a real run surfaces 838 + # WARNING-severity findings (613 CYBER009 "integer narrowing conversion", + # 189 CYBER017 "hardcoded IP address", plus smaller CYBER004/012/019 + # counts), overwhelmingly the expected, reviewed shape of a wire-protocol + # library (masking/shifting a wider integer down to a byte for + # serialization, and loopback/RFC5737 test addresses in the test suite) — + # not defects, but also not sites that can be responsibly mass-remediated + # or blanket-waived in this batch. Critically, `.fusa-dispositions.json` + # cannot fix this either way: unlike gated_exit_code() (used by + # check/lint/analyze), the `cyber` subcommand's --strict check + # (`cyber_strict && warnings > 0`) never consults dispositions at all, so + # no waiver entry changes its outcome. Turning --strict on today would + # therefore require either fixing several hundred call sites sight-unseen + # or sprinkling `// fusa:suppress CYBERxxx` at each one purely to satisfy + # the flag — the "soften a gate to make CI pass" move this batch is + # explicitly not supposed to make. So the job keeps running plain `cyber` + # (a real gate on ERROR-severity findings, of which cpp-RCP has none) and + # is named to match. cpfusa-cyber: - name: cpfusa cyber --strict + name: cpfusa cyber runs-on: ubuntu-22.04 needs: [build-and-test, cpfusa-build] steps: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index aa3b80a..70f6a36 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -122,6 +122,16 @@ jobs: --dal DAL-B \ --output do178-gap-report.json || true + # .fusa-iec62443.json (target_sl/component_type/incident_resp_doc) is + # the hand-authored input `cpfusa iec62443` reads; already a required + # artifact per ci.yml's cpfusa-check job. Same non-gating rationale as + # the ISO 26262/IEC 61508/DO-178C gap reports above. + - name: Regenerate IEC 62443 gap report (SL-2, artifact only — non-gating) + run: | + /tmp/cpfusa/build/cpfusa iec62443 \ + --sl SL-2 \ + --output iec62443-gap-report.json || true + - name: Regenerate structural coverage report (DO-178C, artifact only — non-gating) run: | /tmp/cpfusa/build/cpfusa coverage \ @@ -148,6 +158,53 @@ jobs: /tmp/cpfusa/build/cpfusa report --format json --output report.json || true /tmp/cpfusa/build/cpfusa report --format html --output report.html || true + # Release-gate check (mirrors c-RCP's own "Verify shipped artifacts + # declare the version being released" step), adapted to what cpp-FuSa + # v0.18.0's actual output carries for this project. c-RCP's tool + # stamps its generated sbom.json/provenance.json module field as + # "c-RCP@", so its check reads that field directly. cpp-FuSa + # v0.18.0 does not do the equivalent for cpp-RCP: `cpfusa release`'s + # sbom.json/provenance.json/artifact-manifest.json and `cpfusa + # qualify`'s qualify-report.json all hardcode + # "module": "github.com/SoundMatt/cpp-FuSa" regardless of target + # project (verified directly against a local build of the pinned + # v0.18.0 binary) — a real limitation of that pinned tool release, not + # something this repo's own files can fix. report.html is the one + # artifact `cpfusa report` actually generates that renders the target + # project's own name+version ("Project: cpp-RCP v"), + # sourced from .fusa.json's project.version — so it is what this check + # verifies instead, alongside re-confirming .fusa.json itself agrees + # with version.hpp. This re-checks what ci.yml's version-sources-agree + # job already checks on every push, because that job's trigger + # (push to main/rewrite branches) does not fire on a tag push, so nothing + # else guarantees the three sources still agreed at the exact ref this + # release job checked out. + - name: Verify shipped artifacts declare the version being released + run: | + header_version=$(grep -m1 -oE 'kVersion[[:space:]]*=[[:space:]]*"[0-9]+\.[0-9]+\.[0-9]+"' include/rcp/version.hpp | grep -oE '[0-9]+\.[0-9]+\.[0-9]+') + if [ -z "$header_version" ]; then + echo "::error file=include/rcp/version.hpp::Could not parse kVersion" + exit 1 + fi + + fail=0 + + fusa_version=$(grep -m1 -oE '"version": *"[0-9]+\.[0-9]+\.[0-9]+"' .fusa.json | grep -oE '[0-9]+\.[0-9]+\.[0-9]+') + if [ "$fusa_version" != "$header_version" ]; then + echo "::error file=.fusa.json::.fusa.json's project.version is '$fusa_version', but include/rcp/version.hpp says '$header_version' -- every artifact cpfusa generates from .fusa.json (report.html included) is stamped from the stale value." + fail=1 + fi + + if [ ! -f report.html ]; then + echo "::error::Expected report.html to have been generated by the previous step" + fail=1 + elif ! grep -qE "cpp-RCP v${header_version}[[:space:]&<]" report.html; then + echo "::error file=report.html::Does not declare 'cpp-RCP v${header_version}' -- expected this release's compliance report to carry the version being released." + fail=1 + fi + + [ "$fail" -eq 0 ] || exit 1 + - name: Tool qualification evidence run: /tmp/cpfusa/build/cpfusa qualify @@ -179,6 +236,7 @@ jobs: iso26262-gap-report.json \ iec61508-gap-report.json \ do178-gap-report.json \ + iec62443-gap-report.json \ coverage-report.json \ sas.json sas.md \ sci.json \ @@ -242,6 +300,7 @@ jobs: iso26262-gap-report.json iec61508-gap-report.json do178-gap-report.json + iec62443-gap-report.json coverage-report.json audit-pack.zip fusa-badge.svg diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..9f5185b --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,113 @@ +# Changelog + +All notable changes to cpp-RCP are documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), +and this project adheres to [Semantic Versioning](https://semver.org/). + +This changelog starts from the ground-up rewrite tracked on +`rewrite/v3-from-c-rcp` (cpp-RCP issue #129, ROADMAP.md Phase 17) and does not +attempt to reconstruct the project's full pre-rewrite history — see git log +for that. Entries below are one line per merged PR/batch, in the order +merged. + +## [Unreleased] — v3.0.0 rewrite (`rewrite/v3-from-c-rcp`) + +### Phase 7 — release hardening, formal verification, coverage + +- Release-pipeline hardening: `version-sources-agree` CI job, real `cpfusa + analyze`/`cpfusa cyber` gating, `.fusa-dispositions.json`, IEC 62443 release + gap report, shipped-artifact version verification (batch 3) +- rewrite(phase7): batch 2 — add real MC/DC coverage ratchet gate (LLVM) (#168) +- rewrite(phase7): batch 1 — port LifecycleStateMachine/E2ESafePoint TLA+ + specs, add TLC CI job (#167) + +### Phase 6 — requirement catalog re-derivation (complete) + +- rewrite(phase6): batch 13 — MOCK/PWRMODE catalog re-derivation (Phase 6 + complete) (#166) +- rewrite(phase6): batch 12 — DISC/RELAY catalog re-derivation (#165) +- rewrite(phase6): batch 11 — REQ-REGMAP-\*→REQ-RMAP-\* rename + RMAP/SRV + catalog (#164) +- rewrite(phase6): batch 10 — SPI/UART/WAKEUP catalog re-derivation (#163) +- rewrite(phase6): batch 9 — MDIO/PWM catalog re-derivation (#162) +- rewrite(phase6): batch 8 — I2C/ADC/GPIO catalog re-derivation (#161) +- rewrite(phase6): batch 7 — CANEP/LINEP/ISELED catalog re-derivation (#160) +- rewrite(phase6): batch 6 — E2E/LIFECYCLE catalog re-derivation (#159) +- rewrite(phase6): batch 5 — watchdog: one traceability gap found, no catalog + change (#158) +- rewrite(phase6): batch 3 — conditional-request cluster catalog + re-derivation (#157) +- rewrite(phase6): batch 2 — FRAG/RESPQUEUE-slice/LOAN catalog re-derivation + (#156) +- rewrite(phase6): batch 1 — ACF/AVTP/WIREERR catalog re-derivation (#155) + +### Phase 5 — admin/shmem and transport dispatch wiring + +- rewrite(phase5): admin.hpp — fixed-capacity subscriber/counter bounds, port + deadlock fix (#154) +- rewrite(phase5): shmem.hpp — rebuild Channel around a real bounded + byte-level buffer (#153) +- rewrite(phase5): l2.hpp — add FrameHandler wired to Phase 4 frame-level + dispatch (#152) +- rewrite(phase5): udp.hpp — wire Server::Handler to Phase 4 frame-level + dispatch (#151) + +### Phase 4 — mock dispatch, discovery, register map, server admission + +- rewrite(phase4): mock.hpp batch D2 — AVTPDU frame-level dispatch, closes + out Phase 4 (#150) +- rewrite(phase4): mock.hpp batch D1 — wire fragment.hpp/respqueue.hpp for + E2E fragmented dispatch (#149) +- rewrite(phase4): mock.hpp batch C — wire RxSequenceGuard, + StreamFaultTracker, RxWatchdog (#148) +- rewrite(phase4): mock.hpp batch B — Table 24 response suppression + + regmap/discovery wiring (#147) +- rewrite(phase4): mock.hpp batch A — wire server::Endpoint admission (#146) +- rewrite(phase4): fix adapt.hpp's missing read_size_or_segment_num field, + add test_adapt.cpp (#145) +- rewrite(phase4): port discovery from c-RCP, fix claim-release and + validation gaps (#144) +- rewrite(phase4): port regmap batch B from c-RCP — HW pins, streams, EP-ID + map, optional subsystems (#143) +- rewrite(phase4): port server.c admission/scheduling into new server.hpp + (#141) +- rewrite(phase4): port regmap batch A from c-RCP — general map, EP0, + generic/functional split (#142) + +### Phase 3 — remaining endpoint types + +- rewrite(phase3): port spi and uart from c-RCP, RC5 nr_cs/deassert_cs_pause + fix (#139) +- rewrite(phase3): port pwm and wakeup from c-RCP, fix PWM_OUT Subtract + operand order (#140) +- rewrite(phase3): port mdio from c-RCP, revert an earlier session's own + regression (#138) +- rewrite(phase3): port adc/gpio from c-RCP, fix a critical ADC averaging + regression (#137) +- rewrite(phase3): port can/lin from c-RCP, wire CAN XL fragmentation via + fragment.hpp (#136) +- rewrite(phase3): port iseled/i2c from c-RCP; fix ROADMAP.md's Phase 17 + missing i2c entry (#135) + +### Phase 2 — E2E/lifecycle/watchdog + +- rewrite(phase2): port e2e/lifecycle from c-RCP, correct HARA.md's + overstated H-004 claim (#134) +- rewrite(phase2): port watchdog + build allocation fault-injection seam, + ported from c-RCP (#133) + +### Phase 1 — core wire format foundation + +- rewrite(phase1): port request/sequencer/scheduler from c-RCP's + RC5-conformant reference (#132) +- rewrite(phase1): add fragment/respqueue, convert loan to fixed-capacity, + ported from c-RCP (#131) +- rewrite(phase1): port acf.hpp/avtp.hpp from c-RCP's RC5-conformant + reference (#130) + +### Rewrite kickoff + +- ci: run CI/DCO on the rewrite/v3-from-c-rcp branch too +- docs(roadmap): v3.0.0 becomes a full rewrite ported from c-RCP, not Phase + 16's organic conclusion