---
id: STATE-COM-CAT-007
title: Promotion lifecycle
object: DOM-COM-CAT-005
product_area: commerce
module: catalog
revision: 1
status: draft
design_intent: confirmed
implementation_evidence: observed
source_sha:
  services-commerce: c8b66a98e2490617aec38ba5c8165dd9bd0a8f9b
  react-console: baf6dae382d6f41dce976c7f848667ccc37e076f
states:
  - name: draft
    meaning: No live checkout applies the offer; staff can explicitly include it in a sample preview.
  - name: active
    meaning: The offer participates subject to its dates and limits.
  - name: paused
    meaning: The offer stops participating temporarily while codes and usage remain.
  - name: ended
    meaning: 'The offer cannot run again, but its historical records remain.'
  - name: archived
    meaning: The campaign is retained for history and excluded from ordinary lists.
  - name: locked
    meaning: >-
      This separate protection condition fixes designated economic fields after first redemption without
      replacing lifecycle status.
transitions:
  - from: draft
    to: active
    trigger: Staff request campaign activation.
    guard: >-
      Launch authority requires a complete usable offer, benefit currency and selected eligibility rule, an
      active code for code claims, and capacity under the automatic-campaign ceiling.
    side_effects:
      - Promotion history retains the move and supplied reason.
    who: Selling configuration administrator (commerce.promotions.activate)
    provenance: test
    source: >-
      services-commerce:modules/promotions/tests/e2e/promotions.e2e.spec.js#refuses to activate a coded
      promotion with no active code, 409 no_active_code
  - from: active
    to: paused
    trigger: Staff request a temporary campaign pause.
    guard: The caller must hold campaign management authority.
    side_effects:
      - Promotion history retains the move and supplied reason.
    who: Campaign coordinator (commerce.promotions.manage)
    provenance: test
    source: >-
      services-commerce:modules/promotions/tests/e2e/promotions.e2e.spec.js#refuses every operation from a
      state it does not move, 409 invalid_transition
  - from: paused
    to: active
    trigger: Staff request resumption or activation of a paused campaign.
    guard: Launch authority and the campaign activation conditions must pass.
    side_effects:
      - Promotion history retains the move and supplied reason.
    who: Selling configuration administrator (commerce.promotions.activate)
    provenance: test
    source: >-
      services-commerce:modules/promotions/tests/e2e/promotions-security.e2e.spec.js#cannot activate a draft,
      403 permission_missing
  - from: active
    to: ended
    trigger: Staff request permanent campaign ending.
    guard: The caller must hold campaign management authority.
    side_effects:
      - Promotion history retains the move and supplied reason.
    who: Campaign coordinator (commerce.promotions.manage)
    provenance: observed
    source: 'services-commerce:modules/promotions/services/promotions/promotion-rules.ts:PROMOTION_OPERATIONS'
  - from: paused
    to: ended
    trigger: Staff request permanent campaign ending.
    guard: The caller must hold campaign management authority.
    side_effects:
      - Promotion history retains the move and supplied reason.
    who: Campaign coordinator (commerce.promotions.manage)
    provenance: observed
    source: 'services-commerce:modules/promotions/services/promotions/promotion-rules.ts:PROMOTION_OPERATIONS'
  - from: draft
    to: archived
    trigger: Staff archive a prepared campaign.
    guard: The caller must hold campaign management authority.
    side_effects:
      - Promotion history records the move.
    who: Campaign coordinator (commerce.promotions.manage)
    provenance: observed
    source: 'services-commerce:modules/promotions/services/persistence/promotions.ts:PROMOTION_TRANSITIONS'
  - from: ended
    to: archived
    trigger: Staff archive a campaign that has ended.
    guard: The caller must hold campaign management authority.
    side_effects:
      - Promotion history records the move.
    who: Campaign coordinator (commerce.promotions.manage)
    provenance: observed
    source: 'services-commerce:modules/promotions/services/persistence/promotions.ts:PROMOTION_TRANSITIONS'
  - from: active
    to: locked
    trigger: Placement commits the first campaign redemption.
    guard: The placement must successfully acquire promotion capacity.
    side_effects:
      - >-
        First use stamps protection; later protected-field edits receive a field-specific refusal and
        replacement guidance.
    who: Order placement
    provenance: test
    source: >-
      services-commerce:modules/promotions/tests/integration/services/persistence/promotion-redemptions.integration.spec.js#locks
      the promotion at its first redemption
invalid:
  - from: archived
    to: active
    why: Archive has no resumption operation.
  - from: ended
    to: active
    why: Ending requires a new campaign rather than resumption.
  - from: draft
    to: paused
    why: Preparation cannot pause before activation.
  - from: active
    to: draft
    why: The lifecycle has no return to preparation.
concurrency: >-
  Lifecycle moves compare the read version under a row lock. First use sets economic protection, enforced by a
  database trigger against racing edits. Counter updates do not advance the staff edit version.
invariants:
  - INV-DOM-COM-CAT-005-04
  - INV-DOM-COM-CAT-005-05
evidence:
  - >-
    services-commerce at c8b66a98: modules/promotions/services/persistence/promotions.ts
    (PROMOTION_TRANSITIONS, transitionPromotion, duplicatePromotion); services/promotions/promotion-rules.ts
    (PROMOTION_OPERATIONS, activationRefusal); services/promotions/promotion-workflow.ts (movePromotion);
    db/promotions.table.ts.
  - 'services-commerce at c8b66a98: docs/milestones/plans/COMM-M1-plan.md (scope item 6, A15, A16).'
---

# Promotion lifecycle

## The Lifecycle

```mermaid
stateDiagram-v2
  [*] --> draft: created or duplicated
  draft --> active: activate (launch permission)
  active --> paused: pause
  paused --> active: resume or activate (launch permission)
  active --> ended: end
  paused --> ended: end
  draft --> archived: archive
  ended --> archived: archive
  archived --> [*]
  note right of active: first redemption locks economic terms
```

## Transitions Explained

Draft: No live checkout applies the offer; staff can explicitly include it in a sample preview. Active: The offer participates subject to its dates and limits. Paused: The offer stops participating temporarily while codes and usage remain. Ended: The offer cannot run again, but its historical records remain. Archived: The campaign is retained for history and excluded from ordinary lists. Protection is an independent condition and does not replace lifecycle status.

## What Cannot Happen

Archive has no resumption operation. Ending requires a new campaign rather than resumption. Preparation cannot pause before activation. The lifecycle has no return to preparation.

## Concurrency and Timing

Lifecycle moves compare the read version under a row lock. First use sets economic protection, enforced by a database trigger against racing edits. Counter updates do not advance the staff edit version.
