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

jerryshao 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 4f19f13718 [MINOR] Split tags and policies into concept and API pages 
(#12326)
4f19f13718 is described below

commit 4f19f1371811ee09327a02b437eeed52ce7fe934
Author: Mark Hoerth <[email protected]>
AuthorDate: Tue Aug 4 07:13:55 2026 -0700

    [MINOR] Split tags and policies into concept and API pages (#12326)
    
    ### What changes were proposed in this pull request?
    
    Adds two concept pages and reduces two existing pages to the API
    surface.
    
    New: `docs/tags.md` and `docs/policies.md`. Each covers what the object
    is, which
    metadata object types can carry one, how inheritance resolves and how to
    tell a
    direct attachment from an inherited one, the privileges involved, and
    how to work
    with it in the UI.
    
    Changed: `manage-tags-in-gravitino.md` and
    `manage-policies-in-gravitino.md` keep
    their slugs and now cover the API only. The concept material moves to
    the new
    pages, and each links to its counterpart. The tag page gains Python
    examples for
    every operation, which it never had, and the policy page states that the
    Python
    client does not cover policies. The curl tab is relabeled from Shell to
    REST,
    keeping `value="shell"` so tab sync and existing `?language=shell` links
    still
    work.
    
    The two new pages are not in the sidebar yet. `sidebars.ts` lives in
    gravitino-site, so a companion PR there makes them reachable.
    
    The two new pages are intentionally not in the sidebar in this PR.
    `sidebars.ts`
    lives in gravitino-site rather than here, and I would rather add them as
    part of
    the wider docs reorganization I am working on than land a one-off entry
    now. The
    existing pages keep their slugs, so nothing that is reachable today
    becomes
    unreachable.
    
    ### Why are the changes needed?
    
    Tags and policies had no concept page anywhere in the doc set.
    Catalog-delivered
    object types get a concept page plus a "Manage X" API page, but these
    two got only
    the API layer, so what a tag is, what can carry one, and how inheritance
    resolves
    were buried in an `:::info` block partway down an API reference.
    
    Several claims on the old pages were also wrong or missing. Policies
    cover six
    object types and tags nine, which neither page contrasted. The `enabled`
    flag is a
    marker Gravitino does not act on, which was stated 200 lines below where
    a reader
    creates one. Only service admins can create metalakes and only owners
    can alter
    policies, which no page mentioned.
    
    ### Does this PR introduce _any_ user-facing change?
    
    Documentation only. No API, property, or behavior changes. Two new pages
    at
    `/tags` and `/policies`; the two existing pages keep their slugs, so no
    links
    break.
    
    ### How was this patch tested?
    
    Every claim was checked against the source rather than carried over from
    the
    existing pages, including the taggable and policy-able type sets in
    `TagManager`
    and `PolicyManager`, the privilege bindings in `Privileges`, the
    authorization
    expressions on the REST operations, and the client method coverage in
    `clients/client-java` and `clients/client-python`. UI behavior was
    checked against
    `web-v2`. Cross-page links were verified to resolve against files in the
    repo.
    
    ---------
    
    Co-authored-by: Mark Hoerth <[email protected]>
    Co-authored-by: Jerry Shao <[email protected]>
    Co-authored-by: Jerry Shao <[email protected]>
---
 docs/manage-policies-in-gravitino.md | 350 ++++++++++-------------------------
 docs/manage-tags-in-gravitino.md     | 305 ++++++++++++++----------------
 docs/policies.md                     | 187 +++++++++++++++++++
 docs/tags.md                         | 152 +++++++++++++++
 4 files changed, 581 insertions(+), 413 deletions(-)

diff --git a/docs/manage-policies-in-gravitino.md 
b/docs/manage-policies-in-gravitino.md
index 98f923483e..f9e60ea4e5 100644
--- a/docs/manage-policies-in-gravitino.md
+++ b/docs/manage-policies-in-gravitino.md
@@ -1,7 +1,6 @@
 ---
 title: "Manage Policies"
 slug: "/manage-policies-in-gravitino"
-date: 2025-08-04
 keyword: "policy management, policy, policies, Gravitino, data governance"
 license: "This software is licensed under the Apache License version 2."
 ---
@@ -11,74 +10,33 @@ import TabItem from '@theme/TabItem';
 
 ## Introduction
 
-Gravitino provides a policy system that allows you to manage policies for
-metadata objects. Policies are a set of rules that can be associated with a 
metadata
-object for data governance and similar purposes.
-
-This document provides a brief introduction to using policies in Gravitino, 
covering both the Gravitino Java client and 
-REST APIs. If you want to know more about the policy system in Gravitino, 
refer to the
-Javadoc and REST API documentation.
-
-:::info
-1. Metadata objects are objects that are managed in Gravitino, such as 
`CATALOG`, `SCHEMA`, `TABLE`,
-   `FILESET`, `TOPIC`, and `MODEL`. A metadata object is combined by a `type` 
and a dot-separated
-   `name`. For example, a `CATALOG` object has a name "catalog1" with type 
"CATALOG", a `SCHEMA`
-   object has a name "catalog1.schema1" with type "SCHEMA", a `TABLE` object 
has a name
-   "catalog1.schema1.table1" with type "TABLE".
-2`CATALOG`, `SCHEMA`, `TABLE`, `FILESET`, `TOPIC`, and `MODEL` objects can be
-   associated with policies. 
-3. Policies in Gravitino are inheritable, so listing policies of a metadata 
object will also list the
-   policies of its parent metadata objects. For example, listing policies of a 
`Table` will also list
-   the policies of its parent `Schema` and `Catalog`. For catalogs that 
support multi-level
-   (hierarchical) schemas, such as a schema named `a:b:c` (using the 
configured schema separator),
-   the intermediate parent schemas `a:b` and `a` are also part of the 
hierarchy, so their policies
-   are inherited as well.
-4. The same policy can be associated with both parent and child metadata 
objects. But when you list the
-   associated policies of a child metadata object, this policy will be 
included only once in the result
-   list with `inherited` value `false`.
-:::
+This page covers the Gravitino API for policies. For what a policy is, which 
object types can carry one, what goes in
+policy content, how inheritance resolves, and how to work with policies in the 
UI, see
+[Policies](./policies.md).
 
-## Policy Operations
-
-### Create New Policies
+The Python client does not cover policies, so the examples below are REST and 
Java only.
 
-The first step to managing policies is to create new policies. Create a policy 
by providing a
-name, type, and other optional fields like comment, enabled, etc.
+## Policy Operations
 
-Gravitino supports two kinds of policies: built-in policies and custom 
policies.
-For built-in policies, the `policyType` starts with `system_` and the 
`supportedObjectTypes` in the policy content is predefined.
-For custom policies, the `policyType` must be `custom` and the 
`supportedObjectTypes` can be any combination of metadata object types.
+### Create a Policy
 
-:::note
-1. The field `supportedObjectTypes` in the content is immutable after the 
policy is created.
-:::
+A policy needs a name and a type. Content carries the rules, the object types 
the policy supports,
+and optional properties. `supportedObjectTypes` cannot be changed after 
creation.
 
 <Tabs groupId='language' queryString>
-<TabItem value="shell" label="Shell">
+<TabItem value="shell" label="REST">
 
 ```shell
-# Create a custom policy
 curl -X POST -H "Accept: application/vnd.gravitino.v1+json" \
--H "Content-Type: application/json" -d '{
-  "name": "my_policy1",
-  "comment": "This is a test policy",
+  -H "Content-Type: application/json" -d '{
+  "name": "retention_30d",
+  "comment": "Thirty day retention",
   "policyType": "custom",
   "enabled": true,
   "content": {
-    "customRules": {
-      "rule1": 123
-    },
-    "supportedObjectTypes": [
-      "CATALOG",
-      "SCHEMA",
-      "TABLE",
-      "FILESET",
-      "TOPIC",
-      "MODEL"
-    ],
-    "properties": {
-      "key1": "value1"
-    }
+    "customRules": {"retentionDays": 30},
+    "supportedObjectTypes": ["CATALOG", "SCHEMA", "TABLE"],
+    "properties": {"owner": "platform"}
   }
 }' http://localhost:8090/api/metalakes/test/policies
 ```
@@ -87,194 +45,149 @@ curl -X POST -H "Accept: 
application/vnd.gravitino.v1+json" \
 <TabItem value="java" label="Java">
 
 ```java
-GravitinoClient client = ...
-
-// Create a custom policy
 PolicyContent content = PolicyContents.custom(
-    ImmutableMap.of("rule1", 123),
+    ImmutableMap.of("retentionDays", 30),
     ImmutableSet.of(
         MetadataObject.Type.CATALOG,
+        MetadataObject.Type.SCHEMA,
         MetadataObject.Type.TABLE),
-    ImmutableMap.of("key1", "value1"));
+    ImmutableMap.of("owner", "platform"));
+
 Policy policy = client.createPolicy(
-    "my_policy1",
-    "custom",
-    "This is a test policy",
-    true /* enabled */,
-    content);
+    "retention_30d", "custom", "Thirty day retention", true, content);
 ```
 
 </TabItem>
 </Tabs>
 
-### Built-In Iceberg Compaction Policy
+The built-in compaction policy has a fixed content shape, documented in
+[Iceberg compaction policy](./iceberg-compaction-policy.md), and a helper that 
builds it with
+defaults.
 
-For the built-in `system_iceberg_compaction` policy content, field 
definitions, and examples, see [Iceberg compaction 
policy](./iceberg-compaction-policy.md).
+```java
+Policy policy = client.createPolicy(
+    "nightly_compaction",
+    "system_iceberg_compaction",
+    "Compaction defaults",
+    true,
+    PolicyContents.icebergDataCompaction());
+```
 
 ### List Policies
 
-List all the policy names as well as policy objects in a metalake in Gravitino.
+Listing returns names, or full policy objects when `details=true` is set.
 
 <Tabs groupId='language' queryString>
-<TabItem value="shell" label="Shell">
+<TabItem value="shell" label="REST">
 
 ```shell
-# List policy names
 curl -X GET -H "Accept: application/vnd.gravitino.v1+json" \
-http://localhost:8090/api/metalakes/test/policies
+  http://localhost:8090/api/metalakes/test/policies
 
-# List policy details
 curl -X GET -H "Accept: application/vnd.gravitino.v1+json" \
-http://localhost:8090/api/metalakes/test/policies?details=true
+  "http://localhost:8090/api/metalakes/test/policies?details=true";
 ```
 
 </TabItem>
 <TabItem value="java" label="Java">
 
 ```java
-GravitinoClient client = ...
 String[] policyNames = client.listPolicies();
-
 Policy[] policies = client.listPolicyInfos();
 ```
 
 </TabItem>
 </Tabs>
 
-### Get a Policy by Name
-
-Get a policy by its name.
+### Get a Policy
 
 <Tabs groupId='language' queryString>
-<TabItem value="shell" label="Shell">
+<TabItem value="shell" label="REST">
 
 ```shell
 curl -X GET -H "Accept: application/vnd.gravitino.v1+json" \
-http://localhost:8090/api/metalakes/test/policies/my_policy1
+  http://localhost:8090/api/metalakes/test/policies/retention_30d
 ```
 
 </TabItem>
 <TabItem value="java" label="Java">
 
 ```java
-GravitinoClient client = ...
-Policy policy = client.getPolicy("my_policy1");
+Policy policy = client.getPolicy("retention_30d");
 ```
 
 </TabItem>
 </Tabs>
 
-### Update a Policy
+### Alter a Policy
+
+Changes are applied as a list in one request.
 
-Gravitino allows you to update a policy by providing changes.
+| Change             | JSON                                                    
             | Java                                               |
+|--------------------|----------------------------------------------------------------------|----------------------------------------------------|
+| Rename             | `{"@type":"rename","newName":"policy_renamed"}`         
             | `PolicyChange.rename("policy_renamed")`            |
+| Update the comment | `{"@type":"updateComment","newComment":"new_comment"}`  
             | `PolicyChange.updateComment("new_comment")`        |
+| Update the content | 
`{"@type":"updateContent","policyType":"custom","newContent":{...}}` | 
`PolicyChange.updateContent("custom", newContent)` |
 
 <Tabs groupId='language' queryString>
-<TabItem value="shell" label="Shell">
+<TabItem value="shell" label="REST">
 
 ```shell
 curl -X PUT -H "Accept: application/vnd.gravitino.v1+json" \
--H "Content-Type: application/json" -d '{
+  -H "Content-Type: application/json" -d '{
   "updates": [
-    {
-      "@type": "rename",
-      "newName": "my_policy_new"
-    },
-    {
-      "@type": "updateComment",
-      "newComment": "This is my new policy comment"
-    },
     {
       "@type": "updateContent",
       "policyType": "custom",
       "newContent": {
-        "customRules": {
-          "rule1": 456
-        },
-        "supportedObjectTypes": [
-          "CATALOG",
-          "TABLE"
-        ],
-        "properties": {
-          "key1": "new_value1",
-          "key2": "new_value2"
-        }
+        "customRules": {"retentionDays": 90},
+        "supportedObjectTypes": ["CATALOG", "SCHEMA", "TABLE"],
+        "properties": {"owner": "platform"}
       }
     }
   ]
-}' http://localhost:8090/api/metalakes/test/policies/my_policy1
+}' http://localhost:8090/api/metalakes/test/policies/retention_30d
 ```
 
 </TabItem>
 <TabItem value="java" label="Java">
 
 ```java
-GravitinoClient client = ...
 PolicyContent newContent = PolicyContents.custom(
-    ImmutableMap.of("rule1", 456),
+    ImmutableMap.of("retentionDays", 90),
     ImmutableSet.of(
         MetadataObject.Type.CATALOG,
+        MetadataObject.Type.SCHEMA,
         MetadataObject.Type.TABLE),
-    ImmutableMap.of("key1", "new_value1", "key2", "new_value2"));
+    ImmutableMap.of("owner", "platform"));
 
 Policy policy = client.alterPolicy(
-    "my_policy1",
-    PolicyChange.rename("my_policy_new"),
-    PolicyChange.updateComment("This is my new policy comment"),
-    PolicyChange.updateContent("custom", newContent));
+    "retention_30d", PolicyChange.updateContent("custom", newContent));
 ```
 
 </TabItem>
 </Tabs>
 
-Gravitino supports the following policy changes:
-
-| Supported modification | JSON                                                
                 | Java                                                  |
-|------------------------|----------------------------------------------------------------------|-------------------------------------------------------|
-| Rename a policy        | `{"@type":"rename","newName":"policy_renamed"}`     
                 | `PolicyChange.rename("policy_renamed")`               |
-| Update a comment       | 
`{"@type":"updateComment","newComment":"new_comment"}`               | 
`PolicyChange.updateComment("new_comment")`           |
-| Update policy content  | 
`{"@type":"updateContent","policyType":"custom","newContent":{...}}` | 
`PolicyChange.updateContent("test_type", newContent)` |
-
 ### Enable or Disable a Policy
 
-Enable or disable a policy.
-
-The `enabled` field of a policy is only a display attribute that marks whether 
the policy is enabled or disabled.
-It does not affect the actual behavior or characteristics of the policy 
itself. This field is intended for 
-external presentation and does not control policy application logic in 
Gravitino.
-
-The `enabled` field can be used for various purposes, such as:
-- You may want to temporarily disable a policy for auditing or review 
purposes, without deleting it or changing its content.
-- Enabling a policy can be used to indicate that it is ready for use or has 
passed necessary approvals.
-- The `enabled` status can be used in UI filtering or reporting to distinguish 
between active and inactive policies. 
-- An external policy enforcement system can use this field to determine 
whether to execute the corresponding policy.
+The flag is a marker for readers. Gravitino does not act on it, and disabling 
a policy neither
+detaches it nor changes what a consumer receives.
 
 <Tabs groupId='language' queryString>
-<TabItem value="shell" label="Shell">
+<TabItem value="shell" label="REST">
 
 ```shell
-# Disable a policy
-curl -X PATCH -H "Accept: application/vnd.gravitino.v1+json" \
--H "Content-Type: application/json" -d '{
-  "enable": false
-}' http://localhost:8090/api/metalakes/test/policies/my_policy_new
-
-# Enable a policy
 curl -X PATCH -H "Accept: application/vnd.gravitino.v1+json" \
--H "Content-Type: application/json" -d '{
-  "enable": true
-}' http://localhost:8090/api/metalakes/test/policies/my_policy_new
+  -H "Content-Type: application/json" -d '{"enable": false}' \
+  http://localhost:8090/api/metalakes/test/policies/retention_30d
 ```
 
 </TabItem>
 <TabItem value="java" label="Java">
 
 ```java
-GravitinoClient client = ...
-// Disable a policy
-client.disablePolicy("my_policy_new");
-
-// Enable a policy
-client.enablePolicy("my_policy_new");
+client.disablePolicy("retention_30d");
+client.enablePolicy("retention_30d");
 ```
 
 </TabItem>
@@ -282,190 +195,123 @@ client.enablePolicy("my_policy_new");
 
 ### Delete a Policy
 
-Delete a policy by its name.
+Deleting a policy also removes it from every object it was attached to.
 
 <Tabs groupId='language' queryString>
-<TabItem value="shell" label="Shell">
+<TabItem value="shell" label="REST">
 
 ```shell
 curl -X DELETE -H "Accept: application/vnd.gravitino.v1+json" \
-http://localhost:8090/api/metalakes/test/policies/my_policy_new
+  http://localhost:8090/api/metalakes/test/policies/retention_30d
 ```
 
 </TabItem>
 <TabItem value="java" label="Java">
 
 ```java
-GravitinoClient client = ...
-client.deletePolicy("my_policy_new");
+client.deletePolicy("retention_30d");
 ```
 
 </TabItem>
 </Tabs>
 
-## Policy Associations
-
-Gravitino lets you associate and disassociate policies with metadata objects. 
The `CATALOG`, `SCHEMA`, `TABLE`, `FILESET`, `TOPIC`, and `MODEL` object types 
can have policies.
+## Object Operations
 
-### Associate and Disassociate Policies with a Metadata Object
+### Attach and Detach Policies
 
-Associate and disassociate policies with a metadata object by providing the 
object type, object
-name and policy names.
-
-The request path for REST API is 
`/api/metalakes/{metalake}/objects/{metadataObjectType}/{metadataObjectFullName}/policies`.
+Both happen in one request, and either list can be omitted. Catalogs, schemas, 
tables, filesets,
+topics, and models can carry a policy.
 
 <Tabs groupId='language' queryString>
-<TabItem value="shell" label="Shell">
+<TabItem value="shell" label="REST">
 
 ```shell
-# First, create some policies to associate
 curl -X POST -H "Accept: application/vnd.gravitino.v1+json" \
--H "Content-Type: application/json" \
--d '{
-  "name": "policy1", 
-  "policyType": "custom", 
-  "content": {
-    "supportedObjectTypes": ["CATALOG", "TABLE"]
-  }
-}' http://localhost:8090/api/metalakes/test/policies
-
-curl -X POST -H "Accept: application/vnd.gravitino.v1+json" \
--H "Content-Type: application/json" \
--d '{
-  "name": "policy2", 
-  "policyType": "custom", 
-  "content": {
-    "supportedObjectTypes": ["CATALOG", "TABLE"]
-  }
-}' http://localhost:8090/api/metalakes/test/policies
-
-curl -X POST -H "Accept: application/vnd.gravitino.v1+json" \
--H "Content-Type: application/json" \
--d '{
-  "name": "policy3", 
-  "policyType": "custom", 
-  "content": {
-    "supportedObjectTypes": ["CATALOG", "TABLE"]
-  }
-}' http://localhost:8090/api/metalakes/test/policies
-
-# Associate and disassociate policies with a catalog
-curl -X POST -H "Accept: application/vnd.gravitino.v1+json" \
--H "Content-Type: application/json" -d '{
-  "policiesToAdd": ["policy1", "policy2"],
-  "policiesToRemove": ["policy3"]
-}' http://localhost:8090/api/metalakes/test/objects/catalog/my_catalog/policies
-
-# Associate policies with a schema
-curl -X POST -H "Accept: application/vnd.gravitino.v1+json" \
--H "Content-Type: application/json" -d '{
-  "policiesToAdd": ["policy1"]
-}' 
http://localhost:8090/api/metalakes/test/objects/schema/my_catalog.my_schema/policies
+  -H "Content-Type: application/json" -d '{
+  "policiesToAdd": ["retention_30d"],
+  "policiesToRemove": ["retention_7d"]
+}' http://localhost:8090/api/metalakes/test/objects/catalog/catalog1/policies
 ```
 
 </TabItem>
 <TabItem value="java" label="Java">
 
 ```java
-// Assume catalog 'my_catalog' and schema 'my_catalog.my_schema' exist
-Catalog catalog = client.loadCatalog("my_catalog");
+Catalog catalog = client.loadCatalog("catalog1");
 catalog.supportsPolicies().associatePolicies(
-    new String[] {"policy1", "policy2"},
-    new String[] {"policy3"});
+    new String[] {"retention_30d"},
+    new String[] {"retention_7d"});
 
-// You need to load the schema from the catalog
-Schema schema = catalog.asSchemas().loadSchema("my_schema");
-schema.supportsPolicies().associatePolicies(new String[] {"policy1"}, null);
+Schema schema = catalog.asSchemas().loadSchema("schema1");
+schema.supportsPolicies().associatePolicies(new String[] {"retention_30d"}, 
null);
 ```
 
 </TabItem>
 </Tabs>
 
-### List Associated Policies for a Metadata Object
+### List Policies on an Object
 
-List all the policies associated with a metadata object. If a policy is 
inheritable, 
-listing policies of a metadata object will also list the policies of its 
parent metadata objects,
-including the intermediate parent schemas of a multi-level (hierarchical) 
schema.
-
-The request path for REST API is 
`/api/metalakes/{metalake}/objects/{metadataObjectType}/{metadataObjectFullName}/policies`.
+The response includes policies inherited from ancestors. With `details=true` 
each policy carries an
+`inherited` field, which a plain name listing does not.
 
 <Tabs groupId='language' queryString>
-<TabItem value="shell" label="Shell">
+<TabItem value="shell" label="REST">
 
 ```shell
-# List policy names for a catalog
-curl -X GET -H "Accept: application/vnd.gravitino.v1+json" \
-http://localhost:8090/api/metalakes/test/objects/catalog/my_catalog/policies
-
-# List policy details for a schema
 curl -X GET -H "Accept: application/vnd.gravitino.v1+json" \
-http://localhost:8090/api/metalakes/test/objects/schema/my_catalog.my_schema/policies?details=true
+  
"http://localhost:8090/api/metalakes/test/objects/catalog/catalog1/policies?details=true";
 ```
 
 </TabItem>
 <TabItem value="java" label="Java">
 
 ```java
-Catalog catalog = client.loadCatalog("my_catalog");
+Catalog catalog = client.loadCatalog("catalog1");
 String[] policyNames = catalog.supportsPolicies().listPolicies();
 Policy[] policies = catalog.supportsPolicies().listPolicyInfos();
-
-Schema schema = catalog.asSchemas().loadSchema("my_schema");
-String[] schemaPolicyNames = schema.supportsPolicies().listPolicies();
-Policy[] schemaPolicies = schema.supportsPolicies().listPolicyInfos();
 ```
 
 </TabItem>
 </Tabs>
 
-### Get an Associated Policy by Name for a Metadata Object
-
-Get an associated policy by its name for a metadata object.
-
-The request path for REST API is 
`/api/metalakes/{metalake}/objects/{metadataObjectType}/{metadataObjectFullName}/policies/{policy}`.
+### Get One Policy on an Object
 
 <Tabs groupId='language' queryString>
-<TabItem value="shell" label="Shell">
+<TabItem value="shell" label="REST">
 
 ```shell
 curl -X GET -H "Accept: application/vnd.gravitino.v1+json" \
-http://localhost:8090/api/metalakes/test/objects/catalog/my_catalog/policies/policy1
-
-curl -X GET -H "Accept: application/vnd.gravitino.v1+json" \
-http://localhost:8090/api/metalakes/test/objects/schema/my_catalog.my_schema/policies/policy1
+  
http://localhost:8090/api/metalakes/test/objects/catalog/catalog1/policies/retention_30d
 ```
 
 </TabItem>
 <TabItem value="java" label="Java">
 
 ```java
-Catalog catalog = client.loadCatalog("my_catalog");
-Policy policy = catalog.supportsPolicies().getPolicy("policy1");
-
-Schema schema = catalog.asSchemas().loadSchema("my_schema");
-Policy schemaPolicy = schema.supportsPolicies().getPolicy("policy1");
+Policy policy = catalog.supportsPolicies().getPolicy("retention_30d");
 ```
 
 </TabItem>
 </Tabs>
 
-### List Metadata Objects Associated with a Policy
+### List Objects Carrying a Policy
 
-List all the metadata objects **directly associated with** a policy.
+The response lists direct attachments only, so a policy attached to a catalog 
returns that catalog
+rather than the objects beneath it.
 
 <Tabs groupId='language' queryString>
-<TabItem value="shell" label="Shell">
+<TabItem value="shell" label="REST">
 
 ```shell
 curl -X GET -H "Accept: application/vnd.gravitino.v1+json" \
-http://localhost:8090/api/metalakes/test/policies/policy1/objects
+  http://localhost:8090/api/metalakes/test/policies/retention_30d/objects
 ```
 
 </TabItem>
 <TabItem value="java" label="Java">
 
 ```java
-Policy policy = client.getPolicy("policy1");
+Policy policy = client.getPolicy("retention_30d");
 MetadataObject[] objects = policy.associatedObjects().objects();
 int count = policy.associatedObjects().count();
 ```
diff --git a/docs/manage-tags-in-gravitino.md b/docs/manage-tags-in-gravitino.md
index 192ce3ded8..016709b7ac 100644
--- a/docs/manage-tags-in-gravitino.md
+++ b/docs/manage-tags-in-gravitino.md
@@ -1,7 +1,6 @@
 ---
 title: "Manage Tags"
 slug: "/manage-tags-in-gravitino"
-date: 2024-07-24
 keyword: "tag management, tag, tags, Gravitino"
 license: "This software is licensed under the Apache License version 2."
 ---
@@ -11,55 +10,24 @@ import TabItem from '@theme/TabItem';
 
 ## Introduction
 
-Gravitino provides a tag system that allows you to manage tags for
-metadata objects. Tags are a way to categorize and organize metadata objects 
in Gravitino.
-
-This document briefly introduces how to use tags in Gravitino by both 
Gravitino Java client and
-REST APIs. If you want to know more about the tag system in Gravitino, refer 
to the
-Javadoc and REST API documentation.
-
-Note that current tag system is a basic implementation, some advanced features 
will be added in
-the future versions.
-
-:::info
-1. Metadata objects are objects that are managed in Gravitino, such as 
`CATALOG`, `SCHEMA`, `TABLE`,
-   `VIEW`, `FUNCTION`, `FILESET`, `TOPIC`, `COLUMN`, `MODEL`, etc. A metadata 
object is combined by a
-   `type` and a dot-separated `name`. For example, a `CATALOG` object has a 
name "catalog1" with type
-   "CATALOG", a `SCHEMA` object has a name "catalog1.schema1" with type 
"SCHEMA", a `TABLE`
-   object has a name "catalog1.schema1.table1" with type "TABLE", a `COLUMN` 
object has a name 
-   "catalog1.schema1.table1.column1" with type "COLUMN".
-2. `CATALOG`, `SCHEMA`, `TABLE`, `VIEW`, `FUNCTION`, `FILESET`, `TOPIC`, 
`MODEL`, and `COLUMN`
-   objects can be tagged.
-3. Tags in Gravitino is inheritable, so listing tags of a metadata object will 
also list the
-   tags of its parent metadata objects. For example, listing tags of a `Table` 
will also list
-   the tags of its parent `Schema` and `Catalog`. For catalogs that support 
multi-level
-   (hierarchical) schemas, such as a schema named `a:b:c` (using the 
configured schema
-   separator), the intermediate parent schemas `a:b` and `a` are also part of 
the hierarchy, so
-   their tags are inherited as well.
-4. The same tag can be associated with both parent and child metadata objects. 
But when you list the
-   associated tags of a child metadata object, this tag will be included only 
once in the result
-   list with `inherited` value `false`.
-:::
+This page covers the Gravitino API for tags. For what a tag is, which object 
types can carry one, how inheritance
+resolves, and how to work with tags in the UI, see [Tags](./tags.md).
 
 ## Tag Operations
 
-### Create New Tags
+### Create a Tag
 
-The first step to manage tags is to create new tags. Create a tag by providing 
a
-name, optional comment, and properties.
+A tag needs a name, and can carry a comment and properties.
 
 <Tabs groupId='language' queryString>
-<TabItem value="shell" label="Shell">
+<TabItem value="shell" label="REST">
 
 ```shell
 curl -X POST -H "Accept: application/vnd.gravitino.v1+json" \
--H "Content-Type: application/json" -d '{
-  "name": "tag1",
-  "comment": "This is a tag",
-  "properties": {
-    "key1": "value1",
-    "key2": "value2"
-  }
+  -H "Content-Type: application/json" -d '{
+  "name": "pii",
+  "comment": "Personally identifiable information",
+  "properties": {"owner": "data-governance"}
 }' http://localhost:8090/api/metalakes/test/tags
 ```
 
@@ -68,8 +36,20 @@ curl -X POST -H "Accept: application/vnd.gravitino.v1+json" \
 
 ```java
 GravitinoClient client = ...
-Tag tag =
-    client.createTag("tag1", "This is a tag", ImmutableMap.of("key1", 
"value1", "key2", "value2"));
+Tag tag = client.createTag(
+    "pii",
+    "Personally identifiable information",
+    ImmutableMap.of("owner", "data-governance"));
+```
+
+</TabItem>
+<TabItem value="python" label="Python">
+
+```python
+tag = client.create_tag(
+    tag_name="pii",
+    comment="Personally identifiable information",
+    properties={"owner": "data-governance"})
 ```
 
 </TabItem>
@@ -77,268 +57,271 @@ Tag tag =
 
 ### List Tags
 
-List all the tag names as well as tag objects in a metalake in Gravitino.
+Listing returns names, or full tag objects when `details=true` is set.
 
 <Tabs groupId='language' queryString>
-<TabItem value="shell" label="Shell">
+<TabItem value="shell" label="REST">
 
 ```shell
 curl -X GET -H "Accept: application/vnd.gravitino.v1+json" \
-http://localhost:8090/api/metalakes/test/tags
+  http://localhost:8090/api/metalakes/test/tags
 
 curl -X GET -H "Accept: application/vnd.gravitino.v1+json" \
-http://localhost:8090/api/metalakes/test/tags?details=true
+  "http://localhost:8090/api/metalakes/test/tags?details=true";
 ```
 
 </TabItem>
 <TabItem value="java" label="Java">
 
 ```java
-GravitinoClient client = ...
 String[] tagNames = client.listTags();
-
 Tag[] tags = client.listTagsInfo();
 ```
 
 </TabItem>
-</Tabs>
+<TabItem value="python" label="Python">
 
-### Get a Tag by Name
+```python
+tag_names = client.list_tags()
+tags = client.list_tags_info()
+```
+
+</TabItem>
+</Tabs>
 
-Get a tag by its name.
+### Get a Tag
 
 <Tabs groupId='language' queryString>
-<TabItem value="shell" label="Shell">
+<TabItem value="shell" label="REST">
 
 ```shell
 curl -X GET -H "Accept: application/vnd.gravitino.v1+json" \
-http://localhost:8090/api/metalakes/test/tags/tag1
+  http://localhost:8090/api/metalakes/test/tags/pii
 ```
 
 </TabItem>
 <TabItem value="java" label="Java">
 
 ```java
-GravitinoClient client = ...
-Tag tag = client.getTag("tag1");
+Tag tag = client.getTag("pii");
+```
+
+</TabItem>
+<TabItem value="python" label="Python">
+
+```python
+tag = client.get_tag("pii")
 ```
 
 </TabItem>
 </Tabs>
 
-### Update a Tag
+### Alter a Tag
 
-Gravitino allows you to update a tag by providing a new tag name, comment and 
properties.
+Changes are applied as a list in one request.
+
+| Change             | JSON                                                    
     | Java                                      | Python                       
                |
+|--------------------|--------------------------------------------------------------|-------------------------------------------|----------------------------------------------|
+| Rename             | `{"@type":"rename","newName":"tag_renamed"}`            
     | `TagChange.rename("tag_renamed")`         | 
`TagChange.rename("tag_renamed")`            |
+| Update the comment | `{"@type":"updateComment","newComment":"new_comment"}`  
     | `TagChange.updateComment("new_comment")`  | 
`TagChange.update_comment("new_comment")`    |
+| Set a property     | 
`{"@type":"setProperty","property":"key1","value":"value1"}` | 
`TagChange.setProperty("key1", "value1")` | `TagChange.set_property("key1", 
"value1")`   |
+| Remove a property  | `{"@type":"removeProperty","property":"key1"}`          
     | `TagChange.removeProperty("key1")`        | 
`TagChange.remove_property("key1")`          |
 
 <Tabs groupId='language' queryString>
-<TabItem value="shell" label="Shell">
+<TabItem value="shell" label="REST">
 
 ```shell
 curl -X PUT -H "Accept: application/vnd.gravitino.v1+json" \
--H "Content-Type: application/json" -d '{
+  -H "Content-Type: application/json" -d '{
   "updates": [
-    {
-      "@type": "rename",
-      "newName": "tag2"
-    },
-    {
-      "@type": "updateComment",
-      "newComment": "This is an updated tag"
-    },
-    {
-      "@type": "setProperty",
-      "property": "key3",
-      "value": "value3"
-    },
-    {
-      "@type": "removeProperty",
-      "property": "key1"
-    }
+    {"@type": "updateComment", "newComment": "Reviewed quarterly"},
+    {"@type": "setProperty", "property": "owner", "value": "privacy-office"}
   ]
-}' http://localhost:8090/api/metalakes/test/tags/tag1
+}' http://localhost:8090/api/metalakes/test/tags/pii
 ```
 
 </TabItem>
 <TabItem value="java" label="Java">
 
 ```java
-GravitinoClient client = ...
 Tag tag = client.alterTag(
-    "tag1",
-    TagChange.rename("tag2"),
-    TagChange.updateComment("This is an updated tag"),
-    TagChange.setProperty("key3", "value3"),
-    TagChange.removeProperty("key1"));
+    "pii",
+    TagChange.updateComment("Reviewed quarterly"),
+    TagChange.setProperty("owner", "privacy-office"));
 ```
 
 </TabItem>
-</Tabs>
+<TabItem value="python" label="Python">
 
-Gravitino supports the following tag changes:
+```python
+tag = client.alter_tag(
+    "pii",
+    TagChange.update_comment("Reviewed quarterly"),
+    TagChange.set_property("owner", "privacy-office"))
+```
 
-| Supported modification | JSON                                                
         | Java                                      |
-|------------------------|--------------------------------------------------------------|-------------------------------------------|
-| Rename a tag           | `{"@type":"rename","newName":"tag_renamed"}`        
         | `TagChange.rename("tag_renamed")`         |
-| Update a comment       | 
`{"@type":"updateComment","newComment":"new_comment"}`       | 
`TagChange.updateComment("new_comment")`  |
-| Set a tag property     | 
`{"@type":"setProperty","property":"key1","value":"value1"}` | 
`TagChange.setProperty("key1", "value1")` |
-| Remove a tag property  | `{"@type":"removeProperty","property":"key1"}`      
         | `TagChange.removeProperty("key1")`        |
+</TabItem>
+</Tabs>
 
 ### Delete a Tag
 
-Delete a tag by its name.
+Deleting a tag also removes it from every object it was attached to.
 
 <Tabs groupId='language' queryString>
-<TabItem value="shell" label="Shell">
+<TabItem value="shell" label="REST">
 
 ```shell
 curl -X DELETE -H "Accept: application/vnd.gravitino.v1+json" \
-http://localhost:8090/api/metalakes/test/tags/tag2
+  http://localhost:8090/api/metalakes/test/tags/pii
 ```
 
 </TabItem>
 <TabItem value="java" label="Java">
 
 ```java
-GravitinoClient client = ...
-client.deleteTag("tag2");
+client.deleteTag("pii");
 ```
 
 </TabItem>
-</Tabs>
+<TabItem value="python" label="Python">
 
-## Tag Associations
+```python
+client.delete_tag("pii")
+```
 
-Gravitino lets you associate and disassociate tags with metadata objects. The 
`CATALOG`, `SCHEMA`,
-`TABLE`, `VIEW`, `FUNCTION`, `FILESET`, `TOPIC`, `MODEL`, and `COLUMN` object 
types can be tagged.
+</TabItem>
+</Tabs>
 
-### Associate and Disassociate Tags with a Metadata Object
+## Object Operations
 
-Associate and disassociate tags with a metadata object by providing the object 
type, object
-name and tag names.
+### Attach and Detach Tags
 
-The request path for REST API is 
`/api/metalakes/{metalake}/objects/{metadataObjectType}/{metadataObjectName}/tags`.
+Both happen in one request, and either list can be omitted. The object type 
and full name go in the
+path, so the same call covers catalogs, schemas, tables, views, columns, 
filesets, topics, models,
+and functions.
 
 <Tabs groupId='language' queryString>
-<TabItem value="shell" label="Shell">
+<TabItem value="shell" label="REST">
 
 ```shell
 curl -X POST -H "Accept: application/vnd.gravitino.v1+json" \
--H "Content-Type: application/json" -d '{
-  "tagsToAdd": ["tag1", "tag2"],
-  "tagsToRemove": ["tag3"]
-}' http://localhost:8090/api/metalakes/test/objects/catalog/catalog1/tags
+  -H "Content-Type: application/json" -d '{
+  "tagsToAdd": ["pii"],
+  "tagsToRemove": ["unreviewed"]
+}' 
http://localhost:8090/api/metalakes/test/objects/table/catalog1.schema1.customers/tags
 
 curl -X POST -H "Accept: application/vnd.gravitino.v1+json" \
--H "Content-Type: application/json" -d '{
-  "tagsToAdd": ["tag1"]
-}' 
http://localhost:8090/api/metalakes/test/objects/schema/catalog1.schema1/tags
+  -H "Content-Type: application/json" -d '{
+  "tagsToAdd": ["pii"]
+}' 
http://localhost:8090/api/metalakes/test/objects/fileset/catalog1.schema1.raw_events/tags
 ```
 
 </TabItem>
 <TabItem value="java" label="Java">
 
 ```java
-Catalog catalog1 = ...
-catalog1.supportsTags().associateTags(
-    new String[] {"tag1", "tag2"},
-    new String[] {"tag3"});
+Table customers = ...
+customers.supportsTags().associateTags(
+    new String[] {"pii"},
+    new String[] {"unreviewed"});
 
-Schema schema1 = ...
-schema1.supportsTags().associateTags(new String[] {"tag1"}, null);
+Fileset rawEvents = ...
+rawEvents.supportsTags().associateTags(new String[] {"pii"}, null);
 ```
 
 </TabItem>
-</Tabs>
+<TabItem value="python" label="Python">
 
-### List Associated Tags for a Metadata Object
+```python
+customers = ...
+customers.supports_tags().associate_tags(["pii"], ["unreviewed"])
 
-List all the tags associated with a metadata object. The tags in Gravitino are
-inheritable, so listing tags of a metadata object will also list the tags of 
its parent metadata
-objects, including the intermediate parent schemas of a multi-level 
(hierarchical) schema.
-
-The request path for REST API is 
`/api/metalakes/{metalake}/objects/{metadataObjectType}/{metadataObjectName}/tags`.
+raw_events = ...
+raw_events.supports_tags().associate_tags(["pii"], None)
+```
 
-<Tabs groupId='language' queryString>
-<TabItem value="shell" label="Shell">
+</TabItem>
+</Tabs>
 
-```shell
-curl -X GET -H "Accept: application/vnd.gravitino.v1+json" \
-http://localhost:8090/api/metalakes/test/objects/catalog/catalog1/tags
+### List Tags on an Object
 
-curl -X GET -H "Accept: application/vnd.gravitino.v1+json" \
-http://localhost:8090/api/metalakes/test/objects/schema/catalog1.schema1/tags
+The response includes tags inherited from ancestors. With `details=true` each 
tag carries an
+`inherited` field, which a plain name listing does not.
 
-curl -X GET -H "Accept: application/vnd.gravitino.v1+json" \
-http://localhost:8090/api/metalakes/test/objects/catalog/catalog1/tags?details=true
+<Tabs groupId='language' queryString>
+<TabItem value="shell" label="REST">
 
+```shell
 curl -X GET -H "Accept: application/vnd.gravitino.v1+json" \
-http://localhost:8090/api/metalakes/test/objects/schema/catalog1.schema1/tags?details=true
+  
"http://localhost:8090/api/metalakes/test/objects/table/catalog1.schema1.customers/tags?details=true";
 ```
 
 </TabItem>
 <TabItem value="java" label="Java">
 
 ```java
-Catalog catalog1 = ...
-String[] tags = catalog1.supportsTags().listTags();
-Tag[] tagsInfo = catalog1.supportsTags().listTagsInfo();
-
-Schema schema1 = ...
-String[] tags = schema1.supportsTags().listTags();
-Tag[] tagsInfo = schema1.supportsTags().listTagsInfo();
+Table customers = ...
+String[] tagNames = customers.supportsTags().listTags();
+Tag[] tags = customers.supportsTags().listTagsInfo();
 ```
 
 </TabItem>
-</Tabs>
+<TabItem value="python" label="Python">
 
-### Get an Associated Tag by Name for a Metadata Object
+```python
+customers = ...
+tag_names = customers.supports_tags().list_tags()
+tags = customers.supports_tags().list_tags_info()
+```
 
-Get an associated tag by its name for a metadata object.
+</TabItem>
+</Tabs>
 
-The request path for REST API is 
`/api/metalakes/{metalake}/objects/{metadataObjectType}/{metadataObjectName}/tags/{tagName}`.
+### Get One Tag on an Object
 
 <Tabs groupId='language' queryString>
-<TabItem value="shell" label="Shell">
+<TabItem value="shell" label="REST">
 
 ```shell
 curl -X GET -H "Accept: application/vnd.gravitino.v1+json" \
-http://localhost:8090/api/metalakes/test/objects/catalog/catalog1/tags/tag1
-
-curl -X GET -H "Accept: application/vnd.gravitino.v1+json" \
-http://localhost:8090/api/metalakes/test/objects/schema/catalog1.schema1/tags/tag1
+  
http://localhost:8090/api/metalakes/test/objects/table/catalog1.schema1.customers/tags/pii
 ```
 
 </TabItem>
 <TabItem value="java" label="Java">
 
 ```java
-Catalog catalog1 = ...
-Tag tag = catalog1.supportsTags().getTag("tag1");
+Tag tag = customers.supportsTags().getTag("pii");
+```
+
+</TabItem>
+<TabItem value="python" label="Python">
 
-Schema schema1 = ...
-Tag tag = schema1.supportsTags().getTag("tag1");
+```python
+tag = customers.supports_tags().get_tag("pii")
 ```
 
 </TabItem>
 </Tabs>
 
-### List Metadata Objects Associated with a Tag
+### List Objects Carrying a Tag
 
-List all the metadata objects associated with a tag.
+The response lists direct attachments only, so a tag attached to a catalog 
returns that catalog
+rather than the objects beneath it.
 
 <Tabs groupId='language' queryString>
-<TabItem value="shell" label="Shell">
+<TabItem value="shell" label="REST">
 
 ```shell
 curl -X GET -H "Accept: application/vnd.gravitino.v1+json" \
-http://localhost:8090/api/metalakes/test/tags/tag1/objects
+  http://localhost:8090/api/metalakes/test/tags/pii/objects
 ```
 
 </TabItem>
 <TabItem value="java" label="Java">
 
 ```java
-Tag tag = ...
+Tag tag = client.getTag("pii");
 MetadataObject[] objects = tag.associatedObjects().objects();
 int count = tag.associatedObjects().count();
 ```
diff --git a/docs/policies.md b/docs/policies.md
new file mode 100644
index 0000000000..828c436831
--- /dev/null
+++ b/docs/policies.md
@@ -0,0 +1,187 @@
+---
+title: "Policies"
+slug: "/policies"
+keyword: "policy, policies, governance, metadata object, Gravitino"
+license: "This software is licensed under the Apache License version 2."
+---
+
+## Introduction
+
+A policy is a named set of rules that you create once in a metalake and attach 
to metadata objects.
+Attaching a policy to a catalog or schema applies it to everything beneath, so 
a setting that varies
+by table can be expressed once at the level where it holds and overridden 
where it does not.
+
+Tags and policies are close cousins, and the difference is what they carry. A 
tag classifies, and
+its content is its name. A policy prescribes, and its content is a set of 
rules something acts on.
+
+Policies come in two kinds. A built-in policy has a type that Gravitino 
defines and a consumer that
+acts on it. A custom policy carries rules of your own, which Gravitino stores, 
inherits, and serves
+back to whatever system you build around it.
+
+Common uses:
+
+- Setting table maintenance behavior for a whole catalog rather than table by 
table, and letting new
+  tables pick it up without further work
+- Recording a rule once against metadata that lives in several catalogs, so 
every engine reaching
+  those objects through Gravitino sees the same rule
+- Feeding an external enforcement or scheduling system that reads policies 
from Gravitino rather
+  than keeping its own copy of what applies where
+
+## Quick Start
+
+**1. Create the policy.** Policies are created from the policy list in the UI, 
which creates custom
+policies. A policy needs a name, the object types it supports, and its rules. 
Built-in policies are
+created over REST.
+
+**2. Attach it to an object.** Open the catalog, schema, table, fileset, 
topic, or model you want to
+govern and add the policy from its policy control. Only policies that already 
exist in the metalake
+are offered.
+
+**3. See where the policy is attached.** Selecting a policy name in the policy 
list shows the
+objects it is attached to directly.
+
+## The Policy Model
+
+### Policy Types
+
+| Type                        | Rules                                | 
Consumed by               |
+|-----------------------------|--------------------------------------|---------------------------|
+| `system_iceberg_compaction` | Compaction thresholds and scheduling | Table 
maintenance service |
+| `custom`                    | A free-form map you define           | A 
system you provide      |
+
+A built-in type has a name beginning with `system_` and a content shape 
Gravitino defines. The
+compaction policy is documented in [Iceberg compaction 
policy](./iceberg-compaction-policy.md), and
+the service that acts on it in
+[Table maintenance service](./table-maintenance-service/optimizer.md).
+
+A custom policy has type `custom`, and Gravitino makes no attempt to interpret 
what is inside
+`customRules`. The rules are stored, inherited down the hierarchy, and 
returned to any client that
+asks.
+
+The UI creates custom policies only. A built-in policy is created over REST 
with its own content
+shape.
+
+### What Can Carry a Policy
+
+A metadata object is identified by a type and a name, with each level below 
the catalog separated by
+a dot. Six object types can carry a policy.
+
+| Object type | Name form                                     |
+|-------------|-----------------------------------------------|
+| `CATALOG`   | `{catalog_name}`                              |
+| `SCHEMA`    | `{catalog_name}.{schema_name}`                |
+| `TABLE`     | `{catalog_name}.{schema_name}.{table_name}`   |
+| `FILESET`   | `{catalog_name}.{schema_name}.{fileset_name}` |
+| `TOPIC`     | `{catalog_name}.{schema_name}.{topic_name}`   |
+| `MODEL`     | `{catalog_name}.{schema_name}.{model_name}`   |
+
+Columns, views, and functions cannot carry a policy, which is narrower than
+[tags](./tags.md). A metalake cannot carry one either, so to reach every object
+in a catalog, attach the policy to the catalog.
+
+Each policy also declares its own `supportedObjectTypes`, which narrows the 
list further for that
+policy.
+
+### Content
+
+Policy content has three parts: the `supportedObjectTypes` list, the rules, 
and properties.
+
+`supportedObjectTypes` is fixed when the policy is created and cannot be 
changed afterward, so a
+policy meant for tables only stays that way for its lifetime.
+
+The rules are what a consumer evaluates. For a custom policy they live under 
`customRules` as a map
+you define, where the name is yours and the value is any JSON value.
+
+```json
+"customRules": {
+  "retentionDays": 30,
+  "maxTableSizeGb": 500,
+  "requiresApproval": true
+}
+```
+
+Gravitino does not interpret those names or values. Whatever consumes the 
policy decides what
+`retentionDays` means and what to do about it.
+
+A built-in policy has a rule set Gravitino defines, and the service that 
consumes it documents how
+those rules are applied. The compaction policy carries `minDataFileMse`, 
`minDeleteFileNumber`,
+`dataFileMseWeight`, `deleteFileNumberWeight`, `max-partition-num`, and a 
trigger and score
+expression, plus any `job.options.` entries passed through to the job. Those 
names and their
+meanings are covered in [Iceberg compaction 
policy](./iceberg-compaction-policy.md).
+
+Properties describe the policy itself rather than the behavior it asks for. 
Rules change as you
+adjust thresholds, and properties stay stable. The compaction policy uses 
properties for its
+strategy type and job template name, which tell the table maintenance service 
what to run, and those
+are set by Gravitino rather than by you. For a custom policy, properties are 
yours, and suit facts
+such as which team owns the policy, which system consumes it, or which version 
of a rule set it
+represents. Anything evaluated against an object belongs in rules instead.
+
+Properties sit on the policy rather than on an attachment, so every object 
carrying the policy sees
+the same values.
+
+### The Enabled Flag
+
+The `enabled` flag marks a policy as active or inactive for readers. Gravitino 
does not act on it,
+so disabling a policy does not detach it or change what a consumer receives. 
Treat it as a signal to
+whoever reads the policy, useful for holding a policy through review without 
deleting it.
+
+### Inheritance
+
+An object shows the policies attached to it plus the policies attached to each 
of its ancestors, so
+a policy on a catalog applies to every schema, table, fileset, topic, and 
model beneath it. For
+catalogs that support multi-level schemas, the intermediate schemas are 
ancestors too.
+
+Each policy appears once, whether it reaches the object through one ancestor 
or several. A policy
+attached directly to the object counts as direct even when an ancestor carries 
it too.
+
+Direct and inherited attachments are distinguishable. In the UI an inherited 
policy is marked with a
+lock icon. Over REST, a policy listing requested with `details=true` carries 
an `inherited` field on
+each policy, which a plain listing of names does not.
+
+A policy that reaches an object only by inheritance cannot be removed there. 
Detach it from the
+ancestor that carries it, which affects every other object beneath that 
ancestor as well.
+
+Inheritance is resolved when the object is read rather than stored on the 
object, so attaching a
+policy to a catalog takes effect immediately for tables created afterward.
+
+## Working With Policies in the UI
+
+### Managing the Policy Set
+
+The policy list holds every policy in the metalake and can be searched. A 
policy can be renamed, its
+comment and rules edited, and its enabled flag switched from there. Policies 
created over REST,
+including built-in ones, appear in the list alongside the rest.
+
+Deleting a policy removes it from every object it was attached to, with no 
warning about how many
+objects that affects and no way to recover the attachments.
+
+### Attaching and Detaching
+
+Policies attach from the object rather than from the policy, so open the 
object and use the policy
+control there. Inherited policies carry no remove control. Detaching removes 
the direct attachment
+only, so an object still shows a policy it inherits from an ancestor.
+
+### Finding Where a Policy Is Used
+
+Selecting a policy name opens a view listing the objects the policy is 
attached to directly.
+Inherited reach is not included, so a policy attached to one catalog lists 
that catalog rather than
+the tables under it.
+
+## Permissions
+
+Policy permissions are held on the policy, and apply in addition to 
permissions on the objects being
+governed.
+
+| Privilege       | Grantable on                 | What it allows              
                   |
+|-----------------|------------------------------|------------------------------------------------|
+| `CREATE_POLICY` | Metalake                     | Creating policies in the 
metalake              |
+| `APPLY_POLICY`  | Metalake, or a single policy | Reading a policy and 
attaching or detaching it |
+
+Altering and deleting a policy are reserved for the metalake owner and the 
policy owner. Attaching a
+policy also requires access to the object being governed. Policy listings show 
only the policies
+that user is allowed to read.
+
+## Using the API
+
+Policies can be created, attached, and read over REST and through the Java 
client. Endpoints, payload
+shapes, and worked examples are in [Manage 
Policies](./manage-policies-in-gravitino.md).
diff --git a/docs/tags.md b/docs/tags.md
new file mode 100644
index 0000000000..ce4a334e63
--- /dev/null
+++ b/docs/tags.md
@@ -0,0 +1,152 @@
+---
+title: "Tags"
+slug: "/tags"
+keyword: "tag, tags, labels, classification, metadata object, Gravitino"
+license: "This software is licensed under the Apache License version 2."
+---
+
+## Introduction
+
+A tag is a named label that you create once in a metalake and attach to any 
number of metadata
+objects. A tag carries an optional comment and a set of properties, so it can 
hold a small amount of
+structured detail beyond its name. The same tag can be attached to a catalog, 
a table, and a single
+column at the same time.
+
+Tags travel with the metadata rather than with the data, so a tag applied to a 
table is visible to
+every engine and every client that reaches that table through Gravitino, no 
matter which system
+actually stores it. That makes a tag the practical way to say something once 
about metadata that
+lives in several catalogs at once.
+
+Gravitino stores tags, resolves them down the metadata hierarchy, and shows 
them wherever the object
+appears. Common uses:
+
+- Recording a classification once, on the catalog or schema, and having every 
table and column
+  beneath it carry that classification without further work
+- Answering coverage questions across catalogs you do not own, such as which 
objects anywhere in the
+  metalake are marked as personal data
+- Carrying a classification that arrived from another catalog through to the 
engines and clients that
+  read metadata from Gravitino
+- Marking objects for a downstream consumer to act on, such as a job that 
reads the tags on an object
+  before deciding what to do with it
+
+## Quick Start
+
+**1. Create the tag.** Tags are created from the tag list in the UI. A tag 
needs a name, and can
+also carry a comment and any properties you want to keep with it.
+
+**2. Attach it to an object.** Open the catalog, schema, or table you want to 
label and add the tag
+from its tag control. Only tags that already exist in the metalake are 
offered, so create the tag
+first and attach it second.
+
+**3. Label a single column.** A table's column list carries its own tag 
control on each row, so a
+tag can sit on one column without applying to the rest of the table.
+
+**4. See where the tag is attached.** Selecting a tag name in the tag list 
shows the objects it is
+attached to directly.
+
+## The Tag Model
+
+### What Can Carry a Tag
+
+A metadata object is identified by a type and a name, with each level below 
the catalog separated by
+a dot. Nine object types can carry a tag.
+
+| Object type | Name form                                                 |
+|-------------|-----------------------------------------------------------|
+| `CATALOG`   | `{catalog_name}`                                          |
+| `SCHEMA`    | `{catalog_name}.{schema_name}`                            |
+| `TABLE`     | `{catalog_name}.{schema_name}.{table_name}`               |
+| `VIEW`      | `{catalog_name}.{schema_name}.{view_name}`                |
+| `COLUMN`    | `{catalog_name}.{schema_name}.{table_name}.{column_name}` |
+| `FILESET`   | `{catalog_name}.{schema_name}.{fileset_name}`             |
+| `TOPIC`     | `{catalog_name}.{schema_name}.{topic_name}`               |
+| `MODEL`     | `{catalog_name}.{schema_name}.{model_name}`               |
+| `FUNCTION`  | `{catalog_name}.{schema_name}.{function_name}`            |
+
+A metalake cannot carry a tag, so there is no single attachment point that 
covers everything at
+once. To reach every object in a catalog, attach the tag to the catalog.
+
+The UI attaches tags on catalogs, schemas, tables, and columns. For the other 
types, use the
+REST API described at the end of this page.
+
+### Names and Properties
+
+A tag name is unique within its metalake and is the identifier used everywhere 
else, so renaming a
+tag changes what every stored request has to ask for.
+
+A name is up to 64 characters of letters, digits, underscores, slashes, equals 
signs, and hyphens.
+A separator convention such as `pii/email` keeps a growing set readable.
+
+Properties are free-form key and value pairs on the tag itself rather than on 
the attachment, so
+every object carrying the tag sees the same values. Properties suit facts 
about the tag, such as
+which team owns it or which external system it came from. Properties do not 
suit facts about one
+tagged object.
+
+### Inheritance
+
+An object shows the tags attached to it plus the tags attached to each of its 
ancestors, so a tag
+on a catalog appears on every schema, table, and column beneath it. For 
catalogs that support
+multi-level schemas, the intermediate schemas are ancestors too, so a schema 
two levels down
+inherits from both of the schemas above it.
+
+Each tag appears once, whether it reaches the object through one ancestor or 
several. A tag attached
+directly to the object counts as direct even when an ancestor carries it too.
+
+The two are distinguishable. In the UI an inherited tag is marked with a lock 
icon. Over REST, a tag
+listing requested with `details=true` carries an `inherited` field on each 
tag, which a plain listing
+of names does not.
+
+A tag that reaches an object only by inheritance cannot be removed there. 
Detach it from the
+ancestor that carries it, which affects every other object beneath that 
ancestor as well.
+
+Inheritance is resolved when the object is read rather than stored on the 
object, so attaching a
+tag to a catalog takes effect immediately for tables created afterward.
+
+## Working With Tags in the UI
+
+### Managing the Tag Set
+
+The tag list holds every tag in the metalake with its comment and creation 
time, and can be
+searched. A tag can be renamed and its comment and properties edited from 
there.
+
+Deleting a tag removes it from every object it was attached to. There is no 
warning about how many
+objects that affects and no way to recover the attachments, so check where a 
tag is used before
+deleting one.
+
+### Attaching and Detaching
+
+Tags attach from the object rather than from the tag, so open the catalog, 
schema, or table and use
+the tag control there. Inherited tags carry no remove control. Detaching 
removes the direct
+attachment only, so an object still shows a tag it inherits from an ancestor.
+
+Column tags are edited from the column list on the table page. A column shows 
its own tags plus
+everything it inherits from the table, schema, and catalog above it, which is 
usually most of what
+is listed.
+
+### Finding Where a Tag Is Used
+
+Selecting a tag name opens a view listing the objects the tag is attached to 
directly. Inherited
+reach is not included, so a tag attached to one catalog lists that catalog 
rather than the tables
+under it. Coverage questions that span a subtree are answered by walking the 
objects and reading
+the tags on each one.
+
+## Permissions
+
+Tag permissions are held on the tag, and apply in addition to permissions on 
the objects being
+tagged.
+
+| Privilege    | Grantable on              | What it allows                    
          |
+|--------------|---------------------------|---------------------------------------------|
+| `CREATE_TAG` | Metalake                  | Creating tags in the metalake     
          |
+| `APPLY_TAG`  | Metalake, or a single tag | Reading a tag and attaching or 
detaching it |
+
+Altering and deleting a tag are reserved for the metalake owner and the tag 
owner.
+Attaching a tag also requires access to the object being tagged, so a user who 
can apply a tag
+cannot use it to reach an object they could not otherwise see. Tag listings 
show only the tags that
+user is allowed to read.
+
+## Using the API
+
+Tags can be created, attached, and read over REST and through the Java and 
Python clients, which is
+also the only way to tag views, filesets, topics, models, and functions today. 
Endpoints, payload
+shapes, and worked examples are in [Manage 
Tags](./manage-tags-in-gravitino.md).

Reply via email to