# Architecture

ATM Tickets is the ticketing module inside ATM. Event apps own event discovery,
public pages, organizer workflows, and their buyer/scanner UX. ATM owns payment
collection and settlement; Tickets owns private capacity, holds, issuance,
forms, waitlists, delivery, scanner grants, and check-in.

The current contract baseline is ATM main commit
`59432c22e2a6dc47d852165a7d1e67c30c4810a1`, checked on 2026-10-12.
See [LEXICONS.md](LEXICONS.md) for schema provenance and regeneration.
Implementation in main is separate from deployment, protocol publication, and
live-sales approval.

## Layers

| Layer | Records or state |
| --- | --- |
| Public event graph | `community.lexicon.calendar.event`, `community.lexicon.calendar.rsvp` |
| Public ticket catalog | `tickets.atmosphere.ticketType`, `tickets.atmosphere.ticketedEvent`, `tickets.atmosphere.checkInList` |
| Payment catalog | `money.atmosphere.product` and immutable `money.atmosphere.price` |
| Private Tickets service | Event configuration, type/event capacity pools, holds, issued tickets, answers, waitlists, scanner grants, admission and lifecycle audit |
| ATM payment service | Checkout, settlement, app fees, refunds, receipts, payment proofs |

Public catalog reads do not reserve inventory or authorize selling. The
organizer owns the ticket configuration; app provenance alone does not grant
editing authority. Seller-channel visibility remains scoped to the calling app
and environment. A public record or recipient approval is not blanket
cross-app access to another channel's private state.

## Purchase flow

1. Select a public community event and create its ticketed-event configuration
   with `createTicketEvent` and organizer consent.
2. Create types with `createTicketType`; configure optional `quantityTotal`
   type caps and shared event capacity through `setEventCapacity`.
3. For a paid type, attach an exact active organizer-owned `kind=ticket`
   Product whose one-time default Price matches the type's currency and amount.
4. Obtain organizer selling approval for the app in the target environment.
5. Read `getTicketAvailability` and render the returned `ticketTypes`.
6. Call `createTicketHold` with `items: [{ ticketTypeId, quantity }]`.
7. Persist the hold id and private `statusToken`; redirect to the top-level `url`.
8. ATM confirms settlement and issues tickets once. A browser return alone is
   not payment or issuance evidence.
9. The app consumes verified callbacks or reads issued-ticket state.

Zero-price types instead use `claimFreeTicket` with required buyer assertion,
private delivery email, and the same item-array shape. They issue immediately
and create no checkout or payment row. A paid type with a 100%-off coupon still
uses checkout and creates a $0 payment row; it publishes no payment proof.

## Hosted surfaces

| Host | Purpose | Runtime |
| --- | --- | --- |
| `atmosphere.tickets` | Landing page and developer docs | This static Vercel repo |
| `events.atmosphere.tickets` | Optional ATM-hosted event selection and buyer OAuth | ATM |
| `tickets.atmosphere.tickets` | Private buyer ticket/QR/wallet presentation | ATM |
| `scan.atmosphere.tickets` | Staff scanner PWA | ATM |

Use ATM-returned `manageUrl` and `scannerUrl` locators where supplied. A URL
never grants organizer or scanner authority.

Presentation is separate from admission and purchase history. Current content
moderation can suppress event/type copy, images, email or wallet presentation
without deleting an issued entitlement or revoking admission. Scanners resolve
private ticket state even when presentation is unavailable. Immutable monetary
and purchase snapshots remain the basis for receipts and audit.
