Refusals the engine raises are built with sprintf, name entity ids, and reach the booker word for word on a public page. They are not translatable, so a French site shows them in English, and they say things a booker cannot act on.

What a booker sees. Both observed on screen, the first in a browser test of the booking page and the second in a kernel run:

  • "Slot tariff 12: 2 of 2 unit(s) left, 10 requested." from BookingManager::assertLimit(). The name is not read from the entity: the label is built as the words "Slot tariff" followed by the id.
  • "Tariff 8 is not on offer on this booking channel." from RequestBooker::assertOnOffer(), which names the tariff id.

There are nine of them, not two. The leak is the catch rather than those two sentences: four places pass a caught message straight to a booker, and all four catch the base BookingException, so anything thrown under them is shown. Every refusal a booker can reach:

  • The quota or the capacity that will not take the whole ask, in BookingManager::assertLimit().
  • A resource, and a tariff, not on offer on this booking channel, in RequestBooker::assertOnOffer().
  • A slot, and a tariff, deleted while a hold waited for its lock, in BookingManager::reloadForCheck().
  • A lock anchor that was missing and could not be written, in LockAnchors.
  • Seats already taken when the hold went to claim them, in SlotPlaces.
  • A place a booking save cannot hold, in SlotPlaceHooks.
  • A hold that had lapsed before it could be confirmed, and an order no longer open for editing.

What is wrong with it. The strings are built with sprintf rather than t(), so they never reach the translation template. They put entity ids on a public page, where an id means nothing to a booker. And they name what the surface has already named: the message is shown beside the offer card that carries the label. SeatingCloseness::message() in yoyaku_placement is written the other way on purpose, and Broken::refusal() states the doctrine in its docblock: what the visitor is told is not what went wrong, and the id belongs to the log.

The fix. A refusal carries two sentences. getMessage() keeps the ids and the states, in English, for the log and the stack trace. What a booker may read is a translatable sentence attached beside it and read back through getTranslatedMessage(), named after core's own UnmetDependenciesException, which splits the same two sentences the same way. Surfaces ask one service, which returns the sentence when there is one and otherwise logs the technical message and answers with a generic one, so a message written for a developer cannot reach a page by accident. No new exception classes are needed: the existing subclasses already carry control-flow meaning, and the sentence is data on the base class.

Also found. The French catalogue carries "The slot is no longer available.", translated, with nothing in the codebase calling t() on it. An orphan of this same bug: the sentence was worth translating and could never be shown.

Found while auditing the branch on #3617848: Claim a party's places through an endpoint of its own, so the booking page stays cacheable and its notices stay long enough to be read, which shows these messages on the offer cards it draws; it does not introduce them.

AI-Generated: Yes (Claude Code was used to help draft this issue summary, swept the engine for the other seven messages, and wrote the code on the merge request. I reviewed both before posting.)

Issue fork yoyaku-3617964

Command icon 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

mably created an issue. See original summary.

mably’s picture

Status: Active » Needs review
mably’s picture

Title: Refuse in the booker words: two engine messages reach the page untranslated, naming entity ids » Refuse in the booker words: nine engine messages reach the page untranslated, naming entity ids
Issue summary: View changes

  • mably committed 7ef59e8c on 1.x
    fix: #3617964 Refuse in the booker words: nine engine messages reach the...
mably’s picture

Status: Needs review » Fixed

Now that this issue is closed, review the contribution record.

As a contributor, attribute any organization that helped you, or if you volunteered your own time.

Maintainers, credit people who helped resolve this issue.

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.