feat(harness): freeze aci tool contracts

This commit is contained in:
zhuyongxin
2026-07-21 18:01:18 +08:00
parent 58c39107c5
commit 4274f3350b
32 changed files with 977 additions and 1 deletions
@@ -0,0 +1,73 @@
package com.superbiz.agent.harness.tool.contract;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.superbiz.agent.harness.contract.EvidenceStatus;
import org.junit.jupiter.api.Test;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertThrows;
class MysqlToolContractTest {
private final ObjectMapper objectMapper = new ObjectMapper();
@Test
void freezesLogicalParameterizedRequestWithoutConnectionData() {
List<Object> params = new ArrayList<>(List.of("order-123"));
MysqlToolRequest request = new MysqlToolRequest(
"order_readonly",
"SELECT order_id, payment_status FROM biz_order WHERE order_id = ?",
params);
params.clear();
JsonNode json = objectMapper.valueToTree(request);
assertEquals(Set.of("data_source", "sql", "params"), ToolContractAssertions.fieldNames(json));
assertEquals(1, json.path("params").size());
assertFalse(json.has("tool_call_id"));
assertFalse(json.has("jdbc_url"));
assertFalse(json.has("username"));
assertFalse(json.has("password"));
assertFalse(json.has("timeout"));
assertThrows(UnsupportedOperationException.class, () -> request.params().add("other"));
}
@Test
void freezesBoundedStructuredRowsWithDeepImmutability() {
Map<String, Object> row = new LinkedHashMap<>();
row.put("order_id", "order-123");
row.put("payment_status", "FAILED");
List<Map<String, Object>> rows = new ArrayList<>(List.of(row));
List<String> columns = new ArrayList<>(List.of("order_id", "payment_status"));
MysqlToolResult result = new MysqlToolResult(
EvidenceStatus.EVIDENCE_FOUND,
"framework-call-mysql-1",
columns,
rows,
1,
false);
row.put("secret", "must-not-appear");
rows.clear();
columns.clear();
JsonNode json = objectMapper.valueToTree(result);
assertEquals(Set.of("evidence_status", "tool_call_id", "columns", "rows",
"returned_count", "truncated"), ToolContractAssertions.fieldNames(json));
assertEquals("framework-call-mysql-1", json.path("tool_call_id").asText());
assertEquals(List.of("order_id", "payment_status"), result.columns());
assertEquals(1, json.path("rows").size());
assertFalse(json.path("rows").get(0).has("secret"));
assertThrows(UnsupportedOperationException.class,
() -> result.rows().get(0).put("other", "value"));
assertEquals("query_mysql", AgentToolContracts.QUERY_MYSQL);
ToolContractAssertions.assertImplementationNeutral(AgentToolContracts.QUERY_MYSQL_DESCRIPTION);
}
}
@@ -0,0 +1,76 @@
package com.superbiz.agent.harness.tool.contract;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.superbiz.agent.harness.contract.EvidenceStatus;
import org.junit.jupiter.api.Test;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertThrows;
class QueryLogsToolContractTest {
private final ObjectMapper objectMapper = new ObjectMapper();
@Test
void freezesLogicalRequestWithoutInfrastructureControls() {
JsonNode json = objectMapper.valueToTree(
new QueryLogsRequest(LogTopic.APPLICATION, "order-service HikariCP timeout", 30));
assertEquals(Set.of("topic", "query", "lookback_minutes"), ToolContractAssertions.fieldNames(json));
assertEquals("APPLICATION", json.path("topic").asText());
assertFalse(json.has("tool_call_id"));
assertFalse(json.has("region"));
assertFalse(json.has("log_topic"));
assertFalse(json.has("limit"));
assertEquals(Set.of("APPLICATION", "DATABASE_SLOW_QUERY", "SYSTEM_EVENTS"),
java.util.Arrays.stream(LogTopic.values()).map(Enum::name)
.collect(java.util.stream.Collectors.toSet()));
}
@Test
void freezesMockProvenanceScopeAndBoundedTimeline() {
List<LogPattern> patterns = new ArrayList<>(List.of(new LogPattern(
84, "2026-07-21T10:00:00Z", "2026-07-21T10:29:00Z",
"ERROR", "order-service", "HikariPool connection is not available")));
List<LogEvent> events = new ArrayList<>(List.of(new LogEvent(
"2026-07-21T10:29:00Z", "ERROR", "order-service",
"HikariPool connection is not available")));
QueryLogsToolResult result = new QueryLogsToolResult(
EvidenceStatus.EVIDENCE_FOUND,
"framework-call-log-1",
LogSourceKind.MOCK,
new LogQueryScope(LogTopic.APPLICATION, "order-service HikariCP timeout",
"2026-07-21T10:00:00Z", "2026-07-21T10:30:00Z"),
126,
1,
patterns,
events,
true);
patterns.clear();
events.clear();
JsonNode json = objectMapper.valueToTree(result);
assertEquals(Set.of("evidence_status", "tool_call_id", "source_kind", "scope",
"match_count", "returned_count", "patterns", "events", "truncated"),
ToolContractAssertions.fieldNames(json));
assertEquals("framework-call-log-1", json.path("tool_call_id").asText());
assertEquals("MOCK", json.path("source_kind").asText());
assertEquals(126, json.path("match_count").asLong());
assertEquals(1, json.path("returned_count").asInt());
assertEquals(1, json.path("patterns").size());
assertEquals(1, json.path("events").size());
assertEquals(Set.of("topic", "query", "start_time", "end_time"),
ToolContractAssertions.fieldNames(json.path("scope")));
assertThrows(UnsupportedOperationException.class,
() -> result.events().add(new LogEvent(null, null, null, null)));
assertEquals("query_logs", AgentToolContracts.QUERY_LOGS);
ToolContractAssertions.assertImplementationNeutral(AgentToolContracts.QUERY_LOGS_DESCRIPTION);
}
}
@@ -0,0 +1,61 @@
package com.superbiz.agent.harness.tool.contract;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.superbiz.agent.harness.contract.EvidenceStatus;
import com.superbiz.agent.harness.contract.InvocationStatus;
import org.junit.jupiter.api.Test;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertThrows;
class RagToolContractTest {
private final ObjectMapper objectMapper = new ObjectMapper();
@Test
void freezesMinimalRequestAndBoundedEvidenceResult() {
RagToolRequest request = new RagToolRequest("支付超时处理方式");
JsonNode requestJson = objectMapper.valueToTree(request);
assertEquals(Set.of("query"), ToolContractAssertions.fieldNames(requestJson));
assertFalse(requestJson.has("tool_call_id"));
List<RagEvidence> source = new ArrayList<>();
source.add(new RagEvidence("payment-timeout-guide", "payment-timeout.md",
"支付超时排查", "支付系统 > 故障排查", "先检查 ERR_TIMEOUT 发生时间。"));
RagToolResult result = new RagToolResult(EvidenceStatus.EVIDENCE_FOUND, "framework-call-rag-1",
request.query(), source, 1, false);
source.clear();
JsonNode json = objectMapper.valueToTree(result);
assertEquals(Set.of("evidence_status", "tool_call_id", "query", "evidence",
"returned_count", "truncated"), ToolContractAssertions.fieldNames(json));
assertEquals("EVIDENCE_FOUND", json.path("evidence_status").asText());
assertEquals("framework-call-rag-1", json.path("tool_call_id").asText());
assertEquals(1, json.path("evidence").size());
assertEquals(Set.of("document_id", "source", "title", "breadcrumb", "excerpt"),
ToolContractAssertions.fieldNames(json.path("evidence").get(0)));
assertFalse(json.has("context_pack"));
assertFalse(json.has("retrieval_trace"));
assertThrows(UnsupportedOperationException.class,
() -> result.evidence().add(new RagEvidence("other", "other.md", null, null, "other")));
}
@Test
void keepsLifecycleAndEvidenceStatusNamespacesIndependent() {
assertEquals(Set.of("PROJECTING", "READY", "ERROR"), enumNames(InvocationStatus.values()));
assertEquals(Set.of("EVIDENCE_FOUND", "NO_EVIDENCE", "ERROR"), enumNames(EvidenceStatus.values()));
assertEquals("lookup_knowledge", AgentToolContracts.LOOKUP_KNOWLEDGE);
ToolContractAssertions.assertImplementationNeutral(AgentToolContracts.LOOKUP_KNOWLEDGE_DESCRIPTION);
}
private Set<String> enumNames(Enum<?>[] values) {
return java.util.Arrays.stream(values).map(Enum::name).collect(java.util.stream.Collectors.toSet());
}
}
@@ -0,0 +1,33 @@
package com.superbiz.agent.harness.tool.contract;
import com.fasterxml.jackson.databind.JsonNode;
import java.util.HashSet;
import java.util.Locale;
import java.util.Set;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
final class ToolContractAssertions {
private static final Set<String> INTERNAL_TERMS = Set.of(
"milvus", "l0", "l1", "rerank", "region", "topicid", "redis",
"jdbc", "credential", "password", "topk", "limit", "trace");
private ToolContractAssertions() {
}
static Set<String> fieldNames(JsonNode node) {
Set<String> names = new HashSet<>();
node.fieldNames().forEachRemaining(names::add);
return names;
}
static void assertImplementationNeutral(String description) {
assertTrue(description.length() < 180, "Tool description must remain concise");
String normalized = description.toLowerCase(Locale.ROOT);
INTERNAL_TERMS.forEach(term -> assertFalse(normalized.contains(term),
() -> "Tool description leaks internal term: " + term));
}
}