Coupons and Guest NPS
Assignment routing, durable coupon lifecycle, Contact identity, and anonymous NPS
Coupons and NPS share location assignments and durable delivery infrastructure. Their metrics, consent, and fulfillment evidence remain separate. The canonical contract is Coupons and Guest NPS.
Routing and authorization
- Public coupon and survey assignment tokens resolve one team, campaign or survey, and restaurant. Client-supplied scope or host headers cannot override it.
- Durable links use configured
GUEST_PUBLIC_ORIGIN. Old/coupon/[slug]QR codes are retired; survey slug compatibility is a separate resolver contract. - An admitted multi-location selector redirects to an existing restaurant assignment; it does not establish physical presence.
- Private issuance entry
/coupon/u/[token]exchanges a bearer token for an issuance-bound session and redirects to/coupon/i/[issuanceId]. - Manager/admin campaign mutations require every existing/requested restaurant in scope. Partial visibility is read-only; hidden assignments and raw guest data are not serialized. Staff confirmation has its own server authorization.
Authoring and issuance recovery
Saved campaign revisions reject stale edits/publication. Publication confirms saved values and produces an immutable offer version, including the exact Toast discount label. Active holds retain the accepted version. Browser drafts are operator/team/campaign scoped, revision-aware, expire after 24 hours, and never publish or reserve capacity.
Unique issuance creation uses an actor/scope/payload-bound command identity plus a durable database fence in the same audited transaction. Replaying one intent cannot issue twice, including after command-outcome cleanup. The private bearer is revealed only once; status recovery returns its issuance identity without recovering that token. Recovery storage contains sanitized command scope and a payload hash, not Contact values or capabilities.
Reservations and fulfillment
Activation requires a normalized email; phone is optional and separately hashed. An advisory eligibility check reserves nothing. Activation atomically rechecks publication, dates, restaurant, terms/version, identity, and capacity. A stable activation attempt reconciles a lost reply without claiming twice.
| Transition | Capacity effect |
|---|---|
| Activate | Increment reserved capacity. |
| Redeem | Decrement reserved capacity; increment completed redemptions. |
| Cancel or expire | Release reserved capacity without consuming a redemption. |
Finite availability uses reservedCount + redeemedCount < useLimit; zero limit
means unlimited. Legacy useCount does not drive availability. Terminal writes
condition on the actual active reservation and update counters/events atomically.
Retry and retired-location re-add require their complete server admission gates.
A unique issuance permits at most one completed redemption.
The current flow is a guest attestation after staff manually applies the offer in Toast. The active screen uses Confirm discount applied, an ordinary button that directly calls the transition after pending/status/deadline checks; no extra confirmation dialog or slide gesture is implemented. Reports label it Guest confirmed (Toast not verified). The app neither writes to Toast nor verifies individual checks. The separate legacy staff-proof route requires authenticated scoped review and explicit confirmation; opening a proof or scanning does not redeem. Complete staff/POS evidence counts as verified fulfillment. Audit evidence survives notification transport cleanup. Toast aggregate reconciliation is advisory and marks incomplete source windows unavailable.
Expiry, sessions, and uncertain outcomes
Expiry is an absolute server timestamp. The display timer ticks locally; bounded status reconciliation runs while visible, on visibility regain, and at zero. Status checks use a separate budget from activation and terminal mutations. Independent background reads cannot supersede a completed cancel/redeem receipt.
Resume cookies are HttpOnly, production-Secure, SameSite=Lax, and path-scoped to the owning assignment or issuance. Raw session values never enter browser storage or URLs. Only owning server transitions/session operations clear cookies; a stale page cannot erase a newer session. An unresolved/offline/rate-limited result keeps the original activation attempt and does not authorize another claim.
Legacy staff proof is a separate capability: its navigation fragment is consumed before telemetry and kept only in document memory. Refresh or a full authentication redirect requires another scan. Real Clerk redirect recovery remains a separate device check. Neither private URLs, cookies, proof fragments nor Contact data belong in observability or support evidence.
Lazy expiry and the admitted protected sweep release capacity and accept a stable expiry event transactionally. Cron scheduling and provider delivery do not own lifecycle correctness.
Contact identity and consent
Coupon activation resolves or creates a same-team customer through
@repo/contact-identity. Unique issuances may select an eligible confirmed Contact.
Ambiguous identity remains a review disposition rather than a guessed merge.
Association writers lock the canonical Contact before coupon/issuance/response
rows. If identity changes concurrently, the entire transaction retries within a
bounded budget.
Merging transfers redemption, issuance-recipient, and survey-response associations together. Historical consent is append-only; effective consent traverses merged aliases and resolves the latest purpose/channel decision, with revocation taking precedence on tied timestamps. Accepting coupon terms is not marketing consent. Ordinary Coupon reads mask Contact values; repair and raw export are distinct authorized, audited operations. See Contacts.
Anonymous NPS and durable delivery
Published NPS instruments are immutable 0–10 versions: detractor 0–6, passive 7–8, promoter 9–10. Show NPS with response count; no responses is nullable, not zero. Coupon feedback is a separate 1–5 metric.
The approved NPS publication mode is anonymous-only. The form renders no contact or consent fields, and server sanitization stores no Contact, callback or marketing record. Optional/required contact modes and Operations Inbox recovery remain gated future modes. A configured neutral review link is available regardless of score.
Survey submission keeps one submission identity across retry and lost replies. Only an applied durable response is completion; a pending result must be reconciled. Notifications are accepted with the domain mutation and delivered after commit through the shared notification queue and Slack ledger. Workers reload authoritative records; a legacy rating hint alone cannot authorize an alert. Delivery failure never reverses an accepted response or redemption. Routing uses the actual location and privacy-safe metadata.
Release and pilot status
The reliability implementation merged in
PR #4131, and the protected
0632_goofy_doctor_doom migration/rollout completed on 2026-10-07 UTC. That release
adds campaign edit revisions and the durable issuance-creation fence. It does not
by itself activate team flags, distribute QR codes, or establish real-device,
privacy, Clerk redirect, or POS pilot acceptance. Contactable NPS is still gated.
Current availability follows ADR-0091 and the rollout runbook. See the published manager guide for operation and the verification report for local evidence and its limits. The 2026-10-07 production release receipt records the completed migration and deployments as a dated snapshot.