πŸ“„ File detail

utils/cronJitterConfig.ts

🧩 .tsπŸ“ 76 linesπŸ’Ύ 3,414 bytesπŸ“ text
← Back to All Files

🎯 Use case

This file lives under β€œutils/”, which covers cross-cutting helpers (shell, tempfiles, settings, messages, process input, …). On the API surface it exposes getCronJitterConfig β€” mainly functions, hooks, or classes. Dependencies touch schema validation. It composes internal code from services, cronTasks, and lazySchema (relative imports). What the file header says: GrowthBook-backed cron jitter configuration. Separated from cronScheduler.ts so the scheduler can be bundled in the Agent SDK public build without pulling in analytics/growthbook.ts and its large transitive dependency set (settings/hooks/config cycle). Usage: REPL (useScheduledTa.

Generated from folder role, exports, dependency roots, and inline comments β€” not hand-reviewed for every path.

🧠 Inline summary

GrowthBook-backed cron jitter configuration. Separated from cronScheduler.ts so the scheduler can be bundled in the Agent SDK public build without pulling in analytics/growthbook.ts and its large transitive dependency set (settings/hooks/config cycle). Usage: REPL (useScheduledTasks.ts): pass `getJitterConfig: getCronJitterConfig` Daemon/SDK: omit getJitterConfig β†’ DEFAULT_CRON_JITTER_CONFIG applies.

πŸ“€ Exports (heuristic)

  • getCronJitterConfig

πŸ“š External import roots

Package roots from from "…" (relative paths omitted).

  • zod

πŸ–₯️ Source preview

// GrowthBook-backed cron jitter configuration.
//
// Separated from cronScheduler.ts so the scheduler can be bundled in the
// Agent SDK public build without pulling in analytics/growthbook.ts and
// its large transitive dependency set (settings/hooks/config cycle).
//
// Usage:
//   REPL (useScheduledTasks.ts): pass `getJitterConfig: getCronJitterConfig`
//   Daemon/SDK: omit getJitterConfig β†’ DEFAULT_CRON_JITTER_CONFIG applies.

import { z } from 'zod/v4'
import { getFeatureValue_CACHED_WITH_REFRESH } from '../services/analytics/growthbook.js'
import {
  type CronJitterConfig,
  DEFAULT_CRON_JITTER_CONFIG,
} from './cronTasks.js'
import { lazySchema } from './lazySchema.js'

// How often to re-fetch tengu_kairos_cron_config from GrowthBook. Short because
// this is an incident lever β€” when we push a config change to shed :00 load,
// we want the fleet to converge within a minute, not on the next process
// restart. The underlying call is a synchronous cache read; the refresh just
// clears the memoized entry so the next read triggers a background fetch.
const JITTER_CONFIG_REFRESH_MS = 60 * 1000

// Upper bounds here are defense-in-depth against fat-fingered GrowthBook
// pushes. Like pollConfig.ts, Zod rejects the whole object on any violation
// rather than partially trusting it β€” a config with one bad field falls back
// to DEFAULT_CRON_JITTER_CONFIG entirely. oneShotFloorMs shares oneShotMaxMs's
// ceiling (floor > max would invert the jitter range) and is cross-checked in
// the refine; the shared ceiling keeps the individual bound explicit in the
// error path. recurringMaxAgeMs uses .default() so a pre-existing GB config
// without the field doesn't get wholesale-rejected β€” the other fields were
// added together at config inception and don't need this.
const HALF_HOUR_MS = 30 * 60 * 1000
const THIRTY_DAYS_MS = 30 * 24 * 60 * 60 * 1000
const cronJitterConfigSchema = lazySchema(() =>
  z
    .object({
      recurringFrac: z.number().min(0).max(1),
      recurringCapMs: z.number().int().min(0).max(HALF_HOUR_MS),
      oneShotMaxMs: z.number().int().min(0).max(HALF_HOUR_MS),
      oneShotFloorMs: z.number().int().min(0).max(HALF_HOUR_MS),
      oneShotMinuteMod: z.number().int().min(1).max(60),
      recurringMaxAgeMs: z
        .number()
        .int()
        .min(0)
        .max(THIRTY_DAYS_MS)
        .default(DEFAULT_CRON_JITTER_CONFIG.recurringMaxAgeMs),
    })
    .refine(c => c.oneShotFloorMs <= c.oneShotMaxMs),
)

/**
 * Read `tengu_kairos_cron_config` from GrowthBook, validate, fall back to
 * defaults on absent/malformed/out-of-bounds config. Called from check()
 * every tick via the `getJitterConfig` callback β€” cheap (synchronous cache
 * hit). Refresh window: JITTER_CONFIG_REFRESH_MS.
 *
 * Exported so ops runbooks can point at a single function when documenting
 * the lever, and so tests can spy on it without mocking GrowthBook itself.
 *
 * Pass this as `getJitterConfig` when calling createCronScheduler in REPL
 * contexts. Daemon/SDK callers omit getJitterConfig and get defaults.
 */
export function getCronJitterConfig(): CronJitterConfig {
  const raw = getFeatureValue_CACHED_WITH_REFRESH<unknown>(
    'tengu_kairos_cron_config',
    DEFAULT_CRON_JITTER_CONFIG,
    JITTER_CONFIG_REFRESH_MS,
  )
  const parsed = cronJitterConfigSchema().safeParse(raw)
  return parsed.success ? parsed.data : DEFAULT_CRON_JITTER_CONFIG
}