Skip to content

Commit 06f1e72

Browse files
feat(feature-flags): migrate 3 env-flags to AppConfig-backed runtime flags (#5086)
* feat(feature-flags): migrate 3 env-flags to AppConfig-backed runtime flags * fix(feature-flags): hardcode workflow-columns on, fix feature-flags tests * chore(feature-flags): document mothership-beta userId targeting limitation
1 parent 05e8c7c commit 06f1e72

16 files changed

Lines changed: 121 additions & 132 deletions

File tree

apps/sim/app/api/files/serve/[...path]/route.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@ async function compileDocumentIfNeeded(
117117
return { buffer: stored.buffer, contentType: stored.contentType }
118118
}
119119

120-
if (isE2BDocEnabled && getE2BDocFormat(filename)) {
120+
if (isE2BDocEnabled && (await getE2BDocFormat(filename))) {
121121
// Artifact not built yet (still generating, or the source didn't compile at
122122
// write time). Signal "not ready" without compiling — handled as 409.
123123
throw new DocCompileUserError('Document is still being generated')

apps/sim/app/api/workspaces/[id]/files/[fileId]/compiled-check/route.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ export const GET = withRouteHandler(
5757
// In the E2B regime ALL four formats compile in the doc sandbox (Node for
5858
// pptx/docx, Python for pdf/xlsx). Gate on the flag (not the stored MIME) so
5959
// a stale file can't trigger an E2B compile when the sandbox is disabled.
60-
const e2bFmt = isE2BDocEnabled ? getE2BDocFormat(fileRecord.name) : null
60+
const e2bFmt = isE2BDocEnabled ? await getE2BDocFormat(fileRecord.name) : null
6161
const taskId = BINARY_DOC_TASKS[ext]
6262
const isMermaidFile = ext === 'mmd' || ext === 'mermaid'
6363
if (!e2bFmt && !taskId && !isMermaidFile) {

apps/sim/app/workspace/[workspaceId]/tables/[tableId]/components/new-column-dropdown/new-column-dropdown.tsx

Lines changed: 8 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,9 @@ import {
1313
DropdownMenuTrigger,
1414
Plus,
1515
} from '@/components/emcn'
16-
import { isWorkflowColumnsEnabledClient } from '@/lib/core/config/env-flags'
1716
import type { ColumnDefinition } from '@/lib/table'
1817
import { COLUMN_TYPE_OPTIONS } from '../column-config-sidebar'
1918

20-
const VISIBLE_COLUMN_TYPE_OPTIONS = isWorkflowColumnsEnabledClient
21-
? COLUMN_TYPE_OPTIONS
22-
: COLUMN_TYPE_OPTIONS.filter((o) => o.type !== 'workflow')
23-
2419
const CELL_HEADER =
2520
'border-[var(--border)] border-r border-b bg-[var(--bg)] px-2 py-[7px] text-left align-middle'
2621

@@ -67,16 +62,14 @@ export function NewColumnDropdown({
6762
)}
6863
</DropdownMenuTrigger>
6964
<DropdownMenuContent align='start' side='bottom' sideOffset={4}>
70-
{isWorkflowColumnsEnabledClient && (
71-
<>
72-
<DropdownMenuItem onSelect={onPickEnrichment}>
73-
<Sparkles className='size-[14px] text-[var(--text-icon)]' />
74-
Enrichments
75-
</DropdownMenuItem>
76-
<DropdownMenuSeparator />
77-
</>
78-
)}
79-
{VISIBLE_COLUMN_TYPE_OPTIONS.map((option) => {
65+
<>
66+
<DropdownMenuItem onSelect={onPickEnrichment}>
67+
<Sparkles className='size-[14px] text-[var(--text-icon)]' />
68+
Enrichments
69+
</DropdownMenuItem>
70+
<DropdownMenuSeparator />
71+
</>
72+
{COLUMN_TYPE_OPTIONS.map((option) => {
8073
const Icon = option.icon
8174
const onSelect =
8275
option.type === 'workflow'

apps/sim/lib/copilot/tools/handlers/function-execute.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { createLogger } from '@sim/logger'
22
import { decodeVfsPathSegments, encodeVfsPathSegments } from '@/lib/copilot/vfs/path-utils'
33
import { resolveWorkflowAliasForWorkspace } from '@/lib/copilot/vfs/workflow-alias-resolver'
44
import { isPlanAliasPath, workflowAliasSandboxPath } from '@/lib/copilot/vfs/workflow-aliases'
5-
import { isMothershipBetaFeaturesEnabled } from '@/lib/core/config/env-flags'
5+
import { isFeatureEnabled } from '@/lib/core/config/feature-flags'
66
import { queryRows } from '@/lib/table/rows/service'
77
import { getTableById, listTables } from '@/lib/table/service'
88
import { listWorkspaceFileFolders } from '@/lib/uploads/contexts/workspace/workspace-file-folder-manager'
@@ -71,10 +71,11 @@ async function resolveInputFiles(
7171
): Promise<SandboxFile[]> {
7272
const sandboxFiles: SandboxFile[] = []
7373
let totalSize = 0
74+
const betaEnabled = await isFeatureEnabled('mothership-beta')
7475

7576
if (inputFiles?.length && workspaceId) {
7677
const allFiles = await listWorkspaceFiles(workspaceId, {
77-
includeReservedSystemFiles: isMothershipBetaFeaturesEnabled,
78+
includeReservedSystemFiles: betaEnabled,
7879
})
7980
for (const fileRef of inputFiles) {
8081
const filePath =
@@ -136,11 +137,11 @@ async function resolveInputFiles(
136137

137138
if (inputDirectories?.length && workspaceId) {
138139
const folders = await listWorkspaceFileFolders(workspaceId, {
139-
includeReservedSystemFolders: isMothershipBetaFeaturesEnabled,
140+
includeReservedSystemFolders: betaEnabled,
140141
})
141142
const allFiles = await listWorkspaceFiles(workspaceId, {
142143
folders,
143-
includeReservedSystemFiles: isMothershipBetaFeaturesEnabled,
144+
includeReservedSystemFiles: betaEnabled,
144145
})
145146
for (const dirRef of inputDirectories) {
146147
const dirPath =

apps/sim/lib/copilot/tools/server/files/doc-compile.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { createLogger } from '@sim/logger'
2-
import { isMothershipBetaFeaturesEnabled } from '@/lib/core/config/env-flags'
2+
import { isFeatureEnabled } from '@/lib/core/config/feature-flags'
33
import { executeInE2B, executeShellInE2B, type SandboxFile } from '@/lib/execution/e2b'
44
import { CodeLanguage } from '@/lib/execution/languages'
55
import {
@@ -53,7 +53,7 @@ export interface E2BDocFormat {
5353
* pptx/docx → node, pdf/xlsx → python. Only meaningful when the E2B doc sandbox
5454
* is enabled; callers gate on isE2BDocEnabled before using this.
5555
*/
56-
export function getE2BDocFormat(fileName: string): E2BDocFormat | null {
56+
export async function getE2BDocFormat(fileName: string): Promise<E2BDocFormat | null> {
5757
const l = fileName.toLowerCase()
5858
if (l.endsWith('.pptx'))
5959
return {
@@ -79,10 +79,10 @@ export function getE2BDocFormat(fileName: string): E2BDocFormat | null {
7979
contentType: PDF_MIME,
8080
sourceMime: PYTHON_PDF_SOURCE_MIME,
8181
}
82-
// xlsx is gated behind the mothership beta flag (like plans/changelog): the
82+
// xlsx is gated behind the mothership-beta feature flag (like plans/changelog): the
8383
// skill + prompt are gated on the Go side, and this is the single Sim chokepoint
8484
// that keeps the compile/serve/check/recalc paths off for xlsx when beta is off.
85-
if (l.endsWith('.xlsx') && isMothershipBetaFeaturesEnabled)
85+
if (l.endsWith('.xlsx') && (await isFeatureEnabled('mothership-beta')))
8686
return {
8787
ext: 'xlsx',
8888
engine: 'python',
@@ -385,7 +385,7 @@ export async function compileDoc(
385385
args: CompileArgs
386386
): Promise<{ buffer: Buffer; contentType: string }> {
387387
const { source, fileName, workspaceId } = args
388-
const fmt = getE2BDocFormat(fileName)
388+
const fmt = await getE2BDocFormat(fileName)
389389
if (!fmt) throw new Error(`Unsupported document format: ${fileName}`)
390390

391391
const existing = await loadCompiledDoc(workspaceId, source, fmt.ext)
@@ -409,7 +409,7 @@ export async function loadCompiledDocByExt(
409409
source: string,
410410
ext: string
411411
): Promise<{ buffer: Buffer; contentType: string } | null> {
412-
const fmt = getE2BDocFormat(`x.${ext}`)
412+
const fmt = await getE2BDocFormat(`x.${ext}`)
413413
if (!fmt) return null
414414
const buffer = await loadCompiledDoc(workspaceId, source, fmt.ext)
415415
return buffer ? { buffer, contentType: fmt.contentType } : null

apps/sim/lib/copilot/tools/server/files/edit-content.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ export const editContentServerTool: BaseServerTool<EditContentArgs, EditContentR
6464
try {
6565
const { operation, fileRecord } = intent
6666
const docInfo = getDocumentFormatInfo(fileRecord.name)
67-
const e2bFmt = isE2BDocEnabled ? getE2BDocFormat(fileRecord.name) : null
67+
const e2bFmt = isE2BDocEnabled ? await getE2BDocFormat(fileRecord.name) : null
6868

6969
let finalContent: string
7070
switch (operation) {

apps/sim/lib/copilot/tools/server/files/workspace-file.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -203,13 +203,13 @@ export async function compileDocForWrite(args: {
203203
}): Promise<CompileForWriteResult> {
204204
const { source, fileName, workspaceId, ownerKey, signal, fallbackMime } = args
205205
const docInfo = getDocumentFormatInfo(fileName)
206-
const e2bFmt = isE2BDocEnabled ? getE2BDocFormat(fileName) : null
206+
const e2bFmt = isE2BDocEnabled ? await getE2BDocFormat(fileName) : null
207207

208208
if (!e2bFmt && fileName.toLowerCase().endsWith('.xlsx')) {
209209
return {
210210
ok: false,
211211
message: isE2BDocEnabled
212-
? 'Excel (.xlsx) generation is currently behind a beta flag (MOTHERSHIP_BETA_FEATURES) and is not available.'
212+
? 'Excel (.xlsx) generation is currently behind the mothership-beta feature flag and is not available.'
213213
: 'Excel (.xlsx) generation requires the E2B document sandbox, which is not enabled in this environment.',
214214
}
215215
}

apps/sim/lib/copilot/vfs/workflow-alias-resolver.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,14 @@ import {
88
resolveWorkspacePlanAliasPath,
99
type WorkflowAliasTarget,
1010
} from '@/lib/copilot/vfs/workflow-aliases'
11-
import { isMothershipBetaFeaturesEnabled } from '@/lib/core/config/env-flags'
11+
import { isFeatureEnabled } from '@/lib/core/config/feature-flags'
1212
import { canonicalizeVfsPath } from './path-utils'
1313

1414
export async function resolveWorkflowAliasForWorkspace(args: {
1515
workspaceId: string
1616
path: string
1717
}): Promise<WorkflowAliasTarget | null> {
18-
if (!isMothershipBetaFeaturesEnabled) return null
18+
if (!(await isFeatureEnabled('mothership-beta'))) return null
1919
if (!isPlanAliasPath(args.path)) return null
2020

2121
let canonicalPath: string

apps/sim/lib/copilot/vfs/workspace-vfs.ts

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,8 @@ import {
8989
workspacePlanBackingPath,
9090
workspacePlansBackingFolderPath,
9191
} from '@/lib/copilot/vfs/workflow-aliases'
92-
import { isE2BDocEnabled, isMothershipBetaFeaturesEnabled } from '@/lib/core/config/env-flags'
92+
import { isE2BDocEnabled } from '@/lib/core/config/env-flags'
93+
import { isFeatureEnabled } from '@/lib/core/config/feature-flags'
9394
import {
9495
getAccessibleEnvCredentials,
9596
getAccessibleOAuthCredentials,
@@ -379,6 +380,7 @@ function getStaticComponentFiles(): Map<string, string> {
379380
export class WorkspaceVFS {
380381
private files: Map<string, string> = new Map()
381382
private _workspaceId = ''
383+
private _betaEnabled = false
382384

383385
get workspaceId(): string {
384386
return this._workspaceId
@@ -393,6 +395,7 @@ export class WorkspaceVFS {
393395
const start = Date.now()
394396
this.files = new Map()
395397
this._workspaceId = workspaceId
398+
this._betaEnabled = await isFeatureEnabled('mothership-beta', { userId })
396399

397400
// Per-phase wall-clock, stamped on the span so a slow materialize in a
398401
// trace names its bottleneck instead of showing up as unattributed dead
@@ -591,7 +594,7 @@ export class WorkspaceVFS {
591594
path: string,
592595
suffix: 'style' | 'compiled-check' | 'compiled' | 'render' | 'extract'
593596
): Promise<WorkspaceFileRecord | null> {
594-
if (!isMothershipBetaFeaturesEnabled && isWorkflowAliasBackingPath(path)) {
597+
if (!this._betaEnabled && isWorkflowAliasBackingPath(path)) {
595598
return null
596599
}
597600
const canonicalMatch = path.match(new RegExp(`^files/(.+)/${suffix}$`))
@@ -642,7 +645,7 @@ export class WorkspaceVFS {
642645
totalLines: 1,
643646
}
644647
}
645-
if (isE2BDocEnabled && getE2BDocFormat(record.name)) {
648+
if (isE2BDocEnabled && (await getE2BDocFormat(record.name))) {
646649
bin = (
647650
await compileDoc({ source: code, fileName: record.name, workspaceId: this._workspaceId })
648651
).buffer
@@ -695,7 +698,7 @@ export class WorkspaceVFS {
695698
record = await this.resolveWorkspaceFileForDynamicRead(path, 'compiled')
696699
if (!record) return null
697700
const ext = record.name.split('.').pop()?.toLowerCase() ?? ''
698-
const e2bFmt = isE2BDocEnabled ? getE2BDocFormat(record.name) : null
701+
const e2bFmt = isE2BDocEnabled ? await getE2BDocFormat(record.name) : null
699702
const taskId = BINARY_DOC_TASKS[ext]
700703
if (!e2bFmt && !taskId) return null
701704

@@ -890,7 +893,7 @@ export class WorkspaceVFS {
890893
record = await this.resolveWorkspaceFileForDynamicRead(path, 'compiled-check')
891894
if (!record) return null
892895
const ext = record.name.split('.').pop()?.toLowerCase() ?? ''
893-
const e2bFmt = isE2BDocEnabled ? getE2BDocFormat(record.name) : null
896+
const e2bFmt = isE2BDocEnabled ? await getE2BDocFormat(record.name) : null
894897
const taskId = BINARY_DOC_TASKS[ext]
895898
const isMermaidFile = ext === 'mmd' || ext === 'mermaid'
896899
if (!e2bFmt && !taskId && !isMermaidFile) return null
@@ -978,7 +981,7 @@ export class WorkspaceVFS {
978981
.replace(/\/content$/, '')
979982
.replace(/^\/+/, '')
980983

981-
if (!isMothershipBetaFeaturesEnabled && isWorkflowAliasBackingPath(fileReference)) {
984+
if (!this._betaEnabled && isWorkflowAliasBackingPath(fileReference)) {
982985
return null
983986
}
984987
if (fileReference.endsWith('/meta.json') || path.endsWith('/meta.json')) return null
@@ -988,7 +991,7 @@ export class WorkspaceVFS {
988991
try {
989992
const files = await listWorkspaceFiles(this._workspaceId, {
990993
scope,
991-
includeReservedSystemFiles: isMothershipBetaFeaturesEnabled,
994+
includeReservedSystemFiles: this._betaEnabled,
992995
})
993996
const record = findWorkspaceFileRecord(files, fileReference)
994997
if (!record) return null
@@ -1021,7 +1024,7 @@ export class WorkspaceVFS {
10211024
* Returns a summary for WORKSPACE.md generation.
10221025
*/
10231026
private async materializeWorkflows(workspaceId: string): Promise<WorkspaceMdData['workflows']> {
1024-
const workflowArtifactsEnabled = isMothershipBetaFeaturesEnabled
1027+
const workflowArtifactsEnabled = this._betaEnabled
10251028
const [workflowRows, folderRows] = await Promise.all([
10261029
listWorkflows(workspaceId),
10271030
listFolders(workspaceId),
@@ -1404,7 +1407,7 @@ export class WorkspaceVFS {
14041407
*/
14051408
private async materializeFiles(workspaceId: string): Promise<WorkspaceMdData['files']> {
14061409
try {
1407-
const workflowArtifactsEnabled = isMothershipBetaFeaturesEnabled
1410+
const workflowArtifactsEnabled = this._betaEnabled
14081411
const folders = await listWorkspaceFileFolders(workspaceId, {
14091412
includeReservedSystemFolders: true,
14101413
})

apps/sim/lib/core/config/env-flags.ts

Lines changed: 0 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -44,14 +44,6 @@ export const isBillingEnabled = isTruthy(env.BILLING_ENABLED)
4444
*/
4545
export const isFreeApiDeploymentGateEnabled = isTruthy(env.FREE_API_DEPLOYMENT_GATE_ENABLED)
4646

47-
/**
48-
* Order table rows by fractional `order_key` (O(1) insert/delete) instead of the
49-
* legacy integer `position`. When off, behavior is unchanged. Keys are written
50-
* regardless of this flag; it only controls which column is authoritative for
51-
* reads/ordering and whether inserts/deletes reshift positions.
52-
*/
53-
export const isTablesFractionalOrderingEnabled = isTruthy(env.TABLES_FRACTIONAL_ORDERING)
54-
5547
/**
5648
* Is email verification enabled
5749
*/
@@ -173,20 +165,6 @@ export const isDataRetentionEnabled = isTruthy(env.DATA_RETENTION_ENABLED)
173165
*/
174166
export const isDataDrainsEnabled = isTruthy(env.DATA_DRAINS_ENABLED)
175167

176-
/**
177-
* Are workflow output columns enabled in user tables.
178-
* Defaults to false; set NEXT_PUBLIC_WORKFLOW_COLUMNS_ENABLED=true to show
179-
* the "Workflow" column type in the new-column dropdown.
180-
*/
181-
export const isWorkflowColumnsEnabledClient = isTruthy(
182-
getEnv('NEXT_PUBLIC_WORKFLOW_COLUMNS_ENABLED')
183-
)
184-
185-
/**
186-
* Enables beta Mothership plan/changelog artifact surfaces.
187-
*/
188-
export const isMothershipBetaFeaturesEnabled = isTruthy(env.MOTHERSHIP_BETA_FEATURES)
189-
190168
/**
191169
* Is E2B enabled for remote code execution
192170
*/

0 commit comments

Comments
 (0)