refactor(cli): extract fast_provision bundle helper, align --fast

Addresses review feedback on the fast_provision experiment expansion:

- Align `--fast` with the experiment `test` variant. Previously --fast
  pushed [tarball, images, parallel, docker] (no sandbox) while the
  silent A/B pushed [images, docker, sandbox]. Add `sandbox` to --fast
  so the explicit user opt-in exercises the same surface as the silent
  experiment, plus the speed-ups outside the experiment scope.

- Extract the experiment bundle into a pure `expandFastProvisionVariant`
  helper in shared/feature-flags.ts so the bundle composition is
  testable in isolation from main() arg parsing. Drop-in replacement
  in index.ts; behavior unchanged for the test variant.

- Add unit tests pinning the bundle: test variant -> [images, docker,
  sandbox], control -> [], unknown variants -> [] (fail-closed). These
  guard against silent drift when someone tweaks the list later.

- Bump CLI 1.0.39 -> 1.0.40 per the version-on-every-cli-change rule.

Verified: bunx biome check src/ clean, bun test 2188/2188 pass.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Claude 2026-05-09 05:42:01 +00:00
parent a5f831e9fb
commit 8f873306b7
4 changed files with 59 additions and 7 deletions

View file

@ -1,6 +1,6 @@
{
"name": "@openrouter/spawn",
"version": "1.0.39",
"version": "1.0.40",
"type": "module",
"bin": {
"spawn": "cli.js"

View file

@ -6,6 +6,7 @@ import { dirname, join } from "node:path";
import {
_awaitBackgroundRefreshForTest,
_resetFeatureFlagsForTest,
expandFastProvisionVariant,
getFeatureFlag,
initFeatureFlags,
} from "../shared/feature-flags.js";
@ -225,4 +226,28 @@ describe("feature flags", () => {
expect(getFeatureFlag("unknown", "default")).toBe("default");
});
});
describe("expandFastProvisionVariant", () => {
// These tests pin the experiment bundle composition so that tweaking the
// list in feature-flags.ts forces an explicit test update — drifting the
// bundle silently is the failure mode we're guarding against.
it("returns the full provisioning-speed bundle for the test variant", () => {
expect(expandFastProvisionVariant("test")).toEqual([
"images",
"docker",
"sandbox",
]);
});
it("returns an empty bundle for the control variant", () => {
expect(expandFastProvisionVariant("control")).toEqual([]);
});
it("returns an empty bundle for unknown variants (fail-closed)", () => {
expect(expandFastProvisionVariant("")).toEqual([]);
expect(expandFastProvisionVariant("rollout")).toEqual([]);
expect(expandFastProvisionVariant("totally-made-up")).toEqual([]);
});
});
});

View file

@ -40,7 +40,7 @@ import {
} from "./commands/index.js";
import { expandEqualsFlags, findUnknownFlag } from "./flags.js";
import { agentKeys, cloudKeys, getCacheAge, loadManifest } from "./manifest.js";
import { getFeatureFlag, initFeatureFlags } from "./shared/feature-flags.js";
import { expandFastProvisionVariant, getFeatureFlag, initFeatureFlags } from "./shared/feature-flags.js";
import { getInstallRefPath } from "./shared/paths.js";
import { asyncTryCatch, asyncTryCatchIf, isFileError, isNetworkError, tryCatch, tryCatchIf } from "./shared/result.js";
import { captureError, initTelemetry, setTelemetryContext } from "./shared/telemetry.js";
@ -962,9 +962,14 @@ async function main(): Promise<void> {
process.exit(1);
}
}
// --fast implies all beta features
// --fast: explicit user opt-in to the full provisioning-speed stack. Kept
// aligned with the `fast_provision` experiment's `test` variant (images,
// docker, sandbox) so the explicit flag and the silent A/B exercise the same
// surface — plus tarball + parallel, which are speed-ups outside the
// experiment scope. If you change either bundle, update the other or the
// alignment comment in expandFastProvisionVariant().
if (process.env.SPAWN_FAST === "1") {
betaFeatures.push("tarball", "images", "parallel", "docker");
betaFeatures.push("tarball", "images", "parallel", "docker", "sandbox");
}
// fast_provision experiment: if the user did NOT pass --beta or --fast,
@ -974,11 +979,11 @@ async function main(): Promise<void> {
// - docker: Docker CE host image on Hetzner/GCP (cloud-side faster boot)
// - sandbox: local agents run in a Docker container (local-side faster boot)
// Exposure is captured for both variants so PostHog can compute conversion.
// Bundle composition lives in expandFastProvisionVariant() for unit testing.
if (!userOptedIntoBeta) {
const variant = getFeatureFlag("fast_provision", "control");
if (variant === "test") {
betaFeatures.push("images", "docker", "sandbox");
}
const variantStr = isString(variant) ? variant : "control";
betaFeatures.push(...expandFastProvisionVariant(variantStr));
}
if (betaFeatures.length > 0) {

View file

@ -201,6 +201,28 @@ export function getFeatureFlag<T extends string | boolean>(key: string, fallback
return value;
}
/**
* Beta features bundled by the `fast_provision` PostHog experiment for a given
* variant. Returns an empty array for `control` or any unknown variant the
* caller is responsible for de-duping against features the user already passed.
*
* Kept as a pure, named export so the bundle composition is testable in
* isolation from `main()` arg parsing. Keep this in sync with the `--fast`
* branch in `index.ts` both opt the user into the same speed-ups; they only
* differ on `tarball` + `parallel`, which `--fast` adds (these aren't part of
* the experiment because they're already on by default in most paths).
*/
export function expandFastProvisionVariant(variant: string): readonly string[] {
if (variant === "test") {
return [
"images",
"docker",
"sandbox",
];
}
return [];
}
/** Test-only: reset module state between tests. */
export function _resetFeatureFlagsForTest(): void {
_flags = null;