Evidence Builder — Procurement & Security Answers

A buyer/security/procurement reference for the multi-tenant Evidence Builder platform. Each item states what a buyer asks, the honest status (Built / Already true / Roadmap), and where it lives.

The foundation everything rests on: every eb_* table now carries a tenant_id and isolation is enforced in the database via row-level security (eb_my_tenants() / eb_is_tenant_admin()), with cascade foreign keys flowing eb_tenants → eb_clients → all child rows. The last legacy per-client tables that predated the tenant wall were migrated onto this model in July 2026 (see the closure note at the end).


1. Self-service tenant admin — Built

A licensee manages its own team without involving us. Tenant admins can invite advisors by email (org-branded), promote/demote between admin and advisor, remove members, toggle per-advisor client scoping, and require MFA — all gated to admins at the database (RLS), not just the UI.

2. Per-tenant data export — Built

"Give us all our data." A tenant admin clicks Export data in Org Settings and downloads one JSON bundle of every record for the organization — clients, evidence, controls, risks, procurement, assessments, and the audit trail. Tenant tables are filtered by tenant_id; legacy per-client tables by the tenant's client set. Integration token references are redacted. The export is itself recorded in the audit log.

3. Per-tenant deletion on termination — Built

Right-to-deletion on contract exit. A deliberate two-step, gated to a platform superadmin (not the tenant's own admin):

  1. eb_suspend_tenant(t) — soft stop (status = 'suspended'); reversible during a wind-down window via eb_reactivate_tenant(t).
  2. eb_terminate_tenant(t) — writes a durable proof-of-deletion record (retained, with the tenant name and record counts), then deletes the tenant row; the cascade removes clients and every child record. Customer operational data is purged; the proof that it was purged survives.

4. Audit log — Built

Who changed what, when — consolidated and tenant-scoped. A generic database trigger (eb_audit_row) records inserts/updates/deletes (with a field-level diff on updates) across the security-relevant tables: membership, integrations, vendors, tools, AI features, risks, escalations, clients. App-level events — invites, exports, terminations — are written too. Reads are admin-only and never cross the tenant boundary; ordinary clients cannot write to it.

5. Billing boundary = tenant — Already true

The licensing and metering unit is the tenant. eb_tenants.plan (standard | enterprise | dedicated) already models the commercial tier per tenant, and every billable record rolls up by tenant_id. Metering integration (e.g. Stripe usage records) is a wiring step, not an architecture change.

6. SSO (SAML / OIDC) — Roadmap (enterprise tier)

Most enterprises require it; it is a host-app auth concern that sits above this schema. Today: Supabase Auth (email/password, magic link, Google OAuth) with MFA/AAL2 step-up and a per-tenant MFA-required gate. SAML/OIDC federation is available on Supabase's enterprise auth tier and is configured per customer IdP — planned, not yet wired. Role mapping would land on eb_tenant_members.role.

7. Data residency — Roadmap (dedicated deployment tier)

The shared multi-tenant deployment runs in a single region (US). Where a contract requires a specific residency or in-region isolation, that is answered by the dedicated deployment tier (plan = 'dedicated'): a separate database/region per the §3.2 deployment model, not a code change to the shared tier.

8. Backups / RPO-RTO — Inherited from Supabase; numbers to confirm

Durability is delegated to managed infrastructure (Supabase, SOC 2 Type II). Supabase provides automated daily backups, with point-in-time recovery available as an add-on that narrows the recovery point objective to minutes. Confirm the active Supabase plan before quoting contractual RPO/RTO figures — the exact numbers are plan-dependent, and enterprise contracts will name specific targets.


Recently closed — full-coverage tenant RLS

Earlier drafts of this document flagged a handful of legacy per-client tables — eb_system_components, eb_data_flows, eb_assessments (+ responses/findings), eb_generated_artifacts, eb_advisor_qa — that predated the tenant wall and used the older advisor-access RLS instead of tenant-scoped RLS. That gap is now closed. A migration adds a tenant_id column (backfilled, NOT NULL, with an insert trigger) and replaces the advisor-access policies with tenant-scoped RLS on each of these tables, matching the model the rest of the schema already used. It was applied to the production database in July 2026 and verified at the schema level (every table now carries tenant_id and tenant-scoped read/write policies).

Remaining validation (honest): a scripted cross-tenant isolation test — one tenant attempting to read another's rows through the API and being denied — is checked in (supabase/seed_cross_tenant_rls_test.sql) but has not yet been executed against a disposable database. The boundary is enforced and schema-verified; the automated proof run is the last step before we'd call it independently validated.