feat(slack): Slack-button approvals for Cedar HITL gates (#112)#316
Closed
flamingquaks wants to merge 1 commit into
Closed
feat(slack): Slack-button approvals for Cedar HITL gates (#112)#316flamingquaks wants to merge 1 commit into
flamingquaks wants to merge 1 commit into
Conversation
…#112) Closes the HITL loop end-to-end: when a Cedar approval gate fires on a Slack-origin task, the thread gets an 🔒 Approval required message with ✅ Approve / ❌ Deny buttons. Clicking records the same atomic decision the CLI's `bgagent approve`/`deny` would. Shared decision core: - New `shared/approval-decision.ts` extracts the trust-critical approve/deny logic (per-user rate limit, ownership-guarded PENDING-only TransactWriteItems across approvals+task tables, approval_decision_recorded audit) into `processApprovalDecision`. - `approve-task.ts` / `deny-task.ts` refactored onto the core (HTTP status mapping unchanged — existing handler tests pass untouched). Slack rendering + routing: - `slack-blocks.ts` renders `approval_requested` (tool, input preview, reason, severity, timeout) with confirm-dialog buttons for low/medium severity; high severity renders a CLI hint instead (§11.2 finding aws-samples#4: high gates are CLI-only). `approval_stranded` renders as a warning. - `approval_requested` / `approval_stranded` graduate from forward-compat: added to ROUTABLE_MILESTONES (agent emits them as agent_milestone carriers) and NOTIFIABLE_EVENTS; consistency test updated to enforce renderability. Interactions handler: - `approve_action:{task_id}:{request_id}` / `deny_action:…` clicks: Slack signature verified → SlackUserMappingTable identity mapping (user-initiated links only) → SERVER-SIDE severity re-check from the approvals row (buttons are UX, not authorization) → shared decision core (ownership enforced in the transaction) → ephemeral feedback. - Slack decisions always record scope=this_call; blanket scopes stay CLI-only. Wiring: - SlackIntegration gains optional `taskApprovalsTable`; interactions Fn gets approvals read/write + events write + env vars; agent stack passes the table through. Docs: CEDAR_HITL_GATES §11.2 implementation note (direct enforcement instead of the sketched sts:AssumeRole proxy), SLACK_SETUP_GUIDE, USER_GUIDE gate-flow, ROADMAP (shipped entry + polish row trimmed), Starlight mirrors regenerated. Tests: approval-decision core (11), slack-interactions approvals (11, incl. forged-action_id high-severity refusal and non-owner no-oracle collapse), Block Kit renderers (7), construct wiring (2). Full cdk suite: 2015 passed. Closes aws-samples#112 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #316 +/- ##
=======================================
Coverage ? 86.72%
=======================================
Files ? 186
Lines ? 43804
Branches ? 3947
=======================================
Hits ? 37987
Misses ? 5817
Partials ? 0 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
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
Implements #112: when a Cedar HITL approval gate fires on a Slack-origin task, the Slack thread now gets an
:lock: Approval requiredBlock Kit message with ✅ Approve / ❌ Deny buttons. Clicking records the same atomic decision the CLI'sbgagent approve/denywould — the operator never leaves Slack.Design
Shared decision core (
cdk/src/handlers/shared/approval-decision.ts). The trust-critical approve/deny logic — per-user rate limit, the ownership-guarded PENDING-onlyTransactWriteItemsacross the approvals + task tables, and theapproval_decision_recordedaudit event — is extracted intoprocessApprovalDecision, and bothapprove-task.ts/deny-task.tsare refactored onto it. The HTTP handlers' status mapping is unchanged (their existing tests pass untouched), and the Slack path can no longer drift from the CLI path.Rendering + routing.
slack-blocks.tsrendersapproval_requestedwith tool name, input preview, reason, severity, and timeout. Buttons (with confirm dialogs) render only for low/medium severity; high severity renders a CLI command hint instead — per design §11.2 finding feat: add FargateAgentStack as alternative compute backend #4, high gates cannot be approved from Slack.approval_requested/approval_strandedgraduate from forward-compat: added to the fanoutROUTABLE_MILESTONESallowlist (the agent emits them asagent_milestonecarriers) and the Slack dispatcher'sNOTIFIABLE_EVENTS; the cross-file consistency test now enforces renderability for both.Interactions handler trust chain (
approve_action:{task_id}:{request_id}/deny_action:…):SlackUserMappingTable(user-initiated linking only; unlinked/pending → ephemeral "link first").action_idis refused here.user_id = :callercondition rejects a linked-but-non-owning user with the same no-oracle "not found" collapse as the HTTP path (§7.1 finding chore(deps): bump defu from 6.1.4 to 6.1.6 in /docs #6).Slack decisions always record
scope: this_call— blanket scoped approvals remain CLI-only.CDK wiring.
SlackIntegrationgains an optionaltaskApprovalsTableprop; the interactions Lambda gets approvals read/write + events write grants and the table env vars; the agent stack passes the existingTaskApprovalsTableV2through. Without the prop, approval clicks degrade to an ephemeral "not configured" message.Docs. CEDAR_HITL_GATES §11.2 gains an implementation note (constraints enforced directly in the interactions Lambda + row conditions, rather than the originally-sketched
sts:AssumeRoleproxy which added no enforcement the conditions don't already provide); SLACK_SETUP_GUIDE, USER_GUIDE gate-flow, and ROADMAP updated; Starlight mirrors regenerated.Tests
approval-decision.test.ts— 11 cases pinning the surface-agnostic invariants (rate-limit short-circuit, ownership/decided collapse tonot_found, task-row →not_awaiting, audit never written on failure, audit failure never fails the decision).slack-interactions-approvals.test.ts— 11 cases including the two critical ones: forged action_id on a high-severity gate is refused server-side, and a non-owner's click fails the ownership condition with no existence oracle.cdksuite: 112 suites / 2015 tests passed; eslint clean; CDK↔CLI type-sync check OK.Closes #112
🤖 Generated with Claude Code