# CreatePipeline

## Permission Scope

pipeline

## Overview

Creates a new pipeline. The pipeline is initialized with ACTIVE state. Each pipeline acts as an independent container that will hold its own ordered set of stages and items. The module is consumer-agnostic: `pipelineType` is a free-form identifier declared by the consuming app (optionally validated against the `pipelineTypes` vocabulary injected via `defineModule`), and an initial lane template can be seeded via the `stages` input.

## Business Rules

- Name is required and must be non-empty
- `pipelineType` is required and must be non-empty; when the module is initialized with a `pipelineTypes` vocabulary, the value must be one of it
- Name must be unique per pipelineType across the app (skipped for `locked` pipelines — see below); the module imposes no visibility partitioning, so the name's namespace is global
- Creator (`createdByUserId`) is required; when the `getUser` dep is supplied, the user must exist in user-management. The field is immutable audit metadata — UpdatePipeline cannot change it
- Consumer extension fields declared via `defineModule({ pipeline: { fields } })` pass through the input to the insert untouched
- Pipeline is created with state ACTIVE
- When `stages` is supplied, the lanes are seeded at creation time (ordered left to right) so the board renders immediately; each entry's category must be a valid stage category
- When `locked=true`, the pipeline is a managed board: its lane template is fixed (stage-mutation commands reject it) and the name uniqueness check is skipped because identity is owned by the managing module

## Process Flow

```mermaid
flowchart TD
    A[Receive createPipeline input] --> B{Name non-empty?}
    B -->|No| C[Return INVALID_NAME]
    B -->|Yes| T{pipelineType non-empty and allowed?}
    T -->|No| U[Return INVALID_PIPELINE_TYPE]
    T -->|Yes| V{All seeded stage categories valid?}
    V -->|No| W[Return INVALID_CATEGORY]
    V -->|Yes| O{getUser dep supplied?}
    O -->|Yes| P{createdByUserId exists?}
    P -->|No| Q[Return USER_NOT_FOUND]
    P -->|Yes| D{Locked pipeline?}
    O -->|No| D
    D -->|No| DD{Name unique for pipelineType?}
    DD -->|No| E[Return DUPLICATE_PIPELINE_NAME]
    DD -->|Yes| F[Create pipeline with state ACTIVE]
    D -->|Yes| F
    F --> H{stages supplied?}
    H -->|Yes| I[Seed initial stages]
    H -->|No| G[Return created Pipeline]
    I --> G
```

## External Dependencies

- `user-management.getUser` (optional dep): cross-module existence check for `createdByUserId`

## Error Scenarios

- **INVALID_NAME**: Name is empty or blank
- **INVALID_PIPELINE_TYPE**: pipelineType is empty, or not part of the vocabulary injected via `defineModule({ pipelineTypes })`
- **INVALID_CATEGORY**: Provided category is not a valid enum value
- **USER_NOT_FOUND**: Specified user ID does not exist in the user-management module
- **DUPLICATE_PIPELINE_NAME**: A pipeline with the same name and pipelineType already exists. Skipped for `locked` pipelines (managed board; identity is owned by the managing module).

## Test Cases

- creates pipeline with valid name and creator
- returns error when name is empty
- returns INVALID_PIPELINE_TYPE when pipelineType is empty
- returns INVALID_PIPELINE_TYPE when pipelineType is outside the injected vocabulary
- accepts any pipelineType when no vocabulary is injected
- returns USER_NOT_FOUND when getUser dep returns null for createdByUserId
- returns error when pipeline name already exists for the pipelineType
- allows same pipeline name under a different pipelineType
- passes consumer extension fields through to the insert
- allows duplicate names for locked pipelines (skips uniqueness check)
- seeds initial stages when stages input is supplied
- returns INVALID_CATEGORY when a seeded stage category is invalid
