SlackRequestVerifier checks the v0 HMAC-SHA256 signature over the timestamp and raw body. The timestamp must be within ±300 s and the comparison is constant-time. A failure returns 401 and records a REJECTED row with no payload.
Button values are tokens signed by the server, covering the message, action, table, record and expiry (7 days by default). They're checked against the stored message the click came from.
SlackActionMetaData (name, label, style, confirm, process and permission) is registered with SlackQBitConfig.withAction. SlackApiRequest.withAction adds buttons to the delivery and notification templates.
POST /qqq/hooks/slack/interactions
Verify the request and acknowledge it, then do the work asynchronously.
Map the Slack user to an ACTIVE connection in the same workspace row as the message. If there isn't one, the outcome is UNLINKED and the user gets an ephemeral "Connect Slack" prompt.
Run the process in that user's own session, so permission, tenant and record locks all apply. The outcome is DONE, DENIED or FAILED, with an ephemeral reply.
On DONE, update the message once, with key interaction:<id>, even if Slack retries the click.
Ephemeral replies go only to https://hooks.slack.com.
POST /qqq/hooks/slack/events
Handles url_verification, tokens_revoked and app_uninstalled. Revocations apply to every tenant row with that team id, and the token columns are cleared.
Audit and built-ins
slack_interaction (Interactions) is a child list on messages, locked by owner and tenant.
Built-in actions:acknowledge_alert and resolve_alert, registered only when processes with those names exist. There's no qbit-alerts import.
Tests
verify is green locally: 158 tests, 91.3% instruction coverage. The new tests cover:
bad signatures and stale timestamps;
forged and expired buttons;
a click from another tenant's workspace row with the same team id, which is UNLINKED and never runs;
Phase 2 of `docs/DESIGN.md` (section 12).
## Requests and buttons
- **`SlackRequestVerifier`** checks the v0 HMAC-SHA256 signature over the timestamp and raw body. The timestamp must be within ±300 s and the comparison is constant-time. A failure returns 401 and records a REJECTED row with no payload.
- **Button values** are tokens signed by the server, covering the message, action, table, record and expiry (7 days by default). They're checked against the stored message the click came from.
- **`SlackActionMetaData`** (name, label, style, confirm, process and permission) is registered with `SlackQBitConfig.withAction`. `SlackApiRequest.withAction` adds buttons to the `delivery` and `notification` templates.
## `POST /qqq/hooks/slack/interactions`
1. Verify the request and acknowledge it, then do the work asynchronously.
2. Map the Slack user to an ACTIVE connection **in the same workspace row as the message**. If there isn't one, the outcome is UNLINKED and the user gets an ephemeral "Connect Slack" prompt.
3. Run the process in that user's own session, so permission, tenant and record locks all apply. The outcome is DONE, DENIED or FAILED, with an ephemeral reply.
4. On DONE, update the message once, with key `interaction:<id>`, even if Slack retries the click.
Ephemeral replies go only to `https://hooks.slack.com`.
## `POST /qqq/hooks/slack/events`
Handles `url_verification`, `tokens_revoked` and `app_uninstalled`. Revocations apply to every tenant row with that team id, and the token columns are cleared.
## Audit and built-ins
- **`slack_interaction`** (Interactions) is a child list on messages, locked by owner and tenant.
- **Built-in actions:** `acknowledge_alert` and `resolve_alert`, registered only when processes with those names exist. There's no qbit-alerts import.
## Tests
`verify` is green locally: 158 tests, 91.3% instruction coverage. The new tests cover:
- bad signatures and stale timestamps;
- forged and expired buttons;
- a click from another tenant's workspace row with the same team id, which is UNLINKED and never runs;
- missing permission;
- approving exactly once on retry;
- a process failure;
- events across tenant rows;
- a canary signing secret that never leaks.
Codex implemented this; I reviewed it.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Phase 2 of
docs/DESIGN.md(section 12).Requests and buttons
SlackRequestVerifierchecks the v0 HMAC-SHA256 signature over the timestamp and raw body. The timestamp must be within ±300 s and the comparison is constant-time. A failure returns 401 and records a REJECTED row with no payload.SlackActionMetaData(name, label, style, confirm, process and permission) is registered withSlackQBitConfig.withAction.SlackApiRequest.withActionadds buttons to thedeliveryandnotificationtemplates.POST /qqq/hooks/slack/interactionsinteraction:<id>, even if Slack retries the click.Ephemeral replies go only to
https://hooks.slack.com.POST /qqq/hooks/slack/eventsHandles
url_verification,tokens_revokedandapp_uninstalled. Revocations apply to every tenant row with that team id, and the token columns are cleared.Audit and built-ins
slack_interaction(Interactions) is a child list on messages, locked by owner and tenant.acknowledge_alertandresolve_alert, registered only when processes with those names exist. There's no qbit-alerts import.Tests
verifyis green locally: 158 tests, 91.3% instruction coverage. The new tests cover:Codex implemented this; I reviewed it.
🤖 Generated with Claude Code