diff --git a/docs/superpowers/plans/2026-08-25-ephemeral-passes-and-multi-claim-events.md b/docs/superpowers/plans/2026-08-25-ephemeral-passes-and-multi-claim-events.md new file mode 100644 index 0000000..b54f59f --- /dev/null +++ b/docs/superpowers/plans/2026-08-25-ephemeral-passes-and-multi-claim-events.md @@ -0,0 +1,194 @@ +# Ephemeral Magic Passes & Multi-Claim Event Passes Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Implement 1-on-1 Ephemeral Magic Links, Multi-Claim Event/Workshop Passes (with short codes, PINs, short URLs, and CLI 1-liners), and a real-time Event Cockpit with a master kill switch. + +**Architecture:** Extend the Auth-Yes zero-trust session engine with a dedicated event registry in PostgreSQL and Valkey. Provide a unified 1-click redemption endpoint (`GET /pass`), a short-code/PIN join portal (`GET /join`, `GET /e/:slug`), CLI environment streams (`GET /join/:slug?format=env`), and live seat metrics with sub-millisecond batch revocation via Valkey RESP3 push tracking. + +**Tech Stack:** Deno 2.x, TypeScript 5.x, Hono SSR JSX (React-free), PostgreSQL 18, Valkey 8, Traefik ForwardAuth. + +**Spec:** [DUAL_AUDIENCE_DEVELOPMENT_GUIDE.md](../../DUAL_AUDIENCE_DEVELOPMENT_GUIDE.md) + +## Global Constraints +- Strictly zero React dependencies; use pure Hono SSR JSX. +- Zero-dependency SDK in `sdk/`. +- All cookie mutations must delete host-only cookies and set wildcard `.atyg.org` domain cookies. +- Every commit and change must be pushed to both GitHub (`origin`) and Gitea (`gitea`). +- Quality gates must pass: `deno fmt`, `deno task lint`, `deno task check`, `deno task test`. + +--- + +### Task 1: 1-on-1 Ephemeral Magic Link Redemption (`/pass`) + +**Files:** +- Modify: `server/main.ts` +- Modify: `ui/components/SessionsPage.tsx` +- Test: `server/main.test.ts` + +**Interfaces:** +- Consumes: `extractAllSessionIds(c)`, `valkey.get()`, `sqlWrapper.sql` +- Produces: `GET /pass?token=...` endpoint and updated Hand-Off Modal in `SessionsPage.tsx` + +- [ ] **Step 1: Write the failing test for `GET /pass`** + +```typescript +// in server/main.test.ts +Deno.test("GET /pass - Ephemeral Magic Link 1-Click Redemption", async (t) => { + await t.step("Valid token sets wildcard cookie and redirects to target app", async () => { + const valkeyStub = stub(valkey, "get", (key: any) => { + if (String(key) === "ay_sess_valid_pass") { + return Promise.resolve( + JSON.stringify({ uuid: "guest-uuid", username: "guest_user", customScopes: ["app:ed-droid"] }), + ); + } + return Promise.resolve(null); + }); + + const originalSql = sqlWrapper.sql; + sqlWrapper.sql = ((_query: any) => { + return Promise.resolve([{ domain: "ed-droid.atyg.org" }]); + }) as any; + + try { + const res = await app.request("/pass?token=ay_sess_valid_pass", { + method: "GET", + }); + assertEquals(res.status, 302); + const setCookie = res.headers.get("set-cookie") || ""; + assert(setCookie.includes("session_id=ay_sess_valid_pass")); + assert(res.headers.get("location")?.includes("ed-droid.atyg.org") || res.headers.get("location") === "/dashboard"); + } finally { + sqlWrapper.sql = originalSql; + valkeyStub.restore(); + } + }); +}); +``` + +- [ ] **Step 2: Run test to verify it fails** +Run: `deno test server/main.test.ts --filter "Ephemeral Magic Link"` +Expected: FAIL (404 Not Found on `/pass`) + +- [ ] **Step 3: Implement `GET /pass` in `server/main.ts`** +Extract token from `?token=...`, validate against Valkey/DB, set cookie with `getCookieDomain()`, resolve target app domain if scoped to a specific app, and return `c.redirect(targetUrl)`. + +- [ ] **Step 4: Update `ui/components/SessionsPage.tsx`** +Add the **"1-Click Magic Link"** copy card tab alongside the CLI and cURL header tabs. + +- [ ] **Step 5: Run tests to verify they pass** +Run: `deno task test` +Expected: PASS (All tests passing) + +- [ ] **Step 6: Commit and Push** +```bash +git add server/main.ts server/main.test.ts ui/components/SessionsPage.tsx +git commit -m "feat(pass): implement 1-click ephemeral magic link redemption route" +git push origin main && git push gitea main +``` + +--- + +### Task 2: Multi-Claim Event Passes Schema & Join Endpoints (`/join`, `/e/:slug`, CLI 1-Liner) + +**Files:** +- Modify: `server/db.ts` +- Modify: `server/main.ts` +- Create: `ui/components/EventJoinPage.tsx` +- Create: `ui/components/EventSplashPage.tsx` +- Test: `server/main.test.ts` + +**Interfaces:** +- Produces: + - Table: `event_passes` in PostgreSQL + - Endpoints: + - `POST /api/events`: Create event pass (slug, pin_code, name, max_seats, lifespan_hours, app_id, role) + - `GET /e/:slug`: Web landing splash with "Enter Workshop" action + - `GET /join`: Universal PIN / word-code entry page + - `POST /api/join`: Redeems code/PIN and creates isolated `guest__` session + - `GET /join/:slug`: CLI 1-liner (`?format=env` or `?format=json`) + +- [ ] **Step 1: Write the failing tests for Event creation and redemption** + +```typescript +Deno.test("Multi-Claim Event Passes & Join Endpoints", async (t) => { + await t.step("POST /api/events creates an event pass", async () => { + // Test event creation + }); + await t.step("POST /api/join provisions an isolated guest seat", async () => { + // Test seat provisioning + }); + await t.step("GET /join/:slug?format=env returns shell export", async () => { + // Test CLI 1-liner + }); +}); +``` + +- [ ] **Step 2: Run test to verify it fails** +Run: `deno test server/main.test.ts --filter "Multi-Claim Event"` +Expected: FAIL + +- [ ] **Step 3: Add `event_passes` schema in `server/db.ts`** +Include columns: `id`, `slug`, `pin_code`, `name`, `app_id`, `role`, `max_seats`, `seats_claimed`, `lifespan_hours`, `created_by`, `is_active`, `expires_at`, `created_at`. + +- [ ] **Step 4: Implement Event API routes in `server/main.ts` and SSR UI Pages** +- Create `ui/components/EventJoinPage.tsx` for `/join` PIN code entry. +- Create `ui/components/EventSplashPage.tsx` for `/e/:slug` 1-click workshop entrance. +- Implement `GET /join/:slug` returning `export AUTH_YES_TOKEN="..."` when `?format=env`. + +- [ ] **Step 5: Run tests to verify they pass** +Run: `deno task test` +Expected: PASS + +- [ ] **Step 6: Commit and Push** +```bash +git add server/db.ts server/main.ts ui/components/EventJoinPage.tsx ui/components/EventSplashPage.tsx server/main.test.ts +git commit -m "feat(events): implement multi-claim event passes, PIN join portal, and CLI 1-liner" +git push origin main && git push gitea main +``` + +--- + +### Task 3: Live Event Cockpit & Master Kill-Switch + +**Files:** +- Modify: `server/main.ts` +- Modify: `ui/components/SessionsPage.tsx` +- Modify: `ui/mod.ts` +- Test: `server/main.test.ts` + +**Interfaces:** +- Produces: + - `POST /api/events/:id/end`: Closes event and immediately revokes all guest sessions + - `POST /api/events/:id/extend`: Adds hours to active event + - Event Management Deck in `SessionsPage.tsx` with live seat counter (`38 / 50`) and master kill switch + +- [ ] **Step 1: Write failing tests for Event Kill-Switch & Extension** +```typescript +Deno.test("Event Cockpit & Master Kill Switch", async (t) => { + await t.step("POST /api/events/:id/end revokes all guest seats instantly", async () => { + // verify sessions deleted and Valkey cleared + }); +}); +``` + +- [ ] **Step 2: Run test to verify it fails** +Run: `deno test server/main.test.ts --filter "Event Cockpit"` +Expected: FAIL + +- [ ] **Step 3: Implement `POST /api/events/:id/end` and `POST /api/events/:id/extend` in `server/main.ts`** +Fetch all session IDs created under the event pass, delete them from Valkey and PostgreSQL, record in audit ledger, and mark event `is_active = false`. + +- [ ] **Step 4: Update `ui/components/SessionsPage.tsx` with Event Cockpit Deck** +Display active events with live progress bar (`Seats Claimed / Capacity`), PIN badge, copy links, `[+1h Extend]` and `[🔴 End Workshop & Revoke All]`. + +- [ ] **Step 5: Run full quality gates** +Run: `deno fmt && deno task lint && deno task check && deno task test` +Expected: All pass. + +- [ ] **Step 6: Commit and Push** +```bash +git add server/main.ts ui/components/SessionsPage.tsx ui/mod.ts server/main.test.ts +git commit -m "feat(cockpit): add live event metrics, seat roster, and master kill-switch" +git push origin main && git push gitea main +``` diff --git a/tasks/new/2026-0825.01.gem.feat.magic-passes.ephemeral-1-click-guest-links-0045.md b/tasks/new/2026-0825.01.gem.feat.magic-passes.ephemeral-1-click-guest-links-0045.md new file mode 100644 index 0000000..3c4ee97 --- /dev/null +++ b/tasks/new/2026-0825.01.gem.feat.magic-passes.ephemeral-1-click-guest-links-0045.md @@ -0,0 +1,36 @@ +# TASK METADATA + +- **Target Files:** + - `server/main.ts` + - `ui/components/SessionsPage.tsx` + - `server/main.test.ts` +- **Core Objective:** Implement `GET /pass?token=...` for 1-click ephemeral session redemption and update the Sessions Hub hand-off UI with copyable magic links. +- **Dependencies:** `server/auth-session.ts`, `server/db.ts` +- **Additional Important Notes:** Sets wildcard `.atyg.org` cookie, cleans host-only cookie, and redirects cleanly to target app domain or dashboard. + +--- + +### 1. Architectural Considerations & Risks + +- **Security & Cookie Scoping:** + - `/pass` must validate that the token is active and not expired before issuing Set-Cookie headers. + - Must use `getCookieDomain()` so subdomains (e.g. `ed-droid.atyg.org`) receive the session cookie immediately. +- **Target URL Redirection:** + - If the session has custom scopes for an application (e.g. `app:ed-droid`), `/pass` looks up the domain of `ed-droid` and redirects directly to `https://ed-droid.atyg.org`. + - If no specific app is scoped, redirects to `/dashboard`. + +--- + +### 2. Proposed Implementation + +1. **Backend Route (`server/main.ts`):** + - Add `GET /pass`: + - Reads `c.req.query("token")`. + - Validates token against Valkey/PostgreSQL. + - Sets `session_id` cookie on `.atyg.org`. + - Determines redirect URL and returns `302 Found`. +2. **UI Update (`ui/components/SessionsPage.tsx`):** + - Add **"1-Click Magic Link"** tab in `#handoffModal`. + - Copyable link: `https://auth.atyg.org/pass?token=ay_sess_...`. +3. **Automated Tests (`server/main.test.ts`):** + - Add test case verifying token validation, cookie setting, and redirection. diff --git a/tasks/new/2026-0825.02.gem.feat.event-passes.multi-claim-workshops-and-kiosks-0046.md b/tasks/new/2026-0825.02.gem.feat.event-passes.multi-claim-workshops-and-kiosks-0046.md new file mode 100644 index 0000000..e3b6139 --- /dev/null +++ b/tasks/new/2026-0825.02.gem.feat.event-passes.multi-claim-workshops-and-kiosks-0046.md @@ -0,0 +1,40 @@ +# TASK METADATA + +- **Target Files:** + - `server/db.ts` + - `server/main.ts` + - `ui/components/EventJoinPage.tsx` + - `ui/components/EventSplashPage.tsx` + - `server/main.test.ts` +- **Core Objective:** Implement Multi-Claim Event Passes with short vanity URLs (`/e/:slug`), universal PIN join portal (`/join`), and CLI environment 1-liner (`/join/:slug?format=env`). +- **Dependencies:** `server/db.ts`, `server/auth-session.ts` +- **Additional Important Notes:** Provisions isolated guest seats (`guest__`) with individual sessions. + +--- + +### 1. Architectural Considerations & Risks + +- **Concurrency & Seat Limits:** + - Ensure atomicity when incrementing `seats_claimed` on `event_passes` so events with strict seat limits (e.g. 50 seats) do not oversubscribe. +- **Multi-Channel Accessibility:** + - Support web browser UI (`/e/:slug`, `/join`), JSON API (`POST /api/join`), and CLI environment output (`GET /join/:slug?format=env`). +- **Session Isolation:** + - Each attendee gets their own dedicated guest session and UUID, preventing state collision in shared sandbox apps. + +--- + +### 2. Proposed Implementation + +1. **Database Schema (`server/db.ts`):** + - Create `event_passes` table with columns: `id`, `slug`, `pin_code`, `name`, `app_id`, `role`, `max_seats`, `seats_claimed`, `lifespan_hours`, `created_by`, `is_active`, `expires_at`, `created_at`. +2. **API Endpoints (`server/main.ts`):** + - `POST /api/events`: Create a new event pass. + - `GET /e/:slug`: Web landing splash with "Enter Workshop" button. + - `GET /join`: Universal PIN / code entry page. + - `POST /api/join`: Redeems code or PIN, creates isolated guest user and session, sets cookie or returns JSON. + - `GET /join/:slug`: Returns CLI 1-liner (`export AUTH_YES_TOKEN="..."` when `?format=env`). +3. **SSR UI Components:** + - `ui/components/EventJoinPage.tsx` + - `ui/components/EventSplashPage.tsx` +4. **Automated Tests (`server/main.test.ts`):** + - Test event creation, PIN redemption, seat limit capping, and CLI output format. diff --git a/tasks/new/2026-0825.03.gem.feat.event-cockpit.live-metrics-and-master-killswitch-0047.md b/tasks/new/2026-0825.03.gem.feat.event-cockpit.live-metrics-and-master-killswitch-0047.md new file mode 100644 index 0000000..49bd2c2 --- /dev/null +++ b/tasks/new/2026-0825.03.gem.feat.event-cockpit.live-metrics-and-master-killswitch-0047.md @@ -0,0 +1,32 @@ +# TASK METADATA + +- **Target Files:** + - `server/main.ts` + - `ui/components/SessionsPage.tsx` + - `ui/mod.ts` + - `server/main.test.ts` +- **Core Objective:** Add real-time event cockpit deck to the Sessions page with active seat counters, PIN display, time extension, and 1-tap master kill switch (`/api/events/:id/end`). +- **Dependencies:** `tasks/new/2026-0825.02.gem.feat.event-passes.multi-claim-workshops-and-kiosks-0046.md` +- **Additional Important Notes:** Sub-millisecond mass revocation of all event guest sessions via Valkey RESP3 push tracking. + +--- + +### 1. Architectural Considerations & Risks + +- **Mass Revocation Performance:** + - When the organizer clicks "End Workshop & Revoke All", the endpoint must delete all associated guest session IDs from both PostgreSQL and Valkey, emitting invalidation events to all connected nodes immediately. +- **Observability:** + - Show real-time seat claim progress bar (`38 / 50 claimed`) and event expiration timer. + +--- + +### 2. Proposed Implementation + +1. **Backend Endpoints (`server/main.ts`):** + - `POST /api/events/:id/end`: Closes the event, deletes all guest sessions under the event, and purges Valkey cache. + - `POST /api/events/:id/extend`: Adds hours to `expires_at` for the event and all active guest sessions. +2. **UI & Data Query (`ui/mod.ts`, `ui/components/SessionsPage.tsx`):** + - Query `event_passes` in `/dashboard/sessions` and render the **Event Cockpit Deck**. + - Display progress bar, quick copy buttons for PIN and Short URL, `[+1h Extend]` and `[🔴 End Workshop & Revoke All]`. +3. **Automated Tests (`server/main.test.ts`):** + - Test event extension and master kill-switch mass revocation.