omgkit 2.22.11 → 2.24.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,559 @@
1
+ ---
2
+ name: Feature Flags and Progressive Delivery
3
+ description: The agent implements feature flag systems for trunk-based development, canary releases, and A/B testing. Use when implementing gradual rollouts, kill switches, or experiment-driven development.
4
+ category: devops
5
+ ---
6
+
7
+ # Feature Flags and Progressive Delivery
8
+
9
+ ## Purpose
10
+
11
+ Feature flags (also known as feature toggles) are a core BigTech practice that enables:
12
+
13
+ - **Trunk-based development** without breaking production
14
+ - **Canary releases** and gradual rollouts
15
+ - **A/B testing** and experimentation
16
+ - **Kill switches** for instant production rollbacks
17
+ - **User targeting** for beta features
18
+
19
+ Google, Netflix, Meta, and Amazon all use feature flags extensively to achieve continuous deployment with minimal risk.
20
+
21
+ ## Features
22
+
23
+ | Feature | Description | Use Case |
24
+ |---------|-------------|----------|
25
+ | Boolean Flags | Simple on/off toggles | Feature enable/disable |
26
+ | Multivariate Flags | Multiple values per flag | A/B/n testing |
27
+ | Percentage Rollouts | Gradual user exposure | Canary releases |
28
+ | User Targeting | Rules-based targeting | Beta programs |
29
+ | Kill Switches | Emergency disable | Incident response |
30
+ | Scheduled Flags | Time-based activation | Launch coordination |
31
+
32
+ ## Feature Flag Providers
33
+
34
+ ### LaunchDarkly (Enterprise)
35
+
36
+ ```typescript
37
+ // LaunchDarkly SDK Integration
38
+ import * as LaunchDarkly from 'launchdarkly-node-server-sdk';
39
+
40
+ const client = LaunchDarkly.init(process.env.LAUNCHDARKLY_SDK_KEY);
41
+
42
+ async function isFeatureEnabled(flagKey: string, user: LDUser): Promise<boolean> {
43
+ await client.waitForInitialization();
44
+ return client.variation(flagKey, user, false);
45
+ }
46
+
47
+ // Usage
48
+ const user = {
49
+ key: 'user-123',
50
+ email: 'user@example.com',
51
+ custom: {
52
+ plan: 'enterprise',
53
+ region: 'us-east'
54
+ }
55
+ };
56
+
57
+ if (await isFeatureEnabled('new-checkout-flow', user)) {
58
+ // New checkout experience
59
+ } else {
60
+ // Legacy checkout
61
+ }
62
+ ```
63
+
64
+ ### Unleash (Open Source)
65
+
66
+ ```typescript
67
+ // Unleash SDK Integration
68
+ import { initialize, isEnabled } from 'unleash-client';
69
+
70
+ const unleash = initialize({
71
+ url: 'https://unleash.example.com/api',
72
+ appName: 'my-app',
73
+ customHeaders: {
74
+ Authorization: process.env.UNLEASH_API_TOKEN
75
+ }
76
+ });
77
+
78
+ // Wait for ready
79
+ unleash.on('ready', () => {
80
+ if (isEnabled('new-feature')) {
81
+ console.log('Feature is enabled!');
82
+ }
83
+ });
84
+
85
+ // With context
86
+ const context = {
87
+ userId: 'user-123',
88
+ sessionId: 'session-abc',
89
+ remoteAddress: '192.168.1.1'
90
+ };
91
+
92
+ if (isEnabled('beta-feature', context)) {
93
+ // Beta experience
94
+ }
95
+ ```
96
+
97
+ ### Flagsmith (Open Source)
98
+
99
+ ```typescript
100
+ // Flagsmith SDK Integration
101
+ import Flagsmith from 'flagsmith-nodejs';
102
+
103
+ const flagsmith = new Flagsmith({
104
+ environmentKey: process.env.FLAGSMITH_ENVIRONMENT_KEY
105
+ });
106
+
107
+ // Get flags for user
108
+ const flags = await flagsmith.getIdentityFlags('user-123', {
109
+ plan: 'premium',
110
+ signup_date: '2024-01-01'
111
+ });
112
+
113
+ if (flags.isFeatureEnabled('dark_mode')) {
114
+ enableDarkMode();
115
+ }
116
+
117
+ // Get feature value
118
+ const buttonColor = flags.getFeatureValue('button_color');
119
+ ```
120
+
121
+ ### Custom Feature Flag Service
122
+
123
+ ```typescript
124
+ // Custom Feature Flag Implementation
125
+ interface FeatureFlag {
126
+ key: string;
127
+ enabled: boolean;
128
+ rolloutPercentage?: number;
129
+ userTargeting?: TargetingRule[];
130
+ schedule?: {
131
+ startAt?: Date;
132
+ endAt?: Date;
133
+ };
134
+ }
135
+
136
+ interface TargetingRule {
137
+ attribute: string;
138
+ operator: 'eq' | 'neq' | 'contains' | 'in';
139
+ value: string | string[];
140
+ }
141
+
142
+ class FeatureFlagService {
143
+ private flags: Map<string, FeatureFlag> = new Map();
144
+
145
+ async isEnabled(flagKey: string, context: Record<string, any>): Promise<boolean> {
146
+ const flag = this.flags.get(flagKey);
147
+ if (!flag) return false;
148
+
149
+ // Check if globally disabled
150
+ if (!flag.enabled) return false;
151
+
152
+ // Check schedule
153
+ if (flag.schedule) {
154
+ const now = new Date();
155
+ if (flag.schedule.startAt && now < flag.schedule.startAt) return false;
156
+ if (flag.schedule.endAt && now > flag.schedule.endAt) return false;
157
+ }
158
+
159
+ // Check targeting rules
160
+ if (flag.userTargeting) {
161
+ const matches = this.evaluateTargeting(flag.userTargeting, context);
162
+ if (!matches) return false;
163
+ }
164
+
165
+ // Check percentage rollout
166
+ if (flag.rolloutPercentage !== undefined) {
167
+ const hash = this.hashUser(context.userId || 'anonymous', flagKey);
168
+ return hash < flag.rolloutPercentage;
169
+ }
170
+
171
+ return true;
172
+ }
173
+
174
+ private hashUser(userId: string, flagKey: string): number {
175
+ // Consistent hashing for stable rollout
176
+ const combined = `${userId}:${flagKey}`;
177
+ let hash = 0;
178
+ for (let i = 0; i < combined.length; i++) {
179
+ hash = ((hash << 5) - hash) + combined.charCodeAt(i);
180
+ hash = hash & hash;
181
+ }
182
+ return Math.abs(hash % 100);
183
+ }
184
+
185
+ private evaluateTargeting(rules: TargetingRule[], context: Record<string, any>): boolean {
186
+ return rules.every(rule => {
187
+ const value = context[rule.attribute];
188
+ switch (rule.operator) {
189
+ case 'eq': return value === rule.value;
190
+ case 'neq': return value !== rule.value;
191
+ case 'contains': return String(value).includes(String(rule.value));
192
+ case 'in': return Array.isArray(rule.value) && rule.value.includes(value);
193
+ default: return false;
194
+ }
195
+ });
196
+ }
197
+ }
198
+ ```
199
+
200
+ ## Framework Integration
201
+
202
+ ### React/Next.js
203
+
204
+ ```typescript
205
+ // React Feature Flag Provider
206
+ import { createContext, useContext, useEffect, useState } from 'react';
207
+
208
+ interface FeatureFlagContextType {
209
+ isEnabled: (flag: string) => boolean;
210
+ getValue: (flag: string) => any;
211
+ loading: boolean;
212
+ }
213
+
214
+ const FeatureFlagContext = createContext<FeatureFlagContextType | null>(null);
215
+
216
+ export function FeatureFlagProvider({ children, userId }: {
217
+ children: React.ReactNode;
218
+ userId: string;
219
+ }) {
220
+ const [flags, setFlags] = useState<Record<string, any>>({});
221
+ const [loading, setLoading] = useState(true);
222
+
223
+ useEffect(() => {
224
+ async function loadFlags() {
225
+ const response = await fetch(`/api/flags?userId=${userId}`);
226
+ const data = await response.json();
227
+ setFlags(data);
228
+ setLoading(false);
229
+ }
230
+ loadFlags();
231
+ }, [userId]);
232
+
233
+ const value = {
234
+ isEnabled: (flag: string) => Boolean(flags[flag]?.enabled),
235
+ getValue: (flag: string) => flags[flag]?.value,
236
+ loading
237
+ };
238
+
239
+ return (
240
+ <FeatureFlagContext.Provider value={value}>
241
+ {children}
242
+ </FeatureFlagContext.Provider>
243
+ );
244
+ }
245
+
246
+ // Custom Hook
247
+ export function useFeatureFlag(flagKey: string) {
248
+ const context = useContext(FeatureFlagContext);
249
+ if (!context) throw new Error('useFeatureFlag must be used within FeatureFlagProvider');
250
+ return {
251
+ enabled: context.isEnabled(flagKey),
252
+ value: context.getValue(flagKey),
253
+ loading: context.loading
254
+ };
255
+ }
256
+
257
+ // Component Usage
258
+ function NewFeature() {
259
+ const { enabled, loading } = useFeatureFlag('new-dashboard');
260
+
261
+ if (loading) return <Spinner />;
262
+ if (!enabled) return <LegacyDashboard />;
263
+
264
+ return <NewDashboard />;
265
+ }
266
+ ```
267
+
268
+ ### Node.js/Express Middleware
269
+
270
+ ```typescript
271
+ // Express Feature Flag Middleware
272
+ import { Request, Response, NextFunction } from 'express';
273
+
274
+ declare global {
275
+ namespace Express {
276
+ interface Request {
277
+ featureFlags: {
278
+ isEnabled: (flag: string) => Promise<boolean>;
279
+ getValue: (flag: string) => Promise<any>;
280
+ };
281
+ }
282
+ }
283
+ }
284
+
285
+ export function featureFlagMiddleware(flagService: FeatureFlagService) {
286
+ return async (req: Request, res: Response, next: NextFunction) => {
287
+ const context = {
288
+ userId: req.user?.id,
289
+ userAgent: req.headers['user-agent'],
290
+ ip: req.ip,
291
+ ...req.user
292
+ };
293
+
294
+ req.featureFlags = {
295
+ isEnabled: (flag: string) => flagService.isEnabled(flag, context),
296
+ getValue: (flag: string) => flagService.getValue(flag, context)
297
+ };
298
+
299
+ next();
300
+ };
301
+ }
302
+
303
+ // Route Usage
304
+ app.get('/api/checkout', async (req, res) => {
305
+ if (await req.featureFlags.isEnabled('new-checkout')) {
306
+ return newCheckoutHandler(req, res);
307
+ }
308
+ return legacyCheckoutHandler(req, res);
309
+ });
310
+ ```
311
+
312
+ ### Python/FastAPI
313
+
314
+ ```python
315
+ # FastAPI Feature Flag Integration
316
+ from fastapi import FastAPI, Depends, Request
317
+ from functools import lru_cache
318
+ import httpx
319
+
320
+ app = FastAPI()
321
+
322
+ class FeatureFlagClient:
323
+ def __init__(self, base_url: str, api_key: str):
324
+ self.base_url = base_url
325
+ self.api_key = api_key
326
+ self._cache = {}
327
+
328
+ async def is_enabled(self, flag: str, context: dict = None) -> bool:
329
+ cache_key = f"{flag}:{hash(frozenset(context.items()) if context else '')}"
330
+ if cache_key in self._cache:
331
+ return self._cache[cache_key]
332
+
333
+ async with httpx.AsyncClient() as client:
334
+ response = await client.post(
335
+ f"{self.base_url}/api/flags/{flag}/evaluate",
336
+ json={"context": context or {}},
337
+ headers={"Authorization": f"Bearer {self.api_key}"}
338
+ )
339
+ result = response.json().get("enabled", False)
340
+ self._cache[cache_key] = result
341
+ return result
342
+
343
+ @lru_cache()
344
+ def get_flag_client():
345
+ return FeatureFlagClient(
346
+ base_url=os.environ["FLAG_SERVICE_URL"],
347
+ api_key=os.environ["FLAG_API_KEY"]
348
+ )
349
+
350
+ async def get_flags(request: Request, client: FeatureFlagClient = Depends(get_flag_client)):
351
+ return client
352
+
353
+ @app.get("/api/dashboard")
354
+ async def dashboard(flags: FeatureFlagClient = Depends(get_flags)):
355
+ if await flags.is_enabled("new_dashboard", {"user_id": "123"}):
356
+ return {"dashboard": "v2"}
357
+ return {"dashboard": "v1"}
358
+ ```
359
+
360
+ ## Rollout Strategies
361
+
362
+ ### Percentage Rollout (Canary)
363
+
364
+ ```yaml
365
+ # Gradual rollout configuration
366
+ feature: new-payment-flow
367
+ rollout:
368
+ - percentage: 1
369
+ duration: 1d
370
+ metrics:
371
+ - error_rate < 0.1%
372
+ - latency_p99 < 500ms
373
+ - percentage: 10
374
+ duration: 2d
375
+ - percentage: 50
376
+ duration: 3d
377
+ - percentage: 100
378
+ duration: stable
379
+ ```
380
+
381
+ ### Ring-based Deployment
382
+
383
+ ```typescript
384
+ // Ring-based rollout
385
+ enum DeploymentRing {
386
+ INTERNAL = 0, // Employee testing
387
+ EARLY_ADOPTERS = 1, // Opt-in beta users
388
+ CANARY = 2, // 1% of users
389
+ PRODUCTION = 3 // 100% of users
390
+ }
391
+
392
+ async function getUserRing(userId: string): Promise<DeploymentRing> {
393
+ const user = await getUser(userId);
394
+
395
+ if (user.isEmployee) return DeploymentRing.INTERNAL;
396
+ if (user.betaOptIn) return DeploymentRing.EARLY_ADOPTERS;
397
+
398
+ // Canary: first 1% based on user ID hash
399
+ const hash = hashUserId(userId);
400
+ if (hash < 1) return DeploymentRing.CANARY;
401
+
402
+ return DeploymentRing.PRODUCTION;
403
+ }
404
+ ```
405
+
406
+ ## Best Practices
407
+
408
+ ### 1. Flag Lifecycle Management
409
+
410
+ ```typescript
411
+ // Flag metadata for lifecycle tracking
412
+ interface FlagMetadata {
413
+ key: string;
414
+ owner: string;
415
+ createdAt: Date;
416
+ expectedRemovalDate: Date;
417
+ type: 'release' | 'experiment' | 'ops' | 'permission';
418
+ status: 'active' | 'deprecated' | 'removed';
419
+ jiraTicket?: string;
420
+ }
421
+
422
+ // Automated cleanup alerts
423
+ async function auditStaleFlags() {
424
+ const flags = await getAllFlags();
425
+ const stale = flags.filter(f =>
426
+ f.status === 'active' &&
427
+ f.expectedRemovalDate < new Date()
428
+ );
429
+
430
+ for (const flag of stale) {
431
+ await notifyOwner(flag.owner, `Flag ${flag.key} is past removal date`);
432
+ }
433
+ }
434
+ ```
435
+
436
+ ### 2. Testing with Feature Flags
437
+
438
+ ```typescript
439
+ // Test utilities for feature flags
440
+ class TestFeatureFlagService extends FeatureFlagService {
441
+ private overrides: Map<string, boolean> = new Map();
442
+
443
+ override(flagKey: string, enabled: boolean): void {
444
+ this.overrides.set(flagKey, enabled);
445
+ }
446
+
447
+ clearOverrides(): void {
448
+ this.overrides.clear();
449
+ }
450
+
451
+ async isEnabled(flagKey: string, context: Record<string, any>): Promise<boolean> {
452
+ if (this.overrides.has(flagKey)) {
453
+ return this.overrides.get(flagKey)!;
454
+ }
455
+ return super.isEnabled(flagKey, context);
456
+ }
457
+ }
458
+
459
+ // In tests
460
+ describe('New Checkout Flow', () => {
461
+ let flagService: TestFeatureFlagService;
462
+
463
+ beforeEach(() => {
464
+ flagService = new TestFeatureFlagService();
465
+ });
466
+
467
+ it('should show new checkout when flag enabled', async () => {
468
+ flagService.override('new-checkout', true);
469
+ const result = await renderCheckout(flagService);
470
+ expect(result).toContain('New Checkout');
471
+ });
472
+
473
+ it('should show legacy checkout when flag disabled', async () => {
474
+ flagService.override('new-checkout', false);
475
+ const result = await renderCheckout(flagService);
476
+ expect(result).toContain('Legacy Checkout');
477
+ });
478
+ });
479
+ ```
480
+
481
+ ### 3. Flag Documentation
482
+
483
+ ```typescript
484
+ // Self-documenting flags
485
+ const FLAGS = {
486
+ NEW_CHECKOUT: {
487
+ key: 'new-checkout-flow',
488
+ description: 'Enables the redesigned checkout experience',
489
+ owner: 'payments-team',
490
+ type: 'release',
491
+ defaultValue: false,
492
+ // Link to design doc, metrics dashboard
493
+ references: {
494
+ designDoc: 'https://docs.example.com/new-checkout',
495
+ metrics: 'https://grafana.example.com/d/checkout'
496
+ }
497
+ }
498
+ } as const;
499
+ ```
500
+
501
+ ## Anti-Patterns to Avoid
502
+
503
+ | Anti-Pattern | Problem | Solution |
504
+ |--------------|---------|----------|
505
+ | Long-lived flags | Technical debt accumulation | Set removal dates, automate cleanup |
506
+ | Nested flags | Complexity explosion | Flatten flag dependencies |
507
+ | Flag in loops | Performance issues | Evaluate once, cache result |
508
+ | No default values | Runtime errors | Always provide defaults |
509
+ | Missing tests | Untested code paths | Test both flag states |
510
+ | No ownership | Orphaned flags | Assign owners, track lifecycle |
511
+
512
+ ## Use Cases
513
+
514
+ ### 1. Trunk-Based Development
515
+
516
+ Enable incomplete features in production without exposing to users:
517
+
518
+ ```typescript
519
+ // Feature under development
520
+ if (await flags.isEnabled('wip-new-editor')) {
521
+ return <NewEditor />; // Only visible to developers
522
+ }
523
+ return <CurrentEditor />;
524
+ ```
525
+
526
+ ### 2. Incident Kill Switch
527
+
528
+ Instantly disable problematic features:
529
+
530
+ ```typescript
531
+ // Kill switch for external service
532
+ if (await flags.isEnabled('external-recommendations')) {
533
+ recommendations = await fetchExternalRecommendations();
534
+ } else {
535
+ recommendations = await getFallbackRecommendations();
536
+ }
537
+ ```
538
+
539
+ ### 3. A/B Testing
540
+
541
+ Run experiments with statistical significance:
542
+
543
+ ```typescript
544
+ const variant = await flags.getValue('pricing-page-variant');
545
+ // variant: 'control' | 'variant-a' | 'variant-b'
546
+ trackExperiment('pricing-page', variant, userId);
547
+ return <PricingPage variant={variant} />;
548
+ ```
549
+
550
+ ## Related Skills
551
+
552
+ - `devops/github-actions` - CI/CD integration
553
+ - `testing/comprehensive-testing` - Testing flag states
554
+ - `devops/observability` - Monitoring rollouts
555
+ - `methodology/finishing-development-branch` - Branch workflow
556
+
557
+ ---
558
+
559
+ *Think Omega. Build Omega. Be Omega.*