mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-12 07:09:59 +00:00
R5 blockers
- Negative install-state latch was cached for the process lifetime, but
only `true` is monotonic across processes. A long-lived preview server
held a stale `false` and could re-enrol after another process tripped
the breaker. Only the positive is cached now; `false` re-reads.
- The real breaker writer used writeConfig(), which collapses
{ok:true, mirrored:false} to success, so a run that mirrored nothing
reported done with the latch only on the erasable store. It consumes
writeConfigWithResult and retries until both stores carry it.
Bucketing integrity
- Storage-restricted Studio profiles all bucketed on the literal
"anonymous": computed against the shipped hash, 100% of them were
enrolled in calibration-50 rather than 50%, and they merged into one
PostHog person. Per-session random id instead — persists nothing.
- bucketSeed had read/write authority backwards: install-state is
write-once authoritative, but readConfig took config.json's blindly, so
the stores could hold different seeds until a re-mint flipped every
cohort. Merged on read, like the latch.
- An unwritable ~/.hyperframes with no config.json re-minted per call,
re-rolling the seed on every command, and the "cohorts will not be
stable" warning was unreachable on that path.
- A corrupt PRE-MOVE state file was never deleted, so a machine reset
with `rm -rf ~/.hyperframes` reported predecessorFound/stateFileCorrupt
forever — poisoning the exact metric this work exists to produce.
Opt-out honoring
- CLI canary decisions memoized per process, so `hyperframes telemetry
disable` during a running preview server was ignored for hours while
the server kept serving pre-opt-out decisions. The memo is keyed on the
telemetry posture.
- shouldTrack() memoized, contradicting policy.ts's documented "not
memoized" contract that policy.test.ts asserts.
- The Studio override path resolved the bucket unit eagerly as an
argument, minting and PERSISTING a tracking id for an opted-out profile
— a value evaluateCanary discards unread.
- Storage reads could throw out of telemetry into a post-commit catch
block, reporting an already-committed edit as failed.
- readConfig printed an unsilenceable stderr warning on every invocation
for installs that opted out of telemetry entirely.
Host split
- isLoopbackHost rejected 0.0.0.0, so the documented
HYPERFRAMES_PREVIEW_HOST LAN mode silently lost CLI→Studio identity
stitching and split one user across two PostHog persons. Identity is
now allowed when the operator explicitly opted into LAN binding.
- Corrected the comment claiming the guard refuses spoofed Hosts: a
non-browser client sets Host freely. It is a browser DNS-rebinding
mitigation, not access control, and now says so.
Semantics and test hygiene
- percentage:100 did not mean everyone — exclude and no_unit_id sat above
the fast path, so the registry's "delete the entry at 100" step was an
unstaged flip for CI and seedless installs.
- CLI cohort adoption returned before evaluateCanary, dropping Studio's
own webdriver exclusion.
- overdueCanaries() was asserted against wall-clock time, so the whole
core suite would go red on 2026-09-15 for every unrelated PR; and `>`
against midnight made a canary overdue ON its sunset date.
- Statistical assertions ran on unseeded randomUUID() populations tight
enough to fail ~1 run in 200. Seeded.
Also: broke a config -> policy -> transport -> config import cycle by
moving POSTHOG_API_KEY to a leaf module.
Tests: 2347 CLI (bundle absent), 3153 Studio, 1450 core. Fault injection
covers the latch, seed authority, LAN identity, webdriver exclusion and
the anonymous-bucket fix. Two pre-existing tests asserted behaviour these
findings identify as wrong (shouldTrack memoization, 100%-excludes-CI)
and were rewritten with the reasoning stated.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
238 lines
8.2 KiB
TypeScript
238 lines
8.2 KiB
TypeScript
import { describe, expect, it, vi, beforeEach } from "vitest";
|
|
|
|
const configState: {
|
|
anonymousId: string;
|
|
bucketSeed: string | undefined;
|
|
telemetryEnabled: boolean;
|
|
} = {
|
|
anonymousId: "db0c1f4a-b95e-4c35-90c6-1a15bd76f717",
|
|
bucketSeed: undefined,
|
|
telemetryEnabled: true,
|
|
};
|
|
const systemState = { is_ci: false };
|
|
// null = no runtime opt-out in force; a string names the source (env var,
|
|
// dev build, ...) exactly as policy.ts reports it.
|
|
const policyState: { runtimeOverride: string | null } = { runtimeOverride: null };
|
|
|
|
vi.mock("./config.js", () => ({
|
|
readConfig: () => ({
|
|
anonymousId: configState.anonymousId,
|
|
bucketSeed: configState.bucketSeed,
|
|
telemetryEnabled: configState.telemetryEnabled,
|
|
}),
|
|
}));
|
|
vi.mock("./system.js", () => ({
|
|
getSystemMeta: () => ({ is_ci: systemState.is_ci }),
|
|
}));
|
|
vi.mock("./policy.js", () => ({
|
|
telemetryRuntimeOverride: () => policyState.runtimeOverride,
|
|
}));
|
|
|
|
// The registry is data; pin a known shape so these tests don't move when a
|
|
// real canary is added or ramped.
|
|
vi.mock("@hyperframes/core/canary-registry", async () => {
|
|
const actual = await vi.importActual<typeof import("@hyperframes/core/canary-registry")>(
|
|
"@hyperframes/core/canary-registry",
|
|
);
|
|
return {
|
|
...actual,
|
|
CANARIES: [
|
|
{
|
|
name: "test-alpha",
|
|
percentage: 100,
|
|
description: "always on",
|
|
owner: "t",
|
|
sunsetAfter: "2099-01-01",
|
|
},
|
|
{
|
|
// A PARTIAL percentage. `exclude` and the unit-id check only apply
|
|
// below 100 — at 100 the guard is about to be deleted, so everyone
|
|
// must already be on it — so exclusion behaviour cannot be expressed
|
|
// against test-alpha.
|
|
name: "test-gamma",
|
|
percentage: 50,
|
|
description: "partial rollout",
|
|
owner: "t",
|
|
sunsetAfter: "2099-01-01",
|
|
},
|
|
{
|
|
name: "test-beta",
|
|
percentage: 0,
|
|
description: "always off",
|
|
owner: "t",
|
|
sunsetAfter: "2099-01-01",
|
|
},
|
|
],
|
|
findCanary: (n: string) =>
|
|
[
|
|
{
|
|
name: "test-alpha",
|
|
percentage: 100,
|
|
description: "",
|
|
owner: "t",
|
|
sunsetAfter: "2099-01-01",
|
|
},
|
|
{
|
|
name: "test-gamma",
|
|
percentage: 50,
|
|
description: "",
|
|
owner: "t",
|
|
sunsetAfter: "2099-01-01",
|
|
},
|
|
{
|
|
name: "test-beta",
|
|
percentage: 0,
|
|
description: "",
|
|
owner: "t",
|
|
sunsetAfter: "2099-01-01",
|
|
},
|
|
].find((c) => c.name === n),
|
|
};
|
|
});
|
|
|
|
const { isCanaryEnabled, resolveCanary, canaryEventProperties, __resetCanaryCacheForTests } =
|
|
await import("./canary.js");
|
|
|
|
beforeEach(() => {
|
|
__resetCanaryCacheForTests();
|
|
configState.anonymousId = "db0c1f4a-b95e-4c35-90c6-1a15bd76f717";
|
|
configState.bucketSeed = undefined;
|
|
configState.telemetryEnabled = true;
|
|
systemState.is_ci = false;
|
|
policyState.runtimeOverride = null;
|
|
delete process.env.HF_CANARY_TEST_ALPHA;
|
|
delete process.env.HF_CANARY_TEST_BETA;
|
|
delete process.env.HF_CANARY_TEST_GAMMA;
|
|
});
|
|
|
|
describe("telemetry opt-out is canary opt-out", () => {
|
|
it("does not enrol when the persisted preference is off", () => {
|
|
configState.telemetryEnabled = false;
|
|
// test-alpha is at 100% — it would be on for everyone otherwise.
|
|
expect(resolveCanary("test-alpha")).toEqual({
|
|
enabled: false,
|
|
reason: "telemetry_opt_out",
|
|
});
|
|
});
|
|
|
|
it.each(["HYPERFRAMES_NO_TELEMETRY", "DO_NOT_TRACK", "dev_mode"])(
|
|
"does not enrol under the %s runtime override, even with the preference on",
|
|
(source) => {
|
|
configState.telemetryEnabled = true;
|
|
policyState.runtimeOverride = source;
|
|
expect(resolveCanary("test-alpha").reason).toBe("telemetry_opt_out");
|
|
},
|
|
);
|
|
|
|
it("never buckets an opted-out install — no cohort is assigned at all", () => {
|
|
configState.telemetryEnabled = false;
|
|
// A bucket number would mean we hashed them into a slice anyway.
|
|
expect(resolveCanary("test-alpha").bucket).toBeUndefined();
|
|
});
|
|
|
|
it("still honours an explicit override — the documented way to test with telemetry off", () => {
|
|
configState.telemetryEnabled = false;
|
|
process.env.HF_CANARY_TEST_BETA = "on";
|
|
expect(resolveCanary("test-beta")).toEqual({ enabled: true, reason: "forced_on" });
|
|
});
|
|
|
|
it("reports every canary as false to PostHog shape when opted out", () => {
|
|
configState.telemetryEnabled = false;
|
|
expect(canaryEventProperties()).toEqual({
|
|
"$feature/canary-test-alpha": "false",
|
|
"$feature/canary-test-gamma": "false",
|
|
"$feature/canary-test-beta": "false",
|
|
});
|
|
});
|
|
});
|
|
|
|
describe("bucketing unit", () => {
|
|
it("buckets on the bucketSeed when present — the unit that survives config wipes", async () => {
|
|
const { evaluateCanary } = await import("@hyperframes/core/canary");
|
|
configState.bucketSeed = "5f1c9d2e-0000-4000-8000-aaaaaaaaaaaa";
|
|
const viaBinding = resolveCanary("test-gamma").bucket;
|
|
// 50, not 100: at 100 evaluateCanary short-circuits before bucketing and
|
|
// reports no bucket at all, which would make this comparison vacuous.
|
|
const bySeed = evaluateCanary({
|
|
feature: "test-gamma",
|
|
unitId: configState.bucketSeed,
|
|
percentage: 50,
|
|
}).bucket;
|
|
const byId = evaluateCanary({
|
|
feature: "test-gamma",
|
|
unitId: configState.anonymousId,
|
|
percentage: 50,
|
|
}).bucket;
|
|
expect(viaBinding).toBe(bySeed);
|
|
// Only meaningful if the two units actually bucket differently.
|
|
expect(bySeed).not.toBe(byId);
|
|
});
|
|
|
|
it("falls back to the anonymousId when no seed exists (failed legacy backfill)", async () => {
|
|
const { evaluateCanary } = await import("@hyperframes/core/canary");
|
|
const viaBinding = resolveCanary("test-gamma").bucket;
|
|
const byId = evaluateCanary({
|
|
feature: "test-gamma",
|
|
unitId: configState.anonymousId,
|
|
percentage: 50,
|
|
}).bucket;
|
|
expect(viaBinding).toBe(byId);
|
|
// Both undefined would satisfy toBe — assert a bucket was actually computed.
|
|
expect(viaBinding).toEqual(expect.any(Number));
|
|
});
|
|
});
|
|
|
|
describe("CLI canary binding", () => {
|
|
it("reads the percentage from the registry", () => {
|
|
expect(isCanaryEnabled("test-alpha")).toBe(true);
|
|
expect(isCanaryEnabled("test-beta")).toBe(false);
|
|
});
|
|
|
|
it("an unregistered name is off, not a throw — a typo must not break a render", () => {
|
|
expect(isCanaryEnabled("does-not-exist")).toBe(false);
|
|
expect(resolveCanary("does-not-exist").reason).toBe("out_of_cohort");
|
|
});
|
|
|
|
it("HF_CANARY_<FEATURE> overrides the registry in both directions", () => {
|
|
process.env.HF_CANARY_TEST_ALPHA = "off";
|
|
process.env.HF_CANARY_TEST_BETA = "on";
|
|
expect(resolveCanary("test-alpha")).toMatchObject({ enabled: false, reason: "forced_off" });
|
|
expect(resolveCanary("test-beta")).toMatchObject({ enabled: true, reason: "forced_on" });
|
|
});
|
|
|
|
it("excludes CI from percentage enrolment, but an override still reaches it", () => {
|
|
systemState.is_ci = true;
|
|
expect(resolveCanary("test-gamma")).toMatchObject({ enabled: false, reason: "excluded" });
|
|
|
|
__resetCanaryCacheForTests();
|
|
process.env.HF_CANARY_TEST_GAMMA = "on";
|
|
expect(resolveCanary("test-gamma")).toMatchObject({ enabled: true, reason: "forced_on" });
|
|
});
|
|
|
|
it("fails closed when the install has no anonymousId", () => {
|
|
configState.anonymousId = "";
|
|
expect(resolveCanary("test-gamma")).toMatchObject({ enabled: false, reason: "no_unit_id" });
|
|
});
|
|
|
|
it("memoizes so a decision cannot change mid-process", () => {
|
|
expect(isCanaryEnabled("test-beta")).toBe(false);
|
|
// A late env change must NOT flip a render that already started.
|
|
process.env.HF_CANARY_TEST_BETA = "on";
|
|
expect(isCanaryEnabled("test-beta")).toBe(false);
|
|
__resetCanaryCacheForTests();
|
|
expect(isCanaryEnabled("test-beta")).toBe(true);
|
|
});
|
|
|
|
it("emits PostHog flag-shaped properties for every registered canary", () => {
|
|
expect(canaryEventProperties()).toEqual({
|
|
"$feature/canary-test-alpha": "true",
|
|
"$feature/canary-test-gamma": expect.stringMatching(/^(true|false)$/),
|
|
"$feature/canary-test-beta": "false",
|
|
});
|
|
|
|
__resetCanaryCacheForTests();
|
|
process.env.HF_CANARY_TEST_ALPHA = "off";
|
|
expect(canaryEventProperties()["$feature/canary-test-alpha"]).toBe("false");
|
|
});
|
|
});
|