mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-07 18:26:17 +00:00
fix(cli,core): refresh telemetry posture at the render boundary
R6/R7 blockers. An already-open Studio kept emitting server-side render telemetry after another process disabled CLI telemetry. refreshTelemetryPosture() only ran while serving a fresh SPA document and on /api/telemetry-identity, which Studio has no consumer for, so the render POST and its async outcome used the posture cached when the preview server booted. It now refreshes at the render boundary and again immediately before the completion/error event, so an opt-out during a long render is honoured. The identity tests were passing vacuously: their mocks omitted readConfigFresh and resetTelemetryPostureCache, and the resulting missing-export error was swallowed by the refresh's own catch. Mocked properly, plus the enabled -> external disable -> next response transition and the suppression path at the layer that drops the event. A full reset also did not persist its new lineage in a long-lived process: syncInstallState returned early on a process-lifetime memo even after ~/.hyperframes was deleted, so install-state was never recreated and the next config-only re-mint rolled a third seed instead of inheriting the second. The memo is now revalidated against the file. Also drops a stale reference to assertNoOverdueCanaries and stops the workflow and docs claiming the sunset job routes anything to the owner — it names them in the run log and notifies nobody. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
cad6b394f4
commit
3f8dca165d
@@ -387,6 +387,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,
|
||||
@@ -466,6 +470,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) {
|
||||
@@ -475,6 +485,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");
|
||||
|
||||
@@ -11,11 +11,20 @@ const readConfig = vi.fn();
|
||||
// don't move every time a canary is added, ramped, or retired.
|
||||
const canaryDecisions = vi.fn<() => Record<string, { enabled: boolean; forced: boolean }>>();
|
||||
|
||||
// Every export the module under test imports must be mocked. Omitting
|
||||
// `resetTelemetryPostureCache` / `readConfigFresh` made `refreshTelemetryPosture`
|
||||
// throw a missing-export error that its own catch swallowed, so every
|
||||
// assertion below ran against a refresh that silently did nothing.
|
||||
const resetPostureCache = vi.fn();
|
||||
const readConfigFresh = vi.fn();
|
||||
|
||||
vi.mock("../telemetry/client.js", () => ({
|
||||
shouldTrack: (...args: unknown[]) => shouldTrack(...args),
|
||||
resetTelemetryPostureCache: () => resetPostureCache(),
|
||||
}));
|
||||
vi.mock("../telemetry/config.js", () => ({
|
||||
readConfig: (...args: unknown[]) => readConfig(...args),
|
||||
readConfigFresh: () => readConfigFresh(),
|
||||
}));
|
||||
vi.mock("../telemetry/canary.js", () => ({
|
||||
canaryDecisionsForStudio: () => canaryDecisions(),
|
||||
@@ -27,6 +36,7 @@ const {
|
||||
buildStudioHeadScripts,
|
||||
isLoopbackHost,
|
||||
buildStudioHeadScriptsForHost,
|
||||
refreshTelemetryPosture,
|
||||
identityAllowed,
|
||||
} = await import("./telemetryIdentity.js");
|
||||
|
||||
@@ -344,3 +354,42 @@ describe("identityAllowed — loopback-bound vs explicitly LAN-bound", () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// A long-lived preview server: the posture it cached at boot must not outlive
|
||||
// an opt-out run in another terminal. Studio has no poller for
|
||||
// /api/telemetry-identity, so the refresh has to happen on the paths that
|
||||
// actually run — the SPA document and the render boundary.
|
||||
describe("cross-process opt-out refresh", () => {
|
||||
beforeEach(() => {
|
||||
resetPostureCache.mockClear();
|
||||
readConfigFresh.mockClear();
|
||||
});
|
||||
|
||||
it("actually invalidates both caches — the mocks used to swallow this", () => {
|
||||
refreshTelemetryPosture();
|
||||
expect(readConfigFresh).toHaveBeenCalledTimes(1);
|
||||
expect(resetPostureCache).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("refreshes before building a head script", () => {
|
||||
shouldTrack.mockReturnValue(true);
|
||||
readConfig.mockReturnValue({ anonymousId: "id-1", bucketSeed: "seed-1" });
|
||||
canaryDecisions.mockReturnValue({});
|
||||
buildStudioHeadScriptsForHost("", "localhost:3000");
|
||||
expect(resetPostureCache).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("stops publishing identity once another process disables telemetry", () => {
|
||||
canaryDecisions.mockReturnValue({});
|
||||
readConfig.mockReturnValue({ anonymousId: "id-1", bucketSeed: "seed-1" });
|
||||
|
||||
shouldTrack.mockReturnValue(true);
|
||||
expect(buildStudioHeadScriptsForHost("", "localhost:3000")).toContain("__HF_CLI_DISTINCT_ID");
|
||||
|
||||
// `hyperframes telemetry disable` in another terminal.
|
||||
shouldTrack.mockReturnValue(false);
|
||||
const after = buildStudioHeadScriptsForHost("", "localhost:3000");
|
||||
expect(after).not.toContain("__HF_CLI_DISTINCT_ID");
|
||||
expect(after).not.toContain("__HF_CLI_BUCKET_SEED");
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user