Files
gh-duongdev-ccpm/commands/_feature-flag-evaluator.md
2025-11-29 18:24:24 +08:00

306 lines
7.7 KiB
Markdown

# 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
```javascript
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:
```javascript
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`:
```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:
```markdown
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:
```bash
# 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:
```bash
# 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):
1. Monitoring detects error rate spike
2. Automatically disables flag via feature-flags.json update
3. Routes traffic to control variant
4. Sends alert to on-call engineer
5. No user action required (automatic recovery)
### Manual Rollback (User-Initiated)
User can disable feature flag:
```bash
/ccpm:config feature-flags optimized_workflow_commands false
```
### Emergency Rollback (Team-Initiated)
Team can trigger full rollback:
```bash
/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 metrics
- `docs/guides/feature-flag-configuration.md` - User guide
- `docs/architecture/feature-flag-system.md` - Design document