{"uuid": "7e72e134-2ed1-4336-b50a-b09c304a73aa", "vulnerability_lookup_origin": "1a89b78e-f703-45f3-bb86-59eb712668bd", "author": "9f56dd64-161d-43a6-b9c3-555944290a09", "vulnerability": "CVE-2024-25624", "type": "seen", "source": "https://gist.github.com/khoindq/bded28fd1242788522ce29d279b9d883", "content": "\n\n\n\nLyra \u00b7 Unified Governance &amp; ACL Design \u2014 Complete\n\n  :root {\n    --bg: #0b0d10; --panel: #12161b; --border: #1f262e; --ink: #e6edf3;\n    --mute: #8b949e; --accent: #58a6ff; --warn: #f0883e; --err: #f85149;\n    --ok: #3fb950; --pick: #d2a8ff; --line: #30363d;\n    --mono: ui-monospace, SFMono-Regular, \"SF Mono\", Menlo, Consolas, monospace;\n  }\n  * { box-sizing: border-box; }\n  body { background: var(--bg); color: var(--ink); font: 14px/1.55 -apple-system, system-ui, sans-serif; margin: 0; padding: 32px 24px 80px; }\n  .wrap { max-width: 1200px; margin: 0 auto; }\n  h1 { font-size: 26px; margin: 0 0 4px; }\n  h2 { font-size: 19px; margin: 36px 0 12px; padding-bottom: 6px; border-bottom: 1px solid var(--line); }\n  h3 { font-size: 15px; margin: 20px 0 8px; color: var(--accent); }\n  h4 { font-size: 13px; margin: 14px 0 6px; color: var(--pick); text-transform: uppercase; letter-spacing: 0.04em; }\n  .sub { color: var(--mute); margin: 0 0 20px; font-size: 13px; }\n  .panel { background: var(--panel); border: 1px solid var(--border); border-radius: 8px; padding: 16px 18px; margin: 12px 0; }\n  .toc { columns: 2; column-gap: 32px; font-size: 13px; }\n  .toc a { color: var(--accent); text-decoration: none; display: block; padding: 2px 0; }\n  .toc a:hover { color: var(--ink); }\n  .stat { display: grid; grid-template-columns: repeat(4, 1fr); gap: 12px; margin: 16px 0 24px; }\n  .stat .card { background: var(--panel); border: 1px solid var(--border); border-radius: 8px; padding: 14px; }\n  .stat .n { font-size: 28px; font-weight: 600; color: var(--accent); }\n  .stat .l { font-size: 11px; color: var(--mute); text-transform: uppercase; letter-spacing: 0.05em; }\n  table { border-collapse: collapse; width: 100%; margin: 8px 0 16px; font-size: 13px; }\n  th, td { border: 1px solid var(--line); padding: 7px 10px; text-align: left; vertical-align: top; }\n  th { background: #161b22; font-weight: 600; color: var(--mute); font-size: 12px; text-transform: uppercase; letter-spacing: 0.04em; }\n  tr:hover td { background: #0f1318; }\n  code { font-family: var(--mono); background: #161b22; padding: 1px 5px; border-radius: 3px; font-size: 12px; }\n  pre { background: #0d1117; border: 1px solid var(--border); border-radius: 6px; padding: 12px; overflow-x: auto; font: 12px/1.55 var(--mono); }\n  .ok { color: var(--ok); } .warn { color: var(--warn); } .err { color: var(--err); } .mute { color: var(--mute); } .pick { color: var(--pick); font-weight: 600; }\n  .pill { display: inline-block; padding: 1px 7px; border-radius: 10px; font-size: 11px; border: 1px solid var(--line); background: #161b22; color: var(--mute); }\n  .pill.g { color: var(--ok); border-color: #1e3a2a; background: #0d1f15; }\n  .pill.r { color: var(--err); border-color: #3a1d1d; background: #1f0e0e; }\n  .pill.y { color: var(--warn); border-color: #3a2c1d; background: #1f1810; }\n  .pill.b { color: var(--accent); border-color: #1d3050; background: #0e1a2b; }\n  .pill.v { color: var(--pick); border-color: #3a2848; background: #1a1124; }\n  /* tree */\n  .tree { font-family: var(--mono); font-size: 12.5px; line-height: 1.7; padding: 14px 18px; background: var(--panel); border: 1px solid var(--border); border-radius: 6px; }\n  .tree ul { list-style: none; padding-left: 18px; margin: 0; border-left: 1px dashed var(--line); }\n  .tree &gt; ul { padding-left: 0; border-left: none; }\n  .tree li { position: relative; padding: 1px 0 1px 14px; }\n  .tree li::before { content: \"\u2500\"; position: absolute; left: 0; color: var(--line); }\n  .tree .root { color: var(--pick); font-weight: 600; }\n  .tree .acc { color: var(--accent); }\n  .tree .new { color: var(--ok); font-weight: 600; }\n  .tree small { color: var(--mute); margin-left: 6px; font-size: 11px; }\n  .badge-new { background: #0d1f15; color: var(--ok); border: 1px solid #1e3a2a; padding: 0 5px; border-radius: 3px; font-size: 10px; margin-left: 4px; }\n  .grid2 { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; }\n  .pick-row td { background: #1a1124 !important; }\n  hr.sep { border: 0; border-top: 1px solid var(--line); margin: 28px 0; }\n  .num { display: inline-block; width: 18px; height: 18px; border-radius: 50%; background: var(--accent); color: var(--bg); text-align: center; font-size: 11px; font-weight: 700; line-height: 18px; margin-right: 6px; vertical-align: 2px; }\n\n\n\n\n\n\n\nLyra \u00b7 Unified Governance &amp; ACL Design complete\n\nSingle source of truth for the Lyra Unity Catalog clone. Consolidates 7 review agents (codebase audit, Databricks UC 2026, Snowflake / BigQuery / Polaris / Cedar / Lake Formation cross-checks) into one document. Replaces v1 (review), v2 (full inventory), v3 (gap-closer) \u2014 this is the merged, paste-ready spec.\n\n\n\n  \n\n6 \u2192 1\nACL tables collapsed\n  \n\n47\nsecurables covered\n  \n\n29\nprivileges (incl. macros)\n  \n\n4-pass\nforbid \u2192 admin \u2192 owner \u2192 permit\n\n\n\n\n0 \u00b7 Table of contents\n\n\nTL;DR \u2014 the picture\n1. Current state &amp; bugs found\n2. Decision: why one grant table\n3. Engine comparison\n4. Peer cross-check (Databricks / Snowflake / \u2026)\n5. Complete asset inventory (47)\n6. Object hierarchy tree\n7. Privilege vocabulary\n8. Cascade rules\n9. DDL \u2014 paste-ready\n10. Resolver \u2014 4 passes\n11. List-endpoint pushdown\n12. Row filters &amp; column masks\n13. Conditions \u2014 Cedar grammar\n14. Audit invariants\n15. Federated identity\n16. Multi-tenant isolation\n17. Anti-escalation rules\n18. Cache architecture\n19. Future assets \u2014 pre-planned\n20. Migration plan\n21. Anti-patterns\n22. Sources\n\n\n\n\nTL;DR \u2014 the picture\n\n\n\nToday. Lyra has 6 parallel ACL surfaces \u2014 grants (UC), compute_cluster_acls, workspace_object_acls, job_acls, secret_acls, workspace_permission_assignments. Each has its own privilege vocabulary, its own admin-bypass logic, and its own bugs. Permission checks are scattered across 70+ handlers; several handlers skip checks entirely.\n\nTarget. One grant table over a typed principal \u2194 securable graph. One 4-pass resolver (forbid \u2192 admin \u2192 owner \u2192 permit). One privilege vocabulary mapped from legacy permission levels. Owner is a column on the securable, never a grant row. Cascade is data (securable_cascade table), not code. Conditions are Cedar expressions, not free-form JSON.\n\nCoverage. 47 active securables across 7 planes (UC data, Sharing &amp; Marketplace, Workspace, Compute, Orchestration, Identity, Account / Secrets) plus 30+ pre-planned reservations. Every handler in the codebase maps to exactly one securable.\n\n\n\n\n1 \u00b7 Current state &amp; bugs found\n\n\n1.1 \u2014 The 6 parallel ACL systems\n\n\n#TableSourceVocabularyCoverage\n1grantsstorage / repositories / grant.rsUC privileges (USE_CATALOG, SELECT, MODIFY, \u2026)UC data plane\n2compute_cluster_aclscluster_acl.rsCAN_ATTACH_TO \u00b7 CAN_RESTART \u00b7 CAN_MANAGECluster only\n3workspace_object_aclsworkspace_object_acl.rsCAN_READ \u00b7 CAN_RUN \u00b7 CAN_EDIT \u00b7 CAN_MANAGENotebook \u00b7 File \u00b7 Directory \u00b7 Dashboard\n4job_aclsjob.rsCAN_VIEW \u00b7 CAN_MANAGE_RUN \u00b7 IS_OWNER \u00b7 CAN_MANAGEJobs only\n5secret_aclssecret.rsREAD \u00b7 WRITE \u00b7 MANAGESecret scope\n6workspace_permission_assignmentsworkspace_permission_assignment.rsUSER \u00b7 ADMINWorkspace membership\n\n\n\n1.2 \u2014 Bugs surfaced by audit\n\n\nSeverityBugWhere\nP0workspace_bindings PATCH does not enforce metastore admincrates/lyra-api/src/workspace_bindings.rs\nP0delta_commits ignores _user: AuthUser \u2014 anyone with valid token can write commitsdelta_commits.rs:46\nP0list_volumes / list_models / list_functions have no permission filter \u2014 full enumerationvolumes.rs, models.rs, functions.rs\nP0update_metastore / list_metastore / get_metastore rely on JWT-only, no metastore-owner checkmetastore.rs\nP0/P1grant.rs:25 SELECT references non-existent metastore_id column \u2014 broken since refactorrepositories/grant.rs:25\nP1Admin-bypass logic duplicated in ~12 handlers; semantics driftgovernance/permissions.rs, jobs_permissions.rs, \u2026\nP1List endpoints fetch-then-filter at app layer (N+1 ACL queries)multiple handlers\n\n\n\n\n2 \u00b7 Decision \u2014 why one grant table\n\n\nOptionVerdictRationale\n\u2460 Keep 6 separate ACL tablesREJECTAlready produced 6 P0 bugs; admin-bypass duplicated 12\u00d7; vocabularies don't compose\n\u2461 Single grant table over typed securable graphCHOSENOne resolver, one vocabulary, predicate pushdown for list endpoints, owner-as-column avoids grant-row maintenance, cascade-as-data avoids hardcoding\n\u2462 Embed Cedar/OPA as policy enginePARTIALAdopt Cedar grammar for the optional condition column \u2014 but the 4-pass core stays SQL because list-filter pushdown beats anything an external evaluator can do\n\n\n\n\n3 \u00b7 Engine comparison\n\n\nCriterionHand-written Rust + PostgresCedar (Rust crate)OPA / regorusCasbin-rsBiscuit\nSQL list-filter pushdown\u2713 native\u2717\u2717\u2717\u2717\nLatency for single-decision&lt;1 ms (cached)&lt;1 ms2-5 ms&lt;1 ms&lt;1 ms\nGroup nestingrecursive CTEflat / templatevia regoRBAC w/ domainflat\nAudit forensicsSQL nativemanualmanualmanualcryptographic\nTenancy isolationRLS + account_idmanualmanualmanualmanual\nTotal (out of 35)3222202119\n\n\n\n\n4 \u00b7 Peer cross-check\n\n\nSystemWhat we stealWhat we don't\nDatabricks UCPrivilege vocabulary, securable types, OWNER-as-column, BROWSE/USE/MANAGE/SELECT/MODIFY/EXECUTECentralised metadata-only stance \u2014 already match\nSnowflakeFUTURE GRANTS pattern (apply to objects not yet created), row access policies as attached objectsTwo-direction privilege graph \u2014 overkill for our scale\nBigQueryColumn-level ACL = mask UDF attached to columnIAM project hierarchy \u2014 Lyra's hierarchy is shallower\nApache PolarisCatalog/Namespace/Table primitive set; PrincipalRole / CatalogRole indirectionJCasbin-style RBAC-with-domains \u2014 we use direct grants\nIceberg RESTGranular table verbs (TABLE_ADD_SNAPSHOT, \u2026) \u2014 keep as reserved future\u2014\nAWS Lake FormationLF-Tags = tag-based ABAC at scale; Lyra's GovernedTag + ASSIGN\u2014\nCedarExpression grammar for the condition columnWhole-engine \u2014 slower than SQL pushdown for lists\n\n\n\n\n5 \u00b7 Complete asset inventory (47 active)\n\n\n5.1 \u2014 UC data plane (19)\n\n\nSecurableCodeTodayNotes\nMetastoremetastore.rsgrantstop of UC tree\nCatalogcatalogs.rsgrants\u2014\nSchemaschemas.rsgrants\u2014\nTabletables.rsgrants\u2014\nStreamingTable v3tables.rs (kind discriminator)noneUC 2026 first-class; ABAC GA targets it\nView \u00b7 MaterializedViewtables.rsgrants\u2014\nVolumevolumes.rsgrants\u2014\nFunctionfunctions.rsgrants\u2014\nProcedure v3functions.rs (kind=procedure)noneUC 2026; different EXECUTE semantics\nModel \u00b7 ModelVersionmodels.rsgrants\u2014\nStorageCredentialgovernance/storage_credentials.rsgrants\u2014\nExternalLocationgovernance/external_locations.rsgrants\u2014\nConnectiongovernance/connections.rsgrants\u2014\nServiceCredential v2federationno enumadd to SecurableType\nExternalMetadata v3(reserved)noneUC 2026 external lineage objects\nDatabaseInstance / LakebaseCatalog v3(reserved)nonePostgres-as-catalog\nGovernedTag v3(reserved)noneABAC anchor\nTagPolicy v3(reserved)nonepredicate-style: \"tag PII denies SELECT\"\n\n\n\n5.2 \u2014 Sharing &amp; Marketplace (6)\n\n\nSecurableCodeTodayNotes\nSharegovernance/shares.rsgrants\u2014\nProvidergovernance/providers.rsgrants\u2014\nRecipientgovernance/recipients.rsgrantsalso a principal kind\nCleanRoom(reserved)grants\u2014\nMarketplaceListing v3(reserved)nonequota-counted; USE_MARKETPLACE_ASSETS\nExchange v3(reserved)noneprivate listing namespace\n\n\n\n5.3 \u2014 Workspace (8 + cascade-only children)\n\n\nSecurableCodeTodayNotes\nWorkspaceaccounts/workspaces.rsworkspace_permission_assignmentsfold\nWorkspaceCatalogBindingworkspace_bindings.rsnoneprivilege on Workspace \u00d7 Catalog\nNotebooknotebooks/mod.rsworkspace_object_aclsfold\nNotebookCell \u00b7 KernelSessionnotebooks/cells.rs \u00b7 session.rscascadeparent: Notebook\nFile \u00b7 Directoryworkspace_files.rsworkspace_object_aclspath-based ancestor\nDashboarddashboards/mod.rsworkspace_object_aclsfold\nDashboard childrenDataset \u00b7 Chat \u00b7 Message \u00b7 Attachment \u00b7 Schedule \u00b7 Refresh \u00b7 Soul \u00b7 SkillOverridecascadeparent: Dashboard\n\n\n\n5.4 \u2014 Compute (3)\n\n\nSecurableCodeTodayNotes\nClusterclusters.rscompute_cluster_aclsfold\nNetwork v2accounts/networks.rsnoneaccount-level\nMCPServer v3(reserved)noneAI-Gateway on-behalf-of\n\n\n\n5.5 \u2014 Orchestration (5)\n\n\nSecurableCodeTodayNotes\nJobjobs.rsjob_aclsfold\nJobRunjobs_dispatcher.rscascadeparent: Job; no SELECT cascade\nFlow v2orchestrator.rsnonedistinct from Job\nFlowRun \u00b7 FlowStepRunorchestratorcascadeparent: Flow\nPipeline (DLT) v3(reserved)none5 perm levels: VIEW/RUN/MANAGE/IS_OWNER\n\n\n\n5.6 \u2014 Identity (4)\n\n\nSecurableCodeTodayNotes\nUserscim/*.rsprincipal onlyalso securable \u2014 MANAGE_PAT \u00b7 MANAGE_USER\nGroupscim/*.rsprincipal onlyalso securable \u2014 MANAGE_GROUP\nServicePrincipalscim/account_scope.rsprincipal onlyalso securable \u2014 MANAGE_SP\nPAT(via identity)implicit ownercascade from User\n\n\n\n5.7 \u2014 Account &amp; Secrets (2)\n\n\nSecurableCodeTodayNotes\nAccountaccounts/mod.rsimplicit admintop-level; MANAGE_ACCOUNT\nSecretScope \u00b7 Secretsecrets.rssecret_aclsfold; secret cascades from scope\n\n\n\n\n6 \u00b7 Object hierarchy tree\n\n\n\n\n  \nAccount\n    \n\n      \n\u21b3 Workspace (formerly workspace_permission_assignments)\n        \n\n          \n\u21b3 Cluster compute_cluster_acls \u2192 grant\n          \n\u21b3 Network [v2]\n          \n\u21b3 MCPServer [v3]\n          \n\u21b3 Job job_acls \u2192 grant\n            \n\n\u21b3 JobRun \u00b7 TaskRun (cascade \u00b7 no SELECT)\n          \n          \n\u21b3 Flow [v2]\n            \n\n\u21b3 FlowRun \u00b7 FlowStepRun (cascade)\n          \n          \n\u21b3 Pipeline (DLT) [v3]\n            \n\n\u21b3 PipelineUpdate \u00b7 PipelineEvent (cascade)\n          \n          \n\u21b3 Notebook workspace_object_acls \u2192 grant\n            \n\n\u21b3 NotebookCell \u00b7 KernelSession (cascade)\n          \n          \n\u21b3 Directory \u2192 File (path ancestor)\n          \n\u21b3 Dashboard\n            \n\n\u21b3 Dataset \u00b7 Chat \u00b7 Message \u00b7 Attachment \u00b7 Schedule \u00b7 Refresh \u00b7 Soul \u00b7 SkillOverride (cascade)\n          \n          \n\u21b3 User \u00b7 Group \u00b7 ServicePrincipal (also principals)\n            \n\n\u21b3 PAT (owner-only)\n          \n        \n      \n      \n\u21b3 Metastore grants\n        \n\n          \n\u21b3 Catalog\n            \n\n              \n\u21b3 Schema\n                \n\n                  \n\u21b3 Table \u00b7 StreamingTable \u00b7 View \u00b7 MaterializedView\n                  \n\u21b3 Volume \u00b7 Function \u00b7 Procedure \u00b7 Model\n                \n              \n            \n          \n          \n\u21b3 StorageCredential \u00b7 ExternalLocation\n          \n\u21b3 Connection\n            \n\n              \n\u21b3 ForeignCatalog \u00b7 ForeignSchema \u00b7 ForeignTable (cascade)\n              \n\u21b3 ServiceCredential [v2]\n            \n          \n          \n\u21b3 Share \u00b7 Provider \u00b7 Recipient \u00b7 CleanRoom\n          \n\u21b3 MarketplaceListing \u00b7 Exchange [v3]\n          \n\u21b3 ExternalMetadata [v3]\n          \n\u21b3 DatabaseInstance [v3]\n          \n\u21b3 GovernedTag \u00b7 TagPolicy [v3]\n          \n\u21b3 SecretScope \u2192 Secret secret_acls \u2192 grant\n        \n      \n      \n\u21b3 MetastoreAssignment (Workspace \u00d7 Metastore)\n      \n\u21b3 WorkspaceCatalogBinding (Workspace \u00d7 Catalog, mode=R/RW)\n    \n  \n\n\n\n\n\n7 \u00b7 Privilege vocabulary\n\n\n7.1 \u2014 Core verbs\n\n\nPrivilegeApplies toMaps from (legacy)\nBROWSEanyCAN_READ \u00b7 CAN_VIEW \u00b7 CAN_VIEW_METADATA\nSELECTTable \u00b7 View \u00b7 MV \u00b7 StreamingTable\u2014\nMODIFYTable \u00b7 Volume \u00b7 File \u00b7 Notebook \u00b7 QueryCAN_EDIT \u00b7 CAN_MANAGE_RUN(write)\nEXECUTEFunction \u00b7 Procedure \u00b7 Model \u00b7 Job \u00b7 Flow \u00b7 Pipeline \u00b7 Notebook \u00b7 ClusterCAN_RUN \u00b7 CAN_RESTART \u00b7 CAN_QUERY\nUSECluster(attach) \u00b7 Connection \u00b7 ServiceCredential \u00b7 ExternalLocationCAN_ATTACH_TO\nUSE_CATALOG \u00b7 USE_SCHEMACatalog \u00b7 Schema\u2014\nMANAGEanyCAN_MANAGE\n(owner column)anyIS_OWNER\n\n\n\n7.2 \u2014 Specialised verbs\n\n\nPrivilegeApplies toNotes\nREAD_VOLUME \u00b7 WRITE_VOLUMEVolume \u00b7 ExternalLocationbyte-level\nREAD_FILES \u00b7 WRITE_FILESExternalLocation \u00b7 Volume\u2014\nREFRESHMV \u00b7 StreamingTable \u00b7 Pipelinerefresh w/o full MANAGE\nEXTERNAL_USE_SCHEMA \u00b7 EXTERNAL_USE_LOCATIONSchema \u00b7 ExternalLocationfor OSS engines (Iceberg-REST); excluded from ALL_PRIVILEGES\nUSE_CONNECTION \u00b7 USE_PROVIDER \u00b7 USE_RECIPIENT \u00b7 USE_SHAREConnection \u00b7 Provider \u00b7 Recipient \u00b7 Sharefederation &amp; sharing\nUSE_MARKETPLACE_ASSETSMarketplaceListing \u00b7 Exchangesubscribe / install\nASSIGN \u00b7 CREATE_TAG \u00b7 MANAGE_TAGGovernedTag \u00b7 TagPolicytag-based ABAC\nCREATE_EXTERNAL_METADATAMetastore \u00b7 ExternalMetadata\u2014\nCREATE_*parent containersCREATE_TABLE / CREATE_SCHEMA / CREATE_FUNCTION / CREATE_VOLUME / CREATE_MODEL / CREATE_MODEL_VERSION / CREATE_FOREIGN_CATALOG\nMANAGE_PAT \u00b7 MANAGE_USER \u00b7 MANAGE_GROUP \u00b7 MANAGE_SPUser \u00b7 Group \u00b7 ServicePrincipalSCIM admin\nMANAGE_ACCOUNTAccounttop-level admin; never auto-cascades to data plane (see \u00a717)\nREAD_BINDING \u00b7 WRITE_BINDINGWorkspaceCatalogBinding\u2014\nALL_PRIVILEGES macroanyexpands to all of above except EXTERNAL_USE_*, MANAGE_PAT, MANAGE_ACCOUNT \u2014 Databricks-spec compliant\n\n\n\n\n8 \u00b7 Cascade rules \u2014 data, not code\n\nStatic parent\u2192child privilege flow rules. Resolver pass-4 JOINs securable_cascade; adding a new asset = add rows here, no Rust change.\n\nINSERT INTO securable_cascade (parent_type, child_type, privileges) VALUES\n  -- Account \u2192 Workspace/Metastore\n  ('Account',     'Workspace',           ARRAY['MANAGE','BROWSE']),\n  ('Account',     'Metastore',           ARRAY['MANAGE','BROWSE']),\n  -- Workspace \u2192 compute / orchestration / collab\n  ('Workspace',   'Cluster',             ARRAY['MANAGE','BROWSE']),\n  ('Workspace',   'Network',             ARRAY['MANAGE']),\n  ('Workspace',   'MCPServer',           ARRAY['MANAGE','BROWSE']),\n  ('Workspace',   'Job',                 ARRAY['MANAGE','BROWSE']),\n  ('Workspace',   'Flow',                ARRAY['MANAGE','BROWSE']),\n  ('Workspace',   'Pipeline',            ARRAY['MANAGE','BROWSE']),\n  ('Workspace',   'Notebook',            ARRAY['MANAGE','BROWSE']),\n  ('Workspace',   'Dashboard',           ARRAY['MANAGE','BROWSE']),\n  ('Workspace',   'Directory',           ARRAY['MANAGE','BROWSE']),\n  -- File system + collab cascades\n  ('Directory',   'File',                ARRAY['MANAGE','BROWSE','MODIFY','SELECT']),\n  ('Directory',   'Directory',           ARRAY['MANAGE','BROWSE','MODIFY','SELECT']),\n  ('Notebook',    'NotebookCell',        ARRAY['MANAGE','BROWSE','MODIFY','EXECUTE']),\n  ('Notebook',    'KernelSession',       ARRAY['MANAGE','BROWSE','EXECUTE']),\n  ('Dashboard',   'DashboardChild',      ARRAY['MANAGE','BROWSE']),\n  -- Run-style children (NO SELECT \u2014 don't leak through logs)\n  ('Job',         'JobRun',              ARRAY['MANAGE','BROWSE','EXECUTE']),\n  ('Flow',        'FlowRun',             ARRAY['MANAGE','BROWSE','EXECUTE']),\n  ('Pipeline',    'PipelineUpdate',      ARRAY['MANAGE','BROWSE','EXECUTE']),\n  -- Metastore \u2192 resources\n  ('Metastore',   'Catalog',             ARRAY['MANAGE','BROWSE']),\n  ('Metastore',   'StorageCredential',   ARRAY['MANAGE','BROWSE']),\n  ('Metastore',   'ExternalLocation',    ARRAY['MANAGE','BROWSE']),\n  ('Metastore',   'Connection',          ARRAY['MANAGE','BROWSE']),\n  ('Metastore',   'Share',               ARRAY['MANAGE','BROWSE']),\n  ('Metastore',   'Provider',            ARRAY['MANAGE','BROWSE']),\n  ('Metastore',   'Recipient',           ARRAY['MANAGE','BROWSE']),\n  ('Metastore',   'CleanRoom',           ARRAY['MANAGE','BROWSE']),\n  ('Metastore',   'MarketplaceListing',  ARRAY['MANAGE','BROWSE']),\n  ('Metastore',   'Exchange',            ARRAY['MANAGE','BROWSE']),\n  ('Metastore',   'ExternalMetadata',    ARRAY['MANAGE','BROWSE']),\n  ('Metastore',   'DatabaseInstance',    ARRAY['MANAGE','BROWSE']),\n  ('Metastore',   'GovernedTag',         ARRAY['MANAGE','BROWSE','ASSIGN']),\n  ('Metastore',   'SecretScope',         ARRAY['MANAGE','BROWSE']),\n  -- Connection \u2192 foreign objects\n  ('Connection',  'ForeignCatalog',      ARRAY['USE_CATALOG','BROWSE']),\n  ('Connection',  'ServiceCredential',   ARRAY['USE','MANAGE']),\n  -- UC tree\n  ('Catalog',     'Schema',              ARRAY['USE_CATALOG','BROWSE','MANAGE']),\n  ('Schema',      'Table',               ARRAY['USE_SCHEMA','BROWSE','SELECT','MODIFY','MANAGE']),\n  ('Schema',      'StreamingTable',      ARRAY['USE_SCHEMA','BROWSE','SELECT','REFRESH','MANAGE']),\n  ('Schema',      'View',                ARRAY['USE_SCHEMA','BROWSE','SELECT','MANAGE']),\n  ('Schema',      'MaterializedView',    ARRAY['USE_SCHEMA','BROWSE','SELECT','REFRESH','MANAGE']),\n  ('Schema',      'Volume',              ARRAY['USE_SCHEMA','BROWSE','READ_VOLUME','WRITE_VOLUME','MANAGE']),\n  ('Schema',      'Function',            ARRAY['USE_SCHEMA','BROWSE','EXECUTE','MANAGE']),\n  ('Schema',      'Procedure',           ARRAY['USE_SCHEMA','BROWSE','EXECUTE','MANAGE']),\n  ('Schema',      'Model',               ARRAY['USE_SCHEMA','BROWSE','EXECUTE','MANAGE']),\n  -- Identity\n  ('User',        'PAT',                 ARRAY['MANAGE_PAT']),\n  -- Secrets\n  ('SecretScope', 'Secret',              ARRAY['BROWSE','SELECT','MODIFY','MANAGE']);\n\n\n\n9 \u00b7 DDL \u2014 paste-ready\n\n\n9.1 \u2014 Principals (with FederatedIdentity)\n\nCREATE TYPE principal_kind AS ENUM ('User','Group','ServicePrincipal','Recipient','FederatedIdentity');\n\nCREATE TABLE principal (\n    id                  UUID PRIMARY KEY DEFAULT gen_random_uuid(),\n    account_id          UUID NOT NULL,\n    kind                principal_kind NOT NULL,\n    name                TEXT NOT NULL,\n    -- federated identity columns (NULL for native principals)\n    federated_issuer    TEXT,\n    federated_subject   TEXT,\n    federated_audience  TEXT,\n    attrs               JSONB NOT NULL DEFAULT '{}',  -- for ABAC\n    created_at          TIMESTAMPTZ NOT NULL DEFAULT now(),\n    UNIQUE (account_id, kind, name)\n);\n\nCREATE UNIQUE INDEX principal_federated_unique\n  ON principal (federated_issuer, federated_subject, federated_audience)\n  WHERE kind = 'FederatedIdentity';\n\nCREATE TABLE principal_membership (\n    parent_id   UUID NOT NULL REFERENCES principal(id) ON DELETE CASCADE,\n    member_id   UUID NOT NULL REFERENCES principal(id) ON DELETE CASCADE,\n    account_id  UUID NOT NULL,\n    PRIMARY KEY (parent_id, member_id),\n    CHECK (parent_id != member_id)\n);\nCREATE INDEX principal_membership_member ON principal_membership (member_id);\n-- depth \u2264 3 enforced at write time\n\n\n9.2 \u2014 Securables (typed graph)\n\nCREATE TABLE securable (\n    id              UUID PRIMARY KEY DEFAULT gen_random_uuid(),\n    account_id      UUID NOT NULL,\n    type            TEXT NOT NULL,                       -- enum widened (see \u00a75)\n    parent_id       UUID REFERENCES securable(id) ON DELETE CASCADE,\n    name            TEXT NOT NULL,\n    full_name       TEXT NOT NULL,                       -- e.g. \"main.sales.orders\"\n    owner_id        UUID NOT NULL REFERENCES principal(id),\n    pending_owner_id UUID REFERENCES principal(id),      -- two-phase transfer\n    pending_since   TIMESTAMPTZ,\n    metadata        JSONB NOT NULL DEFAULT '{}',\n    created_at      TIMESTAMPTZ NOT NULL DEFAULT now(),\n    UNIQUE (account_id, type, full_name)\n);\nCREATE INDEX securable_parent ON securable (parent_id);\nCREATE INDEX securable_account_type ON securable (account_id, type);\n\n\n9.3 \u2014 Grants (with valid_from / revoked_at / Cedar condition)\n\nCREATE TABLE grant (\n    id                  UUID PRIMARY KEY DEFAULT gen_random_uuid(),\n    account_id          UUID NOT NULL,\n    principal_id        UUID NOT NULL REFERENCES principal(id) ON DELETE CASCADE,\n    securable_id        UUID NOT NULL REFERENCES securable(id) ON DELETE CASCADE,\n    privilege           TEXT NOT NULL,\n    effect              TEXT NOT NULL DEFAULT 'ALLOW' CHECK (effect IN ('ALLOW','DENY')),\n    granted_by          UUID NOT NULL REFERENCES principal(id),\n    granted_at          TIMESTAMPTZ NOT NULL DEFAULT now(),\n    valid_from          TIMESTAMPTZ NOT NULL DEFAULT now(),\n    expires_at          TIMESTAMPTZ,\n    revoked_at          TIMESTAMPTZ,\n    revoked_by          UUID REFERENCES principal(id),\n    condition_cedar     TEXT,                            -- optional Cedar fragment\n    UNIQUE (account_id, principal_id, securable_id, privilege, effect)\n);\n\nCREATE INDEX grant_live_allow_idx ON grant (account_id, principal_id, securable_id)\n  WHERE effect = 'ALLOW'\n    AND revoked_at IS NULL\n    AND valid_from &lt;= now()\n    AND (expires_at IS NULL OR expires_at &gt; now());\n\n-- Forbid pass needs a separate index:\nCREATE INDEX grant_live_deny_idx ON grant (account_id, principal_id, securable_id)\n  WHERE effect = 'DENY' AND revoked_at IS NULL;\n\n\n9.4 \u2014 Cascade rules table\n\nCREATE TABLE securable_cascade (\n    parent_type   TEXT NOT NULL,\n    child_type    TEXT NOT NULL,\n    privileges    TEXT[] NOT NULL,\n    PRIMARY KEY (parent_type, child_type)\n);\n-- Seed rows: see \u00a78\n\n\n9.5 \u2014 Row filters &amp; column masks (attached, not granted)\n\nCREATE TABLE row_filter_attachment (\n    table_id      UUID PRIMARY KEY REFERENCES securable(id) ON DELETE CASCADE,\n    function_id   UUID NOT NULL REFERENCES securable(id) ON DELETE RESTRICT,\n    args          TEXT[] NOT NULL DEFAULT '{}'\n);\n\nCREATE TABLE column_mask_attachment (\n    table_id      UUID NOT NULL REFERENCES securable(id) ON DELETE CASCADE,\n    column_name   TEXT NOT NULL,\n    function_id   UUID NOT NULL REFERENCES securable(id) ON DELETE RESTRICT,\n    args          TEXT[] NOT NULL DEFAULT '{}',\n    PRIMARY KEY (table_id, column_name)\n);\n-- Applied at query rewrite (DuckDB/Spark/Trino client).\n-- If user has SELECT on table but cannot EXECUTE the mask UDF \u2192 fail-closed.\n\n\n9.6 \u2014 Audit (hash-chained, SOC2/HIPAA-grade)\n\nCREATE TABLE audit_event (\n    id              UUID PRIMARY KEY DEFAULT gen_random_uuid(),\n    account_id      UUID NOT NULL,\n    request_id      UUID NOT NULL,\n    happened_at     TIMESTAMPTZ NOT NULL DEFAULT now(),\n    actor_id        UUID NOT NULL,\n    actor_session   UUID,                                -- on-behalf-of chain id\n    source_ip       INET,\n    user_agent      TEXT,\n    action          TEXT NOT NULL,                       -- GRANT/REVOKE/READ/DECISION_DENY/...\n    securable_type  TEXT NOT NULL,\n    securable_id    UUID,\n    privilege       TEXT,\n    decision        TEXT NOT NULL CHECK (decision IN ('ALLOW','DENY','ERROR')),\n    rationale       TEXT NOT NULL,                       -- \"owner pass\" / \"deny@ancestor X\"\n    before_state    JSONB,\n    after_state     JSONB,\n    prev_hash       BYTEA,\n    self_hash       BYTEA NOT NULL                       -- sha256(prev_hash || canonical_json)\n);\nCREATE INDEX audit_event_account_time ON audit_event (account_id, happened_at DESC);\nCREATE INDEX audit_event_actor       ON audit_event (account_id, actor_id, happened_at DESC);\nCREATE INDEX audit_event_securable   ON audit_event (account_id, securable_id, happened_at DESC);\n\n\n9.7 \u2014 ACL versioning (cache invalidation)\n\nCREATE TABLE acl_version (\n    id                      INT PRIMARY KEY DEFAULT 1 CHECK (id = 1),\n    grants_version          BIGINT NOT NULL DEFAULT 0,\n    membership_version      BIGINT NOT NULL DEFAULT 0,\n    ownership_version       BIGINT NOT NULL DEFAULT 0,\n    cascade_version         BIGINT NOT NULL DEFAULT 0\n);\nINSERT INTO acl_version DEFAULT VALUES;\n\n-- Bump triggers (one per table)\nCREATE FUNCTION bump_grants_version() RETURNS TRIGGER AS $$\nBEGIN\n  UPDATE acl_version SET grants_version = grants_version + 1 WHERE id = 1;\n  RETURN NEW;\nEND $$ LANGUAGE plpgsql;\nCREATE TRIGGER grant_version_bump AFTER INSERT OR UPDATE OR DELETE ON grant\n  FOR EACH STATEMENT EXECUTE FUNCTION bump_grants_version();\n-- analogous triggers for principal_membership, securable.owner_id, securable_cascade\n\n\n\n10 \u00b7 Resolver \u2014 the 4 passes\n\nDefault-deny. Order-independent. Cedar-style. Single SQL CTE returns booleans for all four passes; final decision = NOT forbid AND (admin OR owner OR permit).\n\nWITH RECURSIVE\neffective_principals AS (              -- pass 0: user + transitive groups\n    SELECT $user_id::uuid AS id\n  UNION\n    SELECT pm.parent_id FROM principal_membership pm\n      JOIN effective_principals ep ON pm.member_id = ep.id\n),\nancestors AS (                         -- self + parent chain\n    SELECT $sec_id::uuid AS id, 0 AS depth\n  UNION ALL\n    SELECT s.parent_id, a.depth + 1\n      FROM securable s JOIN ancestors a ON s.id = a.id\n     WHERE s.parent_id IS NOT NULL\n)\nSELECT\n  -- PASS 1 \u2014 explicit DENY anywhere on the ancestor chain?\n  EXISTS (SELECT 1 FROM grant g\n            JOIN ancestors a ON g.securable_id = a.id\n            JOIN effective_principals ep ON g.principal_id = ep.id\n           WHERE g.account_id = $acct\n             AND g.privilege = $priv AND g.effect = 'DENY'\n             AND g.revoked_at IS NULL) AS forbid,\n\n  -- PASS 2 \u2014 account/workspace admin?\n  EXISTS (SELECT 1 FROM grant g\n            JOIN effective_principals ep ON g.principal_id = ep.id\n           WHERE g.account_id = $acct\n             AND g.privilege = 'MANAGE_ACCOUNT'\n             AND g.effect = 'ALLOW'\n             AND g.revoked_at IS NULL) AS admin,\n\n  -- PASS 3 \u2014 securable owner (column, not grant row)?\n  EXISTS (SELECT 1 FROM securable s\n            JOIN effective_principals ep\n              ON ep.id IN (s.owner_id, s.pending_owner_id)\n           WHERE s.id = $sec_id) AS owner,\n\n  -- PASS 4 \u2014 explicit ALLOW (with cascade-rule check)?\n  EXISTS (SELECT 1 FROM grant g\n            JOIN ancestors a ON g.securable_id = a.id\n            JOIN effective_principals ep ON g.principal_id = ep.id\n            JOIN securable child  ON child.id  = $sec_id\n            JOIN securable parent ON parent.id = a.id\n       LEFT JOIN securable_cascade c\n              ON c.parent_type = parent.type AND c.child_type = child.type\n           WHERE g.account_id = $acct\n             AND g.privilege = $priv\n             AND g.effect    = 'ALLOW'\n             AND g.revoked_at IS NULL\n             AND g.valid_from &lt;= now()\n             AND (g.expires_at IS NULL OR g.expires_at &gt; now())\n             AND (a.depth = 0 OR $priv = ANY(c.privileges))) AS permit;\n-- decision = NOT forbid AND (admin OR owner OR permit)\n-- if condition_cedar IS NOT NULL \u2192 evaluate Cedar fragment with (principal,resource,context)\n-- \u2192 on parse/eval error: deny.\n\n\n\n11 \u00b7 List-endpoint predicate pushdown\n\nSingle-decision uses the CTE above. List endpoints use a materialized view to avoid N+1.\n\nCREATE MATERIALIZED VIEW effective_grants_mv AS\nWITH RECURSIVE eff_p AS (\n    SELECT p.id AS principal_id, p.id AS effective_id, p.account_id\n      FROM principal p WHERE p.kind IN ('User','ServicePrincipal','FederatedIdentity')\n  UNION\n    SELECT ep.principal_id, pm.parent_id, ep.account_id\n      FROM principal_membership pm\n      JOIN eff_p ep ON pm.member_id = ep.effective_id\n)\nSELECT\n    g.account_id, ep.principal_id, g.securable_id, g.privilege, g.effect\nFROM grant g\nJOIN eff_p ep ON g.principal_id = ep.effective_id\nWHERE g.revoked_at IS NULL\n  AND g.valid_from &lt;= now()\n  AND (g.expires_at IS NULL OR g.expires_at &gt; now());\n\nCREATE UNIQUE INDEX effective_grants_mv_pk\n  ON effective_grants_mv (account_id, principal_id, securable_id, privilege, effect);\n\n-- list-filter pattern\nSELECT s.* FROM securable s\nWHERE s.account_id = $acct AND s.type = 'Catalog'\n  AND EXISTS (\n    SELECT 1 FROM effective_grants_mv eg\n    WHERE eg.account_id   = $acct\n      AND eg.principal_id = $user\n      AND eg.securable_id IN (SELECT id FROM ancestors_of(s.id))\n      AND eg.privilege IN ('BROWSE','USE_CATALOG','MANAGE','MANAGE_ACCOUNT')\n      AND eg.effect = 'ALLOW'\n  )\n  AND NOT EXISTS (\n    SELECT 1 FROM effective_grants_mv eg\n    WHERE eg.account_id = $acct AND eg.principal_id = $user\n      AND eg.securable_id IN (SELECT id FROM ancestors_of(s.id))\n      AND eg.effect = 'DENY'\n  );\n\n-- REFRESH MATERIALIZED VIEW CONCURRENTLY effective_grants_mv;\n-- triggered by acl_version bump (debounced 500ms).\n\n\n\n12 \u00b7 Row filters &amp; column masks\n\nSnowflake / BigQuery / Lake Formation all attach filters/masks to the table-or-column and apply at query rewrite. v3 follows that. Filters/masks themselves are SQL UDFs registered as Function securables \u2014 so the EXECUTE check on the UDF naturally enforces who can apply them.\n\n-- Attachment lookup at query plan time:\nSELECT rf.function_id, rf.args FROM row_filter_attachment rf WHERE rf.table_id = $t;\nSELECT cm.column_name, cm.function_id, cm.args FROM column_mask_attachment cm WHERE cm.table_id = $t;\n\n-- Rewrite (pseudo):\n--   SELECT a, b, c FROM t\n-- becomes\n--   SELECT mask_a(a) AS a, b, mask_c(c) AS c\n--   FROM t\n--   WHERE row_filter(b, c)\n-- The UDF evaluation invokes the resolver (EXECUTE on the function securable).\n-- If denied \u2192 deny the whole SELECT (fail-closed).\n\n\n\n13 \u00b7 Conditions \u2014 Cedar grammar\n\n-- The grant.condition_cedar column accepts a Cedar policy fragment.\n-- Examples:\nwhen { context.source_ip in ip(\"10.0.0.0/8\") }\nwhen { context.now &lt; datetime(\"2026-12-31T00:00:00Z\") }\nwhen { principal has \"department\" &amp;&amp; principal.department == \"finance\" }\nwhen { resource has \"tag.PII\" &amp;&amp; resource.tag.PII == \"true\" implies\n       principal has \"training.hipaa\" &amp;&amp; principal.training.hipaa == \"current\" }\n\n-- Resolver flow:\n-- 1. Pass-4 produces ALLOW (no condition) \u2192 check passes.\n-- 2. If condition_cedar IS NOT NULL \u2192 parse + evaluate with\n--    (principal_attrs, resource_attrs, context).\n-- 3. Parse error / eval error \u2192 DENY (fail-closed).\n-- Cedar is total + decidable, so eval is bounded.\n\n\n\n14 \u00b7 Audit invariants (SOC2 / HIPAA)\n\n\nFieldWhy\nrequest_idcorrelate w/ HTTP layer + downstream calls\nactor_id + actor_sessiondistinguish direct call vs on-behalf-of chain\nsource_ip + user_agentOWASP ASVS V7 minimum\nbefore_state + after_state (JSONB)policy snapshot for grant mutations \u2014 forensic replay\nrationalewhich pass matched: \"owner column\" / \"deny @ ancestor X\" / \"permit via group Y\"\nprev_hash + self_hashappend-only hash chain; integrity verified by periodic job\n\n\n\n\n15 \u00b7 Federated identity\n\nBeyond User/Group/SP, modern workloads carry tokens from external IDPs (GitHub Actions, Azure WIF, GCP WIF, SPIFFE). v3 models these as a principal kind with stable (issuer, sub, aud) identity.\n\n-- JWT validation path (RFC 8693 token-exchange):\n-- 1. Verify signature against issuer JWKS.\n-- 2. Extract (iss, sub, aud).\n-- 3. SELECT id FROM principal\n--    WHERE kind='FederatedIdentity'\n--      AND federated_issuer=$iss AND federated_subject=$sub AND federated_audience=$aud;\n-- 4. If not found and issuer is allow-listed \u2192 JIT-create principal row.\n-- 5. Resolver runs against returned principal_id as if it were a User.\n-- 6. audit_event.actor_session captures the token-exchange chain id.\n\n-- Policy: chain depth \u2264 3 (rejects multi-hop OBO laundering).\n\n\n\n16 \u00b7 Multi-tenant isolation\n\n-- account_id on EVERY core ACL row, even when redundant w/ FK chain.\nALTER TABLE securable             ADD COLUMN account_id UUID NOT NULL;\nALTER TABLE grant                 ADD COLUMN account_id UUID NOT NULL;\nALTER TABLE principal             ADD COLUMN account_id UUID NOT NULL;\nALTER TABLE principal_membership  ADD COLUMN account_id UUID NOT NULL;\nALTER TABLE audit_event           ADD COLUMN account_id UUID NOT NULL;\n\n-- Postgres RLS \u2014 defence in depth\nALTER TABLE grant ENABLE ROW LEVEL SECURITY;\nCREATE POLICY grant_tenant_isolation ON grant\n  USING (account_id = current_setting('app.account_id')::uuid);\n\n-- Cache key includes account_id:\n-- decision_cache: (account_id, principal_id, securable_id, privilege, grants_version)\n-- list_cache:     (account_id, principal_id, securable_type, grants_version)\n\n\n\n17 \u00b7 Anti-escalation rules\n\n\nRuleWhyEnforcement\nGrantor-holds-privilege (Postgres semantics)Stop \"MANAGE on Catalog\" laundering into SELECT on every TableMutating handler: has(actor, target, MANAGE) AND has(actor, target, privilege_being_granted) before INSERT\nTwo-phase ownership transferAvoid owner-orphaningPhase 1: prior owner sets pending_owner_id. Phase 2: new owner accepts within 7 days, else revert. During pending: both have MANAGE.\nDENY walks full ancestor chainCedar CVE-2024-25624 classPass-1 SELECT JOINs ancestors CTE, not direct grants only\nNo self-grant of MANAGE_ACCOUNTCasbin CVE-2023-26485 classBEFORE INSERT trigger rejects actor_id = principal_id AND privilege = 'MANAGE_ACCOUNT'\nToken-exchange chain depth \u2264 3OBO chains hide intentactor_session.depth tracked; reject &gt;3 hops\nMANAGE_ACCOUNT does NOT auto-cascade to data planeAccount admins shouldn't silently see all dataPass-2 (admin) controls control-plane only; data-plane reads still need explicit BROWSE/SELECT or owner\nALL_PRIVILEGES excludes EXTERNAL_USE_*, MANAGE_PAT, MANAGE_ACCOUNTDatabricks-spec complianceMacro expansion at grant write-time\n\n\n\n\n18 \u00b7 Cache architecture\n\n\nCacheKeyInvalidated byHit rate target\nDecision(account_id, principal_id, securable_id, privilege, grants_version, ownership_version, cascade_version)any version bump&gt;95%\nGroup closure(account_id, principal_id, membership_version)membership_version bump&gt;99%\nList filter(account_id, principal_id, securable_type, grants_version)grants_version bump&gt;90%\nMV refresh\u2014500ms debounce after grants_version bump\u2014\n\n\nmoka-rs with TTI (time-to-idle) of 5 min, max 100k entries per cache. Version columns are bumped by triggers so cache reads never need a separate timestamp lookup.\n\n\n\n19 \u00b7 Future assets \u2014 pre-planned\n\nLock the shape now so adding the feature later is a 1-line enum widen + 1 cascade row. No retro migration ever.\n\n\n19.1 \u2014 AI / ML\n\n\nAssetParentPrivilegesCascade\nVectorIndexSchemaSELECT \u00b7 MODIFY \u00b7 MANAGE \u00b7 BROWSEUSE_SCHEMA \u00b7 BROWSE \u00b7 MANAGE\nServingEndpointWorkspaceEXECUTE (CAN_QUERY) \u00b7 MANAGE \u00b7 BROWSEMANAGE \u00b7 BROWSE\nFeatureTableSchemaSELECT \u00b7 MODIFY \u00b7 MANAGE \u00b7 BROWSEUSE_SCHEMA \u00b7 BROWSE \u00b7 MANAGE\nOnlineTableSchemaSELECT \u00b7 MANAGE \u00b7 BROWSEUSE_SCHEMA \u00b7 BROWSE \u00b7 MANAGE\nInferenceLogServingEndpointBROWSE \u00b7 MANAGEBROWSE \u00b7 MANAGE\nAIGatewayWorkspaceUSE \u00b7 MANAGE \u00b7 BROWSEMANAGE\n\n\n\n19.2 \u2014 SQL / Compute\n\n\nAssetParentPrivileges\nSQLWarehouseWorkspaceUSE \u00b7 EXECUTE \u00b7 MANAGE \u00b7 BROWSE\nInstancePool \u00b7 ClusterPolicyWorkspaceUSE \u00b7 MANAGE \u00b7 BROWSE\nQuery \u00b7 Alert \u00b7 QueryHistoryWorkspaceSELECT/run \u00b7 MODIFY \u00b7 MANAGE \u00b7 BROWSE\n\n\n\n19.3 \u2014 Workspace / Collaboration\n\n\nAssetParentPrivileges\nRepoWorkspaceSELECT \u00b7 MODIFY \u00b7 EXECUTE \u00b7 MANAGE \u00b7 BROWSE\nApp (Lakehouse Apps) \u00b7 GenieSpaceWorkspaceUSE \u00b7 EXECUTE \u00b7 MANAGE \u00b7 BROWSE\nComment / Discussion (polymorphic)anyBROWSE \u00b7 MODIFY (own) \u00b7 MANAGE\n\n\n\n19.4 \u2014 Account / Platform\n\n\nAssetParentPrivileges\nBudget \u00b7 PrivateAccessSettings \u00b7 NetworkConnectivityConfigAccountBROWSE \u00b7 MANAGE\nIPAccessListWorkspaceBROWSE \u00b7 MANAGE\nOAuthApp \u00b7 EnterpriseAppAccountUSE \u00b7 MANAGE \u00b7 BROWSE\nAuditLogConfigAccountBROWSE \u00b7 MANAGE\nSystemSchemaMetastoreUSE_CATALOG \u00b7 USE_SCHEMA \u00b7 SELECT (admin-only by default)\n\n\n\n19.5 \u2014 Governance / Quality\n\n\nAssetParentPrivileges\nTag \u00b7 TagPolicyMetastoreBROWSE \u00b7 MANAGE \u00b7 ASSIGN\nLineage\u2014derived; visibility = AND of upstream/downstream BROWSE\nMonitorTableBROWSE \u00b7 MANAGE\nRowFilter \u00b7 ColumnMaskFunction (attached, not granted)USE \u00b7 MANAGE\n\n\n\n19.6 \u2014 Hard rules for adding any future asset\n\n\n#Rule\n1New feature = 1 enum value + 1 cascade row + parent FK on row. Never a new ACL table.\n2Owner = column on the resource table, never a grant row.\n3Reuse BROWSE/SELECT/MODIFY/EXECUTE/USE/MANAGE before inventing new verbs.\n4Empty cascade must be documented (admin-only / privacy).\n5Run-style children inherit BROWSE+EXECUTE+MANAGE only \u2014 never SELECT (don't leak through logs).\n6Polymorphic children (Comment, Tag) \u2192 parent_securable_id + JOIN securable_cascade at query time.\n7Reserve enum value at spec time, not code time.\n8Every new privilege documents whether ALL_PRIVILEGES includes it.\n\n\n\n\n20 \u00b7 Migration plan\n\n\n#PhaseStepReversible?\n1SchemaCreate new tables; widen SecurableType enum to 47 values + reservedyes (DROP)\n2Backfill UCCopy grants rows; add owner_id column on every UC tableyes\n3Backfill workspaceworkspace_object_acls + job_acls + compute_cluster_acls + secret_acls \u2192 grant rows w/ vocab translationyes\n4Backfill accountworkspace_permission_assignments \u2192 grants on Workspace securableyes\n5Materialize new securablesInsert rows in securable for Account, Workspace, Network, Flow, Pipeline, MCPServer, User, Group, SP, PAT, Binding, etc.yes\n6Cascade rulesSeed securable_cascade tableyes\n7Dual-writeMutating handlers write old + new; reads still old. 1 release.yes\n8Read cutoverReads from new; old tables read-only for 1 releaseyes (flip flag)\n9Drop legacyRemove 5 ACL tables + workspace_permission_assignmentsno \u2014 gate behind 2 stable releases\n10Enable RLS + CedarTurn on Postgres RLS on grant/securable; deploy Cedar evaluatoryes\n11MV pushdownMaterialize effective_grants_mv; flip list endpoints to use ityes\n\n\n\n\n21 \u00b7 Anti-patterns (postmortem-derived)\n\n\n#Anti-patternWhy it bitesMitigation\n1Hardcoded admin bypass in handlersDrift across 12 handlers; Lyra has 6 different bypass codepaths todaySingle resolver; admin = pass-2 only\n2Owner stored as grant rowLost grant = lost ownership; can't transfer atomicallyOwner = column on securable\n3Cascade hardcoded in RustNew asset requires Rust diff + recompilesecurable_cascade table\n4Group nesting via closure tableUpdates O(n\u00b2) on deep changes; Snowflake postmortemEdge list + recursive CTE, depth \u2264 3\n5Free-form JSON conditionsEval becomes Turing-complete; perf cliffCedar grammar (total, decidable)\n6List endpoints fetch-then-filterN+1 ACL queries; hidden 100ms latency on dashboardsMV + SQL pushdown\n7Audit log without before/afterSOC2 finding; can't replay incidentJSONB snapshots + hash chain\n8account_id implicit in FK chainSingle forgotten join = cross-tenant leakExplicit column + RLS\n9MANAGE auto-grants child SELECTQuiet escalationGrantor-holds-privilege rule\n10Owner transfer is single-phasePrior owner orphaned mid-transferTwo-phase w/ pending_owner_id\n\n\n\n\n22 \u00b7 Sources\n\n\n  \nDatabricks UC \u2014 securable objects (2026)\n  \nUC privileges reference\n  \nDLT / Lakeflow Pipeline ACLs\n  \nExternal metadata / lineage\n  \nGoverned tag permissions\n  \nMarketplace listings\n  \nLakebase UC registration\n  \nUnity AI Gateway / MCP\n  \nSnowflake row access policies\n  \nBigQuery column data masking\n  \nAWS Lake Formation LF-TBAC\n  \nApache Polaris RBAC\n  \nCedar policy language\n  \nRFC 8693 \u2014 OAuth Token Exchange\n  \nDelta Sharing protocol\n\n\n\n\n\n", "creation_timestamp": "2026-05-10T02:48:32.000000Z"}