import { z } from "zod"; export const FEATURE_FLAG = { defaultWorkerInstanceGroupId: "defaultWorkerInstanceGroupId", taskEventRepository: "taskEventRepository", hasQueryAccess: "hasQueryAccess", hasLogsPageAccess: "hasLogsPageAccess", hasAiAccess: "hasAiAccess", hasDashboardAgentAccess: "hasDashboardAgentAccess", hasComputeAccess: "hasComputeAccess", hasPrivateConnections: "hasPrivateConnections", hasSso: "hasSso", mollifierEnabled: "mollifierEnabled", workerQueueScheduledSplitEnabled: "workerQueueScheduledSplitEnabled", realtimeBackend: "realtimeBackend", computeMigrationEnabled: "computeMigrationEnabled", computeMigrationFreePercentage: "computeMigrationFreePercentage", computeMigrationPaidPercentage: "computeMigrationPaidPercentage", computeMigrationRequireTemplate: "computeMigrationRequireTemplate", devBranchesEnabled: "devBranchesEnabled", runOpsMintKind: "runOpsMintKind", } as const; export const FeatureFlagCatalog = { [FEATURE_FLAG.defaultWorkerInstanceGroupId]: z.string(), [FEATURE_FLAG.taskEventRepository]: z.enum(["clickhouse", "clickhouse_v2", "postgres"]), [FEATURE_FLAG.hasQueryAccess]: z.coerce.boolean(), [FEATURE_FLAG.hasLogsPageAccess]: z.coerce.boolean(), [FEATURE_FLAG.hasAiAccess]: z.coerce.boolean(), // Gates the in-dashboard AI agent panel. Controllable globally and per-org // (org wins). Defaults off via DASHBOARD_AGENT_ENABLED. [FEATURE_FLAG.hasDashboardAgentAccess]: z.coerce.boolean(), [FEATURE_FLAG.hasComputeAccess]: z.coerce.boolean(), [FEATURE_FLAG.hasPrivateConnections]: z.coerce.boolean(), [FEATURE_FLAG.hasSso]: z.coerce.boolean(), [FEATURE_FLAG.mollifierEnabled]: z.coerce.boolean(), [FEATURE_FLAG.workerQueueScheduledSplitEnabled]: z.coerce.boolean(), // Which backend serves the realtime run feed. Controllable // globally and per-org (org wins). Defaults to "electric" when unset. // "shadow" serves Electric but diffs the native path in the background. [FEATURE_FLAG.realtimeBackend]: z.enum(["electric", "native", "shadow"]), // Strict z.boolean() (not z.coerce.boolean()): coercion turns the string "false" // into true, which would silently flip this kill switch / per-org exclude the wrong // way if written as a string via the admin PAT route. The admin toggle sends a real // boolean, so this only rejects the dangerous stringified case. [FEATURE_FLAG.computeMigrationEnabled]: z.boolean(), [FEATURE_FLAG.computeMigrationFreePercentage]: z.coerce.number().int().min(0).max(100), [FEATURE_FLAG.computeMigrationPaidPercentage]: z.coerce.number().int().min(0).max(100), // When on, migrated orgs build their compute template in required mode at deploy // (fails the deploy on error) instead of shadow. Strict boolean (see above). [FEATURE_FLAG.computeMigrationRequireTemplate]: z.boolean(), // Per-org access to development branches. Off unless enabled for the org. [FEATURE_FLAG.devBranchesEnabled]: z.coerce.boolean(), // Per-org run-ops-id mint cutover. Defaults to "cuid"; only honored when // RUN_OPS_MINT_ENABLED is on AND isSplitEnabled() is true. [FEATURE_FLAG.runOpsMintKind]: z.enum(["cuid", "runOpsId"]), }; export type FeatureFlagKey = keyof typeof FeatureFlagCatalog; // Infrastructure flags that are read-only on the global flags page. // Shown with current/resolved value but no controls. export const GLOBAL_LOCKED_FLAGS: FeatureFlagKey[] = [ FEATURE_FLAG.defaultWorkerInstanceGroupId, FEATURE_FLAG.taskEventRepository, ]; // Flags that are read-only on the org-level dialog. // Shown with global value but no controls (org can't override these). export const ORG_LOCKED_FLAGS: FeatureFlagKey[] = [ FEATURE_FLAG.defaultWorkerInstanceGroupId, FEATURE_FLAG.taskEventRepository, ]; // Create a Zod schema from the existing catalog export const FeatureFlagCatalogSchema = z.object(FeatureFlagCatalog); export type FeatureFlagCatalog = z.infer; // Utility function to validate a feature flag value export function validateFeatureFlagValue( key: T, value: unknown ): z.SafeParseReturnType> { return FeatureFlagCatalog[key].safeParse(value); } // Utility function to validate all feature flags at once export function validateAllFeatureFlags(values: Record) { return FeatureFlagCatalogSchema.safeParse(values); } // Utility function to validate partial feature flags (all keys optional) export function validatePartialFeatureFlags(values: Record) { return FeatureFlagCatalogSchema.partial().safeParse(values); } // Utility types for catalog-driven UI rendering export type FlagControlType = | { type: "boolean" } | { type: "enum"; options: string[] } | { type: "number"; min?: number; max?: number } | { type: "string" }; export function getFlagControlType(schema: z.ZodTypeAny): FlagControlType { const typeName = schema._def.typeName; if (typeName === "ZodBoolean") { return { type: "boolean" }; } if (typeName === "ZodEnum") { return { type: "enum", options: schema._def.values as string[] }; } // z.coerce.number() reports as ZodNumber; pull min/max out of its checks // so the UI can render a constrained number input instead of free text. if (typeName === "ZodNumber") { const checks = (schema._def.checks ?? []) as Array<{ kind: string; value?: number }>; const min = checks.find((c) => c.kind === "min")?.value; const max = checks.find((c) => c.kind === "max")?.value; return { type: "number", min, max }; } return { type: "string" }; } export function getAllFlagControlTypes(): Record { const result: Record = {}; for (const [key, schema] of Object.entries(FeatureFlagCatalog)) { result[key] = getFlagControlType(schema); } return result; }