Keep ticket waves when a client edits an event without them #65
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "fix/preserve-ticket-waves-on-edit"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Data loss, found while checking whether the webapp had been updated for waves. Refs #33.
The bug
api_event_updatereplacesextrawholesale. A client that rebuilds the envelope instead of round-tripping it destroyed every wave: the list arrived empty,ensure_ticket_wavessynthesized a single primary wave from the event-levelamount_tickets, and a multi-wave event silently collapsed into one tier carrying whatever numbers that client happened to send.Reproduced against a running instance before fixing — a PUT whose
extraomitted the key turned a two-wave event into:No error, 200 response, both tiers gone.
The fix
Carry the stored waves over unless the request names the key.
promo_codeshas had exactly this guard since the v1.6.8 merge, for exactly this reason, ten lines further down the same function.ticket_wavesis the same class of state — organiser-managed, living inextra, invisible to a client that doesn't implement it. I wrote the first guard and didn't extend the reasoning to the second; this closes that.The carry-over runs before
_validate_wave_capacity, which matters: otherwise a client that omitted both the waves and a real capacity would be rejected for a zero-capacity primary wave that only existed because its waves had just been dropped. An explicit[]still resets, matching the promo-codes contract.Scope of exposure
The aio webapp is not what surfaced this and is not affected —
CreateEventDialogdoes...(props.event?.extra ?? {}), so it preserves waves as an undeclared runtime key, by accident rather than design. Any client that buildsextrafrom scratch is exposed, which now includes anything written against the documentedEventExtrashape, sinceticket_wavesisn't in the webapp's type either.Tests
6 new, including one that pins the ordering against
_validate_wave_capacityand one parameterised over both organiser-ownedextrafields, so dropping either guard fails. 126 pass; ruff, black clean; mypy error set unchanged from baseline.Not yet re-run against the live instance — it needs a restart to pick up the change, and I'd batch that with the webapp work rather than ask for two.
Release
mainis already taggedv1.6.8-aio.1, so this wants either av1.6.8-aio.2or a ride-along with the next events release — your call. Nothing is breaking in production today, since the webapp is the only client editing events and it's incidentally safe.`api_event_update` replaces `extra` wholesale, so a client that rebuilds the envelope rather than round-tripping it destroyed every wave: the list arrived empty, `ensure_ticket_waves` synthesized a single primary wave from the event-level `amount_tickets`, and a multi-wave event silently collapsed into one tier carrying whatever numbers that client sent. Reproduced against a running instance before fixing — a PUT whose `extra` omitted the key turned a two-wave event into: amount_tickets=999 price=77.0 waves=1 primary "Primary wave" 77.0 999 `promo_codes` has carried the same guard since the v1.6.8 merge, for the same reason and in the same function; `ticket_waves` is the same class of state — organiser-managed, living in `extra`, and invisible to a client that does not implement it. I added the first and did not extend the reasoning to the second. The carry-over runs before `_validate_wave_capacity` so validation sees the waves the event will actually end up with; otherwise a client that omitted both the waves and a real capacity would be rejected for a zero-capacity primary wave that existed only because its waves had just been dropped. An explicit `[]` still resets, matching promo codes. Note the aio webapp is not what surfaced this — it spreads the existing `extra` and so preserves waves by accident. Any client that does not is exposed. 6 tests, including one pinning the ordering and one that fails if either organiser-owned `extra` field loses its guard. 126 pass.