This is an automated email from the ASF dual-hosted git repository.
jerryshao pushed a commit to branch branch-1.3
in repository https://gitbox.apache.org/repos/asf/gravitino.git
The following commit(s) were added to refs/heads/branch-1.3 by this push:
new 4c2faaa97c [Cherry-pick to branch-1.3] [MINOR] Split tags and policies
into concept and API pages (#12326) (#12362)
4c2faaa97c is described below
commit 4c2faaa97cd6a79e27f11b484180126e33feb1a8
Author: github-actions[bot]
<41898282+github-actions[bot]@users.noreply.github.com>
AuthorDate: Wed Aug 5 17:22:09 2026 +0800
[Cherry-pick to branch-1.3] [MINOR] Split tags and policies into concept
and API pages (#12326) (#12362)
**Cherry-pick Information:**
- Original commit: 4f19f1371811ee09327a02b437eeed52ce7fe934
- Target branch: `branch-1.3`
- Status: ⚠️ **Has conflicts - manual resolution required**
**Do not merge** until conflict markers are resolved and the
`cherry-pick-conflict` label is removed.
Please review and resolve the conflicts before merging.
Co-authored-by: Mark Hoerth <[email protected]>
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 | 303 ++++++++++++++----------------
docs/policies.md | 187 +++++++++++++++++++
docs/tags.md | 152 +++++++++++++++
4 files changed, 581 insertions(+), 411 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 70b5d2b76d..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,54 +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`,
- `COLUMN`, `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`, `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
```
@@ -67,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>
@@ -76,267 +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`, `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).