feat: phase 2 - interactive buttons as the clicking user, Events API revocations #8

Merged
KofTwentyTwo merged 3 commits from feature/interactivity into develop 2026-10-14 19:56:07 +00:00
KofTwentyTwo commented 2026-10-14 18:49:32 +00:00 (Migrated from github.com)

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

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)
Sign in to join this conversation.