Skip to content

PRD: Configuration & deletion policy

ModuleCommerce (CORE-03)PRD IDPRD-CFG-001
StatusShippedOwnerCommerce squad
Date2026-04-03Versionv1.0
Packages@nx/commerceURDDEL · CFG

TL;DR

Gives every merchant a configurable deletion policy - two flags that decide whether a category with linked products can be removed and whether deleting a category cascades to its products - backed by the encrypted per-merchant configuration store. Owners control delete behavior from the merchant, and every delete path (merchant, category, product, variant, sale-channel) routes through one guard so a mistaken delete cannot silently orphan or wipe content.

1. Context & Problem

Commerce already persists per-merchant settings - payment-provider credentials and integration config - as encrypted key-value entries via the ConfigurationService and the SettingsRepository (the Configuration / CFG area). What it lacks is a merchant-configurable answer to a destructive question: what should happen when an owner deletes a category that still has products, or deletes a product that has variants?

Without a policy, delete behavior is hard-coded and uniform. An owner cannot express "block deleting a category that still has products" versus "let the delete cascade to the products," and there is no single place that enforces the guard consistently across the merchant, category, product, variant, and sale-channel delete paths. For the HKD/SME bookkeeping KICKO targets, an accidental cascade - or an accidental orphan - is a data-loss incident.

This increment adds a per-merchant Deletion Policy (DEL) stored in the same encrypted settings store and wires a single guard into every merchant-content delete path.

2. Goals & Non-Goals

Goals

  • Persist a per-merchant deletion policy with two flags - strictCategoryDeletion and cascadeProductDeletion - defaulting to { strict: true, cascade: false }.
  • Expose view and update of the policy on the merchant (GET / POST /{id}/deletion-policy), gated by Merchant.findDeletionPolicy / Merchant.updateDeletionPolicy permissions.
  • Enforce the policy across delete flows: block category deletion when products exist and strict is on; cascade product deletion to variants when cascade is on; block deleting a product or variant that has already been sold.
  • Reuse the encrypted per-merchant configuration store (SettingsRepository) as the policy's home - no new column or table.

Non-Goals

  • Hard-delete of any entity - all deletes remain soft-delete (URD C-03).
  • Standalone category / sale-channel CRUD outside the merchant aggregate (Planned).
  • A campaign hard-deletion flag - campaign deletion is out of scope.
  • Cross-organization configuration sharing.

3. Success Metrics

MetricTarget / signal
Guard coverage100% of merchant-content deletes (merchant, category, product, variant, sale-channel) route through the single deletion guard
Data safetyZero accidental cascades or orphaned content; a sold product/variant is never hard-removed
Policy adoptionMerchants that customize the default policy (strict on / cascade off) where their workflow differs
Default correctnessNew merchants start with { strict: true, cascade: false } with no manual setup

4. Personas & Use Cases

PersonaGoal in this feature
OwnerDecide how deletions cascade for their merchant; view and update the policy
ManagerDelete categories/products within the safety rails the policy enforces
System (delete paths)Apply the policy uniformly so no path can bypass the guard

Core scenarios: an owner views the merchant's deletion policy → toggles strictCategoryDeletion / cascadeProductDeletion → subsequent category/product/variant/sale-channel deletes are evaluated against the policy → a guarded delete either blocks, cascades, or unlinks per the configured flags.

5. User Stories

  • As an owner, I want a per-merchant deletion policy, so that delete behavior matches how my business treats categories and products.
  • As an owner, I want to block deleting a category that still has products, so that I never lose products by removing their group.
  • As an owner, I want the option to cascade a category delete to its products, so that I can clear out a whole group in one action when I intend to.
  • As a manager, I want deletes that would destroy sold items to be refused, so that historical sales stay intact.
  • As an owner, I want the policy stored with my existing encrypted settings, so that there is one home for merchant configuration.

6. Functional Requirements

#RequirementURD ref
FR-1Each merchant carries a configurable deletion policy with two flags, defaulting to { strictCategoryDeletion: true, cascadeProductDeletion: false }URD-DEL-001
FR-2strictCategoryDeletion: when true, block category deletion if the category has linked products; otherwise unlinkURD-DEL-002
FR-3cascadeProductDeletion: when true, deleting a category cascades to its productsURD-DEL-003
FR-4Owner can view and update the policy via GET / POST /{id}/deletion-policy, gated by Merchant.findDeletionPolicy / Merchant.updateDeletionPolicyURD-DEL-004
FR-5Product delete cascades to variants when cascade is on, else blocks if variants exist; asserts the product is not already soldURD-DEL-002..003
FR-6Variant and sale-channel delete assert not-sold and clean up dependenciesURD-DEL-002
FR-7Merchant delete asserts no child merchants and no remaining content, then cleans up meta-links and settingsURD-DEL-001
FR-8The policy is persisted per merchant through the encrypted settings store, grouped by areaURD-CFG-001..003

Full requirement text and acceptance criteria live in the Commerce URD. This PRD references them rather than restating them.

7. Non-Functional Requirements

AreaRequirement
Data integrityAll deletes are soft-delete; a sold product/variant is never removed; deletes are guarded so content is never silently orphaned or cascaded against policy
Single guardOne service centralizes every merchant-content delete guard; no delete path bypasses it
SecurityThe policy is stored in the encrypted per-merchant settings store; sensitive configuration is encrypted at rest
Tenancy & authzPolicy view/update scoped per merchant and gated by Merchant.findDeletionPolicy / Merchant.updateDeletionPolicy
ConsistencyCascade and cleanup happen transactionally; a partial delete does not leave dangling references
i18nUser-facing labels/messages are bilingual ({ en, vi })

8. UX & Flows

Key screens (in apps/client): the merchant settings area exposing the deletion-policy toggles, and the delete confirmations on category / product / variant / sale-channel that surface the guard outcome.

9. Data & Domain

EntityRole
MerchantOwns the deletion policy; delete paths for its content route through the guard
Deletion policy configTwo-flag policy (strictCategoryDeletion, cascadeProductDeletion) persisted per merchant
Settings storeEncrypted per-merchant key-value configuration (principalType = Merchant), grouped by area; home of the policy
Category / Product / ProductVariant / SaleChannelContent whose deletes are evaluated against the policy

Conceptual only - full schema and invariants in the commerce domain model.

10. Dependencies & Assumptions

Depends on

  • Configuration store (URD-CFG-001..003) - the encrypted per-merchant settings mechanism that stores the policy.
  • Merchant (URD-MER) - the policy is owned by, and read from, the merchant.
  • Categories / Products - the content the policy guards on delete.

Assumptions

  • The encrypted settings store (ConfigurationService, SettingsRepository) already exists and is reused, not rebuilt.
  • A merchant always resolves to a policy - the default { strict: true, cascade: false } applies when none is set.
  • Soldness of a product/variant can be determined at delete time.

11. Risks & Open Questions

Risk / questionMitigation / status
A cascade delete removes more than intendedCascade defaults off; cascade only runs when the owner explicitly enables it
A delete path bypasses the guardAll merchant-content deletes route through the single guard service
Deleting content tied to historical salesSold products/variants are asserted not-sold and refused
Encrypted settings unavailable at delete timePolicy read falls back to the safe default (strict on, cascade off)

12. Release Plan & Launch Criteria

AspectPlan
PhaseP2 (full management) - see URD feature catalog
RolloutAll merchants; no feature flag
MigrationNone - policy stored in the existing settings store; default applies when unset
Launch criteriaView/update endpoints permission-gated; strict-block, cascade, and not-sold guards verified across all delete paths
MonitoringBlocked-delete rate, cascade-delete volume, guard-bypass alerts (should be zero)

13. FAQ

What does the deletion policy default to? { strictCategoryDeletion: true, cascadeProductDeletion: false } - the safest combination: a category with products can't be deleted, and a category delete never cascades to products unless you turn cascade on.

Where is the policy stored? In the existing encrypted per-merchant settings store (SettingsRepository), not a new column or table.

Does the policy ever allow hard-deleting data? No - all deletes remain soft-delete (URD C-03). The policy only controls block / cascade / unlink behavior.

Can I delete a product that has already been sold? No - the guard asserts the product (and any variant) is not sold before removal, so historical sales stay intact.

Does this cover campaign deletion? No - campaign deletion is out of scope for this increment.

References

Proprietary and Confidential. Unauthorized copying, distribution, or use of this software is strictly prohibited.