feat(chat): Discord bidirectional driver with attested approvals#1809
Conversation
Connects to Discord via the gateway, dispatches application-command interactions through ``on_command``, decodes Approve/Reject component clicks whose ``custom_id`` encodes ``approve:<id>`` / ``reject:<id>``, debounces per-message edits to one update per second, signs every outbound message with the install's Ed25519 keypair, and appends each approval resolution to the HMAC-chained audit log as a ``chat.discord.approval`` event covering ``(approver, interaction_id, decision, tool_call_hash, worktree_id, partition_id)``. Approvals are pinned both to a worktree and to a channel-scoped scheduling partition: cross-worktree resolutions raise ``CrossWorktreeApprovalError``, cross-partition resolutions raise ``ChannelPartitionMismatchError``, and either failure emits a ``chat.discord.approval_rejected`` audit entry whose details cover both sides of the mismatch. Adds a shared partition helper at ``bernstein.core.orchestration.scheduler_partitions`` (consumed by both the new Discord driver and, going forward, the Slack driver) that owns the canonical ``<platform>:<channel_id>`` label, the alias table for operator-defined channel groupings, and the structured ``PartitionEvent`` audit payload. Optional ``bernstein[discord]`` extra pulls in ``discord.py``; ``start()`` raises ``DiscordDependencyError`` with an install pointer when the SDK is absent. Closes #1795
There was a problem hiding this comment.
Sorry @chernistry, you have reached your weekly rate limit of 2500000 diff characters.
Please try again later or upgrade to continue using Sourcery
|
Caution Review failedPull request was closed or merged during review 📝 WalkthroughWalkthroughThis PR implements a full bidirectional Discord driver with auditable approvals, replacing the previous stub. It introduces channel-scoped partition helpers for scheduler-side dispatch fencing, wires Discord gateway interactions into slash-command and button-callback handlers, enforces worktree pinning and channel-partition boundaries, signs outbound messages with Ed25519, and emits audit-chain entries for all approvals and rejections. Unit tests cover slash dispatch, component interaction decoding, edit debounce, message signing, audit shape, and partition enforcement; an integration test replays the audit chain to verify scheduler state reproducibility. ChangesDiscord Bidirectional Driver & Audit Chain
Sequence Diagram(s)sequenceDiagram
participant User
participant Bot
participant AuditLog
participant Handler
User->>Bot: component interaction (approve)
Bot->>Bot: decode custom_id & validate
Bot->>Bot: enforce partition match
alt partition mismatch
Bot->>AuditLog: emit approval_rejected
Bot->>User: acknowledge
else success
Bot->>AuditLog: emit approval entry
Bot->>Handler: call on_button
Bot->>User: acknowledge
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related issues
Possibly related PRs
Suggested labels
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
Review-bot acknowledgement summary
All must-address findings are resolved or acknowledged. |
Sonar insights (advisory, no merge-block)Snapshot of
Run This comment is a soft signal. The Sonar scan runs on push to |
Dependency ReviewThe following issues were found:
License Issuesuv.lock
OpenSSF Scorecard
Scanned Files
|
|
bernstein doctor observe for PR #1809 ( sonar -- WARN (project bernstein)
code-scanning -- WARN (15 open alert(s))
Skipped backends (credentials not configured)
See docs/observability/unified-doctor.md for backend setup notes. |
Contract drift detected - proposed patchInline autofix push failed ( Three contract tests act as drift detectors against the public CLI / API surface:
One or more failed on this PR. Files changed: How to applyEither run the regen script locally: uv run python scripts/regen_contract_drift.py --fixture all
git add -A && git commit -m "chore(ci): regenerate contract drift allow-lists"
git pushOr apply the patch directly: gh pr checkout 1809
git apply <<'PATCH'
diff --git a/tests/unit/test_readme_api_coverage.py b/tests/unit/test_readme_api_coverage.py
index f492845b..8151f2de 100644
--- a/tests/unit/test_readme_api_coverage.py
+++ b/tests/unit/test_readme_api_coverage.py
@@ -237,6 +237,8 @@ DOCUMENTED_COMMANDS: frozenset[str] = frozenset(
"interop",
# Bot-added: drift autofix (regen_contract_drift.py)
"desktop-register",
+ # Bot-added: drift autofix (regen_contract_drift.py)
+ "schedule",
}
)
PATCH
git add -A && git commit -m "chore(ci): regenerate contract drift allow-lists"
git pushFull diffdiff --git a/tests/unit/test_readme_api_coverage.py b/tests/unit/test_readme_api_coverage.py
index f492845b..8151f2de 100644
--- a/tests/unit/test_readme_api_coverage.py
+++ b/tests/unit/test_readme_api_coverage.py
@@ -237,6 +237,8 @@ DOCUMENTED_COMMANDS: frozenset[str] = frozenset(
"interop",
# Bot-added: drift autofix (regen_contract_drift.py)
"desktop-register",
+ # Bot-added: drift autofix (regen_contract_drift.py)
+ "schedule",
}
)Source CI run: https://github.com/sipyourdrink-ltd/bernstein/actions/runs/26250594928 Refs #1273. |
|
|
||
| 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) |
|
|
||
| def partition_assignments(self) -> dict[str, str]: | ||
| """Return ``tool_call_hash -> partition_id`` for every approved call.""" | ||
| return dict(self._approved_partition_for) |
Summary
Closes #1795.
Implements the bidirectional Discord chat driver to match the Telegram and Slack drivers, plus a shared channel-scoped scheduler partition helper that both Slack and Discord consume.
DiscordBridgeconnects via the gateway, dispatches application-command interactions throughon_command, decodes Approve/Reject component clicks whosecustom_idencodesapprove:<id>/reject:<id>, debounces per-message edits to one update per second, signs every outbound message with the install's Ed25519 keypair, and appends each approval resolution to the HMAC-chained audit log as achat.discord.approvalevent covering(approver, interaction_id, decision, tool_call_hash, worktree_id, partition_id).CrossWorktreeApprovalError, cross-partition resolutions raiseChannelPartitionMismatchError, and either failure emits achat.discord.approval_rejectedaudit entry whose details record both sides of the mismatch.bernstein.core.orchestration.scheduler_partitionsowns the canonical<platform>:<channel_id>label, the alias table for operator-defined channel groupings, and the structuredPartitionEventaudit payload. Designed for reuse across chat drivers.bernstein[discord]extra pulls indiscord.py;start()raisesDiscordDependencyErrorwith an install pointer when the SDK is absent.Files touched
src/bernstein/core/chat/drivers/discord.py(was a stub, now the full driver)src/bernstein/core/orchestration/scheduler_partitions.py(new shared helper)src/bernstein/core/chat/__init__.py(docstring refresh)src/bernstein/cli/commands/chat_cmd.py(Discord token guard, drop unreachable NotImplementedError branch)pyproject.toml(newdiscordextra)docs/operations/chat-bridges.md(Discord setup, channel-partition fence, message verification)CHANGELOG.md(Unreleased entry)tests/unit/core/chat/test_discord_driver.py(new, 15 cases)tests/integration/test_discord_audit_chain_roundtrip.py(new, end-to-end replay)tests/unit/test_scheduler_partitions.py(new, partition helper)tests/unit/test_chat_drivers.py(drop stub assertions superseded by the real driver test module)Acceptance criteria
DiscordBridgeimplementsBridgeProtocolend to end with noNotImplementedErrorremaining.on_commanddispatch from Discord interaction events.push_approvalrenders a button row whosecustom_idencodesapprove:<id>/reject:<id>and routes throughon_buttonwithout extra state.(approver, interaction_id, decision, tool_call_hash, prev_chain_digest); replay over the exported chain reproduces post-approval scheduler state byte-identically.edit_messagedebounces edits per thread under a documented interval to stay inside Discord rate limits (1s, matching Slack).bernstein[discord]extra;start()raisesDiscordDependencyErrorwhen the SDK is absent.Test plan
uv run pytest tests/unit/core/chat/test_discord_driver.py(15 cases)uv run pytest tests/unit/test_scheduler_partitions.py(9 cases)uv run pytest tests/integration/test_discord_audit_chain_roundtrip.py(end-to-end replay)uv run pytest tests/unit/core/chat/ tests/unit/test_chat_drivers.py tests/unit/test_chat_session.py tests/unit/test_handoff_chat.py tests/integration/test_slack_audit_chain_roundtrip.py(regression sweep -- 59 cases green)uv run ruff check . && uv run ruff format .cleanuv run pyright src/bernstein/core/chat/drivers/discord.pycleanSummary by CodeRabbit
Release Notes
New Features
BERNSTEIN_DISCORD_TOKEN).Documentation