You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@hugegraph.apache.org by ji...@apache.org on 2026/07/10 18:52:41 UTC

(hugegraph) branch master updated: feat(server): adapt Hubble 2.0 & add graph/role management (#3008)

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

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


The following commit(s) were added to refs/heads/master by this push:
     new 99936be5f feat(server): adapt Hubble 2.0 & add graph/role management (#3008)
99936be5f is described below

commit 99936be5f41fccd193f120e01206e3cf3c73a050
Author: Yeaury <ye...@qq.com>
AuthorDate: Sat Jul 11 02:52:37 2026 +0800

    feat(server): adapt Hubble 2.0 & add graph/role management (#3008)
    
    Main Changes
    Graph management (GraphsAPI.java)
    GraphSpace and default-role APIs (GraphSpaceAPI.java)
    GraphSpace managers (ManagerAPI.java)
    Schema templates (SchemaTemplateAPI.java)
    Authentication and graph metadata support
    
    ---------
    
    Co-authored-by: imbajin <ji...@apache.org>
---
 .../main/java/org/apache/hugegraph/api/API.java    |  32 +-
 .../org/apache/hugegraph/api/auth/ManagerAPI.java  |  54 +++-
 .../org/apache/hugegraph/api/auth/UserAPI.java     |  26 +-
 .../apache/hugegraph/api/profile/GraphsAPI.java    | 352 ++++++++++++++++++++-
 .../apache/hugegraph/api/space/GraphSpaceAPI.java  | 241 ++++++++++++--
 .../hugegraph/api/space/SchemaTemplateAPI.java     | 221 +++++++++++++
 .../apache/hugegraph/auth/HugeGraphAuthProxy.java  |  65 ++++
 .../org/apache/hugegraph/config/ServerOptions.java |   2 +-
 .../org/apache/hugegraph/core/GraphManager.java    | 121 ++++++-
 .../org/apache/hugegraph/auth/AuthManager.java     |  26 ++
 .../java/org/apache/hugegraph/auth/HugeUser.java   |  32 +-
 .../apache/hugegraph/auth/StandardAuthManager.java | 197 ++++++++++++
 .../hugegraph/auth/StandardAuthManagerV2.java      | 224 ++++++++++++-
 .../backend/cache/CachedSchemaTransactionV2.java   |  10 +-
 .../java/org/apache/hugegraph/util/ConfigUtil.java |  39 +++
 .../org/apache/hugegraph/dist/HugeGraphServer.java | 114 +++++--
 .../apache/hugegraph/dist/HugeGremlinServer.java   |  37 ++-
 .../org/apache/hugegraph/dist/MemoryMonitor.java   |   2 +-
 hugegraph-server/hugegraph-test/pom.xml            |   6 +
 .../org/apache/hugegraph/api/ApiTestSuite.java     |   3 +
 .../java/org/apache/hugegraph/api/BaseApiTest.java |  11 +-
 .../api/DefaultRoleApiStandaloneTest.java          | 182 +++++++++++
 .../apache/hugegraph/api/GraphSpaceApiTest.java    | 156 +++++++++
 .../hugegraph/api/GraphsApiStandaloneTest.java     | 306 ++++++++++++++++++
 .../hugegraph/api/SchemaTemplateApiTest.java       | 133 ++++++++
 .../org/apache/hugegraph/core/EdgeCoreTest.java    |  18 +-
 .../org/apache/hugegraph/core/MultiGraphsTest.java |   6 +-
 .../org/apache/hugegraph/core/RamTableTest.java    |  15 +-
 .../org/apache/hugegraph/core/VertexCoreTest.java  |  27 +-
 .../org/apache/hugegraph/unit/UnitTestSuite.java   |   4 +
 .../unit/api/space/GraphSpaceAPITest.java          | 274 ++++++++++++++++
 .../unit/auth/HugeGraphAuthProxyTest.java          |  49 +++
 .../unit/cache/CachedSchemaTransactionTest.java    |  85 ++---
 .../apache/hugegraph/dist/HugeGraphServerTest.java |  79 +++++
 .../hugegraph/unit/config/ServerOptionsTest.java   |  22 ++
 35 files changed, 3013 insertions(+), 158 deletions(-)

diff --git a/hugegraph-server/hugegraph-api/src/main/java/org/apache/hugegraph/api/API.java b/hugegraph-server/hugegraph-api/src/main/java/org/apache/hugegraph/api/API.java
index 3220cf6b0..b50195b85 100644
--- a/hugegraph-server/hugegraph-api/src/main/java/org/apache/hugegraph/api/API.java
+++ b/hugegraph-server/hugegraph-api/src/main/java/org/apache/hugegraph/api/API.java
@@ -17,6 +17,7 @@
 
 package org.apache.hugegraph.api;
 
+import java.time.format.DateTimeFormatter;
 import java.util.Collection;
 import java.util.Map;
 import java.util.concurrent.Callable;
@@ -86,9 +87,16 @@ public class API {
             MetricsUtil.registerMeter(API.class, "expected-error");
     private static final Meter unknownErrorMeter =
             MetricsUtil.registerMeter(API.class, "unknown-error");
-    private static final String STANDALONE_ERROR =
+    protected static final String STANDALONE_ERROR =
             "GraphSpace management is not supported in standalone mode";
 
+    /**
+     * Shared date formatter for API response timestamps (thread-safe, reusable).
+     * Example output: {@code "2024-05-01 12:30:00"}
+     */
+    protected static final DateTimeFormatter DATE_FORMATTER =
+            DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
+
     public static HugeGraph graph(GraphManager manager, String graphSpace,
                                   String graph) {
         HugeGraph g = manager.graph(graphSpace, graph);
@@ -282,6 +290,28 @@ public class API {
         }
     }
 
+    /**
+     * Returns {@code true} if the profile's {@code "name"} or {@code "nickname"}
+     * field starts with the given prefix.  Returns {@code true} when prefix is
+     * null or empty (i.e. no filtering).
+     *
+     * <p>Used by {@code GraphsAPI.listProfile} and {@code GraphSpaceAPI.listProfile}
+     * to filter results by a user-supplied prefix string.
+     *
+     * @param profile a map containing at least a {@code "name"} key
+     * @param prefix  the prefix to match against; ignored when blank
+     * @return whether the entry matches the prefix filter
+     */
+    protected static boolean isPrefix(Map<String, Object> profile, String prefix) {
+        if (prefix == null || prefix.isEmpty()) {
+            return true;
+        }
+        String name = profile.get("name").toString();
+        Object nicknameObj = profile.get("nickname");
+        String nickname = nicknameObj != null ? nicknameObj.toString() : "";
+        return name.startsWith(prefix) || nickname.startsWith(prefix);
+    }
+
     public static class ApiMeasurer {
 
         public static final String EDGE_ITER = "edge_iterations";
diff --git a/hugegraph-server/hugegraph-api/src/main/java/org/apache/hugegraph/api/auth/ManagerAPI.java b/hugegraph-server/hugegraph-api/src/main/java/org/apache/hugegraph/api/auth/ManagerAPI.java
index 071e4b8a6..37aee8c65 100644
--- a/hugegraph-server/hugegraph-api/src/main/java/org/apache/hugegraph/api/auth/ManagerAPI.java
+++ b/hugegraph-server/hugegraph-api/src/main/java/org/apache/hugegraph/api/auth/ManagerAPI.java
@@ -22,9 +22,12 @@ package org.apache.hugegraph.api.auth;
 import java.util.ArrayList;
 import java.util.List;
 
+import org.apache.commons.lang3.StringUtils;
+
 import org.apache.hugegraph.api.API;
 import org.apache.hugegraph.api.filter.StatusFilter;
 import org.apache.hugegraph.auth.AuthManager;
+import org.apache.hugegraph.auth.HugeDefaultRole;
 import org.apache.hugegraph.auth.HugeGraphAuthProxy;
 import org.apache.hugegraph.auth.HugePermission;
 import org.apache.hugegraph.core.GraphManager;
@@ -259,6 +262,49 @@ public class ManagerAPI extends API {
                                 result));
     }
 
+    @GET
+    @Timed
+    @Path("default")
+    public String checkDefaultRole(@Context GraphManager manager,
+                                   @PathParam("graphspace") String graphSpace,
+                                   @QueryParam("role") String role,
+                                   @QueryParam("graph") String graph) {
+        LOG.debug("check if current user is default role: {} {} {}",
+                  role, graphSpace, graph);
+        ensurePdModeEnabled(manager);
+        AuthManager authManager = manager.authManager();
+        String user = HugeGraphAuthProxy.username();
+
+        E.checkArgument(StringUtils.isNotEmpty(role) &&
+                        StringUtils.isNotEmpty(graphSpace),
+                        "Must pass graphspace and role params");
+
+        HugeDefaultRole defaultRole;
+        try {
+            defaultRole = HugeDefaultRole.valueOf(role.toUpperCase());
+        } catch (IllegalArgumentException e) {
+            E.checkArgument(false, "Invalid role value '%s'", role);
+            defaultRole = null; // unreachable, satisfies compiler
+        }
+        validGraphSpace(manager, graphSpace);
+        boolean hasGraph = defaultRole.equals(HugeDefaultRole.OBSERVER);
+        E.checkArgument(!hasGraph || StringUtils.isNotEmpty(graph),
+                        "Must set a graph for observer");
+        if (hasGraph) {
+            validGraph(manager, graphSpace, graph);
+        }
+
+        boolean result;
+        if (hasGraph) {
+            result = authManager.isDefaultRole(graphSpace, graph, user,
+                                               defaultRole);
+        } else {
+            result = authManager.isDefaultRole(graphSpace, user,
+                                               defaultRole);
+        }
+        return manager.serializer().writeMap(ImmutableMap.of("check", result));
+    }
+
     private void validUser(AuthManager authManager, String user) {
         E.checkArgument(authManager.findUser(user) != null ||
                         authManager.findGroup(user) != null,
@@ -274,7 +320,13 @@ public class ManagerAPI extends API {
 
     private void validGraphSpace(GraphManager manager, String graphSpace) {
         E.checkArgument(manager.graphSpace(graphSpace) != null,
-                        "The graph space is not exist");
+                        "The graph space '%s' does not exist", graphSpace);
+    }
+
+    private void validGraph(GraphManager manager, String graphSpace,
+                            String graph) {
+        E.checkArgument(manager.graph(graphSpace, graph) != null,
+                        "Graph '%s/%s' does not exist", graphSpace, graph);
     }
 
     private static class JsonManager implements Checkable {
diff --git a/hugegraph-server/hugegraph-api/src/main/java/org/apache/hugegraph/api/auth/UserAPI.java b/hugegraph-server/hugegraph-api/src/main/java/org/apache/hugegraph/api/auth/UserAPI.java
index de51e6955..7504ad732 100644
--- a/hugegraph-server/hugegraph-api/src/main/java/org/apache/hugegraph/api/auth/UserAPI.java
+++ b/hugegraph-server/hugegraph-api/src/main/java/org/apache/hugegraph/api/auth/UserAPI.java
@@ -108,9 +108,20 @@ public class UserAPI extends API {
     public String list(@Context GraphManager manager,
                        @Parameter(description = "The graph space name")
                        @PathParam("graphspace") String graphSpace,
+                       @Parameter(description = "Filter by user name")
+                       @QueryParam("name") String name,
                        @Parameter(description = "The limit of results to return")
                        @QueryParam("limit") @DefaultValue("100") long limit) {
-        LOG.debug("GraphSpace [{}] list users", graphSpace);
+        LOG.debug("GraphSpace [{}] list users, name={}", graphSpace, name);
+
+        if (StringUtils.isNotEmpty(name)) {
+            HugeUser user = manager.authManager().findUser(name);
+            if (user == null) {
+                throw new NotFoundException("Can't find user with name '%s'",
+                                            name);
+            }
+            return manager.serializer().writeAuthElement(user);
+        }
 
         List<HugeUser> users = manager.authManager().listAllUsers(limit);
         return manager.serializer().writeAuthElements("users", users);
@@ -178,6 +189,9 @@ public class UserAPI extends API {
         @JsonProperty("user_password")
         @Schema(description = "The user password", required = true)
         private String password;
+        @JsonProperty("user_nickname")
+        @Schema(description = "The user nickname")
+        private String nickname;
         @JsonProperty("user_phone")
         @Schema(description = "The user phone number")
         private String phone;
@@ -197,6 +211,9 @@ public class UserAPI extends API {
             if (this.password != null) {
                 user.password(StringEncoding.hashPassword(this.password));
             }
+            if (this.nickname != null) {
+                user.nickname(this.nickname);
+            }
             if (this.phone != null) {
                 user.phone(this.phone);
             }
@@ -215,6 +232,7 @@ public class UserAPI extends API {
         public HugeUser build() {
             HugeUser user = new HugeUser(this.name);
             user.password(StringEncoding.hashPassword(this.password));
+            user.nickname(this.nickname);
             user.phone(this.phone);
             user.email(this.email);
             user.avatar(this.avatar);
@@ -233,10 +251,12 @@ public class UserAPI extends API {
         @Override
         public void checkUpdate() {
             E.checkArgument(!StringUtils.isEmpty(this.password) ||
+                            this.nickname != null ||
                             this.phone != null ||
                             this.email != null ||
-                            this.avatar != null,
-                            "Expect one of user password/phone/email/avatar]");
+                            this.avatar != null ||
+                            this.description != null,
+                            "Expect one of user password/nickname/phone/email/avatar/description");
         }
     }
 }
diff --git a/hugegraph-server/hugegraph-api/src/main/java/org/apache/hugegraph/api/profile/GraphsAPI.java b/hugegraph-server/hugegraph-api/src/main/java/org/apache/hugegraph/api/profile/GraphsAPI.java
index 9316d7341..94169e4a9 100644
--- a/hugegraph-server/hugegraph-api/src/main/java/org/apache/hugegraph/api/profile/GraphsAPI.java
+++ b/hugegraph-server/hugegraph-api/src/main/java/org/apache/hugegraph/api/profile/GraphsAPI.java
@@ -18,16 +18,22 @@
 package org.apache.hugegraph.api.profile;
 
 import java.io.File;
+import java.time.LocalDateTime;
+import java.time.ZoneId;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Date;
 import java.util.HashMap;
 import java.util.HashSet;
+import java.util.List;
 import java.util.Map;
 import java.util.Set;
-
 import org.apache.commons.lang3.StringUtils;
 import org.apache.hugegraph.HugeException;
 import org.apache.hugegraph.HugeGraph;
 import org.apache.hugegraph.api.API;
 import org.apache.hugegraph.api.filter.StatusFilter;
+import org.apache.hugegraph.auth.AuthManager;
 import org.apache.hugegraph.auth.HugeAuthenticator.RequiredPerm;
 import org.apache.hugegraph.auth.HugeGraphAuthProxy;
 import org.apache.hugegraph.auth.HugePermission;
@@ -36,10 +42,10 @@ import org.apache.hugegraph.core.GraphManager;
 import org.apache.hugegraph.space.GraphSpace;
 import org.apache.hugegraph.type.define.GraphMode;
 import org.apache.hugegraph.type.define.GraphReadMode;
+import org.apache.hugegraph.util.ConfigUtil;
 import org.apache.hugegraph.util.E;
 import org.apache.hugegraph.util.JsonUtil;
 import org.apache.hugegraph.util.Log;
-import org.apache.logging.log4j.util.Strings;
 import org.slf4j.Logger;
 
 import com.codahale.metrics.annotation.Timed;
@@ -74,12 +80,22 @@ public class GraphsAPI extends API {
     private static final String CONFIRM_DROP = "I'm sure to drop the graph";
     private static final String GRAPH_DESCRIPTION = "description";
     private static final String GRAPH_ACTION = "action";
+    private static final String UPDATE = "update";
     private static final String GRAPH_ACTION_RELOAD = "reload";
 
     private static Map<String, Object> convConfig(Map<String, Object> config) {
         Map<String, Object> result = new HashMap<>(config.size());
         for (Map.Entry<String, Object> entry : config.entrySet()) {
-            result.put(entry.getKey(), entry.getValue().toString());
+            Object value = entry.getValue();
+            E.checkArgument(value != null,
+                            "The config value for '%s' cannot be null",
+                            entry.getKey());
+            E.checkArgument(value instanceof CharSequence ||
+                            value instanceof Number ||
+                            value instanceof Boolean,
+                            "The config value for '%s' must be scalar",
+                            entry.getKey());
+            result.put(entry.getKey(), value.toString());
         }
         return result;
     }
@@ -101,11 +117,12 @@ public class GraphsAPI extends API {
                   graphs.size());
         // Filter by user role
         Set<String> filterGraphs = new HashSet<>();
+        boolean adminManager = isAdminManager(manager);
         for (String graph : graphs) {
             LOG.debug("Get graph {} and verify auth", graph);
             String role = RequiredPerm.roleFor(graphSpace, graph,
                                                HugePermission.READ);
-            if (sc.isUserInRole(role)) {
+            if (adminManager || sc.isUserInRole(role)) {
                 try {
                     graph(manager, graphSpace, graph);
                     filterGraphs.add(graph);
@@ -120,6 +137,100 @@ public class GraphsAPI extends API {
         return ImmutableMap.of("graphs", filterGraphs);
     }
 
+    @GET
+    @Timed
+    @Path("profile")
+    @Produces(APPLICATION_JSON_WITH_CHARSET)
+    @RolesAllowed({"space_member", "$dynamic"})
+    public Object listProfile(@Context GraphManager manager,
+                              @Parameter(description = "The graph space name")
+                              @PathParam("graphspace") String graphSpace,
+                              @Parameter(description = "Filter graphs by name or nickname prefix")
+                              @QueryParam("prefix") String prefix,
+                              @Context SecurityContext sc) {
+        LOG.debug("List graph profiles in graph space {}", graphSpace);
+        if (null == manager.graphSpace(graphSpace)) {
+            throw new HugeException("Graphspace not exist!");
+        }
+        GraphSpace gs = manager.graphSpace(graphSpace);
+        // graphSpace.nickname() may be null in non-PD mode (GraphManager returns
+        // a placeholder GraphSpace without a nickname set)
+        String gsNickname = gs.nickname() != null ? gs.nickname() : graphSpace;
+
+        String user = HugeGraphAuthProxy.username();
+        Map<String, Date> defaultGraphs;
+        try {
+            AuthManager authManager = manager.authManager();
+            defaultGraphs = authManager.getDefaultGraph(graphSpace, user);
+        } catch (IllegalStateException ignored) {
+            // authManager is not configured (standalone mode without auth)
+            defaultGraphs = Collections.emptyMap();
+        }
+
+        Set<String> graphs = manager.graphs(graphSpace);
+        List<Map<String, Object>> profiles = new ArrayList<>();
+        List<Map<String, Object>> defaultProfiles = new ArrayList<>();
+        boolean adminManager = isAdminManager(manager);
+        for (String graph : graphs) {
+            String role = RequiredPerm.roleFor(graphSpace, graph,
+                                               HugePermission.READ);
+            if (!adminManager && !sc.isUserInRole(role)) {
+                continue;
+            }
+            try {
+                HugeGraph hg = graph(manager, graphSpace, graph);
+                HugeConfig config = (HugeConfig) hg.configuration();
+                String configResp = ConfigUtil.writeConfigToString(config);
+                Map<String, Object> profile =
+                        JsonUtil.fromJson(configResp, Map.class);
+                profile.put("name", graph);
+                profile.put("nickname", hg.nickname());
+                if (!isPrefix(profile, prefix)) {
+                    continue;
+                }
+                profile.put("graphspace_nickname", gsNickname);
+
+                boolean isDefault = defaultGraphs.containsKey(graph);
+                profile.put("default", isDefault);
+                if (isDefault) {
+                    Date defaultUpdateTime = defaultGraphs.get(graph);
+                    if (defaultUpdateTime != null) {
+                        LocalDateTime ldt = defaultUpdateTime.toInstant()
+                                .atZone(ZoneId.systemDefault()).toLocalDateTime();
+                        profile.put("default_update_time",
+                                    DATE_FORMATTER.format(ldt));
+                    }
+                }
+
+                Date createTime = hg.createTime();
+                if (createTime != null) {
+                    LocalDateTime ldt = createTime.toInstant()
+                            .atZone(ZoneId.systemDefault()).toLocalDateTime();
+                    profile.put("create_time", DATE_FORMATTER.format(ldt));
+                }
+
+                if (isDefault) {
+                    defaultProfiles.add(profile);
+                } else {
+                    profiles.add(profile);
+                }
+            } catch (ForbiddenException ignored) {
+                // ignore graphs the current user has no access to
+            }
+        }
+        defaultProfiles.addAll(profiles);
+        return defaultProfiles;
+    }
+
+    private static boolean isAdminManager(GraphManager manager) {
+        try {
+            return manager.authManager().isAdminManager(
+                    HugeGraphAuthProxy.username());
+        } catch (IllegalStateException ignored) {
+            return false;
+        }
+    }
+
     @GET
     @Timed
     @Path("{name}")
@@ -136,6 +247,80 @@ public class GraphsAPI extends API {
         return ImmutableMap.of("name", g.name(), "backend", g.backend());
     }
 
+    @POST
+    @Timed
+    @Path("{name}/default")
+    @Produces(APPLICATION_JSON_WITH_CHARSET)
+    @RolesAllowed({"space_member", "$owner=$name"})
+    public Map<String, Object> setDefault(@Context GraphManager manager,
+                                          @Parameter(description = "The graph space name")
+                                          @PathParam("graphspace") String graphSpace,
+                                          @Parameter(description = "The graph name")
+                                          @PathParam("name") String name) {
+        LOG.debug("Set default graph '{}' in graph space '{}'", name, graphSpace);
+        E.checkArgument(manager.graphSpace(graphSpace) != null,
+                        "The graph space '%s' does not exist", graphSpace);
+        E.checkArgument(manager.graph(graphSpace, name) != null,
+                        "Graph '%s/%s' does not exist", graphSpace, name);
+        String user = HugeGraphAuthProxy.username();
+        AuthManager authManager;
+        try {
+            authManager = manager.authManager();
+        } catch (IllegalStateException e) {
+            throw new HugeException(STANDALONE_ERROR);
+        }
+        authManager.setDefaultGraph(graphSpace, name, user);
+        Map<String, Date> defaults = authManager.getDefaultGraph(graphSpace, user);
+        return ImmutableMap.of("default_graph", defaults.keySet());
+    }
+
+    @DELETE
+    @Timed
+    @Path("{name}/default")
+    @Produces(APPLICATION_JSON_WITH_CHARSET)
+    @RolesAllowed({"space_member", "$owner=$name"})
+    public Map<String, Object> unsetDefault(@Context GraphManager manager,
+                                            @Parameter(description = "The graph space name")
+                                            @PathParam("graphspace") String graphSpace,
+                                            @Parameter(description = "The graph name")
+                                            @PathParam("name") String name) {
+        LOG.debug("Unset default graph '{}' in graph space '{}'", name, graphSpace);
+        E.checkArgument(manager.graphSpace(graphSpace) != null,
+                        "The graph space '%s' does not exist", graphSpace);
+        E.checkArgument(manager.graph(graphSpace, name) != null,
+                        "Graph '%s/%s' does not exist", graphSpace, name);
+        String user = HugeGraphAuthProxy.username();
+        AuthManager authManager;
+        try {
+            authManager = manager.authManager();
+        } catch (IllegalStateException e) {
+            throw new HugeException(STANDALONE_ERROR);
+        }
+        authManager.unsetDefaultGraph(graphSpace, name, user);
+        Map<String, Date> defaults = authManager.getDefaultGraph(graphSpace, user);
+        return ImmutableMap.of("default_graph", defaults.keySet());
+    }
+
+    @GET
+    @Timed
+    @Path("default")
+    @Produces(APPLICATION_JSON_WITH_CHARSET)
+    @RolesAllowed({"space_member", "$dynamic"})
+    public Map<String, Object> getDefault(@Context GraphManager manager,
+                                          @Parameter(description = "The graph space name")
+                                          @PathParam("graphspace") String graphSpace) {
+        LOG.debug("Get default graphs in graph space '{}'", graphSpace);
+        String user = HugeGraphAuthProxy.username();
+        AuthManager authManager;
+        try {
+            authManager = manager.authManager();
+        } catch (IllegalStateException e) {
+            throw new HugeException(STANDALONE_ERROR);
+        }
+        Map<String, Date> defaults = authManager.getDefaultGraph(graphSpace, user);
+        return ImmutableMap.of("default_graph", defaults.keySet());
+    }
+
     @DELETE
     @Timed
     @Path("{name}")
@@ -155,6 +340,89 @@ public class GraphsAPI extends API {
         manager.dropGraph(graphSpace, name, true);
     }
 
+    @PUT
+    @Timed
+    @Path("{name}")
+    @Consumes(APPLICATION_JSON)
+    @Produces(APPLICATION_JSON_WITH_CHARSET)
+    @RolesAllowed({"space"})
+    public Map<String, String> manage(@Context GraphManager manager,
+                                      @Parameter(description = "The graph space name")
+                                      @PathParam("graphspace") String graphSpace,
+                                      @Parameter(description = "The graph name")
+                                      @PathParam("name") String name,
+                                      @Parameter(description = "Action map: {'action':'update','update':{...}}")
+                                      Map<String, Object> actionMap) {
+        LOG.debug("Manage graph '{}' with action '{}'", name, actionMap);
+        E.checkArgument(actionMap != null && actionMap.containsKey(GRAPH_ACTION),
+                        "Invalid request body '%s'", actionMap);
+        Object value = actionMap.get(GRAPH_ACTION);
+        E.checkArgument(value instanceof String,
+                        "Invalid action type '%s', must be string",
+                        value == null ? "null" : value.getClass().getSimpleName());
+        String action = (String) value;
+        switch (action) {
+            case UPDATE:
+                E.checkArgument(actionMap.containsKey(UPDATE),
+                                "Please pass '%s' for graph update",
+                                UPDATE);
+                value = actionMap.get(UPDATE);
+                E.checkArgument(value instanceof Map,
+                                "The '%s' must be map, but got %s",
+                                UPDATE,
+                                value == null ? "null" : value.getClass().getSimpleName());
+                @SuppressWarnings("unchecked")
+                Map<String, Object> graphMap = (Map<String, Object>) value;
+                Object graphNameValue = graphMap.get("name");
+                E.checkArgument(graphNameValue == null ||
+                                graphNameValue instanceof String,
+                                "The 'name' must be string, but got %s",
+                                graphNameValue == null ? "null" :
+                                graphNameValue.getClass().getSimpleName());
+                String graphName = (String) graphNameValue;
+                E.checkArgument(graphName != null && graphName.equals(name),
+                                "Different name in update body '%s' with path '%s'",
+                                graphName, name);
+                HugeGraph exist = graph(manager, graphSpace, name);
+                Object nicknameValue = graphMap.get("nickname");
+                E.checkArgument(nicknameValue == null ||
+                                nicknameValue instanceof String,
+                                "The 'nickname' must be string, but got %s",
+                                nicknameValue == null ? "null" :
+                                nicknameValue.getClass().getSimpleName());
+                String nickname = (String) nicknameValue;
+                if (!StringUtils.isEmpty(nickname)) {
+                    GraphManager.checkNickname(nickname);
+                    boolean existedNickname;
+                    if (manager.isPDEnabled()) {
+                        existedNickname = manager.isExistedGraphNickname(graphSpace, nickname);
+                    } else {
+                        existedNickname = false;
+                        for (String existedGraphName : manager.graphs(graphSpace)) {
+                            if (name.equals(existedGraphName)) {
+                                continue;
+                            }
+                            HugeGraph graph = manager.graph(graphSpace, existedGraphName);
+                            if (graph != null && nickname.equals(graph.nickname())) {
+                                existedNickname = true;
+                                break;
+                            }
+                        }
+                    }
+                    E.checkArgument(!existedNickname || nickname.equals(exist.nickname()),
+                                    "Nickname '%s' has already existed in graphspace '%s'",
+                                    nickname, graphSpace);
+                    // Delegate to GraphManager: handles both in-memory update and
+                    // PD-mode persistence (non-PD mode is in-memory only).
+                    manager.updateGraphNickname(graphSpace, name, nickname);
+                }
+                return ImmutableMap.of(name, "updated");
+            default:
+                E.checkArgument(false, "Invalid graph action: '%s'", action);
+                return ImmutableMap.of(name, "invalid");
+        }
+    }
+
     @PUT
     @Timed
     @Path("manage")
@@ -174,8 +442,8 @@ public class GraphsAPI extends API {
             manager.reload();
             return ImmutableMap.of("graphs", "reloaded");
         }
-        throw new AssertionError(String.format(
-                "Invalid graphs action: '%s'", action));
+        E.checkArgument(false, "Invalid graphs action: '%s'", action);
+        return ImmutableMap.of("graphs", "invalid");
     }
 
     @POST
@@ -207,11 +475,32 @@ public class GraphsAPI extends API {
         if (StringUtils.isEmpty(clone)) {
             // Only check required parameters when creating new graph, not when cloning
             E.checkArgument(configs != null, "Config parameters cannot be null");
-            String[] requiredKeys = {"backend", "serializer", "store"};
-            for (String key : requiredKeys) {
-                Object value = configs.get(key);
-                E.checkArgument(value instanceof String && !StringUtils.isEmpty((String) value),
-                                "Required parameter '%s' is missing or empty", key);
+            // Always required by TinkerPop's GraphFactory.open()
+            configs.putIfAbsent("gremlin.graph",
+                                "org.apache.hugegraph.HugeFactory");
+            if (manager.isPDEnabled()) {
+                // Auto-fill HStore/PD mode defaults only when in distributed mode
+                configs.putIfAbsent("backend", "hstore");
+            } else {
+                // Auto-fill standalone (RocksDB) mode defaults
+                configs.putIfAbsent("backend", "rocksdb");
+            }
+            configs.putIfAbsent("serializer", "binary");
+            // 'store' is safe to default to graph name in both PD and non-PD modes
+            configs.putIfAbsent("store", name);
+            // Map frontend 'schema' field to backend config key
+            boolean hasSchema = configs.containsKey("schema");
+            Object schema = configs.remove("schema");
+            if (hasSchema) {
+                E.checkArgument(schema != null,
+                                "The config value for 'schema' cannot be null");
+                E.checkArgument(schema instanceof CharSequence ||
+                                schema instanceof Number ||
+                                schema instanceof Boolean,
+                                "The config value for 'schema' must be scalar");
+            }
+            if (schema != null && !schema.toString().isEmpty()) {
+                configs.put("schema.init_template", schema.toString());
             }
         }
 
@@ -220,15 +509,19 @@ public class GraphsAPI extends API {
         if (StringUtils.isNotEmpty(clone)) {
             // Clone from existing graph
             LOG.debug("Clone graph '{}' to '{}' in graph space '{}'", clone, name, graphSpace);
-            graph = manager.cloneGraph(graphSpace, clone, name, convConfig(configs));
+            Map<String, Object> cloneConfigs = configs != null ? configs : new HashMap<>();
+            graph = manager.cloneGraph(graphSpace, clone, name, convConfig(cloneConfigs));
         } else {
             // Create new graph
             graph = manager.createGraph(graphSpace, name, creator,
                                         convConfig(configs), true);
         }
-        String description = (String) configs.get(GRAPH_DESCRIPTION);
+        Object descriptionValue = (configs != null) ?
+                                  configs.get(GRAPH_DESCRIPTION) : null;
+        String description = descriptionValue != null ?
+                             descriptionValue.toString() : null;
         if (description == null) {
-            description = Strings.EMPTY;
+            description = StringUtils.EMPTY;
         }
         Object result = ImmutableMap.of("name", graph.name(),
                                         "nickname", graph.nickname(),
@@ -239,6 +532,37 @@ public class GraphsAPI extends API {
         return result;
     }
 
+    /**
+     * Create graph via text/plain (hugegraph-client compatibility).
+     * Client sends: POST /graphspaces/{graphspace}/graphs/{name}
+     * with Content-Type: text/plain and body containing JSON config string.
+     */
+    @POST
+    @Timed
+    @Path("{name}")
+    @StatusFilter.Status(StatusFilter.Status.CREATED)
+    @Consumes("text/plain")
+    @Produces(APPLICATION_JSON_WITH_CHARSET)
+    @RolesAllowed({"space"})
+    public Object createByText(@Context GraphManager manager,
+                               @Parameter(description = "The graph space name")
+                               @PathParam("graphspace") String graphSpace,
+                               @Parameter(description = "The graph name to create")
+                               @PathParam("name") String name,
+                               @Parameter(description = "The graph name to clone from (optional)")
+                               @QueryParam("clone_graph_name") String clone,
+                               String configText) {
+        LOG.debug("Create graph {} with text config in graph space '{}'",
+                  name, graphSpace);
+        Map<String, Object> configs = null;
+        if (configText != null && !configText.isEmpty()) {
+            configs = JsonUtil.fromJson(configText, Map.class);
+        }
+        return create(manager, graphSpace, name, clone, configs);
+    }
+
+
+
     @GET
     @Timed
     @Path("{name}/conf")
diff --git a/hugegraph-server/hugegraph-api/src/main/java/org/apache/hugegraph/api/space/GraphSpaceAPI.java b/hugegraph-server/hugegraph-api/src/main/java/org/apache/hugegraph/api/space/GraphSpaceAPI.java
index 35bc40aed..81f13cf3f 100644
--- a/hugegraph-server/hugegraph-api/src/main/java/org/apache/hugegraph/api/space/GraphSpaceAPI.java
+++ b/hugegraph-server/hugegraph-api/src/main/java/org/apache/hugegraph/api/space/GraphSpaceAPI.java
@@ -1,35 +1,37 @@
 /*
- * Copyright 2017 HugeGraph Authors
- *
  * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with this
- * work for additional information regarding copyright ownership. The ASF
- * licenses this file to You under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
  *
  *     http://www.apache.org/licenses/LICENSE-2.0
  *
  * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations
- * under the License.
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
  */
 
 package org.apache.hugegraph.api.space;
 
-import java.text.SimpleDateFormat;
+import java.time.LocalDateTime;
+import java.time.ZoneId;
 import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
 
 import org.apache.commons.codec.digest.DigestUtils;
-import org.apache.commons.lang.StringUtils;
+import org.apache.commons.lang3.StringUtils;
+
 import org.apache.hugegraph.api.API;
 import org.apache.hugegraph.api.filter.StatusFilter.Status;
 import org.apache.hugegraph.auth.AuthManager;
+import org.apache.hugegraph.auth.HugeDefaultRole;
 import org.apache.hugegraph.auth.HugeGraphAuthProxy;
 import org.apache.hugegraph.core.GraphManager;
 import org.apache.hugegraph.define.Checkable;
@@ -38,10 +40,10 @@ import org.apache.hugegraph.space.GraphSpace;
 import org.apache.hugegraph.util.E;
 import org.apache.hugegraph.util.JsonUtil;
 import org.apache.hugegraph.util.Log;
-import org.apache.logging.log4j.util.Strings;
 import org.slf4j.Logger;
 
 import com.codahale.metrics.annotation.Timed;
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
 import com.fasterxml.jackson.annotation.JsonProperty;
 import com.google.common.collect.ImmutableMap;
 
@@ -52,6 +54,7 @@ import jakarta.annotation.security.RolesAllowed;
 import jakarta.inject.Singleton;
 import jakarta.ws.rs.Consumes;
 import jakarta.ws.rs.DELETE;
+import jakarta.ws.rs.ForbiddenException;
 import jakarta.ws.rs.GET;
 import jakarta.ws.rs.POST;
 import jakarta.ws.rs.PUT;
@@ -103,6 +106,172 @@ public class GraphSpaceAPI extends API {
         return gsInfo;
     }
 
+    @POST
+    @Timed
+    @Status(Status.CREATED)
+    @Consumes(APPLICATION_JSON)
+    @Produces(APPLICATION_JSON_WITH_CHARSET)
+    @Path("{graphspace}/role")
+    @RolesAllowed({"admin", "space"})
+    public String setDefaultRole(@Context GraphManager manager,
+                                 @PathParam("graphspace") String name,
+                                 JsonDefaultRole jsonRole) {
+        ensurePdModeEnabled(manager);
+        E.checkArgumentNotNull(jsonRole, "Request body cannot be null");
+        E.checkArgument(StringUtils.isNotEmpty(jsonRole.user),
+                        "The 'user' field cannot be null or empty");
+        E.checkArgument(StringUtils.isNotEmpty(jsonRole.role),
+                        "The 'role' field cannot be null or empty");
+        String user = jsonRole.user;
+        String graph = jsonRole.graph;
+        HugeDefaultRole role;
+        try {
+            role = HugeDefaultRole.valueOf(jsonRole.role.toUpperCase());
+        } catch (IllegalArgumentException e) {
+            E.checkArgument(false, "Invalid role value '%s'", jsonRole.role);
+            role = null; // unreachable, satisfies compiler
+        }
+        validGraphSpace(manager, name);
+        LOG.debug("Create default role: {} {} {}", user, role,
+                              name);
+        AuthManager authManager = manager.authManager();
+        String operator = HugeGraphAuthProxy.username();
+        validPermission(hasAdminOrSpaceManagerPerm(manager, name, operator),
+                        operator, "default_role.create");
+        E.checkArgument(authManager.findUser(user) != null ||
+                        authManager.findGroup(user) != null,
+                        "The user or group is not exist");
+        // only admin can set space admin
+        if (!authManager.isAdminManager(operator) && role.equals(HugeDefaultRole.SPACE)) {
+            throw new ForbiddenException("Forbidden to set role " + role.toString());
+        }
+
+        boolean hasGraph = role.equals(HugeDefaultRole.OBSERVER);
+
+        E.checkArgument(!hasGraph || StringUtils.isNotEmpty(graph),
+                        "Must set a graph for observer");
+        if (hasGraph) {
+            validGraph(manager, name, graph);
+        }
+
+        Map<String, String> result = new HashMap<>();
+        result.put("user", user);
+        result.put("role", jsonRole.role);
+        result.put("graphSpace", name);
+
+        if (hasGraph) {
+            authManager.createDefaultRole(name, user, role, graph);
+            result.put("graph", graph);
+        } else {
+            authManager.createSpaceDefaultRole(name, user, role);
+        }
+
+        return manager.serializer().writeMap(result);
+    }
+
+    @GET
+    @Timed
+    @Path("{graphspace}/role")
+    @RolesAllowed({"admin", "space"})
+    public String checkDefaultRole(@Context GraphManager manager,
+                                   @PathParam("graphspace") String name,
+                                   @QueryParam("user") String user,
+                                   @QueryParam("role") String role,
+                                   @QueryParam("graph") String graph) {
+        ensurePdModeEnabled(manager);
+        E.checkArgument(StringUtils.isNotEmpty(user),
+                        "The 'user' query param cannot be null or empty");
+        E.checkArgument(StringUtils.isNotEmpty(role),
+                        "The 'role' query param cannot be null or empty");
+        LOG.debug("Check space role: {} {} {}", user, role,
+                              name);
+        AuthManager authManager = manager.authManager();
+
+        HugeDefaultRole defaultRole;
+        try {
+            defaultRole = HugeDefaultRole.valueOf(role.toUpperCase());
+        } catch (IllegalArgumentException e) {
+            E.checkArgument(false, "Invalid role value '%s'", role);
+            defaultRole = null; // unreachable, satisfies compiler
+        }
+        validGraphSpace(manager, name);
+        String operator = HugeGraphAuthProxy.username();
+        validPermission(hasAdminOrSpaceManagerPerm(manager, name, operator),
+                        operator, "default_role.check");
+        // Only admin can inspect the space manager default role.
+        if (!authManager.isAdminManager(operator) &&
+            defaultRole.equals(HugeDefaultRole.SPACE)) {
+            throw new ForbiddenException("Forbidden to check role " + role);
+        }
+        boolean hasGraph = defaultRole.equals(HugeDefaultRole.OBSERVER);
+        E.checkArgument(!hasGraph || StringUtils.isNotEmpty(graph),
+                        "Must set a graph for observer");
+        if (hasGraph) {
+            validGraph(manager, name, graph);
+        }
+
+        boolean result;
+        if (hasGraph) {
+            result = authManager.isDefaultRole(name, graph, user,
+                                               defaultRole);
+        } else {
+            result = authManager.isDefaultRole(name, user,
+                                               defaultRole);
+        }
+        return manager.serializer().writeMap(ImmutableMap.of("check", result));
+    }
+
+    @DELETE
+    @Timed
+    @Path("{graphspace}/role")
+    @RolesAllowed({"admin", "space"})
+    public void deleteDefaultRole(@Context GraphManager manager,
+                                  @PathParam("graphspace") String name,
+                                  @QueryParam("user") String user,
+                                  @QueryParam("role") String role,
+                                  @QueryParam("graph") String graph) {
+        ensurePdModeEnabled(manager);
+        E.checkArgument(StringUtils.isNotEmpty(user),
+                        "The 'user' query param cannot be null or empty");
+        E.checkArgument(StringUtils.isNotEmpty(role),
+                        "The 'role' query param cannot be null or empty");
+        LOG.debug("Delete space role: {} {} {}", user, role,
+                              name);
+
+        AuthManager authManager = manager.authManager();
+        validGraphSpace(manager, name);
+        String operator = HugeGraphAuthProxy.username();
+        validPermission(hasAdminOrSpaceManagerPerm(manager, name, operator),
+                        operator, "default_role.delete");
+        E.checkArgument(authManager.findUser(user) != null ||
+                        authManager.findGroup(user) != null,
+                        "The user or group is not exist");
+
+        if (!authManager.isAdminManager(operator) &&
+            role.equalsIgnoreCase(HugeDefaultRole.SPACE.toString())) {
+            throw new ForbiddenException("Forbidden to delete role " + role);
+        }
+
+        HugeDefaultRole defaultRole;
+        try {
+            defaultRole = HugeDefaultRole.valueOf(role.toUpperCase());
+        } catch (IllegalArgumentException e) {
+            E.checkArgument(false, "Invalid role value '%s'", role);
+            defaultRole = null; // unreachable, satisfies compiler
+        }
+        boolean hasGraph = defaultRole.equals(HugeDefaultRole.OBSERVER);
+        E.checkArgument(!hasGraph || StringUtils.isNotEmpty(graph),
+                        "Must set a graph for observer");
+        if (hasGraph) {
+            validGraph(manager, name, graph);
+        }
+        if (hasGraph) {
+            authManager.deleteDefaultRole(name, user, defaultRole, graph);
+        } else {
+            authManager.deleteDefaultRole(name, user, defaultRole);
+        }
+    }
+
     @GET
     @Timed
     @Path("profile")
@@ -121,7 +290,6 @@ public class GraphSpaceAPI extends API {
         AuthManager authManager = manager.authManager();
         // FIXME: defaultSpace related interface is not implemented
         // String defaultSpace = authManager.getDefaultSpace(user);
-        SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
         for (String sp : spaces) {
             manager.getSpaceStorage(sp);
             GraphSpace gs = space(manager, sp);
@@ -135,8 +303,12 @@ public class GraphSpaceAPI extends API {
                 gsProfile.put("authed", true);
             }
 
-            gsProfile.put("create_time", format.format(gs.createTime()));
-            gsProfile.put("update_time", format.format(gs.updateTime()));
+            gsProfile.put("create_time",
+                          DATE_FORMATTER.format(LocalDateTime.ofInstant(
+                                  gs.createTime().toInstant(), ZoneId.systemDefault())));
+            gsProfile.put("update_time",
+                          DATE_FORMATTER.format(LocalDateTime.ofInstant(
+                                  gs.updateTime().toInstant(), ZoneId.systemDefault())));
             if (!isPrefix(gsProfile, prefix)) {
                 continue;
             }
@@ -175,15 +347,6 @@ public class GraphSpaceAPI extends API {
         return manager.serializer().writeGraphSpace(space);
     }
 
-    public boolean isPrefix(Map<String, Object> profile, String prefix) {
-        if (StringUtils.isEmpty(prefix)) {
-            return true;
-        }
-        // graph name or nickname is not empty
-        String name = profile.get("name").toString();
-        String nickname = profile.get("nickname").toString();
-        return name.startsWith(prefix) || nickname.startsWith(prefix);
-    }
 
     @PUT
     @Timed
@@ -225,13 +388,13 @@ public class GraphSpaceAPI extends API {
                 }
 
                 String nickname = (String) graphSpaceMap.get("nickname");
-                if (!Strings.isEmpty(nickname)) {
+                if (!StringUtils.isEmpty(nickname)) {
                     GraphManager.checkNickname(nickname);
                     exist.nickname(nickname);
                 }
 
                 String description = (String) graphSpaceMap.get("description");
-                if (!Strings.isEmpty(description)) {
+                if (!StringUtils.isEmpty(description)) {
                     exist.description(description);
                 }
 
@@ -272,31 +435,31 @@ public class GraphSpaceAPI extends API {
                 String oltpNamespace =
                         (String) graphSpaceMap.get("oltp_namespace");
                 if (oltpNamespace != null &&
-                    !Strings.isEmpty(oltpNamespace)) {
+                    !StringUtils.isEmpty(oltpNamespace)) {
                     exist.oltpNamespace(oltpNamespace);
                 }
                 String olapNamespace =
                         (String) graphSpaceMap.get("olap_namespace");
                 if (olapNamespace != null &&
-                    !Strings.isEmpty(olapNamespace)) {
+                    !StringUtils.isEmpty(olapNamespace)) {
                     exist.olapNamespace(olapNamespace);
                 }
                 String storageNamespace =
                         (String) graphSpaceMap.get("storage_namespace");
                 if (storageNamespace != null &&
-                    !Strings.isEmpty(storageNamespace)) {
+                    !StringUtils.isEmpty(storageNamespace)) {
                     exist.storageNamespace(storageNamespace);
                 }
 
                 String operatorImagePath = (String) graphSpaceMap
                         .getOrDefault("operator_image_path", "");
-                if (!Strings.isEmpty(operatorImagePath)) {
+                if (!StringUtils.isEmpty(operatorImagePath)) {
                     exist.operatorImagePath(operatorImagePath);
                 }
 
                 String internalAlgorithmImageUrl = (String) graphSpaceMap
                         .getOrDefault("internal_algorithm_image_url", "");
-                if (!Strings.isEmpty(internalAlgorithmImageUrl)) {
+                if (!StringUtils.isEmpty(internalAlgorithmImageUrl)) {
                     exist.internalAlgorithmImageUrl(internalAlgorithmImageUrl);
                 }
 
@@ -346,6 +509,18 @@ public class GraphSpaceAPI extends API {
                authManager.isSpaceMember(graphSpace, user);
     }
 
+    private static void validGraphSpace(GraphManager manager, String graphSpace) {
+        E.checkArgument(manager.graphSpace(graphSpace) != null,
+                        "The graph space '%s' does not exist", graphSpace);
+    }
+
+    private static void validGraph(GraphManager manager, String graphSpace,
+                                   String graph) {
+        E.checkArgument(manager.graph(graphSpace, graph) != null,
+                        "Graph '%s/%s' does not exist", graphSpace, graph);
+    }
+
+    @JsonIgnoreProperties(ignoreUnknown = true)
     private static class JsonGraphSpace implements Checkable {
 
         @JsonProperty("name")
diff --git a/hugegraph-server/hugegraph-api/src/main/java/org/apache/hugegraph/api/space/SchemaTemplateAPI.java b/hugegraph-server/hugegraph-api/src/main/java/org/apache/hugegraph/api/space/SchemaTemplateAPI.java
new file mode 100644
index 000000000..b2c151687
--- /dev/null
+++ b/hugegraph-server/hugegraph-api/src/main/java/org/apache/hugegraph/api/space/SchemaTemplateAPI.java
@@ -0,0 +1,221 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.hugegraph.api.space;
+
+import java.util.Date;
+import java.util.Objects;
+import java.util.Set;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.hugegraph.HugeException;
+import org.apache.hugegraph.api.API;
+import org.apache.hugegraph.api.filter.StatusFilter;
+import org.apache.hugegraph.auth.HugeGraphAuthProxy;
+import org.apache.hugegraph.core.GraphManager;
+import org.apache.hugegraph.define.Checkable;
+import org.apache.hugegraph.space.SchemaTemplate;
+import org.apache.hugegraph.util.E;
+import org.apache.hugegraph.util.Log;
+import org.slf4j.Logger;
+
+import com.codahale.metrics.annotation.Timed;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.google.common.collect.ImmutableMap;
+
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.annotation.security.RolesAllowed;
+import jakarta.inject.Singleton;
+import jakarta.ws.rs.Consumes;
+import jakarta.ws.rs.DELETE;
+import jakarta.ws.rs.ForbiddenException;
+import jakarta.ws.rs.GET;
+import jakarta.ws.rs.POST;
+import jakarta.ws.rs.PUT;
+import jakarta.ws.rs.Path;
+import jakarta.ws.rs.PathParam;
+import jakarta.ws.rs.Produces;
+import jakarta.ws.rs.core.Context;
+import jakarta.ws.rs.core.SecurityContext;
+
+@Path("graphspaces/{graphspace}/schematemplates")
+@Singleton
+@Tag(name = "SchemaTemplateAPI")
+public class SchemaTemplateAPI extends API {
+
+    private static final Logger LOG = Log.logger(SchemaTemplateAPI.class);
+
+    @GET
+    @Timed
+    @Produces(APPLICATION_JSON_WITH_CHARSET)
+    @RolesAllowed({"space_member", "$dynamic"})
+    public Object list(@Context GraphManager manager,
+                       @PathParam("graphspace") String graphSpace) {
+        LOG.debug("List all schema templates for graph space {}", graphSpace);
+        ensurePdModeEnabled(manager);
+
+        Set<String> templates = manager.schemaTemplates(graphSpace);
+        return ImmutableMap.of("schema_templates", templates);
+    }
+
+    @GET
+    @Timed
+    @Path("{name}")
+    @Produces(APPLICATION_JSON_WITH_CHARSET)
+    @RolesAllowed({"space_member", "$dynamic"})
+    public Object get(@Context GraphManager manager,
+                      @PathParam("graphspace") String graphSpace,
+                      @PathParam("name") String name) {
+        LOG.debug("Get schema template by name '{}' for graph space {}",
+                  name, graphSpace);
+        ensurePdModeEnabled(manager);
+
+        return manager.serializer().writeSchemaTemplate(
+                       schemaTemplate(manager, graphSpace, name));
+    }
+
+    @POST
+    @Timed
+    @StatusFilter.Status(StatusFilter.Status.CREATED)
+    @Consumes(APPLICATION_JSON)
+    @Produces(APPLICATION_JSON_WITH_CHARSET)
+    @RolesAllowed({"space_member", "$dynamic"})
+    public String create(@Context GraphManager manager,
+                         @PathParam("graphspace") String graphSpace,
+                         JsonSchemaTemplate jsonSchemaTemplate) {
+        LOG.debug("Create schema template {} for graph space: '{}'",
+                  jsonSchemaTemplate, graphSpace);
+        ensurePdModeEnabled(manager);
+        E.checkArgumentNotNull(jsonSchemaTemplate,
+                               "Request body cannot be null or empty");
+        jsonSchemaTemplate.checkCreate(false);
+
+        E.checkArgument(manager.graphSpace(graphSpace) != null,
+                        "The graph space '%s' is not exist", graphSpace);
+
+        SchemaTemplate template = jsonSchemaTemplate.toSchemaTemplate();
+        template.create(new Date());
+        template.creator(HugeGraphAuthProxy.username());
+        manager.createSchemaTemplate(graphSpace, template);
+        return manager.serializer().writeSchemaTemplate(template);
+    }
+
+    @DELETE
+    @Timed
+    @Path("{name}")
+    @Produces(APPLICATION_JSON_WITH_CHARSET)
+    @RolesAllowed({"space_member", "$dynamic"})
+    public void delete(@Context GraphManager manager,
+                       @PathParam("graphspace") String graphSpace,
+                       @PathParam("name") String name,
+                       @Context SecurityContext sc) {
+        LOG.debug("Remove schema template by name '{}' for graph space '{}'",
+                  name, graphSpace);
+        ensurePdModeEnabled(manager);
+        E.checkArgument(manager.graphSpace(graphSpace) != null,
+                        "The graph space '%s' is not exist", graphSpace);
+
+        SchemaTemplate st = schemaTemplate(manager, graphSpace, name);
+        E.checkArgument(st != null,
+                        "Schema template '%s' does not exist", name);
+
+        String username = HugeGraphAuthProxy.username();
+        boolean isSpace = manager.authManager()
+                                 .isSpaceManager(graphSpace, username);
+        if (Objects.equals(st.creator(), username) || isSpace) {
+            manager.dropSchemaTemplate(graphSpace, name);
+        } else {
+            throw new ForbiddenException("No permission to delete schema template");
+        }
+    }
+
+    @PUT
+    @Timed
+    @Path("{name}")
+    @Consumes(APPLICATION_JSON)
+    @Produces(APPLICATION_JSON_WITH_CHARSET)
+    @RolesAllowed({"space_member", "$dynamic"})
+    public String update(@Context GraphManager manager,
+                       @PathParam("graphspace") String graphSpace,
+                       @PathParam("name") String name,
+                       @Context SecurityContext sc,
+                       JsonSchemaTemplate jsonSchemaTemplate) {
+        ensurePdModeEnabled(manager);
+        E.checkArgumentNotNull(jsonSchemaTemplate,
+                               "Request body cannot be null or empty");
+        jsonSchemaTemplate.checkUpdate();
+
+        SchemaTemplate old = schemaTemplate(manager, graphSpace, name);
+        if (null == old) {
+            throw new HugeException("Schema template '%s' does not exist", name);
+        }
+
+        String username = HugeGraphAuthProxy.username();
+        boolean isSpace = manager.authManager()
+                                 .isSpaceManager(graphSpace, username);
+        if (Objects.equals(old.creator(), username) || isSpace) {
+            SchemaTemplate template = jsonSchemaTemplate.build(old);
+            template.creator(old.creator());
+            template.create(old.create());
+            template.refreshUpdateTime();
+
+            manager.updateSchemaTemplate(graphSpace, template);
+            return manager.serializer().writeSchemaTemplate(template);
+        }
+        throw new ForbiddenException("No permission to update schema template");
+
+    }
+
+    private static class JsonSchemaTemplate implements Checkable {
+
+        @JsonProperty("name")
+        public String name;
+        @JsonProperty("schema")
+        public String schema;
+
+        @Override
+        public void checkCreate(boolean isBatch) {
+            E.checkArgument(this.name != null && !this.name.isEmpty(),
+                            "The name of schema template can't be null or " +
+                            "empty");
+
+            E.checkArgument(this.schema != null && !this.schema.isEmpty(),
+                            "The schema can't be null or empty");
+        }
+
+        @Override
+        public void checkUpdate() {
+            E.checkArgument(this.schema != null && !this.schema.isEmpty(),
+                            "The schema can't be null or empty");
+        }
+
+        public SchemaTemplate toSchemaTemplate() {
+            return new SchemaTemplate(this.name, this.schema);
+        }
+
+        public SchemaTemplate build(SchemaTemplate old) {
+            E.checkArgument(StringUtils.isEmpty(this.name) || old.name().equals(this.name),
+                            "The name of template can't be updated");
+            return new SchemaTemplate(old.name(), this.schema);
+        }
+
+        public String toString() {
+            return String.format("JsonSchemaTemplate{name=%s, schema=%s}",
+                                 this.name, this.schema);
+        }
+    }
+}
diff --git a/hugegraph-server/hugegraph-api/src/main/java/org/apache/hugegraph/auth/HugeGraphAuthProxy.java b/hugegraph-server/hugegraph-api/src/main/java/org/apache/hugegraph/auth/HugeGraphAuthProxy.java
index 5b117faae..8dabfe061 100644
--- a/hugegraph-server/hugegraph-api/src/main/java/org/apache/hugegraph/auth/HugeGraphAuthProxy.java
+++ b/hugegraph-server/hugegraph-api/src/main/java/org/apache/hugegraph/auth/HugeGraphAuthProxy.java
@@ -24,6 +24,7 @@ import java.util.Collections;
 import java.util.Date;
 import java.util.Iterator;
 import java.util.List;
+import java.util.Map;
 import java.util.Objects;
 import java.util.Optional;
 import java.util.Set;
@@ -2007,6 +2008,70 @@ public final class HugeGraphAuthProxy implements HugeGraph {
             return this.authManager.findGroup(name);
         }
 
+        @Override
+        public void setDefaultGraph(String graphSpace, String graph, String user) {
+            this.authManager.setDefaultGraph(graphSpace, graph, user);
+        }
+
+        @Override
+        public void unsetDefaultGraph(String graphSpace, String graph, String user) {
+            this.authManager.unsetDefaultGraph(graphSpace, graph, user);
+        }
+
+        @Override
+        public Map<String, Date> getDefaultGraph(String graphSpace, String user) {
+            return this.authManager.getDefaultGraph(graphSpace, user);
+        }
+
+        @Override
+        public Id createDefaultRole(String graphSpace, String owner, HugeDefaultRole role,
+                                    String graph) {
+            try {
+                return this.authManager.createDefaultRole(graphSpace, owner, role, graph);
+            } finally {
+                this.invalidRoleCache();
+            }
+        }
+
+        @Override
+        public Id createSpaceDefaultRole(String graphSpace, String owner, HugeDefaultRole role) {
+            try {
+                return this.authManager.createSpaceDefaultRole(graphSpace, owner, role);
+            } finally {
+                this.invalidRoleCache();
+            }
+        }
+
+        @Override
+        public boolean isDefaultRole(String graphSpace, String owner, HugeDefaultRole role) {
+            return this.authManager.isDefaultRole(graphSpace, owner, role);
+        }
+
+        @Override
+        public boolean isDefaultRole(String graphSpace, String graph, String owner,
+                                     HugeDefaultRole role) {
+            return this.authManager.isDefaultRole(graphSpace, graph, owner, role);
+        }
+
+        @Override
+        public void deleteDefaultRole(String graphSpace, String owner, HugeDefaultRole role) {
+            try {
+                this.authManager.deleteDefaultRole(graphSpace, owner, role);
+            } finally {
+                this.invalidRoleCache();
+            }
+        }
+
+        @Override
+        public void deleteDefaultRole(String graphSpace, String owner, HugeDefaultRole role,
+                                      String graph) {
+            try {
+                this.authManager.deleteDefaultRole(graphSpace, owner, role, graph);
+            } finally {
+                this.invalidRoleCache();
+            }
+        }
+
         @Override
         public String loginUser(String username, String password) {
             return this.loginUser(username, password, -1L);
diff --git a/hugegraph-server/hugegraph-api/src/main/java/org/apache/hugegraph/config/ServerOptions.java b/hugegraph-server/hugegraph-api/src/main/java/org/apache/hugegraph/config/ServerOptions.java
index 1aed0434d..7d706de8f 100644
--- a/hugegraph-server/hugegraph-api/src/main/java/org/apache/hugegraph/config/ServerOptions.java
+++ b/hugegraph-server/hugegraph-api/src/main/java/org/apache/hugegraph/config/ServerOptions.java
@@ -659,7 +659,7 @@ public class ServerOptions extends OptionHolder {
                     "memory_monitor.period",
                     "The period in ms of JVM memory usage monitoring, in each period we will " +
                     "detect the jvm memory usage and take corresponding actions.",
-                    nonNegativeInt(),
+                    positiveInt(),
                     2000
             );
     public static ConfigOption<String> K8S_INTERNAL_ALGORITHM_IMAGE_URL =
diff --git a/hugegraph-server/hugegraph-api/src/main/java/org/apache/hugegraph/core/GraphManager.java b/hugegraph-server/hugegraph-api/src/main/java/org/apache/hugegraph/core/GraphManager.java
index ff0435964..362a2f9a3 100644
--- a/hugegraph-server/hugegraph-api/src/main/java/org/apache/hugegraph/core/GraphManager.java
+++ b/hugegraph-server/hugegraph-api/src/main/java/org/apache/hugegraph/core/GraphManager.java
@@ -2110,6 +2110,19 @@ public final class GraphManager {
         for (String key : this.metaManager.graphConfigs(graphSpace).keySet()) {
             graphs.add(key.split(DELIMITER)[1]);
         }
+        if (DEFAULT_GRAPH_SPACE_SERVICE_NAME.equals(graphSpace)) {
+            String prefix = spaceGraphName(graphSpace, "");
+            for (String key : this.graphs.keySet()) {
+                if (!key.startsWith(prefix)) {
+                    continue;
+                }
+                String graph = key.substring(prefix.length());
+                if (SYS_GRAPH.equals(graph)) {
+                    continue;
+                }
+                graphs.add(graph);
+            }
+        }
         return graphs;
     }
 
@@ -2119,7 +2132,14 @@ public final class GraphManager {
         }
         GraphSpace space = this.graphSpaces.get(name);
         if (space == null) {
+            // Cache miss: try to load from etcd and populate the local cache
+            // so that subsequent calls (e.g. validGraphSpace checks) don't fail
+            // due to a race between the graphspace-create event and the listener
+            // updating this.graphSpaces.
             space = this.metaManager.graphSpace(name);
+            if (space != null) {
+                this.graphSpaces.putIfAbsent(name, space);
+            }
         }
         return space;
     }
@@ -2147,9 +2167,27 @@ public final class GraphManager {
         if (StringUtils.isEmpty(nickname)) {
             return false;
         }
+        if (!isPDEnabled()) {
+            // In non-PD mode, metaManager is not initialized.
+            // Check nickname against in-memory graphs (same pattern as graphs()).
+            for (Map.Entry<String, Graph> entry : this.graphs.entrySet()) {
+                String key = entry.getKey();
+                String[] parts = key.split(DELIMITER, 2);
+                if (parts.length == 2 && parts[0].equals(graphSpace) &&
+                    entry.getValue() instanceof HugeGraph) {
+                    HugeGraph hg = (HugeGraph) entry.getValue();
+                    if (nickname.equals(hg.nickname())) {
+                        return true;
+                    }
+                }
+            }
+            return false;
+        }
+        // PD mode: check via metaManager
         for (Map<String, Object> graphConfig :
                 this.metaManager.graphConfigs(graphSpace).values()) {
-            if (nickname.equals(graphConfig.get("nickname").toString())) {
+            Object nick = graphConfig.get("nickname");
+            if (nick != null && nickname.equals(nick.toString())) {
                 return true;
             }
         }
@@ -2185,6 +2223,50 @@ public final class GraphManager {
         return this.metaManager.getGraphConfig(graphSpace, graphName);
     }
 
+    /**
+     * Update the nickname of a graph.
+     * In non-PD mode (standalone RocksDB), only the in-memory instance is updated
+     * since local config files cannot be hot-reloaded.
+     * In PD mode, the change is also persisted to the meta storage so it
+     * survives restarts.
+     */
+    public void updateGraphNickname(String graphSpace, String graphName,
+                                    String nickname) {
+        // Always update the in-memory graph instance first
+        HugeGraph g = this.graph(graphSpace, graphName);
+        // Capture the old nickname so we can restore it on failure
+        String oldNickname = g != null ? g.nickname() : null;
+        if (g != null) {
+            g.nickname(nickname);
+        }
+        if (!isPDEnabled()) {
+            // Non-PD mode: in-memory only, acceptable for standalone RocksDB
+            return;
+        }
+        Map<String, Object> configs;
+        try {
+            configs = this.metaManager.getGraphConfig(graphSpace, graphName);
+            if (configs == null) {
+                return;
+            }
+            configs.put("nickname", nickname);
+            this.metaManager.updateGraphConfig(graphSpace, graphName, configs);
+        } catch (Exception e) {
+            // Roll back only if the PD metadata write did not succeed.
+            if (g != null) {
+                g.nickname(oldNickname);
+            }
+            throw new HugeException("Failed to persist nickname for graph " +
+                                    "'%s/%s'", e, graphSpace, graphName);
+        }
+        try {
+            this.metaManager.notifyGraphUpdate(graphSpace, graphName);
+        } catch (Exception e) {
+            LOG.warn("Failed to notify graph nickname update for graph '{}/{}'",
+                     graphSpace, graphName, e);
+        }
+    }
+
     public String pdPeers() {
         return this.pdPeers;
     }
@@ -2193,6 +2275,43 @@ public final class GraphManager {
         return this.cluster;
     }
 
+    public Set<String> schemaTemplates(String graphSpace) {
+        if (!isPDEnabled()) {
+            throw new HugeException("Schema templates are not supported in " +
+                                    "standalone (non-PD) mode");
+        }
+        return this.metaManager.schemaTemplates(graphSpace);
+    }
+
+    public void createSchemaTemplate(String graphSpace, SchemaTemplate template) {
+        if (!isPDEnabled()) {
+            throw new HugeException("Schema templates are not supported in " +
+                                    "standalone (non-PD) mode");
+        }
+        checkSchemaTemplateName(template.name());
+        this.metaManager.addSchemaTemplate(graphSpace, template);
+    }
+
+    public void dropSchemaTemplate(String graphSpace, String name) {
+        if (!isPDEnabled()) {
+            throw new HugeException("Schema templates are not supported in " +
+                                    "standalone (non-PD) mode");
+        }
+        this.metaManager.removeSchemaTemplate(graphSpace, name);
+    }
+
+    public void updateSchemaTemplate(String graphSpace, SchemaTemplate template) {
+        if (!isPDEnabled()) {
+            throw new HugeException("Schema templates are not supported in " +
+                                    "standalone (non-PD) mode");
+        }
+        this.metaManager.updateSchemaTemplate(graphSpace, template);
+    }
+
+    private static void checkSchemaTemplateName(String name) {
+        checkName(name, "schema template");
+    }
+
     private enum PdRegisterType {
         NODE_PORT,
         DDS
diff --git a/hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/auth/AuthManager.java b/hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/auth/AuthManager.java
index af0027c02..8b36dd9be 100644
--- a/hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/auth/AuthManager.java
+++ b/hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/auth/AuthManager.java
@@ -17,7 +17,9 @@
 
 package org.apache.hugegraph.auth;
 
+import java.util.Date;
 import java.util.List;
+import java.util.Map;
 import java.util.Set;
 
 import javax.security.sasl.AuthenticationException;
@@ -164,4 +166,28 @@ public interface AuthManager {
     boolean isAdminManager(String user);
 
     HugeGroup findGroup(String name);
+
+    void setDefaultGraph(String graphSpace, String graph, String user);
+
+    void unsetDefaultGraph(String graphSpace, String graph, String user);
+
+    Map<String, Date> getDefaultGraph(String graphSpace, String user);
+
+    Id createDefaultRole(String graphSpace, String owner,
+                         HugeDefaultRole role, String graph);
+
+    Id createSpaceDefaultRole(String graphSpace, String owner,
+                              HugeDefaultRole role);
+
+    boolean isDefaultRole(String graphSpace, String owner,
+                          HugeDefaultRole role);
+
+    boolean isDefaultRole(String graphSpace, String graph, String owner,
+                          HugeDefaultRole role);
+
+    void deleteDefaultRole(String graphSpace, String owner,
+                           HugeDefaultRole role);
+
+    void deleteDefaultRole(String graphSpace, String owner,
+                           HugeDefaultRole role, String graph);
 }
diff --git a/hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/auth/HugeUser.java b/hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/auth/HugeUser.java
index 064e32656..8a90de0b1 100644
--- a/hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/auth/HugeUser.java
+++ b/hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/auth/HugeUser.java
@@ -146,6 +146,9 @@ public class HugeUser extends Entity {
             case P.NAME:
                 this.name = (String) value;
                 break;
+            case P.NICKNAME:
+                this.nickname = (String) value;
+                break;
             case P.PASSWORD:
                 this.password = (String) value;
                 break;
@@ -172,7 +175,7 @@ public class HugeUser extends Entity {
         E.checkState(this.name != null, "User name can't be null");
         E.checkState(this.password != null, "User password can't be null");
 
-        List<Object> list = new ArrayList<>(18);
+        List<Object> list = new ArrayList<>(20);
 
         list.add(T.label);
         list.add(P.USER);
@@ -183,6 +186,11 @@ public class HugeUser extends Entity {
         list.add(P.PASSWORD);
         list.add(this.password);
 
+        if (this.nickname != null) {
+            list.add(P.NICKNAME);
+            list.add(this.nickname);
+        }
+
         if (this.phone != null) {
             list.add(P.PHONE);
             list.add(this.phone);
@@ -216,6 +224,10 @@ public class HugeUser extends Entity {
         map.put(Hidden.unHide(P.NAME), this.name);
         map.put(Hidden.unHide(P.PASSWORD), this.password);
 
+        if (this.nickname != null) {
+            map.put(Hidden.unHide(P.NICKNAME), this.nickname);
+        }
+
         if (this.phone != null) {
             map.put(Hidden.unHide(P.PHONE), this.phone);
         }
@@ -253,6 +265,7 @@ public class HugeUser extends Entity {
         public static final String LABEL = T.label.getAccessor();
 
         public static final String NAME = "~user_name";
+        public static final String NICKNAME = "~user_nickname";
         public static final String PASSWORD = "~user_password";
         public static final String PHONE = "~user_phone";
         public static final String EMAIL = "~user_email";
@@ -277,6 +290,8 @@ public class HugeUser extends Entity {
         @Override
         public void initSchemaIfNeeded() {
             if (this.existVertexLabel(this.label)) {
+                // Schema already exists: do incremental upgrade for new properties
+                upgradeSchemaIfNeeded();
                 return;
             }
 
@@ -287,16 +302,29 @@ public class HugeUser extends Entity {
                                     .properties(properties)
                                     .usePrimaryKeyId()
                                     .primaryKeys(P.NAME)
-                                    .nullableKeys(P.PHONE, P.EMAIL, P.AVATAR, P.DESCRIPTION)
+                                    .nullableKeys(P.NICKNAME, P.PHONE, P.EMAIL, P.AVATAR, P.DESCRIPTION)
                                     .enableLabelIndex(true)
                                     .build();
             this.graph.schemaTransaction().addVertexLabel(label);
         }
 
+        private void upgradeSchemaIfNeeded() {
+            // Add user_nickname property key if missing (new in this version)
+            if (!this.graph.graph().existsPropertyKey(P.NICKNAME)) {
+                createPropertyKey(P.NICKNAME);
+                VertexLabel vl = this.graph.graph().vertexLabel(this.label);
+                Id nickname = this.graph.graph().propertyKey(P.NICKNAME).id();
+                vl.property(nickname);
+                vl.nullableKey(nickname);
+                this.graph.schemaTransaction().updateVertexLabel(vl);
+            }
+        }
+
         private String[] initProperties() {
             List<String> props = new ArrayList<>();
 
             props.add(createPropertyKey(P.NAME));
+            props.add(createPropertyKey(P.NICKNAME));
             props.add(createPropertyKey(P.PASSWORD));
             props.add(createPropertyKey(P.PHONE));
             props.add(createPropertyKey(P.EMAIL));
diff --git a/hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/auth/StandardAuthManager.java b/hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/auth/StandardAuthManager.java
index a3224811f..cac9ba53d 100644
--- a/hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/auth/StandardAuthManager.java
+++ b/hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/auth/StandardAuthManager.java
@@ -19,6 +19,7 @@ package org.apache.hugegraph.auth;
 
 import java.time.Duration;
 import java.util.ArrayList;
+import java.util.Date;
 import java.util.HashSet;
 import java.util.LinkedHashMap;
 import java.util.List;
@@ -810,9 +811,205 @@ public class StandardAuthManager implements AuthManager {
 
     @Override
     public HugeGroup findGroup(String name) {
+        for (HugeGroup group : this.groups.list(-1L)) {
+            if (name.equals(group.name())) {
+                return group;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * <h3>Design Note: Default graph / default role persistence (workaround)</h3>
+     *
+     * <p>We reuse the existing {@code HugeGroup} / {@code HugeBelong} auth entities as a storage
+     * mechanism to avoid introducing new schema or storage APIs.
+     *
+     * <p><b>How it works:</b>
+     * <ul>
+     * <li>A "marker group" with a special prefixed name (e.g. {@code "~default_graph:gs:g"})
+     * is created in the system graph to represent a (graphSpace, graph) binding.</li>
+     * <li>A {@code HugeBelong} edge from the user vertex to this marker group records which
+     * user has set which graph as their default.</li>
+     * <li>The Belong ID is deterministic: {@code "{userId}->ug->{groupId}"}, which lets us
+     * check existence without a full scan.</li>
+     * </ul>
+     *
+     * <p><b>Known limitations:</b>
+     * <ol>
+     * <li>These marker groups appear in {@code listGroups()} results and may confuse callers.</li>
+     * <li>This mechanism only works in <b>PD mode</b> (system graph backed by HStore).
+     * In non-PD (standalone RocksDB) mode, the API layer degrades gracefully
+     * by returning empty results (see {@code GraphsAPI.setDefault / getDefault}).</li>
+     * <li>The belong ID format depends on the internal convention {@code "{u}->ug->{g}"}.</li>
+     * </ol>
+     *
+     * @todo Consider introducing a dedicated lightweight KV store or a separate
+     * vertex label for user-preference data to avoid polluting the auth graph.
+     */
+    private static final String DEFAULT_GRAPH_MARKER = "~default_graph";
+
+    private String defaultGraphGroupName(String graphSpace, String graph) {
+        return DEFAULT_GRAPH_MARKER + ":" + graphSpace + ":" + graph;
+    }
+
+    @Override
+    public void setDefaultGraph(String graphSpace, String graph, String user) {
+        // Use a special-named HugeGroup as a marker for the default graph,
+        // then create a HugeBelong (user -> marker-group) to persist the binding.
+        String markerName = defaultGraphGroupName(graphSpace, graph);
+        Id groupId = ensureMarkerGroup(markerName, user);
+        createBelongBinding(user, groupId);
+    }
+
+    @Override
+    public void unsetDefaultGraph(String graphSpace, String graph, String user) {
+        String markerName = defaultGraphGroupName(graphSpace, graph);
+        Id groupId = IdGenerator.of(markerName);
+        removeBelongBinding(user, groupId);
+    }
+
+    @Override
+    public Map<String, Date> getDefaultGraph(String graphSpace, String user) {
+        Id userId = IdGenerator.of(user);
+        List<HugeBelong> belongs = this.belong.list(userId, Directions.OUT,
+                                                    HugeBelong.P.BELONG, -1);
+        String prefix = DEFAULT_GRAPH_MARKER + ":" + graphSpace + ":";
+        Map<String, Date> result = new LinkedHashMap<>();
+        for (HugeBelong b : belongs) {
+            String targetName = b.target().asString();
+            if (targetName.startsWith(prefix)) {
+                String graphName = targetName.substring(prefix.length());
+                result.put(graphName, b.update());
+            }
+        }
+        return result;
+    }
+
+    private static final String DEFAULT_ROLE_MARKER = "~default_role";
+
+    /**
+     * Build marker group name for a space-level role.
+     * Format: ~default_role:<graphSpace>:<role>
+     */
+    private String defaultRoleGroupName(String graphSpace, String role) {
+        return DEFAULT_ROLE_MARKER + ":" + graphSpace + ":" + role;
+    }
+
+    /**
+     * Build marker group name for a graph-level role (e.g. OBSERVER).
+     * Format: ~default_role:<graphSpace>:<role>:<graph>
+     */
+    private String defaultRoleGroupName(String graphSpace, String role,
+                                        String graph) {
+        return DEFAULT_ROLE_MARKER + ":" + graphSpace + ":" + role +
+               ":" + graph;
+    }
+
+    private Id ensureMarkerGroup(String markerName, String creator) {
+        Id groupId = IdGenerator.of(markerName);
+        HugeGroup markerGroup = this.findGroup(markerName);
+        if (markerGroup != null) {
+            return groupId;
+        }
+        markerGroup = new HugeGroup(markerName);
+        markerGroup.creator(creator);
+        this.groups.add(markerGroup);
+        return groupId;
+    }
+
+    private HugeBelong findBelongBinding(String owner, Id groupId) {
+        Id userId = IdGenerator.of(owner);
+        List<HugeBelong> belongs = this.belong.list(userId, Directions.OUT,
+                                                     HugeBelong.P.BELONG, -1);
+        for (HugeBelong b : belongs) {
+            if (groupId.equals(b.target())) {
+                return b;
+            }
+        }
         return null;
     }
 
+    private Id createBelongBinding(String owner, Id groupId) {
+        HugeBelong existing = findBelongBinding(owner, groupId);
+        if (existing != null) {
+            return existing.id();
+        }
+        Id userId = IdGenerator.of(owner);
+        HugeBelong belong = new HugeBelong(userId, groupId);
+        belong.creator(owner);
+        Id id = this.belong.add(belong);
+        this.invalidateUserCache();
+        return id;
+    }
+
+    private void removeBelongBinding(String owner, Id groupId) {
+        HugeBelong existing = findBelongBinding(owner, groupId);
+        if (existing != null) {
+            this.belong.delete(existing.id());
+            this.invalidateUserCache();
+        }
+    }
+
+    private boolean existsBelongBinding(String owner, Id groupId) {
+        return findBelongBinding(owner, groupId) != null;
+    }
+
+    @Override
+    public Id createDefaultRole(String graphSpace, String owner,
+                                HugeDefaultRole role, String graph) {
+        LOG.debug("Create default role: {} {} {} {}", owner, role,
+                  graphSpace, graph);
+        String markerName = defaultRoleGroupName(graphSpace,
+                                                  role.toString(), graph);
+        Id groupId = ensureMarkerGroup(markerName, owner);
+        return createBelongBinding(owner, groupId);
+    }
+
+    @Override
+    public Id createSpaceDefaultRole(String graphSpace, String owner,
+                                     HugeDefaultRole role) {
+        LOG.debug("Create space default role: {} {} {}", owner, role,
+                  graphSpace);
+        String markerName = defaultRoleGroupName(graphSpace, role.toString());
+        Id groupId = ensureMarkerGroup(markerName, owner);
+        return createBelongBinding(owner, groupId);
+    }
+
+    @Override
+    public boolean isDefaultRole(String graphSpace, String owner,
+                                 HugeDefaultRole role) {
+        String markerName = defaultRoleGroupName(graphSpace, role.toString());
+        Id groupId = IdGenerator.of(markerName);
+        return existsBelongBinding(owner, groupId);
+    }
+
+    @Override
+    public boolean isDefaultRole(String graphSpace, String graph,
+                                 String owner, HugeDefaultRole role) {
+        String markerName = defaultRoleGroupName(graphSpace,
+                                                  role.toString(), graph);
+        Id groupId = IdGenerator.of(markerName);
+        return existsBelongBinding(owner, groupId);
+    }
+
+    @Override
+    public void deleteDefaultRole(String graphSpace, String owner,
+                                  HugeDefaultRole role) {
+        String markerName = defaultRoleGroupName(graphSpace, role.toString());
+        Id groupId = IdGenerator.of(markerName);
+        removeBelongBinding(owner, groupId);
+    }
+
+    @Override
+    public void deleteDefaultRole(String graphSpace, String owner,
+                                  HugeDefaultRole role, String graph) {
+        String markerName = defaultRoleGroupName(graphSpace,
+                                                  role.toString(), graph);
+        Id groupId = IdGenerator.of(markerName);
+        removeBelongBinding(owner, groupId);
+    }
+
     public <R> R commit(Callable<R> callable) {
         this.groups.autoCommit(false);
         this.access.autoCommit(false);
diff --git a/hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/auth/StandardAuthManagerV2.java b/hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/auth/StandardAuthManagerV2.java
index d2df45626..30cb05c1f 100644
--- a/hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/auth/StandardAuthManagerV2.java
+++ b/hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/auth/StandardAuthManagerV2.java
@@ -21,6 +21,7 @@ import java.io.IOException;
 import java.time.Duration;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Date;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.LinkedHashMap;
@@ -110,9 +111,9 @@ public class StandardAuthManagerV2 implements AuthManager {
      * Update creator from current context (from TaskManager ThreadLocal or direct call)
      */
     private AuthElement updateCreator(AuthElement elem) {
-        String username = currentUsername();
-        if (username != null && elem.creator() == null) {
-            elem.creator(username);
+        if (elem.creator() == null) {
+            String username = currentUsername();
+            elem.creator(username != null ? username : "system");
         }
         return elem;
     }
@@ -200,6 +201,7 @@ public class StandardAuthManagerV2 implements AuthManager {
 
         try {
             user.create(user.update());
+            updateCreator(user);
             this.metaManager.createUser(user);
 
             // Update cache after successful creation
@@ -374,6 +376,7 @@ public class StandardAuthManagerV2 implements AuthManager {
     public Id createGroup(HugeGroup group) {
         try {
             group.create(group.update());
+            updateCreator(group);
             this.metaManager.createGroup(group);
             Id result = IdGenerator.of(group.name());
             group.id(result);
@@ -461,6 +464,7 @@ public class StandardAuthManagerV2 implements AuthManager {
     public Id createTarget(HugeTarget target) {
         try {
             target.create(target.update());
+            updateCreator(target);
             Id result = this.metaManager.createTarget(graphSpace, target);
             this.invalidateUserCache();
             return result;
@@ -548,6 +552,7 @@ public class StandardAuthManagerV2 implements AuthManager {
     public Id createBelong(HugeBelong belong) {
         try {
             belong.create(belong.update());
+            updateCreator(belong);
             this.invalidateUserCache();
             return this.metaManager.createBelong(graphSpace, belong);
         } catch (IOException e) {
@@ -662,6 +667,7 @@ public class StandardAuthManagerV2 implements AuthManager {
     public Id createAccess(HugeAccess access) {
         try {
             access.create(access.update());
+            updateCreator(access);
             Id result = this.metaManager.createAccess(graphSpace, access);
             this.invalidateUserCache();
             return result;
@@ -1510,6 +1516,218 @@ public class StandardAuthManagerV2 implements AuthManager {
         return result;
     }
 
+    @Override
+    public void setDefaultGraph(String graphSpace, String graph, String user) {
+        try {
+            String role = graph + DEFAULT_SETTER_ROLE_KEY;
+            String belongId = this.metaManager.belongId(user, role);
+            Id id = IdGenerator.of(belongId);
+            // Idempotent: if binding already exists, treat as success
+            if (this.metaManager.existBelong(graphSpace, id)) {
+                return;
+            }
+            HugeBelong belong = new HugeBelong(graphSpace,
+                                               IdGenerator.of(user),
+                                               IdGenerator.of(graph +
+                                                              DEFAULT_SETTER_ROLE_KEY));
+            this.tryInitDefaultGraph(graphSpace, graph);
+            this.updateCreator(belong);
+            belong.create(belong.update());
+            this.metaManager.createBelong(graphSpace, belong);
+            this.invalidateUserCache();
+        } catch (Exception e) {
+            throw new HugeException("Exception occurs when " +
+                                    "set default graph", e);
+        }
+    }
+
+    @Override
+    public void unsetDefaultGraph(String graphSpace, String graph, String user) {
+        String role = graph + DEFAULT_SETTER_ROLE_KEY;
+        String belongId = this.metaManager.belongId(user, role);
+        Id id = IdGenerator.of(belongId);
+        // Check if belong exists before attempting to delete to make this operation idempotent
+        if (!this.metaManager.existBelong(graphSpace, id)) {
+            // Already unset, treat as success (idempotent behavior)
+            return;
+        }
+        try {
+            this.metaManager.deleteBelong(graphSpace, id);
+            this.invalidateUserCache();
+        } catch (Exception e) {
+            throw new HugeException("Exception occurs when unset default " +
+                                    "graph", e);
+        }
+    }
+
+    @Override
+    public Map<String, Date> getDefaultGraph(String graphSpace, String user) {
+        List<HugeBelong> belongs = this.listBelongBySource(graphSpace,
+                                                           IdGenerator.of(user),
+                                                           HugeBelong.UR, -1);
+        Map<String, Date> map = new HashMap<>();
+        for (HugeBelong belong : belongs) {
+            String role = belong.target().asString();
+            if (role.endsWith(DEFAULT_SETTER_ROLE_KEY) &&
+                role.length() != DEFAULT_SETTER_ROLE_KEY.length()) {
+                map.put(role.substring(0, role.lastIndexOf(
+                        DEFAULT_SETTER_ROLE_KEY)), belong.update());
+            }
+        }
+        return map;
+    }
+
+    @Override
+    public Id createDefaultRole(String graphSpace, String owner,
+                                HugeDefaultRole role, String graph) {
+        String roleName = (role.isGraphRole()) ?
+                getGraphDefaultRole(graph, role.toString()) : role.toString();
+        try {
+            HugeBelong belong;
+            String link;
+            HugeUser user = this.findUser(owner);
+            HugeGroup group = this.findGroup(owner);
+            if (group != null) {
+                belong = new HugeBelong(graphSpace, null,
+                                        IdGenerator.of(owner),
+                                        IdGenerator.of(roleName),
+                                        HugeBelong.GR);
+                link = HugeBelong.GR;
+            } else if (user != null) {
+                belong = new HugeBelong(graphSpace, IdGenerator.of(owner),
+                                        null, IdGenerator.of(roleName),
+                                        HugeBelong.UR);
+                link = HugeBelong.UR;
+            } else {
+                throw new HugeException("The user or group is not exist");
+            }
+
+            // Idempotent: if binding already exists, treat as success
+            String belongId = this.metaManager.belongId(owner, roleName, link);
+            if (this.metaManager.existBelong(graphSpace, IdGenerator.of(belongId))) {
+                return IdGenerator.of(belongId);
+            }
+
+            this.tryInitDefaultRole(graphSpace, roleName, graph);
+            this.updateCreator(belong);
+            belong.create(belong.update());
+            Id result = this.metaManager.createBelong(graphSpace, belong);
+            this.invalidateUserCache();
+            return result;
+        } catch (Exception e) {
+            throw new HugeException("Exception occurs when " +
+                                    "create " + role + ".", e);
+        }
+    }
+
+    @Override
+    public Id createSpaceDefaultRole(String graphSpace, String owner,
+                                     HugeDefaultRole role) {
+        return createDefaultRole(graphSpace, owner, role, ALL_GRAPHS);
+    }
+
+    @Override
+    public boolean isDefaultRole(String graphSpace, String owner,
+                                 HugeDefaultRole role) {
+        return isDefaultRole(graphSpace, owner, role.toString());
+    }
+
+    @Override
+    public boolean isDefaultRole(String graphSpace, String graph,
+                                 String owner, HugeDefaultRole role) {
+        String roleName = getGraphDefaultRole(graph, role.toString());
+        return isDefaultRole(graphSpace, owner, roleName);
+    }
+
+    @Override
+    public void deleteDefaultRole(String graphSpace, String owner,
+                                  HugeDefaultRole role) {
+        deleteDefaultRoleByName(graphSpace, owner, role.toString());
+    }
+
+    @Override
+    public void deleteDefaultRole(String graphSpace, String owner,
+                                  HugeDefaultRole role, String graph) {
+        String roleName = getGraphDefaultRole(graph, role.toString());
+        deleteDefaultRoleByName(graphSpace, owner, roleName);
+    }
+
+    private boolean isDefaultRole(String graphSpace, String owner,
+                                  String role) {
+        try {
+            String belongId;
+            if (this.findGroup(owner) != null) {
+                belongId = this.metaManager.belongId(owner, role,
+                                                     HugeBelong.GR);
+                return this.metaManager.existBelong(graphSpace,
+                                                    IdGenerator.of(belongId));
+            }
+
+            List<HugeGroup> groups = this.listGroupsByUser(owner, -1);
+            for (HugeGroup group : groups) {
+                String belongIdG = this.metaManager.belongId(group.name(),
+                                                             role,
+                                                             HugeBelong.GR);
+                if (this.metaManager.existBelong(graphSpace,
+                                                 IdGenerator.of(belongIdG))) {
+                    return true;
+                }
+            }
+
+            belongId = this.metaManager.belongId(owner, role);
+            return this.metaManager.existBelong(graphSpace,
+                                                IdGenerator.of(belongId));
+        } catch (Exception e) {
+            throw new HugeException("Exception occurs when check if is " +
+                                    role + ".", e);
+        }
+    }
+
+    private void deleteDefaultRoleByName(String graphSpace, String owner,
+                                         String role) {
+        try {
+            String belongId;
+            if (this.findGroup(owner) != null) {
+                belongId = this.metaManager.belongId(owner, role,
+                                                     HugeBelong.GR);
+            } else {
+                belongId = this.metaManager.belongId(owner, role,
+                                                     HugeBelong.UR);
+            }
+            Id id = IdGenerator.of(belongId);
+            if (!this.metaManager.existBelong(graphSpace, id)) {
+                return;
+            }
+            this.metaManager.deleteBelong(graphSpace, id);
+            this.invalidateUserCache();
+        } catch (Exception e) {
+            throw new HugeException("Exception occurs when " +
+                                    "delete " + role + ".", e);
+        }
+    }
+
+    private void tryInitDefaultGraph(String graphSpace, String graph) {
+        try {
+            HugeRole role = this.metaManager.findRole(
+                    graphSpace,
+                    IdGenerator.of(graph + DEFAULT_SETTER_ROLE_KEY));
+            if (role != null) {
+                return;
+            }
+            role = new HugeRole(graph + DEFAULT_SETTER_ROLE_KEY, graphSpace);
+            this.updateCreator(role);
+            role.create(role.update());
+            this.metaManager.createRole(graphSpace, role);
+        } catch (Exception e) {
+            throw new HugeException("Exception occurs when " +
+                                    "init default graph role", e);
+        }
+    }
+
+    public String getGraphDefaultRole(String graph, String role) {
+        return graph + "_" + role;
+    }
+
     private void tryInitDefaultRole(String graphSpace,
                                     String roleName,
                                     String graph) {
diff --git a/hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/backend/cache/CachedSchemaTransactionV2.java b/hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/backend/cache/CachedSchemaTransactionV2.java
index b6c47e705..99a393f6b 100644
--- a/hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/backend/cache/CachedSchemaTransactionV2.java
+++ b/hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/backend/cache/CachedSchemaTransactionV2.java
@@ -47,8 +47,14 @@ import com.google.common.collect.ImmutableSet;
 
 public class CachedSchemaTransactionV2 extends SchemaTransactionV2 {
 
-    private static final String ID_CACHE_PREFIX = "schema-id";
-    private static final String NAME_CACHE_PREFIX = "schema-name";
+    /*
+     * V1 and V2 transactions can coexist in the same JVM for graphs with the
+     * same name but different backends. Keep their cache entries isolated:
+     * besides holding different schema data, their attachments use different
+     * SchemaCaches classes and therefore can't be shared safely.
+     */
+    private static final String ID_CACHE_PREFIX = "schema-v2-id";
+    private static final String NAME_CACHE_PREFIX = "schema-v2-name";
 
     // MetaDriver doesn't expose unlisten, register the meta listener once.
     // Lifecycle: this JVM-global flag is intentionally never reset by
diff --git a/hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/util/ConfigUtil.java b/hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/util/ConfigUtil.java
index 8df1b3064..54b539237 100644
--- a/hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/util/ConfigUtil.java
+++ b/hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/util/ConfigUtil.java
@@ -23,6 +23,8 @@ import java.io.Reader;
 import java.io.StringReader;
 import java.nio.file.Path;
 import java.nio.file.Paths;
+import java.util.HashMap;
+import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
 
@@ -188,4 +190,41 @@ public final class ConfigUtil {
                         "Graph name can only contain letters, numbers, hyphens and underscores: %s",
                         graphName);
     }
+
+    /**
+     * Serialize a HugeConfig to a JSON string for frontend consumption.
+     * <p>
+     * Sensitive configuration keys (containing keywords such as "password",
+     * "secret", "token", "credential", "auth.key") are excluded to prevent
+     * accidental exposure of credentials through the API response.
+     */
+    public static String writeConfigToString(HugeConfig config) {
+        Map<String, Object> configMap = new HashMap<>();
+        Iterator<String> iterator = config.getKeys();
+        while (iterator.hasNext()) {
+            String key = iterator.next();
+            if (isSensitiveKey(key)) {
+                continue;
+            }
+            configMap.put(key, config.getProperty(key));
+        }
+        return JsonUtil.toJson(configMap);
+    }
+
+    /**
+     * Returns true if the config key looks like it may contain sensitive data.
+     * Uses a case-insensitive keyword blacklist.
+     */
+    private static boolean isSensitiveKey(String key) {
+        if (key == null) {
+            return false;
+        }
+        String lower = key.toLowerCase();
+        return lower.contains("password") ||
+               lower.contains("secret")   ||
+               lower.contains("token")    ||
+               lower.contains("credential") ||
+               lower.contains("private_key") ||
+               lower.contains("auth.key");
+    }
 }
diff --git a/hugegraph-server/hugegraph-dist/src/main/java/org/apache/hugegraph/dist/HugeGraphServer.java b/hugegraph-server/hugegraph-dist/src/main/java/org/apache/hugegraph/dist/HugeGraphServer.java
index 64c4c98cf..214a09c3e 100644
--- a/hugegraph-server/hugegraph-dist/src/main/java/org/apache/hugegraph/dist/HugeGraphServer.java
+++ b/hugegraph-server/hugegraph-dist/src/main/java/org/apache/hugegraph/dist/HugeGraphServer.java
@@ -54,42 +54,104 @@ public class HugeGraphServer {
         // Only switch on security manager after HugeGremlinServer started
         SecurityManager securityManager = System.getSecurityManager();
         System.setSecurityManager(null);
+        RestServer restServer = null;
+        GremlinServer preparedGremlinServer = null;
+        GremlinServer gremlinServer = null;
+        MemoryMonitor memoryMonitor = null;
+        try {
+            ConfigUtil.checkGremlinConfig(gremlinServerConf);
+            HugeConfig restServerConfig = new HugeConfig(restServerConf);
+            String graphsDir = restServerConfig.get(ServerOptions.GRAPHS);
+            EventHub hub = new EventHub("gremlin=>hub<=rest");
+
+            PdMetaDriver.PDAuthConfig.setAuthority(
+                    ServiceConstant.SERVICE_NAME,
+                    ServiceConstant.AUTHORITY);
+
+            // Prepare GremlinServer (registers GRAPH_CREATE listener) BEFORE
+            // RestServer starts loading graphs from PD/meta. This ensures that
+            // graphs loaded during RestServer initialization are captured by
+            // ContextGremlinServer's listener and injected into Gremlin's bindings.
+            try {
+                preparedGremlinServer = HugeGremlinServer.prepare(
+                        gremlinServerConf, graphsDir, hub);
+            } catch (Throwable e) {
+                LOG.error("HugeGremlinServer prepare error: ", e);
+                throw e;
+            }
 
-        ConfigUtil.checkGremlinConfig(gremlinServerConf);
-        HugeConfig restServerConfig = new HugeConfig(restServerConf);
-        String graphsDir = restServerConfig.get(ServerOptions.GRAPHS);
-        EventHub hub = new EventHub("gremlin=>hub<=rest");
+            try {
+                // Start HugeRestServer (loads graphs from PD; events go to
+                // ContextGremlinServer which is already listening)
+                restServer = HugeRestServer.start(restServerConf, hub);
+            } catch (Throwable e) {
+                LOG.error("HugeRestServer start error: ", e);
+                stopPreparedGremlinServer(preparedGremlinServer);
+                throw e;
+            }
 
-        PdMetaDriver.PDAuthConfig.setAuthority(
-                ServiceConstant.SERVICE_NAME,
-                ServiceConstant.AUTHORITY);
-        try {
-            // Start HugeRestServer
-            this.restServer = HugeRestServer.start(restServerConf, hub);
-        } catch (Throwable e) {
-            LOG.error("HugeRestServer start error: ", e);
-            throw e;
+            try {
+                // Now start the pre-prepared GremlinServer
+                gremlinServer = HugeGremlinServer.startPrepared(
+                        preparedGremlinServer);
+            } catch (Throwable e) {
+                LOG.error("HugeGremlinServer start error: ", e);
+                try {
+                    restServer.shutdown().get();
+                } catch (Throwable t) {
+                    LOG.error("HugeRestServer stop error: ", t);
+                }
+                throw e;
+            }
+
+            try {
+                // Start (In-Heap) Memory Monitor
+                memoryMonitor = new MemoryMonitor(restServerConf);
+                memoryMonitor.start();
+            } catch (Throwable e) {
+                LOG.error("MemoryMonitor start error: ", e);
+                rollbackStartup(memoryMonitor, gremlinServer, restServer);
+                throw e;
+            }
+        } finally {
+            System.setSecurityManager(securityManager);
         }
+        this.restServer = restServer;
+        this.gremlinServer = gremlinServer;
+        this.memoryMonitor = memoryMonitor;
+    }
 
+    private static void stopPreparedGremlinServer(GremlinServer server) {
+        if (server == null) {
+            return;
+        }
         try {
-            // Start GremlinServer
-            this.gremlinServer = HugeGremlinServer.start(gremlinServerConf,
-                                                         graphsDir, hub);
-        } catch (Throwable e) {
-            LOG.error("HugeGremlinServer start error: ", e);
+            server.stop().get();
+        } catch (Throwable t) {
+            LOG.error("HugeGremlinServer stop error: ", t);
+        }
+    }
+
+    static void rollbackStartup(MemoryMonitor monitor,
+                                GremlinServer gremlinServer,
+                                RestServer restServer) {
+        if (monitor != null) {
             try {
-                this.restServer.shutdown().get();
+                monitor.stop();
             } catch (Throwable t) {
-                LOG.error("HugeRestServer stop error: ", t);
+                LOG.error("MemoryMonitor stop error: ", t);
             }
-            throw e;
-        } finally {
-            System.setSecurityManager(securityManager);
         }
 
-        // Start (In-Heap) Memory Monitor
-        this.memoryMonitor = new MemoryMonitor(restServerConf);
-        this.memoryMonitor.start();
+        stopPreparedGremlinServer(gremlinServer);
+
+        if (restServer != null) {
+            try {
+                restServer.shutdown().get();
+            } catch (Throwable t) {
+                LOG.error("HugeRestServer stop error: ", t);
+            }
+        }
     }
 
     public void stop() {
diff --git a/hugegraph-server/hugegraph-dist/src/main/java/org/apache/hugegraph/dist/HugeGremlinServer.java b/hugegraph-server/hugegraph-dist/src/main/java/org/apache/hugegraph/dist/HugeGremlinServer.java
index b3f397f54..d03f8172d 100644
--- a/hugegraph-server/hugegraph-dist/src/main/java/org/apache/hugegraph/dist/HugeGremlinServer.java
+++ b/hugegraph-server/hugegraph-dist/src/main/java/org/apache/hugegraph/dist/HugeGremlinServer.java
@@ -36,9 +36,16 @@ public class HugeGremlinServer {
 
     private static final Logger LOG = Log.logger(HugeGremlinServer.class);
 
-    public static GremlinServer start(String conf, String graphsDir,
-                                      EventHub hub) throws Exception {
-        // Start GremlinServer with inject traversal source
+    /**
+     * Construct a ContextGremlinServer and register its EventHub listeners
+     * WITHOUT starting it yet. This allows the GRAPH_CREATE listener to be
+     * in place before RestServer loads graphs from PD/meta, so that every
+     * graph gets injected into Gremlin's global bindings automatically.
+     *
+     * Call {@link #startPrepared(GremlinServer)} afterwards to actually start.
+     */
+    public static GremlinServer prepare(String conf, String graphsDir,
+                                        EventHub hub) throws Exception {
         LOG.info(GremlinServer.getHeader());
         final Settings settings;
         try {
@@ -48,7 +55,6 @@ public class HugeGremlinServer {
                       "being parsed properly. [{}]", conf, e.getMessage());
             throw e;
         }
-        // Scan graph confs and inject into gremlin server context
         E.checkState(settings.graphs != null,
                      "The GremlinServer's settings.graphs is null");
         if (graphsDir != null) {
@@ -61,18 +67,37 @@ public class HugeGremlinServer {
         }
 
         LOG.info("Configuring Gremlin Server from {}", conf);
+        // Constructing ContextGremlinServer registers GRAPH_CREATE/GRAPH_DROP
+        // listeners on the hub immediately, before any graphs are loaded.
         ContextGremlinServer server = new ContextGremlinServer(settings, hub);
 
-        // Inject customized traversal source
+        // Inject traversal sources for graphs in static config files
         server.injectTraversalSource();
 
+        return server;
+    }
+
+    /**
+     * Start a ContextGremlinServer that was previously prepared via
+     * {@link #prepare(String, String, EventHub)}.
+     */
+    public static GremlinServer startPrepared(GremlinServer server)
+            throws Exception {
         server.start().exceptionally(t -> {
             LOG.error("Gremlin Server was unable to start and will " +
                       "shutdown now: {}", t.getMessage());
             server.stop().join();
             throw new HugeException("Failed to start Gremlin Server");
         }).join();
-
         return server;
     }
+
+    /**
+     * Convenience method: prepare and start in one call (original behavior,
+     * kept for backward compatibility with any other callers).
+     */
+    public static GremlinServer start(String conf, String graphsDir,
+                                      EventHub hub) throws Exception {
+        return startPrepared(prepare(conf, graphsDir, hub));
+    }
 }
diff --git a/hugegraph-server/hugegraph-dist/src/main/java/org/apache/hugegraph/dist/MemoryMonitor.java b/hugegraph-server/hugegraph-dist/src/main/java/org/apache/hugegraph/dist/MemoryMonitor.java
index 18b7cc170..f197bc65e 100644
--- a/hugegraph-server/hugegraph-dist/src/main/java/org/apache/hugegraph/dist/MemoryMonitor.java
+++ b/hugegraph-server/hugegraph-dist/src/main/java/org/apache/hugegraph/dist/MemoryMonitor.java
@@ -102,7 +102,7 @@ public class MemoryMonitor {
 
     public void start() {
         if (MEMORY_MONITOR_THRESHOLD >= 1.0) {
-            LOG.info("Invalid parameter, MEMORY_MONITOR_THRESHOLD should ≤ 1.0.");
+            LOG.info("Memory monitoring is disabled by threshold setting.");
             return;
         }
         this.scheduler.scheduleAtFixedRate(this::runMemoryDetect, 0, MEMORY_MONITOR_DETECT_PERIOD,
diff --git a/hugegraph-server/hugegraph-test/pom.xml b/hugegraph-server/hugegraph-test/pom.xml
index 7a5daeec5..b3fc645f7 100644
--- a/hugegraph-server/hugegraph-test/pom.xml
+++ b/hugegraph-server/hugegraph-test/pom.xml
@@ -170,6 +170,12 @@
                     <execution>
                         <id>api-test</id>
                         <configuration>
+                            <!-- FIXME: HStore core guards and capability failures are tracked in
+                                 https://github.com/apache/hugegraph/issues/3090.
+                                 Keep backend scoped to API tests until the issue is resolved. -->
+                            <systemPropertyVariables>
+                                <backend>${backend}</backend>
+                            </systemPropertyVariables>
                             <testSourceDirectory>${basedir}/src/main/java/
                             </testSourceDirectory>
                             <testClassesDirectory>${basedir}/target/classes/
diff --git a/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/api/ApiTestSuite.java b/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/api/ApiTestSuite.java
index 1fe8fc45f..f4d74e49b 100644
--- a/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/api/ApiTestSuite.java
+++ b/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/api/ApiTestSuite.java
@@ -44,6 +44,9 @@ import org.junit.runners.Suite;
         GraphSpaceApiTest.class,
         GraphSpaceApiStandaloneTest.class,
         ManagerApiStandaloneTest.class,
+        GraphsApiStandaloneTest.class,
+        SchemaTemplateApiTest.class,
+        DefaultRoleApiStandaloneTest.class,
 })
 public class ApiTestSuite {
 
diff --git a/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/api/BaseApiTest.java b/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/api/BaseApiTest.java
index a6f84de33..abe4b6839 100644
--- a/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/api/BaseApiTest.java
+++ b/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/api/BaseApiTest.java
@@ -707,7 +707,7 @@ public class BaseApiTest {
     public static RestClient userClient(String username) {
         String user1 = "{\"user_name\":\"%s\"," +
                        "\"user_password\":\"%s\"}";
-        Response r = client.post("auth/users",
+        Response r = client.post("graphspaces/DEFAULT/auth/users",
                                  String.format(user1, username, username));
         assertResponseStatus(201, r);
 
@@ -721,11 +721,12 @@ public class BaseApiTest {
 
         String spaceBody = "{\n" +
                            "  \"user\": \"%s\",\n" +
-                           "  \"type\": \"SPACE\",\n" +
-                           "  \"graphspace\": \"%s\"\n" +
+                           "  \"type\": \"SPACE\"\n" +
                            "}";
-        client.post("auth/managers", String.format(spaceBody, username,
-                                                   graphSpace));
+        Response r = client.post(String.format("graphspaces/%s/auth/managers",
+                                               graphSpace),
+                                 String.format(spaceBody, username));
+        assertResponseStatus(201, r);
         return spaceClient;
     }
 
diff --git a/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/api/DefaultRoleApiStandaloneTest.java b/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/api/DefaultRoleApiStandaloneTest.java
new file mode 100644
index 000000000..bab36ab9e
--- /dev/null
+++ b/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/api/DefaultRoleApiStandaloneTest.java
@@ -0,0 +1,182 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.hugegraph.api;
+
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+import com.google.common.collect.ImmutableMap;
+
+import jakarta.ws.rs.core.Response;
+
+/**
+ * Tests for the Hubble 2.0 default-role APIs added to GraphSpaceAPI and ManagerAPI
+ * in standalone (RocksDB) mode.
+ *
+ * In standalone mode these endpoints should return HTTP 400 with a
+ * friendly "not supported" error, because role/graphspace management
+ * requires the PD/hstore backend.
+ *
+ * Covered endpoints:
+ *  POST   /graphspaces/{gs}/role
+ *  GET    /graphspaces/{gs}/role?user=&role=
+ *  DELETE /graphspaces/{gs}/role?user=&role=
+ *  GET    /graphspaces/{gs}/auth/managers/default?role=
+ */
+public class
+DefaultRoleApiStandaloneTest extends BaseApiTest {
+
+    private static final String SPACE = "DEFAULT";
+
+    private static String rolePath(String graphspace) {
+        return String.format("graphspaces/%s/role", graphspace);
+    }
+
+    private static String managerDefaultPath(String graphspace) {
+        return String.format("graphspaces/%s/auth/managers/default", graphspace);
+    }
+
+    @Before
+    public void assumeStandalone() {
+        assumeStandaloneMode();
+    }
+
+    // ------------------------------------------------------------------ //
+    //  POST /graphspaces/{gs}/role
+    // ------------------------------------------------------------------ //
+
+    @Test
+    public void testCreateAnalystRoleReturnsFriendlyError() {
+        String body = "{\"user\":\"admin\",\"role\":\"ANALYST\"}";
+        Response r = client().post(rolePath(SPACE), body);
+        String content = assertResponseStatus(400, r);
+        Assert.assertTrue("should contain standalone error message",
+                          content.contains(STANDALONE_ERROR));
+    }
+
+    @Test
+    public void testCreateObserverRoleReturnsFriendlyError() {
+        // OBSERVER requires a 'graph' field
+        String body = "{\"user\":\"admin\",\"role\":\"OBSERVER\",\"graph\":\"hugegraph\"}";
+        Response r = client().post(rolePath(SPACE), body);
+        String content = assertResponseStatus(400, r);
+        Assert.assertTrue(content.contains(STANDALONE_ERROR));
+    }
+
+    @Test
+    public void testCreateSpaceRoleReturnsFriendlyError() {
+        String body = "{\"user\":\"admin\",\"role\":\"SPACE\"}";
+        Response r = client().post(rolePath(SPACE), body);
+        String content = assertResponseStatus(400, r);
+        Assert.assertTrue(content.contains(STANDALONE_ERROR));
+    }
+
+    // ------------------------------------------------------------------ //
+    //  GET /graphspaces/{gs}/role?user=&role=
+    // ------------------------------------------------------------------ //
+
+    @Test
+    public void testCheckAnalystRoleReturnsFriendlyError() {
+        Response r = client().get(rolePath(SPACE),
+                                  ImmutableMap.of("user", "admin", "role", "ANALYST"));
+        String content = assertResponseStatus(400, r);
+        Assert.assertTrue(content.contains(STANDALONE_ERROR));
+    }
+
+    @Test
+    public void testCheckObserverRoleReturnsFriendlyError() {
+        Response r = client().get(rolePath(SPACE),
+                                  ImmutableMap.of("user", "admin",
+                                                  "role", "OBSERVER",
+                                                  "graph", "hugegraph"));
+        String content = assertResponseStatus(400, r);
+        Assert.assertTrue(content.contains(STANDALONE_ERROR));
+    }
+
+    // ------------------------------------------------------------------ //
+    //  DELETE /graphspaces/{gs}/role?user=&role=
+    // ------------------------------------------------------------------ //
+
+    @Test
+    public void testDeleteAnalystRoleReturnsFriendlyError() {
+        Response r = client().delete(rolePath(SPACE),
+                                     ImmutableMap.of("user", "admin", "role", "ANALYST"));
+        String content = assertResponseStatus(400, r);
+        Assert.assertTrue(content.contains(STANDALONE_ERROR));
+    }
+
+    @Test
+    public void testDeleteObserverRoleReturnsFriendlyError() {
+        Response r = client().delete(rolePath(SPACE),
+                                     ImmutableMap.of("user", "admin",
+                                                     "role", "OBSERVER",
+                                                     "graph", "hugegraph"));
+        String content = assertResponseStatus(400, r);
+        Assert.assertTrue(content.contains(STANDALONE_ERROR));
+    }
+
+    // ------------------------------------------------------------------ //
+    //  GET /graphspaces/{gs}/auth/managers/default?role=
+    // ------------------------------------------------------------------ //
+
+    @Test
+    public void testCheckDefaultAnalystRoleReturnsFriendlyError() {
+        Response r = client().get(managerDefaultPath(SPACE),
+                                  ImmutableMap.of("role", "ANALYST"));
+        String content = assertResponseStatus(400, r);
+        Assert.assertTrue(content.contains(STANDALONE_ERROR));
+    }
+
+    @Test
+    public void testCheckDefaultObserverRoleReturnsFriendlyError() {
+        Response r = client().get(managerDefaultPath(SPACE),
+                                  ImmutableMap.of("role", "OBSERVER",
+                                                  "graph", "hugegraph"));
+        String content = assertResponseStatus(400, r);
+        Assert.assertTrue(content.contains(STANDALONE_ERROR));
+    }
+
+    // ------------------------------------------------------------------ //
+    //  Non-existent graphspace — should also return 400 in standalone
+    // ------------------------------------------------------------------ //
+
+    @Test
+    public void testCreateRoleNonExistentSpaceReturnsFriendlyError() {
+        String body = "{\"user\":\"admin\",\"role\":\"ANALYST\"}";
+        Response r = client().post(rolePath("nonexistent"), body);
+        String content = assertResponseStatus(400, r);
+        Assert.assertTrue(content.contains(STANDALONE_ERROR));
+    }
+
+    @Test
+    public void testCheckRoleNonExistentSpaceReturnsFriendlyError() {
+        Response r = client().get(rolePath("nonexistent"),
+                                  ImmutableMap.of("user", "admin", "role", "ANALYST"));
+        String content = assertResponseStatus(400, r);
+        Assert.assertTrue(content.contains(STANDALONE_ERROR));
+    }
+
+    @Test
+    public void testDeleteRoleNonExistentSpaceReturnsFriendlyError() {
+        Response r = client().delete(rolePath("nonexistent"),
+                                     ImmutableMap.of("user", "admin", "role", "ANALYST"));
+        String content = assertResponseStatus(400, r);
+        Assert.assertTrue(content.contains(STANDALONE_ERROR));
+    }
+}
diff --git a/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/api/GraphSpaceApiTest.java b/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/api/GraphSpaceApiTest.java
index 1c3eb7799..98240e76f 100644
--- a/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/api/GraphSpaceApiTest.java
+++ b/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/api/GraphSpaceApiTest.java
@@ -41,8 +41,11 @@ public class GraphSpaceApiTest extends BaseApiTest {
                           Objects.equals("hstore", System.getProperty("backend")));
         Response r = this.client().get(PATH);
         String result = r.readEntity(String.class);
+        Assert.assertEquals(result, 200, r.getStatus());
         Map<String, Object> resultMap = JsonUtil.fromJson(result, Map.class);
         List<String> spaces = (List<String>)resultMap.get("graphSpaces");
+        Assert.assertNotNull("graphspace API should be available in hstore",
+                             spaces);
         for (String space : spaces) {
             if (!"DEFAULT".equals(space)) {
                 this.client().delete(PATH, space);
@@ -431,4 +434,157 @@ public class GraphSpaceApiTest extends BaseApiTest {
         }
         Assert.assertTrue("auth_test_space not found in profile list", found);
     }
+
+    @Test
+    public void testDefaultGraphLifecycle() {
+        String space = "default_graph_space";
+        String graph = "default_graph_test";
+        createSpace(space, true);
+        Response r = createGraph(space, graph, "DefaultGraphNick");
+        assertResponseStatus(201, r);
+
+        String graphsPath = String.format("graphspaces/%s/graphs", space);
+        r = this.client().post(graphsPath + "/" + graph + "/default", "");
+        String result = assertResponseStatus(200, r);
+        Assert.assertTrue(result.contains(graph));
+
+        r = this.client().get(graphsPath + "/default");
+        result = assertResponseStatus(200, r);
+        Assert.assertTrue(result.contains(graph));
+
+        r = this.client().get(graphsPath + "/profile");
+        result = assertResponseStatus(200, r);
+        @SuppressWarnings("unchecked")
+        List<Map<String, Object>> profiles = JsonUtil.fromJson(result, List.class);
+        Map<String, Object> profile = profiles.stream()
+                                              .filter(p -> graph.equals(p.get("name")))
+                                              .findFirst()
+                                              .orElse(null);
+        Assert.assertNotNull("Default graph should appear in profile list", profile);
+        Assert.assertEquals(Boolean.TRUE, profile.get("default"));
+
+        r = this.client().delete(graphsPath + "/" + graph + "/default",
+                                 ImmutableMap.of());
+        result = assertResponseStatus(200, r);
+        Assert.assertFalse(result.contains(graph));
+    }
+
+    @Test
+    public void testDefaultRoleLifecycle() {
+        String space = "default_role_space";
+        createSpace(space, true);
+
+        String rolePath = String.format("graphspaces/%s/role", space);
+        String body = "{\"user\":\"admin\",\"role\":\"ANALYST\"}";
+        Response r = this.client().post(rolePath, body);
+        assertResponseStatus(201, r);
+
+        r = this.client().get(rolePath,
+                              ImmutableMap.of("user", "admin",
+                                              "role", "ANALYST"));
+        String result = assertResponseStatus(200, r);
+        Assert.assertTrue(result.contains("true"));
+
+        r = this.client().delete(rolePath,
+                                 ImmutableMap.of("user", "admin",
+                                                 "role", "ANALYST"));
+        assertResponseStatus(204, r);
+
+        r = this.client().get(rolePath,
+                              ImmutableMap.of("user", "admin",
+                                              "role", "ANALYST"));
+        result = assertResponseStatus(200, r);
+        Assert.assertTrue(result.contains("false"));
+    }
+
+    @Test
+    public void testDefaultRoleMutationRequiresSpaceManager() {
+        String space = "default_role_auth_space";
+        String analyst = "default_role_analyst";
+        String target = "default_role_target";
+        String manager = "default_role_manager";
+        createSpace(space, true);
+
+        RestClient analystClient = userClient(analyst);
+        userClient(target);
+        RestClient managerClient = spaceManagerClient(space, manager);
+
+        String rolePath = String.format("graphspaces/%s/role", space);
+        String analystBody = String.format("{\"user\":\"%s\",\"role\":\"ANALYST\"}",
+                                           analyst);
+        Response r = this.client().post(rolePath, analystBody);
+        assertResponseStatus(201, r);
+
+        String grantTargetBody = String.format("{\"user\":\"%s\",\"role\":\"ANALYST\"}",
+                                               target);
+        r = analystClient.post(rolePath, grantTargetBody);
+        assertResponseStatus(403, r);
+
+        r = analystClient.get(rolePath,
+                              ImmutableMap.of("user", target,
+                                              "role", "ANALYST"));
+        assertResponseStatus(403, r);
+
+        r = analystClient.delete(rolePath,
+                                 ImmutableMap.of("user", analyst,
+                                                 "role", "ANALYST"));
+        assertResponseStatus(403, r);
+
+        r = managerClient.post(rolePath, grantTargetBody);
+        assertResponseStatus(201, r);
+
+        r = managerClient.get(rolePath,
+                              ImmutableMap.of("user", target,
+                                              "role", "ANALYST"));
+        String result = assertResponseStatus(200, r);
+        Assert.assertTrue(result.contains("true"));
+
+        String spaceRoleBody = String.format("{\"user\":\"%s\",\"role\":\"SPACE\"}",
+                                             target);
+        r = managerClient.post(rolePath, spaceRoleBody);
+        assertResponseStatus(403, r);
+
+        r = managerClient.delete(rolePath,
+                                 ImmutableMap.of("user", target,
+                                                 "role", "ANALYST"));
+        assertResponseStatus(204, r);
+    }
+
+    @Test
+    public void testSchemaTemplateLifecycle() {
+        String space = "schema_template_space";
+        String template = "hubble_schema_template";
+        String schema = "schema.propertyKey('name').asText().ifNotExist().create();";
+        String path = String.format("graphspaces/%s/schematemplates", space);
+        createSpace(space, true);
+
+        String body = String.format("{\"name\":\"%s\",\"schema\":\"%s\"}",
+                                    template, schema);
+        Response r = this.client().post(path, body);
+        String result = assertResponseStatus(201, r);
+        Assert.assertTrue(result.contains(template));
+
+        r = this.client().get(path, template);
+        result = assertResponseStatus(200, r);
+        Assert.assertTrue(result.contains(template));
+
+        r = this.client().get(path);
+        result = assertResponseStatus(200, r);
+        Assert.assertTrue(result.contains(template));
+
+        String updated = "schema.propertyKey('age').asInt().ifNotExist().create();";
+        body = String.format("{\"schema\":\"%s\"}", updated);
+        r = this.client().target(baseUrl())
+                         .path(path + "/" + template)
+                         .request()
+                         .put(jakarta.ws.rs.client.Entity.json(body));
+        result = assertResponseStatus(200, r);
+        Assert.assertTrue(result.contains("age"));
+
+        r = this.client().delete(path, template);
+        assertResponseStatus(204, r);
+
+        r = this.client().get(path, template);
+        assertResponseStatus(404, r);
+    }
 }
diff --git a/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/api/GraphsApiStandaloneTest.java b/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/api/GraphsApiStandaloneTest.java
new file mode 100644
index 000000000..bdd65be9a
--- /dev/null
+++ b/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/api/GraphsApiStandaloneTest.java
@@ -0,0 +1,306 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.hugegraph.api;
+
+import java.util.List;
+import java.util.Map;
+
+import org.apache.hugegraph.util.JsonUtil;
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+import com.google.common.collect.ImmutableMap;
+
+import jakarta.ws.rs.core.Response;
+
+/**
+ * Tests for Hubble-compatible graph profile and default graph APIs
+ * in standalone (RocksDB) mode:
+ *  - GET  /graphspaces/{gs}/graphs/profile         — works with default graph
+ *  - POST /graphspaces/{gs}/graphs/{name}/default  — sets default graph
+ *  - DELETE /graphspaces/{gs}/graphs/{name}/default — unsets default graph
+ *  - GET  /graphspaces/{gs}/graphs/default          — gets default graph
+ *  - PUT  /graphspaces/{gs}/graphs/{name}           — update nickname
+ */
+public class GraphsApiStandaloneTest extends BaseApiTest {
+
+    private static final String SPACE = "DEFAULT";
+    private static final String GRAPHS_PATH = "graphspaces/DEFAULT/graphs";
+    private static final String PROFILE_PATH = GRAPHS_PATH + "/profile";
+    private static final String DEFAULT_PATH = GRAPHS_PATH + "/default";
+
+    /** Graph names created per test — cleaned up in teardown */
+    private static final String TEST_GRAPH = "gst_test_graph";
+    private static final String TEST_GRAPH2 = "gst_test_graph2";
+
+    @Before
+    public void assumeStandalone() {
+        assumeStandaloneMode();
+    }
+
+    @After
+    @Override
+    public void teardown() throws Exception {
+        client().delete(GRAPHS_PATH + "/" + TEST_GRAPH + "/default",
+                        ImmutableMap.of());
+        client().delete(GRAPHS_PATH + "/" + TEST_GRAPH2 + "/default",
+                        ImmutableMap.of());
+        Map<String, Object> drop = ImmutableMap.of(
+                "confirm_message", "I'm sure to drop the graph");
+        client().delete(GRAPHS_PATH + "/" + TEST_GRAPH, drop);
+        client().delete(GRAPHS_PATH + "/" + TEST_GRAPH2, drop);
+        super.teardown();
+    }
+
+    // ------------------------------------------------------------------ //
+    //  GET /graphspaces/{gs}/graphs/profile
+    // ------------------------------------------------------------------ //
+
+    @Test
+    public void testListProfileReturnsGraphEntries() {
+        Response r = client().get(PROFILE_PATH);
+        String content = assertResponseStatus(200, r);
+
+        @SuppressWarnings("unchecked")
+        List<Map<String, Object>> profiles = JsonUtil.fromJson(content, List.class);
+        Assert.assertFalse("profile list should not be empty", profiles.isEmpty());
+
+        for (Map<String, Object> p : profiles) {
+            Assert.assertTrue("profile entry must have 'name'", p.containsKey("name"));
+            Assert.assertTrue("profile entry must have 'default'", p.containsKey("default"));
+        }
+    }
+
+    @Test
+    public void testListProfileContainsCreatedGraph() {
+        Response r = createGraphInRocksDB(SPACE, TEST_GRAPH, "ProfileTestNick");
+        assertResponseStatus(201, r);
+
+        Response profile = client().get(PROFILE_PATH);
+        String content = assertResponseStatus(200, profile);
+
+        @SuppressWarnings("unchecked")
+        List<Map<String, Object>> profiles = JsonUtil.fromJson(content, List.class);
+        boolean found = profiles.stream()
+                                .anyMatch(p -> TEST_GRAPH.equals(p.get("name")));
+        Assert.assertTrue("Newly created graph should appear in profile list", found);
+    }
+
+    @Test
+    public void testListProfileWithPrefixFilter() {
+        Response r1 = createGraphInRocksDB(SPACE, TEST_GRAPH, "PrefixNick");
+        assertResponseStatus(201, r1);
+        Response r2 = createGraphInRocksDB(SPACE, TEST_GRAPH2, "OtherNick");
+        assertResponseStatus(201, r2);
+
+        Response profile = client().get(PROFILE_PATH,
+                                        ImmutableMap.of("prefix", TEST_GRAPH));
+        String content = assertResponseStatus(200, profile);
+
+        @SuppressWarnings("unchecked")
+        List<Map<String, Object>> profiles = JsonUtil.fromJson(content, List.class);
+        for (Map<String, Object> p : profiles) {
+            String name = String.valueOf(p.get("name"));
+            Object nickObj = p.get("nickname");
+            String nick = nickObj != null ? String.valueOf(nickObj) : "";
+            boolean matches = name.startsWith(TEST_GRAPH) ||
+                              nick.startsWith(TEST_GRAPH);
+            Assert.assertTrue("All returned profiles should match prefix: " + name,
+                              matches);
+        }
+    }
+
+    @Test
+    public void testListProfileNicknameField() {
+        Response r = createGraphInRocksDB(SPACE, TEST_GRAPH, "MyNickname");
+        assertResponseStatus(201, r);
+
+        Response profile = client().get(PROFILE_PATH);
+        String content = assertResponseStatus(200, profile);
+
+        @SuppressWarnings("unchecked")
+        List<Map<String, Object>> profiles = JsonUtil.fromJson(content, List.class);
+        Map<String, Object> entry = profiles.stream()
+                                            .filter(p -> TEST_GRAPH.equals(p.get("name")))
+                                            .findFirst()
+                                            .orElse(null);
+        Assert.assertNotNull("Graph should be present in profile list", entry);
+        Assert.assertTrue("Profile entry should contain 'nickname'",
+                          entry.containsKey("nickname"));
+        Assert.assertEquals("MyNickname", entry.get("nickname"));
+    }
+
+    @Test
+    public void testListProfileContainsDefaultGraph() {
+        Response r = createGraphInRocksDB(SPACE, TEST_GRAPH);
+        assertResponseStatus(201, r);
+        Response setR = client().post(GRAPHS_PATH + "/" + TEST_GRAPH + "/default", "");
+        assertResponseStatus(200, setR);
+
+        Response profile = client().get(PROFILE_PATH);
+        String content = assertResponseStatus(200, profile);
+
+        @SuppressWarnings("unchecked")
+        List<Map<String, Object>> profiles = JsonUtil.fromJson(content, List.class);
+        Map<String, Object> entry = profiles.stream()
+                                            .filter(p -> TEST_GRAPH.equals(p.get("name")))
+                                            .findFirst()
+                                            .orElse(null);
+        Assert.assertNotNull(entry);
+        Assert.assertEquals("default graph should be marked in standalone mode",
+                            Boolean.TRUE, entry.get("default"));
+    }
+
+    // ------------------------------------------------------------------ //
+    //  Canonical default graph operations work in standalone mode
+    // ------------------------------------------------------------------ //
+
+    @Test
+    public void testSetDefaultGraph() {
+        Response r = createGraphInRocksDB(SPACE, TEST_GRAPH);
+        assertResponseStatus(201, r);
+
+        Response setR = client().post(GRAPHS_PATH + "/" + TEST_GRAPH + "/default", "");
+        String content = assertResponseStatus(200, setR);
+        Assert.assertTrue(content.contains(TEST_GRAPH));
+
+        Response repeatedSetR = client().post(GRAPHS_PATH + "/" + TEST_GRAPH +
+                                              "/default", "");
+        content = assertResponseStatus(200, repeatedSetR);
+        Assert.assertTrue(content.contains(TEST_GRAPH));
+    }
+
+    @Test
+    public void testUnsetDefaultGraph() {
+        Response r = createGraphInRocksDB(SPACE, TEST_GRAPH);
+        assertResponseStatus(201, r);
+
+        Response setR = client().post(GRAPHS_PATH + "/" + TEST_GRAPH + "/default", "");
+        assertResponseStatus(200, setR);
+
+        Response unsetR = client().delete(GRAPHS_PATH + "/" + TEST_GRAPH + "/default",
+                                          ImmutableMap.of());
+        String content = assertResponseStatus(200, unsetR);
+        Assert.assertFalse(content.contains(TEST_GRAPH));
+    }
+
+    @Test
+    public void testSetDefaultGraphWithGetIsNotAllowed() {
+        Response r = createGraphInRocksDB(SPACE, TEST_GRAPH);
+        assertResponseStatus(201, r);
+
+        Response setR = client().get(GRAPHS_PATH + "/" + TEST_GRAPH + "/default");
+        assertResponseStatus(405, setR);
+    }
+
+    @Test
+    public void testUnsetDefaultGraphWithGetIsNotFound() {
+        Response r = createGraphInRocksDB(SPACE, TEST_GRAPH);
+        assertResponseStatus(201, r);
+
+        Response unsetR = client().get(GRAPHS_PATH + "/" + TEST_GRAPH + "/undefault");
+        assertResponseStatus(404, unsetR);
+    }
+
+    @Test
+    public void testGetDefaultGraph() {
+        Response r = createGraphInRocksDB(SPACE, TEST_GRAPH);
+        assertResponseStatus(201, r);
+        Response setR = client().post(GRAPHS_PATH + "/" + TEST_GRAPH + "/default", "");
+        assertResponseStatus(200, setR);
+
+        Response getR = client().get(DEFAULT_PATH);
+        String content = assertResponseStatus(200, getR);
+        Assert.assertTrue(content.contains(TEST_GRAPH));
+    }
+
+    // ------------------------------------------------------------------ //
+    //  PUT /graphspaces/{gs}/graphs/{name}  — update nickname
+    // ------------------------------------------------------------------ //
+
+    @Test
+    public void testUpdateGraphNickname() {
+        Response r = createGraphInRocksDB(SPACE, TEST_GRAPH, "OldNickname");
+        assertResponseStatus(201, r);
+
+        Response updateR = updateGraph("update", SPACE, TEST_GRAPH, "NewNickname");
+        String content = assertResponseStatus(200, updateR);
+
+        @SuppressWarnings("unchecked")
+        Map<String, String> result = JsonUtil.fromJson(content, Map.class);
+        Assert.assertTrue("Response should contain graph name key",
+                          result.containsKey(TEST_GRAPH));
+        Assert.assertEquals("updated", result.get(TEST_GRAPH));
+    }
+
+    @Test
+    public void testUpdateGraphWithInvalidAction() {
+        Response r = createGraphInRocksDB(SPACE, TEST_GRAPH);
+        assertResponseStatus(201, r);
+
+        String badBody = "{\"action\": \"invalid_action\", \"update\": {\"name\": \"" +
+                         TEST_GRAPH + "\", \"nickname\": \"X\"}}";
+        Response updateR = client().target(baseUrl())
+                                   .path(GRAPHS_PATH + "/" + TEST_GRAPH)
+                                   .request()
+                                   .put(jakarta.ws.rs.client.Entity.json(badBody));
+        assertResponseStatus(400, updateR);
+    }
+
+    @Test
+    public void testUpdateGraphWithInvalidFieldTypeReturnsBadRequest() {
+        Response r = createGraphInRocksDB(SPACE, TEST_GRAPH);
+        assertResponseStatus(201, r);
+
+        String badNameBody = "{\"action\": \"update\", \"update\": {\"name\": 123, " +
+                             "\"nickname\": \"X\"}}";
+        Response updateR = client().target(baseUrl())
+                                   .path(GRAPHS_PATH + "/" + TEST_GRAPH)
+                                   .request()
+                                   .put(jakarta.ws.rs.client.Entity.json(badNameBody));
+        assertResponseStatus(400, updateR);
+
+        String badNicknameBody = "{\"action\": \"update\", \"update\": {\"name\": \"" +
+                                 TEST_GRAPH + "\", \"nickname\": 123}}";
+        updateR = client().target(baseUrl())
+                          .path(GRAPHS_PATH + "/" + TEST_GRAPH)
+                          .request()
+                          .put(jakarta.ws.rs.client.Entity.json(badNicknameBody));
+        assertResponseStatus(400, updateR);
+    }
+
+    @Test
+    public void testCreateGraphWithInvalidConfigValueReturnsBadRequest() {
+        String nullValueBody = "{\"nickname\":\"NullConfig\",\"search.text_analyzer\":null}";
+        Response createR = client().target(baseUrl())
+                                   .path(GRAPHS_PATH + "/" + TEST_GRAPH)
+                                   .request()
+                                   .post(jakarta.ws.rs.client.Entity.json(nullValueBody));
+        assertResponseStatus(400, createR);
+
+        String objectValueBody = "{\"nickname\":\"ObjectConfig\"," +
+                                 "\"schema\":{\"name\":\"template\"}}";
+        createR = client().target(baseUrl())
+                          .path(GRAPHS_PATH + "/" + TEST_GRAPH2)
+                          .request()
+                          .post(jakarta.ws.rs.client.Entity.json(objectValueBody));
+        assertResponseStatus(400, createR);
+    }
+}
diff --git a/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/api/SchemaTemplateApiTest.java b/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/api/SchemaTemplateApiTest.java
new file mode 100644
index 000000000..64a63a660
--- /dev/null
+++ b/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/api/SchemaTemplateApiTest.java
@@ -0,0 +1,133 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.hugegraph.api;
+
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+import jakarta.ws.rs.core.Response;
+
+/**
+ * Tests for Schema Template APIs in standalone (RocksDB) mode.
+ *
+ * SchemaTemplate management requires the PD/hstore backend (it relies on
+ * the distributed meta manager). In standalone mode every endpoint must
+ * return HTTP 400 with a friendly "not supported" error message, matching
+ * the behaviour of other PD-only APIs (ManagerAPI, GraphSpaceAPI roles).
+ *
+ * Covered endpoints:
+ *  GET    /graphspaces/{gs}/schematemplates
+ *  GET    /graphspaces/{gs}/schematemplates/{name}
+ *  POST   /graphspaces/{gs}/schematemplates
+ *  PUT    /graphspaces/{gs}/schematemplates/{name}
+ *  DELETE /graphspaces/{gs}/schematemplates/{name}
+ */
+public class SchemaTemplateApiTest extends BaseApiTest {
+
+    private static final String PATH = "graphspaces/DEFAULT/schematemplates";
+    private static final String TEMPLATE_NAME = "st_test_template";
+    private static final String TEMPLATE_SCHEMA =
+            "schema.propertyKey('name').asText().ifNotExist().create();";
+
+    @Before
+    public void assumeStandalone() {
+        assumeStandaloneMode();
+    }
+
+    // ------------------------------------------------------------------ //
+    //  GET /graphspaces/{gs}/schematemplates
+    // ------------------------------------------------------------------ //
+
+    @Test
+    public void testListSchemaTemplatesReturnsFriendlyError() {
+        Response r = client().get(PATH);
+        String content = assertResponseStatus(400, r);
+        Assert.assertTrue(content.contains(STANDALONE_ERROR));
+    }
+
+    // ------------------------------------------------------------------ //
+    //  GET /graphspaces/{gs}/schematemplates/{name}
+    // ------------------------------------------------------------------ //
+
+    @Test
+    public void testGetSchemaTemplateReturnsFriendlyError() {
+        Response r = client().get(PATH, TEMPLATE_NAME);
+        String content = assertResponseStatus(400, r);
+        Assert.assertTrue(content.contains(STANDALONE_ERROR));
+    }
+
+    // ------------------------------------------------------------------ //
+    //  POST /graphspaces/{gs}/schematemplates
+    // ------------------------------------------------------------------ //
+
+    @Test
+    public void testCreateSchemaTemplateReturnsFriendlyError() {
+        String body = String.format("{\"name\":\"%s\",\"schema\":\"%s\"}",
+                                   TEMPLATE_NAME, TEMPLATE_SCHEMA);
+        Response r = client().post(PATH, body);
+        String content = assertResponseStatus(400, r);
+        Assert.assertTrue(content.contains(STANDALONE_ERROR));
+    }
+
+    // ------------------------------------------------------------------ //
+    //  PUT /graphspaces/{gs}/schematemplates/{name}
+    // ------------------------------------------------------------------ //
+
+    @Test
+    public void testUpdateSchemaTemplateReturnsFriendlyError() {
+        String body = String.format("{\"schema\":\"%s\"}", TEMPLATE_SCHEMA);
+        Response r = client().target(baseUrl())
+                             .path(PATH + "/" + TEMPLATE_NAME)
+                             .request()
+                             .put(jakarta.ws.rs.client.Entity.json(body));
+        String content = assertResponseStatus(400, r);
+        Assert.assertTrue(content.contains(STANDALONE_ERROR));
+    }
+
+    // ------------------------------------------------------------------ //
+    //  DELETE /graphspaces/{gs}/schematemplates/{name}
+    // ------------------------------------------------------------------ //
+
+    @Test
+    public void testDeleteSchemaTemplateReturnsFriendlyError() {
+        Response r = client().delete(PATH, TEMPLATE_NAME);
+        String content = assertResponseStatus(400, r);
+        Assert.assertTrue(content.contains(STANDALONE_ERROR));
+    }
+
+    // ------------------------------------------------------------------ //
+    //  Non-existent graphspace — also returns 400 in standalone
+    // ------------------------------------------------------------------ //
+
+    @Test
+    public void testListSchemaTemplatesNonExistentSpaceReturnsFriendlyError() {
+        Response r = client().get("graphspaces/nonexistent/schematemplates");
+        String content = assertResponseStatus(400, r);
+        Assert.assertTrue(content.contains(STANDALONE_ERROR));
+    }
+
+    @Test
+    public void testCreateSchemaTemplateNonExistentSpaceReturnsFriendlyError() {
+        String body = String.format("{\"name\":\"%s\",\"schema\":\"%s\"}",
+                                   TEMPLATE_NAME, TEMPLATE_SCHEMA);
+        Response r = client().post("graphspaces/nonexistent/schematemplates", body);
+        String content = assertResponseStatus(400, r);
+        Assert.assertTrue(content.contains(STANDALONE_ERROR));
+    }
+}
diff --git a/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/core/EdgeCoreTest.java b/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/core/EdgeCoreTest.java
index bbf7db656..eadc27713 100644
--- a/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/core/EdgeCoreTest.java
+++ b/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/core/EdgeCoreTest.java
@@ -3584,7 +3584,8 @@ public class EdgeCoreTest extends BaseCoreTest {
 
     @Test
     public void testQueryOutEdgesOfVertexBySortkeyWithRange() {
-        // FIXME: skip this test for hstore
+        // FIXME: The legacy HStore guard and related coverage debt are tracked in
+        // https://github.com/apache/hugegraph/issues/3090
         Assume.assumeTrue("skip this test for hstore",
                           Objects.equals("hstore", System.getProperty("backend")));
 
@@ -3682,7 +3683,8 @@ public class EdgeCoreTest extends BaseCoreTest {
 
     @Test
     public void testQueryOutEdgesOfVertexBySortkeyWithPrefix() {
-        // FIXME: skip this test for hstore
+        // FIXME: The legacy HStore guard and related coverage debt are tracked in
+        // https://github.com/apache/hugegraph/issues/3090
         Assume.assumeTrue("skip this test for hstore",
                           Objects.equals("hstore", System.getProperty("backend")));
 
@@ -3780,7 +3782,8 @@ public class EdgeCoreTest extends BaseCoreTest {
 
     @Test
     public void testQueryOutEdgesOfVertexBySortkeyWithPrefixInPage() {
-        // FIXME: skip this test for hstore
+        // FIXME: The legacy HStore guard and related coverage debt are tracked in
+        // https://github.com/apache/hugegraph/issues/3090
         Assume.assumeTrue("skip this test for hstore",
                           Objects.equals("hstore", System.getProperty("backend")));
 
@@ -3895,7 +3898,8 @@ public class EdgeCoreTest extends BaseCoreTest {
 
     @Test
     public void testQueryOutEdgesOfVertexBySortkeyWithMoreFields() {
-        // FIXME: skip this test for hstore
+        // FIXME: The legacy HStore guard and related coverage debt are tracked in
+        // https://github.com/apache/hugegraph/issues/3090
         Assume.assumeTrue("skip this test for hstore",
                           Objects.equals("hstore", System.getProperty("backend")));
 
@@ -4116,7 +4120,8 @@ public class EdgeCoreTest extends BaseCoreTest {
 
     @Test
     public void testQueryOutEdgesOfVertexBySortkeyWithMoreFieldsInPage() {
-        // FIXME: skip this test for hstore
+        // FIXME: The legacy HStore guard and related coverage debt are tracked in
+        // https://github.com/apache/hugegraph/issues/3090
         Assume.assumeTrue("skip this test for hstore",
                           Objects.equals("hstore", System.getProperty("backend")));
 
@@ -5244,7 +5249,8 @@ public class EdgeCoreTest extends BaseCoreTest {
 
     @Test
     public void testScanEdgeInPaging() {
-        // FIXME: skip this test for hstore
+        // FIXME: The legacy HStore guard and related coverage debt are tracked in
+        // https://github.com/apache/hugegraph/issues/3090
         Assume.assumeTrue("skip this test for hstore",
                           Objects.equals("hstore", System.getProperty("backend")));
 
diff --git a/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/core/MultiGraphsTest.java b/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/core/MultiGraphsTest.java
index 2c9c11021..70aa5df7d 100644
--- a/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/core/MultiGraphsTest.java
+++ b/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/core/MultiGraphsTest.java
@@ -83,7 +83,8 @@ public class MultiGraphsTest extends BaseCoreTest {
 
     @Test
     public void testCopySchemaWithMultiGraphs() {
-        // FIXME: skip this test for hstore
+        // FIXME: The legacy HStore guard and related coverage debt are tracked in
+        // https://github.com/apache/hugegraph/issues/3090
         Assume.assumeTrue("skip this test for hstore",
                           Objects.equals("hstore", System.getProperty("backend")));
 
@@ -292,7 +293,8 @@ public class MultiGraphsTest extends BaseCoreTest {
 
     @Test
     public void testCreateGraphWithSameNameDifferentBackends() throws Exception {
-        // FIXME: skip this test for hstore
+        // FIXME: The legacy HStore guard and related coverage debt are tracked in
+        // https://github.com/apache/hugegraph/issues/3090
         Assume.assumeTrue("skip this test for hstore",
                           Objects.equals("hstore", System.getProperty("backend")));
 
diff --git a/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/core/RamTableTest.java b/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/core/RamTableTest.java
index 54d1bd7bc..8a1f6c491 100644
--- a/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/core/RamTableTest.java
+++ b/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/core/RamTableTest.java
@@ -92,7 +92,8 @@ public class RamTableTest extends BaseCoreTest {
 
     @Test
     public void testReloadAndQuery() throws Exception {
-        // FIXME: skip this test for hstore
+        // FIXME: The legacy HStore guard and related coverage debt are tracked in
+        // https://github.com/apache/hugegraph/issues/3090
         Assume.assumeTrue("skip this test for hstore",
                           Objects.equals("hstore", System.getProperty("backend")));
 
@@ -143,7 +144,8 @@ public class RamTableTest extends BaseCoreTest {
 
     @Test
     public void testReloadFromFileAndQuery() throws Exception {
-        // FIXME: skip this test for hstore
+        // FIXME: The legacy HStore guard and related coverage debt are tracked in
+        // https://github.com/apache/hugegraph/issues/3090
         Assume.assumeTrue("skip this test for hstore",
                           Objects.equals("hstore", System.getProperty("backend")));
 
@@ -221,7 +223,8 @@ public class RamTableTest extends BaseCoreTest {
 
     @Test
     public void testReloadAndQueryWithMultiEdges() throws Exception {
-        // FIXME: skip this test for hstore
+        // FIXME: The legacy HStore guard and related coverage debt are tracked in
+        // https://github.com/apache/hugegraph/issues/3090
         Assume.assumeTrue("skip this test for hstore",
                           Objects.equals("hstore", System.getProperty("backend")));
 
@@ -352,7 +355,8 @@ public class RamTableTest extends BaseCoreTest {
 
     @Test
     public void testReloadAndQueryWithBigVertex() throws Exception {
-        // FIXME: skip this test for hstore
+        // FIXME: The legacy HStore guard and related coverage debt are tracked in
+        // https://github.com/apache/hugegraph/issues/3090
         Assume.assumeTrue("skip this test for hstore",
                           Objects.equals("hstore", System.getProperty("backend")));
 
@@ -420,7 +424,8 @@ public class RamTableTest extends BaseCoreTest {
 
     @Test
     public void testReloadAndQueryWithProperty() throws Exception {
-        // FIXME: skip this test for hstore
+        // FIXME: The legacy HStore guard and related coverage debt are tracked in
+        // https://github.com/apache/hugegraph/issues/3090
         Assume.assumeTrue("skip this test for hstore",
                           Objects.equals("hstore", System.getProperty("backend")));
 
diff --git a/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/core/VertexCoreTest.java b/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/core/VertexCoreTest.java
index d33f9bb07..4b7ec65bc 100644
--- a/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/core/VertexCoreTest.java
+++ b/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/core/VertexCoreTest.java
@@ -2241,7 +2241,8 @@ public class VertexCoreTest extends BaseCoreTest {
 
     @Test
     public void testAddOlapSecondaryProperties() {
-        // FIXME: skip this test for hstore
+        // FIXME: The legacy HStore guard and related coverage debt are tracked in
+        // https://github.com/apache/hugegraph/issues/3090
         Assume.assumeTrue("skip this test for hstore",
                           Objects.equals("hstore", System.getProperty("backend")));
 
@@ -2332,7 +2333,8 @@ public class VertexCoreTest extends BaseCoreTest {
 
     @Test
     public void testAddOlapRangeProperties() {
-        // FIXME: skip this test for hstore
+        // FIXME: The legacy HStore guard and related coverage debt are tracked in
+        // https://github.com/apache/hugegraph/issues/3090
         Assume.assumeTrue("skip this test for hstore",
                           Objects.equals("hstore", System.getProperty("backend")));
 
@@ -2458,7 +2460,8 @@ public class VertexCoreTest extends BaseCoreTest {
 
     @Test
     public void testAddOlapRangeAndOlapSecondaryProperties() {
-        // FIXME: skip this test for hstore
+        // FIXME: The legacy HStore guard and related coverage debt are tracked in
+        // https://github.com/apache/hugegraph/issues/3090
         Assume.assumeTrue("skip this test for hstore",
                           Objects.equals("hstore", System.getProperty("backend")));
 
@@ -2632,7 +2635,8 @@ public class VertexCoreTest extends BaseCoreTest {
 
     @Test
     public void testQueryOlapRangeAndRegularSecondaryProperties() {
-        // FIXME: skip this test for hstore
+        // FIXME: The legacy HStore guard and related coverage debt are tracked in
+        // https://github.com/apache/hugegraph/issues/3090
         Assume.assumeTrue("skip this test for hstore",
                           Objects.equals("hstore", System.getProperty("backend")));
 
@@ -2761,7 +2765,8 @@ public class VertexCoreTest extends BaseCoreTest {
 
     @Test
     public void testQueryOlapWithUpdates() {
-        // FIXME: skip this test for hstore
+        // FIXME: The legacy HStore guard and related coverage debt are tracked in
+        // https://github.com/apache/hugegraph/issues/3090
         Assume.assumeTrue("skip this test for hstore",
                           Objects.equals("hstore", System.getProperty("backend")));
 
@@ -7734,7 +7739,8 @@ public class VertexCoreTest extends BaseCoreTest {
 
     @Test
     public void testScanVertexInPaging() {
-        // FIXME: skip this test for hstore
+        // FIXME: The legacy HStore guard and related coverage debt are tracked in
+        // https://github.com/apache/hugegraph/issues/3090
         Assume.assumeTrue("skip this test for hstore",
                           Objects.equals("hstore", System.getProperty("backend")));
 
@@ -8480,7 +8486,8 @@ public class VertexCoreTest extends BaseCoreTest {
 
     @Test
     public void testQueryByPropertyInPageWithLimitGtPageSize() {
-        // FIXME: skip this test for hstore
+        // FIXME: The legacy HStore guard and related coverage debt are tracked in
+        // https://github.com/apache/hugegraph/issues/3090
         Assume.assumeTrue("skip this test for hstore",
                           Objects.equals("hstore", System.getProperty("backend")));
 
@@ -8560,7 +8567,8 @@ public class VertexCoreTest extends BaseCoreTest {
 
     @Test
     public void testQueryBySingleRangePropertyInPage() {
-        // FIXME: skip this test for hstore
+        // FIXME: The legacy HStore guard and related coverage debt are tracked in
+        // https://github.com/apache/hugegraph/issues/3090
         Assume.assumeTrue("skip this test for hstore",
                           Objects.equals("hstore", System.getProperty("backend")));
 
@@ -8709,7 +8717,8 @@ public class VertexCoreTest extends BaseCoreTest {
 
     @Test
     public void testQueryByRangeIndexInPage() {
-        // FIXME: skip this test for hstore
+        // FIXME: The legacy HStore guard and related coverage debt are tracked in
+        // https://github.com/apache/hugegraph/issues/3090
         Assume.assumeTrue("skip this test for hstore",
                           Objects.equals("hstore", System.getProperty("backend")));
 
diff --git a/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/unit/UnitTestSuite.java b/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/unit/UnitTestSuite.java
index f7b2729fe..433e75a81 100644
--- a/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/unit/UnitTestSuite.java
+++ b/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/unit/UnitTestSuite.java
@@ -24,6 +24,7 @@ import org.apache.hugegraph.traversal.optimize.TraversalUtilOptimizeTest;
 import org.apache.hugegraph.unit.api.filter.LoadDetectFilterTest;
 import org.apache.hugegraph.unit.api.filter.PathFilterTest;
 import org.apache.hugegraph.unit.api.gremlin.GremlinQueryAPITest;
+import org.apache.hugegraph.unit.api.space.GraphSpaceAPITest;
 import org.apache.hugegraph.unit.auth.HugeGraphAuthProxyTest;
 import org.apache.hugegraph.unit.cache.CacheManagerTest;
 import org.apache.hugegraph.unit.cache.CacheTest;
@@ -95,6 +96,9 @@ import org.junit.runners.Suite;
         /* api gremlin */
         GremlinQueryAPITest.class,
 
+        /* api space */
+        GraphSpaceAPITest.class,
+
         /* cache */
         CacheTest.RamCacheTest.class,
         CacheTest.OffheapCacheTest.class,
diff --git a/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/unit/api/space/GraphSpaceAPITest.java b/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/unit/api/space/GraphSpaceAPITest.java
new file mode 100644
index 000000000..caa659a4d
--- /dev/null
+++ b/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/unit/api/space/GraphSpaceAPITest.java
@@ -0,0 +1,274 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.hugegraph.unit.api.space;
+
+import static org.apache.hugegraph.HugeFactory.SYS_GRAPH;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.util.Collections;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+
+import org.apache.hugegraph.HugeGraph;
+import org.apache.hugegraph.api.auth.ManagerAPI;
+import org.apache.hugegraph.api.profile.GraphsAPI;
+import org.apache.hugegraph.api.space.GraphSpaceAPI;
+import org.apache.hugegraph.auth.AuthManager;
+import org.apache.hugegraph.auth.HugeAuthenticator;
+import org.apache.hugegraph.auth.HugeDefaultRole;
+import org.apache.hugegraph.auth.HugeGraphAuthProxy;
+import org.apache.hugegraph.auth.HugeUser;
+import org.apache.hugegraph.auth.RolePermission;
+import org.apache.hugegraph.core.GraphManager;
+import org.apache.hugegraph.meta.MetaManager;
+import org.apache.hugegraph.space.GraphSpace;
+import org.apache.hugegraph.testutil.Assert;
+import org.apache.hugegraph.testutil.Whitebox;
+import org.apache.hugegraph.unit.BaseUnitTest;
+import org.apache.tinkerpop.gremlin.structure.Graph;
+import org.junit.After;
+import org.junit.Test;
+import org.mockito.Mockito;
+
+import jakarta.ws.rs.ForbiddenException;
+import jakarta.ws.rs.core.SecurityContext;
+import sun.misc.Unsafe;
+
+public class GraphSpaceAPITest extends BaseUnitTest {
+
+    private static final String GRAPHSPACE = "default_role_space";
+    private static final String ADMIN = "admin";
+    private static final String OPERATOR = "space_manager";
+    private static final String TARGET = "target_user";
+    private static final String GRAPH = "hugegraph";
+
+    @After
+    public void tearDown() {
+        HugeGraphAuthProxy.resetContext();
+    }
+
+    @Test
+    public void testSpaceManagerCannotCheckSpaceDefaultRole() {
+        GraphSpaceAPI api = new GraphSpaceAPI();
+        GraphManager manager = managerWithDefaultRoleContext(OPERATOR, false);
+        setContext(OPERATOR);
+
+        Assert.assertThrows(ForbiddenException.class, () -> {
+            api.checkDefaultRole(manager, GRAPHSPACE, TARGET, "SPACE", null);
+        });
+    }
+
+    @Test
+    public void testAdminCanCheckSpaceDefaultRole() {
+        GraphSpaceAPI api = new GraphSpaceAPI();
+        GraphManager manager = managerWithDefaultRoleContext(ADMIN, true);
+        setContext(ADMIN);
+
+        String result = api.checkDefaultRole(manager, GRAPHSPACE, TARGET,
+                                             "SPACE", null);
+
+        Assert.assertContains("\"check\":true", result);
+    }
+
+    @Test
+    public void testManagerDefaultRoleRejectsMissingGraphSpace() {
+        ManagerAPI api = new ManagerAPI();
+        GraphManager manager = managerWithDefaultRoleContext(TARGET, false);
+        Whitebox.setInternalState(manager, "graphSpaces",
+                                  new ConcurrentHashMap<>());
+        setContext(TARGET);
+
+        Assert.assertThrows(IllegalArgumentException.class, () -> {
+            api.checkDefaultRole(manager, GRAPHSPACE, "SPACE", null);
+        }, e -> {
+            Assert.assertContains("graph space", e.getMessage());
+            Assert.assertContains("does not exist", e.getMessage());
+        });
+    }
+
+    @Test
+    public void testManagerDefaultRoleRejectsMissingObserverGraph() {
+        ManagerAPI api = new ManagerAPI();
+        GraphManager manager = managerWithDefaultRoleContext(TARGET, false);
+        setContext(TARGET);
+
+        Assert.assertThrows(IllegalArgumentException.class, () -> {
+            api.checkDefaultRole(manager, GRAPHSPACE, "OBSERVER", "missing");
+        }, e -> {
+            Assert.assertContains("Graph", e.getMessage());
+            Assert.assertContains("does not exist", e.getMessage());
+        });
+    }
+
+    @Test
+    public void testManagerDefaultRoleAcceptsExistingResources() {
+        ManagerAPI api = new ManagerAPI();
+        GraphManager manager = managerWithDefaultRoleContext(TARGET, false);
+        setContext(TARGET);
+
+        String spaceResult = api.checkDefaultRole(manager, GRAPHSPACE,
+                                                  "SPACE", null);
+        String observerResult = api.checkDefaultRole(manager, GRAPHSPACE,
+                                                     "OBSERVER", GRAPH);
+
+        Assert.assertContains("\"check\":true", spaceResult);
+        Assert.assertContains("\"check\":true", observerResult);
+    }
+
+    @Test
+    public void testSpaceManagerCannotDeleteSpaceDefaultRole() {
+        GraphSpaceAPI api = new GraphSpaceAPI();
+        GraphManager manager = managerWithDefaultRoleContext(OPERATOR, false);
+        setContext(OPERATOR);
+
+        Assert.assertThrows(ForbiddenException.class, () -> {
+            api.deleteDefaultRole(manager, GRAPHSPACE, TARGET, "SPACE", null);
+        });
+    }
+
+    @Test
+    public void testPdGraphsIncludesLoadedLocalConfigGraphs() {
+        GraphManager manager = allocateGraphManager();
+        Whitebox.setInternalState(manager, "PDExist", true);
+        Whitebox.setInternalState(manager, "localGraphs",
+                                  Collections.emptySet());
+        MetaManager metaManager = Mockito.mock(MetaManager.class);
+        Mockito.when(metaManager.graphConfigs("DEFAULT"))
+               .thenReturn(Collections.emptyMap());
+        Whitebox.setInternalState(manager, "metaManager", metaManager);
+
+        Map<String, Graph> graphs = new ConcurrentHashMap<>();
+        graphs.put("DEFAULT-hugegraph", Mockito.mock(Graph.class));
+        graphs.put("DEFAULT-" + SYS_GRAPH, Mockito.mock(Graph.class));
+        Whitebox.setInternalState(manager, "graphs", graphs);
+
+        Set<String> result = manager.graphs("DEFAULT");
+
+        Assert.assertTrue(result.contains("hugegraph"));
+        Assert.assertFalse(result.contains(SYS_GRAPH));
+    }
+
+    @Test
+    public void testAdminCanListGraphsWithoutGraphReadRole() {
+        GraphManager manager = managerWithLoadedDefaultGraph(ADMIN, true);
+        setContext(ADMIN);
+
+        SecurityContext securityContext = Mockito.mock(SecurityContext.class);
+        Mockito.when(securityContext.isUserInRole(Mockito.anyString()))
+               .thenReturn(false);
+
+        @SuppressWarnings("unchecked")
+        Map<String, Object> result = (Map<String, Object>) new GraphsAPI()
+                .list(manager, "DEFAULT", securityContext);
+
+        Assert.assertEquals(Collections.singleton("hugegraph"),
+                            result.get("graphs"));
+    }
+
+    private static GraphManager managerWithDefaultRoleContext(String operator,
+                                                             boolean admin) {
+        AuthManager authManager = Mockito.mock(AuthManager.class);
+        Mockito.when(authManager.isAdminManager(operator)).thenReturn(admin);
+        Mockito.when(authManager.isSpaceManager(GRAPHSPACE, operator))
+               .thenReturn(!admin);
+        Mockito.when(authManager.isDefaultRole(GRAPHSPACE, TARGET,
+                                               HugeDefaultRole.SPACE))
+               .thenReturn(true);
+        Mockito.when(authManager.findUser(TARGET))
+               .thenReturn(new HugeUser(TARGET));
+
+        HugeAuthenticator authenticator = Mockito.mock(HugeAuthenticator.class);
+        Mockito.when(authenticator.authManager()).thenReturn(authManager);
+
+        GraphManager manager = allocateGraphManager();
+        Whitebox.setInternalState(manager, "PDExist", true);
+        Whitebox.setInternalState(manager, "authenticator", authenticator);
+        Map<String, GraphSpace> spaces = new ConcurrentHashMap<>();
+        spaces.put(GRAPHSPACE, new GraphSpace(GRAPHSPACE));
+        Whitebox.setInternalState(manager, "graphSpaces", spaces);
+
+        MetaManager metaManager = Mockito.mock(MetaManager.class);
+        Mockito.when(metaManager.graphConfigs(GRAPHSPACE))
+               .thenReturn(Collections.emptyMap());
+        Whitebox.setInternalState(manager, "metaManager", metaManager);
+
+        Map<String, Graph> graphs = new ConcurrentHashMap<>();
+        graphs.put(GRAPHSPACE + "-" + GRAPH, Mockito.mock(HugeGraph.class));
+        Whitebox.setInternalState(manager, "graphs", graphs);
+
+        Mockito.when(authManager.isDefaultRole(GRAPHSPACE, GRAPH, TARGET,
+                                               HugeDefaultRole.OBSERVER))
+               .thenReturn(true);
+        return manager;
+    }
+
+    private static GraphManager managerWithLoadedDefaultGraph(String operator,
+                                                             boolean admin) {
+        GraphManager manager = allocateGraphManager();
+        Whitebox.setInternalState(manager, "PDExist", true);
+        Whitebox.setInternalState(manager, "localGraphs",
+                                  Collections.singleton("hugegraph"));
+        MetaManager metaManager = Mockito.mock(MetaManager.class);
+        Mockito.when(metaManager.graphConfigs("DEFAULT"))
+               .thenReturn(Collections.emptyMap());
+        Whitebox.setInternalState(manager, "metaManager", metaManager);
+
+        AuthManager authManager = Mockito.mock(AuthManager.class);
+        Mockito.when(authManager.isAdminManager(operator)).thenReturn(admin);
+        HugeAuthenticator authenticator = Mockito.mock(HugeAuthenticator.class);
+        Mockito.when(authenticator.authManager()).thenReturn(authManager);
+        Whitebox.setInternalState(manager, "authenticator", authenticator);
+
+        Map<String, GraphSpace> spaces = new ConcurrentHashMap<>();
+        spaces.put("DEFAULT", new GraphSpace("DEFAULT"));
+        Whitebox.setInternalState(manager, "graphSpaces", spaces);
+
+        Map<String, Graph> graphs = new ConcurrentHashMap<>();
+        graphs.put("DEFAULT-hugegraph", Mockito.mock(HugeGraph.class));
+        Whitebox.setInternalState(manager, "graphs", graphs);
+        return manager;
+    }
+
+    private static GraphManager allocateGraphManager() {
+        try {
+            Field field = Unsafe.class.getDeclaredField("theUnsafe");
+            field.setAccessible(true);
+            Unsafe unsafe = (Unsafe) field.get(null);
+            return (GraphManager) unsafe.allocateInstance(GraphManager.class);
+        } catch (Exception e) {
+            throw new AssertionError(e);
+        }
+    }
+
+    private static void setContext(String username) {
+        try {
+            HugeAuthenticator.User user = new HugeAuthenticator.User(
+                    username, RolePermission.admin());
+            HugeGraphAuthProxy.Context context =
+                    new HugeGraphAuthProxy.Context(user);
+            Method method = HugeGraphAuthProxy.class.getDeclaredMethod(
+                    "setContext", HugeGraphAuthProxy.Context.class);
+            method.setAccessible(true);
+            method.invoke(null, context);
+        } catch (Exception e) {
+            throw new AssertionError(e);
+        }
+    }
+}
diff --git a/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/unit/auth/HugeGraphAuthProxyTest.java b/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/unit/auth/HugeGraphAuthProxyTest.java
index 1bff9b29d..e6520b018 100644
--- a/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/unit/auth/HugeGraphAuthProxyTest.java
+++ b/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/unit/auth/HugeGraphAuthProxyTest.java
@@ -19,14 +19,23 @@ package org.apache.hugegraph.unit.auth;
 
 import java.lang.reflect.Method;
 
+import org.apache.hugegraph.HugeGraph;
+import org.apache.hugegraph.auth.AuthManager;
 import org.apache.hugegraph.auth.HugeAuthenticator;
+import org.apache.hugegraph.auth.HugeDefaultRole;
 import org.apache.hugegraph.auth.HugeGraphAuthProxy;
 import org.apache.hugegraph.auth.RolePermission;
+import org.apache.hugegraph.auth.UserWithRole;
+import org.apache.hugegraph.backend.id.IdGenerator;
+import org.apache.hugegraph.config.AuthOptions;
+import org.apache.hugegraph.config.HugeConfig;
 import org.apache.hugegraph.task.TaskManager;
+import org.apache.hugegraph.task.TaskScheduler;
 import org.apache.hugegraph.testutil.Assert;
 import org.apache.hugegraph.unit.BaseUnitTest;
 import org.junit.After;
 import org.junit.Test;
+import org.mockito.Mockito;
 
 public class HugeGraphAuthProxyTest extends BaseUnitTest {
 
@@ -195,4 +204,44 @@ public class HugeGraphAuthProxyTest extends BaseUnitTest {
         // username() should now return "anonymous"
         Assert.assertEquals("anonymous", HugeGraphAuthProxy.username());
     }
+
+    @Test
+    public void testDefaultRoleMutationInvalidatesUserRoleCache()
+            throws Exception {
+        HugeGraph graph = Mockito.mock(HugeGraph.class);
+        HugeConfig config = Mockito.mock(HugeConfig.class);
+        AuthManager authManager = Mockito.mock(AuthManager.class);
+        TaskScheduler scheduler = Mockito.mock(TaskScheduler.class);
+
+        Mockito.when(graph.spaceGraphName()).thenReturn("hugegraph");
+        Mockito.when(graph.configuration()).thenReturn(config);
+        Mockito.when(graph.authManager()).thenReturn(authManager);
+        Mockito.when(graph.taskScheduler()).thenReturn(scheduler);
+        Mockito.when(config.get(AuthOptions.AUTH_CACHE_EXPIRE))
+               .thenReturn(3600L);
+        Mockito.when(config.get(AuthOptions.AUTH_CACHE_CAPACITY))
+               .thenReturn(100L);
+        Mockito.when(config.get(AuthOptions.AUTH_AUDIT_LOG_RATE))
+               .thenReturn(1000D);
+        Mockito.when(authManager.validateUser("cache_user", "pass"))
+               .thenReturn(new UserWithRole("cache_user"));
+        Mockito.when(authManager.createDefaultRole("DEFAULT", "cache_user",
+                                                  HugeDefaultRole.ANALYST,
+                                                  "hugegraph"))
+               .thenReturn(IdGenerator.of("default-role"));
+
+        HugeGraphAuthProxy proxy = new HugeGraphAuthProxy(graph);
+        AuthManager proxyAuthManager = proxy.authManager();
+
+        proxyAuthManager.validateUser("cache_user", "pass");
+        proxyAuthManager.validateUser("cache_user", "pass");
+        Mockito.verify(authManager, Mockito.times(1))
+               .validateUser("cache_user", "pass");
+
+        proxyAuthManager.createDefaultRole("DEFAULT", "cache_user",
+                                           HugeDefaultRole.ANALYST, "hugegraph");
+        proxyAuthManager.validateUser("cache_user", "pass");
+        Mockito.verify(authManager, Mockito.times(2))
+               .validateUser("cache_user", "pass");
+    }
 }
diff --git a/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/unit/cache/CachedSchemaTransactionTest.java b/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/unit/cache/CachedSchemaTransactionTest.java
index 9921670ee..9e7aec384 100644
--- a/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/unit/cache/CachedSchemaTransactionTest.java
+++ b/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/unit/cache/CachedSchemaTransactionTest.java
@@ -397,16 +397,15 @@ public class CachedSchemaTransactionTest extends BaseUnitTest {
         String graphName = "DEFAULT-unit-test-v2";
         String otherGraphName = "DEFAULT-other-v2";
 
-        Cache<Id, Object> idCache = CacheManager.instance()
-                                                .cache("schema-id-" +
-                                                       graphName, 10L);
-        Cache<Id, Object> nameCache = CacheManager.instance()
-                                                  .cache("schema-name-" +
-                                                         graphName, 10L);
-        Cache<Id, Object> otherIdCache = CacheManager.instance()
-                                                     .cache("schema-id-" +
-                                                            otherGraphName,
-                                                            10L);
+        Cache<Id, Object> idCache = v2IdCache(graphName);
+        Cache<Id, Object> nameCache = v2NameCache(graphName);
+        Cache<Id, Object> otherIdCache = v2IdCache(otherGraphName);
+        Cache<Id, Object> v1IdCache = CacheManager.instance()
+                                                    .cache("schema-id-" +
+                                                           graphName, 10L);
+        Cache<Id, Object> v1NameCache = CacheManager.instance()
+                                                      .cache("schema-name-" +
+                                                             graphName, 10L);
         Object arrayCaches = idCache.attachment(newV2SchemaCaches(10));
         Id arrayCacheId = IdGenerator.of(1);
         SchemaElement arrayCacheSchema =
@@ -420,10 +419,16 @@ public class CachedSchemaTransactionTest extends BaseUnitTest {
             idCache.update(IdGenerator.of(1), "fake-pk-by-id");
             nameCache.update(IdGenerator.of("fake-pk"), "fake-pk-by-name");
             otherIdCache.update(IdGenerator.of(2), "other-pk-by-id");
+            v1IdCache.update(IdGenerator.of(3), "v1-pk-by-id");
+            v1NameCache.update(IdGenerator.of("v1-pk"), "v1-pk-by-name");
 
+            Assert.assertNotSame(v1IdCache, idCache);
+            Assert.assertNotSame(v1NameCache, nameCache);
             Assert.assertEquals(1L, idCache.size());
             Assert.assertEquals(1L, nameCache.size());
             Assert.assertEquals(1L, otherIdCache.size());
+            Assert.assertEquals(1L, v1IdCache.size());
+            Assert.assertEquals(1L, v1NameCache.size());
             Assert.assertSame(arrayCacheSchema,
                               getV2SchemaCache(arrayCaches,
                                                HugeType.PROPERTY_KEY,
@@ -436,6 +441,8 @@ public class CachedSchemaTransactionTest extends BaseUnitTest {
             Assert.assertEquals(0L, idCache.size());
             Assert.assertEquals(0L, nameCache.size());
             Assert.assertEquals(1L, otherIdCache.size());
+            Assert.assertEquals(1L, v1IdCache.size());
+            Assert.assertEquals(1L, v1NameCache.size());
             Assert.assertNull(getV2SchemaCache(arrayCaches,
                                                HugeType.PROPERTY_KEY,
                                                arrayCacheId));
@@ -444,9 +451,28 @@ public class CachedSchemaTransactionTest extends BaseUnitTest {
             idCache.clear();
             nameCache.clear();
             otherIdCache.clear();
+            v1IdCache.clear();
+            v1NameCache.clear();
         }
     }
 
+    private static Cache<Id, Object> v2IdCache(String graphName) {
+        return v2Cache("ID_CACHE_PREFIX", graphName);
+    }
+
+    private static Cache<Id, Object> v2NameCache(String graphName) {
+        return v2Cache("NAME_CACHE_PREFIX", graphName);
+    }
+
+    private static Cache<Id, Object> v2Cache(String prefixField,
+                                             String graphName) {
+        // Keep test fixtures aligned with the private cache namespace used by
+        // CachedSchemaTransactionV2 without duplicating production literals.
+        String prefix = Whitebox.getInternalState(
+                CachedSchemaTransactionV2.class, prefixField);
+        return CacheManager.instance().cache(prefix + "-" + graphName, 10L);
+    }
+
     private static Object newV2SchemaCaches(int size) {
         for (Class<?> clazz :
              CachedSchemaTransactionV2.class.getDeclaredClasses()) {
@@ -534,10 +560,8 @@ public class CachedSchemaTransactionTest extends BaseUnitTest {
         // IntObjectMap (pks/vls/els/ils) inside the array attachment so
         // stale entries are not served after a meta event.
         String graphName = "DEFAULT-unit-test-v2-array";
-        Cache<Id, Object> idCache =
-                CacheManager.instance().cache("schema-id-" + graphName, 10L);
-        Cache<Id, Object> nameCache =
-                CacheManager.instance().cache("schema-name-" + graphName, 10L);
+        Cache<Id, Object> idCache = v2IdCache(graphName);
+        Cache<Id, Object> nameCache = v2NameCache(graphName);
         // Size must comfortably exceed the largest id below: IntObjectMap
         // grows by doubling and refuses to write past currentSize even after
         // a single expansion, so a small capacity rejects mid-range keys.
@@ -598,11 +622,8 @@ public class CachedSchemaTransactionTest extends BaseUnitTest {
     public void testHandleSchemaCacheClearEventSkipsLocalSource()
             throws Exception {
         String graphName = "DEFAULT-meta-local-source-v2";
-        Cache<Id, Object> idCache =
-                CacheManager.instance().cache("schema-id-" + graphName, 10L);
-        Cache<Id, Object> nameCache =
-                CacheManager.instance()
-                            .cache("schema-name-" + graphName, 10L);
+        Cache<Id, Object> idCache = v2IdCache(graphName);
+        Cache<Id, Object> nameCache = v2NameCache(graphName);
 
         MetaDriver mockDriver = Mockito.mock(MetaDriver.class);
         Object localResponse = new Object();
@@ -658,13 +679,9 @@ public class CachedSchemaTransactionTest extends BaseUnitTest {
         String targetGraph = "DEFAULT-meta-target-v2";
         String otherGraph = "DEFAULT-meta-other-v2";
 
-        Cache<Id, Object> targetIdCache =
-                CacheManager.instance().cache("schema-id-" + targetGraph, 10L);
-        Cache<Id, Object> targetNameCache =
-                CacheManager.instance()
-                            .cache("schema-name-" + targetGraph, 10L);
-        Cache<Id, Object> otherIdCache =
-                CacheManager.instance().cache("schema-id-" + otherGraph, 10L);
+        Cache<Id, Object> targetIdCache = v2IdCache(targetGraph);
+        Cache<Id, Object> targetNameCache = v2NameCache(targetGraph);
+        Cache<Id, Object> otherIdCache = v2IdCache(otherGraph);
 
         MetaDriver mockDriver = Mockito.mock(MetaDriver.class);
         Object response = new Object();
@@ -703,8 +720,7 @@ public class CachedSchemaTransactionTest extends BaseUnitTest {
         // A response that yields no graph names (extractor returns null) must
         // be a strict noop: caches stay populated.
         String graphName = "DEFAULT-meta-noop-v2";
-        Cache<Id, Object> idCache =
-                CacheManager.instance().cache("schema-id-" + graphName, 10L);
+        Cache<Id, Object> idCache = v2IdCache(graphName);
 
         MetaDriver mockDriver = Mockito.mock(MetaDriver.class);
         Object response = new Object();
@@ -735,10 +751,8 @@ public class CachedSchemaTransactionTest extends BaseUnitTest {
         // them must have its V2 caches cleared.
         String graphA = "DEFAULT-meta-multi-a";
         String graphB = "DEFAULT-meta-multi-b";
-        Cache<Id, Object> idA =
-                CacheManager.instance().cache("schema-id-" + graphA, 10L);
-        Cache<Id, Object> idB =
-                CacheManager.instance().cache("schema-id-" + graphB, 10L);
+        Cache<Id, Object> idA = v2IdCache(graphA);
+        Cache<Id, Object> idB = v2IdCache(graphB);
 
         MetaDriver mockDriver = Mockito.mock(MetaDriver.class);
         Object response = new Object();
@@ -811,11 +825,8 @@ public class CachedSchemaTransactionTest extends BaseUnitTest {
         // MetaDriver, then invoke it as the watch would and assert the V2
         // caches for the named graph are cleared.
         String graphName = "DEFAULT-end-to-end-v2";
-        Cache<Id, Object> idCache =
-                CacheManager.instance().cache("schema-id-" + graphName, 10L);
-        Cache<Id, Object> nameCache =
-                CacheManager.instance()
-                            .cache("schema-name-" + graphName, 10L);
+        Cache<Id, Object> idCache = v2IdCache(graphName);
+        Cache<Id, Object> nameCache = v2NameCache(graphName);
 
         MetaDriver mockDriver = Mockito.mock(MetaDriver.class);
         Object response = new Object();
diff --git a/hugegraph-server/hugegraph-test/src/test/java/org/apache/hugegraph/dist/HugeGraphServerTest.java b/hugegraph-server/hugegraph-test/src/test/java/org/apache/hugegraph/dist/HugeGraphServerTest.java
new file mode 100644
index 000000000..dafe38caa
--- /dev/null
+++ b/hugegraph-server/hugegraph-test/src/test/java/org/apache/hugegraph/dist/HugeGraphServerTest.java
@@ -0,0 +1,79 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.hugegraph.dist;
+
+import java.util.concurrent.CompletableFuture;
+
+import org.apache.hugegraph.server.RestServer;
+import org.apache.tinkerpop.gremlin.server.GremlinServer;
+import org.junit.Test;
+import org.mockito.Mockito;
+
+public class HugeGraphServerTest {
+
+    @Test
+    public void testRollbackStartupStopsAllStartedResources() {
+        MemoryMonitor monitor = Mockito.mock(MemoryMonitor.class);
+        GremlinServer gremlinServer = Mockito.mock(GremlinServer.class);
+        RestServer restServer = Mockito.mock(RestServer.class);
+        Mockito.when(gremlinServer.stop()).thenReturn(
+                CompletableFuture.completedFuture(null));
+        Mockito.when(restServer.shutdown()).thenReturn(
+                CompletableFuture.completedFuture(null));
+
+        HugeGraphServer.rollbackStartup(monitor, gremlinServer, restServer);
+
+        Mockito.verify(monitor).stop();
+        Mockito.verify(gremlinServer).stop();
+        Mockito.verify(restServer).shutdown();
+    }
+
+    @Test
+    public void testRollbackStartupAfterMonitorConstructionFailure() {
+        GremlinServer gremlinServer = Mockito.mock(GremlinServer.class);
+        RestServer restServer = Mockito.mock(RestServer.class);
+        Mockito.when(gremlinServer.stop()).thenReturn(
+                CompletableFuture.completedFuture(null));
+        Mockito.when(restServer.shutdown()).thenReturn(
+                CompletableFuture.completedFuture(null));
+
+        HugeGraphServer.rollbackStartup(null, gremlinServer, restServer);
+
+        Mockito.verify(gremlinServer).stop();
+        Mockito.verify(restServer).shutdown();
+    }
+
+    @Test
+    public void testRollbackStartupContinuesAfterStopFailure() {
+        MemoryMonitor monitor = Mockito.mock(MemoryMonitor.class);
+        GremlinServer gremlinServer = Mockito.mock(GremlinServer.class);
+        RestServer restServer = Mockito.mock(RestServer.class);
+        Mockito.doThrow(new RuntimeException("monitor stop failed"))
+               .when(monitor).stop();
+        Mockito.when(gremlinServer.stop())
+               .thenThrow(new RuntimeException("gremlin stop failed"));
+        Mockito.when(restServer.shutdown()).thenReturn(
+                CompletableFuture.completedFuture(null));
+
+        HugeGraphServer.rollbackStartup(monitor, gremlinServer, restServer);
+
+        Mockito.verify(monitor).stop();
+        Mockito.verify(gremlinServer).stop();
+        Mockito.verify(restServer).shutdown();
+    }
+}
diff --git a/hugegraph-server/hugegraph-test/src/test/java/org/apache/hugegraph/unit/config/ServerOptionsTest.java b/hugegraph-server/hugegraph-test/src/test/java/org/apache/hugegraph/unit/config/ServerOptionsTest.java
index 6113c748b..53a9a7f83 100644
--- a/hugegraph-server/hugegraph-test/src/test/java/org/apache/hugegraph/unit/config/ServerOptionsTest.java
+++ b/hugegraph-server/hugegraph-test/src/test/java/org/apache/hugegraph/unit/config/ServerOptionsTest.java
@@ -18,6 +18,7 @@
 package org.apache.hugegraph.unit.config;
 
 import org.apache.commons.configuration2.PropertiesConfiguration;
+import org.apache.hugegraph.config.ConfigException;
 import org.apache.hugegraph.config.HugeConfig;
 import org.apache.hugegraph.config.OptionSpace;
 import org.apache.hugegraph.config.ServerOptions;
@@ -119,4 +120,25 @@ public class ServerOptionsTest {
         Assert.assertEquals("https://127.0.0.1:8080",
                             config.get(ServerOptions.REST_SERVER_URL));
     }
+
+    @Test
+    public void testMemoryMonitorPeriodMustBePositive() {
+        PropertiesConfiguration conf = new PropertiesConfiguration();
+        conf.setProperty("memory_monitor.period", "0");
+
+        Assert.assertThrows(ConfigException.class, () -> {
+            new HugeConfig(conf);
+        });
+    }
+
+    @Test
+    public void testMemoryMonitorThresholdOneIsValid() {
+        PropertiesConfiguration conf = new PropertiesConfiguration();
+        conf.setProperty("memory_monitor.threshold", "1.0");
+
+        HugeConfig config = new HugeConfig(conf);
+
+        Assert.assertEquals(1.0D,
+                            config.get(ServerOptions.JVM_MEMORY_MONITOR_THRESHOLD));
+    }
 }