Skip to content

Fix macOS + determinism CI: Release rejectSimKey guard; preemption-tolerant system timing tests - #46

Merged
offdev merged 1 commit into
masterfrom
fix/ci-master-detcheck-macos-cfg01
Sep 21, 2026
Merged

offdev merged 1 commit into
masterfrom
fix/ci-master-detcheck-macos-cfg01

Conversation

@offdev

@offdev offdev commented Sep 21, 2026

Copy link
Copy Markdown
Owner

Fixes the two red jobs on master after #45 (run 35603728019).

1. Determinism check — Build Release (g++) compile error

src/laige-sim/config.cpp:585: kConfigHotReloadRejectedMessage was not declared in scope. The M1-CFG-01 hot-reload helper rejectSimKey names a debug-only message constant (declared in config.h under #ifndef NDEBUG), but the helper itself was not guarded — so the Release build (the only Release build in CI, the detcheck job's pair B) failed to compile.

Fix: guard rejectSimKey with #ifndef NDEBUG. The Release poll/create paths return early and never reach the helper; compiling it out keeps both trees warning-free (unguarded it is a hard error; declared-but-unused it would trip -Wunused-function under -Werror).

2. macOS arm64 — flaky SystemTiming.OverBudgetSystemWarnsAtTheDocumentedMultiplier

The test assumed the 6.5 ms busy-wait burn stays under the 15 ms critical threshold (3× the 5 ms budget) — "the margin holds even under preemption". A shared-runner deschedule stretched the run past 3×, so the documented budget_critical ERROR fired alongside the warn and the exact-entry-count assertion failed (2 entries, expected 1). macOS Intel passed the same run by luck; the failure is preemption-driven, not arch-specific.

Key fact: preemption can only stretch a measured run, never shorten it. So the test now branches on the measured time:

  • nominal (< 15 ms): exactly one entry — the warn, with all the documented field checks (unchanged);
  • stretched (≥ 15 ms): the documented same-tick order — warn first, critical after.

Counters, rate-limit summaries, and entry counts are asserted per branch; every possible measured time maps to one consistent, documented state, so the test is immune to the preemption that failed it. The 1×/3× multiplier contract, warn-before-critical order, and LOG-004 rate limiting are all still asserted (the sibling CriticallyOverBudgetSystemErrorsAfterTheWarn pins the same-tick order unconditionally).

Same latent flake family, fixed in the same change:

  • EventsFollowTheErrorGrammar — its warn-only system's event sequence depended on whether preemption stretched it past 3×. Both systems now carry the 2 ms budget, so both event classes fire on every tick deterministically; the NFR-13.3 grammar is checked on the same two event classes, and the LOG-004 summaries are asserted per class.
  • HealthyTicksLogNothingAndTrackStats / HealthyTicksAllocateNothing — 1 ms budgets on microsecond noop ticks: a >1 ms preemption fires a warn and breaks the success-path silence assertion. Budgets raised to 100 ms — the shared-runner noise floor this repo already treats as negligible (the archetype churn's preemption-immunity rationale). lastMs upper bound moves from 1.0 to the 100.0 budget; the tracked-state assertions (runs, counters, window) are unchanged.

RollingWindowDropsOldestSamples keeps its 7 ms/50 ms/5 ms parameters: its core assertions (count/totalRecorded rollover) are already preemption-immune, and separating a 7 ms burn from preemption-stretched fast ticks is impossible at any finite threshold — noted as a residual, much lower-probability flake risk rather than re-engineered.

Validation

  • Full CI detcheck job replica green locally: all 4 build configs (reference Debug g++, clang++ Debug, Debug+ASan clang++, Release g++), reference baseline sanity (both backends), synthetic scenario self-check, pair A (g++ vs clang++ Debug) and pair B (Debug+ASan vs Release) — both fixed_point_16_16 and float_pinned_32.
  • laige-sim_tests full suite (88 ctest entries) green in the Debug tree.
  • The stretched branch exercised directly: scratch build with a forced 16 ms burn — both branch variants pass.
  • system_timing entry green in the clang++, ASan, and TSan trees (all warning-clean under -Wall -Werror).
  • Untested here: the macOS arm64/intel and MSVC lanes themselves (no such runners locally) — the changes are pure C++20 standard library code with no platform branches; CI will confirm on push.

… timing tests

Two red jobs on master after M1-CFG-01 (cf6c850):

1. Determinism check — 'Build Release (g++)' failed:
   src/laige-sim/config.cpp:585 — 'kConfigHotReloadRejectedMessage' was
   not declared in scope. rejectSimKey (the hot-reload rejection helper)
   names a debug-only message constant (config.h declares it under
   #ifndef NDEBUG) but the helper itself was not guarded, so the
   Release build (the only Release build in CI, the detcheck job's
   pair B) errored. Guard rejectSimKey with #ifndef NDEBUG: the Release
   poll/create paths return early and never reach the helper, and
   compiling it out keeps both trees warning-free (unguarded it is an
   error; declared-but-unused it would be -Wunused-function under
   -Werror).

2. macOS arm64 — SystemTiming.OverBudgetSystemWarnsAtTheDocumentedMultiplier
   failed: the test assumed the 6.5 ms busy-wait burn stays under the
   15 ms (3x the 5 ms budget) critical threshold — 'the margin holds
   even under preemption' — but a shared-runner deschedule stretched
   the run past 3x, so the documented budget_critical ERROR fired in
   addition to the warn and the exact-entry-count assertion failed
   (2 entries, expected 1). Preemption can only stretch a measured run,
   never shorten it, so the test now branches on the measured time:
   nominal (warn only) vs stretched (warn + same-tick critical), with
   the counters, rate-limit summaries, and entry counts asserted per
   branch. Every possible measured time maps to a consistent,
   documented state — the test is immune to the preemption that
   failed it.
   - EventsFollowTheErrorGrammar had the identical latent flake (its
     warn-only system's event sequence depended on whether preemption
     stretched it past 3x): both systems now carry the 2 ms budget so
     both event classes fire on every tick deterministically.
   - The two healthy-path tests' 1 ms budgets are the same flake family
     (a >1 ms preemption on a microsecond noop tick fires a warn and
     breaks the silence assertion): budgets raised to 100 ms, the
     shared-runner noise floor this repo already treats as negligible
     (the archetype churn's preemption-immunity rationale).

Validated: full CI detcheck job replica green locally (4 build configs:
reference Debug g++, clang++ Debug, Debug+ASan clang++, Release g++;
baseline sanity, synthetic self-check, pairs A and B both backends);
laige-sim_tests full suite green in the Debug tree; the stretched
branch exercised with a forced 16 ms burn (scratch build, both
branches' assertions pass); clang/ASan/TSan trees build and pass the
system_timing entry.
@offdev
offdev merged commit 27b1d1f into master Sep 21, 2026
11 checks passed
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.

1 participant