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),

Reply via email to