# README

## Overview

The Pipeline Management module provides a generic, domain-agnostic engine for pipeline-based workflow management. It manages pipelines (named containers of stages and items), configurable stages per pipeline, items (work records that move through stages), stage transition history, and item comments. The module is designed to be composed by domain modules — a consuming module creates its own pipelines and extends items with domain-specific fields and metadata. Approval workflows live on the consuming domain entities; the module itself does not implement approval logic.

The module does not know about any domain-specific concepts. It only knows about pipelines, stages, and items. Domain modules compose with it in two ways: (1) **field extension** — every model is exposed as a DB type factory (`createPipelineItemType({ fields })`, `createPipelineType({ fields })`, …) following the erp-kit built-in module convention, so consumers can add their own columns to module-owned tables via `defineModule({ pipelineItem: { fields: { … } } })`; and (2) **composition by FK** — domain modules keep their own tables and hold a foreign key to PipelineItem when the domain data deserves a separate entity.

## Key Features

- **[Pipeline Lifecycle](docs/feature/pipeline-lifecycle.md)**: Create and manage named containers with lifecycle (ACTIVE/ARCHIVED)
- **[Configurable Stages](docs/feature/configurable-stages.md)**: Define ordered stages per pipeline with position, category (NOT_STARTED/IN_PROGRESS/DONE/CANCELED), and optional color
- **[PipelineItem Lifecycle](docs/feature/item-lifecycle.md)**: Domain-agnostic work records with title, description, assignee, priority, due date, position (for visual ordering within stages), explicit lifecycle state (DRAFT/OPEN/CLOSED), and reopen support
- **[PipelineStage Transition History](docs/feature/stage-transition-history.md)**: Append-only audit trail of all item stage movements for analytics and compliance
- **[PipelineItem History](docs/feature/item-history.md)**: Append-only audit log of non-stage item changes (content edits, field changes, lifecycle transitions, label attach/detach) for timeline reconstruction
- **[PipelineItem Comment](docs/feature/item-comment.md)**: Chronological discussion thread per item; consuming modules layer visibility rules on top
- **[Labels](docs/feature/labels.md)**: Free-form, pipeline-scoped classification tags that can be attached to any number of items within the same pipeline

## Module Scope

### In Scope

- Pipeline CRUD with ACTIVE/ARCHIVED lifecycle and per-pipelineType name uniqueness
- Per-pipeline stage definitions with ordering (position integer), categories, and color
- PipelineItem CRUD with title, description, assignee, priority (LOW/MEDIUM/HIGH/URGENT), due date, and position (for drag-and-drop ordering within stages)
- Explicit item lifecycle state (DRAFT/OPEN/CLOSED) orthogonal to stage category
- PipelineItem reopen from CLOSED back to OPEN
- PipelineItem stage transitions with automatic history recording
- PipelineStage transition history as immutable append-only records
- PipelineItem auto-placement in first stage (lowest position) on creation
- PipelineItem comments with edit/delete by original author (no visibility scoping at this layer)
- Pipeline-scoped Labels with CRUD, attach/detach to Items, and many-to-many join via PipelineItemLabel
- DB type factories (`create<Model>Type(params)`) with a `fields` extension point for every model, so consumers can extend module-owned tables without forking the module
- Read queries over the module's own tables that encode module business rules (existence checks, ordering): `listPipelineStagesByPipeline`, `listPipelineItemsByStage`, `listPipelineStageTransitionsByItem`, `listPipelineItemCommentsByItem`, `listPipelineLabelsByPipeline`, `listPipelineLabelsByItem`

### Out of Scope

- Domain-specific fields — owned by consuming modules
- Transition rules (restricting allowed stage-to-stage moves) — future enhancement
- WIP limits per stage — future enhancement
- Pipeline templates for quick pipeline cloning — future enhancement
- Automations (on-enter/on-exit stage triggers) — handled at app layer
- SLA tracking and escalation — domain-specific, owned by consuming modules
- Swimlanes and item grouping — UI concern, not module responsibility
- PipelineItem types / multiple schemas per pipeline — future enhancement
- Organizational scoping (company, project, workspace) and visibility partitioning — apps deploy single-tenant, and a board's organizational attribution is an app/consumer decision; consumers add their own FK extension fields (e.g. `defineModule({ pipeline: { fields: { teamId: db.uuid() } } })`) and enforce visibility at the consuming layer by filtering on that field in their own queries/resolvers
- Comment visibility scoping (internal vs external) — domain-specific, owned by consuming modules
- Comment attachments — future enhancement
- Real-time collaboration / WebSocket events — handled outside the module
- Basic single-record / list reads (`getPipeline`, `getPipelineItem`, `listPipelineItems`, …) — served by the auto-generated tailordb GraphQL CRUD API; the module does not duplicate them as imperative queries
- Cross-domain read aggregations (pipeline dashboards, KPI / funnel analytics joining domain tables) — owned by app-layer resolvers

### Scope Decision Rationale

The module focuses on the core pipeline engine that is reusable across business domains. Features like transition rules, WIP limits, and templates are valuable but not required for the initial use cases. They can be added incrementally without breaking the core model. Domain-specific logic is explicitly excluded to maintain the module's reusability — consuming modules compose on top of the generic PipelineItem entity rather than polluting the pipeline engine with domain knowledge. PipelineItem comments are included because chronological discussion is a near-universal need on work items; however, comment visibility scoping (internal vs externally visible) is deferred to consuming modules to keep this layer domain-agnostic.

The read-query boundary follows the same reusability principle. The module owns a read query only when it encodes a module business rule over module-owned tables (parent existence validation plus canonical ordering — the six `list*` queries above). Plain record access is intentionally left to the auto-generated tailordb GraphQL CRUD API, so the module does not mirror it with `getPipeline` / `getPipelineItem`-style queries. Reads that join module tables with domain tables (dashboards, stage analytics) belong to app-layer resolvers because they require domain knowledge this module must not have. When an app exposes a module query over GraphQL, the resolver must stay a thin adapter that calls the query through the module's `defineModule` surface (`queries.*`) rather than re-implementing or deep-importing it, keeping the module as the single owner of the read logic.

## Module Dependencies

- [user-management](../user-management/README.md) — pipeline creator, item assignment, comment authorship, and audit actors (`Pipeline.createdByUserId`, `PipelineItem.assigneeId`, `PipelineItemComment.authorUserId`, `PipelineItemChange.changedByUserId`, `PipelineStageTransition.movedByUserId`); the real User type is injected via `defineModule({ userManagement: { db: { user } } })`

Beyond the per-model `fields` extension points, `defineModule` accepts two wiring options:

- `pipelineTypes?: readonly string[]` — the allowed `pipelineType` vocabulary, declared by the consuming app (e.g. `["TASK_BOARD", "SUPPORT_QUEUE"]`). The module itself is type-agnostic; when omitted, `createPipeline` accepts any non-empty string.
- `userManagement.getUser?: (id) => Promise<{ id } | null>` — optional user-existence check. When provided, `createPipeline` validates `createdByUserId` and `createPipelineItem` / `updatePipelineItem` validate `assigneeId`, failing with `USER_NOT_FOUND` for unknown users. When omitted, the IDs are stored as-is.
