You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@polaris.apache.org by su...@apache.org on 2026/07/08 14:46:45 UTC

(polaris) branch main updated: runtime: Auth Refactor - Update `CatalogHandler`s and `PolarisAdminService` to use `resolveAuthorizationInputs` (#4356)

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

sungwy pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/polaris.git


The following commit(s) were added to refs/heads/main by this push:
     new 896841eac6 runtime: Auth Refactor - Update `CatalogHandler`s and `PolarisAdminService` to use `resolveAuthorizationInputs` (#4356)
896841eac6 is described below

commit 896841eac6830375f20b67d7159cf91bca9026f4
Author: Sung Yun <10...@users.noreply.github.com>
AuthorDate: Wed Jul 8 10:46:41 2026 -0400

    runtime: Auth Refactor - Update `CatalogHandler`s and `PolarisAdminService` to use `resolveAuthorizationInputs` (#4356)
    
    * use resolveAuthorizationInputs
    
    * clean up
    
    * use PolarisSecurable.of directly
    
    * implement resolveAuthorizationInputs in RangerPolarisAuthorizer
    
    * lint
    
    * fix tests
    
    * adopt review feedback
    
    * use constructor
    
    * fix
    
    * Use request-scoped CDI AuthorizationState instead of local instantiation
    
    * make fallbackToFullLoadTable reuse resolved entity instead of authorizing twice
    
    * update test suite to refresh used request-scoped context
    
    * update test suite to refresh used request-scoped context
    
    * adopt feedback - thanks @flyrain
    
    * AuthorizationState no longer request-scoped
    
    * only allow resolveAll to succeed once
---
 CHANGELOG.md                                       |   1 +
 .../auth/opa/OpaPolarisAuthorizerTest.java         |  21 +-
 .../auth/ranger/RangerPolarisAuthorizer.java       |   8 +-
 .../polaris/core/auth/AuthorizationState.java      |  30 +-
 .../resolver/PolarisResolutionManifest.java        |   9 +
 .../core/auth/PolarisAuthorizerImplTest.java       |  22 +-
 .../polaris/service/admin/PolarisAdminService.java | 121 ++++++-
 .../service/catalog/common/CatalogHandler.java     | 155 ++++++++-
 .../catalog/common/PolarisSecurableMapper.java     | 119 +++++++
 .../generic/GenericTableCatalogHandler.java        |   6 +-
 .../catalog/iceberg/IcebergCatalogHandler.java     | 127 +++++--
 .../catalog/policy/PolicyCatalogHandler.java       |  61 +++-
 .../polaris/service/config/ServiceProducers.java   |   7 -
 .../admin/PolarisAdminServiceAuthzTest.java        | 268 ++++++++-------
 .../service/admin/PolarisAdminServiceTest.java     |  64 +++-
 .../admin/PolarisAdminServiceTestSupport.java      |  52 +++
 .../service/admin/PolarisAuthzTestBase.java        | 107 +++---
 .../service/admin/PolarisAuthzTestsFactory.java    |  75 ++++-
 .../service/auth/DefaultAuthenticatorTest.java     |  37 +-
 .../catalog/common/PolarisSecurableMapperTest.java |  52 +++
 .../AbstractPolarisGenericTableCatalogTest.java    |   3 +-
 ...PolarisGenericTableCatalogHandlerAuthzTest.java |  48 ++-
 .../AbstractIcebergCatalogHandlerAuthzTest.java    | 373 +++++++++++++--------
 .../iceberg/AbstractLocalIcebergCatalogTest.java   | 168 ++++++----
 .../AbstractLocalIcebergCatalogViewTest.java       |  71 ++--
 ...ebergCatalogHandlerFineGrainedDisabledTest.java |  10 +-
 .../catalog/iceberg/IcebergCatalogHandlerTest.java | 295 +++++++++++++++-
 .../catalog/policy/AbstractPolicyCatalogTest.java  |   3 +-
 .../policy/PolicyCatalogHandlerAuthzTest.java      | 112 ++++---
 .../org/apache/polaris/service/TestServices.java   | 259 ++++++++------
 30 files changed, 1945 insertions(+), 739 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 7abb675ad4..7971817141 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -36,6 +36,7 @@ request adding CHANGELOG notes for breaking (!) changes and possibly other secti
 
 ### Breaking changes
 - The `MaintenanceService.performMaintenance()` signature now requires an explicit `OptionalLong overrideRunId` argument to supersede the latest unfinished maintenance run.
+- Admin grant APIs now reject table-like privilege targets with an empty namespace. A table-like target without a namespace is considered invalid input.
 - The REST layer now enforces stricter validation for entity names (including namespaces, tables, views, and generic tables). Requests containing invalid names will be rejected with an HTTP 400 error. Existing clients should verify and rename entities before upgrading if their names fall into the following forbidden categories:
     - Empty strings
     - Names consisting solely of `.` or `..`
diff --git a/extensions/auth/opa/src/test/java/org/apache/polaris/extension/auth/opa/OpaPolarisAuthorizerTest.java b/extensions/auth/opa/src/test/java/org/apache/polaris/extension/auth/opa/OpaPolarisAuthorizerTest.java
index 02009d8d51..bfae693671 100644
--- a/extensions/auth/opa/src/test/java/org/apache/polaris/extension/auth/opa/OpaPolarisAuthorizerTest.java
+++ b/extensions/auth/opa/src/test/java/org/apache/polaris/extension/auth/opa/OpaPolarisAuthorizerTest.java
@@ -575,8 +575,7 @@ public class OpaPolarisAuthorizerTest {
             JsonMapper.builder().build(),
             null);
     PolarisResolutionManifest resolutionManifest = mock(PolarisResolutionManifest.class);
-    AuthorizationState authzState = new AuthorizationState();
-    authzState.setResolutionManifest(resolutionManifest);
+    AuthorizationState authzState = new AuthorizationState(resolutionManifest);
     PolarisPrincipal principal = PolarisPrincipal.of("alice", Map.of(), Set.of("role-1"));
 
     authorizer.resolveAuthorizationInputs(authzState, requestWithCatalogTarget(principal));
@@ -594,8 +593,7 @@ public class OpaPolarisAuthorizerTest {
     ClassicHttpResponse mockResponse = new BasicClassicHttpResponse(200);
     mockResponse.setEntity(mockEntity);
     PolarisResolutionManifest resolutionManifest = mock(PolarisResolutionManifest.class);
-    AuthorizationState authzState = new AuthorizationState();
-    authzState.setResolutionManifest(resolutionManifest);
+    AuthorizationState authzState = new AuthorizationState(resolutionManifest);
 
     OpaPolarisAuthorizer authorizer =
         new OpaPolarisAuthorizer(
@@ -643,8 +641,7 @@ public class OpaPolarisAuthorizerTest {
     ClassicHttpResponse mockResponse = new BasicClassicHttpResponse(200);
     mockResponse.setEntity(mockEntity);
     PolarisResolutionManifest resolutionManifest = mock(PolarisResolutionManifest.class);
-    AuthorizationState authzState = new AuthorizationState();
-    authzState.setResolutionManifest(resolutionManifest);
+    AuthorizationState authzState = new AuthorizationState(resolutionManifest);
 
     OpaPolarisAuthorizer authorizer =
         new OpaPolarisAuthorizer(
@@ -691,8 +688,7 @@ public class OpaPolarisAuthorizerTest {
     ClassicHttpResponse mockResponse = new BasicClassicHttpResponse(200);
     mockResponse.setEntity(mockEntity);
     PolarisResolutionManifest resolutionManifest = mock(PolarisResolutionManifest.class);
-    AuthorizationState authzState = new AuthorizationState();
-    authzState.setResolutionManifest(resolutionManifest);
+    AuthorizationState authzState = new AuthorizationState(resolutionManifest);
 
     OpaPolarisAuthorizer authorizer =
         new OpaPolarisAuthorizer(
@@ -861,8 +857,7 @@ public class OpaPolarisAuthorizerTest {
     mockResponse.setEntity(mockEntity);
 
     PolarisResolutionManifest resolutionManifest = mock(PolarisResolutionManifest.class);
-    AuthorizationState authzState = new AuthorizationState();
-    authzState.setResolutionManifest(resolutionManifest);
+    AuthorizationState authzState = new AuthorizationState(resolutionManifest);
 
     AuthorizationRequest request =
         new AuthorizationRequest(
@@ -941,8 +936,7 @@ public class OpaPolarisAuthorizerTest {
     mockResponse.setEntity(mockEntity);
 
     PolarisResolutionManifest resolutionManifest = mock(PolarisResolutionManifest.class);
-    AuthorizationState authzState = new AuthorizationState();
-    authzState.setResolutionManifest(resolutionManifest);
+    AuthorizationState authzState = new AuthorizationState(resolutionManifest);
     PolarisPrincipal principal = PolarisPrincipal.of("alice", Map.of(), Set.of("role-1"));
 
     OpaPolarisAuthorizer authorizer =
@@ -1023,8 +1017,7 @@ public class OpaPolarisAuthorizerTest {
     mockResponse.setEntity(mockEntity);
 
     PolarisResolutionManifest resolutionManifest = mock(PolarisResolutionManifest.class);
-    AuthorizationState authzState = new AuthorizationState();
-    authzState.setResolutionManifest(resolutionManifest);
+    AuthorizationState authzState = new AuthorizationState(resolutionManifest);
     PolarisPrincipal principal = PolarisPrincipal.of("alice", Map.of(), Set.of("role-1"));
     PolarisSecurable tableTarget =
         PolarisSecurable.of(
diff --git a/extensions/auth/ranger/src/main/java/org/apache/polaris/extension/auth/ranger/RangerPolarisAuthorizer.java b/extensions/auth/ranger/src/main/java/org/apache/polaris/extension/auth/ranger/RangerPolarisAuthorizer.java
index ea32709eb4..4be898f951 100644
--- a/extensions/auth/ranger/src/main/java/org/apache/polaris/extension/auth/ranger/RangerPolarisAuthorizer.java
+++ b/extensions/auth/ranger/src/main/java/org/apache/polaris/extension/auth/ranger/RangerPolarisAuthorizer.java
@@ -75,10 +75,16 @@ public class RangerPolarisAuthorizer implements PolarisAuthorizer {
     this.realmConextIdentifier = aRealmContext.getRealmIdentifier();
   }
 
+  /**
+   * Resolves authorization inputs using {@code resolveAll()} for backward compatibility.
+   *
+   * <p>This scope is intentionally broad for now and will be narrowed in a future refactoring to
+   * resolve only the selections required by Ranger authorization.
+   */
   @Override
   public void resolveAuthorizationInputs(
       @NonNull AuthorizationState authzState, @NonNull AuthorizationRequest request) {
-    throw new UnsupportedOperationException("resolveAuthorizationInputs is not implemented yet");
+    authzState.getResolutionManifest().resolveAll();
   }
 
   @Override
diff --git a/polaris-core/src/main/java/org/apache/polaris/core/auth/AuthorizationState.java b/polaris-core/src/main/java/org/apache/polaris/core/auth/AuthorizationState.java
index 8d056c7630..4e1e8f11da 100644
--- a/polaris-core/src/main/java/org/apache/polaris/core/auth/AuthorizationState.java
+++ b/polaris-core/src/main/java/org/apache/polaris/core/auth/AuthorizationState.java
@@ -19,35 +19,25 @@
 package org.apache.polaris.core.auth;
 
 import java.util.Objects;
-import java.util.concurrent.atomic.AtomicReference;
 import org.apache.polaris.core.persistence.resolver.PolarisResolutionManifest;
 import org.jspecify.annotations.NonNull;
 
 /**
- * Request-scoped authorization state shared across authorization phases.
+ * Authorization state shared across authorization phases for one resolved view.
  *
- * <p>Used to carry authorization-specific data, such as a {@link PolarisResolutionManifest} created
- * by the caller and populated by the authorizer.
+ * <p>Used to carry authorization-specific data for the current resolution flow, such as a {@link
+ * PolarisResolutionManifest} created by the caller and populated by the authorizer.
  */
 public class AuthorizationState {
-  private final AtomicReference<PolarisResolutionManifest> resolutionManifest =
-      new AtomicReference<>();
+  private final PolarisResolutionManifest resolutionManifest;
 
-  /** Returns the request-scoped resolution manifest used for authorization. */
-  @NonNull
-  public PolarisResolutionManifest getResolutionManifest() {
-    PolarisResolutionManifest manifest = resolutionManifest.get();
-    if (manifest == null) {
-      throw new IllegalStateException("AuthorizationState resolution manifest is not set");
-    }
-    return manifest;
+  public AuthorizationState(@NonNull PolarisResolutionManifest resolutionManifest) {
+    this.resolutionManifest = Objects.requireNonNull(resolutionManifest, "resolutionManifest");
   }
 
-  /** Sets the request-scoped resolution manifest used for authorization. */
-  public void setResolutionManifest(@NonNull PolarisResolutionManifest resolutionManifest) {
-    Objects.requireNonNull(resolutionManifest, "resolutionManifest");
-    if (!this.resolutionManifest.compareAndSet(null, resolutionManifest)) {
-      throw new IllegalStateException("AuthorizationState resolution manifest already set");
-    }
+  /** Returns the resolution manifest used for the current authorization flow. */
+  @NonNull
+  public PolarisResolutionManifest getResolutionManifest() {
+    return resolutionManifest;
   }
 }
diff --git a/polaris-core/src/main/java/org/apache/polaris/core/persistence/resolver/PolarisResolutionManifest.java b/polaris-core/src/main/java/org/apache/polaris/core/persistence/resolver/PolarisResolutionManifest.java
index d254f0317f..20716e26ae 100644
--- a/polaris-core/src/main/java/org/apache/polaris/core/persistence/resolver/PolarisResolutionManifest.java
+++ b/polaris-core/src/main/java/org/apache/polaris/core/persistence/resolver/PolarisResolutionManifest.java
@@ -156,6 +156,15 @@ public class PolarisResolutionManifest implements PolarisResolutionManifestCatal
     return primaryResolverStatus;
   }
 
+  public ResolverStatus getPrimaryResolverStatusOrThrow() {
+    ResolverStatus status = primaryResolverStatus;
+    diagnostics.checkNotNull(
+        status,
+        "resolver_not_run_before_access",
+        "resolveAll() or resolveSelections() must be called before reading resolver status");
+    return status;
+  }
+
   public boolean getIsPassthroughFacade() {
     return primaryResolver.getIsPassthroughFacade();
   }
diff --git a/polaris-core/src/test/java/org/apache/polaris/core/auth/PolarisAuthorizerImplTest.java b/polaris-core/src/test/java/org/apache/polaris/core/auth/PolarisAuthorizerImplTest.java
index 6c815995e2..ac70bb5ffd 100644
--- a/polaris-core/src/test/java/org/apache/polaris/core/auth/PolarisAuthorizerImplTest.java
+++ b/polaris-core/src/test/java/org/apache/polaris/core/auth/PolarisAuthorizerImplTest.java
@@ -62,8 +62,8 @@ public class PolarisAuthorizerImplTest {
   @Test
   void resolveAuthorizationInputsResolvesAll() {
     PolarisAuthorizerImpl authorizer = new PolarisAuthorizerImpl(mock(RealmConfig.class));
-    AuthorizationState authzState = new AuthorizationState();
     PolarisResolutionManifest manifest = mock(PolarisResolutionManifest.class);
+    AuthorizationState authzState = new AuthorizationState(manifest);
     PolarisPrincipal principal = PolarisPrincipal.of("alice", Map.of(), Set.of("role"));
     AuthorizationRequest request =
         new AuthorizationRequest(
@@ -73,8 +73,6 @@ public class PolarisAuthorizerImplTest {
                     PolarisAuthorizableOperation.GET_CATALOG,
                     PolarisSecurable.of(new PathSegment(PolarisEntityType.CATALOG, "catalog")))));
 
-    authzState.setResolutionManifest(manifest);
-
     authorizer.resolveAuthorizationInputs(authzState, request);
 
     verify(manifest).resolveAll();
@@ -85,13 +83,12 @@ public class PolarisAuthorizerImplTest {
     // Verify that new authorize SPI call without primary target uses root_container
     // for resolution and authorization
     PolarisAuthorizerImpl authorizer = spy(new PolarisAuthorizerImpl(mock(RealmConfig.class)));
-    AuthorizationState authzState = new AuthorizationState();
     PolarisResolutionManifest manifest = mock(PolarisResolutionManifest.class);
+    AuthorizationState authzState = new AuthorizationState(manifest);
     PolarisResolvedPathWrapper rootWrapper = mock(PolarisResolvedPathWrapper.class);
     PolarisResolvedPathWrapper principalRoleWrapper = mock(PolarisResolvedPathWrapper.class);
     PolarisPrincipal principal = PolarisPrincipal.of("alice", Map.of(), Set.of("role"));
 
-    authzState.setResolutionManifest(manifest);
     when(manifest.getResolvedRootContainerEntityAsPath()).thenReturn(rootWrapper);
     when(manifest.getResolvedTopLevelEntity("analytics-admin", PolarisEntityType.PRINCIPAL_ROLE))
         .thenReturn(principalRoleWrapper);
@@ -131,12 +128,11 @@ public class PolarisAuthorizerImplTest {
     // Verify that new authorize SPI call without primary target uses root_container
     // for resolution and authorization
     PolarisAuthorizerImpl authorizer = spy(new PolarisAuthorizerImpl(mock(RealmConfig.class)));
-    AuthorizationState authzState = new AuthorizationState();
     PolarisResolutionManifest manifest = mock(PolarisResolutionManifest.class);
+    AuthorizationState authzState = new AuthorizationState(manifest);
     PolarisResolvedPathWrapper rootWrapper = mock(PolarisResolvedPathWrapper.class);
     PolarisPrincipal principal = PolarisPrincipal.of("alice", Map.of(), Set.of("role"));
 
-    authzState.setResolutionManifest(manifest);
     when(manifest.getResolvedRootContainerEntityAsPath()).thenReturn(rootWrapper);
     when(manifest.getAllActivatedCatalogRoleAndPrincipalRoles()).thenReturn(Set.of());
     doNothing()
@@ -170,12 +166,11 @@ public class PolarisAuthorizerImplTest {
     // Verify authorize call that includes Catalog name in the PolarisSecurable
     // successfully resolves the correct namespace
     PolarisAuthorizerImpl authorizer = spy(new PolarisAuthorizerImpl(mock(RealmConfig.class)));
-    AuthorizationState authzState = new AuthorizationState();
     PolarisResolutionManifest manifest = mock(PolarisResolutionManifest.class);
+    AuthorizationState authzState = new AuthorizationState(manifest);
     PolarisResolvedPathWrapper namespaceWrapper = mock(PolarisResolvedPathWrapper.class);
     PolarisPrincipal principal = PolarisPrincipal.of("alice", Map.of(), Set.of("role"));
 
-    authzState.setResolutionManifest(manifest);
     when(manifest.getResolvedPath(
             ResolvedPathKey.of(List.of("ns"), PolarisEntityType.NAMESPACE), true))
         .thenReturn(namespaceWrapper);
@@ -216,13 +211,12 @@ public class PolarisAuthorizerImplTest {
   @Test
   void authorizeSingleOperationMultiIntentRequestEvaluatesSequentially() {
     PolarisAuthorizerImpl authorizer = spy(new PolarisAuthorizerImpl(mock(RealmConfig.class)));
-    AuthorizationState authzState = new AuthorizationState();
     PolarisResolutionManifest manifest = mock(PolarisResolutionManifest.class);
+    AuthorizationState authzState = new AuthorizationState(manifest);
     PolarisResolvedPathWrapper firstCatalogWrapper = mock(PolarisResolvedPathWrapper.class);
     PolarisResolvedPathWrapper secondCatalogWrapper = mock(PolarisResolvedPathWrapper.class);
     PolarisPrincipal principal = PolarisPrincipal.of("alice", Map.of(), Set.of("role"));
 
-    authzState.setResolutionManifest(manifest);
     when(manifest.getResolvedTopLevelEntity("catalog1", PolarisEntityType.CATALOG))
         .thenReturn(firstCatalogWrapper);
     when(manifest.getResolvedTopLevelEntity("catalog2", PolarisEntityType.CATALOG))
@@ -272,12 +266,11 @@ public class PolarisAuthorizerImplTest {
   @Test
   void authorizeUpdateTableMultiIntentRequestEvaluatesSequentially() {
     PolarisAuthorizerImpl authorizer = spy(new PolarisAuthorizerImpl(mock(RealmConfig.class)));
-    AuthorizationState authzState = new AuthorizationState();
     PolarisResolutionManifest manifest = mock(PolarisResolutionManifest.class);
+    AuthorizationState authzState = new AuthorizationState(manifest);
     PolarisResolvedPathWrapper tableWrapper = mock(PolarisResolvedPathWrapper.class);
     PolarisPrincipal principal = PolarisPrincipal.of("alice", Map.of(), Set.of("role"));
 
-    authzState.setResolutionManifest(manifest);
     when(manifest.getResolvedPath(
             ResolvedPathKey.of(List.of("ns", "table"), PolarisEntityType.TABLE_LIKE), true))
         .thenReturn(tableWrapper);
@@ -338,12 +331,11 @@ public class PolarisAuthorizerImplTest {
   @Test
   void authorizeReturnsDenyDecision() {
     PolarisAuthorizerImpl authorizer = spy(new PolarisAuthorizerImpl(mock(RealmConfig.class)));
-    AuthorizationState authzState = new AuthorizationState();
     PolarisResolutionManifest manifest = mock(PolarisResolutionManifest.class);
+    AuthorizationState authzState = new AuthorizationState(manifest);
     PolarisResolvedPathWrapper catalogWrapper = mock(PolarisResolvedPathWrapper.class);
     PolarisPrincipal principal = PolarisPrincipal.of("alice", Map.of(), Set.of("role"));
 
-    authzState.setResolutionManifest(manifest);
     when(manifest.getResolvedTopLevelEntity("catalog", PolarisEntityType.CATALOG))
         .thenReturn(catalogWrapper);
     when(manifest.getAllActivatedCatalogRoleAndPrincipalRoles()).thenReturn(Set.of());
diff --git a/runtime/service/src/main/java/org/apache/polaris/service/admin/PolarisAdminService.java b/runtime/service/src/main/java/org/apache/polaris/service/admin/PolarisAdminService.java
index e455628328..a2a872f475 100644
--- a/runtime/service/src/main/java/org/apache/polaris/service/admin/PolarisAdminService.java
+++ b/runtime/service/src/main/java/org/apache/polaris/service/admin/PolarisAdminService.java
@@ -73,9 +73,18 @@ import org.apache.polaris.core.admin.model.UpdatePrincipalRequest;
 import org.apache.polaris.core.admin.model.UpdatePrincipalRoleRequest;
 import org.apache.polaris.core.admin.model.ViewGrant;
 import org.apache.polaris.core.admin.model.ViewPrivilege;
+import org.apache.polaris.core.auth.AuthorizationRequest;
+import org.apache.polaris.core.auth.AuthorizationState;
+import org.apache.polaris.core.auth.PathSegment;
 import org.apache.polaris.core.auth.PolarisAuthorizableOperation;
 import org.apache.polaris.core.auth.PolarisAuthorizer;
 import org.apache.polaris.core.auth.PolarisPrincipal;
+import org.apache.polaris.core.auth.PolarisSecurable;
+import org.apache.polaris.core.auth.PrivilegeGrantAuthorizationIntent;
+import org.apache.polaris.core.auth.RoleAssignmentAuthorizationIntent;
+import org.apache.polaris.core.auth.RootPrivilegeGrantAuthorizationIntent;
+import org.apache.polaris.core.auth.SingleTargetAuthorizationIntent;
+import org.apache.polaris.core.auth.TargetlessAuthorizationIntent;
 import org.apache.polaris.core.catalog.PolarisCatalogHelpers;
 import org.apache.polaris.core.config.FeatureConfiguration;
 import org.apache.polaris.core.config.RealmConfig;
@@ -121,6 +130,7 @@ import org.apache.polaris.core.storage.PolarisStorageConfigurationInfo;
 import org.apache.polaris.core.storage.StorageLocation;
 import org.apache.polaris.core.storage.aws.AwsStorageConfigurationInfo;
 import org.apache.polaris.core.storage.azure.AzureStorageConfigurationInfo;
+import org.apache.polaris.service.catalog.common.PolarisSecurableMapper;
 import org.apache.polaris.service.config.ReservedProperties;
 import org.apache.polaris.service.types.PolicyIdentifier;
 import org.jspecify.annotations.NonNull;
@@ -227,7 +237,10 @@ public class PolarisAdminService {
 
   private void authorizeBasicRootOperationOrThrow(PolarisAuthorizableOperation op) {
     PolarisResolutionManifest resolutionManifest = newResolutionManifest(null);
-    resolutionManifest.resolveAll();
+    AuthorizationState authorizationState = new AuthorizationState(resolutionManifest);
+    authorizer.resolveAuthorizationInputs(
+        authorizationState,
+        new AuthorizationRequest(polarisPrincipal, List.of(new TargetlessAuthorizationIntent(op))));
     PolarisResolvedPathWrapper rootContainerWrapper =
         resolutionManifest.getResolvedRootContainerEntityAsPath();
     authorizer.authorizeOrThrow(
@@ -253,7 +266,15 @@ public class PolarisAdminService {
       @Nullable String referenceCatalogName) {
     PolarisResolutionManifest resolutionManifest = newResolutionManifest(referenceCatalogName);
     resolutionManifest.addTopLevelName(topLevelEntityName, entityType, false /* isOptional */);
-    ResolverStatus status = resolutionManifest.resolveAll();
+    AuthorizationState authorizationState = new AuthorizationState(resolutionManifest);
+    authorizer.resolveAuthorizationInputs(
+        authorizationState,
+        new AuthorizationRequest(
+            polarisPrincipal,
+            List.of(
+                new SingleTargetAuthorizationIntent(
+                    op, PolarisSecurable.of(new PathSegment(entityType, topLevelEntityName))))));
+    ResolverStatus status = resolutionManifest.getPrimaryResolverStatusOrThrow();
     if (status.getStatus() == ResolverStatus.StatusEnum.ENTITY_COULD_NOT_BE_RESOLVED) {
       throw new NotFoundException(
           "TopLevelEntity of type %s does not exist: %s", entityType, topLevelEntityName);
@@ -305,7 +326,14 @@ public class PolarisAdminService {
       PolarisAuthorizableOperation op, String catalogName, String catalogRoleName) {
     PolarisResolutionManifest resolutionManifest = newResolutionManifest(catalogName);
     resolutionManifest.addPath(new ResolverPath(ResolvedPathKey.ofCatalogRole(catalogRoleName)));
-    resolutionManifest.resolveAll();
+    AuthorizationState authorizationState = new AuthorizationState(resolutionManifest);
+    authorizer.resolveAuthorizationInputs(
+        authorizationState,
+        new AuthorizationRequest(
+            polarisPrincipal,
+            List.of(
+                new SingleTargetAuthorizationIntent(
+                    op, PolarisSecurableMapper.catalogRole(catalogName, catalogRoleName)))));
     PolarisResolvedPathWrapper target =
         resolutionManifest.getResolvedPath(ResolvedPathKey.ofCatalogRole(catalogRoleName), true);
     if (target == null) {
@@ -325,7 +353,17 @@ public class PolarisAdminService {
     PolarisResolutionManifest resolutionManifest = newResolutionManifest(null);
     resolutionManifest.addTopLevelName(
         principalRoleName, PolarisEntityType.PRINCIPAL_ROLE, false /* isOptional */);
-    ResolverStatus status = resolutionManifest.resolveAll();
+    AuthorizationState authorizationState = new AuthorizationState(resolutionManifest);
+    authorizer.resolveAuthorizationInputs(
+        authorizationState,
+        new AuthorizationRequest(
+            polarisPrincipal,
+            List.of(
+                new RootPrivilegeGrantAuthorizationIntent(
+                    op,
+                    PolarisSecurable.of(
+                        new PathSegment(PolarisEntityType.PRINCIPAL_ROLE, principalRoleName))))));
+    ResolverStatus status = resolutionManifest.getPrimaryResolverStatusOrThrow();
 
     if (status.getStatus() == ResolverStatus.StatusEnum.ENTITY_COULD_NOT_BE_RESOLVED) {
       throw new NotFoundException(
@@ -355,7 +393,19 @@ public class PolarisAdminService {
         principalRoleName, PolarisEntityType.PRINCIPAL_ROLE, false /* isOptional */);
     resolutionManifest.addTopLevelName(
         principalName, PolarisEntityType.PRINCIPAL, false /* isOptional */);
-    ResolverStatus status = resolutionManifest.resolveAll();
+    AuthorizationState authorizationState = new AuthorizationState(resolutionManifest);
+    authorizer.resolveAuthorizationInputs(
+        authorizationState,
+        new AuthorizationRequest(
+            polarisPrincipal,
+            List.of(
+                new RoleAssignmentAuthorizationIntent(
+                    op,
+                    PolarisSecurable.of(
+                        new PathSegment(PolarisEntityType.PRINCIPAL_ROLE, principalRoleName)),
+                    PolarisSecurable.of(
+                        new PathSegment(PolarisEntityType.PRINCIPAL, principalName))))));
+    ResolverStatus status = resolutionManifest.getPrimaryResolverStatusOrThrow();
 
     if (status.getStatus() == ResolverStatus.StatusEnum.ENTITY_COULD_NOT_BE_RESOLVED) {
       throw new NotFoundException(
@@ -387,7 +437,18 @@ public class PolarisAdminService {
     resolutionManifest.addPath(new ResolverPath(ResolvedPathKey.ofCatalogRole(catalogRoleName)));
     resolutionManifest.addTopLevelName(
         principalRoleName, PolarisEntityType.PRINCIPAL_ROLE, false /* isOptional */);
-    ResolverStatus status = resolutionManifest.resolveAll();
+    AuthorizationState authorizationState = new AuthorizationState(resolutionManifest);
+    authorizer.resolveAuthorizationInputs(
+        authorizationState,
+        new AuthorizationRequest(
+            polarisPrincipal,
+            List.of(
+                new RoleAssignmentAuthorizationIntent(
+                    op,
+                    PolarisSecurableMapper.catalogRole(catalogName, catalogRoleName),
+                    PolarisSecurable.of(
+                        new PathSegment(PolarisEntityType.PRINCIPAL_ROLE, principalRoleName))))));
+    ResolverStatus status = resolutionManifest.getPrimaryResolverStatusOrThrow();
 
     if (status.getStatus() == ResolverStatus.StatusEnum.ENTITY_COULD_NOT_BE_RESOLVED) {
       throw new NotFoundException(
@@ -420,7 +481,17 @@ public class PolarisAdminService {
     resolutionManifest.addTopLevelName(
         catalogName, PolarisEntityType.CATALOG, false /* isOptional */);
     resolutionManifest.addPath(new ResolverPath(ResolvedPathKey.ofCatalogRole(catalogRoleName)));
-    ResolverStatus status = resolutionManifest.resolveAll();
+    AuthorizationState authorizationState = new AuthorizationState(resolutionManifest);
+    authorizer.resolveAuthorizationInputs(
+        authorizationState,
+        new AuthorizationRequest(
+            polarisPrincipal,
+            List.of(
+                new PrivilegeGrantAuthorizationIntent(
+                    op,
+                    PolarisSecurable.of(new PathSegment(PolarisEntityType.CATALOG, catalogName)),
+                    PolarisSecurableMapper.catalogRole(catalogName, catalogRoleName)))));
+    ResolverStatus status = resolutionManifest.getPrimaryResolverStatusOrThrow();
 
     if (status.getStatus() == ResolverStatus.StatusEnum.ENTITY_COULD_NOT_BE_RESOLVED) {
       throw new NotFoundException("Catalog not found: %s", catalogName);
@@ -450,7 +521,17 @@ public class PolarisAdminService {
     resolutionManifest.addPassthroughPath(
         new ResolverPath(Arrays.asList(namespace.levels()), PolarisEntityType.NAMESPACE));
     resolutionManifest.addPath(new ResolverPath(ResolvedPathKey.ofCatalogRole(catalogRoleName)));
-    ResolverStatus status = resolutionManifest.resolveAll();
+    AuthorizationState authorizationState = new AuthorizationState(resolutionManifest);
+    authorizer.resolveAuthorizationInputs(
+        authorizationState,
+        new AuthorizationRequest(
+            polarisPrincipal,
+            List.of(
+                new PrivilegeGrantAuthorizationIntent(
+                    op,
+                    PolarisSecurableMapper.namespace(catalogName, namespace),
+                    PolarisSecurableMapper.catalogRole(catalogName, catalogRoleName)))));
+    ResolverStatus status = resolutionManifest.getPrimaryResolverStatusOrThrow();
 
     if (status.getStatus() == ResolverStatus.StatusEnum.ENTITY_COULD_NOT_BE_RESOLVED) {
       throw new NotFoundException("Catalog not found: %s", catalogName);
@@ -491,7 +572,17 @@ public class PolarisAdminService {
         new ResolverPath(
             PolarisCatalogHelpers.tableIdentifierToList(identifier), PolarisEntityType.TABLE_LIKE));
     resolutionManifest.addPath(new ResolverPath(ResolvedPathKey.ofCatalogRole(catalogRoleName)));
-    ResolverStatus status = resolutionManifest.resolveAll();
+    AuthorizationState authorizationState = new AuthorizationState(resolutionManifest);
+    authorizer.resolveAuthorizationInputs(
+        authorizationState,
+        new AuthorizationRequest(
+            polarisPrincipal,
+            List.of(
+                new PrivilegeGrantAuthorizationIntent(
+                    op,
+                    PolarisSecurableMapper.tableLike(catalogName, identifier),
+                    PolarisSecurableMapper.catalogRole(catalogName, catalogRoleName)))));
+    ResolverStatus status = resolutionManifest.getPrimaryResolverStatusOrThrow();
 
     if (status.getStatus() == ResolverStatus.StatusEnum.ENTITY_COULD_NOT_BE_RESOLVED) {
       throw new NotFoundException("Catalog not found: %s", catalogName);
@@ -538,7 +629,17 @@ public class PolarisAdminService {
             PolarisCatalogHelpers.identifierToList(identifier.namespace(), identifier.name()),
             PolarisEntityType.POLICY));
     resolutionManifest.addPath(new ResolverPath(ResolvedPathKey.ofCatalogRole(catalogRoleName)));
-    ResolverStatus status = resolutionManifest.resolveAll();
+    AuthorizationState authorizationState = new AuthorizationState(resolutionManifest);
+    authorizer.resolveAuthorizationInputs(
+        authorizationState,
+        new AuthorizationRequest(
+            polarisPrincipal,
+            List.of(
+                new PrivilegeGrantAuthorizationIntent(
+                    op,
+                    PolarisSecurableMapper.policy(catalogName, identifier),
+                    PolarisSecurableMapper.catalogRole(catalogName, catalogRoleName)))));
+    ResolverStatus status = resolutionManifest.getPrimaryResolverStatusOrThrow();
     if (status.getStatus() == ResolverStatus.StatusEnum.ENTITY_COULD_NOT_BE_RESOLVED) {
       throw new NotFoundException("Catalog not found: %s", catalogName);
     } else if (status.getStatus() == ResolverStatus.StatusEnum.PATH_COULD_NOT_BE_FULLY_RESOLVED) {
diff --git a/runtime/service/src/main/java/org/apache/polaris/service/catalog/common/CatalogHandler.java b/runtime/service/src/main/java/org/apache/polaris/service/catalog/common/CatalogHandler.java
index a908cb18ec..2995bdba7e 100644
--- a/runtime/service/src/main/java/org/apache/polaris/service/catalog/common/CatalogHandler.java
+++ b/runtime/service/src/main/java/org/apache/polaris/service/catalog/common/CatalogHandler.java
@@ -18,6 +18,7 @@
  */
 package org.apache.polaris.service.catalog.common;
 
+import static com.google.common.base.Preconditions.checkArgument;
 import static org.apache.polaris.service.catalog.common.ExceptionUtils.alreadyExistsExceptionWithSameNameForTableLikeEntity;
 import static org.apache.polaris.service.catalog.common.ExceptionUtils.entityNameForSubType;
 import static org.apache.polaris.service.catalog.common.ExceptionUtils.noSuchNamespaceException;
@@ -30,9 +31,14 @@ import java.util.Optional;
 import org.apache.iceberg.catalog.Namespace;
 import org.apache.iceberg.catalog.TableIdentifier;
 import org.apache.iceberg.exceptions.AlreadyExistsException;
+import org.apache.polaris.core.auth.AuthorizationIntent;
+import org.apache.polaris.core.auth.AuthorizationRequest;
+import org.apache.polaris.core.auth.AuthorizationState;
 import org.apache.polaris.core.auth.PolarisAuthorizableOperation;
 import org.apache.polaris.core.auth.PolarisAuthorizer;
 import org.apache.polaris.core.auth.PolarisPrincipal;
+import org.apache.polaris.core.auth.RenameAuthorizationIntent;
+import org.apache.polaris.core.auth.SingleTargetAuthorizationIntent;
 import org.apache.polaris.core.catalog.PolarisCatalogHelpers;
 import org.apache.polaris.core.config.RealmConfig;
 import org.apache.polaris.core.context.CallContext;
@@ -131,7 +137,18 @@ public abstract class CatalogHandler {
       }
     }
 
-    resolutionManifest.resolveAll();
+    AuthorizationState authorizationState = new AuthorizationState(resolutionManifest);
+    authorizer()
+        .resolveAuthorizationInputs(
+            authorizationState,
+            new AuthorizationRequest(
+                polarisPrincipal(),
+                List.of(
+                    new SingleTargetAuthorizationIntent(
+                        op,
+                        namespace.isEmpty()
+                            ? PolarisSecurableMapper.catalog(catalogName())
+                            : PolarisSecurableMapper.namespace(catalogName(), namespace)))));
     PolarisResolvedPathWrapper target =
         resolutionManifest.getResolvedPath(ResolvedPathKey.ofNamespace(namespace), true);
     if (target == null) {
@@ -163,7 +180,18 @@ public abstract class CatalogHandler {
     resolutionManifest.addPassthroughPath(
         new ResolverPath(
             Arrays.asList(namespace.levels()), PolarisEntityType.NAMESPACE, true /* optional */));
-    resolutionManifest.resolveAll();
+    AuthorizationState authorizationState = new AuthorizationState(resolutionManifest);
+    authorizer()
+        .resolveAuthorizationInputs(
+            authorizationState,
+            new AuthorizationRequest(
+                polarisPrincipal(),
+                List.of(
+                    new SingleTargetAuthorizationIntent(
+                        op,
+                        parentNamespace.isEmpty()
+                            ? PolarisSecurableMapper.catalog(catalogName())
+                            : PolarisSecurableMapper.namespace(catalogName(), parentNamespace)))));
     PolarisResolvedPathWrapper target =
         resolutionManifest.getResolvedPath(ResolvedPathKey.ofNamespace(parentNamespace), true);
     if (target == null) {
@@ -189,22 +217,40 @@ public abstract class CatalogHandler {
         new ResolverPath(Arrays.asList(namespace.levels()), PolarisEntityType.NAMESPACE));
 
     // When creating an entity under a namespace, the authz target is the namespace, but we must
-    // also
-    // add the actual path that will be created as an "optional" passthrough resolution path to
-    // indicate that the underlying catalog is "allowed" to check the creation path for a
-    // conflicting
-    // entity.
+    // also add the actual path that will be created as an "optional" passthrough resolution path to
+    // indicate that the underlying catalog is "allowed" to check for a conflicting entity at the
+    // creation path.
     resolutionManifest.addPassthroughPath(
         new ResolverPath(
             PolarisCatalogHelpers.tableIdentifierToList(identifier),
             PolarisEntityType.TABLE_LIKE,
             true /* optional */));
-    resolutionManifest.resolveAll();
+    AuthorizationState authorizationState = new AuthorizationState(resolutionManifest);
+    authorizer()
+        .resolveAuthorizationInputs(
+            authorizationState,
+            new AuthorizationRequest(
+                polarisPrincipal(),
+                List.of(
+                    new SingleTargetAuthorizationIntent(
+                        op, PolarisSecurableMapper.namespace(catalogName(), namespace)))));
+    authorizeResolvedCreateTableLikeUnderNamespaceOperationOrThrow(op, identifier);
+  }
+
+  protected void authorizeResolvedCreateTableLikeUnderNamespaceOperationOrThrow(
+      PolarisAuthorizableOperation op, TableIdentifier identifier) {
+    if (resolutionManifest == null) {
+      throw new IllegalStateException(
+          "Resolved authorization requires resolveAuthorizationInputs to run first");
+    }
+
+    Namespace namespace = identifier.namespace();
     PolarisResolvedPathWrapper target =
         resolutionManifest.getResolvedPath(ResolvedPathKey.ofNamespace(namespace), true);
     if (target == null) {
       throw noSuchNamespaceException(namespace);
     }
+
     authorizer()
         .authorizeOrThrow(
             polarisPrincipal(),
@@ -234,7 +280,29 @@ public abstract class CatalogHandler {
             PolarisCatalogHelpers.tableIdentifierToList(identifier),
             PolarisEntityType.TABLE_LIKE,
             true /* optional */));
-    resolutionManifest.resolveAll();
+    AuthorizationState authorizationState = new AuthorizationState(resolutionManifest);
+    authorizer()
+        .resolveAuthorizationInputs(
+            authorizationState,
+            new AuthorizationRequest(
+                polarisPrincipal(),
+                List.of(
+                    new SingleTargetAuthorizationIntent(
+                        overwriteOp,
+                        PolarisSecurableMapper.tableLike(catalogName(), identifier)))));
+    authorizeResolvedRegisterTableOverwriteOrThrow(overwriteOp, fallbackOp, identifier);
+  }
+
+  protected void authorizeResolvedRegisterTableOverwriteOrThrow(
+      PolarisAuthorizableOperation overwriteOp,
+      PolarisAuthorizableOperation fallbackOp,
+      TableIdentifier identifier) {
+    if (resolutionManifest == null) {
+      throw new IllegalStateException(
+          "Resolved authorization requires resolveAuthorizationInputs to run first");
+    }
+
+    Namespace namespace = identifier.namespace();
 
     // Early check so that a caller that has table-level REGISTER_TABLE_OVERWRITE but not
     // namespace-level REGISTER_TABLE doesn't get a permission error instead of
@@ -291,13 +359,47 @@ public abstract class CatalogHandler {
               PolarisCatalogHelpers.tableIdentifierToList(identifier),
               PolarisEntityType.TABLE_LIKE,
               true /* optional */));
-      resolutionManifest.resolveAll();
     }
   }
 
-  protected void authorizeBasicTableLikeOperationOrThrow(
+  protected void resolveBasicTableLikeTargetOrThrow(
+      PolarisAuthorizableOperation op, TableIdentifier identifier) {
+    ensureResolutionManifestForTable(identifier);
+
+    AuthorizationState authorizationState = new AuthorizationState(resolutionManifest);
+    authorizer()
+        .resolveAuthorizationInputs(
+            authorizationState,
+            new AuthorizationRequest(
+                polarisPrincipal(),
+                List.of(
+                    new SingleTargetAuthorizationIntent(
+                        op, PolarisSecurableMapper.tableLike(catalogName(), identifier)))));
+  }
+
+  protected void authorizeResolvedBasicTableLikeOperationOrThrow(
+      PolarisAuthorizableOperation op, PolarisEntitySubType subType, TableIdentifier identifier) {
+    PolarisResolvedPathWrapper target =
+        resolutionManifest.getResolvedPath(ResolvedPathKey.ofTableLike(identifier), subType, true);
+    if (target == null) {
+      throw notFoundExceptionForTableLikeEntity(identifier, subType);
+    }
+
+    authorizer()
+        .authorizeOrThrow(
+            polarisPrincipal(),
+            resolutionManifest.getAllActivatedCatalogRoleAndPrincipalRoles(),
+            op,
+            target,
+            null /* secondary */);
+
+    initializeCatalog();
+  }
+
+  protected void resolveAndAuthorizeBasicTableLikeOperationOrThrow(
       PolarisAuthorizableOperation op, PolarisEntitySubType subType, TableIdentifier identifier) {
-    authorizeBasicTableLikeOperationsOrThrow(EnumSet.of(op), subType, identifier);
+    resolveBasicTableLikeTargetOrThrow(op, identifier);
+    authorizeResolvedBasicTableLikeOperationOrThrow(op, subType, identifier);
   }
 
   protected void authorizeBasicTableLikeOperationsOrThrow(
@@ -305,6 +407,9 @@ public abstract class CatalogHandler {
       PolarisEntitySubType subType,
       TableIdentifier identifier) {
     ensureResolutionManifestForTable(identifier);
+    checkArgument(!ops.isEmpty(), "No operations provided for table-like authorization");
+    resolutionManifest.getPrimaryResolverStatusOrThrow();
+
     PolarisResolvedPathWrapper target =
         resolutionManifest.getResolvedPath(ResolvedPathKey.ofTableLike(identifier), subType, true);
     if (target == null) {
@@ -336,7 +441,18 @@ public abstract class CatalogHandler {
                     PolarisCatalogHelpers.tableIdentifierToList(identifier),
                     PolarisEntityType.TABLE_LIKE)));
 
-    ResolverStatus status = resolutionManifest.resolveAll();
+    AuthorizationState authorizationState = new AuthorizationState(resolutionManifest);
+    authorizer()
+        .resolveAuthorizationInputs(
+            authorizationState,
+            new AuthorizationRequest(
+                polarisPrincipal(),
+                ids.stream()
+                    .map(identifier -> PolarisSecurableMapper.tableLike(catalogName(), identifier))
+                    .<AuthorizationIntent>map(
+                        target -> new SingleTargetAuthorizationIntent(op, target))
+                    .toList()));
+    ResolverStatus status = resolutionManifest.getPrimaryResolverStatusOrThrow();
 
     // If one of the paths failed to resolve, throw exception based on the one that
     // we first failed to resolve.
@@ -385,7 +501,18 @@ public abstract class CatalogHandler {
             PolarisCatalogHelpers.tableIdentifierToList(dst),
             PolarisEntityType.TABLE_LIKE,
             true /* optional */));
-    ResolverStatus status = resolutionManifest.resolveAll();
+    AuthorizationState authorizationState = new AuthorizationState(resolutionManifest);
+    authorizer()
+        .resolveAuthorizationInputs(
+            authorizationState,
+            new AuthorizationRequest(
+                polarisPrincipal(),
+                List.of(
+                    new RenameAuthorizationIntent(
+                        op,
+                        PolarisSecurableMapper.tableLike(catalogName(), src),
+                        PolarisSecurableMapper.tableLike(catalogName(), dst)))));
+    ResolverStatus status = resolutionManifest.getPrimaryResolverStatusOrThrow();
     if (status.getStatus() == ResolverStatus.StatusEnum.PATH_COULD_NOT_BE_FULLY_RESOLVED
         && status.getFailedToResolvePath().lastEntityType() == PolarisEntityType.NAMESPACE) {
       throw noSuchNamespaceException(dst.namespace());
diff --git a/runtime/service/src/main/java/org/apache/polaris/service/catalog/common/PolarisSecurableMapper.java b/runtime/service/src/main/java/org/apache/polaris/service/catalog/common/PolarisSecurableMapper.java
new file mode 100644
index 0000000000..733aa5a225
--- /dev/null
+++ b/runtime/service/src/main/java/org/apache/polaris/service/catalog/common/PolarisSecurableMapper.java
@@ -0,0 +1,119 @@
+/*
+ * 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.polaris.service.catalog.common;
+
+import java.util.Arrays;
+import java.util.List;
+import org.apache.iceberg.catalog.Namespace;
+import org.apache.iceberg.catalog.TableIdentifier;
+import org.apache.polaris.core.auth.ImmutablePolarisSecurable;
+import org.apache.polaris.core.auth.PathSegment;
+import org.apache.polaris.core.auth.PolarisSecurable;
+import org.apache.polaris.core.entity.PolarisEntityType;
+import org.apache.polaris.service.types.PolicyAttachmentTarget;
+import org.apache.polaris.service.types.PolicyIdentifier;
+
+/**
+ * Utility mapper for translating Polaris domain identifiers into canonical {@link
+ * org.apache.polaris.core.auth.PolarisSecurable} paths.
+ */
+public final class PolarisSecurableMapper {
+  private PolarisSecurableMapper() {}
+
+  public static PolarisSecurable catalog(String catalogName) {
+    return PolarisSecurable.of(new PathSegment(PolarisEntityType.CATALOG, catalogName));
+  }
+
+  public static PolarisSecurable catalogRole(String catalogName, String catalogRoleName) {
+    return ImmutablePolarisSecurable.builder()
+        .addPathSegment(new PathSegment(PolarisEntityType.CATALOG, catalogName))
+        .addPathSegment(new PathSegment(PolarisEntityType.CATALOG_ROLE, catalogRoleName))
+        .build();
+  }
+
+  public static PolarisSecurable namespace(String catalogName, Namespace namespace) {
+    if (namespace.isEmpty()) {
+      throw new IllegalArgumentException("Namespace target must not be empty");
+    }
+    ImmutablePolarisSecurable.Builder builder =
+        ImmutablePolarisSecurable.builder()
+            .addPathSegment(new PathSegment(PolarisEntityType.CATALOG, catalogName));
+    Arrays.stream(namespace.levels())
+        .map(level -> new PathSegment(PolarisEntityType.NAMESPACE, level))
+        .forEach(builder::addPathSegment);
+    return builder.build();
+  }
+
+  public static PolarisSecurable tableLike(String catalogName, TableIdentifier identifier) {
+    if (identifier.namespace().isEmpty()) {
+      throw new IllegalArgumentException("Table-like target cannot have an empty namespace");
+    }
+    ImmutablePolarisSecurable.Builder builder =
+        ImmutablePolarisSecurable.builder()
+            .addPathSegment(new PathSegment(PolarisEntityType.CATALOG, catalogName));
+    Arrays.stream(identifier.namespace().levels())
+        .map(level -> new PathSegment(PolarisEntityType.NAMESPACE, level))
+        .forEach(builder::addPathSegment);
+    return builder
+        .addPathSegment(new PathSegment(PolarisEntityType.TABLE_LIKE, identifier.name()))
+        .build();
+  }
+
+  public static PolarisSecurable policy(String catalogName, PolicyIdentifier identifier) {
+    ImmutablePolarisSecurable.Builder builder =
+        ImmutablePolarisSecurable.builder()
+            .addPathSegment(new PathSegment(PolarisEntityType.CATALOG, catalogName));
+    Arrays.stream(identifier.namespace().levels())
+        .map(level -> new PathSegment(PolarisEntityType.NAMESPACE, level))
+        .forEach(builder::addPathSegment);
+    return builder
+        .addPathSegment(new PathSegment(PolarisEntityType.POLICY, identifier.name()))
+        .build();
+  }
+
+  public static PolarisSecurable policyAttachmentTarget(
+      String catalogName, PolicyAttachmentTarget target) {
+    ImmutablePolarisSecurable.Builder builder =
+        ImmutablePolarisSecurable.builder()
+            .addPathSegment(new PathSegment(PolarisEntityType.CATALOG, catalogName));
+    switch (target.getType()) {
+      case CATALOG:
+        return catalog(catalogName);
+      case NAMESPACE:
+        if (target.getPath().isEmpty()) {
+          throw new IllegalArgumentException("Namespace target path must not be empty");
+        }
+        target.getPath().stream()
+            .map(level -> new PathSegment(PolarisEntityType.NAMESPACE, level))
+            .forEach(builder::addPathSegment);
+        return builder.build();
+      case TABLE_LIKE:
+        List<String> path = target.getPath();
+        path.subList(0, path.size() - 1).stream()
+            .map(level -> new PathSegment(PolarisEntityType.NAMESPACE, level))
+            .forEach(builder::addPathSegment);
+        return builder
+            .addPathSegment(
+                new PathSegment(PolarisEntityType.TABLE_LIKE, path.get(path.size() - 1)))
+            .build();
+      default:
+        throw new IllegalArgumentException("Unsupported target type: " + target.getType());
+    }
+  }
+}
diff --git a/runtime/service/src/main/java/org/apache/polaris/service/catalog/generic/GenericTableCatalogHandler.java b/runtime/service/src/main/java/org/apache/polaris/service/catalog/generic/GenericTableCatalogHandler.java
index 00ad96e85a..3546385ddd 100644
--- a/runtime/service/src/main/java/org/apache/polaris/service/catalog/generic/GenericTableCatalogHandler.java
+++ b/runtime/service/src/main/java/org/apache/polaris/service/catalog/generic/GenericTableCatalogHandler.java
@@ -131,14 +131,16 @@ public abstract class GenericTableCatalogHandler extends CatalogHandler {
 
   public boolean dropGenericTable(TableIdentifier identifier) {
     PolarisAuthorizableOperation op = PolarisAuthorizableOperation.DROP_TABLE_WITHOUT_PURGE;
-    authorizeBasicTableLikeOperationOrThrow(op, PolarisEntitySubType.GENERIC_TABLE, identifier);
+    resolveAndAuthorizeBasicTableLikeOperationOrThrow(
+        op, PolarisEntitySubType.GENERIC_TABLE, identifier);
 
     return this.genericTableCatalog.dropGenericTable(identifier);
   }
 
   public LoadGenericTableResponse loadGenericTable(TableIdentifier identifier) {
     PolarisAuthorizableOperation op = PolarisAuthorizableOperation.LOAD_TABLE;
-    authorizeBasicTableLikeOperationOrThrow(op, PolarisEntitySubType.GENERIC_TABLE, identifier);
+    resolveAndAuthorizeBasicTableLikeOperationOrThrow(
+        op, PolarisEntitySubType.GENERIC_TABLE, identifier);
 
     GenericTableEntity loadedEntity = this.genericTableCatalog.loadGenericTable(identifier);
     GenericTable loadedTable =
diff --git a/runtime/service/src/main/java/org/apache/polaris/service/catalog/iceberg/IcebergCatalogHandler.java b/runtime/service/src/main/java/org/apache/polaris/service/catalog/iceberg/IcebergCatalogHandler.java
index 3ecd2af752..3e3638353b 100644
--- a/runtime/service/src/main/java/org/apache/polaris/service/catalog/iceberg/IcebergCatalogHandler.java
+++ b/runtime/service/src/main/java/org/apache/polaris/service/catalog/iceberg/IcebergCatalogHandler.java
@@ -92,9 +92,13 @@ import org.apache.iceberg.rest.responses.LoadTableResponse;
 import org.apache.iceberg.rest.responses.LoadViewResponse;
 import org.apache.iceberg.rest.responses.UpdateNamespacePropertiesResponse;
 import org.apache.polaris.core.PolarisDiagnostics;
+import org.apache.polaris.core.auth.AuthorizationRequest;
+import org.apache.polaris.core.auth.AuthorizationState;
 import org.apache.polaris.core.auth.PolarisAuthorizableOperation;
+import org.apache.polaris.core.auth.SingleTargetAuthorizationIntent;
 import org.apache.polaris.core.catalog.FederatedCatalogFactory;
 import org.apache.polaris.core.catalog.LocalCatalogFactory;
+import org.apache.polaris.core.catalog.PolarisCatalogHelpers;
 import org.apache.polaris.core.config.FeatureConfiguration;
 import org.apache.polaris.core.connection.ConnectionConfigInfoDpo;
 import org.apache.polaris.core.connection.ConnectionType;
@@ -113,6 +117,7 @@ import org.apache.polaris.core.persistence.pagination.PageToken;
 import org.apache.polaris.core.persistence.resolver.ResolvedPathKey;
 import org.apache.polaris.core.persistence.resolver.Resolver;
 import org.apache.polaris.core.persistence.resolver.ResolverFactory;
+import org.apache.polaris.core.persistence.resolver.ResolverPath;
 import org.apache.polaris.core.persistence.resolver.ResolverStatus;
 import org.apache.polaris.core.rest.NamespaceUtils;
 import org.apache.polaris.core.rest.PolarisEndpoints;
@@ -126,6 +131,7 @@ import org.apache.polaris.service.catalog.CatalogPrefixParser;
 import org.apache.polaris.service.catalog.SupportsNotifications;
 import org.apache.polaris.service.catalog.common.CatalogHandler;
 import org.apache.polaris.service.catalog.common.CatalogUtils;
+import org.apache.polaris.service.catalog.common.PolarisSecurableMapper;
 import org.apache.polaris.service.catalog.io.StorageAccessConfigProvider;
 import org.apache.polaris.service.config.ReservedProperties;
 import org.apache.polaris.service.events.EventAttributeMap;
@@ -685,7 +691,8 @@ public abstract class IcebergCatalogHandler extends CatalogHandler implements Au
             ? PolarisAuthorizableOperation.REPORT_READ_METRICS
             : PolarisAuthorizableOperation.REPORT_WRITE_METRICS;
 
-    authorizeBasicTableLikeOperationOrThrow(op, PolarisEntitySubType.ICEBERG_TABLE, identifier);
+    resolveAndAuthorizeBasicTableLikeOperationOrThrow(
+        op, PolarisEntitySubType.ICEBERG_TABLE, identifier);
 
     // Get catalog and table IDs from resolved entities (already resolved during authorization)
     CatalogEntity catalogEntity = getResolvedCatalogEntity();
@@ -792,7 +799,7 @@ public abstract class IcebergCatalogHandler extends CatalogHandler implements Au
     // Note: this check must come after authorizeLoadTable because baseCatalog is
     // initialized lazily during authorization.
     if (!(baseCatalog instanceof LocalIcebergCatalog)) {
-      return fallbackToFullLoadTable(tableIdentifier, refreshCredentialsEndpoint);
+      return fallbackToFullLoadTable(tableIdentifier, refreshCredentialsEndpoint, actionsRequested);
     }
 
     IcebergTableLikeEntity entity = getTableEntity(tableIdentifier);
@@ -811,7 +818,7 @@ public abstract class IcebergCatalogHandler extends CatalogHandler implements Au
               "Entity missing location in internal properties, requires backfill "
                   + "as it was likely not updated with stored property changes. "
                   + "Falling back to full loadTable path");
-      return fallbackToFullLoadTable(tableIdentifier, refreshCredentialsEndpoint);
+      return fallbackToFullLoadTable(tableIdentifier, refreshCredentialsEndpoint, actionsRequested);
     }
 
     Set<String> tableLocations =
@@ -846,7 +853,7 @@ public abstract class IcebergCatalogHandler extends CatalogHandler implements Au
   private Set<PolarisStorageActions> authorizeLoadTable(
       TableIdentifier tableIdentifier, boolean delegationRequested) {
     if (!delegationRequested) {
-      authorizeBasicTableLikeOperationOrThrow(
+      resolveAndAuthorizeBasicTableLikeOperationOrThrow(
           PolarisAuthorizableOperation.LOAD_TABLE,
           PolarisEntitySubType.ICEBERG_TABLE,
           tableIdentifier);
@@ -863,16 +870,17 @@ public abstract class IcebergCatalogHandler extends CatalogHandler implements Au
     PolarisAuthorizableOperation write =
         PolarisAuthorizableOperation.LOAD_TABLE_WITH_WRITE_DELEGATION;
 
+    resolveBasicTableLikeTargetOrThrow(write, tableIdentifier);
+
     Set<PolarisStorageActions> actionsRequested =
         new HashSet<>(Set.of(PolarisStorageActions.READ, PolarisStorageActions.LIST));
     try {
-      // TODO: Refactor to have a boolean-return version of the helpers so we can fallthrough
-      // easily.
-      authorizeBasicTableLikeOperationOrThrow(
+      authorizeResolvedBasicTableLikeOperationOrThrow(
           write, PolarisEntitySubType.ICEBERG_TABLE, tableIdentifier);
       actionsRequested.add(PolarisStorageActions.WRITE);
     } catch (ForbiddenException e) {
-      authorizeBasicTableLikeOperationOrThrow(
+      // Reuse the already-resolved table view for the read-delegation fallback.
+      authorizeResolvedBasicTableLikeOperationOrThrow(
           read, PolarisEntitySubType.ICEBERG_TABLE, tableIdentifier);
     }
 
@@ -903,25 +911,49 @@ public abstract class IcebergCatalogHandler extends CatalogHandler implements Au
       Set<PolarisStorageActions> actionsRequested =
           EnumSet.of(PolarisStorageActions.READ, PolarisStorageActions.LIST);
 
+      Namespace namespace = tableIdentifier.namespace();
+      resolutionManifest = newResolutionManifest();
+      resolutionManifest.addPath(
+          new ResolverPath(Arrays.asList(namespace.levels()), PolarisEntityType.NAMESPACE));
+      resolutionManifest.addPassthroughPath(
+          new ResolverPath(
+              PolarisCatalogHelpers.tableIdentifierToList(tableIdentifier),
+              PolarisEntityType.TABLE_LIKE,
+              true /* optional */));
+
+      // Resolve once for the shared table target before auth fallback. Today resolution depends on
+      // the principal and target, not the register-table operation, so either delegation op is
+      // sufficient for building the manifest.
+      AuthorizationState authorizationState = new AuthorizationState(resolutionManifest);
+      authorizer()
+          .resolveAuthorizationInputs(
+              authorizationState,
+              new AuthorizationRequest(
+                  polarisPrincipal(),
+                  List.of(
+                      new SingleTargetAuthorizationIntent(
+                          PolarisAuthorizableOperation.REGISTER_TABLE_WITH_READ_DELEGATION,
+                          PolarisSecurableMapper.tableLike(catalogName(), tableIdentifier)))));
+
       try {
         if (overwrite) {
-          authorizeRegisterTableOverwriteOrThrow(
+          authorizeResolvedRegisterTableOverwriteOrThrow(
               PolarisAuthorizableOperation.REGISTER_TABLE_OVERWRITE_WITH_WRITE_DELEGATION,
               PolarisAuthorizableOperation.REGISTER_TABLE_WITH_WRITE_DELEGATION,
               tableIdentifier);
         } else {
-          authorizeCreateTableLikeUnderNamespaceOperationOrThrow(
+          authorizeResolvedCreateTableLikeUnderNamespaceOperationOrThrow(
               PolarisAuthorizableOperation.REGISTER_TABLE_WITH_WRITE_DELEGATION, tableIdentifier);
         }
         actionsRequested.add(PolarisStorageActions.WRITE);
       } catch (ForbiddenException e) {
         if (overwrite) {
-          authorizeRegisterTableOverwriteOrThrow(
+          authorizeResolvedRegisterTableOverwriteOrThrow(
               PolarisAuthorizableOperation.REGISTER_TABLE_OVERWRITE_WITH_READ_DELEGATION,
               PolarisAuthorizableOperation.REGISTER_TABLE_WITH_READ_DELEGATION,
               tableIdentifier);
         } else {
-          authorizeCreateTableLikeUnderNamespaceOperationOrThrow(
+          authorizeResolvedCreateTableLikeUnderNamespaceOperationOrThrow(
               PolarisAuthorizableOperation.REGISTER_TABLE_WITH_READ_DELEGATION, tableIdentifier);
         }
       }
@@ -941,6 +973,22 @@ public abstract class IcebergCatalogHandler extends CatalogHandler implements Au
         authorizeLoadTable(tableIdentifier, !delegationModes.isEmpty());
     Optional<AccessDelegationMode> resolvedMode = resolveAccessDelegationModes(delegationModes);
 
+    return loadTableWithResolvedAuthorization(
+        tableIdentifier,
+        snapshots,
+        ifNoneMatch,
+        actionsRequested,
+        resolvedMode,
+        refreshCredentialsEndpoint);
+  }
+
+  private Optional<LoadTableResponse> loadTableWithResolvedAuthorization(
+      TableIdentifier tableIdentifier,
+      String snapshots,
+      IfNoneMatch ifNoneMatch,
+      Set<PolarisStorageActions> actionsRequested,
+      Optional<AccessDelegationMode> resolvedMode,
+      Optional<String> refreshCredentialsEndpoint) {
     if (ifNoneMatch != null) {
       // Perform freshness-aware table loading if caller specified ifNoneMatch.
       IcebergTableLikeEntity tableEntity = getTableEntity(tableIdentifier);
@@ -1099,13 +1147,22 @@ public abstract class IcebergCatalogHandler extends CatalogHandler implements Au
 
   public LoadTableResponse updateTable(
       TableIdentifier tableIdentifier, UpdateTableRequest request) {
-
-    // Ensure resolution manifest is initialized so we can determine whether
-    // fine grained authz model is enabled at the catalog level
     ensureResolutionManifestForTable(tableIdentifier);
+    // Pre-resolve once so we can read catalog-scoped config from the shared manifest before
+    // deriving the final per-update operation set.
+    AuthorizationState authorizationState = new AuthorizationState(resolutionManifest);
+    authorizer()
+        .resolveAuthorizationInputs(
+            authorizationState,
+            new AuthorizationRequest(
+                polarisPrincipal(),
+                List.of(
+                    new SingleTargetAuthorizationIntent(
+                        PolarisAuthorizableOperation.UPDATE_TABLE,
+                        PolarisSecurableMapper.tableLike(catalogName(), tableIdentifier)))));
 
     EnumSet<PolarisAuthorizableOperation> authorizableOperations =
-        getUpdateTableAuthorizableOperations(request);
+        getUpdateTableAuthorizableOperations(request, getResolvedCatalogEntity());
 
     authorizeBasicTableLikeOperationsOrThrow(
         authorizableOperations, PolarisEntitySubType.ICEBERG_TABLE, tableIdentifier);
@@ -1133,7 +1190,7 @@ public abstract class IcebergCatalogHandler extends CatalogHandler implements Au
 
   public void dropTableWithoutPurge(TableIdentifier tableIdentifier) {
     PolarisAuthorizableOperation op = PolarisAuthorizableOperation.DROP_TABLE_WITHOUT_PURGE;
-    authorizeBasicTableLikeOperationOrThrow(
+    resolveAndAuthorizeBasicTableLikeOperationOrThrow(
         op, PolarisEntitySubType.ICEBERG_TABLE, tableIdentifier);
 
     catalogHandlerUtils().dropTable(baseCatalog, tableIdentifier);
@@ -1141,7 +1198,7 @@ public abstract class IcebergCatalogHandler extends CatalogHandler implements Au
 
   public void dropTableWithPurge(TableIdentifier tableIdentifier) {
     PolarisAuthorizableOperation op = PolarisAuthorizableOperation.DROP_TABLE_WITH_PURGE;
-    authorizeBasicTableLikeOperationOrThrow(
+    resolveAndAuthorizeBasicTableLikeOperationOrThrow(
         op, PolarisEntitySubType.ICEBERG_TABLE, tableIdentifier);
 
     CatalogEntity catalog = getResolvedCatalogEntity();
@@ -1153,7 +1210,7 @@ public abstract class IcebergCatalogHandler extends CatalogHandler implements Au
 
   public void tableExists(TableIdentifier tableIdentifier) {
     PolarisAuthorizableOperation op = PolarisAuthorizableOperation.TABLE_EXISTS;
-    authorizeBasicTableLikeOperationOrThrow(
+    resolveAndAuthorizeBasicTableLikeOperationOrThrow(
         op, PolarisEntitySubType.ICEBERG_TABLE, tableIdentifier);
 
     // TODO: Just skip CatalogHandlers for this one maybe
@@ -1377,14 +1434,16 @@ public abstract class IcebergCatalogHandler extends CatalogHandler implements Au
 
   public LoadViewResponse loadView(TableIdentifier viewIdentifier) {
     PolarisAuthorizableOperation op = PolarisAuthorizableOperation.LOAD_VIEW;
-    authorizeBasicTableLikeOperationOrThrow(op, PolarisEntitySubType.ICEBERG_VIEW, viewIdentifier);
+    resolveAndAuthorizeBasicTableLikeOperationOrThrow(
+        op, PolarisEntitySubType.ICEBERG_VIEW, viewIdentifier);
 
     return catalogHandlerUtils().loadView(viewCatalog, viewIdentifier);
   }
 
   public LoadViewResponse replaceView(TableIdentifier viewIdentifier, UpdateTableRequest request) {
     PolarisAuthorizableOperation op = PolarisAuthorizableOperation.REPLACE_VIEW;
-    authorizeBasicTableLikeOperationOrThrow(op, PolarisEntitySubType.ICEBERG_VIEW, viewIdentifier);
+    resolveAndAuthorizeBasicTableLikeOperationOrThrow(
+        op, PolarisEntitySubType.ICEBERG_VIEW, viewIdentifier);
 
     CatalogEntity catalog = getResolvedCatalogEntity();
     if (catalog.isStaticFacade()) {
@@ -1396,14 +1455,16 @@ public abstract class IcebergCatalogHandler extends CatalogHandler implements Au
 
   public void dropView(TableIdentifier viewIdentifier) {
     PolarisAuthorizableOperation op = PolarisAuthorizableOperation.DROP_VIEW;
-    authorizeBasicTableLikeOperationOrThrow(op, PolarisEntitySubType.ICEBERG_VIEW, viewIdentifier);
+    resolveAndAuthorizeBasicTableLikeOperationOrThrow(
+        op, PolarisEntitySubType.ICEBERG_VIEW, viewIdentifier);
 
     catalogHandlerUtils().dropView(viewCatalog, viewIdentifier);
   }
 
   public void viewExists(TableIdentifier viewIdentifier) {
     PolarisAuthorizableOperation op = PolarisAuthorizableOperation.VIEW_EXISTS;
-    authorizeBasicTableLikeOperationOrThrow(op, PolarisEntitySubType.ICEBERG_VIEW, viewIdentifier);
+    resolveAndAuthorizeBasicTableLikeOperationOrThrow(
+        op, PolarisEntitySubType.ICEBERG_VIEW, viewIdentifier);
 
     // TODO: Just skip CatalogHandlers for this one maybe
     catalogHandlerUtils().loadView(viewCatalog, viewIdentifier);
@@ -1447,12 +1508,11 @@ public abstract class IcebergCatalogHandler extends CatalogHandler implements Au
   }
 
   private EnumSet<PolarisAuthorizableOperation> getUpdateTableAuthorizableOperations(
-      UpdateTableRequest request) {
+      UpdateTableRequest request, CatalogEntity catalogEntity) {
     boolean useFineGrainedOperations =
         realmConfig()
             .getConfig(
-                FeatureConfiguration.ENABLE_FINE_GRAINED_UPDATE_TABLE_PRIVILEGES,
-                getResolvedCatalogEntity());
+                FeatureConfiguration.ENABLE_FINE_GRAINED_UPDATE_TABLE_PRIVILEGES, catalogEntity);
 
     if (useFineGrainedOperations) {
       EnumSet<PolarisAuthorizableOperation> actions =
@@ -1603,9 +1663,20 @@ public abstract class IcebergCatalogHandler extends CatalogHandler implements Au
   }
 
   private ImmutableLoadCredentialsResponse fallbackToFullLoadTable(
-      TableIdentifier tableIdentifier, Optional<String> refreshCredentialsEndpoint) {
+      TableIdentifier tableIdentifier,
+      Optional<String> refreshCredentialsEndpoint,
+      Set<PolarisStorageActions> actionsRequested) {
+    Optional<AccessDelegationMode> resolvedMode =
+        resolveAccessDelegationModes(EnumSet.of(VENDED_CREDENTIALS));
     LoadTableResponse loadTableResponse =
-        loadTableWithAccessDelegation(tableIdentifier, "all", refreshCredentialsEndpoint);
+        loadTableWithResolvedAuthorization(
+                tableIdentifier,
+                "all",
+                null,
+                actionsRequested,
+                resolvedMode,
+                refreshCredentialsEndpoint)
+            .orElseThrow();
     return ImmutableLoadCredentialsResponse.builder()
         .credentials(loadTableResponse.credentials())
         .build();
diff --git a/runtime/service/src/main/java/org/apache/polaris/service/catalog/policy/PolicyCatalogHandler.java b/runtime/service/src/main/java/org/apache/polaris/service/catalog/policy/PolicyCatalogHandler.java
index a966371359..e731289dd1 100644
--- a/runtime/service/src/main/java/org/apache/polaris/service/catalog/policy/PolicyCatalogHandler.java
+++ b/runtime/service/src/main/java/org/apache/polaris/service/catalog/policy/PolicyCatalogHandler.java
@@ -28,7 +28,11 @@ import org.apache.iceberg.catalog.Namespace;
 import org.apache.iceberg.catalog.TableIdentifier;
 import org.apache.iceberg.exceptions.NoSuchTableException;
 import org.apache.iceberg.exceptions.NotFoundException;
+import org.apache.polaris.core.auth.AuthorizationRequest;
+import org.apache.polaris.core.auth.AuthorizationState;
 import org.apache.polaris.core.auth.PolarisAuthorizableOperation;
+import org.apache.polaris.core.auth.PolicyAttachmentAuthorizationIntent;
+import org.apache.polaris.core.auth.SingleTargetAuthorizationIntent;
 import org.apache.polaris.core.catalog.PolarisCatalogHelpers;
 import org.apache.polaris.core.entity.PolarisEntitySubType;
 import org.apache.polaris.core.entity.PolarisEntityType;
@@ -40,6 +44,7 @@ import org.apache.polaris.core.policy.PolicyType;
 import org.apache.polaris.core.policy.exceptions.NoSuchPolicyException;
 import org.apache.polaris.immutables.PolarisImmutable;
 import org.apache.polaris.service.catalog.common.CatalogHandler;
+import org.apache.polaris.service.catalog.common.PolarisSecurableMapper;
 import org.apache.polaris.service.types.AttachPolicyRequest;
 import org.apache.polaris.service.types.CreatePolicyRequest;
 import org.apache.polaris.service.types.DetachPolicyRequest;
@@ -144,7 +149,15 @@ public abstract class PolicyCatalogHandler extends CatalogHandler {
             PolarisCatalogHelpers.identifierToList(identifier.namespace(), identifier.name()),
             PolarisEntityType.POLICY,
             true /* optional */));
-    resolutionManifest.resolveAll();
+    AuthorizationState authorizationState = new AuthorizationState(resolutionManifest);
+    authorizer()
+        .resolveAuthorizationInputs(
+            authorizationState,
+            new AuthorizationRequest(
+                polarisPrincipal(),
+                List.of(
+                    new SingleTargetAuthorizationIntent(
+                        op, PolarisSecurableMapper.policy(catalogName(), identifier)))));
 
     PolarisResolvedPathWrapper target =
         resolutionManifest.getResolvedPath(
@@ -182,14 +195,22 @@ public abstract class PolicyCatalogHandler extends CatalogHandler {
       PolarisAuthorizableOperation op =
           PolarisAuthorizableOperation.GET_APPLICABLE_POLICIES_ON_TABLE;
       // only Iceberg tables are supported
-      authorizeBasicTableLikeOperationOrThrow(
+      resolveAndAuthorizeBasicTableLikeOperationOrThrow(
           op, PolarisEntitySubType.ICEBERG_TABLE, tableIdentifier);
     }
   }
 
   private void authorizeBasicCatalogOperationOrThrow(PolarisAuthorizableOperation op) {
     resolutionManifest = newResolutionManifest();
-    resolutionManifest.resolveAll();
+    AuthorizationState authorizationState = new AuthorizationState(resolutionManifest);
+    authorizer()
+        .resolveAuthorizationInputs(
+            authorizationState,
+            new AuthorizationRequest(
+                polarisPrincipal(),
+                List.of(
+                    new SingleTargetAuthorizationIntent(
+                        op, PolarisSecurableMapper.catalog(catalogName())))));
 
     PolarisResolvedPathWrapper targetCatalog =
         resolutionManifest.getResolvedReferenceCatalogEntity();
@@ -234,7 +255,20 @@ public abstract class PolicyCatalogHandler extends CatalogHandler {
       default -> throw new IllegalArgumentException("Unsupported target type: " + target.getType());
     }
 
-    ResolverStatus status = resolutionManifest.resolveAll();
+    PolarisAuthorizableOperation requestedOp =
+        determineRequestedPolicyMappingOperation(target, isAttach);
+    AuthorizationState authorizationState = new AuthorizationState(resolutionManifest);
+    authorizer()
+        .resolveAuthorizationInputs(
+            authorizationState,
+            new AuthorizationRequest(
+                polarisPrincipal(),
+                List.of(
+                    new PolicyAttachmentAuthorizationIntent(
+                        requestedOp,
+                        PolarisSecurableMapper.policy(catalogName(), identifier),
+                        PolarisSecurableMapper.policyAttachmentTarget(catalogName(), target)))));
+    ResolverStatus status = resolutionManifest.getPrimaryResolverStatusOrThrow();
 
     throwNotFoundExceptionIfFailToResolve(status, identifier);
 
@@ -287,6 +321,25 @@ public abstract class PolicyCatalogHandler extends CatalogHandler {
     };
   }
 
+  private PolarisAuthorizableOperation determineRequestedPolicyMappingOperation(
+      PolicyAttachmentTarget target, boolean isAttach) {
+    return switch (target.getType()) {
+      case CATALOG ->
+          isAttach
+              ? PolarisAuthorizableOperation.ATTACH_POLICY_TO_CATALOG
+              : PolarisAuthorizableOperation.DETACH_POLICY_FROM_CATALOG;
+      case NAMESPACE ->
+          isAttach
+              ? PolarisAuthorizableOperation.ATTACH_POLICY_TO_NAMESPACE
+              : PolarisAuthorizableOperation.DETACH_POLICY_FROM_NAMESPACE;
+      case TABLE_LIKE ->
+          isAttach
+              ? PolarisAuthorizableOperation.ATTACH_POLICY_TO_TABLE
+              : PolarisAuthorizableOperation.DETACH_POLICY_FROM_TABLE;
+      default -> throw new IllegalArgumentException("Unsupported target type: " + target.getType());
+    };
+  }
+
   private void throwNotFoundExceptionIfFailToResolve(
       ResolverStatus status, PolicyIdentifier identifier) {
     if ((status.getStatus() == ResolverStatus.StatusEnum.PATH_COULD_NOT_BE_FULLY_RESOLVED)) {
diff --git a/runtime/service/src/main/java/org/apache/polaris/service/config/ServiceProducers.java b/runtime/service/src/main/java/org/apache/polaris/service/config/ServiceProducers.java
index be42448943..6f33157e92 100644
--- a/runtime/service/src/main/java/org/apache/polaris/service/config/ServiceProducers.java
+++ b/runtime/service/src/main/java/org/apache/polaris/service/config/ServiceProducers.java
@@ -38,7 +38,6 @@ import java.util.stream.Collectors;
 import org.apache.polaris.core.PolarisCallContext;
 import org.apache.polaris.core.PolarisDefaultDiagServiceImpl;
 import org.apache.polaris.core.PolarisDiagnostics;
-import org.apache.polaris.core.auth.AuthorizationState;
 import org.apache.polaris.core.auth.PolarisAuthorizer;
 import org.apache.polaris.core.auth.PolarisAuthorizerFactory;
 import org.apache.polaris.core.config.RealmConfig;
@@ -174,12 +173,6 @@ public class ServiceProducers {
     return factory.create(realmConfig);
   }
 
-  @Produces
-  @RequestScoped
-  public AuthorizationState authorizationState() {
-    return new AuthorizationState();
-  }
-
   @Produces
   @RequestScoped
   public ResolverFactory resolverFactory(
diff --git a/runtime/service/src/test/java/org/apache/polaris/service/admin/PolarisAdminServiceAuthzTest.java b/runtime/service/src/test/java/org/apache/polaris/service/admin/PolarisAdminServiceAuthzTest.java
index 77866abbc9..69c776f374 100644
--- a/runtime/service/src/test/java/org/apache/polaris/service/admin/PolarisAdminServiceAuthzTest.java
+++ b/runtime/service/src/test/java/org/apache/polaris/service/admin/PolarisAdminServiceAuthzTest.java
@@ -48,7 +48,7 @@ public class PolarisAdminServiceAuthzTest extends PolarisAuthzTestBase {
   private PolarisAdminService newTestAdminService(Set<String> activatedPrincipalRoles) {
     final PolarisPrincipal authenticatedPrincipal =
         PolarisPrincipal.of(principalEntity, activatedPrincipalRoles);
-    return new PolarisAdminService(
+    return PolarisAdminServiceTestSupport.newAdminService(
         callContext,
         resolutionManifestFactory,
         metaStoreManager,
@@ -65,12 +65,12 @@ public class PolarisAdminServiceAuthzTest extends PolarisAuthzTestBase {
         .action(() -> newTestAdminService().listCatalogs())
         .grantAction(
             privilege ->
-                adminService.grantPrivilegeOnRootContainerToPrincipalRole(
-                    PRINCIPAL_ROLE1, privilege))
+                newRootAdminService()
+                    .grantPrivilegeOnRootContainerToPrincipalRole(PRINCIPAL_ROLE1, privilege))
         .revokeAction(
             privilege ->
-                adminService.revokePrivilegeOnRootContainerFromPrincipalRole(
-                    PRINCIPAL_ROLE1, privilege))
+                newRootAdminService()
+                    .revokePrivilegeOnRootContainerFromPrincipalRole(PRINCIPAL_ROLE1, privilege))
         .shouldPassWith(PolarisPrivilege.CATALOG_LIST)
         .shouldPassWith(PolarisPrivilege.CATALOG_CREATE)
         .shouldPassWith(PolarisPrivilege.CATALOG_FULL_METADATA)
@@ -86,8 +86,9 @@ public class PolarisAdminServiceAuthzTest extends PolarisAuthzTestBase {
   Stream<DynamicNode> testCreateCatalogPrivileges() {
     // Cleanup with PRINCIPAL_ROLE2
     assertSuccess(
-        adminService.grantPrivilegeOnRootContainerToPrincipalRole(
-            PRINCIPAL_ROLE2, PolarisPrivilege.CATALOG_DROP));
+        newRootAdminService()
+            .grantPrivilegeOnRootContainerToPrincipalRole(
+                PRINCIPAL_ROLE2, PolarisPrivilege.CATALOG_DROP));
     final CatalogEntity newCatalog = new CatalogEntity.Builder().setName("new_catalog").build();
     final CreateCatalogRequest createRequest =
         new CreateCatalogRequest(newCatalog.asCatalog(serviceIdentityProvider));
@@ -98,12 +99,12 @@ public class PolarisAdminServiceAuthzTest extends PolarisAuthzTestBase {
             () -> newTestAdminService(Set.of(PRINCIPAL_ROLE2)).deleteCatalog(newCatalog.getName()))
         .grantAction(
             privilege ->
-                adminService.grantPrivilegeOnRootContainerToPrincipalRole(
-                    PRINCIPAL_ROLE1, privilege))
+                newRootAdminService()
+                    .grantPrivilegeOnRootContainerToPrincipalRole(PRINCIPAL_ROLE1, privilege))
         .revokeAction(
             privilege ->
-                adminService.revokePrivilegeOnRootContainerFromPrincipalRole(
-                    PRINCIPAL_ROLE1, privilege))
+                newRootAdminService()
+                    .revokePrivilegeOnRootContainerFromPrincipalRole(PRINCIPAL_ROLE1, privilege))
         .shouldPassWith(PolarisPrivilege.CATALOG_CREATE)
         .shouldPassWith(PolarisPrivilege.CATALOG_FULL_METADATA)
         .shouldPassWith(PolarisPrivilege.SERVICE_MANAGE_ACCESS)
@@ -116,12 +117,12 @@ public class PolarisAdminServiceAuthzTest extends PolarisAuthzTestBase {
         .action(() -> newTestAdminService().getCatalog(CATALOG_NAME))
         .grantAction(
             privilege ->
-                adminService.grantPrivilegeOnRootContainerToPrincipalRole(
-                    PRINCIPAL_ROLE1, privilege))
+                newRootAdminService()
+                    .grantPrivilegeOnRootContainerToPrincipalRole(PRINCIPAL_ROLE1, privilege))
         .revokeAction(
             privilege ->
-                adminService.revokePrivilegeOnRootContainerFromPrincipalRole(
-                    PRINCIPAL_ROLE1, privilege))
+                newRootAdminService()
+                    .revokePrivilegeOnRootContainerFromPrincipalRole(PRINCIPAL_ROLE1, privilege))
         .shouldPassWith(PolarisPrivilege.CATALOG_READ_PROPERTIES)
         .shouldPassWith(PolarisPrivilege.CATALOG_FULL_METADATA)
         .shouldPassWith(PolarisPrivilege.CATALOG_MANAGE_CONTENT)
@@ -149,12 +150,12 @@ public class PolarisAdminServiceAuthzTest extends PolarisAuthzTestBase {
             })
         .grantAction(
             privilege ->
-                adminService.grantPrivilegeOnRootContainerToPrincipalRole(
-                    PRINCIPAL_ROLE1, privilege))
+                newRootAdminService()
+                    .grantPrivilegeOnRootContainerToPrincipalRole(PRINCIPAL_ROLE1, privilege))
         .revokeAction(
             privilege ->
-                adminService.revokePrivilegeOnRootContainerFromPrincipalRole(
-                    PRINCIPAL_ROLE1, privilege))
+                newRootAdminService()
+                    .revokePrivilegeOnRootContainerFromPrincipalRole(PRINCIPAL_ROLE1, privilege))
         .shouldPassWith(PolarisPrivilege.CATALOG_WRITE_PROPERTIES)
         .shouldPassWith(PolarisPrivilege.CATALOG_FULL_METADATA)
         .shouldPassWith(PolarisPrivilege.CATALOG_MANAGE_CONTENT)
@@ -167,12 +168,13 @@ public class PolarisAdminServiceAuthzTest extends PolarisAuthzTestBase {
   Stream<DynamicNode> testDeleteCatalogPrivileges() {
     // Cleanup with PRINCIPAL_ROLE2
     assertSuccess(
-        adminService.grantPrivilegeOnRootContainerToPrincipalRole(
-            PRINCIPAL_ROLE2, PolarisPrivilege.CATALOG_CREATE));
+        newRootAdminService()
+            .grantPrivilegeOnRootContainerToPrincipalRole(
+                PRINCIPAL_ROLE2, PolarisPrivilege.CATALOG_CREATE));
     final CatalogEntity newCatalog = new CatalogEntity.Builder().setName("new_catalog").build();
     final CreateCatalogRequest createRequest =
         new CreateCatalogRequest(newCatalog.asCatalog(serviceIdentityProvider));
-    adminService.createCatalog(createRequest);
+    newRootAdminService().createCatalog(createRequest);
 
     return authzTestsBuilder("deleteCatalog")
         .action(
@@ -181,12 +183,12 @@ public class PolarisAdminServiceAuthzTest extends PolarisAuthzTestBase {
             () -> newTestAdminService(Set.of(PRINCIPAL_ROLE2)).createCatalog(createRequest))
         .grantAction(
             privilege ->
-                adminService.grantPrivilegeOnRootContainerToPrincipalRole(
-                    PRINCIPAL_ROLE1, privilege))
+                newRootAdminService()
+                    .grantPrivilegeOnRootContainerToPrincipalRole(PRINCIPAL_ROLE1, privilege))
         .revokeAction(
             privilege ->
-                adminService.revokePrivilegeOnRootContainerFromPrincipalRole(
-                    PRINCIPAL_ROLE1, privilege))
+                newRootAdminService()
+                    .revokePrivilegeOnRootContainerFromPrincipalRole(PRINCIPAL_ROLE1, privilege))
         .shouldPassWith(PolarisPrivilege.CATALOG_DROP)
         .shouldPassWith(PolarisPrivilege.CATALOG_FULL_METADATA)
         .shouldPassWith(PolarisPrivilege.SERVICE_MANAGE_ACCESS)
@@ -199,12 +201,12 @@ public class PolarisAdminServiceAuthzTest extends PolarisAuthzTestBase {
         .action(() -> newTestAdminService().listPrincipals())
         .grantAction(
             privilege ->
-                adminService.grantPrivilegeOnRootContainerToPrincipalRole(
-                    PRINCIPAL_ROLE1, privilege))
+                newRootAdminService()
+                    .grantPrivilegeOnRootContainerToPrincipalRole(PRINCIPAL_ROLE1, privilege))
         .revokeAction(
             privilege ->
-                adminService.revokePrivilegeOnRootContainerFromPrincipalRole(
-                    PRINCIPAL_ROLE1, privilege))
+                newRootAdminService()
+                    .revokePrivilegeOnRootContainerFromPrincipalRole(PRINCIPAL_ROLE1, privilege))
         .shouldPassWith(PolarisPrivilege.PRINCIPAL_LIST)
         .shouldPassWith(PolarisPrivilege.PRINCIPAL_CREATE)
         .shouldPassWith(PolarisPrivilege.PRINCIPAL_READ_PROPERTIES)
@@ -218,8 +220,9 @@ public class PolarisAdminServiceAuthzTest extends PolarisAuthzTestBase {
   Stream<DynamicNode> testCreatePrincipalPrivileges() {
     // Cleanup with PRINCIPAL_ROLE2
     assertSuccess(
-        adminService.grantPrivilegeOnRootContainerToPrincipalRole(
-            PRINCIPAL_ROLE2, PolarisPrivilege.PRINCIPAL_DROP));
+        newRootAdminService()
+            .grantPrivilegeOnRootContainerToPrincipalRole(
+                PRINCIPAL_ROLE2, PolarisPrivilege.PRINCIPAL_DROP));
     final PrincipalEntity newPrincipal =
         new PrincipalEntity.Builder().setName("new_principal").build();
 
@@ -231,12 +234,12 @@ public class PolarisAdminServiceAuthzTest extends PolarisAuthzTestBase {
                     .deletePrincipal(newPrincipal.getName()))
         .grantAction(
             privilege ->
-                adminService.grantPrivilegeOnRootContainerToPrincipalRole(
-                    PRINCIPAL_ROLE1, privilege))
+                newRootAdminService()
+                    .grantPrivilegeOnRootContainerToPrincipalRole(PRINCIPAL_ROLE1, privilege))
         .revokeAction(
             privilege ->
-                adminService.revokePrivilegeOnRootContainerFromPrincipalRole(
-                    PRINCIPAL_ROLE1, privilege))
+                newRootAdminService()
+                    .revokePrivilegeOnRootContainerFromPrincipalRole(PRINCIPAL_ROLE1, privilege))
         .shouldPassWith(PolarisPrivilege.PRINCIPAL_CREATE)
         .shouldPassWith(PolarisPrivilege.PRINCIPAL_FULL_METADATA)
         .shouldPassWith(PolarisPrivilege.SERVICE_MANAGE_ACCESS)
@@ -249,12 +252,12 @@ public class PolarisAdminServiceAuthzTest extends PolarisAuthzTestBase {
         .action(() -> newTestAdminService().getPrincipal(PRINCIPAL_NAME))
         .grantAction(
             privilege ->
-                adminService.grantPrivilegeOnRootContainerToPrincipalRole(
-                    PRINCIPAL_ROLE1, privilege))
+                newRootAdminService()
+                    .grantPrivilegeOnRootContainerToPrincipalRole(PRINCIPAL_ROLE1, privilege))
         .revokeAction(
             privilege ->
-                adminService.revokePrivilegeOnRootContainerFromPrincipalRole(
-                    PRINCIPAL_ROLE1, privilege))
+                newRootAdminService()
+                    .revokePrivilegeOnRootContainerFromPrincipalRole(PRINCIPAL_ROLE1, privilege))
         .shouldPassWith(PolarisPrivilege.PRINCIPAL_READ_PROPERTIES)
         .shouldPassWith(PolarisPrivilege.PRINCIPAL_WRITE_PROPERTIES)
         .shouldPassWith(PolarisPrivilege.PRINCIPAL_FULL_METADATA)
@@ -280,12 +283,12 @@ public class PolarisAdminServiceAuthzTest extends PolarisAuthzTestBase {
             })
         .grantAction(
             privilege ->
-                adminService.grantPrivilegeOnRootContainerToPrincipalRole(
-                    PRINCIPAL_ROLE1, privilege))
+                newRootAdminService()
+                    .grantPrivilegeOnRootContainerToPrincipalRole(PRINCIPAL_ROLE1, privilege))
         .revokeAction(
             privilege ->
-                adminService.revokePrivilegeOnRootContainerFromPrincipalRole(
-                    PRINCIPAL_ROLE1, privilege))
+                newRootAdminService()
+                    .revokePrivilegeOnRootContainerFromPrincipalRole(PRINCIPAL_ROLE1, privilege))
         .shouldPassWith(PolarisPrivilege.PRINCIPAL_WRITE_PROPERTIES)
         .shouldPassWith(PolarisPrivilege.PRINCIPAL_FULL_METADATA)
         .shouldPassWith(PolarisPrivilege.SERVICE_MANAGE_ACCESS)
@@ -296,11 +299,12 @@ public class PolarisAdminServiceAuthzTest extends PolarisAuthzTestBase {
   Stream<DynamicNode> testDeletePrincipalPrivileges() {
     // Cleanup with PRINCIPAL_ROLE2
     assertSuccess(
-        adminService.grantPrivilegeOnRootContainerToPrincipalRole(
-            PRINCIPAL_ROLE2, PolarisPrivilege.PRINCIPAL_CREATE));
+        newRootAdminService()
+            .grantPrivilegeOnRootContainerToPrincipalRole(
+                PRINCIPAL_ROLE2, PolarisPrivilege.PRINCIPAL_CREATE));
     final PrincipalEntity newPrincipal =
         new PrincipalEntity.Builder().setName("new_principal").build();
-    adminService.createPrincipal(newPrincipal);
+    newRootAdminService().createPrincipal(newPrincipal);
 
     return authzTestsBuilder("deletePrincipal")
         .action(
@@ -311,12 +315,12 @@ public class PolarisAdminServiceAuthzTest extends PolarisAuthzTestBase {
             () -> newTestAdminService(Set.of(PRINCIPAL_ROLE2)).createPrincipal(newPrincipal))
         .grantAction(
             privilege ->
-                adminService.grantPrivilegeOnRootContainerToPrincipalRole(
-                    PRINCIPAL_ROLE1, privilege))
+                newRootAdminService()
+                    .grantPrivilegeOnRootContainerToPrincipalRole(PRINCIPAL_ROLE1, privilege))
         .revokeAction(
             privilege ->
-                adminService.revokePrivilegeOnRootContainerFromPrincipalRole(
-                    PRINCIPAL_ROLE1, privilege))
+                newRootAdminService()
+                    .revokePrivilegeOnRootContainerFromPrincipalRole(PRINCIPAL_ROLE1, privilege))
         .shouldPassWith(PolarisPrivilege.PRINCIPAL_DROP)
         .shouldPassWith(PolarisPrivilege.PRINCIPAL_FULL_METADATA)
         .shouldPassWith(PolarisPrivilege.SERVICE_MANAGE_ACCESS)
@@ -329,12 +333,12 @@ public class PolarisAdminServiceAuthzTest extends PolarisAuthzTestBase {
         .action(() -> newTestAdminService().listPrincipalRoles())
         .grantAction(
             privilege ->
-                adminService.grantPrivilegeOnRootContainerToPrincipalRole(
-                    PRINCIPAL_ROLE1, privilege))
+                newRootAdminService()
+                    .grantPrivilegeOnRootContainerToPrincipalRole(PRINCIPAL_ROLE1, privilege))
         .revokeAction(
             privilege ->
-                adminService.revokePrivilegeOnRootContainerFromPrincipalRole(
-                    PRINCIPAL_ROLE1, privilege))
+                newRootAdminService()
+                    .revokePrivilegeOnRootContainerFromPrincipalRole(PRINCIPAL_ROLE1, privilege))
         .shouldPassWith(PolarisPrivilege.PRINCIPAL_ROLE_LIST)
         .shouldPassWith(PolarisPrivilege.PRINCIPAL_ROLE_CREATE)
         .shouldPassWith(PolarisPrivilege.PRINCIPAL_ROLE_READ_PROPERTIES)
@@ -348,8 +352,9 @@ public class PolarisAdminServiceAuthzTest extends PolarisAuthzTestBase {
   Stream<DynamicNode> testCreatePrincipalRolePrivileges() {
     // Cleanup with PRINCIPAL_ROLE2
     assertSuccess(
-        adminService.grantPrivilegeOnRootContainerToPrincipalRole(
-            PRINCIPAL_ROLE2, PolarisPrivilege.PRINCIPAL_ROLE_DROP));
+        newRootAdminService()
+            .grantPrivilegeOnRootContainerToPrincipalRole(
+                PRINCIPAL_ROLE2, PolarisPrivilege.PRINCIPAL_ROLE_DROP));
     final PrincipalRoleEntity newPrincipalRole =
         new PrincipalRoleEntity.Builder().setName("new_principal_role").build();
 
@@ -363,12 +368,12 @@ public class PolarisAdminServiceAuthzTest extends PolarisAuthzTestBase {
                     .deletePrincipalRole(newPrincipalRole.getName()))
         .grantAction(
             privilege ->
-                adminService.grantPrivilegeOnRootContainerToPrincipalRole(
-                    PRINCIPAL_ROLE1, privilege))
+                newRootAdminService()
+                    .grantPrivilegeOnRootContainerToPrincipalRole(PRINCIPAL_ROLE1, privilege))
         .revokeAction(
             privilege ->
-                adminService.revokePrivilegeOnRootContainerFromPrincipalRole(
-                    PRINCIPAL_ROLE1, privilege))
+                newRootAdminService()
+                    .revokePrivilegeOnRootContainerFromPrincipalRole(PRINCIPAL_ROLE1, privilege))
         .shouldPassWith(PolarisPrivilege.PRINCIPAL_ROLE_CREATE)
         .shouldPassWith(PolarisPrivilege.PRINCIPAL_ROLE_FULL_METADATA)
         .shouldPassWith(PolarisPrivilege.SERVICE_MANAGE_ACCESS)
@@ -381,12 +386,12 @@ public class PolarisAdminServiceAuthzTest extends PolarisAuthzTestBase {
         .action(() -> newTestAdminService().getPrincipalRole(PRINCIPAL_ROLE2))
         .grantAction(
             privilege ->
-                adminService.grantPrivilegeOnRootContainerToPrincipalRole(
-                    PRINCIPAL_ROLE1, privilege))
+                newRootAdminService()
+                    .grantPrivilegeOnRootContainerToPrincipalRole(PRINCIPAL_ROLE1, privilege))
         .revokeAction(
             privilege ->
-                adminService.revokePrivilegeOnRootContainerFromPrincipalRole(
-                    PRINCIPAL_ROLE1, privilege))
+                newRootAdminService()
+                    .revokePrivilegeOnRootContainerFromPrincipalRole(PRINCIPAL_ROLE1, privilege))
         .shouldPassWith(PolarisPrivilege.PRINCIPAL_ROLE_READ_PROPERTIES)
         .shouldPassWith(PolarisPrivilege.PRINCIPAL_ROLE_WRITE_PROPERTIES)
         .shouldPassWith(PolarisPrivilege.PRINCIPAL_ROLE_FULL_METADATA)
@@ -414,12 +419,12 @@ public class PolarisAdminServiceAuthzTest extends PolarisAuthzTestBase {
             })
         .grantAction(
             privilege ->
-                adminService.grantPrivilegeOnRootContainerToPrincipalRole(
-                    PRINCIPAL_ROLE1, privilege))
+                newRootAdminService()
+                    .grantPrivilegeOnRootContainerToPrincipalRole(PRINCIPAL_ROLE1, privilege))
         .revokeAction(
             privilege ->
-                adminService.revokePrivilegeOnRootContainerFromPrincipalRole(
-                    PRINCIPAL_ROLE1, privilege))
+                newRootAdminService()
+                    .revokePrivilegeOnRootContainerFromPrincipalRole(PRINCIPAL_ROLE1, privilege))
         .shouldPassWith(PolarisPrivilege.PRINCIPAL_ROLE_WRITE_PROPERTIES)
         .shouldPassWith(PolarisPrivilege.PRINCIPAL_ROLE_FULL_METADATA)
         .shouldPassWith(PolarisPrivilege.SERVICE_MANAGE_ACCESS)
@@ -430,11 +435,12 @@ public class PolarisAdminServiceAuthzTest extends PolarisAuthzTestBase {
   Stream<DynamicNode> testDeletePrincipalRolePrivileges() {
     // Cleanup with PRINCIPAL_ROLE2
     assertSuccess(
-        adminService.grantPrivilegeOnRootContainerToPrincipalRole(
-            PRINCIPAL_ROLE2, PolarisPrivilege.PRINCIPAL_ROLE_CREATE));
+        newRootAdminService()
+            .grantPrivilegeOnRootContainerToPrincipalRole(
+                PRINCIPAL_ROLE2, PolarisPrivilege.PRINCIPAL_ROLE_CREATE));
     final PrincipalRoleEntity newPrincipalRole =
         new PrincipalRoleEntity.Builder().setName("new_principal_role").build();
-    adminService.createPrincipalRole(newPrincipalRole);
+    newRootAdminService().createPrincipalRole(newPrincipalRole);
 
     return authzTestsBuilder("deletePrincipalRole")
         .action(
@@ -446,12 +452,12 @@ public class PolarisAdminServiceAuthzTest extends PolarisAuthzTestBase {
                 newTestAdminService(Set.of(PRINCIPAL_ROLE2)).createPrincipalRole(newPrincipalRole))
         .grantAction(
             privilege ->
-                adminService.grantPrivilegeOnRootContainerToPrincipalRole(
-                    PRINCIPAL_ROLE1, privilege))
+                newRootAdminService()
+                    .grantPrivilegeOnRootContainerToPrincipalRole(PRINCIPAL_ROLE1, privilege))
         .revokeAction(
             privilege ->
-                adminService.revokePrivilegeOnRootContainerFromPrincipalRole(
-                    PRINCIPAL_ROLE1, privilege))
+                newRootAdminService()
+                    .revokePrivilegeOnRootContainerFromPrincipalRole(PRINCIPAL_ROLE1, privilege))
         .shouldPassWith(PolarisPrivilege.PRINCIPAL_ROLE_DROP)
         .shouldPassWith(PolarisPrivilege.PRINCIPAL_ROLE_FULL_METADATA)
         .shouldPassWith(PolarisPrivilege.SERVICE_MANAGE_ACCESS)
@@ -475,8 +481,9 @@ public class PolarisAdminServiceAuthzTest extends PolarisAuthzTestBase {
   Stream<DynamicNode> testCreateCatalogRolePrivileges() {
     // Cleanup with CATALOG_ROLE2
     assertSuccess(
-        adminService.grantPrivilegeOnCatalogToRole(
-            CATALOG_NAME, CATALOG_ROLE2, PolarisPrivilege.CATALOG_ROLE_DROP));
+        newRootAdminService()
+            .grantPrivilegeOnCatalogToRole(
+                CATALOG_NAME, CATALOG_ROLE2, PolarisPrivilege.CATALOG_ROLE_DROP));
     final CatalogRoleEntity newCatalogRole =
         new CatalogRoleEntity.Builder().setName("new_catalog_role").build();
 
@@ -534,11 +541,12 @@ public class PolarisAdminServiceAuthzTest extends PolarisAuthzTestBase {
   Stream<DynamicNode> testDeleteCatalogRolePrivileges() {
     // Cleanup with CATALOG_ROLE2
     assertSuccess(
-        adminService.grantPrivilegeOnCatalogToRole(
-            CATALOG_NAME, CATALOG_ROLE2, PolarisPrivilege.CATALOG_ROLE_CREATE));
+        newRootAdminService()
+            .grantPrivilegeOnCatalogToRole(
+                CATALOG_NAME, CATALOG_ROLE2, PolarisPrivilege.CATALOG_ROLE_CREATE));
     final CatalogRoleEntity newCatalogRole =
         new CatalogRoleEntity.Builder().setName("new_catalog_role").build();
-    adminService.createCatalogRole(CATALOG_NAME, newCatalogRole);
+    newRootAdminService().createCatalogRole(CATALOG_NAME, newCatalogRole);
 
     return authzTestsBuilder("deleteCatalogRole")
         .action(
@@ -557,7 +565,8 @@ public class PolarisAdminServiceAuthzTest extends PolarisAuthzTestBase {
 
   @TestFactory
   Stream<DynamicNode> testAssignPrincipalRolePrivileges() {
-    adminService.createPrincipal(new PrincipalEntity.Builder().setName("newprincipal").build());
+    newRootAdminService()
+        .createPrincipal(new PrincipalEntity.Builder().setName("newprincipal").build());
 
     // Assign only requires privileges on the securable.
     return authzTestsBuilder("assignPrincipalRole")
@@ -567,12 +576,12 @@ public class PolarisAdminServiceAuthzTest extends PolarisAuthzTestBase {
                     .assignPrincipalRole("newprincipal", PRINCIPAL_ROLE2))
         .grantAction(
             privilege ->
-                adminService.grantPrivilegeOnRootContainerToPrincipalRole(
-                    PRINCIPAL_ROLE1, privilege))
+                newRootAdminService()
+                    .grantPrivilegeOnRootContainerToPrincipalRole(PRINCIPAL_ROLE1, privilege))
         .revokeAction(
             privilege ->
-                adminService.revokePrivilegeOnRootContainerFromPrincipalRole(
-                    PRINCIPAL_ROLE1, privilege))
+                newRootAdminService()
+                    .revokePrivilegeOnRootContainerFromPrincipalRole(PRINCIPAL_ROLE1, privilege))
         .shouldPassWith(PolarisPrivilege.PRINCIPAL_ROLE_MANAGE_GRANTS_ON_SECURABLE)
         .shouldPassWith(PolarisPrivilege.SERVICE_MANAGE_ACCESS)
         .createTests();
@@ -580,7 +589,8 @@ public class PolarisAdminServiceAuthzTest extends PolarisAuthzTestBase {
 
   @TestFactory
   Stream<DynamicNode> testRevokePrincipalRolePrivileges() {
-    adminService.createPrincipal(new PrincipalEntity.Builder().setName("newprincipal").build());
+    newRootAdminService()
+        .createPrincipal(new PrincipalEntity.Builder().setName("newprincipal").build());
 
     // Revoke requires privileges both on the "securable" (PrincipalRole) as well as the "grantee"
     // (Principal).
@@ -591,12 +601,12 @@ public class PolarisAdminServiceAuthzTest extends PolarisAuthzTestBase {
                     .revokePrincipalRole("newprincipal", PRINCIPAL_ROLE2))
         .grantAction(
             privilege ->
-                adminService.grantPrivilegeOnRootContainerToPrincipalRole(
-                    PRINCIPAL_ROLE1, privilege))
+                newRootAdminService()
+                    .grantPrivilegeOnRootContainerToPrincipalRole(PRINCIPAL_ROLE1, privilege))
         .revokeAction(
             privilege ->
-                adminService.revokePrivilegeOnRootContainerFromPrincipalRole(
-                    PRINCIPAL_ROLE1, privilege))
+                newRootAdminService()
+                    .revokePrivilegeOnRootContainerFromPrincipalRole(PRINCIPAL_ROLE1, privilege))
         .shouldPassWith(PolarisPrivilege.SERVICE_MANAGE_ACCESS)
         .shouldPassWith(
             PolarisPrivilege.PRINCIPAL_ROLE_MANAGE_GRANTS_ON_SECURABLE,
@@ -614,12 +624,12 @@ public class PolarisAdminServiceAuthzTest extends PolarisAuthzTestBase {
                     .assignCatalogRoleToPrincipalRole(PRINCIPAL_ROLE2, CATALOG_NAME, CATALOG_ROLE1))
         .grantAction(
             privilege ->
-                adminService.grantPrivilegeOnRootContainerToPrincipalRole(
-                    PRINCIPAL_ROLE1, privilege))
+                newRootAdminService()
+                    .grantPrivilegeOnRootContainerToPrincipalRole(PRINCIPAL_ROLE1, privilege))
         .revokeAction(
             privilege ->
-                adminService.revokePrivilegeOnRootContainerFromPrincipalRole(
-                    PRINCIPAL_ROLE1, privilege))
+                newRootAdminService()
+                    .revokePrivilegeOnRootContainerFromPrincipalRole(PRINCIPAL_ROLE1, privilege))
         .shouldPassWith(PolarisPrivilege.CATALOG_ROLE_MANAGE_GRANTS_ON_SECURABLE)
         .shouldPassWith(PolarisPrivilege.CATALOG_MANAGE_ACCESS)
         .createTests();
@@ -636,12 +646,12 @@ public class PolarisAdminServiceAuthzTest extends PolarisAuthzTestBase {
                         PRINCIPAL_ROLE2, CATALOG_NAME, CATALOG_ROLE1))
         .grantAction(
             privilege ->
-                adminService.grantPrivilegeOnRootContainerToPrincipalRole(
-                    PRINCIPAL_ROLE1, privilege))
+                newRootAdminService()
+                    .grantPrivilegeOnRootContainerToPrincipalRole(PRINCIPAL_ROLE1, privilege))
         .revokeAction(
             privilege ->
-                adminService.revokePrivilegeOnRootContainerFromPrincipalRole(
-                    PRINCIPAL_ROLE1, privilege))
+                newRootAdminService()
+                    .revokePrivilegeOnRootContainerFromPrincipalRole(PRINCIPAL_ROLE1, privilege))
         .shouldPassWith(PolarisPrivilege.CATALOG_ROLE_MANAGE_GRANTS_ON_SECURABLE)
         .shouldPassWith(PolarisPrivilege.CATALOG_MANAGE_ACCESS)
         .createTests();
@@ -656,11 +666,12 @@ public class PolarisAdminServiceAuthzTest extends PolarisAuthzTestBase {
                     .listAssigneePrincipalRolesForCatalogRole(CATALOG_NAME, CATALOG_ROLE2))
         .grantAction(
             privilege ->
-                adminService.grantPrivilegeOnCatalogToRole(CATALOG_NAME, CATALOG_ROLE1, privilege))
+                newRootAdminService()
+                    .grantPrivilegeOnCatalogToRole(CATALOG_NAME, CATALOG_ROLE1, privilege))
         .revokeAction(
             privilege ->
-                adminService.revokePrivilegeOnCatalogFromRole(
-                    CATALOG_NAME, CATALOG_ROLE1, privilege))
+                newRootAdminService()
+                    .revokePrivilegeOnCatalogFromRole(CATALOG_NAME, CATALOG_ROLE1, privilege))
         .shouldPassWith(PolarisPrivilege.CATALOG_ROLE_LIST_GRANTS)
         .shouldPassWith(PolarisPrivilege.CATALOG_ROLE_MANAGE_GRANTS_ON_SECURABLE)
         .shouldPassWith(PolarisPrivilege.CATALOG_ROLE_MANAGE_GRANTS_FOR_GRANTEE)
@@ -677,11 +688,12 @@ public class PolarisAdminServiceAuthzTest extends PolarisAuthzTestBase {
                     .listGrantsForCatalogRole(CATALOG_NAME, CATALOG_ROLE2))
         .grantAction(
             privilege ->
-                adminService.grantPrivilegeOnCatalogToRole(CATALOG_NAME, CATALOG_ROLE1, privilege))
+                newRootAdminService()
+                    .grantPrivilegeOnCatalogToRole(CATALOG_NAME, CATALOG_ROLE1, privilege))
         .revokeAction(
             privilege ->
-                adminService.revokePrivilegeOnCatalogFromRole(
-                    CATALOG_NAME, CATALOG_ROLE1, privilege))
+                newRootAdminService()
+                    .revokePrivilegeOnCatalogFromRole(CATALOG_NAME, CATALOG_ROLE1, privilege))
         .shouldPassWith(PolarisPrivilege.CATALOG_ROLE_LIST_GRANTS)
         .shouldPassWith(PolarisPrivilege.CATALOG_ROLE_MANAGE_GRANTS_ON_SECURABLE)
         .shouldPassWith(PolarisPrivilege.CATALOG_ROLE_MANAGE_GRANTS_FOR_GRANTEE)
@@ -699,12 +711,12 @@ public class PolarisAdminServiceAuthzTest extends PolarisAuthzTestBase {
                         PRINCIPAL_ROLE2, PolarisPrivilege.SERVICE_MANAGE_ACCESS))
         .grantAction(
             privilege ->
-                adminService.grantPrivilegeOnRootContainerToPrincipalRole(
-                    PRINCIPAL_ROLE1, privilege))
+                newRootAdminService()
+                    .grantPrivilegeOnRootContainerToPrincipalRole(PRINCIPAL_ROLE1, privilege))
         .revokeAction(
             privilege ->
-                adminService.revokePrivilegeOnRootContainerFromPrincipalRole(
-                    PRINCIPAL_ROLE1, privilege))
+                newRootAdminService()
+                    .revokePrivilegeOnRootContainerFromPrincipalRole(PRINCIPAL_ROLE1, privilege))
         .shouldPassWith(PolarisPrivilege.SERVICE_MANAGE_ACCESS)
         .createTests();
   }
@@ -719,12 +731,12 @@ public class PolarisAdminServiceAuthzTest extends PolarisAuthzTestBase {
                         PRINCIPAL_ROLE2, PolarisPrivilege.SERVICE_MANAGE_ACCESS))
         .grantAction(
             privilege ->
-                adminService.grantPrivilegeOnRootContainerToPrincipalRole(
-                    PRINCIPAL_ROLE1, privilege))
+                newRootAdminService()
+                    .grantPrivilegeOnRootContainerToPrincipalRole(PRINCIPAL_ROLE1, privilege))
         .revokeAction(
             privilege ->
-                adminService.revokePrivilegeOnRootContainerFromPrincipalRole(
-                    PRINCIPAL_ROLE1, privilege))
+                newRootAdminService()
+                    .revokePrivilegeOnRootContainerFromPrincipalRole(PRINCIPAL_ROLE1, privilege))
         .shouldPassWith(PolarisPrivilege.SERVICE_MANAGE_ACCESS)
         .createTests();
   }
@@ -773,12 +785,14 @@ public class PolarisAdminServiceAuthzTest extends PolarisAuthzTestBase {
                                     PolarisPrivilege.CATALOG_MANAGE_ACCESS))
                     .grantAction(
                         privilege ->
-                            adminService.grantPrivilegeOnCatalogToRole(
-                                catalogName, CATALOG_ROLE1, privilege))
+                            newRootAdminService()
+                                .grantPrivilegeOnCatalogToRole(
+                                    catalogName, CATALOG_ROLE1, privilege))
                     .revokeAction(
                         privilege ->
-                            adminService.revokePrivilegeOnCatalogFromRole(
-                                catalogName, CATALOG_ROLE1, privilege))
+                            newRootAdminService()
+                                .revokePrivilegeOnCatalogFromRole(
+                                    catalogName, CATALOG_ROLE1, privilege))
                     .shouldPassWith(PolarisPrivilege.NAMESPACE_MANAGE_GRANTS_ON_SECURABLE)
                     .shouldPassWith(PolarisPrivilege.CATALOG_MANAGE_ACCESS)
                     .createTests());
@@ -798,12 +812,14 @@ public class PolarisAdminServiceAuthzTest extends PolarisAuthzTestBase {
                         PolarisPrivilege.CATALOG_MANAGE_ACCESS))
         .grantAction(
             privilege ->
-                adminService.grantPrivilegeOnCatalogToRole(
-                    FEDERATED_CATALOG_NAME, CATALOG_ROLE1, privilege))
+                newRootAdminService()
+                    .grantPrivilegeOnCatalogToRole(
+                        FEDERATED_CATALOG_NAME, CATALOG_ROLE1, privilege))
         .revokeAction(
             privilege ->
-                adminService.revokePrivilegeOnCatalogFromRole(
-                    FEDERATED_CATALOG_NAME, CATALOG_ROLE1, privilege))
+                newRootAdminService()
+                    .revokePrivilegeOnCatalogFromRole(
+                        FEDERATED_CATALOG_NAME, CATALOG_ROLE1, privilege))
         .shouldPassWith(PolarisPrivilege.NAMESPACE_MANAGE_GRANTS_ON_SECURABLE)
         .shouldPassWith(PolarisPrivilege.CATALOG_MANAGE_ACCESS)
         .createTests();
@@ -840,12 +856,14 @@ public class PolarisAdminServiceAuthzTest extends PolarisAuthzTestBase {
                                     PolarisPrivilege.CATALOG_MANAGE_ACCESS))
                     .grantAction(
                         privilege ->
-                            adminService.grantPrivilegeOnCatalogToRole(
-                                catalogName, CATALOG_ROLE1, privilege))
+                            newRootAdminService()
+                                .grantPrivilegeOnCatalogToRole(
+                                    catalogName, CATALOG_ROLE1, privilege))
                     .revokeAction(
                         privilege ->
-                            adminService.revokePrivilegeOnCatalogFromRole(
-                                catalogName, CATALOG_ROLE1, privilege))
+                            newRootAdminService()
+                                .revokePrivilegeOnCatalogFromRole(
+                                    catalogName, CATALOG_ROLE1, privilege))
                     .shouldPassWith(PolarisPrivilege.TABLE_MANAGE_GRANTS_ON_SECURABLE)
                     .shouldPassWith(PolarisPrivilege.CATALOG_MANAGE_ACCESS)
                     .createTests());
diff --git a/runtime/service/src/test/java/org/apache/polaris/service/admin/PolarisAdminServiceTest.java b/runtime/service/src/test/java/org/apache/polaris/service/admin/PolarisAdminServiceTest.java
index e2b7936de9..511a2feb79 100644
--- a/runtime/service/src/test/java/org/apache/polaris/service/admin/PolarisAdminServiceTest.java
+++ b/runtime/service/src/test/java/org/apache/polaris/service/admin/PolarisAdminServiceTest.java
@@ -23,6 +23,7 @@ import static org.assertj.core.api.Assertions.assertThat;
 import static org.assertj.core.api.Assertions.assertThatThrownBy;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
 
@@ -32,6 +33,7 @@ import org.apache.iceberg.catalog.TableIdentifier;
 import org.apache.iceberg.exceptions.NoSuchTableException;
 import org.apache.iceberg.exceptions.NotFoundException;
 import org.apache.polaris.core.PolarisCallContext;
+import org.apache.polaris.core.auth.AuthorizationState;
 import org.apache.polaris.core.auth.PolarisAuthorizer;
 import org.apache.polaris.core.auth.PolarisPrincipal;
 import org.apache.polaris.core.catalog.PolarisCatalogHelpers;
@@ -97,8 +99,18 @@ public class PolarisAdminServiceTest {
 
     when(resolutionManifestFactory.createResolutionManifest(any(), any()))
         .thenReturn(resolutionManifest);
-    when(resolutionManifest.resolveAll()).thenReturn(createSuccessfulResolverStatus());
+    ResolverStatus successStatus = createSuccessfulResolverStatus();
+    when(resolutionManifest.resolveAll()).thenReturn(successStatus);
+    when(resolutionManifest.getPrimaryResolverStatusOrThrow()).thenReturn(successStatus);
     when(resolutionManifest.getIsPassthroughFacade()).thenReturn(false);
+    doAnswer(
+            invocation -> {
+              AuthorizationState authzState = invocation.getArgument(0);
+              authzState.getResolutionManifest().resolveAll();
+              return null;
+            })
+        .when(authorizer)
+        .resolveAuthorizationInputs(any(), any());
 
     adminService =
         new PolarisAdminService(
@@ -146,7 +158,9 @@ public class PolarisAdminServiceTest {
 
     when(resolutionManifestFactory.createResolutionManifest(any(), eq(catalogName)))
         .thenReturn(resolutionManifest);
-    when(resolutionManifest.resolveAll()).thenReturn(createSuccessfulResolverStatus());
+    ResolverStatus successStatus = createSuccessfulResolverStatus();
+    when(resolutionManifest.resolveAll()).thenReturn(successStatus);
+    when(resolutionManifest.getPrimaryResolverStatusOrThrow()).thenReturn(successStatus);
 
     PolarisEntity catalogEntity = createEntity(catalogName, PolarisEntityType.CATALOG);
     when(resolutionManifest.getResolvedCatalogEntity()).thenReturn(CatalogEntity.of(catalogEntity));
@@ -181,7 +195,9 @@ public class PolarisAdminServiceTest {
 
     when(resolutionManifestFactory.createResolutionManifest(any(), eq(catalogName)))
         .thenReturn(resolutionManifest);
-    when(resolutionManifest.resolveAll()).thenReturn(createSuccessfulResolverStatus());
+    ResolverStatus successStatus = createSuccessfulResolverStatus();
+    when(resolutionManifest.resolveAll()).thenReturn(successStatus);
+    when(resolutionManifest.getPrimaryResolverStatusOrThrow()).thenReturn(successStatus);
 
     PolarisEntity catalogEntity = createEntity(catalogName, PolarisEntityType.CATALOG, 1L);
     when(resolutionManifest.getResolvedCatalogEntity()).thenReturn(CatalogEntity.of(catalogEntity));
@@ -243,7 +259,9 @@ public class PolarisAdminServiceTest {
 
     when(resolutionManifestFactory.createResolutionManifest(any(), eq(catalogName)))
         .thenReturn(resolutionManifest);
-    when(resolutionManifest.resolveAll()).thenReturn(createSuccessfulResolverStatus());
+    ResolverStatus successStatus = createSuccessfulResolverStatus();
+    when(resolutionManifest.resolveAll()).thenReturn(successStatus);
+    when(resolutionManifest.getPrimaryResolverStatusOrThrow()).thenReturn(successStatus);
 
     PolarisResolvedPathWrapper catalogRoleWrapper = mock(PolarisResolvedPathWrapper.class);
     PolarisEntity catalogRoleEntity =
@@ -275,7 +293,9 @@ public class PolarisAdminServiceTest {
 
     when(resolutionManifestFactory.createResolutionManifest(any(), eq(catalogName)))
         .thenReturn(resolutionManifest);
-    when(resolutionManifest.resolveAll()).thenReturn(createSuccessfulResolverStatus());
+    ResolverStatus successStatus = createSuccessfulResolverStatus();
+    when(resolutionManifest.resolveAll()).thenReturn(successStatus);
+    when(resolutionManifest.getPrimaryResolverStatusOrThrow()).thenReturn(successStatus);
 
     PolarisResolvedPathWrapper catalogRoleWrapper = mock(PolarisResolvedPathWrapper.class);
     PolarisEntity catalogRoleEntity =
@@ -617,7 +637,8 @@ public class PolarisAdminServiceTest {
   void testGrantPrivilegeOnTableLikeToRole_SyntheticEntityCreationFails() throws Exception {
     String catalogName = "test-catalog";
     String catalogRoleName = "test-role";
-    TableIdentifier identifier = TableIdentifier.of(Namespace.empty(), "test-table");
+    Namespace namespace = Namespace.of("valid-ns");
+    TableIdentifier identifier = TableIdentifier.of(namespace, "test-table");
     PolarisPrivilege privilege = PolarisPrivilege.TABLE_WRITE_DATA;
 
     PolarisEntity catalogEntity = createEntity(catalogName, PolarisEntityType.CATALOG);
@@ -633,6 +654,7 @@ public class PolarisAdminServiceTest {
 
     PolarisResolvedPathWrapper existingPathWrapper = mock(PolarisResolvedPathWrapper.class);
     when(existingPathWrapper.getRawFullPath()).thenReturn(List.of(catalogEntity));
+    when(existingPathWrapper.getRawLeafEntity()).thenReturn(catalogEntity);
     when(resolutionManifest.getResolvedPath(
             eq(
                 ResolvedPathKey.of(
@@ -640,23 +662,29 @@ public class PolarisAdminServiceTest {
                     PolarisEntityType.TABLE_LIKE)),
             eq(PolarisEntitySubType.ANY_SUBTYPE)))
         .thenReturn(existingPathWrapper);
-    when(existingPathWrapper.getRawLeafEntity()).thenReturn(catalogEntity);
 
     GenerateEntityIdResult idResult = mock(GenerateEntityIdResult.class);
     when(idResult.getId()).thenReturn(3L);
     when(metaStoreManager.generateNewEntityId(any())).thenReturn(idResult);
+    EntityResult namespaceCreateResult = mock(EntityResult.class);
+    when(namespaceCreateResult.isSuccess()).thenReturn(true);
+    PolarisEntity namespaceEntity = createNamespaceEntity(namespace, 4L, catalogEntity.getId());
+    when(namespaceCreateResult.getEntity()).thenReturn(namespaceEntity);
+
     EntityResult tableCreateResult = mock(EntityResult.class);
-    when(metaStoreManager.createEntityIfNotExists(any(), any(), any()))
-        .thenReturn(tableCreateResult);
     when(tableCreateResult.isSuccess()).thenReturn(false);
+    when(metaStoreManager.createEntityIfNotExists(any(), any(), any()))
+        .thenReturn(namespaceCreateResult, tableCreateResult);
 
-    when(resolutionManifest.getResolvedPath(
-            eq(
-                ResolvedPathKey.of(
-                    PolarisCatalogHelpers.tableIdentifierToList(identifier),
-                    PolarisEntityType.TABLE_LIKE))))
-        .thenReturn(existingPathWrapper);
-    when(existingPathWrapper.getRawLeafEntity()).thenReturn(catalogEntity);
+    PolarisResolvedPathWrapper namespacePathWrapper = mock(PolarisResolvedPathWrapper.class);
+    when(namespacePathWrapper.getRawLeafEntity()).thenReturn(namespaceEntity);
+    when(namespacePathWrapper.getRawFullPath()).thenReturn(List.of(catalogEntity, namespaceEntity));
+    when(namespacePathWrapper.isFullyResolvedNamespace(eq(catalogName), eq(namespace)))
+        .thenReturn(true);
+    when(resolutionManifest.getPassthroughResolvedPath(eq(ResolvedPathKey.ofNamespace(namespace))))
+        .thenReturn(namespacePathWrapper);
+    when(resolutionManifest.getPassthroughResolvedPath(eq(ResolvedPathKey.ofTableLike(identifier))))
+        .thenReturn(null);
 
     assertThatThrownBy(
             () ->
@@ -740,7 +768,9 @@ public class PolarisAdminServiceTest {
 
     when(resolutionManifestFactory.createResolutionManifest(any(), eq(catalogName)))
         .thenReturn(resolutionManifest);
-    when(resolutionManifest.resolveAll()).thenReturn(createSuccessfulResolverStatus());
+    ResolverStatus successStatus = createSuccessfulResolverStatus();
+    when(resolutionManifest.resolveAll()).thenReturn(successStatus);
+    when(resolutionManifest.getPrimaryResolverStatusOrThrow()).thenReturn(successStatus);
     when(resolutionManifest.getResolvedPath(
             eq(ResolvedPathKey.of(List.of(namespace.levels()), PolarisEntityType.NAMESPACE))))
         .thenReturn(resolvedPathWrapper);
diff --git a/runtime/service/src/test/java/org/apache/polaris/service/admin/PolarisAdminServiceTestSupport.java b/runtime/service/src/test/java/org/apache/polaris/service/admin/PolarisAdminServiceTestSupport.java
new file mode 100644
index 0000000000..901716760f
--- /dev/null
+++ b/runtime/service/src/test/java/org/apache/polaris/service/admin/PolarisAdminServiceTestSupport.java
@@ -0,0 +1,52 @@
+/*
+ * 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.polaris.service.admin;
+
+import org.apache.polaris.core.auth.PolarisAuthorizer;
+import org.apache.polaris.core.auth.PolarisPrincipal;
+import org.apache.polaris.core.context.CallContext;
+import org.apache.polaris.core.identity.provider.ServiceIdentityProvider;
+import org.apache.polaris.core.persistence.PolarisMetaStoreManager;
+import org.apache.polaris.core.persistence.resolver.ResolutionManifestFactory;
+import org.apache.polaris.core.secrets.UserSecretsManager;
+import org.apache.polaris.service.config.ReservedProperties;
+
+public final class PolarisAdminServiceTestSupport {
+  private PolarisAdminServiceTestSupport() {}
+
+  public static PolarisAdminService newAdminService(
+      CallContext callContext,
+      ResolutionManifestFactory resolutionManifestFactory,
+      PolarisMetaStoreManager metaStoreManager,
+      UserSecretsManager userSecretsManager,
+      ServiceIdentityProvider serviceIdentityProvider,
+      PolarisPrincipal principal,
+      PolarisAuthorizer authorizer,
+      ReservedProperties reservedProperties) {
+    return new PolarisAdminService(
+        callContext,
+        resolutionManifestFactory,
+        metaStoreManager,
+        userSecretsManager,
+        serviceIdentityProvider,
+        principal,
+        authorizer,
+        reservedProperties);
+  }
+}
diff --git a/runtime/service/src/test/java/org/apache/polaris/service/admin/PolarisAuthzTestBase.java b/runtime/service/src/test/java/org/apache/polaris/service/admin/PolarisAuthzTestBase.java
index 5a5a69015e..8dd07048c8 100644
--- a/runtime/service/src/test/java/org/apache/polaris/service/admin/PolarisAuthzTestBase.java
+++ b/runtime/service/src/test/java/org/apache/polaris/service/admin/PolarisAuthzTestBase.java
@@ -222,7 +222,7 @@ public abstract class PolarisAuthzTestBase {
     QuarkusMock.installMockForType(authenticatedRoot, PolarisPrincipal.class);
 
     this.adminService =
-        new PolarisAdminService(
+        PolarisAdminServiceTestSupport.newAdminService(
             callContext,
             resolutionManifestFactory,
             metaStoreManager,
@@ -286,21 +286,25 @@ public abstract class PolarisAuthzTestBase {
             .build();
 
     catalogEntity =
-        adminService.createCatalog(
-            new CreateCatalogRequest(
-                new CatalogEntity.Builder()
-                    .setName(CATALOG_NAME)
-                    .setCatalogType("INTERNAL")
-                    .setDefaultBaseLocation(storageLocation)
-                    .setStorageConfigurationInfo(realmConfig, storageConfigModel, storageLocation)
-                    .build()
-                    .asCatalog(serviceIdentityProvider)));
-    federatedCatalogEntity = adminService.createCatalog(new CreateCatalogRequest(externalCatalog));
+        newRootAdminService()
+            .createCatalog(
+                new CreateCatalogRequest(
+                    new CatalogEntity.Builder()
+                        .setName(CATALOG_NAME)
+                        .setCatalogType("INTERNAL")
+                        .setDefaultBaseLocation(storageLocation)
+                        .setStorageConfigurationInfo(
+                            realmConfig, storageConfigModel, storageLocation)
+                        .build()
+                        .asCatalog(serviceIdentityProvider)));
+    federatedCatalogEntity =
+        newRootAdminService().createCatalog(new CreateCatalogRequest(externalCatalog));
 
     initBaseCatalog();
 
     PrincipalWithCredentials principal =
-        adminService.createPrincipal(new PrincipalEntity.Builder().setName(PRINCIPAL_NAME).build());
+        newRootAdminService()
+            .createPrincipal(new PrincipalEntity.Builder().setName(PRINCIPAL_NAME).build());
     principalEntity =
         rotateAndRefreshPrincipal(
             metaStoreManager, PRINCIPAL_NAME, principal.getCredentials(), polarisContext);
@@ -308,42 +312,49 @@ public abstract class PolarisAuthzTestBase {
     // Pre-create the principal roles and catalog roles without any grants on securables, but
     // assign both principal roles to the principal, then CATALOG_ROLE1 to PRINCIPAL_ROLE1,
     // CATALOG_ROLE2 to PRINCIPAL_ROLE2, and CATALOG_ROLE_SHARED to both.
-    adminService.createPrincipalRole(
-        new PrincipalRoleEntity.Builder().setName(PRINCIPAL_ROLE1).build());
-    adminService.createPrincipalRole(
-        new PrincipalRoleEntity.Builder().setName(PRINCIPAL_ROLE2).build());
-    adminService.createCatalogRole(
-        CATALOG_NAME, new CatalogRoleEntity.Builder().setName(CATALOG_ROLE1).build());
-    adminService.createCatalogRole(
-        CATALOG_NAME, new CatalogRoleEntity.Builder().setName(CATALOG_ROLE2).build());
-    adminService.createCatalogRole(
-        CATALOG_NAME, new CatalogRoleEntity.Builder().setName(CATALOG_ROLE_SHARED).build());
-    adminService.createCatalogRole(
-        FEDERATED_CATALOG_NAME, new CatalogRoleEntity.Builder().setName(CATALOG_ROLE1).build());
-    adminService.createCatalogRole(
-        FEDERATED_CATALOG_NAME, new CatalogRoleEntity.Builder().setName(CATALOG_ROLE2).build());
-
-    assertSuccess(adminService.assignPrincipalRole(PRINCIPAL_NAME, PRINCIPAL_ROLE1));
-    assertSuccess(adminService.assignPrincipalRole(PRINCIPAL_NAME, PRINCIPAL_ROLE2));
+    newRootAdminService()
+        .createPrincipalRole(new PrincipalRoleEntity.Builder().setName(PRINCIPAL_ROLE1).build());
+    newRootAdminService()
+        .createPrincipalRole(new PrincipalRoleEntity.Builder().setName(PRINCIPAL_ROLE2).build());
+    newRootAdminService()
+        .createCatalogRole(
+            CATALOG_NAME, new CatalogRoleEntity.Builder().setName(CATALOG_ROLE1).build());
+    newRootAdminService()
+        .createCatalogRole(
+            CATALOG_NAME, new CatalogRoleEntity.Builder().setName(CATALOG_ROLE2).build());
+    newRootAdminService()
+        .createCatalogRole(
+            CATALOG_NAME, new CatalogRoleEntity.Builder().setName(CATALOG_ROLE_SHARED).build());
+    newRootAdminService()
+        .createCatalogRole(
+            FEDERATED_CATALOG_NAME, new CatalogRoleEntity.Builder().setName(CATALOG_ROLE1).build());
+    newRootAdminService()
+        .createCatalogRole(
+            FEDERATED_CATALOG_NAME, new CatalogRoleEntity.Builder().setName(CATALOG_ROLE2).build());
+
+    assertSuccess(newRootAdminService().assignPrincipalRole(PRINCIPAL_NAME, PRINCIPAL_ROLE1));
+    assertSuccess(newRootAdminService().assignPrincipalRole(PRINCIPAL_NAME, PRINCIPAL_ROLE2));
 
     assertSuccess(
-        adminService.assignCatalogRoleToPrincipalRole(
-            PRINCIPAL_ROLE1, CATALOG_NAME, CATALOG_ROLE1));
+        newRootAdminService()
+            .assignCatalogRoleToPrincipalRole(PRINCIPAL_ROLE1, CATALOG_NAME, CATALOG_ROLE1));
     assertSuccess(
-        adminService.assignCatalogRoleToPrincipalRole(
-            PRINCIPAL_ROLE2, CATALOG_NAME, CATALOG_ROLE2));
+        newRootAdminService()
+            .assignCatalogRoleToPrincipalRole(PRINCIPAL_ROLE2, CATALOG_NAME, CATALOG_ROLE2));
     assertSuccess(
-        adminService.assignCatalogRoleToPrincipalRole(
-            PRINCIPAL_ROLE1, CATALOG_NAME, CATALOG_ROLE_SHARED));
+        newRootAdminService()
+            .assignCatalogRoleToPrincipalRole(PRINCIPAL_ROLE1, CATALOG_NAME, CATALOG_ROLE_SHARED));
     assertSuccess(
-        adminService.assignCatalogRoleToPrincipalRole(
-            PRINCIPAL_ROLE2, CATALOG_NAME, CATALOG_ROLE_SHARED));
+        newRootAdminService()
+            .assignCatalogRoleToPrincipalRole(PRINCIPAL_ROLE2, CATALOG_NAME, CATALOG_ROLE_SHARED));
     assertSuccess(
-        adminService.assignCatalogRoleToPrincipalRole(
-            PRINCIPAL_ROLE1, FEDERATED_CATALOG_NAME, CATALOG_ROLE1));
+        newRootAdminService()
+            .assignCatalogRoleToPrincipalRole(
+                PRINCIPAL_ROLE1, FEDERATED_CATALOG_NAME, CATALOG_ROLE1));
     assertSuccess(
-        adminService.assignCatalogRoleToPrincipalRole(
-            PRINCIPAL_ROLE2, FEDERATED_CATALOG_NAME, CATALOG_ROLE2));
+        newRootAdminService()
+            .assignCatalogRoleToPrincipalRole(
+                PRINCIPAL_ROLE2, FEDERATED_CATALOG_NAME, CATALOG_ROLE2));
 
     // Do some shared setup with non-authz-aware baseCatalog.
     baseCatalog.createNamespace(NS1);
@@ -524,9 +535,21 @@ public abstract class PolarisAuthzTestBase {
     }
   }
 
+  protected PolarisAdminService newRootAdminService() {
+    return PolarisAdminServiceTestSupport.newAdminService(
+        callContext,
+        resolutionManifestFactory,
+        metaStoreManager,
+        userSecretsManager,
+        serviceIdentityProvider,
+        authenticatedRoot,
+        polarisAuthorizer,
+        reservedProperties);
+  }
+
   protected PolarisAuthzTestsFactory.Builder authzTestsBuilder(String operationName) {
     return PolarisAuthzTestsFactory.builder()
-        .adminService(adminService)
+        .adminServiceSupplier(this::newRootAdminService)
         .operationName(operationName);
   }
 }
diff --git a/runtime/service/src/test/java/org/apache/polaris/service/admin/PolarisAuthzTestsFactory.java b/runtime/service/src/test/java/org/apache/polaris/service/admin/PolarisAuthzTestsFactory.java
index db0fda2935..b08dbc3ea6 100644
--- a/runtime/service/src/test/java/org/apache/polaris/service/admin/PolarisAuthzTestsFactory.java
+++ b/runtime/service/src/test/java/org/apache/polaris/service/admin/PolarisAuthzTestsFactory.java
@@ -23,6 +23,8 @@ import static org.assertj.core.api.Assertions.assertThatCode;
 import static org.assertj.core.api.Assertions.assertThatThrownBy;
 
 import com.google.errorprone.annotations.CanIgnoreReturnValue;
+import io.quarkus.arc.Arc;
+import io.quarkus.arc.ManagedContext;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Comparator;
@@ -31,6 +33,7 @@ import java.util.List;
 import java.util.Optional;
 import java.util.Set;
 import java.util.function.Function;
+import java.util.function.Supplier;
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
 import org.apache.iceberg.exceptions.ForbiddenException;
@@ -63,14 +66,37 @@ import org.junit.jupiter.api.DynamicTest;
 @PolarisImmutable
 public abstract class PolarisAuthzTestsFactory {
 
+  private static void runInFreshRequestContext(Runnable action) {
+    ManagedContext requestContext = Arc.container().requestContext();
+    boolean wasActive = requestContext.isActive();
+    if (wasActive) {
+      requestContext.terminate();
+    }
+
+    requestContext.activate();
+    try {
+      action.run();
+    } finally {
+      requestContext.terminate();
+      if (wasActive) {
+        requestContext.activate();
+      }
+    }
+  }
+
   protected abstract String operationName();
 
-  protected abstract PolarisAdminService adminService();
+  protected abstract Supplier<PolarisAdminService> adminServiceSupplier();
 
   protected abstract Runnable action();
 
   protected abstract Optional<Runnable> cleanupAction();
 
+  @Value.Default
+  protected boolean useFreshRequestContext() {
+    return false;
+  }
+
   @Value.Default
   protected String principalName() {
     return PolarisAuthzTestBase.PRINCIPAL_NAME;
@@ -84,7 +110,8 @@ public abstract class PolarisAuthzTestsFactory {
   @Value.Default
   protected Function<PolarisPrivilege, PrivilegeResult> grantAction() {
     return privilege ->
-        adminService()
+        adminServiceSupplier()
+            .get()
             .grantPrivilegeOnCatalogToRole(
                 catalogName(), PolarisAuthzTestBase.CATALOG_ROLE1, privilege);
   }
@@ -92,7 +119,8 @@ public abstract class PolarisAuthzTestsFactory {
   @Value.Default
   protected Function<PolarisPrivilege, PrivilegeResult> revokeAction() {
     return privilege ->
-        adminService()
+        adminServiceSupplier()
+            .get()
             .revokePrivilegeOnCatalogFromRole(
                 catalogName(), PolarisAuthzTestBase.CATALOG_ROLE1, privilege);
   }
@@ -145,6 +173,10 @@ public abstract class PolarisAuthzTestsFactory {
     @CanIgnoreReturnValue
     public abstract Builder operationName(String operationName);
 
+    @CanIgnoreReturnValue
+    public abstract Builder adminServiceSupplier(
+        Supplier<PolarisAdminService> adminServiceSupplier);
+
     /** Sets the action to test. */
     @CanIgnoreReturnValue
     public abstract Builder action(Runnable action);
@@ -167,6 +199,9 @@ public abstract class PolarisAuthzTestsFactory {
     @CanIgnoreReturnValue
     public abstract Builder catalogName(String catalogName);
 
+    @CanIgnoreReturnValue
+    public abstract Builder useFreshRequestContext(boolean useFreshRequestContext);
+
     @CanIgnoreReturnValue
     public abstract Builder grantAction(Function<PolarisPrivilege, PrivilegeResult> grantAction);
 
@@ -218,9 +253,6 @@ public abstract class PolarisAuthzTestsFactory {
     @CanIgnoreReturnValue
     protected abstract Builder from(PolarisAuthzTestsFactory factory);
 
-    @CanIgnoreReturnValue
-    protected abstract Builder adminService(PolarisAdminService adminService);
-
     @CanIgnoreReturnValue
     protected abstract Builder addSufficientPrivilegeSet(Set<PolarisPrivilege> set);
 
@@ -251,13 +283,22 @@ public abstract class PolarisAuthzTestsFactory {
                                   .isTrue();
                             }
 
-                            assertThatCode(action()::run)
+                            Runnable authzAction =
+                                useFreshRequestContext()
+                                    ? () -> runInFreshRequestContext(action())
+                                    : action();
+                            assertThatCode(() -> authzAction.run())
                                 .describedAs(
                                     "Expected success with sufficient privileges '%s'",
                                     privilegeSet)
                                 .doesNotThrowAnyException();
 
-                            assertThatCode(cleanupAction().orElse(() -> {})::run)
+                            Runnable cleanup =
+                                useFreshRequestContext()
+                                    ? () ->
+                                        runInFreshRequestContext(cleanupAction().orElse(() -> {}))
+                                    : cleanupAction().orElse(() -> {});
+                            assertThatCode(() -> cleanup.run())
                                 .describedAs(
                                     "Cleanup action with sufficient privileges '%s'", privilegeSet)
                                 .doesNotThrowAnyException();
@@ -273,8 +314,12 @@ public abstract class PolarisAuthzTestsFactory {
                                 assertThat(revokeAction().apply(privilege).isSuccess())
                                     .describedAs("Expected success after revoking '%s'", privilege)
                                     .isTrue();
+                                Runnable authzAction =
+                                    useFreshRequestContext()
+                                        ? () -> runInFreshRequestContext(action())
+                                        : action();
                                 assertThatThrownBy(
-                                        action()::run,
+                                        () -> authzAction.run(),
                                         "Expected ForbiddenException after revoking sufficient privilege '%s' from set '%s'",
                                         privilege,
                                         privilegeSet)
@@ -298,8 +343,12 @@ public abstract class PolarisAuthzTestsFactory {
                                   .describedAs("Expected success after revoking '%s'", privilege)
                                   .isTrue();
                             }
+                            Runnable authzAction =
+                                useFreshRequestContext()
+                                    ? () -> runInFreshRequestContext(action())
+                                    : action();
                             assertThatThrownBy(
-                                    action()::run,
+                                    () -> authzAction.run(),
                                     "Expected ForbiddenException after revoking all sufficient privileges '%s'",
                                     privilegeSet)
                                 .isInstanceOf(ForbiddenException.class)
@@ -327,9 +376,13 @@ public abstract class PolarisAuthzTestsFactory {
                                   .describedAs("Expected success after granting '%s'", privilege)
                                   .isTrue();
                             }
+                            Runnable authzAction =
+                                useFreshRequestContext()
+                                    ? () -> runInFreshRequestContext(action())
+                                    : action();
 
                             assertThatThrownBy(
-                                    action()::run,
+                                    () -> authzAction.run(),
                                     "Expected ForbiddenException with insufficient privilege set '%s'",
                                     privilegeSet)
                                 .isInstanceOf(ForbiddenException.class)
diff --git a/runtime/service/src/test/java/org/apache/polaris/service/auth/DefaultAuthenticatorTest.java b/runtime/service/src/test/java/org/apache/polaris/service/auth/DefaultAuthenticatorTest.java
index aa62db2f1c..95fd04558b 100644
--- a/runtime/service/src/test/java/org/apache/polaris/service/auth/DefaultAuthenticatorTest.java
+++ b/runtime/service/src/test/java/org/apache/polaris/service/auth/DefaultAuthenticatorTest.java
@@ -32,14 +32,20 @@ import java.util.Map;
 import java.util.Set;
 import org.apache.iceberg.exceptions.NotAuthorizedException;
 import org.apache.polaris.core.admin.model.PrincipalWithCredentialsCredentials;
+import org.apache.polaris.core.auth.PolarisAuthorizer;
 import org.apache.polaris.core.auth.PolarisPrincipal;
 import org.apache.polaris.core.context.CallContext;
 import org.apache.polaris.core.entity.PolarisEntityConstants;
 import org.apache.polaris.core.entity.PolarisEntityType;
 import org.apache.polaris.core.entity.PrincipalEntity;
 import org.apache.polaris.core.entity.PrincipalRoleEntity;
+import org.apache.polaris.core.identity.provider.ServiceIdentityProvider;
 import org.apache.polaris.core.persistence.PolarisMetaStoreManager;
+import org.apache.polaris.core.persistence.resolver.ResolutionManifestFactory;
+import org.apache.polaris.core.secrets.UserSecretsManager;
 import org.apache.polaris.service.admin.PolarisAdminService;
+import org.apache.polaris.service.admin.PolarisAdminServiceTestSupport;
+import org.apache.polaris.service.config.ReservedProperties;
 import org.apache.polaris.service.context.catalog.RealmContextHolder;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
@@ -63,20 +69,26 @@ public class DefaultAuthenticatorTest {
   @SuppressWarnings("CdiInjectionPointsInspection")
   CurrentIdentityAssociation identityAssociation;
 
-  @Inject PolarisAdminService adminService;
   @Inject RealmContextHolder realmContextHolder;
   @Inject PolarisMetaStoreManager metaStoreManager;
   @Inject CallContext callContext;
+  @Inject ResolutionManifestFactory resolutionManifestFactory;
+  @Inject UserSecretsManager userSecretsManager;
+  @Inject ServiceIdentityProvider serviceIdentityProvider;
+  @Inject PolarisAuthorizer authorizer;
+  @Inject ReservedProperties reservedProperties;
 
   private PrincipalEntity principalEntity;
   private PrincipalEntity principalEntityNoRoles;
+  private PolarisPrincipal authenticatedRoot;
 
   @BeforeEach
   public void setup(TestInfo testInfo) {
     realmContextHolder.set(() -> testInfo.getTestMethod().orElseThrow().getName());
-    PolarisPrincipal root =
+    authenticatedRoot =
         PolarisPrincipal.of(PolarisEntityConstants.getRootPrincipalName(), Map.of(), Set.of());
-    identityAssociation.setIdentity(QuarkusSecurityIdentity.builder().setPrincipal(root).build());
+    identityAssociation.setIdentity(
+        QuarkusSecurityIdentity.builder().setPrincipal(authenticatedRoot).build());
     principalEntity = createPrincipal(PRINCIPAL_NAME, PRINCIPAL_ROLE1, PRINCIPAL_ROLE2);
     principalEntityNoRoles = createPrincipal(PRINCIPAL_NAME_NO_ROLES);
   }
@@ -306,7 +318,7 @@ public class DefaultAuthenticatorTest {
   private PrincipalEntity createPrincipal(String name, String... roles) {
 
     PrincipalWithCredentialsCredentials credentials =
-        adminService
+        newAdminService()
             .createPrincipal(new PrincipalEntity.Builder().setName(name).build())
             .getCredentials();
 
@@ -326,13 +338,26 @@ public class DefaultAuthenticatorTest {
             .orElseThrow();
 
     for (String role : roles) {
-      adminService.createPrincipalRole(new PrincipalRoleEntity.Builder().setName(role).build());
-      adminService.assignPrincipalRole(name, role);
+      newAdminService()
+          .createPrincipalRole(new PrincipalRoleEntity.Builder().setName(role).build());
+      newAdminService().assignPrincipalRole(name, role);
     }
 
     return principalEntity;
   }
 
+  private PolarisAdminService newAdminService() {
+    return PolarisAdminServiceTestSupport.newAdminService(
+        callContext,
+        resolutionManifestFactory,
+        metaStoreManager,
+        userSecretsManager,
+        serviceIdentityProvider,
+        authenticatedRoot,
+        authorizer,
+        reservedProperties);
+  }
+
   private void assertPrincipal(PolarisPrincipal result, PrincipalEntity entity, String... roles) {
     assertThat(result).isNotNull();
     assertThat(result.getName()).isEqualTo(entity.getName());
diff --git a/runtime/service/src/test/java/org/apache/polaris/service/catalog/common/PolarisSecurableMapperTest.java b/runtime/service/src/test/java/org/apache/polaris/service/catalog/common/PolarisSecurableMapperTest.java
new file mode 100644
index 0000000000..2a1ef675b9
--- /dev/null
+++ b/runtime/service/src/test/java/org/apache/polaris/service/catalog/common/PolarisSecurableMapperTest.java
@@ -0,0 +1,52 @@
+/*
+ * 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.polaris.service.catalog.common;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+
+import org.apache.iceberg.catalog.Namespace;
+import org.apache.iceberg.catalog.TableIdentifier;
+import org.apache.polaris.core.auth.PathSegment;
+import org.apache.polaris.core.entity.PolarisEntityType;
+import org.junit.jupiter.api.Test;
+
+class PolarisSecurableMapperTest {
+
+  @Test
+  void tableLikeRejectsEmptyNamespace() {
+    TableIdentifier identifier = TableIdentifier.of(Namespace.empty(), "table");
+
+    assertThatThrownBy(() -> PolarisSecurableMapper.tableLike("catalog", identifier))
+        .isInstanceOf(IllegalArgumentException.class)
+        .hasMessage("Table-like target cannot have an empty namespace");
+  }
+
+  @Test
+  void tableLikeMapsNamespaceAndTableName() {
+    TableIdentifier identifier = TableIdentifier.of(Namespace.of("ns1", "ns2"), "table");
+
+    assertThat(PolarisSecurableMapper.tableLike("catalog", identifier).getPathSegments())
+        .containsExactly(
+            new PathSegment(PolarisEntityType.CATALOG, "catalog"),
+            new PathSegment(PolarisEntityType.NAMESPACE, "ns1"),
+            new PathSegment(PolarisEntityType.NAMESPACE, "ns2"),
+            new PathSegment(PolarisEntityType.TABLE_LIKE, "table"));
+  }
+}
diff --git a/runtime/service/src/test/java/org/apache/polaris/service/catalog/generic/AbstractPolarisGenericTableCatalogTest.java b/runtime/service/src/test/java/org/apache/polaris/service/catalog/generic/AbstractPolarisGenericTableCatalogTest.java
index dc657387bf..013973825e 100644
--- a/runtime/service/src/test/java/org/apache/polaris/service/catalog/generic/AbstractPolarisGenericTableCatalogTest.java
+++ b/runtime/service/src/test/java/org/apache/polaris/service/catalog/generic/AbstractPolarisGenericTableCatalogTest.java
@@ -61,6 +61,7 @@ import org.apache.polaris.core.storage.aws.AwsCredentialsStorageIntegration;
 import org.apache.polaris.core.storage.aws.AwsStorageConfigurationInfo;
 import org.apache.polaris.core.storage.cache.StorageCredentialCache;
 import org.apache.polaris.service.admin.PolarisAdminService;
+import org.apache.polaris.service.admin.PolarisAdminServiceTestSupport;
 import org.apache.polaris.service.catalog.PolarisPassthroughResolutionView;
 import org.apache.polaris.service.catalog.iceberg.LocalIcebergCatalog;
 import org.apache.polaris.service.catalog.io.FileIOFactory;
@@ -157,7 +158,7 @@ public abstract class AbstractPolarisGenericTableCatalogTest {
     ReservedProperties reservedProperties = ReservedProperties.NONE;
 
     adminService =
-        new PolarisAdminService(
+        PolarisAdminServiceTestSupport.newAdminService(
             polarisContext,
             resolutionManifestFactory,
             metaStoreManager,
diff --git a/runtime/service/src/test/java/org/apache/polaris/service/catalog/generic/PolarisGenericTableCatalogHandlerAuthzTest.java b/runtime/service/src/test/java/org/apache/polaris/service/catalog/generic/PolarisGenericTableCatalogHandlerAuthzTest.java
index bfa75b04cf..bb330a8df3 100644
--- a/runtime/service/src/test/java/org/apache/polaris/service/catalog/generic/PolarisGenericTableCatalogHandlerAuthzTest.java
+++ b/runtime/service/src/test/java/org/apache/polaris/service/catalog/generic/PolarisGenericTableCatalogHandlerAuthzTest.java
@@ -20,12 +20,15 @@ package org.apache.polaris.service.catalog.generic;
 
 import io.quarkus.test.junit.QuarkusTest;
 import io.quarkus.test.junit.TestProfile;
-import jakarta.inject.Inject;
+import jakarta.enterprise.inject.Any;
+import jakarta.enterprise.inject.Instance;
 import java.util.Map;
 import java.util.Set;
 import java.util.stream.Stream;
 import org.apache.iceberg.catalog.TableIdentifier;
 import org.apache.polaris.core.auth.PolarisPrincipal;
+import org.apache.polaris.core.catalog.FederatedCatalogFactory;
+import org.apache.polaris.core.credentials.PolarisCredentialManager;
 import org.apache.polaris.core.entity.PolarisGrantRecord;
 import org.apache.polaris.core.entity.PolarisPrivilege;
 import org.apache.polaris.core.persistence.dao.entity.BaseResult;
@@ -39,8 +42,8 @@ import org.junit.jupiter.api.TestFactory;
 @QuarkusTest
 @TestProfile(Profiles.PolarisAuthzBaseProfile.class)
 public class PolarisGenericTableCatalogHandlerAuthzTest extends PolarisAuthzTestBase {
-
-  @Inject GenericTableCatalogHandlerFactory genericTableCatalogHandlerFactory;
+  @jakarta.inject.Inject PolarisCredentialManager credentialManager;
+  @jakarta.inject.Inject @Any Instance<FederatedCatalogFactory> federatedCatalogFactories;
 
   private GenericTableCatalogHandler newWrapper() {
     return newWrapper(Set.of());
@@ -54,7 +57,16 @@ public class PolarisGenericTableCatalogHandlerAuthzTest extends PolarisAuthzTest
       Set<String> activatedPrincipalRoles, String catalogName) {
     PolarisPrincipal authenticatedPrincipal =
         PolarisPrincipal.of(principalEntity, activatedPrincipalRoles);
-    return genericTableCatalogHandlerFactory.createHandler(catalogName, authenticatedPrincipal);
+    return ImmutableGenericTableCatalogHandler.builder()
+        .catalogName(catalogName)
+        .polarisPrincipal(authenticatedPrincipal)
+        .callContext(callContext)
+        .resolutionManifestFactory(resolutionManifestFactory)
+        .metaStoreManager(metaStoreManager)
+        .authorizer(polarisAuthorizer)
+        .credentialManager(credentialManager)
+        .federatedCatalogFactories(federatedCatalogFactories)
+        .build();
   }
 
   @TestFactory
@@ -76,11 +88,13 @@ public class PolarisGenericTableCatalogHandlerAuthzTest extends PolarisAuthzTest
   @TestFactory
   Stream<DynamicNode> testCreateGenericTablePrivileges() {
     assertSuccess(
-        adminService.grantPrivilegeOnCatalogToRole(
-            CATALOG_NAME, CATALOG_ROLE2, PolarisPrivilege.TABLE_DROP));
+        newRootAdminService()
+            .grantPrivilegeOnCatalogToRole(
+                CATALOG_NAME, CATALOG_ROLE2, PolarisPrivilege.TABLE_DROP));
     assertSuccess(
-        adminService.grantPrivilegeOnCatalogToRole(
-            CATALOG_NAME, CATALOG_ROLE2, PolarisPrivilege.TABLE_WRITE_DATA));
+        newRootAdminService()
+            .grantPrivilegeOnCatalogToRole(
+                CATALOG_NAME, CATALOG_ROLE2, PolarisPrivilege.TABLE_WRITE_DATA));
 
     final TableIdentifier newtable = TableIdentifier.of(NS2, "newtable");
 
@@ -115,8 +129,9 @@ public class PolarisGenericTableCatalogHandlerAuthzTest extends PolarisAuthzTest
   @TestFactory
   Stream<DynamicNode> testDropGenericTablePrivileges() {
     assertSuccess(
-        adminService.grantPrivilegeOnCatalogToRole(
-            CATALOG_NAME, CATALOG_ROLE2, PolarisPrivilege.TABLE_CREATE));
+        newRootAdminService()
+            .grantPrivilegeOnCatalogToRole(
+                CATALOG_NAME, CATALOG_ROLE2, PolarisPrivilege.TABLE_CREATE));
 
     return authzTestsBuilder("dropGenericTable")
         .action(() -> newWrapper(Set.of(PRINCIPAL_ROLE1)).dropGenericTable(TABLE_NS1_1_GENERIC))
@@ -137,13 +152,13 @@ public class PolarisGenericTableCatalogHandlerAuthzTest extends PolarisAuthzTest
     return authzTestsBuilder(operationName)
         .grantAction(
             priv ->
-                adminService.grantPrivilegeOnTableToRole(
-                    CATALOG_NAME, CATALOG_ROLE1, tableId, priv))
+                newRootAdminService()
+                    .grantPrivilegeOnTableToRole(CATALOG_NAME, CATALOG_ROLE1, tableId, priv))
         .revokeAction(
             priv -> {
               PrivilegeResult res =
-                  adminService.revokePrivilegeOnTableFromRole(
-                      CATALOG_NAME, CATALOG_ROLE1, tableId, priv);
+                  newRootAdminService()
+                      .revokePrivilegeOnTableFromRole(CATALOG_NAME, CATALOG_ROLE1, tableId, priv);
               // After table drop + recreate, grants on the old entity no longer exist on the
               // new entity. Only treat GRANT_NOT_FOUND or ENTITY_NOT_FOUND as acceptable —
               // any other failure should propagate to avoid masking real errors.
@@ -166,8 +181,9 @@ public class PolarisGenericTableCatalogHandlerAuthzTest extends PolarisAuthzTest
   @TestFactory
   Stream<DynamicNode> testDropGenericTableWithTableLevelPrivileges() {
     assertSuccess(
-        adminService.grantPrivilegeOnCatalogToRole(
-            CATALOG_NAME, CATALOG_ROLE2, PolarisPrivilege.TABLE_CREATE));
+        newRootAdminService()
+            .grantPrivilegeOnCatalogToRole(
+                CATALOG_NAME, CATALOG_ROLE2, PolarisPrivilege.TABLE_CREATE));
 
     return tableLevelAuthzTestsBuilder("dropGenericTableWithTableLevelGrant", TABLE_NS1_1_GENERIC)
         .action(() -> newWrapper(Set.of(PRINCIPAL_ROLE1)).dropGenericTable(TABLE_NS1_1_GENERIC))
diff --git a/runtime/service/src/test/java/org/apache/polaris/service/catalog/iceberg/AbstractIcebergCatalogHandlerAuthzTest.java b/runtime/service/src/test/java/org/apache/polaris/service/catalog/iceberg/AbstractIcebergCatalogHandlerAuthzTest.java
index d386609535..9de758f0f3 100644
--- a/runtime/service/src/test/java/org/apache/polaris/service/catalog/iceberg/AbstractIcebergCatalogHandlerAuthzTest.java
+++ b/runtime/service/src/test/java/org/apache/polaris/service/catalog/iceberg/AbstractIcebergCatalogHandlerAuthzTest.java
@@ -29,6 +29,7 @@ import java.util.Map;
 import java.util.Optional;
 import java.util.Set;
 import java.util.UUID;
+import java.util.function.Supplier;
 import java.util.stream.Stream;
 import org.apache.iceberg.CatalogProperties;
 import org.apache.iceberg.CatalogUtil;
@@ -80,6 +81,7 @@ import org.apache.polaris.core.entity.PrincipalEntity;
 import org.apache.polaris.core.persistence.dao.entity.CreatePrincipalResult;
 import org.apache.polaris.core.persistence.resolver.PolarisResolutionManifest;
 import org.apache.polaris.service.admin.PolarisAuthzTestBase;
+import org.apache.polaris.service.admin.PolarisAuthzTestsFactory;
 import org.apache.polaris.service.catalog.AccessDelegationMode;
 import org.apache.polaris.service.context.catalog.PolarisLocalCatalogFactory;
 import org.apache.polaris.service.http.IfNoneMatch;
@@ -108,6 +110,11 @@ import org.mockito.Mockito;
 @SuppressWarnings("resource")
 public abstract class AbstractIcebergCatalogHandlerAuthzTest extends PolarisAuthzTestBase {
 
+  @Override
+  protected PolarisAuthzTestsFactory.Builder authzTestsBuilder(String operationName) {
+    return super.authzTestsBuilder(operationName).useFreshRequestContext(true);
+  }
+
   @Inject LocalCatalogFactory localCatalogFactory;
   @Inject IcebergCatalogHandlerFactory icebergCatalogHandlerFactory;
 
@@ -125,13 +132,12 @@ public abstract class AbstractIcebergCatalogHandlerAuthzTest extends PolarisAuth
         PolarisPrincipal.of(principalEntity, activatedPrincipalRoles);
     IcebergCatalogHandler handler =
         icebergCatalogHandlerFactory.createHandler(catalogName, authenticatedPrincipal);
-    if (factory == localCatalogFactory) {
-      return handler;
+    ImmutableIcebergCatalogHandler.Builder builder =
+        ImmutableIcebergCatalogHandler.builder().from(handler);
+    if (factory != localCatalogFactory) {
+      builder.localCatalogFactory(factory);
     }
-    return ImmutableIcebergCatalogHandler.builder()
-        .from(handler)
-        .localCatalogFactory(factory)
-        .build();
+    return builder.build();
   }
 
   @TestFactory
@@ -159,14 +165,19 @@ public abstract class AbstractIcebergCatalogHandlerAuthzTest extends PolarisAuth
                 .setCreateTimestamp(Instant.now().toEpochMilli())
                 .setCredentialRotationRequiredState()
                 .build());
-    adminService.assignPrincipalRole(principalName, PRINCIPAL_ROLE1);
-    adminService.assignPrincipalRole(principalName, PRINCIPAL_ROLE2);
+    newRootAdminService().assignPrincipalRole(principalName, PRINCIPAL_ROLE1);
+    newRootAdminService().assignPrincipalRole(principalName, PRINCIPAL_ROLE2);
 
     PolarisPrincipal authenticatedPrincipal =
         PolarisPrincipal.of(newPrincipal.getPrincipal(), Set.of(PRINCIPAL_ROLE1, PRINCIPAL_ROLE2));
 
-    IcebergCatalogHandler handler =
-        icebergCatalogHandlerFactory.createHandler(CATALOG_NAME, authenticatedPrincipal);
+    Supplier<IcebergCatalogHandler> handler =
+        () ->
+            ImmutableIcebergCatalogHandler.builder()
+                .from(
+                    icebergCatalogHandlerFactory.createHandler(
+                        CATALOG_NAME, authenticatedPrincipal))
+                .build();
 
     // a variety of actions are all disallowed because the principal's credentials must be rotated
 
@@ -174,20 +185,22 @@ public abstract class AbstractIcebergCatalogHandlerAuthzTest extends PolarisAuth
     Stream<DynamicNode> beforeRotationTests =
         Stream.of(
                 authzTestsBuilder("listNamespaces (before rotation)")
-                    .action(() -> handler.listNamespaces(Namespace.of()))
+                    .action(() -> handler.get().listNamespaces(Namespace.of()))
                     .principalName(principalName)
                     .shouldFailWithAnyPrivilege()
                     .createTests(),
                 authzTestsBuilder("createNamespace (before rotation)")
                     .action(
                         () ->
-                            handler.createNamespace(
-                                CreateNamespaceRequest.builder().withNamespace(ns3).build()))
+                            handler
+                                .get()
+                                .createNamespace(
+                                    CreateNamespaceRequest.builder().withNamespace(ns3).build()))
                     .principalName(principalName)
                     .shouldFailWithAnyPrivilege()
                     .createTests(),
                 authzTestsBuilder("listTables (before rotation)")
-                    .action(() -> handler.listTables(NS1))
+                    .action(() -> handler.get().listTables(NS1))
                     .principalName(principalName)
                     .shouldFailWithAnyPrivilege()
                     .createTests())
@@ -204,22 +217,26 @@ public abstract class AbstractIcebergCatalogHandlerAuthzTest extends PolarisAuth
     PolarisPrincipal authenticatedPrincipal1 =
         PolarisPrincipal.of(refreshPrincipal, Set.of(PRINCIPAL_ROLE1, PRINCIPAL_ROLE2));
 
-    IcebergCatalogHandler refreshedWrapper =
-        ImmutableIcebergCatalogHandler.builder()
-            .from(handler)
-            .polarisPrincipal(authenticatedPrincipal1)
-            .build();
+    Supplier<IcebergCatalogHandler> refreshedWrapper =
+        () ->
+            ImmutableIcebergCatalogHandler.builder()
+                .from(
+                    icebergCatalogHandlerFactory.createHandler(
+                        CATALOG_NAME, authenticatedPrincipal1))
+                .polarisPrincipal(authenticatedPrincipal1)
+                .build();
 
     // Grant NAMESPACE_DROP to CATALOG_ROLE2 so cleanup can work
     assertSuccess(
-        adminService.grantPrivilegeOnCatalogToRole(
-            CATALOG_NAME, CATALOG_ROLE2, PolarisPrivilege.NAMESPACE_DROP));
+        newRootAdminService()
+            .grantPrivilegeOnCatalogToRole(
+                CATALOG_NAME, CATALOG_ROLE2, PolarisPrivilege.NAMESPACE_DROP));
 
     // Tests after credential rotation - actions should succeed with proper privileges
     Stream<DynamicNode> afterRotationTests =
         Stream.of(
                 authzTestsBuilder("listNamespaces (after rotation)")
-                    .action(() -> refreshedWrapper.listNamespaces(Namespace.of()))
+                    .action(() -> refreshedWrapper.get().listNamespaces(Namespace.of()))
                     .principalName(principalName)
                     .shouldPassWith(PolarisPrivilege.NAMESPACE_LIST)
                     .shouldPassWith(PolarisPrivilege.NAMESPACE_CREATE)
@@ -232,8 +249,10 @@ public abstract class AbstractIcebergCatalogHandlerAuthzTest extends PolarisAuth
                 authzTestsBuilder("createNamespace (after rotation)")
                     .action(
                         () ->
-                            refreshedWrapper.createNamespace(
-                                CreateNamespaceRequest.builder().withNamespace(ns3).build()))
+                            refreshedWrapper
+                                .get()
+                                .createNamespace(
+                                    CreateNamespaceRequest.builder().withNamespace(ns3).build()))
                     .cleanupAction(() -> newHandler(Set.of(PRINCIPAL_ROLE2)).dropNamespace(ns3))
                     .principalName(principalName)
                     .shouldPassWith(PolarisPrivilege.NAMESPACE_CREATE)
@@ -242,7 +261,7 @@ public abstract class AbstractIcebergCatalogHandlerAuthzTest extends PolarisAuth
                     .shouldPassWith(PolarisPrivilege.CATALOG_MANAGE_METADATA)
                     .createTests(),
                 authzTestsBuilder("listTables (after rotation)")
-                    .action(() -> refreshedWrapper.listTables(NS1))
+                    .action(() -> refreshedWrapper.get().listTables(NS1))
                     .principalName(principalName)
                     .shouldPassWith(PolarisPrivilege.TABLE_LIST)
                     .shouldPassWith(PolarisPrivilege.TABLE_CREATE)
@@ -263,8 +282,9 @@ public abstract class AbstractIcebergCatalogHandlerAuthzTest extends PolarisAuth
   public void testListNamespacesCatalogLevelWithPrincipalRoleActivation() {
     // Grant catalog-level privilege to CATALOG_ROLE1
     assertSuccess(
-        adminService.grantPrivilegeOnCatalogToRole(
-            CATALOG_NAME, CATALOG_ROLE1, PolarisPrivilege.NAMESPACE_LIST));
+        newRootAdminService()
+            .grantPrivilegeOnCatalogToRole(
+                CATALOG_NAME, CATALOG_ROLE1, PolarisPrivilege.NAMESPACE_LIST));
     Assertions.assertThat(newHandler().listNamespaces(Namespace.of()).namespaces())
         .containsAll(List.of(NS1, NS2));
 
@@ -281,8 +301,9 @@ public abstract class AbstractIcebergCatalogHandlerAuthzTest extends PolarisAuth
 
     // If we revoke, then it should fail again even with all principal roles activated.
     assertSuccess(
-        adminService.revokePrivilegeOnCatalogFromRole(
-            CATALOG_NAME, CATALOG_ROLE1, PolarisPrivilege.NAMESPACE_LIST));
+        newRootAdminService()
+            .revokePrivilegeOnCatalogFromRole(
+                CATALOG_NAME, CATALOG_ROLE1, PolarisPrivilege.NAMESPACE_LIST));
     Assertions.assertThatThrownBy(() -> newHandler().listNamespaces(Namespace.of()))
         .isInstanceOf(ForbiddenException.class);
   }
@@ -291,8 +312,9 @@ public abstract class AbstractIcebergCatalogHandlerAuthzTest extends PolarisAuth
   public void testListNamespacesChildOnly() {
     // Grant only NS1-level privilege to CATALOG_ROLE1
     assertSuccess(
-        adminService.grantPrivilegeOnNamespaceToRole(
-            CATALOG_NAME, CATALOG_ROLE1, NS1, PolarisPrivilege.NAMESPACE_LIST));
+        newRootAdminService()
+            .grantPrivilegeOnNamespaceToRole(
+                CATALOG_NAME, CATALOG_ROLE1, NS1, PolarisPrivilege.NAMESPACE_LIST));
 
     // Listing directly on NS1 succeeds
     Assertions.assertThat(newHandler().listNamespaces(NS1).namespaces())
@@ -314,8 +336,9 @@ public abstract class AbstractIcebergCatalogHandlerAuthzTest extends PolarisAuth
   @TestFactory
   Stream<DynamicNode> testCreateNamespacePrivileges() {
     assertSuccess(
-        adminService.grantPrivilegeOnCatalogToRole(
-            CATALOG_NAME, CATALOG_ROLE2, PolarisPrivilege.NAMESPACE_DROP));
+        newRootAdminService()
+            .grantPrivilegeOnCatalogToRole(
+                CATALOG_NAME, CATALOG_ROLE2, PolarisPrivilege.NAMESPACE_DROP));
 
     // Use PRINCIPAL_ROLE1 for privilege-testing, PRINCIPAL_ROLE2 for cleanup.
     return authzTestsBuilder("createNamespace")
@@ -378,8 +401,9 @@ public abstract class AbstractIcebergCatalogHandlerAuthzTest extends PolarisAuth
   @TestFactory
   Stream<DynamicNode> testDropNamespacePrivileges() {
     assertSuccess(
-        adminService.grantPrivilegeOnCatalogToRole(
-            CATALOG_NAME, CATALOG_ROLE2, PolarisPrivilege.NAMESPACE_CREATE));
+        newRootAdminService()
+            .grantPrivilegeOnCatalogToRole(
+                CATALOG_NAME, CATALOG_ROLE2, PolarisPrivilege.NAMESPACE_CREATE));
 
     return authzTestsBuilder("dropNamespace")
         .action(() -> newHandler(Set.of(PRINCIPAL_ROLE1)).dropNamespace(NS1AA))
@@ -433,11 +457,13 @@ public abstract class AbstractIcebergCatalogHandlerAuthzTest extends PolarisAuth
   @TestFactory
   Stream<DynamicNode> testCreateTableDirectPrivileges() {
     assertSuccess(
-        adminService.grantPrivilegeOnCatalogToRole(
-            CATALOG_NAME, CATALOG_ROLE2, PolarisPrivilege.TABLE_DROP));
+        newRootAdminService()
+            .grantPrivilegeOnCatalogToRole(
+                CATALOG_NAME, CATALOG_ROLE2, PolarisPrivilege.TABLE_DROP));
     assertSuccess(
-        adminService.grantPrivilegeOnCatalogToRole(
-            CATALOG_NAME, CATALOG_ROLE2, PolarisPrivilege.TABLE_WRITE_DATA));
+        newRootAdminService()
+            .grantPrivilegeOnCatalogToRole(
+                CATALOG_NAME, CATALOG_ROLE2, PolarisPrivilege.TABLE_WRITE_DATA));
 
     final TableIdentifier newtable = TableIdentifier.of(NS2, "newtable");
     final CreateTableRequest createRequest =
@@ -457,11 +483,13 @@ public abstract class AbstractIcebergCatalogHandlerAuthzTest extends PolarisAuth
   @TestFactory
   Stream<DynamicNode> testCreateTableDirectWithWriteDelegationPrivileges() {
     assertSuccess(
-        adminService.grantPrivilegeOnCatalogToRole(
-            CATALOG_NAME, CATALOG_ROLE2, PolarisPrivilege.TABLE_DROP));
+        newRootAdminService()
+            .grantPrivilegeOnCatalogToRole(
+                CATALOG_NAME, CATALOG_ROLE2, PolarisPrivilege.TABLE_DROP));
     assertSuccess(
-        adminService.grantPrivilegeOnCatalogToRole(
-            CATALOG_NAME, CATALOG_ROLE2, PolarisPrivilege.TABLE_WRITE_DATA));
+        newRootAdminService()
+            .grantPrivilegeOnCatalogToRole(
+                CATALOG_NAME, CATALOG_ROLE2, PolarisPrivilege.TABLE_WRITE_DATA));
 
     final TableIdentifier newtable = TableIdentifier.of(NS2, "newtable");
     final CreateTableRequest createDirectWithWriteDelegationRequest =
@@ -487,8 +515,9 @@ public abstract class AbstractIcebergCatalogHandlerAuthzTest extends PolarisAuth
   @TestFactory
   Stream<DynamicNode> testCreateTableStagedPrivileges() {
     assertSuccess(
-        adminService.grantPrivilegeOnCatalogToRole(
-            CATALOG_NAME, CATALOG_ROLE2, PolarisPrivilege.TABLE_DROP));
+        newRootAdminService()
+            .grantPrivilegeOnCatalogToRole(
+                CATALOG_NAME, CATALOG_ROLE2, PolarisPrivilege.TABLE_DROP));
 
     final CreateTableRequest createStagedRequest =
         CreateTableRequest.builder()
@@ -511,8 +540,9 @@ public abstract class AbstractIcebergCatalogHandlerAuthzTest extends PolarisAuth
   @TestFactory
   Stream<DynamicNode> testCreateTableStagedWithWriteDelegationPrivileges() {
     assertSuccess(
-        adminService.grantPrivilegeOnCatalogToRole(
-            CATALOG_NAME, CATALOG_ROLE2, PolarisPrivilege.TABLE_DROP));
+        newRootAdminService()
+            .grantPrivilegeOnCatalogToRole(
+                CATALOG_NAME, CATALOG_ROLE2, PolarisPrivilege.TABLE_DROP));
 
     final CreateTableRequest createStagedWithWriteDelegationRequest =
         CreateTableRequest.builder()
@@ -535,11 +565,13 @@ public abstract class AbstractIcebergCatalogHandlerAuthzTest extends PolarisAuth
   @TestFactory
   Stream<DynamicNode> testRegisterTablePrivileges() {
     assertSuccess(
-        adminService.grantPrivilegeOnCatalogToRole(
-            CATALOG_NAME, CATALOG_ROLE2, PolarisPrivilege.TABLE_DROP));
+        newRootAdminService()
+            .grantPrivilegeOnCatalogToRole(
+                CATALOG_NAME, CATALOG_ROLE2, PolarisPrivilege.TABLE_DROP));
     assertSuccess(
-        adminService.grantPrivilegeOnCatalogToRole(
-            CATALOG_NAME, CATALOG_ROLE2, PolarisPrivilege.TABLE_READ_PROPERTIES));
+        newRootAdminService()
+            .grantPrivilegeOnCatalogToRole(
+                CATALOG_NAME, CATALOG_ROLE2, PolarisPrivilege.TABLE_READ_PROPERTIES));
 
     // To get a handy metadata file we can use one from another table.
     // to avoid overlapping directories, drop the original table and recreate it via registerTable
@@ -573,14 +605,17 @@ public abstract class AbstractIcebergCatalogHandlerAuthzTest extends PolarisAuth
   @TestFactory
   Stream<DynamicNode> testRegisterTableInsufficientPermissions() {
     assertSuccess(
-        adminService.grantPrivilegeOnCatalogToRole(
-            CATALOG_NAME, CATALOG_ROLE2, PolarisPrivilege.TABLE_READ_PROPERTIES));
+        newRootAdminService()
+            .grantPrivilegeOnCatalogToRole(
+                CATALOG_NAME, CATALOG_ROLE2, PolarisPrivilege.TABLE_READ_PROPERTIES));
     assertSuccess(
-        adminService.grantPrivilegeOnCatalogToRole(
-            CATALOG_NAME, CATALOG_ROLE2, PolarisPrivilege.TABLE_CREATE));
+        newRootAdminService()
+            .grantPrivilegeOnCatalogToRole(
+                CATALOG_NAME, CATALOG_ROLE2, PolarisPrivilege.TABLE_CREATE));
     assertSuccess(
-        adminService.grantPrivilegeOnCatalogToRole(
-            CATALOG_NAME, CATALOG_ROLE2, PolarisPrivilege.TABLE_DROP));
+        newRootAdminService()
+            .grantPrivilegeOnCatalogToRole(
+                CATALOG_NAME, CATALOG_ROLE2, PolarisPrivilege.TABLE_DROP));
 
     final TableIdentifier sourceTable =
         TableIdentifier.of(NS2, "register_table_insufficient_source");
@@ -659,8 +694,9 @@ public abstract class AbstractIcebergCatalogHandlerAuthzTest extends PolarisAuth
     // level to the test role, is sufficient to perform a registerTable with overwrite=true.
 
     assertSuccess(
-        adminService.grantPrivilegeOnCatalogToRole(
-            CATALOG_NAME, CATALOG_ROLE2, PolarisPrivilege.TABLE_READ_PROPERTIES));
+        newRootAdminService()
+            .grantPrivilegeOnCatalogToRole(
+                CATALOG_NAME, CATALOG_ROLE2, PolarisPrivilege.TABLE_READ_PROPERTIES));
     final String metadataLocation =
         newHandler(Set.of(PRINCIPAL_ROLE2)).loadTable(TABLE_NS1_1, "all").metadataLocation();
 
@@ -699,11 +735,13 @@ public abstract class AbstractIcebergCatalogHandlerAuthzTest extends PolarisAuth
     // When overwrite is specified and the table does not exist, the authorization should
     // fall back to a non-overwrite authorization level instead.
     assertSuccess(
-        adminService.grantPrivilegeOnCatalogToRole(
-            CATALOG_NAME, CATALOG_ROLE2, PolarisPrivilege.TABLE_DROP));
+        newRootAdminService()
+            .grantPrivilegeOnCatalogToRole(
+                CATALOG_NAME, CATALOG_ROLE2, PolarisPrivilege.TABLE_DROP));
     assertSuccess(
-        adminService.grantPrivilegeOnCatalogToRole(
-            CATALOG_NAME, CATALOG_ROLE2, PolarisPrivilege.TABLE_READ_PROPERTIES));
+        newRootAdminService()
+            .grantPrivilegeOnCatalogToRole(
+                CATALOG_NAME, CATALOG_ROLE2, PolarisPrivilege.TABLE_READ_PROPERTIES));
 
     String metadataLocation = newHandler().loadTable(TABLE_NS1_1, "all").metadataLocation();
     newHandler(Set.of(PRINCIPAL_ROLE2)).dropTableWithoutPurge(TABLE_NS1_1);
@@ -753,8 +791,9 @@ public abstract class AbstractIcebergCatalogHandlerAuthzTest extends PolarisAuth
     // REGISTER_TABLE_OVERWRITE authorizes against the table entity, so TABLE_FULL_METADATA
     // granted directly on the target table (not at catalog/namespace level) must suffice.
     assertSuccess(
-        adminService.grantPrivilegeOnCatalogToRole(
-            CATALOG_NAME, CATALOG_ROLE2, PolarisPrivilege.TABLE_READ_PROPERTIES));
+        newRootAdminService()
+            .grantPrivilegeOnCatalogToRole(
+                CATALOG_NAME, CATALOG_ROLE2, PolarisPrivilege.TABLE_READ_PROPERTIES));
     String metadataLocation =
         newHandler(Set.of(PRINCIPAL_ROLE2)).loadTable(TABLE_NS1_1, "all").metadataLocation();
 
@@ -766,12 +805,14 @@ public abstract class AbstractIcebergCatalogHandlerAuthzTest extends PolarisAuth
     return authzTestsBuilder("registerTableOverwriteTableLevel")
         .grantAction(
             privilege ->
-                adminService.grantPrivilegeOnTableToRole(
-                    CATALOG_NAME, CATALOG_ROLE1, TABLE_NS1_1, privilege))
+                newRootAdminService()
+                    .grantPrivilegeOnTableToRole(
+                        CATALOG_NAME, CATALOG_ROLE1, TABLE_NS1_1, privilege))
         .revokeAction(
             privilege ->
-                adminService.revokePrivilegeOnTableFromRole(
-                    CATALOG_NAME, CATALOG_ROLE1, TABLE_NS1_1, privilege))
+                newRootAdminService()
+                    .revokePrivilegeOnTableFromRole(
+                        CATALOG_NAME, CATALOG_ROLE1, TABLE_NS1_1, privilege))
         .action(
             () ->
                 newHandler(Set.of(PRINCIPAL_ROLE1))
@@ -798,11 +839,13 @@ public abstract class AbstractIcebergCatalogHandlerAuthzTest extends PolarisAuth
   @TestFactory
   Stream<DynamicNode> testRegisterTableWithWriteDelegation() {
     assertSuccess(
-        adminService.grantPrivilegeOnCatalogToRole(
-            CATALOG_NAME, CATALOG_ROLE2, PolarisPrivilege.TABLE_DROP));
+        newRootAdminService()
+            .grantPrivilegeOnCatalogToRole(
+                CATALOG_NAME, CATALOG_ROLE2, PolarisPrivilege.TABLE_DROP));
     assertSuccess(
-        adminService.grantPrivilegeOnCatalogToRole(
-            CATALOG_NAME, CATALOG_ROLE2, PolarisPrivilege.TABLE_READ_PROPERTIES));
+        newRootAdminService()
+            .grantPrivilegeOnCatalogToRole(
+                CATALOG_NAME, CATALOG_ROLE2, PolarisPrivilege.TABLE_READ_PROPERTIES));
 
     String metadataLocation = newHandler().loadTable(TABLE_NS1_1, "all").metadataLocation();
     newHandler(Set.of(PRINCIPAL_ROLE2)).dropTableWithoutPurge(TABLE_NS1_1);
@@ -836,11 +879,13 @@ public abstract class AbstractIcebergCatalogHandlerAuthzTest extends PolarisAuth
   @TestFactory
   Stream<DynamicNode> testRegisterTableOverwriteWithWriteDelegation() {
     assertSuccess(
-        adminService.grantPrivilegeOnCatalogToRole(
-            CATALOG_NAME, CATALOG_ROLE2, PolarisPrivilege.TABLE_DROP));
+        newRootAdminService()
+            .grantPrivilegeOnCatalogToRole(
+                CATALOG_NAME, CATALOG_ROLE2, PolarisPrivilege.TABLE_DROP));
     assertSuccess(
-        adminService.grantPrivilegeOnCatalogToRole(
-            CATALOG_NAME, CATALOG_ROLE2, PolarisPrivilege.TABLE_READ_PROPERTIES));
+        newRootAdminService()
+            .grantPrivilegeOnCatalogToRole(
+                CATALOG_NAME, CATALOG_ROLE2, PolarisPrivilege.TABLE_READ_PROPERTIES));
 
     String metadataLocation = newHandler().loadTable(TABLE_NS1_1, "all").metadataLocation();
 
@@ -874,11 +919,13 @@ public abstract class AbstractIcebergCatalogHandlerAuthzTest extends PolarisAuth
   @TestFactory
   Stream<DynamicNode> testRegisterTableOverwriteWithWriteDelegationNonExistentTable() {
     assertSuccess(
-        adminService.grantPrivilegeOnCatalogToRole(
-            CATALOG_NAME, CATALOG_ROLE2, PolarisPrivilege.TABLE_DROP));
+        newRootAdminService()
+            .grantPrivilegeOnCatalogToRole(
+                CATALOG_NAME, CATALOG_ROLE2, PolarisPrivilege.TABLE_DROP));
     assertSuccess(
-        adminService.grantPrivilegeOnCatalogToRole(
-            CATALOG_NAME, CATALOG_ROLE2, PolarisPrivilege.TABLE_READ_PROPERTIES));
+        newRootAdminService()
+            .grantPrivilegeOnCatalogToRole(
+                CATALOG_NAME, CATALOG_ROLE2, PolarisPrivilege.TABLE_READ_PROPERTIES));
 
     String metadataLocation = newHandler().loadTable(TABLE_NS1_1, "all").metadataLocation();
     newHandler(Set.of(PRINCIPAL_ROLE2)).dropTableWithoutPurge(TABLE_NS1_1);
@@ -914,11 +961,13 @@ public abstract class AbstractIcebergCatalogHandlerAuthzTest extends PolarisAuth
   @TestFactory
   Stream<DynamicNode> testRegisterTableOverwriteWithWriteDelegationTableLevel() {
     assertSuccess(
-        adminService.grantPrivilegeOnCatalogToRole(
-            CATALOG_NAME, CATALOG_ROLE2, PolarisPrivilege.TABLE_DROP));
+        newRootAdminService()
+            .grantPrivilegeOnCatalogToRole(
+                CATALOG_NAME, CATALOG_ROLE2, PolarisPrivilege.TABLE_DROP));
     assertSuccess(
-        adminService.grantPrivilegeOnCatalogToRole(
-            CATALOG_NAME, CATALOG_ROLE2, PolarisPrivilege.TABLE_READ_PROPERTIES));
+        newRootAdminService()
+            .grantPrivilegeOnCatalogToRole(
+                CATALOG_NAME, CATALOG_ROLE2, PolarisPrivilege.TABLE_READ_PROPERTIES));
 
     String metadataLocation = newHandler().loadTable(TABLE_NS1_1, "all").metadataLocation();
 
@@ -932,12 +981,14 @@ public abstract class AbstractIcebergCatalogHandlerAuthzTest extends PolarisAuth
     return authzTestsBuilder("registerTableOverwriteWithWriteDelegationTableLevel")
         .grantAction(
             privilege ->
-                adminService.grantPrivilegeOnTableToRole(
-                    CATALOG_NAME, CATALOG_ROLE1, TABLE_NS1_1, privilege))
+                newRootAdminService()
+                    .grantPrivilegeOnTableToRole(
+                        CATALOG_NAME, CATALOG_ROLE1, TABLE_NS1_1, privilege))
         .revokeAction(
             privilege ->
-                adminService.revokePrivilegeOnTableFromRole(
-                    CATALOG_NAME, CATALOG_ROLE1, TABLE_NS1_1, privilege))
+                newRootAdminService()
+                    .revokePrivilegeOnTableFromRole(
+                        CATALOG_NAME, CATALOG_ROLE1, TABLE_NS1_1, privilege))
         .action(
             () ->
                 newHandler(Set.of(PRINCIPAL_ROLE1))
@@ -1177,8 +1228,9 @@ public abstract class AbstractIcebergCatalogHandlerAuthzTest extends PolarisAuth
   @TestFactory
   Stream<DynamicNode> testDropTableWithoutPurgePrivileges() {
     assertSuccess(
-        adminService.grantPrivilegeOnCatalogToRole(
-            CATALOG_NAME, CATALOG_ROLE2, PolarisPrivilege.TABLE_CREATE));
+        newRootAdminService()
+            .grantPrivilegeOnCatalogToRole(
+                CATALOG_NAME, CATALOG_ROLE2, PolarisPrivilege.TABLE_CREATE));
 
     final CreateTableRequest createRequest =
         CreateTableRequest.builder().withName(TABLE_NS1_1.name()).withSchema(SCHEMA).build();
@@ -1200,8 +1252,9 @@ public abstract class AbstractIcebergCatalogHandlerAuthzTest extends PolarisAuth
   @TestFactory
   Stream<DynamicNode> testDropTableWithPurgePrivileges() {
     assertSuccess(
-        adminService.grantPrivilegeOnCatalogToRole(
-            CATALOG_NAME, CATALOG_ROLE2, PolarisPrivilege.TABLE_CREATE));
+        newRootAdminService()
+            .grantPrivilegeOnCatalogToRole(
+                CATALOG_NAME, CATALOG_ROLE2, PolarisPrivilege.TABLE_CREATE));
 
     final CreateTableRequest createRequest =
         CreateTableRequest.builder().withName(TABLE_NS1_1.name()).withSchema(SCHEMA).build();
@@ -1265,11 +1318,13 @@ public abstract class AbstractIcebergCatalogHandlerAuthzTest extends PolarisAuth
 
     // Minimum privileges should succeed -- drop on src, create on dst parent.
     assertSuccess(
-        adminService.grantPrivilegeOnTableToRole(
-            CATALOG_NAME, CATALOG_ROLE1, srcTable, PolarisPrivilege.TABLE_DROP));
+        newRootAdminService()
+            .grantPrivilegeOnTableToRole(
+                CATALOG_NAME, CATALOG_ROLE1, srcTable, PolarisPrivilege.TABLE_DROP));
     assertSuccess(
-        adminService.grantPrivilegeOnNamespaceToRole(
-            CATALOG_NAME, CATALOG_ROLE1, dstTable.namespace(), PolarisPrivilege.TABLE_CREATE));
+        newRootAdminService()
+            .grantPrivilegeOnNamespaceToRole(
+                CATALOG_NAME, CATALOG_ROLE1, dstTable.namespace(), PolarisPrivilege.TABLE_CREATE));
 
     // Initial rename should succeed
     newHandler().renameTable(rename1);
@@ -1280,8 +1335,9 @@ public abstract class AbstractIcebergCatalogHandlerAuthzTest extends PolarisAuth
 
     // Now grant TABLE_DROP on dst
     assertSuccess(
-        adminService.grantPrivilegeOnTableToRole(
-            CATALOG_NAME, CATALOG_ROLE1, dstTable, PolarisPrivilege.TABLE_DROP));
+        newRootAdminService()
+            .grantPrivilegeOnTableToRole(
+                CATALOG_NAME, CATALOG_ROLE1, dstTable, PolarisPrivilege.TABLE_DROP));
 
     // Still not enough without TABLE_CREATE at source
     Assertions.assertThatThrownBy(() -> newHandler().renameTable(rename2))
@@ -1289,8 +1345,9 @@ public abstract class AbstractIcebergCatalogHandlerAuthzTest extends PolarisAuth
 
     // Even grant CATALOG_MANAGE_CONTENT under all of NS1
     assertSuccess(
-        adminService.grantPrivilegeOnNamespaceToRole(
-            CATALOG_NAME, CATALOG_ROLE1, NS1, PolarisPrivilege.CATALOG_MANAGE_CONTENT));
+        newRootAdminService()
+            .grantPrivilegeOnNamespaceToRole(
+                CATALOG_NAME, CATALOG_ROLE1, NS1, PolarisPrivilege.CATALOG_MANAGE_CONTENT));
 
     // Still not enough to rename back to src since src was NS2.
     Assertions.assertThatThrownBy(() -> newHandler().renameTable(rename2))
@@ -1298,8 +1355,9 @@ public abstract class AbstractIcebergCatalogHandlerAuthzTest extends PolarisAuth
 
     // Finally, grant TABLE_CREATE on NS2 and it should succeed to rename back to src.
     assertSuccess(
-        adminService.grantPrivilegeOnNamespaceToRole(
-            CATALOG_NAME, CATALOG_ROLE1, NS2, PolarisPrivilege.TABLE_CREATE));
+        newRootAdminService()
+            .grantPrivilegeOnNamespaceToRole(
+                CATALOG_NAME, CATALOG_ROLE1, NS2, PolarisPrivilege.TABLE_CREATE));
     newHandler().renameTable(rename2);
   }
 
@@ -1335,36 +1393,41 @@ public abstract class AbstractIcebergCatalogHandlerAuthzTest extends PolarisAuth
 
     // Grant TABLE_CREATE for all of NS1
     assertSuccess(
-        adminService.grantPrivilegeOnNamespaceToRole(
-            CATALOG_NAME, CATALOG_ROLE1, NS1, PolarisPrivilege.TABLE_CREATE));
+        newRootAdminService()
+            .grantPrivilegeOnNamespaceToRole(
+                CATALOG_NAME, CATALOG_ROLE1, NS1, PolarisPrivilege.TABLE_CREATE));
     Assertions.assertThatThrownBy(() -> newHandler().commitTransaction(req))
         .isInstanceOf(ForbiddenException.class);
 
     // Grant TABLE_FULL_METADATA directly on TABLE_NS1_1
     assertSuccess(
-        adminService.grantPrivilegeOnTableToRole(
-            CATALOG_NAME, CATALOG_ROLE1, TABLE_NS1_1, PolarisPrivilege.TABLE_FULL_METADATA));
+        newRootAdminService()
+            .grantPrivilegeOnTableToRole(
+                CATALOG_NAME, CATALOG_ROLE1, TABLE_NS1_1, PolarisPrivilege.TABLE_FULL_METADATA));
     Assertions.assertThatThrownBy(() -> newHandler().commitTransaction(req))
         .isInstanceOf(ForbiddenException.class);
 
     // Grant TABLE_WRITE_PROPERTIES on NS1A namespace
     assertSuccess(
-        adminService.grantPrivilegeOnNamespaceToRole(
-            CATALOG_NAME, CATALOG_ROLE1, NS1A, PolarisPrivilege.TABLE_WRITE_PROPERTIES));
+        newRootAdminService()
+            .grantPrivilegeOnNamespaceToRole(
+                CATALOG_NAME, CATALOG_ROLE1, NS1A, PolarisPrivilege.TABLE_WRITE_PROPERTIES));
     Assertions.assertThatThrownBy(() -> newHandler().commitTransaction(req))
         .isInstanceOf(ForbiddenException.class);
 
     // Grant TABLE_WRITE_DATA directly on TABLE_NS1B_1
     assertSuccess(
-        adminService.grantPrivilegeOnTableToRole(
-            CATALOG_NAME, CATALOG_ROLE1, TABLE_NS1B_1, PolarisPrivilege.TABLE_WRITE_DATA));
+        newRootAdminService()
+            .grantPrivilegeOnTableToRole(
+                CATALOG_NAME, CATALOG_ROLE1, TABLE_NS1B_1, PolarisPrivilege.TABLE_WRITE_DATA));
     Assertions.assertThatThrownBy(() -> newHandler().commitTransaction(req))
         .isInstanceOf(ForbiddenException.class);
 
     // Grant TABLE_WRITE_PROPERTIES directly on TABLE_NS2_1
     assertSuccess(
-        adminService.grantPrivilegeOnTableToRole(
-            CATALOG_NAME, CATALOG_ROLE1, TABLE_NS2_1, PolarisPrivilege.TABLE_WRITE_PROPERTIES));
+        newRootAdminService()
+            .grantPrivilegeOnTableToRole(
+                CATALOG_NAME, CATALOG_ROLE1, TABLE_NS2_1, PolarisPrivilege.TABLE_WRITE_PROPERTIES));
     Assertions.assertThatThrownBy(() -> newHandler().commitTransaction(req))
         .isInstanceOf(ForbiddenException.class);
 
@@ -1373,8 +1436,9 @@ public abstract class AbstractIcebergCatalogHandlerAuthzTest extends PolarisAuth
     // and update, then this one should only be TABLE_CREATE on the *parent* of this last table
     // and the table shouldn't exist.
     assertSuccess(
-        adminService.grantPrivilegeOnTableToRole(
-            CATALOG_NAME, CATALOG_ROLE1, TABLE_NS2_1, PolarisPrivilege.TABLE_CREATE));
+        newRootAdminService()
+            .grantPrivilegeOnTableToRole(
+                CATALOG_NAME, CATALOG_ROLE1, TABLE_NS2_1, PolarisPrivilege.TABLE_CREATE));
     newHandler().commitTransaction(req);
   }
 
@@ -1395,8 +1459,9 @@ public abstract class AbstractIcebergCatalogHandlerAuthzTest extends PolarisAuth
   @TestFactory
   Stream<DynamicNode> testCreateViewPrivileges() {
     assertSuccess(
-        adminService.grantPrivilegeOnCatalogToRole(
-            CATALOG_NAME, CATALOG_ROLE2, PolarisPrivilege.VIEW_DROP));
+        newRootAdminService()
+            .grantPrivilegeOnCatalogToRole(
+                CATALOG_NAME, CATALOG_ROLE2, PolarisPrivilege.VIEW_DROP));
 
     final TableIdentifier newview = TableIdentifier.of(NS2, "newview");
     final CreateViewRequest createRequest =
@@ -1453,8 +1518,9 @@ public abstract class AbstractIcebergCatalogHandlerAuthzTest extends PolarisAuth
   @TestFactory
   Stream<DynamicNode> testDropViewPrivileges() {
     assertSuccess(
-        adminService.grantPrivilegeOnCatalogToRole(
-            CATALOG_NAME, CATALOG_ROLE2, PolarisPrivilege.VIEW_CREATE));
+        newRootAdminService()
+            .grantPrivilegeOnCatalogToRole(
+                CATALOG_NAME, CATALOG_ROLE2, PolarisPrivilege.VIEW_CREATE));
 
     final CreateViewRequest createRequest =
         ImmutableCreateViewRequest.builder()
@@ -1532,11 +1598,13 @@ public abstract class AbstractIcebergCatalogHandlerAuthzTest extends PolarisAuth
 
     // Minimum privileges should succeed -- drop on src, create on dst parent.
     assertSuccess(
-        adminService.grantPrivilegeOnViewToRole(
-            CATALOG_NAME, CATALOG_ROLE1, srcView, PolarisPrivilege.VIEW_DROP));
+        newRootAdminService()
+            .grantPrivilegeOnViewToRole(
+                CATALOG_NAME, CATALOG_ROLE1, srcView, PolarisPrivilege.VIEW_DROP));
     assertSuccess(
-        adminService.grantPrivilegeOnNamespaceToRole(
-            CATALOG_NAME, CATALOG_ROLE1, dstView.namespace(), PolarisPrivilege.VIEW_CREATE));
+        newRootAdminService()
+            .grantPrivilegeOnNamespaceToRole(
+                CATALOG_NAME, CATALOG_ROLE1, dstView.namespace(), PolarisPrivilege.VIEW_CREATE));
 
     // Initial rename should succeed
     newHandler().renameView(rename1);
@@ -1547,8 +1615,9 @@ public abstract class AbstractIcebergCatalogHandlerAuthzTest extends PolarisAuth
 
     // Now grant VIEW_DROP on dst
     assertSuccess(
-        adminService.grantPrivilegeOnViewToRole(
-            CATALOG_NAME, CATALOG_ROLE1, dstView, PolarisPrivilege.VIEW_DROP));
+        newRootAdminService()
+            .grantPrivilegeOnViewToRole(
+                CATALOG_NAME, CATALOG_ROLE1, dstView, PolarisPrivilege.VIEW_DROP));
 
     // Still not enough without VIEW_CREATE at source
     Assertions.assertThatThrownBy(() -> newHandler().renameView(rename2))
@@ -1556,8 +1625,9 @@ public abstract class AbstractIcebergCatalogHandlerAuthzTest extends PolarisAuth
 
     // Even grant CATALOG_MANAGE_CONTENT under all of NS1
     assertSuccess(
-        adminService.grantPrivilegeOnNamespaceToRole(
-            CATALOG_NAME, CATALOG_ROLE1, NS1, PolarisPrivilege.CATALOG_MANAGE_CONTENT));
+        newRootAdminService()
+            .grantPrivilegeOnNamespaceToRole(
+                CATALOG_NAME, CATALOG_ROLE1, NS1, PolarisPrivilege.CATALOG_MANAGE_CONTENT));
 
     // Still not enough to rename back to src since src was NS2.
     Assertions.assertThatThrownBy(() -> newHandler().renameView(rename2))
@@ -1565,8 +1635,9 @@ public abstract class AbstractIcebergCatalogHandlerAuthzTest extends PolarisAuth
 
     // Finally, grant VIEW_CREATE on NS2 and it should succeed to rename back to src.
     assertSuccess(
-        adminService.grantPrivilegeOnNamespaceToRole(
-            CATALOG_NAME, CATALOG_ROLE1, NS2, PolarisPrivilege.VIEW_CREATE));
+        newRootAdminService()
+            .grantPrivilegeOnNamespaceToRole(
+                CATALOG_NAME, CATALOG_ROLE1, NS2, PolarisPrivilege.VIEW_CREATE));
     newHandler().renameView(rename2);
   }
 
@@ -1837,26 +1908,32 @@ public abstract class AbstractIcebergCatalogHandlerAuthzTest extends PolarisAuth
         FileStorageConfigInfo.builder()
             .setStorageType(StorageConfigInfo.StorageTypeEnum.FILE)
             .build();
-    adminService.createCatalog(
-        new CreateCatalogRequest(
-            new CatalogEntity.Builder()
-                .setName(externalCatalog)
-                .setDefaultBaseLocation(storageLocation)
-                .setStorageConfigurationInfo(realmConfig, storageConfigModel, storageLocation)
-                .setCatalogType("EXTERNAL")
-                .build()
-                .asCatalog(serviceIdentityProvider)));
-    adminService.createCatalogRole(
-        externalCatalog, new CatalogRoleEntity.Builder().setName(CATALOG_ROLE1).build());
-    adminService.createCatalogRole(
-        externalCatalog, new CatalogRoleEntity.Builder().setName(CATALOG_ROLE2).build());
-
-    adminService.assignPrincipalRole(PRINCIPAL_NAME, PRINCIPAL_ROLE1);
-    adminService.assignCatalogRoleToPrincipalRole(PRINCIPAL_ROLE1, externalCatalog, CATALOG_ROLE1);
-    adminService.assignCatalogRoleToPrincipalRole(PRINCIPAL_ROLE2, externalCatalog, CATALOG_ROLE2);
+    newRootAdminService()
+        .createCatalog(
+            new CreateCatalogRequest(
+                new CatalogEntity.Builder()
+                    .setName(externalCatalog)
+                    .setDefaultBaseLocation(storageLocation)
+                    .setStorageConfigurationInfo(realmConfig, storageConfigModel, storageLocation)
+                    .setCatalogType("EXTERNAL")
+                    .build()
+                    .asCatalog(serviceIdentityProvider)));
+    newRootAdminService()
+        .createCatalogRole(
+            externalCatalog, new CatalogRoleEntity.Builder().setName(CATALOG_ROLE1).build());
+    newRootAdminService()
+        .createCatalogRole(
+            externalCatalog, new CatalogRoleEntity.Builder().setName(CATALOG_ROLE2).build());
+
+    newRootAdminService().assignPrincipalRole(PRINCIPAL_NAME, PRINCIPAL_ROLE1);
+    newRootAdminService()
+        .assignCatalogRoleToPrincipalRole(PRINCIPAL_ROLE1, externalCatalog, CATALOG_ROLE1);
+    newRootAdminService()
+        .assignCatalogRoleToPrincipalRole(PRINCIPAL_ROLE2, externalCatalog, CATALOG_ROLE2);
     assertSuccess(
-        adminService.grantPrivilegeOnCatalogToRole(
-            externalCatalog, CATALOG_ROLE2, PolarisPrivilege.CATALOG_MANAGE_CONTENT));
+        newRootAdminService()
+            .grantPrivilegeOnCatalogToRole(
+                externalCatalog, CATALOG_ROLE2, PolarisPrivilege.CATALOG_MANAGE_CONTENT));
   }
 
   private PolarisLocalCatalogFactory createExternalCatalogFactory(String externalCatalog) {
diff --git a/runtime/service/src/test/java/org/apache/polaris/service/catalog/iceberg/AbstractLocalIcebergCatalogTest.java b/runtime/service/src/test/java/org/apache/polaris/service/catalog/iceberg/AbstractLocalIcebergCatalogTest.java
index b0d16ff437..5f79c191c2 100644
--- a/runtime/service/src/test/java/org/apache/polaris/service/catalog/iceberg/AbstractLocalIcebergCatalogTest.java
+++ b/runtime/service/src/test/java/org/apache/polaris/service/catalog/iceberg/AbstractLocalIcebergCatalogTest.java
@@ -102,6 +102,7 @@ import org.apache.polaris.core.admin.model.AwsStorageConfigInfo;
 import org.apache.polaris.core.admin.model.CreateCatalogRequest;
 import org.apache.polaris.core.admin.model.StorageConfigInfo;
 import org.apache.polaris.core.admin.model.UpdateCatalogRequest;
+import org.apache.polaris.core.auth.PolarisAuthorizer;
 import org.apache.polaris.core.auth.PolarisPrincipal;
 import org.apache.polaris.core.config.FeatureConfiguration;
 import org.apache.polaris.core.config.RealmConfig;
@@ -125,6 +126,7 @@ import org.apache.polaris.core.persistence.pagination.Page;
 import org.apache.polaris.core.persistence.pagination.PageToken;
 import org.apache.polaris.core.persistence.resolver.ResolutionManifestFactory;
 import org.apache.polaris.core.persistence.resolver.ResolverFactory;
+import org.apache.polaris.core.secrets.UserSecretsManager;
 import org.apache.polaris.core.storage.CredentialVendingContext;
 import org.apache.polaris.core.storage.LocationGrant;
 import org.apache.polaris.core.storage.PolarisStorageActions;
@@ -135,11 +137,13 @@ import org.apache.polaris.core.storage.aws.AwsCredentialsStorageIntegration;
 import org.apache.polaris.core.storage.aws.AwsStorageConfigurationInfo;
 import org.apache.polaris.core.storage.cache.StorageCredentialCache;
 import org.apache.polaris.service.admin.PolarisAdminService;
+import org.apache.polaris.service.admin.PolarisAdminServiceTestSupport;
 import org.apache.polaris.service.catalog.PolarisPassthroughResolutionView;
 import org.apache.polaris.service.catalog.io.ExceptionMappingFileIO;
 import org.apache.polaris.service.catalog.io.FileIOFactory;
 import org.apache.polaris.service.catalog.io.MeasuredFileIOFactory;
 import org.apache.polaris.service.catalog.io.StorageAccessConfigProvider;
+import org.apache.polaris.service.config.ReservedProperties;
 import org.apache.polaris.service.context.catalog.RealmContextHolder;
 import org.apache.polaris.service.events.EventAttributes;
 import org.apache.polaris.service.events.PolarisEvent;
@@ -243,7 +247,9 @@ public abstract class AbstractLocalIcebergCatalogTest extends CatalogTests<Local
   @Inject FileIOFactory fileIOFactory;
   @Inject TaskFileIOSupplier taskFileIOSupplier;
   @Inject PolarisPrincipal authenticatedRoot;
-  @Inject PolarisAdminService adminService;
+  @Inject UserSecretsManager userSecretsManager;
+  @Inject PolarisAuthorizer authorizer;
+  @Inject ReservedProperties reservedProperties;
   @Inject ResolverFactory resolverFactory;
   @Inject PolarisEventDispatcher polarisEventDispatcher;
 
@@ -264,6 +270,18 @@ public abstract class AbstractLocalIcebergCatalogTest extends CatalogTests<Local
 
   protected void bootstrapRealm(String realmName) {}
 
+  protected PolarisAdminService newAdminService() {
+    return PolarisAdminServiceTestSupport.newAdminService(
+        callContext,
+        resolutionManifestFactory,
+        metaStoreManager,
+        userSecretsManager,
+        serviceIdentityProvider,
+        authenticatedRoot,
+        authorizer,
+        reservedProperties);
+  }
+
   @BeforeEach
   @SuppressWarnings("unchecked")
   public void before(TestInfo testInfo) {
@@ -287,23 +305,27 @@ public abstract class AbstractLocalIcebergCatalogTest extends CatalogTests<Local
             .setAllowedLocations(List.of(STORAGE_LOCATION, "s3://externally-owned-bucket"))
             .build();
     catalogEntity =
-        adminService.createCatalog(
-            new CreateCatalogRequest(
-                new CatalogEntity.Builder()
-                    .setName(CATALOG_NAME)
-                    .setDefaultBaseLocation(STORAGE_LOCATION)
-                    .addProperty(
-                        FeatureConfiguration.ALLOW_EXTERNAL_TABLE_LOCATION.catalogConfig(), "true")
-                    .addProperty(
-                        FeatureConfiguration.ALLOW_UNSTRUCTURED_TABLE_LOCATION.catalogConfig(),
-                        "true")
-                    .addProperty(
-                        FeatureConfiguration.DROP_WITH_PURGE_ENABLED.catalogConfig(), "true")
-                    .addProperty(
-                        FeatureConfiguration.PURGE_VIEW_METADATA_ON_DROP.catalogConfig(), "true")
-                    .setStorageConfigurationInfo(realmConfig, storageConfigModel, STORAGE_LOCATION)
-                    .build()
-                    .asCatalog(serviceIdentityProvider)));
+        newAdminService()
+            .createCatalog(
+                new CreateCatalogRequest(
+                    new CatalogEntity.Builder()
+                        .setName(CATALOG_NAME)
+                        .setDefaultBaseLocation(STORAGE_LOCATION)
+                        .addProperty(
+                            FeatureConfiguration.ALLOW_EXTERNAL_TABLE_LOCATION.catalogConfig(),
+                            "true")
+                        .addProperty(
+                            FeatureConfiguration.ALLOW_UNSTRUCTURED_TABLE_LOCATION.catalogConfig(),
+                            "true")
+                        .addProperty(
+                            FeatureConfiguration.DROP_WITH_PURGE_ENABLED.catalogConfig(), "true")
+                        .addProperty(
+                            FeatureConfiguration.PURGE_VIEW_METADATA_ON_DROP.catalogConfig(),
+                            "true")
+                        .setStorageConfigurationInfo(
+                            realmConfig, storageConfigModel, STORAGE_LOCATION)
+                        .build()
+                        .asCatalog(serviceIdentityProvider)));
 
     StsClient stsClient = Mockito.mock(StsClient.class);
     when(stsClient.assumeRole(isA(AssumeRoleRequest.class)))
@@ -1386,13 +1408,14 @@ public abstract class AbstractLocalIcebergCatalogTest extends CatalogTests<Local
     final String metadataLocation = "file:///etc/metadata.json/../passwd";
     String catalogWithoutStorage = "catalogWithoutStorage";
     PolarisEntity catalogEntity =
-        adminService.createCatalog(
-            new CreateCatalogRequest(
-                new CatalogEntity.Builder()
-                    .setDefaultBaseLocation("file://")
-                    .setName(catalogWithoutStorage)
-                    .build()
-                    .asCatalog(serviceIdentityProvider)));
+        newAdminService()
+            .createCatalog(
+                new CreateCatalogRequest(
+                    new CatalogEntity.Builder()
+                        .setDefaultBaseLocation("file://")
+                        .setName(catalogWithoutStorage)
+                        .build()
+                        .asCatalog(serviceIdentityProvider)));
 
     LocalIcebergCatalog catalog = newIcebergCatalog(catalogWithoutStorage);
     catalog.initialize(
@@ -1436,13 +1459,14 @@ public abstract class AbstractLocalIcebergCatalogTest extends CatalogTests<Local
         supportsNotifications(), "Only applicable if notifications are supported");
 
     String catalogName = "catalogForMaliciousDomain";
-    adminService.createCatalog(
-        new CreateCatalogRequest(
-            new CatalogEntity.Builder()
-                .setDefaultBaseLocation("http://maliciousdomain.com")
-                .setName(catalogName)
-                .build()
-                .asCatalog(serviceIdentityProvider)));
+    newAdminService()
+        .createCatalog(
+            new CreateCatalogRequest(
+                new CatalogEntity.Builder()
+                    .setDefaultBaseLocation("http://maliciousdomain.com")
+                    .setName(catalogName)
+                    .build()
+                    .asCatalog(serviceIdentityProvider)));
 
     LocalIcebergCatalog catalog = newIcebergCatalog(catalogName);
     catalog.initialize(
@@ -1986,20 +2010,23 @@ public abstract class AbstractLocalIcebergCatalogTest extends CatalogTests<Local
             .setUserArn("aws::a:user:arn")
             .setStorageType(StorageConfigInfo.StorageTypeEnum.S3)
             .build();
-    adminService.createCatalog(
-        new CreateCatalogRequest(
-            new CatalogEntity.Builder()
-                .setName(noPurgeCatalogName)
-                .setDefaultBaseLocation(storageLocation)
-                .addProperty(
-                    FeatureConfiguration.ALLOW_EXTERNAL_TABLE_LOCATION.catalogConfig(), "true")
-                .addProperty(
-                    FeatureConfiguration.ALLOW_UNSTRUCTURED_TABLE_LOCATION.catalogConfig(), "true")
-                .addProperty(FeatureConfiguration.DROP_WITH_PURGE_ENABLED.catalogConfig(), "false")
-                .setStorageConfigurationInfo(
-                    realmConfig, noPurgeStorageConfigModel, storageLocation)
-                .build()
-                .asCatalog(serviceIdentityProvider)));
+    newAdminService()
+        .createCatalog(
+            new CreateCatalogRequest(
+                new CatalogEntity.Builder()
+                    .setName(noPurgeCatalogName)
+                    .setDefaultBaseLocation(storageLocation)
+                    .addProperty(
+                        FeatureConfiguration.ALLOW_EXTERNAL_TABLE_LOCATION.catalogConfig(), "true")
+                    .addProperty(
+                        FeatureConfiguration.ALLOW_UNSTRUCTURED_TABLE_LOCATION.catalogConfig(),
+                        "true")
+                    .addProperty(
+                        FeatureConfiguration.DROP_WITH_PURGE_ENABLED.catalogConfig(), "false")
+                    .setStorageConfigurationInfo(
+                        realmConfig, noPurgeStorageConfigModel, storageLocation)
+                    .build()
+                    .asCatalog(serviceIdentityProvider)));
     LocalIcebergCatalog noPurgeCatalog =
         newIcebergCatalog(noPurgeCatalogName, metaStoreManager, fileIOFactory);
     noPurgeCatalog.initialize(
@@ -2606,14 +2633,15 @@ public abstract class AbstractLocalIcebergCatalogTest extends CatalogTests<Local
   public void createCatalogWithReservedProperty() {
     Assertions.assertThatCode(
             () -> {
-              adminService.createCatalog(
-                  new CreateCatalogRequest(
-                      new CatalogEntity.Builder()
-                          .setDefaultBaseLocation("file://")
-                          .setName("createCatalogWithReservedProperty")
-                          .setProperties(ImmutableMap.of("polaris.reserved", "true"))
-                          .build()
-                          .asCatalog(serviceIdentityProvider)));
+              newAdminService()
+                  .createCatalog(
+                      new CreateCatalogRequest(
+                          new CatalogEntity.Builder()
+                              .setDefaultBaseLocation("file://")
+                              .setName("createCatalogWithReservedProperty")
+                              .setProperties(ImmutableMap.of("polaris.reserved", "true"))
+                              .build()
+                              .asCatalog(serviceIdentityProvider)));
             })
         .isInstanceOf(IllegalArgumentException.class)
         .hasMessageContaining("reserved prefix");
@@ -2621,26 +2649,28 @@ public abstract class AbstractLocalIcebergCatalogTest extends CatalogTests<Local
 
   @Test
   public void updateCatalogWithReservedProperty() {
-    adminService.createCatalog(
-        new CreateCatalogRequest(
-            new CatalogEntity.Builder()
-                .setDefaultBaseLocation("file://")
-                .setName("updateCatalogWithReservedProperty")
-                .setProperties(ImmutableMap.of("a", "b"))
-                .build()
-                .asCatalog(serviceIdentityProvider)));
+    newAdminService()
+        .createCatalog(
+            new CreateCatalogRequest(
+                new CatalogEntity.Builder()
+                    .setDefaultBaseLocation("file://")
+                    .setName("updateCatalogWithReservedProperty")
+                    .setProperties(ImmutableMap.of("a", "b"))
+                    .build()
+                    .asCatalog(serviceIdentityProvider)));
     Assertions.assertThatCode(
             () -> {
-              adminService.updateCatalog(
-                  "updateCatalogWithReservedProperty",
-                  UpdateCatalogRequest.builder()
-                      .setCurrentEntityVersion(1)
-                      .setProperties(ImmutableMap.of("polaris.reserved", "true"))
-                      .build());
+              newAdminService()
+                  .updateCatalog(
+                      "updateCatalogWithReservedProperty",
+                      UpdateCatalogRequest.builder()
+                          .setCurrentEntityVersion(1)
+                          .setProperties(ImmutableMap.of("polaris.reserved", "true"))
+                          .build());
             })
         .isInstanceOf(IllegalArgumentException.class)
         .hasMessageContaining("reserved prefix");
-    adminService.deleteCatalog("updateCatalogWithReservedProperty");
+    newAdminService().deleteCatalog("updateCatalogWithReservedProperty");
   }
 
   @ParameterizedTest
diff --git a/runtime/service/src/test/java/org/apache/polaris/service/catalog/iceberg/AbstractLocalIcebergCatalogViewTest.java b/runtime/service/src/test/java/org/apache/polaris/service/catalog/iceberg/AbstractLocalIcebergCatalogViewTest.java
index f037778a75..f18678b4ad 100644
--- a/runtime/service/src/test/java/org/apache/polaris/service/catalog/iceberg/AbstractLocalIcebergCatalogViewTest.java
+++ b/runtime/service/src/test/java/org/apache/polaris/service/catalog/iceberg/AbstractLocalIcebergCatalogViewTest.java
@@ -52,6 +52,7 @@ import org.apache.polaris.core.persistence.resolver.ResolverFactory;
 import org.apache.polaris.core.secrets.UserSecretsManager;
 import org.apache.polaris.core.storage.cache.StorageCredentialCache;
 import org.apache.polaris.service.admin.PolarisAdminService;
+import org.apache.polaris.service.admin.PolarisAdminServiceTestSupport;
 import org.apache.polaris.service.catalog.PolarisPassthroughResolutionView;
 import org.apache.polaris.service.catalog.io.FileIOFactory;
 import org.apache.polaris.service.catalog.io.StorageAccessConfigProvider;
@@ -114,6 +115,9 @@ public abstract class AbstractLocalIcebergCatalogViewTest
 
   private String realmName;
   private PolarisCallContext polarisContext;
+  private PolarisPrincipal authenticatedRoot;
+  private PolarisAuthorizer authorizer;
+  private ReservedProperties reservedProperties;
 
   private TestPolarisEventListener testPolarisEventListener;
 
@@ -126,6 +130,18 @@ public abstract class AbstractLocalIcebergCatalogViewTest
 
   protected void bootstrapRealm(String realmName) {}
 
+  protected PolarisAdminService newAdminService() {
+    return PolarisAdminServiceTestSupport.newAdminService(
+        polarisContext,
+        resolutionManifestFactory,
+        metaStoreManager,
+        userSecretsManager,
+        serviceIdentityProvider,
+        authenticatedRoot,
+        authorizer,
+        reservedProperties);
+  }
+
   @BeforeEach
   public void before(TestInfo testInfo) {
     storageCredentialCache.invalidateAll();
@@ -141,38 +157,33 @@ public abstract class AbstractLocalIcebergCatalogViewTest
 
     PrincipalEntity rootPrincipal =
         metaStoreManager.findRootPrincipal(polarisContext).orElseThrow();
-    PolarisPrincipal authenticatedRoot = PolarisPrincipal.of(rootPrincipal, Set.of());
+    authenticatedRoot = PolarisPrincipal.of(rootPrincipal, Set.of());
 
-    PolarisAuthorizer authorizer = new PolarisAuthorizerImpl(realmConfig);
-    ReservedProperties reservedProperties = ReservedProperties.NONE;
+    authorizer = new PolarisAuthorizerImpl(realmConfig);
+    reservedProperties = ReservedProperties.NONE;
 
-    PolarisAdminService adminService =
-        new PolarisAdminService(
-            polarisContext,
-            resolutionManifestFactory,
-            metaStoreManager,
-            userSecretsManager,
-            serviceIdentityProvider,
-            authenticatedRoot,
-            authorizer,
-            reservedProperties);
-    adminService.createCatalog(
-        new CreateCatalogRequest(
-            new CatalogEntity.Builder()
-                .setName(CATALOG_NAME)
-                .addProperty(
-                    FeatureConfiguration.ALLOW_EXTERNAL_TABLE_LOCATION.catalogConfig(), "true")
-                .addProperty(
-                    FeatureConfiguration.ALLOW_UNSTRUCTURED_TABLE_LOCATION.catalogConfig(), "true")
-                .addProperty(FeatureConfiguration.DROP_WITH_PURGE_ENABLED.catalogConfig(), "true")
-                .setDefaultBaseLocation("file://tmp")
-                .setStorageConfigurationInfo(
-                    realmConfig,
-                    new FileStorageConfigInfo(
-                        StorageConfigInfo.StorageTypeEnum.FILE, List.of("file://tmp", "*"), null),
-                    "file://tmp")
-                .build()
-                .asCatalog(serviceIdentityProvider)));
+    newAdminService()
+        .createCatalog(
+            new CreateCatalogRequest(
+                new CatalogEntity.Builder()
+                    .setName(CATALOG_NAME)
+                    .addProperty(
+                        FeatureConfiguration.ALLOW_EXTERNAL_TABLE_LOCATION.catalogConfig(), "true")
+                    .addProperty(
+                        FeatureConfiguration.ALLOW_UNSTRUCTURED_TABLE_LOCATION.catalogConfig(),
+                        "true")
+                    .addProperty(
+                        FeatureConfiguration.DROP_WITH_PURGE_ENABLED.catalogConfig(), "true")
+                    .setDefaultBaseLocation("file://tmp")
+                    .setStorageConfigurationInfo(
+                        realmConfig,
+                        new FileStorageConfigInfo(
+                            StorageConfigInfo.StorageTypeEnum.FILE,
+                            List.of("file://tmp", "*"),
+                            null),
+                        "file://tmp")
+                    .build()
+                    .asCatalog(serviceIdentityProvider)));
 
     PolarisPassthroughResolutionView passthroughView =
         new PolarisPassthroughResolutionView(
diff --git a/runtime/service/src/test/java/org/apache/polaris/service/catalog/iceberg/IcebergCatalogHandlerFineGrainedDisabledTest.java b/runtime/service/src/test/java/org/apache/polaris/service/catalog/iceberg/IcebergCatalogHandlerFineGrainedDisabledTest.java
index e9955c0796..8496b6ad59 100644
--- a/runtime/service/src/test/java/org/apache/polaris/service/catalog/iceberg/IcebergCatalogHandlerFineGrainedDisabledTest.java
+++ b/runtime/service/src/test/java/org/apache/polaris/service/catalog/iceberg/IcebergCatalogHandlerFineGrainedDisabledTest.java
@@ -33,6 +33,7 @@ import org.apache.polaris.core.auth.PolarisPrincipal;
 import org.apache.polaris.core.entity.PolarisPrivilege;
 import org.apache.polaris.service.Profiles;
 import org.apache.polaris.service.admin.PolarisAuthzTestBase;
+import org.apache.polaris.service.admin.PolarisAuthzTestsFactory;
 import org.junit.jupiter.api.DynamicNode;
 import org.junit.jupiter.api.TestFactory;
 
@@ -48,7 +49,14 @@ public class IcebergCatalogHandlerFineGrainedDisabledTest extends PolarisAuthzTe
 
   private IcebergCatalogHandler newHandler() {
     PolarisPrincipal authenticatedPrincipal = PolarisPrincipal.of(principalEntity, Set.of());
-    return icebergCatalogHandlerFactory.createHandler(CATALOG_NAME, authenticatedPrincipal);
+    IcebergCatalogHandler handler =
+        icebergCatalogHandlerFactory.createHandler(CATALOG_NAME, authenticatedPrincipal);
+    return ImmutableIcebergCatalogHandler.builder().from(handler).build();
+  }
+
+  @Override
+  protected PolarisAuthzTestsFactory.Builder authzTestsBuilder(String operationName) {
+    return super.authzTestsBuilder(operationName).useFreshRequestContext(true);
   }
 
   public static class Profile extends Profiles.PolarisAuthzBaseProfile {
diff --git a/runtime/service/src/test/java/org/apache/polaris/service/catalog/iceberg/IcebergCatalogHandlerTest.java b/runtime/service/src/test/java/org/apache/polaris/service/catalog/iceberg/IcebergCatalogHandlerTest.java
index ebf1feac31..31a864d296 100644
--- a/runtime/service/src/test/java/org/apache/polaris/service/catalog/iceberg/IcebergCatalogHandlerTest.java
+++ b/runtime/service/src/test/java/org/apache/polaris/service/catalog/iceberg/IcebergCatalogHandlerTest.java
@@ -20,8 +20,12 @@ package org.apache.polaris.service.catalog.iceberg;
 
 import static org.apache.polaris.service.catalog.AccessDelegationMode.VENDED_CREDENTIALS;
 import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.nullable;
+import static org.mockito.Mockito.doThrow;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
@@ -29,21 +33,33 @@ import static org.mockito.Mockito.when;
 
 import jakarta.enterprise.inject.Instance;
 import java.time.Clock;
+import java.util.EnumSet;
+import java.util.List;
 import java.util.Map;
 import java.util.Optional;
 import java.util.Set;
 import org.apache.iceberg.BaseTable;
+import org.apache.iceberg.MetadataUpdate;
 import org.apache.iceberg.TableMetadata;
 import org.apache.iceberg.TableOperations;
 import org.apache.iceberg.catalog.Catalog;
 import org.apache.iceberg.catalog.Namespace;
 import org.apache.iceberg.catalog.TableIdentifier;
+import org.apache.iceberg.exceptions.ForbiddenException;
 import org.apache.iceberg.rest.credentials.Credential;
+import org.apache.iceberg.rest.requests.ImmutableRegisterTableRequest;
+import org.apache.iceberg.rest.requests.RegisterTableRequest;
+import org.apache.iceberg.rest.requests.UpdateTableRequest;
 import org.apache.iceberg.rest.responses.ImmutableLoadCredentialsResponse;
+import org.apache.iceberg.rest.responses.LoadTableResponse;
 import org.apache.polaris.core.PolarisDiagnostics;
+import org.apache.polaris.core.auth.AuthorizationRequest;
+import org.apache.polaris.core.auth.AuthorizationState;
+import org.apache.polaris.core.auth.PolarisAuthorizableOperation;
 import org.apache.polaris.core.auth.PolarisAuthorizer;
 import org.apache.polaris.core.auth.PolarisPrincipal;
 import org.apache.polaris.core.catalog.LocalCatalogFactory;
+import org.apache.polaris.core.config.FeatureConfiguration;
 import org.apache.polaris.core.config.RealmConfig;
 import org.apache.polaris.core.context.CallContext;
 import org.apache.polaris.core.context.RealmContext;
@@ -58,7 +74,9 @@ import org.apache.polaris.core.persistence.PolarisMetaStoreManager;
 import org.apache.polaris.core.persistence.PolarisResolvedPathWrapper;
 import org.apache.polaris.core.persistence.resolver.PolarisResolutionManifest;
 import org.apache.polaris.core.persistence.resolver.ResolutionManifestFactory;
+import org.apache.polaris.core.persistence.resolver.ResolvedPathKey;
 import org.apache.polaris.core.persistence.resolver.ResolverFactory;
+import org.apache.polaris.core.storage.PolarisStorageActions;
 import org.apache.polaris.core.storage.StorageAccessConfig;
 import org.apache.polaris.service.catalog.AccessDelegationModeResolver;
 import org.apache.polaris.service.catalog.CatalogPrefixParser;
@@ -66,13 +84,16 @@ import org.apache.polaris.service.catalog.io.StorageAccessConfigProvider;
 import org.apache.polaris.service.config.ReservedProperties;
 import org.apache.polaris.service.events.EventAttributeMap;
 import org.apache.polaris.service.reporting.PolarisMetricsReporter;
+import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
+import org.mockito.ArgumentCaptor;
 
 class IcebergCatalogHandlerTest {
 
   private static final String CATALOG_NAME = "test";
   private static final Namespace NS1 = Namespace.of("ns1");
   private static final TableIdentifier TABLE2 = TableIdentifier.of(NS1, "table2");
+  private static final String TABLE_LOCATION = "s3://fake-bucket/tables/table2";
 
   private final PolarisResolutionManifest resolutionManifest =
       mock(PolarisResolutionManifest.class);
@@ -85,6 +106,19 @@ class IcebergCatalogHandlerTest {
       mock(AccessDelegationModeResolver.class);
   private final StorageAccessConfigProvider storageAccessConfigProvider =
       mock(StorageAccessConfigProvider.class);
+  private final PolarisAuthorizer authorizer = mock(PolarisAuthorizer.class);
+  private final CatalogHandlerUtils catalogHandlerUtils = mock(CatalogHandlerUtils.class);
+
+  @BeforeEach
+  void setUp() {
+    StorageAccessConfig storageAccessConfig =
+        StorageAccessConfig.builder()
+            .putCredential("fake.access.key", "AKIAFAKE")
+            .putCredential("fake.secret.key", "fakeSecret")
+            .build();
+    when(storageAccessConfigProvider.getStorageAccessConfig(any(), any(), any(), any(), any()))
+        .thenReturn(storageAccessConfig);
+  }
 
   @SuppressWarnings({"unchecked"})
   private IcebergCatalogHandler newHandler() {
@@ -100,6 +134,7 @@ class IcebergCatalogHandlerTest {
     // Authorization path: any resolved path lookup returns a non-null wrapper so the
     // "not found" check in CatalogHandler#authorizeBasicTableLikeOperationsOrThrow passes.
     when(resolutionManifest.getResolvedPath(any(), any(), anyBoolean())).thenReturn(resolvedPath);
+    when(resolutionManifest.getResolvedPath(any(), anyBoolean())).thenReturn(resolvedPath);
     when(resolutionManifest.getResolvedPath(any(), any())).thenReturn(resolvedPath);
     when(resolutionManifest.getResolvedPath(any())).thenReturn(resolvedPath);
     when(resolutionManifest.getAllActivatedCatalogRoleAndPrincipalRoles()).thenReturn(Set.of());
@@ -115,7 +150,7 @@ class IcebergCatalogHandlerTest {
         .callContext(callContext)
         .metaStoreManager(mock(PolarisMetaStoreManager.class))
         .resolutionManifestFactory(resolutionManifestFactory)
-        .authorizer(mock(PolarisAuthorizer.class))
+        .authorizer(authorizer)
         .diagnostics(mock(PolarisDiagnostics.class))
         .credentialManager(mock(PolarisCredentialManager.class))
         .federatedCatalogFactories(mock(Instance.class))
@@ -123,7 +158,7 @@ class IcebergCatalogHandlerTest {
         .resolverFactory(mock(ResolverFactory.class))
         .localCatalogFactory(localCatalogFactory)
         .reservedProperties(mock(ReservedProperties.class))
-        .catalogHandlerUtils(mock(CatalogHandlerUtils.class))
+        .catalogHandlerUtils(catalogHandlerUtils)
         .storageAccessConfigProvider(storageAccessConfigProvider)
         .eventAttributeMap(mock(EventAttributeMap.class))
         .metricsReporter(mock(PolarisMetricsReporter.class))
@@ -132,6 +167,229 @@ class IcebergCatalogHandlerTest {
         .build();
   }
 
+  private Catalog mockRegisterTableCatalog(boolean overwrite) {
+    Catalog catalog = mock(Catalog.class);
+    BaseTable table = baseTable();
+    when(catalog.registerTable(TABLE2, TABLE_LOCATION, overwrite)).thenReturn(table);
+    when(localCatalogFactory.createCatalog(any())).thenReturn(catalog);
+    return catalog;
+  }
+
+  private static RegisterTableRequest registerTableRequest(boolean overwrite) {
+    return ImmutableRegisterTableRequest.builder()
+        .name(TABLE2.name())
+        .metadataLocation(TABLE_LOCATION)
+        .overwrite(overwrite)
+        .build();
+  }
+
+  private static BaseTable baseTable() {
+    TableMetadata metadata = mock(TableMetadata.class);
+    when(metadata.location()).thenReturn(TABLE_LOCATION);
+    when(metadata.properties()).thenReturn(Map.of());
+
+    TableOperations ops = mock(TableOperations.class);
+    when(ops.current()).thenReturn(metadata);
+
+    BaseTable table = mock(BaseTable.class);
+    when(table.operations()).thenReturn(ops);
+    return table;
+  }
+
+  @SuppressWarnings({"unchecked", "rawtypes"})
+  private void assertVendedActions(PolarisStorageActions... actions) {
+    ArgumentCaptor<Set<PolarisStorageActions>> actionsCaptor =
+        (ArgumentCaptor) ArgumentCaptor.forClass(Set.class);
+
+    verify(storageAccessConfigProvider)
+        .getStorageAccessConfig(
+            eq(TABLE2), any(), actionsCaptor.capture(), eq(Optional.empty()), eq(resolvedPath));
+    assertThat(actionsCaptor.getValue()).containsExactlyInAnyOrder(actions);
+  }
+
+  @Test
+  void registerTableWithVendedCredentialsVendsReadWriteActionsWhenWriteDelegationAuthorized() {
+    Catalog catalog = mockRegisterTableCatalog(false);
+    when(accessDelegationModeResolver.resolve(eq(EnumSet.of(VENDED_CREDENTIALS)), any()))
+        .thenReturn(Optional.of(VENDED_CREDENTIALS));
+
+    @SuppressWarnings("resource")
+    IcebergCatalogHandler handler = newHandler();
+
+    LoadTableResponse response =
+        handler.registerTable(
+            NS1, registerTableRequest(false), EnumSet.of(VENDED_CREDENTIALS), Optional.empty());
+
+    verify(catalog).registerTable(TABLE2, TABLE_LOCATION, false);
+    assertThat(response.credentials()).hasSize(1);
+    assertVendedActions(
+        PolarisStorageActions.READ, PolarisStorageActions.LIST, PolarisStorageActions.WRITE);
+  }
+
+  @Test
+  void registerTableWithVendedCredentialsVendsReadActionsWhenWriteDelegationFallsBackToRead() {
+    Catalog catalog = mockRegisterTableCatalog(false);
+    when(accessDelegationModeResolver.resolve(eq(EnumSet.of(VENDED_CREDENTIALS)), any()))
+        .thenReturn(Optional.of(VENDED_CREDENTIALS));
+    doThrow(new ForbiddenException("write delegation denied"))
+        .when(authorizer)
+        .authorizeOrThrow(
+            any(),
+            any(),
+            eq(PolarisAuthorizableOperation.REGISTER_TABLE_WITH_WRITE_DELEGATION),
+            nullable(PolarisResolvedPathWrapper.class),
+            nullable(PolarisResolvedPathWrapper.class));
+
+    @SuppressWarnings("resource")
+    IcebergCatalogHandler handler = newHandler();
+
+    LoadTableResponse response =
+        handler.registerTable(
+            NS1, registerTableRequest(false), EnumSet.of(VENDED_CREDENTIALS), Optional.empty());
+
+    verify(catalog).registerTable(TABLE2, TABLE_LOCATION, false);
+    assertThat(response.credentials()).hasSize(1);
+    assertVendedActions(PolarisStorageActions.READ, PolarisStorageActions.LIST);
+  }
+
+  @Test
+  void registerTableOverwriteWithVendedCredentialsVendsReadWriteActionsWhenTableExists() {
+    Catalog catalog = mockRegisterTableCatalog(true);
+    when(catalogEntity.isExternal()).thenReturn(false);
+    when(accessDelegationModeResolver.resolve(eq(EnumSet.of(VENDED_CREDENTIALS)), any()))
+        .thenReturn(Optional.of(VENDED_CREDENTIALS));
+
+    @SuppressWarnings("resource")
+    IcebergCatalogHandler handler = newHandler();
+
+    LoadTableResponse response =
+        handler.registerTable(
+            NS1, registerTableRequest(true), EnumSet.of(VENDED_CREDENTIALS), Optional.empty());
+
+    verify(catalog).registerTable(TABLE2, TABLE_LOCATION, true);
+    assertThat(response.credentials()).hasSize(1);
+    assertVendedActions(
+        PolarisStorageActions.READ, PolarisStorageActions.LIST, PolarisStorageActions.WRITE);
+  }
+
+  @Test
+  void registerTableOverwriteWithVendedCredentialsVendsReadActionsForMissingTable() {
+    Catalog catalog = mockRegisterTableCatalog(true);
+    when(catalogEntity.isExternal()).thenReturn(false);
+    when(accessDelegationModeResolver.resolve(eq(EnumSet.of(VENDED_CREDENTIALS)), any()))
+        .thenReturn(Optional.of(VENDED_CREDENTIALS));
+    doThrow(new ForbiddenException("write delegation denied"))
+        .when(authorizer)
+        .authorizeOrThrow(
+            any(),
+            any(),
+            eq(PolarisAuthorizableOperation.REGISTER_TABLE_WITH_WRITE_DELEGATION),
+            nullable(PolarisResolvedPathWrapper.class),
+            nullable(PolarisResolvedPathWrapper.class));
+
+    @SuppressWarnings("resource")
+    IcebergCatalogHandler handler = newHandler();
+    when(resolutionManifest.getResolvedPath(
+            eq(ResolvedPathKey.ofTableLike(TABLE2)),
+            eq(PolarisEntitySubType.ICEBERG_TABLE),
+            anyBoolean()))
+        .thenReturn(null);
+
+    LoadTableResponse response =
+        handler.registerTable(
+            NS1, registerTableRequest(true), EnumSet.of(VENDED_CREDENTIALS), Optional.empty());
+
+    verify(catalog).registerTable(TABLE2, TABLE_LOCATION, true);
+    assertThat(response.credentials()).hasSize(1);
+    assertVendedActions(PolarisStorageActions.READ, PolarisStorageActions.LIST);
+  }
+
+  @Test
+  void loadCredentialsFallbackResolvesOnceThenAuthorizesReadDelegation() {
+    Catalog catalog = mockRegisterTableCatalog(false);
+    BaseTable table = baseTable();
+    when(catalog.loadTable(TABLE2)).thenReturn(table);
+    when(accessDelegationModeResolver.resolve(any(), any()))
+        .thenReturn(Optional.of(VENDED_CREDENTIALS));
+    doThrow(new ForbiddenException("write delegation denied"))
+        .when(authorizer)
+        .authorizeOrThrow(
+            any(),
+            any(),
+            eq(PolarisAuthorizableOperation.LOAD_TABLE_WITH_WRITE_DELEGATION),
+            nullable(PolarisResolvedPathWrapper.class),
+            nullable(PolarisResolvedPathWrapper.class));
+    @SuppressWarnings("unchecked")
+    ArgumentCaptor<AuthorizationRequest> requestCaptor =
+        ArgumentCaptor.forClass(AuthorizationRequest.class);
+    ArgumentCaptor<AuthorizationState> stateCaptor =
+        ArgumentCaptor.forClass(AuthorizationState.class);
+    ArgumentCaptor<PolarisAuthorizableOperation> operationCaptor =
+        ArgumentCaptor.forClass(PolarisAuthorizableOperation.class);
+
+    @SuppressWarnings("resource")
+    IcebergCatalogHandler handler = newHandler();
+
+    handler.loadCredentials(TABLE2, Optional.empty());
+
+    verify(authorizer).resolveAuthorizationInputs(stateCaptor.capture(), requestCaptor.capture());
+    assertThat(stateCaptor.getValue().getResolutionManifest()).isSameAs(resolutionManifest);
+    assertThat(requestCaptor.getValue().intents().getFirst().getOperation())
+        .isEqualTo(PolarisAuthorizableOperation.LOAD_TABLE_WITH_WRITE_DELEGATION);
+    verify(authorizer, org.mockito.Mockito.times(2))
+        .authorizeOrThrow(
+            any(),
+            any(),
+            operationCaptor.capture(),
+            nullable(PolarisResolvedPathWrapper.class),
+            nullable(PolarisResolvedPathWrapper.class));
+    assertThat(operationCaptor.getAllValues())
+        .containsExactly(
+            PolarisAuthorizableOperation.LOAD_TABLE_WITH_WRITE_DELEGATION,
+            PolarisAuthorizableOperation.LOAD_TABLE_WITH_READ_DELEGATION);
+  }
+
+  @Test
+  void updateTableResolvesOnceThenAuthorizesActualOperations() {
+    UpdateTableRequest request =
+        UpdateTableRequest.create(
+            TABLE2, List.of(), List.of(new MetadataUpdate.SetProperties(Map.of("k", "v"))));
+    Catalog catalog = mock(Catalog.class);
+    when(localCatalogFactory.createCatalog(any())).thenReturn(catalog);
+    when(realmConfig.getConfig(
+            eq(FeatureConfiguration.ENABLE_FINE_GRAINED_UPDATE_TABLE_PRIVILEGES),
+            eq(catalogEntity)))
+        .thenReturn(true);
+    when(catalogHandlerUtils.updateTable(eq(catalog), eq(TABLE2), any(UpdateTableRequest.class)))
+        .thenReturn(mock(LoadTableResponse.class));
+    @SuppressWarnings("unchecked")
+    ArgumentCaptor<AuthorizationRequest> requestCaptor =
+        ArgumentCaptor.forClass(AuthorizationRequest.class);
+    ArgumentCaptor<AuthorizationState> stateCaptor =
+        ArgumentCaptor.forClass(AuthorizationState.class);
+    ArgumentCaptor<PolarisAuthorizableOperation> operationCaptor =
+        ArgumentCaptor.forClass(PolarisAuthorizableOperation.class);
+
+    @SuppressWarnings("resource")
+    IcebergCatalogHandler handler = newHandler();
+
+    handler.updateTable(TABLE2, request);
+
+    verify(authorizer).resolveAuthorizationInputs(stateCaptor.capture(), requestCaptor.capture());
+    assertThat(stateCaptor.getValue().getResolutionManifest()).isSameAs(resolutionManifest);
+    assertThat(requestCaptor.getValue().intents().getFirst().getOperation())
+        .isEqualTo(PolarisAuthorizableOperation.UPDATE_TABLE);
+    verify(authorizer)
+        .authorizeOrThrow(
+            any(),
+            any(),
+            operationCaptor.capture(),
+            nullable(PolarisResolvedPathWrapper.class),
+            nullable(PolarisResolvedPathWrapper.class));
+    assertThat(operationCaptor.getValue())
+        .isEqualTo(PolarisAuthorizableOperation.SET_TABLE_PROPERTIES);
+  }
+
   /**
    * For external (non-Polaris) catalogs, loadCredentials must skip the optimized
    * entity-properties-based path and fall through to a full loadTable on the underlying catalog,
@@ -139,12 +397,11 @@ class IcebergCatalogHandlerTest {
    */
   @Test
   void loadCredentialsFallsBackForExternalCatalog() {
-    String tableLocation = "s3://fake-bucket/tables/table2";
     Map<String, String> fakeCredentials =
         Map.of("fake.access.key", "AKIAFAKE", "fake.secret.key", "fakeSecret");
 
     TableMetadata metadata = mock(TableMetadata.class);
-    when(metadata.location()).thenReturn(tableLocation);
+    when(metadata.location()).thenReturn(TABLE_LOCATION);
     when(metadata.properties()).thenReturn(Map.of());
     TableOperations ops = mock(TableOperations.class);
     when(ops.current()).thenReturn(metadata);
@@ -177,7 +434,7 @@ class IcebergCatalogHandlerTest {
         .singleElement()
         .satisfies(
             (Credential c) -> {
-              assertThat(c.prefix()).isEqualTo(tableLocation);
+              assertThat(c.prefix()).isEqualTo(TABLE_LOCATION);
               assertThat(c.config()).containsExactlyInAnyOrderEntriesOf(fakeCredentials);
             });
   }
@@ -288,6 +545,7 @@ class IcebergCatalogHandlerTest {
 
     // Missing LOCATION on the entity must force the fallback — loadTable is the proof.
     verify(icebergCatalog).loadTable(TABLE2);
+    verify(accessDelegationModeResolver).resolve(eq(EnumSet.of(VENDED_CREDENTIALS)), any());
     assertThat(response.credentials())
         .singleElement()
         .satisfies(
@@ -296,4 +554,31 @@ class IcebergCatalogHandlerTest {
               assertThat(c.config()).containsExactlyInAnyOrderEntriesOf(fakeCredentials);
             });
   }
+
+  @Test
+  void loadCredentialsFallbackPreservesDelegationModeValidation() {
+    PolarisEntity leafEntity =
+        new PolarisEntity(
+            new PolarisBaseEntity.Builder()
+                .typeCode(PolarisEntityType.TABLE_LIKE.getCode())
+                .subTypeCode(PolarisEntitySubType.ICEBERG_TABLE.getCode())
+                .name(TABLE2.name())
+                .internalPropertiesAsMap(Map.of())
+                .build());
+    when(resolvedPath.getRawLeafEntity()).thenReturn(leafEntity);
+
+    LocalIcebergCatalog icebergCatalog = mock(LocalIcebergCatalog.class);
+    when(localCatalogFactory.createCatalog(any())).thenReturn(icebergCatalog);
+
+    when(accessDelegationModeResolver.resolve(eq(EnumSet.of(VENDED_CREDENTIALS)), any()))
+        .thenThrow(new IllegalArgumentException("credential vending disabled"));
+
+    @SuppressWarnings("resource")
+    IcebergCatalogHandler handler = newHandler();
+
+    assertThatThrownBy(() -> handler.loadCredentials(TABLE2, Optional.empty()))
+        .isInstanceOf(IllegalArgumentException.class)
+        .hasMessageContaining("credential vending disabled");
+    verify(icebergCatalog, never()).loadTable(any());
+  }
 }
diff --git a/runtime/service/src/test/java/org/apache/polaris/service/catalog/policy/AbstractPolicyCatalogTest.java b/runtime/service/src/test/java/org/apache/polaris/service/catalog/policy/AbstractPolicyCatalogTest.java
index cb302eb9c8..6cd0c39972 100644
--- a/runtime/service/src/test/java/org/apache/polaris/service/catalog/policy/AbstractPolicyCatalogTest.java
+++ b/runtime/service/src/test/java/org/apache/polaris/service/catalog/policy/AbstractPolicyCatalogTest.java
@@ -73,6 +73,7 @@ import org.apache.polaris.core.storage.aws.AwsCredentialsStorageIntegration;
 import org.apache.polaris.core.storage.aws.AwsStorageConfigurationInfo;
 import org.apache.polaris.core.storage.cache.StorageCredentialCache;
 import org.apache.polaris.service.admin.PolarisAdminService;
+import org.apache.polaris.service.admin.PolarisAdminServiceTestSupport;
 import org.apache.polaris.service.catalog.PolarisPassthroughResolutionView;
 import org.apache.polaris.service.catalog.iceberg.LocalIcebergCatalog;
 import org.apache.polaris.service.catalog.io.FileIOFactory;
@@ -178,7 +179,7 @@ public abstract class AbstractPolicyCatalogTest {
     ReservedProperties reservedProperties = ReservedProperties.NONE;
 
     adminService =
-        new PolarisAdminService(
+        PolarisAdminServiceTestSupport.newAdminService(
             polarisContext,
             resolutionManifestFactory,
             metaStoreManager,
diff --git a/runtime/service/src/test/java/org/apache/polaris/service/catalog/policy/PolicyCatalogHandlerAuthzTest.java b/runtime/service/src/test/java/org/apache/polaris/service/catalog/policy/PolicyCatalogHandlerAuthzTest.java
index 5fd566febd..d14a2ee325 100644
--- a/runtime/service/src/test/java/org/apache/polaris/service/catalog/policy/PolicyCatalogHandlerAuthzTest.java
+++ b/runtime/service/src/test/java/org/apache/polaris/service/catalog/policy/PolicyCatalogHandlerAuthzTest.java
@@ -20,7 +20,6 @@ package org.apache.polaris.service.catalog.policy;
 
 import io.quarkus.test.junit.QuarkusTest;
 import io.quarkus.test.junit.TestProfile;
-import jakarta.inject.Inject;
 import java.util.Arrays;
 import java.util.Set;
 import java.util.stream.Stream;
@@ -43,8 +42,6 @@ import org.junit.jupiter.api.TestFactory;
 @TestProfile(Profiles.PolarisAuthzBaseProfile.class)
 public class PolicyCatalogHandlerAuthzTest extends PolarisAuthzTestBase {
 
-  @Inject PolicyCatalogHandlerFactory policyCatalogHandlerFactory;
-
   private PolicyCatalogHandler newHandler() {
     return newHandler(Set.of());
   }
@@ -52,7 +49,14 @@ public class PolicyCatalogHandlerAuthzTest extends PolarisAuthzTestBase {
   private PolicyCatalogHandler newHandler(Set<String> activatedPrincipalRoles) {
     PolarisPrincipal authenticatedPrincipal =
         PolarisPrincipal.of(principalEntity, activatedPrincipalRoles);
-    return policyCatalogHandlerFactory.createHandler(CATALOG_NAME, authenticatedPrincipal);
+    return ImmutablePolicyCatalogHandler.builder()
+        .catalogName(CATALOG_NAME)
+        .polarisPrincipal(authenticatedPrincipal)
+        .callContext(callContext)
+        .resolutionManifestFactory(resolutionManifestFactory)
+        .metaStoreManager(metaStoreManager)
+        .authorizer(polarisAuthorizer)
+        .build();
   }
 
   @TestFactory
@@ -73,8 +77,9 @@ public class PolicyCatalogHandlerAuthzTest extends PolarisAuthzTestBase {
   @TestFactory
   Stream<DynamicNode> testCreatePolicyPrivileges() {
     assertSuccess(
-        adminService.grantPrivilegeOnCatalogToRole(
-            CATALOG_NAME, CATALOG_ROLE2, PolarisPrivilege.POLICY_DROP));
+        newRootAdminService()
+            .grantPrivilegeOnCatalogToRole(
+                CATALOG_NAME, CATALOG_ROLE2, PolarisPrivilege.POLICY_DROP));
 
     final PolicyIdentifier newPolicy = new PolicyIdentifier(NS2, "newPolicy");
     final CreatePolicyRequest createPolicyRequest =
@@ -140,8 +145,9 @@ public class PolicyCatalogHandlerAuthzTest extends PolarisAuthzTestBase {
   @TestFactory
   Stream<DynamicNode> testDropPolicyPrivileges() {
     assertSuccess(
-        adminService.grantPrivilegeOnCatalogToRole(
-            CATALOG_NAME, CATALOG_ROLE2, PolarisPrivilege.POLICY_CREATE));
+        newRootAdminService()
+            .grantPrivilegeOnCatalogToRole(
+                CATALOG_NAME, CATALOG_ROLE2, PolarisPrivilege.POLICY_CREATE));
 
     final CreatePolicyRequest createPolicyRequest =
         CreatePolicyRequest.builder()
@@ -171,11 +177,13 @@ public class PolicyCatalogHandlerAuthzTest extends PolarisAuthzTestBase {
   @TestFactory
   Stream<DynamicNode> testAttachPolicyToCatalogPrivileges() {
     assertSuccess(
-        adminService.grantPrivilegeOnCatalogToRole(
-            CATALOG_NAME, CATALOG_ROLE2, PolarisPrivilege.POLICY_DETACH));
+        newRootAdminService()
+            .grantPrivilegeOnCatalogToRole(
+                CATALOG_NAME, CATALOG_ROLE2, PolarisPrivilege.POLICY_DETACH));
     assertSuccess(
-        adminService.grantPrivilegeOnCatalogToRole(
-            CATALOG_NAME, CATALOG_ROLE2, PolarisPrivilege.CATALOG_DETACH_POLICY));
+        newRootAdminService()
+            .grantPrivilegeOnCatalogToRole(
+                CATALOG_NAME, CATALOG_ROLE2, PolarisPrivilege.CATALOG_DETACH_POLICY));
     PolicyAttachmentTarget catalogTarget =
         PolicyAttachmentTarget.builder().setType(PolicyAttachmentTarget.TypeEnum.CATALOG).build();
     AttachPolicyRequest attachPolicyRequest =
@@ -203,11 +211,13 @@ public class PolicyCatalogHandlerAuthzTest extends PolarisAuthzTestBase {
   @TestFactory
   Stream<DynamicNode> testAttachPolicyToNamespacePrivileges() {
     assertSuccess(
-        adminService.grantPrivilegeOnCatalogToRole(
-            CATALOG_NAME, CATALOG_ROLE2, PolarisPrivilege.POLICY_DETACH));
+        newRootAdminService()
+            .grantPrivilegeOnCatalogToRole(
+                CATALOG_NAME, CATALOG_ROLE2, PolarisPrivilege.POLICY_DETACH));
     assertSuccess(
-        adminService.grantPrivilegeOnCatalogToRole(
-            CATALOG_NAME, CATALOG_ROLE2, PolarisPrivilege.NAMESPACE_DETACH_POLICY));
+        newRootAdminService()
+            .grantPrivilegeOnCatalogToRole(
+                CATALOG_NAME, CATALOG_ROLE2, PolarisPrivilege.NAMESPACE_DETACH_POLICY));
 
     PolicyAttachmentTarget namespaceTarget =
         PolicyAttachmentTarget.builder()
@@ -239,11 +249,13 @@ public class PolicyCatalogHandlerAuthzTest extends PolarisAuthzTestBase {
   @TestFactory
   Stream<DynamicNode> testAttachPolicyToTablePrivileges() {
     assertSuccess(
-        adminService.grantPrivilegeOnCatalogToRole(
-            CATALOG_NAME, CATALOG_ROLE2, PolarisPrivilege.POLICY_DETACH));
+        newRootAdminService()
+            .grantPrivilegeOnCatalogToRole(
+                CATALOG_NAME, CATALOG_ROLE2, PolarisPrivilege.POLICY_DETACH));
     assertSuccess(
-        adminService.grantPrivilegeOnCatalogToRole(
-            CATALOG_NAME, CATALOG_ROLE2, PolarisPrivilege.TABLE_DETACH_POLICY));
+        newRootAdminService()
+            .grantPrivilegeOnCatalogToRole(
+                CATALOG_NAME, CATALOG_ROLE2, PolarisPrivilege.TABLE_DETACH_POLICY));
 
     PolicyAttachmentTarget tableTarget =
         PolicyAttachmentTarget.builder()
@@ -275,17 +287,21 @@ public class PolicyCatalogHandlerAuthzTest extends PolarisAuthzTestBase {
   @TestFactory
   Stream<DynamicNode> testDetachPolicyFromCatalogPrivileges() {
     assertSuccess(
-        adminService.grantPrivilegeOnCatalogToRole(
-            CATALOG_NAME, CATALOG_ROLE2, PolarisPrivilege.POLICY_ATTACH));
+        newRootAdminService()
+            .grantPrivilegeOnCatalogToRole(
+                CATALOG_NAME, CATALOG_ROLE2, PolarisPrivilege.POLICY_ATTACH));
     assertSuccess(
-        adminService.grantPrivilegeOnCatalogToRole(
-            CATALOG_NAME, CATALOG_ROLE2, PolarisPrivilege.CATALOG_ATTACH_POLICY));
+        newRootAdminService()
+            .grantPrivilegeOnCatalogToRole(
+                CATALOG_NAME, CATALOG_ROLE2, PolarisPrivilege.CATALOG_ATTACH_POLICY));
     assertSuccess(
-        adminService.grantPrivilegeOnCatalogToRole(
-            CATALOG_NAME, CATALOG_ROLE2, PolarisPrivilege.POLICY_DETACH));
+        newRootAdminService()
+            .grantPrivilegeOnCatalogToRole(
+                CATALOG_NAME, CATALOG_ROLE2, PolarisPrivilege.POLICY_DETACH));
     assertSuccess(
-        adminService.grantPrivilegeOnCatalogToRole(
-            CATALOG_NAME, CATALOG_ROLE2, PolarisPrivilege.CATALOG_DETACH_POLICY));
+        newRootAdminService()
+            .grantPrivilegeOnCatalogToRole(
+                CATALOG_NAME, CATALOG_ROLE2, PolarisPrivilege.CATALOG_DETACH_POLICY));
     PolicyAttachmentTarget catalogTarget =
         PolicyAttachmentTarget.builder().setType(PolicyAttachmentTarget.TypeEnum.CATALOG).build();
     AttachPolicyRequest attachPolicyRequest =
@@ -315,17 +331,21 @@ public class PolicyCatalogHandlerAuthzTest extends PolarisAuthzTestBase {
   @TestFactory
   Stream<DynamicNode> testDetachPolicyFromNamespacePrivileges() {
     assertSuccess(
-        adminService.grantPrivilegeOnCatalogToRole(
-            CATALOG_NAME, CATALOG_ROLE2, PolarisPrivilege.POLICY_ATTACH));
+        newRootAdminService()
+            .grantPrivilegeOnCatalogToRole(
+                CATALOG_NAME, CATALOG_ROLE2, PolarisPrivilege.POLICY_ATTACH));
     assertSuccess(
-        adminService.grantPrivilegeOnCatalogToRole(
-            CATALOG_NAME, CATALOG_ROLE2, PolarisPrivilege.NAMESPACE_ATTACH_POLICY));
+        newRootAdminService()
+            .grantPrivilegeOnCatalogToRole(
+                CATALOG_NAME, CATALOG_ROLE2, PolarisPrivilege.NAMESPACE_ATTACH_POLICY));
     assertSuccess(
-        adminService.grantPrivilegeOnCatalogToRole(
-            CATALOG_NAME, CATALOG_ROLE2, PolarisPrivilege.POLICY_DETACH));
+        newRootAdminService()
+            .grantPrivilegeOnCatalogToRole(
+                CATALOG_NAME, CATALOG_ROLE2, PolarisPrivilege.POLICY_DETACH));
     assertSuccess(
-        adminService.grantPrivilegeOnCatalogToRole(
-            CATALOG_NAME, CATALOG_ROLE2, PolarisPrivilege.NAMESPACE_DETACH_POLICY));
+        newRootAdminService()
+            .grantPrivilegeOnCatalogToRole(
+                CATALOG_NAME, CATALOG_ROLE2, PolarisPrivilege.NAMESPACE_DETACH_POLICY));
 
     PolicyAttachmentTarget namespaceTarget =
         PolicyAttachmentTarget.builder()
@@ -359,17 +379,21 @@ public class PolicyCatalogHandlerAuthzTest extends PolarisAuthzTestBase {
   @TestFactory
   Stream<DynamicNode> testDetachPolicyFromTablePrivileges() {
     assertSuccess(
-        adminService.grantPrivilegeOnCatalogToRole(
-            CATALOG_NAME, CATALOG_ROLE2, PolarisPrivilege.POLICY_ATTACH));
+        newRootAdminService()
+            .grantPrivilegeOnCatalogToRole(
+                CATALOG_NAME, CATALOG_ROLE2, PolarisPrivilege.POLICY_ATTACH));
     assertSuccess(
-        adminService.grantPrivilegeOnCatalogToRole(
-            CATALOG_NAME, CATALOG_ROLE2, PolarisPrivilege.TABLE_ATTACH_POLICY));
+        newRootAdminService()
+            .grantPrivilegeOnCatalogToRole(
+                CATALOG_NAME, CATALOG_ROLE2, PolarisPrivilege.TABLE_ATTACH_POLICY));
     assertSuccess(
-        adminService.grantPrivilegeOnCatalogToRole(
-            CATALOG_NAME, CATALOG_ROLE2, PolarisPrivilege.POLICY_DETACH));
+        newRootAdminService()
+            .grantPrivilegeOnCatalogToRole(
+                CATALOG_NAME, CATALOG_ROLE2, PolarisPrivilege.POLICY_DETACH));
     assertSuccess(
-        adminService.grantPrivilegeOnCatalogToRole(
-            CATALOG_NAME, CATALOG_ROLE2, PolarisPrivilege.TABLE_DETACH_POLICY));
+        newRootAdminService()
+            .grantPrivilegeOnCatalogToRole(
+                CATALOG_NAME, CATALOG_ROLE2, PolarisPrivilege.TABLE_DETACH_POLICY));
 
     PolicyAttachmentTarget tableTarget =
         PolicyAttachmentTarget.builder()
diff --git a/runtime/service/src/testFixtures/java/org/apache/polaris/service/TestServices.java b/runtime/service/src/testFixtures/java/org/apache/polaris/service/TestServices.java
index 549d9f26b2..e0b9d5ae86 100644
--- a/runtime/service/src/testFixtures/java/org/apache/polaris/service/TestServices.java
+++ b/runtime/service/src/testFixtures/java/org/apache/polaris/service/TestServices.java
@@ -37,6 +37,7 @@ import java.util.function.UnaryOperator;
 import org.apache.polaris.core.PolarisCallContext;
 import org.apache.polaris.core.PolarisDefaultDiagServiceImpl;
 import org.apache.polaris.core.PolarisDiagnostics;
+import org.apache.polaris.core.auth.AuthorizationState;
 import org.apache.polaris.core.auth.PolarisAuthorizer;
 import org.apache.polaris.core.auth.PolarisPrincipal;
 import org.apache.polaris.core.catalog.FederatedCatalogFactory;
@@ -112,11 +113,11 @@ import software.amazon.awssdk.services.sts.model.Credentials;
 
 public record TestServices(
     Clock clock,
-    PolarisCatalogsApi catalogsApi,
-    IcebergRestCatalogApi restApi,
-    PolarisCatalogGenericTableApi genericTableApi,
-    IcebergRestConfigurationApi restConfigurationApi,
-    IcebergCatalogAdapter catalogAdapter,
+    Supplier<PolarisCatalogsApi> catalogsApiSupplier,
+    Supplier<IcebergRestCatalogApi> restApiSupplier,
+    Supplier<PolarisCatalogGenericTableApi> genericTableApiSupplier,
+    Supplier<IcebergRestConfigurationApi> restConfigurationApiSupplier,
+    Supplier<IcebergCatalogAdapter> catalogAdapterSupplier,
     RealmConfigurationSource configurationSource,
     PolarisDiagnostics polarisDiagnostics,
     StorageCredentialCache storageCredentialCache,
@@ -134,6 +135,26 @@ public record TestServices(
     PolarisEventMetadataFactory eventMetadataFactory,
     StorageAccessConfigProvider storageAccessConfigProvider) {
 
+  public PolarisCatalogsApi catalogsApi() {
+    return catalogsApiSupplier.get();
+  }
+
+  public IcebergRestCatalogApi restApi() {
+    return restApiSupplier.get();
+  }
+
+  public PolarisCatalogGenericTableApi genericTableApi() {
+    return genericTableApiSupplier.get();
+  }
+
+  public IcebergRestConfigurationApi restConfigurationApi() {
+    return restConfigurationApiSupplier.get();
+  }
+
+  public IcebergCatalogAdapter catalogAdapter() {
+    return catalogAdapterSupplier.get();
+  }
+
   private static final RealmContext TEST_REALM = () -> "test-realm";
   private static final String GCP_ACCESS_TOKEN = "abc";
 
@@ -213,6 +234,14 @@ public record TestServices(
     public TestServices build() {
       RealmConfigurationSource configurationSource = (rc, name) -> config.get(name);
       PolarisAuthorizer authorizer = Mockito.mock(PolarisAuthorizer.class);
+      Mockito.doAnswer(
+              invocation -> {
+                AuthorizationState authzState = invocation.getArgument(0);
+                authzState.getResolutionManifest().resolveAll();
+                return null;
+              })
+          .when(authorizer)
+          .resolveAuthorizationInputs(any(), any());
 
       // Application level
       StorageCredentialCacheConfig storageCredentialCacheConfig = () -> 10_000;
@@ -345,117 +374,131 @@ public record TestServices(
 
       EventAttributeMap eventAttributeMap = new EventAttributeMap();
 
-      IcebergCatalogHandlerFactory handlerFactory =
-          new IcebergCatalogHandlerFactory() {
-            @Override
-            public IcebergCatalogHandler createHandler(
-                String catalogName, PolarisPrincipal principal) {
-              return ImmutableIcebergCatalogHandler.builder()
-                  .catalogName(catalogName)
-                  .polarisPrincipal(principal)
-                  .diagnostics(diagnostics)
-                  .callContext(callContext)
-                  .prefixParser(new DefaultCatalogPrefixParser())
-                  .resolverFactory(resolverFactory)
-                  .resolutionManifestFactory(resolutionManifestFactory)
-                  .metaStoreManager(metaStoreManager)
-                  .credentialManager(credentialManager)
-                  .localCatalogFactory(localCatalogFactory)
-                  .authorizer(authorizer)
-                  .reservedProperties(reservedProperties)
-                  .catalogHandlerUtils(catalogHandlerUtils)
-                  .federatedCatalogFactories(federatedCatalogFactory)
-                  .storageAccessConfigProvider(storageAccessConfigProvider)
-                  .eventAttributeMap(eventAttributeMap)
-                  .metricsReporter(new DefaultMetricsReporter())
-                  .clock(clock)
-                  .accessDelegationModeResolver(
-                      new DefaultAccessDelegationModeResolver(realmConfig))
-                  .build();
+      Supplier<IcebergCatalogAdapter> catalogAdapterSupplier =
+          () -> {
+            IcebergCatalogHandlerFactory handlerFactory =
+                new IcebergCatalogHandlerFactory() {
+                  @Override
+                  public IcebergCatalogHandler createHandler(
+                      String catalogName, PolarisPrincipal principal) {
+                    return ImmutableIcebergCatalogHandler.builder()
+                        .catalogName(catalogName)
+                        .polarisPrincipal(principal)
+                        .diagnostics(diagnostics)
+                        .callContext(callContext)
+                        .prefixParser(new DefaultCatalogPrefixParser())
+                        .resolverFactory(resolverFactory)
+                        .resolutionManifestFactory(resolutionManifestFactory)
+                        .metaStoreManager(metaStoreManager)
+                        .credentialManager(credentialManager)
+                        .localCatalogFactory(localCatalogFactory)
+                        .authorizer(authorizer)
+                        .reservedProperties(reservedProperties)
+                        .catalogHandlerUtils(catalogHandlerUtils)
+                        .federatedCatalogFactories(federatedCatalogFactory)
+                        .storageAccessConfigProvider(storageAccessConfigProvider)
+                        .eventAttributeMap(eventAttributeMap)
+                        .metricsReporter(new DefaultMetricsReporter())
+                        .clock(clock)
+                        .accessDelegationModeResolver(
+                            new DefaultAccessDelegationModeResolver(realmConfig))
+                        .build();
+                  }
+                };
+
+            return new IcebergCatalogAdapter(
+                callContext, new DefaultCatalogPrefixParser(), reservedProperties, handlerFactory);
+          };
+
+      Supplier<IcebergRestCatalogApi> restApiSupplier =
+          () -> {
+            IcebergCatalogAdapter catalogService = catalogAdapterSupplier.get();
+            IcebergRestCatalogApiService finalRestCatalogService = catalogService;
+            if (useEventDelegator) {
+              finalRestCatalogService =
+                  new IcebergRestCatalogEventServiceDelegator(
+                      catalogService,
+                      polarisEventDispatcher,
+                      eventMetadataFactory,
+                      new DefaultCatalogPrefixParser(),
+                      eventAttributeMap);
             }
+            return new IcebergRestCatalogApi(finalRestCatalogService);
           };
 
-      IcebergCatalogAdapter catalogService =
-          new IcebergCatalogAdapter(
-              callContext, new DefaultCatalogPrefixParser(), reservedProperties, handlerFactory);
-
-      // Optionally wrap with event delegator
-      IcebergRestCatalogApiService finalRestCatalogService = catalogService;
-      IcebergRestConfigurationApiService finalRestConfigurationService = catalogService;
-      if (useEventDelegator) {
-        finalRestCatalogService =
-            new IcebergRestCatalogEventServiceDelegator(
-                catalogService,
-                polarisEventDispatcher,
-                eventMetadataFactory,
-                new DefaultCatalogPrefixParser(),
-                eventAttributeMap);
-        finalRestConfigurationService =
-            new IcebergRestConfigurationEventServiceDelegator(
-                catalogService, polarisEventDispatcher, eventMetadataFactory);
-      }
-
-      IcebergRestCatalogApi restApi = new IcebergRestCatalogApi(finalRestCatalogService);
-      IcebergRestConfigurationApi restConfigurationApi =
-          new IcebergRestConfigurationApi(finalRestConfigurationService);
-
-      GenericTableCatalogHandlerFactory genericHandlerFactory =
-          new GenericTableCatalogHandlerFactory() {
-            @Override
-            public GenericTableCatalogHandler createHandler(
-                String catalogName, PolarisPrincipal principal) {
-              return ImmutableGenericTableCatalogHandler.builder()
-                  .catalogName(catalogName)
-                  .polarisPrincipal(principal)
-                  .callContext(callContext)
-                  .resolutionManifestFactory(resolutionManifestFactory)
-                  .metaStoreManager(metaStoreManager)
-                  .authorizer(authorizer)
-                  .credentialManager(credentialManager)
-                  .federatedCatalogFactories(federatedCatalogFactory)
-                  .build();
+      Supplier<IcebergRestConfigurationApi> restConfigurationApiSupplier =
+          () -> {
+            IcebergCatalogAdapter catalogService = catalogAdapterSupplier.get();
+            IcebergRestConfigurationApiService finalRestConfigurationService = catalogService;
+            if (useEventDelegator) {
+              finalRestConfigurationService =
+                  new IcebergRestConfigurationEventServiceDelegator(
+                      catalogService, polarisEventDispatcher, eventMetadataFactory);
             }
+            return new IcebergRestConfigurationApi(finalRestConfigurationService);
+          };
+
+      Supplier<PolarisCatalogGenericTableApi> genericTableApiSupplier =
+          () -> {
+            GenericTableCatalogHandlerFactory genericHandlerFactory =
+                new GenericTableCatalogHandlerFactory() {
+                  @Override
+                  public GenericTableCatalogHandler createHandler(
+                      String catalogName, PolarisPrincipal principal) {
+                    return ImmutableGenericTableCatalogHandler.builder()
+                        .catalogName(catalogName)
+                        .polarisPrincipal(principal)
+                        .callContext(callContext)
+                        .resolutionManifestFactory(resolutionManifestFactory)
+                        .metaStoreManager(metaStoreManager)
+                        .authorizer(authorizer)
+                        .credentialManager(credentialManager)
+                        .federatedCatalogFactories(federatedCatalogFactory)
+                        .build();
+                  }
+                };
+            GenericTableCatalogAdapter genericTableCatalogAdapter =
+                new GenericTableCatalogAdapter(
+                    callContext,
+                    new DefaultCatalogPrefixParser(),
+                    reservedProperties,
+                    genericHandlerFactory);
+            PolarisCatalogGenericTableApiService genericTableService = genericTableCatalogAdapter;
+            if (useEventDelegator) {
+              genericTableService =
+                  new CatalogGenericTableEventServiceDelegator(
+                      genericTableCatalogAdapter,
+                      polarisEventDispatcher,
+                      eventMetadataFactory,
+                      new DefaultCatalogPrefixParser());
+            }
+            return new PolarisCatalogGenericTableApi(genericTableService);
+          };
+
+      Supplier<PolarisCatalogsApi> catalogsApiSupplier =
+          () -> {
+            PolarisAdminService adminService =
+                new PolarisAdminService(
+                    callContext,
+                    resolutionManifestFactory,
+                    metaStoreManager,
+                    userSecretsManager,
+                    serviceIdentityProvider,
+                    principal,
+                    authorizer,
+                    reservedProperties);
+            return new PolarisCatalogsApi(
+                new PolarisServiceImpl(
+                    realmConfig, reservedProperties, adminService, serviceIdentityProvider));
           };
-      GenericTableCatalogAdapter genericTableCatalogAdapter =
-          new GenericTableCatalogAdapter(
-              callContext,
-              new DefaultCatalogPrefixParser(),
-              reservedProperties,
-              genericHandlerFactory);
-      PolarisCatalogGenericTableApiService genericTableService = genericTableCatalogAdapter;
-      if (useEventDelegator) {
-        genericTableService =
-            new CatalogGenericTableEventServiceDelegator(
-                genericTableCatalogAdapter,
-                polarisEventDispatcher,
-                eventMetadataFactory,
-                new DefaultCatalogPrefixParser());
-      }
-      PolarisCatalogGenericTableApi genericTableApi =
-          new PolarisCatalogGenericTableApi(genericTableService);
-
-      PolarisAdminService adminService =
-          new PolarisAdminService(
-              callContext,
-              resolutionManifestFactory,
-              metaStoreManager,
-              userSecretsManager,
-              serviceIdentityProvider,
-              principal,
-              authorizer,
-              reservedProperties);
-      PolarisCatalogsApi catalogsApi =
-          new PolarisCatalogsApi(
-              new PolarisServiceImpl(
-                  realmConfig, reservedProperties, adminService, serviceIdentityProvider));
 
       return new TestServices(
           clock,
-          catalogsApi,
-          restApi,
-          genericTableApi,
-          restConfigurationApi,
-          catalogService,
+          catalogsApiSupplier,
+          restApiSupplier,
+          genericTableApiSupplier,
+          restConfigurationApiSupplier,
+          catalogAdapterSupplier,
           configurationSource,
           diagnostics,
           storageCredentialCache,