Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 0 additions & 5 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,6 @@ on:
branches: [main, staging, dev]
pull_request:
branches: [main, staging, dev, e2e/settings-playwright]
# Docs content and markdown don't affect the app build or images; push
# runs stay unfiltered because they feed the deploy pipeline.
paths-ignore:
- 'apps/docs/content/**'
- '**/*.md'

concurrency:
group: ci-${{ github.ref }}
Expand Down
5 changes: 5 additions & 0 deletions .github/workflows/migrations.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ jobs:
name: Apply Database Migrations
runs-on: ${{ (vars.CI_PROVIDER == '' || vars.CI_PROVIDER == 'blacksmith') && 'blacksmith-4vcpu-ubuntu-2404' || 'ubuntu-latest' }}
timeout-minutes: 45
environment: ${{ inputs.environment }}

steps:
- name: Checkout code
Expand Down Expand Up @@ -62,6 +63,10 @@ jobs:
DATABASE_URL: ${{ inputs.environment == 'production' && secrets.DATABASE_URL || inputs.environment == 'staging' && secrets.STAGING_DATABASE_URL || inputs.environment == 'dev' && secrets.DEV_DATABASE_URL || '' }}
MIGRATION_DATABASE_URL: ${{ inputs.environment == 'production' && secrets.MIGRATION_DATABASE_URL || inputs.environment == 'staging' && secrets.STAGING_MIGRATION_DATABASE_URL || '' }}
ENVIRONMENT: ${{ inputs.environment }}
# One-shot migration 0266 inputs. Configure them independently in
# each protected GitHub environment before its first rollout.
SSO_PROVIDER_WRITES_QUIESCED: ${{ secrets.SSO_PROVIDER_WRITES_QUIESCED }}
SSO_AUDIT_APPROVED_PROVIDER_IDS: ${{ secrets.SSO_AUDIT_APPROVED_PROVIDER_IDS }}
run: |
if [ -z "$DATABASE_URL" ]; then
echo "ERROR: no database URL secret resolved for environment '${ENVIRONMENT}'" >&2
Expand Down
30 changes: 20 additions & 10 deletions .github/workflows/test-build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -261,10 +261,11 @@ jobs:
run: bunx turbo run build --filter=sim

settings-e2e:
name: Settings E2E (informational)
runs-on: blacksmith-8vcpu-ubuntu-2404
name: Settings E2E
# The production Next build peaks above the free GitHub runner's memory
# ceiling. Keep the break-glass provider predicate aligned with Build App.
runs-on: ${{ (vars.CI_PROVIDER == '' || vars.CI_PROVIDER == 'blacksmith') && 'blacksmith-8vcpu-ubuntu-2404' || 'linux-x64-8-core' }}
timeout-minutes: 45
continue-on-error: true

services:
postgres:
Expand Down Expand Up @@ -295,35 +296,44 @@ jobs:
with:
node-version: 22

- name: Mount Bun cache (Sticky Disk)
uses: useblacksmith/stickydisk@4c034ba57b706cf0e3b4b0ce098c2a3b1071580c # v1
- name: Mount Bun cache
uses: ./.github/actions/cache-mount
with:
provider: ${{ vars.CI_PROVIDER }}
key: ${{ github.repository }}-bun-cache-${{ github.event_name }}${{ github.event.pull_request.head.repo.fork && '-fork' || '' }}
path: ~/.bun/install/cache

- name: Mount node_modules (Sticky Disk)
uses: useblacksmith/stickydisk@4c034ba57b706cf0e3b4b0ce098c2a3b1071580c # v1
- name: Mount node_modules
uses: ./.github/actions/cache-mount
with:
provider: ${{ vars.CI_PROVIDER }}
key: ${{ github.repository }}-node-modules-${{ github.event_name }}${{ github.event.pull_request.head.repo.fork && '-fork' || '' }}
path: ./node_modules

- name: Mount Playwright browsers (Sticky Disk)
uses: useblacksmith/stickydisk@4c034ba57b706cf0e3b4b0ce098c2a3b1071580c # v1
- name: Mount Playwright browsers
uses: ./.github/actions/cache-mount
with:
provider: ${{ vars.CI_PROVIDER }}
key: ${{ github.repository }}-playwright-browsers-${{ github.event_name }}${{ github.event.pull_request.head.repo.fork && '-fork' || '' }}
path: ~/.cache/ms-playwright

- name: Install dependencies
run: bun install --frozen-lockfile

- name: Rehearse SSO migration hardening
working-directory: packages/db
env:
E2E_PG_ADMIN_URL: 'postgresql://postgres:postgres@127.0.0.1:5432/postgres'
run: bun run db:rehearse-sso-migration

- name: Configure E2E hostnames
run: echo "127.0.0.1 e2e.sim.ai mcp.e2e.sim.ai" | sudo tee -a /etc/hosts

- name: Install Chromium
working-directory: apps/sim
run: bun run test:e2e:install-browsers -- --with-deps

- name: Run settings E2E foundation
- name: Run complete settings E2E suite
timeout-minutes: 40
working-directory: apps/sim
env:
Expand Down
6 changes: 2 additions & 4 deletions apps/docs/app/api/chat/route.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { openai } from '@ai-sdk/openai'
import { convertToModelMessages, stepCountIs, streamText, tool, type UIMessage } from 'ai'
import { sql } from 'drizzle-orm'
import { z } from 'zod'
import { db, docsEmbeddings } from '@/lib/db'
import { generateSearchEmbedding } from '@/lib/embeddings'
import { searchDocsInputSchema } from '@/lib/search-tool-schema'

export const runtime = 'nodejs'
export const maxDuration = 30
Expand Down Expand Up @@ -332,9 +332,7 @@ export async function POST(req: Request) {
searchDocs: tool({
description:
'Search the Sim documentation for relevant content. Use this before answering any question about Sim.',
inputSchema: z.object({
query: z.string().describe('A focused natural-language search query.'),
}),
inputSchema: searchDocsInputSchema,
execute: async ({ query }) => searchDocs(query, locale),
}),
},
Expand Down
64 changes: 48 additions & 16 deletions apps/docs/content/docs/en/platform/enterprise/sso.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -281,8 +281,8 @@ Self-hosted deployments use environment variables instead of the billing/plan ch
SSO_ENABLED=true
NEXT_PUBLIC_SSO_ENABLED=true

# Enable only after the SSO schema migration, provider audit, and approved
# domain-verification backfill described below.
# Enable only after the one-shot pre-migration audit, schema migration,
# separately approved domain-ownership backfill, and live TXT check below.
SSO_DOMAIN_VERIFICATION_ENABLED=false

# Required if you want users auto-added to your organization on first SSO sign-in
Expand All @@ -307,22 +307,54 @@ SSO_TRUSTED_PROVIDER_IDS=custom-oidc,partner-saml
</Callout>

<Callout type="warning">
For upgrades, leave domain verification disabled until the database migration
has landed and
`SSO_AUDIT_APPROVED_PROVIDER_IDS=reviewed-provider-ids bun run --cwd packages/db db:audit-sso-providers`
passes. The approval list records the providers whose existing account links
and active sessions operators chose to retain; omit a provider until its
links are migrated or removed and its sessions are revoked. The audit reports
linked-user and active-session counts and requires one owner organization and
one registrable domain per provider.
Remediate legacy user-scoped, public-suffix, duplicate, or overlapping rows;
explicitly backfill `domain_verified` only for domains your operators have
verified; quiesce SSO writes and rerun the audit immediately before enabling
the flag. Internal pseudo-domains are not eligible for verified-domain
enforcement. Existing internal-domain providers can remain unchanged while
the flag is off, but must move to a registrable domain before activation.
Migration 0266 runs a one-shot legacy-provider audit before changing the
schema. If the legacy provider table exists, disable SSO/provider mutations
and keep them disabled through migration, then set
`SSO_PROVIDER_WRITES_QUIESCED=true` even when the table is empty. Provide
`SSO_AUDIT_APPROVED_PROVIDER_IDS` only for providers whose existing account
links and active sessions operators chose to retain. Omit a provider until
its links are migrated or removed and its sessions are revoked.
Remediate user-scoped, public-suffix, unknown-suffix, duplicate, or
overlapping rows before retrying. The quiescence flag is an acknowledgement,
not a technical lock against an older running application.

Account/session approval does not prove domain ownership. After migration,
transactionally backfill `domain_verified` only for domains supported by
independently reviewed ownership evidence, read the values back, and keep
unknown rows false. Complete a live TXT verification before enabling
`SSO_DOMAIN_VERIFICATION_ENABLED`. Internal pseudo-domains are not eligible
for verified-domain enforcement.
</Callout>

For the first 0266 upgrade with Docker Compose, set the one-shot migration
inputs in the Compose environment while writes are actually disabled:

```bash
export SSO_PROVIDER_WRITES_QUIESCED=true
export SSO_AUDIT_APPROVED_PROVIDER_IDS=reviewed-provider-ids
docker compose -f docker-compose.prod.yml up
```

For Helm, use inline migration values or a dedicated Secret:

```yaml
migrations:
ssoPreflight:
providerWritesQuiesced: "true"
auditApprovedProviderIds: "reviewed-provider-ids"
# Or set existingSecret to a Secret containing
# SSO_PROVIDER_WRITES_QUIESCED and SSO_AUDIT_APPROVED_PROVIDER_IDS.
```

Secret-backed keys are optional at Kubernetes pod admission so removing retired
one-shot keys cannot block deployments after 0266. A pending 0266 migration
still fails closed unless `SSO_PROVIDER_WRITES_QUIESCED` resolves to `true`;
`SSO_AUDIT_APPROVED_PROVIDER_IDS` may be absent when no linked providers require
an explicit approval.

Remove the one-shot values after 0266 is journaled. Do not leave provider writes
disabled or interpret the flag itself as quiescence.

You can register providers through the **Settings UI** (same as cloud) or by running the registration script directly against your database.

### Script-based registration
Expand Down
24 changes: 24 additions & 0 deletions apps/docs/lib/search-tool-schema.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { describe, expect, it } from 'vitest'
import { validateSearchDocsInput } from './search-tool-schema'

describe('search documentation tool schema', () => {
it('accepts exactly one string query', () => {
expect(validateSearchDocsInput({ query: 'SSO setup' })).toEqual({
success: true,
value: { query: 'SSO setup' },
})
})

it.each([
['null', null],
['array', []],
['missing query', {}],
['non-string query', { query: 7 }],
['extra properties', { query: 'SSO setup', unexpected: true }],
])('rejects malformed input: %s', (_label, value) => {
expect(validateSearchDocsInput(value)).toMatchObject({
success: false,
error: expect.any(TypeError),
})
})
})
41 changes: 41 additions & 0 deletions apps/docs/lib/search-tool-schema.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { jsonSchema } from 'ai'

interface SearchDocsInput {
query: string
}

export function validateSearchDocsInput(value: unknown) {
if (
typeof value !== 'object' ||
value === null ||
Array.isArray(value) ||
Object.keys(value).length !== 1 ||
!('query' in value) ||
typeof value.query !== 'string'
) {
return {
success: false as const,
error: new TypeError('Search documentation input must contain only a string query'),
}
}

return {
success: true as const,
value: { query: value.query },
}
}

export const searchDocsInputSchema = jsonSchema<SearchDocsInput>(
{
type: 'object',
properties: {
query: {
type: 'string',
description: 'A focused natural-language search query.',
},
},
required: ['query'],
additionalProperties: false,
},
{ validate: validateSearchDocsInput }
)
4 changes: 3 additions & 1 deletion apps/docs/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
"build": "fumadocs-mdx && NODE_OPTIONS='--max-old-space-size=8192' next build",
"start": "next start",
"postinstall": "fumadocs-mdx",
"test": "vitest run",
"type-check": "tsc --noEmit",
"lint": "biome check --write --unsafe .",
"lint:check": "biome check .",
Expand Down Expand Up @@ -53,6 +54,7 @@
"@typescript/native-preview": "7.0.0-dev.20260707.2",
"postcss": "^8.5.3",
"tailwindcss": "^4.0.12",
"typescript": "^7.0.2"
"typescript": "^7.0.2",
"vitest": "^4.1.0"
}
}
Loading
Loading