chore(updater): delete the rollout-flag module nothing reads (#30211)

The nudge request parsed and stored a rollout block, but no build ever read a flag from it.
Drops the module, its call site, its tests, and the e2e env that fed it.
This commit is contained in:
Neil
2026-10-17 23:38:45 -07:00
committed by GitHub
parent 2a8b4c31a5
commit 9a86d271dc
6 changed files with 3 additions and 383 deletions
-1
View File
@@ -5991,7 +5991,6 @@
"src/main/updater.quit-and-install.test.ts": 4994,
"src/main/updater.startup-scheduling.test.ts": 3378,
"src/main/updater/mac-browser-sidecar-recovery.test.ts": 81,
"src/main/updater/rollout-flags.test.ts": 285,
"src/main/updater/serve-supervisor-install-park.test.ts": 151,
"src/main/updater/shipit-leftover-recovery.test.ts": 103,
"src/main/usage-worktree-canonicalizer.test.ts": 32,
-36
View File
@@ -9,12 +9,10 @@ vi.mock('electron', () => ({
}))
import { fetchNudge, versionMatchesRange, shouldApplyNudge } from './updater-nudge'
import { isRolloutFlagActive, resetRolloutConfigForTests } from './updater/rollout-flags'
describe('updater-nudge', () => {
beforeEach(() => {
netFetchMock.mockReset()
resetRolloutConfigForTests()
})
describe('fetchNudge', () => {
@@ -128,40 +126,6 @@ describe('updater-nudge', () => {
})
})
describe('rollout block on the same request', () => {
const install = { appVersion: '1.5.0', installId: 'install-a' }
const killSwitch = { version: 1, flags: { 'pinned-relay-default': { state: 'on' } } }
it('records the block without changing the nudge it rides on', async () => {
netFetchMock.mockResolvedValue({
ok: true,
json: async () => ({ id: 'campaign-1', minVersion: '1.0.0', rollout: killSwitch })
})
await expect(fetchNudge()).resolves.toEqual({ id: 'campaign-1', minVersion: '1.0.0' })
expect(isRolloutFlagActive('pinned-relay-default', install)).toBe(true)
expect(netFetchMock).toHaveBeenCalledTimes(1)
})
it('reads a block even when there is no nudge campaign', async () => {
netFetchMock.mockResolvedValue({ ok: true, json: async () => ({ rollout: killSwitch }) })
await expect(fetchNudge()).resolves.toBeNull()
expect(isRolloutFlagActive('pinned-relay-default', install)).toBe(true)
})
it('keeps the last block when a later request fails', async () => {
netFetchMock.mockResolvedValueOnce({ ok: true, json: async () => ({ rollout: killSwitch }) })
await fetchNudge()
netFetchMock.mockResolvedValueOnce({ ok: false })
await fetchNudge()
netFetchMock.mockRejectedValueOnce(new Error('network down'))
await fetchNudge()
expect(isRolloutFlagActive('pinned-relay-default', install)).toBe(true)
})
})
describe('versionMatchesRange', () => {
it('bounded range match', () => {
expect(versionMatchesRange('1.1.5', { minVersion: '1.1.0', maxVersion: '1.1.19' })).toBe(true)
-3
View File
@@ -1,6 +1,5 @@
import { net } from 'electron'
import { compareVersions, isValidVersion } from './updater-fallback'
import { parseRolloutConfig, recordRolloutConfig } from './updater/rollout-flags'
export type NudgeConfig = {
id: string
@@ -18,8 +17,6 @@ export async function fetchNudge(): Promise<NudgeConfig | null> {
}
const json: unknown = await res.json()
// Why here: the rollout block rides on this same request; older builds ignore the key.
recordRolloutConfig(parseRolloutConfig(json))
if (!json || typeof json !== 'object' || Array.isArray(json)) {
return null
}
-173
View File
@@ -1,173 +0,0 @@
import { mkdtempSync, rmSync, writeFileSync } from 'node:fs'
import { tmpdir } from 'node:os'
import { join } from 'node:path'
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import { installFakeAppEnvironment } from '../../../config/scripts/vitest-host-ports-setup'
import {
BAKED_ROLLOUT_FLAGS,
ROLLOUT_FLAG_NAMES,
isRolloutFlagActive,
parseRolloutConfig,
readE2ERolloutConfigOverride,
recordRolloutConfig,
resetRolloutConfigForTests,
resolveRolloutFlag,
rolloutBucket,
type RolloutConfig
} from './rollout-flags'
const install = { appVersion: '1.5.0', installId: 'install-a' }
function campaign(flags: Record<string, unknown>, version: unknown = 1): unknown {
return { id: 'campaign-1', minVersion: '1.0.0', rollout: { version, flags } }
}
describe('rollout flags', () => {
beforeEach(() => {
resetRolloutConfigForTests()
})
it('ships every flip inactive', () => {
expect(ROLLOUT_FLAG_NAMES.every((name) => BAKED_ROLLOUT_FLAGS[name] === false)).toBe(true)
})
it('resolves to the baked value when the block is absent, invalid or a newer version', () => {
for (const payload of [
null,
'nudge',
[],
{ id: 'campaign-1', minVersion: '1.0.0' },
{ rollout: 'on' },
{ rollout: { version: 1 } },
campaign({ 'pinned-relay-default': { state: 'on' } }, 2)
]) {
const config = parseRolloutConfig(payload)
expect(config).toBeNull()
expect(resolveRolloutFlag('pinned-relay-default', { ...install, config })).toBe(false)
}
})
it('drops only the entries it cannot read, and ignores flags it does not know', () => {
const config = parseRolloutConfig(
campaign({
'pinned-relay-default': { state: 'on', percent: 100, futureField: true },
'managed-servers-visible': { state: 'sideways' },
'serve-on-orcad-default': { state: 'on', percent: 140 },
'legacy-relay-dir-sweep': { state: 'on', minVersion: '2.0.0', maxVersion: '1.0.0' },
'flag-from-a-newer-build': { state: 'on' }
})
)
expect(config).toEqual({ 'pinned-relay-default': { state: 'on', percent: 100 } })
})
it('keeps the kill switch on and lets "default" defer to the build', () => {
const config: RolloutConfig = {
'pinned-relay-default': { state: 'off' },
'managed-servers-visible': { state: 'default' }
}
expect(resolveRolloutFlag('pinned-relay-default', { ...install, config })).toBe(false)
expect(resolveRolloutFlag('managed-servers-visible', { ...install, config })).toBe(false)
expect(resolveRolloutFlag('serve-on-orcad-default', { ...install, config })).toBe(false)
})
it('applies an entry only inside its version range', () => {
const config: RolloutConfig = {
'pinned-relay-default': { state: 'on', minVersion: '1.5.0', maxVersion: '1.5.9' }
}
expect(resolveRolloutFlag('pinned-relay-default', { ...install, config })).toBe(true)
for (const appVersion of ['1.4.9', '1.6.0', 'not-a-version']) {
expect(resolveRolloutFlag('pinned-relay-default', { ...install, appVersion, config })).toBe(
false
)
}
})
it('admits about the requested share of installs, stably per install and flag', () => {
const config: RolloutConfig = { 'pinned-relay-default': { state: 'on', percent: 25 } }
const ids = Array.from({ length: 4000 }, (_, index) => `install-${index}`)
const admitted = ids.filter((installId) =>
resolveRolloutFlag('pinned-relay-default', { ...install, installId, config })
)
expect(admitted.length / ids.length).toBeGreaterThan(0.22)
expect(admitted.length / ids.length).toBeLessThan(0.28)
expect(rolloutBucket('install-a', 'pinned-relay-default')).toBe(
rolloutBucket('install-a', 'pinned-relay-default')
)
// Why: each flip draws its own cohort, so one 25% step doesn't pick the same users every time.
const sameForOtherFlag = ids.filter(
(installId) =>
rolloutBucket(installId, 'pinned-relay-default') ===
rolloutBucket(installId, 'managed-servers-visible')
)
expect(sameForOtherFlag.length).toBeLessThan(ids.length / 10)
})
it('keeps an install it cannot bucket on the baked value, except at 0% or 100%', () => {
const partial: RolloutConfig = { 'pinned-relay-default': { state: 'on', percent: 50 } }
const full: RolloutConfig = { 'pinned-relay-default': { state: 'on' } }
const none: RolloutConfig = { 'pinned-relay-default': { state: 'on', percent: 0 } }
const noId = { appVersion: '1.5.0', installId: null }
expect(resolveRolloutFlag('pinned-relay-default', { ...noId, config: partial })).toBe(false)
expect(resolveRolloutFlag('pinned-relay-default', { ...noId, config: full })).toBe(true)
expect(resolveRolloutFlag('pinned-relay-default', { ...install, config: none })).toBe(false)
})
it('reads the last recorded block until the next one replaces it', () => {
expect(isRolloutFlagActive('pinned-relay-default', install)).toBe(false)
recordRolloutConfig(parseRolloutConfig(campaign({ 'pinned-relay-default': { state: 'on' } })))
expect(isRolloutFlagActive('pinned-relay-default', install)).toBe(true)
recordRolloutConfig(parseRolloutConfig({ id: 'campaign-2', minVersion: '1.0.0' }))
expect(isRolloutFlagActive('pinned-relay-default', install)).toBe(false)
})
})
describe('e2e rollout override', () => {
const dirs: string[] = []
afterEach(() => {
vi.unstubAllEnvs()
for (const dir of dirs.splice(0)) {
rmSync(dir, { recursive: true, force: true })
}
})
function flagsFile(flags: unknown): string {
const dir = mkdtempSync(join(tmpdir(), 'rollout-e2e-'))
dirs.push(dir)
const file = join(dir, 'flags.json')
writeFileSync(file, JSON.stringify(flags))
return file
}
it('reads the flags file only in an unpackaged e2e launch', () => {
const file = flagsFile({ 'legacy-relay-dir-sweep': { state: 'on' } })
const env = { ORCA_E2E_USER_DATA_DIR: '/e2e', ORCA_E2E_ROLLOUT_FLAGS_FILE: file }
expect(readE2ERolloutConfigOverride(env, false)).toEqual({
'legacy-relay-dir-sweep': { state: 'on' }
})
expect(readE2ERolloutConfigOverride({ ORCA_E2E_ROLLOUT_FLAGS_FILE: file }, false)).toBeNull()
})
it('never reads the flags file in a packaged release', () => {
const file = flagsFile({ 'legacy-relay-dir-sweep': { state: 'on' } })
const env = { ORCA_E2E_USER_DATA_DIR: '/e2e', ORCA_E2E_ROLLOUT_FLAGS_FILE: file }
expect(readE2ERolloutConfigOverride(env, true)).toBeNull()
vi.stubEnv('ORCA_E2E_USER_DATA_DIR', env.ORCA_E2E_USER_DATA_DIR)
vi.stubEnv('ORCA_E2E_ROLLOUT_FLAGS_FILE', file)
const context = { appVersion: '1.4.218', installId: null }
installFakeAppEnvironment({ isPackaged: () => true })
expect(isRolloutFlagActive('legacy-relay-dir-sweep', context)).toBe(false)
installFakeAppEnvironment({ isPackaged: () => false })
expect(isRolloutFlagActive('legacy-relay-dir-sweep', context)).toBe(true)
})
it('treats a missing or unreadable file as no override', () => {
const env = { ORCA_E2E_USER_DATA_DIR: '/e2e' }
expect(readE2ERolloutConfigOverride(env, false)).toBeNull()
expect(
readE2ERolloutConfigOverride(
{ ...env, ORCA_E2E_ROLLOUT_FLAGS_FILE: '/missing/flags.json' },
false
)
).toBeNull()
})
})
-164
View File
@@ -1,164 +0,0 @@
/**
* Server-side kill switches and percent cohorts for the Node runtime rollout flips.
*
* The block rides on the update-campaign payload Orca already polls (`nudge.json`); older builds
* ignore it. Every flag resolves to its baked-in value when the block is absent, invalid, out of
* the flag's version range, or was never received, so the payload can only move what it names.
*/
import { createHash } from 'node:crypto'
import { readFileSync } from 'node:fs'
import { z } from 'zod'
import { getAppEnvironment, hasAppEnvironment } from '../../shared/app-environment'
import { compareVersions, isValidVersion } from '../updater-fallback'
export const ROLLOUT_FLAG_NAMES = [
'pinned-relay-default',
'managed-servers-visible',
'serve-on-orcad-default',
'legacy-npm-rung-retired',
'legacy-relay-dir-sweep',
'windows-relay-wmi-fallback-retired'
] as const
export type RolloutFlagName = (typeof ROLLOUT_FLAG_NAMES)[number]
/** Each flip's shipped behaviour: none of them is active in this build. */
export const BAKED_ROLLOUT_FLAGS: Readonly<Record<RolloutFlagName, boolean>> = {
'pinned-relay-default': false,
'managed-servers-visible': false,
'serve-on-orcad-default': false,
'legacy-npm-rung-retired': false,
'legacy-relay-dir-sweep': false,
'windows-relay-wmi-fallback-retired': false
}
const versionString = z.string().refine(isValidVersion)
// Why not strict: a newer server may add fields; an entry this build can't read falls back alone.
const rolloutFlagEntrySchema = z
.object({
// `off` is the kill switch; `on` admits `percent` of installs; `default` defers to the build.
state: z.enum(['default', 'off', 'on']),
percent: z.number().min(0).max(100).optional(),
minVersion: versionString.optional(),
maxVersion: versionString.optional()
})
.refine(
(entry) =>
entry.minVersion === undefined ||
entry.maxVersion === undefined ||
compareVersions(entry.minVersion, entry.maxVersion) <= 0
)
export type RolloutFlagEntry = z.infer<typeof rolloutFlagEntrySchema>
export type RolloutConfig = Partial<Record<RolloutFlagName, RolloutFlagEntry>>
const campaignRolloutSchema = z.object({
rollout: z.object({
version: z.literal(1),
flags: z.record(z.string(), z.unknown())
})
})
/** Reads the optional `rollout` block of a campaign payload; anything unreadable is null. */
export function parseRolloutConfig(payload: unknown): RolloutConfig | null {
const campaign = campaignRolloutSchema.safeParse(payload)
if (!campaign.success) {
return null
}
const config: RolloutConfig = {}
for (const name of ROLLOUT_FLAG_NAMES) {
const parsed = rolloutFlagEntrySchema.safeParse(campaign.data.rollout.flags[name])
if (parsed.success) {
config[name] = parsed.data
}
}
return config
}
/** Stable per install and flag in [0, 100); salting by flag keeps the flips' cohorts independent. */
export function rolloutBucket(installId: string, name: RolloutFlagName): number {
const digest = createHash('sha256').update(`${name}:${installId}`).digest()
return (digest.readUInt32BE(0) % 10_000) / 100
}
function inVersionRange(version: string, entry: RolloutFlagEntry): boolean {
return (
(entry.minVersion === undefined || compareVersions(version, entry.minVersion) >= 0) &&
(entry.maxVersion === undefined || compareVersions(version, entry.maxVersion) <= 0)
)
}
export type RolloutFlagContext = {
config: RolloutConfig | null
appVersion: string
installId: string | null
}
export function resolveRolloutFlag(name: RolloutFlagName, context: RolloutFlagContext): boolean {
const baked = BAKED_ROLLOUT_FLAGS[name]
const entry = context.config?.[name]
if (!entry || entry.state === 'default') {
return baked
}
if (!isValidVersion(context.appVersion) || !inVersionRange(context.appVersion, entry)) {
return baked
}
if (entry.state === 'off') {
return false
}
const percent = entry.percent ?? 100
if (percent >= 100) {
return true
}
// Why baked without an id: an unbucketable install must not join every cohort at once.
if (!context.installId) {
return baked
}
return rolloutBucket(context.installId, name) < percent
}
let lastRolloutConfig: RolloutConfig | null = null
/**
* Called with each successfully read campaign payload's block. A failed fetch keeps the last
* block, so a transient network error can never lift a kill switch mid-session.
*/
export function recordRolloutConfig(config: RolloutConfig | null): void {
lastRolloutConfig = config
}
export function isRolloutFlagActive(
name: RolloutFlagName,
context: Omit<RolloutFlagContext, 'config'>
): boolean {
return resolveRolloutFlag(name, {
...context,
config: readE2ERolloutConfigOverride() ?? lastRolloutConfig
})
}
/**
* E2E launches only: a `flags` block read from `ORCA_E2E_ROLLOUT_FLAGS_FILE` on every check, so a
* spec can flip a flag between two connects without the campaign fetch or a relaunch.
*/
export function readE2ERolloutConfigOverride(
env: NodeJS.ProcessEnv = process.env,
// Why fail closed: a packaged release must never take rollout flags from an env-named path.
isPackaged: boolean = !hasAppEnvironment() || getAppEnvironment().isPackaged()
): RolloutConfig | null {
const file = env.ORCA_E2E_ROLLOUT_FLAGS_FILE
if (isPackaged || !env.ORCA_E2E_USER_DATA_DIR || !file) {
return null
}
try {
return parseRolloutConfig({
rollout: { version: 1, flags: JSON.parse(readFileSync(file, 'utf8')) }
})
} catch {
return null
}
}
export function resetRolloutConfigForTests(): void {
lastRolloutConfig = null
}
@@ -12,7 +12,7 @@
* Host: `ORCA_E2E_ORCAD_CONVERT_HOST`, a Windows host-cell descriptor. Template:
* `ORCA_E2E_ORCAD_CONVERT_TEMPLATE`, built for that host's target.
*/
import { mkdirSync, rmSync, writeFileSync } from 'node:fs'
import { mkdirSync, rmSync } from 'node:fs'
import os from 'node:os'
import path from 'node:path'
import type { ElectronApplication, Page } from '@stablyai/playwright-test'
@@ -38,7 +38,6 @@ import type {
const HOST = process.env[ORCAD_CONVERT_HOST_ENV]
const TEMPLATE = process.env.ORCA_E2E_ORCAD_CONVERT_TEMPLATE
const SCRATCH = path.join(os.tmpdir(), `orca-orcad-cli-matrix-${process.pid}`)
const FLAGS_FILE = path.join(SCRATCH, 'rollout-flags.json')
const MANAGED_TIMEOUT_MS = 8 * 60_000
type HostRow = {
@@ -59,13 +58,11 @@ function skipUnlessWindowsHost(): void {
function resetScratch(): void {
rmSync(SCRATCH, { recursive: true, force: true })
mkdirSync(SCRATCH, { recursive: true })
writeFileSync(FLAGS_FILE, '{}')
}
function launchEnv(): Record<string, string> {
return {
ORCA_ORCAD_TEMPLATE_PATH: TEMPLATE!,
ORCA_E2E_ROLLOUT_FLAGS_FILE: FLAGS_FILE
ORCA_ORCAD_TEMPLATE_PATH: TEMPLATE!
}
}
@@ -564,7 +561,7 @@ test('@orcad-cli-convert a seeded relay-era profile converts its Windows host, k
const upgraded = await session.launch({ onStderr: sshStderr })
app = upgraded.app
await waitForSessionReady(upgraded.page)
await convertThenRetire(upgraded.page, session.userDataDir, seeded, FLAGS_FILE)
await convertThenRetire(upgraded.page, session.userDataDir, seeded)
const environmentId = await waitForManaged(upgraded.page, seeded.targetId)
expect(await environmentIds(session.userDataDir)).toContain(environmentId)