Merge pull request #2854 from heygen-com/feat/canary-rollouts

feat(core): percentage-based canary rollouts + calibration experiment
This commit is contained in:
Vance Ingalls
2026-08-03 22:35:37 -07:00
committed by GitHub
48 changed files with 4399 additions and 191 deletions
+47 -2
View File
@@ -16,7 +16,12 @@ import {
loadRuntimeSourceSignature,
} from "./runtimeSource.js";
import { VERSION as version } from "../version.js";
import { buildStudioHeadScripts, resolveCliTelemetryDistinctId } from "./telemetryIdentity.js";
import {
buildStudioHeadScriptsForHost,
identityAllowed,
refreshTelemetryPosture,
resolveCliTelemetryDistinctId,
} from "./telemetryIdentity.js";
import { emitStudioRenderComplete, emitStudioRenderError } from "./studioRenderTelemetry.js";
import { isDevMode } from "../utils/env.js";
import {
@@ -424,6 +429,10 @@ export function createStudioServer(options: StudioServerOptions): StudioServer {
rendersDir: () => join(projectDir, "renders"),
startRender(opts): RenderJobState {
// The render POST is a request boundary like any other. Without this an
// already-open Studio tab keeps rendering under the posture cached when
// the server booted.
refreshTelemetryPosture();
const abortController = new AbortController();
const state: RenderJobState = {
id: opts.jobId,
@@ -503,6 +512,12 @@ export function createStudioServer(options: StudioServerOptions): StudioServer {
metaPath,
JSON.stringify({ status: "complete", durationMs: Date.now() - startTime }),
);
// Refreshed HERE, not just at render start: a render can run for
// minutes, and `hyperframes telemetry disable` during one must be
// honoured by the event that reports it. Studio never polls
// /api/telemetry-identity, so this process would otherwise keep its
// startup-cached posture for the life of the preview server.
refreshTelemetryPosture();
emitStudioRenderComplete(opts, Date.now() - startTime, job.perfSummary);
} catch (err) {
if (abortController.signal.aborted) {
@@ -512,6 +527,7 @@ export function createStudioServer(options: StudioServerOptions): StudioServer {
state.status = "failed";
state.error = err instanceof Error ? err.message : String(err);
// fallow-ignore-next-line code-duplication
refreshTelemetryPosture();
emitStudioRenderError(opts, Date.now() - startTime, state.stage, err, renderJob);
try {
const metaPath = opts.outputPath.replace(/\.(mp4|webm|mov)$/, ".meta.json");
@@ -692,7 +708,26 @@ export function createStudioServer(options: StudioServerOptions): StudioServer {
// clients that can't rely on the injected global. Returns the CLI's anonymous
// distinct id (no PII) so the browser session can join the CLI's PostHog
// person, or `{ distinctId: null }` when CLI telemetry is disabled.
//
// Deliberately does NOT serve `bucketSeed`. Studio gets its canary answers
// from the injected `window.__HF_CLI_CANARY_DECISIONS` (booleans, not the
// value cohorts derive from), so nothing needs the seed over HTTP — and an
// unauthenticated local endpoint is a strictly worse place for it than an
// inline script scoped to Studio's own document.
//
// Host-guarded against DNS rebinding: a remote page can point a hostname it
// controls at 127.0.0.1 and read this response as same-origin. Pinning the
// Host header to a loopback name means such a request (which carries the
// attacker's hostname) is refused. Same-origin Studio traffic always
// presents the bound loopback host.
app.get("/api/telemetry-identity", (c) => {
if (!identityAllowed(c.req.header("host"))) {
return c.json({ error: "forbidden" }, 403);
}
// Same request-boundary refresh the head-script route does: this endpoint
// is polled by a long-lived Studio tab, so a cached posture here outlives
// an opt-out run in another terminal just as visibly.
refreshTelemetryPosture();
return c.json({ distinctId: resolveCliTelemetryDistinctId() });
});
@@ -836,7 +871,17 @@ export function createStudioServer(options: StudioServerOptions): StudioServer {
// Inject before the studio bundle runs. Identity script first (see
// buildStudioHeadScripts) so the CLI distinct id is on `window` by the time
// telemetry init reads it.
const headScript = buildStudioHeadScripts(buildRuntimeEnvScript());
//
// Host-guarded for the same reason /api/telemetry-identity is, and it has
// to be checked HERE too: guarding only the endpoint leaves this route as
// an open side door, since a rebound origin can simply fetch `/` and read
// the same distinct id and seed out of the returned HTML.
//
// Only IDENTITY is withheld from an untrusted Host. The canary decisions
// map still goes out — it is non-identifying, and a LAN/remote Studio
// (`HYPERFRAMES_PREVIEW_HOST=0.0.0.0`) needs it to stay in agreement with
// the CLI. See buildStudioHeadScriptsForHost.
const headScript = buildStudioHeadScriptsForHost(buildRuntimeEnvScript(), c.req.header("host"));
if (headScript) {
html = html.replace("<head>", `<head>${headScript}`);
}