---
name: hermes-slack-native-app
description: Bootstrap or repair one dedicated customer-safe Slack app, isolated Hermes profile, workspace-locked Sellable MCP, and native gateway through the single resumable Sellable Admin customer bootstrap command.
---

# Hermes Slack Native App

This factory sits behind the Sellable Admin lifecycle, the sole operator-facing interface.
It creates one native Slack app and one installation per customer profile.
There is no shared listener, shared route registry, or
fallback bot.

The human approves only the immutable onboarding plan. Every technical step
after approval is owned by the VPS `sellable-admin` Hermes profile. It uses a
stable Browserbase direct CDP session, signs into Slack Developer Console by email,
retrieves the newest Slack email or magic link through Gmail/GWS internally,
and satisfies workspace TOTP internally. Routine login, CAPTCHA, install,
token, icon, restart, and rebuild work must finish with
`userActionRequired=false`.

The Sellable Admin lifecycle boundary supplies that authority from one human
authorization and an opaque lifecycle ID. It derives and validates private
child records for apply, resume, rebuild, rollback, and disable. Operators never
copy child identities or record paths. Plan, status, and verify remain read-only
and autonomous.

The production Sellable Reply Bot is product infrastructure, not a Hermes customer bot.
Do not enable Socket Mode on the production Sellable Reply Bot app.
Do not reuse shared Sellable Reply Bot tokens. Its Interactivity & Shortcuts request URL
must remain:

```text
https://app.sellable.dev/api/v2/slack/interactions
```

Only the `sellable-admin` Hermes profile may provision, inspect, rebuild, or
repair customer profiles. Customer profiles are blank, isolated profiles with a
plain Sellable MCP locked by `SELLABLE_LOCK_WORKSPACE_ID`.
They inherit the current `gpt-5.6-sol` model object and use the single Hermes
root `openai-codex` OAuth record through root fallback. Customer-local
`auth.json`, provider pools, OpenRouter, and raw OpenAI API keys are forbidden.

## One Internal Factory Command

The lifecycle broker owns this internal interface:

```bash
sellable-admin-install hermes customer bootstrap --mode <mode> [lifecycle-derived inputs]
```

Supported modes are `plan`, `apply`, `resume`, `status`, `verify`, `rebuild`,
and `rollback`.

- `plan` is read-only. It returns the immutable input identity, run id, exact
  create/reuse actions, and stage graph to the lifecycle broker.
- `apply` receives complete lifecycle-derived authority internally.
- `resume`, `status`, `verify`, `rebuild`, and `rollback` require `--run-id`.
- Never expose or ask the operator to coordinate internal Browserbase CDP,
  Slack API, GWS/Gmail, profile-installer, Sellable
  installer, or Hermes gateway subcommands.
- Slack Developer Console provisioning always runs through the autonomous VPS
  Browserbase CDP agent. There is no secondary provisioning authorization branch.

Internal plan shape:

```bash
sellable-admin-install hermes customer bootstrap \
  --mode plan \
  --customer-slug <customerSlug> \
  --company <company> \
  --team-id <slackTeamId> \
  --workspace-mode <create|reuse> \
  --workspace-id <workspaceId-if-reuse> \
  --workspace-name <workspaceName-if-create> \
  --channel-mode <create|reuse> \
  --channel-id <channelId-if-reuse> \
  --channel-name <channelName-if-create> \
  --allowed-slack-user-ids <U123,U456> \
  --sellable-token-file <mode-0600-customer-token-file> \
  --model gpt-5.6-sol \
  --evidence-target <redacted-evidence-directory> \
  --profiles-root /opt/data/profiles \
  --json
```

After lifecycle authorization, the broker runs apply. For a resumable blocker,
it preserves the returned `runId`, resolves only the stated `requiredAction`,
and uses `--mode resume --run-id <runId>` without an operator handoff.

Apply/resume/rebuild also binds the exact tested Admin install, Admin MCP,
Sellable install, and Sellable MCP tarballs plus their SHA-256 values. `latest`
is not an accepted release-candidate spec.

## Inputs And Fail-Closed Rules

The lifecycle-derived factory input must bind all of these values:

- approved customer slug and company
- exact Slack team id
- Sellable workspace create/reuse mode plus exact id or name
- Slack channel create/reuse mode plus exact id or name
- customer Hermes profile, which must equal the customer slug
- explicit `SLACK_ALLOWED_CHANNELS` and `SLACK_ALLOWED_USERS`
- customer-scoped Sellable token file reference
- expected `openai-codex` model, currently `gpt-5.6-sol`
- exact four-package release-candidate specs and SHA-256 values
- redacted evidence target

Empty or wildcard channel/user values fail. Identity drift creates a new plan;
it never mutates an existing run. Do not accept raw Slack, Sellable, Browser,
Gmail, authenticator-app TOTP, or provider secrets in argv, chat, receipts, or
state. The Slack provisioning TOTP reference is
`sellable-admin-slack-provisioning-totp`; only the internal stage may resolve
it. Do not use local Chrome.
Do not make iMessage or SMS forwarding a product dependency.

## Internal Stage Graph

The packaged command owns these implementation stages:

1. validate immutable identity and exclusive per-customer run ownership
2. create or adopt the dedicated Slack app from the Hermes manifest
3. link/install the app and capture secret file references
4. create or adopt the exact Sellable workspace
5. create the blank isolated customer profile
6. install plain Sellable MCP and prove its workspace lock
7. start exactly one native Hermes gateway for the profile
8. run live positive, negative, restart, and isolation verification

After stage 7 the factory deliberately returns a ready profile with
`live_verification` pending. This is the handoff to the owning Hermes agent,
not a reason to resume or replay stages 2–7. Hermes reuses the same Browserbase
context to perform stage 8, submits the redacted measurements through lifecycle
composition `verify`, and requires factory status `verified` before calling the
lifecycle successful.
The lifecycle projects this state publicly as
`factory.status: live_verification_pending` and
`factory.readyForLiveVerification: true`, with redacted stage/context fields.
Those fields, not the internal ledger's base `approved` status, decide whether
Hermes proceeds to stage 8.

Before Slack app create/adopt/install, manifest authority comes from the
installed Hermes CLI for the target or an owned temporary target profile:

```bash
HERMES_HOME=/opt/data hermes -p <target-or-temp-profile> slack manifest \
  --write <mode-0600-source-path> --name <customer-app-name> \
  --description <customer-app-description>
```

Do not hand-author the source scopes. Record the generated source SHA-256, then
derive the customer-safe manifest deterministically: remove all slash commands
and `commands`, App Home/assistant, DM/MPIM scopes and events; retain required
native public/private-channel behavior, users/files scopes, and Socket Mode.
Record derivative SHA-256 plus every removed surface. Export the adopted app
manifest after reconciliation and require it to match the derivative before
install. The app-level runtime token remains `connections:write` only.

Slack internals use `auth.test`, `apps.connections.open`, app manifest APIs,
Browserbase direct CDP, Sign In With Email, `admin@dittto.ai`, and GWS/Gmail.
Those are stage implementation details, never separate operator commands.
Browserbase direct CDP is the authoritative production Slack provisioning
adapter; alternate browser harnesses cannot satisfy an acceptance row.
The browser adapter uses Gmail/GWS internally and resolves the provisioning
account TOTP internally. It reconciles the exact app/team/install before every
resume and records only redacted stage receipts.

## Resumption, Reconciliation, And Rollback

State lives under:

```text
/opt/data/profiles/sellable-admin/runs/hermes-customer-bootstrap/<runId>/state.json
```

Before skipping any completed stage, the command must re-read Slack team/app/
install state, secret-file modes and fingerprints, profile hashes, MCP lock,
and gateway ownership. A stale receipt is not proof. External drift returns a
typed `external_state_drift` blocker and requires an approved repair plan.

Every blocker has `code`, `retryClass`, `stage`, `requiredAction`, and
`resumePrecondition`. Routine CAPTCHA, email-code, authenticator-app TOTP, and
expired authorization states are handled inside the adapter. Typed blockers
are reserved for external provider outages or Slack policy denial after
autonomous recovery is exhausted. Team, app,
workspace, profile, allowed-user, or channel identity mismatch is terminal
until a new approved plan exists.

Rollback deletes only run-owned resources. It preserves adopted apps,
workspaces, channels, and profiles. Unverified cleanup of a run-owned Slack app
returns `orphan_app_cleanup_required`; it is never silently declared complete.
For a run-owned customer profile, rollback success also proves that the profile
root, s6 definition, runtime link, supervisor, and live run directory are all
absent. The final ledger is retained only as a mode-0600 tombstone under the
rollback-receipts root. Repeating rollback against that tombstone is idempotent.

## Verification Gate

Do not report live-ready until all of the following are present in redacted
receipts:

- dedicated app/team/channel/profile identity and manifest hash
- app name and bot display name both equal `Sellable Agent - <customerSlug>`
- mode-0600 bot/app token file fingerprints, never token values
- `auth.test` and `apps.connections.open` prove the expected dedicated app
- direct MCP SDK stdio probe proves `get_auth_status`,
  `get_active_workspace`, and fail-closed `list_workspaces`
- root OAuth proof shows exactly `openai-codex`, `sourceClass=root_fallback`,
  and zero customer-local auth entries
- the plain Sellable MCP exposes only the locked customer workspace
- dangerous customer Slack `platform_toolsets` remain disabled, including
  `terminal`, `code_execution`, and `session_search`
- exactly one native Hermes gateway owns the enabled profile
- allowed human mention receives one correct reply in the allowed channel
- disallowed channel, disallowed user, bot-origin, and prompt-injected profile
  switching produce no cross-profile action
- production Sellable Reply Bot remains Socket Mode off and its HTTP approval
  button/modal contract remains green

Use `references/profile-env-contract.md` for the stored profile contract and
`references/phase-03-1-live-lessons.md` for redacted historical lessons. These
references explain verification expectations; they do not add public commands.
