Preconditions and environment
- Magento Open Source 2.4.7-p9. Defect is independent of source topology (reproduced in single-source MSI mode but the missing validator applies to all
SourceItemsSaveInterface::execute callers regardless).
- Magento Inventory module versions:
magento/module-inventory 1.2.5 (registers the SourceItemValidatorChain and contains the existing validators that omit a product-type check)
magento/module-inventory-api 1.2.5 (defines SourceItemValidatorChain and SourceItemsSaveInterface)
magento/module-inventory-configuration 1.2.4 (suggested target for the new validator's DI wiring)
magento/module-inventory-configuration-api 1.2.3 (declares IsSourceItemManagementAllowedForProductTypeInterface)
magento/module-inventory-bundle-product 1.2.4 (provides the only existing type-aware validator, ShipmentTypeValidator, which scopes to bundles)
- PHP 8.3, MariaDB.
- At least one configurable (or bundle, or grouped) product enabled in the catalog. Note its SKU — for the repro below, call it
<CONFIGURABLE_SKU>.
Steps to reproduce
-
Confirm no row exists in inventory_source_item for the configurable's SKU:
SELECT * FROM inventory_source_item WHERE sku = '<CONFIGURABLE_SKU>';
-- expect: 0 rows
-
Create a Stock Sources CSV containing the configurable's SKU:
source_code,sku,status,quantity
default,<CONFIGURABLE_SKU>,0,0
-
Admin → System → Data Transfer → Import. Entity Type = Stock Sources. Upload the CSV, submit.
-
The import reports success. Re-query:
SELECT source_code, sku, status, quantity
FROM inventory_source_item
WHERE sku = '<CONFIGURABLE_SKU>';
Expected result
The import (and Magento\InventoryApi\Api\SourceItemsSaveInterface::execute more generally) rejects the row with a per-row validation error identifying the SKU and product type, on the basis that Magento\InventoryConfigurationApi\Model\IsSourceItemManagementAllowedForProductTypeInterface::execute('configurable') returns false. No write occurs.
Actual result
The import succeeds with a "rows imported" success message. The re-query in step 4 returns one row:
source_code | sku | status | quantity
default | <CONFIGURABLE_SKU> | 0 | 0.0000
A new inventory_source_item row exists for a SKU whose product type is declared (by IsSourceItemManagementAllowedForProductTypeInterface) to not be source-managed. The row persists indefinitely and has no UI surface for removal (configurable products do not expose a sources/quantity grid in the product edit form). Removal requires direct SQL or a developer-built command.
The same defect reproduces for bundle and grouped products.
Additional information
Root cause — missing validator. The validator chain at vendor/magento/module-inventory/etc/di.xml against Magento\InventoryApi\Model\SourceItemValidatorChain includes only:
Magento\Inventory\Model\SourceItem\Validator\SkuValidator
Magento\Inventory\Model\SourceItem\Validator\SourceCodeValidator
Magento\Inventory\Model\SourceItem\Validator\QuantityValidator
Magento\InventoryBundleProduct\Model\SourceItem\Validator\ShipmentTypeValidator (added by module-inventory-bundle-product for a bundle-specific check)
None of these consult Magento\InventoryConfigurationApi\Model\IsSourceItemManagementAllowedForProductTypeInterface. SourceItemsSave::execute (vendor/magento/module-inventory/Model/SourceItem/Command/SourceItemsSave.php) proceeds through Magento\Inventory\Model\ResourceModel\SaveMultiple::execute and inserts the row into inventory_source_item unconditionally.
Why the orphan row appears harmless today. The legacy-sync plugin Magento\InventoryCatalog\Plugin\CatalogInventory\UpdateSourceItemAtLegacyStockItemSavePlugin::aroundSave gates its cataloginventory_stock_item / cataloginventory_stock_status writes on the same IsSourceItemManagementAllowedForProductType check, so the orphan row does not propagate to the legacy tables for the configurable. The storefront-visibility pipeline (Magento\CatalogInventory\Helper\Stock, the catalog product collection plugins, the products GraphQL resolver) reads cataloginventory_stock_status and is therefore unaffected today.
Why it matters anyway.
- The orphan row appears in raw exports (
inventory_source_item table dumps, MSI export endpoints).
- It is visible in admin source-item filters and any custom report that joins
inventory_source_item directly.
- It will be consumed by any future or third-party MSI feature that does not also gate on
IsSourceItemManagementAllowedForProductType (a fair assumption given the gate exists in legacy-sync but is not enforced at write time).
- It cannot be removed via admin UI because configurable products don't expose a sources/quantity grid — the row has no UI surface for editing or deletion.
- The invariant being violated — "rows in
inventory_source_item exist only for product types where source-item management is allowed" — is the invariant the rest of MSI relies on when it decides to gate certain code paths by checking IsSourceItemManagementAllowedForProductType instead of by checking inventory_source_item row presence. The gate exists because the invariant is supposed to hold by construction at write time. It doesn't.
Suggested fix — new validator. Add a validator to SourceItemValidatorChain that rejects source items whose SKU resolves to a product type with IsSourceItemManagementAllowedForProductTypeInterface::execute(<typeId>) === false. Skeleton:
namespace Magento\InventoryConfiguration\Model\SourceItem\Validator;
use Magento\Framework\Validation\ValidationResult;
use Magento\Framework\Validation\ValidationResultFactory;
use Magento\InventoryApi\Api\Data\SourceItemInterface;
use Magento\InventoryApi\Model\SourceItemValidatorInterface;
use Magento\InventoryCatalogApi\Model\GetProductTypesBySkusInterface;
use Magento\InventoryConfigurationApi\Model\IsSourceItemManagementAllowedForProductTypeInterface;
class ProductTypeManagementAllowedValidator implements SourceItemValidatorInterface
{
public function __construct(
private readonly GetProductTypesBySkusInterface $getProductTypesBySkus,
private readonly IsSourceItemManagementAllowedForProductTypeInterface $isAllowed,
private readonly ValidationResultFactory $validationResultFactory,
) {}
public function validate(SourceItemInterface $sourceItem): ValidationResult
{
$sku = (string) $sourceItem->getSku();
$type = $this->getProductTypesBySkus->execute([$sku])[$sku] ?? null;
if ($type !== null && !$this->isAllowed->execute($type)) {
return $this->validationResultFactory->create([
'errors' => [
__('Source items are not supported for product type "%1" (SKU "%2").', $type, $sku),
],
]);
}
return $this->validationResultFactory->create(['errors' => []]);
}
}
Wire it into the validator chain via vendor/magento/module-inventory-configuration/etc/di.xml:
<type name="Magento\InventoryApi\Model\SourceItemValidatorChain">
<arguments>
<argument name="validators" xsi:type="array">
<item name="productTypeManagementAllowed"
xsi:type="object">Magento\InventoryConfiguration\Model\SourceItem\Validator\ProductTypeManagementAllowedValidator</item>
</argument>
</arguments>
</type>
This enforces the invariant at the only place it can be enforced authoritatively: the write boundary.
Release note
Add validation to prevent inventory_source_item rows from being created for product types that do not support MSI source-item management (configurable, bundle, grouped). The Stock Sources import (and any other caller of SourceItemsSaveInterface::execute) now rejects rows whose SKU resolves to a non-source-managed product type, preserving the invariant that the rest of the MSI codebase relies on.
Triage and priority
S3 — Average. The orphan rows do not break the storefront-visibility pipeline today (the legacy-sync plugin gates them out), so the visible impact is limited to admin reports, raw exports, and third-party MSI consumers. However, the rows violate an architectural invariant the rest of MSI assumes by construction, have no UI surface for removal, and will silently surface as a defect in any future or third-party code that consumes inventory_source_item without also gating on product type. Best treated as a data-integrity bug fixed at the write boundary rather than worked around by every consumer.
Preconditions and environment
SourceItemsSaveInterface::executecallers regardless).magento/module-inventory1.2.5 (registers theSourceItemValidatorChainand contains the existing validators that omit a product-type check)magento/module-inventory-api1.2.5 (definesSourceItemValidatorChainandSourceItemsSaveInterface)magento/module-inventory-configuration1.2.4 (suggested target for the new validator's DI wiring)magento/module-inventory-configuration-api1.2.3 (declaresIsSourceItemManagementAllowedForProductTypeInterface)magento/module-inventory-bundle-product1.2.4 (provides the only existing type-aware validator,ShipmentTypeValidator, which scopes to bundles)<CONFIGURABLE_SKU>.Steps to reproduce
Confirm no row exists in
inventory_source_itemfor the configurable's SKU:Create a Stock Sources CSV containing the configurable's SKU:
Admin → System → Data Transfer → Import. Entity Type = Stock Sources. Upload the CSV, submit.
The import reports success. Re-query:
Expected result
The import (and
Magento\InventoryApi\Api\SourceItemsSaveInterface::executemore generally) rejects the row with a per-row validation error identifying the SKU and product type, on the basis thatMagento\InventoryConfigurationApi\Model\IsSourceItemManagementAllowedForProductTypeInterface::execute('configurable')returnsfalse. No write occurs.Actual result
The import succeeds with a "rows imported" success message. The re-query in step 4 returns one row:
A new
inventory_source_itemrow exists for a SKU whose product type is declared (byIsSourceItemManagementAllowedForProductTypeInterface) to not be source-managed. The row persists indefinitely and has no UI surface for removal (configurable products do not expose a sources/quantity grid in the product edit form). Removal requires direct SQL or a developer-built command.The same defect reproduces for bundle and grouped products.
Additional information
Root cause — missing validator. The validator chain at
vendor/magento/module-inventory/etc/di.xmlagainstMagento\InventoryApi\Model\SourceItemValidatorChainincludes only:Magento\Inventory\Model\SourceItem\Validator\SkuValidatorMagento\Inventory\Model\SourceItem\Validator\SourceCodeValidatorMagento\Inventory\Model\SourceItem\Validator\QuantityValidatorMagento\InventoryBundleProduct\Model\SourceItem\Validator\ShipmentTypeValidator(added bymodule-inventory-bundle-productfor a bundle-specific check)None of these consult
Magento\InventoryConfigurationApi\Model\IsSourceItemManagementAllowedForProductTypeInterface.SourceItemsSave::execute(vendor/magento/module-inventory/Model/SourceItem/Command/SourceItemsSave.php) proceeds throughMagento\Inventory\Model\ResourceModel\SaveMultiple::executeand inserts the row intoinventory_source_itemunconditionally.Why the orphan row appears harmless today. The legacy-sync plugin
Magento\InventoryCatalog\Plugin\CatalogInventory\UpdateSourceItemAtLegacyStockItemSavePlugin::aroundSavegates itscataloginventory_stock_item/cataloginventory_stock_statuswrites on the sameIsSourceItemManagementAllowedForProductTypecheck, so the orphan row does not propagate to the legacy tables for the configurable. The storefront-visibility pipeline (Magento\CatalogInventory\Helper\Stock, the catalog product collection plugins, theproductsGraphQL resolver) readscataloginventory_stock_statusand is therefore unaffected today.Why it matters anyway.
inventory_source_itemtable dumps, MSI export endpoints).inventory_source_itemdirectly.IsSourceItemManagementAllowedForProductType(a fair assumption given the gate exists in legacy-sync but is not enforced at write time).inventory_source_itemexist only for product types where source-item management is allowed" — is the invariant the rest of MSI relies on when it decides to gate certain code paths by checkingIsSourceItemManagementAllowedForProductTypeinstead of by checkinginventory_source_itemrow presence. The gate exists because the invariant is supposed to hold by construction at write time. It doesn't.Suggested fix — new validator. Add a validator to
SourceItemValidatorChainthat rejects source items whose SKU resolves to a product type withIsSourceItemManagementAllowedForProductTypeInterface::execute(<typeId>) === false. Skeleton:Wire it into the validator chain via
vendor/magento/module-inventory-configuration/etc/di.xml:This enforces the invariant at the only place it can be enforced authoritatively: the write boundary.
Release note
Add validation to prevent
inventory_source_itemrows from being created for product types that do not support MSI source-item management (configurable, bundle, grouped). The Stock Sources import (and any other caller ofSourceItemsSaveInterface::execute) now rejects rows whose SKU resolves to a non-source-managed product type, preserving the invariant that the rest of the MSI codebase relies on.Triage and priority
S3 — Average. The orphan rows do not break the storefront-visibility pipeline today (the legacy-sync plugin gates them out), so the visible impact is limited to admin reports, raw exports, and third-party MSI consumers. However, the rows violate an architectural invariant the rest of MSI assumes by construction, have no UI surface for removal, and will silently surface as a defect in any future or third-party code that consumes
inventory_source_itemwithout also gating on product type. Best treated as a data-integrity bug fixed at the write boundary rather than worked around by every consumer.