#3618663: Take the engine out of another module's schema, in the two places it reached in added a guard listing four field names the engine must not mention. It worked for the two reach-ins it was written for and then let the next one straight through: SingleTariffPerCellValidator asks a tariff hasField('grade'), which is the same defect in the same shape, and it passed because nobody had thought to add that word to the list. A list of forbidden words is always one discovery behind.
So this guard tests the shape instead. A hasField() call on a literal is the engine admitting a column might not be there, which is it admitting the column is not its own: what it defines, it knows about. Literal names are therefore held to a list of the engine's own fields, and every other rule must take the field name in a variable, which is what a rule written for whatever a module added looks like anyway. Sixteen of the engine's eighteen hasField() calls already do one or the other.
Seen to fail twice before passing: once against grade, and once against a planted hasField('place') in an unrelated engine class, which it named and located.
The one exception, listed rather than hidden
grade is named in the guard as a known exception with its reason. The rule that reads it is not misplaced: a cell is a tariff class and a band, and the engine is what prices cells. The answer is for bands to become the engine's own, not for a pricing rule to be exiled to whichever module happens to draw them. Moving the constraint into the placement module was written and then discarded for exactly that reason: it would have entrenched the thing worth fixing.
A guard with one honest exception is worth more than a guard that waits for the exception to be gone, because in the meantime it is the only thing standing between the engine and the next silent leak.
Also found while widening it, not fixed here
Four hardcoded lists of another module's field names, which are a milder species than a reach-in and belong with the vocabulary work rather than with a guard: EntityTitle's scope list, ResourceFieldGroups mapping five fields to a group named after the module that adds them, BookingHooks carrying both defaults and a followed-fields list for three of them, and the machine name widget scoping by one. The defaults are the worst of the four: one of them defaults a field to a plugin id the engine cannot provide.
AI-Generated: Yes (Claude Code wrote the guard and this summary. It was seen to fail against a real leak and against a planted one before it passed, and the discarded alternative is described above because the reasoning is worth keeping.)
Issue fork yoyaku-3618709
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 #4
mably commented