# Slack bot token SSOT — slack.json (MC #102830) — 2026-06-03

## Summary
MC #102830 makes `~/system/config/slack.json` the **single source of truth (SSOT)** for the Slack bot's tokens, with environment-variable fallback, and removes the hardcoded tokens from the LaunchAgent plist. Previously the `com.john.slack-bot.plist` hardcoded both `SLACK_BOT_TOKEN` and `SLACK_APP_TOKEN` in `EnvironmentVariables` — so a token rotation that wasn't mirrored into the plist would strand the daemon with a stale token.

## Change
- **slack.json** (`~/system/config/slack.json`, mode 0600): now holds `token` (xoxb bot), `app_token` (xapp), `workspace`, `bot_name`.
- **slack-bot.js** `loadSlackTokens()` (line ~443): reads slack.json **first** (SSOT); returns `{botToken, appToken}` when both present; otherwise silently falls through to the existing **Keychain → vault → env** chain. Env-var fallback preserved.
- **com.john.slack-bot.plist**: `SLACK_BOT_TOKEN` and `SLACK_APP_TOKEN` removed from `EnvironmentVariables` (GROQ/HOME/PATH untouched). `plutil -lint` OK.
- **run-slack-bot-reload.sh**: reload wrapper added under `~/system/tools/`.

## Token rotation procedure (new)
1. Edit `~/system/config/slack.json` — update `token` (xoxb) and/or `app_token` (xapp).
2. `bash ~/system/tools/run-slack-bot-reload.sh`

No plist edit. No risk of stranding the daemon on rotation.

## Verification
- plist: `grep -c SLACK_*_TOKEN` = 0; `plutil -lint` OK.
- slack.json: mode 0600; keys token/app_token/workspace/bot_name.
- slack-bot.js: `node --check` SYNTAX_OK; SSOT branch at line 443.
- Daemon: `launchctl` PID 42749, LastExitStatus 0, stable; log `Tokens loaded from slack.json (SSOT)` + `Slack bot started (Socket Mode)`.
- Live: `slack.js send #ops` succeeded (token valid).
- Independent verifier (Company Mesh / eval-Proveo): **PASS** — `mesh-thr-b04409c5-ab59-4ff1-bc24-b163433bd063`.
- til-done: **DONE** — `/tmp/til-done/102830-20260603T142915Z.json`.

## Security note
This also improves posture: secrets moved out of a (potentially world-readable) LaunchAgent plist into the 0600 slack.json. Token values are never logged (masked).