diff --git a/bindu/common/protocol/types.py b/bindu/common/protocol/types.py index bfa17f24..90e14df9 100644 --- a/bindu/common/protocol/types.py +++ b/bindu/common/protocol/types.py @@ -1657,6 +1657,61 @@ class AgentTrust(TypedDict): allowed_operations: Dict[str, TrustLevel] +@pydantic.with_config(ConfigDict(alias_generator=to_camel)) +class AgentTrustConfig(TypedDict): + """Agent trust configuration for deployment and validation. + + This TypedDict defines the trust policies and security requirements + for agent deployments, ensuring proper verification and hierarchy constraints. + """ + + identity_provider: NotRequired[IdentityProvider] + """The identity provider for the agent (e.g., 'hydra'). + + Defaults to 'hydra' if not specified. + """ + + required_verification_level: Required[TrustLevel] + """The minimum required verification level for agent operations. + + Examples: 'admin', 'analyst', 'auditor', 'editor', 'guest', 'manager', + 'operator', 'super_admin', 'support', 'viewer' + """ + + allowed_origins: NotRequired[list[str]] + """List of allowed domains/origins that can invoke this agent. + + Examples: ['https://example.com', 'https://api.example.com'] + Wildcard patterns are supported: ['https://*.example.com'] + If empty, all origins are allowed. + """ + + max_agent_hierarchy_depth: Required[int] + """Maximum nesting depth for agent-to-agent calls. + + Prevents circular dependencies and infinite loops. + Value >= 1 is required. Typical value: 5 + """ + + trust_verification_required: NotRequired[bool] + """Whether explicit trust verification is required before execution. + + Defaults to False if not specified. + """ + + certificate_required: NotRequired[bool] + """Whether agent certificate is required for security. + + Defaults to False if not specified. + """ + + metadata: NotRequired[dict[str, Any]] + """Additional trust-related metadata. + + Can include custom fields for specific deployment scenarios. + """ + + # ----------------------------------------------------------------------------- # Agent # ----------------------------------------------------------------------------- @@ -1945,10 +2000,6 @@ class AgentCard(TypedDict): agent_card_ta = pydantic.TypeAdapter(AgentCard) -# Rebuild TypeAdapters to resolve forward references -a2a_request_ta.rebuild() -a2a_response_ta.rebuild() -send_message_request_ta.rebuild() -send_message_response_ta.rebuild() -stream_message_request_ta.rebuild() -stream_message_response_ta.rebuild() +# TypeAdapter in Pydantic 2.x automatically handles forward references +# No need to manually call rebuild() as in v1.x + diff --git a/bindu/penguin/config_validator.py b/bindu/penguin/config_validator.py index c665cddb..17e5619c 100644 --- a/bindu/penguin/config_validator.py +++ b/bindu/penguin/config_validator.py @@ -9,7 +9,7 @@ from typing import Any, Dict from bindu import __version__ -from bindu.common.protocol.types import AgentCapabilities, Skill +from bindu.common.protocol.types import AgentCapabilities, AgentTrustConfig, Skill, TrustLevel class ConfigValidator: @@ -267,6 +267,101 @@ def _validate_hydra_config(cls, auth_config: Dict[str, Any]) -> None: # Telemetry processing # ------------------------------------------------------------------ + @classmethod + def _validate_agent_trust_config(cls, trust_config: AgentTrustConfig) -> None: + """Validate agent trust configuration. + + Args: + trust_config: Agent trust configuration (AgentTrustConfig TypedDict) + + Raises: + ValueError: If trust configuration is invalid + """ + if not isinstance(trust_config, dict): + raise ValueError("Field 'agent_trust' must be a dictionary") + + # Validate required_verification_level + if "required_verification_level" in trust_config: + level = trust_config["required_verification_level"] + valid_levels = [ + "admin", + "analyst", + "auditor", + "editor", + "guest", + "manager", + "operator", + "super_admin", + "support", + "viewer", + ] + if level not in valid_levels: + raise ValueError( + f"Invalid required_verification_level: '{level}'. " + f"Must be one of: {', '.join(valid_levels)}" + ) + + # Validate max_agent_hierarchy_depth + if "max_agent_hierarchy_depth" in trust_config: + depth = trust_config["max_agent_hierarchy_depth"] + if not isinstance(depth, int) or depth < 1: + raise ValueError( + f"Invalid max_agent_hierarchy_depth: '{depth}'. " + f"Must be a positive integer (>= 1)" + ) + + # Validate identity_provider if provided + if "identity_provider" in trust_config: + provider = trust_config["identity_provider"] + if provider not in ["hydra"]: + raise ValueError( + f"Invalid identity_provider: '{provider}'. " + f"Supported providers: hydra" + ) + + # Validate allowed_origins if provided + if "allowed_origins" in trust_config: + origins = trust_config["allowed_origins"] + if not isinstance(origins, list): + raise ValueError( + "Field 'allowed_origins' must be a list of strings" + ) + for origin in origins: + if not isinstance(origin, str): + raise ValueError( + f"Invalid origin in allowed_origins: {origin}. " + f"All origins must be strings" + ) + # Validate URL format + if not ( + origin.startswith("http://") + or origin.startswith("https://") + or "*" in origin + ): + raise ValueError( + f"Invalid origin format: '{origin}'. " + f"Expected http:// or https:// URL or wildcard pattern" + ) + + # Validate trust_verification_required if provided + if "trust_verification_required" in trust_config: + if not isinstance(trust_config["trust_verification_required"], bool): + raise ValueError( + "Field 'trust_verification_required' must be a boolean" + ) + + # Validate certificate_required if provided + if "certificate_required" in trust_config: + if not isinstance(trust_config["certificate_required"], bool): + raise ValueError( + "Field 'certificate_required' must be a boolean" + ) + + # Validate metadata if provided + if "metadata" in trust_config: + if not isinstance(trust_config["metadata"], dict): + raise ValueError("Field 'metadata' must be a dictionary") + @classmethod def _process_oltp_config(cls, config: Dict[str, Any]) -> None: oltp_endpoint = config.get("oltp_endpoint") diff --git a/docs/AGENT_TRUST.md b/docs/AGENT_TRUST.md new file mode 100644 index 00000000..2982589d --- /dev/null +++ b/docs/AGENT_TRUST.md @@ -0,0 +1,399 @@ +# Agent Trust Configuration + +Agent Trust Configuration defines security policies and trust requirements for agent deployments. This ensures proper verification of agent identities and enforces operational constraints to prevent unauthorized or risky agent compositions. + +## Overview + +Trust configuration manages: +- **Verification Levels**: Required trust/authorization levels for agents to operate +- **Origin Control**: Which domains can invoke your agent +- **Hierarchy Constraints**: Maximum nesting depth for agent-to-agent calls +- **Verification Requirements**: Whether explicit trust verification is required +- **Certificate Requirements**: Whether security certificates are mandatory + +## Configuration Structure + +Trust configuration is specified in the agent's configuration file under the `agent_trust` field: + +```json +{ + "author": "agent@example.com", + "deployment": {}, + "agent_trust": { + "identity_provider": "hydra", + "required_verification_level": "manager", + "allowed_origins": [ + "https://api.example.com", + "https://*.getbindu.com" + ], + "max_agent_hierarchy_depth": 5, + "trust_verification_required": true, + "certificate_required": true, + "metadata": { + "region": "us-east-1", + "compliance_level": "high" + } + } +} +``` + +## Configuration Fields + +### identity_provider (Optional) +**Type:** `string` +**Default:** `"hydra"` +**Supported Values:** `"hydra"` + +Specifies the identity provider used for agent trust verification. Currently, only Hydra is supported. + +**Example:** +```json +"identity_provider": "hydra" +``` + +### required_verification_level (Required) +**Type:** `string` +**Required:** Yes + +The minimum authorization level required for agents to perform operations. + +**Supported Levels:** +- `"viewer"` - View-only access, minimal permissions +- `"guest"` - Limited access, read-only operations +- `"analyst"` - Standard read operations +- `"editor"` - Edit operations, moderate risk +- `"operator"` - System operations, moderate risk +- `"manager"` - Management operations, elevated permissions +- `"auditor"` - Sensitive operations, audit access +- `"admin"` - Admin operations, minimal risk +- `"support"` - Support operations, troubleshooting access +- `"super_admin"` - Highest level access, all operations permitted + +**Example - Production Agent:** +```json +"required_verification_level": "manager" +``` + +**Example - Public Agent:** +```json +"required_verification_level": "guest" +``` + +### allowed_origins (Optional) +**Type:** `array[string]` +**Default:** Empty (all origins allowed) + +List of allowed domains/origins that can invoke this agent. Supports wildcard patterns. + +**Format:** +- Full domain: `"https://example.com"` +- Wildcard subdomains: `"https://*.example.com"` +- Wildcards must use `https://` or `http://` prefix + +**Example - Single Origin:** +```json +"allowed_origins": ["https://api.example.com"] +``` + +**Example - Multiple Origins:** +```json +"allowed_origins": [ + "https://api.example.com", + "https://admin.example.com", + "https://*.example.com" +] +``` + +**Example - Internal Network:** +```json +"allowed_origins": [ + "https://*.internal.example.com", + "https://trusted-partner.com" +] +``` + +### max_agent_hierarchy_depth (Required) +**Type:** `integer` +**Required:** Yes +**Constraints:** Must be >= 1 + +Maximum nesting depth for agent-to-agent calls. Prevents circular dependencies and infinite loops when agents call other agents. + +**Value Guide:** +- `1` - No agent-to-agent calls allowed (only direct calls) +- `3` - Shallow hierarchy (A → B → C) +- `5` - Standard (most deployments) +- `10` - Deep hierarchies (complex workflows) + +**Example - Shallow Hierarchy:** +```json +"max_agent_hierarchy_depth": 1 +``` + +**Example - Standard Hierarchy:** +```json +"max_agent_hierarchy_depth": 5 +``` + +**Example - Deep Hierarchy:** +```json +"max_agent_hierarchy_depth": 10 +``` + +### trust_verification_required (Optional) +**Type:** `boolean` +**Default:** `false` + +Whether explicit trust verification must be completed before the agent can execute operations. When `true`, agents must verify trust relationships before processing tasks. + +**Example - Requiring Verification:** +```json +"trust_verification_required": true +``` + +**Example - No Verification Needed:** +```json +"trust_verification_required": false +``` + +### certificate_required (Optional) +**Type:** `boolean` +**Default:** `false` + +Whether agent security certificates are required for operation. When `true`, agents must provide valid certificates for authentication. + +**Example - Certificate Required:** +```json +"certificate_required": true +``` + +### metadata (Optional) +**Type:** `object` +**Default:** `{}` + +Custom metadata for deployment-specific trust requirements. Not validated by the system but available for custom implementations. + +**Example - Compliance Metadata:** +```json +"metadata": { + "region": "us-east-1", + "compliance_level": "high", + "data_residency": "us-only", + "audit_required": true, + "soc2_certified": true +} +``` + +**Example - Business Metadata:** +```json +"metadata": { + "owner": "security-team", + "approval_required": true, + "approval_ticket": "SECURITY-123", + "review_date": "2025-01-15", + "risk_level": "medium" +} +``` + +## Validation Rules + +The ConfigValidator enforces the following rules: + +### Required Fields +- `required_verification_level` - Must be one of the valid levels above +- `max_agent_hierarchy_depth` - Must be a positive integer (>= 1) + +### Type Validation +- `identity_provider` - String, must be "hydra" if provided +- `allowed_origins` - List of strings, each must start with http:// or https://, or contain wildcard +- `trust_verification_required` - Boolean +- `certificate_required` - Boolean +- `metadata` - Dictionary/object + +### Domain Validation +- Origins must be valid URL format: `http://...` or `https://...` +- Wildcards allowed in origin domains only: `https://*.example.com` +- Each origin must be a non-empty string + +## Example Configurations + +### Development Agent (Minimal Trust) +For development and testing with minimal restrictions: + +```json +{ + "author": "dev@example.com", + "deployment": {}, + "agent_trust": { + "required_verification_level": "viewer", + "max_agent_hierarchy_depth": 1, + "trust_verification_required": false, + "certificate_required": false + } +} +``` + +### Production Agent (Standard Trust) +For regular production deployments: + +```json +{ + "author": "prod@example.com", + "deployment": {}, + "agent_trust": { + "identity_provider": "hydra", + "required_verification_level": "manager", + "allowed_origins": [ + "https://api.example.com", + "https://*.example.com" + ], + "max_agent_hierarchy_depth": 5, + "trust_verification_required": true, + "certificate_required": false + } +} +``` + +### High-Security Agent +For sensitive operations with strict requirements: + +```json +{ + "author": "security@example.com", + "deployment": {}, + "agent_trust": { + "identity_provider": "hydra", + "required_verification_level": "super_admin", + "allowed_origins": [ + "https://secure.example.com", + "https://admin.internal.example.com" + ], + "max_agent_hierarchy_depth": 2, + "trust_verification_required": true, + "certificate_required": true, + "metadata": { + "compliance_level": "high", + "audit_required": true, + "soc2_certified": true, + "data_classification": "confidential" + } + } +} +``` + +### Multi-Tenant Agent +For agents serving multiple customers: + +```json +{ + "author": "platform@example.com", + "deployment": {}, + "agent_trust": { + "required_verification_level": "analyst", + "allowed_origins": [ + "https://tenant-1.example.com", + "https://tenant-2.example.com", + "https://tenant-3.example.com", + "https://*.tenant.example.com" + ], + "max_agent_hierarchy_depth": 3, + "trust_verification_required": true, + "certificate_required": true, + "metadata": { + "multi_tenant": true, + "isolation_level": "strict", + "rate_limiting": "per_tenant" + } + } +} +``` + +## Implementation + +Trust configuration is processed during agent initialization through the `ConfigValidator` class: + +```python +from bindu.penguin.config_validator import ConfigValidator + +# Load raw configuration +config = load_config_from_file("agent_config.json") + +# Validate and process - trust config is validated here +validated_config = ConfigValidator.validate_and_process(config) + +# Trust configuration is now available +trust_config = validated_config["agent_trust"] +print(f"Required level: {trust_config['required_verification_level']}") +print(f"Max hierarchy depth: {trust_config['max_agent_hierarchy_depth']}") +``` + +## Validation Errors + +### Missing Required Field Example +``` +ValueError: Invalid agent_trust configuration: Required key 'required_verification_level' missing. +``` + +**Solution:** Add the required field to your `agent_trust` configuration. + +### Invalid Verification Level Example +``` +ValueError: Invalid required_verification_level: 'invalid_level'. +Must be one of: admin, analyst, auditor, editor, guest, manager, operator, super_admin, support, viewer +``` + +**Solution:** Choose a valid level from the supported list. + +### Invalid Hierarchy Depth Example +``` +ValueError: Invalid max_agent_hierarchy_depth: '0'. Must be a positive integer (>= 1) +``` + +**Solution:** Set a positive integer value (minimum 1). + +### Invalid Origin Format Example +``` +ValueError: Invalid origin format: 'example.com'. Expected http:// or https:// URL or wildcard pattern +``` + +**Solution:** Ensure all origins start with `http://` or `https://`. + +## Best Practices + +1. **Principle of Least Privilege**: Use the minimum verification level needed + - Use `viewer` or `guest` for read-only operations + - Use `manager` or `editor` for standard operations + - Use `admin` or `super_admin` only for critical operations + +2. **Origin Control**: Always restrict origins in production + - Use specific domains instead of wildcard `*` + - Use subdomain wildcards: `https://*.example.com` + - Document why each origin needs access + +3. **Hierarchy Depth**: Keep nesting shallow + - Default to `5` for most deployments + - Use `1-2` for simple agents + - Use `>8` only for complex workflows + +4. **Verification**: Enable for production + - Set `trust_verification_required: true` in production + - Set `trust_verification_required: false` for development only + - Review verification requirements regularly + +5. **Certificates**: Use in sensitive deployments + - Enable `certificate_required: true` for financial operations + - Enable for healthcare or regulated data + - Disable for development/testing + +6. **Documentation**: Keep metadata updated + - Document compliance requirements + - Track approval decisions + - Record review dates + - Maintain audit trail + +## Related Documentation + +- [Authentication](./AUTHENTICATION.md) - User/agent authentication with Hydra +- [DID](./DID.md) - Decentralized Identity for agents +- [Security Best Practices](./SECURITY.md) - Broader security guidelines +- [Configuration Guide](../README.md#configuration) - Overall configuration reference diff --git a/examples/test_config_with_trust.json b/examples/test_config_with_trust.json new file mode 100644 index 00000000..3490338f --- /dev/null +++ b/examples/test_config_with_trust.json @@ -0,0 +1,38 @@ +{ + "author": "test@getbindu.com", + "name": "test-agent-with-trust", + "description": "Test agent with complete trust configuration", + "version": "1.0.0", + "recreate_keys": false, + "kind": "agent", + "debug_mode": false, + "debug_level": 1, + "monitoring": false, + "telemetry": true, + "num_history_sessions": 10, + "agent_trust": { + "identity_provider": "hydra", + "required_verification_level": "manager", + "allowed_origins": [ + "https://api.example.com", + "https://*.getbindu.com" + ], + "max_agent_hierarchy_depth": 5, + "trust_verification_required": true, + "certificate_required": true, + "metadata": { + "region": "us-east-1", + "compliance_level": "high" + } + }, + "deployment": { + "type": "cloud", + "expose": true + }, + "storage": { + "type": "memory" + }, + "scheduler": { + "type": "memory" + } +} diff --git a/test_comprehensive_trust.py b/test_comprehensive_trust.py new file mode 100644 index 00000000..b62ecd40 --- /dev/null +++ b/test_comprehensive_trust.py @@ -0,0 +1,181 @@ +#!/usr/bin/env python +"""Comprehensive validation of agent trust implementation.""" + +import json +from pathlib import Path +from bindu.penguin.config_validator import ConfigValidator + +def test_all_scenarios(): + """Test various agent trust scenarios.""" + test_cases = [ + { + "name": "Minimal Valid Config", + "config": { + "author": "test@example.com", + "deployment": {}, + "agent_trust": { + "required_verification_level": "guest", + "max_agent_hierarchy_depth": 1 + } + }, + "should_pass": True + }, + { + "name": "Full Config with All Fields", + "config": { + "author": "test@example.com", + "deployment": {}, + "agent_trust": { + "identity_provider": "hydra", + "required_verification_level": "super_admin", + "allowed_origins": ["https://example.com", "https://*.example.com"], + "max_agent_hierarchy_depth": 10, + "trust_verification_required": True, + "certificate_required": True, + "metadata": {"key": "value"} + } + }, + "should_pass": True + }, + { + "name": "No Agent Trust (None)", + "config": { + "author": "test@example.com", + "deployment": {}, + "agent_trust": None + }, + "should_pass": True + }, + { + "name": "Invalid Verification Level", + "config": { + "author": "test@example.com", + "deployment": {}, + "agent_trust": { + "required_verification_level": "invalid", + "max_agent_hierarchy_depth": 5 + } + }, + "should_pass": False, + "expected_error": "Invalid required_verification_level" + }, + { + "name": "Invalid Hierarchy Depth (Zero)", + "config": { + "author": "test@example.com", + "deployment": {}, + "agent_trust": { + "required_verification_level": "admin", + "max_agent_hierarchy_depth": 0 + } + }, + "should_pass": False, + "expected_error": "Invalid max_agent_hierarchy_depth" + }, + { + "name": "Invalid Origin Format", + "config": { + "author": "test@example.com", + "deployment": {}, + "agent_trust": { + "required_verification_level": "manager", + "allowed_origins": ["not-a-valid-url"], + "max_agent_hierarchy_depth": 5 + } + }, + "should_pass": False, + "expected_error": "Invalid origin format" + }, + { + "name": "All Valid Trust Levels", + "config": { + "author": "test@example.com", + "deployment": {}, + "agent_trust": { + "required_verification_level": "admin", + "max_agent_hierarchy_depth": 5 + } + }, + "should_pass": True, + "validate_levels": True + } + ] + + passed = 0 + failed = 0 + + print("=" * 70) + print("AGENT TRUST CONFIGURATION VALIDATION TESTS") + print("=" * 70) + + for test_case in test_cases: + print(f"\n▶ {test_case['name']}") + + try: + result = ConfigValidator.validate_and_process(test_case['config']) + + if test_case['should_pass']: + print(f" ✅ PASS - Configuration validated successfully") + if 'agent_trust' in result and result['agent_trust']: + trust = result['agent_trust'] + print(f" - Level: {trust.get('required_verification_level', 'N/A')}") + print(f" - Max Depth: {trust.get('max_agent_hierarchy_depth', 'N/A')}") + passed += 1 + else: + print(f" ❌ FAIL - Should have raised error but passed") + failed += 1 + + except ValueError as e: + if not test_case['should_pass']: + if test_case.get('expected_error') in str(e): + print(f" ✅ PASS - Correctly rejected with expected error") + passed += 1 + else: + print(f" ❌ FAIL - Got wrong error: {e}") + failed += 1 + else: + print(f" ❌ FAIL - Unexpected error: {e}") + failed += 1 + + # Validate all trust levels + print("\n▶ Testing All Valid Trust Levels") + trust_levels = [ + "admin", "analyst", "auditor", "editor", "guest", + "manager", "operator", "super_admin", "support", "viewer" + ] + + level_pass = 0 + for level in trust_levels: + config = { + "author": "test@example.com", + "deployment": {}, + "agent_trust": { + "required_verification_level": level, + "max_agent_hierarchy_depth": 5 + } + } + try: + ConfigValidator.validate_and_process(config) + print(f" ✅ {level}") + level_pass += 1 + except ValueError: + print(f" ❌ {level}") + + if level_pass == len(trust_levels): + print(f" ✅ All {level_pass} trust levels validated") + passed += 1 + else: + print(f" ❌ Only {level_pass}/{len(trust_levels)} trust levels validated") + failed += 1 + + # Summary + print("\n" + "=" * 70) + print(f"RESULTS: {passed} passed, {failed} failed") + print("=" * 70) + + return failed == 0 + +if __name__ == "__main__": + import sys + success = test_all_scenarios() + sys.exit(0 if success else 1) diff --git a/test_config_validator_direct.py b/test_config_validator_direct.py new file mode 100644 index 00000000..72995932 --- /dev/null +++ b/test_config_validator_direct.py @@ -0,0 +1,124 @@ +#!/usr/bin/env python +"""Direct test of ConfigValidator without pytest dependencies.""" + +import sys +sys.path.insert(0, '/dev/null') # Prevent issues with cached imports + +from bindu.penguin.config_validator import ConfigValidator + +def test_basic_validation(): + """Test basic configuration validation.""" + print("Testing basic validation...") + config = {"author": "test@example.com", "deployment": {}} + result = ConfigValidator.validate_and_process(config) + assert result["author"] == "test@example.com" + print("✓ Basic validation passed") + +def test_agent_trust_valid(): + """Test valid agent trust configuration.""" + print("Testing valid agent trust...") + config = { + "author": "test@example.com", + "deployment": {}, + "agent_trust": { + "required_verification_level": "admin", + "max_agent_hierarchy_depth": 5, + "allowed_origins": ["https://example.com"], + }, + } + result = ConfigValidator.validate_and_process(config) + assert result["agent_trust"]["required_verification_level"] == "admin" + assert result["agent_trust"]["max_agent_hierarchy_depth"] == 5 + print("✓ Valid agent trust passed") + +def test_agent_trust_invalid_level(): + """Test invalid verification level.""" + print("Testing invalid verification level...") + config = { + "author": "test@example.com", + "deployment": {}, + "agent_trust": { + "required_verification_level": "invalid_level", + "max_agent_hierarchy_depth": 5, + }, + } + try: + ConfigValidator.validate_and_process(config) + print("✗ Should have raised ValueError") + sys.exit(1) + except ValueError as e: + if "Invalid required_verification_level" in str(e): + print("✓ Invalid level correctly rejected") + else: + print(f"✗ Wrong error: {e}") + sys.exit(1) + +def test_agent_trust_invalid_depth(): + """Test invalid hierarchy depth.""" + print("Testing invalid hierarchy depth...") + config = { + "author": "test@example.com", + "deployment": {}, + "agent_trust": { + "required_verification_level": "admin", + "max_agent_hierarchy_depth": 0, + }, + } + try: + ConfigValidator.validate_and_process(config) + print("✗ Should have raised ValueError") + sys.exit(1) + except ValueError as e: + if "Invalid max_agent_hierarchy_depth" in str(e): + print("✓ Invalid depth correctly rejected") + else: + print(f"✗ Wrong error: {e}") + sys.exit(1) + +def test_agent_trust_invalid_origin(): + """Test invalid origin format.""" + print("Testing invalid origin format...") + config = { + "author": "test@example.com", + "deployment": {}, + "agent_trust": { + "required_verification_level": "admin", + "max_agent_hierarchy_depth": 5, + "allowed_origins": ["invalid-origin"], + }, + } + try: + ConfigValidator.validate_and_process(config) + print("✗ Should have raised ValueError") + sys.exit(1) + except ValueError as e: + if "Invalid origin format" in str(e): + print("✓ Invalid origin correctly rejected") + else: + print(f"✗ Wrong error: {e}") + sys.exit(1) + +def test_defaults_applied(): + """Test that defaults are applied.""" + print("Testing defaults...") + config = {"author": "test@example.com", "deployment": {}} + result = ConfigValidator.validate_and_process(config) + assert result["name"] == "bindu-agent" + assert result["debug_mode"] is False + assert result["telemetry"] is True + print("✓ Defaults applied correctly") + +if __name__ == "__main__": + try: + test_basic_validation() + test_agent_trust_valid() + test_agent_trust_invalid_level() + test_agent_trust_invalid_depth() + test_agent_trust_invalid_origin() + test_defaults_applied() + print("\n✅ All tests passed!") + except Exception as e: + print(f"\n❌ Test failed: {e}") + import traceback + traceback.print_exc() + sys.exit(1) diff --git a/test_sample_configs.py b/test_sample_configs.py new file mode 100644 index 00000000..03a48413 --- /dev/null +++ b/test_sample_configs.py @@ -0,0 +1,56 @@ +#!/usr/bin/env python +"""Test ConfigValidator with real sample config files.""" + +import json +from pathlib import Path +from bindu.penguin.config_validator import ConfigValidator, load_and_validate_config + +def test_sample_configs(): + """Test validation of sample config files.""" + + # Test 1: Load and validate the new test config with trust + print("Testing examples/test_config_with_trust.json...") + config_path = Path("examples/test_config_with_trust.json") + + if config_path.exists(): + with open(config_path, "r") as f: + raw_config = json.load(f) + + try: + result = ConfigValidator.validate_and_process(raw_config) + print(f"✓ Config validation successful") + print(f" - Agent name: {result['name']}") + print(f" - Author: {result['author']}") + print(f" - Trust level: {result['agent_trust']['required_verification_level']}") + print(f" - Max hierarchy depth: {result['agent_trust']['max_agent_hierarchy_depth']}") + print(f" - Allowed origins: {result['agent_trust']['allowed_origins']}") + print(f" - Trust verification required: {result['agent_trust']['trust_verification_required']}") + except ValueError as e: + print(f"✗ Validation failed: {e}") + return False + else: + print(f"⚠ Config file not found at {config_path}") + + # Test 2: Test creating bindufy config + print("\nTesting create_bindufy_config...") + try: + bindufy_config = ConfigValidator.create_bindufy_config(raw_config) + print(f"✓ Bindufy config created successfully") + print(f" - Has storage config: {'storage' in bindufy_config}") + print(f" - Has scheduler config: {'scheduler' in bindufy_config}") + print(f" - Has deployment config: {'deployment' in bindufy_config}") + except Exception as e: + print(f"✗ Failed to create bindufy config: {e}") + return False + + return True + +if __name__ == "__main__": + import sys + success = test_sample_configs() + if success: + print("\n✅ All sample config tests passed!") + sys.exit(0) + else: + print("\n❌ Sample config tests failed!") + sys.exit(1) diff --git a/test_sample_direct.py b/test_sample_direct.py new file mode 100644 index 00000000..13634e70 --- /dev/null +++ b/test_sample_direct.py @@ -0,0 +1,59 @@ +#!/usr/bin/env python +"""Test ConfigValidator with sample configs - direct module import.""" + +import sys +import json +from pathlib import Path + +# Direct import to avoid circular dependencies +sys.path.insert(0, str(Path(__file__).parent)) + +from bindu.penguin.config_validator import ConfigValidator + +def test_sample_config(): + """Test validation of sample config with trust configuration.""" + config_path = Path("examples/test_config_with_trust.json") + + if not config_path.exists(): + print(f"Error: Config file not found: {config_path}") + return False + + print(f"Loading config from {config_path}...") + with open(config_path, "r") as f: + raw_config = json.load(f) + + try: + print("Validating configuration...") + result = ConfigValidator.validate_and_process(raw_config) + + print("✅ Configuration validation PASSED") + print(f" Name: {result['name']}") + print(f" Author: {result['author']}") + print(f" Trust Level: {result['agent_trust']['required_verification_level']}") + print(f" Max Hierarchy Depth: {result['agent_trust']['max_agent_hierarchy_depth']}") + print(f" Allowed Origins: {result['agent_trust']['allowed_origins']}") + print(f" Trust Verification Required: {result['agent_trust']['trust_verification_required']}") + print(f" Metadata: {result['agent_trust'].get('metadata', {})}") + + print("\n✅ CREATE BINDUFY CONFIG TEST") + bindufy_config = ConfigValidator.create_bindufy_config(raw_config) + print(f" Storage: {bindufy_config['storage']}") + print(f" Scheduler: {bindufy_config['scheduler']}") + print(f" Deployment: {list(bindufy_config['deployment'].keys())}") + + return True + + except ValueError as e: + print(f"❌ Validation failed: {e}") + import traceback + traceback.print_exc() + return False + except Exception as e: + print(f"❌ Unexpected error: {e}") + import traceback + traceback.print_exc() + return False + +if __name__ == "__main__": + success = test_sample_config() + sys.exit(0 if success else 1) diff --git a/tests/unit/test_config_validator.py b/tests/unit/test_config_validator.py new file mode 100644 index 00000000..b7d956c2 --- /dev/null +++ b/tests/unit/test_config_validator.py @@ -0,0 +1,449 @@ +"""Tests for configuration validation and processing.""" + +import pytest + +from bindu.penguin.config_validator import ConfigValidator + + +class TestConfigValidatorBasics: + """Test basic configuration validation.""" + + def test_validate_required_fields_missing(self): + """Test that missing required fields raise ValueError.""" + config = {"name": "test-agent"} + with pytest.raises(ValueError, match="Missing required fields"): + ConfigValidator.validate_and_process(config) + + def test_validate_required_fields_present(self): + """Test that validation passes with required fields.""" + config = {"author": "test@example.com", "deployment": {}} + result = ConfigValidator.validate_and_process(config) + assert result["author"] == "test@example.com" + assert result["deployment"] == {} + + def test_defaults_applied(self): + """Test that default values are applied.""" + config = {"author": "test@example.com", "deployment": {}} + result = ConfigValidator.validate_and_process(config) + assert result["name"] == "bindu-agent" + assert result["debug_mode"] is False + assert result["telemetry"] is True + assert result["recreate_keys"] is False + + def test_custom_values_override_defaults(self): + """Test that custom values override defaults.""" + config = { + "author": "test@example.com", + "deployment": {}, + "name": "custom-agent", + "debug_mode": True, + } + result = ConfigValidator.validate_and_process(config) + assert result["name"] == "custom-agent" + assert result["debug_mode"] is True + + +class TestAgentTrustValidation: + """Test agent trust configuration validation.""" + + def test_agent_trust_valid_config(self): + """Test valid agent trust configuration.""" + config = { + "author": "test@example.com", + "deployment": {}, + "agent_trust": { + "required_verification_level": "admin", + "max_agent_hierarchy_depth": 5, + "allowed_origins": ["https://example.com"], + }, + } + result = ConfigValidator.validate_and_process(config) + assert result["agent_trust"]["required_verification_level"] == "admin" + assert result["agent_trust"]["max_agent_hierarchy_depth"] == 5 + + def test_agent_trust_missing_required_fields(self): + """Test that missing required fields in agent_trust raise ValueError.""" + config = { + "author": "test@example.com", + "deployment": {}, + "agent_trust": {"identity_provider": "hydra"}, # Missing required fields + } + with pytest.raises(ValueError, match="Invalid agent_trust configuration"): + ConfigValidator.validate_and_process(config) + + def test_agent_trust_invalid_verification_level(self): + """Test invalid verification level.""" + config = { + "author": "test@example.com", + "deployment": {}, + "agent_trust": { + "required_verification_level": "invalid_level", + "max_agent_hierarchy_depth": 5, + }, + } + with pytest.raises( + ValueError, match="Invalid required_verification_level" + ): + ConfigValidator.validate_and_process(config) + + def test_agent_trust_valid_verification_levels(self): + """Test all valid verification levels.""" + valid_levels = [ + "admin", + "analyst", + "auditor", + "editor", + "guest", + "manager", + "operator", + "super_admin", + "support", + "viewer", + ] + for level in valid_levels: + config = { + "author": "test@example.com", + "deployment": {}, + "agent_trust": { + "required_verification_level": level, + "max_agent_hierarchy_depth": 5, + }, + } + result = ConfigValidator.validate_and_process(config) + assert result["agent_trust"]["required_verification_level"] == level + + def test_agent_trust_invalid_hierarchy_depth(self): + """Test invalid agent hierarchy depth.""" + config = { + "author": "test@example.com", + "deployment": {}, + "agent_trust": { + "required_verification_level": "admin", + "max_agent_hierarchy_depth": 0, # Invalid: must be >= 1 + }, + } + with pytest.raises( + ValueError, match="Invalid max_agent_hierarchy_depth" + ): + ConfigValidator.validate_and_process(config) + + def test_agent_trust_valid_hierarchy_depths(self): + """Test valid hierarchy depths.""" + for depth in [1, 2, 5, 10, 100]: + config = { + "author": "test@example.com", + "deployment": {}, + "agent_trust": { + "required_verification_level": "admin", + "max_agent_hierarchy_depth": depth, + }, + } + result = ConfigValidator.validate_and_process(config) + assert result["agent_trust"]["max_agent_hierarchy_depth"] == depth + + def test_agent_trust_invalid_allowed_origins(self): + """Test invalid allowed origins.""" + config = { + "author": "test@example.com", + "deployment": {}, + "agent_trust": { + "required_verification_level": "admin", + "max_agent_hierarchy_depth": 5, + "allowed_origins": "https://example.com", # Should be list + }, + } + with pytest.raises(ValueError, match="allowed_origins"): + ConfigValidator.validate_and_process(config) + + def test_agent_trust_valid_allowed_origins(self): + """Test valid allowed origins.""" + config = { + "author": "test@example.com", + "deployment": {}, + "agent_trust": { + "required_verification_level": "admin", + "max_agent_hierarchy_depth": 5, + "allowed_origins": [ + "https://example.com", + "https://api.example.com", + "https://*.example.com", + ], + }, + } + result = ConfigValidator.validate_and_process(config) + assert len(result["agent_trust"]["allowed_origins"]) == 3 + + def test_agent_trust_invalid_origin_format(self): + """Test invalid origin format.""" + config = { + "author": "test@example.com", + "deployment": {}, + "agent_trust": { + "required_verification_level": "admin", + "max_agent_hierarchy_depth": 5, + "allowed_origins": ["invalid-origin"], # Missing http:// or https:// + }, + } + with pytest.raises(ValueError, match="Invalid origin format"): + ConfigValidator.validate_and_process(config) + + def test_agent_trust_invalid_identity_provider(self): + """Test invalid identity provider.""" + config = { + "author": "test@example.com", + "deployment": {}, + "agent_trust": { + "identity_provider": "invalid_provider", + "required_verification_level": "admin", + "max_agent_hierarchy_depth": 5, + }, + } + with pytest.raises(ValueError, match="Invalid identity_provider"): + ConfigValidator.validate_and_process(config) + + def test_agent_trust_valid_identity_provider(self): + """Test valid identity provider.""" + config = { + "author": "test@example.com", + "deployment": {}, + "agent_trust": { + "identity_provider": "hydra", + "required_verification_level": "admin", + "max_agent_hierarchy_depth": 5, + }, + } + result = ConfigValidator.validate_and_process(config) + assert result["agent_trust"]["identity_provider"] == "hydra" + + def test_agent_trust_boolean_fields(self): + """Test boolean fields in trust config.""" + config = { + "author": "test@example.com", + "deployment": {}, + "agent_trust": { + "required_verification_level": "admin", + "max_agent_hierarchy_depth": 5, + "trust_verification_required": True, + "certificate_required": False, + }, + } + result = ConfigValidator.validate_and_process(config) + assert result["agent_trust"]["trust_verification_required"] is True + assert result["agent_trust"]["certificate_required"] is False + + def test_agent_trust_invalid_boolean_fields(self): + """Test invalid boolean fields in trust config.""" + config = { + "author": "test@example.com", + "deployment": {}, + "agent_trust": { + "required_verification_level": "admin", + "max_agent_hierarchy_depth": 5, + "trust_verification_required": "yes", # Should be boolean + }, + } + with pytest.raises(ValueError, match="trust_verification_required"): + ConfigValidator.validate_and_process(config) + + def test_agent_trust_metadata(self): + """Test metadata field in trust config.""" + config = { + "author": "test@example.com", + "deployment": {}, + "agent_trust": { + "required_verification_level": "admin", + "max_agent_hierarchy_depth": 5, + "metadata": {"custom_key": "custom_value"}, + }, + } + result = ConfigValidator.validate_and_process(config) + assert result["agent_trust"]["metadata"]["custom_key"] == "custom_value" + + def test_agent_trust_null_allowed(self): + """Test that agent_trust can be null.""" + config = {"author": "test@example.com", "deployment": {}, "agent_trust": None} + result = ConfigValidator.validate_and_process(config) + assert result["agent_trust"] is None + + +class TestFieldTypeValidation: + """Test field type validation.""" + + def test_string_field_validation(self): + """Test string field validation.""" + config = { + "author": "test@example.com", + "deployment": {}, + "name": 123, # Should be string + } + with pytest.raises(ValueError, match="Field 'name' must be a string"): + ConfigValidator.validate_and_process(config) + + def test_boolean_field_validation(self): + """Test boolean field validation.""" + config = { + "author": "test@example.com", + "deployment": {}, + "debug_mode": "true", # Should be boolean + } + with pytest.raises(ValueError, match="Field 'debug_mode' must be a boolean"): + ConfigValidator.validate_and_process(config) + + def test_debug_level_validation(self): + """Test debug_level validation.""" + # Valid values + for level in [1, 2]: + config = { + "author": "test@example.com", + "deployment": {}, + "debug_level": level, + } + result = ConfigValidator.validate_and_process(config) + assert result["debug_level"] == level + + # Invalid values + for level in [0, 3, "1"]: + config = { + "author": "test@example.com", + "deployment": {}, + "debug_level": level, + } + with pytest.raises(ValueError, match="debug_level"): + ConfigValidator.validate_and_process(config) + + def test_kind_validation(self): + """Test kind field validation.""" + for kind in ["agent", "team", "workflow"]: + config = { + "author": "test@example.com", + "deployment": {}, + "kind": kind, + } + result = ConfigValidator.validate_and_process(config) + assert result["kind"] == kind + + config = { + "author": "test@example.com", + "deployment": {}, + "kind": "invalid", + } + with pytest.raises(ValueError, match="Field 'kind' must be one of"): + ConfigValidator.validate_and_process(config) + + def test_num_history_sessions_validation(self): + """Test num_history_sessions validation.""" + # Valid values + for sessions in [0, 1, 10, 100]: + config = { + "author": "test@example.com", + "deployment": {}, + "num_history_sessions": sessions, + } + result = ConfigValidator.validate_and_process(config) + assert result["num_history_sessions"] == sessions + + # Invalid values + for sessions in [-1, "10"]: + config = { + "author": "test@example.com", + "deployment": {}, + "num_history_sessions": sessions, + } + with pytest.raises(ValueError, match="num_history_sessions"): + ConfigValidator.validate_and_process(config) + + +class TestComplexFieldProcessing: + """Test processing of complex fields.""" + + def test_process_skills_from_dict_list(self): + """Test processing skills from dictionary list.""" + config = { + "author": "test@example.com", + "deployment": {}, + "skills": [ + { + "id": "skill1", + "name": "Skill 1", + "description": "Test skill", + "tags": ["test"], + "input_modes": ["text/plain"], + "output_modes": ["text/plain"], + } + ], + } + result = ConfigValidator.validate_and_process(config) + assert len(result["skills"]) == 1 + assert result["skills"][0]["name"] == "Skill 1" + + def test_process_capabilities(self): + """Test processing capabilities.""" + config = { + "author": "test@example.com", + "deployment": {}, + "capabilities": {"push_notifications": True, "streaming": True}, + } + result = ConfigValidator.validate_and_process(config) + assert result["capabilities"]["push_notifications"] is True + assert result["capabilities"]["streaming"] is True + + +class TestCreateBindufyConfig: + """Test create_bindufy_config convenience method.""" + + def test_create_bindufy_config_success(self): + """Test successful creation of bindufy config.""" + config = {"author": "test@example.com", "deployment": {}} + result = ConfigValidator.create_bindufy_config(config) + assert result["author"] == "test@example.com" + assert result["deployment"] == {} + assert result["storage"] == {} + assert result["scheduler"] == {} + + def test_create_bindufy_config_missing_required(self): + """Test that missing required fields raise error.""" + config = {"name": "test"} + with pytest.raises(ValueError): + ConfigValidator.create_bindufy_config(config) + + +class TestIntegration: + """Integration tests for ConfigValidator.""" + + def test_full_config_with_agent_trust(self): + """Test full configuration with agent trust.""" + config = { + "author": "test@example.com", + "deployment": {"type": "cloud"}, + "name": "my-agent", + "description": "My test agent", + "version": "1.0.0", + "kind": "agent", + "debug_mode": True, + "telemetry": True, + "agent_trust": { + "required_verification_level": "manager", + "max_agent_hierarchy_depth": 3, + "allowed_origins": [ + "https://example.com", + "https://*.example.com", + ], + "trust_verification_required": True, + "certificate_required": True, + "metadata": {"custom": "data"}, + }, + } + result = ConfigValidator.validate_and_process(config) + assert result["name"] == "my-agent" + assert result["author"] == "test@example.com" + assert result["agent_trust"]["required_verification_level"] == "manager" + assert result["agent_trust"]["max_agent_hierarchy_depth"] == 3 + + def test_minimal_config(self): + """Test minimal configuration.""" + config = {"author": "test@example.com", "deployment": {}} + result = ConfigValidator.validate_and_process(config) + # Verify all defaults are present + assert "name" in result + assert "version" in result + assert "debug_mode" in result