@@ -16,6 +16,7 @@ import { FEATURE_FLAG } from "~/v3/featureFlags";
1616import { makeFlag } from "~/v3/featureFlags.server" ;
1717import {
1818 evaluateGate ,
19+ makeResolveMollifierFlag ,
1920 type GateDependencies ,
2021 type GateInputs ,
2122 type TripDecision ,
@@ -195,6 +196,101 @@ describe("evaluateGate cascade — exhaustive truth table", () => {
195196// we build it via `makeFlag(prisma)` and let the `Organization.featureFlags`
196197// blob flow through `flag()`'s overrides path. The global `FeatureFlag` table
197198// is empty, so the only signal moving outcomes is the per-org JSON.
199+ // Hot-path guard: `triggerTask.server.ts` calls `evaluateGate` on every
200+ // trigger when `MOLLIFIER_ENABLED=1`. The per-org override path must resolve
201+ // without a Prisma round-trip — otherwise the gate adds a DB query to the
202+ // highest-throughput code path in the system (see apps/webapp/CLAUDE.md).
203+ describe ( "resolveMollifierFlag — hot path" , ( ) => {
204+ it ( "returns override value without calling flag() when override is set" , async ( ) => {
205+ let flagCalls = 0 ;
206+ const flagStub : any = async ( ) => {
207+ flagCalls += 1 ;
208+ return false ;
209+ } ;
210+ const resolve = makeResolveMollifierFlag ( flagStub ) ;
211+
212+ const enabled = await resolve ( {
213+ envId : "e" ,
214+ orgId : "o" ,
215+ taskId : "t" ,
216+ orgFeatureFlags : { mollifierEnabled : true } ,
217+ } ) ;
218+ const disabled = await resolve ( {
219+ envId : "e" ,
220+ orgId : "o" ,
221+ taskId : "t" ,
222+ orgFeatureFlags : { mollifierEnabled : false } ,
223+ } ) ;
224+
225+ expect ( enabled ) . toBe ( true ) ;
226+ expect ( disabled ) . toBe ( false ) ;
227+ expect ( flagCalls ) . toBe ( 0 ) ;
228+ } ) ;
229+
230+ it ( "falls back to flag() when org has no override for the key" , async ( ) => {
231+ let flagCalls = 0 ;
232+ const flagStub : any = async ( ) => {
233+ flagCalls += 1 ;
234+ return true ;
235+ } ;
236+ const resolve = makeResolveMollifierFlag ( flagStub ) ;
237+
238+ const fromNull = await resolve ( {
239+ envId : "e" ,
240+ orgId : "o" ,
241+ taskId : "t" ,
242+ orgFeatureFlags : null ,
243+ } ) ;
244+ const fromUnrelatedKeys = await resolve ( {
245+ envId : "e" ,
246+ orgId : "o" ,
247+ taskId : "t" ,
248+ orgFeatureFlags : { hasAiAccess : true } ,
249+ } ) ;
250+
251+ expect ( fromNull ) . toBe ( true ) ;
252+ expect ( fromUnrelatedKeys ) . toBe ( true ) ;
253+ expect ( flagCalls ) . toBe ( 2 ) ;
254+ } ) ;
255+ } ) ;
256+
257+ describe ( "evaluateGate — fail open on resolveOrgFlag error" , ( ) => {
258+ it ( "treats org flag as false when resolveOrgFlag throws, and does not block triggers" , async ( ) => {
259+ const spies : Spies = {
260+ evaluatorCalls : 0 ,
261+ logShadowCalls : [ ] ,
262+ logMollifiedCalls : [ ] ,
263+ recordDecisionCalls : [ ] ,
264+ } ;
265+ const deps : Partial < GateDependencies > = {
266+ isMollifierEnabled : ( ) => true ,
267+ isShadowModeOn : ( ) => false ,
268+ resolveOrgFlag : async ( ) => {
269+ throw new Error ( "simulated prisma timeout" ) ;
270+ } ,
271+ evaluator : async ( ) => {
272+ spies . evaluatorCalls += 1 ;
273+ return trippedDecision ;
274+ } ,
275+ logShadow : ( inputs , decision ) => {
276+ spies . logShadowCalls . push ( { inputs, decision } ) ;
277+ } ,
278+ logMollified : ( inputs , decision ) => {
279+ spies . logMollifiedCalls . push ( { inputs, decision } ) ;
280+ } ,
281+ recordDecision : ( outcome , reason ) => {
282+ spies . recordDecisionCalls . push ( { outcome, reason } ) ;
283+ } ,
284+ } ;
285+
286+ const outcome = await evaluateGate ( inputs , deps ) ;
287+
288+ expect ( outcome . action ) . toBe ( "pass_through" ) ;
289+ expect ( spies . evaluatorCalls ) . toBe ( 0 ) ;
290+ expect ( spies . recordDecisionCalls ) . toEqual ( [ { outcome : "pass_through" , reason : undefined } ] ) ;
291+ } ) ;
292+ } ) ;
293+
198294describe ( "evaluateGate — per-org isolation via Organization.featureFlags" , ( ) => {
199295 function makeIsolationDeps (
200296 realResolveOrgFlag : GateDependencies [ "resolveOrgFlag" ] ,
0 commit comments