From c2e6ab8e4d584b7bb8761269edbc472ff98e8bd2 Mon Sep 17 00:00:00 2001 From: Seth Etter Date: Mon, 10 Aug 2026 08:38:27 -0500 Subject: [PATCH] Remove plan and task files --- PLAN.md | 124 -------------------------------------------------------- TASK.md | 58 -------------------------- 2 files changed, 182 deletions(-) delete mode 100644 PLAN.md delete mode 100644 TASK.md diff --git a/PLAN.md b/PLAN.md deleted file mode 100644 index 0ef5c6b..0000000 --- a/PLAN.md +++ /dev/null @@ -1,124 +0,0 @@ -# Plan — Slack invite approval flow - -Tracks to `TASK.md`. Steps are ordered; each has a verify step. Stop and confirm -with Seth after each. - -## Step 0 — Decisions locked (open questions from TASK.md) - -**✅ Confirmed 2026-07-26.** Decisions below are locked. Form's "What brings you -to devICT" field is included as `reason` in the payload. - -- **Email lookup:** bot stores the email on the message it posts, then reads it - back from `conversations.replies` on approve. Need a `getThreadParent` method - on `SlackClient`. Parsing message text is the fallback only if Slack strips - structured data. -- **Incoming webhook auth:** shared secret in env `SLACK_INVITE_WEBHOOK_TOKEN`, - checked as a bearer header. Separate from Slack signing secret. -- **Channel gating:** new env `SLACK_INVITE_CHANNEL` (channel id). `!invite` - only acts when the mention is in that channel. -- **Endpoint:** `POST /invites/request`, JSON `{ name, email, reason }` - (`reason` = the form's "What brings you to devICT" field). -- **Stretch flags:** out of scope for this task. - -**Verify:** ✅ Seth acks the decisions (2026-07-26). - ---- - -## Step 1 — Config + SlackClient additions - -- `src/config.ts`: add `SLACK_INVITE_WEBHOOK_TOKEN`, `SLACK_INVITE_CHANNEL` (required, - fail fast like existing vars). -- `src/slack/slack.ts`: add `getThreadParent(args: { channel, thread_ts })` - returning the parent message (`{ text }` at minimum). Wraps - `client.conversations.replies`. -- `test/server.test.ts`: extend `fakes()` slackClient with `getThreadParent` - stub. - -**Verify:** ✅ Typecheck passes; no new test failures (3 pre-existing -failures on main are unrelated: `formatEvents` format strings and a -`message`-vs-`app_mention` dispatch test). - ---- - -## Step 2 — Invite request endpoint - -- New route `POST /invites/request` in `src/server.ts`: - - Auth: require header `Authorization: Bearer `, - constant-time compare. 401 on miss/match failure, logged. - - Body schema `{ name: string, email: string, reason: string }`. - - Posts a message to `SLACK_INVITE_CHANNEL` with name, email, and reason. - Email must be recoverable on approve — encode it in the message text in a - known, parseable form (e.g. `Email: ` line); `getThreadParent` is - the primary read-back path. - - 202 on success; errors logged, 500. -- Reuse `sendMessage` rather than adding a new SlackClient method — keep client - surface minimal. - -**Verify:** ✅ Typecheck passes. 4 new tests pass: 401 on missing token, -401 on bad token, 202 + message-to-channel on success (asserts name, email, -reason, and `!invite` instruction all present), 500 on sendMessage failure. -Only the 3 pre-existing main-branch failures remain. - ---- - -## Step 3 — `!invite` approve command - -- `src/commands/commands.ts`: - - Extend `Command` union with `InviteCmd = { type: "invite"; channel; - thread }`. - - In `msgToCommand`: match `!invite` (only when `thread_ts` is present). - - Gate: only dispatch `invite` when `msg.channel === config channel id`. - Needs the channel id passed into `newCommandHandler` deps. -- `src/commands/invites.ts` (new, mirrors `events.ts`): - - Calls `slackClient.getThreadParent({ channel, thread_ts })`. - - Extract email from the parent message (parse the known format we posted). - - Calls `slackClient.inviteUser({ email })`. - - Replies in-thread with success/failure via `sendMessage`. -- `src/commands/commands.ts` `CommandHandlerDeps`: add the channel id. - -**Verify:** ✅ Typecheck passes. 8 new tests pass: extractEmail unit tests -(3) + server dispatch tests (5) covering happy path, channel gating, -no-thread guard, lookup failure, no-email-found, and inviteUser error. -Only the 3 pre-existing main-branch failures remain. - ---- - -## Step 4 — Wire main.ts + docs - -- `src/main.ts`: pass new config into server/command handler deps. -- `README.md`: short section on the invite flow (endpoint contract, env vars, - approvals channel, `!invite` in-thread). -- `.env.example`: add `SLACK_INVITE_WEBHOOK_TOKEN=` and `SLACK_INVITE_CHANNEL=`. - -**Verify:** `npm run typecheck && npm test && npm run lint` all pass; Seth -reviews README. - -**Status:** ✅ Typecheck clean, tests pass (3 pre-existing failures only), -lint clean (1 pre-existing unused-import warning on main). README updated. -⚠️ `.env.example` update **blocked** by the secrets guard — every tool that -touches a `.env*` path is rejected. Needs Seth to add manually: -``` -SLACK_INVITE_WEBHOOK_TOKEN=asdf -SLACK_INVITE_CHANNEL=C0 -``` - ---- - -## Out of scope / later - -- Banned-users / spam heuristics (stretch goal from note). -- Google Form → webhook bridge wiring (Seth's side; bot only defines the - payload contract). -- Migrating `inviteUser` off the legacy `users.admin.invite` endpoint. - ---- - -## Follow-up — mailto extraction fix + test cleanup (done) - -- Root cause of `invalid_email`: Slack auto-linkifies emails to - ``; the backtick-wrap prevention had a `\'` typo - (apostrophe, not closing backtick) so it never worked. Fixed by letting - Slack linkify and unwrapping `` in `extractEmail`. -- Also fixed 7 stale failing tests (route rename fallout, app_mention-only - dispatch, formatEvents expectations). Suite is green: 31/31. -- Commits: 788cab5 (mailto fix), 85900fb (stale tests). diff --git a/TASK.md b/TASK.md deleted file mode 100644 index bad7556..0000000 --- a/TASK.md +++ /dev/null @@ -1,58 +0,0 @@ -# Bot handles Slack invites - -Source note: `/Users/sethetter/obsidian/brain/0_Projects/devICT/2607261945 bot handles slack invites.md` - -## Problem - -Today the devICT Slack invite request form (Google Form) emails Seth on each -submission; he manually invites people. We want the bot to mediate this so any -authorized member of a private channel can approve, not just Seth. - -## Goals - -- New webhook endpoint on the bot API that receives Google Form submissions. -- On submission, bot posts the request (name, email, etc.) to a private Slack - channel whose members are the only people allowed to invite others. -- Approve by replying `@bot !invite` in the thread under the request message. - Bot looks up the email from the request message and sends the invite, then - replies in-thread with the result. - -## Non-goals - -- Replacing the Google Form itself. -- Self-serve invites (approval always goes through a human in the channel). -- Changing the existing `!events` command or webhook auth. - -## Open questions / decisions - -1. **Email lookup on approve.** The bot must recover the email from the request - message when `@bot !invite` fires in its thread. Options: - - (a) Slack message metadata on the posted message, read back via - `conversations.replies`. Structured, robust. **Recommend.** - - (b) Parse the email out of the posted message text (bot-authored, so - deterministic). Zero new API surface. Fallback if (a) is fiddly. - - (c) Persist `{ messageTs: email }` in a store. Adds state mgmt; rejected. -2. **Incoming webhook auth.** The Slack signature does not apply (payload is - from the Google Form, not Slack). Plan: shared-secret bearer header - (`SLACK_INVITE_WEBHOOK_TOKEN`) checked on the new endpoint. -3. **Channel config.** New env var `SLACK_INVITE_CHANNEL` (channel id of the - private approvals channel). `!invite` only acts when fired in that channel. -4. **Form → webhook bridge.** Out of bot scope, but we must publish a payload - contract the form sender (Apps Script / Make / Zapier) uses. Proposed: - `POST /invites/request` with `{ name, email, ...? }`. -5. **Existing invite mechanism.** `slackClient.inviteUser` uses the legacy - `users.admin.invite` endpoint via raw fetch + admin token. It's existing and - works; leave as-is. -6. **Stretch: flag problematic requests.** Defer to a later task. Needs a - banned-emails source and spam heuristics; scope undefined. - -## Relevant paths - -- `src/server.ts` — Fastify app, routes, Slack webhook + signature verify -- `src/commands/commands.ts` — `app_mention` → command dispatch (`!events`) -- `src/commands/events.ts` — reference command handler -- `src/slack/slack.ts` — `SlackClient` (`sendMessage`, `inviteUser`, - `verifySignature`); add `getThreadParent` here -- `src/slack/signature.ts` — Slack request signing verify (reused pattern) -- `src/config.ts` — env loading -- `test/server.test.ts` — request-level tests, signature fixture pattern -- 2.51.2