Files
supabase/apps/studio/lib/api/generate-v4.test.ts
T
Matt Rossman 8aeacc6152 feat(assistant): disable Braintrust tracing for EU regions and DPA signers (#44504)
**Changes**

- Extracted tracing conditional to an `isTracingAllowed` helper with
unit tests (the function is simple but sensitive hence the extra testing
precaution)
- Disables Braintrust tracing for projects in EU database regions
(region prefix `eu-`) to address GDPR data residency concerns
- Disables Braintrust tracing for orgs whose owners have signed the
previous DPA, as a stopgap during the 30-day notice period for the
updated DPA that adds Braintrust as a subprocessor
- Refactored `org-ai-details.ts` → `ai-details.ts`, splitting
`getOrgAIDetails` into separate org and project helpers to cleanly scope
the EU-region check at the project level

DPA check uses the newly added `/documents/dpa-signed` endpoint from
https://github.com/supabase/platform/pull/31060. This PR includes
regenerated `api.d.ts` and `platform.d.ts` from running `pnpm codegen`
in `packages/api-types` to get type safety on this new endpoint.

Note tracing is still yet to be activated in production, this is a
preparatory step.

**To verify**

Send a chat message and check for the `x-braintrust-span-id` response
header on `POST /api/ai/sql/generate-v4` — it should be absent for
DPA-signed orgs or EU-region projects, and present otherwise.

<img width="3594" height="1992" alt="CleanShot 2026-04-03 at 14 28
58@2x"
src="https://github.com/user-attachments/assets/4c91d7ad-2604-4531-a78e-dedf41632fa5"
/>

If you have access to the Braintrust dashboard, you can also verify
whether logs are produced or not in the Assistant project there.


Closes AI-570
Closes AI-569

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

* **New Features**
  * Tracks organization DPA signing and detects EU-region projects
* Assistant tracing now follows a combined compliance policy (HIPAA
addon, DPA, project sensitivity, region)
  * Added helpers to fetch org and project AI details

* **Documentation**
* Expanded API docs with additional examples and clarified parameter
descriptions
  * Added response schemas for subscription preview and document status

* **Tests**
* Added/updated tests covering DPA/region behavior and tracing policy
enforcement
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-04-07 13:09:34 -04:00

89 lines
2.2 KiB
TypeScript

import { UIMessage } from 'ai'
import { sanitizeMessagePart } from 'lib/ai/tools/tool-sanitizer'
import { expect, test, vi } from 'vitest'
// End of third-party imports
import generateV4 from '../../pages/api/ai/sql/generate-v4'
vi.mock('lib/ai/tools/tool-sanitizer', () => ({
sanitizeMessagePart: vi.fn((part) => part),
}))
test('generateV4 calls the tool sanitizer', async () => {
const mockReq = {
method: 'POST',
headers: {
authorization: 'Bearer test-token',
},
body: {
messages: [
{
id: 'test-msg-id',
role: 'assistant',
parts: [
{
type: 'tool-execute_sql',
state: 'output-available',
toolCallId: 'test-tool-call-id',
input: { sql: 'SELECT * FROM users' },
output: [{ id: 1, name: 'test-output' }],
},
],
},
] satisfies UIMessage[],
projectRef: 'test-project',
connectionString: 'test-connection',
orgSlug: 'test-org',
},
on: vi.fn(),
}
const mockRes = {
status: vi.fn(() => mockRes),
json: vi.fn(() => mockRes),
setHeader: vi.fn(() => mockRes),
}
vi.mock('lib/ai/ai-details', () => ({
getOrgAIDetails: vi.fn().mockResolvedValue({
aiOptInLevel: 'schema_and_log_and_data',
hasAccessToAdvanceModel: true,
isDpaSigned: false,
}),
getProjectAIDetails: vi.fn().mockResolvedValue({
region: 'us-east-1',
isSensitive: false,
}),
}))
vi.mock('lib/ai/model', () => ({
getModel: vi.fn().mockResolvedValue({
modelParams: { model: {} },
promptProviderOptions: {},
}),
}))
vi.mock('data/sql/execute-sql-query', () => ({
executeSql: vi.fn().mockResolvedValue({ result: [] }),
}))
vi.mock('lib/ai/tools', () => ({
getTools: vi.fn().mockResolvedValue({}),
}))
vi.mock('ai', async () => {
const actual = await vi.importActual('ai')
return {
...actual,
streamText: vi.fn().mockReturnValue({
pipeUIMessageStreamToResponse: vi.fn(),
}),
}
})
await generateV4(mockReq as any, mockRes as any)
expect(sanitizeMessagePart).toHaveBeenCalled()
})