From 01f17629dbe8d74194a4a28e4288b217ae14adbf Mon Sep 17 00:00:00 2001 From: 1Pro-L4R8 Date: Mon, 2 Nov 2026 00:57:43 +0700 Subject: [PATCH] docs: column-classification row filters on Trino and the attach notice (KI-287, KI-307) Platform #818: - close KI-287 (Trino filters tables where the classification is only on columns) and KI-307 (attach answers a clearance_masks_pending_table notice, shown as a warning toast); add KI-333 (open-mode Trino engine has no per-user baseline) and KI-334 (Data page shows a running engine as provisioning) - masks and row filters, tagging, clearance, explain access, check access and provision pages; upgrade notes for #818 - regenerate the resource-tags and rule-effect REST pages Co-Authored-By: Claude Opus 5.5 --- .../control-plane/query-engines/provision.md | 2 +- .../classifications/clearance.md | 2 +- .../classifications/tagging.md | 13 ++++++------ .../data-governance/data/explain-access.md | 14 ++++--------- .../data-governance/data/masks-and-filters.md | 21 +++++++++---------- .../people-and-groups/check-access.md | 2 +- platform/deploy-operate/upgrade.md | 15 +++++++++++++ platform/known-issues.md | 8 ++++--- .../rest-api/control-plane-catalog.md | 5 +++-- platform/reference/rest-api/perm.md | 1 - 10 files changed, 47 insertions(+), 36 deletions(-) diff --git a/platform/control-plane/query-engines/provision.md b/platform/control-plane/query-engines/provision.md index 45ef4a7..d456da4 100644 --- a/platform/control-plane/query-engines/provision.md +++ b/platform/control-plane/query-engines/provision.md @@ -59,7 +59,7 @@ The delegation modes: | **Auto — JWT, else impersonate (recommended)** | The default. Egret uses a per-user JWT when the coordinator can verify it, and otherwise falls back to impersonation. | | **JWT (per-user)** | Each query carries the caller's own short-lived JWT. No shared credential. OIDC users only. | | **Impersonate (service user)** | Egret connects as a trusted service account and runs the query as the caller. Works for any signed-in user. | -| **None (open engine)** | Every query runs with the connection's own credential. | +| **None (open engine)** | Every query runs with the connection's own credential. On an engine bound to a policy profile every query currently fails with *Principal … cannot become user …* (KI-333). | On the demo stack the default mode ran queries as the signed-in person in both cases: an SSO user (`member-sso`) and the local `admin@egret.local` account. `SELECT current_user` returned `member-sso` for the SSO user, and the grants, masks and row filters written in Governance applied to each of them (see [Masks and row filters](../../data-governance/data/masks-and-filters.md)). diff --git a/platform/data-governance/classifications/clearance.md b/platform/data-governance/classifications/clearance.md index 0460f49..b7cec49 100644 --- a/platform/data-governance/classifications/clearance.md +++ b/platform/data-governance/classifications/clearance.md @@ -56,7 +56,7 @@ On StarRocks the NULL masks need the table's columns, which egret reads from its - A change to the table's columns (add or drop a column), and a create, register or undrop of the table, recompiles the StarRocks profiles at once. The plugin applies the new masks at its next poll (KI-305). - A rename of the table, or of one of its columns, is held until every StarRocks plugin serves the masks under the new name, so a view over the renamed table reads NULL from its first read. See [Renames and drops](./tagging.md#renames-and-drops). - A change that would push a clearance over its ceiling is refused at the Iceberg commit with a 409 that names the table, for example *egret refused the table change: compiling the clearance on tag "PII" would expand to 501 policies on StarRocks, over the ceiling of 500: whole-table on demo_warehouse.sales.events (1 deny + 491 column masks) …*. The same check applies to a register and to an undrop from the **Recycle bin** (*egret refused the undrop: …*). A refused register is dropped again and stays in the Recycle bin (KI-306). -- For a table that is not in the catalog yet, only the select deny is compiled, and **Who can read?** says that a view over it is not protected until the table exists (KI-307). The masks are written when the table is created. +- For a table that is not in the catalog yet (an unknown name, a view, or a table not created yet), only the select deny is compiled, so a view over it is not protected until the table exists. Attaching the classification to such a table on a warehouse a StarRocks engine serves succeeds with a warning toast that says so (since platform #818; the API answers it as a notice, see [Tagging](./tagging.md#tag-a-table-or-a-column)), and **Who can read?** says it on the table's columns. The masks are written when the table is created. The classifications follow a rename of the table or of a column, and a dropped table keeps them for an undrop. See [Renames and drops](./tagging.md#renames-and-drops). diff --git a/platform/data-governance/classifications/tagging.md b/platform/data-governance/classifications/tagging.md index ff34a4f..a04bc37 100644 --- a/platform/data-governance/classifications/tagging.md +++ b/platform/data-governance/classifications/tagging.md @@ -13,9 +13,11 @@ A tag does nothing until it is applied. This page covers how to apply a tag to t Tags attach to tables and columns in egret-managed catalogs (Iceberg warehouses). Tagging needs the permission to update catalogs (**catalog_registry:update**) and **tag:read** to list the tags. 1. Open **Governance → Data** and select the table. -2. To tag the table, press **Add tag** in the header. To tag a column, select the column in **Columns** and press **Add tag** in the column panel. -3. In **Add tag**, open **Tag** and choose a tag. Tags already on the object are not offered; if there are none left the dialog says *Every tag is already on this table* (or *column*). With no tags defined it says *No tags are defined yet.* -4. Press **Add tag**. A toast says *Tag added.* +2. To tag the table, press **Add classification** in the header. To tag a column, select the column in **Columns** and press **Add classification** in the column panel. +3. In **Add classification**, open **Classification** and choose one. Classifications already on the object are not offered; if there are none left the dialog says *Every classification is already on this table.* (or *column*). With none defined it says *No classifications are defined yet.* +4. Press **Add classification**. A toast says *Classification attached.* + +When the attach needs your attention, a warning toast takes the place of the success toast. Today there is one such case: a classification with a [clearance](./clearance.md#on-columns-or-on-a-whole-table) attached to a whole table that a StarRocks engine serves but the catalog does not have yet (an unknown name, a view, or a table not created yet). The attach succeeds, and the warning says that a direct read is refused but a view over the table shows the values until the table exists in the catalog, when egret writes the column masks. The tag control under **Browse** shows the same warning. ![The Data page for the sessions table: header, engine docs-trino, and the Columns list with classifications and rules](/img/platform/admin/gov-table-overview.png) @@ -25,7 +27,7 @@ The tag then appears in the **Tags** column of the table's **Columns** list, in A tag applied to a table reaches its columns for policy purposes: engines roll tags up the resource hierarchy. A tag applied to a column does not show up when you explain access for the table. Ask about the column. ::: -When you add a classification to a whole table, the dialog says: *On a whole table, this classification counts for clearance on every column. A rule that masks by classification needs it on columns, not on the table.* Trino and StarRocks both enforce the [clearance](./clearance.md#on-columns-or-on-a-whole-table) and a classification row filter for a table-level classification. A Trino engine does not apply a classification row filter for a column-level one (KI-287). +When you add a classification to a whole table, the dialog says: *On a whole table, this classification counts for clearance on every column. A rule that masks by classification needs it on columns, not on the table.* Trino and StarRocks both enforce the [clearance](./clearance.md#on-columns-or-on-a-whole-table) and a classification row filter for a table-level classification. A row filter by classification also filters a table where the classification is only on columns, on both engines (since platform #818; see [Masks and row filters](../data/masks-and-filters.md#row-filters-by-classification)). A mask rule that names a classification already on a whole table is refused. The refusal names that table and lists the other tables the classification is on, up to 49; past that it says the list is cut short. @@ -147,9 +149,8 @@ These do not follow a rename: - The tag page counts tagged BI objects only; data tags are visible on the Data page. - A mask by tag on a column type the mask cannot apply to returns an engine error rather than a masked value. -- On Trino, a row filter by a classification that is only on columns filters nothing (KI-287). - A classified rename is held for up to 90 seconds, and a silent bound engine makes it fail (KI-310). Run from SQL Lab, a hold longer than 60 seconds shows a timeout, which says that the rename may still complete. -- Adding a classification with a clearance to a table that is not in the catalog yet does not say that a StarRocks view over it is unprotected until the table exists; **Who can read?** does (KI-307). +- A classification with a clearance attached to a table that is not in the catalog yet leaves a StarRocks view over it unprotected until the table exists. The attach warns about it (since platform #818), and **Who can read?** says so on the table's columns. The warning is given at the attach only, not when a clearance is added later to a classification already on such a table. ## Further reading diff --git a/platform/data-governance/data/explain-access.md b/platform/data-governance/data/explain-access.md index 4e278e5..b3069e9 100644 --- a/platform/data-governance/data/explain-access.md +++ b/platform/data-governance/data/explain-access.md @@ -73,7 +73,7 @@ The mask word is always the one the engine applies (the served clearance mask), | No NULL mask, because the table is not in the catalog | member-sso is **refused** `email` (clearance *PII*, on the whole table). No NULL mask for clearance *PII* is served here: egret found no table `sales.orders` in the catalog, so a view over it shows the values | | No NULL mask, because the column is newer than the compile | … No NULL mask for clearance *PII* is served here: the profile has not been recompiled since this column appeared, so a view over it shows the values | -The last two lines name a gap that closes by itself: the masks are written when the table is created (KI-307) or when the profile is next recompiled (KI-305). +The last two lines name a gap that closes by itself: the masks are written when the table is created or when the profile is next recompiled (KI-305). The first case is also announced when the classification is attached (see [Tagging](../classifications/tagging.md#tag-a-table-or-a-column)). **A reader with no grant.** StarRocks masks an uncleared reader's column to NULL without checking access on it, but the query still needs SELECT on the other columns it reads. When the person has no read on the table, the sentence says they are refused, not that they read NULL: @@ -152,15 +152,9 @@ The members table has a row per member: the **Member** (name and username) and, On StarRocks a read of a `MASK_NULL` column is not access-checked: it returns NULL, and the query is still refused unless the member may read another selected column. Such a member is counted as masked, not allowed. ::: -## Engine gaps the answer states +## Row filters by classification -One kind of rule compiles to nothing on Trino. Until a later release compiles it, the answer says so where you read it, rather than reporting a protection that is not there: - -| Engine | Rule | What the answer says | -|---|---|---| -| Trino | A row filter by classification, where the classification is on a column of the table, not on the table | member-sso sees **every row**: rule *US rows only* does not filter on Trino here, because PII is on a column, not on the table (KI-287) | - -The rule dialog warns about this gap before you create such a rule, see [Masks and row filters](./masks-and-filters.md#row-filters-by-classification-on-trino). Since platform #755 StarRocks enforces both a clearance and a row filter for a classification on a whole table, so the answer no longer states a StarRocks gap. +A row filter by classification filters a table on both engines whether the classification is on the table or on one of its columns, so the answer names the rule for either, for example *member-sso sees rows where region = 'us' (rule Filter rows PII P7)*. Before platform #818 Trino did not filter a table where the classification was only on columns, and the answer said *does not filter on Trino here*; that sentence is gone. See [Masks and row filters](./masks-and-filters.md#row-filters-by-classification). ## What explain does not tell you @@ -176,7 +170,7 @@ Explain works only for engines whose plugin version egret models: Trino with plu ## In the API -`POST /api/control-plane/perm/explain` (one user) and `POST /api/control-plane/perm/explain/principal` (a group's or role's members) return the same answer. Each profile carries `effect_lines`, the sentences as structured parts (`subject`, `strong`, `code`, `rule`, `clearance`, `principal`, `policy`, `catalog`, `text`) with the joined `text`. It also carries `row_filter` (the table's row-filter decision), `other_columns_access` (on StarRocks, when the column is not access-checked), `clearance_masks_missing` (on StarRocks, each clearance on the column or its table whose NULL mask is not served for the column, with `tag_id`, `tag_name` and a `reason` of `table_not_in_catalog` or `not_compiled`) and `row_filter_not_applied` (the Trino gap). Platform #755 removed `clearance_not_enforced`. A rule attribution carries the rule's `name`, and a tie's cause lists each tied policy's `outcomes`. With no profile, the response carries `effect_line`, the **Not enforced** sentence. All of these fields are optional additions; see the [REST API reference](../../reference/rest-api/perm.md). +`POST /api/control-plane/perm/explain` (one user) and `POST /api/control-plane/perm/explain/principal` (a group's or role's members) return the same answer. Each profile carries `effect_lines`, the sentences as structured parts (`subject`, `strong`, `code`, `rule`, `clearance`, `principal`, `policy`, `catalog`, `text`) with the joined `text`. It also carries `row_filter` (the table's row-filter decision), `other_columns_access` (on StarRocks, when the column is not access-checked), `clearance_masks_missing` (on StarRocks, each clearance on the column or its table whose NULL mask is not served for the column, with `tag_id`, `tag_name` and a `reason` of `table_not_in_catalog` or `not_compiled`). Platform #755 removed `clearance_not_enforced`, and platform #818 removed `row_filter_not_applied`, which reported the Trino gap of a column-level classification. A rule attribution carries the rule's `name`, and a tie's cause lists each tied policy's `outcomes`. With no profile, the response carries `effect_line`, the **Not enforced** sentence. All of these fields are optional additions; see the [REST API reference](../../reference/rest-api/perm.md). ## Troubleshooting diff --git a/platform/data-governance/data/masks-and-filters.md b/platform/data-governance/data/masks-and-filters.md index a4666a7..fc0ba06 100644 --- a/platform/data-governance/data/masks-and-filters.md +++ b/platform/data-governance/data/masks-and-filters.md @@ -44,7 +44,7 @@ Each row shows the rule **Name**, the path or tag it selects, its **Kind** (for | **Mask a column** | Replaces the column's values for every non-exempt reader. | | **Filter rows** | Keeps only rows for which the expression is true. | -With **Applies to** set to **Classification** and **Filter rows** chosen, a hint under **Kind** reads: *On Trino, a row filter by classification applies only where the classification is on the whole table.* See [Row filters by classification on Trino](#row-filters-by-classification-on-trino). +With **Applies to** set to **Classification** and **Filter rows** chosen, a hint under **Kind** reads: *A row filter by classification filters every table that carries the classification, on the whole table or on any of its columns, on Trino and on StarRocks.* See [Row filters by classification](#row-filters-by-classification). A new rule's suggested name follows **Kind** until you edit it: a name seeded as *Mask PII Data* becomes *Filter rows PII Data* when you switch to **Filter rows**. @@ -93,20 +93,20 @@ a view shows the masked values and no path. A reader who sees the real value in A change to the masks takes effect on the next compile of the profile. To apply it at once, compile the profile with `POST /api/control-plane/perm/compile/{profile_id}`. -## Row filters by classification on Trino +## Row filters by classification -A row filter by classification filters a table where the classification is on the table, on both engines. Where it is on a column, StarRocks filters the table and Trino does not yet: +A row filter by classification filters every table that carries the classification, on the whole table or on any of its columns, on Trino and on StarRocks (since platform #818): -- **Trino** filters by a classification only where the classification is on the **whole table**. On a table where it is only on columns, the row filter filters nothing on Trino (KI-287). -- **StarRocks** filters every table that carries the classification, on the table or on any of its columns, with one row-filter policy per table. +- **Trino**: a table that carries the classification on the table is filtered by the rule's tag policy. For each table where the classification is only on columns, egret also writes one row-filter policy on that table, named `perm-rule--..`. A table is never filtered twice by one rule. +- **StarRocks**: one row-filter policy per table that carries the classification, at either level. -A later release compiles the Trino case. Until then the preview counts the affected tables and shows a warning, for example: +Attaching or removing the classification, and a rename the classification follows, recompile the Trino and StarRocks profiles, so the per-table policies follow the classification. These tables count toward the rule's expansion ceiling on both engines. An install upgraded from an earlier version recompiles the affected Trino profiles when the server starts. -> On Trino this row filter filters nothing on 3 tables in demo_warehouse: *Personal data (PII)* is on their columns, not on the table. StarRocks filters them. Attach *Personal data (PII)* to the table to filter on Trino. +The preview asks for no tick for this kind of rule. **Who can read?** names the rule that filters the table, for example *member-sso sees rows where region = 'us' (rule Filter rows PII P7)*. -**Create** stays disabled until you tick **Create anyway: I understand this row filter filters nothing on Trino on these tables**; while it waits, its reason reads *Tick 'Create anyway' to create a row filter that filters nothing on Trino on these tables.* When editing a rule the words are **Save anyway** and *save*. Every new preview clears the tick. To filter on both engines, attach the classification to the table. - -**Who can read?** states the same gap for a person: *member-sso sees **every row**: rule US rows only does not filter on Trino here, because PII is on a column, not on the table*. +:::note +On Trino, a table that carries classification A on a column and classification B on the whole table, each with a row-filter rule, is filtered by B's rule only: Ranger lets a tag policy decide before a resource policy of the same priority. **Who can read?** answers what Trino does. +::: ## Edit and delete @@ -138,7 +138,6 @@ The local platform admin `admin@egret.local` got the same masked values: platfor - On a Trino profile without a tag service, tag rules compile to nothing. Egret refuses to bind such a profile to an engine. - A mask on a column whose type the mask cannot apply to can return an error from the engine rather than a masked value. -- A row filter by classification filters nothing on Trino where the classification is only on columns (KI-287). - A rule that selects by path is copied to the new name when its table or column is renamed, and the original stays on the old name; a rule that selects by tag follows because classifications do. A rule that selects by a pattern the new name escapes refuses the rename. See [Renames and drops](./grants.md#renames-and-drops). - A row filter or custom mask expression that names a renamed column is not rewritten (KI-321). diff --git a/platform/data-governance/people-and-groups/check-access.md b/platform/data-governance/people-and-groups/check-access.md index 00043a2..1b05720 100644 --- a/platform/data-governance/people-and-groups/check-access.md +++ b/platform/data-governance/people-and-groups/check-access.md @@ -36,7 +36,7 @@ The shield button at the top right of the People list (**Check access**) opens t |---|---| | **No policy matched** for someone you granted | The grant is on a catalog no engine serves, or the person's group was granted but they have no username yet. | | **Allowed** but the person sees masked values or fewer rows | A mask or row filter applies. Ask about the column: the sentence names the rule, or the clearance on StarRocks. | -| A row filter you wrote does not show | Trino does not compile a row filter by classification on a table where the classification is only on columns. The sentence says *does not filter on Trino here*. See [Explain access](../data/explain-access.md#engine-gaps-the-answer-states). | +| A row filter by classification does not show on a table | The table does not carry the classification, on the table or on any of its columns,. Before platform #818 Trino did not filter a table where the classification was only on columns. See [Explain access](../data/explain-access.md#row-filters-by-classification). | | A whole-table clearance shows *No NULL mask … is served here* | On StarRocks the direct read is refused, but a view over the table shows the values until egret has the table's columns: the table is not in the catalog yet, or the profile has not been recompiled since a column appeared. See [Explain access](../data/explain-access.md#clearance-on-starrocks). | | A renamed table is **Allowed** where a deny or a path mask applied before the rename | It, or the view, was renamed before the version of egret that carries grants and rules to the new name (views and hand-written policies reached through a pattern are covered since platform #814), which left them on the old name. The Governance overview lists the rows left behind as *permissions on tables that do not exist*. Write them on the new name. See [Renames and drops](../data/grants.md#renames-and-drops). | | A person with an allow grant is **Refused** on a table renamed a moment ago | The allow grant moved to the new name after the rename, and the engine applies it at its next policy poll, up to about two polls (KI-317). Ask again in a minute. | diff --git a/platform/deploy-operate/upgrade.md b/platform/deploy-operate/upgrade.md index 873a954..7d4b724 100644 --- a/platform/deploy-operate/upgrade.md +++ b/platform/deploy-operate/upgrade.md @@ -352,6 +352,21 @@ is no migration and no API change. From platform #815 deleting a local group deletes the grants that name it. Migration 0212 deletes the group grants that earlier deletes left behind (KI-324). They gave nobody access, so nobody's access changes. +### Row filters by a column classification on Trino, and the attach notice (platform #818) + +From platform #818 there is no migration. + +- **Behaviour change:** on Trino a row filter by classification now filters every table where the classification is only + on columns, as StarRocks already did, through one row-filter policy per such table. At start, egret recompiles the + Trino profiles this reaches, so a rule written before the upgrade starts filtering those tables without another write + (KI-287). People who read every row of such a table on Trino before + the upgrade see only the filtered rows after it; **Who can read?** shows the rule. +- **API change:** `PUT /api/control-plane/warehouses/{warehouse_id}/resource-tags` answers `{"ok": true, "notices": [...]}`. + Each notice has a `code` and a `message`; the one code today is `clearance_masks_pending_table` (KI-307). See + [Control plane: catalog](../reference/rest-api/control-plane-catalog.md). +- **API change:** the rule effect no longer carries `trinoColumnOnly`, and explain no longer carries + `row_filter_not_applied`. + ## Rolling back There is no supported in-place rollback. Restore the database from the backup taken in step 2, then start the previous diff --git a/platform/known-issues.md b/platform/known-issues.md index 55278d6..c3a3c28 100644 --- a/platform/known-issues.md +++ b/platform/known-issues.md @@ -37,7 +37,7 @@ and missing pieces. Entries are **recorded, not fixed**; they feed the product r ## Findings from the detailed pages -Collected while writing the detailed pages for each area (October 2026), checked against the running demo instance and the product source. KI-154 to KI-213 come from re-verifying every area on the Docker demo stack (`just demo-up`, October 2026), which also closed or updated older rows (marked *Status* or *Docker demo stack*). KI-214 to KI-235 come from a whole-app UI consistency sweep of 78 screens (admin, light theme, 1440×900, same stack), then the dark theme, 768 and 390 px widths, the editors and a non-admin member. KI-236 to KI-246 come from the Wave 1 fixes of the Known-issues fix programme (October 2026), which also closed the rows marked *fixed in platform #…*. KI-247 to KI-259 come from the Wave 2 fixes (SQL Lab, charts, datasets and the Data API: platform #720, #721, #724 and #725), which closed or partly closed the rows they name. KI-260 to KI-266 come from the Wave 2b fixes (alerts and reports delivery, authentication and roles, engines and monitor: platform #726, #727 and #728), which closed the rows they name; KI-021 was closed as by design and KI-148 as no longer applying. KI-267 to KI-269 come from the Wave 3 page-structure fix (titles, page width, tabs, status badges, list links and detail headers: platform #734), which closed KI-214, KI-215, KI-216, KI-222, KI-223 and KI-224; KI-270 was seen during the docs screenshot re-shoot (docs #31). KI-271 to KI-279 come from the Wave 3 controls and states fixes (shared select and date picker, Notifications and the dashboard Filter settings panel: platform #736; the refusal inside the shell, sidebar and settings cleanup, small-screen layout: platform #737) and from the storage and Docker engine fix (platform #735), which closed KI-005, KI-012, KI-217, KI-218 and KI-235 (the last two partly as written), KI-225, KI-227, KI-230, KI-231, KI-233, KI-234, KI-236 and KI-263. KI-280 to KI-283 come from the Wave 3 dashboard fixes (platform #739), which closed KI-003, KI-064 to KI-069, KI-074, KI-075 and KI-168; the SQL Lab fixes of the same wave (platform #740) closed KI-082 to KI-085, KI-089, KI-091 to KI-094, KI-160 to KI-162, KI-228 and KI-229 and found no new defect. KI-303 to KI-307 come from StarRocks enforcing whole-table classifications (platform #755), which closed KI-286 and KI-288; KI-303 was found while building it and the others in its live check. KI-284 and KI-285 are follow-ups of the username takeover fix (platform #741: a deleted user's engine username is never given out again, and the engines' `egret-admin` policies follow the active platform admins). KI-286 to KI-288 are engine gaps that **Who can read?** and the rule dialog state since platform #744 (verify a mask per user), and KI-289 was found in its live check. KI-290 to KI-295 come from the Wave 3 chart editor fixes (platform #743), which closed KI-008, KI-050, KI-053 to KI-056 and KI-058, and the alerts and reports fixes (platform #742), which closed KI-038, KI-039, KI-171 and KI-219 to KI-221; KI-295, an S2 bug in the Schedule tab that #742 found, was fixed by platform #745. KI-296 to KI-299 come from the Wave 3 Data API and Apps fixes (platform #746), which closed KI-025, KI-027, KI-031, KI-042, KI-176, KI-177 and KI-182 to KI-184; the workflows fixes (platform #747), which closed KI-014, KI-119, KI-121 to KI-123, KI-189 to KI-191, KI-193 and KI-226, and found KI-120 not to reproduce; and the Control Plane, Governance and settings fixes (platform #748), which closed KI-001, KI-007, KI-011, KI-016, KI-131, KI-132, KI-203, KI-204 and KI-212. KI-298 and KI-299 were found while writing the docs for them. KI-300 and KI-301 come from the Wave 3 small UX cleanup (platform #751), which closed KI-251, KI-280 to KI-282, KI-290 to KI-294 and KI-296 to KI-299 and left KI-283 open; KI-302 was found while writing the docs for it. The print page and wording fixes (platform #754) closed KI-283, KI-301 and KI-302 and found no new defect. KI-303 to KI-307 come from StarRocks enforcing whole-table classifications (platform #755), which closed KI-286 and KI-288; KI-303 was found while building it and the others in its live check. KI-308 to KI-310 come from classifications following a renamed table or column (platform #757), which closed KI-303: KI-308 and KI-309 are the related defects it recorded and left for a later package, and KI-310 is the cost of its hold. KI-311 to KI-314 come from the catalog guard authorizing renames and commits by the tables they change (platform #758), which closed no row: KI-311 and KI-312 were found while building it, KI-313 and KI-314 in its live check. KI-315 and KI-316 come from vended credentials following catalog grants (platform #760), which closed KI-311: KI-315 was recorded in its design and KI-316 in its review. KI-317 to KI-321 come from grants, rules and catalog grants following a renamed table (platform #764), which closed KI-308 and KI-309 and updated KI-310: KI-317 is the gap its design accepts, KI-318 was found in its live check, and KI-319 to KI-321 are related defects its design recorded and left for later. KI-322 to KI-328 come from the DG debt Wave 2 fixes: tag-capable profiles, the seed and Trino config messages, the policy editor's heading and the SQL Lab schema-change timeout (platform #805), which closed KI-125, KI-135, KI-149, KI-267 and KI-318; the catalog guard's namespace create, listings, body deadline and storage overlap (platform #806), which closed KI-312 and KI-313 and narrowed KI-316; and platform admins held through groups (platform #807), which closed KI-260, KI-284 and KI-285. KI-322 to KI-324 are defects #807 found and left, and KI-325 is the gap #805's design accepts. KI-326 to KI-328 are three migration refusals closed as by design (platform issues #783 to #785), and KI-202, KI-305, KI-306 and KI-310 are kept as limitations. The lock-timeout fix of the same wave (platform #804) changed no documented behaviour. KI-329 to KI-331 come from the DG debt Wave 3 fixes: a classification's **Manual policies** tab now lists the hand-written tag policies the engines apply to it (platform #809), and the permission follow-ups (platform #810: the boot repair of role-referencing profiles is retried, group rename and delete ask for **policy:read** only when they refuse, grant and rule writes re-read the row under the lock, and grant and rule ids outside the caller's workspace answer 404), which closed KI-322 and KI-323. KI-329 to KI-331 are latent defects of the same kind #810 found and left. The second part of DG debt Wave 3 closed KI-329 to KI-331 (deny and expired grants never give a role: platform #811), KI-319 and KI-320 (views follow renames, and hand-written policies reached through a pattern are checked at a rename: platform #814) and KI-324 (deleting a group deletes its grants: platform #815), and narrowed KI-315 (vended credentials live 15 minutes: platform #812). The compile-mint fixes of the same part (platform #813) refuse a write whose set of policy profiles grew while it ran with a retryable 409 instead of a database deadlock; KI-332 is the defect that package found and left. Same legend as above. **Open S1 items:** none (KI-013, KI-040, KI-059, KI-060, KI-112, KI-136, KI-159, KI-175 and KI-187 are closed). +Collected while writing the detailed pages for each area (October 2026), checked against the running demo instance and the product source. KI-154 to KI-213 come from re-verifying every area on the Docker demo stack (`just demo-up`, October 2026), which also closed or updated older rows (marked *Status* or *Docker demo stack*). KI-214 to KI-235 come from a whole-app UI consistency sweep of 78 screens (admin, light theme, 1440×900, same stack), then the dark theme, 768 and 390 px widths, the editors and a non-admin member. KI-236 to KI-246 come from the Wave 1 fixes of the Known-issues fix programme (October 2026), which also closed the rows marked *fixed in platform #…*. KI-247 to KI-259 come from the Wave 2 fixes (SQL Lab, charts, datasets and the Data API: platform #720, #721, #724 and #725), which closed or partly closed the rows they name. KI-260 to KI-266 come from the Wave 2b fixes (alerts and reports delivery, authentication and roles, engines and monitor: platform #726, #727 and #728), which closed the rows they name; KI-021 was closed as by design and KI-148 as no longer applying. KI-267 to KI-269 come from the Wave 3 page-structure fix (titles, page width, tabs, status badges, list links and detail headers: platform #734), which closed KI-214, KI-215, KI-216, KI-222, KI-223 and KI-224; KI-270 was seen during the docs screenshot re-shoot (docs #31). KI-271 to KI-279 come from the Wave 3 controls and states fixes (shared select and date picker, Notifications and the dashboard Filter settings panel: platform #736; the refusal inside the shell, sidebar and settings cleanup, small-screen layout: platform #737) and from the storage and Docker engine fix (platform #735), which closed KI-005, KI-012, KI-217, KI-218 and KI-235 (the last two partly as written), KI-225, KI-227, KI-230, KI-231, KI-233, KI-234, KI-236 and KI-263. KI-280 to KI-283 come from the Wave 3 dashboard fixes (platform #739), which closed KI-003, KI-064 to KI-069, KI-074, KI-075 and KI-168; the SQL Lab fixes of the same wave (platform #740) closed KI-082 to KI-085, KI-089, KI-091 to KI-094, KI-160 to KI-162, KI-228 and KI-229 and found no new defect. KI-303 to KI-307 come from StarRocks enforcing whole-table classifications (platform #755), which closed KI-286 and KI-288; KI-303 was found while building it and the others in its live check. KI-284 and KI-285 are follow-ups of the username takeover fix (platform #741: a deleted user's engine username is never given out again, and the engines' `egret-admin` policies follow the active platform admins). KI-286 to KI-288 are engine gaps that **Who can read?** and the rule dialog state since platform #744 (verify a mask per user), and KI-289 was found in its live check. KI-290 to KI-295 come from the Wave 3 chart editor fixes (platform #743), which closed KI-008, KI-050, KI-053 to KI-056 and KI-058, and the alerts and reports fixes (platform #742), which closed KI-038, KI-039, KI-171 and KI-219 to KI-221; KI-295, an S2 bug in the Schedule tab that #742 found, was fixed by platform #745. KI-296 to KI-299 come from the Wave 3 Data API and Apps fixes (platform #746), which closed KI-025, KI-027, KI-031, KI-042, KI-176, KI-177 and KI-182 to KI-184; the workflows fixes (platform #747), which closed KI-014, KI-119, KI-121 to KI-123, KI-189 to KI-191, KI-193 and KI-226, and found KI-120 not to reproduce; and the Control Plane, Governance and settings fixes (platform #748), which closed KI-001, KI-007, KI-011, KI-016, KI-131, KI-132, KI-203, KI-204 and KI-212. KI-298 and KI-299 were found while writing the docs for them. KI-300 and KI-301 come from the Wave 3 small UX cleanup (platform #751), which closed KI-251, KI-280 to KI-282, KI-290 to KI-294 and KI-296 to KI-299 and left KI-283 open; KI-302 was found while writing the docs for it. The print page and wording fixes (platform #754) closed KI-283, KI-301 and KI-302 and found no new defect. KI-303 to KI-307 come from StarRocks enforcing whole-table classifications (platform #755), which closed KI-286 and KI-288; KI-303 was found while building it and the others in its live check. KI-308 to KI-310 come from classifications following a renamed table or column (platform #757), which closed KI-303: KI-308 and KI-309 are the related defects it recorded and left for a later package, and KI-310 is the cost of its hold. KI-311 to KI-314 come from the catalog guard authorizing renames and commits by the tables they change (platform #758), which closed no row: KI-311 and KI-312 were found while building it, KI-313 and KI-314 in its live check. KI-315 and KI-316 come from vended credentials following catalog grants (platform #760), which closed KI-311: KI-315 was recorded in its design and KI-316 in its review. KI-317 to KI-321 come from grants, rules and catalog grants following a renamed table (platform #764), which closed KI-308 and KI-309 and updated KI-310: KI-317 is the gap its design accepts, KI-318 was found in its live check, and KI-319 to KI-321 are related defects its design recorded and left for later. KI-322 to KI-328 come from the DG debt Wave 2 fixes: tag-capable profiles, the seed and Trino config messages, the policy editor's heading and the SQL Lab schema-change timeout (platform #805), which closed KI-125, KI-135, KI-149, KI-267 and KI-318; the catalog guard's namespace create, listings, body deadline and storage overlap (platform #806), which closed KI-312 and KI-313 and narrowed KI-316; and platform admins held through groups (platform #807), which closed KI-260, KI-284 and KI-285. KI-322 to KI-324 are defects #807 found and left, and KI-325 is the gap #805's design accepts. KI-326 to KI-328 are three migration refusals closed as by design (platform issues #783 to #785), and KI-202, KI-305, KI-306 and KI-310 are kept as limitations. The lock-timeout fix of the same wave (platform #804) changed no documented behaviour. KI-329 to KI-331 come from the DG debt Wave 3 fixes: a classification's **Manual policies** tab now lists the hand-written tag policies the engines apply to it (platform #809), and the permission follow-ups (platform #810: the boot repair of role-referencing profiles is retried, group rename and delete ask for **policy:read** only when they refuse, grant and rule writes re-read the row under the lock, and grant and rule ids outside the caller's workspace answer 404), which closed KI-322 and KI-323. KI-329 to KI-331 are latent defects of the same kind #810 found and left. The second part of DG debt Wave 3 closed KI-329 to KI-331 (deny and expired grants never give a role: platform #811), KI-319 and KI-320 (views follow renames, and hand-written policies reached through a pattern are checked at a rename: platform #814) and KI-324 (deleting a group deletes its grants: platform #815), and narrowed KI-315 (vended credentials live 15 minutes: platform #812). The compile-mint fixes of the same part (platform #813) refuse a write whose set of policy profiles grew while it ran with a retryable 409 instead of a database deadlock; KI-332 is the defect that package found and left. The last DG debt code package (platform #818) closed KI-287 (on Trino a row filter by classification filters tables where the classification is only on columns, as StarRocks does) and KI-307 (attaching a clearance to a table not in the catalog warns at the attach); KI-333 and KI-334 come from its live check. Same legend as above. **Open S1 items:** none (KI-013, KI-040, KI-059, KI-060, KI-112, KI-136, KI-159, KI-175 and KI-187 are closed). ### Data API, Alerts and Apps @@ -337,7 +337,7 @@ Collected while writing the detailed pages for each area (October 2026), checked | KI-284 | Control plane / Users | Gap | S2 | The last active platform admin can still be removed by paths the guard does not cover: removing them from a group that holds `platform_admin` (when that is how they hold it), deleting that group, or `PUT /api/admin/users/{id}/platform-roles`. Disabling or deleting the user and unassigning the role from the user or from a group answer 409 *Cannot remove the last active platform admin*. *Workaround:* before changing an admin group, check on **Governance → People & groups → Roles → platform_admin** that another active user holds the role; if it happens, `egret init --force` creates another local platform admin (see [CLI](./reference/cli#init)). (A follow-up of platform #741.) *Status:* fixed in platform #807 (October 2026): every path that can take `platform_admin` away is guarded inside its own write, under one lock: user and group role revoke, disable, delete (including a Data API service account), removing a group member, deleting a group and `PUT /api/admin/users/{id}/platform-roles` answer 409 *Cannot remove the last active platform admin* and change nothing. Two removals at the same moment can no longer both pass. The OIDC group sync (at sign-in, and the sweep of stale memberships) cannot refuse: it keeps the membership that is the last admin's only hold on `platform_admin`, logs a warning, and applies the rest. The guard counts admins without reading grant expiry (KI-322). | | KI-285 | Control plane / Users | UX | S3 | The Users list and the user page see only roles granted directly. For a user who is a platform admin only through a group, the disable dialog leaves out *While disabled they lose all-access on engines.* although they do lose it, and the early last-active-admin check (*No other active user holds platform_admin directly, …*) can be wrong either way; the server's 409 is what holds. Related: KI-260. (A follow-up of platform #741.) *Status:* fixed in platform #807 (October 2026): the disable dialog, the early last-admin check (now *No other active user holds platform_admin, and the last active platform admin cannot be disabled.*), the **Impersonate** menu item and the Access overview's admin count use the roles held directly or through a group. | | KI-286 | Governance / Classifications | Gap | S2 | On StarRocks a classification with a clearance that is on a **whole table** (not on its columns) compiles no clearance, so the clearance neither refuses nor nulls an uncleared reader who has a grant on the table. Trino enforces it on every column. **Who can read?** says *Clearance … is **not enforced** here: it is on the whole table, and StarRocks applies a clearance only where it is on a column.*, and the Add tag dialog and Clearance tab say StarRocks applies a clearance only where the classification is on a column. *Workaround:* put the classification on the columns. A later release compiles it. (Stated since platform #744.) *Status:* fixed in platform #755 (October 2026): on StarRocks a whole-table classification with a clearance compiles one select deny on the table and a NULL mask per column. An uncleared reader is refused on every direct read of the table (`SELECT *` and `count(*)` included) and reads NULL through a view, and **Who can read?** says *… is **refused** `email` (clearance PII, on the whole table); through a view it reads NULL*. The *not enforced* sentence and the StarRocks half of the dialog and tab texts are gone. A schema change on such a table recompiles the profile at once (KI-305). | -| KI-287 | Governance / Rules | Gap | S2 | On Trino a row filter by classification filters nothing on a table where the classification is on columns only, not on the table. **Who can read?** says *… sees **every row**: rule … does not filter on Trino here, because … is on a column, not on the table*, and the rule dialog counts the tables and asks for **Create anyway**. *Workaround:* attach the classification to the table as well. A later release compiles it. (Stated since platform #744.) | +| KI-287 | Governance / Rules | Gap | S2 | On Trino a row filter by classification filters nothing on a table where the classification is on columns only, not on the table. **Who can read?** says *… sees **every row**: rule … does not filter on Trino here, because … is on a column, not on the table*, and the rule dialog counts the tables and asks for **Create anyway**. *Workaround:* attach the classification to the table as well. A later release compiles it. (Stated since platform #744.) *Status:* fixed in platform #818 (November 2026): on Trino a row filter by classification also filters every table where the classification is only on columns, with one row-filter policy per such table next to the tag policy, so Trino and StarRocks agree. **Who can read?** names the rule instead of the warning, the rule dialog no longer counts the tables or asks for **Create anyway** for it, and the Kind hint says both engines filter. Existing installs recompile the affected Trino profiles at start. | | KI-288 | Governance / Rules | Gap | S2 | On StarRocks a row filter by classification filters nothing on a table where the classification is on the table only, not on any of its columns. **Who can read?** says *… does not filter on StarRocks here, because … is on the table, not on a column*, and the rule dialog counts the tables and asks for **Create anyway**. *Workaround:* attach the classification to a column of the table as well. A later release compiles it. (Found in the live check of platform #744.) *Status:* fixed in platform #755 (October 2026): the row filter now filters on StarRocks where the classification is on the table, in the same per-table policy as for a column-level classification. The StarRocks warning and its **Create anyway** tick are gone from the rule dialog, and the Kind hint mentions Trino only. | | KI-289 | Governance / Rules | Bug | S3 | A second row filter on a table that already has one is refused by the preview (two row filters would decide the table differently), but the refusal links the new rule to `/governance/rules/` with an id that was never stored, so the link opens a page that cannot load the rule (404). *Steps:* Masks & filters on a table with a row filter → **Add rule** → **Filter rows** → **Preview**. (Found in the live check of platform #744; probably older than it.) *Status:* fixed in platform #768 (October 2026): the refusal names the rule being created as plain text, with no link, and links only the rules that are stored. The refusal still says the two row filters decide the table differently. | | KI-290 | Dashboards / cards | Consistency | S3 | A chart on a dashboard does not use the dataset's **Display names**, so since #743 (KI-050) the same chart reads differently on its own page and on a card: the chart page's legend, tooltip and axis title say **Revenue** and its drill menu names columns by their display names, while the card says `revenue` and its drill menu names `orderpriority`. Same cause as KI-251 (the dashboard does not load each card's dataset); a **Column title** set in the chart itself applies on both. *Steps:* "Revenue by order priority" on its chart page, then on a dashboard → hover a bar. (Found while fixing KI-050.) *Status:* fixed in platform #751 (October 2026): same fix as KI-251: a card's legend, tooltip and axis title say **Revenue**, and its drill menu names columns as the card draws them (the chart's **Column title**, else the dataset's **Display name**). | @@ -357,7 +357,7 @@ Collected while writing the detailed pages for each area (October 2026), checked | KI-304 | Governance / Classifications | Security | S3 | On StarRocks, a reader who gets a real mask (not *Unmasked*) on any column of an Iceberg table, such as a rule's **Hash** or a NULL mask, sees the table's hidden `_file` column (the full object-store path of the data file) and `_pos` in `SELECT *`. An uncleared reader of a whole-table classification is refused instead. Older than platform #755: column masks cause it. *Steps:* a mask rule on one column of an Iceberg table; `SELECT *` on StarRocks as a non-exempt reader. (Measured in the live check of platform #755.) *Status:* fixed in platform #766 (October 2026): every StarRocks table with a mask gets one deny on `_file` and `_pos` for each reader who does not see the real value in every masked column. That reader's `SELECT *` on the table is refused with error 5204 on `_file`; named columns and views work as before. A mask on a table selected by a wildcard or an excluded name gets no deny, so the leak stays there. *Status:* fixed in platform #798 (October 2026): a mask whose table selector is a wildcard or an exclusion now gets a hidden-column deny for each table it matches in the registered catalogs, with the same exemption rule as an exact table. A wildcard that matches no registered catalog refuses the write. Not covered: a catalog that egret does not register (for example `default_catalog`), and a table created after the last compile, until the next compile. | | KI-305 | Governance / Classifications | Limitation | S3 | On StarRocks, a view over a table with a whole-table clearance can show an uncleared reader real values for a short time after the table changes: egret writes the NULL masks before an undrop or a column add returns, but the plugin applies them only at its next poll (30 seconds by default). A direct read is refused throughout. If the recompile that follows a table change fails (another permission write held the profile longer than 10 seconds), a view that reads a new column shows its real values until the profile's next recompile or the next start of egret; the Governance overview shows **Profile compile failed** meanwhile. (Measured in the live check of platform #755; the failed recompile is from its design.) The live check of platform #757 measured the undrop window at 23 seconds. #757 holds a classified rename until the plugins serve the new policies, but not an undrop or a column add. During such a hold, an unrelated permission write that recompiles the StarRocks profile drops the renamed table's NULL masks from the compile until the hold finishes, so a view over the new name can show real values for that time; the direct read stays refused. *Status:* kept as a limitation (October 2026). | | KI-306 | Control plane / Catalogs | Limitation | S3 | A table register that egret refuses because the recompile would cross a clearance ceiling (409 *egret refused the table change: …; the table was not registered*) is dropped again, but softly: the table then sits in the catalog's **Recycle bin** for the warehouse's soft-delete retention (7 days by default), and undropping it is refused with *egret refused the undrop: …*. Nothing leaks, but the entry looks recoverable. *Steps:* register a table whose metadata has more columns than the ceiling allows, under a name that carries a whole-table clearance; open the Recycle bin. (Found in the live check of platform #755.) *Status:* kept as a limitation (October 2026). | -| KI-307 | Governance / Classifications | UX | S3 | Attaching a classification with a clearance to a table that is not in the catalog yet (unknown, a view, or not created) compiles the whole-table select deny but no NULL masks on StarRocks, so a view over it is not protected until the table exists. The attach itself answers only success: the skip is reported by **Who can read?** on the table's columns (*No NULL mask for clearance … is served here: egret found no table … in the catalog, so a view over it shows the values*) and by a profile recompile, not to the steward who attached it. The masks are written when the table is created. (Found in the live check of platform #755.) | +| KI-307 | Governance / Classifications | UX | S3 | Attaching a classification with a clearance to a table that is not in the catalog yet (unknown, a view, or not created) compiles the whole-table select deny but no NULL masks on StarRocks, so a view over it is not protected until the table exists. The attach itself answers only success: the skip is reported by **Who can read?** on the table's columns (*No NULL mask for clearance … is served here: egret found no table … in the catalog, so a view over it shows the values*) and by a profile recompile, not to the steward who attached it. The masks are written when the table is created. (Found in the live check of platform #755.) *Status:* fixed in platform #818 (November 2026): the attach answers `notices` with `clearance_masks_pending_table` when the attachment is to a whole table, the classification has a clearance with roles, a StarRocks profile serves the warehouse and the catalog has no active table of that name. Governance **Data** → **Add classification** and the catalog **Browse** tag control show it as a warning toast in place of the success toast. The attach still succeeds and the masks are still written once the table exists. A clearance added later to a classification already on such a table gives no notice. | | KI-308 | Governance / Grants | Security | S2 | A path-selector grant or rule (a grant, a mask or a row filter written on the table's path or one of its columns, not selected by a classification) and a hand-written Ranger policy stay on the old name when a table or column is renamed; only classifications follow it (platform #757). An allow is lost, which fails closed, but a **deny** no longer applies to the renamed table, and a path mask or row filter stops masking or filtering it, so data leaks. A table created later under the old name inherits all of them. *Workaround:* before a rename, write the deny, mask or filter on the new name too (or select by a classification, which follows the table), and remove the old-name grants and rules after it. (Found while building platform #757.) *Status:* fixed in platform #764 (October 2026): a rename of a table or column through egret's Iceberg catalog copies its deny grants, path masks and path row filters to the new name before the catalog commits it, and keeps the originals on the old name, dormant, so a new table at the old name and the swap pattern stay restricted. egret holds the rename until every bound Trino and StarRocks plugin serves the copies (at most 90 s; a bound engine that does not confirm makes the rename a 409, the copies are removed and nothing is renamed), so the renamed table is restricted from its first read. Allow grants move to the new name after the commit and keep their ids (KI-317). A rename is refused with a 409 that names the rows when a deny, mask or row filter covers the old name through a pattern and not the new name, unless a restriction of the same kind and at least the same effect already covers the new name, and when an enabled hand-written policy names the table literally with a deny, a mask, a row filter or an exclusion list. A pattern allow that stops covering the table, and a hand-written policy that only allows it, are recorded in **Recent changes**. A recursive namespace drop deletes the allow grants written on its tables by name and keeps the restrictions. Grants and rules that renames left on old names before #764 are reported in the Governance overview. Not covered: views (KI-319), hand-written policies that name a table only through a pattern (KI-320) and expressions that name a renamed column (KI-321). See [Renames and drops](./data-governance/data/grants#renames-and-drops). | | KI-309 | Control plane / Catalogs | Bug | S2 | A catalog grant on a table (**Control plane → Catalogs → \ → Roles & grants**, for example `analytics.orders · read`) stays on the old name when the table is renamed. A catalog client whose role has only that grant loses access to the renamed table, and gains it on a new table created under the old name. A grant on **All tables** of the namespace, or on the whole catalog, still applies while the table stays in that namespace. *Workaround:* add the grant on the new name and remove the old one. (Found while building platform #757.) *Status:* fixed in platform #764 (October 2026): a catalog grant on a table moves to the new name right after the catalog commits a rename of the table, in any namespace and at any depth, and the vended credentials follow it; a grant the role already holds on the new name is merged. **Recent changes** records it as *… moved catalog grant read of role analysts from sales.orders to sales.orders_v2 (table renamed)* for a reader with `catalog_registry:read`. A recursive namespace drop deletes the table grants under the namespace. Grants that renames left on old names before #764 are reported in the Governance overview (*catalog grants on tables that do not exist*), and so is a name a rename left without its catalog grants under a new table. A grant on a view still stays (KI-319). See [Catalog detail](./control-plane/catalogs/catalog-detail#roles--grants-warehouse). | | KI-310 | Governance / Classifications | Limitation | S3 | A rename of a classified table or column waits until every bound engine serves the moved classifications. With StarRocks bound this costs about two policy polls (36 to 56 seconds measured with the 30-second poll), because the StarRocks plugin reports a version one poll after it downloads it; the limit is 90 seconds. Meanwhile another classified rename in the same catalog, and a schema change or undrop of a table there that carries a whole-table classification with a clearance, wait up to 30 seconds and are then refused with a 409 to retry. A bound engine whose plugins do not poll makes every classified rename fail after 90 seconds with *engines did not confirm the moved classifications within 90 s: …*. A rename run from SQL Lab gives up first, at SQL Lab's 60-second query limit (`engine.query_timeout_secs`), and shows *timeout: the query ran longer than the 60 s limit* instead of egret's reason; egret still refuses the rename and undoes the copy. (Measured in the live check of platform #757.) Since platform #764 the same hold applies to a rename that copies a deny grant, path mask or path row filter, classified or not, and it then also waits for the Trino plugins' policy version: 54 to 57 seconds were measured with both engines bound, and the timeout message reads *engines did not confirm the copied restrictions within 90 s: …* when only restrictions were copied. A rename that carries only allow grants is held without a wait. SQL Lab's limit also hides a hold that succeeds after more than 60 seconds (KI-318). *Status:* kept as a limitation (October 2026). | @@ -383,6 +383,8 @@ Collected while writing the detailed pages for each area (October 2026), checked | KI-330 | Auth / Impersonation | Defect | S3 | **Latent: no API writes such a grant.** Two session checks of workspace roles do not read a grant's `effect`: the re-check of `ws_admin` when an OIDC step-up for a workspace-scoped impersonation completes, and the list of the target's roles in that workspace that the target rule (*a member of the workspace and not its `ws_admin`*) reads. A workspace-scope `deny` grant, if one were written straight into the database, would count as holding that role: a deny of `ws_admin` held by the impersonating admin would pass that re-check, and a deny of any other role held by the target would make the target count as a member who can be impersonated. The workspace members API writes no `effect`, so every grant it writes is `allow`. This is the workspace form of KI-323. (Found while building platform #810.) *Status:* fixed in platform #811 (November 2026): both checks count only live `allow` grants. The same rule now applies to the other reads that decide workspace access: a principal whose only workspace grant is a deny is refused in the workspace (403) and the workspace switcher does not list it, and the last-`ws_admin` guard counts only live `allow` grants, so the last live admin can no longer be removed or demoted (409) while a lapsed or deny `ws_admin` row exists. | | KI-331 | Control plane / Users | Defect | S3 | **Latent: nothing in the server reads it.** `RoleRepo::platform_admin_user_ids`, a list of platform admins (direct and through a group), reads neither `expires_at` nor `effect`. On platform main only tests call it; the engine-admin set and the last-admin guard use their own query, which honours both since platform #810 (KI-322). A check with the same blind spot is in use: when an engine grant would let a user impersonate another, the impersonation-chain check refuses the grant if the chain reaches a platform admin, and it also counts a lapsed or `deny` `platform_admin` grant. That errs on the side of refusing: such a grant is refused although the user it reaches is no longer an admin. (Found while building platform #810.) *Status:* fixed in platform #811 (November 2026): the list counts only live `allow` grants. The impersonation-chain check still counts lapsed and `deny` grants, on purpose: it errs on the side of refusing. The member list's platform-admin badge, the console's effective roles and `egret init`'s check that an admin exists now count only live `allow` grants as well. | | KI-332 | Engines / Policy profiles | Defect | S3 | Moving an engine to another policy profile with `PUT /api/control-plane/engines/{id}` (an `authzServiceId` other than the bound one) bumps the policy version of the old and the new profile in one `UPDATE`, from the engine-binding trigger of migration 0187, in no fixed order. Two such writes at the same time that lock the same two profiles in opposite orders can deadlock; the database then cancels one of them, which fails and changes nothing. The compile-mint lock is not involved, and the console's **Settings** tab of an external engine uses `PATCH …/external`, which locks both profiles first since platform #813. Not reproduced: found by reading the code. *Workaround:* retry the write. (Found while building platform #813.) | +| KI-333 | Engines / Trino | Bug | S2 | A provisioned Trino engine whose **Per-user delegation** is **None (open engine)** gets none of the per-user baseline policies egret writes for a delegating engine (each user may become themself, run queries, and set `query_max_run_time` on their session). The Ranger plugin checks them on every session, so every query fails with *Principal X cannot become user X*. In the source, egret seeds the baseline only for an engine that delegates (JWT or impersonate). *Workaround:* use **Auto**, **JWT** or **Impersonate**. (Found in the live check of platform #818.) | +| KI-334 | Governance / Data | Bug | S3 | The engines in the Governance **Data** page's object header can show a running engine as `provisioning`: in the live check of platform #818 the Trino engine `p7jwt` showed `provisioning` there, while the engine API said it was running. The header shows the `status` the governance browse API answers for each engine attached to the catalog, read from the stored engine row; why that row still said `provisioning` is not confirmed (seen with the Docker provisioner). Nothing else depends on the label. (Found in the live check of platform #818.) | ## Not verified diff --git a/platform/reference/rest-api/control-plane-catalog.md b/platform/reference/rest-api/control-plane-catalog.md index 863e7a2..8ad8901 100644 --- a/platform/reference/rest-api/control-plane-catalog.md +++ b/platform/reference/rest-api/control-plane-catalog.md @@ -758,15 +758,16 @@ Operation id: `assign_resource_tag`. Auth: Session cookie and `X-Egret-CSRF` hea | `column` | string, nullable | no | Empty / omitted ⇒ a table-level tag. | | `tag_id` | string (uuid) | yes | | -**Response 200 (application/json)**: `Ack` +**Response 200 (application/json)**: `AssignResourceTagResponse` | Field | Type | Required | Description | |---|---|---|---| | `ok` | boolean | yes | | +| `notices` | AssignNotice[] | yes | | | Status | Meaning | |---|---| -| 200 | Tag assigned to the catalog resource (idempotent) | +| 200 | Tag assigned to the catalog resource (idempotent). `notices` lists what the author should know about an assignment that succeeded: `clearance_masks_pending_table` when a whole-table classification with a clearance is attached to a table a... | | 400 | The coordinate cannot be stored (an empty schema or table, or `*`/`?` in any level — see migration 0177), or the tag is being attached to a whole TABLE while a tag-addressed mask names it, which would make that table unreadable rather than... | | 401 | Not authenticated | | 403 | Missing catalog_registry:update permission | diff --git a/platform/reference/rest-api/perm.md b/platform/reference/rest-api/perm.md index 2086172..eaac6d5 100644 --- a/platform/reference/rest-api/perm.md +++ b/platform/reference/rest-api/perm.md @@ -959,7 +959,6 @@ What a STORED rule does — the same [`crate::effect::RuleEffect`] a preview ret | `exemptEveryone` | boolean | yes | `exempt.include` names the `public` group: nobody is masked. | | `alreadyMasked` | ExistingMask[] | yes | Other mask rules that reach a column this rule reaches (at most 20). | | `truncated` | boolean | yes | `alreadyMasked` may be incomplete: a cap cut the columns examined (50), the classifications read (1000) or the entries listed (20). | -| `trinoColumnOnly` | TrinoColumnOnly | no | | | Status | Meaning | |---|---|