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

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


The following commit(s) were added to refs/heads/main by this push:
     new 34272ba4fa [#13692] feat(policy): define read restriction policy 
(#13693)
34272ba4fa is described below

commit 34272ba4fa954c671a927619a72850ffdb5b47b5
Author: roryqi <[email protected]>
AuthorDate: Fri Oct 9 11:37:45 2026 +0800

    [#13692] feat(policy): define read restriction policy (#13693)
    
    ### What changes were proposed in this pull request?
    
    Add the first policy-model implementation slice for tag-based Iceberg
    read restrictions:
    
    - Add the built-in `system_row_filter` and `system_column_mask` policy
    types.
    - Define distinct typed content for row filters and column masks.
    - Store exactly one restricted Rego `expression` in each policy, aligned
    with #13546.
    - Share non-blank and 16 KiB UTF-8 source validation.
    - Support create, update, REST serialization, DTO conversion, and
    persisted-content round trips.
    - Reject legacy `rules` content and content that does not match its
    built-in policy type.
    
    Expression parsing, subject and schema binding, policy resolution, and
    Iceberg REST enforcement remain follow-up work.
    
    ### Why are the changes needed?
    
    The tag-based read-restriction design needs typed policy content before
    expressions can be parsed, resolved, and returned through Iceberg REST.
    
    Fix: #13692
    
    ### Does this PR introduce _any_ user-facing change?
    
    Yes. It adds the `system_row_filter` and `system_column_mask` built-in
    policy types and their Java and REST content shape:
    
    ```json
    {
      "expression": "..."
    }
    ```
    
    ### How was this patch tested?
    
    - `./gradlew :api:spotlessApply :common:spotlessApply
    :core:spotlessApply`
    - `./gradlew :api:javadoc :api:test --tests
    org.apache.gravitino.policy.TestReadRestrictionContent --tests
    org.apache.gravitino.policy.TestPolicyBuiltInType`
    - `./gradlew :common:test --tests
    org.apache.gravitino.dto.policy.TestReadRestrictionPolicyDTO`
    - `./gradlew :core:test --tests
    org.apache.gravitino.meta.TestPolicyEntity`
    - `git diff upstream/main...HEAD --check`
---
 .../apache/gravitino/policy/ColumnMaskContent.java |  91 +++++++++
 .../java/org/apache/gravitino/policy/Policy.java   |   6 +
 .../apache/gravitino/policy/PolicyContents.java    |  20 ++
 .../gravitino/policy/ReadRestrictionContent.java   |  52 +++++
 .../apache/gravitino/policy/RowFilterContent.java  |  91 +++++++++
 .../gravitino/policy/TestPolicyBuiltInType.java    |  15 ++
 .../policy/TestReadRestrictionContent.java         |  89 +++++++++
 .../gravitino/dto/policy/PolicyContentDTO.java     | 102 ++++++++++
 .../org/apache/gravitino/dto/policy/PolicyDTO.java |   8 +-
 .../dto/requests/PolicyCreateRequest.java          |   8 +-
 .../dto/requests/PolicyUpdateRequest.java          |   8 +-
 .../apache/gravitino/dto/util/DTOConverters.java   |  28 +++
 .../dto/policy/TestReadRestrictionPolicyDTO.java   | 214 +++++++++++++++++++++
 .../apache/gravitino/meta/TestPolicyEntity.java    |  31 +++
 .../apache/gravitino/policy/TestPolicyManager.java |  25 +++
 docs/open-api/policies.yaml                        | 116 ++++++++++-
 docs/policies.md                                   |  15 +-
 17 files changed, 911 insertions(+), 8 deletions(-)

diff --git 
a/api/src/main/java/org/apache/gravitino/policy/ColumnMaskContent.java 
b/api/src/main/java/org/apache/gravitino/policy/ColumnMaskContent.java
new file mode 100644
index 0000000000..a5120a6607
--- /dev/null
+++ b/api/src/main/java/org/apache/gravitino/policy/ColumnMaskContent.java
@@ -0,0 +1,91 @@
+/*
+ * 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.gravitino.policy;
+
+import com.google.common.collect.ImmutableSet;
+import java.util.Collections;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import javax.annotation.Nullable;
+import org.apache.gravitino.MetadataObject;
+
+/** Built-in policy content for masking columns of tagged tables or tagged 
columns. */
+public final class ColumnMaskContent extends ReadRestrictionContent {
+
+  private static final Set<MetadataObject.Type> SUPPORTED_OBJECT_TYPES =
+      ImmutableSet.of(MetadataObject.Type.TABLE, MetadataObject.Type.COLUMN);
+
+  @Nullable private final String expression;
+
+  /** Default constructor for Jackson deserialization only. */
+  private ColumnMaskContent() {
+    this.expression = null;
+  }
+
+  ColumnMaskContent(String expression) {
+    this.expression = expression;
+    validate();
+  }
+
+  /**
+   * Returns the authored column-mask expression.
+   *
+   * @return column-mask expression
+   */
+  public String expression() {
+    return expression;
+  }
+
+  /** {@inheritDoc} */
+  @Override
+  public Set<MetadataObject.Type> supportedObjectTypes() {
+    return SUPPORTED_OBJECT_TYPES;
+  }
+
+  @Override
+  public Map<String, Object> rules() {
+    return Collections.singletonMap("expression", expression);
+  }
+
+  @Override
+  public void validate() throws IllegalArgumentException {
+    super.validate();
+    validateSource(expression, "column-mask expression");
+  }
+
+  @Override
+  public boolean equals(Object other) {
+    if (!(other instanceof ColumnMaskContent)) {
+      return false;
+    }
+    ColumnMaskContent that = (ColumnMaskContent) other;
+    return Objects.equals(expression, that.expression);
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hash(expression);
+  }
+
+  @Override
+  public String toString() {
+    return "ColumnMaskContent{" + "expression='" + expression + '\'' + '}';
+  }
+}
diff --git a/api/src/main/java/org/apache/gravitino/policy/Policy.java 
b/api/src/main/java/org/apache/gravitino/policy/Policy.java
index 04d30b45c1..80519d1157 100644
--- a/api/src/main/java/org/apache/gravitino/policy/Policy.java
+++ b/api/src/main/java/org/apache/gravitino/policy/Policy.java
@@ -47,6 +47,12 @@ public interface Policy extends Auditable {
         BUILT_IN_TYPE_PREFIX + "iceberg_orphan_file_removal",
         IcebergOrphanFileRemovalContent.class),
 
+    /** Built-in policy type for filtering rows of tagged tables. */
+    ROW_FILTER(BUILT_IN_TYPE_PREFIX + "row_filter", RowFilterContent.class),
+
+    /** Built-in policy type for masking columns of tagged tables or tagged 
columns. */
+    COLUMN_MASK(BUILT_IN_TYPE_PREFIX + "column_mask", ColumnMaskContent.class),
+
     /** Non-built-in policies use the fixed wire value {@code custom}. */
     CUSTOM("custom", PolicyContents.CustomContent.class);
 
diff --git a/api/src/main/java/org/apache/gravitino/policy/PolicyContents.java 
b/api/src/main/java/org/apache/gravitino/policy/PolicyContents.java
index f767d98f75..50e16ac5fe 100644
--- a/api/src/main/java/org/apache/gravitino/policy/PolicyContents.java
+++ b/api/src/main/java/org/apache/gravitino/policy/PolicyContents.java
@@ -127,6 +127,26 @@ public class PolicyContents {
     return new IcebergOrphanFileRemovalContent(olderThanDays, location, 
dryRun);
   }
 
+  /**
+   * Creates a row-filter policy for tagged tables.
+   *
+   * @param expression row-filter expression
+   * @return validated read-restriction policy content
+   */
+  public static RowFilterContent rowFilter(String expression) {
+    return new RowFilterContent(expression);
+  }
+
+  /**
+   * Creates a column-mask policy for tagged tables or columns.
+   *
+   * @param expression column-mask expression
+   * @return validated read-restriction policy content
+   */
+  public static ColumnMaskContent columnMask(String expression) {
+    return new ColumnMaskContent(expression);
+  }
+
   private PolicyContents() {}
 
   /**
diff --git 
a/api/src/main/java/org/apache/gravitino/policy/ReadRestrictionContent.java 
b/api/src/main/java/org/apache/gravitino/policy/ReadRestrictionContent.java
new file mode 100644
index 0000000000..e1bb820df6
--- /dev/null
+++ b/api/src/main/java/org/apache/gravitino/policy/ReadRestrictionContent.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.gravitino.policy;
+
+import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableMap;
+import java.nio.charset.StandardCharsets;
+import java.util.Map;
+import javax.annotation.Nullable;
+import org.apache.commons.lang3.StringUtils;
+
+/** Shared behavior for built-in policies that restrict data reads. */
+public abstract class ReadRestrictionContent implements PolicyContent {
+
+  /** Maximum UTF-8 size of an authored expression. */
+  public static final int MAX_SOURCE_LENGTH_BYTES = 16 * 1024;
+
+  @Override
+  public Map<String, String> properties() {
+    return ImmutableMap.of();
+  }
+
+  @Override
+  public void validate() throws IllegalArgumentException {
+    PolicyContent.super.validate();
+  }
+
+  static void validateSource(@Nullable String source, String fieldName) {
+    Preconditions.checkArgument(StringUtils.isNotBlank(source), "%s cannot be 
blank", fieldName);
+    Preconditions.checkArgument(
+        source.getBytes(StandardCharsets.UTF_8).length <= 
MAX_SOURCE_LENGTH_BYTES,
+        "%s cannot exceed %s UTF-8 bytes",
+        fieldName,
+        MAX_SOURCE_LENGTH_BYTES);
+  }
+}
diff --git 
a/api/src/main/java/org/apache/gravitino/policy/RowFilterContent.java 
b/api/src/main/java/org/apache/gravitino/policy/RowFilterContent.java
new file mode 100644
index 0000000000..9a78e44fd3
--- /dev/null
+++ b/api/src/main/java/org/apache/gravitino/policy/RowFilterContent.java
@@ -0,0 +1,91 @@
+/*
+ * 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.gravitino.policy;
+
+import com.google.common.collect.ImmutableSet;
+import java.util.Collections;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import javax.annotation.Nullable;
+import org.apache.gravitino.MetadataObject;
+
+/** Built-in policy content for filtering rows of tagged tables. */
+public final class RowFilterContent extends ReadRestrictionContent {
+
+  private static final Set<MetadataObject.Type> SUPPORTED_OBJECT_TYPES =
+      ImmutableSet.of(MetadataObject.Type.TABLE);
+
+  @Nullable private final String expression;
+
+  /** Default constructor for Jackson deserialization only. */
+  private RowFilterContent() {
+    this.expression = null;
+  }
+
+  RowFilterContent(String expression) {
+    this.expression = expression;
+    validate();
+  }
+
+  /**
+   * Returns the authored row-filter expression.
+   *
+   * @return row-filter expression
+   */
+  public String expression() {
+    return expression;
+  }
+
+  /** {@inheritDoc} */
+  @Override
+  public Set<MetadataObject.Type> supportedObjectTypes() {
+    return SUPPORTED_OBJECT_TYPES;
+  }
+
+  @Override
+  public Map<String, Object> rules() {
+    return Collections.singletonMap("expression", expression);
+  }
+
+  @Override
+  public void validate() throws IllegalArgumentException {
+    super.validate();
+    validateSource(expression, "row-filter expression");
+  }
+
+  @Override
+  public boolean equals(Object other) {
+    if (!(other instanceof RowFilterContent)) {
+      return false;
+    }
+    RowFilterContent that = (RowFilterContent) other;
+    return Objects.equals(expression, that.expression);
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hash(expression);
+  }
+
+  @Override
+  public String toString() {
+    return "RowFilterContent{" + "expression='" + expression + '\'' + '}';
+  }
+}
diff --git 
a/api/src/test/java/org/apache/gravitino/policy/TestPolicyBuiltInType.java 
b/api/src/test/java/org/apache/gravitino/policy/TestPolicyBuiltInType.java
index 68d2065cf5..9e49f61326 100644
--- a/api/src/test/java/org/apache/gravitino/policy/TestPolicyBuiltInType.java
+++ b/api/src/test/java/org/apache/gravitino/policy/TestPolicyBuiltInType.java
@@ -29,6 +29,10 @@ public class TestPolicyBuiltInType {
     Assertions.assertEquals(
         Policy.BuiltInType.ICEBERG_COMPACTION,
         Policy.BuiltInType.fromPolicyType("system_iceberg_compaction"));
+    Assertions.assertEquals(
+        Policy.BuiltInType.ROW_FILTER, 
Policy.BuiltInType.fromPolicyType("system_row_filter"));
+    Assertions.assertEquals(
+        Policy.BuiltInType.COLUMN_MASK, 
Policy.BuiltInType.fromPolicyType("system_column_mask"));
   }
 
   @Test
@@ -40,15 +44,26 @@ public class TestPolicyBuiltInType {
     Assertions.assertTrue(exception.getMessage().contains("Unknown policy 
type"));
   }
 
+  @Test
+  void testSingleReadRestrictionTypeIsUnsupported() {
+    Assertions.assertThrows(
+        IllegalArgumentException.class,
+        () -> Policy.BuiltInType.fromPolicyType("system_read_restriction"));
+  }
+
   @Test
   void testBuiltInTypePolicyTypeValue() {
     Assertions.assertEquals(
         "system_iceberg_compaction", 
Policy.BuiltInType.ICEBERG_COMPACTION.policyType());
+    Assertions.assertEquals("system_row_filter", 
Policy.BuiltInType.ROW_FILTER.policyType());
+    Assertions.assertEquals("system_column_mask", 
Policy.BuiltInType.COLUMN_MASK.policyType());
   }
 
   @Test
   void testBuiltInTypeContentClass() {
     Assertions.assertEquals(
         IcebergDataCompactionContent.class, 
Policy.BuiltInType.ICEBERG_COMPACTION.contentClass());
+    Assertions.assertEquals(RowFilterContent.class, 
Policy.BuiltInType.ROW_FILTER.contentClass());
+    Assertions.assertEquals(ColumnMaskContent.class, 
Policy.BuiltInType.COLUMN_MASK.contentClass());
   }
 }
diff --git 
a/api/src/test/java/org/apache/gravitino/policy/TestReadRestrictionContent.java 
b/api/src/test/java/org/apache/gravitino/policy/TestReadRestrictionContent.java
new file mode 100644
index 0000000000..4939291249
--- /dev/null
+++ 
b/api/src/test/java/org/apache/gravitino/policy/TestReadRestrictionContent.java
@@ -0,0 +1,89 @@
+/*
+ * 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.gravitino.policy;
+
+import com.google.common.collect.ImmutableSet;
+import java.util.Map;
+import org.apache.gravitino.MetadataObject;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+public class TestReadRestrictionContent {
+
+  @Test
+  void testCreatesReadRestrictionContent() {
+    String rowFilterExpression =
+        "filter := col(\"region\") == \"US\" if is_group_member(\"analysts\") 
else := false";
+    String columnMaskExpression =
+        "mask := action(\"show-last-4\") if is_group_member(\"analysts\") "
+            + "else := action(\"replace-with-null\")";
+
+    RowFilterContent rowFilter = PolicyContents.rowFilter(rowFilterExpression);
+    ColumnMaskContent columnMask = 
PolicyContents.columnMask(columnMaskExpression);
+
+    Assertions.assertEquals(rowFilterExpression, rowFilter.expression());
+    Assertions.assertEquals(columnMaskExpression, columnMask.expression());
+    Assertions.assertEquals(Map.of("expression", rowFilterExpression), 
rowFilter.rules());
+    Assertions.assertEquals(Map.of("expression", columnMaskExpression), 
columnMask.rules());
+    Assertions.assertEquals(
+        ImmutableSet.of(MetadataObject.Type.TABLE), 
rowFilter.supportedObjectTypes());
+    Assertions.assertEquals(
+        ImmutableSet.of(MetadataObject.Type.TABLE, MetadataObject.Type.COLUMN),
+        columnMask.supportedObjectTypes());
+    Assertions.assertTrue(rowFilter.properties().isEmpty());
+    Assertions.assertTrue(columnMask.properties().isEmpty());
+    Assertions.assertDoesNotThrow(rowFilter::validate);
+    Assertions.assertDoesNotThrow(columnMask::validate);
+  }
+
+  @Test
+  void testEqualityUsesPolicyTypeAndExpression() {
+    String expression = "filter := col(\"region\") == \"US\"";
+
+    Assertions.assertEquals(
+        PolicyContents.rowFilter(expression), 
PolicyContents.rowFilter(expression));
+    Assertions.assertNotEquals(
+        PolicyContents.rowFilter(expression), PolicyContents.rowFilter("filter 
:= true"));
+    Assertions.assertNotEquals(
+        PolicyContents.rowFilter(expression), 
PolicyContents.columnMask(expression));
+  }
+
+  @Test
+  void testRejectsMissingOrBlankExpressions() {
+    Assertions.assertThrows(IllegalArgumentException.class, () -> 
PolicyContents.rowFilter(null));
+    Assertions.assertThrows(IllegalArgumentException.class, () -> 
PolicyContents.columnMask(null));
+    Assertions.assertThrows(IllegalArgumentException.class, () -> 
PolicyContents.rowFilter(" \t"));
+    Assertions.assertThrows(IllegalArgumentException.class, () -> 
PolicyContents.columnMask("\n"));
+  }
+
+  @Test
+  void testValidatesUtf8ExpressionLength() {
+    Assertions.assertEquals(16 * 1024, 
ReadRestrictionContent.MAX_SOURCE_LENGTH_BYTES);
+
+    String maximumLength = 
"é".repeat(ReadRestrictionContent.MAX_SOURCE_LENGTH_BYTES / 2);
+    String tooLong = maximumLength + "a";
+
+    Assertions.assertDoesNotThrow(() -> 
PolicyContents.rowFilter(maximumLength));
+    Assertions.assertDoesNotThrow(() -> 
PolicyContents.columnMask(maximumLength));
+    Assertions.assertThrows(
+        IllegalArgumentException.class, () -> 
PolicyContents.rowFilter(tooLong));
+    Assertions.assertThrows(
+        IllegalArgumentException.class, () -> 
PolicyContents.columnMask(tooLong));
+  }
+}
diff --git 
a/common/src/main/java/org/apache/gravitino/dto/policy/PolicyContentDTO.java 
b/common/src/main/java/org/apache/gravitino/dto/policy/PolicyContentDTO.java
index a0c2b71616..e3f0f77583 100644
--- a/common/src/main/java/org/apache/gravitino/dto/policy/PolicyContentDTO.java
+++ b/common/src/main/java/org/apache/gravitino/dto/policy/PolicyContentDTO.java
@@ -29,10 +29,12 @@ import lombok.Builder;
 import lombok.EqualsAndHashCode;
 import lombok.ToString;
 import org.apache.gravitino.MetadataObject;
+import org.apache.gravitino.policy.ColumnMaskContent;
 import org.apache.gravitino.policy.IcebergDataCompactionContent;
 import org.apache.gravitino.policy.IcebergOrphanFileRemovalContent;
 import org.apache.gravitino.policy.PolicyContent;
 import org.apache.gravitino.policy.PolicyContents;
+import org.apache.gravitino.policy.RowFilterContent;
 
 /** Represents a Policy Content Data Transfer Object (DTO). */
 public interface PolicyContentDTO extends PolicyContent {
@@ -273,4 +275,104 @@ public interface PolicyContentDTO extends PolicyContent {
       return PolicyContents.icebergOrphanFileRemoval(olderThanDays(), 
location(), dryRun());
     }
   }
+
+  /** Represents typed row-filter policy content. */
+  @EqualsAndHashCode
+  @ToString
+  @Builder(setterPrefix = "with")
+  @AllArgsConstructor(access = lombok.AccessLevel.PRIVATE)
+  class RowFilterContentDTO implements PolicyContentDTO {
+
+    @JsonProperty("expression")
+    @Nullable
+    private String expression;
+
+    // Default constructor for Jackson deserialization only.
+    private RowFilterContentDTO() {}
+
+    /**
+     * Returns the authored row-filter expression.
+     *
+     * @return row-filter expression
+     */
+    @Nullable
+    public String expression() {
+      return expression;
+    }
+
+    @Override
+    public Set<MetadataObject.Type> supportedObjectTypes() {
+      return toDomainContent().supportedObjectTypes();
+    }
+
+    @Override
+    public Map<String, String> properties() {
+      return toDomainContent().properties();
+    }
+
+    @Override
+    public Map<String, Object> rules() {
+      return toDomainContent().rules();
+    }
+
+    @Override
+    public void validate() throws IllegalArgumentException {
+      PolicyContentDTO.super.validate();
+      toDomainContent().validate();
+    }
+
+    private RowFilterContent toDomainContent() {
+      return PolicyContents.rowFilter(expression);
+    }
+  }
+
+  /** Represents typed column-mask policy content. */
+  @EqualsAndHashCode
+  @ToString
+  @Builder(setterPrefix = "with")
+  @AllArgsConstructor(access = lombok.AccessLevel.PRIVATE)
+  class ColumnMaskContentDTO implements PolicyContentDTO {
+
+    @JsonProperty("expression")
+    @Nullable
+    private String expression;
+
+    // Default constructor for Jackson deserialization only.
+    private ColumnMaskContentDTO() {}
+
+    /**
+     * Returns the authored column-mask expression.
+     *
+     * @return column-mask expression
+     */
+    @Nullable
+    public String expression() {
+      return expression;
+    }
+
+    @Override
+    public Set<MetadataObject.Type> supportedObjectTypes() {
+      return toDomainContent().supportedObjectTypes();
+    }
+
+    @Override
+    public Map<String, String> properties() {
+      return toDomainContent().properties();
+    }
+
+    @Override
+    public Map<String, Object> rules() {
+      return toDomainContent().rules();
+    }
+
+    @Override
+    public void validate() throws IllegalArgumentException {
+      PolicyContentDTO.super.validate();
+      toDomainContent().validate();
+    }
+
+    private ColumnMaskContent toDomainContent() {
+      return PolicyContents.columnMask(expression);
+    }
+  }
 }
diff --git 
a/common/src/main/java/org/apache/gravitino/dto/policy/PolicyDTO.java 
b/common/src/main/java/org/apache/gravitino/dto/policy/PolicyDTO.java
index ce65479c25..2f75784868 100644
--- a/common/src/main/java/org/apache/gravitino/dto/policy/PolicyDTO.java
+++ b/common/src/main/java/org/apache/gravitino/dto/policy/PolicyDTO.java
@@ -60,7 +60,13 @@ public class PolicyDTO implements Policy {
         name = "system_iceberg_compaction"),
     @JsonSubTypes.Type(
         value = PolicyContentDTO.IcebergOrphanFileRemovalContentDTO.class,
-        name = "system_iceberg_orphan_file_removal")
+        name = "system_iceberg_orphan_file_removal"),
+    @JsonSubTypes.Type(
+        value = PolicyContentDTO.RowFilterContentDTO.class,
+        name = "system_row_filter"),
+    @JsonSubTypes.Type(
+        value = PolicyContentDTO.ColumnMaskContentDTO.class,
+        name = "system_column_mask")
   })
   private PolicyContentDTO content;
 
diff --git 
a/common/src/main/java/org/apache/gravitino/dto/requests/PolicyCreateRequest.java
 
b/common/src/main/java/org/apache/gravitino/dto/requests/PolicyCreateRequest.java
index 9ec2e0ccf9..d5ef0430a7 100644
--- 
a/common/src/main/java/org/apache/gravitino/dto/requests/PolicyCreateRequest.java
+++ 
b/common/src/main/java/org/apache/gravitino/dto/requests/PolicyCreateRequest.java
@@ -64,7 +64,13 @@ public class PolicyCreateRequest implements RESTRequest {
         name = "system_iceberg_compaction"),
     @JsonSubTypes.Type(
         value = PolicyContentDTO.IcebergOrphanFileRemovalContentDTO.class,
-        name = "system_iceberg_orphan_file_removal")
+        name = "system_iceberg_orphan_file_removal"),
+    @JsonSubTypes.Type(
+        value = PolicyContentDTO.RowFilterContentDTO.class,
+        name = "system_row_filter"),
+    @JsonSubTypes.Type(
+        value = PolicyContentDTO.ColumnMaskContentDTO.class,
+        name = "system_column_mask")
   })
   private final PolicyContentDTO policyContent;
 
diff --git 
a/common/src/main/java/org/apache/gravitino/dto/requests/PolicyUpdateRequest.java
 
b/common/src/main/java/org/apache/gravitino/dto/requests/PolicyUpdateRequest.java
index 4e362466be..28a37aa25f 100644
--- 
a/common/src/main/java/org/apache/gravitino/dto/requests/PolicyUpdateRequest.java
+++ 
b/common/src/main/java/org/apache/gravitino/dto/requests/PolicyUpdateRequest.java
@@ -145,7 +145,13 @@ public interface PolicyUpdateRequest extends RESTRequest {
           name = "system_iceberg_compaction"),
       @JsonSubTypes.Type(
           value = PolicyContentDTO.IcebergOrphanFileRemovalContentDTO.class,
-          name = "system_iceberg_orphan_file_removal")
+          name = "system_iceberg_orphan_file_removal"),
+      @JsonSubTypes.Type(
+          value = PolicyContentDTO.RowFilterContentDTO.class,
+          name = "system_row_filter"),
+      @JsonSubTypes.Type(
+          value = PolicyContentDTO.ColumnMaskContentDTO.class,
+          name = "system_column_mask")
     })
     private final PolicyContentDTO newContent;
 
diff --git 
a/common/src/main/java/org/apache/gravitino/dto/util/DTOConverters.java 
b/common/src/main/java/org/apache/gravitino/dto/util/DTOConverters.java
index 14f6bc6d64..9851e8e4cd 100644
--- a/common/src/main/java/org/apache/gravitino/dto/util/DTOConverters.java
+++ b/common/src/main/java/org/apache/gravitino/dto/util/DTOConverters.java
@@ -104,10 +104,12 @@ import org.apache.gravitino.json.JsonUtils;
 import org.apache.gravitino.messaging.Topic;
 import org.apache.gravitino.model.Model;
 import org.apache.gravitino.model.ModelVersion;
+import org.apache.gravitino.policy.ColumnMaskContent;
 import org.apache.gravitino.policy.IcebergDataCompactionContent;
 import org.apache.gravitino.policy.IcebergOrphanFileRemovalContent;
 import org.apache.gravitino.policy.PolicyContent;
 import org.apache.gravitino.policy.PolicyContents;
+import org.apache.gravitino.policy.RowFilterContent;
 import org.apache.gravitino.rel.Column;
 import org.apache.gravitino.rel.Representation;
 import org.apache.gravitino.rel.SQLRepresentation;
@@ -697,6 +699,20 @@ public class DTOConverters {
           .build();
     }
 
+    if (policyContent instanceof RowFilterContent) {
+      RowFilterContent content = (RowFilterContent) policyContent;
+      return PolicyContentDTO.RowFilterContentDTO.builder()
+          .withExpression(content.expression())
+          .build();
+    }
+
+    if (policyContent instanceof ColumnMaskContent) {
+      ColumnMaskContent content = (ColumnMaskContent) policyContent;
+      return PolicyContentDTO.ColumnMaskContentDTO.builder()
+          .withExpression(content.expression())
+          .build();
+    }
+
     throw new IllegalArgumentException("Unsupported policy content: " + 
policyContent);
   }
 
@@ -1559,6 +1575,18 @@ public class DTOConverters {
           icebergCompactionContentDTO.rewriteOptions());
     }
 
+    if (policyContentDTO instanceof PolicyContentDTO.RowFilterContentDTO) {
+      PolicyContentDTO.RowFilterContentDTO contentDTO =
+          (PolicyContentDTO.RowFilterContentDTO) policyContentDTO;
+      return PolicyContents.rowFilter(contentDTO.expression());
+    }
+
+    if (policyContentDTO instanceof PolicyContentDTO.ColumnMaskContentDTO) {
+      PolicyContentDTO.ColumnMaskContentDTO contentDTO =
+          (PolicyContentDTO.ColumnMaskContentDTO) policyContentDTO;
+      return PolicyContents.columnMask(contentDTO.expression());
+    }
+
     throw new IllegalArgumentException(
         "Unsupported policy content type: " + policyContentDTO.getClass());
   }
diff --git 
a/common/src/test/java/org/apache/gravitino/dto/policy/TestReadRestrictionPolicyDTO.java
 
b/common/src/test/java/org/apache/gravitino/dto/policy/TestReadRestrictionPolicyDTO.java
new file mode 100644
index 0000000000..9c1dd80bde
--- /dev/null
+++ 
b/common/src/test/java/org/apache/gravitino/dto/policy/TestReadRestrictionPolicyDTO.java
@@ -0,0 +1,214 @@
+/*
+ * 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.gravitino.dto.policy;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException;
+import org.apache.gravitino.dto.requests.PolicyCreateRequest;
+import org.apache.gravitino.dto.requests.PolicyUpdateRequest;
+import org.apache.gravitino.dto.util.DTOConverters;
+import org.apache.gravitino.json.JsonUtils;
+import org.apache.gravitino.policy.ColumnMaskContent;
+import org.apache.gravitino.policy.Policy;
+import org.apache.gravitino.policy.PolicyContent;
+import org.apache.gravitino.policy.PolicyContents;
+import org.apache.gravitino.policy.RowFilterContent;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+public class TestReadRestrictionPolicyDTO {
+
+  @Test
+  void testRowFilterPolicyRoundTrip() throws JsonProcessingException {
+    String expression =
+        "filter := col(\"region\") == \"US\" if is_group_member(\"auditors\") "
+            + "else := col(\"owner\") == session_user()";
+    RowFilterContent content = PolicyContents.rowFilter(expression);
+    PolicyDTO policy =
+        PolicyDTO.builder()
+            .withName("owner-filter")
+            .withPolicyType("system_row_filter")
+            .withEnabled(true)
+            .withContent(DTOConverters.toDTO(content))
+            .build();
+
+    String json = JsonUtils.objectMapper().writeValueAsString(policy);
+    PolicyDTO restored = JsonUtils.objectMapper().readValue(json, 
PolicyDTO.class);
+
+    Assertions.assertEquals(policy, restored);
+    Assertions.assertInstanceOf(PolicyContentDTO.RowFilterContentDTO.class, 
restored.content());
+    Assertions.assertTrue(json.contains("\"expression\":\"filter :="));
+    Assertions.assertFalse(json.contains("\"rules\""));
+    Assertions.assertEquals(content, 
DTOConverters.fromDTO(restored.content()));
+    Assertions.assertDoesNotThrow(restored.content()::validate);
+  }
+
+  @Test
+  void testRowFilterCreateRequestRoundTrip() throws JsonProcessingException {
+    String json =
+        "{"
+            + "\"name\":\"region-filter\","
+            + "\"policyType\":\"system_row_filter\","
+            + "\"enabled\":true,"
+            + "\"content\":{\"expression\":"
+            + "\"filter := col(\\\"region\\\") == \\\"US\\\" if "
+            + "is_group_member(\\\"auditors\\\") else := false\"}}";
+
+    PolicyCreateRequest request =
+        JsonUtils.objectMapper().readValue(json, PolicyCreateRequest.class);
+    Assertions.assertInstanceOf(
+        PolicyContentDTO.RowFilterContentDTO.class, 
request.getPolicyContent());
+    Assertions.assertDoesNotThrow(request::validate);
+
+    RowFilterContent content = (RowFilterContent) 
DTOConverters.fromDTO(request.getPolicyContent());
+    Assertions.assertEquals(
+        "filter := col(\"region\") == \"US\" if is_group_member(\"auditors\") 
else := false",
+        content.expression());
+  }
+
+  @Test
+  void testColumnMaskCreateRequestRoundTrip() throws JsonProcessingException {
+    String json =
+        "{"
+            + "\"name\":\"pii-mask\","
+            + "\"policyType\":\"system_column_mask\","
+            + "\"enabled\":true,"
+            + "\"content\":{\"expression\":"
+            + "\"mask := action(\\\"show-last-4\\\") if "
+            + "is_group_member(\\\"pii_unmasked\\\") "
+            + "else := action(\\\"mask-alphanum\\\")\"}}";
+
+    PolicyCreateRequest request =
+        JsonUtils.objectMapper().readValue(json, PolicyCreateRequest.class);
+    Assertions.assertInstanceOf(
+        PolicyContentDTO.ColumnMaskContentDTO.class, 
request.getPolicyContent());
+    Assertions.assertDoesNotThrow(request::validate);
+
+    ColumnMaskContent content =
+        (ColumnMaskContent) DTOConverters.fromDTO(request.getPolicyContent());
+    Assertions.assertEquals(
+        "mask := action(\"show-last-4\") if is_group_member(\"pii_unmasked\") "
+            + "else := action(\"mask-alphanum\")",
+        content.expression());
+  }
+
+  @Test
+  void testUpdateRequestsUseEachPolicyType() throws JsonProcessingException {
+    assertUpdateRoundTrip(
+        "system_row_filter",
+        PolicyContents.rowFilter(
+            "filter := col(\"region\") == \"US\" if session_user() == 
\"alice\" "
+                + "else := col(\"owner\") == session_user()"));
+    assertUpdateRoundTrip(
+        "system_column_mask",
+        PolicyContents.columnMask(
+            "mask := action(\"show-last-4\") if session_user() == \"alice\" "
+                + "else := action(\"replace-with-null\")"));
+  }
+
+  @Test
+  void testStoredContentRoundTripUsesEachBuiltInContentClass() throws 
JsonProcessingException {
+    RowFilterContent rowFilter =
+        PolicyContents.rowFilter(
+            "filter := col(\"region\") == \"US\" if 
is_group_member(\"analysts\") "
+                + "else := col(\"owner\") == session_user()");
+    ColumnMaskContent columnMask =
+        PolicyContents.columnMask(
+            "mask := action(\"show-last-4\") if is_group_member(\"analysts\") "
+                + "else := action(\"replace-with-null\")");
+
+    PolicyContent restoredRowFilter =
+        JsonUtils.anyFieldMapper()
+            .readValue(
+                JsonUtils.anyFieldMapper().writeValueAsString(rowFilter),
+                Policy.BuiltInType.ROW_FILTER.contentClass());
+    PolicyContent restoredColumnMask =
+        JsonUtils.anyFieldMapper()
+            .readValue(
+                JsonUtils.anyFieldMapper().writeValueAsString(columnMask),
+                Policy.BuiltInType.COLUMN_MASK.contentClass());
+
+    Assertions.assertEquals(rowFilter, restoredRowFilter);
+    Assertions.assertEquals(columnMask, restoredColumnMask);
+    Assertions.assertDoesNotThrow(restoredRowFilter::validate);
+    Assertions.assertDoesNotThrow(restoredColumnMask::validate);
+  }
+
+  @Test
+  void testRejectsLegacyRuleShape() {
+    String rowFilterWithRules =
+        "{"
+            + "\"name\":\"invalid-filter\","
+            + "\"policyType\":\"system_row_filter\","
+            + "\"enabled\":true,"
+            + "\"content\":{\"rules\":[{\"expression\":\"filter := true\"}]}"
+            + "}";
+    String columnMaskWithRules =
+        "{"
+            + "\"name\":\"invalid-mask\","
+            + "\"policyType\":\"system_column_mask\","
+            + "\"enabled\":true,"
+            + "\"content\":{\"rules\":[{\"action\":\"show-first-4\"}]}"
+            + "}";
+
+    assertLegacyRuleShapeRejected(rowFilterWithRules);
+    assertLegacyRuleShapeRejected(columnMaskWithRules);
+  }
+
+  @Test
+  void testRejectsMissingOrBlankExpressions() {
+    PolicyContentDTO.RowFilterContentDTO missingRowFilter =
+        PolicyContentDTO.RowFilterContentDTO.builder().build();
+    PolicyContentDTO.ColumnMaskContentDTO missingColumnMask =
+        PolicyContentDTO.ColumnMaskContentDTO.builder().build();
+    PolicyContentDTO.RowFilterContentDTO blankRowFilter =
+        PolicyContentDTO.RowFilterContentDTO.builder().withExpression(" 
").build();
+    PolicyContentDTO.ColumnMaskContentDTO blankColumnMask =
+        
PolicyContentDTO.ColumnMaskContentDTO.builder().withExpression("\n").build();
+
+    Assertions.assertThrows(IllegalArgumentException.class, 
missingRowFilter::validate);
+    Assertions.assertThrows(IllegalArgumentException.class, 
missingColumnMask::validate);
+    Assertions.assertThrows(IllegalArgumentException.class, 
blankRowFilter::validate);
+    Assertions.assertThrows(IllegalArgumentException.class, 
blankColumnMask::validate);
+  }
+
+  private static void assertUpdateRoundTrip(String policyType, PolicyContent 
content)
+      throws JsonProcessingException {
+    PolicyUpdateRequest.UpdatePolicyContentRequest request =
+        new PolicyUpdateRequest.UpdatePolicyContentRequest(
+            policyType, DTOConverters.toDTO(content));
+    String json = JsonUtils.objectMapper().writeValueAsString(request);
+    PolicyUpdateRequest.UpdatePolicyContentRequest restored =
+        JsonUtils.objectMapper()
+            .readValue(json, 
PolicyUpdateRequest.UpdatePolicyContentRequest.class);
+
+    Assertions.assertEquals(request, restored);
+    Assertions.assertEquals(policyType, restored.getPolicyType());
+    Assertions.assertEquals(content, 
DTOConverters.fromDTO(restored.getNewContent()));
+  }
+
+  private static void assertLegacyRuleShapeRejected(String json) {
+    UnrecognizedPropertyException exception =
+        Assertions.assertThrows(
+            UnrecognizedPropertyException.class,
+            () -> JsonUtils.objectMapper().readValue(json, 
PolicyCreateRequest.class));
+    Assertions.assertEquals("rules", exception.getPropertyName());
+    Assertions.assertTrue(exception.getMessage().contains("rules"));
+  }
+}
diff --git a/core/src/test/java/org/apache/gravitino/meta/TestPolicyEntity.java 
b/core/src/test/java/org/apache/gravitino/meta/TestPolicyEntity.java
index 5152d14429..3ee2fa2ed2 100644
--- a/core/src/test/java/org/apache/gravitino/meta/TestPolicyEntity.java
+++ b/core/src/test/java/org/apache/gravitino/meta/TestPolicyEntity.java
@@ -155,4 +155,35 @@ public class TestPolicyEntity {
                 .withAuditInfo(auditInfo)
                 .build());
   }
+
+  @Test
+  public void testReadRestrictionPolicyContentTypes() {
+    PolicyContent rowFilter = PolicyContents.rowFilter("filter := true");
+    PolicyContent columnMask = PolicyContents.columnMask("mask := 
action(\"replace-with-null\")");
+
+    Assertions.assertDoesNotThrow(
+        () -> readRestrictionPolicy(Policy.BuiltInType.ROW_FILTER, rowFilter));
+    Assertions.assertDoesNotThrow(
+        () -> readRestrictionPolicy(Policy.BuiltInType.COLUMN_MASK, 
columnMask));
+    Assertions.assertThrows(
+        IllegalArgumentException.class,
+        () -> readRestrictionPolicy(Policy.BuiltInType.ROW_FILTER, 
columnMask));
+    Assertions.assertThrows(
+        IllegalArgumentException.class,
+        () -> readRestrictionPolicy(Policy.BuiltInType.COLUMN_MASK, 
rowFilter));
+  }
+
+  private static PolicyEntity readRestrictionPolicy(
+      Policy.BuiltInType type, PolicyContent content) {
+    return PolicyEntity.builder()
+        .withId(1L)
+        .withName("read-restriction")
+        .withNamespace(Namespace.of("m1", "c1", "s1"))
+        .withPolicyType(type)
+        .withEnabled(true)
+        .withContent(content)
+        .withAuditInfo(
+            
AuditInfo.builder().withCreator("test").withCreateTime(Instant.now()).build())
+        .build();
+  }
 }
diff --git 
a/core/src/test/java/org/apache/gravitino/policy/TestPolicyManager.java 
b/core/src/test/java/org/apache/gravitino/policy/TestPolicyManager.java
index 3ec76afae6..dedb7e164e 100644
--- a/core/src/test/java/org/apache/gravitino/policy/TestPolicyManager.java
+++ b/core/src/test/java/org/apache/gravitino/policy/TestPolicyManager.java
@@ -460,6 +460,31 @@ public class TestPolicyManager {
         "format args were not substituted: " + removeEx.getMessage());
   }
 
+  @Test
+  public void testCreateUpdateAndGetRowFilterPolicy() {
+    String policyName = "row_filter_" + 
UUID.randomUUID().toString().replace("-", "");
+    PolicyContent originalContent = PolicyContents.rowFilter("filter := 
col(\"region\") == \"US\"");
+
+    PolicyEntity created =
+        policyManager.createPolicy(
+            METALAKE, policyName, Policy.BuiltInType.ROW_FILTER, null, true, 
originalContent);
+
+    Assertions.assertEquals(originalContent, created.content());
+    Assertions.assertEquals(
+        ImmutableSet.of(MetadataObject.Type.TABLE), 
created.content().supportedObjectTypes());
+    Assertions.assertEquals(
+        originalContent, policyManager.getPolicy(METALAKE, 
policyName).content());
+
+    PolicyContent updatedContent = PolicyContents.rowFilter("filter := 
col(\"region\") == \"EU\"");
+    PolicyEntity updated =
+        policyManager.alterPolicy(
+            METALAKE, policyName, 
PolicyChange.updateContent("system_row_filter", updatedContent));
+
+    Assertions.assertEquals(updatedContent, updated.content());
+    Assertions.assertEquals(
+        updatedContent, policyManager.getPolicy(METALAKE, 
policyName).content());
+  }
+
   @Test
   public void testDeletePolicy() {
     String policyName = "policy1" + UUID.randomUUID().toString().replace("-", 
"");
diff --git a/docs/open-api/policies.yaml b/docs/open-api/policies.yaml
index 2e0d953020..ce5adcac28 100644
--- a/docs/open-api/policies.yaml
+++ b/docs/open-api/policies.yaml
@@ -439,6 +439,78 @@ components:
             content:
               $ref: '#/components/schemas/IcebergOrphanFileRemovalContent'
 
+    RowFilterContent:
+      type: object
+      description: Expression content for a built-in row-filter policy.
+      required:
+        - expression
+      properties:
+        expression:
+          type: string
+          minLength: 1
+          pattern: '[\s\S]*\S[\s\S]*'
+          description: A nonblank expression whose UTF-8 encoding must not 
exceed 16 KiB.
+
+    ColumnMaskContent:
+      type: object
+      description: Expression content for a built-in column-mask policy.
+      required:
+        - expression
+      properties:
+        expression:
+          type: string
+          minLength: 1
+          pattern: '[\s\S]*\S[\s\S]*'
+          description: A nonblank expression whose UTF-8 encoding must not 
exceed 16 KiB.
+
+    RowFilterPolicy:
+      allOf:
+        - $ref: '#/components/schemas/PolicyBase'
+        - type: object
+          required: [content]
+          properties:
+            policyType:
+              type: string
+              enum: [system_row_filter]
+            content:
+              $ref: '#/components/schemas/RowFilterContent'
+
+    RowFilterPolicyCreateRequest:
+      allOf:
+        - $ref: '#/components/schemas/PolicyCreateRequestBase'
+        - type: object
+          required: [content]
+          properties:
+            policyType:
+              type: string
+              enum: [system_row_filter]
+            content:
+              $ref: '#/components/schemas/RowFilterContent'
+
+    ColumnMaskPolicy:
+      allOf:
+        - $ref: '#/components/schemas/PolicyBase'
+        - type: object
+          required: [content]
+          properties:
+            policyType:
+              type: string
+              enum: [system_column_mask]
+            content:
+              $ref: '#/components/schemas/ColumnMaskContent'
+
+    ColumnMaskPolicyCreateRequest:
+      allOf:
+        - $ref: '#/components/schemas/PolicyCreateRequestBase'
+        - type: object
+          required: [content]
+          properties:
+            policyType:
+              type: string
+              enum: [system_column_mask]
+            content:
+              $ref: '#/components/schemas/ColumnMaskContent'
+
     PolicyContentBase:
       type: object
       description: A base content structure for policies
@@ -514,6 +586,10 @@ components:
           required:
             - content
           properties:
+            policyType:
+              type: string
+              not:
+                enum: [system_iceberg_compaction, 
system_iceberg_orphan_file_removal, system_row_filter, system_column_mask]
             content:
               $ref: '#/components/schemas/CustomPolicyContent'
 
@@ -524,12 +600,16 @@ components:
         - $ref: '#/components/schemas/CustomPolicy'
         - $ref: '#/components/schemas/IcebergOrphanFileRemovalPolicy'
         - $ref: '#/components/schemas/IcebergCompactionPolicy'
+        - $ref: '#/components/schemas/RowFilterPolicy'
+        - $ref: '#/components/schemas/ColumnMaskPolicy'
       discriminator:
         propertyName: policyType
         mapping:
           custom: '#/components/schemas/CustomPolicy'
           system_iceberg_orphan_file_removal: 
'#/components/schemas/IcebergOrphanFileRemovalPolicy'
           system_iceberg_compaction: 
'#/components/schemas/IcebergCompactionPolicy'
+          system_row_filter: '#/components/schemas/RowFilterPolicy'
+          system_column_mask: '#/components/schemas/ColumnMaskPolicy'
 
     PolicyListResponse:
       type: object
@@ -597,6 +677,10 @@ components:
           required:
             - content
           properties:
+            policyType:
+              type: string
+              not:
+                enum: [system_iceberg_compaction, 
system_iceberg_orphan_file_removal, system_row_filter, system_column_mask]
             content:
               $ref: '#/components/schemas/CustomPolicyContent'
 
@@ -607,12 +691,16 @@ components:
         - $ref: '#/components/schemas/CustomPolicyCreateRequest'
         - $ref: 
'#/components/schemas/IcebergOrphanFileRemovalPolicyCreateRequest'
         - $ref: '#/components/schemas/IcebergCompactionPolicyCreateRequest'
+        - $ref: '#/components/schemas/RowFilterPolicyCreateRequest'
+        - $ref: '#/components/schemas/ColumnMaskPolicyCreateRequest'
       discriminator:
         propertyName: policyType
         mapping:
           custom: '#/components/schemas/CustomPolicyCreateRequest'
           system_iceberg_orphan_file_removal: 
'#/components/schemas/IcebergOrphanFileRemovalPolicyCreateRequest'
           system_iceberg_compaction: 
'#/components/schemas/IcebergCompactionPolicyCreateRequest'
+          system_row_filter: 
'#/components/schemas/RowFilterPolicyCreateRequest'
+          system_column_mask: 
'#/components/schemas/ColumnMaskPolicyCreateRequest'
 
 
     PolicyUpdatesRequest:
@@ -702,7 +790,7 @@ components:
             policyType:
               type: string
               not:
-                enum: [system_iceberg_compaction, 
system_iceberg_orphan_file_removal]
+                enum: [system_iceberg_compaction, 
system_iceberg_orphan_file_removal, system_row_filter, system_column_mask]
             newContent:
               $ref: '#/components/schemas/CustomPolicyContent'
 
@@ -728,18 +816,44 @@ components:
             newContent:
               $ref: '#/components/schemas/IcebergOrphanFileRemovalContent'
 
+    UpdateRowFilterPolicyContentRequest:
+      allOf:
+        - $ref: '#/components/schemas/UpdatePolicyContentRequestBase'
+        - type: object
+          properties:
+            policyType:
+              type: string
+              enum: [system_row_filter]
+            newContent:
+              $ref: '#/components/schemas/RowFilterContent'
+
+    UpdateColumnMaskPolicyContentRequest:
+      allOf:
+        - $ref: '#/components/schemas/UpdatePolicyContentRequestBase'
+        - type: object
+          properties:
+            policyType:
+              type: string
+              enum: [system_column_mask]
+            newContent:
+              $ref: '#/components/schemas/ColumnMaskContent'
+
     UpdatePolicyContentRequest:
       description: Selects typed content using the sibling policyType field, 
matching REST deserialization.
       oneOf:
         - $ref: '#/components/schemas/UpdateCustomPolicyContentRequest'
         - $ref: 
'#/components/schemas/UpdateIcebergCompactionPolicyContentRequest'
         - $ref: 
'#/components/schemas/UpdateIcebergOrphanFileRemovalPolicyContentRequest'
+        - $ref: '#/components/schemas/UpdateRowFilterPolicyContentRequest'
+        - $ref: '#/components/schemas/UpdateColumnMaskPolicyContentRequest'
       discriminator:
         propertyName: policyType
         mapping:
           custom: '#/components/schemas/UpdateCustomPolicyContentRequest'
           system_iceberg_compaction: 
'#/components/schemas/UpdateIcebergCompactionPolicyContentRequest'
           system_iceberg_orphan_file_removal: 
'#/components/schemas/UpdateIcebergOrphanFileRemovalPolicyContentRequest'
+          system_row_filter: 
'#/components/schemas/UpdateRowFilterPolicyContentRequest'
+          system_column_mask: 
'#/components/schemas/UpdateColumnMaskPolicyContentRequest'
       example: {
         "@type": "updateContent",
         "policyType": "custom",
diff --git a/docs/policies.md b/docs/policies.md
index fa87ebfaed..f39c8b2578 100644
--- a/docs/policies.md
+++ b/docs/policies.md
@@ -35,10 +35,17 @@ policy. A table with only `data_domain=risk` does not.
 
 ### Policy Types and Content
 
-| Type                        | Rules                                | 
Consumer                  |
-|-----------------------------|--------------------------------------|---------------------------|
-| `system_iceberg_compaction` | Compaction thresholds and scheduling | Table 
maintenance service |
-| `custom`                    | A free-form map that you define      | A 
system that you provide |
+| Type                                 | Rules                                
| Consumer                  |
+|--------------------------------------|--------------------------------------|---------------------------|
+| `system_iceberg_compaction`          | Compaction thresholds and scheduling 
| Table maintenance service |
+| `system_iceberg_orphan_file_removal` | Orphan-file cleanup options          
| Table maintenance service |
+| `system_row_filter`                  | Row-filter expression                
| Not enforced yet          |
+| `system_column_mask`                 | Column-mask expression               
| Not enforced yet          |
+| `custom`                             | A free-form map that you define      
| A system that you provide |
+
+The row-filter and column-mask policy types can be created and associated with 
tags, but Gravitino
+does not enforce them yet. Until enforcement is available, these policy types 
do not restrict data
+access.
 
 A custom policy's rules live in `customRules`. Gravitino stores them and 
returns them to clients;
 it does not interpret their names or values. Built-in types have a defined 
content shape. See

Reply via email to