Status: DRAFT Authors: PPM, CXO Date: February 26, 2026 Supersedes: None Related: ADR-045 (Object Model), PDR-001 (FTUX), PDR-002 (Conversational Glue)
This PDR defines the concept model for three core entities — Product, Project, and Repository — establishing their meanings, relationships, and UX surfacing strategy. The goal is to align domain model, database schema, and user mental models into a coherent whole.
ProjectIntegration, requiring a project_id FK. This prevents:
Project extends Product in some places (inheritance)Definition: The thing you’re building and shipping. The whole. What users/customers experience.
User’s mental model:
Typical PM questions about a Product:
Domain characteristics:
Definition: A bounded effort with intent. A workstream, a release, a feature initiative, an epic.
User’s mental model:
Typical PM questions about a Project:
Domain characteristics:
Definition: Where code lives. A technical artifact that may or may not map 1:1 to products or projects.
User’s mental model:
Typical PM questions about a Repository:
Domain characteristics:
| Scenario | Example |
|---|---|
| One product, one project | Solo PM: “Piper Morgan” with “Current Work” |
| One product, many projects | “Piper Morgan” with M0, M1, Website, Content |
| Many products, one project | Platform work serving iOS and Android products |
| Many products, many projects | Enterprise portfolio |
Implementation: product_projects join table
products
├── id
├── name
├── vision
└── ...
product_projects (join table)
├── product_id (FK)
└── project_id (FK)
projects
├── id
├── name
├── description
└── ...
| Scenario | Example |
|---|---|
| One project, one repo | Website project uses website repo |
| One project, many repos | M0 touches platform and web repos |
| Many projects, one repo | Monorepo serves all projects |
| Many projects, many repos | Microservices architecture |
Implementation: project_repositories join table
projects
├── id
└── ...
project_repositories (join table)
├── project_id (FK)
└── repository_id (FK)
repositories (NEW first-class entity)
├── id
├── name
├── url
├── provider (github, gitlab, etc.)
├── default_branch
├── owner_id (FK to users)
└── ...
Products don’t directly own repositories. The relationship is derived through Projects:
Product → Projects → Repositories
This matches user mental models: “Which repos are part of my product?” = “Which repos do my product’s projects use?”
Most users have simple relationships. Don’t force complexity upfront.
Changes:
User experience:
What’s NOT surfaced: Product concept remains hidden
Changes:
User experience:
Changes:
User experience:
Changes:
User experience:
@dataclass
class Repository:
"""A code repository — first-class entity"""
id: str = field(default_factory=lambda: str(uuid4()))
name: str = ""
url: str = ""
provider: str = "github" # github, gitlab, bitbucket
default_branch: str = "main"
owner_id: str = "" # User who connected it
is_active: bool = True
created_at: datetime = field(default_factory=datetime.now)
updated_at: datetime = field(default_factory=datetime.now)
# Derived/cached fields
last_synced_at: Optional[datetime] = None
# Relationships
projects: List["Project"] = field(default_factory=list)
@dataclass
class Project:
"""A PM project — bounded effort with intent"""
id: str = field(default_factory=lambda: str(uuid4()))
name: str = ""
description: str = ""
owner_id: str = ""
is_default: bool = False
is_archived: bool = False
created_at: datetime = field(default_factory=datetime.now)
updated_at: datetime = field(default_factory=datetime.now)
# Relationships
products: List["Product"] = field(default_factory=list) # NEW
repositories: List["Repository"] = field(default_factory=list) # NEW
integrations: List["ProjectIntegration"] = field(default_factory=list)
@dataclass
class Product:
"""A product being managed — the thing you ship"""
id: str = field(default_factory=lambda: str(uuid4()))
name: str = ""
vision: str = ""
strategy: str = ""
owner_id: str = "" # NEW
is_default: bool = False # NEW
is_archived: bool = False # NEW
created_at: datetime = field(default_factory=datetime.now)
updated_at: datetime = field(default_factory=datetime.now)
# Relationships
projects: List["Project"] = field(default_factory=list) # NEW
features: List["Feature"] = field(default_factory=list)
stakeholders: List["Stakeholder"] = field(default_factory=list)
metrics: List["Metric"] = field(default_factory=list)
work_items: List["WorkItem"] = field(default_factory=list)
The current inheritance (class Project(Product)) should be removed. Project and Product are distinct concepts with a many-to-many relationship, not an inheritance hierarchy.
repositories tableproject_repositories join tableProjectIntegration GitHub configs to Repository entitiesproduct_projects join tableowner_id, is_default, is_archived to ProductProject extends Product inheritanceApplying this model to our own usage:
| Entity | Instance |
|---|---|
| Product | Piper Morgan |
| Projects | M0, M1, Website, Content/Newsletter, Alpha Program |
| Repositories | piper-morgan-platform, piper-morgan-web, pipermorgan.ai |
Relationships:
This confirms the model handles real-world complexity.
Should Products have parent/child relationships (product hierarchy)?
Current position: No. Keep it flat for MVP. Hierarchy adds complexity without clear user demand.
If a repo is shared across projects with different owners, who “owns” the repo?
Current position: The user who connected it owns it. Project-level permissions are separate from repo ownership.
What happens to existing ProjectIntegration records with GitHub configs?
Current position: Migrate to Repository entities. Keep ProjectIntegration for non-repo integrations (Slack, Calendar, etc.).
| Metric | Target | Measurement |
|---|---|---|
| Repo connection success rate | >90% | Setup completion analytics |
| Multi-project repo usage | >20% of repos | Join table cardinality |
| Product feature adoption | >10% of users (Phase 2+) | Settings page usage |
| Piper product suggestions accepted | >50% (Phase 3+) | Suggestion acceptance rate |
PDR-003 v1.0 — February 26, 2026