diff --git a/dc3-common/dc3-common-auth/src/main/java/io/github/pnoker/common/auth/biz/impl/OAuthMcpRuntimeServiceImpl.java b/dc3-common/dc3-common-auth/src/main/java/io/github/pnoker/common/auth/biz/impl/OAuthMcpRuntimeServiceImpl.java index d418e0fd2..aa2f0716a 100644 --- a/dc3-common/dc3-common-auth/src/main/java/io/github/pnoker/common/auth/biz/impl/OAuthMcpRuntimeServiceImpl.java +++ b/dc3-common/dc3-common-auth/src/main/java/io/github/pnoker/common/auth/biz/impl/OAuthMcpRuntimeServiceImpl.java @@ -159,6 +159,26 @@ public class OAuthMcpRuntimeServiceImpl implements OAuthMcpRuntimeService { record.setEnableFlag(Boolean.TRUE.equals(quality.getHidden()) ? (byte) 1 : (byte) 0); } + /** + * True when any persisted catalog column differs between the existing row and the freshly-built + * candidate — including the quality fields (title/remark/hints) that a JSON-only edit can change + * without altering the schema hash. Keeps the catalog in sync with x-dc3-ai/description edits. + */ + static boolean toolChanged(McpToolRecord existing, McpToolRecord candidate) { + return !Objects.equals(existing.getSchemaHash(), candidate.getSchemaHash()) + || !Objects.equals(existing.getPermissionCode(), candidate.getPermissionCode()) + || !Objects.equals(existing.getApiPath(), candidate.getApiPath()) + || !Objects.equals(existing.getRiskLevel(), candidate.getRiskLevel()) + || !Objects.equals(existing.getEnableFlag(), candidate.getEnableFlag()) + || !Objects.equals(existing.getToolExt(), candidate.getToolExt()) + || !Objects.equals(existing.getToolTitle(), candidate.getToolTitle()) + || !Objects.equals(existing.getRemark(), candidate.getRemark()) + || !Objects.equals(existing.getReadOnlyHint(), candidate.getReadOnlyHint()) + || !Objects.equals(existing.getDestructiveHint(), candidate.getDestructiveHint()) + || !Objects.equals(existing.getIdempotentHint(), candidate.getIdempotentHint()) + || !Objects.equals(existing.getOpenWorldHint(), candidate.getOpenWorldHint()); + } + private static String normalizeRisk(String declared) { String value = StringUtils.upperCase(StringUtils.trimToEmpty(declared)); return switch (value) { @@ -464,12 +484,7 @@ public class OAuthMcpRuntimeServiceImpl implements OAuthMcpRuntimeService { if (existing == null) { candidate.setId(IdWorker.getId()); changed += oauthMcpMapper.insertTool(candidate); - } else if (!Objects.equals(existing.getSchemaHash(), candidate.getSchemaHash()) - || !Objects.equals(existing.getPermissionCode(), candidate.getPermissionCode()) - || !Objects.equals(existing.getApiPath(), candidate.getApiPath()) - || !Objects.equals(existing.getRiskLevel(), candidate.getRiskLevel()) - || !Objects.equals(existing.getEnableFlag(), candidate.getEnableFlag()) - || !Objects.equals(existing.getToolExt(), candidate.getToolExt())) { + } else if (toolChanged(existing, candidate)) { candidate.setId(existing.getId()); changed += oauthMcpMapper.updateTool(candidate); } diff --git a/dc3-common/dc3-common-auth/src/test/java/io/github/pnoker/common/auth/biz/impl/ToolQualityMergeTest.java b/dc3-common/dc3-common-auth/src/test/java/io/github/pnoker/common/auth/biz/impl/ToolQualityMergeTest.java index 2a9317018..e708c9a51 100644 --- a/dc3-common/dc3-common-auth/src/test/java/io/github/pnoker/common/auth/biz/impl/ToolQualityMergeTest.java +++ b/dc3-common/dc3-common-auth/src/test/java/io/github/pnoker/common/auth/biz/impl/ToolQualityMergeTest.java @@ -74,4 +74,27 @@ class ToolQualityMergeTest { assertThat(r.getEnableFlag()).isEqualTo((byte) 1); // hidden -> disabled } + + @Test + void toolChangedDetectsHintOnlyEdit() { + McpToolRecord existing = record("GET"); + OAuthMcpRuntimeServiceImpl.applyQuality(existing, ToolQuality.builder() + .riskLevel("LOW").destructive(false).idempotent(true).openWorld(false).hidden(false).build()); + McpToolRecord candidate = record("GET"); + OAuthMcpRuntimeServiceImpl.applyQuality(candidate, ToolQuality.builder() + .riskLevel("LOW").destructive(true).idempotent(true).openWorld(false).hidden(false).build()); + // schemaHash/riskLevel/enableFlag/toolExt unchanged; only destructiveHint differs. + assertThat(OAuthMcpRuntimeServiceImpl.toolChanged(existing, candidate)).isTrue(); + } + + @Test + void toolChangedIsFalseForIdenticalRecords() { + McpToolRecord a = record("GET"); + OAuthMcpRuntimeServiceImpl.applyQuality(a, ToolQuality.builder().riskLevel("LOW").build()); + a.setSchemaHash("h"); + McpToolRecord b = record("GET"); + OAuthMcpRuntimeServiceImpl.applyQuality(b, ToolQuality.builder().riskLevel("LOW").build()); + b.setSchemaHash("h"); + assertThat(OAuthMcpRuntimeServiceImpl.toolChanged(a, b)).isFalse(); + } }