This is an automated email from the ASF dual-hosted git repository.
lizhimins pushed a commit to branch rocketmq-studio
in repository https://gitbox.apache.org/repos/asf/rocketmq-dashboard.git
The following commit(s) were added to refs/heads/rocketmq-studio by this push:
new f531613b4 fix(ai): tolerate a null id in ACL and user tool outputs
(#4574)
f531613b4 is described below
commit f531613b4ba74ef538d201b934883ace5b075bd2
Author: Zhao Jianing <[email protected]>
AuthorDate: Sat Sep 26 18:27:36 2026 +0800
fix(ai): tolerate a null id in ACL and user tool outputs (#4574)
TencentAclService deliberately returns rules and users without a
database id ("Tencent roles have no database row; the role name lives
in username, id stays null"), and AclService addresses them by role
name. But the rmq.acl.get / rmq.acl.list / rmq.user.get / rmq.user.list
and rmq.user.create result schemas all declare id as required.
AclRuleItem and AclUserItem serialize with @JsonInclude(NON_NULL), so a
Tencent rule/user is emitted without an id key and ToolValidationFilter
rejects the whole response with "required property 'id' not found" —
for a Tencent instance every one of those tool calls fails.
Declare id as [string, 'null'] and drop it from the required lists, the
same convention acl.yaml already uses for gmtCreate, and lock the shape
in with null-id samples in the ToolOutputSchemaContractTest sweep.
catalog_gen.go is regenerated so the digest matches the new yaml.
Scope note: this fixes the read paths. rmq.acl.update still requires a
numeric id, because AclUpdateToolHandler.parseId rejects a role name
before AclService.updateRule (which does accept one for Tencent
instances) is reached; that gap is left for a follow-up.
---
rmqctl/internal/catalog/catalog_gen.go | 2 +-
server/src/main/resources/tool-catalog/tools/acl.yaml | 10 ++++++----
server/src/main/resources/tool-catalog/tools/user.yaml | 15 +++++++++------
.../ai/tool/service/ToolOutputSchemaContractTest.java | 16 +++++++++++-----
4 files changed, 27 insertions(+), 16 deletions(-)
diff --git a/rmqctl/internal/catalog/catalog_gen.go
b/rmqctl/internal/catalog/catalog_gen.go
index 78319554d..bfd0c07b9 100644
--- a/rmqctl/internal/catalog/catalog_gen.go
+++ b/rmqctl/internal/catalog/catalog_gen.go
@@ -21,7 +21,7 @@ package catalog
var defaultDocument = Document{
Version: "2.0.0",
MinimumClientVersion: "2.0.0",
- Digest:
"b0c1321d5d026c78d0cd51a142bef55f20e3370e5609b79cdcf85c933e208f91",
+ Digest:
"bf33ada8149a42a0f44d3056b999841ae1cc1612735bfe0dc7fa20f5b707a17c",
Tools: []Tool{
{
Name: "rmq.acl.list",
diff --git a/server/src/main/resources/tool-catalog/tools/acl.yaml
b/server/src/main/resources/tool-catalog/tools/acl.yaml
index 64ccf7016..d03f9e30d 100644
--- a/server/src/main/resources/tool-catalog/tools/acl.yaml
+++ b/server/src/main/resources/tool-catalog/tools/acl.yaml
@@ -54,13 +54,14 @@ tools:
items:
type: object
required:
- - id
- principal
- resource
additionalProperties: false
properties:
id:
- type: string
+ type:
+ - string
+ - 'null'
principal:
type: string
resource:
@@ -111,13 +112,14 @@ tools:
outputSchema:
type: object
required:
- - id
- principal
- resource
additionalProperties: false
properties:
id:
- type: string
+ type:
+ - string
+ - 'null'
principal:
type: string
resource:
diff --git a/server/src/main/resources/tool-catalog/tools/user.yaml
b/server/src/main/resources/tool-catalog/tools/user.yaml
index 92cfc16ad..570dda351 100644
--- a/server/src/main/resources/tool-catalog/tools/user.yaml
+++ b/server/src/main/resources/tool-catalog/tools/user.yaml
@@ -29,14 +29,15 @@ tools:
items:
type: object
required:
- - id
- username
- admin
- clusters
additionalProperties: false
properties:
id:
- type: string
+ type:
+ - string
+ - 'null'
username:
type: string
admin:
@@ -72,13 +73,14 @@ tools:
outputSchema:
type: object
required:
- - id
- username
- admin
additionalProperties: false
properties:
id:
- type: string
+ type:
+ - string
+ - 'null'
username:
type: string
admin:
@@ -171,13 +173,14 @@ tools:
result:
type: object
required:
- - id
- username
- admin
additionalProperties: false
properties:
id:
- type: string
+ type:
+ - string
+ - 'null'
username:
type: string
admin:
diff --git
a/server/src/test/java/org/apache/rocketmq/studio/ops/ai/tool/service/ToolOutputSchemaContractTest.java
b/server/src/test/java/org/apache/rocketmq/studio/ops/ai/tool/service/ToolOutputSchemaContractTest.java
index e6aa8f80b..96769814d 100644
---
a/server/src/test/java/org/apache/rocketmq/studio/ops/ai/tool/service/ToolOutputSchemaContractTest.java
+++
b/server/src/test/java/org/apache/rocketmq/studio/ops/ai/tool/service/ToolOutputSchemaContractTest.java
@@ -125,16 +125,22 @@ class ToolOutputSchemaContractTest {
.aclVersion("v2")
.gmtCreate(LocalDateTime.of(2026, 8, 22, 8, 0))
.build();
- samples.put("rmq.acl.get", List.of(aclRule));
- samples.put("rmq.acl.list", List.of(new PageOutput<>(1, 20, 1L,
List.of(aclRule))));
+ // Tencent roles have no database row: id stays null and the role name
(principal)
+ // is the identifier, so tool outputs must tolerate a missing id
+ AclRuleItem tencentAclRule = new AclRuleItem(
+ null, "alice", "orders", "TOPIC", "LITERAL",
+ List.of("PUB"), "ALLOW", INSTANCE, "v2", null);
+ samples.put("rmq.acl.get", List.of(aclRule, tencentAclRule));
+ samples.put("rmq.acl.list", List.of(new PageOutput<>(1, 20, 2L,
List.of(aclRule, tencentAclRule))));
samples.put("rmq.acl.create", List.of(planned(), executed(aclRuleVO)));
samples.put("rmq.acl.update", List.of(planned(), executed(aclRuleVO)));
samples.put("rmq.acl.delete", List.of(planned(), executedVoid()));
AclUserItem user = new AclUserItem("1", "alice", true,
List.of(INSTANCE));
- samples.put("rmq.user.get", List.of(user));
- samples.put("rmq.user.list", List.of(new ListOutput<>(List.of(user))));
- samples.put("rmq.user.create", List.of(planned(), executed(user)));
+ AclUserItem tencentUser = new AclUserItem(null, "alice", false,
List.of("cloud-instance"));
+ samples.put("rmq.user.get", List.of(user, tencentUser));
+ samples.put("rmq.user.list", List.of(new ListOutput<>(List.of(user,
tencentUser))));
+ samples.put("rmq.user.create", List.of(planned(), executed(user),
executed(tencentUser)));
samples.put("rmq.user.delete", List.of(planned(), executedVoid()));
// metric is optional at the service layer (AlertService only
validates name),