feat(studio): enable Assistant tracing for High Compliance projects (#50759)

Assistant chats from High Compliance projects now flow to Braintrust
like any other project. The constraint that required suppressing them no
longer applies, see AI-1241 for the details.

`isTracingAllowed` now takes only the project region to maintain EU
exclusion. Traces also carry an `isHighComplianceProject` metadata
field, so the project's status at the time of the trace is recorded
rather than looked up later against a setting customers can toggle.

To verify, see [this sample
trace](https://www.braintrust.dev/app/supabase.io/p/Assistant/logs?r=afabbdcc-aa89-446e-aa52-78aaa90d44a4&v=Production&s=afabbdcc-aa89-446e-aa52-78aaa90d44a4&tvt=trace)
from a High Compliance project on staging which indicates that tracing
is now enabled for these projects and that it carries metadata showing
the high compliance status.

| High Compliance project setting | `isHighComplianceProject` metadata |
|--------|--------|
| <img width="1554" height="454" alt="CleanShot 2026-09-22 at 5 14 58
PM@2x"
src="https://github.com/user-attachments/assets/23901c6e-0d79-44e8-a6dd-43cdedba1799"
/> | <img width="1674" height="990" alt="CleanShot 2026-09-22 at 5 17 40
PM@2x"
src="https://github.com/user-attachments/assets/fb2fba55-bcc1-4136-a432-b33a5c7f9ca2"
/> |

Closes AI-1241


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **Changes**
* AI project compliance information is now represented by a unified
high-compliance project status.
* AI response tracing is now determined by project region: tracing
remains disabled for EU and unknown regions, while known non-EU regions
are eligible.
* AI feedback and SQL generation now use the updated compliance and
regional handling.
* **Tests**
* Updated coverage to reflect the revised compliance and tracing
behavior.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
Matt Rossman authored and GitHub committed 2026-09-23 09:39:01 -04:00
1 parent 4730e3a640
commit fd5ef806d6
9 files changed
+46 -175

No files matched your search

+15 -53
View File
@@ -10,10 +10,6 @@ vi.mock('@/data/projects/project-detail-query', () => ({
getProjectDetail: vi.fn(),
}))
vi.mock('@/data/subscriptions/org-subscription-query', () => ({
getOrgSubscription: vi.fn(),
}))
vi.mock('@/data/config/project-settings-v2-query', () => ({
getProjectSettings: vi.fn(),
}))
@@ -22,10 +18,6 @@ vi.mock('@/hooks/misc/useOrgOptedIntoAi', () => ({
getAiOptInLevel: vi.fn(),
}))
vi.mock('@/components/interfaces/Billing/Subscription/Subscription.utils', () => ({
subscriptionHasHipaaAddon: vi.fn(),
}))
vi.mock('@/data/entitlements/entitlements-query', () => ({
checkEntitlement: vi.fn(),
}))
@@ -37,29 +29,22 @@ const PROJECT_REF = 'test-project'
describe('getAIDetails', () => {
let mockGetOrganizations: ReturnType<typeof vi.fn>
let mockGetOrgSubscription: ReturnType<typeof vi.fn>
let mockGetProjectDetail: ReturnType<typeof vi.fn>
let mockGetProjectSettings: ReturnType<typeof vi.fn>
let mockGetAiOptInLevel: ReturnType<typeof vi.fn>
let mockSubscriptionHasHipaaAddon: ReturnType<typeof vi.fn>
let mockCheckEntitlement: ReturnType<typeof vi.fn>
beforeEach(async () => {
const orgsQuery = await import('@/data/organizations/organizations-query')
const subscriptionQuery = await import('@/data/subscriptions/org-subscription-query')
const projectQuery = await import('@/data/projects/project-detail-query')
const settingsQuery = await import('@/data/config/project-settings-v2-query')
const aiHook = await import('@/hooks/misc/useOrgOptedIntoAi')
const subscriptionUtils =
await import('@/components/interfaces/Billing/Subscription/Subscription.utils')
const entitlementsQuery = await import('@/data/entitlements/entitlements-query')
mockGetOrganizations = vi.mocked(orgsQuery.getOrganizations)
mockGetOrgSubscription = vi.mocked(subscriptionQuery.getOrgSubscription)
mockGetProjectDetail = vi.mocked(projectQuery.getProjectDetail)
mockGetProjectSettings = vi.mocked(settingsQuery.getProjectSettings)
mockGetAiOptInLevel = vi.mocked(aiHook.getAiOptInLevel)
mockSubscriptionHasHipaaAddon = vi.mocked(subscriptionUtils.subscriptionHasHipaaAddon)
mockCheckEntitlement = vi.mocked(entitlementsQuery.checkEntitlement)
mockGetOrganizations.mockResolvedValue([
@@ -71,8 +56,6 @@ describe('getAIDetails', () => {
organization_id: 1,
})
mockGetProjectSettings.mockResolvedValue({ is_sensitive: false })
mockGetOrgSubscription.mockResolvedValue({ addons: [] })
mockSubscriptionHasHipaaAddon.mockReturnValue(false)
mockCheckEntitlement.mockResolvedValue({ hasAccess: false })
mockGetAiOptInLevel.mockReturnValue('schema')
})
@@ -87,15 +70,26 @@ describe('getAIDetails', () => {
expect(result).toEqual({
aiOptInLevel: 'schema',
hasAccessToAdvanceModel: false,
hasHipaaAddon: false,
orgId: 1,
orgSlug: ORG_SLUG,
planId: 'pro',
region: 'us-east-1',
isSensitive: false,
isHighComplianceProject: false,
})
})
it('flags a High Compliance project', async () => {
mockGetProjectSettings.mockResolvedValue({ is_sensitive: true })
const result = await getAIDetails({
orgSlug: ORG_SLUG,
projectRef: PROJECT_REF,
authorization: AUTH,
})
expect(result.isHighComplianceProject).toBe(true)
})
it('calls getAiOptInLevel with the matched org opt_in_tags', async () => {
const opt_in_tags = ['AI_SQL_GENERATOR_OPT_IN']
mockGetOrganizations.mockResolvedValue([
@@ -144,7 +138,6 @@ describe('getAIDetails', () => {
await getAIDetails({ orgSlug: ORG_SLUG, projectRef: PROJECT_REF, authorization: AUTH })
expect(mockGetOrganizations).toHaveBeenCalledWith({ headers: HEADERS })
expect(mockGetOrgSubscription).toHaveBeenCalledWith({ orgSlug: ORG_SLUG }, undefined, HEADERS)
expect(mockCheckEntitlement).toHaveBeenCalledWith(
ORG_SLUG,
'assistant.advance_model',
@@ -192,16 +185,14 @@ describe('getAIDetails', () => {
expect(result.planId).toBeUndefined()
})
it('leaves hasHipaaAddon undefined so tracing checks fail closed', async () => {
mockSubscriptionHasHipaaAddon.mockReturnValue(false)
it('leaves the region undefined so tracing checks fail closed', async () => {
const result = await getAIDetails({
orgSlug: ORG_SLUG,
projectRef: PROJECT_REF,
authorization: AUTH,
})
expect(result.hasHipaaAddon).toBeUndefined()
expect(result.region).toBeUndefined()
})
})
@@ -231,33 +222,4 @@ describe('getAIDetails', () => {
expect(result.aiOptInLevel).toBe('disabled')
expect(result.region).toBeUndefined()
})
it('keeps the opt-in level for a sensitive project in a HIPAA org', async () => {
mockSubscriptionHasHipaaAddon.mockReturnValue(true)
mockGetAiOptInLevel.mockReturnValue('schema_and_log_and_data')
mockGetProjectSettings.mockResolvedValue({ is_sensitive: true })
const result = await getAIDetails({
orgSlug: ORG_SLUG,
projectRef: PROJECT_REF,
authorization: AUTH,
})
expect(result.aiOptInLevel).toBe('schema_and_log_and_data')
expect(result.hasHipaaAddon).toBe(true)
expect(result.isSensitive).toBe(true)
})
it('keeps the opt-in level for a sensitive project outside a HIPAA org', async () => {
mockSubscriptionHasHipaaAddon.mockReturnValue(false)
mockGetProjectSettings.mockResolvedValue({ is_sensitive: true })
const result = await getAIDetails({
orgSlug: ORG_SLUG,
projectRef: PROJECT_REF,
authorization: AUTH,
})
expect(result.aiOptInLevel).toBe('schema')
})
})
+14 -22
View File
@@ -1,20 +1,18 @@
import { subscriptionHasHipaaAddon } from '@/components/interfaces/Billing/Subscription/Subscription.utils'
import { getProjectSettings } from '@/data/config/project-settings-v2-query'
import { checkEntitlement } from '@/data/entitlements/entitlements-query'
import { getOrganizations } from '@/data/organizations/organizations-query'
import { getProjectDetail } from '@/data/projects/project-detail-query'
import { getOrgSubscription } from '@/data/subscriptions/org-subscription-query'
import { getAiOptInLevel, type AiOptInLevel } from '@/hooks/misc/useOrgOptedIntoAi'
export type AIDetails = {
aiOptInLevel: AiOptInLevel
hasAccessToAdvanceModel: boolean
hasHipaaAddon: boolean | undefined
orgId: number | undefined
orgSlug: string | undefined
planId: string | undefined
region: string | undefined
isSensitive: boolean | null | undefined
/** "High Compliance" in the dashboard, `is_sensitive` in the platform API. */
isHighComplianceProject: boolean | undefined
}
// Resolves the AI opt-in level, model access and tracing inputs for one org/project pair.
@@ -34,19 +32,17 @@ export const getAIDetails = async ({
...(authorization && { Authorization: authorization }),
}
const [organizations, subscription, advanceModelAccess, project, projectSettings] =
await Promise.all([
getOrganizations({ headers }),
getOrgSubscription({ orgSlug }, undefined, headers),
checkEntitlement(orgSlug, 'assistant.advance_model', undefined, headers),
// skipWake: only organization_id and region are needed, neither requires a running project
getProjectDetail({ ref: projectRef, skipWake: true }, undefined, headers),
getProjectSettings({ projectRef }, undefined, headers),
])
const [organizations, advanceModelAccess, project, projectSettings] = await Promise.all([
getOrganizations({ headers }),
checkEntitlement(orgSlug, 'assistant.advance_model', undefined, headers),
// skipWake: only organization_id and region are needed, neither requires a running project
getProjectDetail({ ref: projectRef, skipWake: true }, undefined, headers),
getProjectSettings({ projectRef }, undefined, headers),
])
const selectedOrg = organizations.find((org) => org.slug === orgSlug)
const region = project?.region
const isSensitive = projectSettings?.is_sensitive
const isHighComplianceProject = projectSettings?.is_sensitive ?? undefined
const isProjectInOrg = selectedOrg !== undefined && project?.organization_id === selectedOrg.id
@@ -54,26 +50,22 @@ export const getAIDetails = async ({
return {
aiOptInLevel: 'disabled',
hasAccessToAdvanceModel: false,
// Undefined rather than false so isTracingAllowed fails closed
hasHipaaAddon: undefined,
orgId: undefined,
orgSlug: undefined,
planId: undefined,
region,
isSensitive,
// Undefined rather than the real region so isTracingAllowed fails closed
region: undefined,
isHighComplianceProject: undefined,
}
}
const hasHipaaAddon = subscriptionHasHipaaAddon(subscription)
return {
aiOptInLevel: getAiOptInLevel(selectedOrg.opt_in_tags),
hasAccessToAdvanceModel: advanceModelAccess.hasAccess,
hasHipaaAddon,
orgId: selectedOrg.id,
orgSlug: selectedOrg.slug,
planId: selectedOrg.plan.id,
region,
isSensitive,
isHighComplianceProject,
}
}
+7 -60
View File
@@ -2,71 +2,18 @@ import { describe, expect, it } from 'vitest'
import { isTracingAllowed } from './braintrust-logger'
const baseAllowed = {
orgHasHipaaAddon: false,
projectIsSensitive: false,
projectRegion: 'us-east-1',
}
describe('isTracingAllowed', () => {
it('allows tracing when all flags are explicitly off/non-EU', () => {
expect(isTracingAllowed(baseAllowed)).toBe(true)
})
it('disallows tracing when HIPAA addon is active and project is sensitive', () => {
expect(
isTracingAllowed({ ...baseAllowed, orgHasHipaaAddon: true, projectIsSensitive: true })
).toBe(false)
})
it('allows tracing when HIPAA addon is active but project is not sensitive', () => {
expect(
isTracingAllowed({ ...baseAllowed, orgHasHipaaAddon: true, projectIsSensitive: false })
).toBe(true)
})
it('allows tracing when project is sensitive but no HIPAA addon', () => {
expect(
isTracingAllowed({ ...baseAllowed, orgHasHipaaAddon: false, projectIsSensitive: true })
).toBe(true)
it('allows tracing for non-EU regions', () => {
expect(isTracingAllowed({ projectRegion: 'us-east-1' })).toBe(true)
expect(isTracingAllowed({ projectRegion: 'ap-southeast-1' })).toBe(true)
})
it('disallows tracing for EU regions', () => {
expect(isTracingAllowed({ ...baseAllowed, projectRegion: 'eu-west-1' })).toBe(false)
expect(isTracingAllowed({ ...baseAllowed, projectRegion: 'eu-central-1' })).toBe(false)
expect(isTracingAllowed({ projectRegion: 'eu-west-1' })).toBe(false)
expect(isTracingAllowed({ projectRegion: 'eu-central-1' })).toBe(false)
})
it('allows tracing for non-EU regions', () => {
expect(isTracingAllowed({ ...baseAllowed, projectRegion: 'ap-southeast-1' })).toBe(true)
})
it('allows tracing when HIPAA addon is false and is_sensitive is null (DB default)', () => {
expect(isTracingAllowed({ ...baseAllowed, projectIsSensitive: null })).toBe(true)
})
it('disallows tracing when HIPAA addon is unknown and is_sensitive is null', () => {
expect(
isTracingAllowed({ ...baseAllowed, orgHasHipaaAddon: undefined, projectIsSensitive: null })
).toBe(false)
})
it('disallows tracing when flags are undefined (unknown = restricted)', () => {
expect(
isTracingAllowed({
orgHasHipaaAddon: undefined,
projectIsSensitive: undefined,
projectRegion: undefined,
})
).toBe(false)
expect(isTracingAllowed({ ...baseAllowed, projectRegion: undefined })).toBe(false)
expect(isTracingAllowed({ ...baseAllowed, orgHasHipaaAddon: undefined })).toBe(false)
// projectIsSensitive unknown only matters when orgHasHipaaAddon is also unknown
expect(
isTracingAllowed({
...baseAllowed,
orgHasHipaaAddon: undefined,
projectIsSensitive: undefined,
})
).toBe(false)
it('disallows tracing when the region is unknown', () => {
expect(isTracingAllowed({ projectRegion: undefined })).toBe(false)
})
})
+2 -18
View File
@@ -13,25 +13,9 @@ if (IS_TRACING_ENABLED) {
})
}
// Checks compliance flags for tracing and returns true only when all checks pass.
// Defaults to disabling tracing when states are unknown.
export function isTracingAllowed({
orgHasHipaaAddon,
projectIsSensitive,
projectRegion,
}: {
orgHasHipaaAddon: boolean | undefined
projectIsSensitive: boolean | null | undefined
projectRegion: string | undefined
}) {
// Disable tracing for EU (or unknown) regions
// Checks that the project is outside the EU. An unknown region fails closed.
export function isTracingAllowed({ projectRegion }: { projectRegion: string | undefined }) {
if (projectRegion === undefined || projectRegion.startsWith('eu-')) return false
// Disable tracing for orgs with an unknown HIPAA addon state
if (orgHasHipaaAddon === undefined) return false
// Disable tracing for projects within a HIPAA-enabled org that are sensitive (or unknown sensitivity)
if (orgHasHipaaAddon && projectIsSensitive !== false) return false
return true
}
@@ -41,6 +41,7 @@ export async function generateAssistantResponse({
orgId,
orgSlug,
planId,
isHighComplianceProject,
includesLogsSnippets,
isExplorerEnabled,
systemProviderOptions,
@@ -63,6 +64,7 @@ export async function generateAssistantResponse({
orgId?: number
orgSlug?: string
planId?: string
isHighComplianceProject?: boolean
/** Whether any user message in the conversation attached a logs (ClickHouse) query. */
includesLogsSnippets?: boolean
isExplorerEnabled?: boolean
@@ -175,6 +177,7 @@ export async function generateAssistantResponse({
orgId,
orgSlug,
planId,
isHighComplianceProject,
requestedModel,
gitBranch: process.env.VERCEL_GIT_COMMIT_REF,
environment: process.env.NEXT_PUBLIC_ENVIRONMENT,
-2
View File
@@ -14,9 +14,7 @@ vi.mock('@/lib/ai/ai-details', () => ({
getAIDetails: vi.fn().mockResolvedValue({
aiOptInLevel: 'schema_and_log_and_data',
hasAccessToAdvanceModel: true,
hasHipaaAddon: false,
region: 'us-east-1',
isSensitive: false,
}),
}))
-2
View File
@@ -13,9 +13,7 @@ vi.mock('@/lib/ai/ai-details', () => ({
getAIDetails: vi.fn().mockResolvedValue({
aiOptInLevel: 'schema_and_log_and_data',
hasAccessToAdvanceModel: true,
hasHipaaAddon: false,
region: 'us-east-1',
isSensitive: false,
}),
}))