SeptemCore LogoSeptemCore
SDK Reference

@platform/sdk-flags

sdk-flags wraps the Platform Feature Flag Service (GoFeatureFlag, self-hosted). Exports: isEnabled() (in-memory, nanosecond eval), getVariant() (A/B test variant), snapshot() (immutable session copy for critical flows), listFlags(). 15-second background poll. Safe default: false when cache is cold. Tenant isolation via automatic key namespacing.

@platform/sdk-flags provides feature flags, A/B test variants, and canary release controls for module authors. Flag evaluation is an in-memory lookup — no network call happens at eval time. The SDK polls the GoFeatureFlag service every 15 seconds in the background.


Installation

pnpm add @platform/sdk-flags

isEnabled()

Check whether a feature flag is enabled for the current user and tenant:

import { kernel } from '@platform/sdk-core';

// Simple boolean gate
if (kernel.flags().isEnabled('new-checkout-flow')) {
  return <NewCheckout />;
}

// Usage in React hook form
const showBetaEditor = kernel.flags().isEnabled('beta-editor');

No network call. isEnabled() evaluates against the in-memory cache — response time is in nanoseconds. The background poller refreshes the cache every 15 seconds without blocking your code.

Evaluation Context

The SDK automatically injects evaluation context from the current session — module authors never pass user/tenant data manually:

Context FieldSourceUsed for
userIdJWTPer-user targeting and percentage rollout
tenantIdJWTTenant-level flag isolation
rolesJWTRole-based flag targeting
environmentkernel.init() configEnvironment-specific flags (staging, production)
percentageComputedCanary rollout — consistent hashing ensures user always gets same bucket

Tenant Isolation

The SDK automatically prefixes every flag name with {tenantId}:. Module authors use short names — namespacing is transparent:

kernel.flags().isEnabled('new-checkout-flow')
// SDK resolves: isEnabled('01j9p-tenant:new-checkout-flow')
// Tenant A and Tenant B can have different states for the same flag name

getVariant()

For A/B tests and multivariate experiments, get the variant assigned to the current user:

const variant = kernel.flags().getVariant('checkout-button-color');
// Returns: 'control' | 'blue' | 'green' | 'orange'  (configured in Admin UI)
// Returns: 'control' if flag is disabled or user not in experiment

switch (variant) {
  case 'blue':   return <Button color="#1a73e8">Buy now</Button>;
  case 'green':  return <Button color="#34a853">Buy now</Button>;
  case 'orange': return <Button color="#fa7b17">Buy now</Button>;
  default:       return <Button>Buy now</Button>;
}

Variants are configured in the Admin UI (Settings → Feature Flags). Each flag supports up to 10 variants. Max payload per variant: 4 KB JSON.


snapshot()

Capture an immutable snapshot of all current flag states for a session. Use this for critical user flows (checkout, form multi-step) where a flag change mid-session would break the UX:

class CheckoutSession {
  private flags = kernel.flags().snapshot();

  // During this session, flag values are frozen even if the 15s poll
  // delivers an update.
  isExpressCheckoutEnabled() {
    return this.flags.isEnabled('express-checkout');
  }

  getPaymentVariant() {
    return this.flags.getVariant('payment-layout');
  }
}

snapshot() returns a frozen copy. Subsequent 15-second polls do not affect values inside a snapshot. Create a new snapshot to get fresh values.

Without snapshot(): getVariant() returns the current cached value — it may change between calls if the 15-second poll fires mid-session. For most UI components this is fine. For transactional flows (cart, payments) always use snapshot().


listFlags()

List all flags registered for the current tenant:

const flags = await kernel.flags().listFlags();
// [
//   { key: 'new-checkout-flow', enabled: true,  rollout: 50, variants: [],    stale: false },
//   { key: 'beta-editor',       enabled: false, rollout: 0,  variants: [],    stale: false },
//   { key: 'old-header',        enabled: true,  rollout: 100, variants: [],   stale: true  },
// ]

Caching and Fallback

StateBehavior
Normal operationIn-memory cache, updated every 15 seconds
GoFeatureFlag service downSDK returns last cached values — works autonomously
Cold start (empty cache) + service downAll flags return false (safe default)
Cold start + Valkey availablePod fetches stale cache from Valkey → serves traffic. Pod = NOT READY until at least a stale cache is loaded
Pod with empty cacheKubernetes readiness probe = NOT READY — no traffic is routed to it

Readiness guarantee: A pod with a completely empty flag cache reports NOT READY to Kubernetes. This prevents the flag-less pod from serving requests where a new feature would always appear disabled. Existing warm-cache pods continue serving traffic uninterrupted.


Optimistic Locking (Admin — Conflict Resolution)

Two admins editing the same flag simultaneously is handled by optimistic locking on the version field:

PATCH https://api.septemcore.com/v1/flags/new-checkout-flow
Authorization: Bearer <access_token>
Content-Type: application/json

{ "enabled": true, "rollout": 75, "version": 4 }

If another admin has already updated the flag (version is now 5):

{
  "type":    "https://api.septemcore.com/problems/flag-version-conflict",
  "status":  409,
  "title":   "Flag version conflict",
  "detail":  "Flag was modified by [email protected] at 2026-04-22T01:30:00Z. Current value: rollout=100. Your value: rollout=75."
}

Every flag mutation is logged: flags.updated → Audit Service with { before, after, version }.


Stale Flag Cleanup

ParameterValue
Stale detection thresholdFlag unchanged for 90 days AND evaluates identically for 100% of requests
Admin notificationAdmin UI badge: "X flags may be stale. Archive?"
Archived flag behaviorReturns false (safe default). Referencing archived flag in code = no crash
Archived flag retention180 days in PostgreSQL, then physical delete
Billing limit enforcement402 Payment Required when tenant exceeds plan flag limit. FLAGS_MAX_PER_TENANT=0 = unlimited (safety net env). Stale cleanup keeps active count under limit.

REST API Reference

Base Path

For layout brevity, the /api/v1 base path prefix is omitted from the endpoint table below.

MethodEndpointDescription
GET/flagsList all flags (paginated)
GET/flags/:keyGet flag state and config
POST/flagsCreate flag
PATCH/flags/:keyUpdate flag (requires version)
DELETE/flags/:keyArchive flag (soft delete)

On this page