Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
54 commits
Select commit Hold shift + click to select a range
3c21148
perf(entitlements): cache enforcement plan lookups
cursoragent Jul 31, 2026
7d5d777
style(docs): format entitlement cache notes
cursoragent Jul 31, 2026
0cd468d
Merge remote-tracking branch 'origin/main' into cursor/meter-do-38c8
cursoragent Jul 31, 2026
59a8238
feat(entitlements): add per-user quota meter
cursoragent Aug 1, 2026
8ebf663
feat(account): inventory UserMeter lifecycle
cursoragent Aug 1, 2026
4a60dd9
test(entitlements): provide UserMeter fixtures
cursoragent Aug 1, 2026
73a8e37
test(entitlements): clean meter test imports
cursoragent Aug 1, 2026
8b001bf
style(mcp): format meter gateway tests
cursoragent Aug 1, 2026
df32168
fix(email): read daily usage from UserMeter
cursoragent Aug 1, 2026
ae07b85
Merge remote-tracking branch 'origin/main' into cursor/meter-do-38c8
cursoragent Aug 1, 2026
e6622a4
docs(entitlements): clarify meter cutover semantics
cursoragent Aug 1, 2026
7628aaf
fix(entitlements): address UserMeter review feedback
cursoragent Aug 1, 2026
468ee51
Merge remote-tracking branch 'origin/main' into cursor/meter-do-38c8
cursoragent Aug 1, 2026
a8cba2b
test(entitlements): add RunLog admin fixture
cursoragent Aug 1, 2026
edfd973
test(run-log): isolate usage counts per user
cursoragent Aug 1, 2026
2307d37
Merge remote-tracking branch 'origin/main' into cursor/meter-do-38c8
cursoragent Aug 1, 2026
b18246f
feat(entitlements): shadow storage bytes in UserMeter
cursoragent Aug 1, 2026
eaaa509
docs(entitlements): inventory storage shadow
cursoragent Aug 1, 2026
3c13b53
fix(entitlements): make storage reserves fail closed
cursoragent Aug 1, 2026
68256ea
Merge remote-tracking branch 'origin/main' into cursor/meter-do-38c8
cursoragent Aug 1, 2026
e454e76
feat(services): shadow liveness in UserMeter
cursoragent Aug 1, 2026
9f7be03
docs(services): inventory liveness shadow
cursoragent Aug 1, 2026
581b896
perf(services): defer liveness shadow writes
cursoragent Aug 1, 2026
393b2a4
Merge remote-tracking branch 'origin/main' into cursor/meter-do-38c8
cursoragent Aug 1, 2026
12e7c02
fix(services): serialize liveness shadows
cursoragent Aug 1, 2026
3a0d41d
Merge remote-tracking branch 'origin/main' into cursor/meter-do-38c8
cursoragent Aug 1, 2026
194d5b5
Merge remote-tracking branch 'origin/main' into cursor/meter-do-38c8
cursoragent Aug 1, 2026
5e8d1b9
feat(account): shadow deletion leases in UserMeter
cursoragent Aug 1, 2026
2e92f03
docs(account): inventory deletion shadow
cursoragent Aug 1, 2026
c566c54
Merge remote-tracking branch 'origin/main' into cursor/meter-do-38c8
cursoragent Aug 1, 2026
62f6078
fix(account): reconcile and sanitize deletion shadows
cursoragent Aug 1, 2026
1116e07
Merge remote-tracking branch 'origin/main' into cursor/meter-do-38c8
cursoragent Aug 1, 2026
d20696d
feat(account): move write leases into UserMeter
cursoragent Aug 1, 2026
6c2effd
docs(account): describe UserMeter lease authority
cursoragent Aug 1, 2026
afe0bb2
test(account): provide authoritative meter fixtures
cursoragent Aug 1, 2026
29dc257
test(account): wire meter into workers fixtures
cursoragent Aug 1, 2026
11814af
fix(account): clean partial lease mirrors
cursoragent Aug 1, 2026
7e88800
Merge remote-tracking branch 'origin/main' into cursor/meter-do-38c8
cursoragent Aug 1, 2026
8d368eb
Merge remote-tracking branch 'origin/main' into cursor/meter-do-38c8
cursoragent Aug 1, 2026
4c0aa23
feat(admin): add UserMeter parity report
cursoragent Aug 1, 2026
c44a518
docs(entitlements): define production parity gates
cursoragent Aug 1, 2026
a6180fc
fix(admin): bound parity page walks
cursoragent Aug 1, 2026
5af8263
Merge remote-tracking branch 'origin/main' into cursor/meter-do-38c8
cursoragent Aug 1, 2026
177a471
refactor(entitlements): stop D1 daily counter mirror
cursoragent Aug 1, 2026
b955401
docs(entitlements): stage daily mirror retirement
cursoragent Aug 1, 2026
164b275
fix(entitlements): preserve pre-drop schema guards
cursoragent Aug 1, 2026
275d23b
fix(entitlements): clarify mirror-stop fallbacks
cursoragent Aug 1, 2026
436c272
docs(entitlements): clarify pre-drop inventory reads
cursoragent Aug 1, 2026
a279b9e
Merge remote-tracking branch 'origin/main' into cursor/meter-do-38c8
cursoragent Aug 1, 2026
fc3bf7a
refactor(account): detach pending daily counter table
cursoragent Aug 1, 2026
fe1766b
docs(storage): point pending-drop guardrail correctly
cursoragent Aug 1, 2026
05bc952
Merge remote-tracking branch 'origin/main' into cursor/meter-do-38c8
cursoragent Aug 1, 2026
659033c
refactor(entitlements): drop retired daily counter table
cursoragent Aug 1, 2026
65b2828
docs(entitlements): format retirement references
cursoragent Aug 1, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 16 additions & 18 deletions docs/contributing/architecture/data-storage.md
Original file line number Diff line number Diff line change
Expand Up @@ -131,10 +131,10 @@ every query or Durable Object lookup is scoped to that id.

System email rows owned by `system:email` are intentionally absent from account
exports for the same reason they are absent from deletion: they belong to the
operator inbox surface, not to the exporting user. Pending-drop physical tables
(such as quiescent `entitlement_daily_counters`) are also absent from export
queries. The export manifest lists both under `excludedD1Surfaces` so the
omission is explicit.
operator inbox surface, not to the exporting user. The export manifest lists
that omission under `excludedD1Surfaces` so it is explicit. The retired
`entitlement_daily_counters` D1 mirror is absent from the final schema
(migration `0126`) and therefore from export inventory and `excludedD1Surfaces`.

Platform-feedback submissions are included in the submitting user's own D1
export section. An export never includes submissions owned by other users,
Expand Down Expand Up @@ -644,12 +644,12 @@ time-pruned. Deletion-fence legacy lease rows are bounded by the D1 snapshot
replace on `markDeleting` rather than time retention; DO-authority rows clear on
release/repair/purge.

**D1 daily mirror writes stopped:** enforcement, point reads, bootstrap, mirror,
and account export/deletion inventory paths no longer read or write
`entitlement_daily_counters`. The admin parity report
(`admin_user_meter_parity`) temporarily reads the table while it exists for
migration verification. The physical table stays registered as a pending-drop
coverage exemption until a later migration-only deploy drops it. See
**D1 daily mirror retired:** enforcement, point reads, bootstrap, mirror, and
account export/deletion inventory paths never read or write
`entitlement_daily_counters`. The three-deploy retirement is complete (Workers
`#1133` / `#1134`, then migration `0126-drop-entitlement-daily-counters.sql`).
The final live schema has no table or day index; `admin_user_meter_parity`
reports `daily.mirrorRetired: true` (meter counts only). See
[Entitlements](./entitlements.md#usermeter-expand-phase).

**Daily cold bootstrap:** a missing `(resource, day)` row returns
Expand Down Expand Up @@ -1672,14 +1672,12 @@ Current retention policies:
After Mailbox cut-over, the same 365-day window and blob-before-row ordering
are self-enforced by the Mailbox DO alarm; `system:email` stays on the D1
system-email retention job.
- `entitlement_daily_counters`: **quiescent pending drop** — mirror writes,
bootstrap reads, scheduled retention pruning, and account export/deletion
inventory stopped across the stage 1/2 code deploys. The admin parity report
(`admin_user_meter_parity`) temporarily reads the table while it exists.
Runtime account inventory no longer queries the table; it remains registered
in `accountUserDataPendingDropTargets` for schema coverage until the later
drop migration. Daily counter retention lives in the per-user `UserMeter` DO
(`userMeterDailyCounterRetentionDays`).
- `entitlement_daily_counters`: **retired** — dropped by migration
`0126-drop-entitlement-daily-counters.sql` after stages 1/2 stopped mirror
writes and detached runtime inventory. No scheduled retention disposition or
pending-drop coverage remains. Daily counter retention lives in the per-user
`UserMeter` DO (`userMeterDailyCounterRetentionDays`);
`admin_user_meter_parity` reports `daily.mirrorRetired: true`.
- `usage_rollups`: per user/metric/month rollups keep 24 months by `month` key;
raw Analytics Engine usage events follow platform retention.
- `feature_flag_exposure_rollups`: local-dev/test flag exposure rollups keep 90
Expand Down
86 changes: 42 additions & 44 deletions docs/contributing/architecture/entitlements.md
Original file line number Diff line number Diff line change
Expand Up @@ -152,16 +152,14 @@ then initializes that key at zero via `UserMeter.initialize()`
(`INSERT OR IGNORE`, concurrent-safe) before retrying. Warm enforcement awaits
only the DO RPC and never touches D1 daily counter state.

**D1 daily mirror writes stopped:** consume, refund, inbound charge/read,
point-read surfaces, retention, and generic account export/deletion no longer
read or write `entitlement_daily_counters`. The physical table remains in the
migrated schema as a pending-drop target (`accountUserDataPendingDropTargets`)
so schema coverage still recognizes `entitlement_daily_counters.user_id` without
runtime inventory querying it; export lists the omission under
`excludedD1Surfaces` (no raw rows). A later migration-only deploy drops the
table (migrations apply before Workers); existing rows are quiescent historical
mirror state. Analytics Engine remains the production reporting path for email
send/receive aggregates.
**D1 daily mirror retired:** consume, refund, inbound charge/read, point-read
surfaces, retention, and account export/deletion never read or write
`entitlement_daily_counters`. The three-deploy retirement is complete: Worker
`#1133` stopped mirror writes, `#1134` detached runtime inventory, and migration
`0126-drop-entitlement-daily-counters.sql` dropped the table and day index.
`admin_user_meter_parity` reports `daily.mirrorRetired: true` (meter counts
only; `d1Count`/`delta` null). Analytics Engine remains the production reporting
path for email send/receive aggregates.

**Point-read surfaces** call `readDailyEntitlementResourceUsage` (UserMeter with
the same cold zero-init path):
Expand Down Expand Up @@ -362,39 +360,39 @@ same-token rollout mirrors; email keeps its D1 lease path. Package-service and
storage authority flips remain separate high-risk contract follow-ups after
soak/parity review.

**Daily-counter mirror retirement (three-deploy):** stage 1 (Worker #1133)
stopped all D1 mirror/bootstrap/retention use while leaving the physical
`entitlement_daily_counters` table and account inventory target in place. Stage
2 removes the runtime account export/deletion inventory target and registers the
table in `accountUserDataPendingDropTargets` for schema coverage (export
documents the omission; no raw rows). The third, migration-only deploy drops the
table. Production `admin_user_meter_parity` scans across 38 users showed zero
daily mismatches with Analytics Engine reporting active — the deploy rationale
for stopping mirror writes before the drop. While the table exists, parity still
compares `d1Count === meterCount` (`mirrorRetired: false`); after the drop
migration, `mirrorRetired: true` reports meter counts only.
**Daily-counter mirror retirement (three-deploy, complete):** stage 1 (Worker
`#1133`) stopped all D1 mirror/bootstrap/retention use while leaving the
physical `entitlement_daily_counters` table in place. Stage 2 (`#1134`) detached
the runtime account export/deletion inventory target and kept a pending-drop
schema coverage exemption while the table still existed. Stage 3 (migration
`0126-drop-entitlement-daily-counters.sql`) drops the table and
`idx_entitlement_daily_counters_day`. Production `admin_user_meter_parity` scans
across 38 users showed zero daily mismatches with Analytics Engine reporting
active — the deploy rationale for stopping mirror writes before the drop. Final
live schema has no `entitlement_daily_counters` table; admin parity reports
`daily.mirrorRetired: true` (meter counts only).

### Admin UserMeter parity gates (`admin_user_meter_parity`)

Production verification for mirror retirement and remaining authority flips uses
the admin-only read-only capability `admin_user_meter_parity` (input:
`stable_user_id`). It compares production-shaped D1 rows for one account against
direct UserMeter RPCs and never bootstraps or writes parity state. Daily
comparison retires automatically once the drop migration removes
`entitlement_daily_counters` (`daily.mirrorRetired: true`). Opening a cold
UserMeter stub may still run Durable Object constructor schema maintenance and
opportunistic stale daily-counter pruning. Cold meter rows surface as
`needsBootstrap` with `meterCount`/`meterBytes` null.
direct UserMeter RPCs and never bootstraps or writes parity state. After
migration `0126`, daily comparison is retired (`daily.mirrorRetired: true`): the
report returns meter counts only with `d1Count`/`delta` null and each resource
`parity: true`. Opening a cold UserMeter stub may still run Durable Object
constructor schema maintenance and opportunistic stale daily-counter pruning.
Cold meter rows surface as `needsBootstrap` with `meterCount`/`meterBytes` null.

Interpret the structured report as independent gates:

| Gate | Pass condition |
| -------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Daily counters (current UTC day) | While `daily.mirrorRetired` is false (table present): each of the four daily resources has `parity: true` (`d1Count === meterCount`); aggregate `daily.mismatchCount === 0`. After the drop migration (`mirrorRetired: true`): report meter counts only; `d1Count`/`delta` are null, each resource has `parity: true`, and `mismatchCount === 0` (no D1 comparison). |
| Storage bytes | `storage.parity` — D1 `users.d1_storage_bytes` equals UserMeter `readStorageBytes` (not `needsBootstrap`). |
| Package services | `packageServices.parity` — inventory mismatch category counts are all zero (`d1Only` / `meterOnly` / `statusMismatch` / `startedAtMismatch` / `sourceUpdatedAtMismatch`), fresh-running counts match under the shared 24h stale window, and the meter page walk is not `truncated`. |
| Deletion tombstone | `deletion.deletingAtParity` — D1 `users.deleting_at` matches the meter tombstone. |
| Temporary D1 lease mirror | `deletion.mirrorLeaseParity` — `doOnly === 0`, `legacyWithoutD1 === 0`, inventory not truncated, and `d1ActiveLeaseCount >= doAuthorityLeaseCount` (same-token mirror coverage). `tokenSetMismatches.d1Only` is reported but does **not** fail this gate. |
| Gate | Pass condition |
| -------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Daily counters (current UTC day) | Final post-drop semantics (`daily.mirrorRetired: true`): report meter counts only; `d1Count`/`delta` are null, each of the four daily resources has `parity: true`, and `mismatchCount === 0` (no D1 comparison). Pre-drop Workers that still see the table compare `d1Count === meterCount` with `mirrorRetired: false`. |
| Storage bytes | `storage.parity` — D1 `users.d1_storage_bytes` equals UserMeter `readStorageBytes` (not `needsBootstrap`). |
| Package services | `packageServices.parity` — inventory mismatch category counts are all zero (`d1Only` / `meterOnly` / `statusMismatch` / `startedAtMismatch` / `sourceUpdatedAtMismatch`), fresh-running counts match under the shared 24h stale window, and the meter page walk is not `truncated`. |
| Deletion tombstone | `deletion.deletingAtParity` — D1 `users.deleting_at` matches the meter tombstone. |
| Temporary D1 lease mirror | `deletion.mirrorLeaseParity` — `doOnly === 0`, `legacyWithoutD1 === 0`, inventory not truncated, and `d1ActiveLeaseCount >= doAuthorityLeaseCount` (same-token mirror coverage). `tokenSetMismatches.d1Only` is reported but does **not** fail this gate. |

**D1-only leases:** email and other transition paths that omit `env` still take
exact D1 leases, so `d1Only > 0` is expected until that handoff. Mirror-removal
Expand Down Expand Up @@ -573,9 +571,8 @@ Rules:
`UserMeter.initialize({ count: 0 })` (`INSERT OR IGNORE`) before retrying the
consume. Concurrent cold callers cannot double-apply a non-zero baseline.

**D1 mirror writes stopped:** consume/refund/inbound charge/read paths never
touch `entitlement_daily_counters`; the physical table remains quiescent until
a follow-up migration drops it.
**D1 mirror retired:** consume/refund/inbound charge/read paths never touch
`entitlement_daily_counters`; migration `0126` dropped the table.

A delivery claim remains charged when later storage fails. Cloudflare Email
Routing retries replay that same `delivery_id` through
Expand Down Expand Up @@ -792,10 +789,11 @@ in [`../environment-variables.md`](../environment-variables.md).
`0066-stripe-billing.sql`; owned by `packages/worker/src/billing/`, read by
`getUserPlan` via `resolveEffectivePlan`. `stripe_plan` stays nullable because
it is Stripe-derived; `max` is manual-only.
- `entitlement_daily_counters` — **quiescent pending drop**. Created by
migration `0048-user-plans-and-entitlement-counters.sql`; mirror writes,
bootstrap reads, retention pruning, and account export/deletion inventory
stopped across the stage 1/2 code deploys. The table remains in
`accountUserDataPendingDropTargets` for schema coverage until a later
migration-only PR drops it. Daily counters are authoritative in the per-user
`UserMeter` DO; account export uses UserMeter RPCs (no raw D1 mirror rows).
- `entitlement_daily_counters` — **retired**. Created by migration
`0048-user-plans-and-entitlement-counters.sql`, indexed by
`0055-retention-indexes.sql`, and dropped by
`0126-drop-entitlement-daily-counters.sql` after stages 1/2 stopped mirror
writes and detached runtime inventory. Final live schema has no table or day
index; `admin_user_meter_parity` reports `daily.mirrorRetired: true`. Daily
counters are authoritative in the per-user `UserMeter` DO; account export uses
UserMeter RPCs.
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
-- Stage 3 (migration-only): drop the quiescent entitlement_daily_counters D1
-- mirror after Worker #1133 stopped mirror writes and #1134 detached runtime
-- account export/deletion inventory. Authoritative daily counters live in the
-- per-user UserMeter Durable Object; admin_user_meter_parity reports
-- daily.mirrorRetired: true once this table is absent.

DROP INDEX IF EXISTS idx_entitlement_daily_counters_day;
DROP TABLE IF EXISTS entitlement_daily_counters;
39 changes: 14 additions & 25 deletions packages/worker/src/account/data-targets.node.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -214,26 +214,23 @@ test('every accountUserDataTargets kind has a shared match builder and export gu
).toBe(true)
})

test('pending-drop entitlement_daily_counters stays out of runtime deletion/export SQL while schema coverage holds', () => {
const pendingDrop = accountUserDataPendingDropTargets.find(
(target) => target.table === 'entitlement_daily_counters',
)
expect(pendingDrop).toEqual(
expect.objectContaining({
table: 'entitlement_daily_counters',
column: 'user_id',
surface: 'entitlement_daily_counters',
reason: expect.stringContaining('pending a later drop migration'),
}),
)
expect(pendingDrop?.reason).toContain('No raw rows exported')

test('final schema drops entitlement_daily_counters without stale inventory coverage', () => {
expect(
accountUserDataPendingDropTargets.some(
(target) => target.table === 'entitlement_daily_counters',
),
).toBe(false)
expect(
accountUserDataTargets.some(
(target) =>
'table' in target && target.table === 'entitlement_daily_counters',
),
).toBe(false)
expect(getAccountExportExcludedD1Surfaces()).not.toEqual(
expect.arrayContaining([
expect.objectContaining({ name: 'entitlement_daily_counters' }),
]),
)

const deletionStatements = accountUserDataTargets.map((target) => {
const match = matchFor(target)
Expand All @@ -251,15 +248,6 @@ test('pending-drop entitlement_daily_counters stays out of runtime deletion/expo
})
expect(exportStatements.join('\n')).not.toMatch(/entitlement_daily_counters/u)

expect(getAccountExportExcludedD1Surfaces()).toEqual(
expect.arrayContaining([
expect.objectContaining({
name: 'entitlement_daily_counters',
reason: expect.stringContaining('No raw rows exported'),
}),
]),
)

const db = new DatabaseSync(':memory:')
applyMigrations(db)
const tableExists = db
Expand All @@ -269,7 +257,7 @@ test('pending-drop entitlement_daily_counters stays out of runtime deletion/expo
WHERE type = 'table' AND name = 'entitlement_daily_counters'`,
)
.get() as { present: number } | undefined
expect(tableExists?.present).toBe(1)
expect(tableExists).toBeUndefined()

const liveUserColumns = new Set<string>()
const tables = db
Expand All @@ -291,7 +279,8 @@ test('pending-drop entitlement_daily_counters stays out of runtime deletion/expo
}
}
const coveredColumns = getAccountD1UserColumnCoverage()
expect(coveredColumns.has('entitlement_daily_counters.user_id')).toBe(true)
expect(coveredColumns.has('entitlement_daily_counters.user_id')).toBe(false)
expect(liveUserColumns.has('entitlement_daily_counters.user_id')).toBe(false)
const missing = [...liveUserColumns].filter(
(column) => !coveredColumns.has(column),
)
Expand Down
13 changes: 3 additions & 10 deletions packages/worker/src/account/data-targets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,8 @@ export const accountUserDataExcludedOwnerIds = [
* `user_id` / `*_user_id` columns as covered so the live table does not look
* like a missing inventory target; runtime deletion and export never query
* them. Export documents each omission under `excludedD1Surfaces` (no raw
* rows).
* rows). Empty after `entitlement_daily_counters` was dropped by migration
* `0126`.
*/
export type AccountUserDataPendingDropTarget = {
table: string
Expand All @@ -89,15 +90,7 @@ export type AccountUserDataPendingDropTarget = {
}

export const accountUserDataPendingDropTargets: ReadonlyArray<AccountUserDataPendingDropTarget> =
[
{
table: 'entitlement_daily_counters',
column: 'user_id',
surface: 'entitlement_daily_counters',
reason:
'Quiescent daily-counter D1 mirror pending a later drop migration after mirror-stop Worker #1133. Authoritative daily counters live in UserMeter; account export and deletion must not query this table before the drop. No raw rows exported.',
},
]
[]

/** Targets that account export should skip (deletion still covers them). */
export function isExcludedFromAccountExport(
Expand Down
Loading
Loading