FRE-4510: Implement feature flag checks for spam classification

- Add runtime flag evaluation from FLAG_<KEY> environment variables
- Add enableCallAnalysis flag check to analyzeCall() and interceptCall()
- Add enableFeedbackLoop flag check to recordFeedback()
- Add 19 tests for feature flag behavior (checkFeatureFlag, getters, service integration)
- Add vitest config and test script to spamshield package
This commit is contained in:
2026-05-02 01:53:59 -04:00
parent 90fbbc4465
commit e580a693c7
5 changed files with 225 additions and 4 deletions

View File

@@ -4,7 +4,7 @@ export const spamRateLimits = {
PREMIUM: 2000,
} as const;
export const spamFeatureFlags = {
export const spamFeatureFlagDefaults = {
enableHiyaIntegration: true,
enableTruecallerIntegration: true,
enableSMSClassification: true,
@@ -12,11 +12,42 @@ export const spamFeatureFlags = {
enableFeedbackLoop: true,
} as const;
type FeatureFlagKey = keyof typeof spamFeatureFlagDefaults;
export function checkFeatureFlag(flag: FeatureFlagKey): boolean {
const envKey = `FLAG_${flag.toUpperCase()}`;
const envValue = process.env[envKey];
if (envValue !== undefined) {
return envValue === 'true' || envValue === '1';
}
return spamFeatureFlagDefaults[flag];
}
export const spamFeatureFlags = {
get enableHiyaIntegration() {
return checkFeatureFlag('enableHiyaIntegration');
},
get enableTruecallerIntegration() {
return checkFeatureFlag('enableTruecallerIntegration');
},
get enableSMSClassification() {
return checkFeatureFlag('enableSMSClassification');
},
get enableCallAnalysis() {
return checkFeatureFlag('enableCallAnalysis');
},
get enableFeedbackLoop() {
return checkFeatureFlag('enableFeedbackLoop');
},
} as const;
export const spamConfig = {
maxPhoneNumberLength: 20,
minPhoneNumberLength: 10,
defaultConfidenceThreshold: 0.7,
maxMetadataSize: 1024 * 10, // 10KB
maxMetadataSize: 1024 * 10,
circuitBreakerThreshold: 5,
circuitBreakerTimeout: 60000,
} as const;

View File

@@ -197,6 +197,10 @@ export class SpamShieldService {
confidence: number;
ruleMatches: string[];
}> {
if (!spamFeatureFlags.enableCallAnalysis) {
throw new Error('Call analysis disabled via feature flag');
}
const validated = this.validatePhoneNumber(phoneNumber);
const rules = await this.getActiveRules();
@@ -244,6 +248,10 @@ export class SpamShieldService {
isSpam: boolean,
label?: string
): Promise<void> {
if (!spamFeatureFlags.enableFeedbackLoop) {
throw new Error('Feedback loop disabled via feature flag');
}
const validated = this.validatePhoneNumber(phoneNumber);
const encrypted = FieldEncryptionService.encrypt(validated);
const hash = FieldEncryptionService.hashPhoneNumber(validated);
@@ -391,6 +399,10 @@ export class SpamShieldService {
// Combined interception methods
async interceptCall(call: IncomingCall): Promise<DecisionResult> {
if (!spamFeatureFlags.enableCallAnalysis) {
throw new Error('Call analysis disabled via feature flag');
}
const requestId = call.requestId ?? generateRequestId();
const decision = await this.makeRealTimeDecision(call.phoneNumber, {
callMetadata: {