Transferred ShieldAI-related files mistakenly placed in ~/code/FrenoCorp:
- Services: spamshield (feature-flags, audit-logger, error-handler), voiceprint (config, service, feature-flags), darkwatch (pipeline, scan, scheduler, watchlist, webhook)
- Packages: shared-analytics, shared-auth, shared-ui, shared-utils (new); shared-billing, jobs supplemented with unique FC files
- Server: alerts (FC version newer), routes (spamshield, darkwatch, voiceprint)
- Config: turbo.json, tsconfig.base.json, vite/vitest configs, drizzle, Dockerfile
- VoicePrint ML service
- Examples
Pending: apps/{api,web,mobile}/ structured merge, shared-db/db mapping
Co-Authored-By: Paperclip <noreply@paperclip.ing>
228 lines
6.2 KiB
TypeScript
228 lines
6.2 KiB
TypeScript
/**
|
|
* Feature Flag Management System
|
|
* Centralized feature flag handling with type safety and runtime updates
|
|
*/
|
|
|
|
import type { z } from 'zod';
|
|
|
|
/**
|
|
* Type for feature flag values
|
|
*/
|
|
export type FeatureFlagValue = boolean | string | number;
|
|
|
|
/**
|
|
* Interface for a feature flag definition
|
|
*/
|
|
export interface FeatureFlag<T = FeatureFlagValue> {
|
|
key: string;
|
|
defaultValue: T;
|
|
description?: string;
|
|
allowedValues?: T[]; // For enum-like flags
|
|
category?: string;
|
|
}
|
|
|
|
/**
|
|
* Feature flag registry - stores all defined flags
|
|
*/
|
|
export interface FeatureFlagRegistry {
|
|
[key: string]: FeatureFlag;
|
|
}
|
|
|
|
/**
|
|
* Feature flag resolver - handles flag resolution logic
|
|
*/
|
|
export class FeatureFlagResolver {
|
|
private flags: FeatureFlagRegistry;
|
|
private resolvedCache: Map<string, FeatureFlagValue> = new Map();
|
|
|
|
constructor(flags: FeatureFlagRegistry) {
|
|
this.flags = flags;
|
|
}
|
|
|
|
/**
|
|
* Resolve a feature flag value
|
|
* Priority: Environment > Cache > Default
|
|
*/
|
|
resolve<T>(key: string, defaultValue: T): T {
|
|
// Check cache first
|
|
if (this.resolvedCache.has(key)) {
|
|
return this.resolvedCache.get(key)! as T;
|
|
}
|
|
|
|
// Check environment variable (allows runtime updates)
|
|
const envValue = process.env[`FLAG_${key.toUpperCase()}`];
|
|
if (envValue !== undefined) {
|
|
// Try to parse as JSON first, then as boolean, then as string
|
|
let parsed: FeatureFlagValue;
|
|
try {
|
|
parsed = JSON.parse(envValue);
|
|
} catch {
|
|
parsed = envValue.toLowerCase() === 'true' ? true :
|
|
envValue.toLowerCase() === 'false' ? false :
|
|
envValue;
|
|
}
|
|
|
|
// Validate against allowed values if defined
|
|
const flag = this.flags[key];
|
|
if (flag && flag.allowedValues && !flag.allowedValues.includes(parsed)) {
|
|
console.warn(`Invalid value for flag ${key}: ${parsed}. Using default.`);
|
|
parsed = defaultValue as FeatureFlagValue;
|
|
}
|
|
|
|
this.resolvedCache.set(key, parsed);
|
|
return parsed as T;
|
|
}
|
|
|
|
// Use cached value if available
|
|
if (this.resolvedCache.has(key)) {
|
|
return this.resolvedCache.get(key)! as T;
|
|
}
|
|
|
|
// Return default
|
|
this.resolvedCache.set(key, defaultValue as FeatureFlagValue);
|
|
return defaultValue as T;
|
|
}
|
|
|
|
/**
|
|
* Check if a flag is enabled (boolean check)
|
|
*/
|
|
isEnabled<T>(key: string, defaultValue: T): T {
|
|
return this.resolve(key, defaultValue) as T;
|
|
}
|
|
|
|
/**
|
|
* Get flag definition
|
|
*/
|
|
getDefinition(key: string): FeatureFlag | undefined {
|
|
return this.flags[key];
|
|
}
|
|
|
|
/**
|
|
* List all registered flags
|
|
*/
|
|
getAllFlags(): FeatureFlagRegistry {
|
|
return { ...this.flags };
|
|
}
|
|
|
|
/**
|
|
* Clear the resolution cache (useful for testing)
|
|
*/
|
|
clearCache(): void {
|
|
this.resolvedCache.clear();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Feature flag configuration with pre-defined flags
|
|
*/
|
|
export const featureFlags: FeatureFlagRegistry = {
|
|
// SpamShield Feature Flags
|
|
'spamshield.enable.number.reputation': {
|
|
key: 'spamshield_enable_number_reputation',
|
|
defaultValue: true,
|
|
description: 'Enable number reputation checking (Hiya API integration)',
|
|
category: 'spamshield',
|
|
},
|
|
'spamshield.enable.content.classification': {
|
|
key: 'spamshield_enable_content_classification',
|
|
defaultValue: true,
|
|
description: 'Enable SMS content classification (BERT model)',
|
|
category: 'spamshield',
|
|
},
|
|
'spamshield.enable.behavioral.analysis': {
|
|
key: 'spamshield_enable_behavioral_analysis',
|
|
defaultValue: true,
|
|
description: 'Enable call behavioral analysis',
|
|
category: 'spamshield',
|
|
},
|
|
'spamshield.enable.community.intelligence': {
|
|
key: 'spamshield_enable_community_intelligence',
|
|
defaultValue: true,
|
|
description: 'Enable community intelligence sharing',
|
|
category: 'spamshield',
|
|
},
|
|
'spamshield.enable.real.time.blocking': {
|
|
key: 'spamshield_enable_real_time_blocking',
|
|
defaultValue: true,
|
|
description: 'Enable real-time spam blocking',
|
|
category: 'spamshield',
|
|
},
|
|
'spamshield.enable.multiple.sources': {
|
|
key: 'spamshield_enable_multiple_sources',
|
|
defaultValue: false,
|
|
description: 'Enable multiple reputation source aggregation (Truecaller, etc.)',
|
|
category: 'spamshield',
|
|
},
|
|
'spamshield.enable.ml.classifier': {
|
|
key: 'spamshield_enable_ml_classifier',
|
|
defaultValue: false,
|
|
description: 'Enable ML-based spam classification',
|
|
category: 'spamshield',
|
|
},
|
|
|
|
// VoicePrint Feature Flags
|
|
'voiceprint.enable.ml.service': {
|
|
key: 'voiceprint_enable_ml_service',
|
|
defaultValue: false,
|
|
description: 'Enable ML service integration for voice analysis',
|
|
category: 'voiceprint',
|
|
},
|
|
'voiceprint.enable.faiss.index': {
|
|
key: 'voiceprint_enable_faiss_index',
|
|
defaultValue: true,
|
|
description: 'Enable FAISS index for voice matching',
|
|
category: 'voiceprint',
|
|
},
|
|
'voiceprint.enable.batch.analysis': {
|
|
key: 'voiceprint_enable_batch_analysis',
|
|
defaultValue: true,
|
|
description: 'Enable batch voice analysis',
|
|
category: 'voiceprint',
|
|
},
|
|
'voiceprint.enable.realtime.analysis': {
|
|
key: 'voiceprint_enable_realtime_analysis',
|
|
defaultValue: false,
|
|
description: 'Enable real-time voice analysis',
|
|
category: 'voiceprint',
|
|
},
|
|
'voiceprint.enable.mock.model': {
|
|
key: 'voiceprint_enable_mock_model',
|
|
defaultValue: true,
|
|
description: 'Enable mock model for development',
|
|
category: 'voiceprint',
|
|
},
|
|
|
|
// General Platform Flags
|
|
'platform.enable.audit.logs': {
|
|
key: 'platform_enable_audit_logs',
|
|
defaultValue: true,
|
|
description: 'Enable comprehensive audit logging',
|
|
category: 'platform',
|
|
},
|
|
'platform.enable.kpi.tracking': {
|
|
key: 'platform_enable_kpi_tracking',
|
|
defaultValue: true,
|
|
description: 'Enable KPI snapshot tracking',
|
|
category: 'platform',
|
|
},
|
|
};
|
|
|
|
/**
|
|
* Create a resolver instance with the default flags
|
|
*/
|
|
export const featureFlagResolver = new FeatureFlagResolver(featureFlags);
|
|
|
|
/**
|
|
* Convenience function for quick flag checks
|
|
*/
|
|
export function isFeatureEnabled<T>(key: string, defaultValue: T): T {
|
|
return featureFlagResolver.isEnabled(key, defaultValue);
|
|
}
|
|
|
|
/**
|
|
* Check if a flag is enabled with type safety
|
|
*/
|
|
export function checkFlag<T>(key: string, defaultValue: T): T {
|
|
return featureFlagResolver.resolve(key, defaultValue);
|
|
}
|