Skip to content

Retain deleted account history and preserve credits across signups - #202

Merged
devhims merged 5 commits into
mainfrom
feat/deletion-ledger
Oct 15, 2026
Merged

devhims merged 5 commits into
mainfrom
feat/deletion-ledger

Conversation

@devhims

@devhims devhims commented Oct 15, 2026 •

Copy link
Copy Markdown
Owner

Problem / Motivation

Deleting and recreating an account grants another 1,000 Starter credits. Admins lose the history needed to investigate deleted accounts. Delayed refunds can leave transferred paid credits spendable, while stale refunds can erase a newer payment.

What changed

Retain a permanent deletion record with email, dates, Polar reference, plan, balance and deletion actor. Admin email/ID search includes historical records. Returning signups claim the latest balance once, even after an admin email change.

Keep subscription cancellation, Polar anonymization, admin protection and existing cleanup intact. Shared assets remain available. Direct D1 deletion records history but bypasses external cleanup.

Refunds follow account-ID transfers. Live and deleted accounts use the same credit reconciliation. Receipts require evidence of an adjustment or a justified skip. Applied refunds revoke Builder access. Snapshot writes check the refund lock atomically, preserving newer fulfilled payments. Replay cannot refill spent credits.

Migration 0026 preserves existing paid grants and applied refunds. Ambiguous legacy payment timing returns a retryable error without marking the refund handled. Signed payment replay resolves timing even after anonymization, without granting credits again.

%%{init: {"themeVariables": {"sequenceNumber": "#ffffff", "sequenceNumberColor": "#ffffff", "signalColor": "#475569"}}}%%
sequenceDiagram
    autonumber
    participant User
    participant App
    participant D1
    Note over User,D1: Before: deletion erased identity and signup granted another 1,000
    User->>App: Delete account with 340 credits
    App->>App: Existing cancellation, Polar and private cleanup
    App->>D1: Retain history and remove user atomically
    User->>App: Sign up with the same email
    App->>D1: Claim and restore 340 credits once
    Note over App,D1: Refunds follow the current credit owner
Loading

The deletion record remains; private content is removed.

Tests

Platform build, 1,814 unit tests, 170 auth/billing integrations, 354 storage tests and documentation checks pass. Regressions cover signed webhooks, real upgrades, skipped writes, replay and concurrent deletion. No live billing writes.

Apply updated migration 0026 before the Worker. Deploy promptly. For ambiguous legacy ordering, replay the saved grants' signed payment events, then the refund. Erased history cannot be reconstructed. Production is unchanged.

@vercel

vercel Bot commented Oct 15, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
video2ctx-web Ready Ready Preview Oct 15, 2026 5:01pm UTC

devhims commented Oct 15, 2026

Copy link
Copy Markdown
Owner Author

Changes needed for f9e55e73235b4ef4629fa27bf0b69ba5329ce4ca.

  • P1: Delayed refunds leave carried paid credits spendable. 0026_deleted_accounts.sql:56–57 restores the balance under a fresh ID, but applyRefundedOrder acknowledges and skips refunds for the deleted ID. Reproduced through a signed mock webhook: delete/re-signup with 10,000 paid credits, deliver the delayed full refund, receive 200, then successfully spend 5,000. Refund-before-deletion correctly leaves 1,000. Preserve refund reconciliation across the transfer, including before re-signup and webhook replay.
  • P2: Email changes can restore one snapshot twice. 0026_deleted_accounts.sql:52–59 never marks a balance as transferred. After restoring 340 for email A, an authenticated admin email change to B lets a new A restore another 340: 680 total. This requires the enabled admin update-user endpoint; ordinary users cannot change email. Track the transfer independently of the successor’s current email.

Verification: all eight Verify jobs pass. Independent probes: 20 passed, four failed on these cases. Existing auth/billing 135, platform 1,814, web 109 and five browser checks passed. Builds/typecheck/docs passed. Tests used isolated local D1 and synthetic provider responses; no live billing or production validation.

devhims commented Oct 15, 2026

Copy link
Copy Markdown
Owner Author

Changes needed for e717e088ce620449215da792b5b5e4a2d8cbb486.

P2: An applied refund can leave Builder access enabled. billing.ts:513–526 still gates the entitlement downgrade on the latest metadata timestamp. Reproduced: payment October 1, active/cancel-at-period-end metadata October 3, refund October 2. The signed webhook returns 200, resets credits to 1,000 and records applied, but the account remains Builder and the actual guard permits Builder-only deep imports. Replay does not repair it. This timestamp gate predates the delta; it remains an entitlement gap in the updated refund path. Reconcile access alongside the applied credit decision while preserving genuinely newer payments.

The four prior accounting/restoration failures now pass. Independent probes: 69 passed, two failed on this one issue. Existing platform 1,814, auth/billing 161 and storage 354 tests passed, plus build/typecheck/docs and all eight Verify jobs. Isolated local D1/synthetic provider tests; browser E2E verified in CI, no live billing or production validation.

devhims commented Oct 15, 2026

Copy link
Copy Markdown
Owner Author

Changes needed for e716ff006049cf328588ed54c405819f62a61303.

P2: An in-flight customer snapshot can restore Builder after refund. billing.ts:205–220 reads the refund lock before its write transaction. Reproduced deterministically: an October 3 active snapshot reads first, the October 2 refund commits Starter/1,000 credits, then the snapshot commits Builder. The actual guard allows deep imports again. Active and cancel-at-period-end variants both fail. Make the refund protection atomic with the snapshot write. Replay repairs access, but both original deliveries succeed, so replay is not guaranteed. Base/prior-head controls confirm this race is pre-existing, not introduced by this delta.

All 71 previous cases pass unchanged, including the sequential entitlement fix. Additional probes: 20 passed, two failed on this race. Existing platform/auth/storage suites, build/typecheck/docs and all eight Verify jobs pass. Local synthetic fixtures only; browser E2E verified in CI, no live billing/production validation.

devhims commented Oct 15, 2026

Copy link
Copy Markdown
Owner Author

Approve for 2a59ff8545b5e923ba8c258b96b8889fa917cf56. No material findings remain.

The remaining P2 race is fixed at billing.ts:522–523: both unchanged reproductions now retain Starter/1,000 credits and reject deep imports, while newer fulfilled payments retain Builder access. Earlier carryover, duplicate-restoration, ordering and replay findings remain fixed.

Verification: all 93 prior independent cases plus 17 new controls pass. Platform 1,814, auth/billing 170 and storage 354 tests pass, along with build/typecheck/docs and all eight Verify jobs. Local disposable fixtures and synthetic signed events; browser/container validation relied on CI. No live billing or production validation.

@devhims
devhims merged commit f9a7252 into main Oct 15, 2026
10 checks passed
@devhims
devhims deleted the feat/deletion-ledger branch October 15, 2026 17:17

This branch was successfully deployed

1 active deployment
Preview — 2a59ff85 Deployed Oct 15, 2026 by vercel[bot]
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.

1 participant