Lane 2 · Slack channel
The Slack bot is architecturally sound (fail-closed signature verification, atomic thread claims, real streaming) but has never been proven live post-fix, has an orphaned copy/cards slice, and — the structural blocker for QA — one shared Slack app means staging and beta can never receive events at the same time.
| Flow | Path | State |
|---|---|---|
| DM → job | /api/slack/events → signature verify → Redis SET NX dedup → email resolve → tryInsertThread atomic claim → route (answer/clarify/spawn) → ingestTurn → job + live stream (slack-events.ts:185–341, slack-handlers.ts:102–216) | LANDED · unproven live |
| Progress → delivery | kernel progress → slack-renderer.ts:53 streaming (chat.startStream/append/stop) → terminal Block Kit card via slack-blocks.ts | LANDED |
| Thread reply routing | running → forward; awaiting_input/review → ingestTurn(kind:"answer"); terminal → “already finished” (slack-events.ts:54–180) | LANDED |
| App mention / Home tab / interactive buttons | slack-events.ts:206; slack-home.ts:138,329; /api/slack/interactive accept/revise/disconnect/claude_connect | LANDED · Home tab render has zero test coverage |
| File upload in / publish out | slack-attachment-downloader.ts + HTML-masquerade guard; slack-file-publisher.ts:157 (shells to wrangler, no lock) | LANDED |
| Job cancellation from Slack | — no handler exists; publishCancel only reachable from REST | MISSING |
Never started (stage 1/7 in Program-3 STATUS; zero commits reference it). Its only artifact is an offline eight-state storyboard at docs/plans/pre-uat-and-prod-ready/slack-ux-preview/. Two of its premises don’t match today’s code: there is no “4-label lexicon” anywhere, and the failure card that does exist (slack-blocks.ts:220–236) already has both “Retry in Slack” and “Open in WIN” buttons — but the retry button’s action_id has no handler anywhere. It’s dead UI shipped to users, exactly the non-idempotency trap 1e was written to avoid.
/api/slack/interactive carries its own weaker copy (falsy placeholder check) of the canonical fail-closed slack-verification.ts — drift risk; tampered-signature case untested there.X-Slack-Retry-Num has zero references. Known intentional double-send bug on claimed error-exit (slack-renderer.ts:160–176).event_id; downstream is jobId/channel/threadTs — no propagated trace id. Slack audit-event constants (slack_admin_mutation, slack_token_*) are declared but never invoked. Ops alerting is circular: Slack-health alerts post via the same bot token they monitor.WIN_SLACK_SIGNING_SECRET; packages/config reads SLACK_SIGNING_SECRET. Confirm intent or unify.| # | Scenario | Kind | Verify via |
|---|---|---|---|
| Q1 | DM → job → completion → success card in correct thread (the DM-PROOF) | happy | Slack screenshot + events rows + jobs API |
| Q2 | Duplicate delivery: same event_id twice; genuine retry with X-Slack-Retry-Num | failure | exactly one job, one message |
| Q3 | Thread reply in each job state (running / awaiting_input / awaiting_review / terminal) | happy | correct routing per state |
| Q4 | Signature: valid / missing / stale timestamp / tampered body / wrong secret × both routes | security | 401/503 fail-closed, logged |
| Q5 | Job errors mid-run → failure card, no stack leak, recovery affordance works (post SLK-2) | failure | card assert + console |
| Q6 | Two near-simultaneous DMs in one thread → one job (tryInsertThread atomicity) | race | job count |
| Q7 | Home tab open → full Block Kit render incl. admin-only Claude-connect; all 13 job statuses show a live status dot | happy | view payload assert |
| Q8 | File upload: normal file; HTML-masquerade file rejected | security | workspace contents |
| Q9 | Redis outage during dedup → event processed (fail-open), no duplicate job | chaos | logs + job count |
| Q10 | Token/secret rotation drill: rotate bot token, then signing secret → loud failure, alert reaches a human off-Slack | ops | alert receipt |
Give WIN true per-environment Slack isolation. Today one Slack app serves all envs: its Events Request URL points at exactly one environment (manual console switch), there is no app manifest in the repo, and beta/staging can never receive events simultaneously — blocking all parallel Slack QA. Do: (1) author declarative Slack app manifests checked into config/slack/ (manifest.staging.json, manifest.beta.json, manifest.prod.json) covering bot scopes (chat:write, users:read, users:read.email, im:history), event subscriptions (message.im, app_mention, app_home_opened), interactivity URL, and per-env request URLs; (2) document create/update via Slack's manifest API in docs/operations/SLACK-BOT-SETUP-AND-RESTORE.md, including per-env signing secret + bot token storage (config naming: server reads WIN_SLACK_SIGNING_SECRET, packages/config reads SLACK_SIGNING_SECRET — unify or document); (3) add a win CLI or script smoke check that posts a signed synthetic event to each env's /api/slack/events and asserts 200 + dedup behavior. Do not write to any live Slack workspace without operator confirmation; deliver manifests + docs + smoke tool as the PR.