Skip to content

feat(chat): Slack bidirectional driver with attested approvals#1804

Merged
chernistry merged 1 commit into
mainfrom
feat/1794-slack-driver-attested-approvals
May 21, 2026
Merged

feat(chat): Slack bidirectional driver with attested approvals#1804
chernistry merged 1 commit into
mainfrom
feat/1794-slack-driver-attested-approvals

Conversation

@chernistry

@chernistry chernistry commented May 21, 2026

Copy link
Copy Markdown
Collaborator

Closes #1794.

Summary

  • Replaces the NotImplementedError stub at src/bernstein/core/chat/drivers/slack.py with a Socket Mode driver conforming to BridgeProtocol end to end.
  • Outbound messages carry an Ed25519 detached signature over (install_id, session_id, content_hash); verify_chat_signature lets a recipient with the install's public key confirm authenticity and reject foreign-install forgeries.
  • Each approval resolution lands as a chat.slack.approval entry in the existing HMAC-chained AuditLog; replay over the exported chain reproduces the post-approval scheduler state byte-identically (covered by the integration test).
  • Cross-worktree approvals raise CrossWorktreeApprovalError and emit chat.slack.approval_rejected so the bypass attempt is auditable.
  • edit_message debounces chat.update to one call per channel per second, below Slack's per-channel rate limit.
  • Optional bernstein[slack] extra pulls in slack-sdk; start() raises SlackDependencyError with a pointer to the extra when the SDK is missing.

Files touched

  • src/bernstein/core/chat/drivers/slack.py -- driver implementation, signing helpers, audit-chain wiring.
  • src/bernstein/cli/commands/chat_cmd.py -- two-token bridge construction for Slack (BERNSTEIN_SLACK_TOKEN + BERNSTEIN_SLACK_APP_TOKEN).
  • pyproject.toml, uv.lock -- new bernstein[slack] extra (slack-sdk>=3.27, aiohttp>=3.9).
  • tests/unit/core/chat/test_slack_driver.py -- 14 unit tests covering slash dispatch, button decode, edit-debounce, missing-SDK error path, audit-chain entry shape, cross-worktree resolution rejection, signature verification (including foreign-install rejection).
  • tests/integration/test_slack_audit_chain_roundtrip.py -- replays a real HMAC-chained AuditLog after Slack approvals and asserts the reconstructed scheduler state matches the live state byte-for-byte.
  • tests/unit/test_chat_drivers.py -- drops the stub-message assertions for Slack now that the driver is live.
  • docs/operations/chat-bridges.md -- new operator-facing doc covering Telegram + Slack setup, env vars, signed-envelope verification, and missing-SDK behaviour.
  • CHANGELOG.md -- documents the new driver + docs under [Unreleased].

Acceptance criteria

All seven AC bullets from #1794 are covered:

  1. BridgeProtocol implemented end to end -- no NotImplementedError remains.
  2. Each approval lands as a signed chat.slack.approval entry whose digest covers (approver, message_ts, decision, tool_call_hash, prev_chain_digest); replay reproduces post-approval state byte-identically.
  3. Approvals are worktree-pinned -- /approve from wt-a refuses pending approvals on wt-b.
  4. Outbound messages carry an Ed25519 detached signature; verifier rejects messages signed by a different install.
  5. edit_message debounces to EDIT_THROTTLE_S = 1.0s per channel.
  6. Optional bernstein[slack] extra wired; start() raises a structured SlackDependencyError when slack-sdk is absent.
  7. Tests cover slash dispatch, button decode, edit-debounce, SDK-missing error path, audit-chain entry shape, cross-worktree resolution rejection, and signature verification.

Test plan

  • uv run pytest tests/unit/core/chat/ -q --no-cov --timeout=120 -- 14 passed.
  • uv run pytest tests/integration/test_slack_audit_chain_roundtrip.py -q --no-cov --timeout=120 -- 1 passed.
  • uv run ruff check . -- all checks passed.
  • uv run ruff format . -- all files left unchanged (formatted on commit).
  • uv run pyright src/bernstein/core/chat/drivers/slack.py -- 0 errors.
  • Wider regression sweep across tests/unit/core/, tests/unit/test_chat_*.py, tests/unit/test_audit*.py, tests/unit/test_handoff_chat.py, tests/unit/notifications/test_sinks_shell_telegram.py -- 120 passed.

Summary by CodeRabbit

  • New Features

    • Slack chat bridge is now fully operational with Socket Mode support, message editing capabilities, approval workflows, audit logging, and cross-worktree protection to prevent unauthorized approvals.
  • Documentation

    • Added comprehensive documentation for Slack chat bridge configuration and operations, including Socket Mode setup and required environment variables.

Review Change Stack

Replaces the NotImplementedError stub in
``src/bernstein/core/chat/drivers/slack.py`` with a Socket Mode driver
that conforms to ``BridgeProtocol`` end to end: slash dispatch, button
decode, edit debounce, signed outbound messages, worktree-pinned
approvals, and chained audit-log entries.

* Outbound messages carry an Ed25519 detached signature over
  ``(install_id, session_id, content_hash)``; ``verify_chat_signature``
  lets a recipient with the install's public key confirm authenticity
  and reject foreign-install forgeries.
* Each Slack approval resolution lands as a ``chat.slack.approval``
  event in the existing HMAC-chained ``AuditLog``; replay over the
  exported chain reproduces the post-approval scheduler state
  byte-identically (covered by the new integration test).
* Cross-worktree approvals raise ``CrossWorktreeApprovalError`` and
  log a ``chat.slack.approval_rejected`` entry so the bypass attempt is
  visible to the operator.
* ``edit_message`` debounces ``chat.update`` to one call per channel
  per second, comfortably below Slack's per-channel rate limit.
* Optional ``bernstein[slack]`` extra pulls in ``slack-sdk``; the
  ``start()`` path raises ``SlackDependencyError`` with a pointer to
  the extra when the SDK is absent.

CLI integration: ``bernstein chat serve --platform=slack`` reads the
bot token from ``BERNSTEIN_SLACK_TOKEN`` and the Socket Mode app token
from ``BERNSTEIN_SLACK_APP_TOKEN``. Docs added at
``docs/operations/chat-bridges.md``.

Closes #1794

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry @chernistry, you have reached your weekly rate limit of 2500000 diff characters.

Please try again later or upgrade to continue using Sourcery

@chernistry chernistry enabled auto-merge (squash) May 21, 2026 19:46
@github-actions

Copy link
Copy Markdown
Contributor

Sonar insights (advisory, no merge-block)

Snapshot of bernstein on the configured Sonar instance:

Metric Value
Coverage 13.5
Code smells 123
Bugs 10
Vulnerabilities 2
Security hotspots 87

Run bernstein doctor sonar locally for the full surface.

This comment is a soft signal. The Sonar scan runs on push to main; the PR check itself never fails on smells.

@github-actions github-actions Bot added dependencies Pull requests that update a dependency file core cli docs tests labels May 21, 2026
@github-actions

Copy link
Copy Markdown
Contributor

Review-bot acknowledgement summary

  • Must-address findings: 0 (0 acknowledged, 0 open)
  • Informational findings: 0

All must-address findings are resolved or acknowledged.

@coderabbitai

coderabbitai Bot commented May 21, 2026

Copy link
Copy Markdown

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: c4e4c11c-4dd1-4eac-aee0-5503810033f9

📥 Commits

Reviewing files that changed from the base of the PR and between 4e221e6 and e0ead85.

⛔ Files ignored due to path filters (2)
  • CHANGELOG.md is excluded by !CHANGELOG.md
  • uv.lock is excluded by !**/*.lock, !**/*.lock
📒 Files selected for processing (8)
  • docs/operations/chat-bridges.md
  • pyproject.toml
  • src/bernstein/cli/commands/chat_cmd.py
  • src/bernstein/core/chat/drivers/slack.py
  • tests/integration/test_slack_audit_chain_roundtrip.py
  • tests/unit/core/chat/__init__.py
  • tests/unit/core/chat/test_slack_driver.py
  • tests/unit/test_chat_drivers.py

📝 Walkthrough

Walkthrough

This PR implements a complete Slack Socket Mode bidirectional driver with attested approvals, replacing the stub. The driver supports slash commands, approval routing with cross-worktree enforcement, edit debouncing, HMAC-chained audit logging, and Ed25519 message signing. CLI and optional dependency changes enable app-token support. Comprehensive unit and integration tests verify audit-chain reproducibility and security invariants.

Changes

Slack bidirectional driver with attested approvals

Layer / File(s) Summary
CLI bridge instantiation and optional dependency
src/bernstein/cli/commands/chat_cmd.py, pyproject.toml
chat serve command routes bridge instantiation through _instantiate_bridge() helper, which reads Socket Mode app token from environment for Slack; other platforms use bot token only. Slack optional dependency extra declares slack-sdk>=3.27 and aiohttp>=3.9.
Slack driver types and contracts
src/bernstein/core/chat/drivers/slack.py
Module docstring, imports, and __all__ exports are updated. New exception types SlackDependencyError and CrossWorktreeApprovalError defined. _EditState and PendingApprovalRecord dataclasses establish contracts for edit throttling and approval bookkeeping.
SlackBridge lifecycle and wiring
src/bernstein/core/chat/drivers/slack.py
SlackBridge constructor takes bot and app tokens, expanded with pending approvals map, approved tool-call set, edit throttle state, and lazy keypair storage. Handler registration methods (on_command, on_button, register_pending_approval) wire callbacks. start() lazily imports SDK modules, creates Web/Socket clients, and connects; stop() cancels edits and disconnects.
Outbound message operations
src/bernstein/core/chat/drivers/slack.py
send_message() posts signed metadata envelope. edit_message() debounces rapid edits per thread using async lock and deferred flush. push_approval() renders Block Kit approve/reject actions with approval IDs and posts with signature. _build_signed_metadata() computes content hash, canonicalizes bytes, signs with Ed25519, returns envelope. _ensure_keypair() lazily loads or generates keypair.
Inbound Socket Mode dispatch
src/bernstein/core/chat/drivers/slack.py
_handle_socket_mode_request() routes envelopes by type. _dispatch_slash_command() tokenizes payload, selects handler, constructs ChatMessage. _dispatch_block_action() decodes approve/reject from interactive payload, resolves pending approval, enforces worktree pinning (raises CrossWorktreeApprovalError on mismatch), records decision, invokes handler. _ack() acknowledges envelopes defensively.
Approval recording with audit log
src/bernstein/core/chat/drivers/slack.py
_record_resolved_approval() updates approved hash set and appends chained audit entry with event type chat.slack.approval. _record_rejected_approval() appends rejection entry for cross-worktree attempts with reason and worktree context. Both maintain HMAC chain integrity.
Signature verification and optional imports
src/bernstein/core/chat/drivers/slack.py
Public verify_chat_signature() loads PEM key, base64-decodes signature, recomputes content hash and canonical bytes, verifies Ed25519 without raising on mismatch. _canonical_attestation_bytes() produces stable signing bytes. Optional-import helpers raise SlackDependencyError with guidance when slack-sdk absent.
Unit tests
tests/unit/core/chat/test_slack_driver.py
Fake slack_sdk harness injected into sys.modules. Tests cover constructor validation, runtime SlackDependencyError, slash-command dispatch, block-action decode/route, push_approval rendering, edit debounce collapse, message signature attestation, signature verification success/rejection, audit-chain entry shape and HMAC validation, cross-worktree security, and envelope JSON roundtripping. fake_slack fixture and envelope builder helpers provided.
Integration audit-chain test
tests/integration/test_slack_audit_chain_roundtrip.py
Stubs slack_sdk, drives SlackBridge through three block actions (approve/reject/approve), captures live approved hash set, stops bridge, re-verifies on-disk HMAC chain, reconstructs approved set by scanning JSONL audit entries for chat.slack.approval events, asserts exact set equality and expected hashes. Demonstrates byte-for-byte audit-chain reproducibility.
Operations documentation
docs/operations/chat-bridges.md
New documentation page describes supported platforms, Slack Socket Mode setup (configuration, environment variables), driver guarantees (dispatch, edit debounce, audit logging, worktree pinning, Ed25519 signing), verify_chat_signature example, and SlackDependencyError guidance.
Test cleanup
tests/unit/test_chat_drivers.py
Module docstring updated to remove Slack stub references. Slack-specific imports removed. Stub section renamed from "Discord / Slack stubs" to "Discord stub". Slack stub test cases deleted. Helper function position adjusted.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related issues

  • sipyourdrink-ltd/bernstein#1794: Primary linked issue describing the Slack bidirectional driver with attested approvals requirement that this PR fully implements.
  • sipyourdrink-ltd/bernstein#1795: Discord driver issue that calls for similar audit-chain/attested-approval patterns and CLI bridge-instantiation changes that this PR's approach demonstrates.

Possibly related PRs

  • sipyourdrink-ltd/bernstein#1751: Prior PR modified SlackBridge registration to fail-fast on stubs; this PR replaces the stub with a full implementation, making that prior change no longer applicable.

Suggested labels

core, docs, tests

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/1794-slack-driver-attested-approvals

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions

Copy link
Copy Markdown
Contributor

bernstein doctor observe for PR #1804 (feat/1794-slack-driver-attested-approvals): ok=0, warn=2, fail=0, error=0, skipped=2

sonar -- WARN (project bernstein)

metric value delta threshold status
coverage_pct 13.5% new 80.0% fail
code_smells 123 new 50 warn
bugs 10 new 0 fail
vulnerabilities 2 new 0 warn
security_hotspots 87 new 0 fail

code-scanning -- WARN (4 open alert(s))

metric value delta threshold status
open_alerts 4 new 0 warn
critical_alerts 0 new 0 ok
high_alerts 2 new 0 warn
medium_alerts 0 new - ok
low_alerts 0 new - ok
Skipped backends (credentials not configured)
  • glitchtip: BERNSTEIN_GLITCHTIP_TOKEN not set
  • dt: DTRACK_URL/TOKEN/PROJECT not set

See docs/observability/unified-doctor.md for backend setup notes.

@chernistry chernistry merged commit fe6a831 into main May 21, 2026
61 of 64 checks passed
@chernistry chernistry deleted the feat/1794-slack-driver-attested-approvals branch May 21, 2026 19:47

def approved_tool_call_hashes(self) -> set[str]:
"""Return a snapshot of every tool-call hash that has been approved."""
return set(self._approved_tool_call_hashes)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

cli core dependencies Pull requests that update a dependency file docs size/xl tests

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Slack bidirectional driver with attested approvals

2 participants