Skip to content
This repository was archived by the owner on Aug 12, 2026. It is now read-only.
Open
Show file tree
Hide file tree
Changes from 43 commits
Commits
Show all changes
50 commits
Select commit Hold shift + click to select a range
54d0b92
docs: add delete-my-account plan
lourou Jul 10, 2026
4020335
docs(delete-account): link tombstone gate to subscription ownership r…
lourou Jul 13, 2026
1971741
docs(delete-account): address review findings
lourou Jul 13, 2026
aeeaea1
docs(delete-account): serialize account-linked writers via parent-row…
lourou Jul 13, 2026
ac86f51
feat(deletion): add barrier, deletion-record, outbox, and tombstone m…
lourou Jul 15, 2026
4f0fffb
feat(deletion): barrier at token mint, fail-closed requireAccount, li…
lourou Jul 15, 2026
f791707
feat(deletion): honor subscription tombstones in verify and store web…
lourou Jul 15, 2026
e9f01f4
feat(deletion): DELETE /v2/accounts/me with single-transaction teardown
lourou Jul 15, 2026
9e505d1
feat(deletion): outbox drain worker and external purge executors
lourou Jul 15, 2026
ff1c55f
feat(claim): subscription lineage, custody escrow, and the claim endp…
lourou Jul 15, 2026
e56591a
test(deletion): adversarial invariants, schema guards, and spec amend…
lourou Jul 15, 2026
ce9179b
fix(db): make reclaim migrations additive and database-enforce money …
lourou Jul 15, 2026
c2e8cec
fix(subscriptions): order-identity Google accounting, atomic lineage …
lourou Jul 15, 2026
d6db53a
fix(deletion): co-serialize barrier with mint, fence every account ro…
lourou Jul 15, 2026
9a364bc
fix(claim): fail-closed attestation flag, real pending-transfer push,…
lourou Jul 15, 2026
f725250
test(deletion): round-3 adversarial invariants, router fencing audit,…
lourou Jul 15, 2026
3727d99
fix(claim): exact funding-event restoration, keyless claim fail-close…
lourou Jul 15, 2026
2bc0935
fix(subscriptions): atomic tombstone rotation absorption, fail-closed…
lourou Jul 15, 2026
2ce3974
fix(auth): lossless activity veto, exact delete-replay carve-out, fai…
lourou Jul 15, 2026
5ee7214
feat(subscriptions): reclaim reconciliation sweep (quarantine drain +…
lourou Jul 15, 2026
2e028a3
test(deletion): round-4 invariants — exact-key restoration, veto race…
lourou Jul 15, 2026
3464e24
fix(auth): fail closed when the activity stamp cannot be written
lourou Jul 15, 2026
c673617
feat(subscriptions): durable reconciliation progress state
lourou Jul 15, 2026
296142e
fix(claim): fail closed when restoration finds no funding-event custody
lourou Jul 15, 2026
90a316c
feat(subscriptions): rework reconciliation sweep - retry state, drift…
lourou Jul 15, 2026
98f4de5
test(deletion): round-5 invariants - veto fail-close, drift cursor, f…
lourou Jul 15, 2026
e415a82
fix(reconciliation): composite drift cursor, DB-owned commit time, ro…
lourou Jul 15, 2026
53e389e
test(deletion): cover drift cursor boundaries and mint-stamp failure
lourou Jul 15, 2026
a302528
fix(reconciliation): durable per-lineage drift scheduling, rolling-sa…
lourou Jul 15, 2026
6f3b657
test(deletion): cover drift scheduling fairness, escalation, and back…
lourou Jul 15, 2026
4c4ab71
fix(reconciliation): final provider check at drift deadline and bound…
lourou Jul 15, 2026
4c343ab
fix(migrations): normalize all committed transfer timestamps and free…
lourou Jul 15, 2026
6226698
refactor(subscriptions): defer live transfer, Google claim proof, and…
lourou Jul 16, 2026
6524bb6
test(deletion): retarget the suite at the launch scope and consolidat…
lourou Jul 16, 2026
827f717
fix(deletion): harden purge executors, claim contract, and outbox dra…
lourou Jul 16, 2026
f4788ba
fix(deletion): close purge and fencing gaps from adversarial review
lourou Jul 16, 2026
1cf9743
fix(deletion): fence outbox claim generations and multi-owner notific…
lourou Jul 16, 2026
4678ba1
test(deletion): describe the replay diagnostics in current-behavior t…
lourou Jul 16, 2026
90b578d
fix(deletion): bound and fence remote notification mutations across t…
lourou Jul 16, 2026
d0f3d85
fix(migration): canonicalize Google lineage backfill to chain roots a…
lourou Jul 16, 2026
944cb5e
fix(subscriptions): keep keyless-void quarantine rows operator-owned
lourou Jul 16, 2026
ee946f6
fix(subscriptions): seed billing-grace claims from the status item's …
lourou Jul 16, 2026
d6918d4
feat(deletion): gate the endpoint by ACCOUNT_DELETION_ENABLED env var
lourou Jul 23, 2026
1a9f19d
Merge remote-tracking branch 'origin/otr-dev' into feature/delete-acc…
lourou Jul 24, 2026
37540e6
fix(deletion): report drain counts when only the advisory-lease close…
lourou Jul 24, 2026
c255695
chore(env): order deletion-section keys per dotenv-linter
lourou Jul 24, 2026
45cd889
fix(deletion): silence false-positive narrowing lint on the lease-clo…
lourou Jul 24, 2026
1a02af5
Merge remote-tracking branch 'origin/otr-dev' into feature/delete-acc…
lourou Aug 4, 2026
b735eb2
Merge remote-tracking branch 'origin/otr-dev' into feature/delete-acc…
lourou Aug 4, 2026
24e65af
fix(deletion): complete posthog_person purge as an explicit skip when…
lourou Aug 4, 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
21 changes: 21 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,27 @@ SIWE_ALLOWED_CHAIN_IDS=1
# Generate with: openssl rand -hex 32
# Treat as a secret; rotate via deploy if compromised (invalidates in-flight nonces, 5-min TTL absorbs).
NONCE_HMAC_SECRET=
# REQUIRED — HMAC secret keying account-deletion barrier hashes and pseudonymous
# deletion-record refs. Must be >= 64 hex chars (32 bytes).
# Generate with: openssl rand -hex 32
# PERMANENT: never rotate — rotation orphans every DeletedIdentity barrier row
# (silently lifting the deletion bar) and breaks deletion-record lookups.
DELETION_HASH_SECRET=
# Rollout gate for DELETE /v2/accounts/me — fail-closed, only the exact
# string "true" enables it (flip = infra PR + task-definition roll).
ACCOUNT_DELETION_ENABLED=false
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated
# OPTIONAL — PostHog person deletion for account-deletion purges. The
# ingestion token cannot delete persons; these enable the private-API call.
# When analytics is active but these are unset, posthog purge tasks fail and
# retry (paging ops) instead of silently skipping.
POSTHOG_PERSONAL_API_KEY=
POSTHOG_PROJECT_ID=
POSTHOG_API_HOST=

# --- Subscription restoration ---
# Claims of deleted accounts' Apple subscriptions.
SUBSCRIPTION_CLAIM_TOMBSTONE_ENABLED=true
# Live ownership transfers and Google claim proof are deferred to a follow-up.

# --- Payments / Credits ---
# REQUIRED — All five PAYMENTS_* knobs below are hard-required. Backend
Expand Down
644 changes: 644 additions & 0 deletions docs/plans/delete-my-account.md

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
-- Account deletion: barrier, deletion record, purge outbox, billing
-- tombstones, and the lastAuthAt activity stamp. Purely additive.

-- AlterTable
ALTER TABLE "Account" ADD COLUMN "lastAuthAt" TIMESTAMP(3);

-- CreateTable
CREATE TABLE "DeletedIdentity" (
"identityHash" TEXT NOT NULL,
"deletedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,

CONSTRAINT "DeletedIdentity_pkey" PRIMARY KEY ("identityHash")
);

-- CreateTable
CREATE TABLE "DeletionRecord" (
"operationId" UUID NOT NULL,
"accountRef" TEXT NOT NULL,
"status" TEXT NOT NULL DEFAULT 'purging',
"requestedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"completedAt" TIMESTAMP(3),
"expiresAt" TIMESTAMP(3),
"updatedAt" TIMESTAMP(3) NOT NULL,

CONSTRAINT "DeletionRecord_pkey" PRIMARY KEY ("operationId")
);

-- CreateTable
CREATE TABLE "DeletionTask" (
"id" UUID NOT NULL DEFAULT gen_random_uuid(),
"operationId" UUID NOT NULL,
"kind" TEXT NOT NULL,
"payload" JSONB NOT NULL,
"status" TEXT NOT NULL DEFAULT 'pending',
"attempts" INTEGER NOT NULL DEFAULT 0,
"nextAttemptAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"lastError" TEXT,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
"completedAt" TIMESTAMP(3),

CONSTRAINT "DeletionTask_pkey" PRIMARY KEY ("id")
);

-- CreateTable
CREATE TABLE "SubscriptionTombstone" (
"id" UUID NOT NULL DEFAULT gen_random_uuid(),
"provider" "BillingProvider" NOT NULL,
"providerKey" TEXT NOT NULL,
"accountRef" TEXT NOT NULL,
"deletedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,

CONSTRAINT "SubscriptionTombstone_pkey" PRIMARY KEY ("id")
);

-- CreateIndex
CREATE INDEX "DeletionRecord_accountRef_idx" ON "DeletionRecord"("accountRef");

-- CreateIndex
CREATE INDEX "DeletionRecord_status_requestedAt_idx" ON "DeletionRecord"("status", "requestedAt");

-- CreateIndex
CREATE INDEX "DeletionRecord_expiresAt_idx" ON "DeletionRecord"("expiresAt");

-- CreateIndex
CREATE INDEX "DeletionTask_status_nextAttemptAt_idx" ON "DeletionTask"("status", "nextAttemptAt");

-- CreateIndex
CREATE INDEX "DeletionTask_operationId_idx" ON "DeletionTask"("operationId");

-- CreateIndex
CREATE INDEX "SubscriptionTombstone_accountRef_idx" ON "SubscriptionTombstone"("accountRef");

-- CreateIndex
CREATE UNIQUE INDEX "SubscriptionTombstone_provider_providerKey_key" ON "SubscriptionTombstone"("provider", "providerKey");

-- Backfill: existing accounts start their activity clock at migration time.
-- A null lastAuthAt must never read as "inactive/no veto"; after this
-- backfill, null only ever means a brand-new account that has not minted yet.
UPDATE "Account" SET "lastAuthAt" = CURRENT_TIMESTAMP WHERE "lastAuthAt" IS NULL;
Loading