feat(flags): add seed flags for 6 missing products + 6 new evaluator edge-case tests

- seed.ts: add default flags for jarvisjr (3), peakpulse (3), flowmonk (2), notelett (2), actiontrail (2), localmemgpt (2)
  Previously only chronomind, nomgap, mindlyst, smartauth, lysnrai had seed flags — common flags (maintenance_mode, telemetry_enabled) were not seeded for newer products
- flags.test.ts: 63 → 69 tests (+6):
  - anonymous user with partial percentage returns off
  - partial rule rollout (0%) skips even matching users
  - neq operator (not equal)
  - contains operator
  - gte + lt numeric range on custom attributes
  - missing context attribute returns off
This commit is contained in:
saravanakumardb1 2026-03-21 11:52:08 -07:00
parent dd113b96c9
commit 1e1ee969dc
2 changed files with 247 additions and 0 deletions

View File

@ -680,6 +680,143 @@ describe('evaluateFlag', () => {
expect(result.reason).not.toBe('prerequisite_failed');
});
it('returns off for anonymous user with partial percentage', () => {
const flag = makeFlag({ percentage: 50 });
const result = evaluateFlag({ flag, ctx: {}, allFlags: [flag], segments: [] });
expect(result.reason).toBe('off');
expect(result.value).toBe(false);
});
it('applies partial rollout on targeting rule', () => {
// Rule has 0% rollout — even matching users don't get it
const flag = makeFlag({
percentage: 0,
targetingRules: [
{
id: 'r1',
clauses: [{ attribute: 'platform', operator: 'eq', values: ['ios'] }],
variationKey: 'on',
rolloutPercentage: 0,
},
],
});
const result = evaluateFlag({
flag,
ctx: { userId: 'u1', platform: 'ios' },
allFlags: [flag],
segments: [],
});
expect(result.reason).toBe('off');
});
it('evaluates neq operator', () => {
const flag = makeFlag({
percentage: 0,
targetingRules: [
{
id: 'r1',
clauses: [{ attribute: 'platform', operator: 'neq', values: ['web'] }],
variationKey: 'on',
rolloutPercentage: 100,
},
],
});
const ios = evaluateFlag({
flag,
ctx: { userId: 'u1', platform: 'ios' },
allFlags: [flag],
segments: [],
});
expect(ios.reason).toBe('rule_match');
const web = evaluateFlag({
flag,
ctx: { userId: 'u1', platform: 'web' },
allFlags: [flag],
segments: [],
});
expect(web.reason).toBe('off');
});
it('evaluates contains operator', () => {
const flag = makeFlag({
percentage: 0,
targetingRules: [
{
id: 'r1',
clauses: [{ attribute: 'email', operator: 'contains', values: ['bytelyst'] }],
variationKey: 'on',
rolloutPercentage: 100,
},
],
});
const match = evaluateFlag({
flag,
ctx: { userId: 'u1', email: 'dev@bytelyst.com' },
allFlags: [flag],
segments: [],
});
expect(match.reason).toBe('rule_match');
const noMatch = evaluateFlag({
flag,
ctx: { userId: 'u1', email: 'dev@gmail.com' },
allFlags: [flag],
segments: [],
});
expect(noMatch.reason).toBe('off');
});
it('evaluates gt and lt operators on numeric custom attribute', () => {
const flag = makeFlag({
percentage: 0,
targetingRules: [
{
id: 'r1',
clauses: [
{ attribute: 'age', operator: 'gte', values: [18] },
{ attribute: 'age', operator: 'lt', values: [65] },
],
variationKey: 'on',
rolloutPercentage: 100,
},
],
});
const adult = evaluateFlag({
flag,
ctx: { userId: 'u1', custom: { age: 30 } },
allFlags: [flag],
segments: [],
});
expect(adult.reason).toBe('rule_match');
const minor = evaluateFlag({
flag,
ctx: { userId: 'u1', custom: { age: 15 } },
allFlags: [flag],
segments: [],
});
expect(minor.reason).toBe('off');
});
it('returns off when clause attribute is missing from context', () => {
const flag = makeFlag({
percentage: 0,
targetingRules: [
{
id: 'r1',
clauses: [{ attribute: 'email', operator: 'eq', values: ['test@test.com'] }],
variationKey: 'on',
rolloutPercentage: 100,
},
],
});
const result = evaluateFlag({
flag,
ctx: { userId: 'u1' },
allFlags: [flag],
segments: [],
});
expect(result.reason).toBe('off');
});
it('handles circular prerequisites gracefully', () => {
const a = makeFlag({ key: 'a', prerequisites: [{ flagKey: 'b', variationKey: 'on' }] });
const b = makeFlag({ key: 'b', prerequisites: [{ flagKey: 'a', variationKey: 'on' }] });

View File

@ -235,6 +235,116 @@ const PRODUCT_FLAGS: Record<string, FlagSeedDef[]> = {
percentage: 0,
},
],
jarvisjr: [
{
key: 'voice_sessions_enabled',
enabled: true,
description: 'Voice-first coaching sessions',
platforms: ['ios', 'web'],
percentage: 100,
},
{
key: 'agent_marketplace_enabled',
enabled: false,
description: 'Agent marketplace browse and purchase',
platforms: ['ios', 'web'],
percentage: 0,
},
{
key: 'agent_memory_enabled',
enabled: true,
description: 'Per-agent persistent memory',
platforms: ['ios', 'web'],
percentage: 100,
},
],
peakpulse: [
{
key: 'live_activity_enabled',
enabled: true,
description: 'Dynamic Island + Lock Screen live activity',
platforms: ['ios'],
percentage: 100,
},
{
key: 'ski_intelligence_enabled',
enabled: true,
description: 'Ski run detection and speed zones',
platforms: ['ios'],
percentage: 100,
},
{
key: 'cloud_sync_enabled',
enabled: false,
description: 'Cloud sync for sessions',
platforms: [],
percentage: 0,
},
],
flowmonk: [
{
key: 'agent_recommendations_enabled',
enabled: true,
description: 'AI agent scheduling recommendations',
platforms: ['web', 'mobile'],
percentage: 100,
},
{
key: 'schedule_sse_enabled',
enabled: true,
description: 'Real-time schedule updates via SSE',
platforms: ['web'],
percentage: 100,
},
],
notelett: [
{
key: 'mcp_tools_enabled',
enabled: true,
description: 'MCP tool integration for AI agents',
platforms: [],
percentage: 100,
},
{
key: 'note_artifacts_enabled',
enabled: true,
description: 'File/artifact attachments on notes',
platforms: ['web', 'mobile'],
percentage: 100,
},
],
actiontrail: [
{
key: 'trace_explorer_enabled',
enabled: true,
description: 'Trace grouping and explorer UI',
platforms: ['web'],
percentage: 100,
},
{
key: 'webhook_dispatch_enabled',
enabled: true,
description: 'Webhook event dispatch to subscribers',
platforms: [],
percentage: 100,
},
],
localmemgpt: [
{
key: 'multi_model_compare_enabled',
enabled: true,
description: 'Multi-model compare (SSE)',
platforms: ['web'],
percentage: 100,
},
{
key: 'rag_documents_enabled',
enabled: true,
description: 'RAG document upload and retrieval',
platforms: ['web'],
percentage: 100,
},
],
};
// ─── Seed function ───────────────────────────────────────────────────────────