Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions docs/PROJECT_STATE.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,10 +96,10 @@ Rule: only the canonical workspace switches to `launch-mvp`. Milestone branches
| 1 | Approve merging PR #17 (`launch-mvp` → `main`) and the Production release | open |
| 2 | Production env vars. Missing as of P0 (2026-11-02, names only): `SESSION_SECRET`, `CRON_SECRET`, `TELEGRAM_BOT_USERNAME`, `TELEGRAM_WEBHOOK_SECRET`, `TG_CHAT_ID_EVENTS`, `ADMIN_BOOTSTRAP_TELEGRAM_ID`, `PUBLIC_SITE_URL`, `SUPPORT_EMAIL`, `LEGAL_ENTITY_NAME`, `GRIEVANCE_OFFICER_NAME`, `GRIEVANCE_EMAIL` (`ENABLE_LIVE_PAYMENTS` unset = off) | open, not re-checked in M1 |
| 3 | Reconcile which DB Production points at (§4) | open (owner will supply confirmed project ID & endpoint ID from Vercel → Storage; dynamic block list active) |
| 4 | Domain/DNS for `nightlifez.com` (Cloudflare, Vercel) | open (M8 prepares the steps) |
| 4 | Domain/DNS for `nightlifez.com` (Cloudflare, Vercel) | open (M8 prepares the steps in docs/launch/DNS_AND_EMAIL_ROUTING.md) |
| 5 | BotFather `/setdomain` and profile; Telegram groups and channel | open (M5 prepares the steps) |
| 6 | Vercel Protection Bypass for Automation | open (M9) |
| 7 | Email routing `contact@nightlifez.com` | open (M8) |
| 7 | Email routing `contact@nightlifez.com` | open (M8 prepares the steps in docs/launch/DNS_AND_EMAIL_ROUTING.md) |
| 8 | Legal fields (entity, grievance officer) | open |
| 9 | Approve the branch/worktree deletions proposed in §9 | **completed / approved** (19 fully merged remote branches archived to `archive/<branch>` tags and deleted from origin; `.claude/worktrees/launch-mvp` preserved as is) |
| 10 | Decide on the unpushed Phase 5.6/5.7 reports (local commit `bcb4989`): push and archive, or drop | **completed / approved** (`bcb4989` pushed to `origin/fix/preview-recovery-audit`; reports imported into `docs/history/`) |
Expand Down Expand Up @@ -177,12 +177,15 @@ Per owner approval on 2026-11-03, each of the 19 fully merged remote branches wa
| M5 Telegram & Operational Workflows | `feature/m5-telegram-operational-workflows` / #35 | **Completed** (Telegram identity & account linking, event private supergroup isolation without shared global group, operational recovery on /start, transactional outbox notifications with quiet hours, /report moderation workflow with emergency escalation; 16/16 tests in test/telegram-workflows.test.js, 487/487 unit/integration tests pass, ENABLE_LIVE_PAYMENTS=false preserved) |
| M6 Organiser Workspace, Event Staff & Secure QR Check-in | `feature/m6-organiser-workspace` / #36 | **Completed** (Full organiser workspace with event dashboard, readiness checklist, ticket inventory, guest management, and operational announcements; event-scoped staff roles with organiser, event_manager, door_staff, guest_list_manager, assistant; server-authoritative HMAC-SHA256 signed QR tickets bound to events without leaking attendee PII; mobile-first door check-in scanner with camera QR detection and manual code lookup; atomic duplicate scan and replay prevention; ticket reissue and revocation lifecycle; 15/15 tests in test/m6-organiser-workspace.test.js, 642/642 tests passing across entire regression suite, 145/145 lint clean; `ENABLE_LIVE_PAYMENTS=false` preserved) |
| M7 Attendee Discovery & Experience Hub | `feature/m7-attendee-experience` / #37 | **Completed** (Personalised attendee discovery feed with transparent, explainable recommendation scoring; location and privacy safeguards with manual city default, explicit device GPS distance bands without venue trilateration, and privacy-preserving mystery venue displays; mobile-first event cards with verified host trust badge, 21+ badge, cancellation and refund policies; multi-facet filtering for category, date range [today, tomorrow, weekend, week, month], price [all, free, paid], and sorting [recommended, trending, date_asc, price_low]; attendee experience hub on /me with booking tabs [all, upcoming, past], saved events / bookmarks lifecycle, and host announcements feed; digital QR pass integration with M6 ticket verification and door check-in status display; 12/12 tests in test/m7-attendee-experience.test.js, 8/8 in test/migrations.test.js, 654/654 tests passing across full regression suite, 16/16 Playwright E2E passing, 149/149 lint clean; `ENABLE_LIVE_PAYMENTS=false` preserved) |
| M8 Real-time Notifications & Operational Comms | `feature/m8-realtime-notifications` | **Recommended Next Milestone** |
| M8 Real-time Notifications & Operational Comms | `feature/m8-realtime-notifications` / #38 | **Completed** (Attendee self-cancellation with transactional seat hold release, ticket invalidation, invite revocation & confirmation notice; organiser operational announcements broadcast with quiet hours deferral; event staff scoping invariant fix for door_staff discovery; host approval workflow with automated notification; admin notifications outbox API & retry mechanism; interactive leads view; operational runbooks in `docs/launch/DNS_AND_EMAIL_ROUTING.md` resolving blockers #4 and #7; `ENABLE_LIVE_PAYMENTS=false` preserved) |
| M9 Testing & Security Review | `feature/m9-testing-security-review` | **Recommended Next Milestone** |

## 11. Where the detail lives

| Topic | Doc |
|---|---|
| Real-time notifications & operational comms architecture | [arch/REALTIME_NOTIFICATIONS_AND_COMMS.md](arch/REALTIME_NOTIFICATIONS_AND_COMMS.md) |
| DNS & email routing runbook (Blockers #4 & #7) | [launch/DNS_AND_EMAIL_ROUTING.md](launch/DNS_AND_EMAIL_ROUTING.md) |
| Attendee discovery & experience hub architecture | [arch/ATTENDEE_DISCOVERY_AND_EXPERIENCE.md](arch/ATTENDEE_DISCOVERY_AND_EXPERIENCE.md) |
| Organiser workspace & QR check-in architecture | [arch/ORGANISER_WORKSPACE_AND_QR_CHECKIN.md](arch/ORGANISER_WORKSPACE_AND_QR_CHECKIN.md) |
| UPI security audit | [UPI_SECURITY_AUDIT.md](UPI_SECURITY_AUDIT.md) |
Expand Down
131 changes: 131 additions & 0 deletions docs/arch/REALTIME_NOTIFICATIONS_AND_COMMS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
# Real-Time Notifications & Operational Comms Architecture
**Milestone:** M8 Real-time Notifications & Operational Comms
**Target Branch:** `feature/m8-realtime-notifications`
**Status:** Implemented & Verified

---

## 1. Overview & Objectives

Milestone M8 establishes reliable, real-time operational communications, closing critical communication and lifecycle gaps across all seven platform roles (Attendee, Host, Organiser, Venue/Business Owner, Event Staff, Service Provider, Admin/Support):

1. **Attendee Lifecycle & Cancellation:**
- Server-authoritative attendee self-cancellation endpoint (`POST /api/me/bookings/:id/cancel` and `POST /api/bookings/:id/cancel`).
- Transactional seat hold release, ticket invalidation, and single-use Telegram invite revocation.
- Immediate cancellation confirmation delivery to Telegram.
- Clear visual cancellation state with warning banner and automatic QR pass suppression.

2. **Organiser & Staff Operational Comms:**
- Host announcement broadcasts (`POST /api/organiser/events/:id/announcements`) delivered to confirmed attendees with custom title, body, and direct booking pass deep link.
- Event staff scoping invariant fix: event staff (including `door_staff` and `assistant`) discover and access assigned events via `event.view_inventory` without exposing sensitive attendee guest lists (`event.view_guests`).
- Host approval workflow: when admin approves host via `setOrganiser`, automated `host_approved` notification is enqueued with direct link to the organiser workspace.

3. **Quiet Hours & Notification Delivery Engine:**
- Per-user quiet hours configuration in `Asia/Kolkata` timezone (e.g., 23:00 to 08:00).
- Quiet hours deferral mechanism for non-urgent notifications (`event_announcement`, non-emergency `event_update`, `event_reminder`, `membership_renewed`, `expiry_reminder`).
- Urgent notifications (payment receipts, immediate booking cancellations, security alerts) bypass quiet hours to guarantee critical safety communication.

4. **Admin Outbox & Comms Management:**
- Central outbox inspection API (`GET /api/admin/notifications`) with status filtering (`all`, `pending`, `failed`, `sent`) and 24h KPI statistics.
- Manual retry mechanism (`POST /api/admin/notifications/:id/retry`) allowing support operators to re-attempt deliveries with automatic status updates.
- Interactive lead review for host, venue, and partner applications with direct communication links (Telegram `t.me`, `mailto:`, `tel:`).

---

## 2. Notification Pipeline & State Machine

```mermaid
flowchart TD
Trigger[Operational Event / Broadcast / Cancellation] --> Enqueue[Enqueue in DB Transaction: notifications table]
Enqueue --> OutboxPending[Status: pending]

OutboxPending --> DeliveryCheck{Deliver Called / Cron Due}
DeliveryCheck --> QuietCheck{Non-Urgent & In User Quiet Hours?}

QuietCheck -- Yes --> Defer[Update next_attempt_at to Quiet Hours End in Asia/Kolkata]
Defer --> OutboxPending

QuietCheck -- No --> Attempt[Invoke Delivery Handler]
Attempt --> TelegramAPI{Telegram Bot sendMessage}

TelegramAPI -- Success 200 --> Sent[Status: sent, sent_at: now, error: null]
TelegramAPI -- Error / Timeout --> FailCheck{attempts < 6}

FailCheck -- Yes --> RetryDelay[Status: failed, Exponential Backoff 2^n mins]
RetryDelay --> OutboxPending

FailCheck -- No --> DeadLetter[Status: failed, Awaiting Admin Manual Retry]
DeadLetter --> AdminRetry[Admin Outbox UI: Retry Action]
AdminRetry --> OutboxPending
```

---

## 3. Attendee Self-Cancellation Specification

### 3.1 Invariants
- An attendee can only cancel their own booking (`user_id = $auth_user_id`).
- Checked-in bookings cannot be cancelled (`409 already_checked_in`).
- Already cancelled bookings reject duplicate requests (`409 already_cancelled`).
- Cancelled bookings immediately revoke:
- Any valid tickets (`status = 'cancelled'`, `revoked_by = user_id`, `revoked_at = now()`).
- Any unused private Telegram group invite links (`revoked_at = now()`).
- Seat capacity is automatically recovered because `SEAT_HOLDING_SQL` excludes `status = 'cancelled'`.
- Cancellation is recorded in `audit_log` with action `booking.attendee_cancel`.

### 3.2 Cancellation Sequence

```mermaid
sequenceDiagram
autonumber
actor Attendee
participant Client as Frontend (Digital Pass /me)
participant Router as API Router
participant Svc as Bookings Service
participant DB as Postgres (ACID Transaction)
participant Outbox as Notifications Outbox
participant TG as Telegram Bot API

Attendee->>Client: Click "Cancel Reservation" + Confirm
Client->>Router: POST /api/me/bookings/:id/cancel
Router->>Svc: cancelUserBooking(userId, bookingId)
Svc->>DB: BEGIN TX
Svc->>DB: SELECT * FROM bookings WHERE id = $id AND user_id = $uid FOR UPDATE
Note over DB: Verify ownership & non-checked-in status
Svc->>DB: UPDATE bookings SET status='cancelled', cancelled_at=now()
Svc->>DB: UPDATE tickets SET status='cancelled', revoked_at=now() WHERE status='valid'
Svc->>DB: UPDATE invite_links SET revoked_at=now() WHERE revoked_at IS NULL
Svc->>Outbox: INSERT INTO notifications (kind='booking_cancelled_by_user')
Svc->>DB: INSERT INTO audit_log (action='booking.attendee_cancel')
Svc->>DB: COMMIT TX
Svc-->>Router: { ok: true, status: 'cancelled' }
Router-->>Client: 200 OK
Client->>Client: Re-render Pass: Warning Banner & Suppress QR
Note over Outbox,TG: Cron / Deliver pushes cancellation notice to Attendee Telegram
```

---

## 4. Event Staff Scoping Architecture

To enforce the principle of least privilege while enabling operational door operations:

| Role | Scope | Permissions Granted | Can View Guest PII? | Can Scan Tickets? | Can See Assigned Event in Workspace? |
|:---|:---|:---|:---|:---|:---|
| `organiser` / Owner | Org / Event | All event actions | Yes | Yes | Yes |
| `event_manager` | Event | All event actions except ownership transfer | Yes | Yes | Yes |
| `guest_list_manager` | Event | `event.view_guests`, `event.decide_bookings`, `event.check_in`, `event.view_inventory` | Yes | Yes | Yes |
| `door_staff` | Event | `event.check_in`, `event.view_inventory` | **No** (Strictly blocked) | **Yes** | **Yes** |
| `assistant` | Event | `event.view_guests`, `event.view_inventory` | Yes | No | Yes |

### Key Resolution:
`listOrganiserEvents` and `getEventOverview` now evaluate `event.view_inventory` rather than `event.view_guests`. This guarantees door staff can access their workspace and door scanner (`/organiser/events/:id/scan`), while `listOrganiserBookings` continues to require `event.view_guests`, preventing unauthorized exposure of attendee personal data.

---

## 5. Security & Verification Summary

1. **Fail-Closed Security:** All endpoints require authenticated sessions and strict permission checks.
2. **Cryptographic Integrity:** Ticket QR payloads remain HMAC-SHA256 signed with zero personal data leakage.
3. **Audit Trail:** Every administrative and attendee state transition is immutably logged in `audit_log`.
4. **Zero Live Payments:** `ENABLE_LIVE_PAYMENTS=false` remains strictly enforced across all environments.
144 changes: 144 additions & 0 deletions docs/launch/DNS_AND_EMAIL_ROUTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
# NIGHTLIFEZ Launch Operations: DNS & Email Routing Runbook
**Target Milestone:** M8 Real-time Notifications & Operational Comms
**Scope:** Resolves Blocker #4 (Domain `nightlifez.com` DNS, CNAME flattening, SSL Full Strict) and Blocker #7 (Email routing `contact@nightlifez.com`).
**Status:** Operationally Prepared (Owner Execution Steps Gated for Production Window)

---

## 1. Executive Summary

This runbook provides the exact, production-ready specifications and manual execution checklist required to configure the apex domain `nightlifez.com`, subdomains, Cloudflare SSL/TLS encryption, and inbound email routing for `contact@nightlifez.com`.

All steps respect project boundaries:
- `ENABLE_LIVE_PAYMENTS=false` remains strictly enforced.
- Cloudflare credentials, DNS nameservers, and Production records are not modified automatically.
- This runbook serves as the authoritative, copy-paste ready blueprint for the domain administrator.

---

## 2. Blocker #4: Domain `nightlifez.com` & Cloudflare DNS Configuration

### 2.1 Nameserver Delegation
Configure registrar (Namecheap / GoDaddy / Google Domains) nameservers to point to Cloudflare:
- Primary NS: `aria.ns.cloudflare.com` (or allocated Cloudflare account NS)
- Secondary NS: `dane.ns.cloudflare.com` (or allocated Cloudflare account NS)

### 2.2 DNS Zone Records

| Type | Name | Content / Target | Proxy Status | TTL | Purpose |
|:---|:---|:---|:---|:---|:---|
| **CNAME** | `@` (apex) | `cname.vercel-dns.com.` | Proxied (Orange Cloud) | Auto | Apex CNAME Flattening to Vercel production edge |
| **CNAME** | `www` | `nightlifez.com` | Proxied (Orange Cloud) | Auto | Subdomain redirect / canonical host |
| **CNAME** | `preview` | `cname.vercel-dns.com.` | DNS Only (Grey Cloud) | Auto | Dedicated Vercel preview staging endpoint |
| **A** | `_vercel` | `76.76.21.21` | DNS Only (Grey Cloud) | Auto | Vercel domain verification record (if requested) |

### 2.3 Cloudflare SSL/TLS Settings
1. **Encryption Mode:** `Full (Strict)`
- Requires valid TLS certificate on origin (Vercel automatic Let's Encrypt).
- Mitigates man-in-the-middle attacks between Cloudflare Edge and Vercel Origin.
2. **Edge Certificates:**
- **Always Use HTTPS:** Enabled (`ON`). Automatically redirects `http://*` to `https://*` with 301.
- **HTTP Strict Transport Security (HSTS):** Enabled (`ON`).
- Max-Age: `31536000` (1 year).
- Include Subdomains: `ON`.
- Preload: `OFF` (enable only after 30 days of stable production traffic).
- **Minimum TLS Version:** `TLS 1.2` or `TLS 1.3`.
- **Opportunistic Encryption:** Enabled (`ON`).
- **TLS 1.3:** Enabled (`ON`).
3. **Page Rules / Redirect Rules (Apex Canonicalization):**
- URL Redirect Rule: `http*://www.nightlifez.com/*` -> `https://nightlifez.com/$1` (301 Permanent Redirect).

---

## 3. Blocker #7: Inbound Email Routing (`contact@nightlifez.com`)

To receive platform inquiries, host applications, partnership requests, and safety grievance communications without running a dedicated mail server, Cloudflare Email Routing is utilized to forward inbound mail to the designated administrator inbox.

### 3.1 Cloudflare Email Routing MX Records

| Type | Name | Priority | Content / Mail Server | TTL |
|:---|:---|:---|:---|:---|
| **MX** | `@` | `13` | `route1.mx.cloudflare.net` | Auto |
| **MX** | `@` | `57` | `route2.mx.cloudflare.net` | Auto |
| **MX** | `@` | `89` | `route3.mx.cloudflare.net` | Auto |

### 3.2 Sender Policy Framework (SPF)
Prevents email spoofing on the `nightlifez.com` domain.

| Type | Name | Content | TTL |
|:---|:---|:---|:---|
| **TXT** | `@` | `v=spf1 include:_spf.mx.cloudflare.net ~all` | Auto |

*Note:* If transactional sending (e.g., Postmark or Resend) is added in a subsequent phase, append `include:spf.postmarkapp.com` before `~all`.

### 3.3 DomainKeys Identified Mail (DKIM)
Provided automatically by Cloudflare Email Routing when verification is active.

| Type | Name | Content | TTL |
|:---|:---|:---|:---|
| **TXT** | `*._domainkey` | (As generated by Cloudflare Email Routing setup wizard) | Auto |

### 3.4 Domain-based Message Authentication (DMARC)
Enforces rejection of fraudulent mail impersonating `nightlifez.com` and routes aggregate failure reports.

| Type | Name | Content | TTL |
|:---|:---|:---|:---|
| **TXT** | `_dmarc` | `v=DMARC1; p=reject; sp=reject; pct=100; rua=mailto:contact@nightlifez.com` | Auto |

### 3.5 Routing Rules
In Cloudflare Dashboard -> `nightlifez.com` -> `Email Routing` -> `Routing Rules`:

| Custom Address | Action | Destination Address | Status |
|:---|:---|:---|:---|
| `contact@nightlifez.com` | Send to | `<designated-operations-inbox>@gmail.com` | Active |
| `safety@nightlifez.com` | Send to | `<designated-operations-inbox>@gmail.com` | Active |
| `grievance@nightlifez.com` | Send to | `<designated-operations-inbox>@gmail.com` | Active |
| `catch-all` | Drop | — | Disabled (Drop unexpected addresses) |

---

## 4. Verification & Health Check Procedure

Run the following terminal commands to verify that DNS and Email records have propagated correctly:

### Check Apex Resolution
```bash
dig +short A nightlifez.com
# Should return Cloudflare Anycast IPs (e.g. 104.21.x.x, 172.67.x.x)
```

### Check CNAME Flattening & TLS
```bash
curl -svo /dev/null https://nightlifez.com
# Should complete TLS 1.3 handshake with HTTP 200 / 308 (Vercel)
```

### Check Mail Exchanger (MX) Records
```bash
dig +short MX nightlifez.com
# Expected:
# 13 route1.mx.cloudflare.net.
# 57 route2.mx.cloudflare.net.
# 89 route3.mx.cloudflare.net.
```

### Check SPF Record
```bash
dig +short TXT nightlifez.com | grep "v=spf1"
# Expected: "v=spf1 include:_spf.mx.cloudflare.net ~all"
```

### Check DMARC Policy
```bash
dig +short TXT _dmarc.nightlifez.com
# Expected: "v=DMARC1; p=reject; sp=reject; pct=100; rua=mailto:contact@nightlifez.com"
```

---

## 5. Rollback & Emergency Plan

If Cloudflare DNS proxying causes origin routing loops or header drops:
1. Switch apex `@` and `www` records from **Proxied** (Orange Cloud) to **DNS Only** (Grey Cloud) temporarily.
2. Verify direct resolution to Vercel edge.
3. Check Cloudflare SSL/TLS mode is set to **Full (Strict)**, not **Flexible** (which causes 301 redirection loops with HTTPS-enforcing origins like Vercel).
3 changes: 2 additions & 1 deletion lib/admin-roles.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ export const R = {
INSIGHTS: ['super_admin', 'finance', 'event_manager'],
DISCOVERY: ['super_admin', 'event_manager', 'moderator'],
DISCOVERY_WRITE: ['super_admin', 'event_manager', 'moderator'],
FINANCE_VIEW: ['super_admin', 'finance']
FINANCE_VIEW: ['super_admin', 'finance'],
NOTIFICATIONS: ['super_admin', 'support', 'event_manager']
};

// [tab, label, roles allowed to load it]
Expand Down
Loading
Loading