# Profile Env Contract

Customer Hermes profiles that use native Slack gateway mode must have their own
Slack app token pair and a fail-closed channel allowlist.

Never source these keys from the production Sellable Reply Bot app. That app is
part of the Sellable product and must keep Socket Mode off so Slack buttons,
modals, and approval flows continue to hit the HTTP interactivity endpoint at
`https://app.sellable.dev/api/v2/slack/interactions`.

Customer profiles inherit only the current `openai-codex` model object from the
Hermes VPS `sellable-admin` profile. OAuth stays in the single Hermes root auth
store and customer profiles use root fallback. Do not copy `auth.json`,
`OPENROUTER_API_KEY`, `OPENAI_API_KEY`, arbitrary LLM provider env, Slack
provisioning credentials, or Sellable Admin MCP config into customer profiles.

## Required Profile Files

Preferred profile env target:

```text
/opt/data/profiles/<customerSlug>/.env
```

The file must be owned by the Hermes runtime user and mode `0600`.

Preferred profile model target:

```text
/opt/data/profiles/<customerSlug>/config.yaml
```

For native Hermes replies, `config.yaml` should include an `openai-codex` model
selection inherited from `sellable-admin` when the customer profile lacks one.
This is not an Admin MCP install.

Forbidden profile auth target:

```text
/opt/data/profiles/<customerSlug>/auth.json
```

This file must not exist. Bootstrap validates `/opt/data/auth.json` as the
mode-`0600` Hermes root auth store with exactly one `openai-codex` OAuth record,
writes the current model config, and records `sourceClass=root_fallback` plus
`localAuthEntryCount=0`.

It must also include a Slack platform toolset allowlist. Customer profiles are
allowed to use profile-local MCP servers, but they must not expose generic
local/system tools that can inspect sibling profiles or modify skills:

```yaml
platform_toolsets:
  slack:
    - clarify
    - image_gen
    - kanban
    - memory
    - todo
    - tts
    - vision
    - web
```

This allowlist intentionally excludes `browser`, `terminal`, `file`,
`code_execution`, `skills`, `session_search`, `delegation`, `cronjob`, and
`computer_use`. The `sellable-admin` profile is the only profile that should
retain those tools for Slack-driven provisioning or repair.

Preferred saved manifest target:

```text
/opt/data/profiles/<customerSlug>/slack-manifest.json
```

Preferred non-secret app metadata target:

```text
/opt/data/profiles/<customerSlug>/slack-app.json
```

Preferred profile-local Slack app icon target:

```text
/opt/data/profiles/<customerSlug>/slack-icon.png
```

The icon must be the canonical 512x512 black Sellable mark from the
`hermes-slack-native-app` skill assets or the bundled Admin installer assets.
Slack app manifests do not preserve the binary icon in profile metadata, so the
installer stores the PNG and records its hash/dimensions in `slack-app.json`.

## Required Slack Keys

```env
SLACK_BOT_TOKEN=<profile-local xoxb token>
SLACK_APP_TOKEN=<profile-local xapp token>
SLACK_HOME_CHANNEL=<target Slack channel id>
SLACK_HOME_CHANNEL_NAME=<target Slack channel name>
SLACK_ALLOWED_CHANNELS=<target Slack channel id>
SLACK_ALLOWED_USERS=<operator Slack user ids>
SLACK_REQUIRE_MENTION=true
```

The same `.env` must not contain `OPENROUTER_API_KEY`, `OPENAI_API_KEY`, or
other raw LLM provider keys. Customer profiles use Hermes root Codex OAuth
fallback, not local auth or provider API keys. Receipts and command summaries
must show only key names and fingerprints for allowed Slack tokens, not raw
values.

For existing-profile repairs, the installer may read the current
`SLACK_BOT_TOKEN`, `SLACK_APP_TOKEN`, and `SLACK_HOME_CHANNEL` from this same
profile-local `.env` and rewrite the file without requiring token arguments.
That repair mode is the preferred way to remove stale provider API-key lines
without putting Slack secrets on the command line.

`SLACK_ALLOWED_CHANNELS` must contain Slack ids, not names or wildcards. For a
single customer channel, it should exactly match `SLACK_HOME_CHANNEL`.

## Optional Profile Keys

```env
SLACK_BOT_USER_ID=<bot user id from auth.test>
SLACK_TEAM_ID=<workspace/team id from auth.test>
SLACK_APP_ID=<Slack app id>
SLACK_TOKEN_FINGERPRINT=<short fingerprint, not a token>
```

## Admin Provisioning 2FA

Slack workspace 2FA belongs to the provisioning account, not to a customer
Hermes profile. The scalable path is authenticator-app TOTP for
`admin@dittto.ai`, with the raw seed stored only in secret storage.

Current secret references:

```env
SLACK_PROVISIONING_2FA_MODE=totp
SLACK_PROVISIONING_2FA_ACCOUNT=admin@dittto.ai
SLACK_PROVISIONING_2FA_SECRET_PATH=/opt/data/profiles/sellable-admin/secrets/slack-provisioning-totp.seed
SLACK_PROVISIONING_2FA_KEYCHAIN_SERVICE=sellable-admin-slack-provisioning-totp
SLACK_PROVISIONING_2FA_KEYCHAIN_ACCOUNT=admin@dittto.ai
```

The VPS profile secret file and local Keychain item contain the same TOTP seed.
Sellable Admin `settings.json` should store only lookup metadata such as mode,
account, keychain service/account, and secret path. Do not copy the raw seed
into customer profiles, planning docs, receipts, chat, or committed files.

The VPS adapter resolves this secret reference and computes one-time TOTP codes
internally. Missing or invalid secret material is an implementation/config
defect to repair inside `sellable-admin`; it is never converted into an
operator code-copy step. Do not make iMessage, SMS forwarding, or local Chrome
state a product dependency.

## Saved Manifest Metadata

`slack-app.json` should be non-secret and safe to include in a redacted config
bundle:

```json
{
  "customerSlug": "acme",
  "hermesProfile": "acme",
  "slackAppName": "Sellable Agent - acme",
  "slackBotName": "Sellable Agent",
  "slackAppId": "A0...",
  "slackBotUserId": "U0...",
  "slackTeamId": "T0...",
  "slackHomeChannel": "C0...",
  "slackHomeChannelName": "sellable-acme",
  "manifestPath": "/opt/data/profiles/acme/slack-manifest.json",
  "manifestSha256": "...",
  "slackIconPath": "/opt/data/profiles/acme/slack-icon.png",
  "slackIconSha256": "...",
  "slackIconWidth": 512,
  "slackIconHeight": 512,
  "slackPlatformToolsets": {
    "platform": "slack",
    "mode": "allowlist",
    "allowedToolsets": ["clarify", "image_gen", "kanban", "memory", "todo", "tts", "vision", "web"],
    "blockedGenericToolsets": ["browser", "terminal", "file", "code_execution", "skills", "session_search", "delegation", "cronjob", "computer_use"]
  },
  "botTokenFingerprint": "...",
  "appTokenFingerprint": "...",
  "createdByProfile": "sellable-admin",
  "createdAt": "2026-07-06T00:00:00.000Z"
}
```

The saved manifest plus `manifestSha256` is the durable proof of what was pasted
into Slack. If the Slack app is edited later, save a new manifest and hash before
accepting the change.

## Isolation Requirements

- A profile-local token pair may be used by exactly one active Hermes profile
  gateway.
- The Sellable Admin Hermes profile bootstrap must compute short fingerprints
  for `SLACK_BOT_TOKEN` and `SLACK_APP_TOKEN` from sibling profile env files and
  reject any match before writing gateway/dashboard startup files.
- A customer profile's Slack app must be a dedicated `Sellable Agent -
  <customerSlug>` app created from the saved Hermes manifest, not the production
  Sellable Reply Bot app.
- Slack provisioning-account 2FA material stays in the `sellable-admin` profile
  secret storage or local Keychain and is never copied into customer profiles.
- Customer profiles inherit the `openai-codex` model selection and use the
  Hermes root OAuth fallback. They must not contain local `auth.json`,
  `OPENROUTER_API_KEY`, `OPENAI_API_KEY`, or any other provider secret.
- Customer profiles must restrict Slack `platform_toolsets` to the approved
  allowlist above. They must not expose terminal, file, code execution,
  skill-writing, session-search, delegation, cron, browser automation, or
  computer-use tools on Slack.
- The bot must be invited only to the approved customer channel during UAT.
- The customer profile must use workspace-scoped Sellable MCP credentials and
  the Hermes MCP server env must set
  `SELLABLE_LOCK_WORKSPACE_ID=<customerWorkspaceId>` for the approved customer
  workspace. The plain Sellable MCP must reject workspace switching, workspace
  creation, and requests for any other workspace while this lock is set.
- The customer profile must not have Sellable Admin MCP credentials.
- The customer profile must not inspect `/opt/data/profiles/sellable-admin`,
  sibling customer profiles, or any Sellable Admin config. Cross-profile
  inspection and repair are `sellable-admin` profile duties only.
- Prompt text must never override the profile selected by the running gateway.

## Redaction Rules

Receipts, chat, docs, screenshots, and commits may include:

- app id
- bot user id
- Slack team id
- Slack channel id/name
- token fingerprint
- manifest hash

They must not include full tokens matching:

- `xoxb-...`
- `xapp-...`
- `xoxe-...`
- browser/cloud API keys
- Gmail OAuth refresh tokens

When recording proof, write only the first and last few token characters through
a deliberate fingerprint function, never by slicing a raw token into logs.
