Set up a venue and its door zones
Create the room, give it a capacity, a timezone and its doors — then let a manager decide which of those doors tonight actually uses.
Owned by Admin · 17 steps · about 25 minutes
Why this exists
A venue is the physical room: a name, a permanent slug, an address, an IANA timezone, a capacity, advisory door and curfew times, and the zones inside it — the doors the readers are pinned to. Before this existed the room was a free-typed text column on the event and the zones belonged to nobody, which meant three separate questions — what time is it here, how many people fit and which doors exist — had three different answers depending on who you asked.
The one rule that governs everything about zones
A door reader authorises a scan by comparing strings. The list of zone codes is snapshotted into the pass when it is issued, signed, and hashed; at the door the reader's own code is looked for in that list. Nothing in that comparison knows what a venue is. So a zone code is globally unique, forever — not unique per venue. If two rooms both owned a zone called A, a ticket for the warehouse would scan green at the other room's door, because there is no venue in scope at the moment of the decision.
Three consequences you will meet as soon as you start clicking. Codes are derived, never typed — you give a zone a name and a short suffix and the platform mints the code from the venue's slug. Codes and zone ids are immutable: editing a zone's code is refused outright. And a suffix that merely normalises onto an existing code is refused as a collision even when the two strings look different, which is also why a suffix beginning with zone_ is rejected as reserved.
Two kinds of zone, and why the second one exists
Venue zones are permanent. They own the readers, they are what ticket tiers are mapped to, and they outlive every event.
Event zones are per night. One roster table does two jobs: activation — the backstage door exists but tonight's event does not use it, so a tier that would normally open it does not — and temporary zones: a meet-and-greet room or a second stage that is a real zone for one event and is then retired forever.
The safety net is worth learning explicitly: an event with no roster rows at all allows every zone. That is what keeps every pre-existing event working. The first roster write for an event materialises its current effective zone set as active first, so adding one temporary room can never silently switch the rest of the building off.
Two things the plans describe differently from what was built
First, the per-event roster has no admin page. It is four JSON endpoints. Reading and writing it today means calling them; the venue pages cover the permanent half only.
Second, this is not an admin-only feature. A venue_manager can create venues, edit them, and manage zones. Only two actions are reserved to an admin: archiving a venue, and overriding an event's capacity gate. Both destroy or bypass a control, which is the line this platform draws everywhere else too.
Before you start
- An admin session for the archive and capacity-override steps.
- A venue_manager or admin session for everything else.
- The seed ships three venues: Main Warehouse (250, the default), Blue Room (120) and Mezzanine (60).
- An event to practise the per-event roster against. Any seeded event will do.
Practise with
| Persona | Password | Note | |
|---|---|---|---|
| admin | admin@club.test | admin123 | the only account that can archive a venue or override a capacity gate |
| venue_manager | manager@club.test | manager123 | can create and edit venues and zones; runs the per-event zone roster |
Steps 1–10 — Admin
their manual →-
1Open the venue list. Each row shows the room, its resolved timezone, its capacity and its zones. One venue is flagged as the default — that is the one an event inherits from when it is not bound to a room of its own.Expected result Three seeded venues, with Main Warehouse marked as the default.Watch out for Archived venues are hidden unless you ask for them. A room you cannot find has usually been archived, not deleted — nothing here deletes a venue.
-
2Start a new venue. The four fields that carry real weight are the name, the slug, the timezone and the capacity. Address, contact and notes are description; the other four are load-bearing.Expected result The venue form, with the platform's timezone preselected.Watch out for Leave the slug blank and it is derived from the name. Whatever it ends up as, it is permanent — the slug is immutable and a rename attempt is refused. It is in the URL of the venue page and in every zone code the room ever mints.
-
3Save it. The timezone must be a real IANA name (Europe/Berlin, not CET) and the capacity is the fire-code ceiling for the room, not an ambition.Expected result A 303 straight onto the new venue's own page.Watch out for Capacity is the number that gates ticket sales for every event held here, so it is written to an append-only audit log the moment it changes. Type it as if somebody will ask you to justify it, because that is exactly what the log is for.
-
4Open the venue. The page has four blocks: Details, Zones, Capacity and Events. Read the Capacity block first — it tells you the ceiling and where that ceiling came from.Expected result The room, its zones with reader counts, and the events booked into it.Watch out for Capacity resolves in a fixed order: this venue's own number, then the default venue's, then the platform setting. The default venue outranks the setting on purpose — a room's capacity is a physical fact and a setting is an opinion.
-
5Add a permanent zone from the Add zone form: a human name (Backstage), a short code suffix (backstage), an advisory capacity, and the groups that should be granted it.Expected result A new zone whose code is the venue slug plus your suffix — blue_room_backstage.Watch out for Three refusals live here and all three are protecting the scan path. A suffix starting with zone_ is rejected as reserved. A suffix that normalises onto an existing code is rejected as a collision even if the raw strings differ. And hyphens become underscores, because the zone grammar has no hyphen in it.
-
6Look at the unattached list beside the zones. Those are zones that exist in the database but belong to no room — usually pre-existing zones from before venues had owners.Expected result The venue's own zones, plus any orphans available to adopt.Watch out for One orphan is legitimate and must stay one: the bar's POS pseudo-zone, which every membership card carries and which is not a door in any room. Do not adopt it into a venue.
-
7Adopt a genuine orphan into this venue.Expected result The zone now belongs to the room. Nothing else about it changes.Watch out for Attaching writes the owning venue and nothing else — not the code, not the id, not the readers. That is the whole point: adoption must be invisible to every pass already in somebody's wallet.
-
8Rename a zone or change its advisory capacity when the room is re-laid-out.Expected result The display name and capacity update.Watch out for You may change the name. You may never change the code — that comes back as a 409 telling you the code is immutable. The name is for humans; the code is inside signed credentials that are already issued.
-
9Open the zone map when a scan does not behave. It prints the three vocabularies side by side: the stored zone code, its normalised form, and the code the group-to-zone permissions were written against.Expected result One row per zone showing all three spellings.Watch out for This page exists because those three vocabularies drifting apart is the single hardest bug in the platform to see from anywhere else. If a pass will not open a door, look here before you look anywhere.
-
10Promote a venue to be the platform default when the club moves its main room. Exactly one venue is the default at a time.Expected result The default flag moves; the previous default keeps everything else.Watch out for The default is what an unbound event inherits its timezone and capacity from. Moving it silently re-answers 'what time is it' for every event that never named a room.
Steps 11–15 — Venue Manager
their manual →Runs the per-event zone roster — which of the room's doors tonight's event actually uses.
-
11For tonight's event, read its zone roster. The response tells you the roster rows, the active codes, and — importantly — whether the event is unconstrained.Expected result For an untouched event: an empty roster and unconstrained true.Watch out for Unconstrained means every zone is allowed. That is not an error and not a gap in the data — it is the default state of every event, and it is why this feature could ship without rewriting history. There is no page for this yet; the roster is a JSON surface only.
-
12Switch a zone off for this event — the backstage door on a night with no backstage. Post the zone reference with active set to false.Expected result The roster materialises with every current zone active, then that one flips off.Watch out for Your first write to an event's roster turns 'everything is allowed' into an explicit list. That materialisation is deliberate and it is a one-way door in practice: from now on this event has an opinion about every zone, so check the list afterwards rather than assuming.
-
13Create a temporary zone for tonight only — a meet-and-greet room, a second stage. Give it a name and a suffix exactly like a permanent one.Expected result A real zone, usable by readers and mappable to a tier, whose code carries this event's id — ev3f9a2c_meet_greet.Watch out for The code is namespaced by the EVENT, not the venue, so it can never be minted twice and never collides with a permanent zone. It is still globally unique, because the door still decides by string comparison.
-
14Retire the temporary zone when the night is over. You rarely need to: every temporary zone is retired automatically when the event completes or is cancelled.Expected result The roster row goes inactive, the zone is soft-deleted, and the code is burned forever.Watch out for The response lists any readers still physically pinned to that zone. They are reported, never silently rewritten — the reader inventory belongs to the access module. They are already harmless, because no live pass can carry the code any more.
-
15Before the event goes on sale, come back to the venue page and read the Capacity block against the event's tier inventory.Expected result The room's ceiling, the sum of tier inventory, and any capacity problems.Watch out for Selling more tickets than the room holds is refused at the point the event goes on sale, not at the point you type the number. Discovering it here is a five-minute fix; discovering it at announcement is a phone call to a promoter.
Steps 16–17 — Admin
their manual →-
16When a manager escalates a capacity gate that is genuinely wrong — a licensed extension, a seated layout — record an override with a written reason.Expected result The gate lifts for that event and the reason is stored against it.Watch out for Admin only, and the reason is mandatory. An override with a reason is a decision; an override without one is an unexplained hole in a fire-code control. Prefer fixing the venue's capacity if the room really did change.
-
17Archive a room the club no longer uses. Archiving hides it from selection without touching a single event, zone or pass.Expected result The venue disappears from the default list and can be restored.Watch out for A venue still in use is refused. And archiving deliberately does NOT cascade onto its zones — cascading would leave old readers opening doors while newly issued passes had quietly lost the codes, which is the worst of both outcomes.