7.7 KiB
Feature Flag Evaluator
Evaluates feature flags for Phase 6 rollout with deterministic rollout control and variant assignment.
Purpose
This shared utility handles:
- Feature flag configuration loading
- Deterministic rollout control (consistent user assignment)
- Variant assignment for A/B testing
- Stage transitions (beta → early access → GA)
- User preference overrides
Algorithm
function evaluateFlag(userId, flagName, config) {
// Step 1: Check if flag is enabled globally
const flagDef = config.flags[flagName];
if (!flagDef || !flagDef.enabled) {
return { enabled: false, variant: 'disabled', reason: 'flag_disabled' };
}
// Step 2: Check minimum version requirement
const currentVersion = getPluginVersion(); // e.g., "2.3.0-beta.1"
if (!meetsMinVersion(currentVersion, flagDef.min_version)) {
return {
enabled: false,
variant: 'old_version',
reason: 'version_requirement_not_met',
required_version: flagDef.min_version,
current_version: currentVersion
};
}
// Step 3: Check user overrides (highest priority)
const userConfig = getUserConfig();
if (flagDef.user_override && userConfig.feature_flags?.[flagName]?.override !== null) {
const override = userConfig.feature_flags[flagName].override;
return {
enabled: override,
variant: override ? 'user_enabled' : 'user_disabled',
reason: 'user_override'
};
}
// Step 4: Deterministic rollout assignment
// Use hash(userId + flagName + salt) % 100 for consistent assignment
const hash = deterministicHash(userId, flagName);
const rolloutPercentage = flagDef.rollout_percentage || 0;
if (hash >= rolloutPercentage) {
return {
enabled: false,
variant: 'not_in_rollout',
reason: 'rollout_percentage_not_reached',
rollout_percentage: rolloutPercentage,
user_hash: hash
};
}
// Step 5: Assign variant based on rollout
const variant = assignVariant(flagDef.variants, hash);
return {
enabled: true,
variant: variant,
reason: 'flag_enabled',
rollout_percentage: rolloutPercentage
};
}
function assignVariant(variants, hash) {
// Assign control or treatment variant
if (!variants) return 'default';
let cumulativePercentage = 0;
for (const [variantName, variantDef] of Object.entries(variants)) {
const variantPercentage = variantDef.percentage || 50;
if (hash < cumulativePercentage + variantPercentage) {
return variantName;
}
cumulativePercentage += variantPercentage;
}
return 'default';
}
function deterministicHash(userId, flagName) {
// Simple deterministic hash: consistent across calls
const combined = `${userId}:${flagName}`;
let hash = 0;
for (let i = 0; i < combined.length; i++) {
const char = combined.charCodeAt(i);
hash = ((hash << 5) - hash) + char;
hash = hash & hash; // Convert to 32bit integer
}
return Math.abs(hash % 100);
}
function meetsMinVersion(current, required) {
// Semantic version comparison
const currentParts = current.split(/[.-]/);
const requiredParts = required.split(/[.-]/);
for (let i = 0; i < Math.max(currentParts.length, requiredParts.length); i++) {
const curr = parseInt(currentParts[i]) || 0;
const req = parseInt(requiredParts[i]) || 0;
if (curr > req) return true;
if (curr < req) return false;
}
return true;
}
Feature Flag Caching
Feature flags are loaded and cached at startup with 60-second refresh interval:
class FeatureFlagCache {
constructor() {
this.cache = new Map();
this.lastRefresh = 0;
this.refreshInterval = 60000; // 60 seconds
}
async getFlag(userId, flagName) {
// Return cached result if fresh
const cacheKey = `${userId}:${flagName}`;
if (this.cache.has(cacheKey)) {
const cached = this.cache.get(cacheKey);
if (Date.now() - cached.timestamp < this.refreshInterval) {
return cached.value;
}
}
// Load and cache flag configuration
const config = await loadFeatureFlagsConfig();
const result = evaluateFlag(userId, flagName, config);
this.cache.set(cacheKey, {
value: result,
timestamp: Date.now()
});
return result;
}
invalidate(userId, flagName) {
const cacheKey = `${userId}:${flagName}`;
this.cache.delete(cacheKey);
}
clear() {
this.cache.clear();
}
}
User Configuration
Users can override feature flags in ~/.claude/ccpm-config.json:
{
"feature_flags": {
"optimized_workflow_commands": {
"override": true,
"enabled": true
},
"linear_subagent_enabled": {
"override": false,
"enabled": false
}
}
}
Stage Transitions
Feature flags automatically transition through stages based on configuration:
Beta (Dec 9) → 10% rollout
↓
Early Access (Dec 21) → 30% rollout
↓
General Availability (Jan 6) → 100% rollout
Stages are managed automatically by checking current date against rollout_schedule in feature-flags.json.
Integration with Commands
All commands should check feature flags before execution:
Task(feature-flag-evaluator): `
userId: {{currentUserId}}
flag: "optimized_workflow_commands"
`
If result.enabled is true:
- Use optimized implementation
Else:
- Use legacy implementation
- Show migration hint after completion
Monitoring
Feature flag evaluator tracks:
- Number of users in each rollout stage
- Variant distribution
- Override usage
- Cache hit rates
- Evaluation latency
Metrics are stored in .ccpm/metrics.json and used for dashboards.
Admin Commands
Admins can manage feature flags via commands:
# View current feature flag status
/ccpm:admin:flags --show
# Manually set rollout percentage
/ccpm:admin:flags --set optimized_workflow_commands --percentage 50
# Override flag for specific user
/ccpm:admin:flags --set optimized_workflow_commands --user-override true
# Trigger automatic rollback
/ccpm:admin:flags --rollback optimized_workflow_commands
# View metrics
/ccpm:admin:flags --metrics
Testing
Feature flags can be tested in isolated environments:
# Test with specific rollout percentage
CCPM_FEATURE_FLAG_OVERRIDE=optimized_workflow_commands:50 claude --code
# Test with specific variant
CCPM_FEATURE_FLAG_VARIANT=optimized_workflow_commands:treatment claude --code
# Disable all new features
CCPM_FEATURE_FLAGS_DISABLED=true claude --code
Rollback Procedures
Automatic Rollback (Monitoring)
When critical error threshold reached (5%+ error rate):
- Monitoring detects error rate spike
- Automatically disables flag via feature-flags.json update
- Routes traffic to control variant
- Sends alert to on-call engineer
- No user action required (automatic recovery)
Manual Rollback (User-Initiated)
User can disable feature flag:
/ccpm:config feature-flags optimized_workflow_commands false
Emergency Rollback (Team-Initiated)
Team can trigger full rollback:
/ccpm:admin:flags --emergency-rollback optimized_workflow_commands
This immediately sets rollout_percentage to 0 and notifies all affected users.
Success Metrics
Feature flag evaluator success is measured by:
- ✅ Consistent user assignment (same user gets same variant every time)
- ✅ Accurate rollout percentages (±2% deviation allowed)
- ✅ Sub-100ms evaluation latency
- ✅ >90% cache hit rate
- ✅ Zero cache invalidation bugs
- ✅ Deterministic, reproducible results
References
.ccpm/feature-flags.json- Feature flag configuration~/.claude/ccpm-config.json- User overrides.ccpm/metrics.json- Evaluation metricsdocs/guides/feature-flag-configuration.md- User guidedocs/architecture/feature-flag-system.md- Design document