Repository navigation
feat(entitlements): move daily quotas into UserMeter - #1115
Conversation
Co-authored-by: Kent C. Dodds <me+github@kentcdodds.com>
Co-authored-by: Kent C. Dodds <me+github@kentcdodds.com>
Co-authored-by: Kent C. Dodds <me+github@kentcdodds.com>
Co-authored-by: Kent C. Dodds <me+github@kentcdodds.com>
Co-authored-by: Kent C. Dodds <me+github@kentcdodds.com>
📝 WalkthroughWalkthroughThe change adds a per-user ChangesUserMeter entitlement migration
Estimated code review effort: 5 (Critical) | ~120 minutes Possibly related PRs
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches 💡 1📝 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 |
Co-authored-by: Kent C. Dodds <me+github@kentcdodds.com>
Co-authored-by: Kent C. Dodds <me+github@kentcdodds.com>
Co-authored-by: Kent C. Dodds <me+github@kentcdodds.com>
Co-authored-by: Kent C. Dodds <me+github@kentcdodds.com>
# Conflicts: # packages/worker/src/email/inbound.ts # packages/worker/src/email/outbound.ts # packages/worker/worker-configuration.d.ts Co-authored-by: Kent C. Dodds <me+github@kentcdodds.com>
Co-authored-by: Kent C. Dodds <me+github@kentcdodds.com>
|
@coderabbitai review |
|
🔎 Preview deployed: https://kody-pr-1115.kody-a99.workers.dev Worker: Mocks:
|
There was a problem hiding this comment.
Actionable comments posted: 7
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
docs/contributing/architecture/entitlements.md (1)
1-1: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick winBroken anchor
#user-meter-expand-phaseused in five places. The heading## UserMeter (expand phase)slugifies tousermeter-expand-phaseunder the GitHub/markdownlint heading algorithm (lowercase, strip punctuation including parentheses, then replace spaces with hyphens). Every link below usesuser-meter-expand-phaseinstead, which markdownlint-cli2's MD051 rule flags as invalid on the entitlements.md occurrence.
docs/contributing/architecture/entitlements.md#L110-113: change the self-referencing link[UserMeter (expand phase)](#user-meter-expand-phase)to#usermeter-expand-phase.docs/contributing/architecture/data-storage.md#L194-198: change[Entitlements](./entitlements.md#user-meter-expand-phase)to./entitlements.md#usermeter-expand-phase.docs/contributing/architecture/data-storage.md#L511-519: changeSee [Entitlements](./entitlements.md#user-meter-expand-phase).to./entitlements.md#usermeter-expand-phase.docs/contributing/architecture/data-storage.md#L562-564: changeSee [Entitlements](./entitlements.md#user-meter-expand-phase).to./entitlements.md#usermeter-expand-phase.docs/contributing/architecture/data-storage.md#L630-631: change[Entitlements](./entitlements.md#user-meter-expand-phase))to./entitlements.md#usermeter-expand-phase).🤖 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 `@docs/contributing/architecture/entitlements.md` at line 1, Update every reference to the UserMeter expand-phase heading to use the valid anchor `#usermeter-expand-phase`, including the self-link in entitlements.md and all four links in data-storage.md. Preserve the existing link text and correct the extra closing parenthesis in the final reference.Source: Linters/SAST tools
🧹 Nitpick comments (8)
packages/worker/src/email/inbound-delivery.ts (1)
455-495: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winReuse the shared bootstrap helper instead of repeating the protocol.
readUserInboundReceiveCount(Lines 471-494) andchargeUserInboundDeliveryOnce(Lines 550-575) repeat the same four-step bootstrap protocol: read D1 baseline, callinitialize, retry the meter call, then throw when the outcome is stillneeds_bootstrap.packages/worker/src/entitlements/service.tsalready containsensureUserMeterCounterInitializedfor exactly this step. Extract one local helper, or export and reuse the service helper, so the bootstrap contract has a single definition.🤖 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/email/inbound-delivery.ts` around lines 455 - 495, Reuse the existing ensureUserMeterCounterInitialized helper from the entitlements service in readUserInboundReceiveCount and chargeUserInboundDeliveryOnce, replacing their duplicated baseline-read, initialize, retry, and needs_bootstrap error handling. Preserve each function’s existing meter resource, day, timestamp, and return behavior while centralizing the bootstrap contract in that shared helper.packages/worker/src/mcp/tools/execute.ts (1)
265-269: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winReuse the
waitUntillocal already bound at Line 161.Line 269 calls
agent.waitUntil?.bind(agent)again. The enclosing scope already holds the identical bound callback in thewaitUntillocal, whichrunExecuteToolcloses over and passes torunModuleWithRegistryat Line 358. Use that local for one binding site per call.♻️ Proposed change
await consumeDailyEntitlement({ db: env.APP_DB, env, userId: callerContext.user.userId, email: callerContext.user.email, resource: 'execute_calls_per_day', - waitUntil: agent.waitUntil?.bind(agent), + waitUntil, })🤖 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/mcp/tools/execute.ts` around lines 265 - 269, The execute tool configuration redundantly rebinds agent.waitUntil instead of reusing the existing bound callback. In the resource object near the execute call, replace the agent.waitUntil?.bind(agent) expression with the enclosing waitUntil local already created and used by runExecuteTool/runModuleWithRegistry, preserving a single binding per call.packages/worker/src/mcp/fetch-gateway.ts (1)
132-139: 🚀 Performance & Scalability | 🔵 TrivialPlan for per-user meter serialization on this hot path.
Every sandbox fetch now performs a blocking RPC to the single per-user
UserMeterDurable Object before the outbound request runs. All concurrent fetches for one user serialize on that object, and its region fixes the added latency. Add a metric forconsumeDailyEntitlementduration on this path, and an alert on its tail latency, so a hot user or a distant DO region is visible before it degrades sandbox fetches.🤖 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/mcp/fetch-gateway.ts` around lines 132 - 139, Instrument the consumeDailyEntitlement call in the sandbox fetch path with a duration metric, recording latency for every invocation including failures. Use the project’s existing metrics conventions and add a tail-latency alert for this metric, with thresholds appropriate to detect serialized per-user Durable Object or regional latency before fetches degrade.packages/worker/src/email/inbound-entitlements.workers.test.ts (1)
74-105: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winThree test files reimplement the same UserMeter
daily_countersseeding. The shared root cause is thatpackages/worker/src/test-support/user-meter.tsexposes no counter-seed helper, so each suite writes the Durable Object internal SQLite schema itself. Every copy repeats the samerunInDurableObjectwarm-up read, the sameINSERT ... ON CONFLICTupsert, and the same hardcodedrevision = 1. A change to the DO table layout then breaks three files.
packages/worker/src/email/inbound-entitlements.workers.test.ts#L74-L105: replace the inline block with a call to a newseedUserMeterDailyCounter({ userId, resource: 'email_receives_per_day', day, count })helper inpackages/worker/src/test-support/user-meter.ts, and keep the extra D1 mirror write here.packages/worker/src/email/outbound.workers.test.ts#L83-L104: call the same helper withresource: 'email_sends_per_day'and delete the local block.packages/worker/src/mcp/fetch-gateway.workers.test.ts#L16-L38: call the same helper withresource: 'outbound_fetches_per_day'and delete the local block.In the shared helper, preserve or increment the existing
revisioninstead of forcing1, so seeding never moves a counter revision backwards.🤖 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/email/inbound-entitlements.workers.test.ts` around lines 74 - 105, The three test files duplicate UserMeter daily-counter seeding and hardcode revision 1. In packages/worker/src/test-support/user-meter.ts, add seedUserMeterDailyCounter that performs the existing Durable Object initialization and upsert while preserving or incrementing the current revision. Replace the inline blocks in packages/worker/src/email/inbound-entitlements.workers.test.ts:74-105, packages/worker/src/email/outbound.workers.test.ts:83-104, and packages/worker/src/mcp/fetch-gateway.workers.test.ts:16-38 with calls using their respective resources; retain the inbound test’s additional D1 mirror write.packages/worker/src/app/account-usage-data.node.test.ts (1)
85-207: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueConsider splitting the three scenarios into separate tests.
This test covers three independent scenarios: baseline with no counters, D1 bootstrap, and a warm meter that overrides D1. A failure in the first block hides the precedence coverage in the third block, which is the most important assertion for this migration.
Two smaller points on the baseline block.
expect(baseline?.entitlementConsumption.length).toBeGreaterThan(5)is loose;accountUsageEntitlementResourcesdeclares 13 entries, so an exact count would catch accidental list edits. The baseline block also asserts no daily resource value, so the cold-and-empty path returns zero only implicitly.♻️ Suggested tightening for the baseline assertions
expect(currentFor(baseline, 'saved_packages')).toBe(2) - expect(baseline?.entitlementConsumption.length).toBeGreaterThan(5) + expect(baseline?.entitlementConsumption).toHaveLength(13) + expect(currentFor(baseline, 'email_sends_per_day')).toBe(0) + expect(currentFor(baseline, 'email_receives_per_day')).toBe(0)🤖 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/account-usage-data.node.test.ts` around lines 85 - 207, Split loadAccountUsageData coverage into three independent tests for the empty baseline, D1 bootstrap, and warm UserMeter precedence scenarios so failures do not mask later assertions. In the baseline test, assert entitlementConsumption has exactly the 13 resources declared by accountUsageEntitlementResources and explicitly verify the daily resource value is zero. Preserve the existing assertions for plan, date, saved packages, bootstrap counts, and authoritative warm-meter counts.packages/worker/src/test-support/user-meter.ts (1)
112-125: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win
exportCountersignorespageSizeandstartAfter.The real
UserMeter.exportCountersaccepts{ pageSize?, startAfter? }and returns a bounded, cursor-paginated page (seepackages/worker/src/entitlements/user-meter-do.tsexportCounters). This stub'sexportCounters()takes no parameters and always returns every row in one un-paginated response.TypeScript's structural typing lets this zero-arg function satisfy the
UserMeterEnvinterface, so no compile error occurs. Any future test that reuses this shared stub to exercise export pagination or truncation will silently get the full list instead of a page, masking bugs in pagination logic.Accept
pageSizeandstartAfter, and slice/paginate the in-memory rows to mirror the real contract.♻️ Proposed fix to honor pagination parameters
- async exportCounters() { - const counters = [...rows.entries()].map(([entryKey, row]) => { + async exportCounters(input: { pageSize?: number; startAfter?: string | null } = {}) { + const pageSize = input.pageSize ?? 100 + const sorted = [...rows.entries()] + .map(([entryKey, row]) => { + const [resource, day] = entryKey.split('\0') + return { resource: resource as DailyEntitlementResource, day: day!, row } + }) + .sort((a, b) => a.day.localeCompare(b.day) || a.resource.localeCompare(b.resource)) + const startIndex = input.startAfter + ? sorted.findIndex((entry) => `${entry.day}:${entry.resource}` === input.startAfter) + 1 + : 0 + const page = sorted.slice(startIndex, startIndex + pageSize) + const truncated = startIndex + pageSize < sorted.length + const counters = page.map(({ resource, day, row }) => { return { resource: resource as DailyEntitlementResource, day: day!, count: row.count, revision: row.revision, updatedAt: new Date().toISOString(), mirrorUpdatedAt: userMeterMirrorUpdatedAtToken(row.revision), } }) - return { counters, nextStartAfter: null, truncated: false } + const last = page.at(-1) + return { + counters, + nextStartAfter: truncated && last ? `${last.day}:${last.resource}` : null, + truncated, + } },🤖 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/test-support/user-meter.ts` around lines 112 - 125, Update exportCounters in the user-meter test stub to accept the real contract’s optional pageSize and startAfter parameters. Order the in-memory counter rows consistently, resume after the provided cursor, limit the result to pageSize, and return the correct nextStartAfter and truncated values while preserving the existing counter mapping.packages/worker/src/mcp/tools/execute.node.test.ts (1)
65-70: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winDocstring describes the pre-UserMeter enforcement mechanism.
This comment says the execute entitlement "issues one conditional upsert (allowed when meta.changes > 0)". That describes the old D1-based conditional-upsert enforcement. Per the entitlements docs in this PR, execute-call enforcement now goes through
UserMeter.consumeDailyEntitlement, which performs an atomic check-and-increment inside the Durable Object with revision-checked updates, not a D1 conditional upsert.Update the comment to describe the current UserMeter-based enforcement so it does not mislead future readers of this test file.
🤖 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/mcp/tools/execute.node.test.ts` around lines 65 - 70, Update the docstring above the minimal environment stub to describe the current UserMeter-based execute entitlement flow: the handler calls UserMeter.consumeDailyEntitlement, which performs an atomic check-and-increment using revision-checked Durable Object updates. Remove the outdated reference to conditional D1 upserts while preserving the note that plan lookup does not access D1 for caller contexts without an account email.docs/contributing/architecture/primitives.yaml (1)
643-643: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winClarify the invariant reference against the documented D1 mirror write.
The
no-per-event-shared-writesinvariant states per-event writes go "to Analytics Engine or per-user DO SQLite, never the shared D1 writer."entitlements.md(lines 134-141 in this PR) documents a best-effort, non-awaited mirror write to the shared D1 tableentitlement_daily_countersafter every consume, refund, and inbound claim.The authoritative write does land in the per-user UserMeter DO, so the invariant's intent holds, but the compatibility mirror is still a per-event write to the shared D1 writer. Consider a one-line caveat in the invariant summary noting that expand-phase best-effort D1 mirrors are a documented, time-bounded exception, so future readers don't assume this doc reference implies zero D1 writes per event.
🤖 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 `@docs/contributing/architecture/primitives.yaml` at line 643, Update the no-per-event-shared-writes invariant summary in primitives.yaml to add a concise caveat that documented, time-bounded, best-effort expand-phase mirrors to the shared D1 writer are an exception. Preserve the invariant’s statement that authoritative per-event writes use Analytics Engine or per-user DO SQLite, and align the wording with the documented mirror behavior in entitlements.md.
🤖 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 `@docs/contributing/architecture/entitlements.md`:
- Around line 110-113: Replace every reference to the invalid
`#user-meter-expand-phase` anchor with `#usermeter-expand-phase`, including the link
in the entitlements documentation and all occurrences in data-storage.md. Keep
the existing UserMeter (expand phase) heading unchanged.
In `@packages/worker/src/app/handlers/account-email.node.test.ts`:
- Around line 301-324: Freeze the UTC system time in both date-scoped tests
before deriving or seeding date-based data, and restore real timers after each
test. Apply this to packages/worker/src/app/handlers/account-email.node.test.ts
lines 301-324 and 454-503; update the tests around their existing test bodies
and utcDayKey usage, with no direct changes needed elsewhere.
In `@packages/worker/src/email/inbound-entitlements.workers.test.ts`:
- Around line 207-242: Update withFailingReceiveStartedInsert so its patched
prepare returns the original statement immediately when isDeliveryInsert is
false, preserving normal run, first, all, raw, and batch behavior for unrelated
queries. Keep the existing bind wrapper and failure injection only for the
targeted receive_started delivery insert.
In `@packages/worker/src/email/inbound.workers.test.ts`:
- Around line 866-871: Update readUserTotalReceiveCount to follow the
exportCounters pagination cursor: repeatedly call userMeterRpc with the returned
nextStartAfter while truncated is true, accumulate all pages, then filter
email_receives_per_day rows and sum their counts.
In `@packages/worker/src/entitlements/user-meter-do.ts`:
- Around line 555-559: Update the purge method to execute the storage deletion
and initializeSchema calls inside this.ctx.blockConcurrencyWhile, ensuring no
other RPC can observe the schema between those operations; return { ok: true }
after the concurrency-blocked callback completes.
In `@packages/worker/src/mcp/capabilities/email/email-usage-get.workers.test.ts`:
- Around line 137-144: Compute const day = utcDayKey() once at the beginning of
the test body, pass that day value to every seedDailyCounter call, and reuse it
for all meter reads and response assertions. Remove the later day declaration
and replace any inline utcDayKey() calls so every seeded row and lookup uses the
same key.
In `@packages/worker/src/mcp/fetch-gateway.workers.test.ts`:
- Around line 60-91: Update the test’s deferred-task handling around
executeGatewayFetch to reuse createWaitUntilDrain from the user-meter test
support, replacing the one-time Promise.all wait with the helper’s drain flow.
Ensure drain() runs after all allowed and denied gateway calls so every promise
added to waitUntilTasks settles before the test completes.
---
Outside diff comments:
In `@docs/contributing/architecture/entitlements.md`:
- Line 1: Update every reference to the UserMeter expand-phase heading to use
the valid anchor `#usermeter-expand-phase`, including the self-link in
entitlements.md and all four links in data-storage.md. Preserve the existing
link text and correct the extra closing parenthesis in the final reference.
---
Nitpick comments:
In `@docs/contributing/architecture/primitives.yaml`:
- Line 643: Update the no-per-event-shared-writes invariant summary in
primitives.yaml to add a concise caveat that documented, time-bounded,
best-effort expand-phase mirrors to the shared D1 writer are an exception.
Preserve the invariant’s statement that authoritative per-event writes use
Analytics Engine or per-user DO SQLite, and align the wording with the
documented mirror behavior in entitlements.md.
In `@packages/worker/src/app/account-usage-data.node.test.ts`:
- Around line 85-207: Split loadAccountUsageData coverage into three independent
tests for the empty baseline, D1 bootstrap, and warm UserMeter precedence
scenarios so failures do not mask later assertions. In the baseline test, assert
entitlementConsumption has exactly the 13 resources declared by
accountUsageEntitlementResources and explicitly verify the daily resource value
is zero. Preserve the existing assertions for plan, date, saved packages,
bootstrap counts, and authoritative warm-meter counts.
In `@packages/worker/src/email/inbound-delivery.ts`:
- Around line 455-495: Reuse the existing ensureUserMeterCounterInitialized
helper from the entitlements service in readUserInboundReceiveCount and
chargeUserInboundDeliveryOnce, replacing their duplicated baseline-read,
initialize, retry, and needs_bootstrap error handling. Preserve each function’s
existing meter resource, day, timestamp, and return behavior while centralizing
the bootstrap contract in that shared helper.
In `@packages/worker/src/email/inbound-entitlements.workers.test.ts`:
- Around line 74-105: The three test files duplicate UserMeter daily-counter
seeding and hardcode revision 1. In
packages/worker/src/test-support/user-meter.ts, add seedUserMeterDailyCounter
that performs the existing Durable Object initialization and upsert while
preserving or incrementing the current revision. Replace the inline blocks in
packages/worker/src/email/inbound-entitlements.workers.test.ts:74-105,
packages/worker/src/email/outbound.workers.test.ts:83-104, and
packages/worker/src/mcp/fetch-gateway.workers.test.ts:16-38 with calls using
their respective resources; retain the inbound test’s additional D1 mirror
write.
In `@packages/worker/src/mcp/fetch-gateway.ts`:
- Around line 132-139: Instrument the consumeDailyEntitlement call in the
sandbox fetch path with a duration metric, recording latency for every
invocation including failures. Use the project’s existing metrics conventions
and add a tail-latency alert for this metric, with thresholds appropriate to
detect serialized per-user Durable Object or regional latency before fetches
degrade.
In `@packages/worker/src/mcp/tools/execute.node.test.ts`:
- Around line 65-70: Update the docstring above the minimal environment stub to
describe the current UserMeter-based execute entitlement flow: the handler calls
UserMeter.consumeDailyEntitlement, which performs an atomic check-and-increment
using revision-checked Durable Object updates. Remove the outdated reference to
conditional D1 upserts while preserving the note that plan lookup does not
access D1 for caller contexts without an account email.
In `@packages/worker/src/mcp/tools/execute.ts`:
- Around line 265-269: The execute tool configuration redundantly rebinds
agent.waitUntil instead of reusing the existing bound callback. In the resource
object near the execute call, replace the agent.waitUntil?.bind(agent)
expression with the enclosing waitUntil local already created and used by
runExecuteTool/runModuleWithRegistry, preserving a single binding per call.
In `@packages/worker/src/test-support/user-meter.ts`:
- Around line 112-125: Update exportCounters in the user-meter test stub to
accept the real contract’s optional pageSize and startAfter parameters. Order
the in-memory counter rows consistently, resume after the provided cursor, limit
the result to pageSize, and return the correct nextStartAfter and truncated
values while preserving the existing counter mapping.
🪄 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: 9a683222-bbce-437c-9296-214ce4a04601
📒 Files selected for processing (42)
docs/contributing/architecture/data-storage.mddocs/contributing/architecture/entitlements.mddocs/contributing/architecture/primitives.yamlpackages/worker/src/account/export.node.test.tspackages/worker/src/account/export.tspackages/worker/src/account/user-owned-surfaces.node.test.tspackages/worker/src/account/user-owned-surfaces.tspackages/worker/src/admin/user-usage-data.node.test.tspackages/worker/src/admin/user-usage-data.tspackages/worker/src/app/account-deletion.node.test.tspackages/worker/src/app/account-deletion.tspackages/worker/src/app/account-email-data.tspackages/worker/src/app/account-usage-data.node.test.tspackages/worker/src/app/account-usage-data.tspackages/worker/src/app/handlers/account-email.node.test.tspackages/worker/src/email/inbound-delivery.tspackages/worker/src/email/inbound-entitlements.workers.test.tspackages/worker/src/email/inbound.tspackages/worker/src/email/inbound.workers.test.tspackages/worker/src/email/outbound.tspackages/worker/src/email/outbound.workers.test.tspackages/worker/src/entitlements/entitlements.node.test.tspackages/worker/src/entitlements/service.tspackages/worker/src/entitlements/user-meter-client.tspackages/worker/src/entitlements/user-meter-do.tspackages/worker/src/entitlements/user-meter.workers.test.tspackages/worker/src/index.tspackages/worker/src/mcp/capabilities/admin/admin-capabilities.node.test.tspackages/worker/src/mcp/capabilities/email/email-usage-get.tspackages/worker/src/mcp/capabilities/email/email-usage-get.workers.test.tspackages/worker/src/mcp/capabilities/openapi-provider/operation-request.node.test.tspackages/worker/src/mcp/fetch-gateway.node.test.tspackages/worker/src/mcp/fetch-gateway.tspackages/worker/src/mcp/fetch-gateway.workers.test.tspackages/worker/src/mcp/observability.node.test.tspackages/worker/src/mcp/tools/execute.node.test.tspackages/worker/src/mcp/tools/execute.tspackages/worker/src/test-support/user-meter.tspackages/worker/src/user-scoped-durable-object-name.node.test.tspackages/worker/src/user-scoped-durable-object-name.tspackages/worker/worker-configuration.d.tspackages/worker/wrangler.jsonc
Co-authored-by: Kent C. Dodds <me+github@kentcdodds.com>
# Conflicts: # docs/contributing/architecture/data-storage.md # packages/worker/src/account/export.node.test.ts # packages/worker/src/account/export.ts Co-authored-by: Kent C. Dodds <me+github@kentcdodds.com>
There was a problem hiding this comment.
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/test-support/run-log-usage.ts`:
- Around line 11-22: Update the RUN_LOG test stub so idFromName-derived Durable
Object IDs map to independent active workflow counts, and make RUN_LOG.get use
the requested ID when returning countActiveWorkflowProjections. Replace the
global setActiveWorkflowCount behavior with a per-ID test setter that accepts
the Durable Object ID and updates only that entry, preserving count sanitization
for each user.
🪄 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: a62118ec-4116-4a72-bce0-a180c1b8fcd8
📒 Files selected for processing (11)
docs/contributing/architecture/data-storage.mddocs/contributing/architecture/entitlements.mdpackages/worker/src/account/export.node.test.tspackages/worker/src/account/export.tspackages/worker/src/account/user-owned-surfaces.node.test.tspackages/worker/src/account/user-owned-surfaces.tspackages/worker/src/admin/user-usage-data.node.test.tspackages/worker/src/app/account-usage-data.node.test.tspackages/worker/src/entitlements/entitlements.node.test.tspackages/worker/src/entitlements/service.tspackages/worker/src/test-support/run-log-usage.ts
🚧 Files skipped from review as they are similar to previous changes (9)
- packages/worker/src/account/user-owned-surfaces.node.test.ts
- packages/worker/src/account/export.node.test.ts
- docs/contributing/architecture/entitlements.md
- packages/worker/src/account/export.ts
- packages/worker/src/app/account-usage-data.node.test.ts
- packages/worker/src/account/user-owned-surfaces.ts
- packages/worker/src/entitlements/entitlements.node.test.ts
- docs/contributing/architecture/data-storage.md
- packages/worker/src/admin/user-usage-data.node.test.ts
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 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit a8cba2b. Configure here.
Co-authored-by: Kent C. Dodds <me+github@kentcdodds.com>

Summary
UserMeterDurable Object as the authoritative daily quota coordinatorconcurrent_workflowsreservations and projectionsValidation
CI=1 npm run validate— passed on Node 26 after mergingmainatf4115ea6Deployment notes
This is the expand deployment.
entitlement_daily_countersremains in D1 and is mirrored for reporting compatibility. Mirror/table retirement is deferred until production parity verification. RunLog remains authoritative forconcurrent_workflows; UserMeter does not absorb workflow reservations.System recap — adds User meter (high risk)
Mode: recap · Base:
main@f4115ea6· Head:edfd973aClassification: adds — introduces a per-user SQLite Durable Object and moves daily entitlement authority from shared D1 to UserMeter while composing with RunLog workflow reservations.
Primitives touched
user-meterentitlementsrun-logemailmcp-serveraccount-exportapp-uiSystem map
Daily quota paths coordinate through UserMeter; concurrent workflows reserve through RunLog; D1 receives compatibility mirrors only.
Legend: green = composes (wiring only) · amber = extended by this PR · red = new primitive · gray = context (unchanged, included only when an edge crosses it).
Invariants
userIdConductor report
concurrent_workflowsreservations/projections.f4115ea6; no mailbox-do or cron-restructure changes were made.entitlement_daily_countersretirement remain gated on production parity verification.email/work.Summary by CodeRabbit