Step 590: add crash recovery reliability sweep
This commit is contained in:
@@ -4189,4 +4189,13 @@ target_link_libraries(step589_test PRIVATE
|
||||
tree_sitter_javascript tree_sitter_typescript
|
||||
tree_sitter_java tree_sitter_rust tree_sitter_go)
|
||||
|
||||
add_executable(step590_test tests/step590_test.cpp)
|
||||
target_include_directories(step590_test PRIVATE src)
|
||||
target_link_libraries(step590_test PRIVATE
|
||||
nlohmann_json::nlohmann_json
|
||||
unofficial::tree-sitter::tree-sitter
|
||||
tree_sitter_python tree_sitter_cpp tree_sitter_elisp
|
||||
tree_sitter_javascript tree_sitter_typescript
|
||||
tree_sitter_java tree_sitter_rust tree_sitter_go)
|
||||
|
||||
# Step 12: Dear ImGui shell scaffolding created (main.cpp exists but not built due to dependencies)
|
||||
|
||||
73
editor/src/CrashRecoveryReliabilitySweep.h
Normal file
73
editor/src/CrashRecoveryReliabilitySweep.h
Normal file
@@ -0,0 +1,73 @@
|
||||
#pragma once
|
||||
// Step 590: Crash + Recovery Reliability Sweep
|
||||
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
struct RecoverySessionSnapshot {
|
||||
std::string sessionId;
|
||||
std::string workflowId;
|
||||
int checkpoint = 0;
|
||||
std::vector<std::string> openBuffers;
|
||||
std::vector<std::string> queuedTaskIds;
|
||||
};
|
||||
|
||||
struct RecoveryResult {
|
||||
bool recovered = false;
|
||||
bool workflowContinuity = false;
|
||||
bool queueContinuity = false;
|
||||
bool bufferContinuity = false;
|
||||
std::vector<std::string> notes;
|
||||
};
|
||||
|
||||
class CrashRecoveryReliabilitySweep {
|
||||
public:
|
||||
bool captureSnapshot(const RecoverySessionSnapshot& snapshot, std::string* error) {
|
||||
if (!error) return false;
|
||||
error->clear();
|
||||
if (snapshot.sessionId.empty()) return fail(error, "session_id_missing");
|
||||
if (snapshot.workflowId.empty()) return fail(error, "workflow_id_missing");
|
||||
if (snapshot.checkpoint <= 0) return fail(error, "checkpoint_invalid");
|
||||
snapshots_[snapshot.sessionId] = snapshot;
|
||||
return true;
|
||||
}
|
||||
|
||||
RecoveryResult recover(const std::string& sessionId) const {
|
||||
RecoveryResult result;
|
||||
auto it = snapshots_.find(sessionId);
|
||||
if (it == snapshots_.end()) {
|
||||
result.notes.push_back("recover:session_missing");
|
||||
return result;
|
||||
}
|
||||
const auto& s = it->second;
|
||||
result.workflowContinuity = !s.workflowId.empty() && s.checkpoint > 0;
|
||||
result.queueContinuity = !s.queuedTaskIds.empty();
|
||||
result.bufferContinuity = !s.openBuffers.empty();
|
||||
result.recovered = result.workflowContinuity && result.queueContinuity && result.bufferContinuity;
|
||||
|
||||
if (result.recovered) {
|
||||
result.notes.push_back("recover:ok");
|
||||
} else {
|
||||
result.notes.push_back("recover:partial");
|
||||
if (!result.workflowContinuity) result.notes.push_back("recover:workflow_missing");
|
||||
if (!result.queueContinuity) result.notes.push_back("recover:queue_missing");
|
||||
if (!result.bufferContinuity) result.notes.push_back("recover:buffers_missing");
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
std::vector<std::string> knownSessions() const {
|
||||
std::vector<std::string> ids;
|
||||
for (const auto& kv : snapshots_) ids.push_back(kv.first);
|
||||
return ids;
|
||||
}
|
||||
|
||||
private:
|
||||
std::map<std::string, RecoverySessionSnapshot> snapshots_;
|
||||
|
||||
static bool fail(std::string* error, const char* code) {
|
||||
*error = code;
|
||||
return false;
|
||||
}
|
||||
};
|
||||
186
editor/tests/step590_test.cpp
Normal file
186
editor/tests/step590_test.cpp
Normal file
@@ -0,0 +1,186 @@
|
||||
// Step 590: Crash + Recovery Reliability Sweep (12 tests)
|
||||
|
||||
#include "CrashRecoveryReliabilitySweep.h"
|
||||
|
||||
#include <iostream>
|
||||
|
||||
static int passed = 0, failed = 0;
|
||||
#define TEST(name) { std::cout << " " << #name << "... "; }
|
||||
#define PASS() { std::cout << "PASS\n"; ++passed; }
|
||||
#define FAIL(msg) { std::cout << "FAIL: " << msg << "\n"; ++failed; }
|
||||
#define CHECK(cond, msg) if (!(cond)) { FAIL(msg); return; } else {}
|
||||
|
||||
static RecoverySessionSnapshot snap(const std::string& sessionId) {
|
||||
RecoverySessionSnapshot s;
|
||||
s.sessionId = sessionId;
|
||||
s.workflowId = "wf-1";
|
||||
s.checkpoint = 3;
|
||||
s.openBuffers = {"a.cpp"};
|
||||
s.queuedTaskIds = {"task-1"};
|
||||
return s;
|
||||
}
|
||||
|
||||
static bool hasNote(const RecoveryResult& r, const std::string& note) {
|
||||
for (const auto& n : r.notes) if (n == note) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
void test_capture_snapshot_success() {
|
||||
TEST(capture_snapshot_success);
|
||||
CrashRecoveryReliabilitySweep sweep;
|
||||
std::string error;
|
||||
CHECK(sweep.captureSnapshot(snap("s1"), &error), "capture should succeed");
|
||||
CHECK(sweep.knownSessions().size() == 1, "session should be stored");
|
||||
PASS();
|
||||
}
|
||||
|
||||
void test_capture_snapshot_rejects_missing_session() {
|
||||
TEST(capture_snapshot_rejects_missing_session);
|
||||
CrashRecoveryReliabilitySweep sweep;
|
||||
std::string error;
|
||||
auto s = snap("");
|
||||
CHECK(!sweep.captureSnapshot(s, &error), "capture should fail");
|
||||
CHECK(error == "session_id_missing", "wrong error");
|
||||
PASS();
|
||||
}
|
||||
|
||||
void test_capture_snapshot_rejects_missing_workflow() {
|
||||
TEST(capture_snapshot_rejects_missing_workflow);
|
||||
CrashRecoveryReliabilitySweep sweep;
|
||||
std::string error;
|
||||
auto s = snap("s1");
|
||||
s.workflowId.clear();
|
||||
CHECK(!sweep.captureSnapshot(s, &error), "capture should fail");
|
||||
CHECK(error == "workflow_id_missing", "wrong error");
|
||||
PASS();
|
||||
}
|
||||
|
||||
void test_capture_snapshot_rejects_invalid_checkpoint() {
|
||||
TEST(capture_snapshot_rejects_invalid_checkpoint);
|
||||
CrashRecoveryReliabilitySweep sweep;
|
||||
std::string error;
|
||||
auto s = snap("s1");
|
||||
s.checkpoint = 0;
|
||||
CHECK(!sweep.captureSnapshot(s, &error), "capture should fail");
|
||||
CHECK(error == "checkpoint_invalid", "wrong error");
|
||||
PASS();
|
||||
}
|
||||
|
||||
void test_recover_success_when_all_continuity_present() {
|
||||
TEST(recover_success_when_all_continuity_present);
|
||||
CrashRecoveryReliabilitySweep sweep;
|
||||
std::string error;
|
||||
CHECK(sweep.captureSnapshot(snap("s1"), &error), "capture failed");
|
||||
auto r = sweep.recover("s1");
|
||||
CHECK(r.recovered, "recovery should be complete");
|
||||
CHECK(r.workflowContinuity && r.queueContinuity && r.bufferContinuity, "all continuity flags expected");
|
||||
CHECK(hasNote(r, "recover:ok"), "ok note expected");
|
||||
PASS();
|
||||
}
|
||||
|
||||
void test_recover_missing_session_returns_note() {
|
||||
TEST(recover_missing_session_returns_note);
|
||||
CrashRecoveryReliabilitySweep sweep;
|
||||
auto r = sweep.recover("missing");
|
||||
CHECK(!r.recovered, "recovery should fail");
|
||||
CHECK(hasNote(r, "recover:session_missing"), "missing session note expected");
|
||||
PASS();
|
||||
}
|
||||
|
||||
void test_recover_partial_when_queue_missing() {
|
||||
TEST(recover_partial_when_queue_missing);
|
||||
CrashRecoveryReliabilitySweep sweep;
|
||||
std::string error;
|
||||
auto s = snap("s1");
|
||||
s.queuedTaskIds.clear();
|
||||
CHECK(sweep.captureSnapshot(s, &error), "capture failed");
|
||||
auto r = sweep.recover("s1");
|
||||
CHECK(!r.recovered, "recovery should be partial");
|
||||
CHECK(!r.queueContinuity, "queue continuity should fail");
|
||||
CHECK(hasNote(r, "recover:queue_missing"), "queue missing note expected");
|
||||
PASS();
|
||||
}
|
||||
|
||||
void test_recover_partial_when_buffers_missing() {
|
||||
TEST(recover_partial_when_buffers_missing);
|
||||
CrashRecoveryReliabilitySweep sweep;
|
||||
std::string error;
|
||||
auto s = snap("s1");
|
||||
s.openBuffers.clear();
|
||||
CHECK(sweep.captureSnapshot(s, &error), "capture failed");
|
||||
auto r = sweep.recover("s1");
|
||||
CHECK(!r.recovered, "recovery should be partial");
|
||||
CHECK(!r.bufferContinuity, "buffer continuity should fail");
|
||||
CHECK(hasNote(r, "recover:buffers_missing"), "buffer missing note expected");
|
||||
PASS();
|
||||
}
|
||||
|
||||
void test_recover_partial_when_workflow_invalid() {
|
||||
TEST(recover_partial_when_workflow_invalid);
|
||||
CrashRecoveryReliabilitySweep sweep;
|
||||
std::string error;
|
||||
auto s = snap("s1");
|
||||
s.workflowId = "wf-1";
|
||||
s.checkpoint = -1;
|
||||
s.queuedTaskIds = {"task-1"};
|
||||
s.openBuffers = {"a.cpp"};
|
||||
CHECK(!sweep.captureSnapshot(s, &error), "capture should fail");
|
||||
CHECK(error == "checkpoint_invalid", "wrong checkpoint error");
|
||||
PASS();
|
||||
}
|
||||
|
||||
void test_known_sessions_returns_all_ids() {
|
||||
TEST(known_sessions_returns_all_ids);
|
||||
CrashRecoveryReliabilitySweep sweep;
|
||||
std::string error;
|
||||
CHECK(sweep.captureSnapshot(snap("s1"), &error), "capture s1 failed");
|
||||
CHECK(sweep.captureSnapshot(snap("s2"), &error), "capture s2 failed");
|
||||
auto ids = sweep.knownSessions();
|
||||
CHECK(ids.size() == 2, "two ids expected");
|
||||
PASS();
|
||||
}
|
||||
|
||||
void test_capture_overwrites_existing_session() {
|
||||
TEST(capture_overwrites_existing_session);
|
||||
CrashRecoveryReliabilitySweep sweep;
|
||||
std::string error;
|
||||
auto s = snap("s1");
|
||||
CHECK(sweep.captureSnapshot(s, &error), "capture 1 failed");
|
||||
s.openBuffers = {"b.cpp"};
|
||||
CHECK(sweep.captureSnapshot(s, &error), "capture 2 failed");
|
||||
auto r = sweep.recover("s1");
|
||||
CHECK(r.bufferContinuity, "buffer continuity should remain true");
|
||||
PASS();
|
||||
}
|
||||
|
||||
void test_partial_recovery_has_partial_note() {
|
||||
TEST(partial_recovery_has_partial_note);
|
||||
CrashRecoveryReliabilitySweep sweep;
|
||||
std::string error;
|
||||
auto s = snap("s1");
|
||||
s.openBuffers.clear();
|
||||
CHECK(sweep.captureSnapshot(s, &error), "capture failed");
|
||||
auto r = sweep.recover("s1");
|
||||
CHECK(hasNote(r, "recover:partial"), "partial note expected");
|
||||
PASS();
|
||||
}
|
||||
|
||||
int main() {
|
||||
std::cout << "Step 590: Crash + Recovery Reliability Sweep\n";
|
||||
|
||||
test_capture_snapshot_success(); // 1
|
||||
test_capture_snapshot_rejects_missing_session(); // 2
|
||||
test_capture_snapshot_rejects_missing_workflow(); // 3
|
||||
test_capture_snapshot_rejects_invalid_checkpoint();// 4
|
||||
test_recover_success_when_all_continuity_present();// 5
|
||||
test_recover_missing_session_returns_note(); // 6
|
||||
test_recover_partial_when_queue_missing(); // 7
|
||||
test_recover_partial_when_buffers_missing(); // 8
|
||||
test_recover_partial_when_workflow_invalid(); // 9
|
||||
test_known_sessions_returns_all_ids(); // 10
|
||||
test_capture_overwrites_existing_session(); // 11
|
||||
test_partial_recovery_has_partial_note(); // 12
|
||||
|
||||
std::cout << "\nResults: " << passed << "/" << (passed + failed) << " passed\n";
|
||||
return failed == 0 ? 0 : 1;
|
||||
}
|
||||
33
progress.md
33
progress.md
@@ -11160,3 +11160,36 @@ regression, telemetry, and documentation readiness.
|
||||
- `editor/src/ReleaseReadinessGatePack.h` within header-size limit (`55` <= `600`)
|
||||
- `editor/tests/step589_test.cpp` within test-file size guidance (`154` lines)
|
||||
- Header-only architecture and naming conventions remain aligned with `ARCHITECTURE.md`
|
||||
|
||||
### Step 590: Crash + Recovery Reliability Sweep
|
||||
**Status:** PASS (12/12 tests)
|
||||
|
||||
Implements crash/recovery reliability modeling for session snapshots and
|
||||
continuity checks across workflow, queue, and open-buffer state.
|
||||
|
||||
**Files added:**
|
||||
- `editor/src/CrashRecoveryReliabilitySweep.h` - crash recovery module:
|
||||
- recovery snapshot capture with validation guards
|
||||
- session restore evaluation with continuity dimensions
|
||||
- partial-recovery diagnostic notes
|
||||
- known session inventory helper
|
||||
- `editor/tests/step590_test.cpp` - 12 tests covering:
|
||||
- snapshot capture success/failure behavior
|
||||
- full recovery behavior
|
||||
- partial recovery dimensions and notes behavior
|
||||
- missing-session behavior
|
||||
- snapshot overwrite and session inventory behavior
|
||||
|
||||
**Files modified:**
|
||||
- `editor/CMakeLists.txt` - `step590_test` target
|
||||
|
||||
**Verification run:**
|
||||
- `cmake -S editor -B editor/build-native` - PASS
|
||||
- `cmake --build editor/build-native --target step590_test step589_test` - PASS
|
||||
- `./editor/build-native/step590_test` - PASS (12/12)
|
||||
- `./editor/build-native/step589_test` - PASS (12/12) regression coverage
|
||||
|
||||
**Architecture gate check:**
|
||||
- `editor/src/CrashRecoveryReliabilitySweep.h` within header-size limit (`73` <= `600`)
|
||||
- `editor/tests/step590_test.cpp` within test-file size guidance (`186` lines)
|
||||
- Header-only architecture and naming conventions remain aligned with `ARCHITECTURE.md`
|
||||
|
||||
Reference in New Issue
Block a user