A value object made readonly in 2022, and the requirement in 2026 that would have broken a mutable one.
final readonly class TaxRate
{
public function __construct(
public string $jurisdiction,
public int $basisPoints,
public DateTimeImmutable $effectiveFrom,
) {}
}
// the change: rates become time-varying, so a rate is
// now looked up for a date rather than held.
//
// because it is immutable, every existing instance is
// still valid — they are values at a point in time.
// a mutable one would have been updated in place
// somewhere, and the historical figures would have
// moved.
The property that mattered was not thread safety or defensive copying — it was that a value which cannot change cannot be retroactively wrong. Reports run against orders from 2023 still produce the 2023 figures, which is the requirement nobody stated and immutability satisfied by accident.