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).