Skip to content

feat(agent-mesh): port production trust-engine features from internal#68

Merged
imran-siddique merged 1 commit intomainfrom
feat/port-trust-engine-production
Mar 7, 2026
Merged

feat(agent-mesh): port production trust-engine features from internal#68
imran-siddique merged 1 commit intomainfrom
feat/port-trust-engine-production

Conversation

@imran-siddique
Copy link
Member

Summary

Surgically merges production trust-engine features from the internal repo into the public agent-mesh package. Unlike other modules where internal was a superset, the public agent-mesh has significantly diverged (97 files vs 17 internal), so this required careful feature merging rather than file replacement.

Changes

governance/audit.py — Cryptographic Audit Trail

  • Replaced stub \AuditChain\ with full \MerkleAuditChain\ implementation
  • Real SHA-256 hash computation in \AuditEntry.compute_hash()\ (was returning empty string)
  • Real hash verification in \�erify_hash()\ (was always returning True)
  • Merkle tree proofs with \get_proof()\ and \�erify_proof()\
  • Full chain integrity verification
  • Backward-compatible aliases: \ChainNode = MerkleNode, \AuditChain = MerkleAuditChain\

trust/bridge.py — Protocol Translation

  • Added IATP/Nexus integration imports with graceful fallback
  • Replaced passthrough \send_message()\ with translation-aware version
  • Added _translate(), _a2a_to_mcp(), _mcp_to_a2a()\ methods

reward/trust_decay.py — Network Propagation

  • Added \InteractionEdge\ and \RegimeChangeAlert\ dataclasses
  • Full interaction graph tracking in
    ecord_interaction()\ (was no-op)
  • Recursive network propagation via _propagate()\
  • Regime change detection with KL divergence (\detect_regime_change())

identity/delegation.py — Signature Verification

  • Added _verify_link_signature()\ for Ed25519 signature verification
  • Updated \ScopeChain.verify()\ to include hash integrity and signature checks

Tests

  • 1521 tests pass (matches baseline exactly), 0 regressions
  • Updated 9 tests to verify real implementations instead of stubs

Closes #61

… repo

Surgically merged internal-only features into existing public agent-mesh:

- governance/audit.py: MerkleAuditChain with real SHA-256 hash computation,
  Merkle tree proofs, and chain verification (replaced stubs)
- trust/bridge.py: IATP/Nexus integration with protocol translation
  (_a2a_to_mcp, _mcp_to_a2a) instead of passthrough stubs
- reward/trust_decay.py: InteractionEdge graph tracking, recursive network
  propagation, RegimeChangeAlert with KL divergence detection
- identity/delegation.py: Ed25519 signature verification in delegation chains

All public-only features preserved (ScopeChain, UserContext, CloudEvents, etc).
Backward-compatible aliases maintained (ChainNode=MerkleNode, AuditChain=MerkleAuditChain).
Tests updated to reflect real implementations vs stubs.

1521 tests pass (matching baseline), 0 regressions.

Closes #61

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@github-actions github-actions bot added tests agent-mesh agent-mesh package labels Mar 7, 2026
@github-actions
Copy link

github-actions bot commented Mar 7, 2026

Dependency Review

✅ No vulnerabilities or license issues or OpenSSF Scorecard issues found.

Scanned Files

None

@github-actions github-actions bot added the size/XL Extra large PR (500+ lines) label Mar 7, 2026
@imran-siddique imran-siddique requested a review from Copilot March 7, 2026 20:28
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Ports production-grade “trust engine” features into the public agent-mesh package, upgrading prior stub implementations to real cryptographic audit trails, protocol translation, network-aware trust decay, and delegation-chain verification.

Changes:

  • Implement Merkle-based audit chain with SHA-256 hashing, inclusion proofs, and integrity verification.
  • Add protocol translation support in the trust bridge (A2A ↔ MCP) with optional external integration imports.
  • Expand trust decay into a network-propagating engine with interaction graph tracking and regime-change detection; strengthen scope-chain verification with hash + signature checks.

Reviewed changes

Copilot reviewed 9 out of 9 changed files in this pull request and generated 8 comments.

Show a summary per file
File Description
packages/agent-mesh/src/agentmesh/governance/audit.py Replaces audit stubs with SHA-256 hashing + Merkle audit chain, proofs, and export/proof APIs.
packages/agent-mesh/src/agentmesh/trust/bridge.py Adds protocol translation helpers and optional IATP/Nexus imports; updates send_message() to translate when protocols differ.
packages/agent-mesh/src/agentmesh/reward/trust_decay.py Implements interaction graph tracking, propagation, and KL-divergence regime detection in NetworkTrustEngine.
packages/agent-mesh/src/agentmesh/identity/delegation.py Adds link-hash integrity checking and Ed25519 signature verification during scope-chain verification.
packages/agent-mesh/tests/test_trust_decay.py Updates tests to reflect interaction graph recording and propagation behavior.
packages/agent-mesh/tests/test_trust.py Updates tests to assert translation helpers exist and map fields as expected.
packages/agent-mesh/tests/test_services.py Updates audit service tests to expect a computed Merkle root.
packages/agent-mesh/tests/test_governance.py Updates governance tests to expect real hashes, Merkle roots, and proofs.
packages/agent-mesh/tests/test_coverage_boost.py Adds async translation tests for _translate() and passthrough behavior.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

identity = self.known_identities.get(link.parent_did)
if identity is None:
return True # Graceful fallback — can't verify without identity
signable_data = f"{link.parent_did}:{link.child_did}:{','.join(sorted(link.delegated_capabilities))}"
Copy link

Copilot AI Mar 7, 2026

Choose a reason for hiding this comment

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

The signed payload (signable_data) does not include previous_link_hash, link_id, depth, timestamps, or the computed link_hash. This allows replay/cut-and-paste of a valid signature onto a different chain by changing previous_link_hash and recomputing link_hash (hash is not keyed). Consider signing the canonical bytes of the full link (or at least signing link.compute_hash() / previous_link_hash) so the signature actually binds the link into its chain context.

Suggested change
signable_data = f"{link.parent_did}:{link.child_did}:{','.join(sorted(link.delegated_capabilities))}"
# Sign and verify the canonical hash of the full link so the signature
# is bound to the link's chain context (including previous_link_hash, depth, etc.).
signable_data = link.compute_hash()

Copilot uses AI. Check for mistakes.
Comment on lines +20 to +27
from modules.iatp import IATPClient, IATPMessage, TrustLevel
from modules.nexus import NexusClient, ReputationEngine
AGENT_OS_AVAILABLE = True
except ImportError:
# Fallback if agent-os not installed yet (for development)
AGENT_OS_AVAILABLE = False
IATPClient = None
NexusClient = None
Copy link

Copilot AI Mar 7, 2026

Choose a reason for hiding this comment

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

The optional modules.iatp / modules.nexus imports are unused in this file, which will trigger Ruff F401 during lint. Additionally, the except ImportError fallback only defines IATPClient/NexusClient but not the other imported names, which could lead to NameError if they’re referenced later. Consider removing these imports until they’re needed, or guard them behind TYPE_CHECKING / define consistent sentinels for all imported symbols and import from the actual public package names (not modules.*).

Suggested change
from modules.iatp import IATPClient, IATPMessage, TrustLevel
from modules.nexus import NexusClient, ReputationEngine
AGENT_OS_AVAILABLE = True
except ImportError:
# Fallback if agent-os not installed yet (for development)
AGENT_OS_AVAILABLE = False
IATPClient = None
NexusClient = None
from modules.iatp import IATPClient, IATPMessage, TrustLevel # noqa: F401
from modules.nexus import NexusClient, ReputationEngine # noqa: F401
AGENT_OS_AVAILABLE = True
except ImportError:
# Fallback if agent-os not installed yet (for development)
AGENT_OS_AVAILABLE = False
IATPClient = None
IATPMessage = None
TrustLevel = None
NexusClient = None
ReputationEngine = None

Copilot uses AI. Check for mistakes.
Comment on lines 23 to 28
from agentmesh.constants import TRUST_SCORE_DEFAULT, TRUST_SCORE_MAX

logger = logging.getLogger(__name__)
from collections import Counter, defaultdict
from dataclasses import dataclass, field
from typing import Any, Callable, Dict, List, Optional, Tuple
Copy link

Copilot AI Mar 7, 2026

Choose a reason for hiding this comment

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

There’s an import (from collections import Counter, defaultdict) after the logger = logging.getLogger(__name__) statement, which violates E402 (“module level import not at top of file”) and will be flagged by the repo’s ruff check --select E,F,W lint step. Move the collections import up with the other imports (or add it before any non-import statements).

Copilot uses AI. Check for mistakes.
Comment on lines +154 to +160
"""Return (peer_did, interaction_weight) pairs for *agent_did*."""
neighbors: List[Tuple[str, float]] = []
for (f, t), edge in self._edges.items():
if f == agent_did:
neighbors.append((t, edge.weight))
elif t == agent_did:
neighbors.append((f, edge.weight))
Copy link

Copilot AI Mar 7, 2026

Choose a reason for hiding this comment

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

InteractionEdge is documented as a directed edge, but get_neighbors() treats edges as bidirectional by returning both outgoing neighbors (from_did → to_did) and incoming neighbors (to_did → from_did). Either update the documentation/types to reflect an undirected interaction graph, or adjust neighbor traversal/propagation to respect directionality.

Suggested change
"""Return (peer_did, interaction_weight) pairs for *agent_did*."""
neighbors: List[Tuple[str, float]] = []
for (f, t), edge in self._edges.items():
if f == agent_did:
neighbors.append((t, edge.weight))
elif t == agent_did:
neighbors.append((f, edge.weight))
"""Return (peer_did, interaction_weight) pairs for *agent_did*.
Neighbors are defined via outgoing directed edges (from_did -> to_did).
"""
neighbors: List[Tuple[str, float]] = []
for (f, t), edge in self._edges.items():
if f == agent_did:
neighbors.append((t, edge.weight))

Copilot uses AI. Check for mistakes.
Comment on lines +69 to +72
"resource": self.resource,
"data": self.data,
"outcome": self.outcome,
"previous_hash": self.previous_hash,
Copy link

Copilot AI Mar 7, 2026

Choose a reason for hiding this comment

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

AuditEntry.compute_hash() currently omits several mutable fields (e.g., target_did, policy_decision, matched_rule, trace_id, session_id) from the hashed payload. That means those fields could be altered without verify_hash() detecting tampering. Include all entry fields that should be integrity-protected (or explicitly document/justify exclusions).

Suggested change
"resource": self.resource,
"data": self.data,
"outcome": self.outcome,
"previous_hash": self.previous_hash,
"resource": self.resource,
"target_did": self.target_did,
"data": self.data,
"outcome": self.outcome,
"policy_decision": self.policy_decision,
"matched_rule": self.matched_rule,
"previous_hash": self.previous_hash,
"trace_id": self.trace_id,
"session_id": self.session_id,

Copilot uses AI. Check for mistakes.
"outcome": self.outcome,
"previous_hash": self.previous_hash,
}
canonical = json.dumps(data, sort_keys=True)
Copy link

Copilot AI Mar 7, 2026

Choose a reason for hiding this comment

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

json.dumps(data, sort_keys=True) in compute_hash() will raise TypeError if self.data contains non-JSON-serializable values (e.g., bytes, datetime). Since AuditEntry.data is a free-form dict, consider using a stable serialization that can handle common non-JSON types (e.g., default=str / Pydantic’s JSON encoding) so audit logging can’t fail at runtime on otherwise valid entries.

Suggested change
canonical = json.dumps(data, sort_keys=True)
canonical = json.dumps(data, sort_keys=True, default=str)

Copilot uses AI. Check for mistakes.
Comment on lines +464 to +472
if not proof:
return None

return {
"entry": entry.model_dump(),
"merkle_proof": proof,
"merkle_root": self._chain.get_root_hash(),
"verified": self._chain.verify_proof(
entry.entry_hash, proof, self._chain.get_root_hash()
Copy link

Copilot AI Mar 7, 2026

Choose a reason for hiding this comment

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

AuditLog.get_proof() treats an empty proof list as falsy (if not proof:) and returns None. For a single-entry Merkle tree, a valid inclusion proof is typically the empty list (root == leaf), so this would incorrectly report “no proof”. Check proof is None instead, and consider separately validating that merkle_root is not None before calling verify_proof().

Suggested change
if not proof:
return None
return {
"entry": entry.model_dump(),
"merkle_proof": proof,
"merkle_root": self._chain.get_root_hash(),
"verified": self._chain.verify_proof(
entry.entry_hash, proof, self._chain.get_root_hash()
# A valid Merkle proof may be an empty list (e.g., single-entry tree),
# so only treat None as "no proof".
if proof is None:
return None
merkle_root = self._chain.get_root_hash()
# If there is no Merkle root, we cannot verify the proof.
if merkle_root is None:
return None
return {
"entry": entry.model_dump(),
"merkle_proof": proof,
"merkle_root": merkle_root,
"verified": self._chain.verify_proof(
entry.entry_hash, proof, merkle_root

Copilot uses AI. Check for mistakes.
Comment on lines +255 to +258
identity = self.known_identities.get(link.parent_did)
if identity is None:
return True # Graceful fallback — can't verify without identity
signable_data = f"{link.parent_did}:{link.child_did}:{','.join(sorted(link.delegated_capabilities))}"
Copy link

Copilot AI Mar 7, 2026

Choose a reason for hiding this comment

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

_verify_link_signature() currently returns True when the parent identity is unknown (identity is None). If ScopeChain.verify() is used for authorization decisions, this is effectively fail-open and allows forged delegation links to validate as long as the attacker omits/avoids known identities. Consider failing closed by default (returning False with an error), or making this behavior explicitly configurable (e.g., require_signatures=True).

Copilot uses AI. Check for mistakes.
@imran-siddique imran-siddique merged commit b9d1a5a into main Mar 7, 2026
29 checks passed
@imran-siddique imran-siddique deleted the feat/port-trust-engine-production branch March 7, 2026 21:21
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/XL Extra large PR (500+ lines) tests

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: Port production Trust Engine to AgentMesh from internal

2 participants