mirror of
https://gitee.com/pnoker/iot-dc3.git
synced 2026-09-01 04:48:26 +08:00
refactor(auth): remove legacy scanner-sourced x-dc3-ai path end-to-end
This commit is contained in:
@@ -69,23 +69,8 @@ message GrpcScannedApiDTO {
|
||||
// tree node on the permission resource page.
|
||||
string api_group = 6;
|
||||
|
||||
// Declared MCP risk level (LOW / MEDIUM / HIGH); empty when derived automatically.
|
||||
string risk_level = 7;
|
||||
|
||||
// Declared MCP destructive hint ("true" / "false"); empty when derived.
|
||||
string destructive_hint = 8;
|
||||
|
||||
// Declared MCP open-world hint ("true" / "false"); empty when derived.
|
||||
string open_world_hint = 9;
|
||||
|
||||
// Declared MCP idempotent hint ("true" / "false"); empty when derived.
|
||||
string idempotent_hint = 10;
|
||||
|
||||
// AI-facing MCP tool description override; empty when the operation text is used.
|
||||
string ai_description = 11;
|
||||
|
||||
// Whether the MCP tool is hidden from tools/list by default ("true" / "false"); empty = visible.
|
||||
string hidden = 12;
|
||||
reserved 7, 8, 9, 10, 11, 12;
|
||||
reserved "risk_level", "destructive_hint", "open_world_hint", "idempotent_hint", "ai_description", "hidden";
|
||||
}
|
||||
|
||||
// Response wrapper for the sync result.
|
||||
|
||||
+1
-13
@@ -651,12 +651,6 @@ public class ResourceRegistrySyncServiceImpl implements ResourceRegistrySyncServ
|
||||
content.setTitle(spec.getTitle());
|
||||
content.setUrl(spec.getPath());
|
||||
content.setRemark(spec.getRemark());
|
||||
content.setRiskLevel(spec.getRiskLevel());
|
||||
content.setDestructiveHint(spec.getDestructiveHint());
|
||||
content.setOpenWorldHint(spec.getOpenWorldHint());
|
||||
content.setIdempotentHint(spec.getIdempotentHint());
|
||||
content.setAiDescription(spec.getAiDescription());
|
||||
content.setHidden(spec.getHidden());
|
||||
return content;
|
||||
}
|
||||
|
||||
@@ -672,13 +666,7 @@ public class ResourceRegistrySyncServiceImpl implements ResourceRegistrySyncServ
|
||||
return Objects.isNull(a) && Objects.isNull(b);
|
||||
}
|
||||
return Objects.equals(a.getTitle(), b.getTitle()) && Objects.equals(a.getUrl(), b.getUrl())
|
||||
&& Objects.equals(a.getRemark(), b.getRemark())
|
||||
&& Objects.equals(a.getRiskLevel(), b.getRiskLevel())
|
||||
&& Objects.equals(a.getDestructiveHint(), b.getDestructiveHint())
|
||||
&& Objects.equals(a.getOpenWorldHint(), b.getOpenWorldHint())
|
||||
&& Objects.equals(a.getIdempotentHint(), b.getIdempotentHint())
|
||||
&& Objects.equals(a.getAiDescription(), b.getAiDescription())
|
||||
&& Objects.equals(a.getHidden(), b.getHidden());
|
||||
&& Objects.equals(a.getRemark(), b.getRemark());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
-30
@@ -72,34 +72,4 @@ public class ResourceRegistryScannedApi {
|
||||
*/
|
||||
private String apiGroup;
|
||||
|
||||
/**
|
||||
* Declared MCP risk level (LOW / MEDIUM / HIGH); blank when derived automatically.
|
||||
*/
|
||||
private String riskLevel;
|
||||
|
||||
/**
|
||||
* Declared MCP destructive hint ("true" / "false"); blank when derived.
|
||||
*/
|
||||
private String destructiveHint;
|
||||
|
||||
/**
|
||||
* Declared MCP open-world hint ("true" / "false"); blank when derived.
|
||||
*/
|
||||
private String openWorldHint;
|
||||
|
||||
/**
|
||||
* Declared MCP idempotent hint ("true" / "false"); blank when derived.
|
||||
*/
|
||||
private String idempotentHint;
|
||||
|
||||
/**
|
||||
* AI-facing MCP tool description override; blank when the operation text is used.
|
||||
*/
|
||||
private String aiDescription;
|
||||
|
||||
/**
|
||||
* Whether the MCP tool is hidden from tools/list by default ("true" / "false"); blank = visible.
|
||||
*/
|
||||
private String hidden;
|
||||
|
||||
}
|
||||
|
||||
-6
@@ -61,12 +61,6 @@ public class ResourceRegistryServer extends ResourceRegistryApiGrpc.ResourceRegi
|
||||
.title(dto.getTitle())
|
||||
.remark(dto.getRemark())
|
||||
.apiGroup(dto.getApiGroup())
|
||||
.riskLevel(dto.getRiskLevel())
|
||||
.destructiveHint(dto.getDestructiveHint())
|
||||
.openWorldHint(dto.getOpenWorldHint())
|
||||
.idempotentHint(dto.getIdempotentHint())
|
||||
.aiDescription(dto.getAiDescription())
|
||||
.hidden(dto.getHidden())
|
||||
.build());
|
||||
}
|
||||
return apis;
|
||||
|
||||
-18
@@ -59,22 +59,4 @@ public class FacadeScannedApiBO {
|
||||
*/
|
||||
private String apiGroup;
|
||||
|
||||
|
||||
private String riskLevel;
|
||||
|
||||
|
||||
private String destructiveHint;
|
||||
|
||||
|
||||
private String openWorldHint;
|
||||
|
||||
|
||||
private String idempotentHint;
|
||||
|
||||
|
||||
private String aiDescription;
|
||||
|
||||
|
||||
private String hidden;
|
||||
|
||||
}
|
||||
|
||||
-6
@@ -64,12 +64,6 @@ public class ResourceRegistryGrpcFacade implements ResourceRegistryFacade {
|
||||
.setTitle(Objects.requireNonNullElse(api.getTitle(), ""))
|
||||
.setRemark(Objects.requireNonNullElse(api.getRemark(), ""))
|
||||
.setApiGroup(Objects.requireNonNullElse(api.getApiGroup(), ""))
|
||||
.setRiskLevel(Objects.requireNonNullElse(api.getRiskLevel(), ""))
|
||||
.setDestructiveHint(Objects.requireNonNullElse(api.getDestructiveHint(), ""))
|
||||
.setOpenWorldHint(Objects.requireNonNullElse(api.getOpenWorldHint(), ""))
|
||||
.setIdempotentHint(Objects.requireNonNullElse(api.getIdempotentHint(), ""))
|
||||
.setAiDescription(Objects.requireNonNullElse(api.getAiDescription(), ""))
|
||||
.setHidden(Objects.requireNonNullElse(api.getHidden(), ""))
|
||||
.build());
|
||||
}
|
||||
}
|
||||
|
||||
-6
@@ -61,12 +61,6 @@ public class ResourceRegistryLocalFacade implements ResourceRegistryFacade {
|
||||
.title(bo.getTitle())
|
||||
.remark(bo.getRemark())
|
||||
.apiGroup(bo.getApiGroup())
|
||||
.riskLevel(bo.getRiskLevel())
|
||||
.destructiveHint(bo.getDestructiveHint())
|
||||
.openWorldHint(bo.getOpenWorldHint())
|
||||
.idempotentHint(bo.getIdempotentHint())
|
||||
.aiDescription(bo.getAiDescription())
|
||||
.hidden(bo.getHidden())
|
||||
.build());
|
||||
}
|
||||
return out;
|
||||
|
||||
-36
@@ -72,42 +72,6 @@ public class ApiExt extends BaseExt {
|
||||
@Schema(description = "Free-form description remark of the API interface", example = "Returns the latest temperature reading for a device")
|
||||
private String remark;
|
||||
|
||||
/**
|
||||
* Declared MCP risk level (LOW / MEDIUM / HIGH); blank when derived automatically.
|
||||
*/
|
||||
@Schema(description = "Declared MCP risk level (LOW / MEDIUM / HIGH); blank when derived automatically", example = "MEDIUM")
|
||||
private String riskLevel;
|
||||
|
||||
/**
|
||||
* Declared destructive hint ("true" / "false"); blank when derived.
|
||||
*/
|
||||
@Schema(description = "Declared MCP destructive hint (\"true\" / \"false\"); blank when derived automatically", example = "false")
|
||||
private String destructiveHint;
|
||||
|
||||
/**
|
||||
* Declared open-world hint ("true" / "false"); blank when derived.
|
||||
*/
|
||||
@Schema(description = "Declared MCP open-world hint (\"true\" / \"false\"); blank when derived automatically", example = "false")
|
||||
private String openWorldHint;
|
||||
|
||||
/**
|
||||
* Declared idempotent hint ("true" / "false"); blank when derived.
|
||||
*/
|
||||
@Schema(description = "Declared MCP idempotent hint (\"true\" / \"false\"); blank when derived automatically", example = "true")
|
||||
private String idempotentHint;
|
||||
|
||||
/**
|
||||
* AI-facing description override; blank when the operation text is used.
|
||||
*/
|
||||
@Schema(description = "AI-facing MCP tool description override; blank when the operation text is used", example = "Fetch the current temperature for a given device id")
|
||||
private String aiDescription;
|
||||
|
||||
/**
|
||||
* Whether the tool is hidden from tools/list by default ("true" / "false"); blank = visible.
|
||||
*/
|
||||
@Schema(description = "Whether the tool is hidden from tools/list by default (\"true\" / \"false\"); blank means visible", example = "false")
|
||||
private String hidden;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
-39
@@ -22,8 +22,6 @@ import io.github.pnoker.common.constant.common.SymbolConstant;
|
||||
import io.github.pnoker.common.facade.entity.bo.FacadeScannedApiBO;
|
||||
import io.github.pnoker.common.resource.registrar.config.ResourceRegistrarProperties;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.extensions.Extension;
|
||||
import io.swagger.v3.oas.annotations.extensions.ExtensionProperty;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
@@ -176,7 +174,6 @@ public class ApiEndpointScanner {
|
||||
String title = operation != null && StringUtils.isNotBlank(operation.summary())
|
||||
? operation.summary() : handler.getMethod().getName();
|
||||
String remark = operation != null ? StringUtils.defaultString(operation.description()) : "";
|
||||
Map<String, String> ai = aiMetadata(operation);
|
||||
out.put(key,
|
||||
FacadeScannedApiBO.builder()
|
||||
.method(method.name())
|
||||
@@ -185,47 +182,11 @@ public class ApiEndpointScanner {
|
||||
.title(title)
|
||||
.remark(remark)
|
||||
.apiGroup(handler.getBeanType().getSimpleName())
|
||||
.riskLevel(ai.getOrDefault("riskLevel", ""))
|
||||
.destructiveHint(ai.getOrDefault("destructive", ""))
|
||||
.openWorldHint(ai.getOrDefault("openWorld", ""))
|
||||
.idempotentHint(ai.getOrDefault("idempotent", ""))
|
||||
.aiDescription(ai.getOrDefault("description", ""))
|
||||
.hidden(ai.getOrDefault("hidden", ""))
|
||||
.build());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Read AI tool metadata declared on the operation through the standard OpenAPI
|
||||
* {@code x-dc3-ai} extension, e.g.
|
||||
* <pre>
|
||||
* @Operation(summary = "...", extensions = @Extension(name = "x-dc3-ai", properties = {
|
||||
* @ExtensionProperty(name = "riskLevel", value = "HIGH"),
|
||||
* @ExtensionProperty(name = "openWorld", value = "true")}))
|
||||
* </pre>
|
||||
* Recognised keys: {@code riskLevel}, {@code destructive}, {@code openWorld},
|
||||
* {@code idempotent}, {@code description}, {@code hidden}. Absent keys stay unset so the
|
||||
* tool catalog falls back to its own derivation.
|
||||
*/
|
||||
private Map<String, String> aiMetadata(Operation operation) {
|
||||
if (operation == null) {
|
||||
return Map.of();
|
||||
}
|
||||
Map<String, String> metadata = new LinkedHashMap<>();
|
||||
for (Extension extension : operation.extensions()) {
|
||||
if (!"x-dc3-ai".equalsIgnoreCase(extension.name())) {
|
||||
continue;
|
||||
}
|
||||
for (ExtensionProperty property : extension.properties()) {
|
||||
if (StringUtils.isNotBlank(property.name())) {
|
||||
metadata.put(property.name(), StringUtils.defaultString(property.value()));
|
||||
}
|
||||
}
|
||||
}
|
||||
return metadata;
|
||||
}
|
||||
|
||||
private boolean isPublicEndpoint(HandlerMethod handler) {
|
||||
return handler.hasMethodAnnotation(PublicEndpoint.class)
|
||||
|| handler.getBeanType().isAnnotationPresent(PublicEndpoint.class);
|
||||
|
||||
-51
@@ -21,8 +21,6 @@ import io.github.pnoker.common.annotation.PublicEndpoint;
|
||||
import io.github.pnoker.common.facade.entity.bo.FacadeScannedApiBO;
|
||||
import io.github.pnoker.common.resource.registrar.config.ResourceRegistrarProperties;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.extensions.Extension;
|
||||
import io.swagger.v3.oas.annotations.extensions.ExtensionProperty;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
@@ -89,36 +87,6 @@ class ApiEndpointScannerTest {
|
||||
assertThat(fetched.getRemark()).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
void xDc3AiExtensionPopulatesAiMetadataWithBlankDefaults() {
|
||||
register(McpAnnotatedController.class);
|
||||
ApiEndpointScanner scanner = new ApiEndpointScanner(handlerMapping, new ResourceRegistrarProperties());
|
||||
|
||||
List<FacadeScannedApiBO> apis = scanner.scan();
|
||||
|
||||
FacadeScannedApiBO command = apis.stream()
|
||||
.filter(a -> "/api/mcp/command".equals(a.getPath()))
|
||||
.findFirst().orElseThrow();
|
||||
assertThat(command.getRiskLevel()).isEqualTo("HIGH");
|
||||
assertThat(command.getOpenWorldHint()).isEqualTo("true");
|
||||
assertThat(command.getHidden()).isEqualTo("true");
|
||||
// Attributes left at their annotation defaults stay blank, so derivation still applies.
|
||||
assertThat(command.getDestructiveHint()).isEmpty();
|
||||
assertThat(command.getIdempotentHint()).isEmpty();
|
||||
assertThat(command.getAiDescription()).isEmpty();
|
||||
|
||||
FacadeScannedApiBO plain = apis.stream()
|
||||
.filter(a -> "/api/mcp/plain".equals(a.getPath()))
|
||||
.findFirst().orElseThrow();
|
||||
// No x-dc3-ai extension: every AI metadata field is blank so the catalog derives them.
|
||||
assertThat(plain.getRiskLevel()).isEmpty();
|
||||
assertThat(plain.getDestructiveHint()).isEmpty();
|
||||
assertThat(plain.getOpenWorldHint()).isEmpty();
|
||||
assertThat(plain.getIdempotentHint()).isEmpty();
|
||||
assertThat(plain.getAiDescription()).isEmpty();
|
||||
assertThat(plain.getHidden()).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
void unsupportedMethodsAndDefaultExcludesAreFilteredOut() {
|
||||
register(PatchOnlyController.class);
|
||||
@@ -265,25 +233,6 @@ class ApiEndpointScannerTest {
|
||||
}
|
||||
}
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/mcp")
|
||||
static class McpAnnotatedController {
|
||||
@Operation(summary = "Issue Command", extensions = @Extension(name = "x-dc3-ai", properties = {
|
||||
@ExtensionProperty(name = "riskLevel", value = "HIGH"),
|
||||
@ExtensionProperty(name = "openWorld", value = "true"),
|
||||
@ExtensionProperty(name = "hidden", value = "true")
|
||||
}))
|
||||
@PostMapping("/command")
|
||||
public String command() {
|
||||
return "ok";
|
||||
}
|
||||
|
||||
@PostMapping("/plain")
|
||||
public String plain() {
|
||||
return "ok";
|
||||
}
|
||||
}
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/duplicate")
|
||||
static class DuplicateController {
|
||||
|
||||
Reference in New Issue
Block a user