FabricFabricHarness
Databricks

Local naming and authentication

Understand Fabric job, agent, App, tenant, and Databricks principal names while developing locally.

Local development involves two independent authentication boundaries and several intentionally separate names. Keeping them separate prevents a local API token from being mistaken for a Databricks credential, or a model endpoint name from becoming an agent route.

The complete local request

Rendering diagram...

The Fabric principal answers who may invoke this local runtime and tenant. The Databricks principal answers who performs the model, data, and state operation in the workspace. They can be the same organizational identity, but they use different credentials and are configured independently.

Naming model

Rendering diagram...
NameSourceExampleUsed for
Bundle namemetadata.nameanalytics-agentsDatabricks Asset Bundle identity
App namerun.idPrefixanalytics-appDatabricks App resource
Job nameDefinition name, then relative file pathdaily-reportCLI and /jobs/:name
Persistent agent nameRelative path under agents/support/agents/:name/:instanceId
Instance IDSupplied by the callercustomer-42Addressable persistent agent instance
Session nameSupplied by the caller; default defaultincident-104Conversation within an instance
Databricks modelDATABRICKS_MODEL or definition modelsystem.ai.gpt-oss-20bAI Gateway or serving endpoint selection

Configure the project and App names in .fabricharness/config.ts:

.fabricharness/config.ts
export default {
  metadata: { name: 'analytics-agents' },
  run: {
    idPrefix: 'analytics-app',
    target: 'node',
  },
};

metadata.name and run.idPrefix are sanitized for Databricks resource naming. They do not rename jobs, persistent agents, Unity Catalog objects, or model endpoints.

Finite job names

For finite jobs, a declared name is authoritative:

.fabricharness/jobs/report.ts
import { agent, schema } from '@fabric-harness/sdk';

export default agent({
  name: 'daily-report',
  input: schema.object({ accountId: schema.string() }),
  output: schema.string(),
  model: 'databricks/system.ai.gpt-oss-20b',
  triggers: { manual: true, webhook: true },
  run: async ({ init, input }) => {
    const session = await (await init()).session();
    return session.prompt(`Summarize account ${input.accountId}.`);
  },
});
fh run daily-report --account-id account-42
POST /jobs/daily-report

If name is omitted, jobs/report.ts becomes report. Nested paths remain namespaced: jobs/operations/daily.ts becomes operations/daily.

Persistent agent names

Persistent routes derive from the relative path under .fabricharness/agents/:

.fabricharness/agents/support.ts
import { createAgent } from '@fabric-harness/sdk';

export default createAgent(({ id }) => ({
  model: 'databricks/system.ai.gpt-oss-20b',
  instructions: `Support account ${id} using governed workspace data.`,
  triggers: { webhook: true },
}));

The route below addresses agent support, instance customer-42, session incident-104:

curl -X POST \
  'http://localhost:3000/agents/support/customer-42?wait=false' \
  -H 'content-type: application/json' \
  -d '{"message":"Investigate the failed pipeline.","session":"incident-104"}'

Fabric persists that identity as the tuple (support, customer-42, incident-104). The instance and session names are application identifiers, not Databricks users or Unity Catalog principals.

Fabric authentication while local

Direct CLI execution does not cross an HTTP authentication boundary:

fh run daily-report --account-id account-42 --mock

fh dev permits requests without a token in local mode and records them as the synthetic local-development service principal. This is intended only for loopback development.

To exercise the authenticated HTTP path locally, set an API token before starting the server:

export FABRIC_HARNESS_API_TOKEN="$(openssl rand -hex 32)"
fh dev
curl -X POST http://localhost:3000/jobs/daily-report \
  -H "authorization: Bearer $FABRIC_HARNESS_API_TOKEN" \
  -H 'content-type: application/json' \
  -d '{"accountId":"account-42"}'

When FABRIC_ENV=production or NODE_ENV=production, the server fails closed. With no API token or custom authenticate hook, protected requests receive 401. Query-string tokens exist for limited transport compatibility; use the Authorization header for normal clients so tokens do not enter URLs or access logs.

For OIDC, role, permission, and tenant-aware ingress, use a structured server authenticator. See Authentication and RBAC for JWT validation and Databricks Apps presets.

Tenant selection

A structured Fabric principal can be bound to one tenant or an allowlist:

{
  id: 'analyst-123',
  kind: 'user',
  tenantIds: ['customer-42', 'customer-84'],
  permissions: ['agent:invoke', 'session:read'],
  roles: ['analyst'],
}

The caller selects an allowed tenant with X-Fabric-Tenant:

curl -X POST http://localhost:3000/jobs/daily-report \
  -H "authorization: Bearer $FABRIC_HARNESS_API_TOKEN" \
  -H 'x-fabric-tenant: customer-42' \
  -H 'content-type: application/json' \
  -d '{"accountId":"account-42"}'

A single-tenant principal binds implicitly. A multi-tenant allowlist requires an explicit selection, and selecting a tenant outside the allowlist returns 403.

Databricks authentication while local

Fabric runtime credentials come from environment variables or an explicit DatabricksPrincipal. For local development, the recommended path uses the same Databricks CLI OAuth profile for runtime tokens and deployment. Production Databricks Apps continue to use their app service principal and optional on-behalf-of user token.

Databricks CLI OAuth for local development

Authenticate once with the Databricks CLI:

databricks auth login \
  --host https://adb-1234567890123456.7.azuredatabricks.net \
  --profile fabric-harness

Then select that profile for Harness runtime token resolution:

.env.local
DATABRICKS_HOST=https://adb-1234567890123456.7.azuredatabricks.net
DATABRICKS_CONFIG_PROFILE=fabric-harness
DATABRICKS_AUTH_MODE=cli
DATABRICKS_MODEL=system.ai.gpt-oss-20b

Harness invokes databricks auth token --host ... --profile ... --output json, caches only the returned access token, and refreshes it every 30 minutes with --force-refresh. If the CLI cache is stale, it attempts databricks auth login --no-browser and retries. A pasted AI Gateway URL is normalized to the workspace origin before management or authentication URLs are constructed.

Explicit wiring is also available:

import { databricksIdentity } from '@fabric-harness/databricks';

const token = databricksIdentity({
  kind: 'cli-profile',
  host: process.env.DATABRICKS_HOST!,
  profile: 'fabric-harness',
});

PAT for one developer

Use a personal access token for a controlled, single-user development session:

.env.local
DATABRICKS_HOST=https://adb-1234567890123456.7.azuredatabricks.net
DATABRICKS_TOKEN=dapi...
DATABRICKS_WAREHOUSE_ID=0123456789abcdef
DATABRICKS_MODEL=system.ai.gpt-oss-20b
DATABRICKS_INFERENCE_MODE=auto

The process acts with the PAT owner's Databricks permissions. Do not use a developer PAT as an App or shared production identity. PAT lookup is explicit; DATABRICKS_BEARER is also accepted for headless compatibility with pre-fetched Databricks tokens.

OAuth M2M for production-like development

Use a service principal to reproduce CI and App-style authorization locally:

.env.local
DATABRICKS_HOST=https://adb-1234567890123456.7.azuredatabricks.net
DATABRICKS_CLIENT_ID=00000000-0000-0000-0000-000000000000
DATABRICKS_CLIENT_SECRET=resolve-from-your-secret-manager
DATABRICKS_WAREHOUSE_ID=0123456789abcdef
DATABRICKS_MODEL=system.ai.gpt-oss-20b
DATABRICKS_INFERENCE_MODE=auto

Fabric exchanges the client credentials at the workspace OIDC endpoint, caches the access token, refreshes before expiry, and deduplicates concurrent refreshes. If DATABRICKS_TOKEN and OAuth credentials are both present, the PAT wins; unset DATABRICKS_TOKEN when validating M2M behavior.

Run a live request after loading the environment:

fh run databricks-analyst \
  --question 'Describe main.sales.orders'

Explicit principal wiring

Use an explicit principal when one process needs more than one Databricks identity:

import { databricks } from '@fabric-harness/databricks';

const dbx = databricks({
  host: process.env.DATABRICKS_HOST!,
  principal: {
    kind: 'service-principal',
    host: process.env.DATABRICKS_HOST!,
    clientId: process.env.DATABRICKS_CLIENT_ID!,
    clientSecret: process.env.DATABRICKS_CLIENT_SECRET!,
  },
  model: 'system.ai.gpt-oss-20b',
  warehouseId: process.env.DATABRICKS_WAREHOUSE_ID,
  governance: { catalogs: ['main'] },
});

The token resolver is awaited for each Databricks operation. Raw credentials are not added to model messages, tool inputs, lineage labels, or logs.

OBO for a signed-in user

On-behalf-of authentication preserves a user's Unity Catalog grants:

const principal = {
  kind: 'on-behalf-of' as const,
  userToken: async () => currentUserAccessToken(),
  label: 'signed-in-analyst',
};

Fabric does not manufacture or persist an OBO token. Databricks Apps performs user consent and token refresh, then forwards the current user access token on each request. databricksApp().onBehalfOf() maps that request-scoped token to the Fabric actor and Unity Catalog principal.

For a protected diagnostic route, inspectDatabricksAppUserAuthorization() validates the forwarded token against the workspace current-user API and returns only identity plus issued/expiry metadata. It never returns the token or copies workspace error bodies. The reference App exposes this at GET /certification/obo for lifecycle certification.

Rendering diagram...

One interactive CLI authorization can certify both initial login and refresh. The prepare phase uses the selected U2M profile and stores only non-secret lifetime metadata. After the first one-hour token expires, verify asks the CLI for a refreshed token, calls the App again, and requires the same user, a newer issue time, and a later expiry:

databricks auth login --host "$DATABRICKS_HOST" --profile fabric-harness

DATABRICKS_APP_URL="$DATABRICKS_APP_URL" \
  DATABRICKS_PROFILE=fabric-harness \
  node scripts/certify-databricks-obo-lifecycle.mjs prepare

# Run after the expiry printed by prepare.
DATABRICKS_APP_URL="$DATABRICKS_APP_URL" \
  DATABRICKS_PROFILE=fabric-harness \
  node scripts/certify-databricks-obo-lifecycle.mjs verify

Runtime credentials versus deployment profiles

DATABRICKS_TOKEN or the OAuth environment variables authenticate calls made by the running agent. A Databricks CLI profile authenticates build deployment commands:

databricks auth login --host "$DATABRICKS_HOST" --profile analytics-dev
fh build --target databricks-app
fh deploy --target databricks-app --profile analytics-dev

Passing --profile does not change the identity used later by the deployed App. Databricks Apps supplies its service-principal identity at runtime; configure App resource permissions and secret references separately.

Local verification checklist

  1. Run fh agents and confirm the job and persistent-agent route names.
  2. Run with --mock before adding workspace credentials.
  3. Set only one Databricks credential mode at a time.
  4. Use OAuth M2M to reproduce unattended CI permissions.
  5. Set FABRIC_HARNESS_API_TOKEN to test authenticated local HTTP calls.
  6. Test one allowed and one denied Unity Catalog object.
  7. Verify the selected Fabric tenant appears in audit, lineage, and cost attribution.
  8. Use fh deploy --preview --target databricks-app --profile analytics-dev before deploying.

Continue with the Databricks quickstart to scaffold an App or Enterprise Databricks controls to configure OIDC, policy, durability, lineage, and cost enforcement.