Baseleg Docs
Domain · v1 spine / People · Baseleg Docs
v1 primary

People

Packages: packages/domain/people, packages/application/people

Purpose

The People context is the authoritative source of identity for everyone associated with the organisation. All other contexts that reference a person (Scheduling, Training, Billing, Compliance) do so via the identifiers defined here.

Entities

Person (aggregate root)

The central entity. A Person represents an individual formally associated with the organisation. Every other Type (Student, Instructor, Staff, Admin) is a set of attributes overlaid on a Person — not a separate entity type.

Key attributes:

  • id — unique identifier
  • name — full name
  • email — contact email (unique within the organisation)
  • phone — optional contact number
  • types — set of active Types (student, instructor, staff, admin)
  • statusactive | inactive

Value objects

  • PersonId — typed identifier wrapping a UUID; prevents mixing up IDs across contexts.
  • Email — validated email address; enforces format at construction.
  • PersonType — enum of student | instructor | staff | admin. Defined in packages/domain/people; mirrored (not imported — see package-boundaries.md) by packages/shared/permissions for permission computation.

Domain rules

  1. A Person must have at least one contact method (email required).
  2. Email must be unique within the organisation.
  3. Types are additive — a Person can hold multiple Types simultaneously.
  4. An inactive Person cannot be assigned to new Bookings.
  5. A Person cannot be hard-deleted if they have associated Bookings, Lessons, or Charge Items; deactivation is used instead.
  6. The admin Type should only be granted by a caller who already holds it — enforced as a policy check in registerPerson and assignType (application layer), not in the domain functions. Exempt during bootstrap (the very first Person registered, before any admin exists — see setup.astro).

Note: Type here is organisational involvement, not a decoupled Role/permissions system. See the Type vs Role distinction in ubiquitous-language.md.

Key use cases

  • registerPerson — create a new Person with initial type assignment.
  • updatePerson — update profile details.
  • assignType — add a type to an existing Person.
  • removeType — remove a type (subject to constraint checks).
  • deactivatePerson — mark as inactive; prevents future bookings.

Persistence

  • people — one row per Person (id, name, email, phone, status, auth_user_id FK → auth_users, timestamps). No roles/types column here.
  • people_type — reference table listing the valid type codes (student, instructor, staff, admin) with a human-readable label.
  • person_type_assignments — junction table (person_id FK → people, people_type_id FK → people_type, composite primary key) recording which Types a Person currently holds. This is what backs the types array on the domain entity — a Person can have any number of rows here.

Person onboarding flow

New people are created by an admin in the console (/people/new). There is no self-registration. The flow:

  1. Admin fills in name, email, and types (no password).
  2. Server creates the Person record and an auth user with a random placeholder password.
  3. A password-reset token is issued immediately with mode=invite in the redirect URL.
  4. Better Auth’s sendResetPassword hook detects the invite mode and sends an invitation email to the person.
  5. The person clicks the link, sets their own password, and is redirected to /login.

The placeholder password is never revealed. The person always sets their own password on first access. See email architecture for the full flow diagram.

Cross-context relationships

ContextUsage
SchedulingReferences PersonId for booking owner and instructor assignment
TrainingReferences PersonId for student and instructor on a Lesson
BillingReferences PersonId as the invoice recipient
ComplianceReferences PersonId to track currency requirements
NotificationsUses Person contact details (email, phone) for delivery

The People context does not import from any other domain context. It owns identity; other contexts reference it.