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 c2c7d9286 fix(ai): reject non-positive reset timestamp before any 
broker call (#6893)
c2c7d9286 is described below

commit c2c7d92860c4ee90a114ce3d9e3e7f340171cf06
Author: 九斀 <[email protected]>
AuthorDate: Mon Oct 26 16:28:28 2026 +0800

    fix(ai): reject non-positive reset timestamp before any broker call (#6893)
    
    Fixes #5984
    
    rmq.group.reset_offset only rejected a null timestamp; 0 or negative values 
passed preview and execute. The broker resolves such a timestamp to each queue 
minimum offset, rewinding every queue (mass replay) via the AI tool API, MCP 
tools/call and rmqctl. Reject timestamp <= 0 in the shared handler with the 
REST contract message (400), guarding both preview and execute before any 
broker call. Regression tests cover 0/-1 on both paths.
---
 .../handler/group/GroupResetOffsetToolHandler.java | 10 ++++++-
 .../group/GroupResetOffsetToolHandlerTest.java     | 35 ++++++++++++++++++++++
 2 files changed, 44 insertions(+), 1 deletion(-)

diff --git 
a/server/src/main/java/org/apache/rocketmq/studio/ops/ai/tool/handler/group/GroupResetOffsetToolHandler.java
 
b/server/src/main/java/org/apache/rocketmq/studio/ops/ai/tool/handler/group/GroupResetOffsetToolHandler.java
index 4121e8916..6b2c5f0bf 100644
--- 
a/server/src/main/java/org/apache/rocketmq/studio/ops/ai/tool/handler/group/GroupResetOffsetToolHandler.java
+++ 
b/server/src/main/java/org/apache/rocketmq/studio/ops/ai/tool/handler/group/GroupResetOffsetToolHandler.java
@@ -79,11 +79,19 @@ public class GroupResetOffsetToolHandler extends 
MutationToolHandler<GroupResetO
         return new ResetOffsetOutput(input.groupName(), input.topicName(), 
timestamp);
     }
 
-    /** Decision 13: no server-side default β€” rmqctl fills the current time 
client-side. */
+    /**
+     * Decision 13: no server-side default β€” rmqctl fills the current time 
client-side.
+     * A non-positive timestamp rewinds every queue to its minimum offset (the 
broker
+     * resolves such a value before the first stored message), so it must be 
rejected
+     * with the same contract as {@code POST /api/groups/reset-offset}.
+     */
     private static long requireTimestamp(GroupResetOffsetInput input) {
         if (input.timestamp() == null) {
             throw new BusinessException(400, "timestamp is required");
         }
+        if (input.timestamp() <= 0L) {
+            throw new BusinessException(400, "timestamp must be positive");
+        }
         return input.timestamp();
     }
 
diff --git 
a/server/src/test/java/org/apache/rocketmq/studio/ops/ai/tool/handler/group/GroupResetOffsetToolHandlerTest.java
 
b/server/src/test/java/org/apache/rocketmq/studio/ops/ai/tool/handler/group/GroupResetOffsetToolHandlerTest.java
index 546127439..b3d9cdd00 100644
--- 
a/server/src/test/java/org/apache/rocketmq/studio/ops/ai/tool/handler/group/GroupResetOffsetToolHandlerTest.java
+++ 
b/server/src/test/java/org/apache/rocketmq/studio/ops/ai/tool/handler/group/GroupResetOffsetToolHandlerTest.java
@@ -15,6 +15,8 @@ import 
org.apache.rocketmq.studio.ops.ai.tool.contract.group.GroupResetOffsetInp
 import org.apache.rocketmq.studio.ops.ai.tool.contract.group.ResetOffsetOutput;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.extension.ExtendWith;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.ValueSource;
 import org.mockito.InjectMocks;
 import org.mockito.Mock;
 import org.mockito.junit.jupiter.MockitoExtension;
@@ -65,4 +67,37 @@ class GroupResetOffsetToolHandlerTest {
                 .satisfies(error -> assertThat(((BusinessException) 
error).getCode()).isEqualTo(400));
         verifyNoInteractions(metadataService);
     }
+
+    @ParameterizedTest
+    @ValueSource(longs = {0L, -1L})
+    void applyShouldRejectANonPositiveTimestampTest(long timestamp) {
+        // The broker resolves a non-positive timestamp to the minimum offset, 
which
+        // would rewind every queue; the tool must match the REST contract's 
@Positive.
+        GroupResetOffsetInput input = new GroupResetOffsetInput(
+                "untrusted-instance", "group-1", "TopicA", timestamp);
+        ToolExecutionContext execution = ToolExecutionContext.of(
+                "instance-a", null, Map.of("instanceId", 
"untrusted-instance"));
+
+        assertThatThrownBy(() -> handler.execute(input, execution))
+                .isInstanceOf(BusinessException.class)
+                .hasMessage("timestamp must be positive")
+                .satisfies(error -> assertThat(((BusinessException) 
error).getCode()).isEqualTo(400));
+        verifyNoInteractions(metadataService);
+    }
+
+    @ParameterizedTest
+    @ValueSource(longs = {0L, -1L})
+    void previewShouldRejectANonPositiveTimestampTest(long timestamp) {
+        // The L2 preview must fail before issuing a confirm token for the 
destructive rewind.
+        GroupResetOffsetInput input = new GroupResetOffsetInput(
+                "untrusted-instance", "group-1", "TopicA", timestamp);
+        ToolExecutionContext execution = ToolExecutionContext.of(
+                "instance-a", null, Map.of("instanceId", 
"untrusted-instance"));
+
+        assertThatThrownBy(() -> handler.preview(input, execution))
+                .isInstanceOf(BusinessException.class)
+                .hasMessage("timestamp must be positive")
+                .satisfies(error -> assertThat(((BusinessException) 
error).getCode()).isEqualTo(400));
+        verifyNoInteractions(metadataService);
+    }
 }

Reply via email to