A slot writes a row per seat of its hall when it is created, and every read on the booking path asks those rows. A seat with no row is a seat nobody can buy: it does not appear as taken, it does not appear at all, and the slot looks perfectly healthy while it quietly refuses that seat to everybody. A slot missing all of them reads as a sold-out house, which at least looks wrong to somebody. A slot missing a handful looks like a slot.
Both are reachable. A site whose code moved forward without the module being reinstalled has none of the rows. A hall drawn in the moments a save failed halfway, a table restored from a backup taken before some of it existed, or rows removed by hand while chasing something else leaves some of them. Nothing today notices either.
So the repair should be a reconcile rather than a special case for nothing-at-all: when a slot is saved, the seats its hall has and the rows the slot offers are compared, what is missing is added, and what belongs to a place the hall no longer has goes. It is two set operations rather than a walk, an insert of the places with no row and a delete of the rows with no place, so it costs the same on a studio and on a stadium. A slot is saved by an operator in the quiet weeks, never by a booker during a rush.
One thing it has to get right, and it is the reason this is not a two-line hook. A row added by the repair is free unless a booking already holds that seat, so the same pass has to take whatever the bookings hold, read from the bookings themselves. A repair that opens seats somebody is sitting in does not restore availability, it invents it, and the seat is then sold twice.
What it must not touch is a slot whose rows are all there and all taken. A sold-out house is a legitimate state and reopening it would be the same invention by another road.
This adds no update hook and does not replace one. The project ships none before 1.0 and reinstalling is the upgrade path; what this covers is a slot whose rows are wrong on a site nobody thought to rebuild, which is the case that policy leaves behind.
A seat that leaves the hall while a booking still holds it is the one case the reconcile cannot decide on its own, and it should not arise: the hall generator adds seats and never removes them, so a seat only goes when somebody deletes it deliberately. The repair still has to answer for it, because the answer decides whether a mistake stays a mistake or becomes a double sale.
What it must never do is free the seat. Deleting the row would leave the booking pointing at nothing; marking it free would offer a seat that is not in the building and sell it to somebody else. So a place that has gone with a row nobody holds is dropped, and one with a row somebody holds is kept and marked as gone rather than as free. Every read on the booking path asks whether a seat is free, so a seat marked that way is already out of sale, out of the block counts and out of the seating, without a single query changing.
That turns the flag into a state, which is worth naming as one rather than leaving 0 and 1 to grow a third meaning by accident. And it puts a condition on giving a seat back: releasing a booking on a seat the hall no longer has must drop the row instead of freeing it, which is also the moment the row can finally go, since nothing holds it any more. Refusing to delete a seat that a consuming booking holds, so an operator is stopped while they can still move the booking, belongs with it.
How the repair is reached matters as much as what it does, and the venue's own positions tab already shows the shape: a check that reports on demand and reads nothing back, and a repair that is run deliberately. The same three doors suit this. A check on the slot, comparing its rows against the hall and against the bookings, saying how many seats have no row, how many rows are for seats the hall no longer has, and how many flags disagree with what is booked. A button beside it that repairs. And a command for the whole site.
The button runs through the Batch API rather than in the request that submits it. Opening a hall writes a row per seat, so one slot of a stadium is eighty thousand of them: a plain submit handler would time out on exactly the venues this exists for, and leave the rows half written, which is the state it is meant to cure. Batched, it also gives the operator a count of what it is doing while it does it, which is what makes a repair button trustworthy rather than alarming.
The command is what an upgrade needs. A site that arrives with none of the rows has as many slots as it has evenings, and nobody wants to press a button once per evening: drush yoyaku:slot-seats, reporting by default and repairing with --fix, over one slot or all of them, in the manner of drush yoyaku:venue-positions.
Repairing when a slot is saved, which is the rest of this issue, covers the case nobody thinks to check. These cover the cases somebody already suspects.
The rows and what reads them arrive in #3616896: Seat a party under the hold's own locks, so a rush is served rather than refused.
AI-Generated: Yes (Claude Code was used to help draft this issue summary. I reviewed it before posting; there is no code on this issue yet.)
Issue fork yoyaku-3617528
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 #2
mably commentedComment #3
mably commentedComment #5
mably commentedComment #7
mably commented