# Hermes Slack Profile Scoping

Use this when creating or updating customer Hermes profiles that need Slack
access.

## Default Rule

For Sellable-managed customer channels, use one dedicated Slack app/token pair
per active Hermes customer profile. Run native `hermes -p <profile> gateway run`
for that profile only. Do not run multiple active Hermes profile gateways
against the same `SLACK_APP_TOKEN`.

The customer's Slack app is bound to the exact Slack channel id and the
customer's profile-local Sellable MCP config. Customer profiles must not store
or read Sellable Admin MCP credentials, admin Slack app tokens, or sibling
profile config.

The 2026-07-06 Hostinger POC proved the native Hermes listener for the
`sellable-admin` profile with the existing `Sellable Reply Bot` app in
`#team-chat`: a human mention produced a new Hermes session and a threaded bot
reply. Treat that as proof that the gateway/listener path works for one owning
profile, not as approval for multiple active customer profiles sharing one app
token.

The same day, the `acme` profile reused the same redacted Slack token
fingerprints for an outbound-only smoke to `#sellable-acme` via:

```bash
hermes --profile acme send --to slack:C0BFERDV3N0 --subject '[Hermes acme smoke]' ...
```

That succeeded and is useful as an internal smoke test. It does not change the
inbound listener rule: each active customer profile needs its own native Slack
app token pair.

## Which Slack Values Are Enough

For outbound sends and ordinary Slack Web API calls, the important value is the
profile-local `SLACK_BOT_TOKEN`.

For native Hermes live listening through Slack Socket Mode, the profile also
needs `SLACK_APP_TOKEN`, an app-level `xapp-...` token with `connections:write`.
`SLACK_APP_ID`, `SLACK_CLIENT_ID`, `SLACK_CLIENT_SECRET`,
`SLACK_SIGNING_SECRET`, `SLACK_VERIFICATION_TOKEN`, `SLACK_TEAM_ID`, and
`SLACK_OPERATOR_EMAIL` do not replace `SLACK_APP_TOKEN`.

## Required Slack App Shape

Outbound Slack sends can work with fewer permissions. Native Hermes inbound
listening needs the Slack app itself to be configured for Socket Mode and event
delivery:

- Enable Socket Mode.
- Create an app-level token with `connections:write`.
- Install the bot with `app_mentions:read`, `channels:history`,
  `channels:read`, `chat:write`, `groups:history`, `groups:read`,
  `im:history`, `im:read`, `mpim:history`, `mpim:read`, and `users:read`.
- Subscribe bot events for `app_mention`, `message.channels`,
  `message.groups`, `message.im`, and `message.mpim`.
- Reinstall the app after changing scopes or event subscriptions.

If the Slack app has `incoming-webhook`, Slack will ask for a webhook channel
during reinstall. Pick the profile home channel. That webhook selection does
not replace `SLACK_ALLOWED_CHANNELS`; keep the profile-local allowlist explicit.

## Profile Bootstrap Command

Use the Sellable installer profile bootstrap:

```bash
sellable hermes profile bootstrap \
  --profile acme \
  --profiles-root /srv/hermes/profiles \
  --workspace-id ws_acme \
  --workspace-name Acme \
  --token-file /run/secrets/sellable-acme-token \
  --slack-bot-token "$SLACK_BOT_TOKEN" \
  --slack-app-token "$SLACK_APP_TOKEN" \
  --slack-home-channel C0ACME12345 \
  --slack-home-channel-name sellable-acme \
  --slack-allowed-users U0OPERATOR1 \
  --slack-require-mention true \
  --json
```

If `--slack-home-channel` is supplied and `--slack-allowed-channels` is omitted,
the installer writes `SLACK_ALLOWED_CHANNELS=<home-channel-id>` automatically.
That makes the profile fail closed to the customer channel by default.

Use `--slack-allowed-channels C...,G...` only when a profile is intentionally
allowed to listen in more than one channel. Pass Slack IDs, not `#channel-name`
strings or wildcards.

## Resulting Profile Env

For the dedicated customer Slack app model, the profile-local `.env` should
contain only scoped Slack keys for that profile:

```env
SLACK_BOT_TOKEN=...
SLACK_APP_TOKEN=...
SLACK_HOME_CHANNEL=C0ACME12345
SLACK_HOME_CHANNEL_NAME=sellable-acme
SLACK_ALLOWED_CHANNELS=C0ACME12345
SLACK_ALLOWED_USERS=U0OPERATOR1
SLACK_REQUIRE_MENTION=true
```

Keep the file mode at `0600`. Do not copy raw tokens into planning docs, chat,
or committed artifacts. Record token fingerprints only.

The profile's Hermes MCP env must also include the profile-local Sellable
config, customer workspace lock, and fail-closed guard:

```yaml
SELLABLE_CONFIG_PATH: /srv/hermes/profiles/acme/sellable/config.json
SELLABLE_CONFIGS_DIR: /srv/hermes/profiles/acme/sellable/configs
SELLABLE_LOCK_WORKSPACE_ID: ws_acme
SELLABLE_REQUIRE_WORKSPACE_LOCK: "1"
```

`SELLABLE_REQUIRE_WORKSPACE_LOCK=1` means an unbound customer profile fails
closed before making Sellable API calls instead of falling back to shared or
admin config state. Only the `sellable-admin` profile should provision,
inspect, or repair other profiles.

## Verification

Run the installer and native profile UAT before publishing installer changes:

```bash
npm run test:unit -- tests/install-package/agent-preferences.test.ts
scripts/run-phase92-hermes-profile-bootstrap-uat.sh --mode local-temp
scripts/run-phase92-hermes-profile-bootstrap-uat.sh --mode linux-shaped
```

Expected proof:

- profile-local Sellable config paths remain authoritative
- generated profile `.env` has `SLACK_HOME_CHANNEL`
- generated profile `.env` has `SLACK_ALLOWED_CHANNELS`
- `acme` allowlist points at the `sellable-acme` fixture channel id
- redaction scan passes

## Current Phase 03 Status

Phase 03 proved the profile-scoped installer path, outbound Hermes send path,
and live Slack listener for the `sellable-admin` profile. Phase 03.1 now targets
dedicated native Slack apps per customer profile, profile-local Sellable MCP
locks, and fail-closed customer profile behavior so managed customer channels
can be onboarded through the VPS without local-only browser state, manual SSH
edits, or untracked processes.
