Person vs Group

The Identity kernel contains two core entities:

Person

An individual user. Has authentication credentials, personal settings, and owns private data.

Group

A collection of people who share data. Could be a family, team, or organization. Members have roles.

Every component that stores data references either a Person or Group—never both, and never neither. This is the owner_id pattern.

The owner_id Pattern

Every entity carries an owner_id: a UUID pointing to either a Person or Group in the Identity kernel. This single field enables:

  • Multi-tenancy — Data is scoped by owner; no leakage between users
  • Sharing — Same entity structure works for private (Person) and shared (Group) data
  • Querying — Every repository can filter by owner_id
task.py
@dataclass
class Task:
    id: UUID
    owner_id: UUID      # Person or Group
    title: str
    status: TaskStatus
    container_id: UUID  # Which list/project
    ...

A task owned by a Person is private. The same task structure owned by a Group becomes shared—visible to all group members. The Todos component doesn't distinguish; it just filters by owner_id.

Multi-Tenancy Without Complexity

Components don't know whether they're serving a Person or Group. They just:

  1. Receive owner_id in API requests
  2. Store it on every entity they create
  3. Filter by it on every query

Authorization (can this user access this owner_id?) is handled by Policy, not by individual components.

Ownership Model
%%{init: {"flowchart": {"useMaxWidth": false}}}%% graph LR PERSON["Person"] GROUP["Group"] TASK["Task
owner_id: UUID"] PLAN["BudgetPlan
owner_id: UUID"] TASK -.->|owner_id| PERSON TASK -.->|owner_id| GROUP PLAN -.->|owner_id| PERSON PLAN -.->|owner_id| GROUP style PERSON fill:#f3e8ff,stroke:#9333ea style GROUP fill:#f3e8ff,stroke:#9333ea style TASK fill:#ecfeff,stroke:#0891b2 style PLAN fill:#fef3c7,stroke:#d97706
Rules
  • All entities MUST have owner_id
  • owner_id is the query scope boundary—always filter by it
  • Never expose entities across owner boundaries without explicit sharing
  • The Identity kernel owns Person and Group; components only store references
Pitfalls
  • Don't treat owner_id as auth — Use Policy to check if the current user can access the owner_id
  • Don't branch on Person vs Group — Your component shouldn't care which type it is
  • Don't store Person/Group details — Only store the UUID; fetch details from Identity if needed