mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-07 18:26:17 +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
@@ -18,6 +18,7 @@
|
||||
|
||||
import { readConfig } from "../telemetry/config.js";
|
||||
import { shouldTrack as telemetryShouldTrack } from "../telemetry/client.js";
|
||||
import { canaryDecisionsForStudio } from "../telemetry/canary.js";
|
||||
|
||||
/**
|
||||
* The CLI's anonymous distinct id to hand to Studio, or null when CLI telemetry
|
||||
@@ -34,13 +35,6 @@ export function resolveCliTelemetryDistinctId(): string | null {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* `<script>` tag to inject into the served index.html `<head>`, publishing the
|
||||
* CLI distinct id as `window.__HF_CLI_DISTINCT_ID` before the studio bundle
|
||||
* runs. Preferred over a URL param so the id never leaks into `$current_url` /
|
||||
* `url_hash` telemetry or browser history. Empty string when there's nothing to
|
||||
* seed (telemetry off / no id).
|
||||
*/
|
||||
/**
|
||||
* The CLI's canary bucket seed to hand to Studio, or null. Injected alongside
|
||||
* the distinct id so a CLI-launched Studio buckets canaries on the SAME unit
|
||||
@@ -67,13 +61,65 @@ function encodeInlineScriptValue(value: string): string {
|
||||
return JSON.stringify(value).replace(/</g, "\\u003c").replace(/\//g, "\\/");
|
||||
}
|
||||
|
||||
/**
|
||||
* Same escaping, for a structured value. Separate from the string form
|
||||
* because that one stringifies its argument — passing an object through it
|
||||
* would double-encode into a quoted JSON blob.
|
||||
*
|
||||
* The keys here are registry canary names, so they are developer-authored and
|
||||
* ASCII by convention rather than user input; the escaping is belt-and-braces
|
||||
* for the same reason it is on the ids.
|
||||
*/
|
||||
function encodeInlineScriptJson(value: unknown): string {
|
||||
return JSON.stringify(value).replace(/</g, "\\u003c").replace(/\//g, "\\/");
|
||||
}
|
||||
|
||||
/**
|
||||
* The CLI's resolved canary decisions for a launched Studio, or null when
|
||||
* there are none to publish. Fail-silent, like everything else here.
|
||||
*/
|
||||
function resolveCliCanaryDecisions(): Record<string, boolean> | null {
|
||||
try {
|
||||
const decisions = canaryDecisionsForStudio();
|
||||
return Object.keys(decisions).length > 0 ? decisions : null;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* `<script>` tag to inject into the served index.html `<head>`, publishing the
|
||||
* CLI distinct id as `window.__HF_CLI_DISTINCT_ID` (plus the bucket seed and
|
||||
* resolved canary decisions) before the studio bundle runs. Preferred over a
|
||||
* URL param so the id never leaks into `$current_url` / `url_hash` telemetry
|
||||
* or browser history. Empty string only when there is nothing at all to
|
||||
* publish.
|
||||
*/
|
||||
export function buildCliIdentityScript(): string {
|
||||
const parts: string[] = [];
|
||||
|
||||
const cliId = resolveCliTelemetryDistinctId();
|
||||
if (!cliId) return "";
|
||||
const parts = [`window.__HF_CLI_DISTINCT_ID=${encodeInlineScriptValue(cliId)};`];
|
||||
const seed = resolveCliBucketSeed();
|
||||
if (seed) parts.push(`window.__HF_CLI_BUCKET_SEED=${encodeInlineScriptValue(seed)};`);
|
||||
return `<script>${parts.join("")}</script>`;
|
||||
if (cliId) {
|
||||
parts.push(`window.__HF_CLI_DISTINCT_ID=${encodeInlineScriptValue(cliId)};`);
|
||||
const seed = resolveCliBucketSeed();
|
||||
if (seed) parts.push(`window.__HF_CLI_BUCKET_SEED=${encodeInlineScriptValue(seed)};`);
|
||||
}
|
||||
|
||||
// Emitted even when telemetry is OFF and the identity block above is empty —
|
||||
// that is the case it exists for. With telemetry off the CLI resolves every
|
||||
// canary to `telemetry_opt_out`, but Studio's opt-out is a separate
|
||||
// localStorage flag it cannot see, so left to itself Studio would evaluate
|
||||
// normally and could enrol on a render the CLI had already excluded. Same
|
||||
// for an `HF_CANARY_*` override, which never crosses into the browser.
|
||||
//
|
||||
// Safe to publish unconditionally: these are booleans about features, not
|
||||
// identity, and strictly less than the seed they replace as Studio's input.
|
||||
const decisions = resolveCliCanaryDecisions();
|
||||
if (decisions !== null) {
|
||||
parts.push(`window.__HF_CLI_CANARY_DECISIONS=${encodeInlineScriptJson(decisions)};`);
|
||||
}
|
||||
|
||||
return parts.length === 0 ? "" : `<script>${parts.join("")}</script>`;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user