From cee17e6786bd68dfdf76cf48764c330e111de0b0 Mon Sep 17 00:00:00 2001 From: eric-wang-1990 Date: Fri, 21 Aug 2026 10:07:12 -0700 Subject: [PATCH 1/3] =?UTF-8?q?fix(kernel):=20Azure=20OAuth=20U2M=20?= =?UTF-8?q?=E2=80=94=20forward=20the=20in-house=20app/scopes=20uniformly?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit On the kernel backend (WithUseKernel(true)), Azure OAuth U2M failed: the browser opened a malformed AAD authorize URL (login.microsoftonline.com/{tenant}/v1/authorize) and 404'd. Root cause: the kernel runs a single, cloud-blind in-house U2M flow — it does OIDC discovery against {host}/oidc and uses the discovered authorize endpoint verbatim; it has no Azure branching. resolveKernelAuth was forwarding the cloud-inferred Azure Entra-direct app id (via U2MClientID) + user_impersonation scope (via oauth.GetScopes). Handing those Entra-direct values to the in-house flow made the workspace federation route the browser to AAD. Fix (Go-side only): resolveKernelAuth's U2M case now forwards the in-house app (databricks-sql-connector) + sql/offline_access scopes uniformly for every cloud. AWS/GCP is a no-op (already those values); only Azure changes. The Thrift path keeps the cloud-specific values it needs. No kernel change. Verified end-to-end against a live Azure workspace (SELECT 1 authenticated as the interactive user). Updated the two U2M unit tests to the uniform mapping. Co-authored-by: Isaac Signed-off-by: eric-wang-1990 --- CHANGELOG.md | 1 + kernel_auth_real_test.go | 19 +++++++++++++------ kernel_config.go | 38 ++++++++++++++++++++++++++++---------- kernel_config_test.go | 23 +++++++++++++---------- 4 files changed, 55 insertions(+), 26 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8acc5d8f..347b965c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ## Unreleased - Improve telemetry error reporting: driver failures are now categorized by cause instead of reported as a generic error (databricks/databricks-sql-go#414, #415, #417, #419, #424) +- Fix OAuth U2M on the kernel backend (`WithUseKernel(true)`) against **Azure** workspaces: `resolveKernelAuth` now forwards the in-house OAuth app (`databricks-sql-connector`) + `sql offline_access` scopes **uniformly across all clouds**, instead of the Azure Entra-direct app id + `user_impersonation` scope that the Thrift path infers from the host. The kernel runs a single cloud-blind in-house workspace-federated U2M flow (it uses the workspace's OIDC-discovered authorize endpoint verbatim), so feeding it the Entra-direct app made the workspace federation route the browser to a broken AAD authorize URL. AWS/GCP behavior is unchanged (those already resolved to the in-house app + scopes). The Thrift path is unaffected. ## v1.14.0 (2026-07-13) - **Minimum Go version is now 1.25.0** (previously 1.20): the `go` directive was raised to 1.25.0 while clearing OSV-Scanner findings and updating dependencies. Consumers building with an older toolchain will need to upgrade Go (databricks/databricks-sql-go#368) diff --git a/kernel_auth_real_test.go b/kernel_auth_real_test.go index 1e677725..2cd9c7f1 100644 --- a/kernel_auth_real_test.go +++ b/kernel_auth_real_test.go @@ -4,7 +4,6 @@ import ( "reflect" "testing" - "github.com/databricks/databricks-sql-go/auth/oauth" "github.com/databricks/databricks-sql-go/auth/oauth/m2m" "github.com/databricks/databricks-sql-go/auth/oauth/u2m" "github.com/databricks/databricks-sql-go/internal/backend/kernel" @@ -63,12 +62,20 @@ func TestResolveKernelAuthRealAuthenticators(t *testing.T) { "error here means *u2mAuthenticator no longer satisfies kernel.U2MCredentialsProvider "+ "(U2MClientID renamed?): %v", err) } - if got.Mode != kernel.AuthU2M || got.ClientID == "" { - t.Errorf("auth = %+v, want mode=U2M with a non-empty (cloud-inferred) clientID", got) + if got.Mode != kernel.AuthU2M { + t.Errorf("auth = %+v, want mode=U2M", got) } - // Scopes are forwarded from oauth.GetScopes(cfg.Host) for Thrift parity. - if want := oauth.GetScopes(c.Host, nil); !reflect.DeepEqual(got.Scopes, want) { - t.Errorf("U2M scopes = %v, want %v (Thrift parity)", got.Scopes, want) + // The kernel path forwards the in-house bundle UNIFORMLY across clouds — + // databricks-sql-connector + sql/offline_access — NOT the Azure + // Entra-direct app id / user_impersonation scope, even though this is an + // Azure host. The kernel runs one cloud-blind in-house workspace-federated + // U2M flow; feeding it the Entra-direct app breaks it. See + // resolveKernelAuth's U2M case. + if got.ClientID != "databricks-sql-connector" { + t.Errorf("U2M clientID = %q, want databricks-sql-connector (in-house app, cloud-agnostic on the kernel path)", got.ClientID) + } + if want := []string{"sql", "offline_access"}; !reflect.DeepEqual(got.Scopes, want) { + t.Errorf("U2M scopes = %v, want %v (in-house, cloud-agnostic)", got.Scopes, want) } }) } diff --git a/kernel_config.go b/kernel_config.go index 4a2e346e..a69fd72c 100644 --- a/kernel_config.go +++ b/kernel_config.go @@ -8,7 +8,6 @@ import ( "time" "github.com/databricks/databricks-sql-go/auth/noop" - "github.com/databricks/databricks-sql-go/auth/oauth" "github.com/databricks/databricks-sql-go/auth/pat" dbsqlerr "github.com/databricks/databricks-sql-go/errors" "github.com/databricks/databricks-sql-go/internal/backend/kernel" @@ -23,6 +22,14 @@ import ( // Config field being silently dropped — run under CGO_ENABLED=0. The tagged // newKernelBackend calls validateKernelConfig, then assembles the cgo kernel.Config. +// u2mKernelClientID is the in-house Databricks OAuth app the kernel path uses for +// U2M on EVERY cloud. It is the built-in databricks-sql-connector client — the +// AWS/GCP default and the kernel's own U2M default. Deliberately cloud-agnostic: +// the kernel runs one in-house workspace-federated flow (no Azure branching), so +// the kernel path must not send the Azure Entra-direct app id. See +// resolveKernelAuth's U2M case. +const u2mKernelClientID = "databricks-sql-connector" + // validateKernelConfig enforces the kernel backend's "nothing silently ignored" // contract: it rejects every option the kernel path can't yet honor with a clear // error (rather than dropping it, which would behave differently than Thrift) and @@ -310,15 +317,26 @@ func resolveKernelAuth(cfg *config.Config) (kernel.Auth, error) { clientID, clientSecret := a.M2MCredentials() return kernel.Auth{Mode: kernel.AuthM2M, ClientID: clientID, ClientSecret: clientSecret}, nil case kernel.U2MCredentialsProvider: - // Forward the SAME cloud-specific scopes the Thrift path requests via - // oauth.GetScopes (offline_access + sql on AWS/GCP, offline_access + - // /user_impersonation on Azure), so both backends authorize against - // the built-in databricks-sql-connector client identically. Without this the - // kernel applied its own default set (all-apis + offline_access), which a - // workspace whose public client isn't granted all-apis rejects with - // access_denied. RedirectPort is still left zero (no user option; kernel - // default 8020). Passing nil to GetScopes yields the pure cloud-default set. - return kernel.Auth{Mode: kernel.AuthU2M, ClientID: a.U2MClientID(), Scopes: oauth.GetScopes(cfg.Host, nil)}, nil + // The kernel runs a single, cloud-agnostic in-house U2M flow: it does OIDC + // discovery against {host}/oidc and uses that authorize endpoint verbatim — + // it has NO Azure/cloud branching. That in-house workspace-federated flow + // works on every cloud including Azure (the workspace federates the browser + // login to Entra server-side). So forward the in-house app + + // `sql offline_access` scopes UNIFORMLY across clouds; do NOT forward the + // Azure Entra-direct app id / `user_impersonation` scope that + // a.U2MClientID() / oauth.GetScopes pick for an Azure host. Handing those + // Entra-direct values to the kernel's in-house flow makes the workspace + // federation route the browser to AAD and yields a broken authorize URL + // (login.microsoftonline.com/{tenant}/v1/authorize). The Thrift path keeps + // the cloud-specific values (it needs them); only this kernel-path mapping + // is uniform. On AWS/GCP this is a no-op — a.U2MClientID() is already + // databricks-sql-connector and the scopes are already sql+offline_access. + // RedirectPort stays zero (no user option; kernel default). + return kernel.Auth{ + Mode: kernel.AuthU2M, + ClientID: u2mKernelClientID, + Scopes: []string{"sql", "offline_access"}, + }, nil case nil, *noop.NoopAuth, *pat.PATAuth: // PAT (or no explicit authenticator). WithAccessToken sets both // cfg.AccessToken and a *pat.PATAuth, but WithAuthenticator(&pat.PATAuth{...}) diff --git a/kernel_config_test.go b/kernel_config_test.go index bf0c97c1..425d5aaa 100644 --- a/kernel_config_test.go +++ b/kernel_config_test.go @@ -8,7 +8,6 @@ import ( "testing" "time" - "github.com/databricks/databricks-sql-go/auth/oauth" "github.com/databricks/databricks-sql-go/auth/pat" dbsqlerr "github.com/databricks/databricks-sql-go/errors" "github.com/databricks/databricks-sql-go/internal/backend/kernel" @@ -173,21 +172,25 @@ func TestValidateKernelConfig(t *testing.T) { t.Run("OAuth U2M resolves to a U2M descriptor", func(t *testing.T) { c := baseKernelConfig() c.AccessToken = "" - // A U2M authenticator is the single source of truth; resolveKernelAuth reads - // its (cloud-inferred) client id via the auth.U2MCredentialsProvider interface. - c.Authenticator = fakeU2MAuth{id: "databricks-sql-connector"} + // A U2M authenticator selects the U2M path via the auth.U2MCredentialsProvider + // interface. On the kernel path the descriptor is CLOUD-AGNOSTIC: resolveKernelAuth + // forwards the in-house app + scopes uniformly (it deliberately does NOT read the + // authenticator's cloud-inferred client id), so an Azure host would resolve to the + // same values — see the U2M case in resolveKernelAuth. The fake's id here is + // intentionally NOT what the descriptor carries. + c.Authenticator = fakeU2MAuth{id: "ignored-cloud-inferred-id"} a, err := validateKernelConfig(c) if err != nil { t.Fatalf("U2M should validate, got %v", err) } if a.Mode != kernel.AuthU2M || a.ClientID != "databricks-sql-connector" { - t.Errorf("auth = %+v, want mode=U2M clientID=databricks-sql-connector", a) + t.Errorf("auth = %+v, want mode=U2M clientID=databricks-sql-connector (in-house, cloud-agnostic)", a) } - // Scopes must match what the Thrift path requests for this host, so both - // backends authorize against the same client identically (not the kernel's - // all-apis default). baseKernelConfig's host is AWS → [offline_access, sql]. - if want := oauth.GetScopes(c.Host, nil); !reflect.DeepEqual(a.Scopes, want) { - t.Errorf("U2M scopes = %v, want %v (Thrift parity)", a.Scopes, want) + // The kernel path forwards the fixed in-house scopes for every cloud, not the + // cloud-specific oauth.GetScopes set (which on Azure would add user_impersonation + // and derail the kernel's in-house flow to AAD). + if want := []string{"sql", "offline_access"}; !reflect.DeepEqual(a.Scopes, want) { + t.Errorf("U2M scopes = %v, want %v (in-house, cloud-agnostic)", a.Scopes, want) } }) From 4a00e71020d46feb2df7fd5a3f31948e64d48406 Mon Sep 17 00:00:00 2001 From: "peco-engineer-bot[bot]" Date: Fri, 21 Aug 2026 18:12:15 +0000 Subject: [PATCH 2/3] ai: apply changes for #449 (5 review threads) Addresses: - #3832204630 at kernel_config.go:339 - #3832207441 at kernel_config.go:334 - #3832207487 at kernel_config_test.go:175 - #3832207522 at kernel_config_test.go:188 - #3832207599 at kernel_auth_real_test.go:76 Signed-off-by: peco-engineer-bot[bot] --- auth/oauth/u2m/authenticator.go | 18 ++++++++-------- doc.go | 13 +++++++----- internal/backend/kernel/auth.go | 33 +++++++++++++++++------------- internal/backend/kernel/backend.go | 6 ++++-- kernel_auth_real_test.go | 4 ++-- kernel_config.go | 6 ++++-- kernel_config_test.go | 10 ++++----- 7 files changed, 52 insertions(+), 38 deletions(-) diff --git a/auth/oauth/u2m/authenticator.go b/auth/oauth/u2m/authenticator.go index 834265c0..4c0a7468 100644 --- a/auth/oauth/u2m/authenticator.go +++ b/auth/oauth/u2m/authenticator.go @@ -78,15 +78,17 @@ type u2mAuthenticator struct { mx sync.Mutex } -// U2MClientID exposes the cloud-inferred OAuth client id so the SEA-via-kernel -// backend uses the same client id for the kernel's browser/PKCE flow that the -// Thrift path would, keeping cfg.Authenticator the single source of truth for auth -// mode. It structurally satisfies the U2MCredentialsProvider interface the kernel -// backend asserts (defined in internal/backend/kernel, off the public API). +// U2MClientID exposes the cloud-inferred OAuth client id, keeping +// cfg.Authenticator the single source of truth for auth mode. It structurally +// satisfies the U2MCredentialsProvider interface the kernel backend asserts +// (defined in internal/backend/kernel, off the public API). // -// Only the client id is read here; resolveKernelAuth pairs it with the same -// oauth.GetScopes set the Thrift path requests, so both backends authorize against -// the built-in databricks-sql-connector client identically (not the kernel default). +// NOTE: the kernel path asserts this interface only to DETECT that the U2M flow is +// selected; it does NOT forward this cloud-inferred client id. The kernel runs one +// in-house workspace-federated flow on every cloud, so resolveKernelAuth uses the +// fixed built-in databricks-sql-connector client and offline_access + sql scopes +// instead (see resolveKernelAuth's U2M case). On AWS/GCP that equals this value; +// on Azure it deliberately differs. func (c *u2mAuthenticator) U2MClientID() string { return c.clientID } // Auth will start the OAuth Authorization Flow to authenticate the cli client diff --git a/doc.go b/doc.go index 266deff0..b73930c2 100644 --- a/doc.go +++ b/doc.go @@ -227,11 +227,14 @@ OAuth U2M is interactive: on a cache miss, connecting launches the system browse blocks until login completes or the kernel's ~120s callback timeout expires. Because the C ABI can't interrupt session open mid-call, a connection-context deadline is not honored during that window. Use PAT or OAuth M2M for headless / deadline-bound -connects. The kernel and Thrift backends use the same (cloud-inferred) U2M client id -and request the same default scopes (offline_access + sql, or, on Azure, -offline_access + /user_impersonation): the kernel path forwards exactly the -scopes the Thrift path computes, so both authorize identically against the built-in -public client. Neither backend exposes a U2M-scopes option. +connects. The kernel runs a single, cloud-agnostic in-house U2M flow (OIDC discovery +against {host}/oidc, no Azure branching), so on every cloud the kernel path uses the +built-in databricks-sql-connector client and requests offline_access + sql. It does +NOT forward the cloud-inferred client id / scopes the Thrift path computes: on +AWS/GCP those already match, but on Azure the Thrift path uses the Entra-direct app +id and /user_impersonation scope, which would break the kernel's +workspace-federated flow. So the two backends authorize identically on AWS/GCP and +deliberately diverge on Azure. Neither backend exposes a U2M-scopes option. Experimental kernel-only options (rejected by the default backend; the WithKernel* prefix marks them experimental): diff --git a/internal/backend/kernel/auth.go b/internal/backend/kernel/auth.go index ddef6510..a22f8593 100644 --- a/internal/backend/kernel/auth.go +++ b/internal/backend/kernel/auth.go @@ -20,18 +20,20 @@ const ( // validateKernelConfig); OpenSession maps it to exactly one // kernel_session_config_set_auth_* call. // Scopes and RedirectPort map to the optional args of set_auth_u2m and are wired -// through to it by setAuth. resolveKernelAuth populates Scopes with the same -// cloud-specific set the Thrift path requests (via oauth.GetScopes) so both -// backends authorize identically; RedirectPort stays zero (no user option, kernel -// default 8020) but is kept so kernel.Auth models the full set_auth_u2m surface — -// a future WithOAuthRedirectPort becomes populating it, not re-plumbing the setter. -// TestSetAuthByMode's "U2M full" case pins the marshalling of both. +// through to it by setAuth. For U2M, resolveKernelAuth populates ClientID/Scopes +// with the fixed in-house databricks-sql-connector client and offline_access + sql +// on every cloud (NOT the cloud-inferred Thrift values), because the kernel runs one +// in-house workspace-federated flow with no Azure branching. RedirectPort stays zero +// (no user option, kernel default 8020) but is kept so kernel.Auth models the full +// set_auth_u2m surface — a future WithOAuthRedirectPort becomes populating it, not +// re-plumbing the setter. TestSetAuthByMode's "U2M full" case pins the marshalling +// of both. type Auth struct { Mode AuthMode Token string // PAT - ClientID string // M2M + U2M (U2M: the cloud-inferred Go client id) + ClientID string // M2M + U2M (U2M: fixed in-house client, cloud-agnostic) ClientSecret string // M2M - Scopes []string // U2M — Thrift-parity scopes from oauth.GetScopes; nil → kernel default + Scopes []string // U2M — fixed offline_access + sql (cloud-agnostic); nil → kernel default RedirectPort uint16 // U2M — no user option today; 0 → kernel default port (8020) } @@ -69,12 +71,15 @@ func M2MScopesSupported(scopes []string) bool { } } -// U2MCredentialsProvider is implemented by the OAuth U2M authenticator to expose -// the cloud-inferred client id the kernel should use for its browser/PKCE flow (so -// the kernel path uses the same client id the Thrift path would). Internal for the -// same reason as M2MCredentialsProvider; satisfied structurally by the unexported -// u2m authenticator. +// U2MCredentialsProvider is implemented by the OAuth U2M authenticator. The kernel +// backend asserts this interface only to DETECT that the interactive U2M flow is +// selected — it does NOT forward the returned (cloud-inferred) client id. The kernel +// runs a single in-house workspace-federated flow on every cloud, so resolveKernelAuth +// uses the fixed built-in databricks-sql-connector client and offline_access + sql +// scopes instead. Internal for the same reason as M2MCredentialsProvider; satisfied +// structurally by the unexported u2m authenticator. type U2MCredentialsProvider interface { - // U2MClientID returns the OAuth client id for the U2M browser flow. + // U2MClientID returns the OAuth client id for the U2M browser flow. Used by the + // kernel path only as a presence marker (see the interface doc above). U2MClientID() string } diff --git a/internal/backend/kernel/backend.go b/internal/backend/kernel/backend.go index 500144f6..b17d0ed6 100644 --- a/internal/backend/kernel/backend.go +++ b/internal/backend/kernel/backend.go @@ -400,8 +400,10 @@ func (k *KernelBackend) setAuth(cfg *C.KernelSessionConfig) error { } case AuthU2M: // client id / scopes are optional: NULL when empty lets the kernel use its - // public client / default scopes. We pass Go's cloud-inferred client id when - // set, so the kernel uses the same client id the Thrift path would. + // public client / default scopes. resolveKernelAuth fills these with the + // fixed in-house databricks-sql-connector client and offline_access + sql + // scopes on every cloud (NOT the cloud-inferred Thrift values), so the + // kernel's single in-house workspace-federated flow works uniformly. clientID := newCStrOrNull(k.cfg.Auth.ClientID) defer clientID.free() scopes := newCStrOrNull(joinScopes(k.cfg.Auth.Scopes)) diff --git a/kernel_auth_real_test.go b/kernel_auth_real_test.go index 2cd9c7f1..7e6704a0 100644 --- a/kernel_auth_real_test.go +++ b/kernel_auth_real_test.go @@ -71,8 +71,8 @@ func TestResolveKernelAuthRealAuthenticators(t *testing.T) { // Azure host. The kernel runs one cloud-blind in-house workspace-federated // U2M flow; feeding it the Entra-direct app breaks it. See // resolveKernelAuth's U2M case. - if got.ClientID != "databricks-sql-connector" { - t.Errorf("U2M clientID = %q, want databricks-sql-connector (in-house app, cloud-agnostic on the kernel path)", got.ClientID) + if got.ClientID != u2mKernelClientID { + t.Errorf("U2M clientID = %q, want %s (in-house app, cloud-agnostic on the kernel path)", got.ClientID, u2mKernelClientID) } if want := []string{"sql", "offline_access"}; !reflect.DeepEqual(got.Scopes, want) { t.Errorf("U2M scopes = %v, want %v (in-house, cloud-agnostic)", got.Scopes, want) diff --git a/kernel_config.go b/kernel_config.go index a69fd72c..e277641f 100644 --- a/kernel_config.go +++ b/kernel_config.go @@ -330,12 +330,14 @@ func resolveKernelAuth(cfg *config.Config) (kernel.Auth, error) { // (login.microsoftonline.com/{tenant}/v1/authorize). The Thrift path keeps // the cloud-specific values (it needs them); only this kernel-path mapping // is uniform. On AWS/GCP this is a no-op — a.U2MClientID() is already - // databricks-sql-connector and the scopes are already sql+offline_access. + // databricks-sql-connector and this scope slice is byte-for-byte the set + // oauth.GetScopes(host, nil) returns there (it appends offline_access, + // then sql), so the historical ordering is preserved too. // RedirectPort stays zero (no user option; kernel default). return kernel.Auth{ Mode: kernel.AuthU2M, ClientID: u2mKernelClientID, - Scopes: []string{"sql", "offline_access"}, + Scopes: []string{"offline_access", "sql"}, }, nil case nil, *noop.NoopAuth, *pat.PATAuth: // PAT (or no explicit authenticator). WithAccessToken sets both diff --git a/kernel_config_test.go b/kernel_config_test.go index 425d5aaa..8900a0c7 100644 --- a/kernel_config_test.go +++ b/kernel_config_test.go @@ -17,7 +17,7 @@ import ( // nonPATAuth stands in for any non-PAT, non-OAuth authenticator (token-provider / // external / federated) — the kernel backend must reject it. It implements neither -// auth.M2MCredentialsProvider nor auth.U2MCredentialsProvider. +// kernel.M2MCredentialsProvider nor kernel.U2MCredentialsProvider. type nonPATAuth struct{} func (nonPATAuth) Authenticate(*http.Request) error { return nil } @@ -172,7 +172,7 @@ func TestValidateKernelConfig(t *testing.T) { t.Run("OAuth U2M resolves to a U2M descriptor", func(t *testing.T) { c := baseKernelConfig() c.AccessToken = "" - // A U2M authenticator selects the U2M path via the auth.U2MCredentialsProvider + // A U2M authenticator selects the U2M path via the kernel.U2MCredentialsProvider // interface. On the kernel path the descriptor is CLOUD-AGNOSTIC: resolveKernelAuth // forwards the in-house app + scopes uniformly (it deliberately does NOT read the // authenticator's cloud-inferred client id), so an Azure host would resolve to the @@ -183,13 +183,13 @@ func TestValidateKernelConfig(t *testing.T) { if err != nil { t.Fatalf("U2M should validate, got %v", err) } - if a.Mode != kernel.AuthU2M || a.ClientID != "databricks-sql-connector" { - t.Errorf("auth = %+v, want mode=U2M clientID=databricks-sql-connector (in-house, cloud-agnostic)", a) + if a.Mode != kernel.AuthU2M || a.ClientID != u2mKernelClientID { + t.Errorf("auth = %+v, want mode=U2M clientID=%s (in-house, cloud-agnostic)", a, u2mKernelClientID) } // The kernel path forwards the fixed in-house scopes for every cloud, not the // cloud-specific oauth.GetScopes set (which on Azure would add user_impersonation // and derail the kernel's in-house flow to AAD). - if want := []string{"sql", "offline_access"}; !reflect.DeepEqual(a.Scopes, want) { + if want := []string{"offline_access", "sql"}; !reflect.DeepEqual(a.Scopes, want) { t.Errorf("U2M scopes = %v, want %v (in-house, cloud-agnostic)", a.Scopes, want) } }) From 8edaf4f62185dcdb3864f714bdaa9b0428d866a9 Mon Sep 17 00:00:00 2001 From: "peco-engineer-bot[bot]" Date: Fri, 21 Aug 2026 18:19:05 +0000 Subject: [PATCH 3/3] ai: apply changes for #449 (1 review thread) Addresses: - #3832615584 at kernel_auth_real_test.go:77 Signed-off-by: peco-engineer-bot[bot] --- kernel_auth_real_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kernel_auth_real_test.go b/kernel_auth_real_test.go index 7e6704a0..e112c14c 100644 --- a/kernel_auth_real_test.go +++ b/kernel_auth_real_test.go @@ -74,7 +74,7 @@ func TestResolveKernelAuthRealAuthenticators(t *testing.T) { if got.ClientID != u2mKernelClientID { t.Errorf("U2M clientID = %q, want %s (in-house app, cloud-agnostic on the kernel path)", got.ClientID, u2mKernelClientID) } - if want := []string{"sql", "offline_access"}; !reflect.DeepEqual(got.Scopes, want) { + if want := []string{"offline_access", "sql"}; !reflect.DeepEqual(got.Scopes, want) { t.Errorf("U2M scopes = %v, want %v (in-house, cloud-agnostic)", got.Scopes, want) } })