feat(daemon): Slack runtime control plane (roadmap-v0.6 E.3)#88
Merged
Conversation
Adds crustcore_daemon::slack: a pure SlackAllowlist (per-workspace/channel, deny-all empty — invariants 5, 15), normalize_message(msg, allowlist) -> Option<RuntimeEvent> mapping a Slack event onto the SAME RuntimeEvent stream as Telegram (plain -> QueuedTurn, ! -> Steer, / -> Command, reaction -> ApprovalCallback with the same nonce format) so Slack routes through the same policy gates — not a parallel ungoverned surface (invariants 8, 16) — and render_to_slack which redacts every secret before any message leaves (invariants 1-3). Text untrusted + bounded (7, 11); approvals from Slack users gated by the allowlist, never model output (4); opt-in, operator-bound via CLI (15). The Slack API + Events/Socket-Mode listener is the live #[ignore]d slack_live_round_trip_smoke (TODO(slack-live)), in runbook F.7. 5 new tests; daemon-only; zero nano impact. `cargo xtask verify` green. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Adds slack::SlackSignature — the "is this really from Slack" gate that runs
before normalize_message ever sees the body. Verifies the Slack v0 signature
(HMAC-SHA256 over "v0:{timestamp}:{body}", constant-time compare) AND
timestamp freshness (|now - ts| <= 300s) as a replay defense, mirroring the
GitHub webhook hardening. A forged, tampered, or stale request never forms a
SlackMessage, so it can never reach dispatch (invariants 7, 8, 16). The
signing secret is held only to compute the HMAC — never serialized, logged,
or returned. Std-only (vendored hmac_sha256); the only live inch left is the
Events-API/Socket-Mode HTTP listener that delivers a verified request.
5 new CI tests (genuine/forged/stale/forward-dated/tampered/malformed);
crustcore-daemon (non-nano); zero nano impact. `cargo xtask verify` green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Roadmap-v0.6 E.3 — Slack alongside Telegram + the cockpit, mirroring the allowlist, redaction, and approval-nonce model, feeding the same
RuntimeEventstream. New modulecrustcore_daemon::slack(pure core):SlackAllowlist— per-workspace/per-channel, deny-all when empty (nested so an id collision across workspaces can't leak).normalize_message(msg, allowlist) → Option<RuntimeEvent>— denied →None; plain →QueuedTurn;!→Steer;/→Command; reaction →ApprovalCallback(same nonce). The same dispatch as Telegram — not a parallel ungoverned surface (invariants 8, 16).render_to_slack(text, redactor)— redacts every secret before the message leaves (invariants 1–3), bounded.CI core vs live seam
live,#[ignore],TODO(slack-live)): the Slack Bot API + Events-API/Socket-Mode listener —slack_live_round_trip_smoke, runbook §F.7.Trust boundary
Slack is opt-in, operator-bound via CLI, never the default (invariant 15); message text is untrusted + bounded (invariants 7, 11); approvals come from Slack users gated by the allowlist, never model output (invariants 4, 5); only redacted text reaches Slack (invariants 1–3). Routes through the existing policy gates (invariants 8, 16).
Tests run
cargo xtask verify— green (runbook-check: 29 ignored seams + 20 tags). Nano unchanged at 53.5% (daemon-only). Independent offmain.With this, E.1 (cockpit) is the only remaining roadmap-v0.6 task.
🤖 Generated with Claude Code