Trust and data handling

Built so a closer’s file stays in that closer’s org.

ParcelClear is a Next.js app on Vercel with Supabase Postgres and row-level security. The service role never ships to the browser. Personal information is not written to console logs or Sentry breadcrumbs.

Tenant isolation

File, party, screen, evidence, organization, membership, and audit rows carry organization_id. Policies check membership before a read or a write. An authenticated user who belongs to no organization sees zero rows on those tables. The same user cannot insert a file into another organization: Postgres rejects the write under row-level security. The owning user sees only their own organization's rows, and only the audit rows that are not service-role or system entries. Leads and rate-limit rows have no client policies; only the server service role inserts them.

How that was verified

An account belonging to no organization returns zero rows on every tenant table and is refused on write by the database itself, not by application code. That check was run against production with a Postgres role and JWT-claim impersonation. The tables in that check were files, parties, evidence_packs, audit_logs, organizations, screens, screen_hits, and memberships.

Evidence and the audit record

Paid evidence packs live in a private bucket and are served only by a signed URL that expires in 900 seconds. The public verify page shows the pack SHA-256 so a recipient can confirm a PDF they were handed matches the record. That page is excluded from search indexing. The evidence pack PDF contains no @ character. Rule-set hashes from tx-sb17 1.0.4 forward are computed over canonical JSON, with object keys sorted and whitespace removed, so the same rule set hashes the same after reformatting or a database round trip, and the value can be recomputed from the stored rule set with scripts/verify-rules.ts. The audit log is append-only and hash-chained. After a schema migration the chain was recomputed across every row: zero bad hashes, zero broken links. Audit entries carry organization ids, actor user ids, action names, target ids, hashes, and counts. The actor is stored as a user id, not an email. No audit row contains an @ character.

Fail-closed by design

Missing or unknown facts never produce CLEAR. Sanctions lists older than seven days at screen time produce HOLD. A screen that cannot complete produces HOLD rather than a passing result. An unfunded file is refused rather than silently created.

Telemetry

Page and product events are keyed to identified users only and do not include free-text file content. Sentry records errors with metadata limited to ids, hashes, counts, and event types.

Secrets

Publishable keys are for the browser. The service role, Stripe secret, and Resend key stay on the server.

Auth

Accounted product surfaces use Supabase SSR cookies. The free tools do not create an account.

Where data lives

Data is stored in the United States. Subprocessors are Supabase, Vercel, Stripe, Resend, and Sentry.

What ParcelClear is not

ParcelClear is not a consumer reporting agency. Its output is not a consumer report under the FCRA. It records questionnaire answers, screening results, and the reviewer's disposition. It does not decide whether anyone may buy.