Add semantic hash table tools, lock scaffolding, and H2 dual-hash migration scaffold
This commit is contained in:
@@ -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}});
|
||||
}
|
||||
|
||||
@@ -96,7 +96,9 @@
|
||||
return headlessRpcResult(id,
|
||||
{{"success", true},
|
||||
{"sidecarPath", res.sidecarPath},
|
||||
{"annotationCount", res.annotationCount}});
|
||||
{"annotationCount", res.annotationCount},
|
||||
{"semanticHashTablePath", res.semanticHashTablePath},
|
||||
{"semanticHashCount", res.semanticHashCount}});
|
||||
}
|
||||
|
||||
// --- loadAnnotatedAST ---
|
||||
@@ -130,6 +132,107 @@
|
||||
{{"files", files}, {"count", (int)files.size()}});
|
||||
}
|
||||
|
||||
// --- saveSemanticHashTable ---
|
||||
if (method == "saveSemanticHashTable") {
|
||||
if (!AgentPermissionPolicy::canInvoke(role, method))
|
||||
return headlessRpcError(id, -32031, "Role not permitted");
|
||||
auto params = request.contains("params") ? request["params"] : json::object();
|
||||
std::string path = params.value("path", "");
|
||||
if (path.empty() && state.activeBuffer) path = state.activeBuffer->path;
|
||||
auto it = state.bufferStates.find(path);
|
||||
if (it == state.bufferStates.end())
|
||||
return headlessRpcError(id, -32602, "No buffer: " + path);
|
||||
Module* ast = it->second->sync.getAST();
|
||||
auto res = saveSemanticHashTable(state.workspaceRoot, path, ast);
|
||||
if (!res.success) return headlessRpcError(id, -32010, res.error);
|
||||
return headlessRpcResult(id, {
|
||||
{"success", true},
|
||||
{"semanticHashTablePath", res.path},
|
||||
{"semanticHashCount", res.entryCount}
|
||||
});
|
||||
}
|
||||
|
||||
// --- getSemanticHashTable ---
|
||||
if (method == "getSemanticHashTable") {
|
||||
if (!AgentPermissionPolicy::canInvoke(role, method))
|
||||
return headlessRpcError(id, -32031, "Role not permitted");
|
||||
auto params = request.contains("params") ? request["params"] : json::object();
|
||||
std::string path = params.value("path", "");
|
||||
if (path.empty() && state.activeBuffer) path = state.activeBuffer->path;
|
||||
bool refresh = params.value("refresh", false);
|
||||
auto it = state.bufferStates.find(path);
|
||||
if (it == state.bufferStates.end())
|
||||
return headlessRpcError(id, -32602, "No buffer: " + path);
|
||||
if (refresh) {
|
||||
Module* ast = it->second->sync.getAST();
|
||||
auto save = saveSemanticHashTable(state.workspaceRoot, path, ast);
|
||||
if (!save.success) return headlessRpcError(id, -32010, save.error);
|
||||
}
|
||||
auto res = loadSemanticHashTable(state.workspaceRoot, path);
|
||||
if (!res.success) return headlessRpcError(id, -32010, res.error);
|
||||
return headlessRpcResult(id, {
|
||||
{"success", true},
|
||||
{"semanticHashTablePath", res.path},
|
||||
{"table", res.table}
|
||||
});
|
||||
}
|
||||
|
||||
// --- listSemanticHashTables ---
|
||||
if (method == "listSemanticHashTables") {
|
||||
if (!AgentPermissionPolicy::canInvoke(role, method))
|
||||
return headlessRpcError(id, -32031, "Role not permitted");
|
||||
auto files = listSemanticHashTables(state.workspaceRoot);
|
||||
return headlessRpcResult(id, {
|
||||
{"files", files},
|
||||
{"count", (int)files.size()}
|
||||
});
|
||||
}
|
||||
|
||||
// --- setSemanticHashLock ---
|
||||
if (method == "setSemanticHashLock") {
|
||||
if (!AgentPermissionPolicy::canInvoke(role, method))
|
||||
return headlessRpcError(id, -32031, "Role not permitted");
|
||||
auto params = request.contains("params") ? request["params"] : json::object();
|
||||
std::string nodeId = params.value("nodeId", "");
|
||||
if (nodeId.empty())
|
||||
return headlessRpcError(id, -32602, "nodeId required");
|
||||
bool locked = params.value("locked", true);
|
||||
std::string reason = params.value("reason", "");
|
||||
Module* ast = state.activeAST();
|
||||
if (!ast) return headlessRpcError(id, -32602, "No AST available");
|
||||
ASTNode* node = findNodeById(ast, nodeId);
|
||||
if (!node) return headlessRpcError(id, -32602, "Node not found: " + nodeId);
|
||||
node->semanticHashLockState = locked ? "locked" : "unlocked";
|
||||
node->semanticHashLockReason = reason;
|
||||
return headlessRpcResult(id, {
|
||||
{"success", true},
|
||||
{"nodeId", node->id},
|
||||
{"semanticHash", node->semanticHash},
|
||||
{"semanticHashLockState", node->semanticHashLockState},
|
||||
{"semanticHashLockReason", node->semanticHashLockReason}
|
||||
});
|
||||
}
|
||||
|
||||
// --- getSemanticHashLock ---
|
||||
if (method == "getSemanticHashLock") {
|
||||
if (!AgentPermissionPolicy::canInvoke(role, method))
|
||||
return headlessRpcError(id, -32031, "Role not permitted");
|
||||
auto params = request.contains("params") ? request["params"] : json::object();
|
||||
std::string nodeId = params.value("nodeId", "");
|
||||
if (nodeId.empty())
|
||||
return headlessRpcError(id, -32602, "nodeId required");
|
||||
Module* ast = state.activeAST();
|
||||
if (!ast) return headlessRpcError(id, -32602, "No AST available");
|
||||
ASTNode* node = findNodeById(ast, nodeId);
|
||||
if (!node) return headlessRpcError(id, -32602, "Node not found: " + nodeId);
|
||||
return headlessRpcResult(id, {
|
||||
{"nodeId", node->id},
|
||||
{"semanticHash", node->semanticHash},
|
||||
{"semanticHashLockState", node->semanticHashLockState.empty() ? "unlocked" : node->semanticHashLockState},
|
||||
{"semanticHashLockReason", node->semanticHashLockReason}
|
||||
});
|
||||
}
|
||||
|
||||
// --- setSemanticAnnotation ---
|
||||
if (method == "setSemanticAnnotation") {
|
||||
if (!AgentPermissionPolicy::canInvoke(role, method))
|
||||
@@ -138,6 +241,7 @@
|
||||
: json::object();
|
||||
std::string nodeId = params.value("nodeId", "");
|
||||
std::string type = params.value("type", "");
|
||||
std::string hashLockMode = params.value("hashLockMode", "warn"); // warn|error
|
||||
json fields = params.contains("fields") ? params["fields"]
|
||||
: json::object();
|
||||
if (nodeId.empty() || type.empty())
|
||||
@@ -150,6 +254,10 @@
|
||||
ASTNode* target = findNodeById(ast, nodeId);
|
||||
if (!target)
|
||||
return headlessRpcError(id, -32602, "Node not found: " + nodeId);
|
||||
std::string hashLockWarning = ASTMutationAPI::semanticHashLockWarning(target);
|
||||
if (!hashLockWarning.empty() && hashLockMode == "error") {
|
||||
return headlessRpcError(id, -32012, "Semantic hash lock violation: " + hashLockWarning);
|
||||
}
|
||||
|
||||
// Map type string to conceptType
|
||||
std::string conceptType;
|
||||
@@ -246,7 +354,11 @@
|
||||
return headlessRpcError(id, -32010, "Failed to create annotation");
|
||||
target->addChild("annotations", newAnno);
|
||||
|
||||
return headlessRpcResult(id, {{"success", true}, {"type", type}});
|
||||
return headlessRpcResult(id, {
|
||||
{"success", true},
|
||||
{"type", type},
|
||||
{"hashLockWarning", hashLockWarning}
|
||||
});
|
||||
}
|
||||
|
||||
// --- getSemanticAnnotations ---
|
||||
|
||||
Reference in New Issue
Block a user