Skip to content

Add admin feature flag capability - #788

Merged
kody-bot merged 8 commits into
mainfrom
cursor/admin-feature-flags-9ffe
Jul 19, 2026
Merged

kody-bot merged 8 commits into
mainfrom
cursor/admin-feature-flags-9ffe

Conversation

@kentcdodds

@kentcdodds kentcdodds commented Jul 19, 2026 •

Copy link
Copy Markdown
Member

Adds a fully featured feature-flag capability for admins, used to gate features as Kody develops. Flags exist only in a typed code registry (compile-checked gate sites); D1 stores runtime state (global toggle, percentage rollout, per-user overrides).

What's included

  • Core (packages/worker/src/feature-flags/): typed registry (demo-indicator starter flag), evaluation service with precedence override → global (+deterministic FNV-1a percentage rollout) → registry default, fail-closed on evaluation errors. Migration 0064-feature-flags.sql adds feature_flags and feature_flag_user_overrides (covered by account export/deletion targets).
  • Admin web UI: /admin/feature-flags page + /admin/feature-flags.json API (admin-role gated, audited): global on/off, rollout %, note, per-user overrides by username, stale-flag cleanup (rows whose key left the registry).
  • MCP: admin_feature_flag_list / admin_feature_flag_set / admin_feature_flag_override capabilities, plus an optional featureFlag field on capability definitions enforced centrally in access control (hidden from search + execution denied when off; flag map resolved only when a gated capability exists; flag-gated capabilities require an authenticated, resolvable caller and fail closed otherwise).
  • Client: evaluated flags ship in the session payload (session.featureFlags, cached per request); isFeatureFlagEnabled(session, key) gates UI. The demo-indicator flag renders a small "Demo" badge so the system stays exercised end-to-end.
  • Docs: docs/contributing/architecture/feature-flags.md, architecture index, primitives map entry.
  • Review hardening (from CodeRabbit/Bugbot): omitting note on set_global now preserves the existing operator note; anonymous or identity-unresolvable MCP callers fail closed on flag-gated capabilities.

Testing

  • Unit tests: evaluation precedence/bucketing, admin handler actions, MCP capabilities, access-control gating (green as part of npm run validate).
  • New Playwright spec e2e/admin-feature-flags.spec.ts: admin lifecycle toggling the demo indicator; non-admin 403 + per-user override precedence.
  • Full npm run validate green locally and in CI (format, lint, typecheck, 908+ unit tests, 19 E2E, MCP E2E, primitives check).
  • Manual GUI verification (recorded):

feature_flags_admin_demo.mp4

Admin feature flags page

System recap — adds a new primitive (high risk)

Mode: recap · Base: main @ 6242566b · Head: 73b4f132

Classification: adds — introduces the feature-flags primitive (new registry + service + D1 tables, added to primitives.yaml); extends rbac (new admin surface + capabilities), capability-registry (new featureFlag access field), d1-app-db (migration 0064), app-ui (session payload shape); composes capabilities-execute.

Primitives touched

Primitive Group Impact
feature-flags auth adds — new primitive: typed registry, evaluation service, D1 state
rbac auth extends — /admin/feature-flags route + admin_feature_flag_* capabilities
capability-registry assistant extends — optional featureFlag field enforced in access control
d1-app-db storage extends — feature_flags + feature_flag_user_overrides tables
app-ui surfaces extends — session.featureFlags in the session payload; admin page
capabilities-execute runtime composes — awaits async access assertion

System map

Admins manage flag state through the admin UI and MCP capabilities into D1; evaluation flows back out through the session payload and capability access control.

Legend: green = composes (wiring only) · amber = extended by this PR · red = new primitive · gray = context (unchanged, included only when an edge crosses it).

flowchart LR
	appUi["app-ui<br/>Browser app (Remix 3)"]:::extended
	rbac["rbac<br/>Role-based access control"]:::extended
	featureFlags["feature-flags<br/>Feature flags"]:::added
	d1AppDb["d1-app-db<br/>D1 app database"]:::extended
	capabilityRegistry["capability-registry<br/>Capability registry"]:::extended
	capabilitiesExecute["capabilities-execute<br/>Capability execution"]:::touched
	appUi -->|"/admin/feature-flags page + .json actions"| rbac
	rbac -->|"set global / overrides / delete stale"| featureFlags
	featureFlags -->|"feature_flags + feature_flag_user_overrides (0064)"| d1AppDb
	featureFlags -->|"session.featureFlags via loadSessionInfo"| appUi
	capabilityRegistry -->|"featureFlag field filtered in access-control"| featureFlags
	capabilitiesExecute -->|"await assertCallerCanAccessCapability"| capabilityRegistry
	classDef touched fill:#1a7f37,color:#fff
	classDef extended fill:#9a6700,color:#fff
	classDef added fill:#cf222e,color:#fff
	classDef untouched fill:#57606a,color:#fff
Loading

Before / after

before: no feature-flag mechanism
after:
  feature_flags(key PK, enabled, rollout_percent NULL, note, updated_by, updated_at)
  feature_flag_user_overrides(flag_key, user_id, enabled, updated_by, updated_at, PK(flag_key, user_id))
  SessionInfo += featureFlags: Record<FeatureFlagKey, boolean>
  CapabilityDefinition += featureFlag?: FeatureFlagKey

Invariants

  • Per-user isolation: overrides are keyed by user_id, cascade-deleted with the user, and included in account export/deletion coverage; evaluated flag maps are computed per caller.
  • Registry-owns-existence: DB rows for keys no longer in the code registry surface as stale and can only be deleted, never resurrected from the UI.
Open in Web Open in Cursor 

Summary by CodeRabbit

  • New Features
    • Added admin feature-flag management (global enable/rollout + notes, per-user overrides, and stale-flag deletion) via UI and JSON API, including MCP admin capabilities.
    • Added per-request feature-flag gating for capability visibility and execution, including “fail closed” behavior on evaluation failures.
    • Added a “Demo” badge in the logged-in navigation when the associated flag is enabled.
  • Documentation
    • Documented the feature-flag architecture, evaluation precedence, and feature-flag-gated capability behavior; updated capability-contribution guidance.
  • Tests
    • Added end-to-end and unit coverage for admin lifecycle, overrides, rollouts, and gated capability access control.

cursoragent and others added 6 commits July 19, 2026 17:53
Co-authored-by: Kent C. Dodds <me+github@kentcdodds.com>
Co-authored-by: Kent C. Dodds <me+github@kentcdodds.com>
…ting

Co-authored-by: Kent C. Dodds <me+github@kentcdodds.com>
Co-authored-by: Kent C. Dodds <me+github@kentcdodds.com>
…e, validation hardening

Co-authored-by: Kent C. Dodds <me+github@kentcdodds.com>
Co-authored-by: Kent C. Dodds <me+github@kentcdodds.com>
@coderabbitai

coderabbitai Bot commented Jul 19, 2026 •

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

This PR adds registry-backed feature flags with D1 persistence, deterministic evaluation, per-user overrides, admin UI/API/MCP management, client session exposure, and feature-flag-gated MCP capabilities.

Changes

Feature flag system

Layer / File(s) Summary
Registry, persistence, and evaluation
packages/worker/src/feature-flags/*, packages/worker/migrations/0064-feature-flags.sql, packages/worker/src/app/account-data-targets.ts
Defines typed flags, stores global and per-user state, evaluates rollouts and overrides, and handles stale flags.
Request evaluation and sessions
packages/worker/src/app/request-feature-flags-cache.ts, packages/worker/src/app/session-info.ts, packages/worker/client/session.ts
Caches request evaluations and exposes evaluated flags through server and client sessions.
Capability gating
packages/worker/src/mcp/capabilities/*, packages/worker/src/mcp/run-kody-registry.ts
Adds featureFlag capability metadata and enforces fail-closed access checks during registry filtering and execution.
Admin MCP and HTTP management
packages/worker/src/mcp/capabilities/admin/*, packages/worker/src/app/handlers/admin-feature-flags.ts, packages/worker/src/app/routes.ts, packages/worker/src/app/router.ts
Adds audited MCP and JSON operations for listing, updating, overriding, and removing feature flags.
Client UI and validation
packages/worker/client/routes/admin-feature-flags.tsx, packages/worker/client/app.tsx, e2e/admin-feature-flags.spec.ts
Adds the admin management page, demo indicator rendering, navigation, and end-to-end coverage.
Documentation and metadata
docs/contributing/*, packages/worker/tsconfig-client.json
Documents the feature-flag architecture and gated capabilities and registers architecture/type-system metadata.

Estimated code review effort: 4 (Complex) | ~75 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant SessionInfo
  participant FeatureFlagService
  participant D1
  participant CapabilityRegistry
  Client->>SessionInfo: request session data
  SessionInfo->>FeatureFlagService: evaluate flags for user
  FeatureFlagService->>D1: read global state and overrides
  D1-->>FeatureFlagService: flag rows
  FeatureFlagService-->>SessionInfo: evaluated flags
  SessionInfo-->>Client: session with featureFlags
  CapabilityRegistry->>FeatureFlagService: resolve caller flags
  FeatureFlagService-->>CapabilityRegistry: evaluated flag map
  CapabilityRegistry-->>Client: filtered capabilities
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 4.76% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title is concise and relevant; it captures the admin feature-flag work, though the PR also adds broader feature-flag infrastructure.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch cursor/admin-feature-flags-9ffe

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@kody-bot
kody-bot marked this pull request as ready for review July 19, 2026 18:28
@github-actions

github-actions Bot commented Jul 19, 2026 •

Copy link
Copy Markdown
Contributor

🔎 Preview deployed: https://kody-pr-788.kody-a99.workers.dev

Worker: kody-pr-788
D1: kody-pr-788-db
KV: kody-pr-788-oauth-kv

Mocks:

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/worker/src/app/handlers/admin-feature-flags.ts`:
- Around line 159-172: Update the admin feature-flag handlers to schedule each
asynchronous logAuditEvent call through the request execution context’s
waitUntil mechanism instead of fire-and-forget. Add corresponding audit records
for the 400 and 404 rejection paths, preserving the relevant action, actor,
request metadata, reason, and failure result for each rejected attempt.

In `@packages/worker/src/feature-flags/service.ts`:
- Around line 186-217: Update setFeatureFlagGlobalState so an omitted input.note
preserves the existing database note instead of normalizing to an empty string
and overwriting it. Keep explicit note values, including an explicit empty
value, updating the note, and adjust the upsert binding or conflict-update
expression to distinguish omission using the existing note-presence signal.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 5f96d262-dc2a-4abb-bfe2-bff8636a719a

📥 Commits

Reviewing files that changed from the base of the PR and between 6242566 and 594c8df.

📒 Files selected for processing (43)
  • docs/contributing/adding-capabilities.md
  • docs/contributing/architecture/feature-flags.md
  • docs/contributing/architecture/index.md
  • docs/contributing/architecture/primitives.yaml
  • e2e/admin-feature-flags.spec.ts
  • packages/worker/client/app.tsx
  • packages/worker/client/feature-flags.ts
  • packages/worker/client/routes/account-management-components.tsx
  • packages/worker/client/routes/admin-feature-flags.tsx
  • packages/worker/client/routes/index.tsx
  • packages/worker/client/session.ts
  • packages/worker/migrations/0064-feature-flags.sql
  • packages/worker/src/app/account-data-targets.ts
  • packages/worker/src/app/admin-feature-flags-data.ts
  • packages/worker/src/app/handlers/admin-feature-flags.node.test.ts
  • packages/worker/src/app/handlers/admin-feature-flags.ts
  • packages/worker/src/app/handlers/session-handler.node.test.ts
  • packages/worker/src/app/loader-data.ts
  • packages/worker/src/app/request-feature-flags-cache.ts
  • packages/worker/src/app/router.ts
  • packages/worker/src/app/routes.ts
  • packages/worker/src/app/session-info.ts
  • packages/worker/src/app/ssr-render.node.test.ts
  • packages/worker/src/app/ssr-stubs/app-root.ts
  • packages/worker/src/feature-flags/registry.ts
  • packages/worker/src/feature-flags/service.node.test.ts
  • packages/worker/src/feature-flags/service.ts
  • packages/worker/src/feature-flags/types.ts
  • packages/worker/src/mcp/capabilities/access-control.node.test.ts
  • packages/worker/src/mcp/capabilities/access-control.ts
  • packages/worker/src/mcp/capabilities/admin/admin-feature-flag-capabilities.node.test.ts
  • packages/worker/src/mcp/capabilities/admin/admin-feature-flag-list.ts
  • packages/worker/src/mcp/capabilities/admin/admin-feature-flag-override.ts
  • packages/worker/src/mcp/capabilities/admin/admin-feature-flag-set.ts
  • packages/worker/src/mcp/capabilities/admin/domain.ts
  • packages/worker/src/mcp/capabilities/admin/feature-flag-shared.ts
  • packages/worker/src/mcp/capabilities/build-capability-registry.ts
  • packages/worker/src/mcp/capabilities/define-capability.ts
  • packages/worker/src/mcp/capabilities/registry.node.test.ts
  • packages/worker/src/mcp/capabilities/registry.ts
  • packages/worker/src/mcp/capabilities/types.ts
  • packages/worker/src/mcp/run-kody-registry.ts
  • packages/worker/tsconfig-client.json

Comment on lines +159 to +172
const requestIp = getRequestIp(input.request) ?? undefined
void logAuditEvent({
category: 'admin',
action: 'feature_flag_set_global',
result: 'success',
email: input.actor.email,
ip: requestIp,
path: input.url.pathname,
reason: [
`key=${key}`,
`enabled=${enabled}`,
`rollout_percent=${rolloutPercent === null ? 'null' : String(rolloutPercent)}`,
].join(';'),
})

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# How is logAuditEvent invoked across handlers, and does it use waitUntil internally?
rg -nP 'logAuditEvent' packages/worker/src --type=ts -C1 -g '!*.test.ts'
echo '--- audit-log implementation ---'
fd -t f 'audit-log.ts' packages/worker/src --exec sed -n '1,120p' {}
echo '--- waitUntil usage ---'
rg -nP 'waitUntil' packages/worker/src --type=ts -C2

Repository: kentcdodds/kody

Length of output: 50371


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '--- audit-log outline ---'
ast-grep outline packages/worker/src/app/audit-log.ts --view expanded || true

echo '--- audit-log implementation (relevant slice) ---'
nl -ba packages/worker/src/app/audit-log.ts | sed -n '1,240p'

echo '--- admin-feature-flags handler slice ---'
nl -ba packages/worker/src/app/handlers/admin-feature-flags.ts | sed -n '130,230p'

echo '--- comparable handler patterns ---'
rg -n "void logAuditEvent\(|await logAuditEvent\(" packages/worker/src/app/handlers packages/worker/src/oauth-handlers.ts -g '!*.test.ts' -C 1

Repository: kentcdodds/kody

Length of output: 1080


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '--- audit-log implementation ---'
sed -n '1,220p' packages/worker/src/app/audit-log.ts

echo '--- admin-feature-flags slice ---'
sed -n '130,230p' packages/worker/src/app/handlers/admin-feature-flags.ts

echo '--- other handler audit calls ---'
rg -n "void logAuditEvent\(|await logAuditEvent\(" packages/worker/src/app/handlers packages/worker/src/oauth-handlers.ts -g '!*.test.ts' -C 1

Repository: kentcdodds/kody

Length of output: 29433


Schedule audit writes and cover failure paths — void logAuditEvent(...) is fire-and-forget, but logAuditEvent still does async work and doesn’t use waitUntil(), so these records can be lost once the response returns. Also record the 400/404 admin failures so rejected attempts are audited too.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/worker/src/app/handlers/admin-feature-flags.ts` around lines 159 -
172, Update the admin feature-flag handlers to schedule each asynchronous
logAuditEvent call through the request execution context’s waitUntil mechanism
instead of fire-and-forget. Add corresponding audit records for the 400 and 404
rejection paths, preserving the relevant action, actor, request metadata,
reason, and failure result for each rejected attempt.

Comment thread packages/worker/src/feature-flags/service.ts
Co-authored-by: Kent C. Dodds <me+github@kentcdodds.com>

@cursor cursor Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using default effort and found 2 potential issues.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit c023e5c. Configure here.

Comment thread packages/worker/src/mcp/capabilities/access-control.ts
Comment thread packages/worker/src/mcp/capabilities/access-control.ts
…bilities

Co-authored-by: Kent C. Dodds <me+github@kentcdodds.com>
@kody-bot
kody-bot merged commit 84952ee into main Jul 19, 2026
5 checks passed
@kody-bot
kody-bot deleted the cursor/admin-feature-flags-9ffe branch July 19, 2026 19:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants