Let each entity type say what its rows belong to, instead of the engine holding the list
>>> [!note] Migrated issue
<!-- Drupal.org comment -->
<!-- Migrated from issue #3618762. -->
Reported by: [mably](https://www.drupal.org/user/3375160)
Related to !295
>>>
<p>Child of <span class="drupalorg-gitlab-issue-link drupalorg-gitlab-link-wrapper"><a href="https://git.drupalcode.org/project/yoyaku/-/work_items/3618694" class="drupalorg-gitlab-link">https://git.drupalcode.org/project/yoyaku/-/work_items/3618694</a></span>. Two screens need to know what a row belongs to. A page title names the nearest one, so that two identically named rows are told apart: "Edit section VIP of Grand hall". A machine name is unique within the things it belongs to rather than across the site, so two halls may both name a section "vip".</p>
<p>Both were answered by a list of field names written in the engine. <code>EntityTitle</code> held <code>venue, resource, slot, preset, pinning, configuration</code>; the machine name widget held <code>tenant, venue</code>. Four of those seven names are fields the engine does not own: a hall belongs to the module that has halls. The module that adds a field is the one that knows what it means, and neither list could be kept honest by anything, because a field renamed on the far side leaves a title saying nothing and a key unique site-wide, and neither fails.</p>
<h3>One question, asked of the owner</h3>
<p>They are the same question. An entity type now declares what its rows belong to, nearest first, as <code>parent_fields</code> in its attribute. A title takes the first of the chain that names an entity; a machine name is narrowed by every field in the chain the row has filled. Twenty-two entity types declare their own chain, each in the module that defines it, and the engine names no field it does not own.</p>
<p><code>EntityParents</code> is the one reader, so the property name is written once. The tenant is last in every chain because it is the widest wall, and it is a plain string rather than a reference, which is why a title skips it and a machine name does not: "of default" tells a reader nothing they came for, while a key free to collide across tenants is not unique at all.</p>
<h3>Why this is declared rather than derived</h3>
<p>An entity type attribute's <code>additional</code> is an open bag: a misspelled property is not an error, it is silence, and so is a field name that no longer exists. Both failures are invisible at runtime, which is the same shape of failure as <span class="drupalorg-gitlab-issue-link drupalorg-gitlab-link-wrapper"><a href="https://git.drupalcode.org/project/yoyaku/-/work_items/3618720" class="drupalorg-gitlab-link">https://git.drupalcode.org/project/yoyaku/-/work_items/3618720</a></span> and <span class="drupalorg-gitlab-issue-link drupalorg-gitlab-link-wrapper"><a href="https://git.drupalcode.org/project/yoyaku/-/work_items/3618731" class="drupalorg-gitlab-link">https://git.drupalcode.org/project/yoyaku/-/work_items/3618731</a></span>. So the guard comes with it: a test asserts that every yoyaku content entity type declares a chain, that every field named in one exists on that type, and that each chain ends at the tenant. Both failure modes were seen to fail before the change was trusted, by removing one declaration and by pointing another at a field that does not exist.</p>
<h3>One deliberate change of behaviour</h3>
<p>A resource also references a pinning and a configuration, and a slot references a pinning, and the old list treated those as scopes. They are policies attached to the row, not things it belongs to, so a resource with no hall used to be titled "of" the pinning it happened to use. Its chain is the hall and the tenant, and such a resource now names no parent.</p>
<h3>Not in this change</h3>
<p><code>BookingHooks</code> keeps three lists that name other modules' fields, and it cannot use a declaration: those lists are read inside <code>hook_entity_base_field_info_alter()</code>, which answers by reading the very definitions that would carry it. That needs a mechanism of its own rather than this one bent to fit, so it is a separate issue.</p>
<p><code>ResourceFieldGroups</code> is staying as it is, and not because it was missed. It maps every module's resource fields to a heading on the resource form, and that map is core curating one form's arrangement: the groups are core's own headings, a field no group names is left ungrouped rather than misplaced, and the reason the map is in one place is that two screens were grouping the same fields differently. Handing each module its own group would leave the whole arrangement owned by nobody.</p>
<h3>Verification</h3>
<p>phpstan at level 3 reports no errors, phpcs and cspell clean. Eight kernel classes green, including the machine name screens and the translation screens that read the same widget, plus a new test of the consequence at the surface: a hall that already has a "vip" section refuses a second, and another hall may name one of its own.</p>
<p>AI-Generated: Yes (Claude Code designed and made this change and wrote this summary. The mechanism is the entity type property bag that Drupal core uses for the same purpose, verified against core rather than assumed, and the chains were read off the running site rather than inferred from the code, so that every declaration reproduces what the two screens already did.)</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