---
id: ATS-COM-CAT-017
scope: CAP-COM-CAT-017
product_area: commerce
module: catalog
revision: 2
status: proposed
source_sha:
  services-commerce: c8b66a98e2490617aec38ba5c8165dd9bd0a8f9b
  react-console: baf6dae382d6f41dce976c7f848667ccc37e076f
scenarios:
  - id: ATS-COM-CAT-017-01
    title: Redemption retains terms and establishes usage
    level: api
    proves:
      - BR-CAP-COM-CAT-017-01
    preconditions: A code promotion has no previous redemption
    steps: Place a qualifying order
    expected: 'Usage increments, terms are retained, the promotion locks and history records application'
    negative: false
    data_needs: none
    bound_to:
      - TST-COM-CAT-110
    provenance: test
    source: >-
      services-commerce:modules/promotions/tests/integration/services/persistence/promotion-redemptions.integration.spec.js#counts
      one use on the code and one on the promotion
    category: rule
    binding_evidence:
      - >-
        services-commerce:modules/promotions/tests/integration/services/persistence/promotion-redemptions.integration.spec.js#counts
        one use on the code and one on the promotion
      - >-
        services-commerce:modules/promotions/tests/integration/services/persistence/promotion-redemptions.integration.spec.js#freezes
        the promotion’s configuration and the code in the snapshot
      - >-
        services-commerce:modules/promotions/tests/integration/services/persistence/promotion-redemptions.integration.spec.js#locks
        the promotion at its first redemption
      - >-
        services-commerce:modules/promotions/tests/integration/services/persistence/promotion-redemptions.integration.spec.js#appends
        redemption.applied to the promotion’s history
    step_evidence:
      - do: Place a qualifying order
        provenance: test
        source: >-
          services-commerce:modules/promotions/tests/integration/services/persistence/promotion-redemptions.integration.spec.js#counts
          one use on the code and one on the promotion
    assertions:
      - expect: 'Usage increments, terms are retained, the promotion locks and history records application'
        provenance: test
        source: >-
          services-commerce:modules/promotions/tests/integration/services/persistence/promotion-redemptions.integration.spec.js#counts
          one use on the code and one on the promotion
  - id: ATS-COM-CAT-017-02
    title: Commit rejects allocation mismatch and duplicate redemption
    level: api
    proves:
      - BR-CAP-COM-CAT-017-02
      - INV-DOM-COM-CAT-005-11
    preconditions: Order and promotion fixtures support redemption insertion
    steps: Commit mismatched allocations and a second redemption for the same order and promotion
    expected: Both commits fail
    negative: true
    data_needs: none
    bound_to:
      - TST-COM-CAT-099
    provenance: test
    source: >-
      services-commerce:modules/promotions/tests/integration/db/redemptions.table.integration.spec.js#refuses
      at COMMIT a redemption whose discount is not its allocations’ sum, naming the redemption
    category: invariant
    binding_evidence:
      - >-
        services-commerce:modules/promotions/tests/integration/db/redemptions.table.integration.spec.js#refuses
        at COMMIT a redemption whose discount is not its allocations’ sum, naming the redemption
      - >-
        services-commerce:modules/promotions/tests/integration/db/redemptions.table.integration.spec.js#refuses
        at COMMIT a line whose discount is not its allocations’ sum, SQLSTATE RHC07, naming the line
      - >-
        services-commerce:modules/promotions/tests/integration/db/redemptions.table.integration.spec.js#refuses
        a second redemption of the promotion on the order, by
        promotion_redemptions_order_promotion_uq
    step_evidence:
      - do: Commit mismatched allocations and a second redemption for the same order and promotion
        provenance: test
        source: >-
          services-commerce:modules/promotions/tests/integration/db/redemptions.table.integration.spec.js#refuses
          at COMMIT a redemption whose discount is not its allocations’ sum, naming the redemption
    assertions:
      - expect: Both commits fail
        provenance: test
        source: >-
          services-commerce:modules/promotions/tests/integration/db/redemptions.table.integration.spec.js#refuses
          at COMMIT a redemption whose discount is not its allocations’ sum, naming the redemption
  - id: ATS-COM-CAT-017-03
    title: Redemption status cannot return from released
    level: api
    proves:
      - BR-CAP-COM-CAT-017-03
      - INV-DOM-COM-CAT-005-12
    preconditions: An applied redemption exists
    steps: 'Change its amount, release it and restore applied status'
    expected: The amount and reversal fail while release succeeds
    negative: true
    data_needs: none
    bound_to:
      - TST-COM-CAT-099
    provenance: test
    source: >-
      services-commerce:modules/promotions/tests/integration/db/redemptions.table.integration.spec.js#refuses
      a change of the discount, SQLSTATE RHC04, the row untouched
    category: invariant
    binding_evidence:
      - >-
        services-commerce:modules/promotions/tests/integration/db/redemptions.table.integration.spec.js#refuses
        a change of the discount, SQLSTATE RHC04, the row untouched
      - >-
        services-commerce:modules/promotions/tests/integration/db/redemptions.table.integration.spec.js#lets
        an applied redemption be released with its time and reason
      - >-
        services-commerce:modules/promotions/tests/integration/db/redemptions.table.integration.spec.js#refuses
        moving a released redemption back to applied, SQLSTATE RHC04
    step_evidence:
      - do: 'Change its amount, release it and restore applied status'
        provenance: test
        source: >-
          services-commerce:modules/promotions/tests/integration/db/redemptions.table.integration.spec.js#refuses
          a change of the discount, SQLSTATE RHC04, the row untouched
    assertions:
      - expect: The amount and reversal fail while release succeeds
        provenance: test
        source: >-
          services-commerce:modules/promotions/tests/integration/db/redemptions.table.integration.spec.js#refuses
          a change of the discount, SQLSTATE RHC04, the row untouched
  - id: ATS-COM-CAT-017-04
    title: Release restores capacity once with attribution
    level: api
    proves:
      - BR-CAP-COM-CAT-017-04
      - AS-JNY-COM-CAT-005-03
    preconditions: A single-use code has been redeemed
    steps: 'Release with a reason, repeat and omit the reason on another request'
    expected: The first restores usage and records both histories; repeat and missing-reason requests fail
    negative: true
    data_needs: Planted order
    bound_to:
      - TST-COM-CAT-094
      - TST-COM-CAT-110
    provenance: test
    source: >-
      services-commerce:modules/promotions/tests/e2e/promotion-redemptions.e2e.spec.js#gives the use
      back on the code
    category: transition
    binding_evidence:
      - >-
        services-commerce:modules/promotions/tests/e2e/promotion-redemptions.e2e.spec.js#gives the
        use back on the code
      - >-
        services-commerce:modules/promotions/tests/e2e/promotion-redemptions.e2e.spec.js#records
        order.promotion_released on the order, with the actor and the reason
      - >-
        services-commerce:modules/promotions/tests/e2e/promotion-redemptions.e2e.spec.js#refuses the
        second release, 409 already_released
      - >-
        services-commerce:modules/promotions/tests/e2e/promotion-redemptions.e2e.spec.js#refuses a
        release without a reason, 400
      - >-
        services-commerce:modules/promotions/tests/integration/services/persistence/promotion-redemptions.integration.spec.js#leaves
        the single-use code usable again
    step_evidence:
      - do: 'Release with a reason, repeat and omit the reason on another request'
        provenance: test
        source: >-
          services-commerce:modules/promotions/tests/e2e/promotion-redemptions.e2e.spec.js#gives the
          use back on the code
    assertions:
      - expect: >-
          The first restores usage and records both histories; repeat and missing-reason requests
          fail
        provenance: test
        source: >-
          services-commerce:modules/promotions/tests/e2e/promotion-redemptions.e2e.spec.js#gives the
          use back on the code
  - id: ATS-COM-CAT-017-05
    title: Cancellation release uses retained policy
    level: api
    proves:
      - BR-CAP-COM-CAT-017-05
    preconditions: Orders retain enabled and disabled cancellation-release settings
    steps: 'Fully cancel each, partially cancel another and refund another'
    expected: Only full cancellation under the enabled setting restores usage
    negative: false
    data_needs: Orders with cancellations
    bound_to: []
    provenance: proposed
    source: >-
      services-commerce:docs/milestones/plans/COMM-M1-plan.md#D13. Restore a personal code when its
      order is cancelled?
    category: transition
    step_evidence:
      - do: 'Fully cancel each, partially cancel another and refund another'
        provenance: proposed
        source: >-
          services-commerce:docs/milestones/plans/COMM-M1-plan.md#D13. Restore a personal code when
          its order is cancelled?
    assertions:
      - expect: Only full cancellation under the enabled setting restores usage
        provenance: proposed
        source: >-
          services-commerce:docs/milestones/plans/COMM-M1-plan.md#D13. Restore a personal code when
          its order is cancelled?
  - id: ATS-COM-CAT-017-06
    title: References preserve redeemed parent records
    level: api
    proves:
      - BR-CAP-COM-CAT-017-06
      - INV-DOM-COM-CAT-005-13
    preconditions: 'A redemption references an order, promotion and code'
    steps: Attempt to remove those parents
    expected: Each deletion fails
    negative: true
    data_needs: none
    bound_to:
      - TST-COM-CAT-099
    provenance: test
    source: >-
      services-commerce:modules/promotions/tests/integration/db/redemptions.table.integration.spec.js#refuses
      deleting an order a redemption was earned on, by promotion_redemptions_order_fk (RESTRICT)
    category: invariant
    binding_evidence:
      - >-
        services-commerce:modules/promotions/tests/integration/db/redemptions.table.integration.spec.js#refuses
        deleting an order a redemption was earned on, by promotion_redemptions_order_fk (RESTRICT)
      - >-
        services-commerce:modules/promotions/tests/integration/db/redemptions.table.integration.spec.js#refuses
        deleting a promotion a redemption names, by promotion_redemptions_promotion_fk (RESTRICT)
      - >-
        services-commerce:modules/promotions/tests/integration/db/redemptions.table.integration.spec.js#refuses
        deleting a code a redemption names, by promotion_redemptions_code_fk (RESTRICT)
    step_evidence:
      - do: Attempt to remove those parents
        provenance: test
        source: >-
          services-commerce:modules/promotions/tests/integration/db/redemptions.table.integration.spec.js#refuses
          deleting an order a redemption was earned on, by promotion_redemptions_order_fk (RESTRICT)
    assertions:
      - expect: Each deletion fails
        provenance: test
        source: >-
          services-commerce:modules/promotions/tests/integration/db/redemptions.table.integration.spec.js#refuses
          deleting an order a redemption was earned on, by promotion_redemptions_order_fk (RESTRICT)
  - id: ATS-COM-CAT-017-07
    title: Performance sums applied monetary benefits
    level: api
    proves:
      - BR-CAP-COM-CAT-017-07
    preconditions: Two USD redemptions discount five and three dollars under a hundred-use limit
    steps: Read promotion performance
    expected: 'The report shows two uses, ninety-eight remaining and eight dollars discounted'
    negative: false
    data_needs: Planted redemptions
    bound_to:
      - TST-COM-CAT-094
      - TST-COM-CAT-112
    provenance: test
    source: >-
      services-commerce:modules/promotions/tests/e2e/promotion-redemptions.e2e.spec.js#answers the
      uses, what is left of the limit of 100 and the money of the two uses planted: $5.00 and $3.00
      off, in USD
    category: rule
    binding_evidence:
      - >-
        services-commerce:modules/promotions/tests/e2e/promotion-redemptions.e2e.spec.js#answers the
        uses, what is left of the limit of 100 and the money of the two uses planted: $5.00 and
        $3.00 off, in USD
      - >-
        services-commerce:modules/promotions/tests/integration/services/persistence/promotion-statistics.queries.integration.spec.js#sums
        the discount given of the applied redemptions only: $5.00 and $3.00, not the released $10.00
    step_evidence:
      - do: Read promotion performance
        provenance: test
        source: >-
          services-commerce:modules/promotions/tests/e2e/promotion-redemptions.e2e.spec.js#answers
          the uses, what is left of the limit of 100 and the money of the two uses planted: $5.00
          and $3.00 off, in USD
    assertions:
      - expect: 'The report shows two uses, ninety-eight remaining and eight dollars discounted'
        provenance: test
        source: >-
          services-commerce:modules/promotions/tests/e2e/promotion-redemptions.e2e.spec.js#answers
          the uses, what is left of the limit of 100 and the money of the two uses planted: $5.00
          and $3.00 off, in USD
  - id: ATS-COM-CAT-017-08
    title: Only override authority permits release
    level: api
    proves:
      - BR-CAP-COM-CAT-017-09
    preconditions: 'Callers separately hold manage, activate and override grants'
    steps: Attempt redemption release with each
    expected: Only override succeeds
    negative: true
    data_needs: Personas by key
    bound_to:
      - TST-COM-CAT-095
      - TST-COM-CAT-004
    provenance: test
    source: >-
      services-commerce:modules/promotions/tests/e2e/promotions-security.e2e.spec.js#cannot release
      a redemption, 403 permission_missing
    category: permission
    binding_evidence:
      - >-
        services-commerce:modules/promotions/tests/e2e/promotions-security.e2e.spec.js#cannot
        release a redemption, 403 permission_missing
      - >-
        services-commerce:modules/promotions/tests/e2e/promotions-security.e2e.spec.js#releases a
        redemption
      - >-
        react-console:components/commerce/__tests__/permission-gating.test.tsx#puts Release Use
        behind releasing a redemption, not applying a code
    step_evidence:
      - do: Attempt redemption release with each
        provenance: test
        source: >-
          services-commerce:modules/promotions/tests/e2e/promotions-security.e2e.spec.js#cannot
          release a redemption, 403 permission_missing
    assertions:
      - expect: Only override succeeds
        provenance: test
        source: >-
          services-commerce:modules/promotions/tests/e2e/promotions-security.e2e.spec.js#cannot
          release a redemption, 403 permission_missing
  - id: ATS-COM-CAT-017-09
    title: First redemption changes which promotion edits remain possible
    category: interaction
    level: api
    proves:
      - BR-CAP-COM-CAT-014-12
      - BR-CAP-COM-CAT-017-01
    preconditions: An unused active promotion is editable.
    steps: 'Place its first qualifying sale, change its benefit and then change its display name.'
    expected: 'Usage is retained, economic editing refuses and the display-name edit succeeds.'
    negative: false
    data_needs: Run-owned workspace and records; fixed clock where dates are exercised.
    bound_to: []
    provenance: proposed
    source:
      - >-
        services-commerce:modules/promotions/tests/e2e/promotion-redemptions.e2e.spec.js#names the
        column and offers to end the promotion and duplicate it
      - >-
        services-commerce:modules/promotions/tests/integration/services/persistence/promotion-redemptions.integration.spec.js#locks
        the promotion at its first redemption
    exercises:
      - CAP-COM-CAT-014
      - CAP-COM-CAT-017
    workflows: []
    step_evidence:
      - do: 'Place its first qualifying sale, change its benefit and then change its display name.'
        provenance: proposed
        source:
          - >-
            services-commerce:modules/promotions/tests/e2e/promotion-redemptions.e2e.spec.js#names
            the column and offers to end the promotion and duplicate it
          - >-
            services-commerce:modules/promotions/tests/integration/services/persistence/promotion-redemptions.integration.spec.js#locks
            the promotion at its first redemption
    assertions:
      - expect: 'Usage is retained, economic editing refuses and the display-name edit succeeds.'
        provenance: proposed
        source:
          - >-
            services-commerce:modules/promotions/tests/e2e/promotion-redemptions.e2e.spec.js#names
            the column and offers to end the promotion and duplicate it
          - >-
            services-commerce:modules/promotions/tests/integration/services/persistence/promotion-redemptions.integration.spec.js#locks
            the promotion at its first redemption
  - id: ATS-COM-CAT-017-10
    title: Releasing usage restores capacity without erasing terms
    category: interaction
    level: api
    proves:
      - BR-CAP-COM-CAT-015-08
      - BR-CAP-COM-CAT-015-11
      - BR-CAP-COM-CAT-017-04
      - BR-CAP-COM-CAT-017-05
    preconditions: A personal single-use first code has one applied redemption.
    steps: >-
      Attempt to lower its limit; release with override and a reason; read counts and frozen
      allocations.
    expected: >-
      The invalid limit change refuses; release restores capacity while retaining the discount
      evidence.
    negative: false
    data_needs: Run-owned workspace and records; fixed clock where dates are exercised.
    bound_to: []
    provenance: proposed
    source:
      - >-
        services-commerce:modules/promotions/tests/e2e/promotion-codes.e2e.spec.js#refuses a limit
        below the uses made, 409 limit_below_uses
      - >-
        services-commerce:modules/promotions/tests/integration/services/persistence/promotion-codes.integration.spec.js#turns
        release on cancellation on when the first code is personal and single-use, and says so in
        the history
      - >-
        services-commerce:modules/promotions/tests/e2e/promotion-redemptions.e2e.spec.js#refuses a
        release without a reason, 400
      - >-
        services-commerce:docs/milestones/plans/COMM-M1-plan.md#D13. Restore a personal code when
        its order is cancelled?
    exercises:
      - CAP-COM-CAT-015
      - CAP-COM-CAT-017
    workflows: []
    step_evidence:
      - do: >-
          Attempt to lower its limit; release with override and a reason; read counts and frozen
          allocations.
        provenance: proposed
        source:
          - >-
            services-commerce:modules/promotions/tests/e2e/promotion-codes.e2e.spec.js#refuses a
            limit below the uses made, 409 limit_below_uses
          - >-
            services-commerce:modules/promotions/tests/integration/services/persistence/promotion-codes.integration.spec.js#turns
            release on cancellation on when the first code is personal and single-use, and says so
            in the history
          - >-
            services-commerce:modules/promotions/tests/e2e/promotion-redemptions.e2e.spec.js#refuses
            a release without a reason, 400
          - >-
            services-commerce:docs/milestones/plans/COMM-M1-plan.md#D13. Restore a personal code
            when its order is cancelled?
    assertions:
      - expect: >-
          The invalid limit change refuses; release restores capacity while retaining the discount
          evidence.
        provenance: proposed
        source:
          - >-
            services-commerce:modules/promotions/tests/e2e/promotion-codes.e2e.spec.js#refuses a
            limit below the uses made, 409 limit_below_uses
          - >-
            services-commerce:modules/promotions/tests/integration/services/persistence/promotion-codes.integration.spec.js#turns
            release on cancellation on when the first code is personal and single-use, and says so
            in the history
          - >-
            services-commerce:modules/promotions/tests/e2e/promotion-redemptions.e2e.spec.js#refuses
            a release without a reason, 400
          - >-
            services-commerce:docs/milestones/plans/COMM-M1-plan.md#D13. Restore a personal code
            when its order is cancelled?
  - id: ATS-COM-CAT-017-11
    title: Only an applied benefit consumes redemption capacity
    category: interaction
    level: api
    proves:
      - BR-CAP-COM-CAT-016-12
      - BR-CAP-COM-CAT-016-16
      - BR-CAP-COM-CAT-017-01
      - BR-CAP-COM-CAT-017-02
    preconditions: One code loses to a cheaper audience and another wins.
    steps: Place each result and read redemption rows and usage counters.
    expected: Only the winning applied promotion has a redemption and increased counters.
    negative: false
    data_needs: Run-owned workspace and records; fixed clock where dates are exercised.
    bound_to: []
    provenance: proposed
    source:
      - >-
        services-commerce:modules/promotions/tests/unit/pricing/apply-promotions.spec.js#refuses a
        promotion whose benefit is zero on every qualifying line (an amount off a line already free)
      - >-
        services-commerce:modules/promotions/tests/integration/services/persistence/redemption-concurrency.integration.spec.js#applies
        exactly 100 and answers the other 100 code_exhausted
      - >-
        services-commerce:modules/promotions/tests/integration/services/persistence/promotion-redemptions.integration.spec.js#locks
        the promotion at its first redemption
      - >-
        services-commerce:modules/promotions/tests/integration/db/redemptions.table.integration.spec.js#refuses
        at COMMIT a redemption whose discount is not its allocations’ sum, naming the redemption
    exercises:
      - CAP-COM-CAT-016
      - CAP-COM-CAT-017
    workflows:
      - WF-COM-CAT-014
    step_evidence:
      - do: Place each result and read redemption rows and usage counters.
        provenance: proposed
        source:
          - >-
            services-commerce:modules/promotions/tests/unit/pricing/apply-promotions.spec.js#refuses
            a promotion whose benefit is zero on every qualifying line (an amount off a line already
            free)
          - >-
            services-commerce:modules/promotions/tests/integration/services/persistence/redemption-concurrency.integration.spec.js#applies
            exactly 100 and answers the other 100 code_exhausted
          - >-
            services-commerce:modules/promotions/tests/integration/services/persistence/promotion-redemptions.integration.spec.js#locks
            the promotion at its first redemption
          - >-
            services-commerce:modules/promotions/tests/integration/db/redemptions.table.integration.spec.js#refuses
            at COMMIT a redemption whose discount is not its allocations’ sum, naming the redemption
    assertions:
      - expect: Only the winning applied promotion has a redemption and increased counters.
        provenance: proposed
        source:
          - >-
            services-commerce:modules/promotions/tests/unit/pricing/apply-promotions.spec.js#refuses
            a promotion whose benefit is zero on every qualifying line (an amount off a line already
            free)
          - >-
            services-commerce:modules/promotions/tests/integration/services/persistence/redemption-concurrency.integration.spec.js#applies
            exactly 100 and answers the other 100 code_exhausted
          - >-
            services-commerce:modules/promotions/tests/integration/services/persistence/promotion-redemptions.integration.spec.js#locks
            the promotion at its first redemption
          - >-
            services-commerce:modules/promotions/tests/integration/db/redemptions.table.integration.spec.js#refuses
            at COMMIT a redemption whose discount is not its allocations’ sum, naming the redemption
  - id: ATS-COM-CAT-017-12
    title: Promotion redemption is exercised across every declared move
    category: transition
    level: api
    proves:
      - BR-CAP-COM-CAT-017-03
      - BR-CAP-COM-CAT-017-04
      - BR-CAP-COM-CAT-017-05
    preconditions: >-
      Independent run-owned records start in each declared origin state; guarded variants omit one
      required condition at a time.
    steps: >-
      For each transition case, establish the origin, apply its trigger with the guard satisfied,
      read the resulting state and side effects, then repeat on a separate record with a required
      guard absent.
    expected: >-
      Every allowed case reaches its stated destination and retains its declared side effects;
      failed guards preserve the originating record except for the separately documented partial
      offer action.
    negative: true
    data_needs: Run-owned workspace and records; fixed clock where dates are exercised.
    bound_to: []
    provenance: proposed
    source:
      - >-
        rhythm-v2-docs:specs/commerce/catalog/design/state-models/STATE-COM-CAT-010-promotion-redemption.md#STATE-COM-CAT-010
      - >-
        services-commerce:modules/promotions/services/redemptions/redemption-release.ts:releaseVoidedOrderRedemptions
      - >-
        services-commerce:modules/promotions/services/redemptions/redemption-release.ts:releaseRedemptionsForCancelledLines
      - >-
        services-commerce:modules/promotions/tests/e2e/promotion-redemptions.e2e.spec.js#refuses a
        release without a reason, 400
    state_model: STATE-COM-CAT-010
    transition_cases:
      - from: applied
        to: released
        trigger: Order void releases retained uses.
        guard: The use must still count as applied.
        provenance: proposed
        source:
          - >-
            services-commerce:modules/promotions/services/redemptions/redemption-release.ts:releaseVoidedOrderRedemptions
          - >-
            rhythm-v2-docs:specs/commerce/catalog/design/state-models/STATE-COM-CAT-010-promotion-redemption.md#STATE-COM-CAT-010
      - from: applied
        to: released
        trigger: Cancellation fully cancels every allocated line.
        guard: The redemption’s retained cancellation-release choice must permit release.
        provenance: proposed
        source:
          - >-
            services-commerce:modules/promotions/services/redemptions/redemption-release.ts:releaseRedemptionsForCancelledLines
          - >-
            rhythm-v2-docs:specs/commerce/catalog/design/state-models/STATE-COM-CAT-010-promotion-redemption.md#STATE-COM-CAT-010
      - from: applied
        to: released
        trigger: An authorized staff request supplies a release reason.
        guard: 'Override authority, a release reason and an applied use are required.'
        provenance: proposed
        source:
          - >-
            services-commerce:modules/promotions/tests/e2e/promotion-redemptions.e2e.spec.js#refuses
            a release without a reason, 400
          - >-
            rhythm-v2-docs:specs/commerce/catalog/design/state-models/STATE-COM-CAT-010-promotion-redemption.md#STATE-COM-CAT-010
    step_evidence:
      - do: >-
          For each transition case, establish the origin, apply its trigger with the guard
          satisfied, read the resulting state and side effects, then repeat on a separate record
          with a required guard absent.
        provenance: proposed
        source:
          - >-
            rhythm-v2-docs:specs/commerce/catalog/design/state-models/STATE-COM-CAT-010-promotion-redemption.md#STATE-COM-CAT-010
          - >-
            services-commerce:modules/promotions/services/redemptions/redemption-release.ts:releaseVoidedOrderRedemptions
          - >-
            services-commerce:modules/promotions/services/redemptions/redemption-release.ts:releaseRedemptionsForCancelledLines
          - >-
            services-commerce:modules/promotions/tests/e2e/promotion-redemptions.e2e.spec.js#refuses
            a release without a reason, 400
    assertions:
      - expect: >-
          Every allowed case reaches its stated destination and retains its declared side effects;
          failed guards preserve the originating record except for the separately documented partial
          offer action.
        provenance: proposed
        source:
          - >-
            rhythm-v2-docs:specs/commerce/catalog/design/state-models/STATE-COM-CAT-010-promotion-redemption.md#STATE-COM-CAT-010
          - >-
            services-commerce:modules/promotions/services/redemptions/redemption-release.ts:releaseVoidedOrderRedemptions
          - >-
            services-commerce:modules/promotions/services/redemptions/redemption-release.ts:releaseRedemptionsForCancelledLines
          - >-
            services-commerce:modules/promotions/tests/e2e/promotion-redemptions.e2e.spec.js#refuses
            a release without a reason, 400
evidence:
  - 'Pinned service inputs: test sources in scenario bindings'
  - 'Pinned console inputs: console sources in scenario bindings'
  - >-
    rhythm-v2-docs:specs/commerce/catalog/planning/capabilities/CAP-COM-CAT-017-track-release-and-measure-promotion-uses.md#CAP-COM-CAT-017
  - >-
    rhythm-v2-docs:specs/commerce/catalog/design/workflows/WF-COM-CAT-014-apply-promotions-at-checkout-and-record-their.md#WF-COM-CAT-014
  - >-
    rhythm-v2-docs:specs/commerce/catalog/design/workflows/WF-COM-CAT-015-give-promotion-uses-back.md#WF-COM-CAT-015
product_intent: proposed
coverage:
  rule: covered
  transition: covered
  validation: 'gap: FND-COM-CAT-071'
  permission: covered
  edge: 'gap: FND-COM-CAT-071'
  failure: 'gap: FND-COM-CAT-071'
  boundary: 'gap: FND-COM-CAT-071'
  concurrency: 'gap: FND-COM-CAT-071'
  invariant: covered
  interaction: covered
coverage_notes:
  validation: >-
    No complete validation scenario is established by this back-fill; the finding records the
    missing proof.
  edge: >-
    No complete edge scenario is established by this back-fill; the finding records the missing
    proof.
  failure: >-
    No complete failure scenario is established by this back-fill; the finding records the missing
    proof.
  boundary: >-
    No complete boundary scenario is established by this back-fill; the finding records the missing
    proof.
  concurrency: Concurrent execution has no complete scenario; sequential retry is insufficient proof.
permission_coverage:
  - key: commerce.promotions.read
    coverage: 'gap: FND-COM-CAT-071'
    reason: >-
      Existing permission examples cover selected personas, not the independent allow and deny
      matrix for every named key.
    provenance: unknown
    source:
      - 'rhythm-v2-docs:specs/commerce/catalog/FINDINGS.md#FND-COM-CAT-071'
  - key: commerce.promotions.override
    coverage: 'gap: FND-COM-CAT-071'
    reason: >-
      Existing permission examples cover selected personas, not the independent allow and deny
      matrix for every named key.
    provenance: unknown
    source:
      - 'rhythm-v2-docs:specs/commerce/catalog/FINDINGS.md#FND-COM-CAT-071'
---

# Retain and review promotion usage: Capability Test Specification

## Coverage

| Rule | Proof Scenario or Gap |
|---|---|
| `BR-CAP-COM-CAT-017-01` | `ATS-COM-CAT-017-01`, `ATS-COM-CAT-017-09`, `ATS-COM-CAT-017-11` |
| `BR-CAP-COM-CAT-017-02` | `ATS-COM-CAT-017-02`, `ATS-COM-CAT-017-11` |
| `BR-CAP-COM-CAT-017-03` | `ATS-COM-CAT-017-03`, `ATS-COM-CAT-017-12` |
| `BR-CAP-COM-CAT-017-04` | `ATS-COM-CAT-017-04`, `ATS-COM-CAT-017-10`, `ATS-COM-CAT-017-12` |
| `BR-CAP-COM-CAT-017-05` | `ATS-COM-CAT-017-05`, `ATS-COM-CAT-017-10`, `ATS-COM-CAT-017-12` |
| `BR-CAP-COM-CAT-017-06` | `ATS-COM-CAT-017-06` |
| `BR-CAP-COM-CAT-017-07` | `ATS-COM-CAT-017-07` |
| `BR-CAP-COM-CAT-017-09` | `ATS-COM-CAT-017-08` |

## Interactions With Related Capabilities

First redemption changes which promotion edits remain possible. Releasing usage restores capacity without erasing terms. Only an applied benefit consumes redemption capacity.

Coverage describes specified proof. A binding names the existing assertions that contribute to it; proposed designs remain unexecuted.
