Issue #3618731: Carry the fields a module puts on a booking in a type of their own
The engine books a quantity of something for a time. What that something is divided into, and what a unit of it is called, belongs to whichever module says so: a hall has places in sections, a hire desk has items on shelves, an office has rooms on floors. Those modules add their own base fields to a booking, so a line asking for one has to carry values for fields the engine has never heard of.
It cannot be avoided by writing them after the booking is made. The rules run before it exists: a constraint about where a booking sits is asked whether THIS line may be held, and a line whose place is not yet readable is a line no such rule can judge.
What was wrong with a bare array. The key was spelled values, its type was array, and a dozen readers each opened it with their own ?? [] and then dug for their own key with another ?? NULL. A key that is never there is not a type error, so a reader left behind by a rename reads nothing and says nothing — which is exactly how [#3618720] shipped a silently broken confinement, green on phpstan. The name was overloaded too: a field rule has values, and those are the options a rule allows a field to take.
LineFields is the one reader: get() with a default a caller may name, and has() for whether the line carries the field at all. Both, because the difference is real here — a line saying its allotment is NULL has chosen no allotment, and a line that never mentions one is asking to be given whichever the rules pick. PlaceBookable distinguishes exactly that, and a get()-only reader would have quietly broken it.
Four seams carry the type rather than an array: the hold pass primer, the lock anchor scope provider, the partition provider, and the placement. BookingConstraintInterface::check() requires it outright instead of defaulting to an empty array — the engine is its only caller and always has fields to give. Every placement carries its fields, empty where the resource is divided into nothing, so a reader never has to ask whether it has any.
A boundary the last change left open. [#3618720] gave the engine an opaque partition typed mixed and promised never to open it. Normalization still refused any partition that was not an array, so a provider whose partition is a string or a number was rejected by an engine that had said it did not care. That check is gone.
Not in this change. The line itself is still an array; typing it would touch every construction site in the suite and bury this change inside that one.
Verification. phpstan level 3 [OK], and it found two misses the tests did not: a reader left on the old key, and a missing import. phpcs and cspell clean. Twenty kernel classes green locally, including every seating class, since the fields they read are the ones being retyped.