Slack Ticket Approval (Dry-Run Linear Pipeline)

Purpose: Allow approving, rejecting, or reassigning planned Linear tickets from the dry-run Slack message before they are pushed to Linear.


1. Overview

When the dry-run Linear ticket pipeline runs (e.g. after meeting ingestion or via npm run dry-run-linear -- <meetingId>), the platform sends a Block Kit message to Slack with the same content as before (updates first, then creates) plus Approve, Reject, and Reassign buttons per ticket.

  • Approve: Slack app calls platform POST /api/brainforge/tickets/approve; platform re-runs dry-run, executes that single action (create or update in Linear), then the Slack message is updated to show “Approved”.
  • Reject: Slack app only updates the message to show “Rejected” (no Linear write, no platform call).
  • Reassign (v1): Slack app only updates the message to show “Reassign requested”; assignee picker modal can be added later.

2. Configuration

Platform (The Forge)

  • Route: POST /api/brainforge/tickets/approve
    Body: { meetingId: string, actionIndex: number, assigneeId?: string, assigneeName?: string }
  • Auth: Request must include either:
    • Authorization: Bearer <PLATFORM_API_SECRET>, or
    • x-internal-secret: <PLATFORM_API_SECRET>
  • Env: Set PLATFORM_API_SECRET in the platform (e.g. in Vercel / .env.local). Use a strong random value; same value must be set in the Slack app.

Slack app (brainforge-assistant on Railway)

  • Interactivity: Request URL is already set (e.g. https://<railway-domain>/slack/interactive). No change needed.
  • Env (Railway):
    • PLATFORM_API_URL – Base URL of the platform (e.g. https://platform.brainforge.ai). No trailing slash.
    • PLATFORM_API_SECRET (or SLACK_TICKET_APPROVAL_SECRET) – Same secret as the platform’s PLATFORM_API_SECRET.

If either PLATFORM_API_URL or PLATFORM_API_SECRET is missing, Approve button clicks will log an error and post an ephemeral message to the user; Reject and Reassign still update the message.


3. Credentials

  • Platform secret: Generate a random string (e.g. openssl rand -hex 32) and set it in both:
    • Platform: PLATFORM_API_SECRET
    • Slack app (Railway): PLATFORM_API_SECRET or SLACK_TICKET_APPROVAL_SECRET
  • Prefer storing in 1Password and injecting via deployment secrets rather than committing to the repo.

4. Flow

  1. Meeting created (or manual dry-run) → platform runs runDryRunAndNotifySlack(meetingId).
  2. Platform builds Block Kit via buildDryRunSlackBlocks() (updates first, then creates; button value = meetingId:actionIndex) and sends with sendSlackMessage(..., blocks).
  3. User clicks Approve/Reject/Reassign → Slack sends block_actions to the Slack app’s Request URL.
  4. Slack app (Bolt) runs app.action('ticket_approve'|'ticket_reject'|'ticket_reassign'), acks, then calls platform (Approve) or updates the message (all three).
  5. For Approve, platform re-runs dry-run, runs executeTicketActions([action], meetingId), returns 200; Slack app updates the message to show “Approved”.

5. References

  • Platform: apps/platform/src/lib/linear-ticket-pipeline/dry-run.ts (buildDryRunSlackBlocks, sendDryRunResultToSlack), apps/platform/src/app/api/brainforge/tickets/approve/route.ts
  • Slack app: apps/slack-apps/brainforge-assistant/src/ticket-approval.js, apps/slack-apps/brainforge-assistant/src/index.js (action handlers)
  • Slack app env: apps/slack-apps/brainforge-assistant/ENVIRONMENT.md (Slack ticket approval section)