Skip to content

Associate feature flags with success metrics (exposures + cohort readout) - #1080

Merged
kody-bot merged 6 commits into
mainfrom
cursor/feature-flag-success-metrics-a971
Jul 30, 2026
Merged

kody-bot merged 6 commits into
mainfrom
cursor/feature-flag-success-metrics-a971

Conversation

@kentcdodds

@kentcdodds kentcdodds commented Jul 30, 2026 •

Copy link
Copy Markdown
Member

What

Every feature flag can now declare the metric it exists to move, and the admin surfaces show whether it is actually moving it.

  • Declaration — FeatureFlagDefinition gains an optional successMetric (eventType from the closed usage-metering union, measure, goal, hypothesis), reviewed in the same PR that creates the flag. execute-pre-exec-typecheck declares one (execute error rate should decrease); the permanent demo-indicator deliberately does not.
  • Exposures — for measured flags, the two evaluation chokepoints (app session flag cache, MCP caller flag resolver) record (stable user id, flag key, on/off, assignment source, timestamp). Production/preview writes go to a new FLAG_EXPOSURES Analytics Engine dataset; local dev/tests use a new D1 feature_flag_exposure_rollups table (migration 0117, 90-day retention, covered by account deletion/export targets). The writer mirrors recordUsage's never-throws contract.
  • Readout — success-metric-readout.ts joins exposures with usage events for the declared metric over the current UTC month, splits users into on/off cohorts, and excludes hand-picked override users and mixed-exposure users (reported separately). Attached to /admin/feature-flags and admin_feature_flag_list.
  • Strong recommendation notice — registry flags without a successMetric render a notice in the admin UI and carry a successMetricNotice field in the MCP list output.

Why

Flags gate rollouts, but nothing connected "flag is on" to "the thing we hoped would improve actually improved". Associating each flag with a code-reviewed metric hypothesis plus exposure-based cohort data makes "push forward or kill it" an evidence-based operator decision. Assignment-source tracking is what keeps the comparison honest: current flag state cannot reconstruct who was inside a percentage rollout last week, and override users are selection-biased.

Demo (local dev)

Two seeded users were split by a 20% rollout (jane on, kody off), with seeded execute usage for the month. The admin page shows the missing-metric notice on demo-indicator and the cohort readout on execute-pre-exec-typecheck (on cohort 6.7% error rate vs off cohort 36.0%).

admin_feature_flags_success_metric_readout_demo.mp4

Success metric panel with on/off cohort readout

Notes for review

  • UsageEventType moved to a dependency-free packages/worker/src/usage/event-types.ts (re-exported from record-usage.ts) so the client-safe flag registry can reference it.
  • getFeatureFlagsForUser is now a thin wrapper over getFeatureFlagEvaluationsForUser, which also returns the assignment source (override / global / rollout / default). Evaluation precedence is unchanged.
  • Local Wrangler dev emulates Analytics Engine bindings and injects mock REST credentials, but the SQL API cannot read locally emulated datasets — exposure writes and the readout use the D1 path when WRANGLER_IS_LOCAL_DEV is set.
  • The new D1 table gets a scheduled retention policy (90 days, matching Analytics Engine retention) and an accountUserDataTargets entry.
  • Readout failures degrade to { status: 'unavailable' } so the admin page renders even when the analytics backend is down.
  • The migration was renumbered from 0113 to 0117 after rebasing on main; the branch is rebased on top of the stable-user-id admin transport change (Use stable user IDs across identity boundaries #1072).
System recap — extends feature flags and usage metering (medium risk)

Mode: recap · Base: main @ 3b84c504 · Head: 6580b1bc

Classification: extends — no new primitive; the feature-flags primitive gains success-metric declaration, exposure recording, and a metric readout, joined against the existing usage-metering event stream.

Primitives touched

Primitive Group Impact
feature-flags auth extends — successMetric registry field, evaluation sources, exposure writer, cohort readout
usage-metering storage extends — UsageEventType extracted to a dependency-free module; AE SQL helper exported for reuse
d1-app-db storage extends — new feature_flag_exposure_rollups table (migration 0117)
app-ui surfaces extends — success-metric panel + missing-metric notice on /admin/feature-flags
capability-registry assistant composes — MCP caller flag resolver records exposures; admin_feature_flag_list output extended
scheduled-cron surfaces extends — retention prune for exposure rollups (90 days)
account-export account composes — exposure rollups added to deletion/export targets

System map

Flag evaluation now emits exposures alongside its answer, and the admin surfaces join those exposures with usage events into an on/off cohort readout.

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
	featureFlags["feature-flags<br/>Feature flags"]:::extended
	usageMetering["usage-metering<br/>Usage metering"]:::extended
	d1AppDb["d1-app-db<br/>D1 app database"]:::extended
	appUi["app-ui<br/>Browser app (Remix 3)"]:::extended
	capabilityRegistry["capability-registry<br/>Capability registry"]:::touched
	scheduledCron["scheduled-cron<br/>Scheduled handler"]:::extended
	accountExport["account-export<br/>Account data export"]:::touched
	appUi -->|"session flag cache records exposures"| featureFlags
	capabilityRegistry -->|"resolveCallerFeatureFlags records exposures"| featureFlags
	featureFlags -->|"FLAG_EXPOSURES dataset / feature_flag_exposure_rollups"| d1AppDb
	featureFlags -->|"successMetric.eventType compile-checked"| usageMetering
	appUi -->|"readout joins exposures with usage events"| usageMetering
	scheduledCron -->|"prune exposure rollups > 90 days"| d1AppDb
	accountExport -->|"deletion/export target"| d1AppDb
	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: FeatureFlagDefinition = { key, description, defaultEnabled }
After:  FeatureFlagDefinition = { key, description, defaultEnabled,
          successMetric?: { eventType: UsageEventType, measure, goal, hypothesis } }

New D1 table (local/test sink; production uses the FLAG_EXPOSURES AE dataset):
feature_flag_exposure_rollups (flag_key, user_id, day, enabled, source,
  exposure_count, updated_at) PK (flag_key, user_id, day, enabled, source)

Invariants

Per-user isolation: exposures are keyed by the stable user id, the D1 fallback table is in accountUserDataTargets (deletion + export), and the readout only ever aggregates across users for the admin-only surfaces (admin role required on both the route and the capability).

Open in Web Open in Cursor 

Summary by CodeRabbit

  • New Features
    • Added configurable “success metrics” for feature flags (event type, goals, hypotheses), including measurement windows and on/off cohort readouts in the admin UI.
    • Added success-metric exposure recording, with automatic fallback to local rollups when analytics bindings aren’t available.
  • Bug Fixes
    • Improved retention and pruning for local success-metric exposure rollups, including correct day-boundary behavior.
    • Updated admin display to consistently use stable user identifiers for attribution and overrides.
  • Tests
    • Expanded end-to-end and unit coverage for success-metric UI rendering, exposure recording, metric readouts, and retention edge cases.

@coderabbitai

coderabbitai Bot commented Jul 30, 2026 •

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Feature flags now support optional success metrics, preserve assignment sources during evaluation, record measured exposures through Analytics Engine or D1, aggregate month-to-date cohorts with usage data, and display readouts in web and MCP admin surfaces. Exposure rollups receive migration, account-data, and 90-day retention coverage.

Changes

Feature flag success metrics

Layer / File(s) Summary
Success-metric contracts and bindings
packages/worker/src/feature-flags/registry.ts, packages/worker/src/feature-flags/types.ts, packages/worker/src/usage/*, packages/worker/src/mcp/capabilities/admin/*, packages/worker/src/env-schema.ts, packages/worker/wrangler.jsonc, docs/contributing/architecture/*
Feature definitions, usage event types, admin schemas, Analytics Engine bindings, and architecture documentation now describe success metrics and readouts.
Evaluation and exposure recording
packages/worker/src/feature-flags/service.ts, packages/worker/src/feature-flags/exposure.ts, packages/worker/src/app/*, packages/worker/src/mcp/capabilities/access-control.ts, related tests
Evaluations include assignment sources, and authenticated request/session/MCP paths record measured exposures through Analytics Engine or local D1 rollups without throwing.
Readout aggregation and admin surfaces
packages/worker/src/feature-flags/success-metric-readout.ts, packages/worker/client/routes/admin-feature-flags.tsx, packages/worker/src/app/admin-feature-flags-data.ts, packages/worker/src/mcp/capabilities/admin/admin-feature-flag-list.ts, related tests
Exposure and usage data are joined into on/off cohorts, attached to admin flags, and rendered with missing-metric and unavailable-readout states.
Exposure storage and retention
packages/worker/migrations/0117-feature-flag-exposure-rollups.sql, packages/worker/src/app/retention.ts, packages/worker/src/account/*, tools/migration-ledger.json, related tests
The D1 rollup table is registered for migration, account data handling, and 90-day day-based pruning.

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

Sequence Diagram(s)

sequenceDiagram
  participant UserRequest
  participant FeatureFlagService
  participant ExposureRecorder
  participant ReadoutLoader
  participant AdminSurface
  UserRequest->>FeatureFlagService: evaluate flags with assignment sources
  FeatureFlagService->>ExposureRecorder: record measured exposures
  ExposureRecorder-->>ReadoutLoader: persisted exposure data
  ReadoutLoader->>ReadoutLoader: join exposures with usage events
  ReadoutLoader->>AdminSurface: attach on/off metric readout
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 16.28% 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
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately captures the main change: linking feature flags to success metrics, exposures, and cohort readouts.
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.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch cursor/feature-flag-success-metrics-a971

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 30, 2026 20:32

@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: 3

🧹 Nitpick comments (1)
packages/worker/src/feature-flags/success-metric-readout.ts (1)

325-389: 🚀 Performance & Scalability | 🔵 Trivial

No caching for per-flag external readout queries.

attachFeatureFlagMetricReadouts fires 2 external Analytics Engine REST calls per measured flag on every /admin/feature-flags load and every admin_feature_flag_list MCP call, uncached. Fine today with a single measured flag, but as more flags adopt successMetric (which the UI explicitly encourages), admin page load time will grow linearly with external API round-trips. Worth a short-TTL cache in front of the readout in a follow-up once adoption grows.

🤖 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/feature-flags/success-metric-readout.ts` around lines 325
- 389, Introduce a short-TTL cache for per-flag readouts used by
attachFeatureFlagMetricReadouts, keyed by flag key and success metric so
repeated admin and MCP requests reuse recent external results. Apply caching
around loadFeatureFlagSuccessMetricReadout while preserving the existing
D1/Analytics Engine selection and unavailable fallback behavior.
🤖 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/migrations/0117-feature-flag-exposure-rollups.sql`:
- Around line 20-21: Add a dedicated index on the feature_flag_exposure_rollups
day column alongside the existing user_id index so retention pruning can
efficiently filter and order exposures by day.

In `@packages/worker/src/app/retention.ts`:
- Around line 823-844: Update pruneFeatureFlagExposuresForRetention to require a
userId and scope both the retention SELECT and selectAndDeleteByIds deletion to
that user. Add user_id to the selection predicate and bindings, and configure
the deletion predicate/key so rows from other users cannot be removed.

In `@packages/worker/src/feature-flags/registry.ts`:
- Around line 82-95: Update the measured feature-flag readout flow rooted at
measuredFeatureFlagDefinitions and measuredFeatureFlagKeys so every Analytics
Engine and D1 cohort query receives an authorized userId scope before returning
data. Thread userId through the relevant readout functions and add the
caller-user predicate to each read query, preserving existing aggregation
behavior within that user’s scope.

---

Nitpick comments:
In `@packages/worker/src/feature-flags/success-metric-readout.ts`:
- Around line 325-389: Introduce a short-TTL cache for per-flag readouts used by
attachFeatureFlagMetricReadouts, keyed by flag key and success metric so
repeated admin and MCP requests reuse recent external results. Apply caching
around loadFeatureFlagSuccessMetricReadout while preserving the existing
D1/Analytics Engine selection and unavailable fallback behavior.
🪄 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: 7139ece9-794d-4305-9e21-38d12d1f986c

📥 Commits

Reviewing files that changed from the base of the PR and between 03623fe and 4a3e678.

📒 Files selected for processing (35)
  • docs/contributing/architecture/data-storage.md
  • docs/contributing/architecture/feature-flags.md
  • docs/contributing/architecture/usage-metering.md
  • e2e/admin-feature-flags.spec.ts
  • packages/worker/client/routes/admin-feature-flags.tsx
  • packages/worker/migrations/0117-feature-flag-exposure-rollups.sql
  • packages/worker/src/account/data-targets.ts
  • packages/worker/src/app/account-retention-dispositions.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/session-handler.node.test.ts
  • packages/worker/src/app/request-feature-flags-cache.ts
  • packages/worker/src/app/retention.node.test.ts
  • packages/worker/src/app/retention.ts
  • packages/worker/src/app/session-info.ts
  • packages/worker/src/app/ssr-render.node.test.ts
  • packages/worker/src/env-schema.ts
  • packages/worker/src/feature-flags/exposure.node.test.ts
  • packages/worker/src/feature-flags/exposure.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/success-metric-readout.node.test.ts
  • packages/worker/src/feature-flags/success-metric-readout.ts
  • packages/worker/src/feature-flags/types.ts
  • packages/worker/src/mcp/capabilities/access-control.ts
  • packages/worker/src/mcp/capabilities/admin/admin-feature-flag-list.ts
  • packages/worker/src/mcp/capabilities/admin/feature-flag-shared.ts
  • packages/worker/src/usage/aggregate-rollups.ts
  • packages/worker/src/usage/event-types.ts
  • packages/worker/src/usage/record-usage.ts
  • packages/worker/tsconfig-client.json
  • packages/worker/worker-configuration.d.ts
  • packages/worker/wrangler.jsonc
  • tools/migration-ledger.json

Comment thread packages/worker/migrations/0117-feature-flag-exposure-rollups.sql
Comment on lines +823 to +844
export async function pruneFeatureFlagExposuresForRetention(input: {
db: D1Database
now?: Date
batchSize?: number
}) {
const cutoffDay = cutoffIso(
input.now ?? new Date(),
featureFlagExposureRetentionDays,
).slice(0, 'YYYY-MM-DD'.length)
return selectAndDeleteByIds({
db: input.db,
column: 'rowid',
bindings: [cutoffDay, input.batchSize ?? retentionDefaultBatchSize],
sql: `SELECT rowid
FROM feature_flag_exposure_rollups
WHERE day < ?
ORDER BY day ASC, rowid ASC
LIMIT ?`,
table: 'feature_flag_exposure_rollups',
idColumn: 'rowid',
})
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟠 Major | 🏗️ Heavy lift

Scope retention reads and deletes by userId.

This user-owned table is selected and deleted solely by day; selectAndDeleteByIds consequently removes rows across every user. Make the retention operation explicitly user-scoped and include user_id in both selection and deletion predicates. As per coding guidelines, “every read and write path must be scoped by userId.”

🤖 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/retention.ts` around lines 823 - 844, Update
pruneFeatureFlagExposuresForRetention to require a userId and scope both the
retention SELECT and selectAndDeleteByIds deletion to that user. Add user_id to
the selection predicate and bindings, and configure the deletion predicate/key
so rows from other users cannot be removed.

Source: Coding guidelines

Comment on lines +82 to +95
/**
* Registry flags that declare a success metric. Only these flags get
* exposure recording and admin metric readouts.
*/
export const measuredFeatureFlagDefinitions: ReadonlyArray<
FeatureFlagDefinition & { successMetric: FeatureFlagSuccessMetric }
> = featureFlagDefinitions.flatMap((definition) =>
'successMetric' in definition ? [definition] : [],
)

export const measuredFeatureFlagKeys: ReadonlySet<FeatureFlagKey> = new Set(
measuredFeatureFlagDefinitions.map(
(definition) => definition.key as FeatureFlagKey,
),

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Scope metric readouts by userId.

These measured flags feed the supplied Analytics Engine and D1 readouts, which aggregate every user_id without a caller-user predicate. Thread an authorized userId scope through the readout queries before returning cohort data.

As per coding guidelines, “every read and write path must be scoped by userId.”

🤖 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/feature-flags/registry.ts` around lines 82 - 95, Update
the measured feature-flag readout flow rooted at measuredFeatureFlagDefinitions
and measuredFeatureFlagKeys so every Analytics Engine and D1 cohort query
receives an authorized userId scope before returning data. Thread userId through
the relevant readout functions and add the caller-user predicate to each read
query, preserving existing aggregation behavior within that user’s scope.

Source: Coding guidelines

@cursor
cursor Bot force-pushed the cursor/feature-flag-success-metrics-a971 branch from 4a3e678 to 6580b1b Compare July 30, 2026 20:45
@github-actions

github-actions Bot commented Jul 30, 2026 •

Copy link
Copy Markdown
Contributor

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

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

Mocks:

@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 1 potential issue.

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 573d3b6. Configure here.

Comment thread packages/worker/src/feature-flags/success-metric-readout.ts

@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: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/worker/src/feature-flags/service.ts (1)

297-300: 🔒 Security & Privacy | 🟠 Major | 🏗️ Heavy lift

Scope admin identity reads to the requesting user.

listFeatureFlagsForAdmin has no requester userId, yet now returns every override owner’s and updater’s stableUserId. This exposes cross-user identifiers through both web and MCP admin responses. Pass and enforce the requester scope, or keep cross-user identity data in a separately authorized server-only operation and omit it from this response.

As per coding guidelines, “every read and write path must be scoped by userId” and “Cross-user data sharing is a bug.”

Also applies to: 309-323, 344-350, 373-379

🤖 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/feature-flags/service.ts` around lines 297 - 300, Update
listFeatureFlagsForAdmin and its related queries/response mappings to require
the requesting userId and scope every feature-flag, override-owner, and updater
read to that user. Do not return cross-user stableUserId values through web or
MCP admin responses; omit them unless a separately authorized server-only
operation is used. Propagate the requester scope through all affected call sites
and preserve authorization for each read path.

Source: Coding guidelines

🧹 Nitpick comments (1)
packages/worker/src/feature-flags/success-metric-readout.ts (1)

378-394: 🚀 Performance & Scalability | 🔵 Trivial | 💤 Low value

Readouts are recomputed per flag on every admin request.

Each measured flag costs two Analytics Engine REST round-trips, fanned out unbounded via Promise.all, and this runs on every GET and on every mutation response (admin-feature-flags-data.ts). Fine at one measured flag; consider a short TTL cache (KV or in-worker) or lazily loading the readout on demand before more flags declare metrics.

🤖 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/feature-flags/success-metric-readout.ts` around lines 378
- 394, Update attachFeatureFlagMetricReadouts to avoid recomputing readouts on
every admin request: add a short-TTL cache for
loadFeatureFlagSuccessMetricReadout results, keyed by flag identity and the
relevant metric inputs, and reuse valid cached values across both GET and
mutation-response paths. Preserve stale or metric-less flags and ensure cache
expiry triggers a fresh readout while limiting concurrent Analytics Engine
requests.
🤖 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/feature-flags/success-metric-readout.ts`:
- Around line 280-288: Update the D1 query in the exposure readout flow around
the `exposureResult` query to bound `day` inclusively by both
`window.monthStartDay` and the window end day, matching the Analytics Engine
path. Bind the additional upper-bound parameter using the existing window end
value, while preserving the current grouping and row mapping.

---

Outside diff comments:
In `@packages/worker/src/feature-flags/service.ts`:
- Around line 297-300: Update listFeatureFlagsForAdmin and its related
queries/response mappings to require the requesting userId and scope every
feature-flag, override-owner, and updater read to that user. Do not return
cross-user stableUserId values through web or MCP admin responses; omit them
unless a separately authorized server-only operation is used. Propagate the
requester scope through all affected call sites and preserve authorization for
each read path.

---

Nitpick comments:
In `@packages/worker/src/feature-flags/success-metric-readout.ts`:
- Around line 378-394: Update attachFeatureFlagMetricReadouts to avoid
recomputing readouts on every admin request: add a short-TTL cache for
loadFeatureFlagSuccessMetricReadout results, keyed by flag identity and the
relevant metric inputs, and reuse valid cached values across both GET and
mutation-response paths. Preserve stale or metric-less flags and ensure cache
expiry triggers a fresh readout while limiting concurrent Analytics Engine
requests.
🪄 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: fa6890a5-5ff7-4c63-b2d7-7cf4f266b193

📥 Commits

Reviewing files that changed from the base of the PR and between 4a3e678 and 8e91173.

📒 Files selected for processing (35)
  • docs/contributing/architecture/data-storage.md
  • docs/contributing/architecture/feature-flags.md
  • docs/contributing/architecture/usage-metering.md
  • e2e/admin-feature-flags.spec.ts
  • packages/worker/client/routes/admin-feature-flags.tsx
  • packages/worker/migrations/0117-feature-flag-exposure-rollups.sql
  • packages/worker/src/account/data-targets.ts
  • packages/worker/src/app/account-retention-dispositions.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/session-handler.node.test.ts
  • packages/worker/src/app/request-feature-flags-cache.ts
  • packages/worker/src/app/retention.node.test.ts
  • packages/worker/src/app/retention.ts
  • packages/worker/src/app/session-info.ts
  • packages/worker/src/app/ssr-render.node.test.ts
  • packages/worker/src/env-schema.ts
  • packages/worker/src/feature-flags/exposure.node.test.ts
  • packages/worker/src/feature-flags/exposure.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/success-metric-readout.node.test.ts
  • packages/worker/src/feature-flags/success-metric-readout.ts
  • packages/worker/src/feature-flags/types.ts
  • packages/worker/src/mcp/capabilities/access-control.ts
  • packages/worker/src/mcp/capabilities/admin/admin-feature-flag-list.ts
  • packages/worker/src/mcp/capabilities/admin/feature-flag-shared.ts
  • packages/worker/src/usage/aggregate-rollups.ts
  • packages/worker/src/usage/event-types.ts
  • packages/worker/src/usage/record-usage.ts
  • packages/worker/tsconfig-client.json
  • packages/worker/worker-configuration.d.ts
  • packages/worker/wrangler.jsonc
  • tools/migration-ledger.json
🚧 Files skipped from review as they are similar to previous changes (6)
  • packages/worker/src/app/account-retention-dispositions.ts
  • docs/contributing/architecture/data-storage.md
  • packages/worker/worker-configuration.d.ts
  • packages/worker/src/usage/record-usage.ts
  • docs/contributing/architecture/feature-flags.md
  • docs/contributing/architecture/usage-metering.md

Comment thread packages/worker/src/feature-flags/success-metric-readout.ts

@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: 1

🤖 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/feature-flags/success-metric-readout.ts`:
- Around line 283-290: The exposure query in the success-metric readout must
enforce an authorized user or account boundary instead of aggregating all users
for the flag. Update the query and its bindings around the exposure read to
filter by the applicable user/account scope, and apply the same scope to the
associated usage read before combining results; preserve explicitly authorized
aggregate behavior only if an established aggregate scope is available.
🪄 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: 66612818-2031-48c9-81d3-d6b7d25d951c

📥 Commits

Reviewing files that changed from the base of the PR and between 8e91173 and 4731032.

📒 Files selected for processing (2)
  • packages/worker/src/feature-flags/success-metric-readout.node.test.ts
  • packages/worker/src/feature-flags/success-metric-readout.ts

Comment on lines +283 to +290
const exposureResult = await input.db
.prepare(
`SELECT user_id, enabled, source
FROM feature_flag_exposure_rollups
WHERE flag_key = ? AND day >= ? AND day <= ?
GROUP BY user_id, enabled, source`,
)
.bind(input.flagKey, window.monthStartDay, windowEndDay)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Scope the exposure read by an authorized user or account boundary.

This query reads every user’s exposure for flagKey without a userId or other enforced aggregate scope, allowing user-owned records to be combined into the admin readout. Apply the same boundary to the associated usage read, or introduce an explicitly authorized aggregate scope before reading cross-user data.

As per coding guidelines, every read and write path must be scoped by userId; every Durable Object ID backing user-owned state must be namespaced by userId; and every search or vector path must filter by userId.

🤖 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/feature-flags/success-metric-readout.ts` around lines 283
- 290, The exposure query in the success-metric readout must enforce an
authorized user or account boundary instead of aggregating all users for the
flag. Update the query and its bindings around the exposure read to filter by
the applicable user/account scope, and apply the same scope to the associated
usage read before combining results; preserve explicitly authorized aggregate
behavior only if an established aggregate scope is available.

Source: Coding guidelines

@kody-bot
kody-bot merged commit 21a4353 into main Jul 30, 2026
10 checks passed
@kody-bot
kody-bot deleted the cursor/feature-flag-success-metrics-a971 branch July 30, 2026 21:33
cursor Bot pushed a commit that referenced this pull request Jul 30, 2026
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