Skip to content

feat(appkit): surface caller expiry and support local OBO - #597

Open
MarioCadenas wants to merge 10 commits into
execution-standalone-userfrom
execution-identity-lifecycle
Open

MarioCadenas wants to merge 10 commits into
execution-standalone-userfrom
execution-identity-lifecycle

Conversation

@MarioCadenas

@MarioCadenas MarioCadenas commented Sep 23, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

Caller operations expose downstream credential rejection as IdentityExpiredError (IDENTITY_EXPIRED), and local apps can exercise real user execution through the normal npm run dev workflow.

This is layer 4 of the stack, based on #595 (execution-standalone-user). Design: design-docs/execution-identity-e2e.md, section 5.1.4. Cache partitioning is in #594.

  • AppKit-managed spans record appkit.execution.principal, appkit.execution.principal_id, and the initiating user's appkit.execution.actor_id when present. Tokens are never span attributes.
  • The server plugin injects forwarded user headers before plugin and custom routes in development. It uses DATABRICKS_TOKEN with DATABRICKS_HOST first, otherwise the explicitly configured DATABRICKS_CONFIG_PROFILE. No separate proxy command, target, or port is needed.
  • Credential lookup and refresh are shared and kept in memory. Lookup and refresh failures return 401. Existing forwarded tokens are preserved. Injection accepts same-origin loopback requests and can be disabled with APPKIT_DEV_OBO=false.
  • Header injection does not open a caller scope. asUser(req) selects user execution; unscoped calls keep the app's configured credentials. SP remains the ordinary default, group is deferred, and the marked DEV_OBO_FALLBACK remains when injection is disabled or unconfigured.
  • Validate relative imports while packaging the CLI, with the validator dependency and Knip entry declared explicitly.

Local emulation supplies user credentials; it does not emulate platform consent, scope enforcement, or resource provisioning. A real SP-versus-user comparison requires the app's default credentials to belong to an SP.

Compatibility

Plugin execute() preserves its existing failed-result envelope and adds an optional typed error field. Throwing APIs expose IdentityExpiredError, and HTTP/SSE agent responses carry its stable code. The error retains only a token fingerprint, not credential-bearing upstream errors. Non-401 failures and SP execution retain their existing behavior. Existing public compatibility aliases remain available.

Verification

  • pnpm -r typecheck, full monorepo unit suite: 5,301 passed, one existing skip.
  • Original dev-playground OBO regression tests pass unchanged.
  • pnpm build, pnpm docs:build, formatting/lint, and Knip.
  • AppKit tarball build and packaged CLI --help smoke check.
  • Coverage includes typed expiry, token-free errors, stream failures, span identity, token priority, explicit profile selection, refresh failure, header preservation, local request restrictions, and actual HTTP middleware injection.
  • Tests use fake credentials and loopback servers. No live Databricks credentials were used.

@MarioCadenas
MarioCadenas requested a review from a team as a code owner September 23, 2026 18:22
@MarioCadenas
MarioCadenas requested review from calvarjorge and removed request for a team September 23, 2026 18:22
@MarioCadenas
MarioCadenas added this pull request to stack #602 September 24, 2026 08:14
@github-actions

github-actions Bot commented Sep 24, 2026 •

Copy link
Copy Markdown
Contributor

📦 Bundle size report

Compared against bundle-size-baseline.json (main).

@databricks/appkit

npm tarball (packed): 1.2 MB (+21 KB) — gzipped download (dist + bin; excludes release-only docs/NOTICE).

dist raw gzip
JS (runtime) 1.2 MB (+23 KB) 437 KB (+8.8 KB)
Type declarations 452 KB (+6.1 KB) 164 KB (+2.4 KB)
Source maps 2.4 MB (+41 KB) 818 KB (+16 KB)
Other 11 KB 3.7 KB
Total 4.1 MB (+70 KB) 1.4 MB (+27 KB)
Per-entry composition (own code — deps external (as shipped))
Entry Initial (gz) Lazy (gz) Total (gz) node_modules (min) Own code (min)
. 99 KB (+2.9 KB) 2.5 KB (-2 B) 102 KB (+2.9 KB) external 324 KB (+8.6 KB)
./beta 95 KB (+1.4 KB) 460 B (-18 B) 95 KB (+1.4 KB) external 287 KB (+4.7 KB)
./testing 40 KB (+2.0 KB) 32 KB (+1.0 KB) 72 KB (+3.1 KB) external 210 KB (+7.8 KB)
./tsdown 520 B 0 B 520 B external 813 B
./type-generator 23 KB 0 B 23 KB external 65 KB

Chunks:

Entry Chunk Load Size (gz)
. index.js initial 95 KB
. utils.js initial 4.6 KB
. remote-tunnel-manager.js lazy 2.5 KB
./beta beta.js initial 78 KB
./beta stream-manager.js initial 5.9 KB
./beta service-context.js initial 4.1 KB
./beta databricks.js initial 3.3 KB
./beta wide-event-emitter.js initial 3.2 KB
./beta client.js initial 593 B
./beta index.js initial 20 B
./beta supervisor-api.js lazy 191 B
./beta databricks.js lazy 155 B
./beta index.js lazy 114 B
./testing manifest.js initial 27 KB
./testing index.js initial 10 KB
./testing wide-event-emitter.js initial 2.9 KB
./testing index.js lazy 28 KB
./testing remote-tunnel-manager.js lazy 2.5 KB
./testing utils.js lazy 1.8 KB
./tsdown index.js initial 520 B
./type-generator index.js initial 23 KB

@databricks/appkit-ui

npm tarball (packed): 350 KB — gzipped download (dist + bin; excludes release-only docs/NOTICE).

dist raw gzip
JS (runtime) 395 KB 132 KB
Type declarations 229 KB 84 KB
Source maps 766 KB 253 KB (+1 B)
CSS 16 KB 3.2 KB
Total 1.4 MB 472 KB (+1 B)
Per-entry composition (consumer bundle — deps bundled, peerDeps external)
Entry Initial (gz) Lazy (gz) Total (gz) node_modules (min) Own code (min)
./js 5.3 KB 49 KB 55 KB 208 KB 14 KB
./js/beta 20 B 0 B 20 B 0 B 0 B
./react 432 KB 49 KB 481 KB 1.3 MB 177 KB
./react/beta 1.0 KB 0 B 1.0 KB 0 B 1.9 KB

Chunks:

Entry Chunk Load Size (gz)
./js index.js initial 5.2 KB
./js chunk initial 120 B
./js apache-arrow lazy 49 KB
./js/beta beta.js initial 20 B
./react index.js initial 430 KB
./react tslib initial 2.1 KB
./react apache-arrow lazy 49 KB
./react/beta beta.js initial 1.0 KB

@github-actions

github-actions Bot commented Sep 24, 2026 •

Copy link
Copy Markdown
Contributor

🤖 AppKit PR bot

🔬 Run evals

Start an eval for this PR from the evals-monitor app: Go to Evals Monitor →

📦 Try this PR's app template

Scaffolds a new app from this PR's SDK build. Run it in any folder (requires the GitHub CLI — gh auth login — and the Databricks CLI):

gh run download 36878400847 -R databricks/appkit -n appkit-template-0.81.0-pr.7677039-execution-identity-lifecycle-597 -D appkit-pr-597 \
  && unzip -o "appkit-pr-597/appkit-template-0.81.0-pr.7677039-execution-identity-lifecycle-597.zip" -d "appkit-pr-597" \
  && databricks apps init --template "appkit-pr-597"

The template pins @databricks/appkit and @databricks/appkit-ui to tarballs built from this branch, so the scaffolded app runs against this PR's code.

@MarioCadenas
MarioCadenas force-pushed the execution-identity-lifecycle branch from 2790860 to 25ecd06 Compare September 24, 2026 13:42
@MarioCadenas
MarioCadenas force-pushed the execution-identity-lifecycle branch 2 times, most recently from 45d2d28 to 81ff9a7 Compare September 25, 2026 08:36
@MarioCadenas
MarioCadenas force-pushed the execution-identity-lifecycle branch from e0c118d to d9da769 Compare September 29, 2026 15:03
Signed-off-by: MarioCadenas <MarioCadenas@users.noreply.github.com>
Use plain punctuation in the inherited error comment so generated pages follow repository style.

Signed-off-by: MarioCadenas <MarioCadenas@users.noreply.github.com>
Signed-off-by: MarioCadenas <MarioCadenas@users.noreply.github.com>
@MarioCadenas
MarioCadenas force-pushed the execution-identity-lifecycle branch from d9da769 to 4b22685 Compare September 29, 2026 16:04
MarioCadenas and others added 7 commits October 1, 2026 10:51
Co-authored-by: Isaac <no-reply@databricks.com>
Signed-off-by: MarioCadenas <MarioCadenas@users.noreply.github.com>
Co-authored-by: Isaac <no-reply@databricks.com>
Signed-off-by: MarioCadenas <MarioCadenas@users.noreply.github.com>
Resolves the agents.ts imports: keeps this branch's normalizeIdentityError
and drops createRequestScope, which the routes no longer use now that user
scope applies per plugin tool call.

Co-authored-by: Isaac <no-reply@databricks.com>
Signed-off-by: MarioCadenas <MarioCadenas@users.noreply.github.com>
The identity-expiration route test made the model call fail with a 401.
The model call now runs as the app service principal, so its failure is
not a user identity expiry. The test now fails a plugin tool call, which
runs in user scope, and still checks that both agent routes return
IDENTITY_EXPIRED without leaking the token.

Co-authored-by: Isaac <no-reply@databricks.com>
Signed-off-by: MarioCadenas <MarioCadenas@users.noreply.github.com>
Co-authored-by: Isaac <no-reply@databricks.com>
Signed-off-by: MarioCadenas <MarioCadenas@users.noreply.github.com>
Co-authored-by: Isaac <no-reply@databricks.com>
Signed-off-by: MarioCadenas <MarioCadenas@users.noreply.github.com>
Co-authored-by: Isaac <no-reply@databricks.com>
Signed-off-by: MarioCadenas <MarioCadenas@users.noreply.github.com>

This branch has not been deployed

No deployments
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.

1 participant