Architecture

Privacy and data minimization

Where personal data lives, who controls it, how long it stays, and what can be deleted today.

Prood separates authentication (Better Auth) from commerce (tenant-scoped Postgres). Commerce rows reference buyers by an internal UUID, but they also hold the contact details a store needs to fulfil an order.

The public policies are the source of truth for what Prood promises: privacy policy, data processing terms and sub-processors. This page is the engineering view of the same facts.

Roles

  • Prood is the controller of platform accounts, shopper sign-in accounts, session security, visit statistics and the privacy choice.
  • The merchant is the controller of its shoppers' orders, addresses and contact details. Prood is its processor.

Data zones

ZoneStored dataWho controls itAccess
Better Auth (user, account, session, verification)Name, email, password hash, session IP address and user agentProodAuth layer only. The app database role can read only id and email of user
customers, customer_addressesEmail, first and last name, phone, auth_user_id (null for guests), saved addressesMerchantTenant RLS
orders, carts, returns, reviewsContact email, shipping and billing address snapshots, payment references, tracking, notes, review author nameMerchantTenant RLS
Checkout sessions (Upstash Redis)Contact email, name, phone, addresses, amount, payment sessionMerchantServer only. 24 hours, or 7 days once a payment such as Multibanco has started
Rate limits (Upstash Redis)Client IP address with the request path, per API key countersProodServer only. Lasts the rate-limit window
agent_eventEvent type, agent, capability, status, duration. Never arguments or resultsProodOrganization members
Payment providersWhatever the merchant's own Stripe, Easypay or Ifthenpay account needsMerchant's providerThird party, under the merchant's own contract

Shopper accounts are platform-wide. One email address is one user, whichever store the shopper signed up on. Each store sees only its own customers and orders rows. After a shopper verifies their email, autoLinkGuestCustomers links guest customers rows with that email, in any store, to the account.

Commerce identifier rule

text
customers.id           = internal UUID (commerce-owned)
customers.auth_user_id = FK → Better Auth user.id (nullable for guests)
orders.customer_id     = customers.id (never auth user.id)
carts.customer_id      = customers.id

Orders, carts, payment metadata, webhooks and Agent Auth tools identify a buyer by customerId and orderId. Better Auth user.id must not appear on orders, carts, checkout sessions, webhooks or public API responses.

Contact details travel where they are needed: the checkout session holds the buyer's email, name and phone until the order is placed, and the payment provider receives what it needs to take the payment.

Guest checkout

  1. Create a customers row with the contact details from checkout (auth_user_id null).
  2. Fulfilment details live in the order address snapshot as well as on the customer.
  3. On register or login, link the guest row: set auth_user_id once, and keep using customers.id everywhere.

Merchant visibility

The dashboard lists customers from the tenant's customers rows. A merchant never sees another store's customers or the shopper's account.

Retention and erasure

What exists today:

  • Redis checkout sessions and rate-limit keys expire on their own, as the table says.
  • A daily cron (/internal/retention on apps/api, 03:30 UTC, guarded by CRON_SECRET) runs purgeExpiredAuthData in packages/auth/src/retention.ts. It deletes expired sessions and verification rows, team invitations 30 days after they expire, and agent_event rows after 730 days. The constants are in RETENTION_DAYS: change them together with the privacy policy and _docs/compliance/retention-schedule.md.
  • A merchant can download their account data (/api/account/export) and delete their account (Better Auth deleteUser, refused while they are the only owner of a store). A shopper can do the same on their store account page. Deleting an account calls unlinkAuthUserFromCustomers, so stores keep their orders and customers but nothing points at the deleted account.
  • An owner or admin can download all of a store's data as one JSON file (/api/store/export, a session-only route outside /v1) on every plan. An owner can delete a store: beforeDeleteOrganization refuses while a paid plan is live, then deleteStoreData removes every row with an organization_id in one transaction and fails if any is left.
  • Products, categories, API keys and agents can be deleted by the merchant.
  • An owner or admin can erase one shopper (eraseCustomer in packages/platform/src/privacy/store-data.ts, reached through the session-only DELETE /api/store/customers/{id} and the customer page in the dashboard). The profile, addresses, wishlist and carts go. Orders stay and point at nobody, or, with scrubOrders, lose the shopper's email, addresses and notes. Reviews carry only an author name and aren't linked to the shopper.
  • What a Prood administrator does to an account is written to admin_audit_event (recordAdminAction in packages/auth/src/admin-audit.ts) and shown in the admin app under Audit log. It is kept 730 days (RETENTION_DAYS.adminAuditEvents).

What does not exist yet:

  • Deleting image files from the R2 bucket when a store is deleted. Image upload isn't built, so no merchant file exists there today, and dormant shopper accounts aren't deleted: they are linked to merchants' orders, so they are kept until the person deletes them.

Order address snapshots may be kept where the merchant has a legal basis, such as tax or fulfilment records. Prood acts as processor for merchant storefront data and does not decide those retention windows.