---
id: ATS-COM-CAT-005
scope: CAP-COM-CAT-005
product_area: commerce
module: catalog
revision: 2
status: proposed
source_sha:
  services-commerce: c8b66a98e2490617aec38ba5c8165dd9bd0a8f9b
  react-console: baf6dae382d6f41dce976c7f848667ccc37e076f
scenarios:
  - id: ATS-COM-CAT-005-01
    title: Category requests reject hierarchy and unsupported sorting
    level: api
    proves:
      - BR-CAP-COM-CAT-005-02
      - BR-CAP-COM-CAT-005-03
      - INV-DOM-COM-CAT-003-02
    preconditions: A category request is otherwise valid
    steps: Supply a parent and then an unsupported sort
    expected: Both requests identify the invalid field
    negative: true
    data_needs: none
    bound_to:
      - TST-COM-CAT-014
      - TST-COM-CAT-017
    provenance: test
    source: >-
      services-commerce:modules/catalog/tests/e2e/product-categories.e2e.spec.js#answers 400 to a
      parent_id: the hierarchy is reserved
    category: validation
    binding_evidence:
      - >-
        services-commerce:modules/catalog/tests/e2e/product-categories.e2e.spec.js#answers 400 to a
        parent_id: the hierarchy is reserved
      - >-
        services-commerce:modules/catalog/tests/e2e/product-categories.e2e.spec.js#answers 400 to a
        default sort outside the catalog sorts, naming the property
      - >-
        services-commerce:modules/catalog/tests/integration/db/catalog-tables.table.integration.spec.js#refuses
        a category hierarchy: parent_id is reserved and held NULL
    step_evidence:
      - do: Supply a parent and then an unsupported sort
        provenance: test
        source: >-
          services-commerce:modules/catalog/tests/e2e/product-categories.e2e.spec.js#answers 400 to
          a parent_id: the hierarchy is reserved
    assertions:
      - expect: Both requests identify the invalid field
        provenance: test
        source: >-
          services-commerce:modules/catalog/tests/e2e/product-categories.e2e.spec.js#answers 400 to
          a parent_id: the hierarchy is reserved
  - id: ATS-COM-CAT-005-02
    title: Membership replacement rejects repeats and reconciles shelves
    level: api
    proves:
      - BR-CAP-COM-CAT-005-04
    preconditions: Books has three product memberships
    steps: 'Repeat a product in category replacement, then change a product’s category selection'
    expected: The repeat fails; new selections append memberships and omissions remove them
    negative: true
    data_needs: Three products
    bound_to:
      - TST-COM-CAT-019
    provenance: test
    source: >-
      services-commerce:modules/catalog/tests/integration/services/persistence/product-categories.integration.spec.js#refuses
      a list naming one product twice (400) and an unknown category (404)
    category: rule
    binding_evidence:
      - >-
        services-commerce:modules/catalog/tests/integration/services/persistence/product-categories.integration.spec.js#refuses
        a list naming one product twice (400) and an unknown category (404)
      - >-
        services-commerce:modules/catalog/tests/integration/services/persistence/product-categories.integration.spec.js#states
        the shelves one product sits on: appended last where new, removed where no longer named
    step_evidence:
      - do: 'Repeat a product in category replacement, then change a product’s category selection'
        provenance: test
        source: >-
          services-commerce:modules/catalog/tests/integration/services/persistence/product-categories.integration.spec.js#refuses
          a list naming one product twice (400) and an unknown category (404)
    assertions:
      - expect: The repeat fails; new selections append memberships and omissions remove them
        provenance: test
        source: >-
          services-commerce:modules/catalog/tests/integration/services/persistence/product-categories.integration.spec.js#refuses
          a list naming one product twice (400) and an unknown category (404)
  - id: ATS-COM-CAT-005-03
    title: Dependent stores keep a category from deletion
    level: api
    proves:
      - BR-CAP-COM-CAT-005-05
      - AS-JNY-COM-CAT-001-04
      - INV-DOM-COM-CAT-003-03
    preconditions: Stores refer to the category through home or assortment settings
    steps: Attempt category deletion
    expected: The refusal reports the dependent count and at most five store names
    negative: true
    data_needs: Six stores
    bound_to:
      - TST-COM-CAT-019
      - TST-COM-CAT-014
    provenance: test
    source: >-
      services-commerce:modules/catalog/tests/integration/services/persistence/product-categories.integration.spec.js#is
      refused while stores name it in their assortment, naming five of them and the count
    category: transition
    binding_evidence:
      - >-
        services-commerce:modules/catalog/tests/integration/services/persistence/product-categories.integration.spec.js#is
        refused while stores name it in their assortment, naming five of them and the count
      - >-
        services-commerce:modules/catalog/tests/e2e/product-categories.e2e.spec.js#refuses the
        delete of a category a store names, 409 category_in_use naming the store, and deletes it
        once the store has moved on
    step_evidence:
      - do: Attempt category deletion
        provenance: test
        source: >-
          services-commerce:modules/catalog/tests/integration/services/persistence/product-categories.integration.spec.js#is
          refused while stores name it in their assortment, naming five of them and the count
    assertions:
      - expect: The refusal reports the dependent count and at most five store names
        provenance: test
        source: >-
          services-commerce:modules/catalog/tests/integration/services/persistence/product-categories.integration.spec.js#is
          refused while stores name it in their assortment, naming five of them and the count
  - id: ATS-COM-CAT-005-04
    title: Unreadable category images prevent creation
    level: api
    proves:
      - BR-CAP-COM-CAT-005-07
    preconditions: The image reference cannot be read by the asset pipeline
    steps: Create a category using that reference
    expected: Creation fails without retaining the category
    negative: true
    data_needs: none
    bound_to:
      - TST-COM-CAT-014
      - TST-COM-CAT-019
    provenance: test
    source: >-
      services-commerce:modules/catalog/tests/e2e/product-categories.e2e.spec.js#answers 400 to a
      picture the pipeline cannot read as a file, creating nothing
    category: failure
    binding_evidence:
      - >-
        services-commerce:modules/catalog/tests/e2e/product-categories.e2e.spec.js#answers 400 to a
        picture the pipeline cannot read as a file, creating nothing
      - >-
        services-commerce:modules/catalog/tests/integration/services/persistence/product-categories.integration.spec.js#refuses
        a picture reference the pipeline cannot read as a file (400), writing nothing
    step_evidence:
      - do: Create a category using that reference
        provenance: test
        source: >-
          services-commerce:modules/catalog/tests/e2e/product-categories.e2e.spec.js#answers 400 to
          a picture the pipeline cannot read as a file, creating nothing
    assertions:
      - expect: Creation fails without retaining the category
        provenance: test
        source: >-
          services-commerce:modules/catalog/tests/e2e/product-categories.e2e.spec.js#answers 400 to
          a picture the pipeline cannot read as a file, creating nothing
  - id: ATS-COM-CAT-005-05
    title: Category replacement detects stale versions
    level: api
    proves:
      - BR-CAP-COM-CAT-005-08
    preconditions: The saved category is at version two
    steps: Replace from version one
    expected: A version conflict prevents the change
    negative: true
    data_needs: none
    bound_to:
      - TST-COM-CAT-014
    provenance: test
    source: >-
      services-commerce:modules/catalog/tests/e2e/product-categories.e2e.spec.js#replaces at the
      version read and refuses the stale version 409
    category: concurrency
    binding_evidence:
      - >-
        services-commerce:modules/catalog/tests/e2e/product-categories.e2e.spec.js#replaces at the
        version read and refuses the stale version 409
    step_evidence:
      - do: Replace from version one
        provenance: test
        source: >-
          services-commerce:modules/catalog/tests/e2e/product-categories.e2e.spec.js#replaces at the
          version read and refuses the stale version 409
    assertions:
      - expect: A version conflict prevents the change
        provenance: test
        source: >-
          services-commerce:modules/catalog/tests/e2e/product-categories.e2e.spec.js#replaces at the
          version read and refuses the stale version 409
  - id: ATS-COM-CAT-005-06
    title: Category management requires write authority
    level: api
    proves:
      - BR-CAP-COM-CAT-005-09
    preconditions: The caller has read permissions alone
    steps: Attempt a category write
    expected: The request fails permission evaluation
    negative: true
    data_needs: Read-exclusively persona
    bound_to:
      - TST-COM-CAT-011
    provenance: test
    source: >-
      services-commerce:modules/catalog/tests/e2e/catalog-security.e2e.spec.js#refuses a read-only
      principal every write, 403 permission_missing
    category: permission
    binding_evidence:
      - >-
        services-commerce:modules/catalog/tests/e2e/catalog-security.e2e.spec.js#refuses a read-only
        principal every write, 403 permission_missing
    step_evidence:
      - do: Attempt a category write
        provenance: test
        source: >-
          services-commerce:modules/catalog/tests/e2e/catalog-security.e2e.spec.js#refuses a
          read-only principal every write, 403 permission_missing
    assertions:
      - expect: The request fails permission evaluation
        provenance: test
        source: >-
          services-commerce:modules/catalog/tests/e2e/catalog-security.e2e.spec.js#refuses a
          read-only principal every write, 403 permission_missing
  - id: ATS-COM-CAT-005-07
    title: Shelf protection refuses the protected write
    category: invariant
    level: api
    proves:
      - BR-CAP-COM-CAT-005-10
    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/catalog/services/persistence/product-categories.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/catalog/services/persistence/product-categories.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/catalog/services/persistence/product-categories.ts'
  - id: ATS-COM-CAT-005-08
    title: Moving store references allows a shelf to be removed
    category: interaction
    level: api
    proves:
      - BR-CAP-COM-CAT-005-05
      - BR-CAP-COM-CAT-003-05
    preconditions: Two stores retain a shelf as home or assortment.
    steps: Remove one reference and request deletion; remove the second and repeat.
    expected: Deletion remains refused after the first move and succeeds after the second.
    negative: false
    data_needs: Run-owned workspace and records; fixed clock where dates are exercised.
    bound_to: []
    provenance: proposed
    source:
      - >-
        services-commerce:modules/catalog/tests/integration/services/persistence/product-categories.integration.spec.js#is
        refused while stores name it in their assortment, naming five of them and the count
      - >-
        services-commerce:modules/configuration/tests/integration/services/persistence/stores.integration.spec.js#refuses
        a home category an "all_but" rule leaves out, 400 naming home_category_id
    exercises:
      - CAP-COM-CAT-005
      - CAP-COM-CAT-003
    workflows:
      - WF-COM-CAT-003
      - WF-COM-CAT-004
    step_evidence:
      - do: Remove one reference and request deletion; remove the second and repeat.
        provenance: proposed
        source:
          - >-
            services-commerce:modules/catalog/tests/integration/services/persistence/product-categories.integration.spec.js#is
            refused while stores name it in their assortment, naming five of them and the count
          - >-
            services-commerce:modules/configuration/tests/integration/services/persistence/stores.integration.spec.js#refuses
            a home category an "all_but" rule leaves out, 400 naming home_category_id
    assertions:
      - expect: Deletion remains refused after the first move and succeeds after the second.
        provenance: proposed
        source:
          - >-
            services-commerce:modules/catalog/tests/integration/services/persistence/product-categories.integration.spec.js#is
            refused while stores name it in their assortment, naming five of them and the count
          - >-
            services-commerce:modules/configuration/tests/integration/services/persistence/stores.integration.spec.js#refuses
            a home category an "all_but" rule leaves out, 400 naming home_category_id
  - id: ATS-COM-CAT-005-09
    title: Moving a listing between shelves changes membership order
    category: interaction
    level: api
    proves:
      - BR-CAP-COM-CAT-005-04
      - BR-CAP-COM-CAT-006-02
    preconditions: Two shelves and a published listing exist.
    steps: 'Replace the listing’s shelf selection, then read both memberships.'
    expected: The old membership disappears and the new membership is appended in the new shelf.
    negative: false
    data_needs: Run-owned workspace and records; fixed clock where dates are exercised.
    bound_to: []
    provenance: proposed
    source:
      - >-
        services-commerce:modules/catalog/tests/integration/services/persistence/product-categories.integration.spec.js#refuses
        a list naming one product twice (400) and an unknown category (404)
      - >-
        services-commerce:modules/catalog/tests/unit/storefront/storefront-visibility.spec.js#removes
        a draft everywhere, an unlisted listing from the list only, and a published one nowhere
    exercises:
      - CAP-COM-CAT-005
      - CAP-COM-CAT-006
    workflows:
      - WF-COM-CAT-003
      - WF-COM-CAT-004
    step_evidence:
      - do: 'Replace the listing’s shelf selection, then read both memberships.'
        provenance: proposed
        source:
          - >-
            services-commerce:modules/catalog/tests/integration/services/persistence/product-categories.integration.spec.js#refuses
            a list naming one product twice (400) and an unknown category (404)
          - >-
            services-commerce:modules/catalog/tests/unit/storefront/storefront-visibility.spec.js#removes
            a draft everywhere, an unlisted listing from the list only, and a published one nowhere
    assertions:
      - expect: The old membership disappears and the new membership is appended in the new shelf.
        provenance: proposed
        source:
          - >-
            services-commerce:modules/catalog/tests/integration/services/persistence/product-categories.integration.spec.js#refuses
            a list naming one product twice (400) and an unknown category (404)
          - >-
            services-commerce:modules/catalog/tests/unit/storefront/storefront-visibility.spec.js#removes
            a draft everywhere, an unlisted listing from the list only, and a published one nowhere
  - id: ATS-COM-CAT-005-10
    title: Shelf membership changes the browsable assortment
    category: interaction
    level: api
    proves:
      - BR-CAP-COM-CAT-005-04
      - BR-CAP-COM-CAT-008-01
    preconditions: A store admits only Books and a published product belongs to Apparel.
    steps: Browse; assign the product to Books; browse; remove its Books membership and browse again.
    expected: The product appears after assignment and disappears after removal.
    negative: false
    data_needs: Run-owned workspace and records; fixed clock where dates are exercised.
    bound_to: []
    provenance: proposed
    source:
      - >-
        services-commerce:modules/catalog/tests/integration/services/persistence/product-categories.integration.spec.js#refuses
        a list naming one product twice (400) and an unknown category (404)
      - >-
        services-commerce:modules/catalog/tests/unit/storefront/storefront-visibility.spec.js#removes
        a product not yet on sale, and one whose sale has ended (the end exclusive)
    exercises:
      - CAP-COM-CAT-005
      - CAP-COM-CAT-008
    workflows: []
    step_evidence:
      - do: Browse; assign the product to Books; browse; remove its Books membership and browse again.
        provenance: proposed
        source:
          - >-
            services-commerce:modules/catalog/tests/integration/services/persistence/product-categories.integration.spec.js#refuses
            a list naming one product twice (400) and an unknown category (404)
          - >-
            services-commerce:modules/catalog/tests/unit/storefront/storefront-visibility.spec.js#removes
            a product not yet on sale, and one whose sale has ended (the end exclusive)
    assertions:
      - expect: The product appears after assignment and disappears after removal.
        provenance: proposed
        source:
          - >-
            services-commerce:modules/catalog/tests/integration/services/persistence/product-categories.integration.spec.js#refuses
            a list naming one product twice (400) and an unknown category (404)
          - >-
            services-commerce:modules/catalog/tests/unit/storefront/storefront-visibility.spec.js#removes
            a product not yet on sale, and one whose sale has ended (the end exclusive)
  - id: ATS-COM-CAT-005-11
    title: Shared asset settlement uses each record’s current reference
    category: interaction
    level: api
    proves:
      - BR-CAP-COM-CAT-003-09
      - BR-CAP-COM-CAT-005-07
    preconditions: A store and category each have a prepared picture.
    steps: >-
      Replace both pictures, sweep detached references, restore one reference before consuming and
      inspect both owners.
    expected: Settlement retires the detached picture and retains the picture referenced again.
    negative: false
    data_needs: Run-owned workspace and records; fixed clock where dates are exercised.
    bound_to: []
    provenance: proposed
    source:
      - >-
        services-commerce:modules/catalog/tests/integration/services/assets/catalog-assets.integration.spec.js#detaches
        the store’s picture when the store is deleted
      - >-
        services-commerce:modules/catalog/tests/e2e/product-categories.e2e.spec.js#answers 400 to a
        picture the pipeline cannot read as a file, creating nothing
    exercises:
      - CAP-COM-CAT-003
      - CAP-COM-CAT-005
    workflows:
      - WF-COM-CAT-003
      - WF-COM-CAT-004
    step_evidence:
      - do: >-
          Replace both pictures, sweep detached references, restore one reference before consuming
          and inspect both owners.
        provenance: proposed
        source:
          - >-
            services-commerce:modules/catalog/tests/integration/services/assets/catalog-assets.integration.spec.js#detaches
            the store’s picture when the store is deleted
          - >-
            services-commerce:modules/catalog/tests/e2e/product-categories.e2e.spec.js#answers 400
            to a picture the pipeline cannot read as a file, creating nothing
    assertions:
      - expect: Settlement retires the detached picture and retains the picture referenced again.
        provenance: proposed
        source:
          - >-
            services-commerce:modules/catalog/tests/integration/services/assets/catalog-assets.integration.spec.js#detaches
            the store’s picture when the store is deleted
          - >-
            services-commerce:modules/catalog/tests/e2e/product-categories.e2e.spec.js#answers 400
            to a picture the pipeline cannot read as a file, creating nothing
  - id: ATS-COM-CAT-005-12
    title: Managed catalog file lifecycle is exercised across every declared move
    category: transition
    level: api
    proves:
      - BR-CAP-COM-CAT-005-07
      - BR-CAP-COM-CAT-005-08
      - BR-CAP-COM-CAT-005-10
    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-004-managed-catalog-file.md#STATE-COM-CAT-004
      - >-
        services-commerce:modules/catalog/tests/e2e/product-categories.e2e.spec.js#answers 400 to a
        picture the pipeline cannot read as a file, creating nothing
      - >-
        services-commerce:modules/catalog/tests/integration/services/persistence/product-listings.integration.spec.js#refuses
        a gallery picture written past the pipeline (a reference the save never prepared)
      - 'services-commerce:modules/catalog/services/assets/catalog-assets.ts'
      - >-
        services-commerce:modules/catalog/tests/integration/services/persistence/product-categories.integration.spec.js#deletes
        the category with its memberships and detaches its picture; a second delete is 404
      - 'services-commerce:modules/catalog/services/assets/clean-up-catalog-assets.ts'
      - 'services-commerce:modules/catalog/services/assets/clean-up-catalog-assets.ts'
      - 'services-commerce:modules/catalog/services/assets/clean-up-one-catalog-asset.ts'
      - 'services-commerce:modules/catalog/services/assets/clean-up-one-catalog-asset.ts'
      - 'services-commerce:modules/catalog/aws/lambda/ASSET_CLEANUP_CONSUMER.ts'
    state_model: STATE-COM-CAT-004
    transition_cases:
      - from: preparing
        to: ready
        trigger: Asset copying finishes successfully.
        guard: The asset source must be readable by the pipeline.
        provenance: proposed
        source:
          - >-
            services-commerce:modules/catalog/tests/e2e/product-categories.e2e.spec.js#answers 400
            to a picture the pipeline cannot read as a file, creating nothing
          - >-
            rhythm-v2-docs:specs/commerce/catalog/design/state-models/STATE-COM-CAT-004-managed-catalog-file.md#STATE-COM-CAT-004
      - from: ready
        to: attached
        trigger: The parent save commits the prepared reference.
        guard: The save must have prepared the reference it commits.
        provenance: proposed
        source:
          - >-
            services-commerce:modules/catalog/tests/integration/services/persistence/product-listings.integration.spec.js#refuses
            a gallery picture written past the pipeline (a reference the save never prepared)
          - >-
            rhythm-v2-docs:specs/commerce/catalog/design/state-models/STATE-COM-CAT-004-managed-catalog-file.md#STATE-COM-CAT-004
      - from: ready
        to: abandoned
        trigger: The parent save fails after copying.
        guard: No additional condition applies.
        provenance: proposed
        source:
          - 'services-commerce:modules/catalog/services/assets/catalog-assets.ts'
          - >-
            rhythm-v2-docs:specs/commerce/catalog/design/state-models/STATE-COM-CAT-004-managed-catalog-file.md#STATE-COM-CAT-004
      - from: attached
        to: detached
        trigger: A saved change or removal releases the reference.
        guard: A retained unchanged picture bypasses detachment and keeps its existing reference.
        provenance: proposed
        source:
          - >-
            services-commerce:modules/catalog/tests/integration/services/persistence/product-categories.integration.spec.js#deletes
            the category with its memberships and detaches its picture; a second delete is 404
          - >-
            rhythm-v2-docs:specs/commerce/catalog/design/state-models/STATE-COM-CAT-004-managed-catalog-file.md#STATE-COM-CAT-004
      - from: detached
        to: discarding
        trigger: The scheduled sweep claims a due released file.
        guard: The cleanup due instant must have arrived.
        provenance: proposed
        source:
          - 'services-commerce:modules/catalog/services/assets/clean-up-catalog-assets.ts'
          - >-
            rhythm-v2-docs:specs/commerce/catalog/design/state-models/STATE-COM-CAT-004-managed-catalog-file.md#STATE-COM-CAT-004
      - from: abandoned
        to: discarding
        trigger: The scheduled sweep claims a due abandoned copy.
        guard: The cleanup due instant must have arrived.
        provenance: proposed
        source:
          - 'services-commerce:modules/catalog/services/assets/clean-up-catalog-assets.ts'
          - >-
            rhythm-v2-docs:specs/commerce/catalog/design/state-models/STATE-COM-CAT-004-managed-catalog-file.md#STATE-COM-CAT-004
      - from: discarding
        to: discarded
        trigger: Queue processing finishes cleanup.
        guard: The workspace-scoped owner check must find no retained reference.
        provenance: proposed
        source:
          - 'services-commerce:modules/catalog/services/assets/clean-up-one-catalog-asset.ts'
          - >-
            rhythm-v2-docs:specs/commerce/catalog/design/state-models/STATE-COM-CAT-004-managed-catalog-file.md#STATE-COM-CAT-004
      - from: discarding
        to: attached
        trigger: Settlement finds that a saved record still retains the reference.
        guard: A saved owner record must still name the asset.
        provenance: proposed
        source:
          - 'services-commerce:modules/catalog/services/assets/clean-up-one-catalog-asset.ts'
          - >-
            rhythm-v2-docs:specs/commerce/catalog/design/state-models/STATE-COM-CAT-004-managed-catalog-file.md#STATE-COM-CAT-004
      - from: discarding
        to: failed
        trigger: The final queue delivery cannot complete cleanup.
        guard: The receipt must be the final permitted retry after earlier failures.
        provenance: proposed
        source:
          - 'services-commerce:modules/catalog/aws/lambda/ASSET_CLEANUP_CONSUMER.ts'
          - >-
            rhythm-v2-docs:specs/commerce/catalog/design/state-models/STATE-COM-CAT-004-managed-catalog-file.md#STATE-COM-CAT-004
    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-004-managed-catalog-file.md#STATE-COM-CAT-004
          - >-
            services-commerce:modules/catalog/tests/e2e/product-categories.e2e.spec.js#answers 400
            to a picture the pipeline cannot read as a file, creating nothing
          - >-
            services-commerce:modules/catalog/tests/integration/services/persistence/product-listings.integration.spec.js#refuses
            a gallery picture written past the pipeline (a reference the save never prepared)
          - 'services-commerce:modules/catalog/services/assets/catalog-assets.ts'
          - >-
            services-commerce:modules/catalog/tests/integration/services/persistence/product-categories.integration.spec.js#deletes
            the category with its memberships and detaches its picture; a second delete is 404
          - 'services-commerce:modules/catalog/services/assets/clean-up-catalog-assets.ts'
          - 'services-commerce:modules/catalog/services/assets/clean-up-catalog-assets.ts'
          - 'services-commerce:modules/catalog/services/assets/clean-up-one-catalog-asset.ts'
          - 'services-commerce:modules/catalog/services/assets/clean-up-one-catalog-asset.ts'
          - 'services-commerce:modules/catalog/aws/lambda/ASSET_CLEANUP_CONSUMER.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-004-managed-catalog-file.md#STATE-COM-CAT-004
          - >-
            services-commerce:modules/catalog/tests/e2e/product-categories.e2e.spec.js#answers 400
            to a picture the pipeline cannot read as a file, creating nothing
          - >-
            services-commerce:modules/catalog/tests/integration/services/persistence/product-listings.integration.spec.js#refuses
            a gallery picture written past the pipeline (a reference the save never prepared)
          - 'services-commerce:modules/catalog/services/assets/catalog-assets.ts'
          - >-
            services-commerce:modules/catalog/tests/integration/services/persistence/product-categories.integration.spec.js#deletes
            the category with its memberships and detaches its picture; a second delete is 404
          - 'services-commerce:modules/catalog/services/assets/clean-up-catalog-assets.ts'
          - 'services-commerce:modules/catalog/services/assets/clean-up-catalog-assets.ts'
          - 'services-commerce:modules/catalog/services/assets/clean-up-one-catalog-asset.ts'
          - 'services-commerce:modules/catalog/services/assets/clean-up-one-catalog-asset.ts'
          - 'services-commerce:modules/catalog/aws/lambda/ASSET_CLEANUP_CONSUMER.ts'
  - id: ATS-COM-CAT-005-13
    title: Shared asset settlement respects each saved owner
    category: interaction
    level: api
    proves:
      - BR-CAP-COM-CAT-003-09
      - BR-CAP-COM-CAT-005-07
      - BR-CAP-COM-CAT-006-08
      - BR-CAP-COM-CAT-006-14
    preconditions: 'A store, shelf and listing each retain a prepared picture in one run-owned workspace.'
    steps: >-
      Replace the three pictures, sweep their detached references, restore one owner reference
      before consuming and inspect every owner and managed asset afterward.
    expected: >-
      Only detached, unreferenced pictures retire; the reference restored before settlement remains
      attached and the saved owners keep their current pictures.
    negative: false
    data_needs: 'Run-owned workspace, prepared file fixtures and independent fault injection at the handoff.'
    bound_to: []
    provenance: proposed
    source:
      - >-
        rhythm-v2-docs:specs/commerce/catalog/design/workflows/WF-COM-CAT-003-save-a-record-with-managed-pictures-or-files.md#WF-COM-CAT-003
      - >-
        rhythm-v2-docs:specs/commerce/catalog/design/workflows/WF-COM-CAT-004-sweep-and-settle-released-catalog-files.md#WF-COM-CAT-004
    exercises:
      - CAP-COM-CAT-003
      - CAP-COM-CAT-005
      - CAP-COM-CAT-006
    workflows:
      - WF-COM-CAT-003
      - WF-COM-CAT-004
    step_evidence:
      - do: >-
          Replace the three pictures, sweep their detached references, restore one owner reference
          before consuming and inspect every owner and managed asset afterward.
        provenance: proposed
        source:
          - >-
            rhythm-v2-docs:specs/commerce/catalog/design/workflows/WF-COM-CAT-003-save-a-record-with-managed-pictures-or-files.md#WF-COM-CAT-003
          - >-
            rhythm-v2-docs:specs/commerce/catalog/design/workflows/WF-COM-CAT-004-sweep-and-settle-released-catalog-files.md#WF-COM-CAT-004
    assertions:
      - expect: >-
          Only detached, unreferenced pictures retire; the reference restored before settlement
          remains attached and the saved owners keep their current pictures.
        provenance: proposed
        source:
          - >-
            rhythm-v2-docs:specs/commerce/catalog/design/workflows/WF-COM-CAT-003-save-a-record-with-managed-pictures-or-files.md#WF-COM-CAT-003
          - >-
            rhythm-v2-docs:specs/commerce/catalog/design/workflows/WF-COM-CAT-004-sweep-and-settle-released-catalog-files.md#WF-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-005-arrange-products-onto-shelves.md#CAP-COM-CAT-005
  - >-
    rhythm-v2-docs:specs/commerce/catalog/design/workflows/WF-COM-CAT-003-save-a-record-with-managed-pictures-or-files.md#WF-COM-CAT-003
  - >-
    rhythm-v2-docs:specs/commerce/catalog/design/workflows/WF-COM-CAT-004-sweep-and-settle-released-catalog-files.md#WF-COM-CAT-004
product_intent: proposed
coverage:
  rule: covered
  transition: covered
  validation: covered
  permission: covered
  edge: 'gap: FND-COM-CAT-059'
  failure: covered
  boundary: 'gap: FND-COM-CAT-059'
  concurrency: covered
  invariant: covered
  interaction: covered
coverage_notes:
  edge: >-
    No complete edge 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: >-
    A stale-write example covers version conflict; overlapping operations and external effects
    require separate proof.
permission_coverage:
  - key: commerce.product-categories.read
    coverage: 'gap: FND-COM-CAT-059'
    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-059'
  - key: commerce.product-categories.create
    coverage: 'gap: FND-COM-CAT-059'
    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-059'
  - key: commerce.product-categories.update
    coverage: 'gap: FND-COM-CAT-059'
    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-059'
  - key: commerce.product-categories.delete
    coverage: 'gap: FND-COM-CAT-059'
    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-059'
  - key: commerce.product-categories.bulk
    coverage: 'gap: FND-COM-CAT-059'
    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-059'
---

# Organize product shelves: Capability Test Specification

## Coverage

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

## Interactions With Related Capabilities

Moving store references allows a shelf to be removed. Moving a listing between shelves changes membership order. Shelf membership changes the browsable assortment. Shared asset settlement uses each record’s current reference. Shared asset settlement respects each saved owner.

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