From 965e4b506b20b93f372828d0c044be08920af7be Mon Sep 17 00:00:00 2001 From: "Don Michael Feeney Jr." Date: Thu, 7 May 2026 07:55:18 -0400 Subject: [PATCH] Harden WNN DSM enforcement --- include/itl/itl_manager.hpp | 207 +++++++++++++++++- .../safety/deterministic_safety_monitor.hpp | 39 +++- src/raps/rollback_execution.hpp | 28 ++- tests/sil/CMakeLists.txt | 29 +++ tests/sil/test_dsm_wnn.cpp | 138 ++++++++++++ tests/sil/test_rollback_execution.cpp | 28 ++- 6 files changed, 442 insertions(+), 27 deletions(-) create mode 100644 tests/sil/test_dsm_wnn.cpp diff --git a/include/itl/itl_manager.hpp b/include/itl/itl_manager.hpp index 60aad54..e2af906 100644 --- a/include/itl/itl_manager.hpp +++ b/include/itl/itl_manager.hpp @@ -1,7 +1,9 @@ #pragma once +#include #include #include +#include #include "core/raps_definitions.hpp" #include "platform/platform_hal.hpp" @@ -30,28 +32,217 @@ class ITLManager { void init(); // Non-blocking commit (returns optimistic ID). - // Returns null_hash() if queue is full. + // Returns null_hash() if queue is full or persistence fails. Hash256 commit(const ITLEntry& entry); // Background processing (low-priority task) void flush_pending(); - // Log WNN rollback event - void log_wnn_rollback_event(double curvature, double prefactor); + // Log WNN rollback event. Returns true only when all ledger commits succeed. + bool log_wnn_rollback_event(double curvature, double prefactor); }; -inline void ITLManager::log_wnn_rollback_event(double curvature, double prefactor) { +namespace itl_manager_detail { + +inline size_t effective_payload_len(ITLEntry::Type type) { + switch (type) { + case ITLEntry::Type::STATE_SNAPSHOT: + return sizeof(ITLEntry::StateSnapshotPayload); + case ITLEntry::Type::PREDICTION_COMMIT: + return sizeof(ITLEntry::PredictionCommitPayload); + case ITLEntry::Type::ESE_ALERT: + return sizeof(ITLEntry::ESEAlertPayload); + case ITLEntry::Type::POLICY_PREFLIGHT: + return sizeof(ITLEntry::PolicyPreflightPayload); + case ITLEntry::Type::COMMAND_PENDING: + case ITLEntry::Type::EXECUTION_FAILURE: + case ITLEntry::Type::COMMAND_COMMIT: + case ITLEntry::Type::ROLLBACK_COMMIT: + return sizeof(ITLEntry::CommandExecutionPayload); + case ITLEntry::Type::ROLLBACK_METADATA: + return sizeof(ITLEntry::RollbackMetadataPayload); + case ITLEntry::Type::FALLBACK_TRIGGERED: + return sizeof(ITLEntry::FallbackTriggeredPayload); + case ITLEntry::Type::MERKLE_ANCHOR: + return sizeof(ITLEntry::MerkleAnchorPayload); + case ITLEntry::Type::GOVERNANCE_BUDGET_VIOLATION: + return sizeof(ITLEntry::GovernanceBudgetViolationPayload); + case ITLEntry::Type::NOMINAL_TRACE: + return sizeof(ITLEntry::NominalTracePayload); + case ITLEntry::Type::SUPERVISOR_EXCEPTION: + return sizeof(ITLEntry::SupervisorExceptionPayload); + case ITLEntry::Type::AILEE_SAFETY_STATUS: + return sizeof(ITLEntry::AileeSafetyStatusPayload); + case ITLEntry::Type::AILEE_GRACE_RESULT: + return sizeof(ITLEntry::AileeGraceResultPayload); + case ITLEntry::Type::AILEE_CONSENSUS_RESULT: + return sizeof(ITLEntry::AileeConsensusResultPayload); + case ITLEntry::Type::WNN_ALERT: + return sizeof(ITLEntry::WnnAlertPayload); + default: + return 0U; + } +} + +inline Hash256 compute_entry_id(const ITLEntry& entry, size_t payload_len) { + std::array buf{}; + + size_t offset = 0U; + std::memcpy(buf.data() + offset, &entry.type, sizeof(entry.type)); + offset += sizeof(entry.type); + std::memcpy(buf.data() + offset, &entry.timestamp_ms, sizeof(entry.timestamp_ms)); + offset += sizeof(entry.timestamp_ms); + if (payload_len > 0U) { + std::memcpy(buf.data() + offset, &entry.payload, payload_len); + offset += payload_len; + } + return PlatformHAL::sha256(buf.data(), offset); +} + +inline Hash256 compute_merkle_root(const Hash256* ids, size_t count) { + if (ids == nullptr || count == 0U) { + return Hash256::null_hash(); + } + if (count == 1U) { + return ids[0]; + } + + std::array current{}; + std::array next{}; + for (size_t i = 0U; i < count; ++i) { + current[i] = ids[i]; + } + + size_t current_count = count; + while (current_count > 1U) { + size_t next_count = 0U; + for (size_t i = 0U; i < current_count; i += 2U) { + const Hash256& left = current[i]; + const Hash256& right = (i + 1U < current_count) ? current[i + 1U] : left; + std::array combined{}; + std::memcpy(combined.data(), left.data, 32U); + std::memcpy(combined.data() + 32U, right.data, 32U); + next[next_count++] = PlatformHAL::sha256(combined.data(), combined.size()); + } + for (size_t i = 0U; i < next_count; ++i) { + current[i] = next[i]; + } + current_count = next_count; + } + + return current[0]; +} + +} // namespace itl_manager_detail + +inline void ITLManager::init() { + queue_head_ = 0U; + queue_tail_ = 0U; + queue_count_ = 0U; + merkle_count_ = 0U; + flash_write_cursor_ = 0U; + for (auto& entry : queue_) { + entry = ITLEntry{}; + } + for (auto& hash : merkle_buffer_) { + hash = Hash256::null_hash(); + } +} + +inline Hash256 ITLManager::commit(const ITLEntry& entry_template) { + if (queue_count_ >= RAPSConfig::ITL_QUEUE_SIZE) { + PlatformHAL::metric_emit("itl.commit_dropped", 1.0f, "reason", "queue_full"); + return Hash256::null_hash(); + } + + ITLEntry entry = entry_template; + if (entry.timestamp_ms == 0U) { + entry.timestamp_ms = PlatformHAL::now_ms(); + } + + const size_t payload_len = itl_manager_detail::effective_payload_len(entry.type); + if (payload_len > sizeof(ITLEntry::PayloadData)) { + PlatformHAL::metric_emit("itl.commit_dropped", 1.0f, "reason", "payload_len"); + return Hash256::null_hash(); + } + entry.payload_len = static_cast(payload_len); + entry.entry_id = itl_manager_detail::compute_entry_id(entry, payload_len); + + if (entry.entry_id.is_null()) { + PlatformHAL::metric_emit("itl.commit_dropped", 1.0f, "reason", "entry_hash"); + return Hash256::null_hash(); + } + + if (!PlatformHAL::flash_write(flash_write_cursor_, &entry, sizeof(entry))) { + PlatformHAL::metric_emit("itl.commit_dropped", 1.0f, "reason", "flash_write"); + return Hash256::null_hash(); + } + flash_write_cursor_ += static_cast(sizeof(entry)); + + queue_[queue_tail_] = entry; + queue_tail_ = (queue_tail_ + 1U) % RAPSConfig::ITL_QUEUE_SIZE; + ++queue_count_; + + merkle_buffer_[merkle_count_++] = entry.entry_id; + if (merkle_count_ >= RAPSConfig::MERKLE_BATCH_SIZE) { + process_merkle_batch(); + } + + return entry.entry_id; +} + +inline void ITLManager::process_merkle_batch() { + if (merkle_count_ == 0U) { + return; + } + + const Hash256 root = itl_manager_detail::compute_merkle_root( + merkle_buffer_, + merkle_count_ + ); + if (!root.is_null()) { + ITLEntry anchor{}; + anchor.type = ITLEntry::Type::MERKLE_ANCHOR; + anchor.timestamp_ms = PlatformHAL::now_ms(); + anchor.payload.merkle_anchor.merkle_root = root; + const size_t payload_len = itl_manager_detail::effective_payload_len(anchor.type); + anchor.payload_len = static_cast(payload_len); + anchor.entry_id = itl_manager_detail::compute_entry_id(anchor, payload_len); + if (!anchor.entry_id.is_null()) { + (void)PlatformHAL::flash_write(flash_write_cursor_, &anchor, sizeof(anchor)); + flash_write_cursor_ += static_cast(sizeof(anchor)); + } + } + + for (auto& hash : merkle_buffer_) { + hash = Hash256::null_hash(); + } + merkle_count_ = 0U; +} + +inline void ITLManager::flush_pending() { + process_merkle_batch(); + + while (queue_count_ > 0U) { + queue_[queue_head_] = ITLEntry{}; + queue_head_ = (queue_head_ + 1U) % RAPSConfig::ITL_QUEUE_SIZE; + --queue_count_; + } +} + +inline bool ITLManager::log_wnn_rollback_event(double curvature, double prefactor) { ITLEntry wnn_entry{}; wnn_entry.type = ITLEntry::Type::WNN_ALERT; wnn_entry.timestamp_ms = PlatformHAL::now_ms(); wnn_entry.payload.wnn_alert.curvature_proxy = curvature; wnn_entry.payload.wnn_alert.oscillatory_prefactor = prefactor; - commit(wnn_entry); + const Hash256 wnn_id = commit(wnn_entry); ITLEntry rollback_entry{}; rollback_entry.type = ITLEntry::Type::ROLLBACK_COMMIT; rollback_entry.timestamp_ms = PlatformHAL::now_ms(); - // Payload for rollback commit (CommandExecutionPayload) - // we just commit the entry to mark the rollback execution triggered by WNN - commit(rollback_entry); + const Hash256 rollback_id = commit(rollback_entry); + + return !wnn_id.is_null() && !rollback_id.is_null(); } diff --git a/include/raps/safety/deterministic_safety_monitor.hpp b/include/raps/safety/deterministic_safety_monitor.hpp index 1a40eb3..bf43fc3 100644 --- a/include/raps/safety/deterministic_safety_monitor.hpp +++ b/include/raps/safety/deterministic_safety_monitor.hpp @@ -1,6 +1,7 @@ #pragma once #include +#include #include #include @@ -31,13 +32,15 @@ constexpr double MIN_RESONANCE_AMPLITUDE_CUTOFF = 0.10; // WNN Constraints constexpr double WNN_MAX_CURVATURE_PROXY = 5.0e-11; constexpr double WNN_MIN_OSCILLATORY_PREFACTOR = 0.85; +constexpr double WNN_MAX_OSCILLATORY_PREFACTOR = 1.25; constexpr double INVALID_TELEMETRY_SENTINEL = -1.0; } // namespace DSM_Config struct WnnTelemetry { - double curvature_proxy; - double oscillatory_prefactor; + double curvature_proxy{0.0}; + double oscillatory_prefactor{1.0}; + uint32_t timestamp_ms{0U}; }; // ===================================================== @@ -128,15 +131,18 @@ DeterministicSafetyMonitor::hasInvalidWnnTelemetry( const WnnTelemetry& wnn_telem ) const { return !std::isfinite(wnn_telem.curvature_proxy) || - !std::isfinite(wnn_telem.oscillatory_prefactor); + !std::isfinite(wnn_telem.oscillatory_prefactor) || + wnn_telem.curvature_proxy < 0.0 || + wnn_telem.oscillatory_prefactor < 0.0; } inline bool DeterministicSafetyMonitor::isWnnThresholdBreached( const WnnTelemetry& wnn_telem ) const { - return wnn_telem.curvature_proxy > DSM_Config::WNN_MAX_CURVATURE_PROXY || - wnn_telem.oscillatory_prefactor < DSM_Config::WNN_MIN_OSCILLATORY_PREFACTOR; + return wnn_telem.curvature_proxy >= DSM_Config::WNN_MAX_CURVATURE_PROXY || + wnn_telem.oscillatory_prefactor <= DSM_Config::WNN_MIN_OSCILLATORY_PREFACTOR || + wnn_telem.oscillatory_prefactor >= DSM_Config::WNN_MAX_OSCILLATORY_PREFACTOR; } inline bool @@ -169,10 +175,11 @@ DeterministicSafetyMonitor::evaluateSafety( return ACTION_FULL_SHUTDOWN; } - double R_estimated = + const double R_estimated = estimateCurvatureScalar(inputs.measured_proper_time_dilation); + last_estimated_Rmax_ = R_estimated; - if (checkCurvatureViolation(R_estimated)) { + if (!std::isfinite(R_estimated) || checkCurvatureViolation(R_estimated)) { safing_sequence_active_ = true; std::cerr << "DSM ALERT: ABSOLUTE CURVATURE VIOLATION — FULL SHUTDOWN\n"; @@ -231,14 +238,26 @@ DeterministicSafetyMonitor::pollWnnAndEnforce( ? wnn_telem.oscillatory_prefactor : DSM_Config::INVALID_TELEMETRY_SENTINEL; - // Breach detected! Log to ITL and execute immediate rollback - itl_manager.log_wnn_rollback_event(logged_curvature, logged_prefactor); + // Breach detected! Log to ITL and execute immediate rollback. + const bool ledger_committed = + itl_manager.log_wnn_rollback_event(logged_curvature, logged_prefactor); + if (!ledger_committed) { + PlatformHAL::metric_emit( + "safety.wnn.ledger_commit_failed", + 1.0f + ); + } - return trigger_wnn_immediate_rollback( + const bool rollback_executed = trigger_wnn_immediate_rollback( rollback_store, rollback_count, active_state_pointer ); + PlatformHAL::metric_emit( + "safety.wnn.rollback_executed", + rollback_executed ? 1.0f : 0.0f + ); + return rollback_executed; } return false; // No breach } diff --git a/src/raps/rollback_execution.hpp b/src/raps/rollback_execution.hpp index acdd0c4..d41f67f 100644 --- a/src/raps/rollback_execution.hpp +++ b/src/raps/rollback_execution.hpp @@ -3,6 +3,7 @@ #include #include #include +#include #include "raps/core/raps_core_types.hpp" #include "platform/platform_hal.hpp" @@ -20,12 +21,13 @@ inline bool execute_rollback_plan( } // 2. Validate control inputs (Sanity Checks) - // Thrust cannot be negative - if (rollback.thrust_magnitude_kN < 0.0f) { + // Thrust must be finite and cannot be negative. + if (!std::isfinite(rollback.thrust_magnitude_kN) || + rollback.thrust_magnitude_kN < 0.0f) { return false; } - // Gimbal angles must be finite numbers + // Gimbal angles must be finite numbers. if (!std::isfinite(rollback.gimbal_theta_rad)) { return false; } @@ -53,14 +55,30 @@ inline bool trigger_wnn_immediate_rollback( uint32_t rollback_count, PhysicsState& active_state_pointer ) { - if (rollback_count == 0 || rollback_store == nullptr) { + if (rollback_count == 0U || rollback_store == nullptr) { return false; } - const RollbackPlan& latest_plan = rollback_store[rollback_count - 1]; + if (rollback_count > RAPSConfig::MAX_ROLLBACK_STORE) { + PlatformHAL::metric_emit( + "safety.wnn.rollback_rejected", + 1.0f, + "reason", + "rollback_count_oob" + ); + return false; + } + + const RollbackPlan& latest_plan = rollback_store[rollback_count - 1U]; std::string tx_id; if (!execute_rollback_plan(latest_plan, tx_id)) { + PlatformHAL::metric_emit( + "safety.wnn.rollback_rejected", + 1.0f, + "reason", + "plan_execution_failed" + ); return false; } diff --git a/tests/sil/CMakeLists.txt b/tests/sil/CMakeLists.txt index 417aae4..ea17858 100644 --- a/tests/sil/CMakeLists.txt +++ b/tests/sil/CMakeLists.txt @@ -163,3 +163,32 @@ add_test( NAME raps_sil_stability_test COMMAND raps_sil_stability_tests ) + +# ------------------------------------------------------------ +# DSM WNN Enforcement Tests +# ------------------------------------------------------------ +add_executable(raps_sil_dsm_wnn_tests + test_dsm_wnn.cpp + ../../src/platform/platform_hal.cpp +) + +target_include_directories(raps_sil_dsm_wnn_tests PRIVATE + ${PROJECT_SOURCE_DIR}/../../include + ${PROJECT_SOURCE_DIR}/../../src +) + +if (CMAKE_CXX_COMPILER_ID MATCHES "Clang|GNU") + target_compile_options(raps_sil_dsm_wnn_tests PRIVATE + -Wall + -Wextra + -Wpedantic + -Wshadow + -Wconversion + -Wno-unused-parameter + ) +endif() + +add_test( + NAME raps_sil_dsm_wnn_test + COMMAND raps_sil_dsm_wnn_tests +) diff --git a/tests/sil/test_dsm_wnn.cpp b/tests/sil/test_dsm_wnn.cpp new file mode 100644 index 0000000..6aa6be5 --- /dev/null +++ b/tests/sil/test_dsm_wnn.cpp @@ -0,0 +1,138 @@ +#include +#include +#include + +#include "raps/safety/deterministic_safety_monitor.hpp" +#include "itl/itl_manager.hpp" +#include "platform/platform_hal.hpp" + +static int g_failures = 0; + +static void expect_true(bool cond, const char* msg) { + if (!cond) { + ++g_failures; + std::cerr << "❌ " << msg << "\n"; + } else { + std::cout << "✅ " << msg << "\n"; + } +} + +static void expect_false(bool cond, const char* msg) { + expect_true(!cond, msg); +} + +static RollbackPlan make_valid_plan() { + RollbackPlan plan{}; + plan.valid = true; + plan.thrust_magnitude_kN = 10.0f; + plan.gimbal_theta_rad = 0.01f; + plan.gimbal_phi_rad = -0.01f; + return plan; +} + +static void test_nominal_wnn_is_observational() { + ITLManager itl; + itl.init(); + DeterministicSafetyMonitor dsm; + PhysicsState active_state{}; + RollbackPlan store[1] = {make_valid_plan()}; + + const bool rollback = dsm.pollWnnAndEnforce( + WnnTelemetry{DSM_Config::WNN_MAX_CURVATURE_PROXY * 0.5, 1.0, PlatformHAL::now_ms()}, + itl, + store, + 1U, + active_state + ); + + expect_false(rollback, "nominal WNN telemetry does not trigger rollback"); +} + +static void test_boundary_breaches_trigger_rollback() { + ITLManager itl; + itl.init(); + DeterministicSafetyMonitor dsm; + PhysicsState active_state{}; + RollbackPlan store[1] = {make_valid_plan()}; + + const bool curvature_boundary = dsm.pollWnnAndEnforce( + WnnTelemetry{DSM_Config::WNN_MAX_CURVATURE_PROXY, 1.0, PlatformHAL::now_ms()}, + itl, + store, + 1U, + active_state + ); + expect_true(curvature_boundary, "WNN curvature boundary triggers rollback"); + + const bool prefactor_boundary = dsm.pollWnnAndEnforce( + WnnTelemetry{0.0, DSM_Config::WNN_MIN_OSCILLATORY_PREFACTOR, PlatformHAL::now_ms()}, + itl, + store, + 1U, + active_state + ); + expect_true(prefactor_boundary, "WNN oscillatory lower boundary triggers rollback"); +} + +static void test_invalid_wnn_fails_closed() { + ITLManager itl; + itl.init(); + DeterministicSafetyMonitor dsm; + PhysicsState active_state{}; + RollbackPlan store[1] = {make_valid_plan()}; + + const bool negative_curvature = dsm.pollWnnAndEnforce( + WnnTelemetry{-1.0, 1.0, PlatformHAL::now_ms()}, + itl, + store, + 1U, + active_state + ); + expect_true(negative_curvature, "negative WNN curvature is invalid and fails closed"); + + const bool non_finite_prefactor = dsm.pollWnnAndEnforce( + WnnTelemetry{0.0, std::numeric_limits::quiet_NaN(), PlatformHAL::now_ms()}, + itl, + store, + 1U, + active_state + ); + expect_true(non_finite_prefactor, "non-finite WNN prefactor is invalid and fails closed"); +} + +static void test_dsm_full_shutdown_on_nonfinite_curvature_estimate() { + DeterministicSafetyMonitor dsm; + DsmSensorInputs inputs{}; + inputs.measured_proper_time_dilation = 1.01; + inputs.measured_oscillatory_prefactor_A_t = 1.0; + inputs.measured_tcc_coupling_J = 1.0; + inputs.current_resonance_amplitude = 0.0; + inputs.main_control_system_healthy = true; + + const int action = dsm.evaluateSafety(inputs); + expect_true( + action == DeterministicSafetyMonitor::ACTION_FULL_SHUTDOWN, + "reversed proper-time dilation estimate triggers full shutdown" + ); +} + +int main() { + std::cout << "========================================================\n"; + std::cout << " SIL TEST: DSM WNN Enforcement\n"; + std::cout << "========================================================\n"; + + PlatformHAL::seed_rng_for_stubs(2026U); + + test_nominal_wnn_is_observational(); + test_boundary_breaches_trigger_rollback(); + test_invalid_wnn_fails_closed(); + test_dsm_full_shutdown_on_nonfinite_curvature_estimate(); + + std::cout << "--------------------------------------------------------\n"; + if (g_failures == 0) { + std::cout << "✅ ALL DSM WNN TESTS PASSED\n"; + return 0; + } + std::cout << "❌ FAILURES: " << g_failures << "\n"; + return 1; +} diff --git a/tests/sil/test_rollback_execution.cpp b/tests/sil/test_rollback_execution.cpp index 87ec34e..06f58cb 100644 --- a/tests/sil/test_rollback_execution.cpp +++ b/tests/sil/test_rollback_execution.cpp @@ -67,12 +67,21 @@ void test_rollback_validation() { bool res3 = execute_rollback_plan(plan, tx_id); expect_false(res3, "execute_rollback_plan fails for infinite gimbal"); - // 4. Valid plan + // 4. Non-finite thrust plan.valid = true; - plan.thrust_magnitude_kN = 50.0f; + plan.thrust_magnitude_kN = std::numeric_limits::quiet_NaN(); plan.gimbal_theta_rad = 0.1f; + plan.gimbal_phi_rad = 0.0f; bool res4 = execute_rollback_plan(plan, tx_id); - expect_true(res4, "execute_rollback_plan succeeds for valid inputs"); + expect_false(res4, "execute_rollback_plan fails for non-finite thrust"); + + // 5. Valid plan + plan.valid = true; + plan.thrust_magnitude_kN = 50.0f; + plan.gimbal_theta_rad = 0.1f; + plan.gimbal_phi_rad = 0.0f; + bool res5 = execute_rollback_plan(plan, tx_id); + expect_true(res5, "execute_rollback_plan succeeds for valid inputs"); expect_true(tx_id.length() > 0, "tx_id is generated"); } @@ -94,7 +103,7 @@ void test_wnn_rollback_hardening() { ); // 2. Empty rollback store must fail safely - RollbackPlan store[1]{}; + RollbackPlan store[RAPSConfig::MAX_ROLLBACK_STORE]{}; bool empty_store_result = trigger_wnn_immediate_rollback( store, 0, @@ -104,6 +113,17 @@ void test_wnn_rollback_hardening() { empty_store_result, "trigger_wnn_immediate_rollback fails safely for empty rollback store" ); + + // 3. Out-of-range rollback count must fail before indexing the store + bool oob_count_result = trigger_wnn_immediate_rollback( + store, + static_cast(RAPSConfig::MAX_ROLLBACK_STORE + 1U), + active_state + ); + expect_false( + oob_count_result, + "trigger_wnn_immediate_rollback fails safely for out-of-range rollback count" + ); } int main() {