import { describe, expect, it, vi } from "vitest"; // Stub `~/db.server` before importing anything that transitively imports it. // The real module eagerly calls `prisma.$connect()` at singleton construction // (db.server.ts), so loading it under vitest tries to reach localhost:5432 // and surfaces as an unhandled rejection that fails the whole shard — even // though no test in this file actually uses the default prisma client. vi.mock("~/db.server", () => ({ prisma: {}, $replica: {}, })); import { evaluateGate, makeResolveMollifierFlag, type GateDependencies, type GateInputs, type TripDecision, } from "~/v3/mollifier/mollifierGate.server"; import type { DecisionOutcome, DecisionReason } from "~/v3/mollifier/mollifierTelemetry.server"; // We deliberately don't use vi.fn here. Per repo policy tests shouldn't lean on // mock frameworks for behaviours that are pure functions of the inputs — the // gate is pure decision logic, so a hand-rolled "deps + spy log" wired with // plain closures gives exactly the assertions we need without the indirection. type Spies = { evaluatorCalls: number; logShadowCalls: Array<{ inputs: GateInputs; decision: Extract }>; logMollifiedCalls: Array<{ inputs: GateInputs; decision: Extract; }>; recordDecisionCalls: Array<{ outcome: DecisionOutcome; reason?: DecisionReason; enrolled?: boolean; orgId?: string; }>; }; type Toggles = { enabled: boolean; shadow: boolean; flag: boolean; decision: TripDecision; }; function makeDeps(toggles: Toggles): { deps: GateDependencies; spies: Spies } { const spies: Spies = { evaluatorCalls: 0, logShadowCalls: [], logMollifiedCalls: [], recordDecisionCalls: [], }; const deps: GateDependencies = { isMollifierEnabled: () => toggles.enabled, isShadowModeOn: () => toggles.shadow, resolveOrgFlag: async () => toggles.flag, evaluator: async () => { spies.evaluatorCalls += 1; return toggles.decision; }, logShadow: (inputs, decision) => { spies.logShadowCalls.push({ inputs, decision }); }, logMollified: (inputs, decision) => { spies.logMollifiedCalls.push({ inputs, decision }); }, recordDecision: (outcome, opts) => { spies.recordDecisionCalls.push({ outcome, reason: opts.reason, enrolled: opts.enrolled, orgId: opts.orgId, }); }, }; return { deps, spies }; } const trippedDecision = { divert: true as const, reason: "per_env_rate" as const, count: 150, threshold: 100, windowMs: 200, holdMs: 500, }; const passDecision: TripDecision = { divert: false }; const inputs: GateInputs = { envId: "e1", orgId: "o1", taskId: "t1", orgFeatureFlags: null, }; // Cascade truth table. Every combination of (enabled, shadow, flag, divert) is // enumerated. `evaluatorCalls` is the expected count, not arbitrary: the gate // short-circuits before the evaluator if `!enabled` or (`!flag && !shadow`). // `expectedReason` is the optional second arg to `recordDecision` — only // divert-true paths attach a reason. type Row = { id: number; enabled: boolean; shadow: boolean; flag: boolean; divert: boolean; expected: { action: "pass_through" | "shadow_log" | "mollify"; evaluatorCalls: 0 | 1; logShadowCalls: 0 | 1; logMollifiedCalls: 0 | 1; recordedOutcome: "pass_through" | "shadow_log" | "mollify"; expectedReason: "per_env_rate" | undefined; }; }; // 16 rows = 2^4 input combinations. Comment column shows which gate branch // each row exercises so reviewers can map row → code at a glance. const cascade: Row[] = [ // enabled=F → kill-switch wins; evaluator+flag never consulted (rows 1-8) { id: 1, enabled: false, shadow: false, flag: false, divert: false, expected: { action: "pass_through", evaluatorCalls: 0, logShadowCalls: 0, logMollifiedCalls: 0, recordedOutcome: "pass_through", expectedReason: undefined, }, }, { id: 2, enabled: false, shadow: false, flag: false, divert: true, expected: { action: "pass_through", evaluatorCalls: 0, logShadowCalls: 0, logMollifiedCalls: 0, recordedOutcome: "pass_through", expectedReason: undefined, }, }, { id: 3, enabled: false, shadow: false, flag: true, divert: false, expected: { action: "pass_through", evaluatorCalls: 0, logShadowCalls: 0, logMollifiedCalls: 0, recordedOutcome: "pass_through", expectedReason: undefined, }, }, { id: 4, enabled: false, shadow: false, flag: true, divert: true, expected: { action: "pass_through", evaluatorCalls: 0, logShadowCalls: 0, logMollifiedCalls: 0, recordedOutcome: "pass_through", expectedReason: undefined, }, }, { id: 5, enabled: false, shadow: true, flag: false, divert: false, expected: { action: "pass_through", evaluatorCalls: 0, logShadowCalls: 0, logMollifiedCalls: 0, recordedOutcome: "pass_through", expectedReason: undefined, }, }, { id: 6, enabled: false, shadow: true, flag: false, divert: true, expected: { action: "pass_through", evaluatorCalls: 0, logShadowCalls: 0, logMollifiedCalls: 0, recordedOutcome: "pass_through", expectedReason: undefined, }, }, { id: 7, enabled: false, shadow: true, flag: true, divert: false, expected: { action: "pass_through", evaluatorCalls: 0, logShadowCalls: 0, logMollifiedCalls: 0, recordedOutcome: "pass_through", expectedReason: undefined, }, }, { id: 8, enabled: false, shadow: true, flag: true, divert: true, expected: { action: "pass_through", evaluatorCalls: 0, logShadowCalls: 0, logMollifiedCalls: 0, recordedOutcome: "pass_through", expectedReason: undefined, }, }, // enabled=T, flag=F, shadow=F → both opt-ins off; evaluator never called (rows 9-10) { id: 9, enabled: true, shadow: false, flag: false, divert: false, expected: { action: "pass_through", evaluatorCalls: 0, logShadowCalls: 0, logMollifiedCalls: 0, recordedOutcome: "pass_through", expectedReason: undefined, }, }, { id: 10, enabled: true, shadow: false, flag: false, divert: true, expected: { action: "pass_through", evaluatorCalls: 0, logShadowCalls: 0, logMollifiedCalls: 0, recordedOutcome: "pass_through", expectedReason: undefined, }, }, // enabled=T, flag=F, shadow=T → shadow path; divert routes outcome (rows 11-12) { id: 11, enabled: true, shadow: true, flag: false, divert: false, expected: { action: "pass_through", evaluatorCalls: 1, logShadowCalls: 0, logMollifiedCalls: 0, recordedOutcome: "pass_through", expectedReason: undefined, }, }, { id: 12, enabled: true, shadow: true, flag: false, divert: true, expected: { action: "shadow_log", evaluatorCalls: 1, logShadowCalls: 1, logMollifiedCalls: 0, recordedOutcome: "shadow_log", expectedReason: "per_env_rate", }, }, // enabled=T, flag=T, shadow=F → mollify path (rows 13-14) { id: 13, enabled: true, shadow: false, flag: true, divert: false, expected: { action: "pass_through", evaluatorCalls: 1, logShadowCalls: 0, logMollifiedCalls: 0, recordedOutcome: "pass_through", expectedReason: undefined, }, }, { id: 14, enabled: true, shadow: false, flag: true, divert: true, expected: { action: "mollify", evaluatorCalls: 1, logShadowCalls: 0, logMollifiedCalls: 1, recordedOutcome: "mollify", expectedReason: "per_env_rate", }, }, // enabled=T, flag=T, shadow=T → flag wins over shadow (rows 15-16) { id: 15, enabled: true, shadow: true, flag: true, divert: false, expected: { action: "pass_through", evaluatorCalls: 1, logShadowCalls: 0, logMollifiedCalls: 0, recordedOutcome: "pass_through", expectedReason: undefined, }, }, { id: 16, enabled: true, shadow: true, flag: true, divert: true, expected: { action: "mollify", evaluatorCalls: 1, logShadowCalls: 0, logMollifiedCalls: 1, recordedOutcome: "mollify", expectedReason: "per_env_rate", }, }, ]; describe("evaluateGate cascade — exhaustive truth table", () => { it.each(cascade)( "row $id: enabled=$enabled shadow=$shadow flag=$flag divert=$divert → action=$expected.action", async (row) => { const { deps, spies } = makeDeps({ enabled: row.enabled, shadow: row.shadow, flag: row.flag, decision: row.divert ? trippedDecision : passDecision, }); const outcome = await evaluateGate(inputs, deps); expect(outcome.action).toBe(row.expected.action); expect(spies.evaluatorCalls).toBe(row.expected.evaluatorCalls); expect(spies.logShadowCalls).toHaveLength(row.expected.logShadowCalls); expect(spies.logMollifiedCalls).toHaveLength(row.expected.logMollifiedCalls); // Every evaluation records exactly one decision. expect(spies.recordDecisionCalls).toHaveLength(1); expect(spies.recordDecisionCalls[0].outcome).toBe(row.expected.recordedOutcome); expect(spies.recordDecisionCalls[0].reason).toBe(row.expected.expectedReason); // enrolled label = the resolved per-org flag, now hoisted above the // bypasses so it's set on every decision. orgId is always passed by the // gate; the telemetry layer drops it for non-enrolled (covered in // mollifierDecisionLabels.test.ts). expect(spies.recordDecisionCalls[0].enrolled).toBe(row.flag); expect(spies.recordDecisionCalls[0].orgId).toBe(inputs.orgId); } ); it("divert log carries the full decision (envId, orgId, taskId, reason, count, threshold, windowMs, holdMs)", async () => { const { deps, spies } = makeDeps({ enabled: true, shadow: true, flag: false, decision: trippedDecision, }); await evaluateGate(inputs, deps); expect(spies.logShadowCalls).toEqual([{ inputs, decision: trippedDecision }]); }); it("mollify log carries the full decision (mirrors shadow log)", async () => { const { deps, spies } = makeDeps({ enabled: true, shadow: false, flag: true, decision: trippedDecision, }); await evaluateGate(inputs, deps); expect(spies.logMollifiedCalls).toEqual([{ inputs, decision: trippedDecision }]); }); }); // Hot-path guard: `triggerTask.server.ts` calls `evaluateGate` on every // trigger when `TRIGGER_MOLLIFIER_ENABLED=1`. The per-org override path must resolve // without a Prisma round-trip — otherwise the gate adds a DB query to the // highest-throughput code path in the system (see apps/webapp/CLAUDE.md). describe("resolveMollifierFlag — hot path", () => { it("returns the per-org override when it's set", async () => { const resolve = makeResolveMollifierFlag(); const enabled = await resolve({ envId: "e", orgId: "o", taskId: "t", orgFeatureFlags: { mollifierEnabled: true }, }); const disabled = await resolve({ envId: "e", orgId: "o", taskId: "t", orgFeatureFlags: { mollifierEnabled: false }, }); expect(enabled).toBe(true); expect(disabled).toBe(false); }); it("returns false when the org has no override for the key — no DB query, ever", async () => { // Regression intent: the resolver MUST NOT call `flag()` (which would // query `FeatureFlag` via Prisma) on the trigger hot path. Per-org // rollout via `Organization.featureFlags` JSON is the only enable // path; the fleet-wide kill switch is `TRIGGER_MOLLIFIER_ENABLED`. const resolve = makeResolveMollifierFlag(); const fromNull = await resolve({ envId: "e", orgId: "o", taskId: "t", orgFeatureFlags: null, }); const fromUnrelatedKeys = await resolve({ envId: "e", orgId: "o", taskId: "t", orgFeatureFlags: { hasAiAccess: true }, }); expect(fromNull).toBe(false); expect(fromUnrelatedKeys).toBe(false); }); }); describe("evaluateGate — fail open on evaluator error", () => { it("treats a throwing evaluator as no-divert (pass_through), and never blocks the trigger", async () => { const spies: Spies = { evaluatorCalls: 0, logShadowCalls: [], logMollifiedCalls: [], recordDecisionCalls: [], }; const deps: Partial = { isMollifierEnabled: () => true, isShadowModeOn: () => false, resolveOrgFlag: async () => true, evaluator: async () => { spies.evaluatorCalls += 1; throw new Error("simulated evaluator failure"); }, logShadow: (inputs, decision) => { spies.logShadowCalls.push({ inputs, decision }); }, logMollified: (inputs, decision) => { spies.logMollifiedCalls.push({ inputs, decision }); }, recordDecision: (outcome, opts) => { spies.recordDecisionCalls.push({ outcome, reason: opts.reason, enrolled: opts.enrolled, orgId: opts.orgId, }); }, }; const outcome = await evaluateGate(inputs, deps); expect(outcome.action).toBe("pass_through"); expect(spies.evaluatorCalls).toBe(1); expect(spies.logMollifiedCalls).toHaveLength(0); expect(spies.logShadowCalls).toHaveLength(0); expect(spies.recordDecisionCalls).toHaveLength(1); expect(spies.recordDecisionCalls[0]).toMatchObject({ outcome: "pass_through", reason: undefined, enrolled: true, orgId: inputs.orgId, }); }); }); describe("evaluateGate — fail open on resolveOrgFlag error", () => { it("treats org flag as false when resolveOrgFlag throws, and does not block triggers", async () => { const spies: Spies = { evaluatorCalls: 0, logShadowCalls: [], logMollifiedCalls: [], recordDecisionCalls: [], }; const deps: Partial = { isMollifierEnabled: () => true, isShadowModeOn: () => false, resolveOrgFlag: async () => { throw new Error("simulated prisma timeout"); }, evaluator: async () => { spies.evaluatorCalls += 1; return trippedDecision; }, logShadow: (inputs, decision) => { spies.logShadowCalls.push({ inputs, decision }); }, logMollified: (inputs, decision) => { spies.logMollifiedCalls.push({ inputs, decision }); }, recordDecision: (outcome, opts) => { spies.recordDecisionCalls.push({ outcome, reason: opts.reason, enrolled: opts.enrolled, orgId: opts.orgId, }); }, }; const outcome = await evaluateGate(inputs, deps); expect(outcome.action).toBe("pass_through"); expect(spies.evaluatorCalls).toBe(0); expect(spies.recordDecisionCalls).toHaveLength(1); expect(spies.recordDecisionCalls[0]).toMatchObject({ outcome: "pass_through", reason: undefined, enrolled: false, orgId: inputs.orgId, }); }); }); describe("evaluateGate — per-org isolation via Organization.featureFlags", () => { function makeIsolationDeps(resolveOrgFlag: GateDependencies["resolveOrgFlag"]): { deps: Partial; spies: Spies; } { const spies: Spies = { evaluatorCalls: 0, logShadowCalls: [], logMollifiedCalls: [], recordDecisionCalls: [], }; // Override lifecycle bits and inject the production resolveOrgFlag. // Evaluator returns a fixed tripped decision so the outcome is purely a // function of the flag resolution (which is what we're isolating on). const deps: Partial = { isMollifierEnabled: () => true, isShadowModeOn: () => false, resolveOrgFlag, evaluator: async () => { spies.evaluatorCalls += 1; return trippedDecision; }, logShadow: (inputs, decision) => { spies.logShadowCalls.push({ inputs, decision }); }, logMollified: (inputs, decision) => { spies.logMollifiedCalls.push({ inputs, decision }); }, recordDecision: (outcome, opts) => { spies.recordDecisionCalls.push({ outcome, reason: opts.reason, enrolled: opts.enrolled, orgId: opts.orgId, }); }, }; return { deps, spies }; } // The production resolver — purely in-memory, no Prisma. Mirrors // `defaultGateDependencies.resolveOrgFlag` exactly. const resolve = makeResolveMollifierFlag(); it("opts in only the org whose featureFlags has mollifierEnabled=true", async () => { const orgA = { ...inputs, orgId: "org_a", orgFeatureFlags: { mollifierEnabled: true } }; const orgB = { ...inputs, orgId: "org_b", orgFeatureFlags: { mollifierEnabled: false } }; const orgC = { ...inputs, orgId: "org_c", orgFeatureFlags: null }; const a = makeIsolationDeps(resolve); const b = makeIsolationDeps(resolve); const c = makeIsolationDeps(resolve); const [outcomeA, outcomeB, outcomeC] = await Promise.all([ evaluateGate(orgA, a.deps), evaluateGate(orgB, b.deps), evaluateGate(orgC, c.deps), ]); // Only org A's flag is on → only org A mollifies. Orgs B and C never // reach the evaluator because both flag and shadow-mode are off. expect(outcomeA.action).toBe("mollify"); expect(outcomeB.action).toBe("pass_through"); expect(outcomeC.action).toBe("pass_through"); expect(a.spies.evaluatorCalls).toBe(1); expect(b.spies.evaluatorCalls).toBe(0); expect(c.spies.evaluatorCalls).toBe(0); expect(a.spies.logMollifiedCalls).toHaveLength(1); expect(b.spies.logMollifiedCalls).toHaveLength(0); expect(c.spies.logMollifiedCalls).toHaveLength(0); }); it("another org's beta flags must not opt them into mollifier", async () => { // Org A has mollifier on (plus an unrelated beta). const orgA = { ...inputs, orgId: "org_a", orgFeatureFlags: { mollifierEnabled: true, hasComputeAccess: true }, }; // Org B has *other* betas on but mollifier remains off — keys that gate // compute/AI/query must not bleed across into the mollifier decision. const orgB = { ...inputs, orgId: "org_b", orgFeatureFlags: { hasComputeAccess: true, hasAiAccess: true }, }; const a = makeIsolationDeps(resolve); const b = makeIsolationDeps(resolve); const outcomeA = await evaluateGate(orgA, a.deps); const outcomeB = await evaluateGate(orgB, b.deps); expect(outcomeA.action).toBe("mollify"); expect(outcomeB.action).toBe("pass_through"); }); it("orgs without an explicit override stay off — no global FeatureFlag fallback", async () => { // Regression intent: the resolver MUST NOT consult the global // `FeatureFlag` table on the hot path. An org with `orgFeatureFlags` // unset (the default for almost every org during rollout) gets // pass_through, period. The fleet-wide kill switch lives in // `TRIGGER_MOLLIFIER_ENABLED`, not the FeatureFlag table. const orgInherits = { ...inputs, orgId: "org_inherits", orgFeatureFlags: null }; const orgEmpty = { ...inputs, orgId: "org_empty", orgFeatureFlags: {} }; const orgUnrelated = { ...inputs, orgId: "org_unrelated", orgFeatureFlags: { hasAiAccess: true }, }; const inheritsDeps = makeIsolationDeps(resolve); const emptyDeps = makeIsolationDeps(resolve); const unrelatedDeps = makeIsolationDeps(resolve); const [outInherits, outEmpty, outUnrelated] = await Promise.all([ evaluateGate(orgInherits, inheritsDeps.deps), evaluateGate(orgEmpty, emptyDeps.deps), evaluateGate(orgUnrelated, unrelatedDeps.deps), ]); expect(outInherits.action).toBe("pass_through"); expect(outEmpty.action).toBe("pass_through"); expect(outUnrelated.action).toBe("pass_through"); // None of these reached the evaluator (flag off, shadow off). expect(inheritsDeps.spies.evaluatorCalls).toBe(0); expect(emptyDeps.spies.evaluatorCalls).toBe(0); expect(unrelatedDeps.spies.evaluatorCalls).toBe(0); }); }); // Bypasses: the three categories of trigger that the mollifier never // intercepts, regardless of the per-org flag or the trip-evaluator decision. describe("evaluateGate — debounce / OTU / triggerAndWait bypasses", () => { it("debounce triggers pass through without invoking the evaluator", async () => { const { deps, spies } = makeDeps({ enabled: true, shadow: false, flag: true, decision: trippedDecision, }); const outcome = await evaluateGate({ ...inputs, options: { debounce: { key: "k" } } }, deps); expect(outcome).toEqual({ action: "pass_through" }); expect(spies.evaluatorCalls).toBe(0); }); it("oneTimeUseToken triggers pass through without invoking the evaluator", async () => { const { deps, spies } = makeDeps({ enabled: true, shadow: false, flag: true, decision: trippedDecision, }); const outcome = await evaluateGate( { ...inputs, options: { oneTimeUseToken: "jwt-otu" } }, deps ); expect(outcome).toEqual({ action: "pass_through" }); expect(spies.evaluatorCalls).toBe(0); }); it("single triggerAndWait (parentTaskRunId + resumeParentOnCompletion) passes through", async () => { const { deps, spies } = makeDeps({ enabled: true, shadow: false, flag: true, decision: trippedDecision, }); const outcome = await evaluateGate( { ...inputs, options: { parentTaskRunId: "run_parent", resumeParentOnCompletion: true }, }, deps ); expect(outcome).toEqual({ action: "pass_through" }); expect(spies.evaluatorCalls).toBe(0); }); it("parentTaskRunId alone (no resumeParentOnCompletion) does NOT bypass — must be both", async () => { const { deps, spies } = makeDeps({ enabled: true, shadow: false, flag: true, decision: trippedDecision, }); const outcome = await evaluateGate( { ...inputs, options: { parentTaskRunId: "run_parent" } }, deps ); expect(outcome.action).toBe("mollify"); expect(spies.evaluatorCalls).toBe(1); }); it("bypass records pass_through decision (so observability counters stay accurate)", async () => { const { deps, spies } = makeDeps({ enabled: true, shadow: false, flag: true, decision: trippedDecision, }); await evaluateGate({ ...inputs, options: { debounce: { key: "k" } } }, deps); expect(spies.recordDecisionCalls).toHaveLength(1); expect(spies.recordDecisionCalls[0].outcome).toBe("pass_through"); }); });