Skip to content

Commit f6418e0

Browse files
committed
fix(sea): address review on #409 (packaging, auth-flow docs, context)
- P0 packaging: the napi router's per-platform npm names were garbled — the M0 triple was baked into the prefix for every platform (@databricks/sea-native-linux-x64-gnu-<triple>, and the doubled ...-gnu-linux-x64-gnu). Corrected to the canonical @databricks/sql-kernel-<triple> (matches the loader hint + native/sea/README). Added native-packaging.test to lock the naming. The per-platform packages are unpublished, so they remain undeclared in optionalDependencies (npm ci can't resolve an unpublished pin); README documents the M0 build:native load path. - Moved @napi-rs/cli out of optionalDependencies (a build tool should not land in consumer installs); build:native now fetches it on demand via npx --yes. - P1 auth-flow: documented honestly that SEA's OAuth flow selection DIVERGES from Thrift — Thrift keys off the secret (DBSQLClient.ts:216), SEA keys off oauthClientId presence — including the two real gaps (id+no-secret throws; U2M has no custom client id). Fixed the stale DBSQLClient.ts:143 ref. - P1 stale ref: SeaErrorMapping pointed at DBSQLOperation.ts:209; the Canceled switch is now ~:374. Updated. - P2 context: SeaBackendOptions.context is now required (was an `as IClientContext` downcast of undefined → latent NPE). Tests pass a shared makeFakeContext(). - P2 prependSlash: dropped the dead lib/utils/prependSlash.ts (nothing imported it; SeaAuth and DBSQLClient each keep their own inline copy). Co-authored-by: Isaac Signed-off-by: Madhavendra Rathore <madhavendra.rathore@databricks.com>
1 parent 25925cb commit f6418e0

13 files changed

Lines changed: 165 additions & 119 deletions

lib/sea/SeaAuth.ts

Lines changed: 26 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -128,11 +128,23 @@ export function isBlankOrReserved(s: string): boolean {
128128
* - OAuth U2M: `authType: 'databricks-oauth'` + NO `oauthClientId` and
129129
* NO `oauthClientSecret`. Kernel runs the PKCE auth-code dance (opens
130130
* a browser, listens on localhost:8030, exchanges the code, persists
131-
* to `~/.config/databricks-sql-kernel/oauth/{sha256}.json`). The flow
132-
* selector keys off `oauthClientId` presence: present → M2M, absent →
133-
* U2M. (Round-4 NF3-2 fix; previously secret-keyed — that variant
134-
* routed a typo'd-secret M2M call to the U2M arm and swallowed the
135-
* actionable error.) Mirrors thrift's intent at `DBSQLClient.ts:143`.
131+
* to `~/.config/databricks-sql-kernel/oauth/{sha256}.json`).
132+
*
133+
* **Flow selection — DELIBERATE DIVERGENCE FROM THRIFT.** Thrift's
134+
* `DBSQLClient.createAuthProvider` (`DBSQLClient.ts:216`) keys off the
135+
* *secret* (`oauthClientSecret === undefined ? U2M : M2M`), so a custom
136+
* `oauthClientId` with no secret runs U2M with that id. SEA instead keys
137+
* off `oauthClientId` *presence* (id present → M2M, absent → U2M). The
138+
* trade-off: keying off the id means a caller who set an id but
139+
* typoed/forgot the secret gets the actionable M2M "secret is required"
140+
* error instead of being silently routed to U2M (which would hide their
141+
* intent). The cost is two real behavioural gaps vs Thrift:
142+
* 1. `oauthClientId` + no secret → Thrift runs U2M; SEA throws
143+
* `AuthenticationError` (M2M secret required).
144+
* 2. SEA U2M has NO custom-client-id support — the kernel hardcodes
145+
* `client_id = "databricks-cli"`, and SEA rejects any `oauthClientId`
146+
* on the U2M arm. Thrift U2M honours a custom `clientId`.
147+
* Both are documented limitations of the M0 SEA OAuth surface, not bugs.
136148
*
137149
* Out of scope on the OAuth paths (rejected with a clear error):
138150
* - `azureTenantId` / `useDatabricksOAuthInAzure` → Microsoft Entra
@@ -204,13 +216,15 @@ export function buildSeaConnectionOptions(options: ConnectionOptions): SeaNative
204216
);
205217
}
206218

207-
// Flow selector mirrors thrift's `DBSQLClient.createAuthProvider`
208-
// (`DBSQLClient.ts:143`): presence of `oauthClientId` indicates M2M
209-
// intent, otherwise U2M. Routing decision is based on `oauthClientId`
210-
// (the "do I have an id?" signal) rather than the secret, so a
211-
// user who set an id but typoed/forgot the secret gets the M2M
212-
// "secret is required" error instead of a U2M error that hides
213-
// their actual intent. The U2M arm still defends against an id
219+
// Flow selector — DELIBERATELY DIFFERENT from thrift's
220+
// `DBSQLClient.createAuthProvider` (`DBSQLClient.ts:216`), which keys off
221+
// the secret (`oauthClientSecret === undefined ? U2M : M2M`). SEA keys off
222+
// `oauthClientId` *presence* (the "do I have an id?" signal) instead, so a
223+
// user who set an id but typoed/forgot the secret gets the actionable M2M
224+
// "secret is required" error rather than being silently routed to U2M
225+
// (which would hide their intent). Cost: `id + no secret` throws here
226+
// where thrift would run U2M, and SEA U2M has no custom-client-id support
227+
// (see buildSeaConnectionOptions header). The U2M arm still defends against an id
214228
// sneaking through: fires only when `oauthClientId` is provided as
215229
// a blank-reserved literal (e.g., whitespace, `"null"`, `"undefined"`)
216230
// alongside an absent/blank secret — both `idIsBlank` and

lib/sea/SeaBackend.ts

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -24,13 +24,13 @@ import SeaSessionBackend from './SeaSessionBackend';
2424

2525
export interface SeaBackendOptions {
2626
/**
27-
* Optional in the type so unit tests that only exercise the auth-
28-
* routing surface (which doesn't touch context) can pass
29-
* `{ nativeBinding }`. The constructor downcasts undefined to
30-
* `IClientContext` because runtime callers from `DBSQLClient` always
31-
* supply one — see `lib/DBSQLClient.ts` SEA seam.
27+
* Required. Provides the logger + config the SEA session/operation chain
28+
* logs through. `DBSQLClient` supplies it via the SEA seam
29+
* (`new SeaBackend({ context: this })`); unit tests pass a stub. Kept
30+
* mandatory (rather than an `as IClientContext` downcast of `undefined`)
31+
* so a missing context is a compile error, not a latent runtime NPE.
3232
*/
33-
context?: IClientContext;
33+
context: IClientContext;
3434
/**
3535
* Optional injection seam for unit tests. When provided, replaces the
3636
* default `getSeaNative()` call so tests can swap in a mock napi
@@ -68,9 +68,9 @@ export default class SeaBackend implements IBackend {
6868

6969
private nativeOptions?: SeaNativeConnectionOptions;
7070

71-
constructor(options?: SeaBackendOptions) {
72-
this.context = options?.context as IClientContext;
73-
this.binding = options?.nativeBinding ?? getSeaNative();
71+
constructor(options: SeaBackendOptions) {
72+
this.context = options.context;
73+
this.binding = options.nativeBinding ?? getSeaNative();
7474
}
7575

7676
public async connect(options: ConnectionOptions): Promise<void> {

lib/sea/SeaErrorMapping.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -57,8 +57,8 @@ export type KernelErrorCode =
5757
*
5858
* `errorCode` is namespaced under `kernelMetadata` rather than placed at
5959
* the top level because `OperationStateError` already declares a top-level
60-
* `errorCode: enum` field, and `DBSQLOperation.ts:209` switches on it
61-
* (`err.errorCode === OperationStateErrorCode.Canceled`). Top-level
60+
* `errorCode: enum` field, and `DBSQLOperation.ts` switches on it
61+
* (`err.errorCode === OperationStateErrorCode.Canceled`, around `:374`). Top-level
6262
* defineProperty would clobber that enum with a kernel string and break
6363
* cancel/close detection.
6464
*/
@@ -210,7 +210,7 @@ function buildKernelMetadata(parsed: Record<string, unknown>): KernelMetadata {
210210
* envelope fields under a single non-enumerable `kernelMetadata`
211211
* namespace. Namespacing avoids the collision with
212212
* `OperationStateError.errorCode` (an enum already switched on at the
213-
* JS layer — see `DBSQLOperation.ts:209`).
213+
* JS layer — see `DBSQLOperation.ts` around `:374`).
214214
* - Binding-side error (e.g. `napi::Error::new(InvalidArg, "openSession:
215215
* \`token\` is required for the requested auth mode")` produced by
216216
* the binding's own validation): returned unchanged. These don't

lib/utils/prependSlash.ts

Lines changed: 0 additions & 25 deletions
This file was deleted.

native/sea/README.md

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -57,17 +57,24 @@ nodejs repo.
5757

5858
At release time the kernel's CI publishes
5959
`@databricks/sql-kernel-<triple>` npm packages — one per supported
60-
platform — each containing a single `.node` binary. The nodejs
61-
driver lists them as `optionalDependencies`; npm installs only the
62-
one matching the consumer's `process.platform` / `process.arch`.
63-
`native/sea/index.js` (the napi-rs router) then `require()`s the
64-
installed package at load time.
60+
platform — each containing a single `.node` binary. `native/sea/index.js`
61+
(the napi-rs router) `require()`s the package matching the consumer's
62+
`process.platform` / `process.arch` at load time.
63+
64+
> **M0 status:** these per-platform packages are **not yet published**, so
65+
> they are intentionally **not** declared in the driver's
66+
> `optionalDependencies`. (npm refuses an `npm ci` against a pinned
67+
> dependency it cannot resolve from the registry, so declaring an
68+
> unpublished package would break every install.) Until they ship, the
69+
> binding is produced locally via `npm run build:native` (which copies
70+
> `index.<triple>.node` into this directory). Once the packages are
71+
> published, add `@databricks/sql-kernel-<triple>` back to
72+
> `optionalDependencies` — npm then installs only the matching one.
6573
6674
## Supported platforms (M0)
6775

68-
M0 publishes a **single** triple: **`linux-x64-gnu`** (package
69-
`@databricks/sql-kernel-linux-x64-gnu`). It is the only entry in the
70-
driver's `optionalDependencies`.
76+
M0 targets a **single** triple: **`linux-x64-gnu`** (package
77+
`@databricks/sql-kernel-linux-x64-gnu`, once published).
7178

7279
On every other platform (macOS, Windows, linux-arm64, linux-x64-musl
7380
/ Alpine, …) the SEA binding is simply absent: `SeaNativeLoader`

native/sea/index.js

Lines changed: 18 additions & 18 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package-lock.json

Lines changed: 0 additions & 24 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
"test": "nyc --report-dir=${NYC_REPORT_DIR:-coverage_unit} mocha --config tests/unit/.mocharc.js",
1818
"update-version": "node bin/update-version.js && prettier --write ./lib/version.ts",
1919
"build": "npm run update-version && tsc --project tsconfig.build.json",
20-
"build:native": "bash -c 'cd ${DATABRICKS_SQL_KERNEL_REPO:-../../databricks-sql-kernel}/napi && npx --no-install @napi-rs/cli build --platform ${BUILD_PROFILE:---release} && cp index.* $OLDPWD/native/sea/'",
20+
"build:native": "bash -c 'cd ${DATABRICKS_SQL_KERNEL_REPO:-../../databricks-sql-kernel}/napi && npx --yes @napi-rs/cli@2.18.4 build --platform ${BUILD_PROFILE:---release} && cp index.* $OLDPWD/native/sea/'",
2121
"prepack": "test -f native/sea/index.js || { echo 'ERROR: native/sea/index.js (napi-rs router) is missing — the published tarball would fail to load SEA. It is committed to git; run `npm run build:native` if you removed it.' >&2; exit 1; }",
2222
"watch": "tsc --project tsconfig.build.json --watch",
2323
"type-check": "tsc --noEmit",
@@ -91,7 +91,6 @@
9191
"winston": "^3.8.2"
9292
},
9393
"optionalDependencies": {
94-
"lz4": "^0.6.5",
95-
"@napi-rs/cli": "2.18.4"
94+
"lz4": "^0.6.5"
9695
}
9796
}

0 commit comments

Comments
 (0)