Skip to content

Commit f0ebbfe

Browse files
authored
Merge pull request #25 from dfeen87/codex/code-review-and-harden-codebase-for-wnn-dsm
Harden WNN DSM enforcement
2 parents 9a1bded + 965e4b5 commit f0ebbfe

6 files changed

Lines changed: 442 additions & 27 deletions

File tree

include/itl/itl_manager.hpp

Lines changed: 199 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
#pragma once
22

3+
#include <array>
34
#include <cstddef>
45
#include <cstdint>
6+
#include <cstring>
57

68
#include "core/raps_definitions.hpp"
79
#include "platform/platform_hal.hpp"
@@ -30,28 +32,217 @@ class ITLManager {
3032
void init();
3133

3234
// Non-blocking commit (returns optimistic ID).
33-
// Returns null_hash() if queue is full.
35+
// Returns null_hash() if queue is full or persistence fails.
3436
Hash256 commit(const ITLEntry& entry);
3537

3638
// Background processing (low-priority task)
3739
void flush_pending();
3840

39-
// Log WNN rollback event
40-
void log_wnn_rollback_event(double curvature, double prefactor);
41+
// Log WNN rollback event. Returns true only when all ledger commits succeed.
42+
bool log_wnn_rollback_event(double curvature, double prefactor);
4143
};
4244

43-
inline void ITLManager::log_wnn_rollback_event(double curvature, double prefactor) {
45+
namespace itl_manager_detail {
46+
47+
inline size_t effective_payload_len(ITLEntry::Type type) {
48+
switch (type) {
49+
case ITLEntry::Type::STATE_SNAPSHOT:
50+
return sizeof(ITLEntry::StateSnapshotPayload);
51+
case ITLEntry::Type::PREDICTION_COMMIT:
52+
return sizeof(ITLEntry::PredictionCommitPayload);
53+
case ITLEntry::Type::ESE_ALERT:
54+
return sizeof(ITLEntry::ESEAlertPayload);
55+
case ITLEntry::Type::POLICY_PREFLIGHT:
56+
return sizeof(ITLEntry::PolicyPreflightPayload);
57+
case ITLEntry::Type::COMMAND_PENDING:
58+
case ITLEntry::Type::EXECUTION_FAILURE:
59+
case ITLEntry::Type::COMMAND_COMMIT:
60+
case ITLEntry::Type::ROLLBACK_COMMIT:
61+
return sizeof(ITLEntry::CommandExecutionPayload);
62+
case ITLEntry::Type::ROLLBACK_METADATA:
63+
return sizeof(ITLEntry::RollbackMetadataPayload);
64+
case ITLEntry::Type::FALLBACK_TRIGGERED:
65+
return sizeof(ITLEntry::FallbackTriggeredPayload);
66+
case ITLEntry::Type::MERKLE_ANCHOR:
67+
return sizeof(ITLEntry::MerkleAnchorPayload);
68+
case ITLEntry::Type::GOVERNANCE_BUDGET_VIOLATION:
69+
return sizeof(ITLEntry::GovernanceBudgetViolationPayload);
70+
case ITLEntry::Type::NOMINAL_TRACE:
71+
return sizeof(ITLEntry::NominalTracePayload);
72+
case ITLEntry::Type::SUPERVISOR_EXCEPTION:
73+
return sizeof(ITLEntry::SupervisorExceptionPayload);
74+
case ITLEntry::Type::AILEE_SAFETY_STATUS:
75+
return sizeof(ITLEntry::AileeSafetyStatusPayload);
76+
case ITLEntry::Type::AILEE_GRACE_RESULT:
77+
return sizeof(ITLEntry::AileeGraceResultPayload);
78+
case ITLEntry::Type::AILEE_CONSENSUS_RESULT:
79+
return sizeof(ITLEntry::AileeConsensusResultPayload);
80+
case ITLEntry::Type::WNN_ALERT:
81+
return sizeof(ITLEntry::WnnAlertPayload);
82+
default:
83+
return 0U;
84+
}
85+
}
86+
87+
inline Hash256 compute_entry_id(const ITLEntry& entry, size_t payload_len) {
88+
std::array<uint8_t,
89+
sizeof(entry.type) + sizeof(entry.timestamp_ms) + sizeof(ITLEntry::PayloadData)> buf{};
90+
91+
size_t offset = 0U;
92+
std::memcpy(buf.data() + offset, &entry.type, sizeof(entry.type));
93+
offset += sizeof(entry.type);
94+
std::memcpy(buf.data() + offset, &entry.timestamp_ms, sizeof(entry.timestamp_ms));
95+
offset += sizeof(entry.timestamp_ms);
96+
if (payload_len > 0U) {
97+
std::memcpy(buf.data() + offset, &entry.payload, payload_len);
98+
offset += payload_len;
99+
}
100+
return PlatformHAL::sha256(buf.data(), offset);
101+
}
102+
103+
inline Hash256 compute_merkle_root(const Hash256* ids, size_t count) {
104+
if (ids == nullptr || count == 0U) {
105+
return Hash256::null_hash();
106+
}
107+
if (count == 1U) {
108+
return ids[0];
109+
}
110+
111+
std::array<Hash256, RAPSConfig::MERKLE_BATCH_SIZE> current{};
112+
std::array<Hash256, RAPSConfig::MERKLE_BATCH_SIZE> next{};
113+
for (size_t i = 0U; i < count; ++i) {
114+
current[i] = ids[i];
115+
}
116+
117+
size_t current_count = count;
118+
while (current_count > 1U) {
119+
size_t next_count = 0U;
120+
for (size_t i = 0U; i < current_count; i += 2U) {
121+
const Hash256& left = current[i];
122+
const Hash256& right = (i + 1U < current_count) ? current[i + 1U] : left;
123+
std::array<uint8_t, 64> combined{};
124+
std::memcpy(combined.data(), left.data, 32U);
125+
std::memcpy(combined.data() + 32U, right.data, 32U);
126+
next[next_count++] = PlatformHAL::sha256(combined.data(), combined.size());
127+
}
128+
for (size_t i = 0U; i < next_count; ++i) {
129+
current[i] = next[i];
130+
}
131+
current_count = next_count;
132+
}
133+
134+
return current[0];
135+
}
136+
137+
} // namespace itl_manager_detail
138+
139+
inline void ITLManager::init() {
140+
queue_head_ = 0U;
141+
queue_tail_ = 0U;
142+
queue_count_ = 0U;
143+
merkle_count_ = 0U;
144+
flash_write_cursor_ = 0U;
145+
for (auto& entry : queue_) {
146+
entry = ITLEntry{};
147+
}
148+
for (auto& hash : merkle_buffer_) {
149+
hash = Hash256::null_hash();
150+
}
151+
}
152+
153+
inline Hash256 ITLManager::commit(const ITLEntry& entry_template) {
154+
if (queue_count_ >= RAPSConfig::ITL_QUEUE_SIZE) {
155+
PlatformHAL::metric_emit("itl.commit_dropped", 1.0f, "reason", "queue_full");
156+
return Hash256::null_hash();
157+
}
158+
159+
ITLEntry entry = entry_template;
160+
if (entry.timestamp_ms == 0U) {
161+
entry.timestamp_ms = PlatformHAL::now_ms();
162+
}
163+
164+
const size_t payload_len = itl_manager_detail::effective_payload_len(entry.type);
165+
if (payload_len > sizeof(ITLEntry::PayloadData)) {
166+
PlatformHAL::metric_emit("itl.commit_dropped", 1.0f, "reason", "payload_len");
167+
return Hash256::null_hash();
168+
}
169+
entry.payload_len = static_cast<uint16_t>(payload_len);
170+
entry.entry_id = itl_manager_detail::compute_entry_id(entry, payload_len);
171+
172+
if (entry.entry_id.is_null()) {
173+
PlatformHAL::metric_emit("itl.commit_dropped", 1.0f, "reason", "entry_hash");
174+
return Hash256::null_hash();
175+
}
176+
177+
if (!PlatformHAL::flash_write(flash_write_cursor_, &entry, sizeof(entry))) {
178+
PlatformHAL::metric_emit("itl.commit_dropped", 1.0f, "reason", "flash_write");
179+
return Hash256::null_hash();
180+
}
181+
flash_write_cursor_ += static_cast<uint32_t>(sizeof(entry));
182+
183+
queue_[queue_tail_] = entry;
184+
queue_tail_ = (queue_tail_ + 1U) % RAPSConfig::ITL_QUEUE_SIZE;
185+
++queue_count_;
186+
187+
merkle_buffer_[merkle_count_++] = entry.entry_id;
188+
if (merkle_count_ >= RAPSConfig::MERKLE_BATCH_SIZE) {
189+
process_merkle_batch();
190+
}
191+
192+
return entry.entry_id;
193+
}
194+
195+
inline void ITLManager::process_merkle_batch() {
196+
if (merkle_count_ == 0U) {
197+
return;
198+
}
199+
200+
const Hash256 root = itl_manager_detail::compute_merkle_root(
201+
merkle_buffer_,
202+
merkle_count_
203+
);
204+
if (!root.is_null()) {
205+
ITLEntry anchor{};
206+
anchor.type = ITLEntry::Type::MERKLE_ANCHOR;
207+
anchor.timestamp_ms = PlatformHAL::now_ms();
208+
anchor.payload.merkle_anchor.merkle_root = root;
209+
const size_t payload_len = itl_manager_detail::effective_payload_len(anchor.type);
210+
anchor.payload_len = static_cast<uint16_t>(payload_len);
211+
anchor.entry_id = itl_manager_detail::compute_entry_id(anchor, payload_len);
212+
if (!anchor.entry_id.is_null()) {
213+
(void)PlatformHAL::flash_write(flash_write_cursor_, &anchor, sizeof(anchor));
214+
flash_write_cursor_ += static_cast<uint32_t>(sizeof(anchor));
215+
}
216+
}
217+
218+
for (auto& hash : merkle_buffer_) {
219+
hash = Hash256::null_hash();
220+
}
221+
merkle_count_ = 0U;
222+
}
223+
224+
inline void ITLManager::flush_pending() {
225+
process_merkle_batch();
226+
227+
while (queue_count_ > 0U) {
228+
queue_[queue_head_] = ITLEntry{};
229+
queue_head_ = (queue_head_ + 1U) % RAPSConfig::ITL_QUEUE_SIZE;
230+
--queue_count_;
231+
}
232+
}
233+
234+
inline bool ITLManager::log_wnn_rollback_event(double curvature, double prefactor) {
44235
ITLEntry wnn_entry{};
45236
wnn_entry.type = ITLEntry::Type::WNN_ALERT;
46237
wnn_entry.timestamp_ms = PlatformHAL::now_ms();
47238
wnn_entry.payload.wnn_alert.curvature_proxy = curvature;
48239
wnn_entry.payload.wnn_alert.oscillatory_prefactor = prefactor;
49-
commit(wnn_entry);
240+
const Hash256 wnn_id = commit(wnn_entry);
50241

51242
ITLEntry rollback_entry{};
52243
rollback_entry.type = ITLEntry::Type::ROLLBACK_COMMIT;
53244
rollback_entry.timestamp_ms = PlatformHAL::now_ms();
54-
// Payload for rollback commit (CommandExecutionPayload)
55-
// we just commit the entry to mark the rollback execution triggered by WNN
56-
commit(rollback_entry);
245+
const Hash256 rollback_id = commit(rollback_entry);
246+
247+
return !wnn_id.is_null() && !rollback_id.is_null();
57248
}

include/raps/safety/deterministic_safety_monitor.hpp

Lines changed: 29 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#pragma once
22

33
#include <cmath>
4+
#include <cstdint>
45
#include <iostream>
56
#include <limits>
67

@@ -31,13 +32,15 @@ constexpr double MIN_RESONANCE_AMPLITUDE_CUTOFF = 0.10;
3132
// WNN Constraints
3233
constexpr double WNN_MAX_CURVATURE_PROXY = 5.0e-11;
3334
constexpr double WNN_MIN_OSCILLATORY_PREFACTOR = 0.85;
35+
constexpr double WNN_MAX_OSCILLATORY_PREFACTOR = 1.25;
3436
constexpr double INVALID_TELEMETRY_SENTINEL = -1.0;
3537

3638
} // namespace DSM_Config
3739

3840
struct WnnTelemetry {
39-
double curvature_proxy;
40-
double oscillatory_prefactor;
41+
double curvature_proxy{0.0};
42+
double oscillatory_prefactor{1.0};
43+
uint32_t timestamp_ms{0U};
4144
};
4245

4346
// =====================================================
@@ -128,15 +131,18 @@ DeterministicSafetyMonitor::hasInvalidWnnTelemetry(
128131
const WnnTelemetry& wnn_telem
129132
) const {
130133
return !std::isfinite(wnn_telem.curvature_proxy) ||
131-
!std::isfinite(wnn_telem.oscillatory_prefactor);
134+
!std::isfinite(wnn_telem.oscillatory_prefactor) ||
135+
wnn_telem.curvature_proxy < 0.0 ||
136+
wnn_telem.oscillatory_prefactor < 0.0;
132137
}
133138

134139
inline bool
135140
DeterministicSafetyMonitor::isWnnThresholdBreached(
136141
const WnnTelemetry& wnn_telem
137142
) const {
138-
return wnn_telem.curvature_proxy > DSM_Config::WNN_MAX_CURVATURE_PROXY ||
139-
wnn_telem.oscillatory_prefactor < DSM_Config::WNN_MIN_OSCILLATORY_PREFACTOR;
143+
return wnn_telem.curvature_proxy >= DSM_Config::WNN_MAX_CURVATURE_PROXY ||
144+
wnn_telem.oscillatory_prefactor <= DSM_Config::WNN_MIN_OSCILLATORY_PREFACTOR ||
145+
wnn_telem.oscillatory_prefactor >= DSM_Config::WNN_MAX_OSCILLATORY_PREFACTOR;
140146
}
141147

142148
inline bool
@@ -169,10 +175,11 @@ DeterministicSafetyMonitor::evaluateSafety(
169175
return ACTION_FULL_SHUTDOWN;
170176
}
171177

172-
double R_estimated =
178+
const double R_estimated =
173179
estimateCurvatureScalar(inputs.measured_proper_time_dilation);
180+
last_estimated_Rmax_ = R_estimated;
174181

175-
if (checkCurvatureViolation(R_estimated)) {
182+
if (!std::isfinite(R_estimated) || checkCurvatureViolation(R_estimated)) {
176183
safing_sequence_active_ = true;
177184
std::cerr
178185
<< "DSM ALERT: ABSOLUTE CURVATURE VIOLATION — FULL SHUTDOWN\n";
@@ -231,14 +238,26 @@ DeterministicSafetyMonitor::pollWnnAndEnforce(
231238
? wnn_telem.oscillatory_prefactor
232239
: DSM_Config::INVALID_TELEMETRY_SENTINEL;
233240

234-
// Breach detected! Log to ITL and execute immediate rollback
235-
itl_manager.log_wnn_rollback_event(logged_curvature, logged_prefactor);
241+
// Breach detected! Log to ITL and execute immediate rollback.
242+
const bool ledger_committed =
243+
itl_manager.log_wnn_rollback_event(logged_curvature, logged_prefactor);
244+
if (!ledger_committed) {
245+
PlatformHAL::metric_emit(
246+
"safety.wnn.ledger_commit_failed",
247+
1.0f
248+
);
249+
}
236250

237-
return trigger_wnn_immediate_rollback(
251+
const bool rollback_executed = trigger_wnn_immediate_rollback(
238252
rollback_store,
239253
rollback_count,
240254
active_state_pointer
241255
);
256+
PlatformHAL::metric_emit(
257+
"safety.wnn.rollback_executed",
258+
rollback_executed ? 1.0f : 0.0f
259+
);
260+
return rollback_executed;
242261
}
243262
return false; // No breach
244263
}

src/raps/rollback_execution.hpp

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
#include <string>
44
#include <cmath>
55
#include <limits>
6+
#include <cstdint>
67

78
#include "raps/core/raps_core_types.hpp"
89
#include "platform/platform_hal.hpp"
@@ -20,12 +21,13 @@ inline bool execute_rollback_plan(
2021
}
2122

2223
// 2. Validate control inputs (Sanity Checks)
23-
// Thrust cannot be negative
24-
if (rollback.thrust_magnitude_kN < 0.0f) {
24+
// Thrust must be finite and cannot be negative.
25+
if (!std::isfinite(rollback.thrust_magnitude_kN) ||
26+
rollback.thrust_magnitude_kN < 0.0f) {
2527
return false;
2628
}
2729

28-
// Gimbal angles must be finite numbers
30+
// Gimbal angles must be finite numbers.
2931
if (!std::isfinite(rollback.gimbal_theta_rad)) {
3032
return false;
3133
}
@@ -53,14 +55,30 @@ inline bool trigger_wnn_immediate_rollback(
5355
uint32_t rollback_count,
5456
PhysicsState& active_state_pointer
5557
) {
56-
if (rollback_count == 0 || rollback_store == nullptr) {
58+
if (rollback_count == 0U || rollback_store == nullptr) {
5759
return false;
5860
}
5961

60-
const RollbackPlan& latest_plan = rollback_store[rollback_count - 1];
62+
if (rollback_count > RAPSConfig::MAX_ROLLBACK_STORE) {
63+
PlatformHAL::metric_emit(
64+
"safety.wnn.rollback_rejected",
65+
1.0f,
66+
"reason",
67+
"rollback_count_oob"
68+
);
69+
return false;
70+
}
71+
72+
const RollbackPlan& latest_plan = rollback_store[rollback_count - 1U];
6173

6274
std::string tx_id;
6375
if (!execute_rollback_plan(latest_plan, tx_id)) {
76+
PlatformHAL::metric_emit(
77+
"safety.wnn.rollback_rejected",
78+
1.0f,
79+
"reason",
80+
"plan_execution_failed"
81+
);
6482
return false;
6583
}
6684

0 commit comments

Comments
 (0)