306 lines
7.7 KiB
Markdown
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
|