Happy path (desired)
Staff scans guest QR for a multi-invitation group.
Confirmation screen shows: "[Guest name], party of 4 — 1 arriving, 3 invitations remaining." Inline picker: how many checking in now? Defaults to 1.
Confirm checks in N of M.
Server marks N attendees confirmed-and-checked-in. Remaining (M-N) invitations stay active. Audit log row:
partial_check_inwith N + M.Friend arrives later with the same group QR.
Friend re-scans. Confirmation screen: "Party of 4 — 1 already checked in, 3 remaining." Same picker. Continues until all are checked in.
Group fully checked in — QR retires.
Once 4/4 checked in, scanning the QR again shows "All party members checked in." No further action available.
Failure modes (desired contract)
Parity gap — feature absent
Trigger: matrix=Absent.
Visible "EF-064 leave-behind invitations not yet implemented" panel on the access-type config admin page. Staff-side: scanning a multi-invitation QR currently triggers single-check-in only (no party-of-N logic). Until the feature ships, the gap-panel asserts the absence.
Group QR overscan
Trigger: staff tries to check in 5 of a party-of-4.
Picker max-value enforced client-side AND server-side. Submit returns 422 EXCEEDS_PARTY_SIZE. Harness: 4-person party, attempt 5, banner visible.
Two-staff race on same group QR
Trigger: party of 4 — 2 staff devices both trying to check in different counts simultaneously.
Server-side row-level lock on the group's remaining counter. First commit wins; second sees decremented remaining count + adjusts. Harness: 2 devices submit (3 + 2) within 100ms, server final state is one consistent value (e.g., first lands as 3-checked, second sees 1 remaining + commits 1, total 4).
Group QR with 0 remaining
Trigger: scanning a fully-checked-in group's QR.
Confirmation screen replaced with "All party members checked in" + dismissable. No action available. Harness: stub fully-checked group, scan, message visible.
Audit per partial check-in
Trigger: party-of-4 checks in 2-then-1-then-1 across 3 staff scans.
3 audit rows: partial_check_in(2/4 → 4/4), partial_check_in(3/4 → 4/4), partial_check_in(4/4 → 4/4 + group-complete). Harness: 3 scans, 3 audit rows reconstruct full timeline.
Group revoked mid-event
Trigger: organizer revokes a group invitation while 1 of 4 has already checked in.
Already-checked-in attendees stay confirmed (they're already inside). Remaining invitations transition revoked. Future scans show "Group invitation revoked — contact organizer." Harness: revoke after 1/4 check-in, scan, revoked message visible.
Offline partial check-in
Trigger: device offline; staff partial-checks-in 2/4.
Local row records partial check-in with idempotency-key (deviceId + groupId + scanTimestamp + count). Replay preserves the count. Harness: offline 2/4 scan, reconnect, server matches local state.
Anonymous remaining attendees
Trigger: party-of-4 only has 1 named attendee (the inviter); 3 are unnamed +1s.
Check-in flow handles named (look up by name) AND unnamed (just decrement count) attendees. Audit row records "anonymous +1" for unnamed check-ins. Harness: party with 3 unnamed, full check-in completes without requiring names.
Capacity tracking on group invitations
Trigger: event has capacity 100; current confirmed = 96; group QR scanned for party of 5.
Server checks remaining capacity vs requested check-in count. If insufficient (96 + 5 > 100), surface "Only 4 spots remaining — check in 4 of 5?" Harness: stub 96/100, scan party-of-5, banner offers 4-of-5 partial.
Cross-event group QR
Trigger: group QR from a different event scanned at this event.
Same anti-probing as EF-061 — generic "QR is for a different event" banner, no leak. Harness: cross-event group QR, generic banner.
Stable test attributes
leave-behind-gap-panel | Access-type config + check-in tab | Visible until feature ships |
group-checkin-confirmation | After group QR scan | Shows party-of-N + remaining count |
group-checkin-count-picker | In confirmation | How many checking in now |
group-fully-checked-in-message | If 0 remaining | Dismissable info |
group-exceeds-party-banner | 422 EXCEEDS_PARTY_SIZE | "Cannot exceed party size" |
group-revoked-banner | If group revoked | "Contact organizer" |
group-capacity-partial-offer | If capacity insufficient | "Check in 4 of 5" |
Agent test plan
Probe list
- gap-panel-visible: matrix=Absent, panel visible
- (when shipped) group-checkin-confirmation: scan, picker shows party-of-N
- (when shipped) overscan-422: party-of-4, attempt 5, EXCEEDS_PARTY_SIZE
- (when shipped) two-staff-race-resolved: 2 devices, total never exceeds party size
- (when shipped) zero-remaining-info: fully-checked, scan, info visible
- (when shipped) audit-per-partial: 3 partial check-ins, 3 audit rows
- (when shipped) revoked-mid-event: revoke after partial, future scans show revoked
- (when shipped) offline-partial-replays: offline + reconnect, local-vs-server match
- (when shipped) anonymous-attendees: party with unnamed +1s, check-in completes
- (when shipped) capacity-partial-offer: stub low capacity, offer partial
- cross-event-group-qr: anti-probing banner