Slack reaction-driven expense approval router. Replaces the Pipedream `expenses_pipeline` workflow.
## Flow
A user reacts ✅ to a message in an expense channel. The bot:
1. Maps the source channel to the next stage's channel.
2. Copies the message text (stripped of any prior "react to advance" hint) into the next channel, with a permalink back to the original on the first advance.
3. Replies in-thread on the original message with `➡️ Advanced to {Stage}`.
- **Receiver Lambda** (`src/receiver/`) — fronted by API Gateway HTTP API. Verifies the Slack request signature, handles the URL-verification handshake, and async-invokes the processor. Returns 200 within Slack's 3-second window to prevent retries.
- **Processor Lambda** (`src/processor/`) — does the 5-step sequence of Slack API calls. No inbound HTTP; invoked only by the receiver.
- **Secrets** — Slack bot token and signing secret each stored as separate AWS Secrets Manager secrets. Each Lambda only has IAM access to the secret it needs.
4. After the first deploy, take the `SlackEventsUrl` output and paste it into the Slack app's **Event Subscriptions → Request URL**. Subscribe the bot to the `reaction_added` event.
## Configuration
The channel → stage mapping lives in `src/processor/app.py` (`STAGES`). Update there if channels change. The "on first advance, include a permalink" check is keyed on the Processed-stage channel ID — update that too if the first-stage channel changes.
## Manual testing
Invoke the processor directly with a synthetic Slack reaction event: