Skip to content

feat(agent-mesh): legacy ApprovalHandler to protocol compatibility adapter (ADR-0030 step 3) - #3096

Merged
Imran Siddique (imran-siddique) merged 1 commit into
microsoft:mainfrom
carloshvp:feat/legacy-approval-handler-adapter
Jun 17, 2026
Merged

Imran Siddique (imran-siddique) merged 1 commit into
microsoft:mainfrom
carloshvp:feat/legacy-approval-handler-adapter

Conversation

@carloshvp

Copy link
Copy Markdown
Contributor

Summary

Step 3 of the ADR-0030 migration: a reusable compatibility adapter so existing synchronous ApprovalHandler implementations (callback, console, webhook, auto-reject) drive the action-bound approval protocol.

Step 2 (#3076) bridged the legacy handler into the coordinator inline inside govern(). This PR extracts that into governance/approval_bridge.py, so every approval path (Agent OS escalation, MCP gateway, framework adapters) can reuse one handler-to-protocol mapping instead of reimplementing it.

What changed

  • New governance/approval_bridge.py:
    • LegacyHandlerAdapter(handler, *, approver_kind=HUMAN, identity_assurance="approval-handler").
    • .collect(coordinator, request, legacy_request, *, stage_index=0) -> AdapterResult: asks the handler, maps approved / approver / reason onto one submit_entry, fail-closed on ApprovalProtocolError (unpermitted identity, expired request, unknown stage).
    • AdapterResult(approval, entry, error) with a .submitted convenience.
  • govern.py: the require_approval coordinator path now uses the adapter; behavior is unchanged (net -17 lines there).
  • The approval_protocol foundation package is untouched and never depends on the bridge.

Tests

tests/test_approval_bridge.py (6 tests): approve submits an ALLOW entry and resolves allow; reject submits DENY; an unpermitted identity fails closed (entry=None, error set, vote still reported); an expired request fails closed; reason and approver propagate; custom approver kind and assurance. Existing govern coordinator and approval suites still pass (85 total).

Scope

Single-stage via the synchronous handler, same as step 2. Multi-stage orchestration and async / webhook transport remain steps 4 and beyond.

Refs #2478. ADR-0030 step 3.

…y adapter

Step 3 of the ADR-0030 migration. Extract the legacy-handler-to-coordinator bridge that step 2 placed inline in govern() into a reusable governance/approval_bridge.py LegacyHandlerAdapter, so other approval paths (Agent OS escalation, MCP gateway, framework adapters) can drive a protocol chain entry from an existing synchronous ApprovalHandler without reimplementing the mapping.

The adapter asks the handler, maps approved/approver/reason onto a single submit_entry call, and returns an AdapterResult (the vote, the submitted entry or None, and a fail-closed error on unpermitted identity / expired request / unknown stage). govern()'s require_approval coordinator path is refactored to use it; behavior is identical. The approval_protocol foundation package is unchanged and does not depend on the bridge.

Refs microsoft#2478
@github-actions

Copy link
Copy Markdown
🤖 AI Agent: test-generator — `agentmesh/governance/approval_bridge.py`

AI-generated review output. Treat it as untrusted analysis and verify before acting.

agentmesh/governance/approval_bridge.py

  • test_collect_with_invalid_stage_index -- Validate behavior when an invalid stage_index is passed to collect.
  • test_collect_with_missing_approval_request_id -- Test handling of missing or invalid approval_request_id in collect.
  • test_collect_with_unexpected_handler_exception -- Ensure collect handles unexpected exceptions from the handler gracefully.

agentmesh/governance/govern.py

  • test_handle_approval_with_adapter_error -- Verify behavior when LegacyHandlerAdapter.collect returns an error.
  • test_handle_approval_with_unexpected_adapter_exception -- Ensure _handle_approval_via_coordinator handles unexpected exceptions from the adapter.

@github-actions

Copy link
Copy Markdown
🤖 AI Agent: code-reviewer — Action Items:

AI-generated review output. Treat it as untrusted analysis and verify before acting.

TL;DR: 0 blockers, 1 warning. The PR introduces a compatibility adapter for legacy approval handlers, which is well-implemented and tested, but the error messages could be more structured for better debugging.

# Sev Issue Where
1 Warn Error messages are plain strings, which may hinder debugging. approval_bridge.py

Action Items:

None.

Warnings:

# Issue Where Fine as follow-up PR?
1 Error messages in AdapterResult.error are plain strings. Consider using structured error objects for better debugging and downstream handling. approval_bridge.py Yes

@github-actions

Copy link
Copy Markdown
🤖 AI Agent: docs-sync-checker — Docs Sync

AI-generated review output. Treat it as untrusted analysis and verify before acting.

Docs Sync

Documentation is in sync.

@github-actions

Copy link
Copy Markdown
🤖 AI Agent: security-scanner — View details

AI-generated review output. Treat it as untrusted analysis and verify before acting.

No security issues found.

@github-actions

Copy link
Copy Markdown
🤖 AI Agent: breaking-change-detector — API Compatibility

AI-generated review output. Treat it as untrusted analysis and verify before acting.

API Compatibility

Severity Change Impact
High ApprovalHandler.request_approval is no longer called directly in govern.py. Instead, it is wrapped by LegacyHandlerAdapter.collect. Existing code or extensions relying on direct calls to ApprovalHandler.request_approval in govern.py may break.
High The ApprovalProtocolError exception is now handled within LegacyHandlerAdapter.collect, and no longer propagates directly from govern.py. Code relying on catching ApprovalProtocolError directly in govern.py will no longer function as expected.
Medium The ApprovalHandler interface is now indirectly used via LegacyHandlerAdapter. Custom implementations of ApprovalHandler may require testing to ensure compatibility with the new adapter.

@github-actions

Copy link
Copy Markdown

PR Review Summary

Check Status Details
🔍 Code Review ⚠️ Missing No current-run comment
🛡️ Security Scan ⚠️ Missing No current-run comment
🔄 Breaking Changes ⚠️ Missing No current-run comment
📝 Docs Sync ⚠️ Missing No current-run comment
🧪 Test Coverage ⚠️ Missing No current-run comment

Verdict: ⚠️ AI review incomplete; ready for human review

AI review comments are untrusted advisory output. The summary reports workflow-generated completion status only, not model-authored pass/fail claims.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Clean extraction of the inline approval bridge into a reusable LegacyHandlerAdapter. Fail-closed design is correct: any ApprovalProtocolError from submit_entry returns entry=None with an error string, letting callers deny without special-casing. The govern.py diff confirms the adapter slots in cleanly. 122-line test file covers the key paths. LGTM.

@imran-siddique
Imran Siddique (imran-siddique) merged commit 2456d36 into microsoft:main Jun 17, 2026
14 of 15 checks passed
Imran Siddique (imran-siddique) pushed a commit that referenced this pull request Jun 17, 2026
#3097)

Step 4 of the ADR-0030 migration (section 5). The legacy WebhookApproval sends a thin payload with no request id, action digest, version, or expiry and trusts a body-supplied approver string. Add governance/approval_webhook.py with the versioned, action-bound contract: build_webhook_request() emits a schema-versioned payload carrying the request id, action digest, policy version, chain version, and expiry; parse_webhook_response() requires the response to echo the binding and honours an approve only when the approver identity is verified (deny is always honoured, malformed denies); VersionedWebhookApproval is the protocol-native transport (POSTs with caller-supplied auth headers, reuses the SSRF guard, fails closed on timeout/transport/malformed/binding-mismatch).

Standalone and additive: no changes to govern, the legacy WebhookApproval (kept as the old-payload legacy path), or the approval_protocol foundation. Independent of the step-3 PR (#3096); wiring the transport into the govern/bridge flow is a follow-up. 11 tests, no network (HTTP is injectable).

Refs #2478
@carloshvp
Carlos Hernandez (carloshvp) deleted the feat/legacy-approval-handler-adapter branch June 17, 2026 16:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agent-mesh agent-mesh package size/L Large PR (< 500 lines) tests

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants