Step 524: add typed taskitem contract schema validation

This commit is contained in:
Bill
2026-02-17 09:13:25 -07:00
parent 30440d04ac
commit 2e1dae4239
4 changed files with 305 additions and 0 deletions

View File

@@ -3595,4 +3595,13 @@ target_link_libraries(step523_test PRIVATE
tree_sitter_javascript tree_sitter_typescript
tree_sitter_java tree_sitter_rust tree_sitter_go)
add_executable(step524_test tests/step524_test.cpp)
target_include_directories(step524_test PRIVATE src)
target_link_libraries(step524_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,114 @@
#pragma once
// Step 524: Typed Taskitem Contract Schema
#include <string>
#include <vector>
#include <set>
#include <nlohmann/json.hpp>
using json = nlohmann::json;
struct TaskitemContract {
std::string id;
std::string nodeId;
std::string language;
std::vector<std::string> allowedTargets;
std::vector<std::string> allowedOps;
std::vector<std::string> allowedSymbols;
std::vector<std::string> forbiddenSymbols;
std::vector<std::string> expectedDiagnosticsAdd;
std::vector<std::string> expectedDiagnosticsRemove;
};
struct ContractValidationResult {
bool valid = false;
std::vector<std::string> errors;
TaskitemContract contract;
};
class TypedTaskitemContractSchema {
public:
static ContractValidationResult parseAndValidate(const json& j) {
ContractValidationResult r;
requireString(j, "id", r.errors);
requireString(j, "nodeId", r.errors);
requireString(j, "language", r.errors);
requireStringArray(j, "allowedTargets", r.errors);
requireStringArray(j, "allowedOps", r.errors);
requireStringArray(j, "allowedSymbols", r.errors);
requireStringArray(j, "forbiddenSymbols", r.errors);
requireStringArray(j, "expectedDiagnosticsAdd", r.errors);
requireStringArray(j, "expectedDiagnosticsRemove", r.errors);
if (r.errors.empty()) {
r.contract.id = j["id"].get<std::string>();
r.contract.nodeId = j["nodeId"].get<std::string>();
r.contract.language = j["language"].get<std::string>();
r.contract.allowedTargets = j["allowedTargets"].get<std::vector<std::string>>();
r.contract.allowedOps = j["allowedOps"].get<std::vector<std::string>>();
r.contract.allowedSymbols = j["allowedSymbols"].get<std::vector<std::string>>();
r.contract.forbiddenSymbols = j["forbiddenSymbols"].get<std::vector<std::string>>();
r.contract.expectedDiagnosticsAdd = j["expectedDiagnosticsAdd"].get<std::vector<std::string>>();
r.contract.expectedDiagnosticsRemove = j["expectedDiagnosticsRemove"].get<std::vector<std::string>>();
validateSemanticRules(r.contract, r.errors);
}
r.valid = r.errors.empty();
return r;
}
static bool opAllowed(const TaskitemContract& c, const std::string& op) {
for (const auto& a : c.allowedOps) if (a == op) return true;
return false;
}
static bool symbolAllowed(const TaskitemContract& c, const std::string& sym) {
for (const auto& f : c.forbiddenSymbols) if (f == sym) return false;
for (const auto& a : c.allowedSymbols) if (a == sym) return true;
return false;
}
private:
static void requireString(const json& j, const std::string& key,
std::vector<std::string>& errors) {
if (!j.contains(key) || !j[key].is_string() || j[key].get<std::string>().empty()) {
errors.push_back("missing-or-invalid:" + key);
}
}
static void requireStringArray(const json& j, const std::string& key,
std::vector<std::string>& errors) {
if (!j.contains(key) || !j[key].is_array()) {
errors.push_back("missing-or-invalid:" + key);
return;
}
for (const auto& v : j[key]) {
if (!v.is_string() || v.get<std::string>().empty()) {
errors.push_back("invalid-array-item:" + key);
return;
}
}
}
static void validateSemanticRules(const TaskitemContract& c,
std::vector<std::string>& errors) {
if (c.allowedTargets.empty()) errors.push_back("under-specified:allowedTargets");
if (c.allowedOps.empty()) errors.push_back("under-specified:allowedOps");
std::set<std::string> ops(c.allowedOps.begin(), c.allowedOps.end());
if (ops.size() != c.allowedOps.size()) errors.push_back("duplicate:allowedOps");
std::set<std::string> allowed(c.allowedSymbols.begin(), c.allowedSymbols.end());
for (const auto& f : c.forbiddenSymbols) {
if (allowed.count(f) > 0) {
errors.push_back("symbol-conflict:" + f);
}
}
if (c.expectedDiagnosticsAdd.empty() && c.expectedDiagnosticsRemove.empty()) {
errors.push_back("under-specified:expectedDiagnosticsDelta");
}
}
};

View File

@@ -0,0 +1,148 @@
// Step 524: Typed Taskitem Contract Schema (12 tests)
#include "TypedTaskitemContractSchema.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 json validContractJson() {
return {
{"id", "ti-1"},
{"nodeId", "fn-1"},
{"language", "cpp"},
{"allowedTargets", json::array({"fn-1", "var-2"})},
{"allowedOps", json::array({"update", "rename"})},
{"allowedSymbols", json::array({"foo", "bar"})},
{"forbiddenSymbols", json::array({"baz"})},
{"expectedDiagnosticsAdd", json::array({"E0201"})},
{"expectedDiagnosticsRemove", json::array()}
};
}
void test_valid_contract_passes() {
TEST(valid_contract_passes);
auto r = TypedTaskitemContractSchema::parseAndValidate(validContractJson());
CHECK(r.valid, "valid contract should pass");
PASS();
}
void test_missing_id_rejected() {
TEST(missing_id_rejected);
auto j = validContractJson();
j.erase("id");
auto r = TypedTaskitemContractSchema::parseAndValidate(j);
CHECK(!r.valid, "missing id should fail");
PASS();
}
void test_missing_allowed_targets_rejected() {
TEST(missing_allowed_targets_rejected);
auto j = validContractJson();
j.erase("allowedTargets");
auto r = TypedTaskitemContractSchema::parseAndValidate(j);
CHECK(!r.valid, "missing allowedTargets should fail");
PASS();
}
void test_empty_allowed_ops_rejected() {
TEST(empty_allowed_ops_rejected);
auto j = validContractJson();
j["allowedOps"] = json::array();
auto r = TypedTaskitemContractSchema::parseAndValidate(j);
CHECK(!r.valid, "empty allowedOps should fail");
PASS();
}
void test_duplicate_allowed_ops_rejected() {
TEST(duplicate_allowed_ops_rejected);
auto j = validContractJson();
j["allowedOps"] = json::array({"update", "update"});
auto r = TypedTaskitemContractSchema::parseAndValidate(j);
CHECK(!r.valid, "duplicate allowedOps should fail");
PASS();
}
void test_symbol_conflict_rejected() {
TEST(symbol_conflict_rejected);
auto j = validContractJson();
j["forbiddenSymbols"] = json::array({"foo"});
auto r = TypedTaskitemContractSchema::parseAndValidate(j);
CHECK(!r.valid, "symbol overlap should fail");
PASS();
}
void test_missing_diagnostics_delta_rejected() {
TEST(missing_diagnostics_delta_rejected);
auto j = validContractJson();
j["expectedDiagnosticsAdd"] = json::array();
j["expectedDiagnosticsRemove"] = json::array();
auto r = TypedTaskitemContractSchema::parseAndValidate(j);
CHECK(!r.valid, "missing diagnostics delta should fail");
PASS();
}
void test_invalid_array_item_rejected() {
TEST(invalid_array_item_rejected);
auto j = validContractJson();
j["allowedSymbols"] = json::array({"foo", 1});
auto r = TypedTaskitemContractSchema::parseAndValidate(j);
CHECK(!r.valid, "non-string array item should fail");
PASS();
}
void test_op_allowed_true_for_listed_op() {
TEST(op_allowed_true_for_listed_op);
auto r = TypedTaskitemContractSchema::parseAndValidate(validContractJson());
CHECK(r.valid && TypedTaskitemContractSchema::opAllowed(r.contract, "rename"),
"rename should be allowed");
PASS();
}
void test_op_allowed_false_for_unlisted_op() {
TEST(op_allowed_false_for_unlisted_op);
auto r = TypedTaskitemContractSchema::parseAndValidate(validContractJson());
CHECK(r.valid && !TypedTaskitemContractSchema::opAllowed(r.contract, "delete"),
"delete should not be allowed");
PASS();
}
void test_symbol_allowed_false_for_forbidden() {
TEST(symbol_allowed_false_for_forbidden);
auto r = TypedTaskitemContractSchema::parseAndValidate(validContractJson());
CHECK(r.valid && !TypedTaskitemContractSchema::symbolAllowed(r.contract, "baz"),
"forbidden symbol should be blocked");
PASS();
}
void test_symbol_allowed_false_for_unknown() {
TEST(symbol_allowed_false_for_unknown);
auto r = TypedTaskitemContractSchema::parseAndValidate(validContractJson());
CHECK(r.valid && !TypedTaskitemContractSchema::symbolAllowed(r.contract, "unknown"),
"unknown symbol should be blocked");
PASS();
}
int main() {
std::cout << "Step 524: Typed Taskitem Contract Schema\n";
test_valid_contract_passes(); // 1
test_missing_id_rejected(); // 2
test_missing_allowed_targets_rejected(); // 3
test_empty_allowed_ops_rejected(); // 4
test_duplicate_allowed_ops_rejected(); // 5
test_symbol_conflict_rejected(); // 6
test_missing_diagnostics_delta_rejected(); // 7
test_invalid_array_item_rejected(); // 8
test_op_allowed_true_for_listed_op(); // 9
test_op_allowed_false_for_unlisted_op(); // 10
test_symbol_allowed_false_for_forbidden(); // 11
test_symbol_allowed_false_for_unknown(); // 12
std::cout << "\nResults: " << passed << "/" << (passed + failed) << " passed\n";
return failed == 0 ? 0 : 1;
}

View File

@@ -8557,3 +8557,37 @@ architecture-rule alignment without behavior changes.
- `./editor/build-native/step521_test` - PASS (12/12)
- `./editor/build-native/step522_test` - PASS (12/12)
- `./editor/build-native/step523_test` - PASS (8/8)
### Step 524: Typed Taskitem Contract Schema
**Status:** PASS (12/12 tests)
Implements a strict typed taskitem contract schema for constrained execution
with required fields for allowed targets/ops/symbols, forbidden symbols, and
expected diagnostics deltas, rejecting under-specified taskitems.
**Files added:**
- `editor/src/TypedTaskitemContractSchema.h` - schema module:
- JSON parse + required-field validation
- semantic consistency rules (duplicate op detection, symbol conflict detection)
- under-specification guards for constrained execution path
- helper checks for operation/symbol legality
- `editor/tests/step524_test.cpp` - 12 tests covering:
- valid contract acceptance
- required-field rejection paths
- duplicate-op and symbol-conflict rejection
- diagnostics-delta under-specification rejection
- operation/symbol allow/deny helper behavior
**Files modified:**
- `editor/CMakeLists.txt` - `step524_test` target
**Verification run:**
- `cmake -S editor -B editor/build-native` - PASS
- `cmake --build editor/build-native --target step524_test` - PASS
- `./editor/build-native/step524_test` - PASS (12/12)
- `./editor/build-native/step523_test` - PASS (8/8) regression coverage
**Architecture gate check:**
- `editor/src/TypedTaskitemContractSchema.h` within header-size limit (`114` <= `600`)
- `editor/tests/step524_test.cpp` within test-file size guidance (`148` lines)
- Header-only architecture and naming conventions remain aligned with `ARCHITECTURE.md`