---
id: DOM-COM-CAT-003
title: Catalog Product
realises: CON-COM-CAT-003
product_area: commerce
module: catalog
revision: 1
status: draft
design_intent: proposed
implementation_evidence: partially-observed
source_sha:
  services-commerce: c8b66a98e2490617aec38ba5c8165dd9bd0a8f9b
  react-console: baf6dae382d6f41dce976c7f848667ccc37e076f
owned_by: SVC-COM-CAT-catalog
readable_by:
  - SVC-COM-CAT-merchandise
  - SVC-COM-CAT-pricing
  - SVC-COM-CAT-promotions
  - SVC-COM-CAT-recommendations
  - SVC-COM-CAT-configuration
lifecycle: STATE-COM-CAT-003
custom_fields: none
identity: >-
  Commerce links each listing, merchandise extension and purchasing-settings record to the Accounting product
  key, with at most one record of each kind. Categories and option sets use readable keys. Variants have
  workspace-unique stock codes; gallery pictures and downloadable files receive identifiers before copying
  begins.
attributes:
  - name: accounting product
    kind: reference
    required: true
    computed: false
    notes: >-
      Accounting supplies the product name, type, unit, status, selling window, portal visibility and price
      rows.
  - name: categories (shelves)
    kind: collection
    required: false
    computed: false
    notes: >-
      Flat shelves carry order, sorting and image choices; ordered memberships may place a product on several
      shelves.
  - name: listing state
    kind: enum
    required: true
    computed: false
    notes: 'A listing has draft, published or unlisted visibility; service creation defaults to draft.'
  - name: listing presentation
    kind: collection
    required: false
    computed: false
    notes: >-
      Presentation includes a unique lowercase slug, optional text and primary picture, up to eight gallery
      images and related products, badges, date choices and merchandising order. Self-related products are
      rejected.
  - name: digital files and download settings
    kind: collection
    required: false
    computed: false
    notes: >-
      Digitally delivered merchandise may attach ordered private files and configure download limits and a
      window up to ten years.
  - name: merchandise details
    kind: collection
    required: false
    computed: false
    notes: >-
      Goods details describe delivery mode, packaging, stock and backorder choices, holding time, reorder
      settings and option sets.
  - name: variants
    kind: collection
    required: false
    computed: false
    notes: >-
      Variants describe permitted option combinations with a stock code, active flag and optional price or
      weight adjustment.
  - name: option sets and modifier options
    kind: collection
    required: false
    computed: false
    notes: Reusable option sets supply variant choices or modifier values and price adjustments.
  - name: availability
    kind: computed
    required: false
    computed: true
    notes: >-
      Requested quantities are assessed as available, limited, unavailable, backorder or unknown. Remaining
      units are shown only when configured.
  - name: managed file lifecycle
    kind: computed
    required: true
    computed: true
    notes: >-
      Managed-asset records track the owner field and the preparation, attachment and cleanup of each stored
      file.
  - name: protection and version
    kind: computed
    required: true
    computed: true
    notes: 'Each component record retains its read version, protection and creation and latest-change attribution.'
relationships:
  - to: DOM-COM-CAT-002
    cardinality: many products to many stores through categories
    ownership: Category memberships and the store’s assortment determine storefront inclusion.
    cross_service: false
  - to: DOM-COM-CAT-004
    cardinality: one product to one priced line per quote
    ownership: >-
      Pricing reads Accounting’s grid and Commerce’s purchasing choices; the quote loader currently omits
      variant and modifier adjustments.
    cross_service: false
  - to: DOM-COM-CAT-005
    cardinality: many promotion parts to many products
    ownership: >-
      Promotion merchandise criteria reference products and categories; purchasing settings can exclude a
      product from discounts.
    cross_service: false
  - to: DOM-COM-CAT-006
    cardinality: one listing to up to eight related products
    ownership: Related-product evaluation takes its candidates from the listing’s related-product collection.
    cross_service: false
invariants:
  - id: INV-DOM-COM-CAT-003-01
    statement: 'Each product has no more than one listing, and listing slugs cannot duplicate another workspace listing.'
    enforced_at:
      - api
      - service
      - database
    provenance: test
    source: >-
      services-commerce:modules/catalog/tests/e2e/product-listings.e2e.spec.js#answers 400 to a second listing
      of the same product
  - id: INV-DOM-COM-CAT-003-02
    statement: Category requests reject hierarchy because shelves are flat.
    enforced_at:
      - api
      - database
    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: INV-DOM-COM-CAT-003-03
    statement: >-
      Store assortment and home-category references block category removal; other category deletion removes
      memberships while retaining products.
    enforced_at:
      - service
      - database
    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: INV-DOM-COM-CAT-003-04
    statement: Downloadable files are limited to digitally delivered merchandise and use private storage.
    enforced_at:
      - service
    provenance: test
    source: >-
      services-commerce:modules/catalog/tests/e2e/product-listings.e2e.spec.js#answers 400 to files on a
      product that is not digital merchandise
  - id: INV-DOM-COM-CAT-003-05
    statement: Stored asset references must pass preparation checks; keeping an unchanged picture avoids another copy.
    enforced_at:
      - service
    provenance: test
    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)
  - id: INV-DOM-COM-CAT-003-06
    statement: 'Only an existing Accounting merchandise product can receive a merchandise extension, and only once.'
    enforced_at:
      - service
      - database
    provenance: test
    source: >-
      services-commerce:modules/merchandise/tests/e2e/merchandise.e2e.spec.js#answers 400 to a product the
      tenant has not, a selection off the sets, a SKU in use and a second extension, naming the rule
  - id: INV-DOM-COM-CAT-003-07
    statement: >-
      Stock codes are workspace-unique; variant selections use permitted values from every variant set and
      cannot duplicate another selection.
    enforced_at:
      - service
      - database
    provenance: test
    source: >-
      services-commerce:modules/merchandise/tests/integration/db/merchandise-tables.table.integration.spec.js#refuses
      one SKU twice in a tenant (merchandise_variants_sku_uq) and one selection twice for a product
      (merchandise_variants_selection_uq), while another tenant may use the SKU
  - id: INV-DOM-COM-CAT-003-08
    statement: Variant reconciliation retains the identifier of a stock code that remains in the replacement.
    enforced_at:
      - service
    provenance: test
    source: >-
      services-commerce:modules/merchandise/tests/integration/services/persistence/merchandise.integration.spec.js#replaces
      the extension at its version: the kept SKU keeps its id, a new SKU is added, a dropped one goes
  - id: INV-DOM-COM-CAT-003-09
    statement: Inventory records referencing merchandise variants prevent extension deletion.
    enforced_at:
      - service
    provenance: test
    source: >-
      services-commerce:modules/merchandise/tests/integration/services/persistence/merchandise.integration.spec.js#refuses
      to delete an extension whose SKUs inventory items hold (409), deletes one that has none, and answers 404
      for one the tenant has not
  - id: INV-DOM-COM-CAT-003-10
    statement: The combined merchandise write commits the Accounting product and Commerce extension together.
    enforced_at:
      - service
    provenance: test
    source: >-
      services-commerce:modules/merchandise/tests/integration/services/persistence/merchandise-products.queries.integration.spec.js#leaves
      no product when the database refuses the extension row
  - id: INV-DOM-COM-CAT-003-11
    statement: >-
      Option sets cannot be empty; modifier options must use their set’s values, and variant sets cannot
      contain modifier-option rows.
    enforced_at:
      - service
      - database
    provenance: test
    source: >-
      services-commerce:modules/merchandise/tests/integration/services/persistence/merchandise.integration.spec.js#refuses
      a modifier option off the set’s values, a variant set with options, and an empty set, 400
  - id: INV-DOM-COM-CAT-003-12
    statement: 'Gallery images and related products each have an eight-entry ceiling, and self-reference is rejected.'
    enforced_at:
      - service
      - console
    provenance: test
    source: >-
      services-commerce:modules/catalog/tests/integration/services/persistence/product-listings.integration.spec.js#refuses
      files on a product that is not digital merchandise (none, or physical), and a product that goes with
      itself (400)
physical:
  - product_categories
  - product_category_products
  - product_listings
  - product_listing_images
  - product_listing_files
  - managed_assets
  - merchandise
  - merchandise_variants
  - merchandise_option_sets
  - merchandise_modifier_options
evidence:
  - >-
    services-commerce at c8b66a98: modules/catalog/db/*.table.ts; services/persistence/product-categories.ts,
    product-listings.ts; services/assets/catalog-assets.ts; services/storefront/storefront-visibility.ts.
  - >-
    services-commerce at c8b66a98: modules/merchandise/db/*.table.ts;
    services/persistence/merchandise-extensions.ts, merchandise-option-sets.ts, merchandise-products.ts;
    services/participant/merchandise-availability.ts.
  - >-
    services-commerce at c8b66a98: modules/pricing/services/persistence/catalog-products.ts (variants and
    modifiers loaded as empty lists).
  - >-
    react-console at baf6dae3: components/commerce/{merchandise,catalog}/**,
    components/configuration/resources/product-categories.tsx.
---

# Catalog Product

## What It Models

The catalog product combines Commerce’s presentation and goods-specific details around a product owned by Accounting. Its facets include shelves, storefront content, download files, delivery choices, variants and availability assessment. Accounting continues to own the base product and prices.

## Identity and Attributes

Listings and merchandise details share the product key but have independent record versions. Shelf memberships are ordered. Replacing variants matches stock codes and preserves identifiers for retained variants, protecting inventory references.

## Relationships

Store assortments select products through categories. Pricing reads the Accounting grid and Commerce purchasing settings. Promotion parts reference products or shelves, and related-product recommendations read listing links.

## Invariants

Listings and slugs are unique, category hierarchy is rejected and store references prevent shelf removal. Download files require digital merchandise. Variant codes and selections remain unique. The combined service write is atomic, while the console’s multi-request save is not.

## How It Is Stored and Shared

Catalog owns shelves, listings, images, files and asset lifecycle records. Merchandise owns delivery details, variants and option sets. Both run within Commerce; Accounting product reads cross the service ownership boundary, and product writes call Accounting’s functions. Custom fields and change events are absent.

## Open Design Questions

Quotes currently receive empty variant and modifier collections even though the engine supports adjustments. Console saves can leave an incomplete item. Public purchasing of unlisted products remains an unresolved product question.
