WIN · LAUNCH WORKBOOK pre-UAT → production · mined from Codex session 019f25cc · snapshot 2026-07-06 · origin/main 734eff41

Lane 2 · Slack channel

Slack UX — harden, prove, and QA end-to-end

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.

0
live DM→job proofs captured (DM-PROOF gate)
1
Slack app shared across all envs
~30
Slack test files, none true e2e
1/7
Slice 1e stage (never started)

Flow inventory (verified, with entry points)

FlowPathState
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 → deliverykernel progress → slack-renderer.ts:53 streaming (chat.startStream/append/stop) → terminal Block Kit card via slack-blocks.tsLANDED
Thread reply routingrunning → forward; awaiting_input/review → ingestTurn(kind:"answer"); terminal → “already finished” (slack-events.ts:54–180)LANDED
App mention / Home tab / interactive buttonsslack-events.ts:206; slack-home.ts:138,329; /api/slack/interactive accept/revise/disconnect/claude_connectLANDED · Home tab render has zero test coverage
File upload in / publish outslack-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 RESTMISSING

Slice 1e — confirmed orphaned

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.

Structural risks found in audit

Plan of attack

Implementation worksheet0/9
Slack App Manifest API makes this declarative; kills the one-Request-URL bottleneck and enables parallel QA. Store manifests in repo (e.g. config/slack/manifest.<env>.json); document rotation.
action_id retry_* renders on every failure card and does nothing on click. Either route to a guarded re-run (idempotency key = source job id) or drop the button and keep Open-in-WIN as the recovery path (original 1e intent).
Rewrite the brief against today’s code first — the 4-label premise is stale. Use the eight-state storyboard in docs/plans/pre-uat-and-prod-ready/slack-ux-preview/ as the design source.
slack-verification.test.ts currently reimplements HMAC locally and never imports the real module — fix that too.
SlackTestHarness already computes real HMAC signatures; today’s “integration” test stubs spawn and never asserts on outbound messages. Also add X-Slack-Retry-Num semantics.
Parity gap vs Web/CLI; see Channel Spine tab SPN-2 — same work item, one PR.
Make DM-PROOF verifiable from a query instead of manual DB spelunking: a small admin view over slack_stream/slack_emit event rows keyed by correlation id.
The standing operator gate since #537 merged. Blocked only by deploy + a human sending the DM. Everything else on this page can proceed in parallel.
Track each scenario as a ledger row; failures become slices, not inline fixes (repo slice discipline).

QA scenario matrix

#ScenarioKindVerify via
Q1DM → job → completion → success card in correct thread (the DM-PROOF)happySlack screenshot + events rows + jobs API
Q2Duplicate delivery: same event_id twice; genuine retry with X-Slack-Retry-Numfailureexactly one job, one message
Q3Thread reply in each job state (running / awaiting_input / awaiting_review / terminal)happycorrect routing per state
Q4Signature: valid / missing / stale timestamp / tampered body / wrong secret × both routessecurity401/503 fail-closed, logged
Q5Job errors mid-run → failure card, no stack leak, recovery affordance works (post SLK-2)failurecard assert + console
Q6Two near-simultaneous DMs in one thread → one job (tryInsertThread atomicity)racejob count
Q7Home tab open → full Block Kit render incl. admin-only Claude-connect; all 13 job statuses show a live status dothappyview payload assert
Q8File upload: normal file; HTML-masquerade file rejectedsecurityworkspace contents
Q9Redis outage during dedup → event processed (fail-open), no duplicate jobchaoslogs + job count
Q10Token/secret rotation drill: rotate bot token, then signing secret → loud failure, alert reaches a human off-Slackopsalert receipt

Agent handoff prompt

prompt · SLK-1 env-isolated Slack apps
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.