Skip to main content

feature-switch

Feature switch system guide for gating new user-facing features behind feature flags

설치로 이동

소스 정보

저장소
okou-ai/okou
최근 소스 활동
2026년 9월 23일 03:23
감지된 SKILL.md 언어
영어
스타
1,157
포크
72

설치 방법

기본적으로 소스를 먼저 확인하는 Prompt가 선택됩니다. 직접 명령으로 전환하거나 로컬 사본을 다운로드할 수도 있습니다.

소스 파일 검토

설치 여부를 결정하기 전에 SKILL.md와 SkillsMP에 표시된 보조 파일을 읽어 보세요.

SKILL.md 표시 중

SKILL.md
소스 지침 · 읽기 전용 미리보기
name
feature-switch
description
Feature switch system guide for gating new user-facing features behind feature flags
# Feature Switch Skill This skill documents the feature switch system and provides step-by-step instructions for adding new feature switches. **All new user-facing features must be gated behind a feature switch** for gradual rollout. ## When to Use A feature switch is required when adding: - New UI pages, sections, or sidebar navigation items - New API endpoints exposed to users or agents - New integrations (connectors, Slack, Telegram, etc.) - New agent token capabilities A feature switch is **not** required for: - Internal refactors or code cleanup - Test infrastructure changes - Build/CI configuration - Bug fixes to existing features - Documentation updates ## How to Add a Feature Switch ### Step 1: Add a key to the enum File: `turbo/packages/core/src/feature-switch-key.ts` Add a new entry to `FeatureSwitchKey`: ```typescript export enum FeatureSwitchKey { // ... existing keys MyFeature = "myFeature", } ``` ### Step 2: Register the switch File: `turbo/packages/core/src/feature-switch.ts` Add an entry to the `FEATURE_SWITCHES` record: ```typescript [FeatureSwitchKey.MyFeature]: { maintainer: "you@okou.ai", enabled: false, enabledOrgIdHashes: STAFF_ORG_ID_HASHES, // optional: staff-only access }, ``` **Configuration options:** | Field | Type | Description | | -------------------- | ---------- | -------------------------------------------------- | | `maintainer` | `string` | Email of the responsible person | | `enabled` | `boolean` | `true` = on for everyone, `false` = off by default | | `enabledUserHashes` | `string[]` | FNV-1a hashes of allowed user IDs | | `enabledEmailHashes` | `string[]` | FNV-1a hashes of allowed emails | | `enabledOrgIdHashes` | `string[]` | FNV-1a hashes of allowed org IDs | **Common default states:** - `enabled: false` — fully hidden unless enabled by a per-user API override - `enabled: false` + `enabledOrgIdHashes: STAFF_ORG_ID_HASHES` — staff-only (most common for new features) - `enabled: true` — on for everyone (use when feature is ready for GA) ### Step 3: Gate the feature in application code Choose the pattern that matches where your feature is consumed. #### Server-side (API routes) ```typescript import { isFeatureEnabled, FeatureSwitchKey } from "@okouai/core"; // In route handler: if (!isFeatureEnabled(FeatureSwitchKey.MyFeature, { userId, orgId })) { return createErrorResponse("FORBIDDEN", "Feature not available"); } ``` #### Client-side (Platform UI) ```typescript import { FeatureSwitchKey } from "@okouai/core"; import { featureSwitch$ } from "../../signals/external/feature-switch.ts"; // In component: const features = useLastResolved(featureSwitch$); const showMyFeature = features?.[FeatureSwitchKey.MyFeature] ?? false; // Conditional rendering: {showMyFeature && <MyFeatureComponent />} ``` #### Sidebar navigation gating Sidebar entries are not gated declaratively. The nav item records in `turbo/apps/platform/src/views/okou-page/sidebar.tsx` (`MANAGE_NAV`, `FOOTER_NAV`) carry no feature-switch field. Read `featureSwitch$` in the component that renders the entry and omit the item when the switch is off — `turbo/apps/platform/src/views/okou-page/sidebar-account.tsx` gates the Lab entry this way: ```typescript const features = useLastResolved(featureSwitch$); const labEnabled = features?.[FeatureSwitchKey.Lab] ?? false; // In the render: {labEnabled && <DropdownMenuItem>{/* ... */}</DropdownMenuItem>} ``` #### Connector gating Connector definitions live in `okou-ai/okou-connectors`, but the platform owns the rollout association. Add an entry to `FEATURE_SWITCH_BY_AUTH_METHOD` in `turbo/apps/api/src/signals/services/connector-auth-method-feature-switches.ts`, keyed by `` `${connectorSlug}\0${authMethodId}` ``: ```typescript const FEATURE_SWITCH_BY_AUTH_METHOD = Object.freeze< Record<string, FeatureSwitchKey | undefined> >({ // ... existing entries "my-connector\0oauth": FeatureSwitchKey.MyConnector, }); ``` Deploy the association before publishing a method that should be gated, and remove it once the switch graduates. #### Agent token capability gating In `turbo/apps/api/src/signals/auth/tokens.ts`, add to `CONDITIONAL_CAPABILITIES`: ```typescript const CONDITIONAL_CAPABILITIES = [ // ... existing entries ["my-feature:write", FeatureSwitchKey.MyFeature], ] as const satisfies readonly (readonly [Capability, FeatureSwitchKey])[]; ``` ## Key Files | File | Role | | ------------------------------------------------------------------------------- | ----------------------------------------------------------- | | `turbo/packages/core/src/feature-switch-key.ts` | Enum of all feature switch keys | | `turbo/packages/core/src/feature-switch.ts` | Registry and evaluation logic | | `turbo/apps/platform/src/signals/external/feature-switch.ts` | Client-side reactive state with override layers | | `turbo/apps/platform/src/views/okou-page/sidebar.tsx` | Sidebar nav item records (`MANAGE_NAV`, `FOOTER_NAV`) | | `turbo/apps/api/src/signals/services/connector-auth-method-feature-switches.ts` | Connector auth-method → feature switch rollout associations | | `turbo/apps/api/src/signals/services/feature-switches.service.ts` | Override loading/writing and the org-scoped key list | | `turbo/apps/api/src/signals/auth/tokens.ts` | Token capability gating | ## Override Layers Evaluation has two layers (lowest to highest priority): 1. **Core registry** — static config in source code, evaluated against `userId` / `email` / `orgId` hashes. 2. **DB overrides** — most switches are per-user rows in `user_feature_switches` keyed by `(orgId, userId)`. Some switches are org-scoped and stored under the org sentinel user id (`ORG_SENTINEL_USER_ID`, `"__org__"`); `ORG_SCOPED_FEATURE_SWITCH_KEYS` currently holds `LarkIntegration`. Written via `POST /api/feature-switches` and cleared via `DELETE /api/feature-switches`. The Lab page lets users update or reset these overrides and groups the registry into Released, Beta, Alpha, and Internal categories. Switch keys beginning with `_` are Internal regardless of their rollout audience. The same two-layer resolution applies on the server: route handlers that call `isFeatureEnabled(..., { userId, orgId, overrides })` pass a context built by `loadUserFeatureSwitchContext(db, orgId, userId)`. There is **no** client-only or device-local override layer.
GitHub에서 보기