Skip to content

[CXH-2720] - Add Unity Catalog client, access-path routing, and metastore sync - Databricks Connector - #70

Merged
mateoHernandez123 merged 38 commits into
stagingfrom
mateoHernandez123/unity-catalog-foundation-and-metastore
Oct 28, 2026
Merged

mateoHernandez123 merged 38 commits into
stagingfrom
mateoHernandez123/unity-catalog-foundation-and-metastore

Conversation

@mateoHernandez123

@mateoHernandez123 mateoHernandez123 commented Oct 9, 2026 •

Copy link
Copy Markdown

Description

  • Bug fix
  • New feature

First of four stacked PRs for Unity Catalog (CXH-2720). A metastore lives on the Databricks account plane. Every securable under it (catalog, schema, table, volume) only answers on a workspace host that the metastore is assigned to. This PR adds the Unity Catalog client, that host routing, and the metastore resource.

flowchart TD
  account[account]
  metastore[metastore]
  catalog[catalog]
  schema[schema]
  table[table]
  volume[volume]
  account --> metastore --> catalog --> schema
  schema --> table
  schema --> volume
  style metastore fill:#1f6feb,color:#fff,stroke:#1f6feb
Loading

This PR delivers the blue node. Later PRs add catalog, schema, table and volume.

Order PR Delivers Base
1 #70 Client, routing, metastore main
2 #71 Catalog + scoping config #70
3 #72 Schema #71
4 #73 Table and volume #72

Sync:

  • Metastores (metastore, NEW, opt-in) — listed from GET /api/2.0/accounts/{account_id}/metastores, keyed by metastore id. One entitlement per metastore privilege, plus a read-only owner.
  • A metastore syncs only when a workspace in the sync scope is attached to it. A metastore with no workspace attached, or with every attached workspace left out by workspaces or databricks-exclude-workspaces, is not synced.
  • The connector does not guess when the answer is unknown. If a metastore's workspace assignment list cannot be read (401, 403, or 404), the metastore is kept and the sync fails with FailedPrecondition; the error names the assignment list. PROVISIONING, NOT_PROVISIONED, and CANCELLING stay pending and fail the same way, with the status in the message. CANCELLING is still in process, so pruning it would delete the metastore before the cancellation finishes. FAILED, BANNED, and a status Databricks has not listed are unusable: a metastore whose only workspace is in that state is not synced, so the sync does not fail on every run.
  • A workspaces allowlist that matches no workspace fails the whole sync, including users, groups, service principals, and workspaces. The error separates a name that is also in databricks-exclude-workspaces from a name that is not on the account.
  • A permissions 404 is an absence only when the body carries METASTORE_DOES_NOT_EXIST and the account no longer lists the metastore. Databricks also uses that code when the workspace is not assigned to it. On the first page Grants rebuilds the routing: it returns no grants when the metastore is gone or has left the sync scope, and retries once when the account now names another workspace. That retry is kept only when the new workspace answers in a single page, because a later page resolves its workspace from the profile again; a reassignment whose grants span pages fails, and the next sync re-routes from List. Every other case answers FailedPrecondition. Past the first page it does not re-route, because a page token belongs to the workspace that minted it and ending the pages early would report the grants already emitted as the complete set. Any other 404 is FailedPrecondition too, so the SDK does not skip the page and ConductorOne does not delete the bindings.
  • Existing types — unchanged.

Provisioning:

  • Grant/Revoke metastore privileges — PATCH /api/2.1/unity-catalog/permissions/metastore/{metastore_id}. Databricks no-ops a privilege that is already held or already absent, so the connector confirms the change from the echoed assignment list (GrantAlreadyExists / GrantAlreadyRevoked).
  • Grant and Revoke always rebuild the routing before they write:
    • Metastore deleted from the account: Revoke returns GrantAlreadyRevoked, Grant returns NotFound.
    • Metastore outside the sync scope: both return FailedPrecondition and send no PATCH.
  • Owner is read-only. MANAGE and ALL_PRIVILEGES are rejected on a metastore.
  • Existing provisioning — unchanged.

Auth:

Unchanged account-level OAuth service principal. No new required flag. To read a metastore's grants, the service principal must own the metastore or be assigned Admin directly on a workspace attached to it. Admin inherited through a group is not detected.

Architecture highlights:

  • Routing is one GET /api/2.0/accounts/{account_id}/metastores/{metastore_id}/workspaces call per metastore. A workspace that no list names is not on Unity Catalog. A 401, 403 or 404 on a list marks that metastore unknown, so it is never pruned.
  • Workspace choice: List records the first attached workspace as a hint and does not probe it, so one workspace refusing cannot fail the whole sync. Grants reads that hint back, confirms its access, and rebuilds the routing when the workspace is missing, out of scope, or fails the visibility probe; it then tries each attached workspace and reads through the first one where the service principal owns the metastore or holds Admin directly.
  • Principals resolve per id: account GetUser, then a filtered group list (id eq, count 1), then GetServicePrincipal. Account GET /Groups/{id} is not used, because it returns members. Find* by name runs only when an assignment has no principal_id. The connector does not walk all of account SCIM. SCIM filter values are quoted (RFC 7644).
  • Shared provisioning: resolveSecurableChange and matchHeldPrivilege on securableDeps hold the Grant/Revoke logic. The later PRs reuse them.
  • Syncer gating: every builder is always registered, so metastore stays in capabilities and C1 can show its opt-in toggle. The metastore builder receives explicit booleans for the principal types it may emit grants to. This is the same shape as baton-grafana.
  • HTTP cache: the cache key includes the workspace host. Provisioning pre-reads bypass the cache. After a PATCH, page one is re-read past the cache, so a later sync in the same process sees the new state. A failed refresh is logged and does not fail the write. If that read stops early, later pages stay stale until the next cycle. max_results is 0, so that path is rare.
  • Errors: the vendor error_code is kept on API errors, so *_DOES_NOT_EXIST is distinct from a route 404.
flowchart LR
  ms[ListMetastores] --> assign[ListMetastoreWorkspaces<br/>per metastore]
  ws[ListWorkspaces<br/>allowlist / exclude / RUNNING] --> assign
  assign --> q{usable workspace?}
  q -->|yes| probe[try each workspace<br/>until owner or Admin]
  q -->|none attached / out of scope / FAILED or BANNED| pruned[not synced]
  q -->|list unreadable or PROVISIONING or NOT_PROVISIONED| fail[FailedPrecondition]
  probe --> perms[permissions on that workspace host]
Loading
flowchart TD
  grants[Grants page] --> perms[ListPermissions]
  perms --> each{each assignment}
  each -->|has principal_id| byid[GetUser / group list id eq / GetServicePrincipal]
  each -->|name only| byname[Find* by name]
  perms --> owner[owner from profile] --> byname
  byid --> emit[emit grants]
  byname --> emit
Loading

Useful links:

@linear-code

linear-code Bot commented Oct 9, 2026

Copy link
Copy Markdown

CXH-2720

@github-actions

github-actions Bot commented Oct 9, 2026 •

Copy link
Copy Markdown
Contributor
Superseded — see the current review report for commit e894653eaa93

Connector PR Review: [CXH-2720] - Add Unity Catalog client, access-path routing, and metastore sync - Databricks Connector

Blocking Issues: 1 | Suggestions: 3 | Threads Resolved: 0
Criteria: Criteria status: loaded .claude/skills/ci-review.md from trusted base eaa6e0dcf565.
Review mode: full
View review run

Review Summary

This PR adds a Unity Catalog client with a bounded cursor walker (ctx checks, 1000-page cap, repeated-token detection), a host-valued cache-key header so responses aren't shared across workspace hosts, metastore → workspace routing that falls back from assignment lookups to catalog listings, and a metastore resource type marked OptInRequired. That type has static privilege entitlements, a read-only owner entitlement, grants that are refused unless the principal is metastore owner or workspace admin, and Grant/Revoke that confirm the change from the PATCH echo. I scanned the full diff for security and correctness and found no injection, secret or TLS issues. permissionsUrl rejects names that don't survive URL assembly. The trusted repo-local criteria were applied:

  • S (data loss on empty results): one violation, in metastore Grants().
  • L (log levels): the per-item skips log at Debug, so they're fine.
  • E (error wrapping): grant/revoke errors carry gRPC codes.
  • BP (breaking-change gate): satisfied. The new type is opt-in via OptInRequired and documented in docs/connector.mdx.
  • PR (provisioning): passes. The principal comes from principal.Id and the metastore from entitlement.Resource.Id, and ParentResourceId isn't used.

The 5 interface{} → any files and the vendored tree weren't reviewed in depth.

Security Issues

None found.

Correctness Issues

  • New (high confidence) — pkg/connector/metastore.go:165-171: Grants() returns an empty list with a nil error when the permissions endpoint returns 404. Routing has just confirmed the metastore exists, so a 404 here means the workspace or authorization is wrong, not that the metastore is gone. The result is that C1 revokes every metastore grant (criteria S1/S6). Nothing on ListPermissions establishes that a 404 means the object is gone.

Suggestions

  • New (medium confidence) — pkg/connector/metastore.go:147-157 / pkg/connector/unity_catalog.go:48-55: the visibility probe and all reads/writes use only workspaces[0]. A service principal that is Admin on a different attached workspace still gets a PermissionDenied sync failure, which contradicts the docs.
  • New — pkg/connector/metastore.go has no tests for Grants/Grant/Revoke. The matchHeld principal matching (SCIM id vs. name, "0" ids), the GrantAlreadyExists/GrantAlreadyRevoked paths and owner-grant emission are only covered by the author's live stack run. A regression in the ID-vs-name fallback could report a grant as already held when it isn't, and no test would catch it. The matchHeld closure is also duplicated verbatim between Grant and Revoke.
  • New — pkg/connector/connector.go:54: the metadata description advertises catalogs, schemas, tables, views and volumes, but this PR (1 of 4) only syncs metastores.
Prompt for AI agents
Verify each finding against the current code and only fix it if needed.

## Correctness Issues

In `pkg/connector/metastore.go`:
- Around line 165-171: In metastoreBuilder.Grants, the `isNotFoundError(err)` branch after ListPermissions returns an empty grant list with a nil error. The metastore was just confirmed to exist via buildRouting, so a 404 here means the wrong workspace host or an authorization failure, and returning empty makes C1 revoke every metastore grant. Remove the NotFound special case and return the wrapped error, as the other error branch does. Only keep a skip if a doc comment on databricks.Client.ListPermissions establishes that Databricks returns 404 only for a deleted securable.

## Suggestions

In `pkg/connector/metastore.go` / `pkg/connector/unity_catalog.go`:
- Around metastore.go:133-157 and unity_catalog.go:48-55: workspaceFromSnapshot always picks workspaces[0], and metastoreGrantsAreTrustworthy only probes workspace admin on that host. Iterate usableWorkspaces(snap, metastoreID) and pick the first workspace that passes the probe (owner check first, then isWorkspaceAdmin per workspace). Use that workspace for ListPermissions/UpdatePermissions, and pick it deterministically so a page token stays bound to one host.

In `pkg/connector/metastore.go`:
- Add table-driven tests for Grants (owner grant on the first page only, unresolved principals skipped, unknown privileges skipped, PermissionDenied when not trustworthy), Grant (GrantAlreadyExists, echo-confirmed success, Internal when the echo lacks the privilege, owner/legacy privileges rejected) and Revoke (GrantAlreadyRevoked, removal by principal_id vs. name). Extract the duplicated matchHeld closure into one shared helper and test it directly.

In `pkg/connector/connector.go`:
- Around line 54: Limit the Metadata description to metastores in this PR and add catalogs/schemas/tables/views/volumes in the stacked PRs that implement them.

Comment thread pkg/connector/metastore.go Resolved
Comment thread pkg/connector/metastore.go Resolved
Comment thread pkg/connector/connector.go Resolved
@github-actions

github-actions Bot commented Oct 9, 2026 •

Copy link
Copy Markdown
Contributor
Superseded — see the current review report for commit 019c458ac2b1

Connector PR Review: [CXH-2720] - Add Unity Catalog client, access-path routing, and metastore sync - Databricks Connector

Blocking Issues: 1 | Suggestions: 3 | Threads Resolved: 0
Criteria: Criteria status: loaded .claude/skills/ci-review.md from trusted base eaa6e0dcf565.
Review mode: full
View review run

Review Summary

This PR adds a Unity Catalog client with a bounded cursor walker (ctx checks, 1000-page cap, repeated-token detection), a host-valued cache-key header so responses aren't shared across workspace hosts, metastore → workspace routing that falls back from assignment lookups to catalog listings, and a metastore resource type marked OptInRequired. That type has static privilege entitlements, a read-only owner entitlement, grants that are refused unless the principal is metastore owner or workspace admin, and Grant/Revoke that confirm the change from the PATCH echo. I scanned the full diff for security and correctness and found no injection, secret or TLS issues. permissionsUrl rejects names that don't survive URL assembly. The trusted repo-local criteria were applied:

  • S (data loss on empty results): one violation, in metastore Grants().
  • L (log levels): the per-item skips log at Debug, so they're fine.
  • E (error wrapping): grant/revoke errors carry gRPC codes.
  • BP (breaking-change gate): satisfied. The new type is opt-in via OptInRequired and documented in docs/connector.mdx.
  • PR (provisioning): passes. The principal comes from principal.Id and the metastore from entitlement.Resource.Id, and ParentResourceId isn't used.

The 5 interface{} → any files and the vendored tree weren't reviewed in depth.

Security Issues

None found.

Correctness Issues

  • New (high confidence) — pkg/connector/metastore.go:165-171: Grants() returns an empty list with a nil error when the permissions endpoint returns 404. Routing has just confirmed the metastore exists, so a 404 here means the workspace or authorization is wrong, not that the metastore is gone. The result is that C1 revokes every metastore grant (criteria S1/S6). Nothing on ListPermissions establishes that a 404 means the object is gone.

Suggestions

  • New (medium confidence) — pkg/connector/metastore.go:147-157 / pkg/connector/unity_catalog.go:48-55: the visibility probe and all reads/writes use only workspaces[0]. A service principal that is Admin on a different attached workspace still gets a PermissionDenied sync failure, which contradicts the docs.
  • New — pkg/connector/metastore.go has no tests for Grants/Grant/Revoke. The matchHeld principal matching (SCIM id vs. name, "0" ids), the GrantAlreadyExists/GrantAlreadyRevoked paths and owner-grant emission are only covered by the author's live stack run. A regression in the ID-vs-name fallback could report a grant as already held when it isn't, and no test would catch it. The matchHeld closure is also duplicated verbatim between Grant and Revoke.
  • New — pkg/connector/connector.go:54: the metadata description advertises catalogs, schemas, tables, views and volumes, but this PR (1 of 4) only syncs metastores.
Prompt for AI agents
Verify each finding against the current code and only fix it if needed.

## Correctness Issues

In `pkg/connector/metastore.go`:
- Around line 165-171: In metastoreBuilder.Grants, the `isNotFoundError(err)` branch after ListPermissions returns an empty grant list with a nil error. The metastore was just confirmed to exist via buildRouting, so a 404 here means the wrong workspace host or an authorization failure, and returning empty makes C1 revoke every metastore grant. Remove the NotFound special case and return the wrapped error, as the other error branch does. Only keep a skip if a doc comment on databricks.Client.ListPermissions establishes that Databricks returns 404 only for a deleted securable.

## Suggestions

In `pkg/connector/metastore.go` / `pkg/connector/unity_catalog.go`:
- Around metastore.go:133-157 and unity_catalog.go:48-55: workspaceFromSnapshot always picks workspaces[0], and metastoreGrantsAreTrustworthy only probes workspace admin on that host. Iterate usableWorkspaces(snap, metastoreID) and pick the first workspace that passes the probe (owner check first, then isWorkspaceAdmin per workspace). Use that workspace for ListPermissions/UpdatePermissions, and pick it deterministically so a page token stays bound to one host.

In `pkg/connector/metastore.go`:
- Add table-driven tests for Grants (owner grant on the first page only, unresolved principals skipped, unknown privileges skipped, PermissionDenied when not trustworthy), Grant (GrantAlreadyExists, echo-confirmed success, Internal when the echo lacks the privilege, owner/legacy privileges rejected) and Revoke (GrantAlreadyRevoked, removal by principal_id vs. name). Extract the duplicated matchHeld closure into one shared helper and test it directly.

In `pkg/connector/connector.go`:
- Around line 54: Limit the Metadata description to metastores in this PR and add catalogs/schemas/tables/views/volumes in the stacked PRs that implement them.

Reviewed commit: e894653eaa93

@github-actions github-actions Bot 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.

Blocking issues found — see the full review report

@mateoHernandez123
mateoHernandez123 force-pushed the mateoHernandez123/unity-catalog-foundation-and-metastore branch from e894653 to 019c458 Compare October 9, 2026 13:20
@github-actions

github-actions Bot commented Oct 9, 2026 •

Copy link
Copy Markdown
Contributor
Superseded — see the current review report for commit 019c458ac2b1

Connector PR Review: [CXH-2720] - Add Unity Catalog client, access-path routing, and metastore sync - Databricks Connector

Blocking Issues: 0 | Suggestions: 2 | Threads Resolved: 0
Criteria: Criteria status: loaded .claude/skills/ci-review.md from trusted base eaa6e0dcf565.
Review mode: full
View review run

Review Summary

This PR adds three things:

  • A Unity Catalog client with a self-driven, bounded cursor walker. It handles metastores, workspace metastore assignments, catalogs, and paged GET/PATCH permissions.
  • Per-call routing from a metastore to the running workspaces attached to it (pkg/connector/unity_catalog.go).
  • An opt-in metastore resource type with static privilege entitlements, a read-only owner entitlement, and grants. Grant/Revoke confirm the change from the paginated PATCH echo.

The uhttp cache key is now scoped to the host: prepareRequest sets Host on every request, and client.go:105 passes WithCacheKeyHeaders. I checked this against the vendored CreateCacheKey, and it does fold the opted-in header into the key.

I scanned the full diff for security and correctness and found no security issues. go.mod and go.sum are unchanged.

I applied the repo-local criteria:

  • S (sync data loss): metastore Grants() now returns every error, including 404, instead of returning an empty page. The only empty return is for a metastore that ListMetastores no longer lists.
  • L (log levels): the per-item skips (unresolved principal, unknown privilege) log at Debug and are not error skips.
  • BP (breaking-change gate): the new type is opt-in through OptInRequired and is documented in docs/connector.mdx.
  • PR (provisioning): the principal comes from principal.Id, the metastore from entitlement.Resource.Id, and ParentResourceId is not used. Grant/Revoke return gRPC codes.
  • E (error codes): the 403 path wraps with PermissionDenied.
  • T (tracing): no manual spans.

Security Issues

None found.

Correctness Issues

None found.

Suggestions

  • Prior — still present (medium confidence), pkg/connector/unity_catalog.go:54 and pkg/connector/metastore.go:147:
    • Metastore reads, writes and the visibility probe use only workspaces[0].
    • holdsWorkspaceAdmin (grant_visibility.go:74-107) only matches a direct ADMIN assignment on that one workspace.
    • A principal that is Admin on another attached workspace, or Admin through a group, gets a PermissionDenied failure in metastore Grants(). The docs (connector.mdx:109,115) say workspace Admin is enough.
  • Prior — still present, pkg/connector/metastore.go:350 / :511:
    • There are no tests for metastore Grants/Grant/Revoke. matchHeld (SCIM-id vs. name fallback, "0" ids) is duplicated verbatim in Grant and Revoke.
    • If that matching regressed, Grant could return GrantAlreadyExists or Revoke could return GrantAlreadyRevoked wrongly, and no test would catch it. The connector-package tests only cover the routing cache and principal lookup.

Resolved prior findings

  • Fixed: the metadata description advertised catalogs, schemas, tables, views and volumes. pkg/connector/connector.go:53-54 now says only "Unity Catalog metastores and the privileges granted on them". This was the outdated connector.go thread.
  • Fixed: metastore Grants() returned an empty result on a 404 from the permissions endpoint. pkg/connector/metastore.go:164-170 now returns every ListPermissions error wrapped.
Prompt for AI agents
Verify each finding against the current code and only fix it if needed.

## Suggestions

In `pkg/connector/unity_catalog.go`:
- Around line 48-55: workspaceFromSnapshot always returns workspaces[0], and metastore Grants/Grant/Revoke (pkg/connector/metastore.go ~147, ~320, ~487) probe admin visibility only on that workspace. Iterate the usable workspaces from usableWorkspaces() and pick the first one where metastoreGrantsAreTrustworthy returns true. Return PermissionDenied only when none qualify. Optionally, make holdsWorkspaceAdmin (pkg/connector/grant_visibility.go:74-107) recognise ADMIN granted through a group the connector principal belongs to, or state in docs/connector.mdx that the ADMIN assignment must be direct.

In `pkg/connector/metastore.go`:
- Around lines 350-378 and 511-539: the matchHeld closure is duplicated in Grant and Revoke. Extract it into a single helper that takes (assignments, privilege, scimID, principalName). Add table-driven tests covering: a SCIM id match, a name-only match with id "" or "0", a name match with a different non-zero id (must not match), and no match. Also add tests that Grants emits the owner grant with GrantImmutable on the first page only, and that Grant/Revoke return GrantAlreadyExists/GrantAlreadyRevoked from the pre-read and Internal when the PATCH echo disagrees.

@mateoHernandez123
mateoHernandez123 dismissed github-actions[bot]’s stale review October 9, 2026 13:22

Triaged all three findings. The 404 swallow in Grants() was real and is fixed in 019c458 across all five securable levels, and the metadata description is now scoped per branch in the same commit; both threads are resolved. The workspaces[0] probe is a real robustness gap but it is deferred with evidence on the thread: the documented setup makes the first workspace always eligible, the failure is a loud PermissionDenied rather than a silent revoke, and the fix changes access-path selection, which is the one part of this stack validated live and cannot be re-validated while the Databricks account is out of credits. Dismissing the blocking verdict per team convention; the bot does not approve, so it would otherwise block merge indefinitely.

Comment thread pkg/connector/unity_catalog.go Resolved
Comment thread pkg/connector/metastore.go Resolved

@github-actions github-actions Bot 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.

Blocking issues found — see the full review report

@mateoHernandez123
mateoHernandez123 dismissed github-actions[bot]’s stale review October 26, 2026 13:58

Declining. Routing stays the first running in-scope workspace. Admin is checked only there, and the sync fails closed otherwise.

Comment thread pkg/connector/metastore_helpers.go Resolved
Comment thread pkg/connector/metastore.go Resolved
Comment thread pkg/connector/metastore.go Resolved
Comment thread pkg/connector/metastore.go Resolved
mateoHernandez123 and others added 2 commits October 27, 2026 09:57
A partial permissions read looks like an empty grant list, so List and
provisioning pick the first running in-scope workspace where the connector
owns the metastore or is a direct Admin.

Co-authored-by: Cursor <[email protected]>
Keep the quoted SCIM filters and route group reads through GetUncached.
Account group writes now patch, so the full-replacement displayName guard is gone.

Co-authored-by: Cursor <[email protected]>
@github-actions

github-actions Bot commented Oct 27, 2026 •

Copy link
Copy Markdown
Contributor
Superseded — see the current review report for commit 69528c19140e

Connector PR Review: [CXH-2720] - Add Unity Catalog client, access-path routing, and metastore sync - Databricks Connector

Blocking Issues: 0 | Suggestions: 5 | Threads Resolved: 0
Criteria: Criteria status: loaded .claude/skills/ci-review.md from trusted base 03a4e94d0505.
Review mode: full
View review run

Review Summary

The PR adds a Unity Catalog client with host-scoped caching and an opt-in metastore resource (OptInRequired, static entitlements). List routes each metastore to a running in-scope workspace where the connector owns the metastore or is a direct Admin, and fails the sync instead of skipping when it cannot confirm that. Grants reads only through that profiled route, and Grant and Revoke re-route from the account plane. It also adds a Validate check that the workspaces allowlist matches at least one workspace. I scanned the full PR diff for security and correctness. I applied the trusted criteria, in particular S1–S6 (empty-result data loss), L-rules (log levels), E2 (gRPC codes), and P/PR (provisioning entity sources), to metastore List/Grants/Grant/Revoke. Every error path in a resource-producing method returns the error. The only per-item continues skip an out-of-scope metastore or a principal that does not resolve, neither of which is an S5 error skip. I found no security issues and no blocking correctness issues. The latest commit is a merge from main.

Security Issues

None found.

Correctness Issues

None found.

Suggestions

  • Prior — still present (high confidence): the PR body says Grants rebuilds the routing on METASTORE_DOES_NOT_EXIST, returns no grants for a metastore that is gone, and retries once on a reassigned workspace. The code does none of this. pkg/connector/metastore.go:159-164 maps every permissions error to FailedPrecondition, and APIError.ErrorCode (pkg/databricks/request.go:317) is never read. The code is the safe behavior, but the description does not match it.
  • Prior — still present: test gaps in pkg/connector/metastore_test.go. No test asserts that a failing ListPermissions (404/500) makes Grants return FailedPrecondition, which is the S1 guard. No test covers GrantAlreadyExists. The host-scoped cache key is not tested across two workspaces.
  • Prior — still present (medium confidence): pkg/databricks/client.go:463-467. FindGroupDisplayName returns err (always nil there) on the empty branch, and returns groups[0].DisplayName without checking that the id equals groupID. principalResolver.lookupID relies on it to classify an id as a group. These lines predate the PR, so I did not post an inline comment.
  • Prior — still present: .github/workflows/ci.yaml:14-16. With one global concurrency group and cancel-in-progress: false, only one run can wait in the queue. A third run cancels the one already waiting, so a PR's CI can be silently dropped.
  • Prior — still present (efficiency): principalResolver (pkg/connector/metastore_helpers.go:489-550) makes 1–3 serial SCIM calls per principal, and its cache lasts for one Grants call. routeMetastore lists workspace assignments for each candidate workspace on every metastore in List and on every Grant/Revoke (metastore_helpers.go:279-287).

Resolved prior findings

  • Fixed: routing no longer takes only the first workspace. routeMetastore checks owner, then direct Admin on every running in-scope candidate, and returns PermissionDenied if none qualifies (pkg/connector/metastore_helpers.go:275-289). Grants no longer needs a separate trust check because it reads only through that route. This also covers the earlier "visibility check removed" thread.
  • Fixed: an empty profile workspace now makes Grants return FailedPrecondition instead of a nil error with no grants (pkg/connector/metastore.go:150-153).
  • Fixed: FAILED/BANNED/unlisted workspace statuses are dropped from scope by workspaceIsUsable (metastore_helpers.go:215-234). Only PROVISIONING/NOT_PROVISIONED/CANCELLING keep a metastore pending (metastore.go:104-107).
  • Fixed: an assignment-list 404 is returned, not skipped (metastore.go:98-102, metastore_helpers.go:250-256).
  • Fixed: the README pending-workspace and standing rows, the shared matchHeldPrivilege (metastore_helpers.go:106), and the connector description, which now names only metastores (pkg/connector/connector.go:61-62). A successful Grant PATCH is now tested (metastore_test.go:531-555).
  • Obsolete: the docs-info endpoint row, the unity_catalog.go pruning in the connector (file removed), and HasErrorCode/IsSecurableGone with its comment (removed from request.go).
Prompt for AI agents
Verify each finding against the current code and only fix it if needed.

## Suggestions

In the PR description (and any docs repeating it):
- Remove or rewrite the claims that Grants rebuilds routing on METASTORE_DOES_NOT_EXIST, returns no grants for a gone metastore, and retries once on a reassigned workspace. pkg/connector/metastore.go:159-164 maps every ListPermissions error to FailedPrecondition and APIError.ErrorCode is unused. Describe the actual behavior.

In `pkg/connector/metastore_test.go`:
- Around line 485: add a test where the fake ListPermissions returns 404 and 500 and assert Grants returns codes.FailedPrecondition with no grants. Add a Grant test where the pre-read already contains the privilege for the principal, and assert the GrantAlreadyExists annotation is set and no PATCH is sent.

In `pkg/databricks/permissions_cache_test.go`:
- Add a test with two workspace hosts serving the same permissions path, and assert their cached responses do not collide (the cacheScopeHeader "Host" key).

In `pkg/databricks/client.go`:
- Around line 463-467: return nil explicitly in the empty branch, and only return groups[0].DisplayName when groups[0].ID == groupID. Otherwise return "".

In `.github/workflows/ci.yaml`:
- Around line 14-16: a single global concurrency group with cancel-in-progress false keeps only one pending run, so a third run cancels the waiting one. Accept and document this, or serialize the tenant-mutating step another way.

In `pkg/connector/metastore_helpers.go`:
- Around lines 279-287 and 489-550: consider caching the workspace Admin determination and principal resolutions across Grants pages and metastores, for example on the builder with a sync-scoped cache, to cut serial SCIM and assignment-list calls.

Comment thread pkg/connector/metastore.go Resolved
Comment thread pkg/connector/metastore_test.go Resolved
@github-actions

github-actions Bot commented Oct 27, 2026 •

Copy link
Copy Markdown
Contributor

Connector PR Review: [CXH-2720] - Add Unity Catalog client, access-path routing, and metastore sync - Databricks Connector

Blocking Issues: 0 | Suggestions: 5 | Threads Resolved: 0
Criteria: Criteria status: loaded .claude/skills/ci-review.md from trusted base 03a4e94d0505.
Review mode: full
View review run

Review Summary

The PR adds a Unity Catalog client with host-scoped caching and an opt-in metastore resource (OptInRequired, static entitlements). List routes each metastore to a running in-scope workspace where the connector owns the metastore or is a direct Admin, and fails the sync instead of skipping when it cannot confirm that. Grants reads only through that profiled route, and Grant and Revoke re-route from the account plane. It also adds a Validate check that the workspaces allowlist matches at least one workspace. I scanned the full PR diff for security and correctness. I applied the trusted criteria, in particular S1–S6 (empty-result data loss), L-rules (log levels), E2 (gRPC codes), and P/PR (provisioning entity sources), to metastore List/Grants/Grant/Revoke. Every error path in a resource-producing method returns the error. The only per-item continues skip an out-of-scope metastore or a principal that does not resolve, neither of which is an S5 error skip. I found no security issues and no blocking correctness issues. The latest commit is a merge from main.

Security Issues

None found.

Correctness Issues

None found.

Suggestions

  • Prior — still present (high confidence): the PR body says Grants rebuilds the routing on METASTORE_DOES_NOT_EXIST, returns no grants for a metastore that is gone, and retries once on a reassigned workspace. The code does none of this. pkg/connector/metastore.go:159-164 maps every permissions error to FailedPrecondition, and APIError.ErrorCode (pkg/databricks/request.go:317) is never read. The code is the safe behavior, but the description does not match it.
  • Prior — still present: test gaps in pkg/connector/metastore_test.go. No test asserts that a failing ListPermissions (404/500) makes Grants return FailedPrecondition, which is the S1 guard. No test covers GrantAlreadyExists. The host-scoped cache key is not tested across two workspaces.
  • Prior — still present (medium confidence): pkg/databricks/client.go:463-467. FindGroupDisplayName returns err (always nil there) on the empty branch, and returns groups[0].DisplayName without checking that the id equals groupID. principalResolver.lookupID relies on it to classify an id as a group. These lines predate the PR, so I did not post an inline comment.
  • Prior — still present: .github/workflows/ci.yaml:14-16. With one global concurrency group and cancel-in-progress: false, only one run can wait in the queue. A third run cancels the one already waiting, so a PR's CI can be silently dropped.
  • Prior — still present (efficiency): principalResolver (pkg/connector/metastore_helpers.go:489-550) makes 1–3 serial SCIM calls per principal, and its cache lasts for one Grants call. routeMetastore lists workspace assignments for each candidate workspace on every metastore in List and on every Grant/Revoke (metastore_helpers.go:279-287).

Resolved prior findings

  • Fixed: routing no longer takes only the first workspace. routeMetastore checks owner, then direct Admin on every running in-scope candidate, and returns PermissionDenied if none qualifies (pkg/connector/metastore_helpers.go:275-289). Grants no longer needs a separate trust check because it reads only through that route. This also covers the earlier "visibility check removed" thread.
  • Fixed: an empty profile workspace now makes Grants return FailedPrecondition instead of a nil error with no grants (pkg/connector/metastore.go:150-153).
  • Fixed: FAILED/BANNED/unlisted workspace statuses are dropped from scope by workspaceIsUsable (metastore_helpers.go:215-234). Only PROVISIONING/NOT_PROVISIONED/CANCELLING keep a metastore pending (metastore.go:104-107).
  • Fixed: an assignment-list 404 is returned, not skipped (metastore.go:98-102, metastore_helpers.go:250-256).
  • Fixed: the README pending-workspace and standing rows, the shared matchHeldPrivilege (metastore_helpers.go:106), and the connector description, which now names only metastores (pkg/connector/connector.go:61-62). A successful Grant PATCH is now tested (metastore_test.go:531-555).
  • Obsolete: the docs-info endpoint row, the unity_catalog.go pruning in the connector (file removed), and HasErrorCode/IsSecurableGone with its comment (removed from request.go).
Prompt for AI agents
Verify each finding against the current code and only fix it if needed.

## Suggestions

In the PR description (and any docs repeating it):
- Remove or rewrite the claims that Grants rebuilds routing on METASTORE_DOES_NOT_EXIST, returns no grants for a gone metastore, and retries once on a reassigned workspace. pkg/connector/metastore.go:159-164 maps every ListPermissions error to FailedPrecondition and APIError.ErrorCode is unused. Describe the actual behavior.

In `pkg/connector/metastore_test.go`:
- Around line 485: add a test where the fake ListPermissions returns 404 and 500 and assert Grants returns codes.FailedPrecondition with no grants. Add a Grant test where the pre-read already contains the privilege for the principal, and assert the GrantAlreadyExists annotation is set and no PATCH is sent.

In `pkg/databricks/permissions_cache_test.go`:
- Add a test with two workspace hosts serving the same permissions path, and assert their cached responses do not collide (the cacheScopeHeader "Host" key).

In `pkg/databricks/client.go`:
- Around line 463-467: return nil explicitly in the empty branch, and only return groups[0].DisplayName when groups[0].ID == groupID. Otherwise return "".

In `.github/workflows/ci.yaml`:
- Around line 14-16: a single global concurrency group with cancel-in-progress false keeps only one pending run, so a third run cancels the waiting one. Accept and document this, or serialize the tenant-mutating step another way.

In `pkg/connector/metastore_helpers.go`:
- Around lines 279-287 and 489-550: consider caching the workspace Admin determination and principal resolutions across Grants pages and metastores, for example on the builder with a sync-scoped cache, to cut serial SCIM and assignment-list calls.

Reviewed commit: 69528c19140e

@github-actions github-actions Bot 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.

No blocking issues found — see the full review report

@mateoHernandez123
mateoHernandez123 changed the base branch from main to staging October 27, 2026 13:17
mateoHernandez123 and others added 2 commits October 27, 2026 12:05
…unity-catalog-foundation-and-metastore

Co-authored-by: Cursor <[email protected]>

# Conflicts:
#	pkg/connector/connector.go
#	pkg/connector/roles.go
#	pkg/databricks/request.go

@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.

Round 4: all the round-3 items check out (repro'd on dd1a5ac). Two mediums left, both about re-coding errors: a List 404 still escapes as NotFound, and Grants now hides 429/5xx as FailedPrecondition. Inline.

One for the body (out of scope for the inline stuff): Validate now hard-fails when the workspaces allowlist matches nothing (connector.go:153). That's what sergio/luisina asked for and the README covers it, but it's a behavior change from v0.2.5 (which synced account users/groups/roles with an empty workspace sync), and it also hits tenants that never enabled metastores (opt-in). Worth a line in connector.mdx (the Workspaces field) and the release notes. The PR body still describes the old behavior, and the "sync will be empty" Warn at workspaces.go:104 can't be reached anymore.

Comment thread pkg/connector/metastore.go Resolved
Comment thread pkg/connector/metastore.go Resolved
Comment thread pkg/databricks/client.go Resolved
Comment thread pkg/connector/metastore.go Resolved
Comment thread pkg/connector/metastore.go Resolved
Comment thread README.md Resolved
Comment thread README.md 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/first pass) — 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/metastore.go Resolved
Comment thread pkg/connector/connector.go Resolved
A 404 on the metastore's workspace list, on the account's workspace list,
or on ListMetastores reached the SDK as codes.NotFound. The metastore List
is a typed child of the account, so the SDK runs it with allowWarning,
reads that as "no metastores" and ends the sync successfully with none.
ConductorOne then deletes every metastore and its grants.

Only the not-found case is re-coded to FailedPrecondition. Wrapping every
failure that way, as metastore Grants did, turned a 429 or a 5xx into a
code the SDK does not retry, so one throttled permissions page failed the
whole sync instead of backing off.

Measured against a live tenant through a proxy that injects the failure:
an injected 404 on the assignment list used to exit 0 with zero metastores
and now fails the sync, and an injected 429 on the permissions read used
to stop after one call and now retries with backoff.

Validate no longer reports a workspace that databricks-exclude-workspaces
removed as absent from the account, since ListWorkspaces drops those before
the check sees them.

Co-authored-by: Cursor <[email protected]>
Comment thread README.md Resolved
Comment thread README.md Resolved
Comment thread pkg/connector/connector.go Resolved
Comment thread pkg/connector/metastore.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.

Round 5: M1/M2 check out. I repro'd on e76e8e3 (unit + a real-SDK e2e): a 404 on the assignment list, /metastores or /workspaces now fails the sync instead of emptying it, and 429/503 come through as Unavailable and get retried. Nice. Two leftover NotFound paths are inline (the SCIM lookups), plus a few nits.

For the body, out of scope for inline:

  • PR body is stale. It still says 401/403/404 on the assignment list give FailedPrecondition (only 404 does now; 401/403 are Unauthenticated/PermissionDenied). It says the Validate error "separates a name that is also in databricks-exclude-workspaces" (it's one message, and the case can't happen because of the mutual exclusion). And it says the post-PATCH re-read makes "a later sync see the new state", which isn't true with Cache-Control in the key. It also doesn't mention the Validate change from v0.2.5 or that 429/5xx now retry.
  • Still pending from the last round, as promised: prime the cache in TestASyncReadAfterAGrantSeesThePostChangeState, fix the two cache comments, restore the dropped tests (GrantAlreadyExists, owner GrantImmutable, emitPrincipal, host-scoped cache key), the connector.mdx line for the Validate change, and the release notes.
  • (out of scope, pre-existing): request.go:327 returns the bare parseJSON error when an error body isn't JSON (e.g. a gateway HTML page), so a 404/429/503 loses its status and comes out as Unknown. It fails safe, but behind a proxy a throttle wouldn't be retried. Worth keeping the APIError (status + err) on parse failure at some point.

Comment thread pkg/connector/metastore_helpers.go Resolved
Comment thread pkg/connector/metastore.go Resolved
Comment thread pkg/connector/connector.go Resolved
Comment thread pkg/connector/metastore_helpers.go Resolved
Comment thread pkg/connector/metastore_helpers.go Resolved
@mateoHernandez123
mateoHernandez123 merged commit 4b95690 into staging Oct 28, 2026
7 checks passed
@mateoHernandez123
mateoHernandez123 deleted the mateoHernandez123/unity-catalog-foundation-and-metastore branch October 28, 2026 19:53
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.

9 participants