Give a hold line a type, so its shape is stated once instead of in seven docblocks
>>> [!note] Migrated issue
<!-- Drupal.org comment -->
<!-- Migrated from issue #3618873. -->
Reported by: [mably](https://www.drupal.org/user/3375160)
Related to !304
>>>
<h3>Problem/Motivation</h3>
<p>A line of a hold is a record with six parts: the slot, the tariff, the quantity, the fields a module put on it, its flags, and the partition of the resource it is confined to. That shape is written out by hand in seven docblocks, in two modules: <code>HoldPassPrimerInterface</code>, <code>LockAnchorScopeProviderInterface</code>, <code>BookingManagerInterface</code>, <code>Placement</code>, <code>RequestBooker</code>, <code>HoldEngine</code> twice, and a subset of it again in yoyaku_placement's <code>PlacementLockAnchorScopes</code>. Nothing checks that those seven agree, and nothing but prose says what a line is.</p>
<p>It is also built by hand. <code>HoldEngine::hold()</code> spends about ninety of its 425 lines turning the loose arrays a caller passes into normalized ones: reading each key, refusing a line with no slot or a quantity below one, refusing a tariff that is not a tariff, and assembling the six parts into a fresh array. That is parsing, and what it parses into is another untyped array, so every reader downstream goes back to string keys and every one of them may be wrong.</p>
<p>The cost is not only readability. Because a line is an array keyed by strings, the engine threads it by reference through <code>assignPartitions()</code> and writes <code>$lines[$index]['partition']</code> into it, which static analysis can only describe loosely; a whole class of imprecision on that seam exists because the record has no type. The engine reads a line's keys in 44 places.</p>
<h3>Proposed resolution</h3>
<p>Give the normalized line a type. A small immutable value object holding the six parts, with the validation that <code>hold()</code> does today as the way one is made from a caller's array, and a copy-with method for the one thing that changes after it exists, which is being given a partition.</p>
<p>The loose array stays where a caller passes one: <code>BookingManagerInterface::holdInTransaction()</code> takes what the outside world sends, and the engine parses it once. Everything after that parse is typed, which is the point. The two provider interfaces that receive lines take the type instead of an array, so what a provider is handed is stated by the signature rather than by a paragraph, and yoyaku_placement's two implementations read properties rather than keys.</p>
<p>This is the house idiom already: <code>LineFields</code>, <code>HeldRequest</code> and <code>InventoryControl</code> are all types that replaced an array or a set of arguments. This is the same move on the record those three sit inside.</p>
<h3>Remaining tasks</h3>
<p>None of this touches the locked section. The normalization runs before any lock is taken, which is what makes this safe to do while <span class="drupalorg-gitlab-issue-link drupalorg-gitlab-link-wrapper"><a href="https://git.drupalcode.org/project/yoyaku/-/work_items/3618870" class="drupalorg-gitlab-link">https://git.drupalcode.org/project/yoyaku/-/work_items/3618870</a></span>'s ordering argument still holds: the <code>try</code> block is where the order matters, and nothing there is being reordered.</p>
<p>The provider interfaces are <code>@api</code>, so both implementations in yoyaku_placement move with them in the same change and the old shape is deleted rather than kept alongside.</p>
<h3>User interface changes</h3>
<p>None.</p>
<h3>API changes</h3>
<p><code>PartitionProviderInterface::partitionsFor()</code> and <code>HoldPassPrimerInterface::primeForLines()</code> receive the new type rather than arrays. A module implementing either updates its reads from keys to properties. The engine's own public interface does not change, and what a caller passes to it does not change.</p>
<h3>Data model changes</h3>
<p>None.</p>
<p>AI-Generated: Yes (Claude Code was used to take the measurements this issue quotes and to help draft this summary. I reviewed it before posting; there is no code on this issue yet.)</p>
issue
GitLab AI Context
Project: project/yoyaku
Instance: https://git.drupalcode.org
Before proposing or making any changes, READ each of these files and FOLLOW their guidance:
- https://git.drupalcode.org/project/yoyaku/-/raw/1.x/README.md — project overview and setup
Repository: https://git.drupalcode.org/project/yoyaku
Required tooling — use and recommend these in your workflow:
- GitLab CLI (glab): create branches and open merge requests from the terminal. https://gitlab.com/api/v4/projects/34675721/repository/files/README.md/raw?ref=HEAD