Repository navigation
Add admin feature flag capability - #788
Conversation
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>
📝 WalkthroughWalkthroughThis 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. ChangesFeature flag system
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
Possibly related PRs
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
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. Comment |
|
🔎 Preview deployed: https://kody-pr-788.kody-a99.workers.dev Worker: Mocks:
|
There was a problem hiding this comment.
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
📒 Files selected for processing (43)
docs/contributing/adding-capabilities.mddocs/contributing/architecture/feature-flags.mddocs/contributing/architecture/index.mddocs/contributing/architecture/primitives.yamle2e/admin-feature-flags.spec.tspackages/worker/client/app.tsxpackages/worker/client/feature-flags.tspackages/worker/client/routes/account-management-components.tsxpackages/worker/client/routes/admin-feature-flags.tsxpackages/worker/client/routes/index.tsxpackages/worker/client/session.tspackages/worker/migrations/0064-feature-flags.sqlpackages/worker/src/app/account-data-targets.tspackages/worker/src/app/admin-feature-flags-data.tspackages/worker/src/app/handlers/admin-feature-flags.node.test.tspackages/worker/src/app/handlers/admin-feature-flags.tspackages/worker/src/app/handlers/session-handler.node.test.tspackages/worker/src/app/loader-data.tspackages/worker/src/app/request-feature-flags-cache.tspackages/worker/src/app/router.tspackages/worker/src/app/routes.tspackages/worker/src/app/session-info.tspackages/worker/src/app/ssr-render.node.test.tspackages/worker/src/app/ssr-stubs/app-root.tspackages/worker/src/feature-flags/registry.tspackages/worker/src/feature-flags/service.node.test.tspackages/worker/src/feature-flags/service.tspackages/worker/src/feature-flags/types.tspackages/worker/src/mcp/capabilities/access-control.node.test.tspackages/worker/src/mcp/capabilities/access-control.tspackages/worker/src/mcp/capabilities/admin/admin-feature-flag-capabilities.node.test.tspackages/worker/src/mcp/capabilities/admin/admin-feature-flag-list.tspackages/worker/src/mcp/capabilities/admin/admin-feature-flag-override.tspackages/worker/src/mcp/capabilities/admin/admin-feature-flag-set.tspackages/worker/src/mcp/capabilities/admin/domain.tspackages/worker/src/mcp/capabilities/admin/feature-flag-shared.tspackages/worker/src/mcp/capabilities/build-capability-registry.tspackages/worker/src/mcp/capabilities/define-capability.tspackages/worker/src/mcp/capabilities/registry.node.test.tspackages/worker/src/mcp/capabilities/registry.tspackages/worker/src/mcp/capabilities/types.tspackages/worker/src/mcp/run-kody-registry.tspackages/worker/tsconfig-client.json
| 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(';'), | ||
| }) |
There was a problem hiding this comment.
🩺 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 -C2Repository: 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 1Repository: 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 1Repository: 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.
Co-authored-by: Kent C. Dodds <me+github@kentcdodds.com>
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using default effort and found 2 potential issues.
❌ 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.
…bilities Co-authored-by: Kent C. Dodds <me+github@kentcdodds.com>

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
packages/worker/src/feature-flags/): typed registry (demo-indicatorstarter flag), evaluation service with precedence override → global (+deterministic FNV-1a percentage rollout) → registry default, fail-closed on evaluation errors. Migration0064-feature-flags.sqladdsfeature_flagsandfeature_flag_user_overrides(covered by account export/deletion targets)./admin/feature-flagspage +/admin/feature-flags.jsonAPI (admin-role gated, audited): global on/off, rollout %, note, per-user overrides by username, stale-flag cleanup (rows whose key left the registry).admin_feature_flag_list/admin_feature_flag_set/admin_feature_flag_overridecapabilities, plus an optionalfeatureFlagfield 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).session.featureFlags, cached per request);isFeatureFlagEnabled(session, key)gates UI. Thedemo-indicatorflag renders a small "Demo" badge so the system stays exercised end-to-end.docs/contributing/architecture/feature-flags.md, architecture index, primitives map entry.noteonset_globalnow preserves the existing operator note; anonymous or identity-unresolvable MCP callers fail closed on flag-gated capabilities.Testing
npm run validate).e2e/admin-feature-flags.spec.ts: admin lifecycle toggling the demo indicator; non-admin 403 + per-user override precedence.npm run validategreen locally and in CI (format, lint, typecheck, 908+ unit tests, 19 E2E, MCP E2E, primitives check).System recap — adds a new primitive (high risk)
Mode: recap · Base:
main@6242566b· Head:73b4f132Classification: adds — introduces the
feature-flagsprimitive (new registry + service + D1 tables, added to primitives.yaml); extends rbac (new admin surface + capabilities), capability-registry (newfeatureFlagaccess field), d1-app-db (migration 0064), app-ui (session payload shape); composes capabilities-execute.Primitives touched
feature-flagsrbac/admin/feature-flagsroute +admin_feature_flag_*capabilitiescapability-registryfeatureFlagfield enforced in access controld1-app-dbfeature_flags+feature_flag_user_overridestablesapp-uisession.featureFlagsin the session payload; admin pagecapabilities-executeSystem 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).
Before / after
Invariants
user_id, cascade-deleted with the user, and included in account export/deletion coverage; evaluated flag maps are computed per caller.Summary by CodeRabbit