Child of #3618694: Take every submodule out of the engine's words, in code and in prose. 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. So the values travel with the line.
What was wrong with carrying them in 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. That is not hypothetical: it is exactly how #3618720: Call a partition of a resource a partition, in the engine and across the seam shipped a silently broken confinement, green on phpstan, caught only by five kernel tests and one browser test.
The name was also overloaded. A field rule has values too, and those are the options a rule allows a field to take rather than one booking's own.
What lands
A LineFields type, and the line key renamed from values to fields. It is the one reader: get() for a value, 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. A place the booker chose and a place the house gave are told apart the same way, and a line naming neither means what its own reader says it means.
Four seams carry the type now rather than an array: the hold pass primer, the lock anchor scope provider, the partition provider, and the placement itself. BookingConstraintInterface::check() requires it outright instead of defaulting to an empty array, because the engine has one caller and always has fields to give it. Every placement a provider answers with carries its fields, empty where the resource is divided into nothing, so a reader of a placement never has to ask whether it has any.
A boundary the last change left open
#3618720: Call a partition of a resource a partition, in the engine and across the seam gave the engine an opaque partition, typed mixed, and promised it would never be opened. Its 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.
Deliberately not in this change
The line itself is still an array. Typing it would touch every construction site in the test suite and would bury this change inside that one, so it is its own step. The four lists where the engine hardcodes another module::field names are untouched, as is the theatre in the engine's prose.
Verification
phpstan at level 3 reports no errors, and it found two of the misses in this change that the tests did not: a reader left on the old key, and a missing import. phpcs and cspell clean. The kernel classes that exercise every seam are green, including every seating class, since the fields they read are the ones being retyped.
AI-Generated: Yes (Claude Code designed and made this change and wrote this summary. The shape follows the attribute bag that Symfony and Drupal core both use for extension-owned values on a request, and the accessor set was derived from what the existing readers actually needed rather than guessed: one of them distinguishes a field set to nothing from a field never mentioned, which a get()-only reader would have quietly broken.)
Issue fork yoyaku-3618731
Show commands
Start within a Git clone of the project using the version control instructions.
Or, if you do not have SSH keys set up on git.drupalcode.org:
Comments
Comment #3
mably commentedComment #5
mably commented