From 36733f1163077cd1d7c6ad4764be0d4fdb46fc73 Mon Sep 17 00:00:00 2001 From: Bill Date: Tue, 17 Feb 2026 11:53:19 -0700 Subject: [PATCH] Add step 603 compliance operational readiness --- editor/CMakeLists.txt | 9 ++ editor/src/ComplianceOperationalReadiness.h | 60 ++++++++ editor/tests/step603_test.cpp | 146 ++++++++++++++++++++ progress.md | 30 ++++ 4 files changed, 245 insertions(+) create mode 100644 editor/src/ComplianceOperationalReadiness.h create mode 100644 editor/tests/step603_test.cpp diff --git a/editor/CMakeLists.txt b/editor/CMakeLists.txt index e2fc7a0..4fc1a0b 100644 --- a/editor/CMakeLists.txt +++ b/editor/CMakeLists.txt @@ -4306,4 +4306,13 @@ target_link_libraries(step602_test PRIVATE tree_sitter_javascript tree_sitter_typescript tree_sitter_java tree_sitter_rust tree_sitter_go) +add_executable(step603_test tests/step603_test.cpp) +target_include_directories(step603_test PRIVATE src) +target_link_libraries(step603_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/ComplianceOperationalReadiness.h b/editor/src/ComplianceOperationalReadiness.h new file mode 100644 index 0000000..07fc05a --- /dev/null +++ b/editor/src/ComplianceOperationalReadiness.h @@ -0,0 +1,60 @@ +#pragma once +// Step 603: Compliance Operational Readiness + +#include +#include +#include + +struct ComplianceReadinessSnapshot { + int controlsCovered = 0; + int artifactCount = 0; + int signedAttestations = 0; + int expiringSoon = 0; + int approvedExceptions = 0; + int openRunbooks = 0; +}; + +class ComplianceOperationalReadiness { +public: + static bool validateInput(const ComplianceReadinessSnapshot& s, std::string* error) { + if (!error) return false; + error->clear(); + if (s.controlsCovered < 0) return fail(error, "controls_covered_invalid"); + if (s.artifactCount < 0) return fail(error, "artifact_count_invalid"); + if (s.signedAttestations < 0) return fail(error, "signed_attestations_invalid"); + if (s.expiringSoon < 0) return fail(error, "expiring_soon_invalid"); + if (s.approvedExceptions < 0) return fail(error, "approved_exceptions_invalid"); + if (s.openRunbooks < 0) return fail(error, "open_runbooks_invalid"); + return true; + } + + static int readinessScore(const ComplianceReadinessSnapshot& s) { + int score = s.controlsCovered * 5 + + s.artifactCount * 2 + + s.signedAttestations * 4 + + s.approvedExceptions * 2 - + s.expiringSoon * 3 - + s.openRunbooks * 4; + return std::max(0, std::min(100, score)); + } + + static std::vector blockingFindings(const ComplianceReadinessSnapshot& s) { + std::vector findings; + if (s.controlsCovered == 0) findings.push_back("controls_uncovered"); + if (s.artifactCount == 0) findings.push_back("artifacts_missing"); + if (s.signedAttestations == 0) findings.push_back("attestations_missing"); + if (s.expiringSoon > 0) findings.push_back("attestations_expiring"); + if (s.openRunbooks > 0) findings.push_back("incidents_open"); + return findings; + } + + static bool releaseEligible(const ComplianceReadinessSnapshot& s) { + return blockingFindings(s).empty(); + } + +private: + static bool fail(std::string* error, const char* code) { + *error = code; + return false; + } +}; diff --git a/editor/tests/step603_test.cpp b/editor/tests/step603_test.cpp new file mode 100644 index 0000000..9854408 --- /dev/null +++ b/editor/tests/step603_test.cpp @@ -0,0 +1,146 @@ +// Step 603: Compliance Operational Readiness (12 tests) + +#include "ComplianceOperationalReadiness.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 ComplianceReadinessSnapshot healthySnapshot() { + return {4, 6, 3, 0, 2, 0}; +} + +void test_validate_input_success() { + TEST(validate_input_success); + ComplianceReadinessSnapshot s = healthySnapshot(); + std::string error; + CHECK(ComplianceOperationalReadiness::validateInput(s, &error), "validation should succeed"); + CHECK(error.empty(), "error should be empty"); + PASS(); +} + +void test_validate_input_rejects_negative_controls_covered() { + TEST(validate_input_rejects_negative_controls_covered); + ComplianceReadinessSnapshot s = healthySnapshot(); + s.controlsCovered = -1; + std::string error; + CHECK(!ComplianceOperationalReadiness::validateInput(s, &error), "validation should fail"); + CHECK(error == "controls_covered_invalid", "wrong error"); + PASS(); +} + +void test_validate_input_rejects_negative_artifact_count() { + TEST(validate_input_rejects_negative_artifact_count); + ComplianceReadinessSnapshot s = healthySnapshot(); + s.artifactCount = -1; + std::string error; + CHECK(!ComplianceOperationalReadiness::validateInput(s, &error), "validation should fail"); + CHECK(error == "artifact_count_invalid", "wrong error"); + PASS(); +} + +void test_validate_input_rejects_negative_signed_attestations() { + TEST(validate_input_rejects_negative_signed_attestations); + ComplianceReadinessSnapshot s = healthySnapshot(); + s.signedAttestations = -1; + std::string error; + CHECK(!ComplianceOperationalReadiness::validateInput(s, &error), "validation should fail"); + CHECK(error == "signed_attestations_invalid", "wrong error"); + PASS(); +} + +void test_validate_input_rejects_negative_expiring_soon() { + TEST(validate_input_rejects_negative_expiring_soon); + ComplianceReadinessSnapshot s = healthySnapshot(); + s.expiringSoon = -1; + std::string error; + CHECK(!ComplianceOperationalReadiness::validateInput(s, &error), "validation should fail"); + CHECK(error == "expiring_soon_invalid", "wrong error"); + PASS(); +} + +void test_validate_input_rejects_negative_open_runbooks() { + TEST(validate_input_rejects_negative_open_runbooks); + ComplianceReadinessSnapshot s = healthySnapshot(); + s.openRunbooks = -1; + std::string error; + CHECK(!ComplianceOperationalReadiness::validateInput(s, &error), "validation should fail"); + CHECK(error == "open_runbooks_invalid", "wrong error"); + PASS(); +} + +void test_readiness_score_increases_with_positive_signals() { + TEST(readiness_score_increases_with_positive_signals); + ComplianceReadinessSnapshot low = {1, 1, 0, 0, 0, 0}; + ComplianceReadinessSnapshot high = {5, 8, 4, 0, 2, 0}; + CHECK(ComplianceOperationalReadiness::readinessScore(high) > + ComplianceOperationalReadiness::readinessScore(low), "high score should be greater"); + PASS(); +} + +void test_readiness_score_penalizes_risk_signals() { + TEST(readiness_score_penalizes_risk_signals); + ComplianceReadinessSnapshot clean = healthySnapshot(); + ComplianceReadinessSnapshot risky = healthySnapshot(); + risky.expiringSoon = 3; + risky.openRunbooks = 2; + CHECK(ComplianceOperationalReadiness::readinessScore(risky) < + ComplianceOperationalReadiness::readinessScore(clean), "risky score should be lower"); + PASS(); +} + +void test_readiness_score_clamps_to_zero() { + TEST(readiness_score_clamps_to_zero); + ComplianceReadinessSnapshot s = {0, 0, 0, 9, 0, 9}; + CHECK(ComplianceOperationalReadiness::readinessScore(s) == 0, "score should clamp to zero"); + PASS(); +} + +void test_readiness_score_clamps_to_hundred() { + TEST(readiness_score_clamps_to_hundred); + ComplianceReadinessSnapshot s = {20, 20, 20, 0, 10, 0}; + CHECK(ComplianceOperationalReadiness::readinessScore(s) == 100, "score should clamp to hundred"); + PASS(); +} + +void test_blocking_findings_reports_expected_reasons() { + TEST(blocking_findings_reports_expected_reasons); + ComplianceReadinessSnapshot s = {0, 0, 0, 1, 0, 1}; + const auto findings = ComplianceOperationalReadiness::blockingFindings(s); + CHECK(findings.size() == 5, "finding count mismatch"); + PASS(); +} + +void test_release_eligibility_requires_no_blockers() { + TEST(release_eligibility_requires_no_blockers); + ComplianceReadinessSnapshot healthy = healthySnapshot(); + ComplianceReadinessSnapshot blocked = healthySnapshot(); + blocked.expiringSoon = 1; + CHECK(ComplianceOperationalReadiness::releaseEligible(healthy), "healthy snapshot should be eligible"); + CHECK(!ComplianceOperationalReadiness::releaseEligible(blocked), "blocked snapshot should be ineligible"); + PASS(); +} + +int main() { + std::cout << "Step 603: Compliance Operational Readiness\n"; + + test_validate_input_success(); // 1 + test_validate_input_rejects_negative_controls_covered();// 2 + test_validate_input_rejects_negative_artifact_count(); // 3 + test_validate_input_rejects_negative_signed_attestations();// 4 + test_validate_input_rejects_negative_expiring_soon(); // 5 + test_validate_input_rejects_negative_open_runbooks(); // 6 + test_readiness_score_increases_with_positive_signals(); // 7 + test_readiness_score_penalizes_risk_signals(); // 8 + test_readiness_score_clamps_to_zero(); // 9 + test_readiness_score_clamps_to_hundred(); // 10 + test_blocking_findings_reports_expected_reasons(); // 11 + test_release_eligibility_requires_no_blockers(); // 12 + + std::cout << "\nResults: " << passed << "/" << (passed + failed) << " passed\n"; + return failed == 0 ? 0 : 1; +} diff --git a/progress.md b/progress.md index 7750c3f..6673e5d 100644 --- a/progress.md +++ b/progress.md @@ -11635,3 +11635,33 @@ expiry risk for compliance controls. - `editor/src/ControlAttestationRegistry.h` within header-size limit (`91` <= `600`) - `editor/tests/step602_test.cpp` within test-file size guidance (`157` lines) - Header-only architecture and naming conventions remain aligned with `ARCHITECTURE.md` + +### Step 603: Compliance Operational Readiness +**Status:** PASS (12/12 tests) + +Implements an operational readiness evaluator that scores compliance posture and +surfaces release-blocking findings. + +**Files added:** +- `editor/src/ComplianceOperationalReadiness.h` - readiness evaluation module: + - snapshot validation for non-negative operational metrics + - weighted readiness scoring with `[0,100]` clamp + - blocking-finding generation and release-eligibility decision +- `editor/tests/step603_test.cpp` - 12 tests covering: + - validation success/failure behavior + - positive/risk signal score behavior and clamp boundaries + - blocker reporting and release eligibility behavior + +**Files modified:** +- `editor/CMakeLists.txt` - `step603_test` target + +**Verification run:** +- `cmake -S editor -B editor/build-native` - PASS +- `cmake --build editor/build-native --target step603_test step602_test` - PASS +- `./editor/build-native/step603_test` - PASS (12/12) +- `./editor/build-native/step602_test` - PASS (12/12) regression coverage + +**Architecture gate check:** +- `editor/src/ComplianceOperationalReadiness.h` within header-size limit (`60` <= `600`) +- `editor/tests/step603_test.cpp` within test-file size guidance (`146` lines) +- Header-only architecture and naming conventions remain aligned with `ARCHITECTURE.md`