B2BEA.org V1 Entitlement Model Spec
B2BEA.org V1 Entitlement Model Spec Source of record: RedKey Supabase Studio artifact. Project: B2BEA.org Rebuild Project ID: a820dd0c 6cef 4133 bfbd d802fd806e44 Artifact: entitlement model spec Artifact ID: 355b3249 3af9 45a4 9c45 67777bd2d72d Version: 1 Status: draft Updated: 2026 05 06T23:32:25.756783+00:00 Live Supabase Check Checked against the active ...
Source of record: RedKey Supabase Studio artifact.
- Project:
B2BEA.org Rebuild - Project ID:
a820dd0c-6cef-4133-bfbd-d802fd806e44 - Artifact:
entitlement-model-spec - Artifact ID:
355b3249-3af9-45a4-9c45-67777bd2d72d - Version:
1 - Status:
draft - Updated:
2026-05-06T23:32:25.756783+00:00
Checked against the active B2BEA Supabase project and current B2BEA-org-new code signals.
- Supabase host:
czqxkykbhoyyjccckpqq.supabase.co - Source repo:
/Users/justinking/Vaults/Projects/B2BEA-org-new - Tables checked:
people,person_roles,membership_tiers,memberships,membership_seats,vendor_memberships,organization_members,courses,course_enrollments,events,content_embeddings
Current entitlement-related row counts:
| Table | Rows | Notes |
|---|---:|---|
| membership_tiers | 3 | Seed tiers: Registered, Pro, Vendor. |
| memberships | 1 | One active Pro membership. |
| membership_seats | 0 | Seat assignment base exists but is unused. |
| vendor_memberships | 0 | Vendor commercial/access layer exists but is unused. |
| organization_members | 0 | Company workspace membership base exists but is unused. |
| courses | 9 | Course catalog has mixed access/pricing fields. |
| course_enrollments | 4 | Current learner participation records. |
| person_roles | 8 | Role grants include Pro/vendor/admin-style roles. |
Current tiers:
| Tier | Category | Billing | Seat Model | Access Summary | |---|---|---|---|---| | Registered | practitioner | free | individual | Baseline account/profile access; no premium courses, reports, events, or vendor portal. | | Pro | practitioner | subscription | individual | All courses, reports, and events; no vendor portal; no enhanced directory. | | Vendor | vendor | invoice | company | Vendor portal write access and enhanced directory; no practitioner course/report/event grants by default. |
Code signals:
src/assets/js/authbridge.jsstill checks the legacypersonstable when evaluating Pro access.- Reports and guides call client-side
authbridge.checkProAccess()for Pro gating. - Courses combine
courses.access_tier,courses.is_included_with_pro, pricing fields, andcourse_enrollments. - Admin user tooling edits
people.is_proandperson_roles, which can drift from active memberships. membership_tiers.access_rulesexists but is not yet the single enforced entitlement contract.
| ID | Topic | Decision |
|---|---|---|
| ENT-DEC-001 | Canonical entitlement truth | Active memberships, membership_tiers.access_rules, and membership_seats are the canonical source for paid/product access. Roles are permissions and operational responsibility, not paid entitlement truth. |
| ENT-DEC-002 | people.is_pro | people.is_pro may remain as a denormalized display/cache flag during migration, but it must not be treated as authoritative access truth. |
| ENT-DEC-003 | Server-side enforcement | Protected reads, protected downloads, enrollments, mutations, exports, and portal actions require a server-side entitlement check. Client-side checks may only improve UX. |
| ENT-DEC-004 | Company workspace | Practitioner company workspace uses organization-held memberships plus membership_seats for employee access to academy/careers benefits. Company public profiles are excluded from V1. |
| ENT-DEC-005 | Vendor portal | Vendor portal access requires a vendor relationship role and an active/approved vendor commercial entitlement when commercial gating is required. Vendor public profiles remain V1. |
| ENT-DEC-006 | Course enrollment | course_enrollments repre
- Every protected capability resolves through one entitlement evaluator with auditable inputs and deterministic outputs.
- Entitlement decisions are explicit: subject, action, resource, source, status, start time, end time, and reason.
- Roles grant authority to administer or act on behalf of an account; memberships and seats grant product access.
- A user can hold multiple contexts at once: individual Pro, vendor admin, company admin, employee seat, and internal platform admin.
- Entitlement state should be explainable in the UI for support: why access exists, where it came from, when it expires, and who assigned it.
| Subject | Examples | Current Sources |
|---|---|---|
| Person | Registered account, Pro member, course learner, company employee seat, vendor team member, platform admin | people, person_roles, memberships.held_by_person_id, membership_seats.assigned_person_id, course_enrollments |
| Organization | Private practitioner company workspace, company academy/careers account | organizations, organization_members, memberships.held_by_org_id, membership_seats |
| Vendor | Public vendor profile, vendor management portal account, enhanced directory listing | vendors, vendor_memberships, person_roles, vendor_* portal tables |
| Key | Area | Grants |
|---|---|---|
| account.registered | Identity | Baseline authenticated account and profile access. |
| membership.pro | Membership | Individual Pro status and Pro-only product eligibility. |
| resource.report.read.pro | Resources | Read/download Pro reports and guides. |
| academy.course.enroll.included | Academy | Enroll in courses included with the member tier or company seat. |
| academy.course.purchase | Academy | Enroll in a course through a direct purchase/admin grant path. |
| academy.certification.earn | Academy | Attempt certification requirements and receive certificates. |
| event.register.member | Events | Register for member-included or member-priced events. |
| vendor.portal.read | Vendor | View vendor portal dashboard and own profile/submission state. |
| vendor.portal.write | Vendor | Submit vendor profile edits, case studies, media, and portal updates. |
| vendor.analytics.export | Vendor | Export bounded own-vendor analytics. |
| company.workspace.read | Company | Access private company workspace. |
| company.workspace.admin | Company | Manage company users, jobs, academy assignments, and workspace settings. |
| `com
Use and harden the existing tables:
| Table | Role |
|---|---|
| membership_tiers | Defines tier category, seat model, billing model, and structured access_rules. |
| memberships | Canonical commercial/product entitlement holder for person-held, organization-held, and vendor-held memberships. |
| membership_seats | Seat assignment from an org/vendor/company membership to an individual person. |
| person_roles | Operational authority and relationship roles such as platform_admin, vendor_admin, company_admin, author. |
| course_enrollments | Learning participation/progress after eligibility is granted. |
Add or harden:
| Table | Purpose | Core Fields |
|---|---|---|
| entitlement_grants | Optional normalized/materialized grant table for fast and auditable access decisions. | id, subject_type, subject_id, entitlement_key, source_type, source_id, status, starts_at, ends_at, metadata, timestamps |
| entitlement_audit_events | Audit log for grants, revocations, seat assignments, admin overrides, purchases, cancellations, and policy denials. | id, actor_person_id, subject_type, subject_id, entitlement_key, event_type, source_type, source_id, reason, metadata, created_at |
| course_assignments | Company/admin course assignment layer separate from course participation. | id, course_id, assigned_to_person_id, assigned_by_person_id, organization_id, source_type, status, due_at, timestamps |
| entitlement_policy_tests | Machine-readable policy fixtures for production gating regressions. | id, scenario_key, subject_fixture, resource_fixture, expected_decision, created_at |
Input shape:
| Field | Meaning |
|---|---|
| subject | person_id plus active organization/vendor context when applicable. |
| action | Named capability/action key such as academy.course.enroll or vendor.portal.write. |
| resource | Resource type and ID when relevant, such as course_id, vendor_id, report_id, organization_id. |
| context | Request metadata, impersonation/admin mode, source surface, and time. |
Output shape:
| Field | Meaning |
|---|---|
| allowed | Boolean decision. |
| entitlement_key | Resolved grant key or required key. |
| reason_code | Machine-readable allow/deny reason. |
| source_refs | Membership, role, seat, purchase, admin grant, or policy refs used for the decision. |
| expires_at | Expiration timestamp when access is time-bound. |
Hard rules:
- No protected mutation trusts only
localStorage, client profile state, orpeople.is_pro. - RLS policies and server routes/functions must agree on the same entitlement keys.
- Admin override must be logged with actor, subject, reason, and expiration when temporary.
- Exports must be filtered by own vendor/company context before data leaves Supabase.
| Area | Capability | Required Access | Source |
|---|---|---|---|
| Public site | Read public content/vendor profiles/jobs/events | Anonymous/public unless explicitly gated | Sanity + Supabase public projections |
| Member profile | Update own profile | account.registered + owns profile | people + auth user |
| Member profile | Change password/account settings | Authenticated own account | Auth provider + people |
| Resources | Read/download Pro report or guide | resource.report.read.pro or admin override | Memberships + tier rules + protected resource metadata |
| Academy | Enroll included course | academy.course.enroll.included or course purchase/admin grant | Membership tier, seat, purchase/grant, course policy |
| Academy | Continue enrolled course | Active course_enrollments row and not revoked | course_enrollments + entitlement status |
| Academy | Create/manage course | admin.platform.manage or academy admin role | person_roles/platform_admins |
| Vendor portal | Update vendor profile | vendor.portal.write + vendor relationship | Vendor entitlement + vendor ownership role |
| Vendor portal | Create case study/content submission | vendor.portal.write
| ID | Severity | Area | Current | Required |
|---|---:|---|---|---|
| ENT-GAP-001 | P0 | Legacy identity table | authbridge.js checks persons for Pro access while live schema uses people. | Replace persons usage with people or create/test an intentional compatibility view before launch. |
| ENT-GAP-002 | P0 | Fragmented Pro truth | people.is_pro, person_roles.pro_member, active memberships, and course fields can disagree. | Centralize Pro decision logic and backfill/derive display fields from canonical entitlement state. |
| ENT-GAP-003 | P0 | Client-side gating | Some reports/guides/course paths rely on client authbridge checks. | Server-side checks for protected reads/downloads/enrollments/mutations with RLS/function parity. |
| ENT-GAP-004 | P0 | Course access consistency | Courses have price fields, member prices, access_tier values, and is_included_with_pro flags that are not normalized. | Define a course access policy contract and migrate course rows to consistent values. |
| ENT-GAP-005 | P0 | Company seats | membership_seats and organization_members exist but are empty. | Implement org-held membership, employee role, seat assignment, revoc
1. Inventory and remove/replace all persons table references in production code.
2. Define entitlement key enum/constants shared by server functions, RLS, and UI display.
3. Normalize membership_tiers.access_rules into versioned machine-readable rules.
4. Implement entitlement evaluator and policy test fixtures before wiring new protected features.
5. Backfill current Pro member state from memberships and reconcile people.is_pro/person_roles drift.
6. Normalize course access fields and write course enrollment eligibility tests.
7. Wire vendor portal and company workspace gates to memberships/seats/roles.
8. Add entitlement audit events and support/admin visibility.
9. Add survey/company/vendor export entitlement checks as those modules are built.
- A developer can answer "why does this person have access?" from one entitlement decision path.
- Protected resources cannot be downloaded by editing client state or bypassing the UI.
- Pro, vendor, company, and admin access are separately testable and can coexist on one person account.
- Seat assignment and revocation changes access immediately or within a documented cache window.
- All entitlement-changing operations create an audit event.
- The old
persons-table access path is gone or intentionally shimmed and covered by tests.