Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
75 commits
Select commit Hold shift + click to select a range
8ef367e
feat(email): scaffold per-user Mailbox durable object
cursoragent Aug 1, 2026
c21a0fe
fix(email): harden Mailbox storage contracts
cursoragent Aug 1, 2026
309fc69
fix(email): make Mailbox mirrors full snapshots
cursoragent Aug 1, 2026
8d1f672
Merge main after Mailbox phase 1 deployment
cursoragent Aug 1, 2026
cebf5b3
feat(email): add Mailbox dual-write primitives
cursoragent Aug 1, 2026
8615248
fix(email): align Mailbox mutation parity
cursoragent Aug 1, 2026
ca0e666
fix(email): harden Mailbox snapshot validation
cursoragent Aug 1, 2026
b3f20e6
Merge main after Mailbox phase 2a deployment
cursoragent Aug 1, 2026
ef268fe
feat(email): dual-write non-inbound Mailbox state
cursoragent Aug 1, 2026
e9e9f18
fix(email): batch Mailbox graph event mirrors
cursoragent Aug 1, 2026
d747c2d
fix(email): repair persisted provider mirror gaps
cursoragent Aug 1, 2026
d89910e
Merge main after non-inbound Mailbox deployment
cursoragent Aug 1, 2026
0551015
feat(email): add Mailbox parity soak gate
cursoragent Aug 1, 2026
3492ad5
test(email): register Mailbox parity migration
cursoragent Aug 1, 2026
f03a66f
fix(email): make Mailbox parity reconciliation resumable
cursoragent Aug 1, 2026
93aba3f
chore: refresh Mailbox parity migration hash
cursoragent Aug 1, 2026
9dc335e
fix(email): rebuild Mailbox on parity mismatch
cursoragent Aug 1, 2026
48d23b7
test(email): isolate Mailbox parity lane
cursoragent Aug 1, 2026
4c73520
refactor(email): clarify Mailbox parity phases
cursoragent Aug 1, 2026
a09e7c6
test(email): remove superseded parity suite
cursoragent Aug 1, 2026
c43af54
test(email): exclude parity support from worker build
cursoragent Aug 1, 2026
669e93b
test(email): relocate parity test support
cursoragent Aug 1, 2026
03aa7e9
fix(email): preserve parity test module exports
cursoragent Aug 1, 2026
67527fc
Merge main after Mailbox parity deployment
cursoragent Aug 1, 2026
8ae4d48
feat(email): dual-write finalized inbound Mailbox state
cursoragent Aug 1, 2026
6181071
Remove dormant delete dual-write from repo/service
cursoragent Aug 1, 2026
d195a68
fix(email): serialize inbound Mailbox terminal work
cursoragent Aug 1, 2026
8c7c8fa
docs: format inbound Mailbox architecture
cursoragent Aug 1, 2026
8397c90
docs: name inbound Mailbox coordinators
cursoragent Aug 1, 2026
bc0148a
Merge main after inbound Mailbox deployment
cursoragent Aug 1, 2026
91cf45a
fix(email): batch Mailbox parity event backfill
cursoragent Aug 1, 2026
316f12f
Merge main after parity batch deployment
cursoragent Aug 1, 2026
9c1b9eb
fix(email): extend scheduled Mailbox count timeout
cursoragent Aug 1, 2026
f07aaa2
Merge main before email storage reservation handoff
cursoragent Aug 1, 2026
cc18cda
test(email): cover outbound storage reservation USER_METER shadow han…
cursoragent Aug 1, 2026
2e355c2
Add inbound storage reservation UserMeter handoff workers test
cursoragent Aug 1, 2026
55c9550
fix(email): hand storage reservations to UserMeter
cursoragent Aug 1, 2026
0b35244
fix(email): await outbound storage accounting handoff
cursoragent Aug 1, 2026
928412d
fix(email): await fallback inbound storage accounting
cursoragent Aug 1, 2026
75e4f89
refactor(email): centralize storage accounting lifecycle
cursoragent Aug 1, 2026
8e00834
Merge main before accelerated Mailbox coverage
cursoragent Aug 1, 2026
39eae66
feat(admin): add Mailbox maintenance controls
cursoragent Aug 1, 2026
ab5aaf8
fix(admin): bound Mailbox retention maintenance
cursoragent Aug 1, 2026
bb0d830
fix(admin): gate Mailbox retention on owner D1 cleanup
cursoragent Aug 1, 2026
d8cbf72
feat(admin): add Mailbox canary delete control
cursoragent Aug 1, 2026
a96ea9f
Merge main before Mailbox canary delete
cursoragent Aug 1, 2026
aacb746
fix(admin): verify exact Mailbox delete inventory
cursoragent Aug 1, 2026
8a5c8e2
fix(admin): align canary delete result contract
cursoragent Aug 1, 2026
3425387
Merge main before Mailbox read cutover
cursoragent Aug 2, 2026
3a77820
feat(email): cut owner reads over to Mailbox
cursoragent Aug 2, 2026
ff05007
fix(email): scope cutover attachments and exposures
cursoragent Aug 2, 2026
3b030b3
test(email): use typed account email mock
cursoragent Aug 2, 2026
7635e5c
Merge main before provider reverse index
cursoragent Aug 2, 2026
e72ce79
feat(email): add outbound provider reverse index
cursoragent Aug 2, 2026
730c41c
fix(email): commit provider index atomically
cursoragent Aug 2, 2026
8f95d8e
fix(email): preserve accepted provider sends
cursoragent Aug 2, 2026
a6f46b3
Merge main after provider reverse index
cursoragent Aug 2, 2026
f31c296
feat(email): add Mailbox inbound ledger CAS RPCs
cursoragent Aug 2, 2026
3be4121
test(email): allow replay watermark wall time
cursoragent Aug 2, 2026
6580fb0
fix(email): fence inbound effects by finalization
cursoragent Aug 2, 2026
1e14bb0
docs(email): document effect finalization fence
cursoragent Aug 2, 2026
40904d9
fix(email): admit legacy inbound effect leases
cursoragent Aug 2, 2026
2248adf
test(email): split inbound ledger matrices
cursoragent Aug 2, 2026
36c1370
fix(email): reject negative inbound usage
cursoragent Aug 2, 2026
a6fd1ad
fix(email): reset effects on inbound refinalization
cursoragent Aug 2, 2026
c5e73e1
Merge main before inbound authority flip
cursoragent Aug 2, 2026
f926f5c
feat(email): move inbound authority to Mailbox
cursoragent Aug 2, 2026
ac46a01
fix(email): harden Mailbox inbound authority
cursoragent Aug 2, 2026
152b682
fix(email): fence inbound cleanup projection
cursoragent Aug 2, 2026
942debc
fix(email): enforce Mailbox inbound authority boundary
cursoragent Aug 2, 2026
29e8a61
fix(email): preserve preclaim parity audits
cursoragent Aug 2, 2026
5e767d6
test(email): cover inbound authority mirror guard
cursoragent Aug 2, 2026
c2b7909
fix(email): repair rejected and dedupe projections
cursoragent Aug 2, 2026
a5f3952
fix(email): bootstrap legacy inbound parity rows
cursoragent Aug 2, 2026
cfe74bf
fix(email): preserve legacy reconcile schedules
cursoragent Aug 2, 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
385 changes: 241 additions & 144 deletions docs/contributing/architecture/data-storage.md

Large diffs are not rendered by default.

13 changes: 13 additions & 0 deletions docs/contributing/disaster-recovery.md
Original file line number Diff line number Diff line change
Expand Up @@ -359,6 +359,19 @@ Disable ingress / put the app in maintenance before execute. After restore:
reindex Vectorize, re-arm jobs/alarms from D1, recreate queues from Wrangler
config, and expect users to reauthorize OAuth and remote connectors.

### Mailbox authority rollback repair

Rollback from the USER inbound Mailbox-authority Worker to its predecessor has
an accepted roll-forward caveat: legacy `json_set` lifecycle writes do not bump
`updated_at`, and the roll-forward does not auto-detect newer D1 state when a
Mailbox row already exists. Before a roll-forward after such a rollback, use the
backup-gated, owner-by-owner metadata purge and full D1 parity rebuild procedure
in
[Data storage → Mailbox](./architecture/data-storage.md#durable-objects-mailbox).
That procedure is gated on the verified backup SHA-256 prefix `7787f8c9`, keeps
the rollback Worker quiesced, and requires effect/finalization verification. Do
not use normal purge as an exploratory or routine repair.

## Schedules and freshness

| When (UTC) | Who | What |
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
-- USER inbound delivery authority now lives in the per-owner Mailbox Durable
-- Object. These columns are a complete compatibility projection used for
-- synchronous D1 graph-write fences and global scheduled owner discovery.
-- system:email continues to use the same D1 row as its authority.

ALTER TABLE email_delivery_events ADD COLUMN state TEXT
CHECK (
state IS NULL
OR state IN (
'pending', 'storing', 'cleaning', 'received', 'rejected', 'orphan-cleaned'
)
);
ALTER TABLE email_delivery_events ADD COLUMN fingerprint TEXT;
ALTER TABLE email_delivery_events ADD COLUMN storage_lease TEXT;
ALTER TABLE email_delivery_events ADD COLUMN storage_lease_at TEXT;
ALTER TABLE email_delivery_events ADD COLUMN cleanup_lease TEXT;
ALTER TABLE email_delivery_events ADD COLUMN cleanup_lease_at TEXT;
ALTER TABLE email_delivery_events ADD COLUMN cleanup_retry_at TEXT;
ALTER TABLE email_delivery_events ADD COLUMN expected_attachment_count INTEGER;
ALTER TABLE email_delivery_events ADD COLUMN finalization_token TEXT;
ALTER TABLE email_delivery_events ADD COLUMN reconcile_after TEXT;
ALTER TABLE email_delivery_events ADD COLUMN dedupe_expires_at TEXT;
ALTER TABLE email_delivery_events ADD COLUMN usage_effect_suppressed_at TEXT;
ALTER TABLE email_delivery_events ADD COLUMN usage_started_at TEXT;
ALTER TABLE email_delivery_events ADD COLUMN usage_effect_retry_at TEXT;
ALTER TABLE email_delivery_events ADD COLUMN usage_effect_lease TEXT;
ALTER TABLE email_delivery_events ADD COLUMN usage_effect_lease_at TEXT;
ALTER TABLE email_delivery_events ADD COLUMN subscription_effect_state TEXT
CHECK (
subscription_effect_state IS NULL
OR subscription_effect_state IN ('pending', 'processing', 'complete', 'dead-letter')
);
ALTER TABLE email_delivery_events ADD COLUMN subscription_effect_lease TEXT;
ALTER TABLE email_delivery_events ADD COLUMN subscription_effect_lease_at TEXT;
ALTER TABLE email_delivery_events ADD COLUMN subscription_effect_retry_at TEXT;
ALTER TABLE email_delivery_events ADD COLUMN subscription_effect_attempt_count INTEGER;
ALTER TABLE email_delivery_events ADD COLUMN subscription_effect_dead_letter_at TEXT;
ALTER TABLE email_delivery_events ADD COLUMN subscription_effect_last_error TEXT;
ALTER TABLE email_delivery_events ADD COLUMN updated_at TEXT;

UPDATE email_delivery_events
SET
state = json_extract(detail_json, '$.state'),
fingerprint = json_extract(detail_json, '$.fingerprint'),
storage_lease = json_extract(detail_json, '$.storageLease'),
storage_lease_at = json_extract(detail_json, '$.storageLeaseAt'),
cleanup_lease = json_extract(detail_json, '$.cleanupLease'),
cleanup_lease_at = json_extract(detail_json, '$.cleanupLeaseAt'),
cleanup_retry_at = json_extract(detail_json, '$.cleanupRetryAt'),
expected_attachment_count = json_extract(
detail_json,
'$.expectedAttachmentCount'
),
finalization_token = json_extract(detail_json, '$.finalizationToken'),
reconcile_after = json_extract(detail_json, '$.reconcileAfter'),
dedupe_expires_at = json_extract(detail_json, '$.dedupeExpiresAt'),
usage_effect_suppressed_at = json_extract(
detail_json,
'$.usageEffectSuppressedAt'
),
usage_started_at = json_extract(detail_json, '$.usageStartedAt'),
usage_effect_retry_at = json_extract(detail_json, '$.usageEffectRetryAt'),
usage_effect_lease = json_extract(detail_json, '$.usageEffectLease'),
usage_effect_lease_at = json_extract(detail_json, '$.usageEffectLeaseAt'),
subscription_effect_state = json_extract(
detail_json,
'$.subscriptionEffectState'
),
subscription_effect_lease = json_extract(
detail_json,
'$.subscriptionEffectLease'
),
subscription_effect_lease_at = json_extract(
detail_json,
'$.subscriptionEffectLeaseAt'
),
subscription_effect_retry_at = json_extract(
detail_json,
'$.subscriptionEffectRetryAt'
),
subscription_effect_attempt_count = json_extract(
detail_json,
'$.subscriptionEffectAttemptCount'
),
subscription_effect_dead_letter_at = json_extract(
detail_json,
'$.subscriptionEffectDeadLetterAt'
),
subscription_effect_last_error = json_extract(
detail_json,
'$.subscriptionEffectLastError'
),
updated_at = created_at
WHERE provider IN (
'cloudflare-email-routing',
'cloudflare-email-routing-dedupe'
);

UPDATE email_delivery_events
SET updated_at = created_at
WHERE updated_at IS NULL;

CREATE INDEX IF NOT EXISTS idx_email_delivery_events_user_state_created
ON email_delivery_events(user_id, state, created_at, id)
WHERE provider = 'cloudflare-email-routing' AND state IS NOT NULL;

CREATE INDEX IF NOT EXISTS idx_email_delivery_events_user_dedupe_expires
ON email_delivery_events(user_id, dedupe_expires_at, id)
WHERE provider = 'cloudflare-email-routing-dedupe'
AND dedupe_expires_at IS NOT NULL;

-- Cross-store idempotency for the external D1 rollup effect. Mailbox owns the
-- effect state/lease; this ledger only prevents replay from incrementing the
-- aggregate twice if the Worker fails after D1 commit but before Mailbox CAS.
CREATE TABLE email_inbound_usage_effects (
user_id TEXT NOT NULL,
delivery_id TEXT NOT NULL,
finalization_token TEXT NOT NULL,
created_at TEXT NOT NULL,
PRIMARY KEY (user_id, delivery_id, finalization_token),
FOREIGN KEY (delivery_id) REFERENCES email_delivery_events(id) ON DELETE CASCADE
);

CREATE INDEX IF NOT EXISTS idx_email_inbound_usage_effects_delivery
ON email_inbound_usage_effects(delivery_id);
8 changes: 8 additions & 0 deletions packages/worker/src/account/data-targets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,14 @@ export const accountUserDataTargets: ReadonlyArray<UserScopedDataTarget> = [
{ kind: 'user_id', table: 'workflow_runs' },
{ kind: 'user_id', table: 'package_service_states' },
{ kind: 'user_id', table: 'user_storage_buckets' },
{
kind: 'user_id',
table: 'email_inbound_usage_effects',
includeInExport: false,
surface: 'email_inbound_usage_effects',
reason:
'Internal cross-store effect idempotency ledger with no user-exportable content.',
},
{ kind: 'user_id', table: 'usage_rollups' },
{ kind: 'user_id', table: 'feature_flag_exposure_rollups' },
{ kind: 'user_id', table: 'user_activation_milestones' },
Expand Down
Loading
Loading