From b8667457b24de19f4de3acd41cfcad898861c15e Mon Sep 17 00:00:00 2001 From: Bill Date: Tue, 17 Feb 2026 09:20:10 -0700 Subject: [PATCH] Step 528: integrate constrained taskitem execution gate --- editor/CMakeLists.txt | 9 + editor/src/ConstrainedTaskitemIntegration.h | 86 ++++++++++ editor/tests/step528_test.cpp | 179 ++++++++++++++++++++ progress.md | 41 +++++ 4 files changed, 315 insertions(+) create mode 100644 editor/src/ConstrainedTaskitemIntegration.h create mode 100644 editor/tests/step528_test.cpp diff --git a/editor/CMakeLists.txt b/editor/CMakeLists.txt index d9277e5..3b931a4 100644 --- a/editor/CMakeLists.txt +++ b/editor/CMakeLists.txt @@ -3631,4 +3631,13 @@ target_link_libraries(step527_test PRIVATE tree_sitter_javascript tree_sitter_typescript tree_sitter_java tree_sitter_rust tree_sitter_go) +add_executable(step528_test tests/step528_test.cpp) +target_include_directories(step528_test PRIVATE src) +target_link_libraries(step528_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/ConstrainedTaskitemIntegration.h b/editor/src/ConstrainedTaskitemIntegration.h new file mode 100644 index 0000000..640053c --- /dev/null +++ b/editor/src/ConstrainedTaskitemIntegration.h @@ -0,0 +1,86 @@ +#pragma once +// Step 528: Phase 27a Integration + +#include +#include +#include + +#include + +#include "ConstraintViolationDiagnostics.h" + +using json = nlohmann::json; + +struct ConstrainedExecutionRequest { + json taskitemJson; + std::string nodeKind; + std::string requestedOp; + std::vector requestedSymbols; + std::vector visibleSymbols; +}; + +struct ConstrainedExecutionResult { + bool routed = false; + bool executed = false; + TaskitemContract contract; + ScopeSnapshot scopeSnapshot; + ConstraintDiagnosticPacket diagnostics; + std::vector schemaErrors; +}; + +class ConstrainedTaskitemIntegration { +public: + static ConstrainedExecutionResult run(const ConstrainedExecutionRequest& request, + const LegalOperationGraph& graph, + const SymbolScopeExtractor& scopeExtractor) { + ConstrainedExecutionResult result; + + auto validation = TypedTaskitemContractSchema::parseAndValidate(request.taskitemJson); + if (!validation.valid) { + result.schemaErrors = validation.errors; + result.diagnostics = schemaFailurePacket(validation.errors); + return result; + } + + result.contract = validation.contract; + std::set forbidden(result.contract.forbiddenSymbols.begin(), + result.contract.forbiddenSymbols.end()); + + result.scopeSnapshot = scopeExtractor.buildSnapshot( + result.contract.language, + request.nodeKind, + request.visibleSymbols, + forbidden); + + ConstraintCheckInput checkInput; + checkInput.contract = result.contract; + checkInput.language = result.contract.language; + checkInput.nodeKind = request.nodeKind; + checkInput.requestedOp = request.requestedOp; + checkInput.requestedSymbols = request.requestedSymbols; + checkInput.scopeSnapshot = result.scopeSnapshot; + + result.diagnostics = ConstraintViolationDiagnostics::evaluate( + checkInput, graph, scopeExtractor); + result.routed = result.diagnostics.ok; + result.executed = result.diagnostics.ok; + return result; + } + +private: + static ConstraintDiagnosticPacket schemaFailurePacket( + const std::vector& errors) { + ConstraintDiagnosticPacket packet; + packet.ok = false; + packet.recommendedAction = "escalate"; + for (const auto& error : errors) { + packet.violations.push_back({ + "under_constrained_contract", + "Taskitem contract failed schema validation", + error, + false + }); + } + return packet; + } +}; diff --git a/editor/tests/step528_test.cpp b/editor/tests/step528_test.cpp new file mode 100644 index 0000000..90effc8 --- /dev/null +++ b/editor/tests/step528_test.cpp @@ -0,0 +1,179 @@ +// Step 528: Phase 27a Integration (8 tests) + +#include "ConstrainedTaskitemIntegration.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 json validTaskitem() { + return { + {"id", "ti-528"}, + {"nodeId", "n-1"}, + {"language", "cpp"}, + {"allowedTargets", json::array({"Function"})}, + {"allowedOps", json::array({"rename", "update"})}, + {"allowedSymbols", json::array({"cursor", "count", "EditorState"})}, + {"forbiddenSymbols", json::array({"unsafeGlobal"})}, + {"expectedDiagnosticsAdd", json::array({"none"})}, + {"expectedDiagnosticsRemove", json::array()} + }; +} + +static std::vector visibleSymbols() { + return { + {"cursor", "Variable", 3}, + {"count", "Parameter", 3}, + {"EditorState", "Type", 0}, + {"render", "Function", 1}, + {"unsafeGlobal", "Variable", 0} + }; +} + +static bool hasCode(const ConstrainedExecutionResult& result, + const std::string& code) { + for (const auto& v : result.diagnostics.violations) { + if (v.code == code) return true; + } + return false; +} + +void test_valid_taskitem_routes_and_executes() { + TEST(valid_taskitem_routes_and_executes); + LegalOperationGraph graph; + SymbolScopeExtractor extractor; + + ConstrainedExecutionRequest request{ + validTaskitem(), "Function", "rename", {"cursor", "count"}, visibleSymbols() + }; + auto result = ConstrainedTaskitemIntegration::run(request, graph, extractor); + CHECK(result.routed, "request should route"); + CHECK(result.executed, "request should execute"); + CHECK(result.diagnostics.ok, "diagnostics should pass"); + PASS(); +} + +void test_missing_allowed_ops_is_rejected_as_under_constrained() { + TEST(missing_allowed_ops_is_rejected_as_under_constrained); + LegalOperationGraph graph; + SymbolScopeExtractor extractor; + + auto item = validTaskitem(); + item.erase("allowedOps"); + ConstrainedExecutionRequest request{item, "Function", "rename", {"cursor"}, visibleSymbols()}; + auto result = ConstrainedTaskitemIntegration::run(request, graph, extractor); + CHECK(!result.routed, "under-constrained request should not route"); + CHECK(!result.executed, "under-constrained request should not execute"); + CHECK(hasCode(result, "under_constrained_contract"), "missing schema failure code"); + PASS(); +} + +void test_missing_diagnostics_delta_is_rejected() { + TEST(missing_diagnostics_delta_is_rejected); + LegalOperationGraph graph; + SymbolScopeExtractor extractor; + + auto item = validTaskitem(); + item["expectedDiagnosticsAdd"] = json::array(); + item["expectedDiagnosticsRemove"] = json::array(); + ConstrainedExecutionRequest request{item, "Function", "rename", {"cursor"}, visibleSymbols()}; + auto result = ConstrainedTaskitemIntegration::run(request, graph, extractor); + CHECK(!result.routed, "missing diagnostics delta should not route"); + CHECK(hasCode(result, "under_constrained_contract"), "schema failure code expected"); + PASS(); +} + +void test_illegal_operation_fails_with_actionable_diagnostics() { + TEST(illegal_operation_fails_with_actionable_diagnostics); + LegalOperationGraph graph; + SymbolScopeExtractor extractor; + + ConstrainedExecutionRequest request{ + validTaskitem(), "Function", "delete", {"cursor"}, visibleSymbols() + }; + auto result = ConstrainedTaskitemIntegration::run(request, graph, extractor); + CHECK(!result.executed, "illegal op should block execution"); + CHECK(hasCode(result, "illegal_operation"), "illegal operation code missing"); + CHECK(result.diagnostics.recommendedAction == "escalate", "illegal op should escalate"); + PASS(); +} + +void test_out_of_scope_symbol_fails_with_retry_action() { + TEST(out_of_scope_symbol_fails_with_retry_action); + LegalOperationGraph graph; + SymbolScopeExtractor extractor; + + auto item = validTaskitem(); + item["allowedSymbols"].push_back("ghost"); + ConstrainedExecutionRequest request{ + item, "Function", "rename", {"ghost"}, visibleSymbols() + }; + auto result = ConstrainedTaskitemIntegration::run(request, graph, extractor); + CHECK(!result.executed, "out-of-scope symbol should block execution"); + CHECK(hasCode(result, "out_of_scope_symbol"), "out-of-scope code missing"); + CHECK(result.diagnostics.recommendedAction == "retry", "retryable-only failure should retry"); + PASS(); +} + +void test_forbidden_symbol_fails_with_escalation() { + TEST(forbidden_symbol_fails_with_escalation); + LegalOperationGraph graph; + SymbolScopeExtractor extractor; + + ConstrainedExecutionRequest request{ + validTaskitem(), "Function", "rename", {"unsafeGlobal"}, visibleSymbols() + }; + auto result = ConstrainedTaskitemIntegration::run(request, graph, extractor); + CHECK(!result.executed, "forbidden symbol should block execution"); + CHECK(hasCode(result, "forbidden_symbol"), "forbidden symbol code missing"); + CHECK(result.diagnostics.recommendedAction == "escalate", "forbidden symbol should escalate"); + PASS(); +} + +void test_unsupported_context_fails_fast() { + TEST(unsupported_context_fails_fast); + LegalOperationGraph graph; + SymbolScopeExtractor extractor; + + ConstrainedExecutionRequest request{ + validTaskitem(), "MacroBlock", "rename", {"cursor"}, visibleSymbols() + }; + auto result = ConstrainedTaskitemIntegration::run(request, graph, extractor); + CHECK(!result.executed, "unsupported context should block execution"); + CHECK(hasCode(result, "unsupported_context"), "unsupported context code missing"); + PASS(); +} + +void test_duplicate_allowed_ops_is_rejected_by_schema_gate() { + TEST(duplicate_allowed_ops_is_rejected_by_schema_gate); + LegalOperationGraph graph; + SymbolScopeExtractor extractor; + + auto item = validTaskitem(); + item["allowedOps"] = json::array({"rename", "rename"}); + ConstrainedExecutionRequest request{item, "Function", "rename", {"cursor"}, visibleSymbols()}; + auto result = ConstrainedTaskitemIntegration::run(request, graph, extractor); + CHECK(!result.executed, "duplicate allowedOps should fail schema gate"); + CHECK(hasCode(result, "under_constrained_contract"), "schema gate should annotate failure"); + PASS(); +} + +int main() { + std::cout << "Step 528: Phase 27a Integration\n"; + + test_valid_taskitem_routes_and_executes(); // 1 + test_missing_allowed_ops_is_rejected_as_under_constrained(); // 2 + test_missing_diagnostics_delta_is_rejected(); // 3 + test_illegal_operation_fails_with_actionable_diagnostics(); // 4 + test_out_of_scope_symbol_fails_with_retry_action(); // 5 + test_forbidden_symbol_fails_with_escalation(); // 6 + test_unsupported_context_fails_fast(); // 7 + test_duplicate_allowed_ops_is_rejected_by_schema_gate(); // 8 + + std::cout << "\nResults: " << passed << "/" << (passed + failed) << " passed\n"; + return failed == 0 ? 0 : 1; +} diff --git a/progress.md b/progress.md index ce0945a..f2ee5f7 100644 --- a/progress.md +++ b/progress.md @@ -8697,3 +8697,44 @@ violations with machine-readable retry/escalation payloads. - `editor/src/ConstraintViolationDiagnostics.h` within header-size limit (`169` <= `600`) - `editor/tests/step527_test.cpp` within test-file size guidance (`223` lines) - Header-only architecture and naming conventions remain aligned with `ARCHITECTURE.md` + +### Step 528: Phase 27a Integration +**Status:** PASS (8/8 tests) + +Integrates the Phase 27a constrained pipeline so taskitems must pass typed +contract validation, symbol-scope extraction, legal operation checks, and +constraint diagnostics before execution is allowed. + +**Files added:** +- `editor/src/ConstrainedTaskitemIntegration.h` - phase integration gate: + - schema-first taskitem validation via Step 524 contract module + - forbidden-aware scope snapshot extraction via Step 526 + - constraint diagnostics evaluation via Step 527 + - deterministic route/execute decision (`routed` + `executed`) + - actionable schema-failure packet generation for under-constrained taskitems +- `editor/tests/step528_test.cpp` - 8 tests covering: + - full constrained path success for valid taskitem + - schema rejection for missing required constraint fields + - schema rejection for missing diagnostics delta and duplicate allowed ops + - illegal operation rejection with escalation diagnostics + - out-of-scope symbol rejection with retry diagnostics + - forbidden symbol and unsupported-context rejection paths + +**Files modified:** +- `editor/CMakeLists.txt` - `step528_test` target + +**Verification run:** +- `cmake -S editor -B editor/build-native` - PASS +- `cmake --build editor/build-native --target step528_test step527_test` - PASS +- `./editor/build-native/step528_test` - PASS (8/8) +- `./editor/build-native/step527_test` - PASS (12/12) regression coverage + +**Architecture gate check:** +- `editor/src/ConstrainedTaskitemIntegration.h` within header-size limit (`86` <= `600`) +- `editor/tests/step528_test.cpp` within test-file size guidance (`179` lines) +- Header-only architecture and naming conventions remain aligned with `ARCHITECTURE.md` + +**Phase 27a totals (524-528):** +- **Steps completed:** 5 +- **New tests in phase plan:** 56/56 passing +- **Constraint model + diagnostics + integration:** PASS