You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@shardingsphere.apache.org by zh...@apache.org on 2026/07/01 12:00:25 UTC

(shardingsphere) branch master updated: Refactor MCP workflow support duplication (#38974)

This is an automated email from the ASF dual-hosted git repository.

terrymanu pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/shardingsphere.git


The following commit(s) were added to refs/heads/master by this push:
     new 9e60e6a3c4f Refactor MCP workflow support duplication (#38974)
9e60e6a3c4f is described below

commit 9e60e6a3c4f80a53e6fa60384e8cfbfb83da04e1
Author: Liang Zhang <zh...@apache.org>
AuthorDate: Wed Jul 1 20:00:19 2026 +0800

    Refactor MCP workflow support duplication (#38974)
    
    * Fix documentation accuracy and consistency
    
    - align DistSQL syntax docs, return columns, and examples with current source
    - normalize SQL and server error code documentation
    - fix MCP resources, management metadata docs, links, versions, and typos
    
    * Fix documentation accuracy and consistency
    
    - align DistSQL syntax docs, return columns, and examples with current source
    - normalize SQL and server error code documentation
    - fix MCP resources, management metadata docs, links, versions, and typos
    
    * Refactor MCP workflow support duplication
    
    Centralize repeated workflow validation, identifier checks, DistSQL rule query fallback, and MCP error/recovery mapping while preserving existing behavior. Reuse shared helpers across MCP feature planning/validation services and simplify duplicated MCP E2E test setup helpers.
---
 .../mcp/core/protocol/error/MCPErrorConverter.java |  85 +++----
 .../protocol/error/MCPRecoveryPayloadFactory.java  |  96 ++++----
 .../service/BroadcastWorkflowPlanningService.java  |  23 +-
 .../BroadcastWorkflowValidationService.java        |   8 +-
 .../tool/service/EncryptRuleInspectionService.java |  17 +-
 .../service/EncryptWorkflowValidationService.java  |   8 +-
 .../tool/service/MaskRuleInspectionService.java    |  15 +-
 .../service/MaskWorkflowValidationService.java     |   8 +-
 ...dwriteSplittingRuleWorkflowPlanningService.java |  26 +--
 ...riteSplittingRuleWorkflowValidationService.java |   8 +-
 ...riteSplittingStatusWorkflowPlanningService.java |  15 +-
 ...teSplittingStatusWorkflowValidationService.java |   8 +-
 .../service/ShadowWorkflowPlanningService.java     |  46 ++--
 .../service/ShadowWorkflowValidationService.java   |   8 +-
 .../service/ShardingWorkflowValidationService.java |   8 +-
 .../service/WorkflowDistSQLQueryUtils.java         |  22 ++
 .../WorkflowGuidanceResourceHintProvider.java      | 259 ++++++++++-----------
 .../service/WorkflowIntentResolverSupport.java     |  64 ++---
 .../workflow/service/WorkflowPlanningSupport.java  |  71 +++++-
 .../service/WorkflowValidationSupport.java         |  21 ++
 .../service/WorkflowDistSQLQueryUtilsTest.java     |  33 +++
 .../service/WorkflowPlanningSupportTest.java       |  18 ++
 .../service/WorkflowValidationSupportTest.java     |  32 +++
 .../AbstractHttpProgrammaticRuntimeE2ETest.java    |  15 ++
 .../HttpTransportApprovalSafetyE2ETest.java        |  15 --
 .../HttpTransportCompletionE2ETest.java            |  14 --
 .../support/runtime/MySQLRuntimeTestSupport.java   |  29 ++-
 27 files changed, 493 insertions(+), 479 deletions(-)

diff --git a/mcp/core/src/main/java/org/apache/shardingsphere/mcp/core/protocol/error/MCPErrorConverter.java b/mcp/core/src/main/java/org/apache/shardingsphere/mcp/core/protocol/error/MCPErrorConverter.java
index 8c7fad6d73c..4aa11fb52b9 100644
--- a/mcp/core/src/main/java/org/apache/shardingsphere/mcp/core/protocol/error/MCPErrorConverter.java
+++ b/mcp/core/src/main/java/org/apache/shardingsphere/mcp/core/protocol/error/MCPErrorConverter.java
@@ -36,6 +36,7 @@ import java.sql.SQLException;
 import java.sql.SQLFeatureNotSupportedException;
 import java.sql.SQLSyntaxErrorException;
 import java.sql.SQLTimeoutException;
+import java.util.List;
 import java.util.Objects;
 
 /**
@@ -44,6 +45,26 @@ import java.util.Objects;
 @NoArgsConstructor(access = AccessLevel.PRIVATE)
 public final class MCPErrorConverter {
     
+    private static final List<ErrorMapping> ERROR_MAPPINGS = List.of(
+            new ErrorMapping(UnsupportedToolException.class, "Unsupported tool."),
+            new ErrorMapping(UnsupportedResourceUriException.class, "Unsupported resource URI."),
+            new ErrorMapping(MCPInvalidRequestException.class, "Invalid request."),
+            new ErrorMapping(MCPNotFoundException.class, "MCP operation not found."),
+            new ErrorMapping(MCPUnsupportedException.class, "Unsupported MCP operation."),
+            new ErrorMapping(MCPTimeoutException.class, "MCP operation timeout."),
+            new ErrorMapping(MCPTransactionStateException.class, "MCP transaction operation failed."),
+            new ErrorMapping(MCPQueryFailedException.class, "MCP query failed."),
+            new ErrorMapping(MCPToolCallLimitExceededException.class, "MCP tool call limit exceeded."),
+            new ErrorMapping(MCPUnavailableException.class, "Service is temporarily unavailable."),
+            new ErrorMapping(RuntimeDatabaseConnectionException.class, "Runtime database connection failed."),
+            new ErrorMapping(SQLSyntaxErrorException.class, "Invalid request."),
+            new ErrorMapping(SQLTimeoutException.class, "MCP operation timeout."),
+            new ErrorMapping(SQLFeatureNotSupportedException.class, "Unsupported MCP operation."),
+            new ErrorMapping(UnsupportedOperationException.class, "Unsupported MCP operation."),
+            new ErrorMapping(SQLException.class, "MCP query failed."),
+            new ErrorMapping(IllegalArgumentException.class, "Invalid request."),
+            new ErrorMapping(IllegalStateException.class, "MCP operation failed."));
+    
     /**
      * Convert throwable to MCP error.
      *
@@ -51,59 +72,10 @@ public final class MCPErrorConverter {
      * @return MCP error
      */
     public static MCPErrorResponse convert(final Throwable cause) {
-        if (cause instanceof UnsupportedToolException) {
-            return createError(cause, "Unsupported tool.");
-        }
-        if (cause instanceof UnsupportedResourceUriException) {
-            return createError(cause, "Unsupported resource URI.");
-        }
-        if (cause instanceof MCPInvalidRequestException) {
-            return createError(cause, "Invalid request.");
-        }
-        if (cause instanceof MCPNotFoundException) {
-            return createError(cause, "MCP operation not found.");
-        }
-        if (cause instanceof MCPUnsupportedException) {
-            return createError(cause, "Unsupported MCP operation.");
-        }
-        if (cause instanceof MCPTimeoutException) {
-            return createError(cause, "MCP operation timeout.");
-        }
-        if (cause instanceof MCPTransactionStateException) {
-            return createError(cause, "MCP transaction operation failed.");
-        }
-        if (cause instanceof MCPQueryFailedException) {
-            return createError(cause, "MCP query failed.");
-        }
-        if (cause instanceof MCPToolCallLimitExceededException) {
-            return createError(cause, "MCP tool call limit exceeded.");
-        }
-        if (cause instanceof MCPUnavailableException) {
-            return createError(cause, "Service is temporarily unavailable.");
-        }
-        if (cause instanceof RuntimeDatabaseConnectionException) {
-            return createError(cause, "Runtime database connection failed.");
-        }
-        if (cause instanceof SQLSyntaxErrorException) {
-            return createError(cause, "Invalid request.");
-        }
-        if (cause instanceof SQLTimeoutException) {
-            return createError(cause, "MCP operation timeout.");
-        }
-        if (cause instanceof SQLFeatureNotSupportedException) {
-            return createError(cause, "Unsupported MCP operation.");
-        }
-        if (cause instanceof UnsupportedOperationException) {
-            return createError(cause, "Unsupported MCP operation.");
-        }
-        if (cause instanceof SQLException) {
-            return createError(cause, "MCP query failed.");
-        }
-        if (cause instanceof IllegalArgumentException) {
-            return createError(cause, "Invalid request.");
-        }
-        if (cause instanceof IllegalStateException) {
-            return createError(cause, "MCP operation failed.");
+        for (ErrorMapping each : ERROR_MAPPINGS) {
+            if (each.matches(cause)) {
+                return createError(cause, each.defaultMessage());
+            }
         }
         return createError(cause, "Service is temporarily unavailable.");
     }
@@ -112,4 +84,11 @@ public final class MCPErrorConverter {
         String message = MCPQueryRecoveryPayloadFactory.isQueryFailure(cause) ? defaultMessage : Objects.toString(cause.getMessage(), defaultMessage).trim();
         return new MCPErrorResponse(message, MCPRecoveryPayloadFactory.create(cause));
     }
+    
+    private record ErrorMapping(Class<? extends Throwable> causeType, String defaultMessage) {
+        
+        private boolean matches(final Throwable cause) {
+            return causeType.isInstance(cause);
+        }
+    }
 }
diff --git a/mcp/core/src/main/java/org/apache/shardingsphere/mcp/core/protocol/error/MCPRecoveryPayloadFactory.java b/mcp/core/src/main/java/org/apache/shardingsphere/mcp/core/protocol/error/MCPRecoveryPayloadFactory.java
index 42f969eec99..d7ee80899a7 100644
--- a/mcp/core/src/main/java/org/apache/shardingsphere/mcp/core/protocol/error/MCPRecoveryPayloadFactory.java
+++ b/mcp/core/src/main/java/org/apache/shardingsphere/mcp/core/protocol/error/MCPRecoveryPayloadFactory.java
@@ -37,7 +37,9 @@ import org.apache.shardingsphere.mcp.core.tool.handler.execute.MetadataIntrospec
 import org.apache.shardingsphere.mcp.core.tool.handler.execute.SQLToolMismatchException;
 import org.apache.shardingsphere.mcp.support.database.metadata.jdbc.RuntimeDatabaseConnectionException;
 
+import java.util.List;
 import java.util.Map;
+import java.util.function.Function;
 
 /**
  * MCP recovery payload factory.
@@ -45,61 +47,55 @@ import java.util.Map;
 @NoArgsConstructor(access = AccessLevel.PRIVATE)
 final class MCPRecoveryPayloadFactory {
     
+    private static final List<RecoveryMapping> RECOVERY_MAPPINGS = List.of(
+            new RecoveryMapping(SQLToolMismatchException.class, cause -> MCPSQLRecoveryPayloadFactory.createSQLToolMismatchRecovery((SQLToolMismatchException) cause)),
+            new RecoveryMapping(MetadataIntrospectionSQLStatementException.class,
+                    cause -> MCPSQLRecoveryPayloadFactory.createMetadataIntrospectionSQLRecovery((MetadataIntrospectionSQLStatementException) cause)),
+            new RecoveryMapping(MCPMultipleSQLStatementsException.class, cause -> MCPSQLRecoveryPayloadFactory.createMultipleStatementsRecovery()),
+            new RecoveryMapping(MCPUnsupportedSQLStatementException.class, cause -> MCPSQLRecoveryPayloadFactory.createUnsupportedStatementRecovery()),
+            new RecoveryMapping(MCPBannedSQLStatementException.class, cause -> MCPSQLRecoveryPayloadFactory.createBannedStatementRecovery()),
+            new RecoveryMapping(MCPExecutionModeRequiredException.class,
+                    cause -> MCPWorkflowRecoveryPayloadFactory.createMissingExecutionModeRecovery((MCPExecutionModeRequiredException) cause)),
+            new RecoveryMapping(MCPInvalidExecutionModeException.class,
+                    cause -> MCPWorkflowRecoveryPayloadFactory.createInvalidExecutionModeRecovery((MCPInvalidExecutionModeException) cause)),
+            new RecoveryMapping(MCPInvalidApprovedStepsException.class,
+                    cause -> MCPWorkflowRecoveryPayloadFactory.createInvalidApprovedStepsRecovery((MCPInvalidApprovedStepsException) cause)),
+            new RecoveryMapping(MCPWorkflowStateException.class, cause -> MCPWorkflowRecoveryPayloadFactory.createWorkflowStateRecovery((MCPWorkflowStateException) cause)),
+            new RecoveryMapping(UnsupportedToolException.class, cause -> MCPBasicRecoveryPayloadFactory.createUnsupportedToolRecovery(((UnsupportedToolException) cause).getToolName())),
+            new RecoveryMapping(UnsupportedResourceUriException.class,
+                    cause -> MCPBasicRecoveryPayloadFactory.createUnsupportedResourceRecovery(((UnsupportedResourceUriException) cause).getResourceUri())),
+            new RecoveryMapping(RuntimeDatabaseConnectionException.class,
+                    cause -> MCPBasicRecoveryPayloadFactory.createRuntimeDatabaseConnectionRecovery((RuntimeDatabaseConnectionException) cause)),
+            new RecoveryMapping(MCPToolCallLimitExceededException.class,
+                    cause -> MCPBasicRecoveryPayloadFactory.createToolCallLimitRecovery((MCPToolCallLimitExceededException) cause)),
+            new RecoveryMapping(MCPInvalidToolArgumentException.class, cause -> MCPBasicRecoveryPayloadFactory.createInvalidToolArgumentRecovery((MCPInvalidToolArgumentException) cause)),
+            new RecoveryMapping(MCPToolArgumentContractViolationException.class,
+                    cause -> MCPBasicRecoveryPayloadFactory.createToolArgumentContractViolationRecovery((MCPToolArgumentContractViolationException) cause)),
+            new RecoveryMapping(MCPMissingToolArgumentException.class,
+                    cause -> MCPBasicRecoveryPayloadFactory.createMissingArgumentRecovery(((MCPMissingToolArgumentException) cause).getArgumentName())),
+            new RecoveryMapping(MCPInvalidMetadataObjectTypesException.class,
+                    cause -> MCPBasicRecoveryPayloadFactory.createInvalidObjectTypesRecovery((MCPInvalidMetadataObjectTypesException) cause)));
+    
     static Map<String, Object> create(final Throwable cause) {
-        if (cause instanceof SQLToolMismatchException) {
-            return MCPSQLRecoveryPayloadFactory.createSQLToolMismatchRecovery((SQLToolMismatchException) cause);
-        }
-        if (cause instanceof MetadataIntrospectionSQLStatementException) {
-            return MCPSQLRecoveryPayloadFactory.createMetadataIntrospectionSQLRecovery((MetadataIntrospectionSQLStatementException) cause);
-        }
-        if (cause instanceof MCPMultipleSQLStatementsException) {
-            return MCPSQLRecoveryPayloadFactory.createMultipleStatementsRecovery();
-        }
-        if (cause instanceof MCPUnsupportedSQLStatementException) {
-            return MCPSQLRecoveryPayloadFactory.createUnsupportedStatementRecovery();
-        }
-        if (cause instanceof MCPBannedSQLStatementException) {
-            return MCPSQLRecoveryPayloadFactory.createBannedStatementRecovery();
-        }
-        if (cause instanceof MCPExecutionModeRequiredException) {
-            return MCPWorkflowRecoveryPayloadFactory.createMissingExecutionModeRecovery((MCPExecutionModeRequiredException) cause);
-        }
-        if (cause instanceof MCPInvalidExecutionModeException) {
-            return MCPWorkflowRecoveryPayloadFactory.createInvalidExecutionModeRecovery((MCPInvalidExecutionModeException) cause);
-        }
-        if (cause instanceof MCPInvalidApprovedStepsException) {
-            return MCPWorkflowRecoveryPayloadFactory.createInvalidApprovedStepsRecovery((MCPInvalidApprovedStepsException) cause);
-        }
-        if (cause instanceof MCPWorkflowStateException) {
-            return MCPWorkflowRecoveryPayloadFactory.createWorkflowStateRecovery((MCPWorkflowStateException) cause);
-        }
-        if (cause instanceof UnsupportedToolException) {
-            return MCPBasicRecoveryPayloadFactory.createUnsupportedToolRecovery(((UnsupportedToolException) cause).getToolName());
-        }
-        if (cause instanceof UnsupportedResourceUriException) {
-            return MCPBasicRecoveryPayloadFactory.createUnsupportedResourceRecovery(((UnsupportedResourceUriException) cause).getResourceUri());
-        }
-        if (cause instanceof RuntimeDatabaseConnectionException) {
-            return MCPBasicRecoveryPayloadFactory.createRuntimeDatabaseConnectionRecovery((RuntimeDatabaseConnectionException) cause);
-        }
-        if (cause instanceof MCPToolCallLimitExceededException) {
-            return MCPBasicRecoveryPayloadFactory.createToolCallLimitRecovery((MCPToolCallLimitExceededException) cause);
-        }
-        if (cause instanceof MCPInvalidToolArgumentException) {
-            return MCPBasicRecoveryPayloadFactory.createInvalidToolArgumentRecovery((MCPInvalidToolArgumentException) cause);
-        }
-        if (cause instanceof MCPToolArgumentContractViolationException) {
-            return MCPBasicRecoveryPayloadFactory.createToolArgumentContractViolationRecovery((MCPToolArgumentContractViolationException) cause);
-        }
-        if (cause instanceof MCPMissingToolArgumentException) {
-            return MCPBasicRecoveryPayloadFactory.createMissingArgumentRecovery(((MCPMissingToolArgumentException) cause).getArgumentName());
-        }
-        if (cause instanceof MCPInvalidMetadataObjectTypesException) {
-            return MCPBasicRecoveryPayloadFactory.createInvalidObjectTypesRecovery((MCPInvalidMetadataObjectTypesException) cause);
+        for (RecoveryMapping each : RECOVERY_MAPPINGS) {
+            if (each.matches(cause)) {
+                return each.create(cause);
+            }
         }
         if (MCPQueryRecoveryPayloadFactory.isQueryFailure(cause)) {
             return MCPQueryRecoveryPayloadFactory.create(cause);
         }
         return Map.of();
     }
+    
+    private record RecoveryMapping(Class<? extends Throwable> causeType, Function<Throwable, Map<String, Object>> payloadFactory) {
+        
+        private boolean matches(final Throwable cause) {
+            return causeType.isInstance(cause);
+        }
+        
+        private Map<String, Object> create(final Throwable cause) {
+            return payloadFactory.apply(cause);
+        }
+    }
 }
diff --git a/mcp/features/broadcast/src/main/java/org/apache/shardingsphere/mcp/feature/broadcast/tool/service/BroadcastWorkflowPlanningService.java b/mcp/features/broadcast/src/main/java/org/apache/shardingsphere/mcp/feature/broadcast/tool/service/BroadcastWorkflowPlanningService.java
index 2b5e09b8941..0586b2d20d8 100644
--- a/mcp/features/broadcast/src/main/java/org/apache/shardingsphere/mcp/feature/broadcast/tool/service/BroadcastWorkflowPlanningService.java
+++ b/mcp/features/broadcast/src/main/java/org/apache/shardingsphere/mcp/feature/broadcast/tool/service/BroadcastWorkflowPlanningService.java
@@ -30,7 +30,6 @@ import org.apache.shardingsphere.mcp.support.workflow.service.WorkflowPlanningSu
 import org.apache.shardingsphere.mcp.support.workflow.service.WorkflowRuleValueUtils;
 import org.apache.shardingsphere.mcp.support.workflow.service.WorkflowSQLUtils;
 
-import java.util.Collection;
 import java.util.List;
 import java.util.Map;
 
@@ -113,7 +112,8 @@ public final class BroadcastWorkflowPlanningService {
             snapshot.setStatus(WorkflowLifecycle.STATUS_CLARIFYING);
             return false;
         }
-        if (!ensureSupportedIdentifier("database", request.getDatabase(), snapshot) || !ensureSupportedIdentifiers("tables", request.getTargetTables(), snapshot)) {
+        if (!planningSupport.ensureSupportedIdentifiers("database", List.of(request.getDatabase()), snapshot, "discovering")
+                || !planningSupport.ensureSupportedIdentifiers("tables", request.getTargetTables(), snapshot, "discovering")) {
             snapshot.setStatus(WorkflowLifecycle.STATUS_FAILED);
             return false;
         }
@@ -128,25 +128,6 @@ public final class BroadcastWorkflowPlanningService {
         return true;
     }
     
-    private boolean ensureSupportedIdentifiers(final String fieldName, final Collection<String> identifiers, final WorkflowContextSnapshot snapshot) {
-        for (String each : identifiers) {
-            if (!ensureSupportedIdentifier(fieldName, each, snapshot)) {
-                return false;
-            }
-        }
-        return true;
-    }
-    
-    private boolean ensureSupportedIdentifier(final String fieldName, final String identifier, final WorkflowContextSnapshot snapshot) {
-        if (WorkflowSQLUtils.isSupportedIdentifier(identifier)) {
-            return true;
-        }
-        snapshot.getIssues().add(new WorkflowIssue(WorkflowIssueCode.UNSUPPORTED_IDENTIFIER, "error", "discovering",
-                String.format("%s identifier `%s` contains unsupported characters.", fieldName, identifier),
-                "Use a reviewable logical identifier without NUL or line terminators.", false, Map.of("field", fieldName, "identifier", identifier)));
-        return false;
-    }
-    
     private boolean ensureLifecycleState(final ClarifiedIntent clarifiedIntent, final BroadcastWorkflowRequest request, final List<Map<String, Object>> broadcastRules,
                                          final WorkflowContextSnapshot snapshot, final String databaseType) {
         boolean dropWorkflow = WorkflowLifecycle.OPERATION_DROP.equalsIgnoreCase(clarifiedIntent.getOperationType());
diff --git a/mcp/features/broadcast/src/main/java/org/apache/shardingsphere/mcp/feature/broadcast/tool/service/BroadcastWorkflowValidationService.java b/mcp/features/broadcast/src/main/java/org/apache/shardingsphere/mcp/feature/broadcast/tool/service/BroadcastWorkflowValidationService.java
index 630dd312dc9..d74ce8be30c 100644
--- a/mcp/features/broadcast/src/main/java/org/apache/shardingsphere/mcp/feature/broadcast/tool/service/BroadcastWorkflowValidationService.java
+++ b/mcp/features/broadcast/src/main/java/org/apache/shardingsphere/mcp/feature/broadcast/tool/service/BroadcastWorkflowValidationService.java
@@ -62,13 +62,7 @@ public final class BroadcastWorkflowValidationService implements MCPWorkflowRunt
     public Map<String, Object> validate(final WorkflowSessionContext workflowSessionContext, final MCPMetadataQueryFacade metadataQueryFacade,
                                         final MCPFeatureQueryFacade queryFacade, final MCPFeatureExecutionFacade executionFacade, final String sessionId,
                                         final WorkflowContextSnapshot snapshot) {
-        Map<String, Object> rejectedResponse = validationSupport.checkValidatePreconditions(sessionId, snapshot);
-        if (!rejectedResponse.isEmpty()) {
-            return rejectedResponse;
-        }
-        ValidationReport validationReport = createValidationReport(snapshot, queryFacade);
-        snapshot.setValidationReport(validationReport);
-        return validationSupport.finalizeValidation(workflowSessionContext, snapshot, validationReport);
+        return validationSupport.validateAndFinalize(workflowSessionContext, sessionId, snapshot, () -> createValidationReport(snapshot, queryFacade));
     }
     
     @Override
diff --git a/mcp/features/encrypt/src/main/java/org/apache/shardingsphere/mcp/feature/encrypt/tool/service/EncryptRuleInspectionService.java b/mcp/features/encrypt/src/main/java/org/apache/shardingsphere/mcp/feature/encrypt/tool/service/EncryptRuleInspectionService.java
index 9be1b45ba74..e58cd310301 100644
--- a/mcp/features/encrypt/src/main/java/org/apache/shardingsphere/mcp/feature/encrypt/tool/service/EncryptRuleInspectionService.java
+++ b/mcp/features/encrypt/src/main/java/org/apache/shardingsphere/mcp/feature/encrypt/tool/service/EncryptRuleInspectionService.java
@@ -17,8 +17,8 @@
 
 package org.apache.shardingsphere.mcp.feature.encrypt.tool.service;
 
-import org.apache.shardingsphere.mcp.api.protocol.exception.MCPQueryFailedException;
 import org.apache.shardingsphere.mcp.feature.encrypt.EncryptFeatureDefinition;
+import org.apache.shardingsphere.mcp.api.protocol.exception.MCPQueryFailedException;
 import org.apache.shardingsphere.mcp.support.database.spi.MCPFeatureQueryFacade;
 import org.apache.shardingsphere.mcp.support.workflow.model.AlgorithmPropertyRequirement;
 import org.apache.shardingsphere.mcp.support.workflow.service.WorkflowDistSQLQueryUtils;
@@ -44,7 +44,7 @@ public final class EncryptRuleInspectionService {
      * @return encrypt rules
      */
     public List<Map<String, Object>> queryEncryptRules(final MCPFeatureQueryFacade queryFacade, final String databaseName) {
-        return queryRuleRows(queryFacade, databaseName, String.format("SHOW ENCRYPT RULES FROM %s", WorkflowSQLUtils.formatDistSQLIdentifier(databaseName)));
+        return WorkflowDistSQLQueryUtils.queryRuleRows(queryFacade, databaseName, String.format("SHOW ENCRYPT RULES FROM %s", WorkflowSQLUtils.formatDistSQLIdentifier(databaseName)));
     }
     
     /**
@@ -56,7 +56,7 @@ public final class EncryptRuleInspectionService {
      * @return encrypt rules
      */
     public List<Map<String, Object>> queryEncryptRules(final MCPFeatureQueryFacade queryFacade, final String databaseName, final String tableName) {
-        return queryRuleRows(
+        return WorkflowDistSQLQueryUtils.queryRuleRows(
                 queryFacade, databaseName,
                 String.format("SHOW ENCRYPT TABLE RULE %s FROM %s", WorkflowSQLUtils.formatDistSQLIdentifier(tableName), WorkflowSQLUtils.formatDistSQLIdentifier(databaseName)));
     }
@@ -87,17 +87,6 @@ public final class EncryptRuleInspectionService {
         return result;
     }
     
-    private List<Map<String, Object>> queryRuleRows(final MCPFeatureQueryFacade queryFacade, final String databaseName, final String sql) {
-        try {
-            return queryFacade.query(databaseName, "", sql);
-        } catch (final MCPQueryFailedException ex) {
-            if (WorkflowDistSQLQueryUtils.isUnsupportedDistSQLQueryFailure(ex)) {
-                return List.of();
-            }
-            throw ex;
-        }
-    }
-    
     private List<Map<String, Object>> queryAlgorithmRows(final MCPFeatureQueryFacade queryFacade) {
         try {
             return queryFacade.queryWithAnyDatabase("SHOW ENCRYPT ALGORITHM PLUGINS");
diff --git a/mcp/features/encrypt/src/main/java/org/apache/shardingsphere/mcp/feature/encrypt/tool/service/EncryptWorkflowValidationService.java b/mcp/features/encrypt/src/main/java/org/apache/shardingsphere/mcp/feature/encrypt/tool/service/EncryptWorkflowValidationService.java
index c7417257467..3ecc86e065c 100644
--- a/mcp/features/encrypt/src/main/java/org/apache/shardingsphere/mcp/feature/encrypt/tool/service/EncryptWorkflowValidationService.java
+++ b/mcp/features/encrypt/src/main/java/org/apache/shardingsphere/mcp/feature/encrypt/tool/service/EncryptWorkflowValidationService.java
@@ -72,13 +72,7 @@ public final class EncryptWorkflowValidationService implements MCPWorkflowRuntim
     public Map<String, Object> validate(final WorkflowSessionContext workflowSessionContext, final MCPMetadataQueryFacade metadataQueryFacade,
                                         final MCPFeatureQueryFacade queryFacade, final MCPFeatureExecutionFacade executionFacade, final String sessionId,
                                         final WorkflowContextSnapshot snapshot) {
-        Map<String, Object> rejectedResponse = validationSupport.checkValidatePreconditions(sessionId, snapshot);
-        if (!rejectedResponse.isEmpty()) {
-            return rejectedResponse;
-        }
-        ValidationReport validationReport = createValidationReport(snapshot, queryFacade);
-        snapshot.setValidationReport(validationReport);
-        return validationSupport.finalizeValidation(workflowSessionContext, snapshot, validationReport);
+        return validationSupport.validateAndFinalize(workflowSessionContext, sessionId, snapshot, () -> createValidationReport(snapshot, queryFacade));
     }
     
     @Override
diff --git a/mcp/features/mask/src/main/java/org/apache/shardingsphere/mcp/feature/mask/tool/service/MaskRuleInspectionService.java b/mcp/features/mask/src/main/java/org/apache/shardingsphere/mcp/feature/mask/tool/service/MaskRuleInspectionService.java
index 7de3d5926db..e924daa449e 100644
--- a/mcp/features/mask/src/main/java/org/apache/shardingsphere/mcp/feature/mask/tool/service/MaskRuleInspectionService.java
+++ b/mcp/features/mask/src/main/java/org/apache/shardingsphere/mcp/feature/mask/tool/service/MaskRuleInspectionService.java
@@ -45,7 +45,7 @@ public final class MaskRuleInspectionService {
      * @return mask rules
      */
     public List<Map<String, Object>> queryMaskRules(final MCPFeatureQueryFacade queryFacade, final String databaseName) {
-        return queryRuleRows(queryFacade, databaseName, String.format("SHOW MASK RULES FROM %s", WorkflowSQLUtils.formatDistSQLIdentifier(databaseName)));
+        return WorkflowDistSQLQueryUtils.queryRuleRows(queryFacade, databaseName, String.format("SHOW MASK RULES FROM %s", WorkflowSQLUtils.formatDistSQLIdentifier(databaseName)));
     }
     
     /**
@@ -57,7 +57,7 @@ public final class MaskRuleInspectionService {
      * @return mask rules
      */
     public List<Map<String, Object>> queryMaskRules(final MCPFeatureQueryFacade queryFacade, final String databaseName, final String tableName) {
-        return queryRuleRows(
+        return WorkflowDistSQLQueryUtils.queryRuleRows(
                 queryFacade, databaseName, String.format("SHOW MASK RULE %s FROM %s", WorkflowSQLUtils.formatDistSQLIdentifier(tableName), WorkflowSQLUtils.formatDistSQLIdentifier(databaseName)));
     }
     
@@ -71,17 +71,6 @@ public final class MaskRuleInspectionService {
         return decorateMaskAlgorithms(queryAlgorithmRows(queryFacade));
     }
     
-    private List<Map<String, Object>> queryRuleRows(final MCPFeatureQueryFacade queryFacade, final String databaseName, final String sql) {
-        try {
-            return queryFacade.query(databaseName, "", sql);
-        } catch (final MCPQueryFailedException ex) {
-            if (WorkflowDistSQLQueryUtils.isUnsupportedDistSQLQueryFailure(ex)) {
-                return List.of();
-            }
-            throw ex;
-        }
-    }
-    
     private List<Map<String, Object>> queryAlgorithmRows(final MCPFeatureQueryFacade queryFacade) {
         try {
             return queryFacade.queryWithAnyDatabase("SHOW MASK ALGORITHM PLUGINS");
diff --git a/mcp/features/mask/src/main/java/org/apache/shardingsphere/mcp/feature/mask/tool/service/MaskWorkflowValidationService.java b/mcp/features/mask/src/main/java/org/apache/shardingsphere/mcp/feature/mask/tool/service/MaskWorkflowValidationService.java
index ed1953d3904..8e01b46687d 100644
--- a/mcp/features/mask/src/main/java/org/apache/shardingsphere/mcp/feature/mask/tool/service/MaskWorkflowValidationService.java
+++ b/mcp/features/mask/src/main/java/org/apache/shardingsphere/mcp/feature/mask/tool/service/MaskWorkflowValidationService.java
@@ -70,13 +70,7 @@ public final class MaskWorkflowValidationService implements MCPWorkflowRuntimeHa
     public Map<String, Object> validate(final WorkflowSessionContext workflowSessionContext, final MCPMetadataQueryFacade metadataQueryFacade,
                                         final MCPFeatureQueryFacade queryFacade, final MCPFeatureExecutionFacade executionFacade, final String sessionId,
                                         final WorkflowContextSnapshot snapshot) {
-        Map<String, Object> rejectedResponse = validationSupport.checkValidatePreconditions(sessionId, snapshot);
-        if (!rejectedResponse.isEmpty()) {
-            return rejectedResponse;
-        }
-        ValidationReport validationReport = createValidationReport(snapshot, queryFacade);
-        snapshot.setValidationReport(validationReport);
-        return validationSupport.finalizeValidation(workflowSessionContext, snapshot, validationReport);
+        return validationSupport.validateAndFinalize(workflowSessionContext, sessionId, snapshot, () -> createValidationReport(snapshot, queryFacade));
     }
     
     @Override
diff --git a/mcp/features/readwrite-splitting/src/main/java/org/apache/shardingsphere/mcp/feature/readwritesplitting/tool/service/ReadwriteSplittingRuleWorkflowPlanningService.java b/mcp/features/readwrite-splitting/src/main/java/org/apache/shardingsphere/mcp/feature/readwritesplitting/tool/service/ReadwriteSplittingRuleWorkflowPlanningService.java
index d71b0f52a84..7fc058b4759 100644
--- a/mcp/features/readwrite-splitting/src/main/java/org/apache/shardingsphere/mcp/feature/readwritesplitting/tool/service/ReadwriteSplittingRuleWorkflowPlanningService.java
+++ b/mcp/features/readwrite-splitting/src/main/java/org/apache/shardingsphere/mcp/feature/readwritesplitting/tool/service/ReadwriteSplittingRuleWorkflowPlanningService.java
@@ -128,9 +128,10 @@ public final class ReadwriteSplittingRuleWorkflowPlanningService {
             snapshot.setStatus(WorkflowLifecycle.STATUS_CLARIFYING);
             return false;
         }
-        if (!ensureSupportedIdentifier("database", request.getDatabase(), snapshot) || !ensureSupportedIdentifier(ReadwriteSplittingFeatureDefinition.RULE_FIELD, request.getRuleName(), snapshot)
-                || !ensureSupportedIdentifier(ReadwriteSplittingFeatureDefinition.WRITE_STORAGE_UNIT_FIELD, request.getWriteStorageUnit(), snapshot)
-                || !ensureSupportedIdentifiers(ReadwriteSplittingFeatureDefinition.READ_STORAGE_UNITS_FIELD, request.getReadStorageUnits(), snapshot)) {
+        if (!planningSupport.ensureOptionalSupportedIdentifiers("database", List.of(request.getDatabase()), snapshot, "intaking")
+                || !planningSupport.ensureOptionalSupportedIdentifiers(ReadwriteSplittingFeatureDefinition.RULE_FIELD, List.of(request.getRuleName()), snapshot, "intaking")
+                || !planningSupport.ensureOptionalSupportedIdentifiers(ReadwriteSplittingFeatureDefinition.WRITE_STORAGE_UNIT_FIELD, List.of(request.getWriteStorageUnit()), snapshot, "intaking")
+                || !planningSupport.ensureOptionalSupportedIdentifiers(ReadwriteSplittingFeatureDefinition.READ_STORAGE_UNITS_FIELD, request.getReadStorageUnits(), snapshot, "intaking")) {
             snapshot.setStatus(WorkflowLifecycle.STATUS_FAILED);
             return false;
         }
@@ -167,25 +168,6 @@ public final class ReadwriteSplittingRuleWorkflowPlanningService {
         }
     }
     
-    private boolean ensureSupportedIdentifiers(final String fieldName, final Collection<String> identifiers, final WorkflowContextSnapshot snapshot) {
-        for (String each : identifiers) {
-            if (!ensureSupportedIdentifier(fieldName, each, snapshot)) {
-                return false;
-            }
-        }
-        return true;
-    }
-    
-    private boolean ensureSupportedIdentifier(final String fieldName, final String identifier, final WorkflowContextSnapshot snapshot) {
-        if (identifier.isEmpty() || WorkflowSQLUtils.isSupportedIdentifier(identifier)) {
-            return true;
-        }
-        snapshot.getIssues().add(new WorkflowIssue(WorkflowIssueCode.UNSUPPORTED_IDENTIFIER, "error", "intaking",
-                String.format("%s identifier `%s` contains unsupported characters.", fieldName, identifier),
-                "Use a reviewable logical identifier without NUL or line terminators.", false, Map.of("field", fieldName, "identifier", identifier)));
-        return false;
-    }
-    
     private boolean ensureLifecycleState(final ClarifiedIntent clarifiedIntent, final ReadwriteSplittingRuleWorkflowRequest request, final List<Map<String, Object>> rules,
                                          final WorkflowContextSnapshot snapshot, final String databaseType) {
         boolean ruleExists = containsRule(rules, databaseType, request.getRuleName());
diff --git a/mcp/features/readwrite-splitting/src/main/java/org/apache/shardingsphere/mcp/feature/readwritesplitting/tool/service/ReadwriteSplittingRuleWorkflowValidationService.java b/mcp/features/readwrite-splitting/src/main/java/org/apache/shardingsphere/mcp/feature/readwritesplitting/tool/service/ReadwriteSplittingRuleWorkflowValidationService.java
index 114b490a422..565013675ae 100644
--- a/mcp/features/readwrite-splitting/src/main/java/org/apache/shardingsphere/mcp/feature/readwritesplitting/tool/service/ReadwriteSplittingRuleWorkflowValidationService.java
+++ b/mcp/features/readwrite-splitting/src/main/java/org/apache/shardingsphere/mcp/feature/readwritesplitting/tool/service/ReadwriteSplittingRuleWorkflowValidationService.java
@@ -66,13 +66,7 @@ public final class ReadwriteSplittingRuleWorkflowValidationService implements MC
     public Map<String, Object> validate(final WorkflowSessionContext workflowSessionContext, final MCPMetadataQueryFacade metadataQueryFacade,
                                         final MCPFeatureQueryFacade queryFacade, final MCPFeatureExecutionFacade executionFacade, final String sessionId,
                                         final WorkflowContextSnapshot snapshot) {
-        Map<String, Object> rejectedResponse = validationSupport.checkValidatePreconditions(sessionId, snapshot);
-        if (!rejectedResponse.isEmpty()) {
-            return rejectedResponse;
-        }
-        ValidationReport validationReport = createValidationReport(snapshot, queryFacade);
-        snapshot.setValidationReport(validationReport);
-        return validationSupport.finalizeValidation(workflowSessionContext, snapshot, validationReport);
+        return validationSupport.validateAndFinalize(workflowSessionContext, sessionId, snapshot, () -> createValidationReport(snapshot, queryFacade));
     }
     
     @Override
diff --git a/mcp/features/readwrite-splitting/src/main/java/org/apache/shardingsphere/mcp/feature/readwritesplitting/tool/service/ReadwriteSplittingStatusWorkflowPlanningService.java b/mcp/features/readwrite-splitting/src/main/java/org/apache/shardingsphere/mcp/feature/readwritesplitting/tool/service/ReadwriteSplittingStatusWorkflowPlanningService.java
index 47efe6a6772..4b7816cbbbc 100644
--- a/mcp/features/readwrite-splitting/src/main/java/org/apache/shardingsphere/mcp/feature/readwritesplitting/tool/service/ReadwriteSplittingStatusWorkflowPlanningService.java
+++ b/mcp/features/readwrite-splitting/src/main/java/org/apache/shardingsphere/mcp/feature/readwritesplitting/tool/service/ReadwriteSplittingStatusWorkflowPlanningService.java
@@ -117,8 +117,9 @@ public final class ReadwriteSplittingStatusWorkflowPlanningService {
             snapshot.setStatus(WorkflowLifecycle.STATUS_CLARIFYING);
             return false;
         }
-        if (!ensureSupportedIdentifier("database", request.getDatabase(), snapshot) || !ensureSupportedIdentifier(ReadwriteSplittingFeatureDefinition.RULE_FIELD, request.getRuleName(), snapshot)
-                || !ensureSupportedIdentifier(ReadwriteSplittingFeatureDefinition.STORAGE_UNIT_FIELD, request.getStorageUnit(), snapshot)) {
+        if (!planningSupport.ensureOptionalSupportedIdentifiers("database", List.of(request.getDatabase()), snapshot, "intaking")
+                || !planningSupport.ensureOptionalSupportedIdentifiers(ReadwriteSplittingFeatureDefinition.RULE_FIELD, List.of(request.getRuleName()), snapshot, "intaking")
+                || !planningSupport.ensureOptionalSupportedIdentifiers(ReadwriteSplittingFeatureDefinition.STORAGE_UNIT_FIELD, List.of(request.getStorageUnit()), snapshot, "intaking")) {
             snapshot.setStatus(WorkflowLifecycle.STATUS_FAILED);
             return false;
         }
@@ -143,16 +144,6 @@ public final class ReadwriteSplittingStatusWorkflowPlanningService {
         }
     }
     
-    private boolean ensureSupportedIdentifier(final String fieldName, final String identifier, final WorkflowContextSnapshot snapshot) {
-        if (identifier.isEmpty() || WorkflowSQLUtils.isSupportedIdentifier(identifier)) {
-            return true;
-        }
-        snapshot.getIssues().add(new WorkflowIssue(WorkflowIssueCode.UNSUPPORTED_IDENTIFIER, "error", "intaking",
-                String.format("%s identifier `%s` contains unsupported characters.", fieldName, identifier),
-                "Use a reviewable logical identifier without NUL or line terminators.", false, Map.of("field", fieldName, "identifier", identifier)));
-        return false;
-    }
-    
     private boolean ensureTargetStatusRow(final ReadwriteSplittingStatusWorkflowRequest request, final List<Map<String, Object>> statuses,
                                           final WorkflowContextSnapshot snapshot, final String databaseType) {
         if (statuses.stream().anyMatch(each -> matchesStatusTarget(request, each, databaseType))) {
diff --git a/mcp/features/readwrite-splitting/src/main/java/org/apache/shardingsphere/mcp/feature/readwritesplitting/tool/service/ReadwriteSplittingStatusWorkflowValidationService.java b/mcp/features/readwrite-splitting/src/main/java/org/apache/shardingsphere/mcp/feature/readwritesplitting/tool/service/ReadwriteSplittingStatusWorkflowValidationService.java
index 70ea8adcbb4..42f7b8a3724 100644
--- a/mcp/features/readwrite-splitting/src/main/java/org/apache/shardingsphere/mcp/feature/readwritesplitting/tool/service/ReadwriteSplittingStatusWorkflowValidationService.java
+++ b/mcp/features/readwrite-splitting/src/main/java/org/apache/shardingsphere/mcp/feature/readwritesplitting/tool/service/ReadwriteSplittingStatusWorkflowValidationService.java
@@ -59,13 +59,7 @@ public final class ReadwriteSplittingStatusWorkflowValidationService implements
     public Map<String, Object> validate(final WorkflowSessionContext workflowSessionContext, final MCPMetadataQueryFacade metadataQueryFacade,
                                         final MCPFeatureQueryFacade queryFacade, final MCPFeatureExecutionFacade executionFacade, final String sessionId,
                                         final WorkflowContextSnapshot snapshot) {
-        Map<String, Object> rejectedResponse = validationSupport.checkValidatePreconditions(sessionId, snapshot);
-        if (!rejectedResponse.isEmpty()) {
-            return rejectedResponse;
-        }
-        ValidationReport validationReport = createValidationReport(snapshot, queryFacade);
-        snapshot.setValidationReport(validationReport);
-        return validationSupport.finalizeValidation(workflowSessionContext, snapshot, validationReport);
+        return validationSupport.validateAndFinalize(workflowSessionContext, sessionId, snapshot, () -> createValidationReport(snapshot, queryFacade));
     }
     
     @Override
diff --git a/mcp/features/shadow/src/main/java/org/apache/shardingsphere/mcp/feature/shadow/tool/service/ShadowWorkflowPlanningService.java b/mcp/features/shadow/src/main/java/org/apache/shardingsphere/mcp/feature/shadow/tool/service/ShadowWorkflowPlanningService.java
index 3eca34c9806..48dbc6c3810 100644
--- a/mcp/features/shadow/src/main/java/org/apache/shardingsphere/mcp/feature/shadow/tool/service/ShadowWorkflowPlanningService.java
+++ b/mcp/features/shadow/src/main/java/org/apache/shardingsphere/mcp/feature/shadow/tool/service/ShadowWorkflowPlanningService.java
@@ -73,6 +73,8 @@ public final class ShadowWorkflowPlanningService {
     
     private static final List<String> VALIDATION_LAYERS = List.of("rules", "algorithms");
     
+    private static final String ALGORITHM_ARTIFACT_READY_MESSAGE = "Please use a shadow algorithm visible in the current Proxy and provide required properties.";
+    
     private final WorkflowPlanningSupport planningSupport = new WorkflowPlanningSupport();
     
     private final ShadowInspectionService inspectionService;
@@ -112,15 +114,11 @@ public final class ShadowWorkflowPlanningService {
         ShadowRuleWorkflowRequest mergedRequest = prepareSnapshot(result, request, ShadowFeatureDefinition.RULE_WORKFLOW_KIND,
                 resolveIntent(request, "create"), "Shadow rule workflow plan.", RULE_INTERACTION_STEPS);
         planningSupport.applyResolvedIntent(mergedRequest, result.getClarifiedIntent());
-        if (!WorkflowLifecycle.OPERATION_DROP.equalsIgnoreCase(result.getClarifiedIntent().getOperationType()) && !mergedRequest.getDatabase().isEmpty()) {
-            planAlgorithms(queryFacade, mergedRequest, result);
-        }
+        planAlgorithmsIfRequired(queryFacade, mergedRequest, result);
         if (!ensureRulePlanningContext(mergedRequest, result.getClarifiedIntent(), result)) {
             return workflowSessionContext.persist(result, WorkflowLifecycle.STEP_CLARIFYING, result.getStatus());
         }
-        if (!WorkflowLifecycle.OPERATION_DROP.equalsIgnoreCase(result.getClarifiedIntent().getOperationType())
-                && !planningSupport.isReadyForArtifactPlanning(mergedRequest, result.getClarifiedIntent(), result, findPropertyRequirements(mergedRequest),
-                        "Please use a shadow algorithm visible in the current Proxy and provide required properties.")) {
+        if (!isReadyForAlgorithmArtifactPlanning(mergedRequest, result)) {
             return workflowSessionContext.persist(result, WorkflowLifecycle.STEP_CLARIFYING, WorkflowLifecycle.STATUS_CLARIFYING);
         }
         String databaseType = queryFacade.getDatabaseType(mergedRequest.getDatabase());
@@ -146,15 +144,11 @@ public final class ShadowWorkflowPlanningService {
         ShadowDefaultAlgorithmWorkflowRequest mergedRequest = prepareSnapshot(result, request, ShadowFeatureDefinition.DEFAULT_ALGORITHM_WORKFLOW_KIND,
                 resolveIntent(request, "create"), "Default shadow algorithm workflow plan.", DEFAULT_ALGORITHM_INTERACTION_STEPS);
         planningSupport.applyResolvedIntent(mergedRequest, result.getClarifiedIntent());
-        if (!WorkflowLifecycle.OPERATION_DROP.equalsIgnoreCase(result.getClarifiedIntent().getOperationType()) && !mergedRequest.getDatabase().isEmpty()) {
-            planAlgorithms(queryFacade, mergedRequest, result);
-        }
+        planAlgorithmsIfRequired(queryFacade, mergedRequest, result);
         if (!ensureDefaultAlgorithmPlanningContext(mergedRequest, result.getClarifiedIntent(), result)) {
             return workflowSessionContext.persist(result, WorkflowLifecycle.STEP_CLARIFYING, result.getStatus());
         }
-        if (!WorkflowLifecycle.OPERATION_DROP.equalsIgnoreCase(result.getClarifiedIntent().getOperationType())
-                && !planningSupport.isReadyForArtifactPlanning(mergedRequest, result.getClarifiedIntent(), result, findPropertyRequirements(mergedRequest),
-                        "Please use a shadow algorithm visible in the current Proxy and provide required properties.")) {
+        if (!isReadyForAlgorithmArtifactPlanning(mergedRequest, result)) {
             return workflowSessionContext.persist(result, WorkflowLifecycle.STEP_CLARIFYING, WorkflowLifecycle.STATUS_CLARIFYING);
         }
         boolean exists = !inspectionService.queryDefaultAlgorithm(queryFacade, mergedRequest.getDatabase()).isEmpty();
@@ -270,16 +264,26 @@ public final class ShadowWorkflowPlanningService {
     }
     
     private boolean ensureSupportedIdentifiers(final WorkflowContextSnapshot snapshot, final String... identifiers) {
-        for (String each : identifiers) {
-            if (!each.isEmpty() && !WorkflowSQLUtils.isSupportedIdentifier(each)) {
-                snapshot.getIssues().add(new WorkflowIssue(WorkflowIssueCode.UNSUPPORTED_IDENTIFIER, "error", "intaking",
-                        String.format("Identifier `%s` contains unsupported characters.", each),
-                        "Use reviewable logical identifiers without NUL or line terminators.", false, Map.of("identifier", each)));
-                snapshot.setStatus(WorkflowLifecycle.STATUS_FAILED);
-                return false;
-            }
+        if (planningSupport.ensureOptionalSupportedIdentifiers("", List.of(identifiers), snapshot, "intaking")) {
+            return true;
         }
-        return true;
+        snapshot.setStatus(WorkflowLifecycle.STATUS_FAILED);
+        return false;
+    }
+    
+    private void planAlgorithmsIfRequired(final MCPFeatureQueryFacade queryFacade, final WorkflowRequest request, final WorkflowContextSnapshot snapshot) {
+        if (!isDropWorkflow(snapshot.getClarifiedIntent()) && !request.getDatabase().isEmpty()) {
+            planAlgorithms(queryFacade, request, snapshot);
+        }
+    }
+    
+    private boolean isReadyForAlgorithmArtifactPlanning(final WorkflowRequest request, final WorkflowContextSnapshot snapshot) {
+        return isDropWorkflow(snapshot.getClarifiedIntent())
+                || planningSupport.isReadyForArtifactPlanning(request, snapshot.getClarifiedIntent(), snapshot, findPropertyRequirements(request), ALGORITHM_ARTIFACT_READY_MESSAGE);
+    }
+    
+    private boolean isDropWorkflow(final ClarifiedIntent clarifiedIntent) {
+        return WorkflowLifecycle.OPERATION_DROP.equalsIgnoreCase(clarifiedIntent.getOperationType());
     }
     
     private void addMissingInput(final Collection<String> missingInputs, final String value, final String fieldName) {
diff --git a/mcp/features/shadow/src/main/java/org/apache/shardingsphere/mcp/feature/shadow/tool/service/ShadowWorkflowValidationService.java b/mcp/features/shadow/src/main/java/org/apache/shardingsphere/mcp/feature/shadow/tool/service/ShadowWorkflowValidationService.java
index 21f392c44da..59da9445489 100644
--- a/mcp/features/shadow/src/main/java/org/apache/shardingsphere/mcp/feature/shadow/tool/service/ShadowWorkflowValidationService.java
+++ b/mcp/features/shadow/src/main/java/org/apache/shardingsphere/mcp/feature/shadow/tool/service/ShadowWorkflowValidationService.java
@@ -67,13 +67,7 @@ public final class ShadowWorkflowValidationService implements MCPWorkflowRuntime
     public Map<String, Object> validate(final WorkflowSessionContext workflowSessionContext, final MCPMetadataQueryFacade metadataQueryFacade,
                                         final MCPFeatureQueryFacade queryFacade, final MCPFeatureExecutionFacade executionFacade, final String sessionId,
                                         final WorkflowContextSnapshot snapshot) {
-        Map<String, Object> rejectedResponse = validationSupport.checkValidatePreconditions(sessionId, snapshot);
-        if (!rejectedResponse.isEmpty()) {
-            return rejectedResponse;
-        }
-        ValidationReport validationReport = createValidationReport(snapshot, queryFacade);
-        snapshot.setValidationReport(validationReport);
-        return validationSupport.finalizeValidation(workflowSessionContext, snapshot, validationReport);
+        return validationSupport.validateAndFinalize(workflowSessionContext, sessionId, snapshot, () -> createValidationReport(snapshot, queryFacade));
     }
     
     @Override
diff --git a/mcp/features/sharding/src/main/java/org/apache/shardingsphere/mcp/feature/sharding/tool/service/ShardingWorkflowValidationService.java b/mcp/features/sharding/src/main/java/org/apache/shardingsphere/mcp/feature/sharding/tool/service/ShardingWorkflowValidationService.java
index 0425f2441d0..80134654573 100644
--- a/mcp/features/sharding/src/main/java/org/apache/shardingsphere/mcp/feature/sharding/tool/service/ShardingWorkflowValidationService.java
+++ b/mcp/features/sharding/src/main/java/org/apache/shardingsphere/mcp/feature/sharding/tool/service/ShardingWorkflowValidationService.java
@@ -68,13 +68,7 @@ public final class ShardingWorkflowValidationService implements MCPWorkflowRunti
     public Map<String, Object> validate(final WorkflowSessionContext workflowSessionContext, final MCPMetadataQueryFacade metadataQueryFacade,
                                         final MCPFeatureQueryFacade queryFacade, final MCPFeatureExecutionFacade executionFacade, final String sessionId,
                                         final WorkflowContextSnapshot snapshot) {
-        Map<String, Object> rejectedResponse = validationSupport.checkValidatePreconditions(sessionId, snapshot);
-        if (!rejectedResponse.isEmpty()) {
-            return rejectedResponse;
-        }
-        ValidationReport validationReport = createValidationReport(snapshot, queryFacade);
-        snapshot.setValidationReport(validationReport);
-        return validationSupport.finalizeValidation(workflowSessionContext, snapshot, validationReport);
+        return validationSupport.validateAndFinalize(workflowSessionContext, sessionId, snapshot, () -> createValidationReport(snapshot, queryFacade));
     }
     
     @Override
diff --git a/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/workflow/service/WorkflowDistSQLQueryUtils.java b/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/workflow/service/WorkflowDistSQLQueryUtils.java
index dfb970321da..d0e6aceac32 100644
--- a/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/workflow/service/WorkflowDistSQLQueryUtils.java
+++ b/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/workflow/service/WorkflowDistSQLQueryUtils.java
@@ -18,9 +18,12 @@
 package org.apache.shardingsphere.mcp.support.workflow.service;
 
 import org.apache.shardingsphere.mcp.api.protocol.exception.MCPQueryFailedException;
+import org.apache.shardingsphere.mcp.support.database.spi.MCPFeatureQueryFacade;
 
 import java.sql.SQLSyntaxErrorException;
+import java.util.List;
 import java.util.Locale;
+import java.util.Map;
 import java.util.Objects;
 
 /**
@@ -41,6 +44,25 @@ public final class WorkflowDistSQLQueryUtils {
         return hasSyntaxErrorCause(ex) || hasUnsupportedDistSQLMessage(ex);
     }
     
+    /**
+     * Query DistSQL rule rows, returning an empty list when the current backend does not support the rule DistSQL syntax.
+     *
+     * @param queryFacade query facade
+     * @param databaseName database name
+     * @param sql DistSQL to execute
+     * @return queried rows
+     */
+    public static List<Map<String, Object>> queryRuleRows(final MCPFeatureQueryFacade queryFacade, final String databaseName, final String sql) {
+        try {
+            return queryFacade.query(databaseName, "", sql);
+        } catch (final MCPQueryFailedException ex) {
+            if (isUnsupportedDistSQLQueryFailure(ex)) {
+                return List.of();
+            }
+            throw ex;
+        }
+    }
+    
     private static boolean hasSyntaxErrorCause(final Throwable throwable) {
         Throwable current = throwable;
         while (null != current) {
diff --git a/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/workflow/service/WorkflowGuidanceResourceHintProvider.java b/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/workflow/service/WorkflowGuidanceResourceHintProvider.java
index be9bea56ff5..3e3541bce56 100644
--- a/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/workflow/service/WorkflowGuidanceResourceHintProvider.java
+++ b/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/workflow/service/WorkflowGuidanceResourceHintProvider.java
@@ -40,6 +40,104 @@ import java.util.Optional;
  */
 public final class WorkflowGuidanceResourceHintProvider {
     
+    private static final Map<String, List<ResourceHintTemplate>> FEATURE_RESOURCE_HINTS = Map.ofEntries(
+            Map.entry(WorkflowKindDescriptors.ENCRYPT_RULE, List.of(new ResourceHintTemplate("shardingsphere://features/encrypt/algorithms", "algorithm", "read_first",
+                    "Read encrypt algorithm metadata before choosing algorithm arguments."))),
+            Map.entry(WorkflowKindDescriptors.MASK_RULE, List.of(new ResourceHintTemplate("shardingsphere://features/mask/algorithms", "algorithm", "read_first",
+                    "Read mask algorithm metadata before choosing algorithm arguments."))),
+            Map.entry(WorkflowKindDescriptors.READWRITE_RULE,
+                    List.of(new ResourceHintTemplate("shardingsphere://features/readwrite-splitting/load-balance-algorithm-plugins", "algorithm", "read_first",
+                            "Read load-balance algorithm plugin metadata before choosing algorithm arguments."))),
+            Map.entry(WorkflowKindDescriptors.SHADOW_RULE, List.of(new ResourceHintTemplate("shardingsphere://features/shadow/algorithm-plugins", "algorithm", "read_first",
+                    "Read shadow algorithm plugin metadata before choosing algorithm arguments."))),
+            Map.entry(WorkflowKindDescriptors.SHADOW_DEFAULT_ALGORITHM,
+                    List.of(new ResourceHintTemplate("shardingsphere://features/shadow/algorithm-plugins", "algorithm", "read_first",
+                            "Read shadow algorithm plugin metadata before choosing algorithm arguments."))),
+            Map.entry(WorkflowKindDescriptors.SHARDING_TABLE_RULE,
+                    List.of(new ResourceHintTemplate("shardingsphere://features/sharding/algorithm-plugins", "algorithm", "read_first",
+                            "Read sharding algorithm plugin metadata before choosing algorithm arguments."))),
+            Map.entry(WorkflowKindDescriptors.SHARDING_DEFAULT_STRATEGY,
+                    List.of(new ResourceHintTemplate("shardingsphere://features/sharding/algorithm-plugins", "algorithm", "read_first",
+                            "Read sharding algorithm plugin metadata before choosing algorithm arguments."))),
+            Map.entry(WorkflowKindDescriptors.SHARDING_KEY_GENERATOR,
+                    List.of(new ResourceHintTemplate("shardingsphere://features/sharding/key-generate-algorithm-plugins", "algorithm", "read_first",
+                            "Read key-generate algorithm plugin metadata before choosing generator arguments."))),
+            Map.entry(WorkflowKindDescriptors.SHARDING_KEY_GENERATE_STRATEGY,
+                    List.of(new ResourceHintTemplate("shardingsphere://features/sharding/key-generate-algorithm-plugins", "algorithm", "read_first",
+                            "Read key-generate algorithm plugin metadata before choosing generator arguments."))));
+    
+    private static final Collection<String> STORAGE_UNIT_WORKFLOW_KINDS = List.of(
+            WorkflowKindDescriptors.READWRITE_RULE, WorkflowKindDescriptors.READWRITE_STATUS, WorkflowKindDescriptors.SHADOW_RULE, WorkflowKindDescriptors.SHARDING_TABLE_RULE);
+    
+    private static final Collection<String> SINGLE_TABLE_WORKFLOW_KINDS = List.of(WorkflowKindDescriptors.SHADOW_RULE, WorkflowKindDescriptors.SHARDING_TABLE_RULE);
+    
+    private static final Map<String, List<TableResourceHintTemplate>> FEATURE_TABLE_RULE_HINTS = Map.of(
+            WorkflowKindDescriptors.ENCRYPT_RULE, List.of(new TableResourceHintTemplate("shardingsphere://features/encrypt/databases/%s/tables/%s/rules",
+                    "Inspect current encrypt table rule DistSQL state before planning changes.")),
+            WorkflowKindDescriptors.MASK_RULE, List.of(new TableResourceHintTemplate("shardingsphere://features/mask/databases/%s/tables/%s/rules",
+                    "Inspect current mask table rule DistSQL state before planning changes.")),
+            WorkflowKindDescriptors.SHADOW_RULE, List.of(new TableResourceHintTemplate("shardingsphere://features/shadow/databases/%s/tables/%s/rules",
+                    "Inspect current shadow table rule DistSQL state before planning changes.")),
+            WorkflowKindDescriptors.SHARDING_TABLE_RULE, List.of(
+                    new TableResourceHintTemplate("shardingsphere://features/sharding/databases/%s/tables/%s/table-rule",
+                            "Inspect current sharding table rule DistSQL state before planning changes."),
+                    new TableResourceHintTemplate("shardingsphere://features/sharding/databases/%s/tables/%s/nodes",
+                            "Inspect current sharding table nodes before planning changes.")));
+    
+    private static final Map<String, List<DatabaseResourceHintTemplate>> RULE_RESOURCE_HINTS = Map.ofEntries(
+            Map.entry(WorkflowKindDescriptors.ENCRYPT_RULE, List.of(new DatabaseResourceHintTemplate("shardingsphere://features/encrypt/databases/%s/rules",
+                    "Inspect current encrypt rules before planning changes."))),
+            Map.entry(WorkflowKindDescriptors.MASK_RULE, List.of(new DatabaseResourceHintTemplate("shardingsphere://features/mask/databases/%s/rules",
+                    "Inspect current mask rules before planning changes."))),
+            Map.entry(WorkflowKindDescriptors.BROADCAST_RULE, List.of(new DatabaseResourceHintTemplate("shardingsphere://features/broadcast/databases/%s/rules",
+                    "Inspect current broadcast rules before planning changes."))),
+            Map.entry(WorkflowKindDescriptors.READWRITE_RULE,
+                    List.of(new DatabaseResourceHintTemplate("shardingsphere://features/readwrite-splitting/databases/%s/rules",
+                            "Inspect current readwrite-splitting rules before planning changes."))),
+            Map.entry(WorkflowKindDescriptors.READWRITE_STATUS,
+                    List.of(new DatabaseResourceHintTemplate("shardingsphere://features/readwrite-splitting/databases/%s/status",
+                            "Inspect current readwrite-splitting status before planning changes."))),
+            Map.entry(WorkflowKindDescriptors.SHADOW_RULE, List.of(new DatabaseResourceHintTemplate("shardingsphere://features/shadow/databases/%s/rules",
+                    "Inspect current shadow rules before planning changes."))),
+            Map.entry(WorkflowKindDescriptors.SHADOW_DEFAULT_ALGORITHM,
+                    List.of(new DatabaseResourceHintTemplate("shardingsphere://features/shadow/databases/%s/default-algorithm",
+                            "Inspect current default shadow algorithm before planning changes."))),
+            Map.entry(WorkflowKindDescriptors.SHADOW_ALGORITHM_CLEANUP, List.of(
+                    new DatabaseResourceHintTemplate("shardingsphere://features/shadow/databases/%s/algorithms", "Inspect configured shadow algorithms before planning cleanup."),
+                    new DatabaseResourceHintTemplate("shardingsphere://features/shadow/databases/%s/table-rules", "Inspect shadow table rule references before planning cleanup."),
+                    new DatabaseResourceHintTemplate("shardingsphere://features/shadow/databases/%s/default-algorithm",
+                            "Inspect default shadow algorithm references before planning cleanup."))),
+            Map.entry(WorkflowKindDescriptors.SHARDING_TABLE_RULE, List.of(
+                    new DatabaseResourceHintTemplate("shardingsphere://features/sharding/databases/%s/table-rules", "Inspect current sharding table rules before planning changes."),
+                    new DatabaseResourceHintTemplate("shardingsphere://features/sharding/databases/%s/table-nodes", "Inspect current sharding table nodes before planning changes."))),
+            Map.entry(WorkflowKindDescriptors.SHARDING_TABLE_REFERENCE,
+                    List.of(new DatabaseResourceHintTemplate("shardingsphere://features/sharding/databases/%s/table-reference-rules",
+                            "Inspect current sharding table reference rules before planning changes."))),
+            Map.entry(WorkflowKindDescriptors.SHARDING_DEFAULT_STRATEGY, List.of(
+                    new DatabaseResourceHintTemplate("shardingsphere://features/sharding/databases/%s/default-strategy",
+                            "Inspect current default sharding strategy before planning changes."),
+                    new DatabaseResourceHintTemplate("shardingsphere://features/sharding/databases/%s/algorithms",
+                            "Inspect configured sharding algorithms before planning default strategy changes."))),
+            Map.entry(WorkflowKindDescriptors.SHARDING_KEY_GENERATOR,
+                    List.of(new DatabaseResourceHintTemplate("shardingsphere://features/sharding/databases/%s/key-generators",
+                            "Inspect current sharding key generators before planning changes."))),
+            Map.entry(WorkflowKindDescriptors.SHARDING_KEY_GENERATE_STRATEGY, List.of(
+                    new DatabaseResourceHintTemplate("shardingsphere://features/sharding/databases/%s/key-generate-strategies",
+                            "Inspect current sharding key generate strategies before planning changes."),
+                    new DatabaseResourceHintTemplate("shardingsphere://features/sharding/databases/%s/key-generators",
+                            "Inspect current sharding key generators before planning key generate strategy changes."))),
+            Map.entry(WorkflowKindDescriptors.SHARDING_COMPONENT_CLEANUP, List.of(
+                    new DatabaseResourceHintTemplate("shardingsphere://features/sharding/databases/%s/algorithms", "Inspect configured sharding algorithms before planning cleanup."),
+                    new DatabaseResourceHintTemplate("shardingsphere://features/sharding/databases/%s/key-generators",
+                            "Inspect configured sharding key generators before planning cleanup."),
+                    new DatabaseResourceHintTemplate("shardingsphere://features/sharding/databases/%s/auditors", "Inspect configured sharding auditors before planning cleanup."),
+                    new DatabaseResourceHintTemplate("shardingsphere://features/sharding/databases/%s/unused-algorithms",
+                            "Inspect unused sharding algorithms before planning cleanup."),
+                    new DatabaseResourceHintTemplate("shardingsphere://features/sharding/databases/%s/unused-key-generators",
+                            "Inspect unused sharding key generators before planning cleanup."),
+                    new DatabaseResourceHintTemplate("shardingsphere://features/sharding/databases/%s/unused-auditors",
+                            "Inspect unused sharding auditors before planning cleanup."))));
+    
     /**
      * Create resource hints for a workflow planning response.
      *
@@ -64,123 +162,33 @@ public final class WorkflowGuidanceResourceHintProvider {
     }
     
     private void addFeatureResources(final Collection<Map<String, Object>> resourcesToRead, final WorkflowContextSnapshot snapshot) {
-        switch (resolveWorkflowKind(snapshot)) {
-            case WorkflowKindDescriptors.ENCRYPT_RULE:
-                addResourceHint(resourcesToRead, "shardingsphere://features/encrypt/algorithms", "algorithm", "read_first",
-                        "Read encrypt algorithm metadata before choosing algorithm arguments.");
-                break;
-            case WorkflowKindDescriptors.MASK_RULE:
-                addResourceHint(resourcesToRead, "shardingsphere://features/mask/algorithms", "algorithm", "read_first",
-                        "Read mask algorithm metadata before choosing algorithm arguments.");
-                break;
-            case WorkflowKindDescriptors.READWRITE_RULE:
-                addResourceHint(resourcesToRead, "shardingsphere://features/readwrite-splitting/load-balance-algorithm-plugins", "algorithm", "read_first",
-                        "Read load-balance algorithm plugin metadata before choosing algorithm arguments.");
-                break;
-            case WorkflowKindDescriptors.SHADOW_RULE:
-            case WorkflowKindDescriptors.SHADOW_DEFAULT_ALGORITHM:
-                addResourceHint(resourcesToRead, "shardingsphere://features/shadow/algorithm-plugins", "algorithm", "read_first",
-                        "Read shadow algorithm plugin metadata before choosing algorithm arguments.");
-                break;
-            case WorkflowKindDescriptors.SHARDING_TABLE_RULE:
-            case WorkflowKindDescriptors.SHARDING_DEFAULT_STRATEGY:
-                addResourceHint(resourcesToRead, "shardingsphere://features/sharding/algorithm-plugins", "algorithm", "read_first",
-                        "Read sharding algorithm plugin metadata before choosing algorithm arguments.");
-                break;
-            case WorkflowKindDescriptors.SHARDING_KEY_GENERATOR:
-            case WorkflowKindDescriptors.SHARDING_KEY_GENERATE_STRATEGY:
-                addResourceHint(resourcesToRead, "shardingsphere://features/sharding/key-generate-algorithm-plugins", "algorithm", "read_first",
-                        "Read key-generate algorithm plugin metadata before choosing generator arguments.");
-                break;
-            default:
-                break;
+        for (ResourceHintTemplate each : FEATURE_RESOURCE_HINTS.getOrDefault(resolveWorkflowKind(snapshot), List.of())) {
+            addResourceHint(resourcesToRead, each.uri(), each.resourceKind(), each.action(), each.reason());
         }
     }
     
     private void addGovernanceMetadataResources(final Collection<Map<String, Object>> resourcesToRead, final WorkflowContextSnapshot snapshot, final WorkflowRequest request) {
         String workflowKind = resolveWorkflowKind(snapshot);
-        switch (workflowKind) {
-            case WorkflowKindDescriptors.READWRITE_RULE:
-            case WorkflowKindDescriptors.READWRITE_STATUS:
-            case WorkflowKindDescriptors.SHADOW_RULE:
-            case WorkflowKindDescriptors.SHARDING_TABLE_RULE:
-                addStorageUnitsResourceHint(resourcesToRead, request);
-                break;
-            default:
-                break;
+        if (STORAGE_UNIT_WORKFLOW_KINDS.contains(workflowKind)) {
+            addStorageUnitsResourceHint(resourcesToRead, request);
         }
-        switch (workflowKind) {
-            case WorkflowKindDescriptors.SHADOW_RULE:
-            case WorkflowKindDescriptors.SHARDING_TABLE_RULE:
-                addSingleTablesResourceHint(resourcesToRead, request);
-                if (!request.getTable().isEmpty()) {
-                    addSingleTableResourceHint(resourcesToRead, request);
-                }
-                break;
-            default:
-                break;
+        if (SINGLE_TABLE_WORKFLOW_KINDS.contains(workflowKind)) {
+            addSingleTablesResourceHint(resourcesToRead, request);
+            if (!request.getTable().isEmpty()) {
+                addSingleTableResourceHint(resourcesToRead, request);
+            }
         }
     }
     
     private void addFeatureTableRuleResources(final Collection<Map<String, Object>> resourcesToRead, final WorkflowContextSnapshot snapshot, final WorkflowRequest request) {
-        switch (resolveWorkflowKind(snapshot)) {
-            case WorkflowKindDescriptors.ENCRYPT_RULE:
-                addTableResourceHint(resourcesToRead, request, "shardingsphere://features/encrypt/databases/%s/tables/%s/rules",
-                        "Inspect current encrypt table rule DistSQL state before planning changes.");
-                break;
-            case WorkflowKindDescriptors.MASK_RULE:
-                addTableResourceHint(resourcesToRead, request, "shardingsphere://features/mask/databases/%s/tables/%s/rules",
-                        "Inspect current mask table rule DistSQL state before planning changes.");
-                break;
-            case WorkflowKindDescriptors.SHADOW_RULE:
-                addTableResourceHint(resourcesToRead, request, "shardingsphere://features/shadow/databases/%s/tables/%s/rules",
-                        "Inspect current shadow table rule DistSQL state before planning changes.");
-                break;
-            case WorkflowKindDescriptors.SHARDING_TABLE_RULE:
-                addTableResourceHint(resourcesToRead, request, "shardingsphere://features/sharding/databases/%s/tables/%s/table-rule",
-                        "Inspect current sharding table rule DistSQL state before planning changes.");
-                addTableResourceHint(resourcesToRead, request, "shardingsphere://features/sharding/databases/%s/tables/%s/nodes",
-                        "Inspect current sharding table nodes before planning changes.");
-                break;
-            default:
-                break;
+        for (TableResourceHintTemplate each : FEATURE_TABLE_RULE_HINTS.getOrDefault(resolveWorkflowKind(snapshot), List.of())) {
+            addTableResourceHint(resourcesToRead, request, each.uriTemplate(), each.reason());
         }
     }
     
     private void addRuleResources(final Collection<Map<String, Object>> resourcesToRead, final WorkflowContextSnapshot snapshot, final WorkflowRequest request) {
-        switch (resolveWorkflowKind(snapshot)) {
-            case WorkflowKindDescriptors.ENCRYPT_RULE -> addDatabaseResourceHint(resourcesToRead, request, "shardingsphere://features/encrypt/databases/%s/rules",
-                    "Inspect current encrypt rules before planning changes.");
-            case WorkflowKindDescriptors.MASK_RULE -> addDatabaseResourceHint(resourcesToRead, request, "shardingsphere://features/mask/databases/%s/rules",
-                    "Inspect current mask rules before planning changes.");
-            case WorkflowKindDescriptors.BROADCAST_RULE -> addDatabaseResourceHint(resourcesToRead, request, "shardingsphere://features/broadcast/databases/%s/rules",
-                    "Inspect current broadcast rules before planning changes.");
-            case WorkflowKindDescriptors.READWRITE_RULE -> addDatabaseResourceHint(resourcesToRead, request, "shardingsphere://features/readwrite-splitting/databases/%s/rules",
-                    "Inspect current readwrite-splitting rules before planning changes.");
-            case WorkflowKindDescriptors.READWRITE_STATUS -> addDatabaseResourceHint(resourcesToRead, request, "shardingsphere://features/readwrite-splitting/databases/%s/status",
-                    "Inspect current readwrite-splitting status before planning changes.");
-            case WorkflowKindDescriptors.SHADOW_RULE -> addDatabaseResourceHint(resourcesToRead, request, "shardingsphere://features/shadow/databases/%s/rules",
-                    "Inspect current shadow rules before planning changes.");
-            case WorkflowKindDescriptors.SHADOW_DEFAULT_ALGORITHM -> addDatabaseResourceHint(resourcesToRead, request, "shardingsphere://features/shadow/databases/%s/default-algorithm",
-                    "Inspect current default shadow algorithm before planning changes.");
-            case WorkflowKindDescriptors.SHADOW_ALGORITHM_CLEANUP -> {
-                addDatabaseResourceHint(resourcesToRead, request, "shardingsphere://features/shadow/databases/%s/algorithms",
-                        "Inspect configured shadow algorithms before planning cleanup.");
-                addDatabaseResourceHint(resourcesToRead, request, "shardingsphere://features/shadow/databases/%s/table-rules",
-                        "Inspect shadow table rule references before planning cleanup.");
-                addDatabaseResourceHint(resourcesToRead, request, "shardingsphere://features/shadow/databases/%s/default-algorithm",
-                        "Inspect default shadow algorithm references before planning cleanup.");
-            }
-            case WorkflowKindDescriptors.SHARDING_TABLE_RULE -> addShardingTableRuleResources(resourcesToRead, request);
-            case WorkflowKindDescriptors.SHARDING_TABLE_REFERENCE -> addDatabaseResourceHint(resourcesToRead, request, "shardingsphere://features/sharding/databases/%s/table-reference-rules",
-                    "Inspect current sharding table reference rules before planning changes.");
-            case WorkflowKindDescriptors.SHARDING_DEFAULT_STRATEGY -> addShardingDefaultStrategyResources(resourcesToRead, request);
-            case WorkflowKindDescriptors.SHARDING_KEY_GENERATOR -> addDatabaseResourceHint(resourcesToRead, request, "shardingsphere://features/sharding/databases/%s/key-generators",
-                    "Inspect current sharding key generators before planning changes.");
-            case WorkflowKindDescriptors.SHARDING_KEY_GENERATE_STRATEGY -> addShardingKeyGenerateStrategyResources(resourcesToRead, request);
-            case WorkflowKindDescriptors.SHARDING_COMPONENT_CLEANUP -> addShardingComponentCleanupResources(resourcesToRead, request);
-            default -> {
-            }
+        for (DatabaseResourceHintTemplate each : RULE_RESOURCE_HINTS.getOrDefault(resolveWorkflowKind(snapshot), List.of())) {
+            addDatabaseResourceHint(resourcesToRead, request, each.uriTemplate(), each.reason());
         }
     }
     
@@ -260,42 +268,6 @@ public final class WorkflowGuidanceResourceHintProvider {
         resourcesToRead.add(MCPResourceHintUtils.create(uri, resourceKind, action, reason, MCPPayloadFieldNames.RESOURCES_TO_READ));
     }
     
-    private void addShardingTableRuleResources(final Collection<Map<String, Object>> resourcesToRead, final WorkflowRequest request) {
-        addDatabaseResourceHint(resourcesToRead, request, "shardingsphere://features/sharding/databases/%s/table-rules",
-                "Inspect current sharding table rules before planning changes.");
-        addDatabaseResourceHint(resourcesToRead, request, "shardingsphere://features/sharding/databases/%s/table-nodes",
-                "Inspect current sharding table nodes before planning changes.");
-    }
-    
-    private void addShardingDefaultStrategyResources(final Collection<Map<String, Object>> resourcesToRead, final WorkflowRequest request) {
-        addDatabaseResourceHint(resourcesToRead, request, "shardingsphere://features/sharding/databases/%s/default-strategy",
-                "Inspect current default sharding strategy before planning changes.");
-        addDatabaseResourceHint(resourcesToRead, request, "shardingsphere://features/sharding/databases/%s/algorithms",
-                "Inspect configured sharding algorithms before planning default strategy changes.");
-    }
-    
-    private void addShardingKeyGenerateStrategyResources(final Collection<Map<String, Object>> resourcesToRead, final WorkflowRequest request) {
-        addDatabaseResourceHint(resourcesToRead, request, "shardingsphere://features/sharding/databases/%s/key-generate-strategies",
-                "Inspect current sharding key generate strategies before planning changes.");
-        addDatabaseResourceHint(resourcesToRead, request, "shardingsphere://features/sharding/databases/%s/key-generators",
-                "Inspect current sharding key generators before planning key generate strategy changes.");
-    }
-    
-    private void addShardingComponentCleanupResources(final Collection<Map<String, Object>> resourcesToRead, final WorkflowRequest request) {
-        addDatabaseResourceHint(resourcesToRead, request, "shardingsphere://features/sharding/databases/%s/algorithms",
-                "Inspect configured sharding algorithms before planning cleanup.");
-        addDatabaseResourceHint(resourcesToRead, request, "shardingsphere://features/sharding/databases/%s/key-generators",
-                "Inspect configured sharding key generators before planning cleanup.");
-        addDatabaseResourceHint(resourcesToRead, request, "shardingsphere://features/sharding/databases/%s/auditors",
-                "Inspect configured sharding auditors before planning cleanup.");
-        addDatabaseResourceHint(resourcesToRead, request, "shardingsphere://features/sharding/databases/%s/unused-algorithms",
-                "Inspect unused sharding algorithms before planning cleanup.");
-        addDatabaseResourceHint(resourcesToRead, request, "shardingsphere://features/sharding/databases/%s/unused-key-generators",
-                "Inspect unused sharding key generators before planning cleanup.");
-        addDatabaseResourceHint(resourcesToRead, request, "shardingsphere://features/sharding/databases/%s/unused-auditors",
-                "Inspect unused sharding auditors before planning cleanup.");
-    }
-    
     private void addTableResources(final Collection<Map<String, Object>> resourcesToRead, final WorkflowRequest request) {
         resourcesToRead.add(MCPResourceHintUtils.create(String.format("shardingsphere://databases/%s/schemas/%s/tables/%s/columns", MCPUriPathSegmentUtils.encodePathSegment(request.getDatabase()),
                 MCPUriPathSegmentUtils.encodePathSegment(request.getSchema()), MCPUriPathSegmentUtils.encodePathSegment(request.getTable())),
@@ -306,4 +278,13 @@ public final class WorkflowGuidanceResourceHintProvider {
         WorkflowKind workflowKind = snapshot.getWorkflowKind();
         return null == workflowKind ? "" : workflowKind.getValue();
     }
+    
+    private record ResourceHintTemplate(String uri, String resourceKind, String action, String reason) {
+    }
+    
+    private record DatabaseResourceHintTemplate(String uriTemplate, String reason) {
+    }
+    
+    private record TableResourceHintTemplate(String uriTemplate, String reason) {
+    }
 }
diff --git a/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/workflow/service/WorkflowIntentResolverSupport.java b/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/workflow/service/WorkflowIntentResolverSupport.java
index fa80cbd2f8a..e29ea134928 100644
--- a/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/workflow/service/WorkflowIntentResolverSupport.java
+++ b/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/workflow/service/WorkflowIntentResolverSupport.java
@@ -24,6 +24,8 @@ import org.apache.shardingsphere.mcp.support.workflow.model.WorkflowFieldNames;
 import org.apache.shardingsphere.mcp.support.workflow.model.WorkflowLifecycle;
 import org.apache.shardingsphere.mcp.support.workflow.model.WorkflowRequest;
 
+import java.util.Collection;
+import java.util.List;
 import java.util.Locale;
 
 /**
@@ -34,6 +36,17 @@ public final class WorkflowIntentResolverSupport {
     
     private static final String EXECUTION_MODE_MANUAL_ONLY = "manual-only";
     
+    private static final List<FieldSemanticRule> FIELD_SEMANTIC_RULES = List.of(
+            new FieldSemanticRule("phone", List.of("phone number", "phone", "mobile", "tel", "手机号", "手机", "电话号码", "电话"), List.of("phone", "mobile", "tel")),
+            new FieldSemanticRule("id_card", List.of("identity card", "id card", "身份证", "证件"), List.of("id_card")),
+            new FieldSemanticRule("email", List.of("email", "邮箱", "邮件"), List.of("email")),
+            new FieldSemanticRule("bank_card", List.of("bank card", "银行卡", "银行卡号", "卡号"), List.of("bank_card", "card_no", "card_number")),
+            new FieldSemanticRule("passport", List.of("passport", "护照"), List.of("passport")),
+            new FieldSemanticRule("license_plate", List.of("license plate", "车牌", "车牌号"), List.of("license_plate", "plate_no", "plate_number")),
+            new FieldSemanticRule("birth_date", List.of("birth date", "birthday", "出生日期", "生日"), List.of("birth_date", "birthday", "date_of_birth")),
+            new FieldSemanticRule("address", List.of("address", "地址", "住址"), List.of("address", "addr")),
+            new FieldSemanticRule("name", List.of("real name", "full name", "姓名", "名字"), List.of("real_name", "full_name", "name")));
+    
     /**
      * Resolve workflow operation type from explicit fields and heuristics.
      *
@@ -90,37 +103,10 @@ public final class WorkflowIntentResolverSupport {
         }
         String naturalLanguageIntent = getNaturalLanguageIntent(request);
         String columnName = request.getColumn().toLowerCase(Locale.ENGLISH);
-        if (containsAny(naturalLanguageIntent, "phone number", "phone", "mobile", "tel", "手机号", "手机", "电话号码", "电话")
-                || containsAny(columnName, "phone", "mobile", "tel")) {
-            return recordInferredValue(clarifiedIntent, WorkflowFieldNames.FIELD_SEMANTICS, "phone");
-        }
-        if (containsAny(naturalLanguageIntent, "identity card", "id card", "身份证", "证件") || columnName.contains("id_card")) {
-            return recordInferredValue(clarifiedIntent, WorkflowFieldNames.FIELD_SEMANTICS, "id_card");
-        }
-        if (containsAny(naturalLanguageIntent, "email", "邮箱", "邮件") || columnName.contains("email")) {
-            return recordInferredValue(clarifiedIntent, WorkflowFieldNames.FIELD_SEMANTICS, "email");
-        }
-        if (containsAny(naturalLanguageIntent, "bank card", "银行卡", "银行卡号", "卡号")
-                || containsAny(columnName, "bank_card", "card_no", "card_number")) {
-            return recordInferredValue(clarifiedIntent, WorkflowFieldNames.FIELD_SEMANTICS, "bank_card");
-        }
-        if (containsAny(naturalLanguageIntent, "passport", "护照") || columnName.contains("passport")) {
-            return recordInferredValue(clarifiedIntent, WorkflowFieldNames.FIELD_SEMANTICS, "passport");
-        }
-        if (containsAny(naturalLanguageIntent, "license plate", "车牌", "车牌号")
-                || containsAny(columnName, "license_plate", "plate_no", "plate_number")) {
-            return recordInferredValue(clarifiedIntent, WorkflowFieldNames.FIELD_SEMANTICS, "license_plate");
-        }
-        if (containsAny(naturalLanguageIntent, "birth date", "birthday", "出生日期", "生日")
-                || containsAny(columnName, "birth_date", "birthday", "date_of_birth")) {
-            return recordInferredValue(clarifiedIntent, WorkflowFieldNames.FIELD_SEMANTICS, "birth_date");
-        }
-        if (containsAny(naturalLanguageIntent, "address", "地址", "住址") || containsAny(columnName, "address", "addr")) {
-            return recordInferredValue(clarifiedIntent, WorkflowFieldNames.FIELD_SEMANTICS, "address");
-        }
-        if (containsAny(naturalLanguageIntent, "real name", "full name", "姓名", "名字")
-                || containsAny(columnName, "real_name", "full_name", "name")) {
-            return recordInferredValue(clarifiedIntent, WorkflowFieldNames.FIELD_SEMANTICS, "name");
+        for (FieldSemanticRule each : FIELD_SEMANTIC_RULES) {
+            if (each.matches(naturalLanguageIntent, columnName)) {
+                return recordInferredValue(clarifiedIntent, WorkflowFieldNames.FIELD_SEMANTICS, each.semantics());
+            }
         }
         return recordInferredValue(clarifiedIntent, WorkflowFieldNames.FIELD_SEMANTICS, columnName);
     }
@@ -170,10 +156,26 @@ public final class WorkflowIntentResolverSupport {
         return false;
     }
     
+    private static boolean containsAny(final String value, final Collection<String> candidates) {
+        for (String each : candidates) {
+            if (value.contains(each)) {
+                return true;
+            }
+        }
+        return false;
+    }
+    
     private static String recordInferredValue(final ClarifiedIntent clarifiedIntent, final String fieldName, final String value) {
         if (null != clarifiedIntent) {
             clarifiedIntent.getInferredValues().put(fieldName, value);
         }
         return value;
     }
+    
+    private record FieldSemanticRule(String semantics, Collection<String> intentKeywords, Collection<String> columnKeywords) {
+        
+        private boolean matches(final String naturalLanguageIntent, final String columnName) {
+            return containsAny(naturalLanguageIntent, intentKeywords) || containsAny(columnName, columnKeywords);
+        }
+    }
 }
diff --git a/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/workflow/service/WorkflowPlanningSupport.java b/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/workflow/service/WorkflowPlanningSupport.java
index 6118cc8abb1..d6d2af27db4 100644
--- a/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/workflow/service/WorkflowPlanningSupport.java
+++ b/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/workflow/service/WorkflowPlanningSupport.java
@@ -32,6 +32,7 @@ import org.apache.shardingsphere.mcp.support.workflow.model.WorkflowLifecycle;
 import org.apache.shardingsphere.mcp.support.workflow.model.WorkflowKind;
 import org.apache.shardingsphere.mcp.support.workflow.model.WorkflowRequest;
 
+import java.util.Collection;
 import java.util.LinkedList;
 import java.util.List;
 import java.util.Locale;
@@ -170,6 +171,43 @@ public final class WorkflowPlanningSupport {
         return collectPropertyRequirements(request, clarifiedIntent, snapshot, propertyRequirements);
     }
     
+    /**
+     * Ensure workflow identifiers can be rendered into reviewable DistSQL.
+     *
+     * @param fieldName field name for issue details
+     * @param identifiers identifiers to check
+     * @param snapshot workflow snapshot
+     * @param issueStage issue stage
+     * @return whether all identifiers are supported
+     */
+    public boolean ensureSupportedIdentifiers(final String fieldName, final Collection<String> identifiers, final WorkflowContextSnapshot snapshot,
+                                              final String issueStage) {
+        return ensureIdentifiers(fieldName, identifiers, snapshot, issueStage, false);
+    }
+    
+    /**
+     * Ensure optional workflow identifiers can be rendered into reviewable DistSQL when present.
+     *
+     * @param fieldName field name for issue details
+     * @param identifiers identifiers to check
+     * @param snapshot workflow snapshot
+     * @param issueStage issue stage
+     * @return whether all present identifiers are supported
+     */
+    public boolean ensureOptionalSupportedIdentifiers(final String fieldName, final Collection<String> identifiers, final WorkflowContextSnapshot snapshot,
+                                                      final String issueStage) {
+        return ensureIdentifiers(fieldName, identifiers, snapshot, issueStage, true);
+    }
+    
+    private boolean ensureIdentifiers(final String fieldName, final Collection<String> identifiers, final WorkflowContextSnapshot snapshot, final String issueStage, final boolean allowEmpty) {
+        for (String each : identifiers) {
+            if (!ensureSupportedIdentifier(fieldName, each, snapshot, issueStage, allowEmpty)) {
+                return false;
+            }
+        }
+        return true;
+    }
+    
     /**
      * Ensure workflow planning context is complete and valid.
      *
@@ -188,16 +226,16 @@ public final class WorkflowPlanningSupport {
             snapshot.setStatus(WorkflowLifecycle.STATUS_CLARIFYING);
             return false;
         }
-        if (!ensureSupportedIdentifier(WorkflowFieldNames.DATABASE, request.getDatabase(), snapshot)
-                || !ensureSupportedIdentifier(WorkflowFieldNames.TABLE, request.getTable(), snapshot)
-                || !ensureSupportedIdentifier(WorkflowFieldNames.COLUMN, request.getColumn(), snapshot)) {
+        if (!ensureSupportedIdentifiers(WorkflowFieldNames.DATABASE, List.of(request.getDatabase()), snapshot, "discovering")
+                || !ensureSupportedIdentifiers(WorkflowFieldNames.TABLE, List.of(request.getTable()), snapshot, "discovering")
+                || !ensureSupportedIdentifiers(WorkflowFieldNames.COLUMN, List.of(request.getColumn()), snapshot, "discovering")) {
             snapshot.setStatus(WorkflowLifecycle.STATUS_FAILED);
             return false;
         }
         Optional<MCPDatabaseMetadata> databaseMetadata = metadataQueryFacade.queryDatabase(WorkflowSQLUtils.normalizeIdentifier(request.getDatabase()));
         String databaseType = databaseMetadata.map(MCPDatabaseMetadata::getDatabaseType).orElse("");
         request.setSchema(resolveSchema(databaseMetadata, request, clarifiedIntent, databaseType));
-        if (!ensureSupportedIdentifier(WorkflowFieldNames.SCHEMA, request.getSchema(), snapshot)) {
+        if (!ensureSupportedIdentifiers(WorkflowFieldNames.SCHEMA, List.of(request.getSchema()), snapshot, "discovering")) {
             snapshot.setStatus(WorkflowLifecycle.STATUS_FAILED);
             return false;
         }
@@ -241,16 +279,31 @@ public final class WorkflowPlanningSupport {
         }
     }
     
-    private boolean ensureSupportedIdentifier(final String fieldName, final String identifier, final WorkflowContextSnapshot snapshot) {
-        if (WorkflowSQLUtils.isSupportedIdentifier(identifier)) {
+    private boolean ensureSupportedIdentifier(final String fieldName, final String identifier, final WorkflowContextSnapshot snapshot, final String issueStage, final boolean allowEmpty) {
+        if (allowEmpty && identifier.isEmpty() || WorkflowSQLUtils.isSupportedIdentifier(identifier)) {
             return true;
         }
-        snapshot.getIssues().add(new WorkflowIssue(WorkflowIssueCode.UNSUPPORTED_IDENTIFIER, "error", "discovering",
-                String.format("%s identifier `%s` contains unsupported characters.", fieldName, identifier),
-                "Use a reviewable logical identifier without NUL or line terminators.", false, Map.of("field", fieldName, "identifier", identifier)));
+        snapshot.getIssues().add(new WorkflowIssue(WorkflowIssueCode.UNSUPPORTED_IDENTIFIER, "error", issueStage, createUnsupportedIdentifierMessage(fieldName, identifier),
+                createUnsupportedIdentifierAction(fieldName), false, createUnsupportedIdentifierDetails(fieldName, identifier)));
         return false;
     }
     
+    private String createUnsupportedIdentifierMessage(final String fieldName, final String identifier) {
+        return fieldName.isEmpty()
+                ? String.format("Identifier `%s` contains unsupported characters.", identifier)
+                : String.format("%s identifier `%s` contains unsupported characters.", fieldName, identifier);
+    }
+    
+    private String createUnsupportedIdentifierAction(final String fieldName) {
+        return fieldName.isEmpty()
+                ? "Use reviewable logical identifiers without NUL or line terminators."
+                : "Use a reviewable logical identifier without NUL or line terminators.";
+    }
+    
+    private Map<String, Object> createUnsupportedIdentifierDetails(final String fieldName, final String identifier) {
+        return fieldName.isEmpty() ? Map.of("identifier", identifier) : Map.of("field", fieldName, "identifier", identifier);
+    }
+    
     private String resolveSchema(final Optional<MCPDatabaseMetadata> databaseMetadata, final WorkflowRequest request, final ClarifiedIntent clarifiedIntent, final String databaseType) {
         String actualSchema = request.getSchema();
         if (!isEmptyIdentifier(actualSchema)) {
diff --git a/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/workflow/service/WorkflowValidationSupport.java b/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/workflow/service/WorkflowValidationSupport.java
index d43b54bffdf..1b46bed5434 100644
--- a/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/workflow/service/WorkflowValidationSupport.java
+++ b/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/workflow/service/WorkflowValidationSupport.java
@@ -32,6 +32,7 @@ import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
+import java.util.function.Supplier;
 
 /**
  * Workflow validation support.
@@ -58,6 +59,26 @@ public final class WorkflowValidationSupport {
         return Map.of();
     }
     
+    /**
+     * Validate workflow snapshot and persist validation response.
+     *
+     * @param workflowSessionContext workflow session context
+     * @param sessionId session identifier
+     * @param snapshot workflow snapshot
+     * @param validationReportSupplier validation report supplier
+     * @return validation response
+     */
+    public Map<String, Object> validateAndFinalize(final WorkflowSessionContext workflowSessionContext, final String sessionId, final WorkflowContextSnapshot snapshot,
+                                                   final Supplier<ValidationReport> validationReportSupplier) {
+        Map<String, Object> rejectedResponse = checkValidatePreconditions(sessionId, snapshot);
+        if (!rejectedResponse.isEmpty()) {
+            return rejectedResponse;
+        }
+        ValidationReport validationReport = validationReportSupplier.get();
+        snapshot.setValidationReport(validationReport);
+        return finalizeValidation(workflowSessionContext, snapshot, validationReport);
+    }
+    
     /**
      * Resolve overall validation status from validation sections.
      *
diff --git a/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/workflow/service/WorkflowDistSQLQueryUtilsTest.java b/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/workflow/service/WorkflowDistSQLQueryUtilsTest.java
index ec2809e8f6d..db3b44c10ab 100644
--- a/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/workflow/service/WorkflowDistSQLQueryUtilsTest.java
+++ b/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/workflow/service/WorkflowDistSQLQueryUtilsTest.java
@@ -18,13 +18,22 @@
 package org.apache.shardingsphere.mcp.support.workflow.service;
 
 import org.apache.shardingsphere.mcp.api.protocol.exception.MCPQueryFailedException;
+import org.apache.shardingsphere.mcp.support.database.spi.MCPFeatureQueryFacade;
 import org.junit.jupiter.api.Test;
 
 import java.sql.SQLException;
 import java.sql.SQLSyntaxErrorException;
+import java.util.List;
+import java.util.Map;
 
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.sameInstance;
 import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertThrows;
 import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
 
 class WorkflowDistSQLQueryUtilsTest {
     
@@ -46,4 +55,28 @@ class WorkflowDistSQLQueryUtilsTest {
         MCPQueryFailedException actualException = new MCPQueryFailedException("Connection refused.", new SQLException("Connection refused."));
         assertFalse(WorkflowDistSQLQueryUtils.isUnsupportedDistSQLQueryFailure(actualException));
     }
+    
+    @Test
+    void assertQueryRuleRows() {
+        MCPFeatureQueryFacade queryFacade = mock(MCPFeatureQueryFacade.class);
+        List<Map<String, Object>> expected = List.of(Map.of("name", "orders"));
+        when(queryFacade.query("logic_db", "", "SHOW MASK RULES")).thenReturn(expected);
+        assertThat(WorkflowDistSQLQueryUtils.queryRuleRows(queryFacade, "logic_db", "SHOW MASK RULES"), is(expected));
+    }
+    
+    @Test
+    void assertQueryRuleRowsWithUnsupportedDistSQL() {
+        MCPFeatureQueryFacade queryFacade = mock(MCPFeatureQueryFacade.class);
+        when(queryFacade.query("logic_db", "", "SHOW MASK RULES")).thenThrow(new MCPQueryFailedException("DistSQL syntax is unsupported by this runtime backend."));
+        assertTrue(WorkflowDistSQLQueryUtils.queryRuleRows(queryFacade, "logic_db", "SHOW MASK RULES").isEmpty());
+    }
+    
+    @Test
+    void assertQueryRuleRowsWithConnectionFailure() {
+        MCPFeatureQueryFacade queryFacade = mock(MCPFeatureQueryFacade.class);
+        MCPQueryFailedException expected = new MCPQueryFailedException("Connection refused.", new SQLException("Connection refused."));
+        when(queryFacade.query("logic_db", "", "SHOW MASK RULES")).thenThrow(expected);
+        MCPQueryFailedException actual = assertThrows(MCPQueryFailedException.class, () -> WorkflowDistSQLQueryUtils.queryRuleRows(queryFacade, "logic_db", "SHOW MASK RULES"));
+        assertThat(actual, sameInstance(expected));
+    }
 }
diff --git a/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/workflow/service/WorkflowPlanningSupportTest.java b/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/workflow/service/WorkflowPlanningSupportTest.java
index bee145b6509..d971ea1a162 100644
--- a/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/workflow/service/WorkflowPlanningSupportTest.java
+++ b/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/workflow/service/WorkflowPlanningSupportTest.java
@@ -228,6 +228,24 @@ class WorkflowPlanningSupportTest {
         assertThat(clarifiedIntent.getInferredValues().get("execution_mode"), is("manual-only"));
     }
     
+    @Test
+    void assertEnsureOptionalSupportedIdentifiersAllowsEmptyIdentifier() {
+        WorkflowContextSnapshot snapshot = new WorkflowContextSnapshot();
+        boolean actual = planningSupport.ensureOptionalSupportedIdentifiers("rule", List.of(""), snapshot, "intaking");
+        assertTrue(actual);
+        assertTrue(snapshot.getIssues().isEmpty());
+    }
+    
+    @Test
+    void assertEnsureSupportedIdentifiersRejectsUnsupportedIdentifier() {
+        WorkflowContextSnapshot snapshot = new WorkflowContextSnapshot();
+        boolean actual = planningSupport.ensureSupportedIdentifiers("", List.of("orders\ndrop"), snapshot, "intaking");
+        assertFalse(actual);
+        assertThat(snapshot.getIssues().getFirst().getMessage(), is("Identifier `orders\ndrop` contains unsupported characters."));
+        assertThat(snapshot.getIssues().getFirst().getUserAction(), is("Use reviewable logical identifiers without NUL or line terminators."));
+        assertThat(snapshot.getIssues().getFirst().getDetails(), is(Map.of("identifier", "orders\ndrop")));
+    }
+    
     @ParameterizedTest(name = "{0}")
     @MethodSource("getEnsureLifecycleStateCases")
     void assertEnsureLifecycleState(final String name, final String operationType, final boolean ruleExists, final boolean expectedResult, final String expectedIssueCode) {
diff --git a/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/workflow/service/WorkflowValidationSupportTest.java b/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/workflow/service/WorkflowValidationSupportTest.java
index 559e4f8e792..2aeee3790bc 100644
--- a/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/workflow/service/WorkflowValidationSupportTest.java
+++ b/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/workflow/service/WorkflowValidationSupportTest.java
@@ -30,6 +30,7 @@ import org.junit.jupiter.api.Test;
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.concurrent.atomic.AtomicBoolean;
 
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.hamcrest.Matchers.is;
@@ -77,6 +78,37 @@ class WorkflowValidationSupportTest {
         assertTrue(validationSupport.checkValidatePreconditions("session-1", snapshot).isEmpty());
     }
     
+    @Test
+    void assertValidateAndFinalize() {
+        WorkflowContextSnapshot snapshot = new WorkflowContextSnapshot();
+        snapshot.setPlanId("plan-1");
+        snapshot.setSessionId("session-1");
+        snapshot.setStatus("executed");
+        ValidationReport validationReport = new ValidationReport();
+        validationReport.setOverallStatus("passed");
+        WorkflowSessionContext workflowSessionContext = new TestWorkflowSessionContext();
+        workflowSessionContext.save(snapshot);
+        Map<String, Object> actualResult = validationSupport.validateAndFinalize(workflowSessionContext, "session-1", snapshot, () -> validationReport);
+        assertThat(actualResult.get("response_mode"), is("validation"));
+        assertThat(actualResult.get("status"), is("validated"));
+        assertThat(snapshot.getValidationReport(), is(validationReport));
+    }
+    
+    @Test
+    void assertValidateAndFinalizeRejectsInvalidPrecondition() {
+        WorkflowContextSnapshot snapshot = new WorkflowContextSnapshot();
+        snapshot.setPlanId("plan-1");
+        snapshot.setSessionId("session-1");
+        snapshot.setStatus("clarifying");
+        AtomicBoolean reportCreated = new AtomicBoolean();
+        Map<String, Object> actualResult = validationSupport.validateAndFinalize(new TestWorkflowSessionContext(), "session-1", snapshot, () -> {
+            reportCreated.set(true);
+            return new ValidationReport();
+        });
+        assertThat(actualResult.get("response_mode"), is("terminal"));
+        assertFalse(reportCreated.get());
+    }
+    
     @Test
     void assertResolveOverallStatusReturnsFailedWhenAnySectionFails() {
         String actualStatus = validationSupport.resolveOverallStatus(
diff --git a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/runtime/programmatic/AbstractHttpProgrammaticRuntimeE2ETest.java b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/runtime/programmatic/AbstractHttpProgrammaticRuntimeE2ETest.java
index b7fefc7c313..5bc0b1caff0 100644
--- a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/runtime/programmatic/AbstractHttpProgrammaticRuntimeE2ETest.java
+++ b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/runtime/programmatic/AbstractHttpProgrammaticRuntimeE2ETest.java
@@ -142,6 +142,21 @@ abstract class AbstractHttpProgrammaticRuntimeE2ETest extends AbstractConfigBack
         return result;
     }
     
+    protected final String createMaskRulePlan(final HttpClient httpClient, final String sessionId) throws IOException, InterruptedException {
+        HttpResponse<String> actual = sendToolCallRequest(httpClient, sessionId, "database_gateway_plan_mask_rule", Map.of(
+                "database", "logic_db",
+                "schema", "logic_db",
+                "table", "orders",
+                "column", "status",
+                "operation_type", "create",
+                "algorithm_type", "KEEP_FIRST_N_LAST_M",
+                "primary_algorithm_properties", Map.of("first-n", "1", "last-m", "1", "replace-char", "*")));
+        assertThat(actual.statusCode(), is(200));
+        Map<String, Object> payload = getStructuredContent(actual.body());
+        assertThat(String.valueOf(payload.get("status")), is("planned"));
+        return String.valueOf(payload.get("plan_id"));
+    }
+    
     protected final URI getEndpointUri() throws IOException {
         return getHttpEndpointUri();
     }
diff --git a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/runtime/programmatic/HttpTransportApprovalSafetyE2ETest.java b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/runtime/programmatic/HttpTransportApprovalSafetyE2ETest.java
index fd212ed5e8d..909444bd32f 100644
--- a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/runtime/programmatic/HttpTransportApprovalSafetyE2ETest.java
+++ b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/runtime/programmatic/HttpTransportApprovalSafetyE2ETest.java
@@ -110,21 +110,6 @@ class HttpTransportApprovalSafetyE2ETest extends AbstractSharedHttpProgrammaticR
         return getStructuredContent(actual.body());
     }
     
-    private String createMaskRulePlan(final HttpClient httpClient, final String sessionId) throws IOException, InterruptedException {
-        HttpResponse<String> actual = sendToolCallRequest(httpClient, sessionId, "database_gateway_plan_mask_rule", Map.of(
-                "database", "logic_db",
-                "schema", "logic_db",
-                "table", "orders",
-                "column", "status",
-                "operation_type", "create",
-                "algorithm_type", "KEEP_FIRST_N_LAST_M",
-                "primary_algorithm_properties", Map.of("first-n", "1", "last-m", "1", "replace-char", "*")));
-        assertThat(actual.statusCode(), is(200));
-        Map<String, Object> payload = getStructuredContent(actual.body());
-        assertThat(String.valueOf(payload.get("status")), is("planned"));
-        return String.valueOf(payload.get("plan_id"));
-    }
-    
     private void assertModelFacingPayloadContract(final Map<String, Object> payload) {
         MCPModelContractAssertions.assertCanonicalNextActionLists(payload);
     }
diff --git a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/runtime/programmatic/HttpTransportCompletionE2ETest.java b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/runtime/programmatic/HttpTransportCompletionE2ETest.java
index ef1152a7ed1..b182584f9af 100644
--- a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/runtime/programmatic/HttpTransportCompletionE2ETest.java
+++ b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/runtime/programmatic/HttpTransportCompletionE2ETest.java
@@ -154,18 +154,4 @@ class HttpTransportCompletionE2ETest extends AbstractSharedHttpProgrammaticRunti
                 "completion-1", "completion/complete", params));
     }
     
-    private String createMaskRulePlan(final HttpClient httpClient, final String sessionId) throws IOException, InterruptedException {
-        HttpResponse<String> actual = sendToolCallRequest(httpClient, sessionId, "database_gateway_plan_mask_rule", Map.of(
-                "database", "logic_db",
-                "schema", "logic_db",
-                "table", "orders",
-                "column", "status",
-                "operation_type", "create",
-                "algorithm_type", "KEEP_FIRST_N_LAST_M",
-                "primary_algorithm_properties", Map.of("first-n", "1", "last-m", "1", "replace-char", "*")));
-        assertThat(actual.statusCode(), is(200));
-        Map<String, Object> payload = getStructuredContent(actual.body());
-        assertThat(String.valueOf(payload.get("status")), is("planned"));
-        return String.valueOf(payload.get("plan_id"));
-    }
 }
diff --git a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/support/runtime/MySQLRuntimeTestSupport.java b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/support/runtime/MySQLRuntimeTestSupport.java
index 68c6540c7fd..1ad17219004 100644
--- a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/support/runtime/MySQLRuntimeTestSupport.java
+++ b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/support/runtime/MySQLRuntimeTestSupport.java
@@ -203,14 +203,7 @@ public final class MySQLRuntimeTestSupport {
      * @throws SQLException SQL exception
      */
     public static void initializeDatabase(final GenericContainer<?> container) throws SQLException {
-        executeStatements(container, DATABASE_NAME,
-                "CREATE TABLE IF NOT EXISTS orders (order_id INT PRIMARY KEY, status VARCHAR(32), amount INT)",
-                "CREATE TABLE IF NOT EXISTS order_items (item_id INT PRIMARY KEY, order_id INT, sku VARCHAR(64))",
-                "INSERT INTO orders (order_id, status, amount) VALUES (1, 'NEW', 10) ON DUPLICATE KEY UPDATE status = VALUES(status), amount = VALUES(amount)",
-                "INSERT INTO orders (order_id, status, amount) VALUES (2, 'DONE', 20) ON DUPLICATE KEY UPDATE status = VALUES(status), amount = VALUES(amount)",
-                "INSERT INTO order_items (item_id, order_id, sku) VALUES (1, 1, 'sku-1') ON DUPLICATE KEY UPDATE order_id = VALUES(order_id), sku = VALUES(sku)",
-                "CREATE OR REPLACE VIEW active_orders AS SELECT order_id, status FROM orders WHERE status <> 'DONE'",
-                "CREATE INDEX idx_orders_status ON orders(status)");
+        initializeOrdersSchema(container, DATABASE_NAME);
     }
     
     private static void initializeProgrammaticDatabases(final GenericContainer<?> container) throws SQLException {
@@ -228,14 +221,7 @@ public final class MySQLRuntimeTestSupport {
                 "GRANT ALL PRIVILEGES ON analytics_db.* TO 'mcp_analytics'@'%'",
                 "GRANT ALL PRIVILEGES ON warehouse.* TO 'mcp_warehouse'@'%'",
                 "FLUSH PRIVILEGES");
-        executeStatements(container, "logic_db",
-                "CREATE TABLE IF NOT EXISTS orders (order_id INT PRIMARY KEY, status VARCHAR(32), amount INT)",
-                "CREATE TABLE IF NOT EXISTS order_items (item_id INT PRIMARY KEY, order_id INT, sku VARCHAR(64))",
-                "INSERT INTO orders (order_id, status, amount) VALUES (1, 'NEW', 10) ON DUPLICATE KEY UPDATE status = VALUES(status), amount = VALUES(amount)",
-                "INSERT INTO orders (order_id, status, amount) VALUES (2, 'DONE', 20) ON DUPLICATE KEY UPDATE status = VALUES(status), amount = VALUES(amount)",
-                "INSERT INTO order_items (item_id, order_id, sku) VALUES (1, 1, 'sku-1') ON DUPLICATE KEY UPDATE order_id = VALUES(order_id), sku = VALUES(sku)",
-                "CREATE OR REPLACE VIEW active_orders AS SELECT order_id, status FROM orders WHERE status <> 'DONE'",
-                "CREATE INDEX idx_orders_status ON orders(status)");
+        initializeOrdersSchema(container, "logic_db");
         executeStatements(container, "analytics_db",
                 "CREATE TABLE IF NOT EXISTS metrics (metric_id INT PRIMARY KEY, metric_name VARCHAR(32))",
                 "INSERT INTO metrics (metric_id, metric_name) VALUES (10, 'cpu') ON DUPLICATE KEY UPDATE metric_name = VALUES(metric_name)",
@@ -246,6 +232,17 @@ public final class MySQLRuntimeTestSupport {
                 "INSERT INTO facts (fact_id, total) VALUES (200, 2) ON DUPLICATE KEY UPDATE total = VALUES(total)");
     }
     
+    private static void initializeOrdersSchema(final GenericContainer<?> container, final String databaseName) throws SQLException {
+        executeStatements(container, databaseName,
+                "CREATE TABLE IF NOT EXISTS orders (order_id INT PRIMARY KEY, status VARCHAR(32), amount INT)",
+                "CREATE TABLE IF NOT EXISTS order_items (item_id INT PRIMARY KEY, order_id INT, sku VARCHAR(64))",
+                "INSERT INTO orders (order_id, status, amount) VALUES (1, 'NEW', 10) ON DUPLICATE KEY UPDATE status = VALUES(status), amount = VALUES(amount)",
+                "INSERT INTO orders (order_id, status, amount) VALUES (2, 'DONE', 20) ON DUPLICATE KEY UPDATE status = VALUES(status), amount = VALUES(amount)",
+                "INSERT INTO order_items (item_id, order_id, sku) VALUES (1, 1, 'sku-1') ON DUPLICATE KEY UPDATE order_id = VALUES(order_id), sku = VALUES(sku)",
+                "CREATE OR REPLACE VIEW active_orders AS SELECT order_id, status FROM orders WHERE status <> 'DONE'",
+                "CREATE INDEX idx_orders_status ON orders(status)");
+    }
+    
     /**
      * Detect the schema value surfaced by JDBC metadata for the orders table.
      *