Add semantic hash table tools, lock scaffolding, and H2 dual-hash migration scaffold

This commit is contained in:
Bill
2026-02-24 11:13:31 -07:00
parent 1a761c62e5
commit f4f3633b11
12 changed files with 665 additions and 6 deletions

View File

@@ -200,6 +200,7 @@
std::string type = params.value("type", "");
bool preferImports = params.value("preferImports", false);
bool strictMode = params.value("strictMode", false);
std::string hashLockMode = params.value("hashLockMode", "warn"); // warn|error
Module* ast = state.mutationAST();
if (!ast) return headlessRpcError(id, -32001, "AST unavailable");
ASTMutationAPI mut;
@@ -207,9 +208,22 @@
ASTMutationAPI::MutationResult res;
LibraryPolicyResult policy;
std::vector<std::string> affectedIds;
std::string hashLockWarning;
auto handleHashLock = [&](ASTNode* n, const std::string& context) -> json {
if (!n) return json();
std::string w = ASTMutationAPI::semanticHashLockWarning(n);
if (w.empty()) return json();
hashLockWarning = w;
if (hashLockMode == "error") {
return headlessRpcError(id, -32012, "Semantic hash lock violation (" + context + "): " + w);
}
return json();
};
if (type == "setProperty") {
std::string nodeId = params.value("nodeId", "");
if (!nodeId.empty()) affectedIds.push_back(nodeId);
json lockErr = handleHashLock(findNodeById(ast, nodeId), "setProperty");
if (!lockErr.is_null()) return lockErr;
res = mut.setProperty(params.value("nodeId", ""),
params.value("property", ""),
params.value("value", ""));
@@ -221,10 +235,14 @@
}
std::string nodeId = params.value("nodeId", "");
if (!nodeId.empty()) affectedIds.push_back(nodeId);
json lockErr = handleHashLock(findNodeById(ast, nodeId), "updateNode");
if (!lockErr.is_null()) return lockErr;
res = mut.updateNode(params.value("nodeId", ""), props);
} else if (type == "deleteNode") {
std::string nodeId = params.value("nodeId", "");
if (!nodeId.empty()) affectedIds.push_back(nodeId);
json lockErr = handleHashLock(findNodeById(ast, nodeId), "deleteNode");
if (!lockErr.is_null()) return lockErr;
res = mut.deleteNode(params.value("nodeId", ""));
} else if (type == "insertNode") {
ASTNode* node = nullptr;
@@ -241,6 +259,11 @@
return headlessRpcError(id, -32011, policy.error);
}
}
json lockErr = handleHashLock(findNodeById(ast, params.value("parentId", "")), "insertNode");
if (!lockErr.is_null()) {
if (node) deleteTree(node);
return lockErr;
}
res = mut.insertNode(params.value("parentId", ""),
params.value("role", ""), node);
if (!res.success && node) {
@@ -267,6 +290,7 @@
}
return headlessRpcResult(id, {
{"success", true}, {"warning", res.warning},
{"hashLockWarning", hashLockWarning},
{"libraryWarning", policy.warning},
{"unknownFunctions", policy.unknownFunctions},
{"version", state.active()
@@ -283,6 +307,7 @@
Module* ast = state.mutationAST();
auto params = request.contains("params") ? request["params"]
: json::object();
std::string hashLockMode = params.value("hashLockMode", "warn"); // warn|error
if (!params.contains("mutations") ||
!params["mutations"].is_array())
return headlessRpcError(id, -32602,
@@ -291,6 +316,7 @@
batch.setRoot(ast);
std::vector<BatchMutationAPI::Mutation> mutations;
std::vector<ASTNode*> ownedNodes;
std::vector<std::string> hashLockWarnings;
for (const auto& m : params["mutations"]) {
BatchMutationAPI::Mutation mut;
mut.type = m.value("type", "");
@@ -303,8 +329,19 @@
mut.newNode = fromJson(m["node"]);
if (mut.newNode) ownedNodes.push_back(mut.newNode);
}
ASTNode* guard = nullptr;
if (mut.type == "insertNode") guard = findNodeById(ast, mut.parentId);
else guard = findNodeById(ast, mut.nodeId);
std::string w = ASTMutationAPI::semanticHashLockWarning(guard);
if (!w.empty()) hashLockWarnings.push_back(w);
mutations.push_back(mut);
}
if (!hashLockWarnings.empty() && hashLockMode == "error") {
for (auto* n : ownedNodes) {
if (n->parent == nullptr) deleteTree(n);
}
return headlessRpcError(id, -32012, "Semantic hash lock violation in batch: " + hashLockWarnings.front());
}
auto batchRes = batch.applySequence(mutations);
if (!batchRes.success) {
for (auto* n : ownedNodes) {
@@ -329,6 +366,7 @@
}
return headlessRpcResult(id,
{{"success", true}, {"appliedCount", batchRes.appliedCount},
{"hashLockWarnings", hashLockWarnings},
{"version", state.active()
? state.active()->versionTracker.version : 0}});
}