`SourceItemsSave` accepts `inventory_source_item` rows for product types where source-item management is disallowed (configurable, bundle, grouped)
Nobody has claimed this yet.
- Dominant language
- PHP
- Stars
- 357
- Forks
- 262
- PR merge metrics
- No merged PRs in 30d
Description
### 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 ``.
### Steps to reproduce
1. Confirm no row exists in `inventory_source_item` for the configurable's SKU:
```sql
SELECT * FROM inventory_source_item WHERE sku = '';
-- expect: 0 rows
```
2. Create a Stock Sources CSV containing the configurable's SKU:
```csv
source_code,sku,status,quantity
default,,0,0
```
3. Admin → System → Data Transfer → Import. Entity Type = **Stock Sources**. Upload the CSV, submit.
4. The import reports success. Re-query:
```sql
SELECT source_code, sku, status, quantity
FROM inventory_source_item
WHERE 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 | | 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() === false`. Skeleton:
```php
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`:
```xml
Magento\InventoryConfiguration\Model\SourceItem\Validator\ProductTypeManagementAllowedValidator
```
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.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with vendor/magento/module-inventory/Model/SourceItem/Command/SourceItemsSave.php and the validator chain in vendor/magento/module-inventory/etc/di.xml. Read the existing validators and the product-type interfaces named in the issue, then inspect module-inventory-configuration DI wiring. Done means invalid configurable, bundle, and grouped SKUs are rejected before SaveMultiple writes inventory_source_item rows.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- php
- Domain
- backend, database
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Quiet
- Clarity
- Clearly specified
- Newbie friendliness
- 58/100