Skip to content

feat(daemon): Slack runtime control plane (roadmap-v0.6 E.3)#88

Merged
RNT56 merged 3 commits into
mainfrom
claude/v06-e3-slack
Jun 29, 2026
Merged

feat(daemon): Slack runtime control plane (roadmap-v0.6 E.3)#88
RNT56 merged 3 commits into
mainfrom
claude/v06-e3-slack

Conversation

@RNT56

@RNT56 RNT56 commented Jun 29, 2026

Copy link
Copy Markdown
Owner

Summary

Roadmap-v0.6 E.3 — Slack alongside Telegram + the cockpit, mirroring the allowlist, redaction, and approval-nonce model, feeding the same RuntimeEvent stream. New module crustcore_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

  • CI core (5 tests): empty allowlist denies all; allowed/blocked channel + unknown workspace; plain/steer/command normalization; an unparseable reaction nonce → no event (never a spurious approval); outbound redaction enforced + bounded.
  • 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 verifygreen (runbook-check: 29 ignored seams + 20 tags). Nano unchanged at 53.5% (daemon-only). Independent off main.

With this, E.1 (cockpit) is the only remaining roadmap-v0.6 task.

🤖 Generated with Claude Code

RNT56 and others added 3 commits June 29, 2026 11:34
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>
@RNT56 RNT56 merged commit 8c8d4cb into main Jun 29, 2026
1 of 3 checks passed
@RNT56 RNT56 deleted the claude/v06-e3-slack branch June 29, 2026 21:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant