Skip to main content

The platform catalog

platform is a system virtual catalog (spec 117) that answers an administrator's questions and takes their changes without ACL NATIVE: views over the policy and the node, with typed columns and rows scoped to the caller, and management functions platform.<op>(…) called at the top level of a statement. Who may read or call what is a grant on platform's own objects - a built-in bundle, or a point grant on one view or function - so the tree a database tool shows under platform is exactly what that administrator may use.

-- an administrator through any door or gateway prefix - no ACL NATIVE, no ACL marker
SELECT role, catalog, caps."select", columns FROM platform.grants WHERE catalog = 'sales';
SELECT * FROM platform.sessions;
SELECT platform.grant_catalog('analyst', 'sales', caps := ['select'], main := true);
GRANT CATALOG sales TO ROLE analyst WITH (select) MAIN; -- the same operation, in the grammar

What it is​

  • Synthesized, never stored. Nothing of platform lives in the policy catalog; its views are SQL over the policy catalog's tables (or, for the node's state, internal table functions), substituted for the principal before the function gate, like the metadata surfaces.
  • The name is reserved. CREATE VIRTUAL CATALOG platform (any spelling, "Platform" included), every management call that names a catalog (ADD TABLE … AS platform.x, GRANT CATALOG platform …, acl_create_catalog('platform'), CHECK VIRTUAL CATALOG platform, …), a function-driver source whose role_catalogs answers it, and a function category or function grant naming platform.<f> are all refused. USE platform is refused too: a session's default catalog is a data catalog - name the objects as platform.<view>.
  • Where it appears. In information_schema.*, duckdb_databases() / duckdb_schemas() / duckdb_views() / duckdb_columns() / duckdb_functions(), SHOW and the Flight catalog RPCs - for a principal holding at least one grant on it, and only the objects it holds. One schema, main; a view is listed as VIEW with its description as the comment, a function in duckdb_functions() and acl_function_columns(). It is present under the gateway's ACL ROLE / ACL TOKEN prefixes and on the Flight door, and absent on the quack door (quack loads a catalog whole at ATTACH; the console is Flight / JDBC). A quack admin manages with the grammar.
  • Names. platform.<x> and platform.main.<x> are the same object, compared case-insensitively. The views are read-only: INSERT / UPDATE / DELETE / DDL on them is refused.

In DBeaver (or any JDBC / ADBC tool over the Flight door) an administrator sees platform as one more catalog in the tree: the views under Views (with their column types and comments) and the functions under Functions. A role without administration does not see it at all.

Who may read and call what​

Bundles​

Built-in, fixed sets of platform's objects, held as a set - a role may hold several, and no bundle is "stronger" than another:

BundleGranted withReadsCalls
observeGRANT ADMIN observe TO ROLE rthe node views (sessions, node_load, node_doors, node_streams, drain, lineage_status, audit_events); also the load report and /metrics (spec 097)nothing
operate (spec 118)GRANT ADMIN operate TO ROLE robservethe node's runtime: kill_session, session_audit_level, session_profile, drain, resume - and their grammar (KILL SESSION, SET SESSION … AUDIT LEVEL, PROFILE SESSION, DRAIN NODE, RESUME NODE)
cluster (spec 118 - a platform node's: hugr-node; an open node has no such scope)GRANT ADMIN cluster TO ROLE roperate + the cluster profile's views (cluster_items, cluster_effective, drift)operate + the cluster profile (cluster_extension, cluster_attach, cluster_detach, cluster_setting - resources and tuning only); never ACL NATIVE, never a source's data
policyGRANT ADMIN policy TO ROLE revery policy view, all rows - not the cluster profile'severy policy function except the admin grants, the cluster profile and the node's runtime (operate's)
passthroughGRANT ADMIN passthrough TO ROLE reverythingeverything, plus ACL NATIVE, the bundles and the grants on platform (the break-glass)
manage (spec 009's name)GRANT ADMIN manage TO ROLE rpolicy + observepolicy
a catalog adminGRANT CATALOG c TO ROLE r CAPS '{"manage": true}'the catalog-scoped views, narrowed to its catalogs (below)the functions that take a catalog, on its catalogs; check_catalog on its catalogs

REVOKE ADMIN <bundle> FROM ROLE r takes one bundle (not one another carries: observe from a role holding operate, cluster, manage or passthrough, or operate from one holding cluster or passthrough, is refused - revoke the carrier); REVOKE ADMIN FROM ROLE r takes every bundle, the manage capability of its catalog grants and its point grants on platform. Functions: acl_grant_admin(role, scope), acl_revoke_admin(role[, scope]).

A catalog admin keeps spec 009's limits: it may edit its catalogs' content but not hand out access (grant_catalog, grant_object, grant_schema, alter_grant, drop_catalog need policy), and not run what belongs to no catalog (roles, issuers, categories, resource groups).

Point grants​

One view or one function of platform, granted (or denied) to one role:

GRANT VIEW platform.sessions TO ROLE support; -- support reads the sessions, nothing else
GRANT FUNCTION platform.create_role TO ROLE onboarding; -- may create roles: the call and CREATE ROLE alike
DENY VIEW platform.issuers TO ROLE pol; -- a deny wins over every bundle but passthrough
REVOKE VIEW platform.sessions FROM ROLE support; -- the grant or the deny goes
-- functions: acl_grant_platform(role, 'view' | 'function', object[, allowed]),
-- acl_revoke_platform(role, kind, object)
  • A view granted by name is read whole (all rows); a function granted by name may be called for any catalog.
  • Only passthrough grants on platform (and the bundles) - never policy: a grantor that can grant everything is everything.
  • Never to every role (TO ALL ROLES / role '' is refused); never on grant_admin, revoke_admin, grant_platform, revoke_platform, migrate_catalog (the passthrough scope's own), cluster_extension, cluster_attach, cluster_detach, cluster_setting (the cluster bundle's alone), view_as (the policy bundle's - granted by name it would grant nothing; a DENY on it still works) and console_info (every holder's); only on an object platform has.
  • The grants live in their own table, platform_grants(role, object, kind, allowed) - never in the catalog grants or spec 072's function grants and categories.
  • A deny (allowed = false) anywhere among the principal's roles wins over a bundle; passthrough is never narrowed.

What an administrator may store​

A definition runs as the node when it is read, so what a non-passthrough administrator stores is judged by its own function gate where it is written: every function a view, a macro template, an alias target, an RLS, a column or mask expression (or a grant's policy) calls must be one the author may call itself - its roles' categories and grants by name, a deny winning, the never set (acl_*, query*, a scanner's *_query, ...) always refused; acl_claim / acl_arg are markers, not calls. A body is a constant (never a ?). No definition may name the policy catalog's own tables. Changing an issuer or a client that carries a mapping to an administering role, mapping to such a role, and granting a catalog's manage to a role a mapping reaches are passthrough's. REVOKE ADMIN <bundle> of a bundle the role does not hold - or holds only through manage / passthrough - is refused, naming what to revoke.

Rows per scope​

ScopeRows
policy, passthrough, a point grant on the viewall
a catalog adminits catalogs' objects, columns, schemas, functions, references, keys, the grants on its catalogs, and the names of the roles holding them (roles answers role with a NULL comment); no identity, categories, resource groups, cluster or node views
observe, operatethe node views only
nonethe catalog is absent - not listed, no access to object "platform.<x>"

Privileged roles (spec 095, extended)​

A role holding any bundle, any point grant on platform (grant or deny) or the manage capability on a catalog is privileged: it is reached only through a client's own mapping (MAP … FROM CLIENT c TO ROLE r) - never an issuer-wide mapping, UNMAPPED AS ROLE or ROLES CONSTANT. It is judged at use: a role mapped issuer-wide before its grant stops counting at the next token.

The views​

Every column is typed - no packed JSON or csv. A capability set is caps STRUCT("select" BOOLEAN, "insert" BOOLEAN, "update" BOOLEAN, "delete" BOOLEAN, "merge" BOOLEAN, "create" BOOLEAN, "drop" BOOLEAN, "temp" BOOLEAN, "explain" BOOLEAN, secrets BOOLEAN, lineage BOOLEAN, manage BOOLEAN) (NULL = unstated: on a catalog grant every data capability, on an object grant inherited); a list is VARCHAR[]. The listing of a view and DESCRIBE platform.<view> agree column for column.

Policy views (the policy bundle; marked c - also a catalog admin, narrowed):

ViewColumns
catalogs ccatalog, comment
relations ccatalog, name, form, phys, view_sql, rls, rls_checked BOOLEAN, origin, comment, alias_types, enum_types, created_by
relation_columns ccatalog, relation, pos BIGINT, name, expr, nullable BOOLEAN
schemas ccatalog, path, phys_path, origin, comment, created_by
functions ccatalog, name, kind, form, target, template, params VARCHAR[], comment, created_by
function_columns ccatalog, function, kind, pos BIGINT, name, type, comment, nullable BOOLEAN
references ccatalog, name, from_object, to_object, to_kind, expression, cardinality, optional BOOLEAN, join_method, comment, created_by
reference_columns ccatalog, reference, pos BIGINT, side, column, param
keys ccatalog, object, kind, pos BIGINT, column
roles crole, comment
role_claimsrole, claim, value
grants crole, catalog, is_main BOOLEAN, caps, rls, rls_checked BOOLEAN, columns VARCHAR[]
schema_grants crole, catalog, schema_path, caps, inherited BOOLEAN, into, virtual_only BOOLEAN, comment
object_grants crole, catalog, object, caps, rls, rls_checked BOOLEAN, columns VARCHAR[]
grant_columns crole, catalog, object, pos BIGINT, name, type
adminsrole, scope, catalog (NULL = global)
platform_grantsrole, object, kind, allowed BOOLEAN
issuersname, url, secret_service, secret (a secret by its name, never its content)
clientsname, issuer, audiences VARCHAR[], azp VARCHAR[], requires STRUCT(path VARCHAR, op VARCHAR, "values" VARCHAR[])[], roles_from VARCHAR[], roles_constant VARCHAR[], unmapped, attributes STRUCT(name VARCHAR, paths VARCHAR[], constant VARCHAR)[], subject VARCHAR[], token_type, client_id, flows VARCHAR[], secret_service, secret, implicit BOOLEAN
role_mappingsscope_kind, scope_name, source, external_value, role
jwks_cacheissuer, location, allowed BOOLEAN, fetched_at TIMESTAMP, age_seconds BIGINT, last_tried_at TIMESTAMP, error, keys BIGINT, kids VARCHAR[]
function_categoriescategory, comment, builtin BOOLEAN
function_category_memberscategory, database, schema, name, kind
function_grantsrole, category, database, schema, name, kind, allowed BOOLEAN
function_statusdatabase, schema, name, kind, function_type, categories VARCHAR[], status, present BOOLEAN
resource_groupsgroup, window_start, window_max, batch_bytes, max_result_rows, queue_priority, max_sessions (BIGINT), comment, is_default BOOLEAN
role_resource_groupsrole, group
cluster_items †scope, kind, name, spec MAP(VARCHAR, VARCHAR), class, version BIGINT, depends_on VARCHAR[], comment
cluster_effective †the same, what applies to this node (spec 096)
catalog_schemabuild, build_min_reader, catalog, min_reader (BIGINT), mode

created_by (spec 120): who defined the object first - <issuer>|<subject> for a token, the role for a gateway's ACL ROLE, NULL for the operator's own SQL, the anonymous gateway and anything defined before schema v22; a redefinition and a rename keep it. It is a fact, never a right. What the check (source_not_granted) judges is the current definition's writer: the roles the source gate bound when it was written (an administrator below policy), dropped when another writer redefines the object.

Node views (the observe bundle):

ViewColumns
sessionsid, subject, roles VARCHAR[], door, idle_seconds BIGINT, expires_at TIMESTAMP, audit_level, audit_level_source, profile_level, profile_source, groups VARCHAR[], charged_group - never a handle
node_loaddraining BOOLEAN, observe, group, group_known BOOLEAN, config_target, config_applied, sessions_live, sessions_max (BIGINT), sessions_by_door MAP(VARCHAR, BIGINT), sessions_by_group MAP(VARCHAR, STRUCT(live BIGINT, max BIGINT)), admit_new_session, admit_new_quack_client, admit_new_stream (BOOLEAN) - one row
node_doorsdoor, uri, seats, seated, seats_left (BIGINT) - one row per quack door
node_streamsbudget_bytes, reserve_bytes, reserved_bytes, producing, queued, refused (BIGINT)
draindraining BOOLEAN, sessions BIGINT
lineage_statusstatus, sending BOOLEAN, level, namespace
audit_eventsts TIMESTAMPTZ, seq BIGINT, kind, level, door, session, subject, issuer, roles VARCHAR[], statement, objects STRUCT(name VARCHAR, capability VARCHAR)[], verdict, reason_code, reason, correlation_id, traceparent, rows, duration_us (BIGINT), detail - the node's recent events, its ring (spec 069); the history is the sinks'

† the cluster profile's views are the cluster bundle's (spec 118; or a point grant's) - not the policy bundle's: the policy admin has no cluster or node operations. drift (kind, name, state, node_value, profile_value, scope) is this node against its profile; on a standalone node it is refused.

Unmarked columns are VARCHAR. The views that read the policy catalog's tables need a catalog policy source (acl_use_db); in memory mode and behind a function driver they refuse with a clear error, while the node views, jwks_cache, the function views, the cluster views and catalog_schema answer.

-- what can analyst do in sales, and through which grant?
SELECT g.caps."select", g.caps.insert, o.object, o.caps."select" AS object_select
FROM platform.grants g LEFT JOIN platform.object_grants o USING (role, catalog)
WHERE g.role = 'analyst' AND g.catalog = 'sales';

-- which functions are still uncategorized on this node?
SELECT database, schema, name, kind FROM platform.function_status WHERE status = 'uncategorized';

The physical tree: platform.attached​

Spec 118.3. The node's sources - its attached databases, never the policy store's, a secrets service or a hidden database (another catalog's internals) - appear as metadata in platform: a schema attached.<alias>.<schema> per schema of a source, holding its tables (BASE TABLE) and views (VIEW) with their columns' names and types and the comments. Never a row, a view's SQL or a column default - SELECT … FROM platform.attached… is refused like any object the principal does not hold. The listings read one attached database at a time through its own schema set (acl_platform_attached[_columns]([sources])), and only the databases a principal may see: postgres loads its remote catalog on the first touch (narrow it with ATTACH's SCHEMA), mysql per schema. A source whose catalog cannot be read stays in the tree as attached.<alias> with no schemas, its duckdb_schemas() comment saying why - acl: source <alias> could not be read (<class>), the exception's class and never its text (spec 136).

WhoSees under attachedMay build over
passthrough, policyevery sourceevery source
clusterevery source(holding a catalog's manage too) only the granted ones - it hands out no data
a catalog adminthe sources granted to itthe sources granted to it
observe, operate, a non-adminnothing-
GRANT SOURCE <database>[.<schema>] TO ROLE <role> -- policy or passthrough
REVOKE SOURCE <database>[.<schema>] FROM ROLE <role>
SELECT platform.grant_source('sales_owner', 'pg.sales');

A grant is a platform_grants row of kind source. It names a source of this node (the database must be attached, the schema must exist) and gives its subtree and the right to reference it: a catalog admin's CREATE VIRTUAL TABLE over a physical table, a schema alias or expansion, a function alias, and every table a view, a macro, an RLS, a column expression or a REMAP reads (as the body names it) must lie inside a granted source, named in full (<database>.<schema>.<object>) - anything else is refused at write. A database grant reaches all its schemas; a schema grant reaches that schema (and <database>.<table> when it is main). What was built keeps working: a REVOKE SOURCE, or objects declared before 118.3, are never taken back - only an explicit DROP removes them.

The functions​

platform.main.<op>(…) - one per management operation, the acl_* writer under its plain name.

How a call is written​

  • At the top level only: SELECT platform.f(…) (one select item, no FROM, WHERE, CTE, GROUP BY, ORDER BY or set operation) or CALL platform.f(…). Anywhere else - in FROM, a subquery, per row of another selection, a WHERE - it is refused at rewrite (platform.f is a management function - call it at the top level of a statement). A batch mixing calls and queries is refused.
  • Arguments by position or by name (name := value, the names below). An argument may be a constant, a list literal (['select', 'insert']) or a parameter (? / $1) - except the arguments that carry authorization (marked * below: a catalog, a role, an object, a scope), which must be constants: they are judged before the call runs. Nothing else is an argument (a column, a call, a subquery is refused - a management call reads no data). A left-out argument takes the function's default.
  • Typed arguments: caps takes a capability list (['select', 'insert'] = {"select": true, "insert": true}) or the caps JSON; columns / members a list or the csv; main, cascade, prune, virtual_only a BOOLEAN. Issuer / client / resource-group specs stay JSON - that is their contract.
  • Each call is one change, one admin audit event, its own policy-catalog transaction - a client's ROLLBACK does not undo it, as for the grammar. The grammar compiles into the same calls and is judged by the same authorizer: the grammar and a direct call give the same result and the same refusal.
SELECT platform.create_catalog('sales', comment := 'the sales mart');
CALL platform.add_relation('sales', 'orders', 'pg.public.orders', columns := ['id', 'amount']);
SELECT platform.grant_catalog(role := 'analyst', catalog := 'sales', caps := ['select'], main := true);
SELECT platform.comment('sales', 'orders', column_name := 'amount', comment := 'in cents');
-- prepared, the payload bound later (the catalog stays a constant)
SELECT platform.alter_catalog('sales', comment := ?);

The functions and the right each needs​

Right: CATALOG - policy, or manage on the catalog its catalog argument names; POLICY - policy; HANDS_OUT - policy (handing out access); ESCALATES - passthrough only; INFRASTRUCTURE - the cluster bundle (spec 118; a setting outside resources and tuning passthrough's); OPERATE (the node's runtime) - the operate bundle (spec 118). A point grant on the function stands in for a bundle, except for ESCALATES and INFRASTRUCTURE.

FunctionParameters (* authorization, a constant)Right
create_catalogcatalog*, comment, modeCATALOG
alter_catalogcatalog*, commentCATALOG
add_relationcatalog*, name*, phys*, columns, rls, comment, mode, primary_keyCATALOG
add_viewcatalog*, name*, sql, returns, comment, mode, primary_keyCATALOG
add_schema_alias / expand_schemacatalog*, path*, phys_path*, comment, modeCATALOG
refresh_schema_objectscatalog*, path*, pruneCATALOG
add_table_functioncatalog*, name*, definition, params, returns, comment, mode, primary_keyCATALOG
add_table_function_aliascatalog*, name*, target*, params, returns, comment, modeCATALOG
add_scalarcatalog*, name*, definition, params, returns, comment, modeCATALOG
add_scalar_aliascatalog*, name*, target*, params, returns, comment, modeCATALOG
drop_relationcatalog*, name*, modeCATALOG
alter_relationcatalog*, name*, property*, valueCATALOG
alter_schema_aliascatalog*, path*, phys_path*CATALOG
alter_functioncatalog*, name*, kind*, form*, definitionCATALOG
drop_schema_aliascatalog*, path*, mode, cascadeCATALOG
drop_functioncatalog*, name*, kind*, modeCATALOG
commentcatalog*, name*, kind* (default relation), column_name*, commentCATALOG
refresh_schemacatalog*, name*CATALOG
repair_relationcatalog*, name*, action*, specCATALOG
set_keycatalog*, name*, kind*, primary_keyCATALOG
add_referencecatalog*, name*, from_object*, to_object*, to_kind, arguments, pairs, expression, cardinality, optional, join_method, comment, modeCATALOG
drop_referencecatalog*, name*, modeCATALOG
rematerialize_schema_capscatalog*, path*CATALOG
grant_catalogrole*, catalog*, caps, main, rls, columnsHANDS_OUT
revoke_catalogrole*, catalog*HANDS_OUT
grant_objectrole*, catalog*, name*, caps, rls, columnsHANDS_OUT
grant_schemarole*, catalog*, path*, caps, comment, into_schema*, virtual_onlyHANDS_OUT
revoke_schemarole*, catalog*, path*HANDS_OUT
alter_grantrole*, catalog*, property*, valueHANDS_OUT
drop_catalogcatalog*, cascade, modeHANDS_OUT
create_role / alter_rolerole*, claims (, mode)POLICY
drop_rolerole*, modePOLICY
define_issuername*, spec, modePOLICY
alter_issuername*, specPOLICY
drop_issuer / drop_clientname*, modePOLICY
define_clientname*, issuer*, spec, modePOLICY
alter_clientname*, specPOLICY
map_role / drop_role_mappingscope_kind*, scope_name*, source*, external_value*, role*POLICY
create_function_categorycategory*, commentPOLICY
drop_function_categorycategory*, modePOLICY
function_category_add / _removecategory*, members*POLICY
grant_function_categoryrole*, category*, allowedPOLICY
revoke_function_categoryrole*, category*POLICY
grant_functionrole*, function*, allowedPOLICY
revoke_functionrole*, function*POLICY
session_profilesession*, levelOPERATE
session_audit_levelsession*, level ('' inherits)OPERATE
kill_sessionsession*OPERATE
view_asrole ('' leaves) - this session only, metadata only (spec 120)POLICY
drain / resume-OPERATE
migrate_catalogdatabase*, schema*ESCALATES
comment_rolerole*, commentPOLICY
comment_function_categorycategory*, commentPOLICY
revoke_objectrole*, catalog*, name*HANDS_OUT
alter_referencecatalog*, name*, property* (comment / cardinality / optional), valueCATALOG
grant_source / revoke_sourcerole*, source*HANDS_OUT
create_resource_groupgroup_name*, limits, comment, is_defaultPOLICY
alter_resource_groupgroup_name*, property* (default / limits / comment), valuePOLICY
drop_resource_groupgroup_name*, modePOLICY
grant_resource_group / revoke_resource_grouprole*, group_name*POLICY
grant_adminrole*, scope*ESCALATES
revoke_adminrole*, scope* (omitted: everything)ESCALATES
grant_platformrole*, kind*, object*, allowedESCALATES
revoke_platformrole*, kind*, object*ESCALATES
cluster_extensionverb*, scope*, name*, version, repository*, commentINFRASTRUCTURE
cluster_attachscope*, alias*, path*, type*, secret*, options, depends_on*, comment, lineageINFRASTRUCTURE
cluster_detachscope*, alias*, cascade, forceINFRASTRUCTURE
cluster_settingverb*, scope*, name*, valueINFRASTRUCTURE

The parameters mean what the matching acl_* function's do - see the management SQL reference.

Read functions, in FROM​

  • platform.check_catalog([catalog]) - the catalog check of spec 039 (one row per finding with its repair statement), judged like CHECK VIRTUAL CATALOG: a catalog admin on its catalogs, policy without an argument. CALL platform.check_catalog('sales') is the same.

  • platform.check_grant(role, catalog, object, rls, columns[, caps]) (spec 122) - what writing that grant would say, written nowhere: one row per finding, (item, name, type, status, message). object empty = a catalog grant. In the write's order - the object (it exists; no RLS or columns on a scalar), the column list against the object, the caps (when given: manage is per catalog, a write verb on a function is never consulted), the RLS bound against the object's source, then the list in its stored form (item = 'columns') and each column the role would read (item = 'column': its name, its exposed type, and its mask or expression as message; with no list, every column the object exposes - what a grant that lists none lets the role read). The first refusal ends the answer with the write's own message (status = 'refused'); a catalog grant's columns are unchecked (the write does not probe them either). A body calling a function its author may not call refuses the call itself, as the write would. Who may grant may check: policy, or a point grant on platform.check_grant.

    SELECT * FROM platform.check_grant('analyst', 'sales', 'orders', 'tenant = acl_claim(''tenant'')',
    'id, amount, ssn = 42, address.city');
    -- rls tenant = acl_claim('tenant') ok
    -- columns id, amount, ssn = 42, address.city ok
    -- column id INTEGER ok
    -- column ssn INTEGER ok 42
    -- column address STRUCT(city VARCHAR) ok
  • platform.console_info() - what a console reads to show only what works: build, audit_contract, connection_contract, the catalog schema and window (schema_build, schema_build_min_reader, schema_catalog, schema_min_reader, schema_mode), views / functions (what this node has), my_views / my_functions (what the caller may use), bundles, admin_catalogs, deployment (standalone | cluster) and scopes (the admin scopes this node grants, in the order a picker shows them). A standalone node leaves out of my_views / my_functions what it refuses there: drift and the cluster profile's writes (cluster_*); the profile's reads stay. Every principal holding anything on platform may call it.

  • platform.effective_rights(role, catalog[, object]) (spec 120) - what role gets of each table, view and live schema alias of catalog, and of each object under an alias the role holds a grant on: decided_by (object, schema <path> - the longest prefix the role holds, (inherited) when that row was inherited - or catalog; none without a grant on the catalog), caps (an unstated grant = the five data capabilities; a view lists select at most; manage is shown by platform.admins), main (the catalog is the role's only MAIN one - bare names resolve there), and for a table or a view what a read as the role gets: rls, columns (the role's own columns listing - what its DESCRIBE answers; [] = it reads none), masks (the masks its grants state on those columns), writable, or refused (the resolver's refusal

    • every read of the object fails with it). Answered by the resolver itself; policy for any catalog, a catalog admin on its catalogs.
  • platform.exposures(source[, schema[, object]]) (spec 120) - who reads a physical database, schema or table through the policy: a relation over it, a live schema alias of it (asked inside the alias, the object there), a view, a function, a computed column, a relation's RLS, and a grant's RLS or mask (kind = 'grant', for that role only) whose body names it - and per role holding a grant on the object's catalog its decided_by and caps. A body is parsed, never bound (no source is reached): t is the node's default database's, x.t the default schema of database x. Direct names only - what a physical view or macro reads in turn is not followed, and ACL NATIVE (passthrough) reaches every source outside the policy. An empty schema is any schema. policy only (it spans catalogs).

SELECT bundles, admin_catalogs, my_views FROM platform.console_info();
SELECT problem, object, repair FROM platform.check_catalog('sales');
SELECT object, decided_by, caps, rls, columns FROM platform.effective_rights('analyst', 'sales');
SELECT catalog, object, kind, role, caps FROM platform.exposures('pg', 'public', 'orders');

The grammar without the marker​

Under a principal prefix (ACL ROLE, ACL TOKEN, ACL SESSION - every door) the management grammar needs no ACL marker: GRANT CATALOG sales TO ROLE analyst WITH (select), CREATE ROLE r, CREATE VIRTUAL TABLE … are written as in any DBMS. A statement is management when its leading phrase is one of the grammar's (GRANT / REVOKE / DENY, CREATE|ALTER|DROP ROLE|ISSUER|CLIENT|VIRTUAL …|FUNCTION CATEGORY|RESOURCE GROUP, MAP, DROP MAP|RELATION, ADD, CHECK|REPAIR VIRTUAL, PROFILE SESSION, CLUSTER, COMMENT ON VIRTUAL, ANALYZE VIRTUAL); everything else is an ordinary query - CREATE TABLE, DROP TABLE, ALTER TABLE … RENAME, duckdb's COMMENT ON TABLE, ANALYZE t and CREATE FUNCTION are answered as before. ACL <mgmt> is still accepted; ACL ADMIN and ACL NATIVE are unchanged.

A batch is all management or all queries: one mixing them (in either order, the grammar or a platform call) is refused - send each kind in a batch of its own.

Schema v21​

The platform catalog needs schema version 21: admins is keyed (role, scope, vcat) (a role holds several bundles) and platform_grants is new. An older catalog is migrated with SELECT acl_migrate_catalog('<db>'[, '<schema>']); the rows spec 009 wrote read as bundles (manage → policy + observe, a catalog-scoped manage → that catalog, observe, passthrough as they are) - nothing is rewritten. The step refuses while a virtual catalog (or a grant on one) is named platform: rename or drop it, then migrate. min_reader_version is 21 - a build before it does not know the bundles and does not serve a v21 catalog.

Schema v22 (spec 120) adds created_by and created_roles at the end of relations, schemas, functions and references - nullable, so its min_reader_version stays 21: a v21 build serves a v22 catalog and, inside the window, never writes it. acl_migrate_catalog applies it like any step; objects defined before stay NULL.