magento / magento/inventory

`SourceItemsSave` accepts `inventory_source_item` rows for product types where source-item management is disallowed (configurable, bundle, grouped)

Open
#3,455 3 comments 1 reaction 0 assignees View on GitHub

Nobody has claimed this yet.

Issue: ready for confirmation
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

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.