Issue #3618773: Order the plan's offers by weight and then by id, so a bare seat click always books the same one

The plan sorts the offers it draws by weight alone. Two tariffs an operator never weighted tie, and the database is then free to answer in either order. The plan draws them in that order and a seat clicked without choosing an offer takes the first, so the same click books a different price on different page loads, and a rule attached to one of the two reaches the booking only sometimes.

->sort('weight')->sort('id'), which this same file already does two hundred lines further down for the venue's own rows. First created wins: stable, and what an operator who never reordered anything would expect.

What this was not. It surfaced as a browser test failing about one run in eighty, and five explanations were wrong before the right one: next-major specificity (it reproduces on both lanes), tight wait budgets (twenty seconds against an assertion that resolves in well under one), a held element handle (both clicks reached the server and were answered), two overlapping requests from one booker (the click queue is a no-op while a request is out, so the second click is remembered rather than sent), and a per-transaction ceiling being bypassed (it was never asked).

What settled it was a trace of the resolution itself: the request gathered the hosts governing the line and found no attachment at all, on the right tariff, while the attachment sat in storage. The two readings were of different tariffs. The scenario capped one of its two prices and the click booked the other, so the engine looked for a ceiling on what was actually being booked, correctly found none, and correctly held both places. Every response body had said so from the first capture, naming "tariff":4 while the ceiling was on tariff 3.

The test was wrong too, separately. It claimed to test that a seat refused by a ceiling is not painted as taken, and never established the condition that claim needs: that the click books the tariff it capped. It now asserts both halves of its premise before clicking — the ceiling is really attached, and it is attached to the offer the click will use — and with the order reversed it fails on that at once, naming it, instead of twenty seconds later on a live region.

Verification. A regression test builds two unweighted tariffs and asserts they come back in creation order; it fails when the tie-break is reversed, checked both ways. The browser scenario was then sampled fifteen times per pipeline under the suite's ordinary concurrency, ten pipelines at a time: 300 samples with the fix, zero failures, against a rate of about one in sixty. Every piece of that scaffolding is reverted and .gitlab-ci.yml is byte-identical to 1.x.

phpstan level 3 [OK], phpcs and cspell clean.

Not in here. Twenty-two find(...)->click() sites remain in the two picker tests, holding a handle across two round trips with a redraw behind it — a real defect by the trait's own stated rule, and not what this was. The queue's watchdog also clears its in-flight flag after fifteen seconds without the answer having arrived, which is unsafe on its own terms and did not cause this.

Edited by Frank Mably

Merge request reports

Loading
Loading