Skip to content

[CXH-2722] - Add Unity Catalog schema sync - Databricks Connector - #72

Open
mateoHernandez123 wants to merge 2 commits into
mateoHernandez123/unity-catalog-catalog-syncfrom
mateoHernandez123/unity-catalog-schema-sync
Open

mateoHernandez123 wants to merge 2 commits into
mateoHernandez123/unity-catalog-catalog-syncfrom
mateoHernandez123/unity-catalog-schema-sync

Conversation

@mateoHernandez123

@mateoHernandez123 mateoHernandez123 commented Oct 9, 2026 •

Copy link
Copy Markdown

Description

  • Bug fix
  • New feature

Third of four stacked PRs for Unity Catalog (CXH-2722). A schema sits between a catalog and the tables and volumes an access review certifies. A principal can hold USE_SCHEMA without holding anything on those tables, so the schema is its own resource.

#70 is merged. #71 merges before this one.

What the connector syncs

Before, with #71 applied. The catalog is a leaf.

flowchart TD
  account[account]
  metastore[metastore]
  catalog[catalog]
  other["user, group, service principal, role, workspace"]
  account --> other
  account --> metastore
  metastore --> catalog
Loading

After this PR. Schemas hang from the catalog, with their own entitlements and grants.

flowchart TD
  account[account]
  metastore[metastore]
  catalog[catalog]
  schema[schema]
  other["user, group, service principal, role, workspace"]
  account --> other
  account --> metastore
  metastore --> catalog
  catalog --> schema
  style schema fill:#1f6feb,color:#fff,stroke:#1f6feb
Loading
Order PR Delivers Base State
1 #91 staging landed on main main Open
2 #90 Fix for the metastore layer of #70 #91 Open
3 #71 Catalog #91 Open
4 #72 Schema #71 This PR
5 #73 Table and volume #72 Open

Sync:

flowchart LR
  catalog[catalog]
  info[information_schema]
  schema[schema]
  catalog -.->|skipped| info
  catalog --> schema
  schema --> entitlements[one entitlement per schema privilege]
  schema --> owner[read-only owner]
Loading

Provisioning:

  • Grant/Revoke schema privileges — the same PATCH .../permissions path and the same echo-confirmed idempotency as the catalog: GrantAlreadyExists and GrantAlreadyRevoked.
  • A revoke that fails with SCHEMA_DOES_NOT_EXIST or CATALOG_DOES_NOT_EXIST is already revoked, whether the code comes back from the permissions read or from the trust check before it. Only the error code proves the absence, because an unbound or unprivileged workspace answers 404 as well.
  • Owner is read-only.

Auth:

Credentials are unchanged. There is no new config field.

Schema List fails with PermissionDenied unless the service principal owns the metastore, owns the catalog, or holds MANAGE, USE_SCHEMA or ALL_PRIVILEGES on the catalog. Workspace Admin alone is not enough.

A direct Admin is also enough when the catalog is owned by that workspace's admins group. The connector reads the workspace id off the group name _workspace_admins_<workspace>_<id> and checks that workspace, whatever route the schema is listed through. Permission visibility follows the caller, not the host.

The visibility probe walks the attached workspaces. A 401 or 403 leaves the privilege unproven and the probe tries the next workspace. A workspace answering CATALOG_DOES_NOT_EXIST has only said it does not serve the catalog, since an ISOLATED catalog answers the same way from every workspace it is not bound to, so the walk moves on there too. Only every workspace answering that way, with no refusal among them, lists no schemas. Any other probe error fails that catalog's schema List. The page token carries the workspace and the metastore owner. It does not carry the catalog owner.

Architecture highlights:

  • Shared trust probe: holdsPrivilegeOn is the one uncached read of the connector's own effective privileges. Each level passes the privileges it accepts and the error codes that prove the securable is gone. The schema probes carry those codes, so a 404 naming the missing securable is read as an absence rather than as a privilege the caller cannot see. Without them a principal holding MANAGE but owning neither the metastore nor the catalog got PermissionDenied on a dropped parent instead of a revoke reporting the grant already gone.
  • Proven absence on the write path: the trust check is the first call that can prove a securable gone, and resolveChange runs it before the permissions read. grant and revoke answer that proof with NotFound and GrantAlreadyRevoked rather than returning it as an unreadable securable.
  • Routing: inherited from [CXH-2721] - Add Unity Catalog catalog sync - Databricks Connector #71. The metastore's routes and the workspace Admin check come from the sync session, so the account listings do not run again per catalog.
  • Principals: schema Grants uses the same principal path as [CXH-2720] - Add Unity Catalog client, access-path routing, and metastore sync - Databricks Connector #70. It reads the full account SCIM only when assignments exist, and uses the Find* filters for an owner-only grant.

Useful links:

Merge order and release flow

staging lands on main as its own PR, #91. The rest of the stack is rebased on top of it, so each PR below shows only the layer it adds. #84 is independent of this stack and merges on its own.

  1. Land staging on main - Databricks Connector #91 — staging landed on main (46 files)
  2. [CXH-3600] - Sync metastore privileges held by the workspace admins group - Databricks Connector #90 — metastore privileges held by the workspace admins group (15 files)
  3. [CXH-2721] - Add Unity Catalog catalog sync - Databricks Connector #71 — Unity Catalog catalog sync (25 files)
  4. [CXH-2722] - Add Unity Catalog schema sync - Databricks Connector #72 (this PR) — Unity Catalog schema sync (17 files)
  5. [CXH-2723] - Add Unity Catalog table and volume sync - Databricks Connector #73 — Unity Catalog table and volume sync (19 files)

#90 and #71 target #91 until it merges, and GitHub retargets them to main at that point. #72 and #73 keep their parent branch as base.

Rebased on 2026-11-03, as requested on #90. Before this, #90 and #71 showed 42 staging-only commits and 50+ files, which hid the layer each one adds. Every branch was rebuilt on top of #91. The tree at each head is byte-identical to the one reviewed before the rebase, so no code changed. The commits are squashed into one per PR, which marks earlier review threads as outdated; they stay readable.

Release flow. Cut a test release (-test) from the feature branch and move the PR to draft. The ticket moves to validation. QA validates that release. Merge to main. Cut the official release from main, and the ticket moves to rollout.

@linear-code

linear-code Bot commented Oct 9, 2026

Copy link
Copy Markdown

CXH-2722

@mateoHernandez123
mateoHernandez123 force-pushed the mateoHernandez123/unity-catalog-catalog-sync branch from d90274a to aabdb53 Compare October 9, 2026 13:20
@mateoHernandez123
mateoHernandez123 force-pushed the mateoHernandez123/unity-catalog-schema-sync branch from 920df46 to f9142b4 Compare October 9, 2026 13:20
@mateoHernandez123
mateoHernandez123 force-pushed the mateoHernandez123/unity-catalog-catalog-sync branch from aabdb53 to b12726a Compare October 9, 2026 14:27
@mateoHernandez123
mateoHernandez123 force-pushed the mateoHernandez123/unity-catalog-schema-sync branch from f9142b4 to 29274ee Compare October 9, 2026 14:27
@mateoHernandez123
mateoHernandez123 force-pushed the mateoHernandez123/unity-catalog-catalog-sync branch from b12726a to b2ecaf2 Compare October 9, 2026 16:46
@mateoHernandez123
mateoHernandez123 force-pushed the mateoHernandez123/unity-catalog-schema-sync branch from 29274ee to e74210e Compare October 9, 2026 16:46
@mateoHernandez123
mateoHernandez123 force-pushed the mateoHernandez123/unity-catalog-catalog-sync branch from b2ecaf2 to 00adc02 Compare October 9, 2026 16:54
@mateoHernandez123
mateoHernandez123 force-pushed the mateoHernandez123/unity-catalog-schema-sync branch from e74210e to 7deff05 Compare October 9, 2026 16:54
@mateoHernandez123
mateoHernandez123 force-pushed the mateoHernandez123/unity-catalog-catalog-sync branch from 00adc02 to 5933db6 Compare October 9, 2026 18:16
@mateoHernandez123
mateoHernandez123 force-pushed the mateoHernandez123/unity-catalog-schema-sync branch from 7deff05 to 1253d7e Compare October 9, 2026 18:17

@FeliLucero1 FeliLucero1 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did a pass on this one. The main things are the two mediums: Revoke on a dropped schema fails instead of returning already-revoked, and List swallows a 404 into an empty page. The rest is perf/docs/nits.

Stuff tagged (out of scope) is inherited from #71 or is stack-wide, so FYI, not blocking for this PR. Also checked the schema privilege set against the Databricks docs and it matches exactly (27, minus BROWSE / CREATE_SCHEMA / USE_CATALOG) 👍

Comment thread pkg/connector/schema.go Resolved
Comment thread pkg/connector/schema.go Resolved
Comment thread pkg/connector/schema.go Resolved
Comment thread pkg/connector/schema.go Resolved
Comment thread pkg/connector/schema.go Resolved
Comment thread pkg/connector/schema.go Resolved
Comment thread pkg/connector/schema.go Resolved

@FeliLucero1 FeliLucero1 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Image Image Image

@mateoHernandez123
mateoHernandez123 marked this pull request as draft October 22, 2026 16:10
@mateoHernandez123
mateoHernandez123 marked this pull request as ready for review October 22, 2026 21:38

@FeliLucero1 FeliLucero1 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another pass. Nice work on the live checks (SCHEMA_DOES_NOT_EXIST, the privilege PATCH) and moving Grant/Revoke onto the shared helpers 👍

The main things this round are about schemas silently disappearing: the partial-listing skip (replied on the :73 thread), the usable[0] regression (replied on Sergio's :353 thread), and a 404 that still comes out as NotFound. The rest are nits. (out of scope): the same NotFound-wrapping shape exists in catalog.go pushRoute (#71).

Comment thread pkg/connector/schema.go Resolved
Comment thread pkg/connector/schema.go Resolved
Comment thread docs/connector.mdx Resolved

@sergiocorral-conductorone sergiocorral-conductorone left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Automated review (follow-up)

Head moved since our last LGTM (ce73192 → abee6af). Resolved at head: the usable[0] regression (workspace walk is back, TestSchemaListTriesTheNextWorkspace restored), the partial-listing fail-closed behaviour, the docs nit on the workspace-admins-group exception. Still open from earlier rounds: FeliLucero1's thread on schema.go:269 (plain %w on the metastore-assignment / workspace / metastore-owner reads lets a NotFound become an SDK warning). New this round: 1 regression (Revoke on a dropped schema), 1 medium (built-in catalog skip), 2 low.

drafted 4 → confirmed 4, low-confidence 0, dropped 0, added 0 (panel: opus-5.5 / gpt-6-astra / gemini-4-argon, all high)

Comment thread pkg/connector/schema.go Resolved
Comment thread pkg/connector/catalog.go Resolved
Comment thread pkg/connector/schema.go Resolved
Comment thread pkg/connector/schema_test.go Resolved
Comment thread pkg/connector/catalog.go Resolved
Comment thread pkg/connector/schema.go Resolved
Comment thread pkg/connector/schema.go Resolved
Comment thread pkg/connector/schema.go Resolved
Comment thread pkg/connector/schema.go Resolved
Comment thread pkg/connector/schema.go Resolved
Comment thread pkg/connector/schema.go Resolved
Comment thread pkg/connector/schema.go Resolved

@sergiocorral-conductorone sergiocorral-conductorone left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Automated review (follow-up round on new commits).

Stats: drafted 2 → confirmed 2, low-confidence 0, dropped 0, added 0 (panel: opus-5.5 / gpt-6-astra / gemini-4-argon, all high).

Comment thread pkg/connector/schema.go Resolved
Comment thread pkg/connector/schema.go Resolved

@sergiocorral-conductorone sergiocorral-conductorone left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Automated review

drafted 3 → confirmed 3, low-confidence 0, dropped 0, added 0 (panel: opus-5.5 / gpt-6-astra / gemini-4-argon, all high).

Comment thread pkg/connector/schema.go Resolved
Comment thread pkg/connector/schema.go Resolved
Comment thread docs/docs-info.md Resolved
@mateoHernandez123
mateoHernandez123 force-pushed the mateoHernandez123/unity-catalog-catalog-sync branch from 60501e2 to a343b68 Compare November 3, 2026 18:27
mateoHernandez123 added a commit that referenced this pull request Nov 3, 2026
CXH-2722. Schemas hang from the catalog, keyed {metastore_id}::{catalog}.{schema},
with the catalog privilege set minus BROWSE, CREATE_SCHEMA and USE_CATALOG,
plus a read-only owner. information_schema is skipped. Grant and revoke use
the same echo-confirmed idempotency as the catalog.

The tree is unchanged from 433e33c, the state reviewed and approved on #72.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@mateoHernandez123
mateoHernandez123 force-pushed the mateoHernandez123/unity-catalog-schema-sync branch from 433e33c to b704b02 Compare November 3, 2026 18:27
mateoHernandez123 added a commit that referenced this pull request Nov 3, 2026
CXH-2722. Schemas hang from the catalog, keyed {metastore_id}::{catalog}.{schema},
with the catalog privilege set minus BROWSE, CREATE_SCHEMA and USE_CATALOG,
plus a read-only owner. information_schema is skipped. Grant and revoke use
the same echo-confirmed idempotency as the catalog.

The tree is unchanged from 433e33c, the state reviewed and approved on #72.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@mateoHernandez123
mateoHernandez123 force-pushed the mateoHernandez123/unity-catalog-schema-sync branch from b704b02 to f63db64 Compare November 3, 2026 20:10
mateoHernandez123 and others added 2 commits November 3, 2026 17:45
CXH-2722. Schemas hang from the catalog, keyed {metastore_id}::{catalog}.{schema},
with the catalog privilege set minus BROWSE, CREATE_SCHEMA and USE_CATALOG,
plus a read-only owner. information_schema is skipped. Grant and revoke use
the same echo-confirmed idempotency as the catalog.

The tree is unchanged from 433e33c, the state reviewed and approved on #72.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The trust read is the first call that can prove a securable gone, and it
runs before the permissions read. syncGrants returned its error as is, so
a schema deleted between List and Grants failed the sync with the bare
errSecurableGone sentinel: codes.Unknown, no securable name. Twelve lines
below, the permissions read already treats that same deletion as an empty
result rather than a failure. The two reads see the same fact and now
answer the same way.

A dropped parent catalog reaches the same place: the trust probe reads the
schema's effective permissions, a dropped catalog answers 404 with
CATALOG_DOES_NOT_EXIST, and trustReadError classifies it with the level's
full gone-code set.

The tests use a principal that owns neither the metastore nor the catalog.
With ownership the trust check short-circuits and never reaches the probe,
which is why the existing dropped-schema test did not cover this.

Also removes a docs bullet that said the built-in system catalogs are
synced while the connector owns the metastore. Catalog List skips them
unconditionally, as the bullet three lines above already says.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@mateoHernandez123
mateoHernandez123 force-pushed the mateoHernandez123/unity-catalog-schema-sync branch from f63db64 to 068e05d Compare November 3, 2026 20:46
Comment thread pkg/connector/schema.go
workspace: route.Workspace,
workspaceID: workspaceID,
}, annos)
if err != nil {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[low] schemaListingIsComplete returns errCatalogGone on purpose (:408-409), but here it just return errs, so it goes up raw (codes.Unknown, no prefix) and fails the sync. It's the race where GetCatalog says 200 and the permissions read then says CATALOG_DOES_NOT_EXIST. The catalogThrough branch above already does sawGone = true; continue, so maybe do the same here? There's no test for it either.

Comment thread docs/connector.mdx

Skip this section if you are not syncing schemas, or if the service principal owns the metastore or the catalog.

Workspace **Admin** does not list every schema, except when the catalog is owned by the workspace admins group (`_workspace_admins_<workspace>_<id>`) and the service principal is a direct Admin of that workspace. On any other catalog the service principal does not own, Databricks returns only the schemas it owns or can use, with HTTP 200. To list every schema, the service principal must own the metastore, own the catalog, be that direct Admin, or hold **MANAGE**, **USE SCHEMA**, or **ALL PRIVILEGES** on the catalog.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: MANAGE / USE SCHEMA / ALL PRIVILEGES only makes the listing complete. Grants/Grant/Revoke still need ownership or MANAGE on the schema or catalog (schemaGrantsAreTrustworthy, and ALL_PRIVILEGES is excluded on purpose). So someone who follows this with just USE SCHEMA gets the schemas listed, and then Grants fails with PermissionDenied. Maybe add a line about what reading schema privileges needs, like the catalog section has?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

8 participants