---
id: ATS-COM-CAT-011
scope: CAP-COM-CAT-011
product_area: commerce
module: catalog
revision: 2
status: proposed
source_sha:
  services-commerce: c8b66a98e2490617aec38ba5c8165dd9bd0a8f9b
  react-console: baf6dae382d6f41dce976c7f848667ccc37e076f
scenarios:
  - id: ATS-COM-CAT-011-01
    title: Implemented schedules reject ownership fields
    level: api
    proves:
      - BR-CAP-COM-CAT-011-01
      - INV-DOM-COM-CAT-004-07
    preconditions: A schedule creation payload is otherwise valid
    steps: Supply an event owner
    expected: Schema validation refuses the owner
    negative: true
    data_needs: none
    bound_to:
      - TST-COM-CAT-063
      - TST-COM-CAT-066
    provenance: test
    source: >-
      services-commerce:modules/pricing/tests/e2e/pricing-schedules.e2e.spec.js#answers 400 to an
      event owner (a schedule has none, COMM-M1 D2), to a property outside the contract and to a
      tenant_id other than the path’s
    category: validation
    binding_evidence:
      - >-
        services-commerce:modules/pricing/tests/e2e/pricing-schedules.e2e.spec.js#answers 400 to an
        event owner (a schedule has none, COMM-M1 D2), to a property outside the contract and to a
        tenant_id other than the path’s
      - >-
        services-commerce:modules/pricing/tests/integration/db/member-pricing-tables.table.integration.spec.js#holds
        no owner column (COMM-M1 D2), and refuses a blank zone, which the text-normalization trigger
        makes null, by the NOT NULL
    step_evidence:
      - do: Supply an event owner
        provenance: test
        source: >-
          services-commerce:modules/pricing/tests/e2e/pricing-schedules.e2e.spec.js#answers 400 to
          an event owner (a schedule has none, COMM-M1 D2), to a property outside the contract and
          to a tenant_id other than the path’s
    assertions:
      - expect: Schema validation refuses the owner
        provenance: test
        source: >-
          services-commerce:modules/pricing/tests/e2e/pricing-schedules.e2e.spec.js#answers 400 to
          an event owner (a schedule has none, COMM-M1 D2), to a property outside the contract and
          to a tenant_id other than the path’s
  - id: ATS-COM-CAT-011-02
    title: Period boundaries are exclusive at the end and gap-free
    level: api
    proves:
      - BR-CAP-COM-CAT-011-02
      - BR-CAP-COM-CAT-011-03
    preconditions: Adjacent periods share a boundary
    steps: Resolve that instant and submit one-millisecond gaps and overlaps
    expected: The later period owns the instant and invalid adjacency fails
    negative: true
    data_needs: none
    bound_to:
      - TST-COM-CAT-076
      - TST-COM-CAT-075
      - TST-COM-CAT-063
    provenance: test
    source: >-
      services-commerce:modules/pricing/tests/integration/services/persistence/pricing-schedules.queries.integration.spec.js#finds
      the period containing an instant: the open start, a boundary belonging to the period it
      starts, the open end
    category: boundary
    binding_evidence:
      - >-
        services-commerce:modules/pricing/tests/integration/services/persistence/pricing-schedules.queries.integration.spec.js#finds
        the period containing an instant: the open start, a boundary belonging to the period it
        starts, the open end
      - >-
        services-commerce:modules/pricing/tests/integration/services/persistence/pricing-schedules.integration.spec.js#accepts
        periods whose boundary is the same millisecond, and refuses a gap or an overlap of one
        millisecond
      - >-
        services-commerce:modules/pricing/tests/e2e/pricing-schedules.e2e.spec.js#answers 400 with
        the problem code periods_not_contiguous to periods with a gap, creating nothing
    step_evidence:
      - do: Resolve that instant and submit one-millisecond gaps and overlaps
        provenance: test
        source: >-
          services-commerce:modules/pricing/tests/integration/services/persistence/pricing-schedules.queries.integration.spec.js#finds
          the period containing an instant: the open start, a boundary belonging to the period it
          starts, the open end
    assertions:
      - expect: The later period owns the instant and invalid adjacency fails
        provenance: test
        source: >-
          services-commerce:modules/pricing/tests/integration/services/persistence/pricing-schedules.queries.integration.spec.js#finds
          the period containing an instant: the open start, a boundary belonging to the period it
          starts, the open end
  - id: ATS-COM-CAT-011-03
    title: Historical period changes are refused
    level: api
    proves:
      - BR-CAP-COM-CAT-011-04
    preconditions: An elapsed period and a used period exist
    steps: Rename them and delete the used period or its schedule
    expected: Immutability refusals preserve their history
    negative: true
    data_needs: A recorded first sale
    bound_to:
      - TST-COM-CAT-063
      - TST-COM-CAT-075
      - TST-COM-CAT-066
    provenance: test
    source: >-
      services-commerce:modules/pricing/tests/e2e/pricing-schedules.e2e.spec.js#accepts an elapsed
      period sent back as read, and refuses it a new name, 409 period_immutable
    category: invariant
    binding_evidence:
      - >-
        services-commerce:modules/pricing/tests/e2e/pricing-schedules.e2e.spec.js#accepts an elapsed
        period sent back as read, and refuses it a new name, 409 period_immutable
      - >-
        services-commerce:modules/pricing/tests/integration/services/persistence/pricing-schedules.integration.spec.js#refuses
        the sold-in period left out (its delete), and the whole schedule’s delete, 409
        period_immutable
      - >-
        services-commerce:modules/pricing/tests/integration/db/member-pricing-tables.table.integration.spec.js#records
        the first sale once, never cleared or moved, after which the period keeps its name and is
        never deleted, nor its schedule
    step_evidence:
      - do: Rename them and delete the used period or its schedule
        provenance: test
        source: >-
          services-commerce:modules/pricing/tests/e2e/pricing-schedules.e2e.spec.js#accepts an
          elapsed period sent back as read, and refuses it a new name, 409 period_immutable
    assertions:
      - expect: Immutability refusals preserve their history
        provenance: test
        source: >-
          services-commerce:modules/pricing/tests/e2e/pricing-schedules.e2e.spec.js#accepts an
          elapsed period sent back as read, and refuses it a new name, 409 period_immutable
  - id: ATS-COM-CAT-011-04
    title: Current-end edits coordinate the next period
    level: api
    proves:
      - BR-CAP-COM-CAT-011-05
      - AS-JNY-COM-CAT-002-03
    preconditions: Early Bird is current and already used
    steps: 'Extend its end, end it now, backdate the end and change its start'
    expected: Permitted end edits move Regular’s start; backdating and start changes fail
    negative: true
    data_needs: A recorded first sale
    bound_to:
      - TST-COM-CAT-063
    provenance: test
    source: >-
      services-commerce:modules/pricing/tests/e2e/pricing-schedules.e2e.spec.js#extends the sold-in
      Early Bird by a week, Regular starting where it now ends, its first sale untouched
    category: transition
    binding_evidence:
      - >-
        services-commerce:modules/pricing/tests/e2e/pricing-schedules.e2e.spec.js#extends the
        sold-in Early Bird by a week, Regular starting where it now ends, its first sale untouched
      - >-
        services-commerce:modules/pricing/tests/e2e/pricing-schedules.e2e.spec.js#ends Early Bird
        now, Regular starting then, and locks it fully once the end has passed
      - >-
        services-commerce:modules/pricing/tests/e2e/pricing-schedules.e2e.spec.js#refuses an end in
        the past, 409 period_immutable, the periods as they were
      - >-
        services-commerce:modules/pricing/tests/e2e/pricing-schedules.e2e.spec.js#refuses a sold-in
        period a new start, a new name, or both beside a moved end, 409 period_immutable
    step_evidence:
      - do: 'Extend its end, end it now, backdate the end and change its start'
        provenance: test
        source: >-
          services-commerce:modules/pricing/tests/e2e/pricing-schedules.e2e.spec.js#extends the
          sold-in Early Bird by a week, Regular starting where it now ends, its first sale untouched
    assertions:
      - expect: Permitted end edits move Regular’s start; backdating and start changes fail
        provenance: test
        source: >-
          services-commerce:modules/pricing/tests/e2e/pricing-schedules.e2e.spec.js#extends the
          sold-in Early Bird by a week, Regular starting where it now ends, its first sale untouched
  - id: ATS-COM-CAT-011-05
    title: Period identity cannot cross schedules
    level: api
    proves:
      - BR-CAP-COM-CAT-011-06
    preconditions: Schedule A already owns an Early Bird period
    steps: Create schedule B reusing that identity
    expected: The refusal identifies the occupied period
    negative: true
    data_needs: none
    bound_to:
      - TST-COM-CAT-063
    provenance: test
    source: >-
      services-commerce:modules/pricing/tests/e2e/pricing-schedules.e2e.spec.js#answers 400 with the
      problem code period_id_taken, naming the id, to a second schedule taking a period id, creating
      nothing
    category: validation
    binding_evidence:
      - >-
        services-commerce:modules/pricing/tests/e2e/pricing-schedules.e2e.spec.js#answers 400 with
        the problem code period_id_taken, naming the id, to a second schedule taking a period id,
        creating nothing
    step_evidence:
      - do: Create schedule B reusing that identity
        provenance: test
        source: >-
          services-commerce:modules/pricing/tests/e2e/pricing-schedules.e2e.spec.js#answers 400 with
          the problem code period_id_taken, naming the id, to a second schedule taking a period id,
          creating nothing
    assertions:
      - expect: The refusal identifies the occupied period
        provenance: test
        source: >-
          services-commerce:modules/pricing/tests/e2e/pricing-schedules.e2e.spec.js#answers 400 with
          the problem code period_id_taken, naming the id, to a second schedule taking a period id,
          creating nothing
  - id: ATS-COM-CAT-011-06
    title: Referenced schedules remain usable until products move
    level: api
    proves:
      - BR-CAP-COM-CAT-011-07
    preconditions: A product follows Annual and another schedule is inactive
    steps: 'Deactivate or delete Annual and select the inactive schedule, then move the product away'
    expected: Referenced and inactive choices fail; unreferenced deactivation succeeds
    negative: true
    data_needs: Purchasing settings
    bound_to:
      - TST-COM-CAT-063
      - TST-COM-CAT-075
    provenance: test
    source: >-
      services-commerce:modules/pricing/tests/e2e/pricing-schedules.e2e.spec.js#refuses switching a
      schedule off while products follow it, 409 schedule_in_use with the count and product ids,
      until they move; reactivation works; another workspace’s products do not count
    category: transition
    binding_evidence:
      - >-
        services-commerce:modules/pricing/tests/e2e/pricing-schedules.e2e.spec.js#refuses switching
        a schedule off while products follow it, 409 schedule_in_use with the count and product ids,
        until they move; reactivation works; another workspace’s products do not count
      - >-
        services-commerce:modules/pricing/tests/e2e/pricing-schedules.e2e.spec.js#refuses the delete
        of a schedule a product follows, 409 schedule_in_use, and deletes it with its periods once
        nothing does
      - >-
        services-commerce:modules/pricing/tests/integration/services/persistence/pricing-schedules.integration.spec.js#refuses
        a product choosing a switched-off schedule, 400 schedule_inactive, and leaves its settings
        as they were
    step_evidence:
      - do: 'Deactivate or delete Annual and select the inactive schedule, then move the product away'
        provenance: test
        source: >-
          services-commerce:modules/pricing/tests/e2e/pricing-schedules.e2e.spec.js#refuses
          switching a schedule off while products follow it, 409 schedule_in_use with the count and
          product ids, until they move; reactivation works; another workspace’s products do not
          count
    assertions:
      - expect: Referenced and inactive choices fail; unreferenced deactivation succeeds
        provenance: test
        source: >-
          services-commerce:modules/pricing/tests/e2e/pricing-schedules.e2e.spec.js#refuses
          switching a schedule off while products follow it, 409 schedule_in_use with the count and
          product ids, until they move; reactivation works; another workspace’s products do not
          count
  - id: ATS-COM-CAT-011-07
    title: Schedule replacement reconciles periods at the read version
    level: api
    proves:
      - BR-CAP-COM-CAT-011-08
    preconditions: Three periods exist in a schedule
    steps: 'Keep, change, add and omit periods, then repeat from an old version'
    expected: Allowed reconciliation succeeds and the stale request fails
    negative: true
    data_needs: none
    bound_to:
      - TST-COM-CAT-075
    provenance: test
    source: >-
      services-commerce:modules/pricing/tests/integration/services/persistence/pricing-schedules.integration.spec.js#answers
      the replaced record at version 2: the kept period at version 1, the changed one at version 2,
      the new one inserted, the one left out gone
    category: concurrency
    binding_evidence:
      - >-
        services-commerce:modules/pricing/tests/integration/services/persistence/pricing-schedules.integration.spec.js#answers
        the replaced record at version 2: the kept period at version 1, the changed one at version
        2, the new one inserted, the one left out gone
      - >-
        services-commerce:modules/pricing/tests/integration/services/persistence/pricing-schedules.integration.spec.js#refuses
        a stale version, 409, and a schedule the tenant does not have, 404
    step_evidence:
      - do: 'Keep, change, add and omit periods, then repeat from an old version'
        provenance: test
        source: >-
          services-commerce:modules/pricing/tests/integration/services/persistence/pricing-schedules.integration.spec.js#answers
          the replaced record at version 2: the kept period at version 1, the changed one at version
          2, the new one inserted, the one left out gone
    assertions:
      - expect: Allowed reconciliation succeeds and the stale request fails
        provenance: test
        source: >-
          services-commerce:modules/pricing/tests/integration/services/persistence/pricing-schedules.integration.spec.js#answers
          the replaced record at version 2: the kept period at version 1, the changed one at version
          2, the new one inserted, the one left out gone
  - id: ATS-COM-CAT-011-08
    title: Schedule inspection requires no write permission
    level: api
    proves:
      - BR-CAP-COM-CAT-011-10
    preconditions: A schedule reader has no administrator grants
    steps: Read schedules and attempt changes
    expected: Reads succeed and writes fail permission checks
    negative: true
    data_needs: Read-exclusively persona
    bound_to:
      - TST-COM-CAT-062
    provenance: test
    source: >-
      services-commerce:modules/pricing/tests/e2e/pricing-configuration-security.e2e.spec.js#lists
      and reads the audiences and the schedules
    category: permission
    binding_evidence:
      - >-
        services-commerce:modules/pricing/tests/e2e/pricing-configuration-security.e2e.spec.js#lists
        and reads the audiences and the schedules
      - >-
        services-commerce:modules/pricing/tests/e2e/pricing-configuration-security.e2e.spec.js#is
        refused the creates, replacements and deletes, 403 permission_missing, each leaving the
        record as it was
    step_evidence:
      - do: Read schedules and attempt changes
        provenance: test
        source: >-
          services-commerce:modules/pricing/tests/e2e/pricing-configuration-security.e2e.spec.js#lists
          and reads the audiences and the schedules
    assertions:
      - expect: Reads succeed and writes fail permission checks
        provenance: test
        source: >-
          services-commerce:modules/pricing/tests/e2e/pricing-configuration-security.e2e.spec.js#lists
          and reads the audiences and the schedules
  - id: ATS-COM-CAT-011-09
    title: Schedule protection refuses the protected write
    category: invariant
    level: api
    proves:
      - BR-CAP-COM-CAT-011-11
    preconditions: Separate otherwise editable records use full protection and deletion-only protection.
    steps: >-
      Replace and delete the fully protected record, then replace and delete the deletion-only
      protected record; reread both.
    expected: >-
      The full-protection requests refuse; the deletion-only replacement succeeds and removal
      refuses; refused writes preserve the stored record.
    negative: true
    data_needs: Run-owned workspace and records; fixed clock where dates are exercised.
    bound_to: []
    provenance: proposed
    source:
      - 'services-commerce:modules/pricing/services/persistence/pricing-schedules.ts'
    step_evidence:
      - do: >-
          Replace and delete the fully protected record, then replace and delete the deletion-only
          protected record; reread both.
        provenance: proposed
        source:
          - 'services-commerce:modules/pricing/services/persistence/pricing-schedules.ts'
    assertions:
      - expect: >-
          The full-protection requests refuse; the deletion-only replacement succeeds and removal
          refuses; refused writes preserve the stored record.
        provenance: proposed
        source:
          - 'services-commerce:modules/pricing/services/persistence/pricing-schedules.ts'
  - id: ATS-COM-CAT-011-10
    title: A product reference retains its selected schedule
    category: interaction
    level: api
    proves:
      - BR-CAP-COM-CAT-011-07
      - BR-CAP-COM-CAT-012-04
    preconditions: A product follows Annual and another schedule is inactive.
    steps: >-
      Attempt to disable Annual and select the inactive schedule; move the product to a valid
      alternative and disable Annual.
    expected: Referenced and inactive choices refuse; the unreferenced schedule can be disabled.
    negative: false
    data_needs: Run-owned workspace and records; fixed clock where dates are exercised.
    bound_to: []
    provenance: proposed
    source:
      - >-
        services-commerce:modules/pricing/tests/integration/services/persistence/pricing-schedules.integration.spec.js#refuses
        with schedule_in_use, the count in the sentence, and the count and the first five product
        ids in the details
      - >-
        services-commerce:modules/pricing/tests/e2e/product-purchasing-settings.e2e.spec.js#refuses
        an unknown organization role id with 400 naming it, and stores nothing (COM-ACC-0799)
    exercises:
      - CAP-COM-CAT-011
      - CAP-COM-CAT-012
    workflows:
      - WF-COM-CAT-009
      - WF-COM-CAT-012
    step_evidence:
      - do: >-
          Attempt to disable Annual and select the inactive schedule; move the product to a valid
          alternative and disable Annual.
        provenance: proposed
        source:
          - >-
            services-commerce:modules/pricing/tests/integration/services/persistence/pricing-schedules.integration.spec.js#refuses
            with schedule_in_use, the count in the sentence, and the count and the first five
            product ids in the details
          - >-
            services-commerce:modules/pricing/tests/e2e/product-purchasing-settings.e2e.spec.js#refuses
            an unknown organization role id with 400 naming it, and stores nothing (COM-ACC-0799)
    assertions:
      - expect: Referenced and inactive choices refuse; the unreferenced schedule can be disabled.
        provenance: proposed
        source:
          - >-
            services-commerce:modules/pricing/tests/integration/services/persistence/pricing-schedules.integration.spec.js#refuses
            with schedule_in_use, the count in the sentence, and the count and the first five
            product ids in the details
          - >-
            services-commerce:modules/pricing/tests/e2e/product-purchasing-settings.e2e.spec.js#refuses
            an unknown organization role id with 400 naming it, and stores nothing (COM-ACC-0799)
  - id: ATS-COM-CAT-011-11
    title: A moved boundary changes the next quote without rewriting an earlier sale
    category: interaction
    level: api
    proves:
      - BR-CAP-COM-CAT-011-02
      - BR-CAP-COM-CAT-011-05
      - BR-CAP-COM-CAT-013-03
      - BR-CAP-COM-CAT-013-14
    preconditions: Early Bird has a placed sale and Regular starts at its end.
    steps: >-
      Read the sale, extend Early Bird with Regular’s start moved together, quote on both sides of
      the new boundary and reread the sale.
    expected: >-
      New quotes select the period at their instant; the earlier amount and first-sale stamp stay
      unchanged.
    negative: false
    data_needs: Run-owned workspace and records; fixed clock where dates are exercised.
    bound_to: []
    provenance: proposed
    source:
      - >-
        services-commerce:modules/pricing/tests/integration/services/persistence/pricing-schedules.queries.integration.spec.js#finds
        the period containing an instant: the open start, a boundary belonging to the period it
        starts, the open end
      - >-
        services-commerce:modules/pricing/tests/e2e/pricing-schedules.e2e.spec.js#extends the
        sold-in Early Bird by a week, Regular starting where it now ends, its first sale untouched
      - >-
        services-commerce:modules/pricing/tests/unit/engine/step-3-base-price.spec.js#inherits the
        audience’s always cell when the period has none, never another period’s cell
      - >-
        services-commerce:modules/pricing/tests/integration/services/placement/audience-settlement.integration.spec.js#refuses
        the placement for a lapsed member, one audience_lapsed problem per line with the portal
        sentence, stamping nothing
    exercises:
      - CAP-COM-CAT-011
      - CAP-COM-CAT-013
    workflows:
      - WF-COM-CAT-009
      - WF-COM-CAT-010
    step_evidence:
      - do: >-
          Read the sale, extend Early Bird with Regular’s start moved together, quote on both sides
          of the new boundary and reread the sale.
        provenance: proposed
        source:
          - >-
            services-commerce:modules/pricing/tests/integration/services/persistence/pricing-schedules.queries.integration.spec.js#finds
            the period containing an instant: the open start, a boundary belonging to the period it
            starts, the open end
          - >-
            services-commerce:modules/pricing/tests/e2e/pricing-schedules.e2e.spec.js#extends the
            sold-in Early Bird by a week, Regular starting where it now ends, its first sale
            untouched
          - >-
            services-commerce:modules/pricing/tests/unit/engine/step-3-base-price.spec.js#inherits
            the audience’s always cell when the period has none, never another period’s cell
          - >-
            services-commerce:modules/pricing/tests/integration/services/placement/audience-settlement.integration.spec.js#refuses
            the placement for a lapsed member, one audience_lapsed problem per line with the portal
            sentence, stamping nothing
    assertions:
      - expect: >-
          New quotes select the period at their instant; the earlier amount and first-sale stamp
          stay unchanged.
        provenance: proposed
        source:
          - >-
            services-commerce:modules/pricing/tests/integration/services/persistence/pricing-schedules.queries.integration.spec.js#finds
            the period containing an instant: the open start, a boundary belonging to the period it
            starts, the open end
          - >-
            services-commerce:modules/pricing/tests/e2e/pricing-schedules.e2e.spec.js#extends the
            sold-in Early Bird by a week, Regular starting where it now ends, its first sale
            untouched
          - >-
            services-commerce:modules/pricing/tests/unit/engine/step-3-base-price.spec.js#inherits
            the audience’s always cell when the period has none, never another period’s cell
          - >-
            services-commerce:modules/pricing/tests/integration/services/placement/audience-settlement.integration.spec.js#refuses
            the placement for a lapsed member, one audience_lapsed problem per line with the portal
            sentence, stamping nothing
  - id: ATS-COM-CAT-011-12
    title: Pricing period is exercised across every declared move
    category: transition
    level: api
    proves:
      - BR-CAP-COM-CAT-011-02
      - BR-CAP-COM-CAT-011-03
      - BR-CAP-COM-CAT-011-04
      - BR-CAP-COM-CAT-011-05
      - BR-CAP-COM-CAT-011-07
      - BR-CAP-COM-CAT-011-08
    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-005-pricing-period.md#STATE-COM-CAT-005
      - >-
        services-commerce:modules/pricing/tests/integration/services/persistence/pricing-schedules.queries.integration.spec.js#finds
        the period containing an instant: the open start, a boundary belonging to the period it
        starts, the open end
      - >-
        services-commerce:modules/pricing/tests/integration/db/member-pricing-tables.table.integration.spec.js#records
        the first sale once, never cleared or moved, after which the period keeps its name and is
        never deleted, nor its schedule
      - >-
        services-commerce:modules/pricing/tests/e2e/pricing-schedules.e2e.spec.js#extends the
        sold-in Early Bird by a week, Regular starting where it now ends, its first sale untouched
      - 'services-commerce:modules/pricing/db/pricing-periods.table.ts'
      - 'services-commerce:modules/pricing/db/pricing-periods.table.ts'
      - >-
        services-commerce:modules/pricing/tests/integration/services/persistence/pricing-schedules.integration.spec.js#answers
        the replaced record at version 2: the kept period at version 1, the changed one at version
        2, the new one inserted, the one left out gone
      - 'services-commerce:modules/pricing/db/pricing-periods.table.ts'
    state_model: STATE-COM-CAT-005
    transition_cases:
      - from: upcoming
        to: current
        trigger: The pricing instant reaches the interval start.
        guard: No additional condition applies.
        provenance: proposed
        source:
          - >-
            services-commerce:modules/pricing/tests/integration/services/persistence/pricing-schedules.queries.integration.spec.js#finds
            the period containing an instant: the open start, a boundary belonging to the period it
            starts, the open end
          - >-
            rhythm-v2-docs:specs/commerce/catalog/design/state-models/STATE-COM-CAT-005-pricing-period.md#STATE-COM-CAT-005
      - from: current
        to: current and sold in
        trigger: Placement commits the interval’s first priced sale.
        guard: The sale must commit its placement transaction.
        provenance: proposed
        source:
          - >-
            services-commerce:modules/pricing/tests/integration/db/member-pricing-tables.table.integration.spec.js#records
            the first sale once, never cleared or moved, after which the period keeps its name and
            is never deleted, nor its schedule
          - >-
            rhythm-v2-docs:specs/commerce/catalog/design/state-models/STATE-COM-CAT-005-pricing-period.md#STATE-COM-CAT-005
      - from: current and sold in
        to: current and sold in
        trigger: An API edit changes the sold-in current interval’s end.
        guard: 'The end must be now or later, with the following interval adjusted to retain adjacency.'
        provenance: proposed
        source:
          - >-
            services-commerce:modules/pricing/tests/e2e/pricing-schedules.e2e.spec.js#extends the
            sold-in Early Bird by a week, Regular starting where it now ends, its first sale
            untouched
          - >-
            rhythm-v2-docs:specs/commerce/catalog/design/state-models/STATE-COM-CAT-005-pricing-period.md#STATE-COM-CAT-005
      - from: current
        to: elapsed
        trigger: The interval end is reached.
        guard: No additional condition applies.
        provenance: proposed
        source:
          - 'services-commerce:modules/pricing/db/pricing-periods.table.ts'
          - >-
            rhythm-v2-docs:specs/commerce/catalog/design/state-models/STATE-COM-CAT-005-pricing-period.md#STATE-COM-CAT-005
      - from: current and sold in
        to: elapsed
        trigger: The interval end is reached.
        guard: No additional condition applies.
        provenance: proposed
        source:
          - 'services-commerce:modules/pricing/db/pricing-periods.table.ts'
          - >-
            rhythm-v2-docs:specs/commerce/catalog/design/state-models/STATE-COM-CAT-005-pricing-period.md#STATE-COM-CAT-005
      - from: upcoming
        to: removed
        trigger: 'Replacement omits an editable interval, or staff delete its schedule.'
        guard: >-
          Commit must retain contiguous intervals, and schedule deletion requires no following
          products.
        provenance: proposed
        source:
          - >-
            services-commerce:modules/pricing/tests/integration/services/persistence/pricing-schedules.integration.spec.js#answers
            the replaced record at version 2: the kept period at version 1, the changed one at
            version 2, the new one inserted, the one left out gone
          - >-
            rhythm-v2-docs:specs/commerce/catalog/design/state-models/STATE-COM-CAT-005-pricing-period.md#STATE-COM-CAT-005
      - from: elapsed
        to: removed
        trigger: Staff remove the entire schedule.
        guard: No sale may have used the interval and no product may retain its schedule.
        provenance: proposed
        source:
          - 'services-commerce:modules/pricing/db/pricing-periods.table.ts'
          - >-
            rhythm-v2-docs:specs/commerce/catalog/design/state-models/STATE-COM-CAT-005-pricing-period.md#STATE-COM-CAT-005
    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-005-pricing-period.md#STATE-COM-CAT-005
          - >-
            services-commerce:modules/pricing/tests/integration/services/persistence/pricing-schedules.queries.integration.spec.js#finds
            the period containing an instant: the open start, a boundary belonging to the period it
            starts, the open end
          - >-
            services-commerce:modules/pricing/tests/integration/db/member-pricing-tables.table.integration.spec.js#records
            the first sale once, never cleared or moved, after which the period keeps its name and
            is never deleted, nor its schedule
          - >-
            services-commerce:modules/pricing/tests/e2e/pricing-schedules.e2e.spec.js#extends the
            sold-in Early Bird by a week, Regular starting where it now ends, its first sale
            untouched
          - 'services-commerce:modules/pricing/db/pricing-periods.table.ts'
          - 'services-commerce:modules/pricing/db/pricing-periods.table.ts'
          - >-
            services-commerce:modules/pricing/tests/integration/services/persistence/pricing-schedules.integration.spec.js#answers
            the replaced record at version 2: the kept period at version 1, the changed one at
            version 2, the new one inserted, the one left out gone
          - 'services-commerce:modules/pricing/db/pricing-periods.table.ts'
    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-005-pricing-period.md#STATE-COM-CAT-005
          - >-
            services-commerce:modules/pricing/tests/integration/services/persistence/pricing-schedules.queries.integration.spec.js#finds
            the period containing an instant: the open start, a boundary belonging to the period it
            starts, the open end
          - >-
            services-commerce:modules/pricing/tests/integration/db/member-pricing-tables.table.integration.spec.js#records
            the first sale once, never cleared or moved, after which the period keeps its name and
            is never deleted, nor its schedule
          - >-
            services-commerce:modules/pricing/tests/e2e/pricing-schedules.e2e.spec.js#extends the
            sold-in Early Bird by a week, Regular starting where it now ends, its first sale
            untouched
          - 'services-commerce:modules/pricing/db/pricing-periods.table.ts'
          - 'services-commerce:modules/pricing/db/pricing-periods.table.ts'
          - >-
            services-commerce:modules/pricing/tests/integration/services/persistence/pricing-schedules.integration.spec.js#answers
            the replaced record at version 2: the kept period at version 1, the changed one at
            version 2, the new one inserted, the one left out gone
          - 'services-commerce:modules/pricing/db/pricing-periods.table.ts'
  - id: ATS-COM-CAT-011-13
    title: Invalid open intervals refuse at commit
    category: invariant
    level: api
    proves:
      - INV-DOM-COM-CAT-004-04
      - BR-CAP-COM-CAT-011-03
    preconditions: A three-period schedule has contiguous boundaries.
    steps: >-
      Commit an overlap, a second open end and an open start on a later period in separate
      transactions.
    expected: Each commit refuses its invalid interval layout and the prior schedule remains unchanged.
    negative: true
    data_needs: Run-owned workspace and records; fixed clock where dates are exercised.
    bound_to: []
    provenance: proposed
    source:
      - >-
        services-commerce:modules/pricing/tests/integration/db/member-pricing-tables.table.integration.spec.js#refuses
        an overlap, a second open end and an open start that is not first
      - >-
        rhythm-v2-docs:specs/commerce/catalog/design/objects/DOM-COM-CAT-004-price.md#DOM-COM-CAT-004
    verification_note: >-
      This design references the invariant; executable binding awaits an assertion-by-assertion
      comparison with this complete scenario.
    step_evidence:
      - do: >-
          Commit an overlap, a second open end and an open start on a later period in separate
          transactions.
        provenance: proposed
        source:
          - >-
            services-commerce:modules/pricing/tests/integration/db/member-pricing-tables.table.integration.spec.js#refuses
            an overlap, a second open end and an open start that is not first
          - >-
            rhythm-v2-docs:specs/commerce/catalog/design/objects/DOM-COM-CAT-004-price.md#DOM-COM-CAT-004
    assertions:
      - expect: Each commit refuses its invalid interval layout and the prior schedule remains unchanged.
        provenance: proposed
        source:
          - >-
            services-commerce:modules/pricing/tests/integration/db/member-pricing-tables.table.integration.spec.js#refuses
            an overlap, a second open end and an open start that is not first
          - >-
            rhythm-v2-docs:specs/commerce/catalog/design/objects/DOM-COM-CAT-004-price.md#DOM-COM-CAT-004
  - id: ATS-COM-CAT-011-14
    title: Moving a sold-in current end preserves the first sale
    category: invariant
    level: api
    proves:
      - INV-DOM-COM-CAT-004-05
    preconditions: Early Bird is current and has a first-sale stamp; Regular follows it.
    steps: >-
      Move Early Bird’s end one week later with Regular’s start matching it; then attempt to rename
      the used period, change its start or rewrite the first-sale stamp.
    expected: 'The coordinated end move commits and the protected name, start and first-sale changes refuse.'
    negative: true
    data_needs: Run-owned workspace and records; fixed clock where dates are exercised.
    bound_to: []
    provenance: proposed
    source:
      - >-
        services-commerce:modules/pricing/tests/integration/db/member-pricing-tables.table.integration.spec.js#lets
        a sold-in current period end a week later, the next period starting where it ends
      - >-
        rhythm-v2-docs:specs/commerce/catalog/design/objects/DOM-COM-CAT-004-price.md#DOM-COM-CAT-004
    verification_note: >-
      This design references the invariant; executable binding awaits an assertion-by-assertion
      comparison with this complete scenario.
    step_evidence:
      - do: >-
          Move Early Bird’s end one week later with Regular’s start matching it; then attempt to
          rename the used period, change its start or rewrite the first-sale stamp.
        provenance: proposed
        source:
          - >-
            services-commerce:modules/pricing/tests/integration/db/member-pricing-tables.table.integration.spec.js#lets
            a sold-in current period end a week later, the next period starting where it ends
          - >-
            rhythm-v2-docs:specs/commerce/catalog/design/objects/DOM-COM-CAT-004-price.md#DOM-COM-CAT-004
    assertions:
      - expect: >-
          The coordinated end move commits and the protected name, start and first-sale changes
          refuse.
        provenance: proposed
        source:
          - >-
            services-commerce:modules/pricing/tests/integration/db/member-pricing-tables.table.integration.spec.js#lets
            a sold-in current period end a week later, the next period starting where it ends
          - >-
            rhythm-v2-docs:specs/commerce/catalog/design/objects/DOM-COM-CAT-004-price.md#DOM-COM-CAT-004
  - id: ATS-COM-CAT-011-15
    title: A referenced schedule stays selectable until the product moves
    category: invariant
    level: api
    proves:
      - INV-DOM-COM-CAT-004-06
      - BR-CAP-COM-CAT-011-07
    preconditions: Six products retain Annual and another schedule is inactive.
    steps: >-
      Attempt to disable and remove Annual, inspect the retaining count and first five products,
      then attempt to select the inactive schedule.
    expected: >-
      References prevent disabling or removal and the inactive selection refuses without changing
      the product’s settings.
    negative: true
    data_needs: Run-owned workspace and records; fixed clock where dates are exercised.
    bound_to: []
    provenance: proposed
    source:
      - >-
        services-commerce:modules/pricing/tests/integration/services/persistence/pricing-schedules.integration.spec.js#refuses
        with schedule_in_use, the count in the sentence, and the count and the first five product
        ids in the details
      - >-
        rhythm-v2-docs:specs/commerce/catalog/design/objects/DOM-COM-CAT-004-price.md#DOM-COM-CAT-004
    verification_note: >-
      This design references the invariant; executable binding awaits an assertion-by-assertion
      comparison with this complete scenario.
    step_evidence:
      - do: >-
          Attempt to disable and remove Annual, inspect the retaining count and first five products,
          then attempt to select the inactive schedule.
        provenance: proposed
        source:
          - >-
            services-commerce:modules/pricing/tests/integration/services/persistence/pricing-schedules.integration.spec.js#refuses
            with schedule_in_use, the count in the sentence, and the count and the first five
            product ids in the details
          - >-
            rhythm-v2-docs:specs/commerce/catalog/design/objects/DOM-COM-CAT-004-price.md#DOM-COM-CAT-004
    assertions:
      - expect: >-
          References prevent disabling or removal and the inactive selection refuses without
          changing the product’s settings.
        provenance: proposed
        source:
          - >-
            services-commerce:modules/pricing/tests/integration/services/persistence/pricing-schedules.integration.spec.js#refuses
            with schedule_in_use, the count in the sentence, and the count and the first five
            product ids in the details
          - >-
            rhythm-v2-docs:specs/commerce/catalog/design/objects/DOM-COM-CAT-004-price.md#DOM-COM-CAT-004
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-011-vary-prices-over-dated-periods.md#CAP-COM-CAT-011
  - >-
    rhythm-v2-docs:specs/commerce/catalog/design/workflows/WF-COM-CAT-009-quote-a-price.md#WF-COM-CAT-009
  - >-
    rhythm-v2-docs:specs/commerce/catalog/design/workflows/WF-COM-CAT-010-price-a-checkout-and-settle-its-audiences-at.md#WF-COM-CAT-010
  - >-
    rhythm-v2-docs:specs/commerce/catalog/design/workflows/WF-COM-CAT-012-replace-a-price-schedule-and-reconcile-its.md#WF-COM-CAT-012
product_intent: proposed
coverage:
  rule: 'gap: FND-COM-CAT-065'
  transition: covered
  validation: covered
  permission: covered
  edge: 'gap: FND-COM-CAT-065'
  failure: 'gap: FND-COM-CAT-065'
  boundary: covered
  concurrency: covered
  invariant: covered
  interaction: covered
coverage_notes:
  rule: >-
    No complete rule 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.
  concurrency: >-
    A stale-write example covers version conflict; overlapping operations and external effects
    require separate proof.
permission_coverage:
  - key: commerce.pricing-schedules.read
    coverage: 'gap: FND-COM-CAT-065'
    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-065'
  - key: commerce.pricing-schedules.create
    coverage: 'gap: FND-COM-CAT-065'
    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-065'
  - key: commerce.pricing-schedules.update
    coverage: 'gap: FND-COM-CAT-065'
    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-065'
  - key: commerce.pricing-schedules.delete
    coverage: 'gap: FND-COM-CAT-065'
    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-065'
  - key: commerce.pricing-schedules.bulk
    coverage: 'gap: FND-COM-CAT-065'
    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-065'
---

# Maintain dated pricing periods: Capability Test Specification

## Coverage

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

## Interactions With Related Capabilities

A product reference retains its selected schedule. A moved boundary changes the next quote without rewriting an earlier sale.

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