ProductChange with its actions, holds it for review, and applies it only on confirmation. The same records double as the product’s history.
The product change record
AProductChange (id prefix prodch) points at its product through product_id and carries a status from the ProductChangeStatus enum. It owns one or more ProductChangeAction rows in actions, one per operation it will apply. The change is the unit you review. The actions are what gets written.
Each resolution stamps an actor and a time on the record:
- Staging:
created_by - Confirm:
confirmed_by,confirmed_at - Decline:
declined_by,declined_at,declined_reason - Cancel:
canceled_by,canceled_at
sel_…) into created_by, and admin routes write the operator’s user id. Two notes travel with the change: internal_note for operators and external_note for the seller.
product.changes link instead, covered in Links.
How an edit is staged
Seller edits reach the pipeline through four edit workflows. Each one loads the current product, compares your input against it, and passes only the differences tostageProductChangeWorkflow:
productEditUpdateProductWorkflow: product fields and status, asUPDATEandSTATUS_CHANGEactionsproductEditUpdateVariantsWorkflow: variant add, update, and remove operations, asVARIANT_*actionsproductEditUpdateAttributesWorkflow: attribute add, update, and remove, asATTRIBUTE_*actionsproductEditDeleteProductWorkflow: a singlePRODUCT_DELETEaction
stageProductChangeWorkflow creates the pending change, creates its actions, emits product-change.created, and then runs autoConfirmProductChangeWorkflow. Whether the change confirms in that same run is covered in Status & auto-confirm.
One open change per author
The edit workflows andcreateProductChangeWorkflow refuse to stage a change when the same author already has a pending change on that product. The check is scoped by created_by, so two different sellers can each have one open change on the same shared product. When created_by is missing, the check covers every pending change on the product.
The conflict surfaces as a NOT_ALLOWED error: “There is already an active update request for this product. Only one request can be active at a time.” The author resolves it by canceling the open change or waiting for an operator to resolve it.
Audit changes
Some events change a product without a review, yet still belong in its history.recordProductAuditChangeWorkflow stores them as changes created already confirmed, with every action marked applied, so confirming never replays them. Mercur’s product workflows call it in four places:
The
PRODUCT_ADD record does real work: its created_by is the seller who submitted the product, and the vendor API reads it to tell which products a seller created.
Operator edits through the admin product update route run Medusa’s
updateProductsWorkflow directly and don’t create a change. The pipeline governs seller edits.