Permissions

Definite on-prem has a layered permission model. The layers are independent — a user's application role does not grant data access, and sharing a resource does not change anyone's role.

  1. Application roles — what a user can do in the app.
  2. Content permissions — who can see and edit each app, thread, project, query, automation, agent, or integration.
  3. Data access — which lakehouse tables a user's queries may read.
  4. Per-user query capabilities — whether a user may submit raw SQL or use Fast Explorer.

The deployment is single-tenant: one workspace, no teams. Every check is enforced server-side in the API — the API is the only gateway to the lakehouse, so there is no way around it. Admins bypass all content and data-access checks.

All three layers are managed from three equivalent surfaces: the web UI (Settings → Members & access), the definite run permission CLI, and Fi's permissions skill (admin sessions only).

1. Application roles

Every user has exactly one role:

RoleCan do
viewerRead and run content shared with them. Can create and operate their own read-only Fi threads, but cannot create or modify other content.
editorEverything a viewer can, plus create, edit, and share their own apps, threads, projects, saved queries, automations, and agents.
adminEverything. Manages members and roles, integrations and their credentials, data-access roles, and workspace settings. Bypasses all content and data-access checks.

The role is fixed per user — there is no custom-role builder. Only admins can change roles, and the system refuses to demote the last remaining admin.

  • UI: Settings → Members & access → Members. Each row has a role selector (your own row is locked to prevent self-lockout).
  • CLI: definite run permission users, then definite run permission set-role <user_id> --role editor.

2. Content permissions

Every user-facing resource — app, thread, project, saved query, automation, agent, integration — is private to its creator (plus admins) when created. Sharing is per-resource:

  • Private — only the creator and admins can see it.
  • Shared with a userview (open/run) or edit (also modify and re-share). The creator always keeps edit access.
  • Workspace-visible — every authenticated user can view it. The workspace grant carries its own access level: view, or edit, which also lets users with the editor or admin role modify and run the resource. Users with the viewer role never gain edit from the workspace grant.

Integrations are a special case: they can be shared view only — their public metadata becomes visible, but decrypting or testing credentials, and creating/editing integrations, stays admin-only regardless of any grant.

A workspace edit grant on a project applies to the project itself only. It does not cascade edit access to the project's linked contents — those still require an elevated project membership or a named grant.

The default for newly created content is configurable per deployment — see Deployment settings.

Support-authored content is an exception. Content created by the built-in support user (fi@definite.app, used by Fi support sessions) is always created workspace-shared with edit access (view for integrations), regardless of DEFAULT_CONTENT_VISIBILITY. Support builds content for the customer, so customer editors can run and maintain it. Admins can demote the workspace grant to view or remove it from the Share dialog. On upgrade, a one-shot startup repair applies the same rule to support-authored content that already exists; the repair runs exactly once, so it never re-upgrades a grant an admin demoted.

  • UI: every resource page has a Share button that opens a dialog — toggle private ⇄ workspace, set the workspace access level (Viewer/Editor), add/remove people, set each person's access level. (Agents are shareable via the API and CLI but do not yet have a Share button in the UI.)
  • CLI: definite run permission grants <kind> <id> to see current access; share / unshare to change it. <kind> is one of app, thread, project, query, automation, integration, agent; <id> is the resource's primary-key id.

3. Data access

Data access controls which lakehouse tables a user's SQL can read. It is default-deny: a user with no data-access grant can query nothing.

Grants are grouped into named data-access roles:

  • A data-access role holds a set of (schema, table) grants. * is the only wildcard — sales.* is every table in the sales schema, *.* is every table.
  • A user may hold multiple data-access roles; their effective access is the union of all their roles' grants.
  • One role can be marked default — new users are auto-assigned it.

These roles are entirely separate from the viewer/editor/admin application roles above.

How it is enforced

When a non-admin runs SQL (the Query page, a Fi run, the catalog), the API parses the statement with DuckDB's own parser before executing it:

  • Non-SELECT statements (INSERT/CREATE/ATTACH/COPY/…) are rejected outright.
  • Every base table the query references is checked against the caller's resolved grants; the query is refused if any table is not granted.
  • The catalog (/catalog/tables) is filtered to the tables the caller may read, so the data catalog only shows what they can actually query.

Admins bypass all of this. information_schema / pg_catalog are always readable; DuckLake internal metadata is admin-only.

  • UI: Settings → Members & access → Data Access — create roles, edit their table grants, assign users.
  • CLI: definite run permission data-access roles | create-role | update-role | delete-role | set-grants | role-users | assign | unassign.

4. Per-user query capabilities

Administrators can independently toggle two capabilities from each active member's overflow menu in Settings → Members & access:

  • Raw SQL controls direct user-authored SQL through Query and CSV export, definite run query, definite connect, Fi, and MCP. It defaults on for existing and new users. When off, semantic queries and shared data apps remain available; Fi uses only certified semantic models.
  • Fast Explorer controls the browser-side Arrow/DuckDB table explorer. It remains independent from Raw SQL, so changing one never changes the other.

Raw-SQL denials are recorded in the audit log as query.raw_sql.denied without storing the submitted SQL text. The Raw SQL capability applies to all application roles, including admins and API tokens owned by the user.

Deployment settings

Three process-level settings tune the defaults. They are read from environment variables on the definite-api container (pydantic-settings). They are not yet first-class config.yaml keys — set them via a Helm values override on the api Deployment's env, or leave the defaults.

Env varDefaultEffect
DEFAULT_CONTENT_VISIBILITYprivateVisibility for newly created content. private = creator + admins only; workspace = an everyone-view grant is added on creation. Support-authored content ignores this setting and is always workspace-shared with edit (see above).
DATA_ACCESS_DEFAULT_ROLE(empty)Name of the data-access role auto-assigned to new users. Empty = new users get no data access until an admin grants a role. Created on startup if it does not exist.
DATA_ACCESS_DEFAULT_SCHEMAmainSchema used to resolve unqualified table names when checking data-access grants.

Content that existed before the permission system was introduced is backfilled workspace-visible, so an upgrade never hides anything that was previously visible.

Fi

Viewers can create their own Fi threads. Viewer runs use a read-only tool set: semantic queries, documentation, optional web access, and presentation tools; they never receive shell/file mutation, integration-secret, or platform- mutation tools. If Raw SQL is enabled for that viewer, bounded SQL and grant-filtered catalog tools are also available.

When an admin uses Fi, Fi can manage all permission layers conversationally — "make Priya an editor", "share the revenue app with the team", "let the analysts query sales.orders". Fi drives the same definite run permission CLI under the hood. The skill is surfaced only to admin sessions; for any other user the underlying API calls are rejected anyway.

See also: CLI reference → definite run permission.