Step 527: add structured constraint violation diagnostics

This commit is contained in:
Bill
2026-02-17 09:18:44 -07:00
parent 398efe9455
commit b1f95cd699
4 changed files with 439 additions and 0 deletions

View File

@@ -3622,4 +3622,13 @@ target_link_libraries(step526_test PRIVATE
tree_sitter_javascript tree_sitter_typescript
tree_sitter_java tree_sitter_rust tree_sitter_go)
add_executable(step527_test tests/step527_test.cpp)
target_include_directories(step527_test PRIVATE src)
target_link_libraries(step527_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)

View File

@@ -0,0 +1,169 @@
#pragma once
// Step 527: Constraint Violation Diagnostics
#include <algorithm>
#include <string>
#include <vector>
#include <nlohmann/json.hpp>
#include "LegalOperationGraph.h"
#include "SymbolScopeExtractor.h"
#include "TypedTaskitemContractSchema.h"
using json = nlohmann::json;
struct ConstraintViolation {
std::string code;
std::string message;
std::string field;
bool retryable = false;
};
struct ConstraintDiagnosticPacket {
bool ok = true;
std::string recommendedAction = "proceed";
std::vector<ConstraintViolation> violations;
json toJson() const {
json out;
out["ok"] = ok;
out["recommendedAction"] = recommendedAction;
out["violations"] = json::array();
for (const auto& v : violations) {
out["violations"].push_back({
{"code", v.code},
{"message", v.message},
{"field", v.field},
{"retryable", v.retryable}
});
}
return out;
}
};
struct ConstraintCheckInput {
TaskitemContract contract;
std::string language;
std::string nodeKind;
std::string requestedOp;
std::vector<std::string> requestedSymbols;
ScopeSnapshot scopeSnapshot;
};
class ConstraintViolationDiagnostics {
public:
static ConstraintDiagnosticPacket evaluate(
const ConstraintCheckInput& input,
const LegalOperationGraph& graph,
const SymbolScopeExtractor& scopeExtractor) {
ConstraintDiagnosticPacket packet;
addOperationViolations(packet, input, graph);
addSymbolViolations(packet, input, scopeExtractor);
deduplicate(packet.violations);
packet.ok = packet.violations.empty();
packet.recommendedAction = decideAction(packet.violations);
return packet;
}
private:
static void addOperationViolations(ConstraintDiagnosticPacket& packet,
const ConstraintCheckInput& input,
const LegalOperationGraph& graph) {
auto legal = graph.allowedOps(input.language, input.nodeKind);
if (!legal.supported) {
packet.violations.push_back({
"unsupported_context",
"No legal operation mapping for language/nodeKind",
input.language + "::" + input.nodeKind,
false
});
return;
}
if (!graph.opAllowed(input.language, input.nodeKind, input.requestedOp)) {
packet.violations.push_back({
"illegal_operation",
"Requested operation is illegal for this AST node",
input.requestedOp,
false
});
}
if (!TypedTaskitemContractSchema::opAllowed(input.contract, input.requestedOp)) {
packet.violations.push_back({
"contract_forbidden_operation",
"Requested operation is outside taskitem contract",
input.requestedOp,
false
});
}
}
static void addSymbolViolations(ConstraintDiagnosticPacket& packet,
const ConstraintCheckInput& input,
const SymbolScopeExtractor& scopeExtractor) {
auto unresolved = scopeExtractor.unresolvedSymbols(
input.scopeSnapshot, input.requestedSymbols);
for (const auto& symbol : unresolved) {
packet.violations.push_back({
"out_of_scope_symbol",
"Requested symbol is not in extracted scope snapshot",
symbol,
true
});
}
for (const auto& symbol : input.requestedSymbols) {
if (isForbidden(input.contract, symbol)) {
packet.violations.push_back({
"forbidden_symbol",
"Requested symbol is explicitly forbidden by contract",
symbol,
false
});
continue;
}
if (!TypedTaskitemContractSchema::symbolAllowed(input.contract, symbol)) {
packet.violations.push_back({
"contract_disallowed_symbol",
"Requested symbol is outside contract allowedSymbols",
symbol,
false
});
}
}
}
static bool isForbidden(const TaskitemContract& contract,
const std::string& symbol) {
return std::find(contract.forbiddenSymbols.begin(),
contract.forbiddenSymbols.end(),
symbol) != contract.forbiddenSymbols.end();
}
static void deduplicate(std::vector<ConstraintViolation>& violations) {
std::vector<ConstraintViolation> out;
for (const auto& v : violations) {
bool exists = false;
for (const auto& existing : out) {
if (existing.code == v.code && existing.field == v.field) {
exists = true;
break;
}
}
if (!exists) out.push_back(v);
}
violations.swap(out);
}
static std::string decideAction(const std::vector<ConstraintViolation>& violations) {
if (violations.empty()) return "proceed";
for (const auto& v : violations) {
if (!v.retryable) return "escalate";
}
return "retry";
}
};

View File

@@ -0,0 +1,223 @@
// Step 527: Constraint Violation Diagnostics (12 tests)
#include "ConstraintViolationDiagnostics.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 TaskitemContract makeContract() {
json j = {
{"id", "ti-527"},
{"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()}
};
auto result = TypedTaskitemContractSchema::parseAndValidate(j);
return result.contract;
}
static ScopeSnapshot makeSnapshot(const SymbolScopeExtractor& extractor) {
return extractor.buildSnapshot("cpp", "Function", {
{"cursor", "Variable", 3},
{"count", "Parameter", 3},
{"EditorState", "Type", 0},
{"render", "Function", 1}
});
}
static bool hasCode(const ConstraintDiagnosticPacket& packet,
const std::string& code) {
for (const auto& v : packet.violations) {
if (v.code == code) return true;
}
return false;
}
static int countCode(const ConstraintDiagnosticPacket& packet,
const std::string& code) {
int count = 0;
for (const auto& v : packet.violations) {
if (v.code == code) ++count;
}
return count;
}
void test_valid_request_produces_no_violations() {
TEST(valid_request_produces_no_violations);
LegalOperationGraph graph;
SymbolScopeExtractor extractor;
ConstraintCheckInput input{makeContract(), "cpp", "Function", "rename",
{"cursor", "count"}, makeSnapshot(extractor)};
auto packet = ConstraintViolationDiagnostics::evaluate(input, graph, extractor);
CHECK(packet.ok, "packet should be ok");
CHECK(packet.recommendedAction == "proceed", "action should be proceed");
CHECK(packet.violations.empty(), "no violations expected");
PASS();
}
void test_contract_forbidden_operation_is_reported() {
TEST(contract_forbidden_operation_is_reported);
LegalOperationGraph graph;
SymbolScopeExtractor extractor;
ConstraintCheckInput input{makeContract(), "cpp", "Function", "extract",
{"cursor"}, makeSnapshot(extractor)};
auto packet = ConstraintViolationDiagnostics::evaluate(input, graph, extractor);
CHECK(hasCode(packet, "contract_forbidden_operation"), "missing violation");
CHECK(packet.recommendedAction == "escalate", "non-retryable should escalate");
PASS();
}
void test_illegal_graph_operation_is_reported() {
TEST(illegal_graph_operation_is_reported);
LegalOperationGraph graph;
SymbolScopeExtractor extractor;
ConstraintCheckInput input{makeContract(), "cpp", "Function", "delete",
{"cursor"}, makeSnapshot(extractor)};
auto packet = ConstraintViolationDiagnostics::evaluate(input, graph, extractor);
CHECK(hasCode(packet, "illegal_operation"), "illegal op violation expected");
PASS();
}
void test_unsupported_context_is_reported() {
TEST(unsupported_context_is_reported);
LegalOperationGraph graph;
SymbolScopeExtractor extractor;
ConstraintCheckInput input{makeContract(), "cpp", "MysteryNode", "rename",
{"cursor"}, makeSnapshot(extractor)};
auto packet = ConstraintViolationDiagnostics::evaluate(input, graph, extractor);
CHECK(hasCode(packet, "unsupported_context"), "unsupported context violation expected");
CHECK(packet.recommendedAction == "escalate", "unsupported context should escalate");
PASS();
}
void test_out_of_scope_symbol_is_reported() {
TEST(out_of_scope_symbol_is_reported);
LegalOperationGraph graph;
SymbolScopeExtractor extractor;
ConstraintCheckInput input{makeContract(), "cpp", "Function", "rename",
{"ghost"}, makeSnapshot(extractor)};
auto packet = ConstraintViolationDiagnostics::evaluate(input, graph, extractor);
CHECK(hasCode(packet, "out_of_scope_symbol"), "missing out-of-scope violation");
PASS();
}
void test_retry_action_for_retryable_only_violations() {
TEST(retry_action_for_retryable_only_violations);
LegalOperationGraph graph;
SymbolScopeExtractor extractor;
auto contract = makeContract();
contract.allowedSymbols.push_back("ghost");
ConstraintCheckInput input{contract, "cpp", "Function", "rename",
{"ghost"}, makeSnapshot(extractor)};
auto packet = ConstraintViolationDiagnostics::evaluate(input, graph, extractor);
CHECK(packet.recommendedAction == "retry", "retryable-only violations should retry");
CHECK(countCode(packet, "out_of_scope_symbol") == 1, "expected out-of-scope violation");
PASS();
}
void test_forbidden_symbol_is_reported() {
TEST(forbidden_symbol_is_reported);
LegalOperationGraph graph;
SymbolScopeExtractor extractor;
ConstraintCheckInput input{makeContract(), "cpp", "Function", "rename",
{"unsafeGlobal"}, makeSnapshot(extractor)};
auto packet = ConstraintViolationDiagnostics::evaluate(input, graph, extractor);
CHECK(hasCode(packet, "forbidden_symbol"), "forbidden symbol violation expected");
CHECK(packet.recommendedAction == "escalate", "forbidden symbol should escalate");
PASS();
}
void test_contract_disallowed_symbol_is_reported() {
TEST(contract_disallowed_symbol_is_reported);
LegalOperationGraph graph;
SymbolScopeExtractor extractor;
ConstraintCheckInput input{makeContract(), "cpp", "Function", "rename",
{"render"}, makeSnapshot(extractor)};
auto packet = ConstraintViolationDiagnostics::evaluate(input, graph, extractor);
CHECK(hasCode(packet, "contract_disallowed_symbol"), "contract disallowed violation expected");
PASS();
}
void test_duplicate_symbol_requests_are_deduplicated() {
TEST(duplicate_symbol_requests_are_deduplicated);
LegalOperationGraph graph;
SymbolScopeExtractor extractor;
ConstraintCheckInput input{makeContract(), "cpp", "Function", "rename",
{"ghost", "ghost", "ghost"}, makeSnapshot(extractor)};
auto packet = ConstraintViolationDiagnostics::evaluate(input, graph, extractor);
CHECK(countCode(packet, "out_of_scope_symbol") == 1, "out-of-scope violations should dedupe");
CHECK(countCode(packet, "contract_disallowed_symbol") == 1,
"contract disallowed violations should dedupe");
PASS();
}
void test_multiple_violation_types_are_aggregated() {
TEST(multiple_violation_types_are_aggregated);
LegalOperationGraph graph;
SymbolScopeExtractor extractor;
ConstraintCheckInput input{makeContract(), "cpp", "Function", "delete",
{"ghost", "unsafeGlobal"}, makeSnapshot(extractor)};
auto packet = ConstraintViolationDiagnostics::evaluate(input, graph, extractor);
CHECK(hasCode(packet, "illegal_operation"), "missing illegal operation");
CHECK(hasCode(packet, "contract_forbidden_operation"), "missing forbidden operation");
CHECK(hasCode(packet, "out_of_scope_symbol"), "missing out-of-scope symbol");
CHECK(hasCode(packet, "forbidden_symbol"), "missing forbidden symbol");
PASS();
}
void test_to_json_contains_machine_readable_fields() {
TEST(to_json_contains_machine_readable_fields);
LegalOperationGraph graph;
SymbolScopeExtractor extractor;
ConstraintCheckInput input{makeContract(), "cpp", "Function", "rename",
{"ghost"}, makeSnapshot(extractor)};
auto packet = ConstraintViolationDiagnostics::evaluate(input, graph, extractor);
auto encoded = packet.toJson();
CHECK(encoded.contains("ok"), "json should contain ok");
CHECK(encoded.contains("recommendedAction"), "json should contain recommendedAction");
CHECK(encoded.contains("violations"), "json should contain violations");
CHECK(encoded["violations"].is_array(), "violations should be array");
PASS();
}
void test_empty_symbol_request_is_valid() {
TEST(empty_symbol_request_is_valid);
LegalOperationGraph graph;
SymbolScopeExtractor extractor;
ConstraintCheckInput input{makeContract(), "cpp", "Function", "rename",
{}, makeSnapshot(extractor)};
auto packet = ConstraintViolationDiagnostics::evaluate(input, graph, extractor);
CHECK(packet.ok, "empty symbol set should be valid");
CHECK(packet.recommendedAction == "proceed", "empty request should proceed");
PASS();
}
int main() {
std::cout << "Step 527: Constraint Violation Diagnostics\n";
test_valid_request_produces_no_violations(); // 1
test_contract_forbidden_operation_is_reported(); // 2
test_illegal_graph_operation_is_reported(); // 3
test_unsupported_context_is_reported(); // 4
test_out_of_scope_symbol_is_reported(); // 5
test_retry_action_for_retryable_only_violations(); // 6
test_forbidden_symbol_is_reported(); // 7
test_contract_disallowed_symbol_is_reported(); // 8
test_duplicate_symbol_requests_are_deduplicated(); // 9
test_multiple_violation_types_are_aggregated(); // 10
test_to_json_contains_machine_readable_fields(); // 11
test_empty_symbol_request_is_valid(); // 12
std::cout << "\nResults: " << passed << "/" << (passed + failed) << " passed\n";
return failed == 0 ? 0 : 1;
}

View File

@@ -8659,3 +8659,41 @@ unresolved symbol requests before apply.
- `editor/src/SymbolScopeExtractor.h` within header-size limit (`102` <= `600`)
- `editor/tests/step526_test.cpp` within test-file size guidance (`158` lines)
- Header-only architecture and naming conventions remain aligned with `ARCHITECTURE.md`
### Step 527: Constraint Violation Diagnostics
**Status:** PASS (12/12 tests)
Adds structured constraint diagnostics for constrained execution failures,
including out-of-scope symbol usage, illegal operations, and contract-policy
violations with machine-readable retry/escalation payloads.
**Files added:**
- `editor/src/ConstraintViolationDiagnostics.h` - diagnostics module:
- evaluates operation legality against legal-op graph + taskitem contract
- evaluates symbol usage against extracted scope snapshot + contract policy
- emits structured violation records with retryability metadata
- computes deterministic recommended action (`proceed`/`retry`/`escalate`)
- serializes diagnostics packet to JSON for retry/escalation routing
- `editor/tests/step527_test.cpp` - 12 tests covering:
- clean-path packet behavior with no violations
- contract-forbidden and graph-illegal operation violations
- unsupported context violations
- out-of-scope and forbidden/disallowed symbol violations
- retry-only vs escalate action selection
- violation deduplication and multi-violation aggregation
- machine-readable JSON payload shape
- empty symbol request edge case
**Files modified:**
- `editor/CMakeLists.txt` - `step527_test` target
**Verification run:**
- `cmake -S editor -B editor/build-native` - PASS
- `cmake --build editor/build-native --target step527_test step526_test` - PASS
- `./editor/build-native/step527_test` - PASS (12/12)
- `./editor/build-native/step526_test` - PASS (12/12) regression coverage
**Architecture gate check:**
- `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`