# Slack Connector

**Two-way sync between a Slack workspace and the Fabric graph — channel/DM messages, threads, and reactions. Emits `chat.postMessage` and `reactions.add` back.**

## What it does

* **Ingests** every message + reaction in channels the bot is invited to, plus DMs and group DMs.
* **Backfills** the workspace history via `conversations.list` → `history` → `replies`, page by page.
* **Emits back** when Fabric posts a message or adds a reaction — atomic dedup on echo via a two-tier filter so the bot's own writes never feed back into the graph.
* **Survives reinstall** — uninstall is transactional: workspace claim, credentials, status all flip atomically; reinstalling the same workspace afterward just works.

One install per Slack workspace **across the entire deployment** — Slack's single-app identity model means two tenants installing the same workspace would share bot identity at Slack's side, so we refuse it. See [Conflicts](#conflicts) below.

## Prerequisites

* Slack admin who can install apps in the target workspace.
* A reachable deployment URL for OAuth — during install, Slack redirects the installer's browser to your `redirect_uri` callback, so the deployment must be reachable from the installer (typically a public URL).
* For **HTTP transport**: Slack must also be able to POST Events API payloads to your `/api/v1/connectors/slack/events` endpoint — inbound from Slack's network.
* For **Socket Mode**: no inbound traffic needed. The daemon opens an outbound WSS connection to Slack; OAuth is still subject to the redirect rule above.
* `FABRIC_MASTER_KEY` set on the daemon — the Slack bot token is encrypted at rest with this key.

## Create the Slack app

1. Go to [https://api.slack.com/apps](https://api.slack.com/apps) → **Create New App** → **From a manifest**.
2. Paste the contents of [`slack-app-manifest.yml`](https://github.com/cognisos-ai/Prod_Fabric/blob/main/slack-app-manifest.yml) from this repo (replace `your-host` with your deployment hostname).
3. **Save** → Slack provisions the app. Visit the app's **Basic Information** page and copy:
   * **Signing Secret** → daemon env `SLACK_SIGNING_SECRET` (HTTP transport).
   * **App-Level Token** (generate one with `connections:write`) → daemon env `SLACK_APP_TOKEN` (Socket Mode).

## Pick a transport

The connector supports **two** mutually-exclusive transports — pick one per deployment:

### HTTP Events API (default)

Slack POSTs events to your deployment over the public internet. Easier to debug (events flow through your normal HTTP stack) and matches how production SaaS apps usually deploy.

```bash
export SLACK_CLIENT_ID=…           # from app's "Basic Information"
export SLACK_CLIENT_SECRET=…
export SLACK_SIGNING_SECRET=…
export SLACK_APP_TIER=non_marketplace   # or "marketplace" if approved
# SLACK_USE_SOCKET_MODE unset / false
```

### Socket Mode

Daemon opens an outbound WSS connection to Slack — no inbound HTTP needed, useful behind a NAT/firewall.

```bash
export SLACK_CLIENT_ID=…
export SLACK_CLIENT_SECRET=…
export SLACK_APP_TOKEN=xapp-…      # from app's "App-Level Tokens"
export SLACK_APP_TIER=non_marketplace
export SLACK_USE_SOCKET_MODE=true
# SLACK_SIGNING_SECRET unset — HTTP events route won't be mounted
```

Also flip **Settings → Socket Mode → Enable** in the Slack app config. Slack enforces mutual exclusion at its side too — having both transports armed would either double-deliver events or silently break depending on the app config order.

The daemon reconnects with exponential backoff (1s → 60s, full jitter) if the WSS connection drops.

## Install into a workspace

Once env is set and the daemon is running, walk the OAuth flow per tenant:

```bash
# 1. Mint an install URL — the response carries `authorize_url`.
curl -sX POST \
  "http://localhost:3000/api/v1/connectors/slack/oauth/start" \
  -H "Authorization: Bearer $FABRIC_API_KEY" \
  -d '{"redirect_uri": "https://your-host.example.com/api/v1/connectors/oauth/callback"}'

# 2. Open the returned authorize_url in a browser; Slack walks you through
#    install + scope consent and redirects back to the callback URL above.
#    The callback finalizes the install and writes credentials.

# 3. Verify the install:
curl -s "http://localhost:3000/api/v1/connectors/installs?connector_id=slack" \
  -H "Authorization: Bearer $FABRIC_API_KEY"
```

The connector then:

* Encrypts the bot token under `FABRIC_MASTER_KEY` and stores it in `connector_credentials`.
* Inserts a `slack_workspace_installs` row claiming the workspace globally.
* Starts the backfill orchestrator (paginated `conversations.list` → `history` → `replies`).
* Begins receiving live events on whichever transport is active.

### Conflicts

A second install of the **same** Slack workspace — even from a different tenant — fails with HTTP **409** at the OAuth callback. The Slack-side bot identity is shared across that one app, so two tenants would step on each other; the conflict is enforced atomically by the `slack_workspace_installs.team_id` primary key.

### Reinstall after uninstall

Uninstall (`DELETE /api/v1/connectors/installs/<install_id>`) is transactional: the workspace claim row, credentials revocation, and install status flip happen in one Postgres transaction. After uninstall, the next install of the same workspace **just works** — no 409, no manual cleanup. (Pre-v1 this required operator intervention because the `slack_workspace_installs` row was left orphaned.)

## Emit a message

```bash
curl -sX POST \
  "http://localhost:3000/api/v1/connectors/installs/<install_id>/emit" \
  -H "Authorization: Bearer $FABRIC_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "kind": "slack.message",
    "body": {
      "channel_id": "C0123456789",
      "text": "Hello from Fabric"
    }
  }'
```

Response carries `EmitReceipt.provider_id` = Slack's `ts` (use it to reply in-thread later). Emitted messages are **not** ingested back into the graph — the two-tier self-emit filter drops them on the inbound side.

### Rate limits

Slack throttles `chat.postMessage` at ~1 msg/sec **per channel** on top of the workspace-wide write budget. The emit route charges both buckets atomically — a throttled per-channel post returns **429** with a `Retry-After` header **without** consuming a workspace-wide token. Burst-posting to unrelated channels still works at the workspace cap (~100/min).

### React to a message

```bash
curl -sX POST \
  "http://localhost:3000/api/v1/connectors/installs/<install_id>/emit" \
  -H "Authorization: Bearer $FABRIC_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "kind": "slack.reaction",
    "body": {
      "channel_id": "C0123456789",
      "timestamp": "1736000000.000001",
      "name": "thumbsup"
    }
  }'
```

## Uninstall

```bash
curl -X DELETE \
  "http://localhost:3000/api/v1/connectors/installs/<install_id>" \
  -H "Authorization: Bearer $FABRIC_API_KEY"
```

Effects:

* Slack token revoked at Slack (`auth.revoke`).
* Local credentials marked revoked, install status flipped to `uninstalled`, workspace claim DELETEd — all in one transaction.
* Backfill worker stops ingesting (it gates on `status == "active"`).

If the Slack `auth.revoke` call succeeds but the local transaction then fails, the Slack-side token is dead while the install row stays `active`. Retrying uninstall converges — `auth.revoke` is idempotent at Slack's end and the local tx either commits cleanly or surfaces the same error for ops to investigate.

## Limitations

* **One install per workspace per deployment.** Cross-tenant workspace sharing is rejected with 409 — see [Conflicts](#conflicts).
* **Files are referenced, not downloaded.** A message with attachments lands in Fabric with `payload.files = [{ file_id, name, mimetype, size, permalink }]` — the connector does not fetch file bytes in v1.
* **DM coverage requires scope.** If `mpim:history` is missing from the install consent, MPIMs silently drop out of backfill; the connector logs the missing scope at install time.

## See also

* [All connectors](/#connectors) — other available connector integrations.
* [`slack-app-manifest.yml`](https://github.com/cognisos-ai/Prod_Fabric/blob/main/slack-app-manifest.yml) — Slack app manifest to paste into the App Directory.
* [Issue #231](https://github.com/cognisos-ai/Prod_Fabric/issues/231) — the design issue for this connector.
