One supplier, two API versions running simultaneously during their own migration, and a translation layer built for one shape.
interface ConsignmentMapper
{
public function supports(array $payload): bool;
public function toConsignment(array $payload): Consignment;
}
// selected by a discriminator in the payload, because
// the supplier sends both shapes to the same webhook
// endpoint during their rollout.
final class MapperChain implements ConsignmentMapper { /* ... */ }
The layer had assumed one upstream shape at a time, which is the assumption every translation layer starts with and which a supplier mid-migration violates. A chain selected by a discriminator is the smallest change that handles it, and the discriminator itself was not documented — it was found by logging payloads and diffing.