diff --git a/editor/CMakeLists.txt b/editor/CMakeLists.txt index 4fc1a0b..0417e5e 100644 --- a/editor/CMakeLists.txt +++ b/editor/CMakeLists.txt @@ -4315,4 +4315,13 @@ target_link_libraries(step603_test PRIVATE tree_sitter_javascript tree_sitter_typescript tree_sitter_java tree_sitter_rust tree_sitter_go) +add_executable(step604_test tests/step604_test.cpp) +target_include_directories(step604_test PRIVATE src) +target_link_libraries(step604_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) diff --git a/editor/src/ReleaseCertificationPacket.h b/editor/src/ReleaseCertificationPacket.h new file mode 100644 index 0000000..6de0ab3 --- /dev/null +++ b/editor/src/ReleaseCertificationPacket.h @@ -0,0 +1,68 @@ +#pragma once +// Step 604: Release Certification Packet + +#include +#include +#include + +#include "ValidationErrorUtil.h" + +struct CertificationCheck { + std::string checkId; + std::string category; + bool passed = false; + std::string evidenceRef; +}; + +class ReleaseCertificationPacket { +public: + bool recordCheck(const CertificationCheck& check, std::string* error) { + if (!error) return false; + error->clear(); + if (check.checkId.empty()) return failWith(error, "check_id_missing"); + if (check.category.empty()) return failWith(error, "category_missing"); + if (check.evidenceRef.empty()) return failWith(error, "evidence_ref_missing"); + if (checks_.count(check.checkId) != 0) return failWith(error, "check_duplicate"); + checks_[check.checkId] = check; + order_.push_back(check.checkId); + return true; + } + + bool setResult(const std::string& checkId, bool passed, std::string* error) { + if (!error) return false; + error->clear(); + auto it = checks_.find(checkId); + if (it == checks_.end()) return failWith(error, "check_missing"); + it->second.passed = passed; + return true; + } + + int passedCount() const { + int count = 0; + for (const auto& id : order_) if (checks_.at(id).passed) ++count; + return count; + } + + int failedCount() const { + int count = 0; + for (const auto& id : order_) if (!checks_.at(id).passed) ++count; + return count; + } + + bool allPassed() const { + return !order_.empty() && failedCount() == 0; + } + + std::vector byCategory(const std::string& category) const { + std::vector out; + for (const auto& id : order_) { + const auto& check = checks_.at(id); + if (category.empty() || check.category == category) out.push_back(check); + } + return out; + } + +private: + std::map checks_; + std::vector order_; +}; diff --git a/editor/tests/step604_test.cpp b/editor/tests/step604_test.cpp new file mode 100644 index 0000000..3187706 --- /dev/null +++ b/editor/tests/step604_test.cpp @@ -0,0 +1,154 @@ +// Step 604: Release Certification Packet (12 tests) + +#include "ReleaseCertificationPacket.h" + +#include + +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 CertificationCheck check(const std::string& id, + const std::string& category, + bool passed = false) { + return {id, category, passed, "evidence/" + id}; +} + +void test_record_check_success() { + TEST(record_check_success); + ReleaseCertificationPacket packet; + std::string error; + CHECK(packet.recordCheck(check("c1", "security"), &error), "record should succeed"); + CHECK(packet.failedCount() == 1, "new check defaults to failed"); + PASS(); +} + +void test_record_check_rejects_missing_check_id() { + TEST(record_check_rejects_missing_check_id); + ReleaseCertificationPacket packet; + std::string error; + CHECK(!packet.recordCheck(check("", "security"), &error), "record should fail"); + CHECK(error == "check_id_missing", "wrong error"); + PASS(); +} + +void test_record_check_rejects_missing_category() { + TEST(record_check_rejects_missing_category); + ReleaseCertificationPacket packet; + std::string error; + CHECK(!packet.recordCheck(check("c1", ""), &error), "record should fail"); + CHECK(error == "category_missing", "wrong error"); + PASS(); +} + +void test_record_check_rejects_missing_evidence_ref() { + TEST(record_check_rejects_missing_evidence_ref); + ReleaseCertificationPacket packet; + std::string error; + auto c = check("c1", "security"); + c.evidenceRef.clear(); + CHECK(!packet.recordCheck(c, &error), "record should fail"); + CHECK(error == "evidence_ref_missing", "wrong error"); + PASS(); +} + +void test_record_check_rejects_duplicate() { + TEST(record_check_rejects_duplicate); + ReleaseCertificationPacket packet; + std::string error; + CHECK(packet.recordCheck(check("c1", "security"), &error), "first record failed"); + CHECK(!packet.recordCheck(check("c1", "quality"), &error), "duplicate should fail"); + CHECK(error == "check_duplicate", "wrong error"); + PASS(); +} + +void test_set_result_success() { + TEST(set_result_success); + ReleaseCertificationPacket packet; + std::string error; + CHECK(packet.recordCheck(check("c1", "security"), &error), "record failed"); + CHECK(packet.setResult("c1", true, &error), "set result should succeed"); + CHECK(packet.passedCount() == 1, "passed count mismatch"); + PASS(); +} + +void test_set_result_rejects_missing_check() { + TEST(set_result_rejects_missing_check); + ReleaseCertificationPacket packet; + std::string error; + CHECK(!packet.setResult("missing", true, &error), "set result should fail"); + CHECK(error == "check_missing", "wrong error"); + PASS(); +} + +void test_failed_count_tracks_unpassed_checks() { + TEST(failed_count_tracks_unpassed_checks); + ReleaseCertificationPacket packet; + std::string error; + CHECK(packet.recordCheck(check("c1", "security"), &error), "record c1 failed"); + CHECK(packet.recordCheck(check("c2", "quality"), &error), "record c2 failed"); + CHECK(packet.setResult("c2", true, &error), "set result c2 failed"); + CHECK(packet.failedCount() == 1, "failed count mismatch"); + PASS(); +} + +void test_all_passed_false_when_empty() { + TEST(all_passed_false_when_empty); + ReleaseCertificationPacket packet; + CHECK(!packet.allPassed(), "empty packet should not pass"); + PASS(); +} + +void test_all_passed_true_when_every_check_passes() { + TEST(all_passed_true_when_every_check_passes); + ReleaseCertificationPacket packet; + std::string error; + CHECK(packet.recordCheck(check("c1", "security"), &error), "record c1 failed"); + CHECK(packet.recordCheck(check("c2", "quality"), &error), "record c2 failed"); + CHECK(packet.setResult("c1", true, &error), "set result c1 failed"); + CHECK(packet.setResult("c2", true, &error), "set result c2 failed"); + CHECK(packet.allPassed(), "all checks should pass"); + PASS(); +} + +void test_by_category_filters() { + TEST(by_category_filters); + ReleaseCertificationPacket packet; + std::string error; + CHECK(packet.recordCheck(check("c1", "security"), &error), "record c1 failed"); + CHECK(packet.recordCheck(check("c2", "quality"), &error), "record c2 failed"); + CHECK(packet.byCategory("security").size() == 1, "category filter mismatch"); + PASS(); +} + +void test_by_category_empty_returns_all() { + TEST(by_category_empty_returns_all); + ReleaseCertificationPacket packet; + std::string error; + CHECK(packet.recordCheck(check("c1", "security"), &error), "record c1 failed"); + CHECK(packet.recordCheck(check("c2", "quality"), &error), "record c2 failed"); + CHECK(packet.byCategory("").size() == 2, "empty category should return all"); + PASS(); +} + +int main() { + std::cout << "Step 604: Release Certification Packet\n"; + + test_record_check_success(); // 1 + test_record_check_rejects_missing_check_id(); // 2 + test_record_check_rejects_missing_category(); // 3 + test_record_check_rejects_missing_evidence_ref(); // 4 + test_record_check_rejects_duplicate(); // 5 + test_set_result_success(); // 6 + test_set_result_rejects_missing_check(); // 7 + test_failed_count_tracks_unpassed_checks(); // 8 + test_all_passed_false_when_empty(); // 9 + test_all_passed_true_when_every_check_passes(); // 10 + test_by_category_filters(); // 11 + test_by_category_empty_returns_all(); // 12 + + std::cout << "\nResults: " << passed << "/" << (passed + failed) << " passed\n"; + return failed == 0 ? 0 : 1; +} diff --git a/progress.md b/progress.md index 75ef56c..a5cb2a0 100644 --- a/progress.md +++ b/progress.md @@ -11710,3 +11710,33 @@ without changing behavior. - `editor/src/ControlAttestationRegistry.h` (`88` <= `600`) - `editor/src/ComplianceOperationalReadiness.h` (`58` <= `600`) - Naming conventions and header-only module constraints remain aligned with `ARCHITECTURE.md`. + +### Step 604: Release Certification Packet +**Status:** PASS (12/12 tests) + +Implements release certification check tracking with pass/fail accounting and +category filtering. + +**Files added:** +- `editor/src/ReleaseCertificationPacket.h` - certification packet module: + - check recording with validation/duplicate guards + - pass/fail status mutation per check + - aggregate pass/fail accounting and category filters +- `editor/tests/step604_test.cpp` - 12 tests covering: + - record success/failure behavior + - duplicate/validation behavior + - result mutation, aggregate counters, and category filtering behavior + +**Files modified:** +- `editor/CMakeLists.txt` - `step604_test` target + +**Verification run:** +- `cmake -S editor -B editor/build-native` - PASS +- `cmake --build editor/build-native --target step604_test step603_test` - PASS +- `./editor/build-native/step604_test` - PASS (12/12) +- `./editor/build-native/step603_test` - PASS (12/12) regression coverage + +**Architecture gate check:** +- `editor/src/ReleaseCertificationPacket.h` within header-size limit (`68` <= `600`) +- `editor/tests/step604_test.cpp` within test-file size guidance (`154` lines) +- Header-only architecture and naming conventions remain aligned with `ARCHITECTURE.md`