Skip to content

feat: S08.05 store-and-forward on FatFs#352

Merged
DavidCozens merged 35 commits into
mainfrom
feat/s08-05-store-and-forward-fatfs
May 13, 2026
Merged

feat: S08.05 store-and-forward on FatFs#352
DavidCozens merged 35 commits into
mainfrom
feat/s08-05-store-and-forward-fatfs

Conversation

@DavidCozens

@DavidCozens DavidCozens commented May 12, 2026

Copy link
Copy Markdown
Owner

Purpose

Implements story #270 — store-and-forward on FatFs for the FreeRTOS QEMU
BDD target, so messages survive an oracle outage and a power cycle by
routing through SolidSyslogFileBlockDevice / SolidSyslogBlockStore
against a new SolidSyslogFatFsFile adapter.

Slicing per the issue body (8 slices, one commit each; PR raised after
slice 1 and force-pushed as slices land):

  • 1. Plumb FatFs into host TDD build (placeholder smoke test)
  • 2. SolidSyslogFatFsFile Create + Open/Close/IsOpen
  • 3. Read/Write/SeekTo/Size/Truncate
  • 4. Exists/Delete
  • 5. QEMU semihosting diskio.c + FreeRTOS ffsystem.c + BDD target BlockStore wiring
  • 6. store_and_forward.feature green on QEMU
  • 7. power_cycle_replay.feature green on QEMU
  • 8. store_capacity.feature all 4 scenarios green on QEMU

Closes #270.

Change Description

Slice 1 (this commit). Plumbing-only. No production code or fakes
yet — both land in slice 2.

  • Platform/FatFs/ added as a peer to Platform/Posix / Windows /
    FreeRtos / OpenSsl. Shaped as an INTERFACE library so each
    consumer recompiles the adapter against its own ffconf.h
    header-configured platform pattern (same as Platform/FreeRtos/),
    rationale documented inline in Platform/FatFs/CMakeLists.txt.
  • Tests/Support/FatFsFakes/Interface/ffconf.h provides a
    host-suitable FatFs config (single 512-byte-sector volume,
    FF_FS_REENTRANT=0, FF_USE_LFN=0, FF_FS_NORTC=1,
    FF_USE_MKFS=0) so adapter unit tests compile against ff.h
    without the real ff.c or a RAM-disk diskio.c. The fake itself
    is empty at slice 1 and grows per-slice as the adapter's f_*
    surface drives new behaviour.
  • Tests/FatFs/SolidSyslogFatFsFileTest placeholder exe with one
    plumbing assertion that exercises the contract ff.h enforces with
    #error: this TU finds FatFsFakes/Interface/ffconf.h first on
    the include path, reaches $FATFS_PATH/source/ff.h, and the
    FFCONF_DEF / FF_DEFINED revisions agree (80386 ↔ R0.16).
    The test is removed at slice 2 when the first real
    TEST_GROUP(SolidSyslogFatFsFile) lands.
  • Both Platform/FatFs and Tests/FatFs are gated on
    $ENV{FATFS_PATH} being set, matching Platform/FreeRtos gating
    on FREERTOS_KERNEL_PATH.

Design note: we fake the FatFs API, not run real FatFs. Initial
plan proposed a RAM-disk diskio.c so unit tests could run actual
FatFs against an in-memory backing buffer. Course-corrected: that tests
FatFs's own correctness, not the adapter's. Slices 2–4 fake f_open /
f_read / f_write / f_lseek / f_truncate / f_stat / f_unlink
in Tests/Support/FatFsFakes/Source/FatFsFake.c, matching the existing
SocketFake / WinsockFake / OpenSslFake / FreeRtosSocketsFake
pattern. Real FatFs only enters the build at slice 5 for the QEMU BDD
target, via a semihosting-backed diskio.c under
Bdd/Targets/FreeRtos/.

Test Evidence

Slice 1 adds one plumbing test:
TEST(FatFsPlumbing, FfConfRevisionMatchesFatFsHeader)
LONGS_EQUAL(FF_DEFINED, FFCONF_DEF). Compiles only if ff.h and
ffconf.h resolve correctly; runs only if the placeholder exe links.

Locally on freertos-host (BUILD_PRESET=debug):

cmake --preset debug
cmake --build --preset debug --target junit
# ... SolidSyslogFatFsFileTest: 1 tests, 1 ran, 1 checks ...

Existing test suites all stay green (full junit run completes).

Areas Affected

  • New: Platform/FatFs/ (peer to Posix/Windows/FreeRtos/OpenSsl)
  • New: Tests/Support/FatFsFakes/, Tests/FatFs/
  • Modified: top-level CMakeLists.txt, Tests/CMakeLists.txt
    (FATFS_PATH-gated subdir wiring + junit registration)
  • Untouched: Core, all existing Platform packs, all existing tests

Slices 2–4 will add Platform/FatFs/{Interface,Source}/SolidSyslogFatFsFile.{h,c}
and grow the FatFsFake. Slice 5 will add
Bdd/Targets/FreeRtos/{SemihostingDiskIo,FfSystemFreeRtos,ffconf}.{c,h}
and wire BlockStore into Bdd/Targets/FreeRtos/main.c.

Refs #270.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Optional FatFs filesystem adapter and FAT-backed persistence for the FreeRTOS example.
    • New FreeRTOS CLI options to configure store size, block sizing, discard policy, halt-on-full, SD/store selection, and dynamic rebuild/teardown.
  • Tests

    • Extensive unit and integration tests for FatFs-backed files, block-store drain ordering, TCP/ARP connect behavior, and store transience semantics.
    • Improved BDD: FreeRTOS scenarios enabled, deterministic flag ordering, disk-image reset/cleanup, and richer failure diagnostics.
  • Chores

    • Ignore generated semihosting disk image in VCS.

Review Change Stack

@github-actions

Copy link
Copy Markdown
Contributor

☀️   Quality Summary

   🚦   build-linux-gcc: 100% successful (✔️ 1112 passed, 🙈 3 skipped)
   🚦   build-freertos-host-tdd: 100% successful (✔️ 1200 passed, 🙈 3 skipped)
   🚦   build-linux-clang: 100% successful (✔️ 1064 passed, 🙈 3 skipped)
   🚦   sanitize-linux-gcc: 100% successful (✔️ 1064 passed, 🙈 3 skipped)
   🚦   integration-linux-openssl: 100% successful (✔️ 9 passed)
   🚦   integration-windows-openssl: 100% successful (✔️ 9 passed)
   🚦   bdd-linux-syslog-ng: 94% successful (✔️ 46 passed, 🙈 3 skipped)
   🚦   bdd-windows-otel: 90% successful (✔️ 44 passed, 🙈 5 skipped)
   🚦   bdd-freertos-qemu: 57% successful (✔️ 28 passed, 🙈 21 skipped)
   🚦   build-windows-msvc: 100% successful (✔️ 976 passed, 🙈 1 skipped)
   🚦   build-linux-tunable-override: 100% successful (✔️ 1064 passed, 🙈 3 skipped)
   ⚠️   Clang-Tidy: No warnings
   ⚠️   CPPCheck: No warnings


Created by Quality Monitor v1.14.0 (#f3859fd). More details are shown in the GitHub Checks Result.

@coderabbitai

coderabbitai Bot commented May 12, 2026

Copy link
Copy Markdown
Contributor

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • ✅ Review completed - (🔄 Check again to review again)
📝 Walkthrough

Walkthrough

Gate FatFs into host/test builds when FATFS_PATH is set; add Platform/FatFs INTERFACE adapter and headers; provide host ffconf.h and FatFs fakes; implement SolidSyslogFatFsFile and CppUTest units; stage upstream FatFs into FreeRTOS BDD, add semihosting diskio and ffsystem glue, and extend FreeRTOS demo and Behave/CI wiring for FATFS-backed stores.

Changes

FatFs host TDD build and tests

Layer / File(s) Summary
Root build gating and Platform/FatFs INTERFACE adapter
CMakeLists.txt, Platform/FatFs/CMakeLists.txt
Top-level CMake checks FATFS_PATH and conditionally includes Platform/FatFs. The platform subdirectory defines SolidSyslogFatFs as an INTERFACE library exposing Interface/ includes and installs *.h headers.
FatFsFakes docs and host ffconf.h
Tests/Support/FatFsFakes/CMakeLists.txt, Tests/Support/FatFsFakes/Interface/ffconf.h
Adds test-support CMake documentation and a host-suitable ffconf.h with test-focused FatFs macros (single volume, 512-byte sectors, fixed timestamps, non-reentrant, etc.).
Host test executable build & registration
Tests/CMakeLists.txt, Tests/FatFs/CMakeLists.txt, Tests/FatFs/main.cpp
Conditionally include FatFs test subdirs when FATFS_PATH is set; define SolidSyslogFatFsFileTest executable, link project and CppUTest libs, configure include paths (including $ENV{FATFS_PATH}/source), register with CTest, and provide the CppUTest runner main.
SolidSyslogFatFsFile header and implementation
Platform/FatFs/Interface/SolidSyslogFatFsFile.h, Platform/FatFs/Source/SolidSyslogFatFsFile.c
Adds public header defining storage footprint and create/destroy API and implements a FatFs-backed SolidSyslogFile adapter with open/close/read/write/seek/size/truncate/exists/delete operations.
FatFs adapter unit tests
Tests/FatFs/SolidSyslogFatFsFileTest.cpp
Adds CppUTest SolidSyslogFatFsFile group exercising lifecycle, seek/truncate/size, read/write success and failure cases, exists/delete semantics, and assertions against FatFsFake recorded calls.
FatFs fake API and implementation
Tests/Support/FatFsFakes/Interface/FatFsFake.h, Tests/Support/FatFsFakes/Source/FatFsFake.c
Introduces FatFs fake that records call counts and last arguments, exposes setters for simulated results and returned byte counts, and implements FatFs hook functions used by tests.

FreeRTOS BDD target and QEMU semihosting disk

Layer / File(s) Summary
BDD CMake: stage FatFs and wire upstream objects
Bdd/Targets/FreeRtos/CMakeLists.txt
Adds FATFS_PATH staging of upstream FatFs, compiles staged ff.c and project ffsystem.c into upstream OBJECT library, updates include dirs and adds diskio.c and SolidSyslogFatFsFile.c to the BDD target sources.
QEMU semihosting diskio
Bdd/Targets/FreeRtos/diskio.c
Implements semihosting-backed disk I/O: open/create/ensure disk image size, read/write/seek via semihosting traps, and sector-to-byte translation for FatFs.
FreeRTOS ffsystem and ffconf
Bdd/Targets/FreeRtos/ffsystem.c, Bdd/Targets/FreeRtos/ffconf.h
Adds FreeRTOS OS glue for FatFs (mutex wrappers, optional LFN alloc wrappers) and the FreeRTOS-target ffconf.h configuration macros.
FreeRTOS demo runtime & rebuild wiring
Bdd/Targets/FreeRtos/main.c
Introduces lifecycle mutex/state, set command extensions including store file, rebuild/teardown flow that mounts/formats FATFS and recreates SolidSyslog with file-backed block store, and ServiceTask lifecycle coordination.
Behave runner, steps, and CI
Bdd/features/*, Bdd/features/steps/*, ci/docker-compose.bdd.yml
Adds FreeRTOS disk image cleanup, per-process stdout capture for failed steps, deterministic FreeRTOS set flag translation (including --store), QEMU semihosting arg, stderr routing for FreeRTOS runs, and CI tag expression allowing @store on FreeRTOS runs.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐰 I hopped through CMake gates with care,

headers, fakes, and tests to share.
I baked a semihost disk in place,
so FreeRTOS keeps its stored embrace.
Hooray for builds that pass the race!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 9.40% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The PR title 'feat: S08.05 store-and-forward on FatFs' follows Conventional Commits format and clearly summarizes the main change—adding FatFs support for store-and-forward functionality.
Description check ✅ Passed The PR description comprehensively covers Purpose, Change Description (slice 1 details), Test Evidence, and Areas Affected, matching the template structure and providing necessary implementation context.
Linked Issues check ✅ Passed The code changes implement the requirements for slice 1 of issue #270: FatFs platform pack structure (INTERFACE library), ffconf.h for host TDD, placeholder test, CMake wiring, and FATFS_PATH gating, all as specified.
Out of Scope Changes check ✅ Passed All changes are directly scoped to slice 1 of issue #270: CMake plumbing, ffconf.h for host builds, placeholder test infrastructure, and feature gating—no extraneous production code, fakes beyond ffconf, or unrelated modifications.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/s08-05-store-and-forward-fatfs

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions

Copy link
Copy Markdown
Contributor

☀️   Quality Summary

   🚦   build-linux-gcc: 100% successful (✔️ 1112 passed, 🙈 3 skipped)
   🚦   build-freertos-host-tdd: 100% successful (✔️ 1200 passed, 🙈 3 skipped)
   🚦   build-linux-clang: 100% successful (✔️ 1064 passed, 🙈 3 skipped)
   🚦   sanitize-linux-gcc: 100% successful (✔️ 1064 passed, 🙈 3 skipped)
   🚦   integration-linux-openssl: 100% successful (✔️ 9 passed)
   🚦   integration-windows-openssl: 100% successful (✔️ 9 passed)
   🚦   bdd-linux-syslog-ng: 94% successful (✔️ 46 passed, 🙈 3 skipped)
   🚦   bdd-windows-otel: 90% successful (✔️ 44 passed, 🙈 5 skipped)
   🚦   bdd-freertos-qemu: 57% successful (✔️ 28 passed, 🙈 21 skipped)
   🚦   build-windows-msvc: 100% successful (✔️ 976 passed, 🙈 1 skipped)
   🚦   build-linux-tunable-override: 100% successful (✔️ 1064 passed, 🙈 3 skipped)
   ⚠️   Clang-Tidy: No warnings
   ⚠️   CPPCheck: No warnings


Created by Quality Monitor v1.14.0 (#f3859fd). More details are shown in the GitHub Checks Result.

@github-actions

Copy link
Copy Markdown
Contributor

☀️   Quality Summary

   🚦   build-linux-gcc: 100% successful (✔️ 1112 passed, 🙈 3 skipped)
   🚦   build-freertos-host-tdd: 100% successful (✔️ 1223 passed, 🙈 3 skipped)
   🚦   build-linux-clang: 100% successful (✔️ 1064 passed, 🙈 3 skipped)
   🚦   sanitize-linux-gcc: 100% successful (✔️ 1064 passed, 🙈 3 skipped)
   🚦   integration-linux-openssl: 100% successful (✔️ 9 passed)
   🚦   integration-windows-openssl: 100% successful (✔️ 9 passed)
   🚦   bdd-linux-syslog-ng: 94% successful (✔️ 46 passed, 🙈 3 skipped)
   🚦   bdd-windows-otel: 90% successful (✔️ 44 passed, 🙈 5 skipped)
   🚦   bdd-freertos-qemu: 57% successful (✔️ 28 passed, 🙈 21 skipped)
   🚦   build-windows-msvc: 100% successful (✔️ 976 passed, 🙈 1 skipped)
   🚦   build-linux-tunable-override: 100% successful (✔️ 1064 passed, 🙈 3 skipped)
   ⚠️   Clang-Tidy: No warnings
   ⚠️   CPPCheck: No warnings


Created by Quality Monitor v1.14.0 (#f3859fd). More details are shown in the GitHub Checks Result.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
Platform/FatFs/Source/SolidSyslogFatFsFile.c (1)

110-114: ⚖️ Poor tradeoff

Interface constraint: Truncate cannot report errors.

FatFsFile_Truncate discards return values from f_lseek() and f_truncate(), which may fail on read-only filesystems, disk-full, or I/O errors. However, the SolidSyslogFile_Truncate interface is defined as void across all platforms (FatFs, Windows, Posix), meaning error reporting is not supported by the current contract. Addressing this would require evolving the interface signature to return an error status and updating all platform implementations and call sites.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Platform/FatFs/Source/SolidSyslogFatFsFile.c` around lines 110 - 114,
FatFsFile_Truncate currently ignores errors from f_lseek and f_truncate which
violates robustness because SolidSyslogFile_Truncate is void; change the
SolidSyslogFile_Truncate interface to return a status (e.g., SolidSyslogResult
or int) and update FatFsFile_Truncate to propagate and return the mapped FatFs
error code from f_lseek/f_truncate (check both calls and prefer the first
failing error), then update the Windows and Posix implementations (and all call
sites of SolidSyslogFile_Truncate) to the new return type and handle/report
errors accordingly so truncation failures are no longer silently dropped.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@Platform/FatFs/Source/SolidSyslogFatFsFile.c`:
- Around line 110-114: FatFsFile_Truncate currently ignores errors from f_lseek
and f_truncate which violates robustness because SolidSyslogFile_Truncate is
void; change the SolidSyslogFile_Truncate interface to return a status (e.g.,
SolidSyslogResult or int) and update FatFsFile_Truncate to propagate and return
the mapped FatFs error code from f_lseek/f_truncate (check both calls and prefer
the first failing error), then update the Windows and Posix implementations (and
all call sites of SolidSyslogFile_Truncate) to the new return type and
handle/report errors accordingly so truncation failures are no longer silently
dropped.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 8d67f69a-1705-4b2e-86ce-58d264856a28

📥 Commits

Reviewing files that changed from the base of the PR and between 17f60b3 and d596834.

📒 Files selected for processing (6)
  • Platform/FatFs/Interface/SolidSyslogFatFsFile.h
  • Platform/FatFs/Source/SolidSyslogFatFsFile.c
  • Tests/FatFs/CMakeLists.txt
  • Tests/FatFs/SolidSyslogFatFsFileTest.cpp
  • Tests/Support/FatFsFakes/Interface/FatFsFake.h
  • Tests/Support/FatFsFakes/Source/FatFsFake.c
🚧 Files skipped from review as they are similar to previous changes (1)
  • Tests/FatFs/CMakeLists.txt

@github-actions

Copy link
Copy Markdown
Contributor

☀️   Quality Summary

   🚦   build-linux-gcc: 100% successful (✔️ 1112 passed, 🙈 3 skipped)
   🚦   build-freertos-host-tdd: 100% successful (✔️ 1223 passed, 🙈 3 skipped)
   🚦   build-linux-clang: 100% successful (✔️ 1064 passed, 🙈 3 skipped)
   🚦   sanitize-linux-gcc: 100% successful (✔️ 1064 passed, 🙈 3 skipped)
   🚦   integration-linux-openssl: 100% successful (✔️ 9 passed)
   🚦   integration-windows-openssl: 100% successful (✔️ 9 passed)
   🚦   bdd-linux-syslog-ng: 94% successful (✔️ 46 passed, 🙈 3 skipped)
   🚦   bdd-windows-otel: 90% successful (✔️ 44 passed, 🙈 5 skipped)
   🚦   bdd-freertos-qemu: 0% successful (❌ 36 failed, 🙈 13 skipped)
   🚦   build-windows-msvc: 100% successful (✔️ 976 passed, 🙈 1 skipped)
   🚦   build-linux-tunable-override: 100% successful (✔️ 1064 passed, 🙈 3 skipped)
   ⚠️   Clang-Tidy: No warnings
   ⚠️   CPPCheck: No warnings


Created by Quality Monitor v1.14.0 (#f3859fd). More details are shown in the GitHub Checks Result.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
Bdd/Targets/FreeRtos/main.c (1)

805-871: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

ServiceTask dereferences g_lifecycleMutex after Teardown nulls it.

The interactive teardown sequence at Lines 826-831 destroys the lifecycle mutex and clears g_lifecycleMutex = NULL, then vTaskDelete(NULL). The Service task, however, keeps spinning in its for (;;) loop and unconditionally calls SolidSyslogMutex_Lock(g_lifecycleMutex) (Line 863). Between the moment g_lifecycleMutex becomes NULL and QEMU being killed by the harness, the Service task will dereference NULL inside the mutex API (or block on a freed semaphore handle if FreeRTOS reuses the storage).

Even on this Tier 3 BDD target this widens the failure window during quit cleanup. Two small options:

  • Have ServiceTask observe a g_solidSyslogTeardown flag (or g_lifecycleMutex == NULL) and vTaskDelete(NULL) before the lock call.
  • Or delete the Service task from InteractiveTask before tearing down the mutex.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Bdd/Targets/FreeRtos/main.c` around lines 805 - 871, ServiceTask can
dereference g_lifecycleMutex after InteractiveTask/Teardown destroys it; fix by
making ServiceTask check for teardown before calling SolidSyslogMutex_Lock: at
top of the infinite loop in ServiceTask (function ServiceTask) add a guard that
if g_lifecycleMutex == NULL || g_solidSyslogReady == false (or a new global flag
g_solidSyslogTeardown set by Teardown) then call vTaskDelete(NULL) / break out
instead of locking; ensure Teardown sets the chosen flag (or keeps setting
g_lifecycleMutex = NULL) before deleting its mutex so ServiceTask will observe
it and exit safely.
🧹 Nitpick comments (5)
Bdd/Targets/FreeRtos/main.c (3)

435-445: 💤 Low value

Simplify the discard-policy literal selection.

Lines 437-440 already validated value is exactly one of "oldest" / "newest" / "halt", so the triple strcmp ladder on Line 443 is redundant — assigning value directly would alias an OnSet-local pointer that does not outlive the call. Stash a small lookup or just rebind by the values you already validated:

♻️ Proposed simplification
-        /* String literal storage — target_driver.py emits one of the three
-         * literals above so the pointer stays valid (no copy needed). */
-        g_pendingDiscardPolicy = (strcmp(value, "newest") == 0) ? "newest" : ((strcmp(value, "halt") == 0) ? "halt" : "oldest");
+        /* Bind to a process-lifetime string literal (the caller's `value`
+         * buffer does not outlive OnSet). */
+        if (strcmp(value, "newest") == 0)      { g_pendingDiscardPolicy = "newest"; }
+        else if (strcmp(value, "halt") == 0)   { g_pendingDiscardPolicy = "halt"; }
+        else                                    { g_pendingDiscardPolicy = "oldest"; }
         return true;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Bdd/Targets/FreeRtos/main.c` around lines 435 - 445, The discard-policy
branch validates value is one of "oldest"/"newest"/"halt" but then uses a
ternary strcmp ladder to pick a literal; instead, avoid aliasing the
caller-local value pointer and simply assign g_pendingDiscardPolicy to the
corresponding static literal based on the already-performed checks (e.g. in the
"discard-policy" handler: if strcmp(value,"newest")==0 set
g_pendingDiscardPolicy to "newest", else if strcmp(value,"halt")==0 set to
"halt", otherwise set to "oldest"); this keeps the pointer pointing to static
storage and removes the redundant triple strcmp expression.

474-488: 💤 Low value

Confusing success semantics for set store null.

return !g_currentStoreIsFile; causes a repeat set store null to report failure once the rebuild has already flipped to file-backed. The comment says "succeed only if we're still on the default", but from the harness side (target_driver emits --store last, deterministically), this asymmetric truth-value is hard to read and easy to misuse later. Consider returning true unconditionally for null (true no-op) — the harness never asks to revert, and the rebuild ordering already guarantees --store is the trigger.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Bdd/Targets/FreeRtos/main.c` around lines 474 - 488, The current branch
handling if (strcmp(name, "store") == 0) returns !g_currentStoreIsFile for the
"null" value which makes a no-op sometimes report failure; change the behavior
to always succeed for "null" by replacing the conditional return
(!g_currentStoreIsFile) with an unconditional success (return true) in that
block (leave the "file" branch calling RebuildWithFileStore() and keep
g_currentStoreIsFile usage elsewhere); update the inline comment to reflect that
"null" is a no-op that always reports success.

596-629: 💤 Low value

Diagnostic printf trail in EnsureFatFsMounted looks like the temporary patch — track removal.

[fatfs] mounting volume 0, f_mount initial -> %d, no filesystem, formatting, f_mkfs -> %d, f_mount post-mkfs -> %d, mount complete mirror the diagnostic patch from the commit log. Once the QEMU @store scenarios are stable, these should fold back into the single existing [solidsyslog] fatfs mount failed: ... error path so the UART output stays scannable on success. Leaving the cleanup as a follow-up TODO is fine; flagging so it doesn't quietly become permanent.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Bdd/Targets/FreeRtos/main.c` around lines 596 - 629, EnsureFatFsMounted
contains temporary diagnostic printf calls that should be removed/condensed:
delete the transient debug prints ("[fatfs] mounting volume 0", "[fatfs] f_mount
initial -> ...", "[fatfs] no filesystem, formatting", "[fatfs] f_mkfs -> ...",
"[fatfs] f_mount post-mkfs -> ...", and "[fatfs] mount complete") and keep only
the canonical error path and a single concise success log if desired; leave the
existing failure log "(void) printf(\"[solidsyslog] fatfs mount failed:
FRESULT=%d\\n\", (int) res)" intact, ensure g_fatfsMounted is still set on
success, and reference EnsureFatFsMounted, g_fatfsMounted, f_mount, f_mkfs and
g_fatfs when making the change so the UART output remains scannable once QEMU
`@store` is stable.
Bdd/features/environment.py (1)

186-207: 💤 Low value

Move import sys to module top-level.

import sys is already implicitly available via sys.path.insert at Line 15-16 (re-importing at module level there). Re-importing inside after_step is harmless but unnecessary. Hoisting the import to the existing import block at the top keeps imports in one place.

♻️ Proposed adjustment
-    text = bytes(log).decode("utf-8", errors="replace")
-    import sys
-    print(f"--- last {len(log)} bytes of BDD target stdout ---", file=sys.stderr, flush=True)
+    text = bytes(log).decode("utf-8", errors="replace")
+    print(f"--- last {len(log)} bytes of BDD target stdout ---", file=sys.stderr, flush=True)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Bdd/features/environment.py` around lines 186 - 207, The local import of sys
inside after_step is unnecessary; move the import to the module-level import
block (alongside the existing sys.path manipulation) to keep imports
consolidated. Update the top of the file to add "import sys" and remove the
"import sys" line from inside the after_step function (which references
context.interactive_process and getattr(..., '_solidsyslog_stdout_log')). Ensure
no other references are broken after removing the inner import.
Bdd/Targets/FreeRtos/diskio.c (1)

66-123: ⚡ Quick win

Diagnostic printf tracing in disk_initialize / DiskImageIsReady — track removal.

The inline extern int printf(...) + [diskio] ... prints on lines 68‑69, 75, 81, 87, 91, 104 match the diagnostic patch called out in the PR description ("adds tracing around f_mount/diskio operations"). They're intentional for container debugging, but please make sure they're stripped (or gated behind a debug macro) before this slice lands — otherwise every disk_initialize on boot prints over UART, which is noisy for downstream BDD scenarios that scrape UART output for assertions.

🧹 Suggested cleanup once debugging is no longer needed
 DSTATUS disk_initialize(BYTE pdrv)
 {
-    extern int printf(const char* fmt, ...);
-    (void) printf("[diskio] disk_initialize pdrv=%u\n", (unsigned) pdrv);
     if (pdrv != 0)
     {
         return STA_NOINIT;
     }
-    bool ok = DiskImageIsReady();
-    (void) printf("[diskio] disk_initialize ready=%u\n", (unsigned) ok);
-    return ok ? 0 : STA_NOINIT;
+    return DiskImageIsReady() ? 0 : STA_NOINIT;
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Bdd/Targets/FreeRtos/diskio.c` around lines 66 - 123, Remove or guard the
diagnostic semihosting printf traces in disk_initialize and DiskImageIsReady:
eliminate the inline "extern int printf(...)" declarations and the "[diskio]
..." printf calls (those in disk_initialize and all prints inside
DiskImageIsReady) or wrap them behind a compile-time debug macro (e.g., `#ifdef`
DISKIO_DEBUG) so normal builds do not emit UART output; ensure the behavior and
return logic of disk_initialize and DiskImageIsReady remain unchanged when the
prints are disabled.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@Bdd/Targets/FreeRtos/CMakeLists.txt`:
- Around line 200-214: The target_include_directories for SolidSyslogBddTarget
currently adds ${FATFS_PATH}/source which can cause ff.h to pick the upstream
sample ffconf.h and create ABI mismatch; update the include list in
target_include_directories for SolidSyslogBddTarget to use ${FATFS_STAGE_DIR}
instead of ${FATFS_PATH}/source so the executable and the upstream OBJECT lib
use the same staged headers (and therefore the integrator's ffconf.h) ensuring
consistent typedefs like LBA_t/UINT across translation units.

In `@Bdd/Targets/FreeRtos/main.c`:
- Around line 561-590: RebuildWithFileStore currently ignores NULL returns from
SolidSyslogFatFsFile_Create, SolidSyslogFileBlockDevice_Create,
SolidSyslogCrc16Policy_Create, and SolidSyslogBlockStore_Create and proceeds to
call SolidSyslog_Create and set g_currentStoreIsFile/g_solidSyslogReady;
instead, after each creation call (or at least before setting
g_currentStoreIsFile and calling SolidSyslog_Create) check for NULL and on any
failure undo/cleanup any previously created resources if needed, unlock
g_lifecycleMutex, and return false so the caller/harness sees the failure;
ensure you do not flip g_currentStoreIsFile or g_solidSyslogReady nor call
SolidSyslog_Create when g_currentStore is NULL.
- Around line 631-649: The SemihostingExit function currently uses SYS_EXIT
(opcode 0x18) but passes a pointer parameter block (AArch64 style); change it to
use SYS_EXIT_EXTENDED (opcode 0x20) so the AArch32 semihosting call receives the
parameter block {reason=0x20026, status} correctly: set the register r0 to 0x20
and r1 to the address of args in SemihostingExit, keep the bkpt 0xAB and the
defensive for(;;), and update the comment to reflect SYS_EXIT_EXTENDED usage and
the parameter block format.

---

Outside diff comments:
In `@Bdd/Targets/FreeRtos/main.c`:
- Around line 805-871: ServiceTask can dereference g_lifecycleMutex after
InteractiveTask/Teardown destroys it; fix by making ServiceTask check for
teardown before calling SolidSyslogMutex_Lock: at top of the infinite loop in
ServiceTask (function ServiceTask) add a guard that if g_lifecycleMutex == NULL
|| g_solidSyslogReady == false (or a new global flag g_solidSyslogTeardown set
by Teardown) then call vTaskDelete(NULL) / break out instead of locking; ensure
Teardown sets the chosen flag (or keeps setting g_lifecycleMutex = NULL) before
deleting its mutex so ServiceTask will observe it and exit safely.

---

Nitpick comments:
In `@Bdd/features/environment.py`:
- Around line 186-207: The local import of sys inside after_step is unnecessary;
move the import to the module-level import block (alongside the existing
sys.path manipulation) to keep imports consolidated. Update the top of the file
to add "import sys" and remove the "import sys" line from inside the after_step
function (which references context.interactive_process and getattr(...,
'_solidsyslog_stdout_log')). Ensure no other references are broken after
removing the inner import.

In `@Bdd/Targets/FreeRtos/diskio.c`:
- Around line 66-123: Remove or guard the diagnostic semihosting printf traces
in disk_initialize and DiskImageIsReady: eliminate the inline "extern int
printf(...)" declarations and the "[diskio] ..." printf calls (those in
disk_initialize and all prints inside DiskImageIsReady) or wrap them behind a
compile-time debug macro (e.g., `#ifdef` DISKIO_DEBUG) so normal builds do not
emit UART output; ensure the behavior and return logic of disk_initialize and
DiskImageIsReady remain unchanged when the prints are disabled.

In `@Bdd/Targets/FreeRtos/main.c`:
- Around line 435-445: The discard-policy branch validates value is one of
"oldest"/"newest"/"halt" but then uses a ternary strcmp ladder to pick a
literal; instead, avoid aliasing the caller-local value pointer and simply
assign g_pendingDiscardPolicy to the corresponding static literal based on the
already-performed checks (e.g. in the "discard-policy" handler: if
strcmp(value,"newest")==0 set g_pendingDiscardPolicy to "newest", else if
strcmp(value,"halt")==0 set to "halt", otherwise set to "oldest"); this keeps
the pointer pointing to static storage and removes the redundant triple strcmp
expression.
- Around line 474-488: The current branch handling if (strcmp(name, "store") ==
0) returns !g_currentStoreIsFile for the "null" value which makes a no-op
sometimes report failure; change the behavior to always succeed for "null" by
replacing the conditional return (!g_currentStoreIsFile) with an unconditional
success (return true) in that block (leave the "file" branch calling
RebuildWithFileStore() and keep g_currentStoreIsFile usage elsewhere); update
the inline comment to reflect that "null" is a no-op that always reports
success.
- Around line 596-629: EnsureFatFsMounted contains temporary diagnostic printf
calls that should be removed/condensed: delete the transient debug prints
("[fatfs] mounting volume 0", "[fatfs] f_mount initial -> ...", "[fatfs] no
filesystem, formatting", "[fatfs] f_mkfs -> ...", "[fatfs] f_mount post-mkfs ->
...", and "[fatfs] mount complete") and keep only the canonical error path and a
single concise success log if desired; leave the existing failure log "(void)
printf(\"[solidsyslog] fatfs mount failed: FRESULT=%d\\n\", (int) res)" intact,
ensure g_fatfsMounted is still set on success, and reference EnsureFatFsMounted,
g_fatfsMounted, f_mount, f_mkfs and g_fatfs when making the change so the UART
output remains scannable once QEMU `@store` is stable.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: a862d7fe-b46b-4594-bbe5-2c8c7668df5e

📥 Commits

Reviewing files that changed from the base of the PR and between d596834 and 32593c4.

📒 Files selected for processing (11)
  • Bdd/Targets/FreeRtos/CMakeLists.txt
  • Bdd/Targets/FreeRtos/diskio.c
  • Bdd/Targets/FreeRtos/ffconf.h
  • Bdd/Targets/FreeRtos/ffsystem.c
  • Bdd/Targets/FreeRtos/main.c
  • Bdd/features/capacity_threshold.feature
  • Bdd/features/environment.py
  • Bdd/features/steps/syslog_steps.py
  • Bdd/features/steps/target_driver.py
  • Platform/FatFs/Interface/SolidSyslogFatFsFile.h
  • ci/docker-compose.bdd.yml
✅ Files skipped from review due to trivial changes (2)
  • Bdd/features/capacity_threshold.feature
  • Bdd/Targets/FreeRtos/ffconf.h
🚧 Files skipped from review as they are similar to previous changes (1)
  • Platform/FatFs/Interface/SolidSyslogFatFsFile.h

Comment thread Bdd/Targets/FreeRtos/CMakeLists.txt
Comment thread Bdd/Targets/FreeRtos/main.c Outdated
Comment on lines +561 to +590
g_storeReadFile = SolidSyslogFatFsFile_Create(&g_storeReadFileStorage);
g_storeWriteFile = SolidSyslogFatFsFile_Create(&g_storeWriteFileStorage);
g_storeBlockDevice = SolidSyslogFileBlockDevice_Create(&g_blockDeviceStorage, g_storeReadFile, g_storeWriteFile, STORE_PATH_PREFIX);

struct SolidSyslogSecurityPolicy* policy = SolidSyslogCrc16Policy_Create();
struct SolidSyslogBlockStoreConfig storeConfig = {
.blockDevice = g_storeBlockDevice,
.maxBlockSize = g_pendingMaxBlockSize,
.maxBlocks = g_pendingMaxBlocks,
.discardPolicy = MapDiscardPolicy(g_pendingDiscardPolicy),
.securityPolicy = policy,
.onStoreFull = OnStoreFull,
.storeFullContext = NULL,
.getCapacityThreshold = GetCapacityThreshold,
.onThresholdCrossed = NULL,
.thresholdContext = &g_pendingCapacityThreshold,
};
g_currentStore = SolidSyslogBlockStore_Create(&g_blockStoreStorage, &storeConfig);
g_currentStoreIsFile = true;

g_solidSyslogConfig.store = g_currentStore;
/* Re-honour `set no-sd 1` if it arrived before this rebuild — the
* sort order in target_driver.py guarantees `set no-sd` comes before
* `set store file` so the value is final by the time we get here. */
g_solidSyslogConfig.sdCount = g_pendingNoSd ? 1U : (sizeof(g_sdList) / sizeof(g_sdList[0]));
SolidSyslog_Create(&g_solidSyslogConfig);
g_solidSyslogReady = true;
SolidSyslogMutex_Unlock(g_lifecycleMutex);
return true;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

RebuildWithFileStore ignores allocation failures.

SolidSyslogFatFsFile_Create, SolidSyslogFileBlockDevice_Create, SolidSyslogCrc16Policy_Create, and SolidSyslogBlockStore_Create can each return NULL (heap exhaustion / pre-mount-failure paths), but the function unconditionally proceeds to SolidSyslog_Create(&g_solidSyslogConfig) with potentially NULL .store. That tips a successful set store file reply back to the harness while the target is broken, masking the failure root cause in the diagnostic patch's printf trail.

Cheap defence: check the BlockStore result before flipping g_currentStoreIsFile = true / g_solidSyslogReady = true, return false on failure, and unlock the lifecycle mutex on the way out so the harness sees the failed set reply.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Bdd/Targets/FreeRtos/main.c` around lines 561 - 590, RebuildWithFileStore
currently ignores NULL returns from SolidSyslogFatFsFile_Create,
SolidSyslogFileBlockDevice_Create, SolidSyslogCrc16Policy_Create, and
SolidSyslogBlockStore_Create and proceeds to call SolidSyslog_Create and set
g_currentStoreIsFile/g_solidSyslogReady; instead, after each creation call (or
at least before setting g_currentStoreIsFile and calling SolidSyslog_Create)
check for NULL and on any failure undo/cleanup any previously created resources
if needed, unlock g_lifecycleMutex, and return false so the caller/harness sees
the failure; ensure you do not flip g_currentStoreIsFile or g_solidSyslogReady
nor call SolidSyslog_Create when g_currentStore is NULL.

Comment thread Bdd/Targets/FreeRtos/main.c
@github-actions

Copy link
Copy Markdown
Contributor

☀️   Quality Summary

   🚦   build-linux-gcc: 100% successful (✔️ 1112 passed, 🙈 3 skipped)
   🚦   build-freertos-host-tdd: 100% successful (✔️ 1223 passed, 🙈 3 skipped)
   🚦   build-linux-clang: 100% successful (✔️ 1064 passed, 🙈 3 skipped)
   🚦   sanitize-linux-gcc: 100% successful (✔️ 1064 passed, 🙈 3 skipped)
   🚦   integration-linux-openssl: 100% successful (✔️ 9 passed)
   🚦   integration-windows-openssl: 100% successful (✔️ 9 passed)
   🚦   bdd-linux-syslog-ng: 94% successful (✔️ 46 passed, 🙈 3 skipped)
   🚦   bdd-windows-otel: 90% successful (✔️ 44 passed, 🙈 5 skipped)
   🚦   bdd-freertos-qemu: 59% successful (❌ 7 failed, ✔️ 29 passed, 🙈 13 skipped)
   🚦   build-windows-msvc: 100% successful (✔️ 976 passed, 🙈 1 skipped)
   🚦   build-linux-tunable-override: 100% successful (✔️ 1064 passed, 🙈 3 skipped)
   ⚠️   Clang-Tidy: No warnings
   ⚠️   CPPCheck: No warnings


Created by Quality Monitor v1.14.0 (#f3859fd). More details are shown in the GitHub Checks Result.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@Bdd/features/steps/target_driver.py`:
- Around line 187-194: The parsing loop currently treats the next token as a
value even if it's another flag, so in the block handling next(iterator) (where
variables are flag, value, iterator and pairs), after getting value =
next(iterator) and before appending to pairs, check whether value is a flag
token (e.g., starts with "-" or "--"); if it is, raise a ValueError similar to
the StopIteration case indicating that the key/value flag {flag!r} expects a
value but got another flag {value!r}, otherwise append (flag, value) to pairs.
Ensure the new check sits alongside the existing StopIteration handling so
malformed input like "--facility --severity" fails fast.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 4aea3583-d51d-42b7-8161-b2da99854456

📥 Commits

Reviewing files that changed from the base of the PR and between 32593c4 and 166234b.

📒 Files selected for processing (1)
  • Bdd/features/steps/target_driver.py

Comment thread Bdd/features/steps/target_driver.py
@github-actions

Copy link
Copy Markdown
Contributor

☀️   Quality Summary

   🚦   build-linux-gcc: 100% successful (✔️ 1112 passed, 🙈 3 skipped)
   🚦   build-freertos-host-tdd: 100% successful (✔️ 1223 passed, 🙈 3 skipped)
   🚦   build-linux-clang: 100% successful (✔️ 1064 passed, 🙈 3 skipped)
   🚦   sanitize-linux-gcc: 100% successful (✔️ 1064 passed, 🙈 3 skipped)
   🚦   integration-linux-openssl: 100% successful (✔️ 9 passed)
   🚦   integration-windows-openssl: 100% successful (✔️ 9 passed)
   🚦   bdd-linux-syslog-ng: 94% successful (✔️ 46 passed, 🙈 3 skipped)
   🚦   bdd-windows-otel: 90% successful (✔️ 44 passed, 🙈 5 skipped)
   🚦   bdd-freertos-qemu: 59% successful (❌ 7 failed, ✔️ 29 passed, 🙈 13 skipped)
   🚦   build-windows-msvc: 100% successful (✔️ 976 passed, 🙈 1 skipped)
   🚦   build-linux-tunable-override: 100% successful (✔️ 1064 passed, 🙈 3 skipped)
   ⚠️   Clang-Tidy: No warnings
   ⚠️   CPPCheck: No warnings


Created by Quality Monitor v1.14.0 (#f3859fd). More details are shown in the GitHub Checks Result.

@github-actions

Copy link
Copy Markdown
Contributor

☀️   Quality Summary

   🚦   build-linux-gcc: 100% successful (✔️ 1112 passed, 🙈 3 skipped)
   🚦   build-freertos-host-tdd: 100% successful (✔️ 1223 passed, 🙈 3 skipped)
   🚦   build-linux-clang: 100% successful (✔️ 1064 passed, 🙈 3 skipped)
   🚦   sanitize-linux-gcc: 100% successful (✔️ 1064 passed, 🙈 3 skipped)
   🚦   integration-linux-openssl: 100% successful (✔️ 9 passed)
   🚦   integration-windows-openssl: 100% successful (✔️ 9 passed)
   🚦   bdd-linux-syslog-ng: 94% successful (✔️ 46 passed, 🙈 3 skipped)
   🚦   bdd-windows-otel: 90% successful (✔️ 44 passed, 🙈 5 skipped)
   🚦   bdd-freertos-qemu: 59% successful (❌ 7 failed, ✔️ 29 passed, 🙈 13 skipped)
   🚦   build-windows-msvc: 100% successful (✔️ 976 passed, 🙈 1 skipped)
   🚦   build-linux-tunable-override: 100% successful (✔️ 1064 passed, 🙈 3 skipped)
   ⚠️   Clang-Tidy: No warnings
   ⚠️   CPPCheck: No warnings


Created by Quality Monitor v1.14.0 (#f3859fd). More details are shown in the GitHub Checks Result.

@github-actions

Copy link
Copy Markdown
Contributor

☀️   Quality Summary

   🚦   build-linux-gcc: 100% successful (✔️ 1112 passed, 🙈 3 skipped)
   🚦   build-freertos-host-tdd: 100% successful (✔️ 1223 passed, 🙈 3 skipped)
   🚦   build-linux-clang: 100% successful (✔️ 1064 passed, 🙈 3 skipped)
   🚦   sanitize-linux-gcc: 100% successful (✔️ 1064 passed, 🙈 3 skipped)
   🚦   integration-linux-openssl: 100% successful (✔️ 9 passed)
   🚦   integration-windows-openssl: 100% successful (✔️ 9 passed)
   🚦   bdd-linux-syslog-ng: 94% successful (✔️ 46 passed, 🙈 3 skipped)
   🚦   bdd-windows-otel: 90% successful (✔️ 44 passed, 🙈 5 skipped)
   🚦   bdd-freertos-qemu: 57% successful (❌ 8 failed, ✔️ 28 passed, 🙈 13 skipped)
   🚦   build-windows-msvc: 100% successful (✔️ 976 passed, 🙈 1 skipped)
   🚦   build-linux-tunable-override: 100% successful (✔️ 1064 passed, 🙈 3 skipped)
   ⚠️   Clang-Tidy: No warnings
   ⚠️   CPPCheck: No warnings


Created by Quality Monitor v1.14.0 (#f3859fd). More details are shown in the GitHub Checks Result.

DavidCozens and others added 18 commits May 13, 2026 13:10
Plumbing-only scaffolding for the FatFs adapter slices to come. No
production code yet — first SolidSyslogFatFsFile content lands at
slice 2.

Adds Platform/FatFs/ as a peer to Posix / Windows / FreeRtos / OpenSsl,
shaped as an INTERFACE library so each consumer recompiles the
(future) adapter sources with its own ffconf.h on the include path —
header-configured platform pattern, same as Platform/FreeRtos/.

Adds Tests/Support/FatFsFakes/ holding a host-suitable ffconf.h
(FF_FS_REENTRANT=0, FF_USE_LFN=0, FF_FS_NORTC=1, single 512-byte-sector
volume, FF_USE_MKFS=0) so adapter unit tests can compile against ff.h
without pulling in the real ff.c or a RAM-disk diskio. The FatFsFake.h
control surface and FatFsFake.c bodies land per-slice as adapter
behaviour drives new f_* calls.

Adds Tests/FatFs/SolidSyslogFatFsFileTest with one plumbing assertion
(FfConfRevisionMatchesFatFsHeader) that exercises the include-path
contract ff.h enforces with #error: FatFsFakes/Interface/ffconf.h is
found first, \$FATFS_PATH/source/ff.h is reachable, and the FFCONF_DEF
/ FF_DEFINED revisions agree (80386 ↔ R0.16). This test is removed at
slice 2 when the first real TEST_GROUP(SolidSyslogFatFsFile) lands.

Both Platform/FatFs and Tests/FatFs are gated on \$ENV{FATFS_PATH}
being set, matching the existing Platform/FreeRtos gating on
FREERTOS_KERNEL_PATH.

Refs #270.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
clang-format collapses the tab-aligned columns the upstream FatFs
ffconf.h uses (which mine inherited verbatim) into single-space
delimited #define lines. Values unchanged — purely whitespace.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds the FatFs-backed SolidSyslogFile adapter with Create/Destroy
lifecycle and the Open/Close/IsOpen portion of the vtable. Read/Write/
SeekTo/Size/Truncate land in slice 3, Exists/Delete in slice 4.

Public surface (Platform/FatFs/Interface/SolidSyslogFatFsFile.h)
mirrors SolidSyslogPosixFile.h: SolidSyslogFatFsFileStorage opaque
storage struct, SOLIDSYSLOG_FATFS_FILE_SIZE sized to hold the internal
struct (FIL + base vtable + isOpen flag) on 64-bit hosts, Create takes
a storage pointer and returns &storage->base.

Open uses FA_READ | FA_WRITE | FA_OPEN_ALWAYS, matching PosixFile's
O_RDWR | O_CREAT (read/write, create if absent). Open state is tracked
explicitly via a bool field rather than reading FatFs's internal
fp.obj.fs — cleaner and doesn't depend on FatFs internals.

Destroy delegates to Close; Close is idempotent (guarded by isOpen),
so Destroy on a never-opened or already-closed file is a safe no-op
on f_close.

9 tests driven in ZOMBIES order against a new Tests/Support/FatFsFakes
fake of the FatFs API surface (f_open / f_close only at this slice),
not real FatFs — adapter unit tests verify the translation from
SolidSyslogFile contract to FatFs calls, not FatFs's own correctness.
Real FatFs only enters the build at slice 5 on QEMU. Tests use the
existing CALLED_FAKE / NEVER / ONCE assertion macros from
Tests/Support/TestUtils.h and a pair of CHECK_FILE_IS_OPEN /
CHECK_FILE_CLOSED macros local to the test file.

Static functions in the adapter follow the FatFsFile_* class-name
prefix convention (memory feedback_static_name_prefix), not bare
verbs like the older PosixFile precedent.

Storage size is set to sizeof(intptr_t) * 90 which fits the 64-bit
host TDD struct comfortably. The 32-bit FreeRTOS cross-compile sizing
is revisited at slice 5 when the BDD target first instantiates
storage on Cortex-M.

Refs #270.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds the remaining bulk-IO vtable slots to the FatFs adapter. After
this slice the SolidSyslogFile contract is fully implemented except
Exists / Delete (slice 4).

Mapping:
- Read(buf, count)    -> f_read(fp, buf, count, &br);
                         returns true iff result == FR_OK && br == count.
- Write(buf, count)   -> f_write(fp, buf, count, &bw); same shape.
- SeekTo(offset)      -> f_lseek(fp, offset).
- Size()              -> reads fp->obj.objsize via f_size macro.
                         The macro dereferences FIL state directly so the
                         FatFsFake captures the FIL pointer at f_open and
                         exposes FatFsFake_SetFileSize to populate it for
                         tests.
- Truncate()          -> truncates to zero length:
                         f_lseek(fp, 0); f_truncate(fp);
                         FatFs's f_truncate alone would truncate at the
                         current fptr, which doesn't match PosixFile's
                         "ftruncate(fd, 0)" semantics.

9 tests added (ZOMBIES order):
- TruncateSeeksToZeroAndCallsFTruncate
- SeekToCallsFLseekWithGivenOffset (triangulation built in)
- SizeReturnsFileObjectSize
- ReadCallsFReadWithCorrectDefaults  + partial + FRESULT failure
- WriteCallsFWriteWithCorrectDefaults + partial + FRESULT failure

Tests use new intent-naming CHECK_* macros:
CHECK_LSEEK_OFFSET, CHECK_READ_BUF, CHECK_READ_COUNT,
CHECK_WRITE_BUF, CHECK_WRITE_COUNT.

Production-side DRY: a Self() inline helper extracts the
(struct SolidSyslogFatFsFile*) cast that every vtable function needs;
READ_WRITE_OR_CREATE names the FA_READ | FA_WRITE | FA_OPEN_ALWAYS
flag combination passed to f_open.

FatFsFake state now grouped by FatFs function (f_open / f_close /
f_lseek / f_truncate / f_read / f_write) — each ff function has its
own state block and Reset() walks them top-down.

Refs #270.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…12.1

FatFsFile_Read and FatFsFile_Write returned

    return result == FR_OK && br == count;

MISRA C:2012 Rule 12.1 wants explicit precedence around comparisons
inside &&, so this becomes

    return (result == FR_OK) && (br == count);

CLAUDE.md's "no parens needed for plain && between bool-typed operands"
covers bool *variables* / bool-returning function calls; comparison
expressions are a different case and get parens.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Completes the SolidSyslogFile vtable on the FatFs adapter. Exists and
Delete are stateless with respect to the file handle — neither uses
self — so they wrap f_stat and f_unlink directly with the caller's
path.

- Exists(path)  -> f_stat(path, NULL) == FR_OK
                   NULL FILINFO is documented as supported by FatFs
                   when only the existence check is needed.
- Delete(path)  -> f_unlink(path) == FR_OK

6 tests added:
- ExistsCallsFStatAndReportsTrue        (call shape + path + CHECK_TRUE)
- ExistsUsesPassedPath                  (triangulation)
- ExistsReportsFalseWhenFStatFails      (FR_NO_FILE)
- DeleteCallsFUnlinkAndReportsTrue      (call shape + path + CHECK_TRUE)
- DeleteUsesPassedPath                  (triangulation)
- DeleteReportsFalseWhenFUnlinkFails    (FR_DENIED)

FatFsFake grows f_stat and f_unlink wrappers with the same shape as
the other ff functions (set-result + call-count + last-path).

After this slice, the full SolidSyslogFile contract is implemented on
FatFs. Slice 5 wires the BlockStore composition into the FreeRTOS BDD
target main.c and adds the QEMU semihosting diskio.c that makes real
FatFs run on the target.

Refs #270.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two extractions surfaced in the end-of-slice review pass:

1. Production — Fp(self) inline helper that returns &Self(self)->fp.
   Five vtable functions (Read, Write, SeekTo, Size, Truncate) need
   only the FIL*, not the wider SolidSyslogFatFsFile*. Replacing the
   per-function

       struct SolidSyslogFatFsFile* fatfs = Self(self);
       ... &fatfs->fp ...

   pair with Fp(self) drops one local variable per function and turns
   Size/SeekTo into true one-liners. Open and Close still use the
   wider Self() because they also touch isOpen.

2. Tests — CHECK_OPEN_PATH, CHECK_OPEN_MODE, CHECK_STAT_PATH,
   CHECK_UNLINK_PATH macros. Match the existing intent-naming pattern
   (CHECK_FILE_IS_OPEN / CHECK_LSEEK_OFFSET / CHECK_READ_BUF etc.) and
   replace eight bare STRCMP_EQUAL / LONGS_EQUAL invocations across
   the Open / Exists / Delete tests.

No behaviour change; all 24 tests / 55 checks stay green.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Wires FatFs into the FreeRTOS BDD target so `set store file` over the
UART tears down NullStore and rebuilds SolidSyslog with FatFsFile +
FileBlockDevice + BlockStore on top of a semihosting-backed FAT image.

Integrator glue under Bdd/Targets/FreeRtos/:
- ffconf.h with FF_FS_REENTRANT=1, FF_USE_MKFS=1, FF_VOLUMES=1, FF_FS_NORTC=1
- ffsystem.c vendored from upstream with OS_TYPE 3 (FreeRTOS,
  xSemaphoreCreateMutex). FF_FS_REENTRANT is on even though only the
  Service task touches the store today — a future reentrancy stress
  test exercises the production lock path rather than a no-op
- diskio.c implements disk_initialize/status/read/write/ioctl via
  BKPT 0xAB semihosting traps against solidsyslog-disk.img (~1 MiB
  sparse, 2048 x 512 B). disk_initialize creates the image on first
  open so f_mount returns FR_NO_FILESYSTEM and FatFs falls through to
  f_mkfs

CMake stages ff.c / ff.h / diskio.h alongside our ffconf.h in the
build dir via configure_file, so ff.h's `#include "ffconf.h"` resolves
to our integrator config — works around GCC's "current file's
directory first" rule without vendoring 7000+ lines of upstream FatFs
into the repo.

main.c lifts SolidSyslog config + state to file scope and adds a
SolidSyslogMutex around the Service task vs the rebuild path. The new
`set` keys max-blocks / max-block-size / discard-policy / halt-exit
update pending globals; `set store file` is the rebuild trigger.
Halt-exit fires SYS_EXIT (0x18) via semihosting to terminate QEMU
deterministically, mirroring Linux's _exit(2).

target_driver.py adds five flag translations, sorts emission so
`--store` lands last, supplies `-semihosting-config enable=on,
target=native` to QEMU, and synthesises a `"1"` value for bare
`--halt-exit` so the UART NAME VALUE protocol stays honest.
environment.py removes solidsyslog-disk.img in after_scenario.

SOLIDSYSLOG_FATFS_FILE_SIZE bumped to sizeof(intptr_t) * 180 — real
FatFs FIL with FF_MAX_SS=512 + FF_FS_TINY=0 is ~620 B, the previous
90-intptr constant was tuned to the smaller fake FIL. Tunable
follow-up tracked for S21.03 under E21.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Lights up store_and_forward / power_cycle_replay / store_capacity on
the FreeRTOS BDD target. The slice-5 wiring landed BlockStore plumbing
but every Store_Write was silently a no-op: FatFs requires an explicit
f_mount before any file operation, and slice-5 relied on a non-existent
auto-mount. With NullStore destroyed and the file-backed BlockStore
unable to write, every message fell through DrainBufferIntoStore's
direct-send fallback — fine while the oracle was up (first scenario
message reached it) but lossy as soon as the oracle went down (the
other 5 scenarios saw "1 of N" deliveries).

Fix: RebuildWithFileStore now calls EnsureFatFsMounted() before tearing
down the existing store. f_mount runs with opt=1 to surface
FR_NO_FILESYSTEM eagerly; on a fresh disk image f_mkfs lays down a
FAT12 volume and re-mounts. A mount failure leaves the target on its
original NullStore (zero-disruption) and the OnSet "store" branch
reports false so the harness surfaces the error rather than silently
degrading. Verified locally: `set store file` now leaves a 1 MiB image
with a real FAT12 boot sector (eb fe 90 MSDOS5.0, 55 aa at 510).

Also lands the wiring the three @store features need:
- target_driver.py: `--no-sd` joins the FreeRTOS translation table as
  a bare flag (synthetic "1" value), matching --halt-exit. store_capacity
  scenarios couple --no-sd with --store file so the sort order keeps
  --store last and the rebuild path picks up the final no-sd setting.
- main.c: `set no-sd N` flips g_pendingNoSd; both initial Setup and
  RebuildWithFileStore re-honour it via sdCount = 1 (just metaSd) or
  3 (full SD list).
- ci/docker-compose.bdd.yml: `not @store` lifted from behave-freertos
  so store_and_forward, power_cycle_replay, and store_capacity all run.
  capacity_threshold stays out via per-feature @freertoswip until the
  threshold-marker plumbing has a semihosting equivalent.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Locally the slice-6 f_mount path creates a valid FAT12 disk image and
the first `send 1` succeeds. In David's WSL docker compose run none of
that happens — no disk image is left after a scenario and every first-
message step fails (0 of 1 received), which is a regression from the
previous run where the first message at least reached the oracle via
direct-send fallback. Need visibility into the FreeRTOS guest's UART
output to find the divergence.

Adds:
- [fatfs] traces in main.c around f_mount / f_mkfs (entry, return codes,
  mount-complete confirmation).
- [diskio] traces in diskio.c around disk_initialize, the semihosting
  open(r+b) / open(w+b) handle returns, and Flen result.
- target_driver.py: route QEMU stderr to the parent process's stderr on
  FreeRTOS so semihosting / QEMU runtime errors surface in docker
  compose's behave output.
- syslog_steps.py: tee the FreeRTOS guest's UART output (stdout) into a
  16 KB sliding buffer maintained by the existing reader thread.
- environment.py after_step hook: on step failure, dump that buffer to
  the behave log so we see the [fatfs] / [diskio] / SolidSyslog ErrorHandler
  lines that ran during the failing step.

Once the docker run reveals the divergence, the diagnostic printfs and
target_driver stderr passthrough get reverted; the after_step buffer
dump is general enough to keep.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…no()

The slice-6 diagnostic patch routed QEMU stderr to sys.stderr on the
FreeRTOS target so semihosting errors would surface in docker compose
output. behave's runner wraps sys.stderr with an internal stream that
doesn't support fileno(), so subprocess.Popen raised
io.UnsupportedOperation on every spawn — breaking all FreeRTOS scenarios,
not just the failing @store ones.

Revert to stderr=subprocess.PIPE everywhere. The sliding-buffer +
after_step dump (added in the same diagnostic chore) still gives us
visibility into [fatfs] / [diskio] / [solidsyslog] guest UART output
on step failure, which is the actual diagnostic we want.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Bundles the major CodeRabbit finding fix, three minor cleanups, and the
focused FatFs self-test David suggested to isolate the docker BDD
regression.

ServiceTask race on teardown (CodeRabbit major, also the slice-5
smoke-test hard-fault root cause): InteractiveTask's quit-time teardown
destroyed g_lifecycleMutex and NULLed the pointer; ServiceTask's next
iteration unconditionally locked it, dereferencing NULL or use-after-
free freed kernel state — `qemu: fatal: Lockup` on every quit.

Fix: add a g_solidSyslogTeardown flag, set inside the lifecycle-mutex
critical section so Service observes it atomically with the SolidSyslog
destroy. Service unlocks, vTaskDelete(NULL) before any further mutex
access. Teardown then vTaskDelay(20ms) — generous against Service's
worst-case iteration — before destroying the mutex. Local smoke test:
`quit` now prints the stack-hwm report and exits cleanly with no
HardFault escalation.

Three minor CodeRabbit findings:
- main.c: `set store null` returns true unconditionally now (was
  !g_currentStoreIsFile, asymmetric truth value). The harness always
  emits --store last so the rebuild ordering is the real guard.
- environment.py: hoist `import sys` to the module top-level block
  (was repeated inside after_step).
- target_driver.py: apply_extra_args fails fast when a key/value flag
  is followed by another flag instead of a value — `--facility
  --severity 6` would have silently treated `--severity` as facility's
  value.

FatFs self-test (David's suggestion in lieu of broader instrumentation):
runs the exact sequence BlockStore + FileBlockDevice will use —
open(W), write, close, stat-exists, open(R), read, close, unlink,
stat-not-exists — immediately after EnsureFatFsMounted reports
success. Each step prints [fatfs-test] with its FRESULT. Locally all
nine steps return 0 / FR_NO_FILE=4 as expected. If docker shows any
non-zero, we have the exact failing operation. If docker shows the
same all-pass, the FatFs layer is sound and the regression lives
above it (BlockStore / Sender / Service).

Diagnostic printfs from the prior chore commit stay in place pending
the docker investigation; cleanup is tracked.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The OBJECT lib (containing ff.c + our ffsystem.c) compiles against
FATFS_STAGE_DIR, where our integrator ffconf.h is colocated with the
staged ff.h. But the executable target (main.c, diskio.c,
SolidSyslogFatFsFile.c) pointed at ${FATFS_PATH}/source. When those
.c files include "ff.h" from that path, ff.h's `#include "ffconf.h"`
resolves to upstream's sample in the same directory before searching
-I paths — so the executable's translation units saw FF_FS_REENTRANT=0
(upstream default) while ff.c saw FF_FS_REENTRANT=1 (ours).

Any ffconf.h-conditional layout (FF_FS_REENTRANT controls the sync
object, FF_USE_LFN controls lfnbuf, FF_MAX_SS vs FF_MIN_SS controls
ssize, etc.) would then differ between translation units, opening the
door to silent FATFS struct corruption. Locally the specific combo
happens not to trip an observable failure, but the divergence is wrong
on its face and is a plausible candidate for the docker BDD regression
(where the disk image is created and FAT is laid down correctly but
oracle receives 0 messages).

Fix: point the executable at FATFS_STAGE_DIR too, with a comment
explaining why it must mirror the OBJECT lib's include path. Local
smoke test re-verified: build clean, every FatFs self-test step
passes (return 0 / FR_NO_FILE=4 as expected), `quit` exits without
HardFault.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
… path

Suspected docker BDD regression root cause. FileBlockDevice
(Core/Source/SolidSyslogFileBlockDevice.c) caches readHandle and
writeHandle independently, each holding an underlying FIL open between
calls. When the store has a single active block (the common case
during a short outage burst), BOTH handles target the same path:
writeHandle has STORE00.LOG open from the first Append, readHandle
opens STORE00.LOG for the first SendOneFromStore read. With
FF_FS_LOCK=0 in ffconf.h, the FatFs manual explicitly warns:

  "0: Disable file lock function. To avoid volume corruption,
   application program should avoid illegal open, remove and rename
   to the open objects."

Two concurrent FILs on one path is "illegal open" — undefined
behaviour, can silently corrupt the volume's FAT chain. On POSIX (the
Linux BDD target) two fds on one path are fine; the FileBlockDevice
contract was designed against that, so the same dual-handle pattern
silently breaks on FatFs+FF_FS_LOCK=0 while passing on Linux.

Setting FF_FS_LOCK to a positive value enables FatFs's internal open-
file tracking, which permits multiple FILs on one path with correct
shared-write semantics. 4 gives headroom over the 2 FileBlockDevice
holds (in case rename/delete opens a third FIL transiently inside
f_unlink). Memory cost: ~12 B per slot = 48 B static, trivial against
our 96 KB heap.

Local smoke test (single-FIL self-test) unchanged — sequential ops
weren't hitting the lock path anyway. Validation comes from the BDD
@store scenarios which exercise FileBlockDevice's concurrent pattern.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
S08.05 slice 6 is paused pending a store-layer refactor that removes the
"two file handles open on the same path" requirement. See memory entry
project_s08_05_slice6_status for the full architectural finding and the
next-session brief.

This commit snapshots the slice-6-diagnostic state so the branch is
clean across context switches:

- Bdd/Targets/FreeRtos/ffconf.h: clang-format fix that was queued for
  the next slice-6-targeted commit. Standalone hygiene; not load-bearing.
- Bdd/Targets/FreeRtos/main.c: RunFatFsDualFilProbe — a boot-time probe
  that walks the FileBlockDevice dual-handle open pattern and prints
  FRESULT traces. Designed to surface FatFs's lock semantics (chk_share
  at ff.c:970 rejects 2nd write-mode open with FR_LOCKED) without BDD
  scaffolding. Keep as evidence; revert when slice 6 resumes and greens.
- Tests/Support/FatFsFakes/Source/FatFsFake.c: f_sync stub returning
  FR_OK so FatFsFake-linked unit tests stay green if a future iteration
  reintroduces f_sync calls. Harmless future-proofing.

When slice 6 resumes (post-refactor on main), revert this commit or
strip the probe + earlier [fatfs]/[diskio]/[fatfs-test] diagnostic
prints together as a cleanup commit.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…yslogFile* per S27.01

The S27.01 refactor (PR #354, on main) dropped FileBlockDevice's
readHandle/writeHandle pair in favour of a single handle. The Linux and
Windows BDD targets were updated at that time; the FreeRTOS target lived
on this branch and missed the cascade.

This commit cascades:
- Replaces g_storeReadFile / g_storeReadFileStorage / g_storeWriteFile /
  g_storeWriteFileStorage with a single g_storeFile / g_storeFileStorage.
- Updates SolidSyslogFileBlockDevice_Create and matching Destroys.
- Drops RunFatFsDualFilProbe and its caller — the probe walked
  FileBlockDevice's dual-FIL pattern to surface the FatFs FF_FS_LOCK
  rejection. With S27.01 the pattern is gone, the rejection can't
  happen, and the probe has nothing left to prove.

Cross-built clean against the freertos-cross preset.

FF_FS_LOCK=4 walk-back, [fatfs]/[diskio]/[fatfs-test] diagnostic-print
strip, and the FatFsFake f_sync stub are separate housekeeping commits
to follow.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The [fatfs] / [diskio] / [fatfs-test] traces and RunFatFsSelfTest were
instrumentation added during the S08.05 slice 6 pause to surface the
dual-FIL hazard root-caused in PR #354 (S27.01). With the refactor
merged and the FF_FS_LOCK=4 workaround walked back, the FatFs layer's
behaviour is no longer the suspect — the next iteration of slice-6
debugging is at the store-and-forward drain level, where this noise
just masks the interesting bits.

The [solidsyslog] mount-failed error line stays — that's a genuine
failure path the integrator wants to see.

Cross-built clean against the freertos-cross preset.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@Tests/Support/FatFsFakes/Source/FatFsFake.c`:
- Around line 203-212: The fake f_read reports the wrong number of bytes when
readBytesReturnedOverridden is false: it always sets *br = btr even if only
copyCount (min(btr, readSourceCount)) bytes were copied. Change the assignment
so that when readBytesReturnedOverridden is true you keep *br =
readBytesReturned, otherwise set *br = copyCount (where copyCount is the UINT
computed as (btr <= readSourceCount) ? btr : readSourceCount) so the caller sees
the actual number of bytes copied into buff; update references in f_read
(readBytesReturnedOverridden, readBytesReturned, btr, copyCount,
readSourceCount) accordingly.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 82767d8d-8a6f-4804-b5f0-d1f33fbf5035

📥 Commits

Reviewing files that changed from the base of the PR and between 32593c4 and b217a38.

📒 Files selected for processing (27)
  • .gitignore
  • Bdd/Targets/FreeRtos/CMakeLists.txt
  • Bdd/Targets/FreeRtos/diskio.c
  • Bdd/Targets/FreeRtos/ffconf.h
  • Bdd/Targets/FreeRtos/ffsystem.c
  • Bdd/Targets/FreeRtos/main.c
  • Bdd/features/capacity_threshold.feature
  • Bdd/features/environment.py
  • Bdd/features/steps/syslog_steps.py
  • Bdd/features/steps/target_driver.py
  • CMakeLists.txt
  • Platform/FatFs/CMakeLists.txt
  • Platform/FatFs/Interface/SolidSyslogFatFsFile.h
  • Platform/FatFs/Source/SolidSyslogFatFsFile.c
  • Platform/FreeRtos/Source/SolidSyslogFreeRtosTcpStream.c
  • Tests/CMakeLists.txt
  • Tests/FatFs/CMakeLists.txt
  • Tests/FatFs/SolidSyslogFatFsFileTest.cpp
  • Tests/FatFs/main.cpp
  • Tests/FreeRtos/SolidSyslogFreeRtosTcpStreamTest.cpp
  • Tests/Support/FatFsFakes/CMakeLists.txt
  • Tests/Support/FatFsFakes/Interface/FatFsFake.h
  • Tests/Support/FatFsFakes/Interface/ffconf.h
  • Tests/Support/FatFsFakes/Source/FatFsFake.c
  • Tests/Support/FreeRtosFakes/Interface/FreeRtosSocketsFake.h
  • Tests/Support/FreeRtosFakes/Source/FreeRtosSocketsFake.c
  • ci/docker-compose.bdd.yml
✅ Files skipped from review due to trivial changes (2)
  • Tests/Support/FatFsFakes/CMakeLists.txt
  • .gitignore
🚧 Files skipped from review as they are similar to previous changes (17)
  • CMakeLists.txt
  • Bdd/features/capacity_threshold.feature
  • Bdd/Targets/FreeRtos/ffconf.h
  • Tests/FatFs/main.cpp
  • Platform/FatFs/CMakeLists.txt
  • Platform/FatFs/Interface/SolidSyslogFatFsFile.h
  • Bdd/features/environment.py
  • Bdd/Targets/FreeRtos/CMakeLists.txt
  • Tests/CMakeLists.txt
  • Bdd/Targets/FreeRtos/ffsystem.c
  • Tests/FatFs/SolidSyslogFatFsFileTest.cpp
  • Bdd/features/steps/target_driver.py
  • Platform/FatFs/Source/SolidSyslogFatFsFile.c
  • Tests/FatFs/CMakeLists.txt
  • Bdd/Targets/FreeRtos/diskio.c
  • Bdd/Targets/FreeRtos/main.c
  • Tests/Support/FatFsFakes/Interface/ffconf.h

Comment thread Tests/Support/FatFsFakes/Source/FatFsFake.c
DavidCozens and others added 4 commits May 13, 2026 19:14
When SetReadSource programs fewer bytes than the caller requests, the
fake now sets *br = copyCount (bytes actually memcpy'd into buff) rather
than *br = btr (bytes requested). Production's
`(result == FR_OK) && (br == count)` partial-read check now sees the
short read instead of treating uninitialised buffer memory as valid data.

CodeRabbit catch on PR #352.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
after_scenario already removes solidsyslog-disk.img on the happy path,
but a Ctrl-C / OOM / debugger-detach during a prior run leaves the
image behind. diskio.c::DiskImageIsReady keeps any existing full-size
image, so a stale FAT with STORE*.log content carries silently into
the next scenario's mount. Idempotent FileNotFoundError-tolerant
remove in before_scenario is the belt to after_scenario's brace.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
CLAUDE.md bans Hungarian notation and member-variable prefixes; the
FreeRTOS BDD-target main.c had crept past the rule (~30 file-scope
identifiers). Pure rename, contained to Bdd/Targets/FreeRtos/main.c —
no other file references the old names.

testMessage takes the special-case name to avoid shadowing
ErrorHandler's `message` parameter; everything else just drops g_.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Previously, the `quit` command and `set shutdown 1` had two separate
exit paths. `quit` ran the full destroy chain (SolidSyslog + SDs +
counter + store + Buffer + Mutex + Senders + Streams + Datagram +
Resolver). `set shutdown 1` only ran a partial chain (SolidSyslog +
store + f_unmount), leaving the Senders / Streams / Datagram / Resolver
dangling — most importantly the TCP stream's open connection to
syslog-ng-freertos, which on SemihostingExit leaves the peer with an
ungracefully closed connection.

Single source of truth now: TeardownAll() does the full chain plus
f_unmount. Two callers:
  - `quit`: BddTargetInteractive_Run returns → TeardownAll → vTaskDelete
  - `set shutdown 1`: OnSet → TeardownAll → SemihostingExit(0)

The Setup-locals (resolver, datagram, tcpStream, tcpSender, buffer,
bufferMutex) become file-scope static so both entry points reach them.
No g_* prefix — convention CLAUDE.md mandates.

The standalone ShutdownGracefully is gone; DestroyCurrentStore remains
as a small helper shared by RebuildWithFileStore and TeardownAll.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor

☀️   Quality Summary

   🚦   build-linux-gcc: 100% successful (✔️ 1114 passed, 🙈 3 skipped)
   🚦   build-freertos-host-tdd: 100% successful (✔️ 1230 passed, 🙈 3 skipped)
   🚦   build-linux-clang: 100% successful (✔️ 1066 passed, 🙈 3 skipped)
   🚦   sanitize-linux-gcc: 100% successful (✔️ 1066 passed, 🙈 3 skipped)
   🚦   integration-linux-openssl: 100% successful (✔️ 9 passed)
   🚦   integration-windows-openssl: 100% successful (✔️ 9 passed)
   🚦   bdd-linux-syslog-ng: 94% successful (✔️ 46 passed, 🙈 3 skipped)
   🚦   bdd-windows-otel: 90% successful (✔️ 44 passed, 🙈 5 skipped)
   🚦   bdd-freertos-qemu: 65% successful (❌ 4 failed, ✔️ 32 passed, 🙈 13 skipped)
   🚦   build-windows-msvc: 100% successful (✔️ 978 passed, 🙈 1 skipped)
   🚦   build-linux-tunable-override: 100% successful (✔️ 1066 passed, 🙈 3 skipped)
   ⚠️   Clang-Tidy: No warnings
   ⚠️   CPPCheck: No warnings


Created by Quality Monitor v1.14.0 (#f3859fd). More details are shown in the GitHub Checks Result.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
Tests/Support/FatFsFakes/Source/FatFsFake.c (1)

130-165: ⚡ Quick win

Make f_close, f_lseek, and f_truncate results injectable for error-path tests.

These three hooks always return FR_OK, while other hooks are configurable. This limits coverage of adapter failure handling for close/seek/truncate paths.

Suggested diff
 /* f_close state */
 static int closeCallCount;
+static FRESULT closeResult;

 /* f_lseek state */
 static int     lseekCallCount;
 static FSIZE_t lastLseekOffset;
+static FRESULT lseekResult;

 /* f_truncate state */
 static int truncateCallCount;
+static FRESULT truncateResult;

 void FatFsFake_Reset(void)
 {
@@
     closeCallCount              = 0;
+    closeResult                 = FR_OK;
     lseekCallCount              = 0;
     lastLseekOffset             = 0;
+    lseekResult                 = FR_OK;
     truncateCallCount           = 0;
+    truncateResult              = FR_OK;
@@
 }
+
+void FatFsFake_SetCloseResult(FRESULT result)
+{
+    closeResult = result;
+}
@@
 FRESULT f_close(FIL* fp)
 {
     (void) fp;
     closeCallCount++;
-    return FR_OK;
+    return closeResult;
 }
+
+void FatFsFake_SetLseekResult(FRESULT result)
+{
+    lseekResult = result;
+}
@@
 FRESULT f_lseek(FIL* fp, FSIZE_t ofs)
 {
     (void) fp;
     lseekCallCount++;
     lastLseekOffset = ofs;
-    return FR_OK;
+    return lseekResult;
 }
+
+void FatFsFake_SetTruncateResult(FRESULT result)
+{
+    truncateResult = result;
+}
@@
 FRESULT f_truncate(FIL* fp)
 {
     (void) fp;
     truncateCallCount++;
-    return FR_OK;
+    return truncateResult;
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Tests/Support/FatFsFakes/Source/FatFsFake.c` around lines 130 - 165, The
functions f_close, f_lseek and f_truncate always return FR_OK which prevents
testing error paths; change them to return injectable result variables (e.g.,
closeResult, lseekResult, truncateResult) instead of the hardcoded FR_OK, update
f_lseek to still set lastLseekOffset and increment lseekCallCount but return
lseekResult, and add simple setter functions (e.g., FatFsFake_SetCloseResult,
FatFsFake_SetLseekResult, FatFsFake_SetTruncateResult) so tests can configure
failure values; reference the existing symbols closeCallCount, lseekCallCount,
truncateCallCount and lastLseekOffset when making these changes.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@Bdd/features/steps/target_driver.py`:
- Around line 197-201: The current guard in target_driver.py that raises
ValueError when value.startswith("-") (inside the extra_args/FreeRTOS parsing
where variables flag and value are checked) wrongly rejects legitimate
hyphen-prefixed values; change the predicate so it only treats value as “another
flag” when it matches a known flag token instead of any hyphen prefix—e.g.,
introduce or reuse a set/list of known FreeRTOS flags (e.g.
FREE_RTOS_KNOWN_FLAGS or known_flags) and replace the condition with a
membership check (or normalize to the flag name when handling assignments like
--foo=bar) so only values present in that known-flag set trigger the ValueError
while allowing other hyphen-prefixed values like "-hello".

---

Nitpick comments:
In `@Tests/Support/FatFsFakes/Source/FatFsFake.c`:
- Around line 130-165: The functions f_close, f_lseek and f_truncate always
return FR_OK which prevents testing error paths; change them to return
injectable result variables (e.g., closeResult, lseekResult, truncateResult)
instead of the hardcoded FR_OK, update f_lseek to still set lastLseekOffset and
increment lseekCallCount but return lseekResult, and add simple setter functions
(e.g., FatFsFake_SetCloseResult, FatFsFake_SetLseekResult,
FatFsFake_SetTruncateResult) so tests can configure failure values; reference
the existing symbols closeCallCount, lseekCallCount, truncateCallCount and
lastLseekOffset when making these changes.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: fa36df16-84e1-44c9-9592-6b5d3f42e397

📥 Commits

Reviewing files that changed from the base of the PR and between 32593c4 and 5d15667.

📒 Files selected for processing (27)
  • .gitignore
  • Bdd/Targets/FreeRtos/CMakeLists.txt
  • Bdd/Targets/FreeRtos/diskio.c
  • Bdd/Targets/FreeRtos/ffconf.h
  • Bdd/Targets/FreeRtos/ffsystem.c
  • Bdd/Targets/FreeRtos/main.c
  • Bdd/features/capacity_threshold.feature
  • Bdd/features/environment.py
  • Bdd/features/steps/syslog_steps.py
  • Bdd/features/steps/target_driver.py
  • CMakeLists.txt
  • Platform/FatFs/CMakeLists.txt
  • Platform/FatFs/Interface/SolidSyslogFatFsFile.h
  • Platform/FatFs/Source/SolidSyslogFatFsFile.c
  • Platform/FreeRtos/Source/SolidSyslogFreeRtosTcpStream.c
  • Tests/CMakeLists.txt
  • Tests/FatFs/CMakeLists.txt
  • Tests/FatFs/SolidSyslogFatFsFileTest.cpp
  • Tests/FatFs/main.cpp
  • Tests/FreeRtos/SolidSyslogFreeRtosTcpStreamTest.cpp
  • Tests/Support/FatFsFakes/CMakeLists.txt
  • Tests/Support/FatFsFakes/Interface/FatFsFake.h
  • Tests/Support/FatFsFakes/Interface/ffconf.h
  • Tests/Support/FatFsFakes/Source/FatFsFake.c
  • Tests/Support/FreeRtosFakes/Interface/FreeRtosSocketsFake.h
  • Tests/Support/FreeRtosFakes/Source/FreeRtosSocketsFake.c
  • ci/docker-compose.bdd.yml
✅ Files skipped from review due to trivial changes (4)
  • Bdd/features/capacity_threshold.feature
  • .gitignore
  • Tests/Support/FatFsFakes/CMakeLists.txt
  • Tests/Support/FatFsFakes/Interface/ffconf.h
🚧 Files skipped from review as they are similar to previous changes (15)
  • Tests/FatFs/main.cpp
  • Platform/FatFs/CMakeLists.txt
  • Platform/FatFs/Interface/SolidSyslogFatFsFile.h
  • Tests/CMakeLists.txt
  • Bdd/Targets/FreeRtos/ffconf.h
  • ci/docker-compose.bdd.yml
  • CMakeLists.txt
  • Tests/Support/FatFsFakes/Interface/FatFsFake.h
  • Tests/FatFs/CMakeLists.txt
  • Bdd/Targets/FreeRtos/ffsystem.c
  • Bdd/Targets/FreeRtos/CMakeLists.txt
  • Platform/FatFs/Source/SolidSyslogFatFsFile.c
  • Tests/FatFs/SolidSyslogFatFsFileTest.cpp
  • Bdd/Targets/FreeRtos/diskio.c
  • Bdd/Targets/FreeRtos/main.c

Comment thread Bdd/features/steps/target_driver.py Outdated
DavidCozens and others added 2 commits May 13, 2026 19:48
Mirrors the SolidSyslogFreeRtosDatagram ARP-prime that landed in
S08.03 slice 3b.1.5. Before slice 6's SO_RCVTIMEO fix, FreeRTOS_connect
defaulted to 50 s of patience and that absorbed the cold-start ARP
resolution delay invisibly; with the connect now bounded at 200 ms,
a cold first SYN gets dropped at the IP layer while ARP resolves and
the bounded timer expires before the retransmit-SYN cycle completes.

PrimeArpIfMissing runs before every connect: xIsIPInARPCache, on miss
fire FreeRTOS_OutputARPRequest then vTaskDelay 50 ms so the reply has
landed by the time SYN goes out. Symmetric with the Datagram path.

Hypothesis: this also explains why tcp_reconnect:7 and tcp_transport:9
regressed in CI between slice 6 baseline (8a23ac6) and slice 7's first
push (b217a38) — both single-shot cold-connect scenarios with no Service
retry to absorb the first dropped SYN. CI will confirm.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The next-token-is-another-flag guard rejected any value starting with
'-', which would refuse legitimate hyphen-prefixed values like
`--message -hello`. Match against _FREERTOS_SET_TRANSLATION instead so
only actual known flag tokens trigger the ValueError.

CodeRabbit catch on PR #352.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@DavidCozens

Copy link
Copy Markdown
Owner Author

CodeRabbit nit on Tests/Support/FatFsFakes/Source/FatFsFake.c:130-165 (injectable f_close/f_lseek/f_truncate results): deferred.

Production code (FatFsFile_Close, FatFsFile_SeekTo, FatFsFile_Truncate in Platform/FatFs/Source/SolidSyslogFatFsFile.c) currently doesn't observe those return values — Close is void in the public file interface, and SeekTo/Truncate discard the FRESULT. Adding fake-side error injection without a production-side observer is dead scaffolding. The right time to add the setters is when (or if) we tighten those vtable signatures to surface those failure modes — happy to revisit then.

@github-actions

Copy link
Copy Markdown
Contributor

☀️   Quality Summary

   🚦   build-linux-gcc: 100% successful (✔️ 1114 passed, 🙈 3 skipped)
   🚦   build-freertos-host-tdd: 100% successful (✔️ 1234 passed, 🙈 3 skipped)
   🚦   build-linux-clang: 100% successful (✔️ 1066 passed, 🙈 3 skipped)
   🚦   sanitize-linux-gcc: 100% successful (✔️ 1066 passed, 🙈 3 skipped)
   🚦   integration-linux-openssl: 100% successful (✔️ 9 passed)
   🚦   integration-windows-openssl: 100% successful (✔️ 9 passed)
   🚦   bdd-linux-syslog-ng: 94% successful (✔️ 46 passed, 🙈 3 skipped)
   🚦   bdd-windows-otel: 90% successful (✔️ 44 passed, 🙈 5 skipped)
   🚦   bdd-freertos-qemu: 69% successful (❌ 2 failed, ✔️ 34 passed, 🙈 13 skipped)
   🚦   build-windows-msvc: 100% successful (✔️ 978 passed, 🙈 1 skipped)
   🚦   build-linux-tunable-override: 100% successful (✔️ 1066 passed, 🙈 3 skipped)
   ⚠️   Clang-Tidy: No warnings
   ⚠️   CPPCheck: No warnings


Created by Quality Monitor v1.14.0 (#f3859fd). More details are shown in the GitHub Checks Result.

On AArch32 ARM Semihosting, SYS_EXIT (0x18) treats R1 as a *literal*
reason code, not a parameter-block pointer. Passing &{reason, status}
as R1 resolved to "unrecognised reason" → QEMU exit 1 regardless of
the status field. SYS_EXIT_EXTENDED (0x20) is the form that accepts
a {reason, subcode} parameter block and propagates subcode as the
exit status.

Caught by store_capacity.feature:37 — Halt stops asserts exit code 2
but kept seeing 1. CodeRabbit flagged this on the prior round as a
🔴 follow-up.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor

☀️   Quality Summary

   🚦   build-linux-gcc: 100% successful (✔️ 1114 passed, 🙈 3 skipped)
   🚦   build-freertos-host-tdd: 100% successful (✔️ 1234 passed, 🙈 3 skipped)
   🚦   build-linux-clang: 100% successful (✔️ 1066 passed, 🙈 3 skipped)
   🚦   sanitize-linux-gcc: 100% successful (✔️ 1066 passed, 🙈 3 skipped)
   🚦   integration-linux-openssl: 100% successful (✔️ 9 passed)
   🚦   integration-windows-openssl: 100% successful (✔️ 9 passed)
   🚦   bdd-linux-syslog-ng: 94% successful (✔️ 46 passed, 🙈 3 skipped)
   🚦   bdd-windows-otel: 90% successful (✔️ 44 passed, 🙈 5 skipped)
   🚦   bdd-freertos-qemu: 71% successful (❌ 1 failed, ✔️ 35 passed, 🙈 13 skipped)
   🚦   build-windows-msvc: 100% successful (✔️ 978 passed, 🙈 1 skipped)
   🚦   build-linux-tunable-override: 100% successful (✔️ 1066 passed, 🙈 3 skipped)
   ⚠️   Clang-Tidy: No warnings
   ⚠️   CPPCheck: No warnings


Created by Quality Monitor v1.14.0 (#f3859fd). More details are shown in the GitHub Checks Result.

DavidCozens and others added 3 commits May 13, 2026 21:16
Host-side integration test over the real BlockStore + BlockSequence +
RecordStore + FileBlockDevice stack with FileFake at the bottom. Motivated
by the discard-newest BDD failure on freertos-cross — oracle saw
[1, 11, 2, 3, 4, 5, 6] which on its face looks like a BlockStore drain
ordering bug.

The first reproducer test in this harness (Outage drain produces ascending
sequenceIds) PASSES — so BlockStore alone isn't the culprit. That moves
the search up one layer to the SolidSyslog_Service drain algorithm
(Buffer -> Store -> Sender) which is where the [1, 11, 2..] interleave
actually arises. Follow-up commits will add a Service-level test and the
fix.

The harness drives the Store interface directly (Write / HasUnsent /
ReadNextUnsent / MarkSent), parameterised by maxBlocks / maxBlockSize /
payloadSize / discardPolicy via a DrainTestConfig struct so future tests
can sweep configurations without rewriting the fixture.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Wires BufferFake-replacement (real CircularBuffer + NullMutex) + real
BlockStore + a local SenderSpy with sticky outage mode to drive
SolidSyslog_Service through the exact BDD discard-newest scenario shape
at unit-test speed.

When TEST'd (not IGNORE_TEST'd), the assertion fires at:
  Send order descended: ids[2]=2 after ids[1]=11

Reproducing the BDD failure host-side: oracle log [1, 11, 2, 3] —
sequenceId 11 (newest, queued in CircularBuffer at the moment of
oracle recovery) bypasses the older stored 2, 3, ... via
DrainBufferIntoStore's fallback-to-Sender_Send path
(Core/Source/SolidSyslog.c:269-272). That path is correct for
NullStore configurations but breaks the discard-newest promise on
BlockStore: "newest discarded" turns into "newest delivered first."

Marked IGNORE_TEST so CI stays green until the next commit lands the
fix (Store_IsTransient vtable method gating the fallthrough). The
next commit flips this to TEST and the assertion goes from RED to
GREEN.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds Store_IsTransient to the Store vtable, distinguishing stores that
never retain (NullStore, NilStore) from those that retain durably
(BlockStore). Service's DrainBufferIntoStore now gates its fallthrough-
to-Sender_Send on transience: only transient stores can bypass to the
sender on Write rejection.

This fixes the BDD discard-newest failure on freertos-cross. The
previous Service algorithm fell through to direct-send whenever
Store_Write returned false. On BlockStore in discard-newest mode this
meant: when the store filled and rejected the *newest* buffered message,
the message escaped via direct send the instant the oracle recovered —
bypassing the older retained records that had been queued during the
outage. Oracle saw [1, 11, 2, 3, 4, 5, 6] when it should have seen
[1, 2, 3, 4, 5, 6] (with 11 discarded by the discard-newest policy).

The host-side reproducer test in the previous commit (RED, IGNORE_TEST)
is now flipped to TEST and goes green: successful-send order is
[1, 2, 3] — no bypass.

Vtable additions:
- NullStore: IsTransient = true (matches its "never retain" contract).
- NilStore (internal fallback before Create / on NULL config.store):
  IsTransient = true (same role).
- BlockStore: IsTransient = false (rejection is the discard policy
  speaking; messages do not escape).
- StoreFake (test): IsTransient = false (models a real store).

The existing test ServiceSendsDirectlyWhenStoreWriteFails pinned the
old fallthrough-on-StoreFake-rejection behaviour. Renamed and updated to
ServiceDoesNotBypassToSenderWhenNonTransientStoreRejectsWrite, asserting
the new contract. NullStore-side fallthrough is still covered by
ServiceSendsBufferedMessageWithNullStore.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor

☀️   Quality Summary

   🚦   build-linux-gcc: 100% successful (✔️ 1116 passed, 🙈 3 skipped)
   🚦   build-freertos-host-tdd: 100% successful (✔️ 1236 passed, 🙈 3 skipped)
   🚦   build-linux-clang: 100% successful (✔️ 1068 passed, 🙈 3 skipped)
   🚦   sanitize-linux-gcc: 100% successful (✔️ 1068 passed, 🙈 3 skipped)
   🚦   integration-linux-openssl: 100% successful (✔️ 9 passed)
   🚦   integration-windows-openssl: 100% successful (✔️ 9 passed)
   🚦   bdd-linux-syslog-ng: 94% successful (✔️ 46 passed, 🙈 3 skipped)
   🚦   bdd-windows-otel: 90% successful (✔️ 44 passed, 🙈 5 skipped)
   🚦   bdd-freertos-qemu: 73% successful (✔️ 36 passed, 🙈 13 skipped)
   🚦   build-windows-msvc: 100% successful (✔️ 980 passed, 🙈 1 skipped)
   🚦   build-linux-tunable-override: 100% successful (❌ 1 failed, ✔️ 1067 passed, 🙈 3 skipped)
   ⚠️   Clang-Tidy: 16 warnings (high: 16)
   ⚠️   CPPCheck: No warnings


Created by Quality Monitor v1.14.0 (#f3859fd). More details are shown in the GitHub Checks Result.

- Add [[nodiscard]] + const-qualify WriteMessage / DrainOne / DrainAll;
  the test methods are pure observers of fixture state.
- NOLINTNEXTLINE for the SenderSpy reinterpret_cast (vtable downcast
  pattern), the swappable {sequenceId, payloadSize} pair (distinct
  concepts, both numeric is incidental), and the snprintf calls used
  to build CppUTest FAIL messages.
- Drop the diagnostic printfs that were useful during bug investigation
  but added vararg-call surface for tidy without buying anything for
  the persisted test.
- Derive payloadSize from SOLIDSYSLOG_MAX_MESSAGE_SIZE so the Service-
  level reproducer overflows the store on both default (MAX=2048) and
  tunable-override (MAX=512) builds — the test pins drain ordering, not
  a fixed byte count.
- Trim <string.h>, add <cstdio> for snprintf — IWYU hygiene.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor

☀️   Quality Summary

   🚦   build-linux-gcc: 100% successful (✔️ 1116 passed, 🙈 3 skipped)
   🚦   build-freertos-host-tdd: 100% successful (✔️ 1236 passed, 🙈 3 skipped)
   🚦   build-linux-clang: 100% successful (✔️ 1068 passed, 🙈 3 skipped)
   🚦   sanitize-linux-gcc: 100% successful (✔️ 1068 passed, 🙈 3 skipped)
   🚦   integration-linux-openssl: 100% successful (✔️ 9 passed)
   🚦   integration-windows-openssl: 100% successful (✔️ 9 passed)
   🚦   bdd-linux-syslog-ng: 94% successful (✔️ 46 passed, 🙈 3 skipped)
   🚦   bdd-windows-otel: 90% successful (✔️ 44 passed, 🙈 5 skipped)
   🚦   bdd-freertos-qemu: 73% successful (✔️ 36 passed, 🙈 13 skipped)
   🚦   build-windows-msvc: 100% successful (✔️ 980 passed, 🙈 1 skipped)
   🚦   build-linux-tunable-override: 100% successful (✔️ 1068 passed, 🙈 3 skipped)
   ⚠️   Clang-Tidy: No warnings
   ⚠️   CPPCheck: No warnings


Created by Quality Monitor v1.14.0 (#f3859fd). More details are shown in the GitHub Checks Result.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (3)
Bdd/features/steps/syslog_steps.py (1)

1124-1149: ⚡ Quick win

Graceful shutdown logic is sound, but consider hardening the kill fallback.

The FreeRTOS shutdown sequence correctly attempts graceful termination via set shutdown 1 before falling back to kill(). However, the second process.wait(timeout=5) after process.kill() at line 1143 is not wrapped in exception handling. While SIGKILL should terminate immediately, an uncaught TimeoutExpired here would propagate and potentially leave context.interactive_process alive.

🛡️ Optional hardening for the kill fallback
         except subprocess.TimeoutExpired:
             process.kill()
-            process.wait(timeout=5)
+            try:
+                process.wait(timeout=5)
+            except subprocess.TimeoutExpired:
+                # Kill should terminate immediately; if we still timeout, log and proceed
+                pass
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Bdd/features/steps/syslog_steps.py` around lines 1124 - 1149, The second
process.wait(timeout=5) after calling process.kill() (operating on
context.interactive_process) can raise subprocess.TimeoutExpired and is not
caught; wrap that wait in a try/except for subprocess.TimeoutExpired and handle
it (e.g., call process.kill() again or process.kill() + process.terminate()
fallback, optionally log the failure) to ensure the process is cleaned up and
the subsequent del context.interactive_process always runs; update the FreeRTOS
branch around the process.kill() / process.wait sequence to catch TimeoutExpired
and proceed safely.
Core/Source/SolidSyslogStore.c (1)

42-45: ⚡ Quick win

Add a conservative fallback when IsTransient is not wired.

Line 44 assumes every integrator-provided store vtable already sets IsTransient. A legacy/custom store missing this field can crash on the first write rejection path. Defaulting to false keeps ordering-safe behavior.

Proposed patch
 bool SolidSyslogStore_IsTransient(struct SolidSyslogStore* store)
 {
-    return store->IsTransient(store);
+    if (store->IsTransient == NULL)
+    {
+        return false;
+    }
+    return store->IsTransient(store);
 }

As per coding guidelines, Core/Interface/**/*.h: Tier 1 public headers are a stable API boundary with the highest review bar.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Core/Source/SolidSyslogStore.c` around lines 42 - 45,
SolidSyslogStore_IsTransient currently blindly calls the integrator-provided
vtable entry and can crash if the IsTransient function pointer is NULL; update
SolidSyslogStore_IsTransient to check the store->IsTransient pointer on the
SolidSyslogStore struct and return false as a conservative fallback when it is
NULL, otherwise invoke the function pointer normally to preserve current
behavior.
Tests/SolidSyslogBlockStoreDrainOrderingTest.cpp (1)

323-332: ⚡ Quick win

Replace duplicated manual ordering checks with a shared CHECK_* macro.

The same assertion shape is hand-implemented twice using if (...) FAIL(...). Use a macro once and reuse in both tests to reduce duplication and failure-message drift. As per coding guidelines, Test code: use CHECK_* macros for repeated assertion shapes; implement as macros (not functions) with // NOLINTBEGIN wrapper and do { ... } while(0) for single-statement safety.

Also applies to: 367-376

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Tests/SolidSyslogBlockStoreDrainOrderingTest.cpp` around lines 323 - 332,
Replace the duplicated manual ordering checks (the for-loop that uses if (...)
FAIL(...) with snprintf) by defining a reusable test macro (e.g.,
CHECK_ASCENDING_ORDER(ids, count)) implemented as a macro (not a function)
wrapped with // NOLINTBEGIN/END and structured as do { ... } while(0) for
single-statement safety; the macro should iterate over the container (using the
same logic that checks ids[i] < ids[i-1]) and call FAIL(...) with the same
formatted message via snprintf when an out-of-order pair is found, then replace
both occurrences (the loop around ids and the duplicate at the later location)
with calls to the new macro to remove duplication and keep failure messages
consistent.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@Tests/SolidSyslogBlockStoreDrainOrderingTest.cpp`:
- Around line 109-185: Extract the duplicated storage/test fixture into a shared
TEST_GROUP_BASE (e.g., BlockStoreTestBase) that contains the members
fileStorage, file, deviceStorage, device, storeStorage, store, policy and the
setup()/teardown() implementations plus helper methods CreateStore(),
WriteMessage(), DrainOne(), and DrainAll(); then have
TEST_GROUP(BlockStoreDrainOrdering) (and the other TEST_GROUP at 198-280)
inherit from that base and remove the duplicated members and lifecycle code from
their bodies so both tests reuse the single centralized fixture.
- Around line 153-160: The WriteMessage helper writes sequence bytes into
buf[0..3] without validating payloadSize and there are corresponding decoding
sites that read payload[0..3] unchecked; add guards so both the writer and all
places that decode the sequence (the WriteMessage function and the
payload-decoding code paths in this test file that access payload[0..3]) first
verify payload size >= 4 (e.g., if payloadSize < 4 return false / fail the test
or skip parsing), and handle the error path deterministically to avoid OOB
access and flaky crashes.
- Around line 273-279: ServiceTickUntilQuiet can under-drain because it always
runs a fixed number of SolidSyslog_Service() ticks; change it to poll until the
syslog reports no more work and return a success flag so the test can assert
recovery completed. Specifically, modify ServiceTickUntilQuiet(size_t cap) to
call SolidSyslog_Service() in a loop up to cap but break early when a readiness
check (e.g., SolidSyslog_IsIdle() or SolidSyslog_HasPending()—use the
appropriate existing status function) indicates no pending work, return true if
idle reached and false on cap exhaustion, and update the test call to
ServiceTickUntilQuiet(10) to assert the returned value is true (or otherwise
verify progress/completion) instead of relying only on monotonic order.

---

Nitpick comments:
In `@Bdd/features/steps/syslog_steps.py`:
- Around line 1124-1149: The second process.wait(timeout=5) after calling
process.kill() (operating on context.interactive_process) can raise
subprocess.TimeoutExpired and is not caught; wrap that wait in a try/except for
subprocess.TimeoutExpired and handle it (e.g., call process.kill() again or
process.kill() + process.terminate() fallback, optionally log the failure) to
ensure the process is cleaned up and the subsequent del
context.interactive_process always runs; update the FreeRTOS branch around the
process.kill() / process.wait sequence to catch TimeoutExpired and proceed
safely.

In `@Core/Source/SolidSyslogStore.c`:
- Around line 42-45: SolidSyslogStore_IsTransient currently blindly calls the
integrator-provided vtable entry and can crash if the IsTransient function
pointer is NULL; update SolidSyslogStore_IsTransient to check the
store->IsTransient pointer on the SolidSyslogStore struct and return false as a
conservative fallback when it is NULL, otherwise invoke the function pointer
normally to preserve current behavior.

In `@Tests/SolidSyslogBlockStoreDrainOrderingTest.cpp`:
- Around line 323-332: Replace the duplicated manual ordering checks (the
for-loop that uses if (...) FAIL(...) with snprintf) by defining a reusable test
macro (e.g., CHECK_ASCENDING_ORDER(ids, count)) implemented as a macro (not a
function) wrapped with // NOLINTBEGIN/END and structured as do { ... } while(0)
for single-statement safety; the macro should iterate over the container (using
the same logic that checks ids[i] < ids[i-1]) and call FAIL(...) with the same
formatted message via snprintf when an out-of-order pair is found, then replace
both occurrences (the loop around ids and the duplicate at the later location)
with calls to the new macro to remove duplication and keep failure messages
consistent.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 3ad2d860-fecd-4163-ae93-1d4d8023c841

📥 Commits

Reviewing files that changed from the base of the PR and between 32593c4 and 5852ba3.

📒 Files selected for processing (37)
  • .gitignore
  • Bdd/Targets/FreeRtos/CMakeLists.txt
  • Bdd/Targets/FreeRtos/diskio.c
  • Bdd/Targets/FreeRtos/ffconf.h
  • Bdd/Targets/FreeRtos/ffsystem.c
  • Bdd/Targets/FreeRtos/main.c
  • Bdd/features/capacity_threshold.feature
  • Bdd/features/environment.py
  • Bdd/features/steps/syslog_steps.py
  • Bdd/features/steps/target_driver.py
  • CMakeLists.txt
  • Core/Interface/SolidSyslogStore.h
  • Core/Interface/SolidSyslogStoreDefinition.h
  • Core/Source/SolidSyslog.c
  • Core/Source/SolidSyslogBlockStore.c
  • Core/Source/SolidSyslogNullStore.c
  • Core/Source/SolidSyslogStore.c
  • Platform/FatFs/CMakeLists.txt
  • Platform/FatFs/Interface/SolidSyslogFatFsFile.h
  • Platform/FatFs/Source/SolidSyslogFatFsFile.c
  • Platform/FreeRtos/Source/SolidSyslogFreeRtosTcpStream.c
  • Tests/CMakeLists.txt
  • Tests/FatFs/CMakeLists.txt
  • Tests/FatFs/SolidSyslogFatFsFileTest.cpp
  • Tests/FatFs/main.cpp
  • Tests/FreeRtos/CMakeLists.txt
  • Tests/FreeRtos/SolidSyslogFreeRtosTcpStreamTest.cpp
  • Tests/SolidSyslogBlockStoreDrainOrderingTest.cpp
  • Tests/SolidSyslogTest.cpp
  • Tests/StoreFake.c
  • Tests/Support/FatFsFakes/CMakeLists.txt
  • Tests/Support/FatFsFakes/Interface/FatFsFake.h
  • Tests/Support/FatFsFakes/Interface/ffconf.h
  • Tests/Support/FatFsFakes/Source/FatFsFake.c
  • Tests/Support/FreeRtosFakes/Interface/FreeRtosSocketsFake.h
  • Tests/Support/FreeRtosFakes/Source/FreeRtosSocketsFake.c
  • ci/docker-compose.bdd.yml
✅ Files skipped from review due to trivial changes (4)
  • Tests/Support/FreeRtosFakes/Interface/FreeRtosSocketsFake.h
  • .gitignore
  • Tests/Support/FatFsFakes/CMakeLists.txt
  • Platform/FatFs/Interface/SolidSyslogFatFsFile.h
🚧 Files skipped from review as they are similar to previous changes (13)
  • CMakeLists.txt
  • Tests/FatFs/main.cpp
  • Tests/FatFs/CMakeLists.txt
  • Bdd/features/capacity_threshold.feature
  • Platform/FatFs/CMakeLists.txt
  • Tests/Support/FatFsFakes/Interface/ffconf.h
  • Tests/CMakeLists.txt
  • Bdd/Targets/FreeRtos/CMakeLists.txt
  • ci/docker-compose.bdd.yml
  • Bdd/Targets/FreeRtos/ffconf.h
  • Bdd/Targets/FreeRtos/diskio.c
  • Bdd/Targets/FreeRtos/ffsystem.c
  • Bdd/Targets/FreeRtos/main.c

Comment thread Tests/SolidSyslogBlockStoreDrainOrderingTest.cpp Outdated
Comment on lines +153 to +160
[[nodiscard]] bool WriteMessage(uint32_t sequenceId, size_t payloadSize) const
{
std::vector<uint8_t> buf(payloadSize, 'x');
buf[0] = static_cast<uint8_t>(sequenceId & 0xFFU);
buf[1] = static_cast<uint8_t>((sequenceId >> 8) & 0xFFU);
buf[2] = static_cast<uint8_t>((sequenceId >> 16) & 0xFFU);
buf[3] = static_cast<uint8_t>((sequenceId >> 24) & 0xFFU);
return SolidSyslogStore_Write(store, buf.data(), buf.size());

@coderabbitai coderabbitai Bot May 13, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Guard 4-byte sequence encoding/decoding preconditions.

Line 153 and Line 263 write buf[0..3], and Line 81 / Line 173 decode payload[0..3] without checking size. A future smaller payload/read will cause OOB access and flaky crashes.

Proposed fix
 [[nodiscard]] bool WriteMessage(uint32_t sequenceId, size_t payloadSize) const
 {
+    CHECK_TRUE_TEXT(payloadSize >= 4U, "payloadSize must be >= 4 to encode sequence id");
     std::vector<uint8_t> buf(payloadSize, 'x');
@@
 static uint32_t DecodeSequenceId(const std::vector<uint8_t>& payload)
 {
+    CHECK_TRUE_TEXT(payload.size() >= 4U, "payload must be >= 4 bytes");
     return static_cast<uint32_t>(payload[0]) | (static_cast<uint32_t>(payload[1]) << 8) | (static_cast<uint32_t>(payload[2]) << 16) |
            (static_cast<uint32_t>(payload[3]) << 24);
 }
@@
 [[nodiscard]] uint32_t DrainOne() const
 {
@@
     CHECK_TRUE(SolidSyslogStore_ReadNextUnsent(store, buf, sizeof(buf), &bytesRead));
+    CHECK_TRUE_TEXT(bytesRead >= 4U, "record must be >= 4 bytes to decode sequence id");
     SolidSyslogStore_MarkSent(store);
@@
 void Enqueue(uint32_t sequenceId, size_t payloadSize) const
 {
+    CHECK_TRUE_TEXT(payloadSize >= 4U, "payloadSize must be >= 4 to encode sequence id");
     std::vector<uint8_t> buf(payloadSize, 'x');

Also applies to: 263-270, 81-85, 169-174

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Tests/SolidSyslogBlockStoreDrainOrderingTest.cpp` around lines 153 - 160, The
WriteMessage helper writes sequence bytes into buf[0..3] without validating
payloadSize and there are corresponding decoding sites that read payload[0..3]
unchecked; add guards so both the writer and all places that decode the sequence
(the WriteMessage function and the payload-decoding code paths in this test file
that access payload[0..3]) first verify payload size >= 4 (e.g., if payloadSize
< 4 return false / fail the test or skip parsing), and handle the error path
deterministically to avoid OOB access and flaky crashes.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Declining. payloadSize in this fixture is derived from either SOLIDSYSLOG_MAX_MESSAGE_SIZE - 100 (>= 412 on the smallest tunable-override build) or hardcoded 64 — the < 4 scenario can't reach the writer. CLAUDE.md: "Don't add error handling, fallbacks, or validation for scenarios that can't happen. Trust internal code and framework guarantees." The guards add noise without buying real safety. If a future contributor genuinely shrinks the payload below 4 the existing buf[0..3] will assert/segfault on first run and surface the misconfiguration loudly.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@DavidCozens, understood — the constraint is fully explained by the derivation from SOLIDSYSLOG_MAX_MESSAGE_SIZE - 100 / hardcoded 64, and the CLAUDE.md guideline is clear. I'll defer to the project philosophy here.


✏️ Learnings added
Learnt from: DavidCozens
Repo: DavidCozens/solid-syslog PR: 352
File: Tests/SolidSyslogBlockStoreDrainOrderingTest.cpp:175-182
Timestamp: 2026-05-13T22:05:22.272Z
Learning: In the `solid-syslog` project, the CLAUDE.md guideline explicitly discourages adding defensive error handling or validation for scenarios that cannot happen given the existing constraints. For example, test fixture helpers like `WriteMessage` in `Tests/SolidSyslogBlockStoreDrainOrderingTest.cpp` intentionally omit payload-size guards because `payloadSize` is always derived from `SOLIDSYSLOG_MAX_MESSAGE_SIZE - 100` (>= 412) or hardcoded values (e.g., 64), making the < 4 case unreachable. Do not suggest adding such guards in this codebase.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

Comment thread Tests/SolidSyslogBlockStoreDrainOrderingTest.cpp
IWYU: drop unused SolidSyslogFile.h include from the test file — the
forward declaration that arrives via SolidSyslogFileBlockDevice.h is
enough since the test only holds the struct as an opaque pointer.

CodeRabbit accepts:
- Tests/SolidSyslogBlockStoreDrainOrderingTest.cpp: extract a shared
  DrainTestFixtureBase (TEST_BASE) holding the file / block-device /
  null-security-policy fixture; both TEST_GROUPs derive from it,
  eliminating ~14 lines of duplicated setup/teardown plumbing. Matches
  the existing BlockDeviceTestBase pattern in SolidSyslogBlockStoreTest.cpp.
- Tests/SolidSyslogBlockStoreDrainOrderingTest.cpp::
  DiscardNewestDoesNotLetNewestBypassOldestOnRecovery: assert
  !HasUnsent + successfulSends > 1 after the recovery drain window —
  catches under-drain (ServiceTickUntilQuiet's fixed cap exhausting
  before the store is empty) instead of silently passing on monotonic
  order alone.
- Bdd/features/steps/syslog_steps.py::step_client_is_killed: wrap the
  post-SIGKILL process.wait in try/except so a degenerate runtime that
  blocks even on SIGKILL doesn't leak context.interactive_process.

CodeRabbit declines (replied on threads):
- SolidSyslogStore_IsTransient NULL guard — CLAUDE.md "trust internal
  code; don't add fallbacks for scenarios that can't happen." None of
  the other Store_* wrappers guard their vtable methods; adding it for
  IsTransient alone would mask a wiring bug we want to surface loudly.
- 4-byte payload precondition CHECK_TRUE_TEXT guards — payloadSize is
  derived from SOLIDSYSLOG_MAX_MESSAGE_SIZE (>= 512) or hardcoded 64;
  the < 4 scenario can't reach the writer. Guards only add noise.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@DavidCozens

Copy link
Copy Markdown
Owner Author

@coderabbitai responses to the two review-body nitpicks:

Bdd/features/steps/syslog_steps.py:1124-1149 — kill fallback hardening: Accepted, fixed in eb2d909. The post-SIGKILL process.wait(timeout=5) is now wrapped in a try/except subprocess.TimeoutExpired so a degenerate runtime that ignores SIGKILL doesn't leak context.interactive_process. Suggested fix applied verbatim.

Core/Source/SolidSyslogStore.c:42-45IsTransient NULL guard: Declining. CLAUDE.md is explicit: "Don't add error handling, fallbacks, or validation for scenarios that can't happen. Trust internal code and framework guarantees. Only validate at system boundaries (user input, external APIs)."

The Store vtable is wired by integrator setup code, not user input. The other six SolidSyslogStore_* wrappers in the same file all dereference their vtable methods unconditionally; adding a guard for IsTransient alone would be inconsistent. A custom store that forgets to wire IsTransient is a wiring bug we want to surface immediately as a crash, not silently default to false and let the divergence propagate. The internal NilStore is already wired with IsTransient = true so the pre-Create / post-Destroy state is covered.

@coderabbitai

coderabbitai Bot commented May 13, 2026

Copy link
Copy Markdown
Contributor

Tip

For best results, initiate chat on the files or code changes.

@DavidCozens: Thanks for the detailed responses on both.

  1. syslog_steps.py — kill fallback hardening: Glad the fix landed cleanly in eb2d909. ✅

  2. SolidSyslogStore.cIsTransient NULL guard: Understood and agreed. The CLAUDE.md principle is clear and the consistency argument is compelling — guarding IsTransient alone while the other six wrappers dereference unconditionally would be worse than no guard at all. A misconfigured vtable should crash loudly at the wiring site, not silently default. Withdrawing the suggestion.


✏️ Learnings added
Learnt from: DavidCozens
URL: https://github.com/DavidCozens/solid-syslog/pull/352

Timestamp: 2026-05-13T22:05:44.121Z
Learning: In this codebase (solid-syslog), CLAUDE.md explicitly forbids adding error handling, fallbacks, or validation for scenarios that "can't happen". Internal vtable wrappers (e.g. in `Core/Source/SolidSyslogStore.c`) dereference function pointers unconditionally — a NULL vtable method is a wiring bug that should surface as an immediate crash, not be silently defaulted. Validation is only added at system boundaries (user input, external APIs).

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

@github-actions

Copy link
Copy Markdown
Contributor

☀️   Quality Summary

   🚦   build-linux-gcc: 100% successful (✔️ 1116 passed, 🙈 3 skipped)
   🚦   build-freertos-host-tdd: 100% successful (✔️ 1236 passed, 🙈 3 skipped)
   🚦   build-linux-clang: 100% successful (✔️ 1068 passed, 🙈 3 skipped)
   🚦   sanitize-linux-gcc: 100% successful (✔️ 1068 passed, 🙈 3 skipped)
   🚦   integration-linux-openssl: 100% successful (✔️ 9 passed)
   🚦   integration-windows-openssl: 100% successful (✔️ 9 passed)
   🚦   bdd-linux-syslog-ng: 94% successful (✔️ 46 passed, 🙈 3 skipped)
   🚦   bdd-windows-otel: 90% successful (✔️ 44 passed, 🙈 5 skipped)
   🚦   bdd-freertos-qemu: 73% successful (✔️ 36 passed, 🙈 13 skipped)
   🚦   build-windows-msvc: 100% successful (✔️ 980 passed, 🙈 1 skipped)
   🚦   build-linux-tunable-override: 100% successful (✔️ 1068 passed, 🙈 3 skipped)
   ⚠️   Clang-Tidy: No warnings
   ⚠️   CPPCheck: No warnings


Created by Quality Monitor v1.14.0 (#f3859fd). More details are shown in the GitHub Checks Result.

…ked docs

CLAUDE.md: add SolidSyslogFatFsFile.h row under Public header audiences
(ChaN FatFs adapter, f_sync per write, integrator-supplied diskio.c).

DEVLOG: full S08.05 entry — design decisions (FatFs over Plus-FAT,
Platform/FatFs/ peer pack, f_sync-per-write, graceful shutdown for
BDD power-cycle, unified TeardownAll, ARP-prime on TCP connect,
SYS_EXIT_EXTENDED for AArch32 status propagation, Store_IsTransient
to keep discard-newest honest), the host-side BlockStore drain-
ordering harness that pinned the [1, 11, 2, 3, ...] bug, ChaN
license-compatibility note (BSD-style ⊂ PolyForm Noncommercial
1.0.0 — preserve ChaN's notice in ff.c), deferred items.

README: FreeRTOS support paragraph now reflects UDP+TCP and FatFs
store-and-forward; add SolidSyslogFreeRtosTcpStream + SolidSyslogFatFsFile
to the platform-helpers list; FreeRTOS BDD target row mentions the
new `set store file` / `set shutdown 1` UART commands and the
store_and_forward / power_cycle_replay / store_capacity scenarios
that now run on QEMU.

Bdd/Targets/FreeRtos/README.md: dedicated section on persistent
store-and-forward via FatFs over semihosting — covers the BDD
diskio.c shape, the production-integrator handoff (own diskio.c +
ffsystem.c), and the graceful shutdown contract.

Bdd/README.md + docs/bdd.md: FreeRTOS local-run and per-runner tag
filters caught up to what `ci/docker-compose.bdd.yml` actually uses
(`not @wip and not @freertoswip and not @rtc and not @windows_wip
and (@udp or @tcp)`).

docs/containers.md: cpputest-freertos-cross row now mentions the
FreeRTOS-Kernel / Plus-TCP / FatFs source mounts that the cross
image carries.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor

☀️   Quality Summary

   🚦   build-linux-gcc: 100% successful (✔️ 1116 passed, 🙈 3 skipped)
   🚦   build-freertos-host-tdd: 100% successful (✔️ 1236 passed, 🙈 3 skipped)
   🚦   build-linux-clang: 100% successful (✔️ 1068 passed, 🙈 3 skipped)
   🚦   sanitize-linux-gcc: 100% successful (✔️ 1068 passed, 🙈 3 skipped)
   🚦   integration-linux-openssl: 100% successful (✔️ 9 passed)
   🚦   integration-windows-openssl: 100% successful (✔️ 9 passed)
   🚦   bdd-linux-syslog-ng: 94% successful (✔️ 46 passed, 🙈 3 skipped)
   🚦   bdd-windows-otel: 90% successful (✔️ 44 passed, 🙈 5 skipped)
   🚦   bdd-freertos-qemu: 73% successful (✔️ 36 passed, 🙈 13 skipped)
   🚦   build-windows-msvc: 100% successful (✔️ 980 passed, 🙈 1 skipped)
   🚦   build-linux-tunable-override: 100% successful (✔️ 1068 passed, 🙈 3 skipped)
   ⚠️   Clang-Tidy: No warnings
   ⚠️   CPPCheck: No warnings


Created by Quality Monitor v1.14.0 (#f3859fd). More details are shown in the GitHub Checks Result.

@DavidCozens DavidCozens merged commit 65ac026 into main May 13, 2026
20 checks passed
@DavidCozens DavidCozens deleted the feat/s08-05-store-and-forward-fatfs branch May 13, 2026 22:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

S08.05: Store-and-forward on FreeRTOS-FatFS

1 participant