mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-08 02:36:10 +00:00
fix(cli,studio): adopt the CLI's canary decisions in a launched Studio
Closes both cross-surface findings with one mechanism. The CLI publishes
window.__HF_CLI_CANARY_DECISIONS ({ name: boolean }); a CLI-launched
Studio takes it as authoritative over its own seed, URL override and the
registry percentage.
Studio re-deriving could not agree with the CLI in three cases:
- Telemetry off. The CLI resolves telemetry_opt_out, but Studio's
opt-out is a separate localStorage flag it cannot see, so it would
evaluate normally and could enrol on a render the CLI excluded. The
previous commit gated each surface independently; that fixed silent
enrolment per surface but NOT the disagreement between them.
- HF_CANARY_* override. Env vars never cross into the browser — Studio
reads only its URL param / sessionStorage — so a support session
forcing a canary on got the CLI forced and Studio guessing.
- No seed injected. Studio falls back to a different unit id, i.e. a
different bucket.
Shipping the decision instead of the inputs makes divergence structurally
impossible: one evaluation, two surfaces. It also exposes strictly less —
booleans about features, rather than the seed buckets derive from — which
is why it is safe to publish with telemetry off, the case it exists for.
Studio still evaluates locally when standalone, or for a canary the CLI
did not publish, and ignores a non-boolean value rather than trusting it.
Tests: 6 Studio (CLI-off wins over unset local flag, CLI-on with no URL
param, beats contradicting override, beats seed, falls back per-canary,
rejects non-boolean) and 4 CLI (decisions with telemetry off and no
identity, alongside identity when on, script-tag escaping on a hostile
canary name, throwing resolver degrades to identity only). Four existing
identity tests asserted the old "nothing when telemetry off" contract and
were updated; the registry is now mocked there so string assertions don't
move when a canary is added or ramped. Fault injection: dropping the
adoption fails 4.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
4f464dc424
commit
98b23a8850
@@ -6,6 +6,9 @@ import { describe, expect, it, vi, beforeEach } from "vitest";
|
||||
|
||||
const shouldTrack = vi.fn();
|
||||
const readConfig = vi.fn();
|
||||
// Pinned rather than using the real registry, so these string assertions
|
||||
// don't move every time a canary is added, ramped, or retired.
|
||||
const canaryDecisions = vi.fn<() => Record<string, boolean>>();
|
||||
|
||||
vi.mock("../telemetry/client.js", () => ({
|
||||
shouldTrack: (...args: unknown[]) => shouldTrack(...args),
|
||||
@@ -13,6 +16,9 @@ vi.mock("../telemetry/client.js", () => ({
|
||||
vi.mock("../telemetry/config.js", () => ({
|
||||
readConfig: (...args: unknown[]) => readConfig(...args),
|
||||
}));
|
||||
vi.mock("../telemetry/canary.js", () => ({
|
||||
canaryDecisionsForStudio: () => canaryDecisions(),
|
||||
}));
|
||||
|
||||
const { resolveCliTelemetryDistinctId, buildCliIdentityScript, buildStudioHeadScripts } =
|
||||
await import("./telemetryIdentity.js");
|
||||
@@ -21,6 +27,8 @@ describe("resolveCliTelemetryDistinctId", () => {
|
||||
beforeEach(() => {
|
||||
shouldTrack.mockReset();
|
||||
readConfig.mockReset();
|
||||
canaryDecisions.mockReset();
|
||||
canaryDecisions.mockReturnValue({});
|
||||
});
|
||||
|
||||
it("returns the CLI anonymousId when telemetry is enabled", () => {
|
||||
@@ -56,6 +64,8 @@ describe("buildCliIdentityScript", () => {
|
||||
beforeEach(() => {
|
||||
shouldTrack.mockReset();
|
||||
readConfig.mockReset();
|
||||
canaryDecisions.mockReset();
|
||||
canaryDecisions.mockReturnValue({});
|
||||
});
|
||||
|
||||
it("emits a script that sets window.__HF_CLI_DISTINCT_ID when telemetry is on", () => {
|
||||
@@ -74,11 +84,56 @@ describe("buildCliIdentityScript", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("emits an empty string when telemetry is disabled (nothing to seed)", () => {
|
||||
it("emits an empty string when telemetry is off and there are no canaries", () => {
|
||||
shouldTrack.mockReturnValue(false);
|
||||
expect(buildCliIdentityScript()).toBe("");
|
||||
});
|
||||
|
||||
// The cross-surface fix: with telemetry off the CLI resolves every canary
|
||||
// to telemetry_opt_out, and Studio cannot see that from its own separate
|
||||
// localStorage flag. Publishing the DECISIONS (not the identity) is what
|
||||
// stops Studio evaluating independently and enrolling anyway.
|
||||
it("still publishes canary decisions when telemetry is off, but no identity", () => {
|
||||
shouldTrack.mockReturnValue(false);
|
||||
canaryDecisions.mockReturnValue({ "de-parallel-router": false });
|
||||
const script = buildCliIdentityScript();
|
||||
expect(script).toBe(
|
||||
'<script>window.__HF_CLI_CANARY_DECISIONS={"de-parallel-router":false};</script>',
|
||||
);
|
||||
expect(script).not.toContain("__HF_CLI_DISTINCT_ID");
|
||||
expect(script).not.toContain("__HF_CLI_BUCKET_SEED");
|
||||
});
|
||||
|
||||
it("publishes decisions alongside the identity when telemetry is on", () => {
|
||||
shouldTrack.mockReturnValue(true);
|
||||
readConfig.mockReturnValue({ anonymousId: "machine-uuid", bucketSeed: "seed-uuid" });
|
||||
canaryDecisions.mockReturnValue({ "de-parallel-router": true });
|
||||
expect(buildCliIdentityScript()).toBe(
|
||||
'<script>window.__HF_CLI_DISTINCT_ID="machine-uuid";' +
|
||||
'window.__HF_CLI_BUCKET_SEED="seed-uuid";' +
|
||||
'window.__HF_CLI_CANARY_DECISIONS={"de-parallel-router":true};</script>',
|
||||
);
|
||||
});
|
||||
|
||||
it("escapes a canary name that tries to close the script tag", () => {
|
||||
shouldTrack.mockReturnValue(false);
|
||||
canaryDecisions.mockReturnValue({ "</script><script>alert(1)": true });
|
||||
const script = buildCliIdentityScript();
|
||||
expect(script).not.toContain("</script><script>alert(1)");
|
||||
expect(script).toContain("__HF_CLI_CANARY_DECISIONS");
|
||||
});
|
||||
|
||||
it("survives a throwing canary resolver — telemetry must never break preview", () => {
|
||||
shouldTrack.mockReturnValue(true);
|
||||
readConfig.mockReturnValue({ anonymousId: "machine-uuid" });
|
||||
canaryDecisions.mockImplementation(() => {
|
||||
throw new Error("registry blew up");
|
||||
});
|
||||
expect(buildCliIdentityScript()).toBe(
|
||||
'<script>window.__HF_CLI_DISTINCT_ID="machine-uuid";</script>',
|
||||
);
|
||||
});
|
||||
|
||||
it("JSON-encodes the id so it can't break out of the script literal", () => {
|
||||
shouldTrack.mockReturnValue(true);
|
||||
readConfig.mockReturnValue({ anonymousId: "</script><script>alert(1)" });
|
||||
@@ -93,6 +148,8 @@ describe("buildStudioHeadScripts", () => {
|
||||
beforeEach(() => {
|
||||
shouldTrack.mockReset();
|
||||
readConfig.mockReset();
|
||||
canaryDecisions.mockReset();
|
||||
canaryDecisions.mockReturnValue({});
|
||||
});
|
||||
|
||||
const ENV_SCRIPT = "<script>window.__HF_STUDIO_ENV__={};</script>";
|
||||
@@ -105,7 +162,7 @@ describe("buildStudioHeadScripts", () => {
|
||||
expect(head.indexOf("__HF_CLI_DISTINCT_ID")).toBeLessThan(head.indexOf("__HF_STUDIO_ENV__"));
|
||||
});
|
||||
|
||||
it("returns just the env script when identity is suppressed (telemetry off)", () => {
|
||||
it("returns just the env script when there is no identity and no canary", () => {
|
||||
shouldTrack.mockReturnValue(false);
|
||||
expect(buildStudioHeadScripts(ENV_SCRIPT)).toBe(ENV_SCRIPT);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user