# Fold assertion policy checkpoint Date: 2026-06-04 PBI: PBI-012 Task: assertion-invariant-hardening Owner: codex ## Research question How should Fold add C++ assertions to catch invariant drift in tests and benchmarks without turning recoverable scan/input failures into process aborts? ## Evidence table | Source | Confidence | Relevance | |---|---:|---| | C++ standard / cppreference `assert` behavior | High | Plain `assert` is disabled when `NDEBUG` is defined, so hard invariants should not rely on it in release-like builds. | | Fold `meson.build` | High | Fold defaults to `buildtype=debugoptimized` and `b_ndebug=if-release`; assertion behavior must be explicit rather than accidental. | | C++ Core Guidelines / GSL `Expects` and `Ensures` | Medium-high | Supports explicit precondition/postcondition vocabulary for programmer contracts. | | Herb Sutter GotW contracts series | Medium-high | Reinforces separating assertions/preconditions/postconditions from recoverable error handling. | | Repo assertion audit (`rg assert/CHECK/UNREACHABLE`) | High | Fold had no project-local assertion layer; existing assertion text was mostly SMT strings, Catch2 checks, and target-code `DEBUGASSERT` modeling. | ## Recommended assertion tiers - `FOLD_ASSERT(cond, msg)`: always-on hard invariant. Use for corruption, accounting underflow, impossible internal states, and UB-risk programmer bugs. - `FOLD_PRECONDITION(cond, msg)`: internal API contract. Use when callers inside Fold must pass a valid state/value. - `FOLD_POSTCONDITION(cond, msg)`: mutation/result contract. Use after state transitions and path/fact transformations. - `FOLD_DCHECK(cond, msg)`: debug/canary-only consistency check. Use for extra diagnostics that should not change release behavior. - `FOLD_UNREACHABLE(msg)`: impossible path only after real invariant reasoning. Do not use these for malformed user input, missing files, toolchain absence, unsupported syntax, solver timeout, or ordinary scan/report failures. ## Changes applied - Added header-only assertion surface: `include/fold/core/assert.hpp`. - Enabled `FOLD_ENABLE_DCHECK=1` whenever Meson is configured with `build-tests=true`, so Fold's test build path exercises debug checks in core code and test binaries. - Added explicit `-Denable-dcheck=true` Meson option for non-test canary/benchmark builds that should exercise `FOLD_DCHECK`. - Added signal-isolation death tests for `FOLD_ASSERT`, `FOLD_PRECONDITION`, and `FOLD_DCHECK` in `tests/unit/test_signal_isolation.cpp`. - Added boolean-path pre/postcondition checks to `src/symbolic/source_path_state.cpp`. - Added guarded-fact invariant checks to `src/symbolic/guarded_facts.cpp`. - Added P-Code encoder invariants for initialized boolean path state, bit-vector value coercion, boolean predicate outputs, and observer path readiness in `src/symbolic/pcode_encoder.cpp`. - Migrated a first detector/router invariant batch: - `src/cli/detector_plan.cpp`: enabled detector flags must match generated profile lists; security detector count cannot exceed total profiles. - `src/cli/scan_router.cpp`: router result must run at least one backend lane, record a non-empty mode, and keep merged finding counters consistent. ## Validation Passed focused gates: ```sh meson test -C build/release \ fold:source_path_state fold:guarded_facts fold:pcode_encoder \ fold:div_by_zero fold:null_deref fold:oob_access fold:scan_e2e \ --print-errorlogs meson test -C build/release \ fold:signal_isolation fold:source_path_state fold:guarded_facts \ fold:pcode_encoder fold:scan_e2e fold:subprocess_fanout \ --print-errorlogs git diff --check ``` Results: first gate `7/7 OK`, second gate `6/6 OK`, 0 failures. ## Remaining rollout items - Consider a small helper wrapper around `runWithSignalIsolation` for future assertion death tests to reduce boilerplate. - Continue migrating older unreachable/default cases after a broader detector/router audit, prioritizing lifecycle/counter/state-machine invariants over recoverable input failures.