# UpdatePipelineItem

## Permission Scope

item

## Overview

Updates the mutable fields of an existing item: title, description, assigneeId, priority, dueDate, and position. Only provided fields are updated; omitted fields remain unchanged. Closed items cannot be updated.

For audit history, the command also writes:

- One [PipelineItemChange](../model/PipelineItemChange.md) row per content field (`TITLE`, `DESCRIPTION`) that actually changes.
- One [PipelineItemChange](../model/PipelineItemChange.md) row per scalar field (`ASSIGNEE`, `PRIORITY`, `DUE_DATE`) that actually changes. An update that flips multiple tracked fields produces multiple rows.

`position`-only updates do not produce any audit row.

## Business Rules

- PipelineItem must exist
- PipelineItem must not be CLOSED
- If title is provided, it must be non-empty
- When the `getUser` dep is supplied and a non-null `assigneeId` is provided, the assignee must exist in user-management (`assigneeId: null` clears the assignee and needs no check)
- Only mutable fields (title, description, assigneeId, priority, dueDate, position) can be changed
- A provided `priority` must be one of LOW, MEDIUM, HIGH, or URGENT; any other value is rejected with INVALID_PRIORITY
- Consumer extension fields (added via the `fields` extension point) are passed through to the update; they produce no audit rows. Module-managed columns (`id`, `pipelineId`, `stageId`, `itemNumber`, `lifecycle`, timestamps) are stripped and cannot be set this way
- `pipelineId` and `stageId` are not directly updatable through this command (use MovePipelineItem to change the stage)
- A PipelineItemChange is appended per content field (title/description) when its value differs from the current value
- A PipelineItemChange is appended per tracked scalar field (assignee/priority/dueDate) when its value differs from the current value

## Process Flow

```mermaid
flowchart TD
    A[Receive updatePipelineItem input] --> B{PipelineItem exists?}
    B -->|No| C[Return ITEM_NOT_FOUND]
    B -->|Yes| D{PipelineItem CLOSED?}
    D -->|Yes| E[Return ITEM_CLOSED]
    D -->|No| F{Title provided?}
    F -->|Yes| G{Title non-empty?}
    G -->|No| H[Return INVALID_TITLE]
    G -->|Yes| R{Priority valid or omitted?}
    F -->|No| R
    R -->|No| S[Return INVALID_PRIORITY]
    R -->|Yes| O{getUser dep supplied and non-null assigneeId provided?}
    O -->|Yes| P{Assignee exists?}
    P -->|No| Q[Return USER_NOT_FOUND]
    P -->|Yes| I[Update item fields]
    O -->|No| I
    I --> J[Return updated PipelineItem]
```

## External Dependencies

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

## Error Scenarios

- **ITEM_NOT_FOUND**: Specified item ID does not exist
- **ITEM_CLOSED**: PipelineItem is closed and cannot be updated
- **INVALID_TITLE**: PipelineItem title is empty or blank
- **INVALID_PRIORITY**: Provided priority is not a valid enum value
- **USER_NOT_FOUND**: Specified user ID does not exist in the user-management module

## Test Cases

- updates item title
- passes consumer extension fields through to the update
- passes extension fields through but strips reserved model columns
- updates item description
- updates item assignee
- updates item priority
- updates item due date
- updates item position within stage
- writes a PipelineItemChange when title changes
- writes a PipelineItemChange when description changes
- does not write a PipelineItemChange when neither title nor description changes
- writes a PipelineItemChange when assignee changes
- writes a PipelineItemChange when priority changes
- writes a PipelineItemChange when due date changes
- does not write a PipelineItemChange when the field value is unchanged
- returns error when priority is invalid
- returns error when item not found
- returns error when item is closed
- returns error when title is empty
- returns USER_NOT_FOUND when getUser dep returns null for assigneeId
- clearing assignee via null skips the getUser check
