yuqi1129 commented on code in PR #13867: URL: https://github.com/apache/gravitino/pull/13867#discussion_r4283701196
########## design-docs/domain-design.md: ########## @@ -0,0 +1,261 @@ +<!-- + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you under the Apache License, Version 2.0 (the + "License"); you may not use this file except in compliance + with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. +--> + +# Design: Domain + +| Version | Author | Date | Status | +| :---- | :---- | :---- | :---- | +| 1.23 | Jerry Shao | 2026-10-15 | Draft | + +## Goals + +1. **Business grouping**: group assets from any catalog under named domains (e.g. "Finance") for discovery, browsing and agent retrieval. An asset can belong to several domains. +2. **Membership is tags**: an asset is a member when it carries the domain's backing tag, directly or inherited from its catalog or schema. There is no separate membership store. +3. **Separate privileges**: domain privileges govern the `Domain` object; tag privileges govern membership. Membership grants no access to data. +4. **One level of subdomains**, each with one parent; a subdomain can be moved. +5. **Draft and published status**: a `DRAFT` domain is visible only to its owner and managers, the same on REST, MCP and any UI. +6. **Inherited membership**: tagging a catalog or schema makes the objects under it members, through Gravitino's existing tag inheritance. + +## Non-Goals + +1. **Approval workflow**: adding members, attaching subdomains and publishing take effect at once; status is a manual switch. +2. **Data-access grants on domains**: a domain carries no data privileges such as `SELECT_TABLE` (see Grants and policies). +3. **Deeper or multi-parent hierarchies**: the storage allows them later without a schema change. +4. **Automatic membership**, glossary and metric designs, and the ABAC policy language. Glossary terms can become members once they are taggable and added to the member types. +5. **UI**: this design covers the server, REST APIs, clients and MCP tools. + +## Usage Scenario + +1. A steward with `MANAGE_DOMAIN` and `CREATE_TAG` on the metalake creates domain Finance. Gravitino creates tag `Finance` for it; the steward owns both. +2. The steward creates Trading under Finance (`"parent": "Finance"`); it gets tag `Finance/Trading`. The steward grants `APPLY_TAG` on that tag to the trading table owners. +3. A table owner tags `cat.db.trades` with `Finance/Trading`. The table is in Trading, and appears under Finance through the subdomain roll-up. Tagging schema `cat.db` with `Finance` would put every table in it in Finance too. +4. A user or agent who can see Trading browses it and gets the tables they can access. +5. Trading is moved under Markets. Its members now appear under Markets; nothing is retagged and the tag keeps its name. + +## Design + +### Data model + +``` +Domain { id, name, comment, status: DRAFT | PUBLISHED, properties, audit_info } +DomainRel { parentId, childId, audit_info } // one edge per subdomain +``` + +- A unique index on `DomainRel (child_id, deleted_at)` keeps one parent per domain. `parent` is not stored on `Domain`; the API derives it. Dropping the index and checks would allow multiple parents later without a schema change. +- Domains live under a metalake. A subdomain and its parent are in the same metalake. +- Domain names are unique within a metalake across both levels: top-level domains and subdomains share one namespace, so a domain is addressed by its name alone. + +### Backing tag + +Each domain has **at most one** backing tag: a tag, optionally with a value (tag values: apache/gravitino#12162). Creating a domain always sets one; a domain has none only after its backing tag is removed or deleted, until a new one is set. + +- **Default**: if none is given at creation, Gravitino creates a `NO_VALUE` tag named after the domain's path, as in Unity Catalog: `Finance`, and `Finance/Tax` for subdomain Tax. The creator owns it, so creating a domain this way also needs `CREATE_TAG`. If a tag with that name already exists, the domain adopts it, with the assets that carry it, only when the tag backs no other domain and the caller owns the tag or the metalake. If the tag already backs another domain, creation fails with 400; if the caller may not adopt it, with 403. +- **Name clashes**: default tag names can clash even without `/` in domain names, because tags are never renamed on move or rename. For example, rename subdomain Tax under Finance to VAT (its tag stays `Finance/Tax`), then create a new Tax under Finance: both want `Finance/Tax`. Banning `/` would not prevent this, so domain names may contain `/`, and the adoption rule above resolves clashes. +- **Matching** follows the tag lookup semantics (`GET /tags/{tag}/objects`). A backing tag without a value matches every assignment of the tag, with or without values, like a lookup without `value`. A backing tag with a value matches assignments whose values contain it, exact and case-sensitive, like `?value=`; a valueless assignment never matches it. The same rule applies to member listing, domains of an object and domain filters, always against the object's effective assignment, and to a default-name tag adopted at creation, which is bound without a value. +- **Explicit**: any existing (tag, value), such as `dept=finance`, bound by that tag's owner (or the metalake owner). A (tag, value) may be bound explicitly to several domains, which then have the same members. +- **Storage**: the backing tag is the `DOMAIN` object's own row in `tag_relation_meta`; there is no extra field to keep in sync. Reverse lookup uses `idx_tid_value` filtered to `DOMAIN` objects. Domains never inherit tags (`METALAKE` is not taggable). +- **Changing it**: `PUT /domains/{domain}/backingTag` replaces or removes it on the server, under the domain's row lock. A missing tag fails with 404, a value the tag does not allow with 400, and a tag the caller does not own (nor the metalake) with 403; setting the current tag is a no-op. Assets carrying only the old tag drop out; nothing is migrated. +- **Not through the generic tag association API**: it rejects `DOMAIN` targets, so the endpoint above is the only way to change a backing tag. Through the generic API a missing tag is silently ignored, so replacing the tag with a misspelled one would strip the backing tag and all members without an error; the call returns tag names instead of the domain; and `tagsToRemove` would come from a possibly stale snapshot. +- **Never renamed**: moving or renaming a domain does not rename its tag; the hierarchy lives in `DomainRel`. +- **Deleting the tag** is allowed, as for any tag; its domains are left with no backing tag and no members until a new one is set. There is no domain-side audit of this. +- **Tag object listings**: a backing tag is a tag association of a `DOMAIN` object, so `GET /tags/{tag}/objects` and `Tag#associatedObjects` include the domains the tag backs, filtered by the domain visibility rule (see Status). A tag owner can then see which domains the tag backs. + +### Subdomains + +Every operation that sets a parent (create with `parent`, attach, move) checks: + +| # | Constraint | +| :---- | :---- | +| C1 | A domain cannot be its own parent | +| C2 | The parent is top-level | +| C3 | The attached domain has no subdomains | +| C4 | Parent and child are in the same metalake | +| C5 | At most one parent; attaching a domain that has one is a move | +| C6 | A `PUBLISHED` domain can only be attached under a `PUBLISHED` parent | + +C1–C3 keep the hierarchy one level deep and acyclic, so no cycle detection is needed. + +- **Attach / move** re-points the child's single edge in one transaction. Attaching to the current parent is a no-op. +- **Detach** removes the edge; the child becomes top-level. +- **Locking**: each operation locks the domain rows involved (child, new parent, old parent) in id order, then checks the constraints. Otherwise two valid calls could together create two levels, a cycle, or a dangling edge. +- **Delete** is refused while the domain has subdomains. Deleting removes the domain's own parent edge and its backing tag association. The tag and the tags on assets stay. +- **Membership and scope**: membership comes from backing tags only (see Membership and inheritance), so an asset tagged only `Finance/Trading` is a member of Trading, not of Finance. A top-level domain's *scope* is its own members plus the members of the subdomains the caller can see; a subdomain's scope is its own members. Member listing and domain filters use scope, as in Unity Catalog, where a subdomain's assets also appear under its parent domain. Review Comment: **Membership vs scope: one definition per consumer** Thanks for 394f9be7cb / fbe71ab048. Splitting *membership* from *scope* and expanding listings to leaf objects fixes most of what I wanted to raise here. Below is the frame I used, so the remaining gaps are easy to see. An object can relate to Finance through three independent paths: | Path | Example | Hierarchy it follows | | :---- | :---- | :---- | | ① direct | `cat.db.t` carries `Finance` | none | | ② inherited | schema `cat.db` carries `Finance`, so `cat.db.a` is in Finance | catalog → schema → leaf | | ③ roll-up | `cat.other.x` carries `Finance/Trading`, and Trading is under Finance | domain → subdomain | In the current text, *membership* = ① + ② (leaf objects only) and *scope* = membership + ③ over visible subdomains. My concern was that every consumer picked its own combination. Here is where each consumer stands now: | Consumer | Uses | Status | | :---- | :---- | :---- | | Member listing | scope, expanded to leaves | ✅ defined | | Domain filters | scope | ✅ defined | | Domains of a leaf object | membership | ✅ defined (`Finance › Trading` from `parent`) | | Domains of a catalog/schema | "carries the tag" | ⚠️ a third relation, see 3 | | Policies (L170) | membership, implicitly (tag-based) | ⚠️ not stated, see 1 | | Counts (L129) | "consistent with the listing" | ⚠️ ambiguous, see 2 | Remaining points: 1. **Policies follow membership, not scope. Please say so explicitly here.** With `cat.other.x` tagged only `Finance/Trading`, the Finance page lists `cat.other.x` (scope), but Finance's policies do not apply to it (membership). Users browsing Finance will assume they do. One sentence in this bullet, stating which concepts use scope (browse, filter, count?) and which use membership (policies, domains of an object), would make that boundary a decision rather than an accident. Open Question 1 can then stay focused on whether to change it. 2. **Counts.** "Consistent with the listing" means scope, and the listing depends on the caller: visible subdomains (drafts) and `filterMetadataObject`. So a domain's "asset count" is per viewer, not a property of the domain. If that is intended, please write it down: a UI card showing "Finance: 1,204 assets" will show different numbers to different people. Otherwise, define the count over membership or over the unfiltered scope. 3. **Domains of an object on a catalog or schema** return domains the container *carries* through the same response shape that, for a leaf, means *is a member of*. A client cannot tell the two apart from the DTO without checking the object type itself. Consider either not supporting this endpoint on catalogs and schemas, or adding an explicit marker on each entry (e.g. `relation: MEMBER | CARRIES`). 4. **Store-only expansion (L127) creates a second source of truth.** For a table in an external catalog that Gravitino has not recorded yet, the Rule (L111) says it is a member through the catalog's tag, but the listing omits it. Please state which one is authoritative, e.g. "the listing is a view of recorded objects; membership is defined by the Rule". Then MCP and agent callers know that an empty listing does not mean "not in Finance". 5. **Cost of steps 2–3.** As written, expansion followed by a per-candidate effective-tag check is N × (leaf + schema + catalog relation reads) for a catalog with N leaves. All leaves in a schema share the schema's and catalog's tags, so one batched read of the leaves' own tags per schema is enough. It would help to note that batching here, and for domain filters (L116), which also check each candidate. ########## design-docs/domain-design.md: ########## @@ -0,0 +1,261 @@ +<!-- + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you under the Apache License, Version 2.0 (the + "License"); you may not use this file except in compliance + with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. +--> + +# Design: Domain + +| Version | Author | Date | Status | +| :---- | :---- | :---- | :---- | +| 1.23 | Jerry Shao | 2026-10-15 | Draft | + +## Goals + +1. **Business grouping**: group assets from any catalog under named domains (e.g. "Finance") for discovery, browsing and agent retrieval. An asset can belong to several domains. +2. **Membership is tags**: an asset is a member when it carries the domain's backing tag, directly or inherited from its catalog or schema. There is no separate membership store. +3. **Separate privileges**: domain privileges govern the `Domain` object; tag privileges govern membership. Membership grants no access to data. +4. **One level of subdomains**, each with one parent; a subdomain can be moved. +5. **Draft and published status**: a `DRAFT` domain is visible only to its owner and managers, the same on REST, MCP and any UI. +6. **Inherited membership**: tagging a catalog or schema makes the objects under it members, through Gravitino's existing tag inheritance. + +## Non-Goals + +1. **Approval workflow**: adding members, attaching subdomains and publishing take effect at once; status is a manual switch. +2. **Data-access grants on domains**: a domain carries no data privileges such as `SELECT_TABLE` (see Grants and policies). +3. **Deeper or multi-parent hierarchies**: the storage allows them later without a schema change. +4. **Automatic membership**, glossary and metric designs, and the ABAC policy language. Glossary terms can become members once they are taggable and added to the member types. +5. **UI**: this design covers the server, REST APIs, clients and MCP tools. + +## Usage Scenario + +1. A steward with `MANAGE_DOMAIN` and `CREATE_TAG` on the metalake creates domain Finance. Gravitino creates tag `Finance` for it; the steward owns both. +2. The steward creates Trading under Finance (`"parent": "Finance"`); it gets tag `Finance/Trading`. The steward grants `APPLY_TAG` on that tag to the trading table owners. +3. A table owner tags `cat.db.trades` with `Finance/Trading`. The table is in Trading, and appears under Finance through the subdomain roll-up. Tagging schema `cat.db` with `Finance` would put every table in it in Finance too. +4. A user or agent who can see Trading browses it and gets the tables they can access. +5. Trading is moved under Markets. Its members now appear under Markets; nothing is retagged and the tag keeps its name. + +## Design + +### Data model + +``` +Domain { id, name, comment, status: DRAFT | PUBLISHED, properties, audit_info } +DomainRel { parentId, childId, audit_info } // one edge per subdomain +``` + +- A unique index on `DomainRel (child_id, deleted_at)` keeps one parent per domain. `parent` is not stored on `Domain`; the API derives it. Dropping the index and checks would allow multiple parents later without a schema change. +- Domains live under a metalake. A subdomain and its parent are in the same metalake. +- Domain names are unique within a metalake across both levels: top-level domains and subdomains share one namespace, so a domain is addressed by its name alone. + +### Backing tag + +Each domain has **at most one** backing tag: a tag, optionally with a value (tag values: apache/gravitino#12162). Creating a domain always sets one; a domain has none only after its backing tag is removed or deleted, until a new one is set. + +- **Default**: if none is given at creation, Gravitino creates a `NO_VALUE` tag named after the domain's path, as in Unity Catalog: `Finance`, and `Finance/Tax` for subdomain Tax. The creator owns it, so creating a domain this way also needs `CREATE_TAG`. If a tag with that name already exists, the domain adopts it, with the assets that carry it, only when the tag backs no other domain and the caller owns the tag or the metalake. If the tag already backs another domain, creation fails with 400; if the caller may not adopt it, with 403. +- **Name clashes**: default tag names can clash even without `/` in domain names, because tags are never renamed on move or rename. For example, rename subdomain Tax under Finance to VAT (its tag stays `Finance/Tax`), then create a new Tax under Finance: both want `Finance/Tax`. Banning `/` would not prevent this, so domain names may contain `/`, and the adoption rule above resolves clashes. +- **Matching** follows the tag lookup semantics (`GET /tags/{tag}/objects`). A backing tag without a value matches every assignment of the tag, with or without values, like a lookup without `value`. A backing tag with a value matches assignments whose values contain it, exact and case-sensitive, like `?value=`; a valueless assignment never matches it. The same rule applies to member listing, domains of an object and domain filters, always against the object's effective assignment, and to a default-name tag adopted at creation, which is bound without a value. +- **Explicit**: any existing (tag, value), such as `dept=finance`, bound by that tag's owner (or the metalake owner). A (tag, value) may be bound explicitly to several domains, which then have the same members. +- **Storage**: the backing tag is the `DOMAIN` object's own row in `tag_relation_meta`; there is no extra field to keep in sync. Reverse lookup uses `idx_tid_value` filtered to `DOMAIN` objects. Domains never inherit tags (`METALAKE` is not taggable). +- **Changing it**: `PUT /domains/{domain}/backingTag` replaces or removes it on the server, under the domain's row lock. A missing tag fails with 404, a value the tag does not allow with 400, and a tag the caller does not own (nor the metalake) with 403; setting the current tag is a no-op. Assets carrying only the old tag drop out; nothing is migrated. +- **Not through the generic tag association API**: it rejects `DOMAIN` targets, so the endpoint above is the only way to change a backing tag. Through the generic API a missing tag is silently ignored, so replacing the tag with a misspelled one would strip the backing tag and all members without an error; the call returns tag names instead of the domain; and `tagsToRemove` would come from a possibly stale snapshot. +- **Never renamed**: moving or renaming a domain does not rename its tag; the hierarchy lives in `DomainRel`. +- **Deleting the tag** is allowed, as for any tag; its domains are left with no backing tag and no members until a new one is set. There is no domain-side audit of this. +- **Tag object listings**: a backing tag is a tag association of a `DOMAIN` object, so `GET /tags/{tag}/objects` and `Tag#associatedObjects` include the domains the tag backs, filtered by the domain visibility rule (see Status). A tag owner can then see which domains the tag backs. + +### Subdomains + +Every operation that sets a parent (create with `parent`, attach, move) checks: + +| # | Constraint | +| :---- | :---- | +| C1 | A domain cannot be its own parent | +| C2 | The parent is top-level | +| C3 | The attached domain has no subdomains | +| C4 | Parent and child are in the same metalake | +| C5 | At most one parent; attaching a domain that has one is a move | +| C6 | A `PUBLISHED` domain can only be attached under a `PUBLISHED` parent | + +C1–C3 keep the hierarchy one level deep and acyclic, so no cycle detection is needed. + +- **Attach / move** re-points the child's single edge in one transaction. Attaching to the current parent is a no-op. +- **Detach** removes the edge; the child becomes top-level. +- **Locking**: each operation locks the domain rows involved (child, new parent, old parent) in id order, then checks the constraints. Otherwise two valid calls could together create two levels, a cycle, or a dangling edge. +- **Delete** is refused while the domain has subdomains. Deleting removes the domain's own parent edge and its backing tag association. The tag and the tags on assets stay. +- **Membership and scope**: membership comes from backing tags only (see Membership and inheritance), so an asset tagged only `Finance/Trading` is a member of Trading, not of Finance. A top-level domain's *scope* is its own members plus the members of the subdomains the caller can see; a subdomain's scope is its own members. Member listing and domain filters use scope, as in Unity Catalog, where a subdomain's assets also appear under its parent domain. + +### Status + +- New domains are `DRAFT`. The owner or a `MANAGE_DOMAIN` holder publishes or unpublishes, at any time and without approval. +- **Visibility is the same on every surface** (REST, MCP, UI). A `DRAFT` domain is visible only to its owner and `MANAGE_DOMAIN` holders, including those on its parent. For anyone else it does not exist: lists omit it, get returns 404, and it is not in `list_domains`, member listings, an object's domains, a tag's objects or a parent's roll-up. Responses carry `status`, so a caller that sees drafts can filter them out. +- **Parent names** are the one exception: `parent` is always returned, even when the caller cannot see the parent. Only the name is exposed; getting the parent still returns 404, and nothing can be done on it. This happens only to the owner or child-level managers of a `DRAFT` subdomain under a parent they cannot see (C6 keeps published subdomains under published parents), or to a caller with privileges on a subdomain but no `USE_DOMAIN` on its published parent. +- **Consistency**: publishing a subdomain needs a published parent; unpublishing needs no published subdomains (C6 covers attach and move). +- Assets can be tagged into a `DRAFT` domain to prepare it; anyone with `APPLY_TAG` on its tag can do so, as in Unity Catalog. +- Roll-ups depend on the caller: someone who can see a `DRAFT` subdomain sees its members under the parent; others do not. + +### Membership and inheritance + +- **Rule**: membership resolves an object's effective tags without filtering them by tag privileges. `MetadataObjectTagOperations#listTagsForMetadataObject` cannot be reused as is: it drops the tags the caller cannot load (`LOAD_TAG_AUTHORIZATION_EXPRESSION`: owner, `VIEW_TAG` or `APPLY_TAG`), so a caller without tag privileges would lose the backing tag before matching. Its inheritance resolution moves into a shared helper that the tag endpoint keeps filtering and domain matching uses unfiltered; domain visibility and object access are applied afterwards. An object's effective tags are its own plus its schema's and catalog's; for the same tag the nearest level wins, and different tags accumulate. A leaf object (see Member types) is a member of domain D when its effective tags include D's backing (tag, value). +- **Dedicated tags (the default)** accumulate: with schema `Finance` and table `Sales`, the table is in both, and a child cannot leave a domain it inherits, as in Unity Catalog. +- **Values of one tag** backing different domains override each other: with schema `dept=finance` and table `dept=hr`, the table is only in the `dept=hr` domain, unless it uses `dept=[finance, hr]`. This is the one case where a child can leave an inherited domain, and it follows from the tag owner's choice of binding. +- **Member types**: leaf objects only: tables, views, filesets, topics, models and semantic models (`DomainMember.SUPPORTED_TYPES`). Catalogs and schemas are never members: one that carries a backing tag passes membership to the leaf objects under it through tag inheritance, as in Unity Catalog, where tagging a catalog or schema makes the assets inside it appear in the domain. Columns and functions are not members either: a column or function carrying a backing tag is not listed. A type that becomes taggable later joins only when it is added to this list. Metrics belong through their semantic model. +- **Domains of an object**: the object's effective tags mapped to the domains the caller can see. Only the domains the object is a member of are returned; a parent reached only through a subdomain is not, and clients show it from `parent` (for example `Finance › Trading`). An object that is a member of both Finance and Trading gets both. On a catalog or schema, which is never a member, it returns the domains whose backing tag the catalog's or schema's own effective tags match: this marks it as carrying the domain for the objects under it, not as a member. For an inherited membership, `inheritedFrom` names the catalog or schema it comes from; it is absent for direct membership. The source is known while resolving inheritance, at no extra cost. +- **Domain filters**, for APIs that filter objects by domain, match scope, consistent with member listing: a filter on Finance accepts members of Finance and of its visible subdomains; a filter on a subdomain accepts only its own members. Each candidate object is checked with the rule above instead of listing all members first. + +### Member listing + +`GET /domains/{domain}/objects` lists leaf objects, expanding catalogs and schemas: + +1. Resolve the backing tags of the domains in the selected scopes (by default the listed domain's whole scope: itself and its visible subdomains). +2. List the objects carrying them (`listMetadataObjectsForTag`, by tag and value). Leaf objects are candidates as they are; a tagged catalog or schema is replaced by the leaf objects under it, from Gravitino's store. +3. Keep the candidates whose effective tags match a selected backing tag (the matching rules above, nearest level wins), so a leaf object that overrides an inherited value drops out. +4. Drop objects the caller cannot access (`filterMetadataObject`), de-duplicate, and record the subdomain each member comes from (see `DomainMemberDTO`). No tag privilege is needed. + +- **Store only**: expansion uses the objects recorded in Gravitino's store. Objects in an external catalog that Gravitino has not recorded yet are not listed. +- **Cost**: a domain backed by a tag on a large catalog lists every recorded leaf object under it. Paging is a follow-up, not part of Phase 1 (see REST API). +- **Counts**: a domain's asset count is the number of expanded leaf members, consistent with the listing. + +### Access control + +**Domain privileges** + +| Privilege | On the metalake | On a domain | +| :---- | :---- | :---- | +| `MANAGE_DOMAIN` | Create domains and manage all of them | Edit, publish, subdomains, backing tag, delete; see it while `DRAFT` | +| `USE_DOMAIN` | See all published domains and browse their members | See this domain and browse its members | + +`MANAGE_DOMAIN` includes `USE_DOMAIN`; owners and the metalake owner hold both. Privileges on a parent also apply to its subdomains, as in Unity Catalog. There is no separate `CREATE_DOMAIN`. Review Comment: **Parent → subdomain privilege inheritance does not fit today's authorization model, and the parent is mutable** This line, together with L103 (draft visible to `MANAGE_DOMAIN` holders "on its parent"), L104, L155 (detach via the parent's privilege) and L165, depends on a capability the authorization framework does not have yet. I think it deserves its own design paragraph. **How inheritance works today.** Expressions such as `ANY(OWNER, METALAKE, CATALOG, SCHEMA)` are expanded textually into `METALAKE::OWNER || CATALOG::OWNER || SCHEMA::OWNER` ([`AuthorizationExpressionConverter#replaceAnyExpressions`](https://github.com/apache/gravitino/blob/19d88d7bad614f45d50b68aecd32824cbb4bdb30/server-common/src/main/java/org/apache/gravitino/server/authorization/expression/AuthorizationExpressionConverter.java#L142)). Each scope is resolved from the request's identifier path `metalake.catalog.schema.table`, so an ancestor is always derivable from the name. **Why that does not carry over to domains.** - `DOMAIN` is a single-level name ([`MetadataObjects.parse`](https://github.com/apache/gravitino/blob/19d88d7bad614f45d50b68aecd32824cbb4bdb30/api/src/main/java/org/apache/gravitino/MetadataObjects.java#L179-L189) keeps single-level types whole), and L62 makes domains addressable by name alone. - The parent lives only in `DomainRel`, and it can change. - So no `X::MANAGE_DOMAIN` scope can name "the current parent of this domain". - L165 says the declarative check covers the domain itself because there is a single `{domain}` path parameter. That is exactly why it cannot cover the inherited part. **What has to be designed.** 1. **A new scope resolver** that looks up the parent in `DomainRel` during authorization. That lookup runs on every check, including per item when `filterMetadataObject` filters a domain list or a parent's roll-up. Listing N domains means N parent lookups unless batched. 2. **Invalidation across nodes.** Whatever the lookup reads (the entity cache of `Domain`/`DomainRel`, or anything precomputed in the authorizer) must be invalidated on other nodes after move, detach and delete, through the changelog. Otherwise node B keeps authorizing the old parent's principals after node A has moved the subdomain. 3. **Move and detach change access without any grant changing.** For example: - Alice has `USE_DOMAIN` and Bob has `MANAGE_DOMAIN` on Finance. Published Trading is under Finance, so both see it and Bob manages it. - Carol, holding `MANAGE_DOMAIN` on Trading and on Markets, moves Trading under Markets. No role was touched, yet Alice loses visibility and Bob loses management, while Markets' holders gain both. - Per L155, Bob alone can also detach Trading using only his inherited Finance privilege. After that he has no privilege on Trading and cannot re-attach it. If this is intended, please document that structural operations are access-changing, and make sure their events carry enough detail (old and new parent) for an auditor reconstructing "why could X see Y". **Options I see.** - **(a)** No inheritance in Phase 1. Grants on a subdomain are explicit; its creator owns it, and the metalake owner / metalake-level `MANAGE_DOMAIN` still covers everything. It is simpler, matches the "one level, keep it small" spirit, and inheritance can be added later as a pure widening. - **(b)** Keep inheritance, and specify the `PARENT_DOMAIN`-style scope: how it is resolved (one lookup, batched for lists), which cache it reads, which changelog entries invalidate it, and the access-changing semantics of move and detach above. Which one do you prefer? I'm fine with either, as long as the doc spells it out before the authorization task in Phase 1 starts. -- This is an automated message from the Apache Git Service. To respond to the message, please log on to GitHub and use the URL above to go to the specific comment. To unsubscribe, e-mail: [email protected] For queries about this service, please contact Infrastructure at: [email protected]
