import { z } from "zod";
import { MachinePresetName } from "@trigger.dev/core/v3";
import { BoolEnv } from "./utils/boolEnv";
import { isValidDatabaseUrl } from "./utils/db";
import { isValidRegex } from "./utils/regex";
import { isValidDuration } from "./services/realtime/duration.server";

// `z.string()` constrained to a `parseDuration`-parseable string (e.g.
// `7d`, `1h`). Validated at boot so a typo'd duration fails fast.
function durationString() {
  return z.string().refine(isValidDuration, "must be a duration like 7d, 30d, 365d, 1h, 1y");
}

// Parses a CSV of machine preset names (e.g. "small-1x,small-2x") into a
// non-empty array of MachinePresetName. Used by COMPUTE_TEMPLATE_MACHINE_PRESETS
// and its _REQUIRED variant. Adds zod issues for empty input or unknown names.
const parseMachinePresetCsv = (raw: string, ctx: z.RefinementCtx): MachinePresetName[] => {
  const names = raw
    .split(",")
    .map((s) => s.trim())
    .filter(Boolean);
  if (names.length === 0) {
    ctx.addIssue({
      code: z.ZodIssueCode.custom,
      message: "must list at least one machine preset",
    });
    return z.NEVER;
  }
  const out: MachinePresetName[] = [];
  for (const name of names) {
    const parsed = MachinePresetName.safeParse(name);
    if (!parsed.success) {
      ctx.addIssue({
        code: z.ZodIssueCode.custom,
        message: `unknown machine preset: "${name}"`,
      });
      return z.NEVER;
    }
    out.push(parsed.data);
  }
  return out;
};

const GithubAppEnvSchema = z.preprocess(
  (val) => {
    const obj = val as any;
    if (!obj || !obj.GITHUB_APP_ENABLED) {
      return { ...obj, GITHUB_APP_ENABLED: "0" };
    }
    return obj;
  },
  z.discriminatedUnion("GITHUB_APP_ENABLED", [
    z.object({
      GITHUB_APP_ENABLED: z.literal("1"),
      GITHUB_APP_ID: z.string(),
      GITHUB_APP_PRIVATE_KEY: z.string(),
      GITHUB_APP_WEBHOOK_SECRET: z.string(),
      GITHUB_APP_SLUG: z.string(),
    }),
    z.object({
      GITHUB_APP_ENABLED: z.literal("0"),
    }),
  ])
);

// eventually we can make all S2 env vars required once the S2 OSS version is out
const S2EnvSchema = z.preprocess(
  (val) => {
    const obj = val as any;
    if (!obj || !obj.S2_ENABLED) {
      return { ...obj, S2_ENABLED: "0" };
    }
    return obj;
  },
  z.discriminatedUnion("S2_ENABLED", [
    z.object({
      S2_ENABLED: z.literal("1"),
      S2_ACCESS_TOKEN: z.string(),
      S2_DEPLOYMENT_LOGS_BASIN_NAME: z.string(),
      S2_DEPLOYMENT_STREAMS_LOCAL: z.string().default("0"),
    }),
    z.object({
      S2_ENABLED: z.literal("0"),
    }),
  ])
);

const EnvironmentSchema = z
  .object({
    NODE_ENV: z.union([z.literal("development"), z.literal("production"), z.literal("test")]),
    DATABASE_URL: z
      .string()
      .refine(
        isValidDatabaseUrl,
        "DATABASE_URL is invalid, for details please check the additional output above this message."
      ),
    DATABASE_CONNECTION_LIMIT: z.coerce.number().int().default(10),
    DATABASE_POOL_TIMEOUT: z.coerce.number().int().default(60),
    DATABASE_CONNECTION_TIMEOUT: z.coerce.number().int().default(20),
    // Dashboard-agent conversation store. Cloud points this at the dedicated
    // PlanetScale database; when unset it falls back to DATABASE_URL (OSS), where
    // the tables live in the isolated `trigger_dashboard_agent` schema.
    DASHBOARD_AGENT_DATABASE_URL: z.string().optional(),
    // The secret key (tr_*) for the runtime environment the dashboard-agent task
    // is deployed to. The chat session is created in that environment via the
    // standard chat.agent SDK flow. When unset, the live agent is disabled — the
    // conversation store / History still work, no chat can start.
    DASHBOARD_AGENT_SECRET_KEY: z.string().optional(),
    // Global default for the `hasDashboardAgentAccess` flag. "0" (off) ships the
    // agent dark; flip to "1" to enable it for everyone at GA. Per-org overrides
    // (org featureFlags) win regardless.
    DASHBOARD_AGENT_ENABLED: z.string().default("0"),
    // "1" gives admins/impersonators an everywhere-preview (default off),
    // separate from the per-org rollout flag above.
    DASHBOARD_AGENT_ADMIN_PREVIEW: z.string().default("0"),
    // Anthropic key for the dashboard agent's Head Start route only (the warm
    // first-turn step-1 LLM call runs in this process). The agent run itself
    // uses its own key on the Trigger side. When unset, Head Start is disabled
    // and the first turn falls back to the normal cold-start path.
    ANTHROPIC_API_KEY: z.string().optional(),
    DIRECT_URL: z
      .string()
      .refine(
        isValidDatabaseUrl,
        "DIRECT_URL is invalid, for details please check the additional output above this message."
      ),
    DATABASE_READ_REPLICA_URL: z.string().optional(),
    SESSION_SECRET: z.string(),
    MAGIC_LINK_SECRET: z.string(),
    ENCRYPTION_KEY: z
      .string()
      .refine(
        (val) => Buffer.from(val, "utf8").length === 32,
        "ENCRYPTION_KEY must be exactly 32 bytes"
      ),
    WHITELISTED_EMAILS: z
      .string()
      .refine(isValidRegex, "WHITELISTED_EMAILS must be a valid regex.")
      .optional(),
    ADMIN_EMAILS: z.string().refine(isValidRegex, "ADMIN_EMAILS must be a valid regex.").optional(),
    REMIX_APP_PORT: z.string().optional(),
    LOGIN_ORIGIN: z.string().default("http://localhost:3030"),
    LOGIN_RATE_LIMITS_ENABLED: BoolEnv.default(true),
    APP_ORIGIN: z.string().default("http://localhost:3030"),
    API_ORIGIN: z.string().optional(),
    STREAM_ORIGIN: z.string().optional(),
    ELECTRIC_ORIGIN: z.string().default("http://localhost:3060"),
    // A comma separated list of electric origins to shard into different electric instances by environmentId
    // example: "http://localhost:3060,http://localhost:3061,http://localhost:3062"
    ELECTRIC_ORIGIN_SHARDS: z.string().optional(),
    APP_ENV: z.string().default(process.env.NODE_ENV),
    SERVICE_NAME: z.string().default("trigger.dev webapp"),
    SENTRY_DSN: z.string().optional(),
    POSTHOG_PROJECT_KEY: z.string().default("phc_LFH7kJiGhdIlnO22hTAKgHpaKhpM8gkzWAFvHmf5vfS"),
    TRIGGER_TELEMETRY_DISABLED: z.string().optional(),
    AUTH_GITHUB_CLIENT_ID: z.string().optional(),
    AUTH_GITHUB_CLIENT_SECRET: z.string().optional(),
    AUTH_GOOGLE_CLIENT_ID: z.string().optional(),
    AUTH_GOOGLE_CLIENT_SECRET: z.string().optional(),
    EMAIL_TRANSPORT: z.enum(["resend", "smtp", "aws-ses"]).optional(),
    FROM_EMAIL: z.string().optional(),
    REPLY_TO_EMAIL: z.string().optional(),
    RESEND_API_KEY: z.string().optional(),
    SMTP_HOST: z.string().optional(),
    SMTP_PORT: z.coerce.number().optional(),
    SMTP_SECURE: BoolEnv.optional(),
    SMTP_USER: z.string().optional(),
    SMTP_PASSWORD: z.string().optional(),

    PLAIN_API_KEY: z.string().optional(),
    PLAIN_CUSTOMER_CARDS_SECRET: z.string().optional(),
    PLAIN_CUSTOMER_CARDS_KEY: z.string().optional(),
    PLAIN_CUSTOMER_CARDS_HEADERS: z.string().optional(),
    WORKER_SCHEMA: z.string().default("graphile_worker"),
    WORKER_CONCURRENCY: z.coerce.number().int().default(10),
    WORKER_POLL_INTERVAL: z.coerce.number().int().default(1000),
    // How often each replica reloads the global flags snapshot from the DB.
    // Sets kill/ramp propagation latency.
    GLOBAL_FLAGS_RELOAD_INTERVAL_MS: z.coerce.number().int().min(1000).default(5000),
    WORKER_ENABLED: z.string().default("true"),
    GRACEFUL_SHUTDOWN_TIMEOUT: z.coerce.number().int().default(60000),
    DISABLE_SSE: z.string().optional(),
    OPENAI_API_KEY: z.string().optional(),

    // Redis options
    REDIS_HOST: z.string().optional(),
    REDIS_READER_HOST: z.string().optional(),
    REDIS_READER_PORT: z.coerce.number().optional(),
    REDIS_PORT: z.coerce.number().optional(),
    REDIS_USERNAME: z.string().optional(),
    REDIS_PASSWORD: z.string().optional(),
    REDIS_TLS_DISABLED: z.string().optional(),

    RATE_LIMIT_REDIS_HOST: z
      .string()
      .optional()
      .transform((v) => v ?? process.env.REDIS_HOST),
    RATE_LIMIT_REDIS_READER_HOST: z
      .string()
      .optional()
      .transform((v) => v ?? process.env.REDIS_READER_HOST),
    RATE_LIMIT_REDIS_READER_PORT: z.coerce
      .number()
      .optional()
      .transform(
        (v) =>
          v ?? (process.env.REDIS_READER_PORT ? parseInt(process.env.REDIS_READER_PORT) : undefined)
      ),
    RATE_LIMIT_REDIS_PORT: z.coerce
      .number()
      .optional()
      .transform(
        (v) => v ?? (process.env.REDIS_PORT ? parseInt(process.env.REDIS_PORT) : undefined)
      ),
    RATE_LIMIT_REDIS_USERNAME: z
      .string()
      .optional()
      .transform((v) => v ?? process.env.REDIS_USERNAME),
    RATE_LIMIT_REDIS_PASSWORD: z
      .string()
      .optional()
      .transform((v) => v ?? process.env.REDIS_PASSWORD),
    RATE_LIMIT_REDIS_TLS_DISABLED: z.string().default(process.env.REDIS_TLS_DISABLED ?? "false"),
    RATE_LIMIT_REDIS_CLUSTER_MODE_ENABLED: z.string().default("0"),

    CACHE_REDIS_HOST: z
      .string()
      .optional()
      .transform((v) => v ?? process.env.REDIS_HOST),
    CACHE_REDIS_READER_HOST: z
      .string()
      .optional()
      .transform((v) => v ?? process.env.REDIS_READER_HOST),
    CACHE_REDIS_READER_PORT: z.coerce
      .number()
      .optional()
      .transform(
        (v) =>
          v ?? (process.env.REDIS_READER_PORT ? parseInt(process.env.REDIS_READER_PORT) : undefined)
      ),
    CACHE_REDIS_PORT: z.coerce
      .number()
      .optional()
      .transform(
        (v) => v ?? (process.env.REDIS_PORT ? parseInt(process.env.REDIS_PORT) : undefined)
      ),
    CACHE_REDIS_USERNAME: z
      .string()
      .optional()
      .transform((v) => v ?? process.env.REDIS_USERNAME),
    CACHE_REDIS_PASSWORD: z
      .string()
      .optional()
      .transform((v) => v ?? process.env.REDIS_PASSWORD),
    CACHE_REDIS_TLS_DISABLED: z.string().default(process.env.REDIS_TLS_DISABLED ?? "false"),
    CACHE_REDIS_CLUSTER_MODE_ENABLED: z.string().default("0"),

    TASK_META_CACHE_REDIS_HOST: z
      .string()
      .optional()
      .transform((v) => v ?? process.env.REDIS_HOST),
    TASK_META_CACHE_REDIS_PORT: z.coerce
      .number()
      .optional()
      .transform(
        (v) => v ?? (process.env.REDIS_PORT ? parseInt(process.env.REDIS_PORT) : undefined)
      ),
    TASK_META_CACHE_REDIS_USERNAME: z
      .string()
      .optional()
      .transform((v) => v ?? process.env.REDIS_USERNAME),
    TASK_META_CACHE_REDIS_PASSWORD: z
      .string()
      .optional()
      .transform((v) => v ?? process.env.REDIS_PASSWORD),
    TASK_META_CACHE_REDIS_TLS_DISABLED: z
      .string()
      .default(process.env.REDIS_TLS_DISABLED ?? "false"),
    TASK_META_CACHE_CURRENT_ENV_TTL_SECONDS: z.coerce.number().default(86400),
    TASK_META_CACHE_BY_WORKER_TTL_SECONDS: z.coerce.number().default(2592000),

    REALTIME_STREAMS_REDIS_HOST: z
      .string()
      .optional()
      .transform((v) => v ?? process.env.REDIS_HOST),
    REALTIME_STREAMS_REDIS_READER_HOST: z
      .string()
      .optional()
      .transform((v) => v ?? process.env.REDIS_READER_HOST),
    REALTIME_STREAMS_REDIS_READER_PORT: z.coerce
      .number()
      .optional()
      .transform(
        (v) =>
          v ?? (process.env.REDIS_READER_PORT ? parseInt(process.env.REDIS_READER_PORT) : undefined)
      ),
    REALTIME_STREAMS_REDIS_PORT: z.coerce
      .number()
      .optional()
      .transform(
        (v) => v ?? (process.env.REDIS_PORT ? parseInt(process.env.REDIS_PORT) : undefined)
      ),
    REALTIME_STREAMS_REDIS_USERNAME: z
      .string()
      .optional()
      .transform((v) => v ?? process.env.REDIS_USERNAME),
    REALTIME_STREAMS_REDIS_PASSWORD: z
      .string()
      .optional()
      .transform((v) => v ?? process.env.REDIS_PASSWORD),
    REALTIME_STREAMS_REDIS_TLS_DISABLED: z
      .string()
      .default(process.env.REDIS_TLS_DISABLED ?? "false"),
    REALTIME_STREAMS_REDIS_CLUSTER_MODE_ENABLED: z.string().default("0"),
    REALTIME_STREAMS_INACTIVITY_TIMEOUT_MS: z.coerce.number().int().default(60000), // 1 minute

    REALTIME_MAXIMUM_CREATED_AT_FILTER_AGE_IN_MS: z.coerce
      .number()
      .int()
      .default(24 * 60 * 60 * 1000), // 1 day in milliseconds

    // Master switch for the native realtime backend; off = Electric serves everything, publishes no-op.
    REALTIME_BACKEND_NATIVE_ENABLED: z.string().default("0"),
    // Live long-poll backstop hold (ms); matches Electric's ~20s cadence.
    REALTIME_BACKEND_NATIVE_LIVE_POLL_TIMEOUT_MS: z.coerce.number().int().default(20_000),
    // Jitter ratio on the live-poll hold (0.15 = ±15%) to avoid synchronized refetch herds.
    REALTIME_BACKEND_NATIVE_LIVE_POLL_JITTER_RATIO: z.coerce.number().default(0.15),
    // Hard cap on the tag-list snapshot size.
    REALTIME_BACKEND_NATIVE_MAX_LIST_RESULTS: z.coerce.number().int().default(1_000),
    // TTL/size of the coalescing cache for the multi-run resolve+hydrate (same-filter feeds share one query).
    REALTIME_BACKEND_NATIVE_RUNSET_CACHE_TTL_MS: z.coerce.number().int().default(1_000),
    REALTIME_BACKEND_NATIVE_RUNSET_CACHE_MAX_ENTRIES: z.coerce.number().int().default(5_000),
    // Size/TTL of the per-handle working-set cache used to diff multi-run live polls.
    REALTIME_BACKEND_NATIVE_WORKING_SET_MAX_ENTRIES: z.coerce.number().int().default(10_000),
    REALTIME_BACKEND_NATIVE_WORKING_SET_TTL_MS: z.coerce.number().int().default(300_000),
    // Bucket (ms) the tag-list createdAt floor is quantized to so same-tag feeds share a cache entry; 0 disables.
    REALTIME_BACKEND_NATIVE_RUNSET_CREATED_AT_BUCKET_MS: z.coerce.number().int().default(60_000),
    // Leading-edge throttle (ms) on per-env wake delivery; 0 wakes on every change.
    REALTIME_BACKEND_NATIVE_ENV_WAKE_COALESCE_WINDOW_MS: z.coerce.number().int().default(250),
    // "1" shares per-connection replay cursors fleet-wide via Redis, so a load-balancer hop reads the connection's true inter-poll gap instead of cold-resolving.
    REALTIME_BACKEND_NATIVE_SHARED_REPLAY_CURSORS: z.string().default("1"),
    // "1" holds a multi-run live poll open on a non-matching wake instead of replying up-to-date.
    REALTIME_BACKEND_NATIVE_HOLD_ON_EMPTY: z.string().default("1"),
    // Max concurrent fresh ClickHouse resolves per instance (reconnect-stampede gate); 0 disables.
    REALTIME_BACKEND_NATIVE_RESOLVE_ADMISSION_LIMIT: z.coerce.number().int().default(16),
    // Replay window (ms) for buffered change records delivered to newly-armed feeds; 0 disables.
    REALTIME_BACKEND_NATIVE_REPLAY_WINDOW_MS: z.coerce.number().int().default(2_000),
    // Cap on buffered recent records per env (latest record per run).
    REALTIME_BACKEND_NATIVE_REPLAY_MAX_RUNS: z.coerce.number().int().default(512),
    // Keep an env subscribed + buffering this long (ms) after its last feed closes; 0 disables.
    REALTIME_BACKEND_NATIVE_UNSUBSCRIBE_LINGER_MS: z.coerce.number().int().default(5_000),
    // Fallback per-env concurrent-connection limit when the org has none configured.
    REALTIME_BACKEND_NATIVE_DEFAULT_CONCURRENCY_LIMIT: z.coerce.number().int().default(100_000),
    // TTL/size of the single-run read-through cache that collapses duplicate refetch bursts.
    REALTIME_BACKEND_NATIVE_RUN_CACHE_TTL_MS: z.coerce.number().int().default(250),
    REALTIME_BACKEND_NATIVE_RUN_CACHE_MAX_ENTRIES: z.coerce.number().int().default(5_000),
    // TTL/size of the per-org realtimeBackend flag cache used to pick the serving backend.
    REALTIME_BACKEND_FLAG_CACHE_TTL_MS: z.coerce.number().int().default(30_000),
    REALTIME_BACKEND_FLAG_CACHE_MAX_ENTRIES: z.coerce.number().int().default(50_000),
    // "1" enables the read-your-writes gate: wake hydrates wait out the measured replica lag
    // (anchored to the change record's updatedAtMs) and stale reads are retried.
    REALTIME_BACKEND_NATIVE_REPLICA_LAG_GATE_ENABLED: z.string().default("1"),
    // Reader-side lag probe cadence while the router is active; probing pauses when idle.
    REALTIME_BACKEND_NATIVE_REPLICA_LAG_SAMPLE_INTERVAL_MS: z.coerce.number().int().default(250),
    REALTIME_BACKEND_NATIVE_REPLICA_LAG_IDLE_AFTER_MS: z.coerce.number().int().default(30_000),
    // The lag estimate is the max sample inside this window (spikes widen it immediately).
    REALTIME_BACKEND_NATIVE_REPLICA_LAG_WINDOW_MS: z.coerce.number().int().default(5_000),
    // Estimate before the first sample lands (and the floor when probing is unavailable).
    REALTIME_BACKEND_NATIVE_REPLICA_LAG_DEFAULT_MS: z.coerce.number().int().default(30),
    // Safety margin (clock skew + scheduling) added on top of the lag estimate.
    REALTIME_BACKEND_NATIVE_REPLICA_LAG_MARGIN_MS: z.coerce.number().int().default(10),
    // Hard cap on any single gate delay — a sick replica degrades freshness, never liveness.
    REALTIME_BACKEND_NATIVE_REPLICA_LAG_MAX_DELAY_MS: z.coerce.number().int().default(1_000),
    // Re-hydrate attempts for rows the tripwire still finds stale after the delay.
    REALTIME_BACKEND_NATIVE_STALE_HYDRATE_RETRIES: z.coerce.number().int().default(3),
    // How long a tripwire-observed staleness floors the lag estimate (vanilla-PG replicas
    // can't measure mid-apply lag, so observations carry the estimate between races).
    REALTIME_BACKEND_NATIVE_REPLICA_LAG_OBSERVED_FLOOR_TTL_MS: z.coerce
      .number()
      .int()
      .default(60_000),

    PUBSUB_REDIS_HOST: z
      .string()
      .optional()
      .transform((v) => v ?? process.env.REDIS_HOST),
    PUBSUB_REDIS_READER_HOST: z
      .string()
      .optional()
      .transform((v) => v ?? process.env.REDIS_READER_HOST),
    PUBSUB_REDIS_READER_PORT: z.coerce
      .number()
      .optional()
      .transform(
        (v) =>
          v ?? (process.env.REDIS_READER_PORT ? parseInt(process.env.REDIS_READER_PORT) : undefined)
      ),
    PUBSUB_REDIS_PORT: z.coerce
      .number()
      .optional()
      .transform(
        (v) => v ?? (process.env.REDIS_PORT ? parseInt(process.env.REDIS_PORT) : undefined)
      ),
    PUBSUB_REDIS_USERNAME: z
      .string()
      .optional()
      .transform((v) => v ?? process.env.REDIS_USERNAME),
    PUBSUB_REDIS_PASSWORD: z
      .string()
      .optional()
      .transform((v) => v ?? process.env.REDIS_PASSWORD),
    PUBSUB_REDIS_TLS_DISABLED: z.string().default(process.env.REDIS_TLS_DISABLED ?? "false"),
    PUBSUB_REDIS_CLUSTER_MODE_ENABLED: z.string().default("0"),

    // Dedicated pub/sub Redis for the native realtime backend; falls back to PUBSUB_REDIS_* then REDIS_*.
    REALTIME_BACKEND_NATIVE_PUBSUB_REDIS_HOST: z
      .string()
      .optional()
      .transform((v) => v ?? process.env.PUBSUB_REDIS_HOST ?? process.env.REDIS_HOST),
    REALTIME_BACKEND_NATIVE_PUBSUB_REDIS_PORT: z.coerce
      .number()
      .optional()
      .transform((v) => {
        if (v !== undefined) return v;
        const raw = process.env.PUBSUB_REDIS_PORT ?? process.env.REDIS_PORT;
        return raw ? parseInt(raw) : undefined;
      }),
    REALTIME_BACKEND_NATIVE_PUBSUB_REDIS_USERNAME: z
      .string()
      .optional()
      .transform((v) => v ?? process.env.PUBSUB_REDIS_USERNAME ?? process.env.REDIS_USERNAME),
    REALTIME_BACKEND_NATIVE_PUBSUB_REDIS_PASSWORD: z
      .string()
      .optional()
      .transform((v) => v ?? process.env.PUBSUB_REDIS_PASSWORD ?? process.env.REDIS_PASSWORD),
    REALTIME_BACKEND_NATIVE_PUBSUB_REDIS_TLS_DISABLED: z
      .string()
      .default(process.env.PUBSUB_REDIS_TLS_DISABLED ?? process.env.REDIS_TLS_DISABLED ?? "false"),
    REALTIME_BACKEND_NATIVE_PUBSUB_REDIS_CLUSTER_MODE_ENABLED: z
      .string()
      .default(process.env.PUBSUB_REDIS_CLUSTER_MODE_ENABLED ?? "0"),
    // Use sharded pub/sub (SSUBSCRIBE/SPUBLISH) in cluster mode; "0" forces classic pub/sub.
    REALTIME_BACKEND_NATIVE_PUBSUB_REDIS_SHARDED_ENABLED: z.string().default("1"),

    DEFAULT_ENV_EXECUTION_CONCURRENCY_LIMIT: z.coerce.number().int().default(100),
    DEFAULT_ENV_EXECUTION_CONCURRENCY_BURST_FACTOR: z.coerce.number().default(1.0),
    DEFAULT_ORG_EXECUTION_CONCURRENCY_LIMIT: z.coerce.number().int().default(300),
    DEFAULT_DEV_ENV_EXECUTION_ATTEMPTS: z.coerce.number().int().positive().default(1),

    //API Rate limiting
    /**
     * @example "60s"
     * @example "1m"
     * @example "1h"
     * @example "1d"
     * @example "1000ms"
     * @example "1000s"
     */
    API_RATE_LIMIT_REFILL_INTERVAL: z.string().default("10s"), // refill 250 tokens every 10 seconds
    API_RATE_LIMIT_MAX: z.coerce.number().int().default(750), // allow bursts of 750 requests
    API_RATE_LIMIT_REFILL_RATE: z.coerce.number().int().default(250), // refix 250 tokens every 10 seconds
    API_RATE_LIMIT_REQUEST_LOGS_ENABLED: z.string().default("0"),
    API_RATE_LIMIT_REJECTION_LOGS_ENABLED: z.string().default("1"),
    API_RATE_LIMIT_LIMITER_LOGS_ENABLED: z.string().default("0"),

    API_RATE_LIMIT_JWT_WINDOW: z.string().default("1m"),
    API_RATE_LIMIT_JWT_TOKENS: z.coerce.number().int().default(60),

    //v3
    PROVIDER_SECRET: z.string().default("provider-secret"),
    COORDINATOR_SECRET: z.string().default("coordinator-secret"),
    DEPOT_TOKEN: z.string().optional(),
    DEPOT_ORG_ID: z.string().optional(),
    DEPOT_REGION: z.string().default("us-east-1"),

    // Deployment registry (v3)
    DEPLOY_REGISTRY_HOST: z.string().min(1),
    DEPLOY_REGISTRY_USERNAME: z.string().optional(),
    DEPLOY_REGISTRY_PASSWORD: z.string().optional(),
    DEPLOY_REGISTRY_NAMESPACE: z.string().min(1).default("trigger"),
    DEPLOY_REGISTRY_ECR_TAGS: z.string().optional(), // csv, for example: "key1=value1,key2=value2"
    DEPLOY_REGISTRY_ECR_ASSUME_ROLE_ARN: z.string().optional(),
    DEPLOY_REGISTRY_ECR_ASSUME_ROLE_EXTERNAL_ID: z.string().optional(),
    DEPLOY_REGISTRY_ECR_DEFAULT_REPOSITORY_POLICY: z.string().optional(), // raw IAM policy JSON applied to every repo created by the webapp

    // Deployment registry (v4) - falls back to v3 registry if not specified
    V4_DEPLOY_REGISTRY_HOST: z
      .string()
      .optional()
      .transform((v) => v ?? process.env.DEPLOY_REGISTRY_HOST)
      .pipe(z.string().min(1)), // Ensure final type is required string
    V4_DEPLOY_REGISTRY_USERNAME: z
      .string()
      .optional()
      .transform((v) => v ?? process.env.DEPLOY_REGISTRY_USERNAME),
    V4_DEPLOY_REGISTRY_PASSWORD: z
      .string()
      .optional()
      .transform((v) => v ?? process.env.DEPLOY_REGISTRY_PASSWORD),
    V4_DEPLOY_REGISTRY_NAMESPACE: z
      .string()
      .optional()
      .transform((v) => v ?? process.env.DEPLOY_REGISTRY_NAMESPACE)
      .pipe(z.string().min(1).default("trigger")), // Ensure final type is required string
    V4_DEPLOY_REGISTRY_ECR_TAGS: z
      .string()
      .optional()
      .transform((v) => v ?? process.env.DEPLOY_REGISTRY_ECR_TAGS),
    V4_DEPLOY_REGISTRY_ECR_ASSUME_ROLE_ARN: z
      .string()
      .optional()
      .transform((v) => v ?? process.env.DEPLOY_REGISTRY_ECR_ASSUME_ROLE_ARN),
    V4_DEPLOY_REGISTRY_ECR_ASSUME_ROLE_EXTERNAL_ID: z
      .string()
      .optional()
      .transform((v) => v ?? process.env.DEPLOY_REGISTRY_ECR_ASSUME_ROLE_EXTERNAL_ID),
    V4_DEPLOY_REGISTRY_ECR_DEFAULT_REPOSITORY_POLICY: z
      .string()
      .optional()
      .transform((v) => v ?? process.env.DEPLOY_REGISTRY_ECR_DEFAULT_REPOSITORY_POLICY),

    // Compute gateway (template creation during deploy finalize)
    COMPUTE_GATEWAY_URL: z.string().optional(),
    COMPUTE_GATEWAY_AUTH_TOKEN: z.string().optional(),
    COMPUTE_TEMPLATE_SHADOW_ROLLOUT_PCT: z.string().optional(),
    // Comma-separated machine preset names to build boot snapshots for on
    // deploy (e.g. "small-1x,small-2x,medium-1x"). Default: "small-1x".
    COMPUTE_TEMPLATE_MACHINE_PRESETS: z
      .string()
      .default("small-1x")
      .transform(parseMachinePresetCsv),
    // Subset of COMPUTE_TEMPLATE_MACHINE_PRESETS that must succeed for a
    // required-mode deploy to be considered successful. Failures of presets
    // outside this list are logged but don't fail the deploy. Defaults to the
    // full COMPUTE_TEMPLATE_MACHINE_PRESETS list when unset (everything required).
    COMPUTE_TEMPLATE_MACHINE_PRESETS_REQUIRED: z
      .string()
      .optional()
      .transform((v, ctx) =>
        parseMachinePresetCsv(v ?? process.env.COMPUTE_TEMPLATE_MACHINE_PRESETS ?? "small-1x", ctx)
      ),

    DEPLOY_IMAGE_PLATFORM: z.string().default("linux/amd64"),
    DEPLOY_TIMEOUT_MS: z.coerce
      .number()
      .int()
      .default(60 * 1000 * 8), // 8 minutes
    DEPLOY_QUEUE_TIMEOUT_MS: z.coerce
      .number()
      .int()
      .default(60 * 1000 * 15), // 15 minutes

    // When enabled, reject deploys made by v3 CLI versions (i.e. payloads that
    // omit the `type` field). v4 CLI versions always send `type` ("MANAGED" or "V1"),
    // so they are unaffected. Defaults to off so detection can run in
    // log-only mode before enforcement.
    DEPRECATE_V3_CLI_DEPLOYS_ENABLED: z.string().default("0"),

    // Master switch for the v3 engine (RunEngineVersion.V1) shutdown. When
    // enabled it: rejects triggers that resolve to V1 (single, batch, schedule,
    // replay, triggerAndWait) with a graceful error pointing at the v4 migration
    // guide; closes the legacy `trigger dev` websocket used by v3 CLIs; and turns
    // the V1 run-lifecycle background jobs (heartbeat timeout, TTL expiry, retry,
    // resume, scheduled fires) into no-ops so abandoned V1 runs stop generating
    // database load. v4 (V2) is never affected (every gate also checks the run is
    // V1). Defaults to off so self-hosted instances still on V1 keep working.
    DEPRECATE_V3_ENABLED: z.string().default("0"),

    // Verify the deploy image exists before promoting. Disable for out-of-band/air-gapped push. ECR only.
    DEPLOY_IMAGE_VERIFICATION_ENABLED: BoolEnv.default(true),

    OBJECT_STORE_BASE_URL: z.string().optional(),
    OBJECT_STORE_BUCKET: z.string().optional(),
    OBJECT_STORE_ACCESS_KEY_ID: z.string().optional(),
    OBJECT_STORE_SECRET_ACCESS_KEY: z.string().optional(),
    OBJECT_STORE_REGION: z.string().optional(),
    OBJECT_STORE_SERVICE: z.string().default("s3"),

    // Protocol to use for new uploads (e.g., "s3", "r2"). Data without protocol uses default provider above.
    // If specified, you must configure the corresponding provider using OBJECT_STORE_{PROTOCOL}_* env vars.
    // Example: OBJECT_STORE_DEFAULT_PROTOCOL=s3 requires OBJECT_STORE_S3_BASE_URL, OBJECT_STORE_S3_ACCESS_KEY_ID, etc.
    // Enables zero-downtime migration between providers (old data keeps working, new data uses new provider).
    OBJECT_STORE_DEFAULT_PROTOCOL: z
      .string()
      .regex(/^[a-z0-9]+$/)
      .optional(),

    ARTIFACTS_OBJECT_STORE_BUCKET: z.string().optional(),
    ARTIFACTS_OBJECT_STORE_BASE_URL: z.string().optional(),
    ARTIFACTS_OBJECT_STORE_ACCESS_KEY_ID: z.string().optional(),
    ARTIFACTS_OBJECT_STORE_SECRET_ACCESS_KEY: z.string().optional(),
    ARTIFACTS_OBJECT_STORE_REGION: z.string().optional(),
    EVENTS_BATCH_SIZE: z.coerce.number().int().default(100),
    EVENTS_BATCH_INTERVAL: z.coerce.number().int().default(1000),
    EVENTS_DEFAULT_LOG_RETENTION: z.coerce.number().int().default(7),
    EVENTS_MIN_CONCURRENCY: z.coerce.number().int().default(1),
    EVENTS_MAX_CONCURRENCY: z.coerce.number().int().default(10),
    EVENTS_MAX_BATCH_SIZE: z.coerce.number().int().default(500),
    EVENTS_MEMORY_PRESSURE_THRESHOLD: z.coerce.number().int().default(5000),
    EVENTS_LOAD_SHEDDING_THRESHOLD: z.coerce.number().int().default(100000),
    EVENTS_LOAD_SHEDDING_ENABLED: z.string().default("1"),
    SHARED_QUEUE_CONSUMER_POOL_SIZE: z.coerce.number().int().default(10),
    SHARED_QUEUE_CONSUMER_INTERVAL_MS: z.coerce.number().int().default(100),
    SHARED_QUEUE_CONSUMER_NEXT_TICK_INTERVAL_MS: z.coerce.number().int().default(100),
    SHARED_QUEUE_CONSUMER_EMIT_RESUME_DEPENDENCY_TIMEOUT_MS: z.coerce.number().int().default(1000),
    SHARED_QUEUE_CONSUMER_RESOLVE_PAYLOADS_BATCH_SIZE: z.coerce.number().int().default(25),

    MANAGED_WORKER_SECRET: z.string().default("managed-secret"),

    // Development OTEL environment variables
    DEV_OTEL_EXPORTER_OTLP_ENDPOINT: z.string().optional(),
    DEV_OTEL_METRICS_ENDPOINT: z.string().optional(),
    // If this is set to 1, then the below variables are used to configure the batch processor for spans and logs
    DEV_OTEL_BATCH_PROCESSING_ENABLED: z.string().default("0"),
    DEV_OTEL_SPAN_MAX_EXPORT_BATCH_SIZE: z.string().default("64"),
    DEV_OTEL_SPAN_SCHEDULED_DELAY_MILLIS: z.string().default("200"),
    DEV_OTEL_SPAN_EXPORT_TIMEOUT_MILLIS: z.string().default("30000"),
    DEV_OTEL_SPAN_MAX_QUEUE_SIZE: z.string().default("512"),
    DEV_OTEL_LOG_MAX_EXPORT_BATCH_SIZE: z.string().default("64"),
    DEV_OTEL_LOG_SCHEDULED_DELAY_MILLIS: z.string().default("200"),
    DEV_OTEL_LOG_EXPORT_TIMEOUT_MILLIS: z.string().default("30000"),
    DEV_OTEL_LOG_MAX_QUEUE_SIZE: z.string().default("512"),
    DEV_OTEL_METRICS_EXPORT_INTERVAL_MILLIS: z.string().optional(),
    DEV_OTEL_METRICS_EXPORT_TIMEOUT_MILLIS: z.string().optional(),
    DEV_OTEL_METRICS_COLLECTION_INTERVAL_MILLIS: z.string().optional(),

    PROD_OTEL_BATCH_PROCESSING_ENABLED: z.string().default("0"),
    PROD_OTEL_SPAN_MAX_EXPORT_BATCH_SIZE: z.string().default("64"),
    PROD_OTEL_SPAN_SCHEDULED_DELAY_MILLIS: z.string().default("200"),
    PROD_OTEL_SPAN_EXPORT_TIMEOUT_MILLIS: z.string().default("30000"),
    PROD_OTEL_SPAN_MAX_QUEUE_SIZE: z.string().default("512"),
    PROD_OTEL_LOG_MAX_EXPORT_BATCH_SIZE: z.string().default("64"),
    PROD_OTEL_LOG_SCHEDULED_DELAY_MILLIS: z.string().default("200"),
    PROD_OTEL_LOG_EXPORT_TIMEOUT_MILLIS: z.string().default("30000"),
    PROD_OTEL_LOG_MAX_QUEUE_SIZE: z.string().default("512"),
    PROD_OTEL_METRICS_EXPORT_INTERVAL_MILLIS: z.string().optional(),
    PROD_OTEL_METRICS_EXPORT_TIMEOUT_MILLIS: z.string().optional(),
    PROD_OTEL_METRICS_COLLECTION_INTERVAL_MILLIS: z.string().optional(),

    TRIGGER_OTEL_SPAN_ATTRIBUTE_COUNT_LIMIT: z.string().default("1024"),
    TRIGGER_OTEL_LOG_ATTRIBUTE_COUNT_LIMIT: z.string().default("1024"),
    TRIGGER_OTEL_SPAN_ATTRIBUTE_VALUE_LENGTH_LIMIT: z.string().default("131072"),
    TRIGGER_OTEL_LOG_ATTRIBUTE_VALUE_LENGTH_LIMIT: z.string().default("131072"),
    TRIGGER_OTEL_SPAN_EVENT_COUNT_LIMIT: z.string().default("10"),
    TRIGGER_OTEL_LINK_COUNT_LIMIT: z.string().default("2"),
    TRIGGER_OTEL_ATTRIBUTE_PER_LINK_COUNT_LIMIT: z.string().default("10"),
    TRIGGER_OTEL_ATTRIBUTE_PER_EVENT_COUNT_LIMIT: z.string().default("10"),

    CHECKPOINT_THRESHOLD_IN_MS: z.coerce.number().int().default(30000),

    // Internal OTEL environment variables
    INTERNAL_OTEL_TRACE_EXPORTER_URL: z.string().optional(),
    INTERNAL_OTEL_TRACE_EXPORTER_AUTH_HEADERS: z.string().optional(),
    INTERNAL_OTEL_TRACE_LOGGING_ENABLED: z.string().default("1"),
    // this means 1/20 traces or 5% of traces will be sampled (sampled = recorded)
    INTERNAL_OTEL_TRACE_SAMPLING_RATE: z.string().default("20"),
    INTERNAL_OTEL_TRACE_INSTRUMENT_PRISMA_ENABLED: z.string().default("0"),
    INTERNAL_OTEL_TRACE_DISABLED: z.string().default("0"),
    DISABLE_HTTP_INSTRUMENTATION: BoolEnv.default(false),

    INTERNAL_OTEL_LOG_EXPORTER_URL: z.string().optional(),
    INTERNAL_OTEL_METRIC_EXPORTER_URL: z.string().optional(),
    INTERNAL_OTEL_METRIC_EXPORTER_AUTH_HEADERS: z.string().optional(),
    INTERNAL_OTEL_METRIC_EXPORTER_ENABLED: z.string().default("0"),
    INTERNAL_OTEL_METRIC_EXPORTER_INTERVAL_MS: z.coerce.number().int().default(30_000),
    INTERNAL_OTEL_HOST_METRICS_ENABLED: BoolEnv.default(true),
    INTERNAL_OTEL_NODEJS_METRICS_ENABLED: BoolEnv.default(true),
    INTERNAL_OTEL_ADDITIONAL_DETECTORS_ENABLED: BoolEnv.default(true),

    ORG_SLACK_INTEGRATION_CLIENT_ID: z.string().optional(),
    ORG_SLACK_INTEGRATION_CLIENT_SECRET: z.string().optional(),

    /** Vercel integration OAuth credentials */
    VERCEL_INTEGRATION_CLIENT_ID: z.string().optional(),
    VERCEL_INTEGRATION_CLIENT_SECRET: z.string().optional(),
    VERCEL_INTEGRATION_APP_SLUG: z.string().optional(),

    /** These enable the alerts feature in v3 */
    ALERT_EMAIL_TRANSPORT: z.enum(["resend", "smtp", "aws-ses"]).optional(),
    ALERT_FROM_EMAIL: z.string().optional(),
    ALERT_REPLY_TO_EMAIL: z.string().optional(),
    ALERT_RESEND_API_KEY: z.string().optional(),
    ALERT_SMTP_HOST: z.string().optional(),
    ALERT_SMTP_PORT: z.coerce.number().optional(),
    ALERT_SMTP_SECURE: BoolEnv.optional(),
    ALERT_SMTP_USER: z.string().optional(),
    ALERT_SMTP_PASSWORD: z.string().optional(),
    ALERT_RATE_LIMITER_EMISSION_INTERVAL: z.coerce.number().int().default(2_500),
    ALERT_RATE_LIMITER_BURST_TOLERANCE: z.coerce.number().int().default(10_000),
    ALERT_RATE_LIMITER_REDIS_HOST: z
      .string()
      .optional()
      .transform((v) => v ?? process.env.REDIS_HOST),
    ALERT_RATE_LIMITER_REDIS_READER_HOST: z
      .string()
      .optional()
      .transform((v) => v ?? process.env.REDIS_READER_HOST),
    ALERT_RATE_LIMITER_REDIS_READER_PORT: z.coerce
      .number()
      .optional()
      .transform(
        (v) =>
          v ?? (process.env.REDIS_READER_PORT ? parseInt(process.env.REDIS_READER_PORT) : undefined)
      ),
    ALERT_RATE_LIMITER_REDIS_PORT: z.coerce
      .number()
      .optional()
      .transform(
        (v) => v ?? (process.env.REDIS_PORT ? parseInt(process.env.REDIS_PORT) : undefined)
      ),
    ALERT_RATE_LIMITER_REDIS_USERNAME: z
      .string()
      .optional()
      .transform((v) => v ?? process.env.REDIS_USERNAME),
    ALERT_RATE_LIMITER_REDIS_PASSWORD: z
      .string()
      .optional()
      .transform((v) => v ?? process.env.REDIS_PASSWORD),
    ALERT_RATE_LIMITER_REDIS_TLS_DISABLED: z
      .string()
      .default(process.env.REDIS_TLS_DISABLED ?? "false"),
    ALERT_RATE_LIMITER_REDIS_CLUSTER_MODE_ENABLED: z.string().default("0"),

    LOOPS_API_KEY: z.string().optional(),
    ATTIO_API_KEY: z.string().optional(),
    MARQS_DISABLE_REBALANCING: BoolEnv.default(false),
    MARQS_VISIBILITY_TIMEOUT_MS: z.coerce
      .number()
      .int()
      .default(60 * 1000 * 15),
    MARQS_SHARED_QUEUE_LIMIT: z.coerce.number().int().default(1000),
    MARQS_MAXIMUM_QUEUE_PER_ENV_COUNT: z.coerce.number().int().default(50),
    MARQS_DEV_QUEUE_LIMIT: z.coerce.number().int().default(1000),
    MARQS_MAXIMUM_NACK_COUNT: z.coerce.number().int().default(64),
    MARQS_CONCURRENCY_LIMIT_BIAS: z.coerce.number().default(0.75),
    MARQS_AVAILABLE_CAPACITY_BIAS: z.coerce.number().default(0.3),
    MARQS_QUEUE_AGE_RANDOMIZATION_BIAS: z.coerce.number().default(0.25),
    MARQS_REUSE_SNAPSHOT_COUNT: z.coerce.number().int().default(0),
    MARQS_MAXIMUM_ENV_COUNT: z.coerce.number().int().optional(),
    MARQS_SHARED_WORKER_QUEUE_CONSUMER_INTERVAL_MS: z.coerce.number().int().default(250),
    MARQS_SHARED_WORKER_QUEUE_MAX_MESSAGE_COUNT: z.coerce.number().int().default(10),

    MARQS_SHARED_WORKER_QUEUE_EAGER_DEQUEUE_ENABLED: z.string().default("0"),
    MARQS_WORKER_ENABLED: z.string().default("0"),
    MARQS_WORKER_COUNT: z.coerce.number().int().default(2),
    MARQS_WORKER_CONCURRENCY_LIMIT: z.coerce.number().int().default(50),
    MARQS_WORKER_CONCURRENCY_TASKS_PER_WORKER: z.coerce.number().int().default(5),
    MARQS_WORKER_POLL_INTERVAL_MS: z.coerce.number().int().default(100),
    MARQS_WORKER_IMMEDIATE_POLL_INTERVAL_MS: z.coerce.number().int().default(100),
    MARQS_WORKER_SHUTDOWN_TIMEOUT_MS: z.coerce.number().int().default(60_000),
    MARQS_SHARED_WORKER_QUEUE_COOLOFF_COUNT_THRESHOLD: z.coerce.number().int().default(10),
    MARQS_SHARED_WORKER_QUEUE_COOLOFF_PERIOD_MS: z.coerce.number().int().default(5_000),

    PROD_TASK_HEARTBEAT_INTERVAL_MS: z.coerce.number().int().optional(),

    VERBOSE_GRAPHILE_LOGGING: z.string().default("false"),
    V2_MARQS_ENABLED: z.string().default("0"),
    V2_MARQS_CONSUMER_POOL_ENABLED: z.string().default("0"),
    V2_MARQS_CONSUMER_POOL_SIZE: z.coerce.number().int().default(10),
    V2_MARQS_CONSUMER_POLL_INTERVAL_MS: z.coerce.number().int().default(1000),
    V2_MARQS_QUEUE_SELECTION_COUNT: z.coerce.number().int().default(36),
    V2_MARQS_VISIBILITY_TIMEOUT_MS: z.coerce
      .number()
      .int()
      .default(60 * 1000 * 15),
    V2_MARQS_DEFAULT_ENV_CONCURRENCY: z.coerce.number().int().default(100),
    V2_MARQS_VERBOSE: z.string().default("0"),
    V3_MARQS_CONCURRENCY_MONITOR_ENABLED: z.string().default("0"),
    V2_MARQS_CONCURRENCY_MONITOR_ENABLED: z.string().default("0"),
    /* Usage settings */
    USAGE_EVENT_URL: z.string().optional(),
    PROD_USAGE_HEARTBEAT_INTERVAL_MS: z.coerce.number().int().optional(),

    CENTS_PER_RUN: z.coerce.number().default(0),

    EVENT_LOOP_MONITOR_ENABLED: z.string().default("1"),
    RESOURCE_MONITOR_ENABLED: z.string().default("0"),
    MAXIMUM_LIVE_RELOADING_EVENTS: z.coerce.number().int().default(1000),
    MAXIMUM_TRACE_SUMMARY_VIEW_COUNT: z.coerce.number().int().default(25_000),
    MAXIMUM_TRACE_DETAILED_SUMMARY_VIEW_COUNT: z.coerce.number().int().default(10_000),
    // Emergency circuit breaker: when set, clamps the trace summary and detailed
    // summary span limits on both event store paths to this value. Unset = disabled.
    TRACE_VIEW_EMERGENCY_SPAN_CAP: z.coerce.number().int().positive().optional(),
    TASK_PAYLOAD_OFFLOAD_THRESHOLD: z.coerce.number().int().default(524_288), // 512KB
    BATCH_PAYLOAD_OFFLOAD_THRESHOLD: z.coerce.number().int().optional(), // Defaults to TASK_PAYLOAD_OFFLOAD_THRESHOLD if not set
    TASK_PAYLOAD_MAXIMUM_SIZE: z.coerce.number().int().default(3_145_728), // 3MB
    BATCH_TASK_PAYLOAD_MAXIMUM_SIZE: z.coerce.number().int().default(1_000_000), // 1MB
    TASK_RUN_METADATA_MAXIMUM_SIZE: z.coerce.number().int().default(262_144), // 256KB

    MAXIMUM_DEV_QUEUE_SIZE: z.coerce.number().int().optional(),
    MAXIMUM_DEPLOYED_QUEUE_SIZE: z.coerce.number().int().optional(),
    QUEUE_SIZE_CACHE_TTL_MS: z.coerce.number().int().optional().default(1_000), // 1 second
    QUEUE_SIZE_CACHE_MAX_SIZE: z.coerce.number().int().optional().default(5_000),
    QUEUE_SIZE_CACHE_ENABLED: z.coerce.number().int().optional().default(1),
    MAX_BATCH_V2_TRIGGER_ITEMS: z.coerce.number().int().default(500),
    MAX_BATCH_AND_WAIT_V2_TRIGGER_ITEMS: z.coerce.number().int().default(500),

    // 2-phase batch API settings
    STREAMING_BATCH_MAX_ITEMS: z.coerce.number().int().default(1_000), // Max items in streaming batch
    STREAMING_BATCH_ITEM_MAXIMUM_SIZE: z.coerce.number().int().default(3_145_728),
    // Number of streamed batch items ingested concurrently in Phase 2. Peak
    // in-flight memory per request ≈ this × STREAMING_BATCH_ITEM_MAXIMUM_SIZE,
    // so raise with care. Set to 1 for fully sequential ingestion.
    STREAMING_BATCH_INGEST_CONCURRENCY: z.coerce.number().int().positive().default(10),
    BATCH_RATE_LIMIT_REFILL_RATE: z.coerce.number().int().default(100),
    BATCH_RATE_LIMIT_MAX: z.coerce.number().int().default(1200),
    BATCH_RATE_LIMIT_REFILL_INTERVAL: z.string().default("10s"),
    BATCH_CONCURRENCY_LIMIT_DEFAULT: z.coerce.number().int().default(5),

    REALTIME_STREAM_VERSION: z.enum(["v1", "v2"]).default("v1"),
    REALTIME_STREAM_MAX_LENGTH: z.coerce.number().int().default(1000),
    REALTIME_STREAM_TTL: z.coerce
      .number()
      .int()
      .default(60 * 60 * 24), // 1 day in seconds
    BATCH_METADATA_OPERATIONS_FLUSH_INTERVAL_MS: z.coerce.number().int().default(1000),
    BATCH_METADATA_OPERATIONS_FLUSH_ENABLED: z.string().default("1"),
    BATCH_METADATA_OPERATIONS_FLUSH_LOGGING_ENABLED: z.string().default("1"),

    // Run Engine 2.0
    RUN_ENGINE_WORKER_COUNT: z.coerce.number().int().default(4),
    RUN_ENGINE_TASKS_PER_WORKER: z.coerce.number().int().default(10),
    RUN_ENGINE_WORKER_CONCURRENCY_LIMIT: z.coerce.number().int().default(10),
    RUN_ENGINE_WORKER_POLL_INTERVAL: z.coerce.number().int().default(100),
    RUN_ENGINE_WORKER_IMMEDIATE_POLL_INTERVAL: z.coerce.number().int().default(100),
    RUN_ENGINE_TIMEOUT_PENDING_EXECUTING: z.coerce.number().int().default(60_000),
    RUN_ENGINE_TIMEOUT_PENDING_CANCEL: z.coerce.number().int().default(60_000),
    RUN_ENGINE_TIMEOUT_EXECUTING: z.coerce.number().int().default(300_000), // 5 minutes
    RUN_ENGINE_TIMEOUT_EXECUTING_WITH_WAITPOINTS: z.coerce.number().int().default(300_000), // 5 minutes
    RUN_ENGINE_TIMEOUT_SUSPENDED: z.coerce
      .number()
      .int()
      .default(60_000 * 10),
    RUN_ENGINE_DEBUG_WORKER_NOTIFICATIONS: BoolEnv.default(false),
    RUN_ENGINE_PARENT_QUEUE_LIMIT: z.coerce.number().int().default(1000),
    RUN_ENGINE_CONCURRENCY_LIMIT_BIAS: z.coerce.number().default(0.75),
    RUN_ENGINE_AVAILABLE_CAPACITY_BIAS: z.coerce.number().default(0.3),
    RUN_ENGINE_QUEUE_AGE_RANDOMIZATION_BIAS: z.coerce.number().default(0.25),
    RUN_ENGINE_REUSE_SNAPSHOT_COUNT: z.coerce.number().int().default(0),
    RUN_ENGINE_MAXIMUM_ENV_COUNT: z.coerce.number().int().optional(),
    RUN_ENGINE_RUN_QUEUE_SHARD_COUNT: z.coerce.number().int().default(4),
    RUN_ENGINE_WORKER_SHUTDOWN_TIMEOUT_MS: z.coerce.number().int().default(60_000),
    RUN_ENGINE_RETRY_WARM_START_THRESHOLD_MS: z.coerce.number().int().default(30_000),
    RUN_ENGINE_PROCESS_WORKER_QUEUE_DEBOUNCE_MS: z.coerce.number().int().default(200),
    RUN_ENGINE_DEQUEUE_BLOCKING_TIMEOUT_SECONDS: z.coerce.number().int().default(10),
    RUN_ENGINE_DEQUEUE_DISABLED_WORKER_QUEUES: z.string().optional(),
    RUN_ENGINE_MASTER_QUEUE_CONSUMERS_INTERVAL_MS: z.coerce.number().int().default(1000),
    // Off by default. Enable on a single service (e.g. the engine worker) so only one
    // instance reports worker queue length, rather than every replica.
    RUN_ENGINE_WORKER_QUEUE_OBSERVER_ENABLED: z.string().default("0"),
    RUN_ENGINE_WORKER_QUEUE_OBSERVER_INTERVAL_MS: z.coerce.number().int().default(30_000),
    // Comma-separated cloud providers to exclude from worker queue length observation.
    RUN_ENGINE_WORKER_QUEUE_OBSERVER_EXCLUDED_CLOUD_PROVIDERS: z.string().default("digitalocean"),
    RUN_ENGINE_MASTER_QUEUE_COOLOFF_PERIOD_MS: z.coerce.number().int().default(10_000),
    RUN_ENGINE_MASTER_QUEUE_COOLOFF_COUNT_THRESHOLD: z.coerce.number().int().default(10),
    RUN_ENGINE_MASTER_QUEUE_CONSUMER_DEQUEUE_COUNT: z.coerce.number().int().default(10),
    RUN_ENGINE_CONCURRENCY_SWEEPER_SCAN_SCHEDULE: z.string().optional(),
    RUN_ENGINE_CONCURRENCY_SWEEPER_PROCESS_MARKED_SCHEDULE: z.string().optional(),
    RUN_ENGINE_CONCURRENCY_SWEEPER_SCAN_JITTER_IN_MS: z.coerce.number().int().optional(),
    RUN_ENGINE_CONCURRENCY_SWEEPER_PROCESS_MARKED_JITTER_IN_MS: z.coerce.number().int().optional(),

    // TTL System settings for automatic run expiration
    RUN_ENGINE_TTL_SYSTEM_DISABLED: BoolEnv.default(false),
    RUN_ENGINE_TTL_SYSTEM_SHARD_COUNT: z.coerce.number().int().optional(),
    RUN_ENGINE_TTL_SYSTEM_POLL_INTERVAL_MS: z.coerce.number().int().default(1_000),
    RUN_ENGINE_TTL_SYSTEM_BATCH_SIZE: z.coerce.number().int().default(100),
    RUN_ENGINE_TTL_WORKER_CONCURRENCY: z.coerce.number().int().default(1),
    RUN_ENGINE_TTL_WORKER_BATCH_MAX_SIZE: z.coerce.number().int().default(50),
    RUN_ENGINE_TTL_CONSUMERS_DISABLED: BoolEnv.default(false),
    RUN_ENGINE_TTL_WORKER_BATCH_MAX_WAIT_MS: z.coerce.number().int().default(5_000),

    /** Optional maximum TTL for all runs (e.g. "14d"). If set, runs without an explicit TTL
     *  will use this as their TTL, and runs with a TTL larger than this will be clamped. */
    RUN_ENGINE_DEFAULT_MAX_TTL: z.string().optional(),

    RUN_ENGINE_RUN_LOCK_DURATION: z.coerce.number().int().default(5000),
    RUN_ENGINE_RUN_LOCK_AUTOMATIC_EXTENSION_THRESHOLD: z.coerce.number().int().default(1000),
    RUN_ENGINE_RUN_LOCK_MAX_RETRIES: z.coerce.number().int().default(10),
    RUN_ENGINE_RUN_LOCK_BASE_DELAY: z.coerce.number().int().default(100),
    RUN_ENGINE_RUN_LOCK_MAX_DELAY: z.coerce.number().int().default(3000),
    RUN_ENGINE_RUN_LOCK_BACKOFF_MULTIPLIER: z.coerce.number().default(1.8),
    RUN_ENGINE_RUN_LOCK_JITTER_FACTOR: z.coerce.number().default(0.15),
    RUN_ENGINE_RUN_LOCK_MAX_TOTAL_WAIT_TIME: z.coerce.number().int().default(15000),

    RUN_ENGINE_SUSPENDED_HEARTBEAT_RETRIES_MAX_COUNT: z.coerce.number().int().default(12),
    RUN_ENGINE_SUSPENDED_HEARTBEAT_RETRIES_MAX_DELAY_MS: z.coerce
      .number()
      .int()
      .default(60_000 * 60 * 6),
    RUN_ENGINE_SUSPENDED_HEARTBEAT_RETRIES_INITIAL_DELAY_MS: z.coerce
      .number()
      .int()
      .default(60_000),
    RUN_ENGINE_SUSPENDED_HEARTBEAT_RETRIES_FACTOR: z.coerce.number().default(2),

    /** Maximum duration in milliseconds that a run can be debounced. Default: 1 hour (3,600,000ms) */
    RUN_ENGINE_MAXIMUM_DEBOUNCE_DURATION_MS: z.coerce
      .number()
      .int()
      .default(60_000 * 60), // 1 hour

    /**
     * Bucket size in milliseconds used to quantize the newly computed `delayUntil`
     * in the debounce system. Quantization collapses concurrent triggers on the
     * same hot debounce key onto the same target time so the unlocked fast-path
     * skip is effective. Set to 0 to disable. Default: 1000ms (1s).
     */
    RUN_ENGINE_DEBOUNCE_QUANTIZE_NEW_DELAY_UNTIL_MS: z.coerce.number().int().min(0).default(1000),

    /**
     * Whether the unlocked fast-path skip is enabled in the debounce system.
     * Acts as a kill switch in case the fast-path needs to be disabled in
     * production without a redeploy. Default: "1" (enabled).
     */
    RUN_ENGINE_DEBOUNCE_FAST_PATH_SKIP_ENABLED: z.string().default("1"),

    RUN_ENGINE_WORKER_REDIS_HOST: z
      .string()
      .optional()
      .transform((v) => v ?? process.env.REDIS_HOST),
    RUN_ENGINE_WORKER_REDIS_READER_HOST: z
      .string()
      .optional()
      .transform((v) => v ?? process.env.REDIS_READER_HOST),
    RUN_ENGINE_WORKER_REDIS_READER_PORT: z.coerce
      .number()
      .optional()
      .transform(
        (v) =>
          v ?? (process.env.REDIS_READER_PORT ? parseInt(process.env.REDIS_READER_PORT) : undefined)
      ),
    RUN_ENGINE_WORKER_REDIS_PORT: z.coerce
      .number()
      .optional()
      .transform(
        (v) => v ?? (process.env.REDIS_PORT ? parseInt(process.env.REDIS_PORT) : undefined)
      ),
    RUN_ENGINE_WORKER_REDIS_USERNAME: z
      .string()
      .optional()
      .transform((v) => v ?? process.env.REDIS_USERNAME),
    RUN_ENGINE_WORKER_REDIS_PASSWORD: z
      .string()
      .optional()
      .transform((v) => v ?? process.env.REDIS_PASSWORD),
    RUN_ENGINE_WORKER_REDIS_TLS_DISABLED: z
      .string()
      .default(process.env.REDIS_TLS_DISABLED ?? "false"),

    RUN_ENGINE_RUN_QUEUE_REDIS_HOST: z
      .string()
      .optional()
      .transform((v) => v ?? process.env.REDIS_HOST),
    RUN_ENGINE_RUN_QUEUE_REDIS_READER_HOST: z
      .string()
      .optional()
      .transform((v) => v ?? process.env.REDIS_READER_HOST),
    RUN_ENGINE_RUN_QUEUE_REDIS_READER_PORT: z.coerce
      .number()
      .optional()
      .transform(
        (v) =>
          v ?? (process.env.REDIS_READER_PORT ? parseInt(process.env.REDIS_READER_PORT) : undefined)
      ),
    RUN_ENGINE_RUN_QUEUE_REDIS_PORT: z.coerce
      .number()
      .optional()
      .transform(
        (v) => v ?? (process.env.REDIS_PORT ? parseInt(process.env.REDIS_PORT) : undefined)
      ),
    RUN_ENGINE_RUN_QUEUE_REDIS_USERNAME: z
      .string()
      .optional()
      .transform((v) => v ?? process.env.REDIS_USERNAME),
    RUN_ENGINE_RUN_QUEUE_REDIS_PASSWORD: z
      .string()
      .optional()
      .transform((v) => v ?? process.env.REDIS_PASSWORD),
    RUN_ENGINE_RUN_QUEUE_REDIS_TLS_DISABLED: z
      .string()
      .default(process.env.REDIS_TLS_DISABLED ?? "false"),

    RUN_ENGINE_RUN_LOCK_REDIS_HOST: z
      .string()
      .optional()
      .transform((v) => v ?? process.env.REDIS_HOST),
    RUN_ENGINE_RUN_LOCK_REDIS_READER_HOST: z
      .string()
      .optional()
      .transform((v) => v ?? process.env.REDIS_READER_HOST),
    RUN_ENGINE_RUN_LOCK_REDIS_READER_PORT: z.coerce
      .number()
      .optional()
      .transform(
        (v) =>
          v ?? (process.env.REDIS_READER_PORT ? parseInt(process.env.REDIS_READER_PORT) : undefined)
      ),
    RUN_ENGINE_RUN_LOCK_REDIS_PORT: z.coerce
      .number()
      .optional()
      .transform(
        (v) => v ?? (process.env.REDIS_PORT ? parseInt(process.env.REDIS_PORT) : undefined)
      ),
    RUN_ENGINE_RUN_LOCK_REDIS_USERNAME: z
      .string()
      .optional()
      .transform((v) => v ?? process.env.REDIS_USERNAME),
    RUN_ENGINE_RUN_LOCK_REDIS_PASSWORD: z
      .string()
      .optional()
      .transform((v) => v ?? process.env.REDIS_PASSWORD),
    RUN_ENGINE_RUN_LOCK_REDIS_TLS_DISABLED: z
      .string()
      .default(process.env.REDIS_TLS_DISABLED ?? "false"),

    RUN_ENGINE_DEV_PRESENCE_REDIS_HOST: z
      .string()
      .optional()
      .transform((v) => v ?? process.env.REDIS_HOST),
    RUN_ENGINE_DEV_PRESENCE_REDIS_READER_HOST: z
      .string()
      .optional()
      .transform((v) => v ?? process.env.REDIS_READER_HOST),
    RUN_ENGINE_DEV_PRESENCE_REDIS_READER_PORT: z.coerce
      .number()
      .optional()
      .transform(
        (v) =>
          v ?? (process.env.REDIS_READER_PORT ? parseInt(process.env.REDIS_READER_PORT) : undefined)
      ),
    RUN_ENGINE_DEV_PRESENCE_REDIS_PORT: z.coerce
      .number()
      .optional()
      .transform(
        (v) => v ?? (process.env.REDIS_PORT ? parseInt(process.env.REDIS_PORT) : undefined)
      ),
    RUN_ENGINE_DEV_PRESENCE_REDIS_USERNAME: z
      .string()
      .optional()
      .transform((v) => v ?? process.env.REDIS_USERNAME),
    RUN_ENGINE_DEV_PRESENCE_REDIS_PASSWORD: z
      .string()
      .optional()
      .transform((v) => v ?? process.env.REDIS_PASSWORD),
    RUN_ENGINE_DEV_PRESENCE_REDIS_TLS_DISABLED: z
      .string()
      .default(process.env.REDIS_TLS_DISABLED ?? "false"),

    //API Rate limiting
    /**
     * @example "60s"
     * @example "1m"
     * @example "1h"
     * @example "1d"
     * @example "1000ms"
     * @example "1000s"
     */
    RUN_ENGINE_RATE_LIMIT_REFILL_INTERVAL: z.string().default("10s"), // refill 250 tokens every 10 seconds
    RUN_ENGINE_RATE_LIMIT_MAX: z.coerce.number().int().default(1200), // allow bursts of 750 requests
    RUN_ENGINE_RATE_LIMIT_REFILL_RATE: z.coerce.number().int().default(400), // refix 250 tokens every 10 seconds
    RUN_ENGINE_RATE_LIMIT_REQUEST_LOGS_ENABLED: z.string().default("0"),
    RUN_ENGINE_RATE_LIMIT_REJECTION_LOGS_ENABLED: z.string().default("1"),
    RUN_ENGINE_RATE_LIMIT_LIMITER_LOGS_ENABLED: z.string().default("0"),

    RUN_ENGINE_RELEASE_CONCURRENCY_ENABLED: z.string().default("0"),
    RUN_ENGINE_RELEASE_CONCURRENCY_DISABLE_CONSUMERS: z.string().default("0"),
    RUN_ENGINE_RELEASE_CONCURRENCY_MAX_TOKENS_RATIO: z.coerce.number().default(1),
    RUN_ENGINE_RELEASE_CONCURRENCY_RELEASINGS_MAX_AGE: z.coerce
      .number()
      .int()
      .default(60_000 * 30),
    RUN_ENGINE_RELEASE_CONCURRENCY_RELEASINGS_POLL_INTERVAL: z.coerce
      .number()
      .int()
      .default(60_000),
    RUN_ENGINE_RELEASE_CONCURRENCY_MAX_RETRIES: z.coerce.number().int().default(3),
    RUN_ENGINE_RELEASE_CONCURRENCY_CONSUMERS_COUNT: z.coerce.number().int().default(1),
    RUN_ENGINE_RELEASE_CONCURRENCY_POLL_INTERVAL: z.coerce.number().int().default(500),
    RUN_ENGINE_RELEASE_CONCURRENCY_BATCH_SIZE: z.coerce.number().int().default(10),

    RUN_ENGINE_WORKER_ENABLED: z.string().default("1"),
    RUN_ENGINE_WORKER_LOG_LEVEL: z.enum(["log", "error", "warn", "info", "debug"]).default("info"),
    RUN_ENGINE_RUN_QUEUE_LOG_LEVEL: z
      .enum(["log", "error", "warn", "info", "debug"])
      .default("info"),
    RUN_ENGINE_TREAT_PRODUCTION_EXECUTION_STALLS_AS_OOM: z.string().default("0"),
    RUN_ENGINE_READ_REPLICA_SNAPSHOTS_SINCE_ENABLED: z.string().default("0"),
    RUN_ENGINE_SNAPSHOTS_SINCE_REPLICA_RETRY_MIN_MS: z.coerce.number().int().default(50),
    RUN_ENGINE_SNAPSHOTS_SINCE_REPLICA_RETRY_MAX_MS: z.coerce.number().int().default(200),
    RUN_ENGINE_DEBOUNCE_USE_REPLICA_FOR_FAST_PATH_READ: z.string().default("0"),

    /** How long should the presence ttl last */
    DEV_PRESENCE_SSE_TIMEOUT: z.coerce.number().int().default(30_000),
    DEV_PRESENCE_TTL_MS: z.coerce.number().int().default(5_000),
    DEV_PRESENCE_POLL_MS: z.coerce.number().int().default(1_000),
    /** How many ms to wait until dequeuing again, if there was a run last time */
    DEV_DEQUEUE_INTERVAL_WITH_RUN: z.coerce.number().int().default(250),
    /** How many ms to wait until dequeuing again, if there was no run last time */
    DEV_DEQUEUE_INTERVAL_WITHOUT_RUN: z.coerce.number().int().default(1_000),
    /** The max number of runs per API call that we'll dequeue in DEV */
    DEV_DEQUEUE_MAX_RUNS_PER_PULL: z.coerce.number().int().default(10),

    /** The maximum concurrent local run processes executing at once in dev. This is a hard limit */
    DEV_MAX_CONCURRENT_RUNS: z.coerce.number().int().optional(),

    /** The CLI should connect to this for dev runs */
    DEV_ENGINE_URL: z.string().default(process.env.APP_ORIGIN ?? "http://localhost:3030"),

    LEGACY_RUN_ENGINE_WORKER_ENABLED: z.string().default(process.env.WORKER_ENABLED ?? "true"),
    LEGACY_RUN_ENGINE_WORKER_CONCURRENCY_WORKERS: z.coerce.number().int().default(2),
    LEGACY_RUN_ENGINE_WORKER_CONCURRENCY_TASKS_PER_WORKER: z.coerce.number().int().default(1),
    LEGACY_RUN_ENGINE_WORKER_POLL_INTERVAL: z.coerce.number().int().default(1000),
    LEGACY_RUN_ENGINE_WORKER_IMMEDIATE_POLL_INTERVAL: z.coerce.number().int().default(50),
    LEGACY_RUN_ENGINE_WORKER_CONCURRENCY_LIMIT: z.coerce.number().int().default(50),
    LEGACY_RUN_ENGINE_WORKER_SHUTDOWN_TIMEOUT_MS: z.coerce.number().int().default(60_000),
    LEGACY_RUN_ENGINE_WORKER_LOG_LEVEL: z
      .enum(["log", "error", "warn", "info", "debug"])
      .default("info"),

    LEGACY_RUN_ENGINE_WORKER_REDIS_HOST: z
      .string()
      .optional()
      .transform((v) => v ?? process.env.REDIS_HOST),
    LEGACY_RUN_ENGINE_WORKER_REDIS_READER_HOST: z
      .string()
      .optional()
      .transform((v) => v ?? process.env.REDIS_READER_HOST),
    LEGACY_RUN_ENGINE_WORKER_REDIS_READER_PORT: z.coerce
      .number()
      .optional()
      .transform(
        (v) =>
          v ?? (process.env.REDIS_READER_PORT ? parseInt(process.env.REDIS_READER_PORT) : undefined)
      ),
    LEGACY_RUN_ENGINE_WORKER_REDIS_PORT: z.coerce
      .number()
      .optional()
      .transform(
        (v) => v ?? (process.env.REDIS_PORT ? parseInt(process.env.REDIS_PORT) : undefined)
      ),
    LEGACY_RUN_ENGINE_WORKER_REDIS_USERNAME: z
      .string()
      .optional()
      .transform((v) => v ?? process.env.REDIS_USERNAME),
    LEGACY_RUN_ENGINE_WORKER_REDIS_PASSWORD: z
      .string()
      .optional()
      .transform((v) => v ?? process.env.REDIS_PASSWORD),
    LEGACY_RUN_ENGINE_WORKER_REDIS_TLS_DISABLED: z
      .string()
      .default(process.env.REDIS_TLS_DISABLED ?? "false"),
    LEGACY_RUN_ENGINE_WORKER_REDIS_CLUSTER_MODE_ENABLED: z.string().default("0"),

    LEGACY_RUN_ENGINE_WAITING_FOR_DEPLOY_BATCH_SIZE: z.coerce.number().int().default(100),
    LEGACY_RUN_ENGINE_WAITING_FOR_DEPLOY_BATCH_STAGGER_MS: z.coerce.number().int().default(1_000),
    LEGACY_RUN_ENGINE_WAITING_FOR_DEPLOY_DISABLED: z.string().default("0"),

    COMMON_WORKER_ENABLED: z.string().default(process.env.WORKER_ENABLED ?? "true"),
    COMMON_WORKER_CONCURRENCY_WORKERS: z.coerce.number().int().default(2),
    COMMON_WORKER_CONCURRENCY_TASKS_PER_WORKER: z.coerce.number().int().default(10),
    COMMON_WORKER_POLL_INTERVAL: z.coerce.number().int().default(1000),
    COMMON_WORKER_IMMEDIATE_POLL_INTERVAL: z.coerce.number().int().default(50),
    COMMON_WORKER_CONCURRENCY_LIMIT: z.coerce.number().int().default(50),
    COMMON_WORKER_SHUTDOWN_TIMEOUT_MS: z.coerce.number().int().default(60_000),
    COMMON_WORKER_LOG_LEVEL: z.enum(["log", "error", "warn", "info", "debug"]).default("info"),

    COMMON_WORKER_REDIS_HOST: z
      .string()
      .optional()
      .transform((v) => v ?? process.env.REDIS_HOST),
    COMMON_WORKER_REDIS_READER_HOST: z
      .string()
      .optional()
      .transform((v) => v ?? process.env.REDIS_READER_HOST),
    COMMON_WORKER_REDIS_READER_PORT: z.coerce
      .number()
      .optional()
      .transform(
        (v) =>
          v ?? (process.env.REDIS_READER_PORT ? parseInt(process.env.REDIS_READER_PORT) : undefined)
      ),
    COMMON_WORKER_REDIS_PORT: z.coerce
      .number()
      .optional()
      .transform(
        (v) => v ?? (process.env.REDIS_PORT ? parseInt(process.env.REDIS_PORT) : undefined)
      ),
    COMMON_WORKER_REDIS_USERNAME: z
      .string()
      .optional()
      .transform((v) => v ?? process.env.REDIS_USERNAME),
    COMMON_WORKER_REDIS_PASSWORD: z
      .string()
      .optional()
      .transform((v) => v ?? process.env.REDIS_PASSWORD),
    COMMON_WORKER_REDIS_TLS_DISABLED: z.string().default(process.env.REDIS_TLS_DISABLED ?? "false"),
    COMMON_WORKER_REDIS_CLUSTER_MODE_ENABLED: z.string().default("0"),

    // Global default for the scheduled worker-queue split. When "1", runs in a
    // scheduled lineage (rootTriggerSource === "schedule") are routed to a
    // dedicated `<region>:scheduled` worker queue so a separate consumer fleet
    // can dequeue them independently of standard/agent runs. The per-org
    // `workerQueueScheduledSplitEnabled` feature flag overrides this default in
    // BOTH directions (an org set to false stays on the single queue even when
    // this is "1"; an org set to true splits even when this is "0"). Never
    // applies to DEVELOPMENT environments.
    TRIGGER_WORKER_QUEUE_SCHEDULED_SPLIT_ENABLED: z.string().default("0"),

    TRIGGER_MOLLIFIER_ENABLED: z.string().default("0"),
    // Separate switch for the drainer (consumer side) so it can be split
    // off onto a dedicated worker service. Unset → inherits
    // TRIGGER_MOLLIFIER_ENABLED, so single-container self-hosters don't have to
    // flip two switches. Multi-replica drainers are correct — `popAndMarkDraining`
    // is an atomic RPOP + status flip in one Lua call, so only one replica
    // can win any given entry — but inefficient: polling load (SMEMBERS +
    // per-env scans) multiplies by N, and `TRIGGER_MOLLIFIER_DRAIN_CONCURRENCY`
    // is per-process so engine load also multiplies. Splitting the drainer
    // onto a dedicated worker keeps that traffic off the request-serving
    // replicas. `TRIGGER_MOLLIFIER_ENABLED` is still the master kill switch;
    // setting this to "1" while `TRIGGER_MOLLIFIER_ENABLED` is "0" is a
    // no-op because the gate-side singleton refuses to construct a buffer
    // when the system is off.
    TRIGGER_MOLLIFIER_DRAINER_ENABLED: z
      .string()
      .default(process.env.TRIGGER_MOLLIFIER_ENABLED ?? "0"),
    TRIGGER_MOLLIFIER_SHADOW_MODE: z.string().default("0"),
    TRIGGER_MOLLIFIER_REDIS_HOST: z
      .string()
      .optional()
      .transform((v) => v ?? process.env.REDIS_HOST),
    TRIGGER_MOLLIFIER_REDIS_PORT: z.coerce
      .number()
      .optional()
      .transform(
        (v) => v ?? (process.env.REDIS_PORT ? parseInt(process.env.REDIS_PORT) : undefined)
      ),
    TRIGGER_MOLLIFIER_REDIS_USERNAME: z
      .string()
      .optional()
      .transform((v) => v ?? process.env.REDIS_USERNAME),
    TRIGGER_MOLLIFIER_REDIS_PASSWORD: z
      .string()
      .optional()
      .transform((v) => v ?? process.env.REDIS_PASSWORD),
    TRIGGER_MOLLIFIER_REDIS_TLS_DISABLED: z
      .string()
      .default(process.env.REDIS_TLS_DISABLED ?? "false"),
    TRIGGER_MOLLIFIER_TRIP_WINDOW_MS: z.coerce.number().int().positive().default(200),
    TRIGGER_MOLLIFIER_TRIP_THRESHOLD: z.coerce.number().int().positive().default(100),
    TRIGGER_MOLLIFIER_HOLD_MS: z.coerce.number().int().positive().default(500),
    TRIGGER_MOLLIFIER_DRAIN_CONCURRENCY: z.coerce.number().int().positive().default(50),
    TRIGGER_MOLLIFIER_DRAIN_MAX_ATTEMPTS: z.coerce.number().int().positive().default(3),
    TRIGGER_MOLLIFIER_DRAIN_SHUTDOWN_TIMEOUT_MS: z.coerce.number().int().positive().default(30_000),
    TRIGGER_MOLLIFIER_DRAIN_MAX_ORGS_PER_TICK: z.coerce.number().int().positive().default(500),
    // Per-env per-tick pop cap. The drainer rotates one env per org per
    // tick; this bounds how many entries it pops from that env before
    // dispatching them through the shared `DRAIN_CONCURRENCY`-bounded
    // limiter. Default matches `DRAIN_CONCURRENCY` so a single-env burst
    // uses the full handler-parallelism budget — for 20k buffered on one
    // env this is the difference between ~17m (one-pop-per-tick × ~50ms)
    // and ~20s (400 ticks × concurrent engine.trigger). Org/env fairness
    // is preserved because the per-tick env selection is unchanged; only
    // the in-env pop count grows.
    TRIGGER_MOLLIFIER_DRAIN_BATCH_SIZE: z.coerce.number().int().positive().default(50),
    // Periodic sweep that scans buffer queue LISTs for entries whose
    // dwell exceeds the stale threshold. Independent of the drainer —
    // its job is exactly to make a stuck/offline drainer visible to
    // ops. Defaults: explicitly opt-in (a separate kill switch from
    // the mollifier itself), run every 5 minutes, alert on anything
    // that's been dwelling for 5+ minutes (matches the sweep interval
    // — "anything still here when we check" is the simplest threshold
    // that converges).
    //
    // The sweep was previously defaulting to inherit
    // `TRIGGER_MOLLIFIER_ENABLED`, which meant any deployment already
    // running with the mollifier on would auto-start the sweep worker
    // on upgrade — turning on new background load with no explicit
    // rollout step. Hard-defaulting to "0" preserves the intent of
    // exposing the sweep as a separate switch.
    TRIGGER_MOLLIFIER_STALE_SWEEP_ENABLED: z.string().default("0"),
    TRIGGER_MOLLIFIER_STALE_SWEEP_INTERVAL_MS: z.coerce
      .number()
      .int()
      .positive()
      .default(5 * 60_000),
    TRIGGER_MOLLIFIER_STALE_SWEEP_THRESHOLD_MS: z.coerce
      .number()
      .int()
      .positive()
      .default(5 * 60_000),
    // Bounds for one stale-sweep pass (see mollifierStaleSweep.server.ts).
    // Max entries scanned per env and max orgs visited per tick — together
    // they cap the Redis traffic / wall-time of a single sweep pass.
    TRIGGER_MOLLIFIER_STALE_SWEEP_MAX_ENTRIES_PER_ENV: z.coerce
      .number()
      .int()
      .positive()
      .default(1000),
    TRIGGER_MOLLIFIER_STALE_SWEEP_MAX_ORGS_PER_PASS: z.coerce
      .number()
      .int()
      .positive()
      .default(100),

    // --- Mollifier buffer internals (wired into MollifierBuffer in
    // mollifierBuffer.server.ts). ---
    // Grace TTL applied to the entry hash on drainer ack so direct reads
    // (retrieve, trace) have a safety net while PG replica lag settles.
    TRIGGER_MOLLIFIER_ACK_GRACE_TTL_SECONDS: z.coerce.number().int().positive().default(30),
    // ioredis per-request retry limit on the buffer's Redis client.
    TRIGGER_MOLLIFIER_REDIS_MAX_RETRIES_PER_REQUEST: z.coerce.number().int().positive().default(20),
    // ioredis reconnect backoff envelope for the buffer client: the base
    // grows by `STEP_MS` per attempt, capped at `MAX_MS`, then equal-jittered.
    TRIGGER_MOLLIFIER_REDIS_RECONNECT_STEP_MS: z.coerce.number().int().positive().default(50),
    TRIGGER_MOLLIFIER_REDIS_RECONNECT_MAX_MS: z.coerce.number().int().positive().default(1000),

    // --- Mollifier drainer loop internals (wired into MollifierDrainer in
    // mollifierDrainer.server.ts). ---
    // Tick gap when a tick drained nothing; under backlog ticks run back-to-back.
    TRIGGER_MOLLIFIER_DRAIN_POLL_INTERVAL_MS: z.coerce.number().int().positive().default(100),
    // Cap on the drainer's exponential backoff after consecutive runOnce errors.
    TRIGGER_MOLLIFIER_DRAIN_MAX_BACKOFF_MS: z.coerce.number().int().positive().default(5_000),
    // Floor for the drainer's backoff base (so a tiny poll interval doesn't
    // collapse the backoff to near-zero during a sustained outage).
    TRIGGER_MOLLIFIER_DRAIN_BACKOFF_FLOOR_MS: z.coerce.number().int().positive().default(100),
    // Required margin between the drainer's shutdown deadline and
    // GRACEFUL_SHUTDOWN_TIMEOUT; boot fails loud if the timeout leaves less.
    TRIGGER_MOLLIFIER_DRAIN_SHUTDOWN_MARGIN_MS: z.coerce.number().int().positive().default(1_000),

    // How often the draining-tracker ZSET cardinality is polled into the gauge.
    TRIGGER_MOLLIFIER_DRAINING_GAUGE_INTERVAL_MS: z.coerce
      .number()
      .int()
      .positive()
      .default(15_000),

    // --- Pre-gate idempotency claim (idempotencyClaim.server.ts). ---
    // TTL on the claim key (and the upper clamp on the customer-derived
    // claim TTL), how long a waiter blocks before timing out, and the
    // waiter poll interval.
    TRIGGER_MOLLIFIER_CLAIM_TTL_SECONDS: z.coerce.number().int().positive().default(30),
    TRIGGER_MOLLIFIER_CLAIM_WAIT_MS: z.coerce.number().int().positive().default(5_000),
    TRIGGER_MOLLIFIER_CLAIM_POLL_MS: z.coerce.number().int().positive().default(25),

    // --- Buffered-run mutate-with-fallback wait loop (mutateWithFallback.server.ts). ---
    // Ceiling on the wait-for-materialisation loop, initial poll gap, the
    // backoff ceiling, and the exponential growth factor (a float).
    TRIGGER_MOLLIFIER_MUTATE_SAFETY_NET_MS: z.coerce.number().int().positive().default(2_000),
    TRIGGER_MOLLIFIER_MUTATE_POLL_STEP_MS: z.coerce.number().int().positive().default(20),
    TRIGGER_MOLLIFIER_MUTATE_MAX_POLL_STEP_MS: z.coerce.number().int().positive().default(250),
    TRIGGER_MOLLIFIER_MUTATE_BACKOFF_FACTOR: z.coerce.number().gt(1).default(1.7),

    // --- Buffered-run metadata CAS retry loop (applyMetadataMutation.server.ts). ---
    // Retry budget for concurrent metadata writers, and the jittered
    // conflict-backoff envelope: random in [0, base + attempt * step) ms.
    TRIGGER_MOLLIFIER_METADATA_MAX_RETRIES: z.coerce.number().int().positive().default(12),
    TRIGGER_MOLLIFIER_METADATA_BACKOFF_BASE_MS: z.coerce.number().int().positive().default(5),
    TRIGGER_MOLLIFIER_METADATA_BACKOFF_STEP_MS: z.coerce.number().int().positive().default(5),

    BATCH_TRIGGER_PROCESS_JOB_VISIBILITY_TIMEOUT_MS: z.coerce
      .number()
      .int()
      .default(60_000 * 5), // 5 minutes

    BATCH_TRIGGER_CACHED_RUNS_CHECK_ENABLED: BoolEnv.default(false),

    BATCH_TRIGGER_WORKER_ENABLED: z.string().default(process.env.WORKER_ENABLED ?? "true"),
    BATCH_TRIGGER_WORKER_CONCURRENCY_WORKERS: z.coerce.number().int().default(2),
    BATCH_TRIGGER_WORKER_CONCURRENCY_TASKS_PER_WORKER: z.coerce.number().int().default(10),
    BATCH_TRIGGER_WORKER_POLL_INTERVAL: z.coerce.number().int().default(1000),
    BATCH_TRIGGER_WORKER_IMMEDIATE_POLL_INTERVAL: z.coerce.number().int().default(50),
    BATCH_TRIGGER_WORKER_CONCURRENCY_LIMIT: z.coerce.number().int().default(20),
    BATCH_TRIGGER_WORKER_SHUTDOWN_TIMEOUT_MS: z.coerce.number().int().default(60_000),
    BATCH_TRIGGER_WORKER_LOG_LEVEL: z
      .enum(["log", "error", "warn", "info", "debug"])
      .default("info"),

    BATCH_TRIGGER_WORKER_REDIS_HOST: z
      .string()
      .optional()
      .transform((v) => v ?? process.env.REDIS_HOST),
    BATCH_TRIGGER_WORKER_REDIS_READER_HOST: z
      .string()
      .optional()
      .transform((v) => v ?? process.env.REDIS_READER_HOST),
    BATCH_TRIGGER_WORKER_REDIS_READER_PORT: z.coerce
      .number()
      .optional()
      .transform(
        (v) =>
          v ?? (process.env.REDIS_READER_PORT ? parseInt(process.env.REDIS_READER_PORT) : undefined)
      ),
    BATCH_TRIGGER_WORKER_REDIS_PORT: z.coerce
      .number()
      .optional()
      .transform(
        (v) => v ?? (process.env.REDIS_PORT ? parseInt(process.env.REDIS_PORT) : undefined)
      ),
    BATCH_TRIGGER_WORKER_REDIS_USERNAME: z
      .string()
      .optional()
      .transform((v) => v ?? process.env.REDIS_USERNAME),
    BATCH_TRIGGER_WORKER_REDIS_PASSWORD: z
      .string()
      .optional()
      .transform((v) => v ?? process.env.REDIS_PASSWORD),
    BATCH_TRIGGER_WORKER_REDIS_TLS_DISABLED: z
      .string()
      .default(process.env.REDIS_TLS_DISABLED ?? "false"),
    BATCH_TRIGGER_WORKER_REDIS_CLUSTER_MODE_ENABLED: z.string().default("0"),

    // BatchQueue DRR settings (Run Engine v2)
    BATCH_QUEUE_DRR_QUANTUM: z.coerce.number().int().default(25),
    BATCH_QUEUE_MAX_DEFICIT: z.coerce.number().int().default(100),
    BATCH_QUEUE_CONSUMER_COUNT: z.coerce.number().int().default(3),
    BATCH_QUEUE_CONSUMER_INTERVAL_MS: z.coerce.number().int().default(50),
    BATCH_QUEUE_WORKER_ENABLED: BoolEnv.default(true),
    // Number of master queue shards for horizontal scaling
    BATCH_QUEUE_SHARD_COUNT: z.coerce.number().int().default(1),
    // Maximum queues to fetch from master queue per iteration
    BATCH_QUEUE_MASTER_QUEUE_LIMIT: z.coerce.number().int().default(1000),
    // Enable worker queue for two-stage processing (claim messages, push to worker queue, process from worker queue)
    BATCH_QUEUE_WORKER_QUEUE_ENABLED: BoolEnv.default(true),
    // Worker queue blocking timeout in seconds (for two-stage processing, only used when BATCH_QUEUE_WORKER_QUEUE_ENABLED is true)
    BATCH_QUEUE_WORKER_QUEUE_TIMEOUT_SECONDS: z.coerce.number().int().default(10),
    // Global rate limit: max items processed per second across all consumers
    // If not set, no global rate limiting is applied
    BATCH_QUEUE_GLOBAL_RATE_LIMIT: z.coerce.number().int().positive().optional(),
    // Max items in the worker queue before claiming pauses (protects visibility timeouts)
    // If not set, no depth limit is applied
    BATCH_QUEUE_WORKER_QUEUE_MAX_DEPTH: z.coerce.number().int().positive().optional(),

    ADMIN_WORKER_ENABLED: z.string().default(process.env.WORKER_ENABLED ?? "true"),
    ADMIN_WORKER_CONCURRENCY_WORKERS: z.coerce.number().int().default(2),
    ADMIN_WORKER_CONCURRENCY_TASKS_PER_WORKER: z.coerce.number().int().default(10),
    ADMIN_WORKER_POLL_INTERVAL: z.coerce.number().int().default(1000),
    ADMIN_WORKER_IMMEDIATE_POLL_INTERVAL: z.coerce.number().int().default(50),
    ADMIN_WORKER_CONCURRENCY_LIMIT: z.coerce.number().int().default(20),
    ADMIN_WORKER_SHUTDOWN_TIMEOUT_MS: z.coerce.number().int().default(60_000),
    ADMIN_WORKER_LOG_LEVEL: z.enum(["log", "error", "warn", "info", "debug"]).default("info"),

    ADMIN_WORKER_REDIS_HOST: z
      .string()
      .optional()
      .transform((v) => v ?? process.env.REDIS_HOST),
    ADMIN_WORKER_REDIS_READER_HOST: z
      .string()
      .optional()
      .transform((v) => v ?? process.env.REDIS_READER_HOST),
    ADMIN_WORKER_REDIS_READER_PORT: z.coerce
      .number()
      .optional()
      .transform(
        (v) =>
          v ?? (process.env.REDIS_READER_PORT ? parseInt(process.env.REDIS_READER_PORT) : undefined)
      ),
    ADMIN_WORKER_REDIS_PORT: z.coerce
      .number()
      .optional()
      .transform(
        (v) => v ?? (process.env.REDIS_PORT ? parseInt(process.env.REDIS_PORT) : undefined)
      ),
    ADMIN_WORKER_REDIS_USERNAME: z
      .string()
      .optional()
      .transform((v) => v ?? process.env.REDIS_USERNAME),
    ADMIN_WORKER_REDIS_PASSWORD: z
      .string()
      .optional()
      .transform((v) => v ?? process.env.REDIS_PASSWORD),
    ADMIN_WORKER_REDIS_TLS_DISABLED: z.string().default(process.env.REDIS_TLS_DISABLED ?? "false"),
    ADMIN_WORKER_REDIS_CLUSTER_MODE_ENABLED: z.string().default("0"),

    ALERTS_WORKER_ENABLED: z.string().default(process.env.WORKER_ENABLED ?? "true"),
    ALERTS_WORKER_CONCURRENCY_WORKERS: z.coerce.number().int().default(2),
    ALERTS_WORKER_CONCURRENCY_TASKS_PER_WORKER: z.coerce.number().int().default(10),
    ALERTS_WORKER_POLL_INTERVAL: z.coerce.number().int().default(1000),
    ALERTS_WORKER_IMMEDIATE_POLL_INTERVAL: z.coerce.number().int().default(100),
    ALERTS_WORKER_CONCURRENCY_LIMIT: z.coerce.number().int().default(50),
    ALERTS_WORKER_SHUTDOWN_TIMEOUT_MS: z.coerce.number().int().default(60_000),
    ALERTS_WORKER_LOG_LEVEL: z.enum(["log", "error", "warn", "info", "debug"]).default("info"),

    ALERTS_WORKER_REDIS_HOST: z
      .string()
      .optional()
      .transform((v) => v ?? process.env.REDIS_HOST),
    ALERTS_WORKER_REDIS_READER_HOST: z
      .string()
      .optional()
      .transform((v) => v ?? process.env.REDIS_READER_HOST),
    ALERTS_WORKER_REDIS_READER_PORT: z.coerce
      .number()
      .optional()
      .transform(
        (v) =>
          v ?? (process.env.REDIS_READER_PORT ? parseInt(process.env.REDIS_READER_PORT) : undefined)
      ),
    ALERTS_WORKER_REDIS_PORT: z.coerce
      .number()
      .optional()
      .transform(
        (v) => v ?? (process.env.REDIS_PORT ? parseInt(process.env.REDIS_PORT) : undefined)
      ),
    ALERTS_WORKER_REDIS_USERNAME: z
      .string()
      .optional()
      .transform((v) => v ?? process.env.REDIS_USERNAME),
    ALERTS_WORKER_REDIS_PASSWORD: z
      .string()
      .optional()
      .transform((v) => v ?? process.env.REDIS_PASSWORD),
    ALERTS_WORKER_REDIS_TLS_DISABLED: z.string().default(process.env.REDIS_TLS_DISABLED ?? "false"),
    ALERTS_WORKER_REDIS_CLUSTER_MODE_ENABLED: z.string().default("0"),

    BILLING_LIMIT_WORKER_ENABLED: z.string().default(process.env.WORKER_ENABLED ?? "true"),
    BILLING_LIMIT_WORKER_CONCURRENCY_WORKERS: z.coerce.number().int().default(2),
    BILLING_LIMIT_WORKER_CONCURRENCY_TASKS_PER_WORKER: z.coerce.number().int().default(10),
    BILLING_LIMIT_WORKER_POLL_INTERVAL: z.coerce.number().int().default(1000),
    BILLING_LIMIT_WORKER_IMMEDIATE_POLL_INTERVAL: z.coerce.number().int().default(50),
    BILLING_LIMIT_WORKER_CONCURRENCY_LIMIT: z.coerce.number().int().default(20),
    BILLING_LIMIT_WORKER_SHUTDOWN_TIMEOUT_MS: z.coerce.number().int().default(60_000),
    BILLING_LIMIT_WORKER_LOG_LEVEL: z
      .enum(["log", "error", "warn", "info", "debug"])
      .default("info"),
    BILLING_LIMIT_RECONCILE_INTERVAL_MS: z.coerce.number().int().default(90_000),
    BILLING_LIMIT_WORKER_REDIS_HOST: z
      .string()
      .optional()
      .transform((v) => v ?? process.env.REDIS_HOST),
    BILLING_LIMIT_WORKER_REDIS_PORT: z.coerce
      .number()
      .optional()
      .transform(
        (v) => v ?? (process.env.REDIS_PORT ? parseInt(process.env.REDIS_PORT) : undefined)
      ),
    BILLING_LIMIT_WORKER_REDIS_USERNAME: z
      .string()
      .optional()
      .transform((v) => v ?? process.env.REDIS_USERNAME),
    BILLING_LIMIT_WORKER_REDIS_PASSWORD: z
      .string()
      .optional()
      .transform((v) => v ?? process.env.REDIS_PASSWORD),
    BILLING_LIMIT_WORKER_REDIS_TLS_DISABLED: z
      .string()
      .default(process.env.REDIS_TLS_DISABLED ?? "false"),

    SCHEDULE_ENGINE_LOG_LEVEL: z.enum(["log", "error", "warn", "info", "debug"]).default("info"),
    SCHEDULE_WORKER_ENABLED: z.string().default(process.env.WORKER_ENABLED ?? "true"),
    SCHEDULE_WORKER_CONCURRENCY_WORKERS: z.coerce.number().int().default(2),
    SCHEDULE_WORKER_CONCURRENCY_TASKS_PER_WORKER: z.coerce.number().int().default(10),
    SCHEDULE_WORKER_POLL_INTERVAL: z.coerce.number().int().default(1000),
    SCHEDULE_WORKER_IMMEDIATE_POLL_INTERVAL: z.coerce.number().int().default(50),
    SCHEDULE_WORKER_CONCURRENCY_LIMIT: z.coerce.number().int().default(50),
    SCHEDULE_WORKER_SHUTDOWN_TIMEOUT_MS: z.coerce.number().int().default(30_000),
    SCHEDULE_WORKER_DISTRIBUTION_WINDOW_SECONDS: z.coerce.number().int().default(30),

    SCHEDULE_WORKER_REDIS_HOST: z
      .string()
      .optional()
      .transform((v) => v ?? process.env.REDIS_HOST),
    SCHEDULE_WORKER_REDIS_READER_HOST: z
      .string()
      .optional()
      .transform((v) => v ?? process.env.REDIS_READER_HOST),
    SCHEDULE_WORKER_REDIS_READER_PORT: z.coerce
      .number()
      .optional()
      .transform(
        (v) =>
          v ?? (process.env.REDIS_READER_PORT ? parseInt(process.env.REDIS_READER_PORT) : undefined)
      ),
    SCHEDULE_WORKER_REDIS_PORT: z.coerce
      .number()
      .optional()
      .transform(
        (v) => v ?? (process.env.REDIS_PORT ? parseInt(process.env.REDIS_PORT) : undefined)
      ),
    SCHEDULE_WORKER_REDIS_USERNAME: z
      .string()
      .optional()
      .transform((v) => v ?? process.env.REDIS_USERNAME),
    SCHEDULE_WORKER_REDIS_PASSWORD: z
      .string()
      .optional()
      .transform((v) => v ?? process.env.REDIS_PASSWORD),
    SCHEDULE_WORKER_REDIS_TLS_DISABLED: z
      .string()
      .default(process.env.REDIS_TLS_DISABLED ?? "false"),
    SCHEDULE_WORKER_REDIS_CLUSTER_MODE_ENABLED: z.string().default("0"),

    TASK_EVENT_PARTITIONING_ENABLED: z.string().default("0"),
    TASK_EVENT_PARTITIONED_WINDOW_IN_SECONDS: z.coerce.number().int().default(60), // 1 minute

    DEPLOYMENTS_AUTORELOAD_POLL_INTERVAL_MS: z.coerce.number().int().default(5_000),
    BULK_ACTION_AUTORELOAD_POLL_INTERVAL_MS: z.coerce.number().int().default(1_000),
    QUEUES_AUTORELOAD_POLL_INTERVAL_MS: z.coerce.number().int().default(5_000),

    SLACK_BOT_TOKEN: z.string().optional(),
    SLACK_SIGNUP_REASON_CHANNEL_ID: z.string().optional(),

    // kapa.ai
    KAPA_AI_WEBSITE_ID: z.string().optional(),

    // BetterStack
    BETTERSTACK_API_KEY: z.string().optional(),
    BETTERSTACK_STATUS_PAGE_ID: z.string().optional(),

    RUN_REPLICATION_REDIS_HOST: z
      .string()
      .optional()
      .transform((v) => v ?? process.env.REDIS_HOST),
    RUN_REPLICATION_REDIS_READER_HOST: z
      .string()
      .optional()
      .transform((v) => v ?? process.env.REDIS_READER_HOST),
    RUN_REPLICATION_REDIS_READER_PORT: z.coerce
      .number()
      .optional()
      .transform(
        (v) =>
          v ?? (process.env.REDIS_READER_PORT ? parseInt(process.env.REDIS_READER_PORT) : undefined)
      ),
    RUN_REPLICATION_REDIS_PORT: z.coerce
      .number()
      .optional()
      .transform(
        (v) => v ?? (process.env.REDIS_PORT ? parseInt(process.env.REDIS_PORT) : undefined)
      ),
    RUN_REPLICATION_REDIS_USERNAME: z
      .string()
      .optional()
      .transform((v) => v ?? process.env.REDIS_USERNAME),
    RUN_REPLICATION_REDIS_PASSWORD: z
      .string()
      .optional()
      .transform((v) => v ?? process.env.REDIS_PASSWORD),
    RUN_REPLICATION_REDIS_TLS_DISABLED: z
      .string()
      .default(process.env.REDIS_TLS_DISABLED ?? "false"),

    RUN_REPLICATION_CLICKHOUSE_URL: z.string().optional(),
    RUN_REPLICATION_ENABLED: z.string().default("0"),
    RUN_REPLICATION_SLOT_NAME: z.string().default("task_runs_to_clickhouse_v1"),
    RUN_REPLICATION_PUBLICATION_NAME: z.string().default("task_runs_to_clickhouse_v1_publication"),
    RUN_REPLICATION_MAX_FLUSH_CONCURRENCY: z.coerce.number().int().default(2),
    RUN_REPLICATION_FLUSH_INTERVAL_MS: z.coerce.number().int().default(1000),
    RUN_REPLICATION_FLUSH_BATCH_SIZE: z.coerce.number().int().default(100),
    RUN_REPLICATION_LEADER_LOCK_TIMEOUT_MS: z.coerce.number().int().default(30_000),
    RUN_REPLICATION_LEADER_LOCK_EXTEND_INTERVAL_MS: z.coerce.number().int().default(10_000),
    RUN_REPLICATION_ACK_INTERVAL_SECONDS: z.coerce.number().int().default(10),
    RUN_REPLICATION_LOG_LEVEL: z.enum(["log", "error", "warn", "info", "debug"]).default("info"),
    RUN_REPLICATION_CLICKHOUSE_LOG_LEVEL: z
      .enum(["log", "error", "warn", "info", "debug"])
      .default("info"),
    RUN_REPLICATION_LEADER_LOCK_ADDITIONAL_TIME_MS: z.coerce.number().int().default(10_000),
    RUN_REPLICATION_LEADER_LOCK_RETRY_INTERVAL_MS: z.coerce.number().int().default(500),
    RUN_REPLICATION_WAIT_FOR_ASYNC_INSERT: z.string().default("0"),
    RUN_REPLICATION_KEEP_ALIVE_ENABLED: z.string().default("0"),
    RUN_REPLICATION_KEEP_ALIVE_IDLE_SOCKET_TTL_MS: z.coerce.number().int().optional(),
    RUN_REPLICATION_MAX_OPEN_CONNECTIONS: z.coerce.number().int().default(10),
    // Retry configuration for insert operations
    RUN_REPLICATION_INSERT_MAX_RETRIES: z.coerce.number().int().default(3),
    RUN_REPLICATION_INSERT_BASE_DELAY_MS: z.coerce.number().int().default(100),
    RUN_REPLICATION_INSERT_MAX_DELAY_MS: z.coerce.number().int().default(2000),
    RUN_REPLICATION_INSERT_STRATEGY: z.enum(["insert", "insert_async"]).default("insert"),
    RUN_REPLICATION_DISABLE_PAYLOAD_INSERT: z.string().default("0"),
    RUN_REPLICATION_DISABLE_ERROR_FINGERPRINTING: z.string().default("0"),

    // Session replication (Postgres → ClickHouse sessions_v1). Shares Redis
    // with the runs replicator for leader locking but has its own slot and
    // publication so the two consume independently.
    SESSION_REPLICATION_CLICKHOUSE_URL: z.string().optional(),
    SESSION_REPLICATION_ENABLED: z.string().default("0"),
    SESSION_REPLICATION_SLOT_NAME: z.string().default("sessions_to_clickhouse_v1"),
    SESSION_REPLICATION_PUBLICATION_NAME: z
      .string()
      .default("sessions_to_clickhouse_v1_publication"),
    SESSION_REPLICATION_MAX_FLUSH_CONCURRENCY: z.coerce.number().int().default(1),
    SESSION_REPLICATION_FLUSH_INTERVAL_MS: z.coerce.number().int().default(1000),
    SESSION_REPLICATION_FLUSH_BATCH_SIZE: z.coerce.number().int().default(100),
    SESSION_REPLICATION_LEADER_LOCK_TIMEOUT_MS: z.coerce.number().int().default(30_000),
    SESSION_REPLICATION_LEADER_LOCK_EXTEND_INTERVAL_MS: z.coerce.number().int().default(10_000),
    SESSION_REPLICATION_LEADER_LOCK_ADDITIONAL_TIME_MS: z.coerce.number().int().default(10_000),
    SESSION_REPLICATION_LEADER_LOCK_RETRY_INTERVAL_MS: z.coerce.number().int().default(500),
    SESSION_REPLICATION_ACK_INTERVAL_SECONDS: z.coerce.number().int().default(10),
    SESSION_REPLICATION_LOG_LEVEL: z
      .enum(["log", "error", "warn", "info", "debug"])
      .default("info"),
    SESSION_REPLICATION_CLICKHOUSE_LOG_LEVEL: z
      .enum(["log", "error", "warn", "info", "debug"])
      .default("info"),
    SESSION_REPLICATION_WAIT_FOR_ASYNC_INSERT: z.string().default("0"),
    SESSION_REPLICATION_KEEP_ALIVE_ENABLED: z.string().default("0"),
    SESSION_REPLICATION_KEEP_ALIVE_IDLE_SOCKET_TTL_MS: z.coerce.number().int().optional(),
    SESSION_REPLICATION_MAX_OPEN_CONNECTIONS: z.coerce.number().int().default(10),
    SESSION_REPLICATION_INSERT_STRATEGY: z.enum(["insert", "insert_async"]).default("insert"),
    SESSION_REPLICATION_INSERT_MAX_RETRIES: z.coerce.number().int().default(3),
    SESSION_REPLICATION_INSERT_BASE_DELAY_MS: z.coerce.number().int().default(100),
    SESSION_REPLICATION_INSERT_MAX_DELAY_MS: z.coerce.number().int().default(2000),

    // Clickhouse
    CLICKHOUSE_URL: z.string(),
    CLICKHOUSE_KEEP_ALIVE_ENABLED: z.string().default("1"),
    CLICKHOUSE_KEEP_ALIVE_IDLE_SOCKET_TTL_MS: z.coerce.number().int().optional(),
    CLICKHOUSE_MAX_OPEN_CONNECTIONS: z.coerce.number().int().default(10),
    CLICKHOUSE_LOG_LEVEL: z.enum(["log", "error", "warn", "info", "debug"]).default("info"),
    CLICKHOUSE_COMPRESSION_REQUEST: z.string().default("1"),

    // Logs Query Settings
    CLICKHOUSE_LOGS_LIST_MAX_MEMORY_USAGE: z.coerce.number().int().default(1_000_000_000),
    CLICKHOUSE_LOGS_LIST_MAX_BYTES_BEFORE_EXTERNAL_SORT: z.coerce
      .number()
      .int()
      .default(256_000_000),
    CLICKHOUSE_LOGS_LIST_MAX_THREADS: z.coerce.number().int().default(2),
    CLICKHOUSE_LOGS_LIST_MAX_ROWS_TO_READ: z.coerce.number().int().default(10_000_000),
    CLICKHOUSE_LOGS_LIST_MAX_EXECUTION_TIME: z.coerce.number().int().default(120),
    // Bound read-in-order memory on object-storage reads: each part opens a per-column read
    // stream, and the default ~1 MiB+ S3 buffers dominate peak memory. These two byte sizes
    // cap the per-stream buffers and exist on every supported ClickHouse, so they are always on.
    CLICKHOUSE_LOGS_LIST_PREFETCH_BUFFER_SIZE: z.coerce
      .number()
      .int()
      .nonnegative()
      .default(262_144),
    CLICKHOUSE_LOGS_LIST_MAX_READ_BUFFER_SIZE: z.coerce
      .number()
      .int()
      .nonnegative()
      .default(262_144),
    // The decisive lever on Cloud SharedMergeTree, but it only exists on newer ClickHouse and
    // is a no-op on local-disk MergeTree, so it is opt-in: unset means it is never sent (safe on
    // any self-hosted version). Set to 0 on object-storage deployments to get the memory win.
    CLICKHOUSE_LOGS_LIST_FILESYSTEM_CACHE_PREFER_BIGGER_BUFFER_SIZE: z.coerce
      .number()
      .int()
      .nonnegative()
      .optional(),

    // Logs list pagination tuning (page sizing + recent-first probe windows).
    LOGS_LIST_DEFAULT_PAGE_SIZE: z.coerce.number().int().positive().default(50),
    LOGS_LIST_MAX_PAGE_SIZE: z.coerce.number().int().positive().default(100),
    // Days back from the page ceiling to probe before widening to the full requested window,
    // comma-separated. Empty disables narrowing (a single full-window query).
    LOGS_LIST_RECENT_FIRST_PROBE_DAYS: z
      .string()
      .default("1,7")
      .transform((s) =>
        s
          .split(",")
          .map((v) => Number(v.trim()))
          .filter((n) => Number.isFinite(n) && n > 0)
      ),

    // Query feature flag
    QUERY_FEATURE_ENABLED: z.string().default("1"),

    // AI features (Prompts, Models, AI Metrics sidebar section)
    AI_FEATURES_ENABLED: z.string().default("0"),

    // Logs page ClickHouse URL (for logs queries)
    LOGS_CLICKHOUSE_URL: z
      .string()
      .optional()
      .transform((v) => v ?? process.env.CLICKHOUSE_URL),

    // Query page ClickHouse limits (for TSQL queries)
    QUERY_CLICKHOUSE_URL: z
      .string()
      .optional()
      .transform((v) => v ?? process.env.CLICKHOUSE_URL),
    QUERY_CLICKHOUSE_MAX_EXECUTION_TIME: z.coerce.number().int().default(10),
    QUERY_CLICKHOUSE_MAX_MEMORY_USAGE: z.coerce.number().int().default(1_073_741_824), // 1GB in bytes
    QUERY_CLICKHOUSE_MAX_AST_ELEMENTS: z.coerce.number().int().default(4_000_000),
    QUERY_CLICKHOUSE_MAX_EXPANDED_AST_ELEMENTS: z.coerce.number().int().default(4_000_000),
    QUERY_CLICKHOUSE_MAX_BYTES_BEFORE_EXTERNAL_GROUP_BY: z.coerce.number().int().default(0),
    QUERY_CLICKHOUSE_MAX_RETURNED_ROWS: z.coerce.number().int().default(10_000),

    // Query page concurrency limits
    QUERY_DEFAULT_ORG_CONCURRENCY_LIMIT: z.coerce.number().int().default(3),
    QUERY_GLOBAL_CONCURRENCY_LIMIT: z.coerce.number().int().default(100),

    // Metric widget concurrency limits
    METRIC_WIDGET_DEFAULT_ORG_CONCURRENCY_LIMIT: z.coerce.number().int().default(30),

    // Admin ClickHouse URL (for admin dashboard queries like missing models)
    ADMIN_CLICKHOUSE_URL: z
      .string()
      .optional()
      .transform((v) => v ?? process.env.CLICKHOUSE_URL),

    EVENTS_CLICKHOUSE_URL: z
      .string()
      .optional()
      .transform((v) => v ?? process.env.CLICKHOUSE_URL),
    EVENTS_CLICKHOUSE_KEEP_ALIVE_ENABLED: z.string().default("1"),
    EVENTS_CLICKHOUSE_KEEP_ALIVE_IDLE_SOCKET_TTL_MS: z.coerce.number().int().optional(),
    EVENTS_CLICKHOUSE_MAX_OPEN_CONNECTIONS: z.coerce.number().int().default(10),
    EVENTS_CLICKHOUSE_LOG_LEVEL: z.enum(["log", "error", "warn", "info", "debug"]).default("info"),
    EVENTS_CLICKHOUSE_COMPRESSION_REQUEST: z.string().default("1"),
    // ClickHouse client used by @internal/run-engine's PendingVersionSystem.
    // Kept on its own URL + pool so this low-QPS path can't contend with
    // the main analytics client (CLICKHOUSE_URL). Falls back to the main
    // URL when unset so unconfigured environments still work.
    RUN_ENGINE_CLICKHOUSE_URL: z
      .string()
      .optional()
      .transform((v) => v ?? process.env.CLICKHOUSE_URL),
    RUN_ENGINE_CLICKHOUSE_KEEP_ALIVE_ENABLED: z.string().default("1"),
    RUN_ENGINE_CLICKHOUSE_KEEP_ALIVE_IDLE_SOCKET_TTL_MS: z.coerce.number().int().optional(),
    RUN_ENGINE_CLICKHOUSE_MAX_OPEN_CONNECTIONS: z.coerce.number().int().default(5),
    RUN_ENGINE_CLICKHOUSE_LOG_LEVEL: z
      .enum(["log", "error", "warn", "info", "debug"])
      .default("info"),
    RUN_ENGINE_CLICKHOUSE_COMPRESSION_REQUEST: z.string().default("1"),
    // Dedicated ClickHouse pool for the native backend's tag/batch id resolution; falls back to CLICKHOUSE_URL.
    REALTIME_BACKEND_NATIVE_CLICKHOUSE_URL: z
      .string()
      .optional()
      .transform((v) => v ?? process.env.CLICKHOUSE_URL),
    REALTIME_BACKEND_NATIVE_CLICKHOUSE_KEEP_ALIVE_ENABLED: z.string().default("1"),
    REALTIME_BACKEND_NATIVE_CLICKHOUSE_KEEP_ALIVE_IDLE_SOCKET_TTL_MS: z.coerce
      .number()
      .int()
      .optional(),
    REALTIME_BACKEND_NATIVE_CLICKHOUSE_MAX_OPEN_CONNECTIONS: z.coerce.number().int().default(10),
    REALTIME_BACKEND_NATIVE_CLICKHOUSE_LOG_LEVEL: z
      .enum(["log", "error", "warn", "info", "debug"])
      .default("info"),
    REALTIME_BACKEND_NATIVE_CLICKHOUSE_COMPRESSION_REQUEST: z.string().default("1"),
    EVENTS_CLICKHOUSE_BATCH_SIZE: z.coerce.number().int().default(1000),
    EVENTS_CLICKHOUSE_FLUSH_INTERVAL_MS: z.coerce.number().int().default(1000),
    METRICS_CLICKHOUSE_BATCH_SIZE: z.coerce.number().int().default(10000),
    METRICS_CLICKHOUSE_FLUSH_INTERVAL_MS: z.coerce.number().int().default(1000),
    METRICS_CLICKHOUSE_MAX_CONCURRENCY: z.coerce.number().int().default(3),
    EVENTS_CLICKHOUSE_INSERT_STRATEGY: z.enum(["insert", "insert_async"]).default("insert"),
    EVENTS_CLICKHOUSE_WAIT_FOR_ASYNC_INSERT: z.string().default("1"),
    EVENTS_CLICKHOUSE_ASYNC_INSERT_MAX_DATA_SIZE: z.coerce.number().int().default(10485760),
    EVENTS_CLICKHOUSE_ASYNC_INSERT_BUSY_TIMEOUT_MS: z.coerce.number().int().default(5000),
    EVENTS_CLICKHOUSE_START_TIME_MAX_AGE_MS: z.coerce
      .number()
      .int()
      .default(60_000 * 5), // 5 minutes
    EVENT_REPOSITORY_DEFAULT_STORE: z
      .enum(["postgres", "clickhouse", "clickhouse_v2"])
      .default("postgres"),
    EVENT_REPOSITORY_DEBUG_LOGS_DISABLED: BoolEnv.default(false),
    EVENTS_CLICKHOUSE_MAX_TRACE_SUMMARY_VIEW_COUNT: z.coerce.number().int().default(25_000),
    EVENTS_CLICKHOUSE_MAX_TRACE_DETAILED_SUMMARY_VIEW_COUNT: z.coerce.number().int().default(5_000),
    EVENTS_CLICKHOUSE_MAX_LIVE_RELOADING_SETTING: z.coerce.number().int().default(2000),

    // Organization data stores registry
    ORGANIZATION_DATA_STORES_RELOAD_INTERVAL_MS: z.coerce
      .number()
      .int()
      .default(60 * 1000), // 1 minute

    // LLM cost tracking
    LLM_COST_TRACKING_ENABLED: BoolEnv.default(true),
    LLM_PRICING_RELOAD_INTERVAL_MS: z.coerce
      .number()
      .int()
      .default(5 * 60 * 1000), // 5 minutes
    LLM_PRICING_RELOAD_CHANNEL: z.string().default("llm-registry:reload"),
    LLM_PRICING_RELOAD_DEBOUNCE_MS: z.coerce.number().int().default(1000),
    // Whether to subscribe this process to the LLM_PRICING_RELOAD_CHANNEL.
    // Default off — only OTel-ingesting services need real-time pricing
    // freshness; dashboard/worker processes are fine on the existing
    // 5-minute periodic reload. In multi-service deployments, set this to
    // true on the span-ingesting services.
    LLM_PRICING_RELOAD_PUBSUB_ENABLED: BoolEnv.default(false),
    LLM_PRICING_SEED_ON_STARTUP: BoolEnv.default(false),
    LLM_PRICING_READY_TIMEOUT_MS: z.coerce.number().int().default(500),
    LLM_METRICS_BATCH_SIZE: z.coerce.number().int().default(5000),
    LLM_METRICS_FLUSH_INTERVAL_MS: z.coerce.number().int().default(2000),
    LLM_METRICS_MAX_BATCH_SIZE: z.coerce.number().int().default(10000),
    LLM_METRICS_MAX_CONCURRENCY: z.coerce.number().int().default(2),

    // Bootstrap
    TRIGGER_BOOTSTRAP_ENABLED: z.string().default("0"),
    TRIGGER_BOOTSTRAP_WORKER_GROUP_NAME: z.string().optional(),
    TRIGGER_BOOTSTRAP_WORKER_TOKEN_PATH: z.string().optional(),

    // Machine presets
    MACHINE_PRESETS_OVERRIDE_PATH: z.string().optional(),

    // CLI package tag (e.g. "latest", "v4-beta", "4.0.0") - used for setup commands
    TRIGGER_CLI_TAG: z.string().default("latest"),

    HEALTHCHECK_DATABASE_DISABLED: z.string().default("0"),

    REQUEST_IDEMPOTENCY_REDIS_HOST: z
      .string()
      .optional()
      .transform((v) => v ?? process.env.REDIS_HOST),
    REQUEST_IDEMPOTENCY_REDIS_READER_HOST: z
      .string()
      .optional()
      .transform((v) => v ?? process.env.REDIS_READER_HOST),
    REQUEST_IDEMPOTENCY_REDIS_READER_PORT: z.coerce
      .number()
      .optional()
      .transform(
        (v) =>
          v ?? (process.env.REDIS_READER_PORT ? parseInt(process.env.REDIS_READER_PORT) : undefined)
      ),
    REQUEST_IDEMPOTENCY_REDIS_PORT: z.coerce
      .number()
      .optional()
      .transform(
        (v) => v ?? (process.env.REDIS_PORT ? parseInt(process.env.REDIS_PORT) : undefined)
      ),
    REQUEST_IDEMPOTENCY_REDIS_USERNAME: z
      .string()
      .optional()
      .transform((v) => v ?? process.env.REDIS_USERNAME),
    REQUEST_IDEMPOTENCY_REDIS_PASSWORD: z
      .string()
      .optional()
      .transform((v) => v ?? process.env.REDIS_PASSWORD),
    REQUEST_IDEMPOTENCY_REDIS_TLS_DISABLED: z
      .string()
      .default(process.env.REDIS_TLS_DISABLED ?? "false"),

    REQUEST_IDEMPOTENCY_LOG_LEVEL: z
      .enum(["log", "error", "warn", "info", "debug"])
      .default("info"),

    REQUEST_IDEMPOTENCY_TTL_IN_MS: z.coerce
      .number()
      .int()
      .default(60_000 * 60 * 24),

    // Bulk action
    BULK_ACTION_BATCH_SIZE: z.coerce.number().int().default(100),
    BULK_ACTION_BATCH_DELAY_MS: z.coerce.number().int().default(200),
    BULK_ACTION_SUBBATCH_CONCURRENCY: z.coerce.number().int().default(5),

    // AI Run Filter
    AI_RUN_FILTER_MODEL: z.string().optional(),

    EVENT_LOOP_MONITOR_THRESHOLD_MS: z.coerce.number().int().default(100),
    EVENT_LOOP_MONITOR_UTILIZATION_INTERVAL_MS: z.coerce.number().int().default(1000),
    EVENT_LOOP_MONITOR_UTILIZATION_SAMPLE_RATE: z.coerce.number().default(0.05),
    EVENT_LOOP_MONITOR_NOTIFY_ENABLED: z.string().default("0"),

    VERY_SLOW_QUERY_THRESHOLD_MS: z.coerce.number().int().optional(),

    REALTIME_STREAMS_S2_BASIN: z.string().optional(),
    REALTIME_STREAMS_S2_ACCESS_TOKEN: z.string().optional(),
    REALTIME_STREAMS_S2_ENDPOINT: z.string().optional(),
    REALTIME_STREAMS_S2_SKIP_ACCESS_TOKENS: z.enum(["true", "false"]).default("false"),
    REALTIME_STREAMS_S2_ACCESS_TOKEN_EXPIRATION_IN_MS: z.coerce
      .number()
      .int()
      .default(60_000 * 60 * 24), // 1 day
    REALTIME_STREAMS_S2_LOG_LEVEL: z
      .enum(["log", "error", "warn", "info", "debug"])
      .default("info"),
    REALTIME_STREAMS_S2_FLUSH_INTERVAL_MS: z.coerce.number().int().default(100),
    REALTIME_STREAMS_S2_MAX_RETRIES: z.coerce.number().int().default(10),
    REALTIME_STREAMS_S2_WAIT_SECONDS: z.coerce.number().int().default(60),
    // When "true", provision a dedicated S2 basin per org and stamp
    // `streamBasinName` on new rows. Off keeps everything on the single
    // basin defined by `REALTIME_STREAMS_S2_BASIN`.
    REALTIME_STREAMS_PER_ORG_BASINS_ENABLED: z.enum(["true", "false"]).default("false"),
    // Per-org basin name = `{prefix}-{env}-org-{orgId}`.
    REALTIME_STREAMS_BASIN_NAME_PREFIX: z.string().default("triggerdotdev"),
    REALTIME_STREAMS_BASIN_NAME_ENV: z.string().default("dev"),
    REALTIME_STREAMS_BASIN_DEFAULT_RETENTION: durationString().default("30d"),
    REALTIME_STREAMS_BASIN_STORAGE_CLASS: z.enum(["express", "standard"]).default("express"),
    REALTIME_STREAMS_BASIN_DELETE_ON_EMPTY_MIN_AGE: durationString().default("1h"),
    REALTIME_STREAMS_DEFAULT_VERSION: z.enum(["v1", "v2"]).default("v1"),
    WAIT_UNTIL_TIMEOUT_MS: z.coerce.number().int().default(600_000),

    // Private connections
    PRIVATE_CONNECTIONS_ENABLED: z.string().optional(),
    PRIVATE_CONNECTIONS_AWS_ACCOUNT_IDS: z.string().optional(),

    // Force RBAC to not use the plugin
    RBAC_FORCE_FALLBACK: BoolEnv.default(false),

    // Force SSO to not use the plugin (contributors without the cloud
    // plugin installed can opt in to a clean OSS-only experience).
    SSO_FORCE_FALLBACK: BoolEnv.default(false),
    // Emit a console.log when the SSO fallback is selected because no
    // plugin is installed. Default off so OSS deployments stay quiet.
    SSO_LOG_FALLBACK: BoolEnv.default(false),
    // Master deploy gate for the whole SSO feature. Default OFF so the
    // image can ship dark and be flipped on only once the SSO plugin's
    // backing services are available. When false, the SSO controller is
    // forced to the OSS fallback — login link hidden, SSO login disabled,
    // settings inert, and session re-validation skipped.
    SSO_ENABLED: BoolEnv.default(false),
    // How often (seconds) a live SSO session is re-validated against the
    // identity provider. The check is single-flight per user, so this is
    // the minimum interval between plugin round-trips, not a per-request
    // cost. Defaults to 5 minutes: every active SSO user drives one
    // billing→IdP round-trip per window, so a seconds-scale default
    // exhausts vendor rate limits at trivial user counts (masked by
    // fail-open, so it degrades silently).
    SSO_SESSION_REVALIDATION_INTERVAL_SECONDS: z.coerce.number().int().positive().default(300),
    // Hard timeout (ms) on the re-validation round-trip. If the SSO plugin
    // doesn't answer within this window the check fails OPEN (session kept)
    // and emits a `sso.revalidation.timeout` warn log — alert on an
    // elevated rate of those to catch a slow/unhealthy SSO dependency.
    SSO_SESSION_REVALIDATION_TIMEOUT_MS: z.coerce.number().int().positive().default(2000),
  })
  .and(GithubAppEnvSchema)
  .and(S2EnvSchema)
  .superRefine((env, ctx) => {
    const presets = new Set(env.COMPUTE_TEMPLATE_MACHINE_PRESETS);
    for (const required of env.COMPUTE_TEMPLATE_MACHINE_PRESETS_REQUIRED) {
      if (!presets.has(required)) {
        ctx.addIssue({
          code: z.ZodIssueCode.custom,
          path: ["COMPUTE_TEMPLATE_MACHINE_PRESETS_REQUIRED"],
          message: `"${required}" is not in COMPUTE_TEMPLATE_MACHINE_PRESETS`,
        });
      }
    }
  });

export type Environment = z.infer<typeof EnvironmentSchema>;
export const env = EnvironmentSchema.parse(process.env);
